kubeagent 0.1.10 → 0.1.11

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/config.d.ts CHANGED
@@ -38,7 +38,19 @@ export interface PagerDutyChannel {
38
38
  severity: NotificationSeverity;
39
39
  label?: string;
40
40
  }
41
- export type NotificationChannel = SlackChannel | TelegramChannel | WebhookChannel | PagerDutyChannel;
41
+ export interface DiscordChannel {
42
+ type: "discord";
43
+ webhook_url: string;
44
+ severity: NotificationSeverity;
45
+ label?: string;
46
+ }
47
+ export interface TeamsChannel {
48
+ type: "teams";
49
+ webhook_url: string;
50
+ severity: NotificationSeverity;
51
+ label?: string;
52
+ }
53
+ export type NotificationChannel = SlackChannel | TelegramChannel | WebhookChannel | PagerDutyChannel | DiscordChannel | TeamsChannel;
42
54
  export declare const ALL_ACTIONS: readonly ["restart_pod", "rollout_restart", "scale_deployment", "set_resources"];
43
55
  export type RemediationAction = (typeof ALL_ACTIONS)[number];
44
56
  export interface KubeAgentConfig {
@@ -0,0 +1,4 @@
1
+ import type { Issue } from "../monitor/types.js";
2
+ import type { DiscordChannel } from "../config.js";
3
+ export declare function sendDiscordQuestion(channel: DiscordChannel, question: string, choices: string[] | undefined, clusterContext?: string): Promise<void>;
4
+ export declare function sendDiscord(issues: Issue[], channel: DiscordChannel, clusterContext?: string): Promise<void>;
@@ -0,0 +1,80 @@
1
+ const CRITICAL_COLOR = 0xe74c3c; // red
2
+ const WARNING_COLOR = 0xf39c12; // yellow
3
+ const INFO_COLOR = 0x3498db; // blue
4
+ function formatDiscordPayload(issues, clusterContext) {
5
+ const critical = issues.filter((i) => i.severity === "critical");
6
+ const warning = issues.filter((i) => i.severity === "warning");
7
+ const summary = [
8
+ critical.length ? `${critical.length} critical` : "",
9
+ warning.length ? `${warning.length} warning` : "",
10
+ ].filter(Boolean).join(", ");
11
+ const topSeverity = critical.length ? "critical" : warning.length ? "warning" : "info";
12
+ const color = topSeverity === "critical" ? CRITICAL_COLOR : topSeverity === "warning" ? WARNING_COLOR : INFO_COLOR;
13
+ const description = issues
14
+ .map((i) => {
15
+ const icon = i.severity === "critical" ? "🔴" : i.severity === "warning" ? "🟡" : "🔵";
16
+ const ns = i.namespace ? ` \`${i.namespace}\`` : "";
17
+ return `${icon} ${i.message}${ns}`;
18
+ })
19
+ .join("\n");
20
+ return {
21
+ embeds: [
22
+ {
23
+ title: "⚠ KubeAgent Alert",
24
+ description: clusterContext
25
+ ? `**Cluster:** \`${clusterContext}\`\n${summary}\n\n${description}`
26
+ : `${summary}\n\n${description}`,
27
+ color,
28
+ timestamp: new Date().toISOString(),
29
+ footer: { text: "KubeAgent" },
30
+ },
31
+ ],
32
+ };
33
+ }
34
+ async function postToDiscord(webhookUrl, payload) {
35
+ try {
36
+ const url = new URL(webhookUrl);
37
+ if (!["https:", "http:"].includes(url.protocol)) {
38
+ console.error("Discord: invalid URL protocol");
39
+ return;
40
+ }
41
+ }
42
+ catch {
43
+ console.error("Discord: invalid webhook URL");
44
+ return;
45
+ }
46
+ try {
47
+ const res = await fetch(webhookUrl, {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/json" },
50
+ body: JSON.stringify(payload),
51
+ signal: AbortSignal.timeout(10_000),
52
+ });
53
+ if (!res.ok)
54
+ console.error(`Discord webhook failed: ${res.status} ${res.statusText}`);
55
+ }
56
+ catch (err) {
57
+ console.error(`Discord webhook error: ${err.message}`);
58
+ }
59
+ }
60
+ export async function sendDiscordQuestion(channel, question, choices, clusterContext) {
61
+ const ctx = clusterContext ? `\n**Cluster:** \`${clusterContext}\`` : "";
62
+ const choiceLines = choices?.map((c, i) => `${i + 1}. ${c}`) ?? [];
63
+ const body = choiceLines.length
64
+ ? `${question}\n\n${choiceLines.join("\n")}\n\n*Answer via terminal or reply in your incident channel.*`
65
+ : `${question}\n\n*Answer required via terminal.*`;
66
+ await postToDiscord(channel.webhook_url, {
67
+ embeds: [
68
+ {
69
+ title: "❓ KubeAgent needs input",
70
+ description: `**Question**${ctx}\n\n${body}`,
71
+ color: INFO_COLOR,
72
+ timestamp: new Date().toISOString(),
73
+ footer: { text: "KubeAgent" },
74
+ },
75
+ ],
76
+ });
77
+ }
78
+ export async function sendDiscord(issues, channel, clusterContext) {
79
+ await postToDiscord(channel.webhook_url, formatDiscordPayload(issues, clusterContext));
80
+ }
@@ -2,6 +2,8 @@ import { sendSlack, sendSlackQuestion } from "./slack.js";
2
2
  import { sendTelegram, sendTelegramQuestion } from "./telegram.js";
3
3
  import { sendWebhook } from "./webhook.js";
4
4
  import { sendPagerDuty, resolvePagerDuty } from "./pagerduty.js";
5
+ import { sendDiscord, sendDiscordQuestion } from "./discord.js";
6
+ import { sendTeams } from "./teams.js";
5
7
  const SEVERITY_ORDER = { info: 0, warning: 1, critical: 2 };
6
8
  export async function sendNotification(issues, config, clusterContext) {
7
9
  if (!config.notifications.channels.length)
@@ -24,6 +26,12 @@ export async function sendNotification(issues, config, clusterContext) {
24
26
  case "pagerduty":
25
27
  await sendPagerDuty(filtered, channel, clusterContext);
26
28
  break;
29
+ case "discord":
30
+ await sendDiscord(filtered, channel, clusterContext);
31
+ break;
32
+ case "teams":
33
+ await sendTeams(filtered, channel, clusterContext);
34
+ break;
27
35
  }
28
36
  }));
