kubeagent 0.1.23 → 0.1.25

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
@@ -24,10 +24,15 @@ kubeagent watch
24
24
 
25
25
  | Command | Description |
26
26
  |---------|-------------|
27
- | `status` | Quick cluster health check |
27
+ | `status` | Quick cluster health check (no LLM) |
28
28
  | `onboard` | Scan cluster + codebases, generate knowledge base |
29
29
  | `watch` | Continuous monitoring with auto-remediation |
30
30
  | `diagnose <resource>` | One-shot diagnosis of a pod/deployment/service |
31
+ | `scan <directory>` | Match local project directories to cluster deployments |
32
+ | `notify` | Manage notification channels (`list`, `add`, `remove`, `test`) |
33
+ | `login` | Log in to KubeAgent (browser or `--device` for headless) |
34
+ | `account` | Show account info and token balance |
35
+ | `logout` | Clear saved credentials |
31
36
  | `<prompt>` | Ask a freeform question about your cluster |
32
37
 
33
38
  ### Common Options
@@ -35,6 +40,7 @@ kubeagent watch
35
40
  - `-c, --context <name>` — Kubernetes context (defaults to current)
36
41
  - `-n, --namespace <name>` — Namespace (for `diagnose`)
37
42
  - `-i, --interval <seconds>` — Check interval for `watch` (default: 300)
43
+ - `--no-interactive` — Auto-deny all approvals, skip questions (for `watch` in background/CI)
38
44
 
39
45
  ## Knowledge Base
40
46
 
@@ -64,25 +70,25 @@ Actions are classified into tiers:
64
70
 
65
71
  ## Notifications
66
72
 
67
- Configure notification channels from the dashboard at [app.kubeagent.net](https://app.kubeagent.net):
73
+ Add and manage channels from the CLI:
74
+
75
+ ```bash
76
+ kubeagent notify add # interactive setup
77
+ kubeagent notify list # show configured channels
78
+ kubeagent notify test # send a test alert to all channels
79
+ kubeagent notify remove 1 # remove channel by index
80
+ ```
81
+
82
+ Supported channels:
68
83
 
69
84
  | Channel | Setup |
70
85
  |---------|-------|
71
- | **Slack** | Connect via OAuth from the dashboard |
86
+ | **Slack** | Connect via OAuth from the dashboard at [app.kubeagent.net](https://app.kubeagent.net), or paste a webhook URL |
72
87
  | **PagerDuty** | Add your integration key — verified automatically |
73
88
  | **Discord** | Paste a Discord webhook URL |
74
89
  | **Microsoft Teams** | Paste a Teams incoming webhook URL |
75
90
  | **Custom Webhook** | Any HTTP endpoint that accepts JSON payloads |
76
91
 
77
- You can also configure webhooks manually in `~/.kubeagent/config.yaml`:
78
-
79
- ```yaml
80
- webhooks:
81
- - url: https://hooks.slack.com/services/T.../B.../xxx
82
- type: slack
83
- severityFilter: critical
84
- ```
85
-
86
92
  ## Requirements
87
93
 
88
94
  - Node.js >= 20
@@ -105,7 +111,7 @@ Each instance operates independently and reports incidents to the same account.
105
111
  Safe actions (pod restarts, rollout restarts, scaling up) are applied automatically. Anything potentially destructive pauses and waits for your approval.
106
112
 
107
113
  **Where is my API key stored?**
108
- Credentials are stored locally at `~/.kubeagent/config.yaml` and are never sent anywhere except the KubeAgent API.
114
+ Credentials are stored locally at `~/.kubeagent/auth.json` and are never sent anywhere except the KubeAgent API.
109
115
 
110
116
  ## License
111
117
 
package/dist/auth.d.ts CHANGED
@@ -15,10 +15,15 @@ export declare function createApiKey(auth: AuthState, name: string): Promise<{
15
15
  key: string;
16
16
  prefix: string;
17
17
  }>;
18
+ export interface BalanceInfo {
19
+ monthlyRemaining: number;
20
+ extraRemaining: number;
21
+ totalRemaining: number;
22
+ resetsAt: string;
23
+ plan: string | null;
24
+ planName: string | null;
25
+ monthlyTotal: number;
26
+ }
18
27
  export declare function showAccount(auth: AuthState): Promise<{
19
- balance: {
20
- monthlyRemaining: number;
21
- extraRemaining: number;
22
- totalRemaining: number;
23
- };
28
+ balance: BalanceInfo;
24
29
  }>;
package/dist/cli.js CHANGED
@@ -92,6 +92,23 @@ program
92
92
  preflight.fail(`Cannot reach cluster: ${err.message}`);
93
93
  process.exit(1);
94
94
  }
95
+ // Load auth early so it's available for balance check and Slack merge below
96
+ const auth = loadAuth();
97
+ // Pre-flight: warn if token balance is low (< 20% of monthly allocation)
98
+ if (auth?.apiKey) {
99
+ try {
100
+ const { balance } = await showAccount(auth);
101
+ if (balance.monthlyTotal > 0 && balance.monthlyRemaining < balance.monthlyTotal * 0.2) {
102
+ const pct = Math.round((balance.monthlyRemaining / balance.monthlyTotal) * 100);
103
+ const upgradeUrl = `${auth.appUrl ?? auth.serverUrl}/billing`;
104
+ console.log(chalk.yellow(` ⚠ Low token balance: ${pct}% remaining (${balance.monthlyRemaining.toLocaleString()} tokens).`));
105
+ console.log(chalk.dim(` Upgrade at: `) + chalk.cyan(upgradeUrl) + "\n");
106
+ }
107
+ }
108
+ catch {
109
+ // Non-fatal — balance check failure should not prevent monitoring
110
+ }
111
+ }
95
112
  const intervalLabel = intervalSec >= 60
96
113
  ? `${Math.floor(intervalSec / 60)}m`
97
114
  : `${intervalSec}s`;
@@ -107,7 +124,6 @@ program
107
124
  console.log(chalk.dim(" Non-interactive mode — approvals auto-denied, questions skipped\n"));
108
125
  }
