kubeagent 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -10,7 +10,7 @@ Built for solo DevOps engineers and small teams who want an intelligent on-call
10
10
  # Install
11
11
  npm install -g kubeagent
12
12
 
13
- # Log in (creates your account / links your cluster)
13
+ # Log in (opens browser; uses device-code flow on headless machines)
14
14
  kubeagent login
15
15
 
16
16
  # Onboard — scan cluster, detect your services, build knowledge base
@@ -64,7 +64,17 @@ Actions are classified into tiers:
64
64
 
65
65
  ## Notifications
66
66
 
67
- Configure Slack notifications from the dashboard at [app.kubeagent.net](https://app.kubeagent.net), or set up webhooks manually in `~/.kubeagent/config.yaml`:
67
+ Configure notification channels from the dashboard at [app.kubeagent.net](https://app.kubeagent.net):
68
+
69
+ | Channel | Setup |
70
+ |---------|-------|
71
+ | **Slack** | Connect via OAuth from the dashboard |
72
+ | **PagerDuty** | Add your integration key — verified automatically |
73
+ | **Discord** | Paste a Discord webhook URL |
74
+ | **Microsoft Teams** | Paste a Teams incoming webhook URL |
75
+ | **Custom Webhook** | Any HTTP endpoint that accepts JSON payloads |
76
+
77
+ You can also configure webhooks manually in `~/.kubeagent/config.yaml`:
68
78
 
69
79
  ```yaml
70
80
  webhooks:
package/dist/cli.js CHANGED
@@ -326,7 +326,7 @@ notifyCmd
326
326
  });
327
327
  notifyCmd
328
328
  .command("add")
329
- .description("Add a Slack or Telegram notification channel")
329
+ .description("Add a notification channel")
330
330
  .action(async () => {
331
331
  const { interactiveAddChannel } = await import("./notify/setup.js");
332
332
  const channel = await interactiveAddChannel();
@@ -113,7 +113,7 @@ export async function onboard() {
113
113
  if (existingChannels.length > 0) {
114
114
  console.log(chalk.dim(` ${existingChannels.length} channel(s) already configured.`));
115
115
  }
116
- const addNotify = await ask("Set up a notification channel (Slack / Telegram)? [y/N]");
116
+ const addNotify = await ask("Set up a notification channel? [y/N]");
117
117
  if (addNotify.toLowerCase() === "y") {
118
118
  let adding = true;
119
119
  while (adding) {
@@ -290,7 +290,7 @@ export async function onboard() {
290
290
  if (existingChannels.length > 0) {
291
291
  console.log(chalk.dim(` ${existingChannels.length} channel(s) already configured.`));
292
292
  }
293
- const addNotify = await ask("Set up a notification channel (Slack / Telegram)? [y/N]");
293
+ const addNotify = await ask("Set up a notification channel? [y/N]");
294
294
  if (addNotify.toLowerCase() === "y") {
295
295
  let adding = true;
296
296
  while (adding) {
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { diagnose } from "./diagnoser/index.js";
3
3
  import { verify } from "./verifier.js";
4
- import { sendNotification } from "./notify/index.js";
4
+ import { notifyViaServer } from "./proxy-client.js";
5
5
  import { writeIncident, writeUnresolved } from "./kb/writer.js";
6
6
  import { join } from "node:path";
7
7
  import { configDir } from "./config.js";
@@ -11,6 +11,7 @@ import { renderMarkdown, sectionHeader, commandBox } from "./render.js";
11
11
  import { broadcastQuestion } from "./notify/index.js";
12
12
  import readline from "node:readline";
13
13
  const MAX_ATTEMPTS = 3;
14
+ let localChannelWarningShown = false;
14
15
  const issueHistory = new Map();
15
16
  function issueKey(issue) {
16
17
  return `${issue.kind}:${issue.namespace ?? ""}:${issue.resource ?? ""}`;
@@ -115,7 +116,19 @@ export async function handleIssues(issues, config, clusterContext, noInteractive
115
116
  }
116
117
  if (actionableIssues.length === 0)
117
118
  return;
118
- await sendNotification(actionableIssues, config, clusterContext);
119
+ // Warn once if user still has local notification channels configured
120
+ if (config.notifications.channels.length && !localChannelWarningShown) {
121
+ localChannelWarningShown = true;
122
+ console.log(chalk.yellow("⚠")
123
+ + " Local notification channels detected in config. "
124
+ + "These are no longer used — configure notifications on your dashboard instead: "
125
+ + chalk.cyan("https://app.kubeagent.net/dashboard"));
126
+ }
127
+ // Dispatch via server when authenticated (SaaS-managed integrations)
128
+ const auth = loadAuth();
129
+ if (auth?.apiKey) {
130
+ await notifyViaServer(auth, actionableIssues, clusterContext);
131
+ }
119
132
  // ── Diagnosing ────────────────────────────────────────────
120
133
  sectionHeader("Diagnosing", chalk.blue.bold);
121
134
  const result = await diagnose(actionableIssues, kbDir, clusterContext, {
@@ -200,7 +213,6 @@ export async function handleIssues(issues, config, clusterContext, noInteractive
200
213
  ].join("\n");
201
214
  writeIncident(kbDir, dateStr, incidentName, incidentContent);
202
215
  // Report to SaaS if authenticated
203
- const auth = loadAuth();
204
216
  if (auth?.apiKey) {
205
217
  try {
206
218
  await reportIncident(auth, {
@@ -16,3 +16,10 @@ export declare function reportIncident(auth: AuthState, incident: {
16
16
  export declare function registerWebhook(auth: AuthState, url: string, label: string | undefined, severity: string): Promise<{
17
17
  secret?: string;
18
18
  }>;
19
+ export declare function notifyViaServer(auth: AuthState, issues: Array<{
20
+ kind: string;
21
+ severity: string;
22
+ message: string;
23
+ namespace?: string;
24
+ resource?: string;
25
+ }>, clusterContext?: string): Promise<void>;
@@ -89,3 +89,29 @@ export async function registerWebhook(auth, url, label, severity) {
89
89
  return {};
90
90
  }
91
91
  }
92
+ export async function notifyViaServer(auth, issues, clusterContext) {
93
+ const apiKey = auth.apiKey ?? auth.token;
94
+ try {
95
+ await fetch(`${auth.serverUrl}/notify`, {
96
+ method: "POST",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ Authorization: `ApiKey ${apiKey}`,
100
+ },
101
+ body: JSON.stringify({
102
+ issues: issues.map((i) => ({
103
+ kind: i.kind,
104
+ severity: i.severity,
105
+ message: i.message,
106
+ namespace: i.namespace,
107
+ resource: i.resource,
108
+ })),
109
+ clusterContext,
110
+ }),
111
+ signal: AbortSignal.timeout(15_000),
112
+ });
113
+ }
114
+ catch {
115
+ // Silent — don't break the watch loop if server is unreachable
116
+ }
117
+ }
package/dist/telemetry.js CHANGED
@@ -8,16 +8,16 @@ export function sendTelemetry(cliVersion) {
8
8
  // Only fire once per machine
9
9
  if (existsSync(SENT_FILE))
10
10
  return;
11
- // Mark as sent immediately to avoid duplicate attempts
11
+ // Ensure directory exists
12
12
  try {
13
13
  if (!existsSync(TELEMETRY_DIR))
14
14
  mkdirSync(TELEMETRY_DIR, { recursive: true });
15
- writeFileSync(SENT_FILE, new Date().toISOString());
16
15
  }
17
16
  catch {
18
17
  return;
19
18
  }
20
19
  // Fire-and-forget — never block the CLI
20
+ // Only mark as sent after successful delivery
21
21
  const payload = JSON.stringify({
22
22
  event: "cli_first_run",
23
23
  cliVersion,
@@ -30,5 +30,9 @@ export function sendTelemetry(cliVersion) {
30
30
  headers: { "Content-Type": "application/json" },
31
31
  body: payload,
32
32
  signal: AbortSignal.timeout(5000),
33
+ }).then((res) => {
34
+ if (res.ok) {
35
+ writeFileSync(SENT_FILE, new Date().toISOString());
36
+ }
33
37
  }).catch(() => { });
34
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",