29
37
  }
@@ -36,7 +44,10 @@ export async function broadcastQuestion(question, choices, config, clusterContex
36
44
  case "telegram": return sendTelegramQuestion(channel, question, choices, clusterContext);
37
45
  case "webhook":
38
46
  case "pagerduty":
47
+ case "teams":
39
48
  return;
49
+ case "discord":
50
+ return sendDiscordQuestion(channel, question, choices, clusterContext);
40
51
  }
41
52
  }));
42
53
  }
@@ -61,5 +72,9 @@ export function describeChannel(channel) {
61
72
  return `Webhook${label} ${channel.url.slice(0, 40)}… min: ${channel.severity}`;
62
73
  case "pagerduty":
63
74
  return `PagerDuty${label} key: ${channel.routing_key.slice(0, 8)}… min: ${channel.severity}`;
75
+ case "discord":
76
+ return `Discord${label} ${channel.webhook_url.slice(0, 40)}… min: ${channel.severity}`;
77
+ case "teams":
78
+ return `Teams${label} ${channel.webhook_url.slice(0, 40)}… min: ${channel.severity}`;
64
79
  }
65
80
  }
@@ -1,7 +1,9 @@
1
1
  import type { NotificationChannel, SlackChannel, TelegramChannel } from "../config.js";
2
- import type { WebhookChannel, PagerDutyChannel } from "../config.js";
2
+ import type { WebhookChannel, PagerDutyChannel, DiscordChannel, TeamsChannel } from "../config.js";
3
3
  export declare function setupSlack(): Promise<SlackChannel | null>;