109
126
  // Merge server-stored Slack webhook (from dashboard "Add to Slack") into local config channels
110
- const auth = loadAuth();
111
127
  if (auth?.apiKey) {
112
128
  const serverSlack = await fetchSlackWebhook(auth);
113
129
  if (serverSlack.connected && serverSlack.webhookUrl) {
@@ -421,10 +437,37 @@ program
421
437
  }
422
438
  try {
423
439
  const { balance } = await showAccount(auth);
424
- console.log(chalk.bold("Token Balance"));
425
- console.log(` Monthly remaining: ${balance.monthlyRemaining.toLocaleString()}`);
426
- console.log(` Extra credits: ${balance.extraRemaining.toLocaleString()}`);
427
- console.log(` Total: ${balance.totalRemaining.toLocaleString()}`);
440
+ const upgradeUrl = `${auth.appUrl ?? auth.serverUrl}/billing`;
441
+ console.log(chalk.bold("\nToken Balance"));
442
+ if (balance.planName) {
443
+ console.log(` Plan: ${chalk.cyan(balance.planName)}`);
444
+ }
445
+ if (balance.monthlyTotal > 0) {
446
+ const usedPct = Math.round(((balance.monthlyTotal - balance.monthlyRemaining) / balance.monthlyTotal) * 100);
447
+ console.log(` Monthly tokens: ${balance.monthlyRemaining.toLocaleString()} / ${balance.monthlyTotal.toLocaleString()} (${usedPct}% used)`);
448
+ }
449
+ else {
450
+ console.log(` Monthly remaining: ${balance.monthlyRemaining.toLocaleString()}`);
451
+ }
452
+ if (balance.extraRemaining > 0) {
453
+ console.log(` Extra credits: ${balance.extraRemaining.toLocaleString()}`);
454
+ }
455
+ console.log(` Total remaining: ${balance.totalRemaining.toLocaleString()}`);
456
+ if (balance.resetsAt) {
457
+ const resetDate = new Date(balance.resetsAt).toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
458
+ console.log(` Resets: ${resetDate}`);
459
+ }
460
+ // Exhausted balance warning
461
+ if (balance.totalRemaining === 0) {
462
+ console.log("\n" + chalk.red(" Balance exhausted.") + " Upgrade or buy credits at " + chalk.cyan(upgradeUrl));
463
+ // Low-balance warning: < 20% of monthly allocation remaining
464
+ }
465
+ else if (balance.monthlyTotal > 0 && balance.monthlyRemaining < balance.monthlyTotal * 0.2) {
466
+ const pct = Math.round((balance.monthlyRemaining / balance.monthlyTotal) * 100);
467
+ console.log("\n" + chalk.yellow(` ⚠ Low balance: ${pct}% of monthly tokens remaining.`));
468
+ console.log(chalk.dim(` Upgrade or buy extra credits: `) + chalk.cyan(upgradeUrl));
469
+ }
470
+ console.log();
428
471
  }
429
472
  catch (err) {
430
473
  console.error(chalk.red(err.message));
@@ -461,8 +504,9 @@ program
461
504
  }
462
505
  catch (err) {
463
506
  const e = err;
464
- if (e.status === 429) {
465
- console.error(chalk.red("Token balance exhausted.") + " " + chalk.dim("Run: kubeagent account") + " to check your balance, then upgrade at " + chalk.cyan("https://app.kubeagent.net"));
507
+ if (e.status === 402 || e.code === "token_exhausted") {
508
+ const upgradeUrl = `${auth.appUrl ?? auth.serverUrl}/billing`;
509
+ console.error(chalk.red("Token balance exhausted (402).") + " " + chalk.dim("Run: ") + chalk.cyan("kubeagent account") + chalk.dim(" to check, or upgrade at ") + chalk.cyan(upgradeUrl));
466
510
  }
467
511
  else if (e.status === 401) {
468
512
  console.error(chalk.red("Not authenticated.") + " " + chalk.dim("Run: kubeagent login"));
@@ -28,6 +28,16 @@ export async function proxyRequest(auth, body) {
28
28
  dbg("proxy", `response ${res.status} from ${url}`);
29
29
  if (res.status !== 429 && res.status !== 503)
30
30
  break;
31
+ // 402 is handled above (not 429/503), but peek 429 bodies to avoid
32
+ // retrying when tokens are permanently exhausted.
33
+ if (res.status === 429) {
34
+ const clone = res.clone();
35
+ const body429 = await clone.json().catch(() => ({}));
36
+ if (body429.code === "token_exhausted") {
37
+ dbg("proxy", "token exhausted — not retrying");
38
+ break;
39
+ }
40
+ }
31
41
  dbg("proxy", `transient error ${res.status}, will retry`);
32
42
  lastErr = res;
33
43
  }
@@ -41,6 +51,7 @@ export async function proxyRequest(auth, body) {
41
51
  : "";
42
52
  throw Object.assign(new Error(`KubeAgent proxy: ${message}${hint}`), {
43
53
  status: res.status,
54
+ code: errBody.code,
44
55
  });
45
56
  }
46
57
  return (await res.json());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",