kubeagent 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.js +10 -3
- package/dist/cli.js +10 -2
- package/dist/notify/setup.js +20 -8
- package/dist/notify/telegram.d.ts +1 -0
- package/dist/notify/telegram.js +29 -0
- package/package.json +1 -1
- package/dist/notify/webhook.d.ts +0 -3
- package/dist/notify/webhook.js +0 -49
package/dist/auth.js
CHANGED
|
@@ -152,9 +152,16 @@ export async function createApiKey(auth, name) {
|
|
|
152
152
|
return data;
|
|
153
153
|
}
|
|
154
154
|
export async function showAccount(auth) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const url = `${auth.serverUrl}/v1/balance`;
|
|
156
|
+
let res;
|
|
157
|
+
try {
|
|
158
|
+
res = await fetch(url, {
|
|
159
|
+
headers: { Authorization: `ApiKey ${auth.apiKey ?? auth.token}` },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
throw new Error(`Could not reach server at ${url} — is it running?\n (${err.message})`);
|
|
164
|
+
}
|
|
158
165
|
if (!res.ok) {
|
|
159
166
|
throw new Error(`Failed to fetch account info (${res.status})`);
|
|
160
167
|
}
|
package/dist/cli.js
CHANGED
|
@@ -108,12 +108,20 @@ program
|
|
|
108
108
|
if (serverSlack.connected && serverSlack.webhookUrl) {
|
|
109
109
|
const alreadyConfigured = config.notifications.channels.some((ch) => ch.type === "slack" && ch.webhook_url === serverSlack.webhookUrl);
|
|
110
110
|
if (!alreadyConfigured) {
|
|
111
|
-
|
|
111
|
+
const dashboardChannel = {
|
|
112
112
|
type: "slack",
|
|
113
113
|
webhook_url: serverSlack.webhookUrl,
|
|
114
114
|
label: serverSlack.teamName ?? serverSlack.channelName ?? "KubeAgent",
|
|
115
115
|
severity: "warning",
|
|
116
|
-
}
|
|
116
|
+
};
|
|
117
|
+
config.notifications.channels.unshift(dashboardChannel);
|
|
118
|
+
// Persist to local config so it survives restarts and shows in `notify list`
|
|
119
|
+
const savedConfig = loadConfig();
|
|
120
|
+
const alreadySaved = savedConfig.notifications.channels.some((ch) => ch.type === "slack" && ch.webhook_url === serverSlack.webhookUrl);
|
|
121
|
+
if (!alreadySaved) {
|
|
122
|
+
savedConfig.notifications.channels.unshift(dashboardChannel);
|
|
123
|
+
saveConfig(savedConfig);
|
|
124
|
+
}
|
|
117
125
|
console.log(chalk.dim(` Slack: ${serverSlack.teamName ?? ""}${serverSlack.channelName ? ` ${serverSlack.channelName}` : ""} (from dashboard)\n`));
|
|
118
126
|
}
|
|
119
127
|
}
|
package/dist/notify/setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { sendSlack } from "./slack.js";
|
|
4
|
-
import { testTelegramCredentials } from "./telegram.js";
|
|
4
|
+
import { testTelegramCredentials, autoDetectTelegramChatId } from "./telegram.js";
|
|
5
5
|
async function ask(question, hint) {
|
|
6
6
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7
7
|
const hintStr = hint ? chalk.dim(` (${hint})`) : "";
|
|
@@ -43,16 +43,28 @@ export async function setupSlack() {
|
|
|
43
43
|
}
|
|
44
44
|
export async function setupTelegram() {
|
|
45
45
|
console.log(chalk.bold("\n Telegram Setup"));
|
|
46
|
-
console.log(chalk.dim(" 1. Create a bot via @BotFather and copy
|
|
47
|
-
console.log(chalk.dim(" 2. Start a chat with your bot
|
|
48
|
-
console.log(chalk.dim(" 3. Get your chat ID: message the bot, then visit:"));
|
|
49
|
-
console.log(chalk.dim(" https://api.telegram.org/bot<TOKEN>/getUpdates\n"));
|
|
46
|
+
console.log(chalk.dim(" 1. Create a bot via @BotFather and copy its token"));
|
|
47
|
+
console.log(chalk.dim(" 2. Start a chat with your bot, or add it to a group/channel\n"));
|
|
50
48
|
const bot_token = await ask("Bot token");
|
|
51
49
|
if (!bot_token)
|
|
52
50
|
return null;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
// Auto-detect chat ID
|
|
52
|
+
console.log(chalk.dim("\n Now send any message to your bot (or the group/channel where it was added)."));
|
|
53
|
+
process.stdout.write(chalk.cyan(" Waiting for a message (30s)..."));
|
|
54
|
+
const detected = await autoDetectTelegramChatId(bot_token);
|
|
55
|
+
let chat_id;
|
|
56
|
+
if (detected) {
|
|
57
|
+
console.log(chalk.green(` detected: ${detected}`));
|
|
58
|
+
chat_id = detected;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log(chalk.yellow(" timed out."));
|
|
62
|
+
console.log(chalk.dim(" Find it manually: send a message to your bot, then visit:"));
|
|
63
|
+
console.log(chalk.dim(` https://api.telegram.org/bot${bot_token}/getUpdates\n`));
|
|
64
|
+
chat_id = await ask("Chat ID");
|
|
65
|
+
if (!chat_id)
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
56
68
|
const label = await ask("Label (optional)", "e.g. ops-alerts");
|
|
57
69
|
const severity = await pickSeverity();
|
|
58
70
|
// Test credentials
|
|
@@ -2,6 +2,7 @@ import type { Issue } from "../monitor/types.js";
|
|
|
2
2
|
import type { TelegramChannel } from "../config.js";
|
|
3
3
|
export declare function sendTelegramQuestion(channel: TelegramChannel, question: string, choices: string[] | undefined, clusterContext?: string): Promise<void>;
|
|
4
4
|
export declare function sendTelegram(issues: Issue[], channel: TelegramChannel, clusterContext?: string): Promise<void>;
|
|
5
|
+
export declare function autoDetectTelegramChatId(botToken: string): Promise<string | null>;
|
|
5
6
|
export declare function testTelegramCredentials(botToken: string, chatId: string): Promise<{
|
|
6
7
|
ok: boolean;
|
|
7
8
|
error?: string;
|
package/dist/notify/telegram.js
CHANGED
|
@@ -43,6 +43,35 @@ export async function sendTelegramQuestion(channel, question, choices, clusterCo
|
|
|
43
43
|
export async function sendTelegram(issues, channel, clusterContext) {
|
|
44
44
|
await postToTelegram(channel, formatTelegramMessage(issues, clusterContext));
|
|
45
45
|
}
|
|
46
|
+
export async function autoDetectTelegramChatId(botToken) {
|
|
47
|
+
const url = `${TELEGRAM_API}/bot${botToken}/getUpdates`;
|
|
48
|
+
const deadline = Date.now() + 30_000;
|
|
49
|
+
let offset = 0;
|
|
50
|
+
// Clear any pending updates first so we only see new messages
|
|
51
|
+
try {
|
|
52
|
+
const clearRes = await fetch(`${url}?offset=-1`);
|
|
53
|
+
const clearData = await clearRes.json();
|
|
54
|
+
if (clearData.ok && clearData.result.length > 0) {
|
|
55
|
+
offset = clearData.result[clearData.result.length - 1].update_id + 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch { /* ignore */ }
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch(`${url}?offset=${offset}&timeout=5&limit=1`);
|
|
62
|
+
const data = await res.json();
|
|
63
|
+
if (data.ok && data.result.length > 0) {
|
|
64
|
+
const update = data.result[0];
|
|
65
|
+
offset = update.update_id + 1;
|
|
66
|
+
const chat = update.message?.chat ?? update.channel_post?.chat ?? update.my_chat_member?.chat;
|
|
67
|
+
if (chat)
|
|
68
|
+
return String(chat.id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* ignore, keep polling */ }
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
46
75
|
export async function testTelegramCredentials(botToken, chatId) {
|
|
47
76
|
try {
|
|
48
77
|
const res = await fetch(`${TELEGRAM_API}/bot${botToken}/sendMessage`, {
|
package/package.json
CHANGED
package/dist/notify/webhook.d.ts
DELETED
package/dist/notify/webhook.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
function formatMessage(issues) {
|
|
2
|
-
const header = `**KubeAgent Alert** — ${issues.length} issue(s) detected\n`;
|
|
3
|
-
const body = issues
|
|
4
|
-
.map((i) => {
|
|
5
|
-
const icon = i.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}";
|
|
6
|
-
return `${icon} **${i.kind}**: ${i.message}`;
|
|
7
|
-
})
|
|
8
|
-
.join("\n");
|
|
9
|
-
return header + body;
|
|
10
|
-
}
|
|
11
|
-
export async function sendWebhook(issues, config) {
|
|
12
|
-
const message = formatMessage(issues);
|
|
13
|
-
let payload;
|
|
14
|
-
switch (config.type) {
|
|
15
|
-
case "discord":
|
|
16
|
-
payload = { content: message };
|
|
17
|
-
break;
|
|
18
|
-
case "slack":
|
|
19
|
-
case "mattermost":
|
|
20
|
-
default:
|
|
21
|
-
payload = { text: message };
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
// Validate URL to prevent SSRF
|
|
25
|
-
try {
|
|
26
|
-
const url = new URL(config.url);
|
|
27
|
-
if (!["https:", "http:"].includes(url.protocol)) {
|
|
28
|
-
console.error(`Webhook error: unsupported protocol ${url.protocol}`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
console.error(`Webhook error: invalid URL ${config.url}`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const response = await fetch(config.url, {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: { "Content-Type": "application/json" },
|
|
40
|
-
body: JSON.stringify(payload),
|
|
41
|
-
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
console.error(`Webhook failed: ${response.status} ${response.statusText}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
console.error(`Webhook error: ${err.message}`);
|
|
48
|
-
}
|
|
49
|
-
}
|