kubeagent 0.1.29 → 0.1.30

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 CHANGED
@@ -133,7 +133,8 @@ export async function loginBrowser(serverUrl, appUrl) {
133
133
  }
134
134
  console.log(`\nOpening browser for authentication...`);
135
135
  console.log(`If the browser didn't open, visit:\n ${authUrl}`);
136
- console.log(`Or on a headless/remote machine, run: kubeagent login --device\n`);
136
+ console.log(`Or on a headless/remote machine, run: kubeagent login --device`);
137
+ console.log(`\nTip: if you signed up with email/password, check your inbox and verify your email first, then approve here.\n`);
137
138
  });
138
139
  // Timeout after 5 minutes
139
140
  const timeoutId = setTimeout(() => {
package/dist/cli.js CHANGED
@@ -37,14 +37,17 @@ program
37
37
  .option("-c, --context <context>", "Kubernetes context")
38
38
  .action(async (opts) => {
39
39
  try {
40
- const issues = await runChecks({ context: opts.context });
40
+ const { issues, podCount, nodeCount, namespaceCount } = await runChecks({ context: opts.context }, true);
41
+ const clusterName = opts.context ?? "current context";
42
+ const summary = chalk.dim(`Checked ${podCount} pod${podCount !== 1 ? "s" : ""}, ${nodeCount} node${nodeCount !== 1 ? "s" : ""}, ${namespaceCount} namespace${namespaceCount !== 1 ? "s" : ""} on ${chalk.bold(clusterName)}`);
41
43
  if (issues.length === 0) {
42
- console.log(chalk.green("All clear. No issues detected."));
44
+ console.log(summary + " — " + chalk.green("All clear."));
43
45
  }
44
46
  else {
47
+ console.log(summary + "\n");
45
48
  for (const issue of issues) {
46
49
  const color = issue.severity === "critical" ? chalk.red : chalk.yellow;
47
- console.log(color(`[${issue.severity}] ${issue.message}`));
50
+ console.log(color(`[${issue.severity}] [${clusterName}] ${issue.message}`));
48
51
  }
49
52
  }
50
53
  }
@@ -2,7 +2,14 @@ import { type KubectlOptions } from "../kubectl.js";
2
2
  import type { Issue } from "./types.js";
3
3
  export type IssueCallback = (issues: Issue[]) => void | Promise<void>;
4
4
  export type ResolveCallback = (resolvedKeys: string[]) => void | Promise<void>;
5
+ export interface CheckSummary {
6
+ issues: Issue[];
7
+ podCount: number;
8
+ nodeCount: number;
9
+ namespaceCount: number;
10
+ }
5
11
  export declare function runChecks(options: KubectlOptions): Promise<Issue[]>;
12
+ export declare function runChecks(options: KubectlOptions, withSummary: true): Promise<CheckSummary>;
6
13
  export declare function computeResolvedKeys(activeKeys: Set<string>, currentKeys: Set<string>): string[];
7
14
  export declare function updateActiveKeys(activeKeys: Set<string>, resolvedKeys: string[], currentKeys: Set<string>): void;
8
15
  export declare function startMonitor(options: KubectlOptions, intervalMs: number, onIssues: IssueCallback, onResolved?: ResolveCallback): {
@@ -33,7 +33,7 @@ function deduplicateIssues(issues) {
33
33
  return true;
34
34
  });
35
35
  }
36
- export async function runChecks(options) {
36
+ export async function runChecks(options, withSummary) {
37
37
  const allIssues = [];
38
38
  // Get all pods across namespaces
39
39
  const podList = (await kubectlJson(["get", "pods", "--all-namespaces"], options));
@@ -44,7 +44,18 @@ export async function runChecks(options) {
44
44
  // Get failed jobs
45
45
  const jobList = (await kubectlJson(["get", "jobs", "--all-namespaces", "--field-selector=status.failed>0"], options).catch(() => ({ items: [] })));
46
46
  allIssues.push(...findJobIssues(jobList));
47
- return deduplicateIssues(allIssues);
47
+ const issues = deduplicateIssues(allIssues);
48
+ if (!withSummary)
49
+ return issues;
50
+ // Fetch namespace count (non-fatal)
51
+ const nsList = await kubectlJson(["get", "namespaces"], options)
52
+ .catch(() => ({ items: [] }));
53
+ return {
54
+ issues,
55
+ podCount: podList.items.length,
56
+ nodeCount: nodeList.items.length,
57
+ namespaceCount: nsList.items.length,
58
+ };
48
59
  }
49
60
  // How long a pod must be Pending before it's reported as an issue.
50
61
  const PENDING_GRACE_MS = 60_000;
@@ -1,8 +1,32 @@
1
1
  import chalk from "chalk";
2
2
  import { createRequire } from "node:module";
3
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
3
6
  const require = createRequire(import.meta.url);
4
7
  const { version: currentVersion } = require("../package.json");
5
8
  const REGISTRY_URL = "https://registry.npmjs.org/kubeagent/latest";
9
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ function cacheFilePath() {
11
+ return join(homedir(), ".kubeagent", "update-check.json");
12
+ }
13
+ function readCache() {
14
+ try {
15
+ return JSON.parse(readFileSync(cacheFilePath(), "utf-8"));
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function writeCache(cache) {
22
+ try {
23
+ mkdirSync(join(homedir(), ".kubeagent"), { recursive: true });
24
+ writeFileSync(cacheFilePath(), JSON.stringify(cache), "utf-8");
25
+ }
26
+ catch {
27
+ // Non-fatal
28
+ }
29
+ }
6
30
  function isNewer(latest, current) {
7
31
  const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
8
32
  const [lMaj, lMin, lPatch] = parse(latest);
@@ -15,22 +39,40 @@ function isNewer(latest, current) {
15
39
  }
16
40
  export async function checkForUpdate() {
17
41
  try {
18
- const controller = new AbortController();
19
- const timeout = setTimeout(() => controller.abort(), 3000);
20
- const res = await fetch(REGISTRY_URL, { signal: controller.signal });
21
- clearTimeout(timeout);
22
- if (!res.ok)
23
- return;
24
- const data = (await res.json());
25
- const latest = data.version;
42
+ const now = Date.now();
43
+ const cache = readCache();
44
+ // Fetch from registry if no cache or cache is stale (> 24h)
45
+ let latest;
46
+ if (cache && now - cache.checkedAt < CACHE_TTL_MS) {
47
+ latest = cache.latestVersion;
48
+ }
49
+ else {
50
+ const controller = new AbortController();
51
+ const timeout = setTimeout(() => controller.abort(), 3000);
52
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
53
+ clearTimeout(timeout);
54
+ if (!res.ok)
55
+ return;
56
+ const data = (await res.json());
57
+ latest = data.version;
58
+ if (latest) {
59
+ writeCache({ checkedAt: now, latestVersion: latest, shownAt: cache?.shownAt });
60
+ }
61
+ }
26
62
  if (!latest || !isNewer(latest, currentVersion))
27
63
  return;
64
+ // Show notification at most once per 24h regardless of how many commands are run
65
+ if (cache?.shownAt && now - cache.shownAt < CACHE_TTL_MS)
66
+ return;
28
67
  console.log("\n" +
29
68
  chalk.yellow(` Update available: ${currentVersion} → ${latest}`) +
30
69
  "\n" +
31
70
  chalk.dim(" Run ") +
32
71
  chalk.cyan("npm update -g kubeagent") +
33
72
  chalk.dim(" to update\n"));
73
+ // Record that we showed the notification; always use `now` for checkedAt so
74
+ // a fresh registry fetch isn't overwritten by the stale cached timestamp.
75
+ writeCache({ checkedAt: now, latestVersion: latest, shownAt: now });
34
76
  }
35
77
  catch {
36
78
  // Network errors, timeouts — silently ignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",