4
4
  export declare function setupTelegram(): Promise<TelegramChannel | null>;
5
5
  export declare function setupWebhook(): Promise<WebhookChannel | null>;
6
6
  export declare function setupPagerDuty(): Promise<PagerDutyChannel | null>;
7
+ export declare function setupDiscord(): Promise<DiscordChannel | null>;
8
+ export declare function setupTeams(): Promise<TeamsChannel | null>;
7
9
  export declare function interactiveAddChannel(): Promise<NotificationChannel | null>;
@@ -4,6 +4,8 @@ import { sendSlack } from "./slack.js";
4
4
  import { testTelegramCredentials, autoDetectTelegramChatId } from "./telegram.js";
5
5
  import { testPagerDutyCredentials } from "./pagerduty.js";
6
6
  import { sendWebhook } from "./webhook.js";
7
+ import { sendDiscord } from "./discord.js";
8
+ import { sendTeams } from "./teams.js";
7
9
  import { loadAuth } from "../auth.js";
8
10
  import { registerWebhook } from "../proxy-client.js";
9
11
  import { randomBytes } from "node:crypto";
@@ -151,18 +153,60 @@ export async function setupPagerDuty() {
151
153
  }
152
154
  return { type: "pagerduty", routing_key, severity, label: label || undefined };
153
155
  }
