kubeagent 0.1.2 → 0.1.5
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 +14 -3
- 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
|
@@ -3,6 +3,9 @@ import { Command } from "commander";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import ora from "ora";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { version } = require("../package.json");
|
|
6
9
|
function expandPath(p) {
|
|
7
10
|
return p.startsWith("~/") ? homedir() + p.slice(1) : p;
|
|
8
11
|
}
|
|
@@ -25,7 +28,7 @@ const program = new Command();
|
|
|
25
28
|
program
|
|
26
29
|
.name("kubeagent")
|
|
27
30
|
.description("AI-powered Kubernetes management CLI")
|
|
28
|
-
.version(
|
|
31
|
+
.version(version);
|
|
29
32
|
program
|
|
30
33
|
.command("status")
|
|
31
34
|
.description("Quick cluster health summary (no LLM)")
|
|
@@ -108,12 +111,20 @@ program
|
|
|
108
111
|
if (serverSlack.connected && serverSlack.webhookUrl) {
|
|
109
112
|
const alreadyConfigured = config.notifications.channels.some((ch) => ch.type === "slack" && ch.webhook_url === serverSlack.webhookUrl);
|
|
110
113
|
if (!alreadyConfigured) {
|
|
111
|
-
|
|
114
|
+
const dashboardChannel = {
|
|
112
115
|
type: "slack",
|
|
113
116
|
webhook_url: serverSlack.webhookUrl,
|
|
114
117
|
label: serverSlack.teamName ?? serverSlack.channelName ?? "KubeAgent",
|
|
115
118
|
severity: "warning",
|
|
116
|
-
}
|
|
119
|
+
};
|
|
120
|
+
config.notifications.channels.unshift(dashboardChannel);
|
|
121
|
+
// Persist to local config so it survives restarts and shows in `notify list`
|
|
122
|
+
const savedConfig = loadConfig();
|
|
123
|
+
const alreadySaved = savedConfig.notifications.channels.some((ch) => ch.type === "slack" && ch.webhook_url === serverSlack.webhookUrl);
|
|
124
|
+
if (!alreadySaved) {
|
|
125
|
+
savedConfig.notifications.channels.unshift(dashboardChannel);
|
|
126
|
+
saveConfig(savedConfig);
|
|
127
|
+
}
|
|
117
128
|
console.log(chalk.dim(` Slack: ${serverSlack.teamName ?? ""}${serverSlack.channelName ? ` ${serverSlack.channelName}` : ""} (from dashboard)\n`));
|
|
118
129
|
}
|
|
119
130
|
}
|
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
|
-
}
|