156
+ export async function setupDiscord() {
157
+ console.log(chalk.bold("\n Discord Setup"));
158
+ console.log(chalk.dim(" Create an incoming webhook in your Discord server:"));
159
+ console.log(chalk.dim(" Server Settings → Integrations → Webhooks → New Webhook\n"));
160
+ const webhook_url = await ask("Webhook URL");
161
+ if (!webhook_url)
162
+ return null;
163
+ const label = await ask("Label (optional)", "e.g. #alerts");
164
+ const severity = await pickSeverity();
165
+ const channel = { type: "discord", webhook_url, severity, label: label || undefined };
166
+ process.stdout.write(chalk.dim("\n Sending test message..."));
167
+ await sendDiscord([{ kind: "pod_pending", severity: "warning", namespace: "test", resource: "kubeagent-test", message: "KubeAgent Discord integration working ✓", details: {}, timestamp: new Date() }], channel);
168
+ console.log(chalk.green(" sent!"));
169
+ const confirmed = await ask("Did you receive it? [y/N]");
170
+ if (confirmed.toLowerCase() !== "y") {
171
+ console.log(chalk.yellow(" Not confirmed — channel not saved."));
172
+ return null;
173
+ }
174
+ return channel;
175
+ }
176
+ export async function setupTeams() {
177
+ console.log(chalk.bold("\n Microsoft Teams Setup"));
178
+ console.log(chalk.dim(" Create an incoming webhook in your Teams channel:"));
179
+ console.log(chalk.dim(" Channel → ... → Connectors → Incoming Webhook → Configure\n"));
180
+ const webhook_url = await ask("Webhook URL");
181
+ if (!webhook_url)
182
+ return null;
183
+ const label = await ask("Label (optional)", "e.g. ops-alerts");
184
+ const severity = await pickSeverity();
185
+ const channel = { type: "teams", webhook_url, severity, label: label || undefined };
186
+ process.stdout.write(chalk.dim("\n Sending test message..."));
187
+ await sendTeams([{ kind: "pod_pending", severity: "warning", namespace: "test", resource: "kubeagent-test", message: "KubeAgent Teams integration working ✓", details: {}, timestamp: new Date() }], channel);
188
+ console.log(chalk.green(" sent!"));
189
+ const confirmed = await ask("Did you receive it? [y/N]");
190
+ if (confirmed.toLowerCase() !== "y") {
191
+ console.log(chalk.yellow(" Not confirmed — channel not saved."));
192
+ return null;
193
+ }
194
+ return channel;
195
+ }
154
196
  export async function interactiveAddChannel() {
155
197
  console.log(chalk.bold("\nAdd notification channel:\n"));
156
198
  console.log(` ${chalk.cyan("1")}. Slack`);
157
199
  console.log(` ${chalk.cyan("2")}. Telegram`);
158
200
  console.log(` ${chalk.cyan("3")}. Webhook`);
159
201
  console.log(` ${chalk.cyan("4")}. PagerDuty`);
160
- console.log(` ${chalk.cyan("5")}. Cancel\n`);
202
+ console.log(` ${chalk.cyan("5")}. Discord`);
203
+ console.log(` ${chalk.cyan("6")}. Microsoft Teams`);
204
+ console.log(` ${chalk.cyan("7")}. Cancel\n`);
161
205
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
162
206
  const choice = await new Promise((resolve) => {
163
207
  rl.question(chalk.cyan(" Choice [1]: "), (a) => { rl.close(); resolve(a.trim()); });
164
208
  });
165
- if (choice === "5" || choice === "")
209
+ if (choice === "7" || choice === "")
166
210
  return null;
167
211
  if (choice === "2")
168
212
  return setupTelegram();
@@ -170,5 +214,9 @@ export async function interactiveAddChannel() {
170
214
  return setupWebhook();
171
215
  if (choice === "4")
172
216
  return setupPagerDuty();
217
+ if (choice === "5")
218
+ return setupDiscord();
219
+ if (choice === "6")
220
+ return setupTeams();
173
221
  return setupSlack();
174
222
  }
@@ -0,0 +1,3 @@
1
+ import type { Issue } from "../monitor/types.js";
2
+ import type { TeamsChannel } from "../config.js";
3
+ export declare function sendTeams(issues: Issue[], channel: TeamsChannel, clusterContext?: string): Promise<void>;
@@ -0,0 +1,55 @@
1
+ function formatTeamsPayload(issues, clusterContext) {
2
+ const critical = issues.filter((i) => i.severity === "critical");
3
+ const warning = issues.filter((i) => i.severity === "warning");
4
+ const summary = [
5
+ critical.length ? `${critical.length} critical` : "",
6
+ warning.length ? `${warning.length} warning` : "",
7
+ ].filter(Boolean).join(", ");
8
+ const themeColor = critical.length ? "e74c3c" : warning.length ? "f39c12" : "3498db";
9
+ const facts = issues.map((i) => ({
10
+ name: i.severity === "critical" ? "🔴 Critical" : i.severity === "warning" ? "🟡 Warning" : "🔵 Info",
11
+ value: i.namespace ? `${i.message} \`${i.namespace}\`` : i.message,
12
+ }));
13
+ const sections = [];
14
+ if (clusterContext) {
15
+ sections.push({ facts: [{ name: "Cluster", value: `\`${clusterContext}\`` }] });
16
+ }
17
+ sections.push({ facts });
18
+ return {
19
+ "@type": "MessageCard",
20
+ "@context": "https://schema.org/extensions",
21
+ themeColor,
22
+ summary: `KubeAgent Alert — ${summary}`,
23
+ title: "⚠ KubeAgent Alert",
24
+ sections,
25
+ };
26
+ }
27
+ async function postToTeams(webhookUrl, payload) {
28
+ try {
29
+ const url = new URL(webhookUrl);
30
+ if (!["https:", "http:"].includes(url.protocol)) {
31
+ console.error("Teams: invalid URL protocol");
32
+ return;
33
+ }
34
+ }
35
+ catch {
36
+ console.error("Teams: invalid webhook URL");
37
+ return;
38
+ }
39
+ try {
40
+ const res = await fetch(webhookUrl, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify(payload),
44
+ signal: AbortSignal.timeout(10_000),
45
+ });
46
+ if (!res.ok)
47
+ console.error(`Teams webhook failed: ${res.status} ${res.statusText}`);
48
+ }
49
+ catch (err) {
50
+ console.error(`Teams webhook error: ${err.message}`);
51
+ }
52
+ }
53
+ export async function sendTeams(issues, channel, clusterContext) {
54
+ await postToTeams(channel.webhook_url, formatTeamsPayload(issues, clusterContext));
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",