@pleri/olam-cli 0.1.188 → 0.1.195

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.
Files changed (156) hide show
  1. package/README.md +1 -1
  2. package/dist/ask/knowledge-pack.generated.d.ts.map +1 -1
  3. package/dist/ask/knowledge-pack.generated.js +37 -12
  4. package/dist/ask/knowledge-pack.generated.js.map +1 -1
  5. package/dist/commands/bootstrap.d.ts +4 -0
  6. package/dist/commands/bootstrap.d.ts.map +1 -1
  7. package/dist/commands/bootstrap.js +6 -9
  8. package/dist/commands/bootstrap.js.map +1 -1
  9. package/dist/commands/clean.js +1 -1
  10. package/dist/commands/clean.js.map +1 -1
  11. package/dist/commands/completion.d.ts.map +1 -1
  12. package/dist/commands/completion.js +1 -4
  13. package/dist/commands/completion.js.map +1 -1
  14. package/dist/commands/create.d.ts.map +1 -1
  15. package/dist/commands/create.js +6 -0
  16. package/dist/commands/create.js.map +1 -1
  17. package/dist/commands/crystallize.js +12 -14
  18. package/dist/commands/crystallize.js.map +1 -1
  19. package/dist/commands/destroy.d.ts +13 -1
  20. package/dist/commands/destroy.d.ts.map +1 -1
  21. package/dist/commands/destroy.js +52 -6
  22. package/dist/commands/destroy.js.map +1 -1
  23. package/dist/commands/dispatch.d.ts +9 -0
  24. package/dist/commands/dispatch.d.ts.map +1 -1
  25. package/dist/commands/dispatch.js +21 -2
  26. package/dist/commands/dispatch.js.map +1 -1
  27. package/dist/commands/doctor.d.ts +1 -1
  28. package/dist/commands/doctor.d.ts.map +1 -1
  29. package/dist/commands/doctor.js +29 -22
  30. package/dist/commands/doctor.js.map +1 -1
  31. package/dist/commands/enter.d.ts +3 -3
  32. package/dist/commands/enter.d.ts.map +1 -1
  33. package/dist/commands/enter.js +57 -44
  34. package/dist/commands/enter.js.map +1 -1
  35. package/dist/commands/flywheel/index.d.ts.map +1 -1
  36. package/dist/commands/flywheel/index.js +1 -1
  37. package/dist/commands/flywheel/index.js.map +1 -1
  38. package/dist/commands/host-cp.d.ts.map +1 -1
  39. package/dist/commands/host-cp.js +2 -1
  40. package/dist/commands/host-cp.js.map +1 -1
  41. package/dist/commands/implode.d.ts.map +1 -1
  42. package/dist/commands/implode.js +1 -1
  43. package/dist/commands/implode.js.map +1 -1
  44. package/dist/commands/init.d.ts +20 -0
  45. package/dist/commands/init.d.ts.map +1 -1
  46. package/dist/commands/init.js +102 -9
  47. package/dist/commands/init.js.map +1 -1
  48. package/dist/commands/kg-build.d.ts.map +1 -1
  49. package/dist/commands/kg-build.js +3 -0
  50. package/dist/commands/kg-build.js.map +1 -1
  51. package/dist/commands/kg-classify.d.ts +20 -0
  52. package/dist/commands/kg-classify.d.ts.map +1 -1
  53. package/dist/commands/kg-classify.js +59 -42
  54. package/dist/commands/kg-classify.js.map +1 -1
  55. package/dist/commands/kg-mirror.d.ts +40 -0
  56. package/dist/commands/kg-mirror.d.ts.map +1 -0
  57. package/dist/commands/kg-mirror.js +228 -0
  58. package/dist/commands/kg-mirror.js.map +1 -0
  59. package/dist/commands/mcp/index.js +1 -1
  60. package/dist/commands/mcp/index.js.map +1 -1
  61. package/dist/commands/memory/index.d.ts.map +1 -1
  62. package/dist/commands/memory/index.js +1 -1
  63. package/dist/commands/memory/index.js.map +1 -1
  64. package/dist/commands/resume.d.ts.map +1 -1
  65. package/dist/commands/resume.js +1 -1
  66. package/dist/commands/resume.js.map +1 -1
  67. package/dist/commands/services-tls.d.ts +120 -0
  68. package/dist/commands/services-tls.d.ts.map +1 -0
  69. package/dist/commands/services-tls.js +434 -0
  70. package/dist/commands/services-tls.js.map +1 -0
  71. package/dist/commands/services.d.ts.map +1 -1
  72. package/dist/commands/services.js +28 -1
  73. package/dist/commands/services.js.map +1 -1
  74. package/dist/commands/setup-linux-gate.d.ts.map +1 -1
  75. package/dist/commands/setup-linux-gate.js +1 -3
  76. package/dist/commands/setup-linux-gate.js.map +1 -1
  77. package/dist/commands/setup-metrics.d.ts.map +1 -1
  78. package/dist/commands/setup-metrics.js +1 -2
  79. package/dist/commands/setup-metrics.js.map +1 -1
  80. package/dist/commands/setup-phase-5a-skill-source.d.ts +17 -1
  81. package/dist/commands/setup-phase-5a-skill-source.d.ts.map +1 -1
  82. package/dist/commands/setup-phase-5a-skill-source.js +69 -6
  83. package/dist/commands/setup-phase-5a-skill-source.js.map +1 -1
  84. package/dist/commands/setup.d.ts +26 -1
  85. package/dist/commands/setup.d.ts.map +1 -1
  86. package/dist/commands/setup.js +189 -47
  87. package/dist/commands/setup.js.map +1 -1
  88. package/dist/commands/skills-onboard.d.ts.map +1 -1
  89. package/dist/commands/skills-onboard.js +4 -1
  90. package/dist/commands/skills-onboard.js.map +1 -1
  91. package/dist/commands/skills-source.d.ts.map +1 -1
  92. package/dist/commands/skills-source.js +20 -4
  93. package/dist/commands/skills-source.js.map +1 -1
  94. package/dist/commands/status.js +1 -1
  95. package/dist/commands/status.js.map +1 -1
  96. package/dist/commands/upgrade.d.ts.map +1 -1
  97. package/dist/commands/upgrade.js +1 -3
  98. package/dist/commands/upgrade.js.map +1 -1
  99. package/dist/commands/yolo.d.ts.map +1 -1
  100. package/dist/commands/yolo.js +1 -1
  101. package/dist/commands/yolo.js.map +1 -1
  102. package/dist/context.d.ts +4 -0
  103. package/dist/context.d.ts.map +1 -1
  104. package/dist/context.js +3 -2
  105. package/dist/context.js.map +1 -1
  106. package/dist/image-digests.json +8 -8
  107. package/dist/index.js +3846 -2232
  108. package/dist/index.js.map +1 -1
  109. package/dist/lib/auth-refresh-kubernetes.d.ts.map +1 -1
  110. package/dist/lib/auth-refresh-kubernetes.js +14 -5
  111. package/dist/lib/auth-refresh-kubernetes.js.map +1 -1
  112. package/dist/lib/bootstrap-kubernetes.d.ts +41 -0
  113. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  114. package/dist/lib/bootstrap-kubernetes.js +289 -36
  115. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  116. package/dist/lib/cf-access-token.d.ts.map +1 -1
  117. package/dist/lib/cf-access-token.js +2 -3
  118. package/dist/lib/cf-access-token.js.map +1 -1
  119. package/dist/lib/help-groups.d.ts +36 -0
  120. package/dist/lib/help-groups.d.ts.map +1 -0
  121. package/dist/lib/help-groups.js +124 -0
  122. package/dist/lib/help-groups.js.map +1 -0
  123. package/dist/lib/k8s-bootstrap.d.ts +6 -0
  124. package/dist/lib/k8s-bootstrap.d.ts.map +1 -1
  125. package/dist/lib/k8s-bootstrap.js +15 -2
  126. package/dist/lib/k8s-bootstrap.js.map +1 -1
  127. package/dist/lib/k8s-secret-render.d.ts.map +1 -1
  128. package/dist/lib/k8s-secret-render.js +17 -10
  129. package/dist/lib/k8s-secret-render.js.map +1 -1
  130. package/dist/lib/memory-secret.d.ts +15 -2
  131. package/dist/lib/memory-secret.d.ts.map +1 -1
  132. package/dist/lib/memory-secret.js +25 -8
  133. package/dist/lib/memory-secret.js.map +1 -1
  134. package/dist/lib/upgrade-check.d.ts +60 -0
  135. package/dist/lib/upgrade-check.d.ts.map +1 -0
  136. package/dist/lib/upgrade-check.js +169 -0
  137. package/dist/lib/upgrade-check.js.map +1 -0
  138. package/dist/lib/upgrade-kubernetes.d.ts +17 -0
  139. package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
  140. package/dist/lib/upgrade-kubernetes.js +125 -1
  141. package/dist/lib/upgrade-kubernetes.js.map +1 -1
  142. package/dist/mcp-server.js +2651 -2850
  143. package/hermes-bundle/version.json +1 -1
  144. package/host-cp/k8s/manifests/30-configmap.yaml +8 -1
  145. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  146. package/host-cp/k8s/manifests/60-service.yaml +12 -4
  147. package/host-cp/k8s/manifests/70-ingressroute.yaml +58 -0
  148. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  149. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  150. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  151. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  152. package/host-cp/src/plan-chat-secret.mjs +16 -1
  153. package/host-cp/src/plan-chat-service.mjs +493 -11
  154. package/host-cp/src/planning-sessions.mjs +252 -0
  155. package/host-cp/src/server.mjs +92 -2
  156. package/package.json +2 -1
@@ -16,7 +16,8 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, renameSyn
16
16
  import { homedir } from 'node:os';
17
17
  import { join, dirname } from 'node:path';
18
18
  import { randomBytes } from 'node:crypto';
19
- export const MEMORY_SECRET_PATH = join(homedir(), '.olam', 'memory-secret');
19
+ import { resolveSecretPath, canonicalSecretPath } from '@olam/core/src/secrets/paths.js';
20
+ export const MEMORY_SECRET_PATH = resolveSecretPath('memory-secret');
20
21
  /**
21
22
  * Default cloud-mode secret path. Phase C C2.
22
23
  *
@@ -25,7 +26,13 @@ export const MEMORY_SECRET_PATH = join(homedir(), '.olam', 'memory-secret');
25
26
  * config-resolver (C4) handles the precedence; this constant is the
26
27
  * fall-through when the operator doesn't override.
27
28
  */
28
- export const CLOUD_MEMORY_SECRET_PATH = join(homedir(), '.olam', 'cloud-memory-secret');
29
+ export const CLOUD_MEMORY_SECRET_PATH = resolveSecretPath('cloud-memory-secret');
30
+ /**
31
+ * Canonical (new) location for memory secret — used by writers.
32
+ * Defined here as a convenience alias so callers don't need to import from core.
33
+ */
34
+ export const MEMORY_SECRET_CANONICAL_PATH = canonicalSecretPath('memory-secret');
35
+ export const CLOUD_MEMORY_SECRET_CANONICAL_PATH = canonicalSecretPath('cloud-memory-secret');
29
36
  export const SECRET_LEN_BYTES = 32; // 64 hex chars
30
37
  /** Generate a 64-char hex secret. */
31
38
  export function generateSecret() {
@@ -36,7 +43,7 @@ export function generateSecret() {
36
43
  * created with default mode if absent.
37
44
  */
38
45
  export function writeSecretAtPath(path, value) {
39
- mkdirSync(dirname(path), { recursive: true });
46
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
40
47
  const tmp = `${path}.tmp.${process.pid}`;
41
48
  writeFileSync(tmp, value, { mode: 0o600 });
42
49
  // writeFileSync's mode is umask-respecting; chmod explicitly to be safe.
@@ -65,15 +72,20 @@ export function readSecretAtPath(path) {
65
72
  return v;
66
73
  }
67
74
  /**
68
- * Ensure `~/.olam/memory-secret` exists with mode 0600. Generates a
75
+ * Ensure `~/.olam/secrets/memory-secret` exists with mode 0600. Generates a
69
76
  * fresh 64-char hex secret on first call. Idempotent.
77
+ *
78
+ * Reads from the resolved path (new or legacy fallback); writes to canonical.
70
79
  */
71
80
  export function ensureMemorySecret(path = MEMORY_SECRET_PATH) {
72
81
  const existing = readSecretAtPathOrNull(path);
73
82
  if (existing)
74
83
  return existing;
75
84
  const fresh = generateSecret();
76
- writeSecretAtPath(path, fresh);
85
+ // When using the default resolved path, write to canonical new location.
86
+ // When given an explicit path (test seam or direct caller), honour it for write too.
87
+ const writePath = path === MEMORY_SECRET_PATH ? MEMORY_SECRET_CANONICAL_PATH : path;
88
+ writeSecretAtPath(writePath, fresh);
77
89
  return fresh;
78
90
  }
79
91
  /** Read the local-mode memory secret (no-throw shape). */
@@ -88,8 +100,10 @@ export function readMemorySecret(path = MEMORY_SECRET_PATH) {
88
100
  * Rotate the secret: generate fresh value, atomically replace the file,
89
101
  * return the new value. Caller is responsible for restarting any
90
102
  * running memory service so it picks up the new value.
103
+ *
104
+ * Always writes to canonical path (~/.olam/secrets/memory-secret).
91
105
  */
92
- export function rotateMemorySecret(path = MEMORY_SECRET_PATH) {
106
+ export function rotateMemorySecret(path = MEMORY_SECRET_CANONICAL_PATH) {
93
107
  const fresh = generateSecret();
94
108
  writeSecretAtPath(path, fresh);
95
109
  return fresh;
@@ -119,8 +133,11 @@ export function readCloudMemorySecretOrNull(path = CLOUD_MEMORY_SECRET_PATH) {
119
133
  export function readCloudMemorySecret(path = CLOUD_MEMORY_SECRET_PATH) {
120
134
  return readSecretAtPath(path);
121
135
  }
122
- /** Atomic write 0600. Caller supplies the value (cloud secrets are operator-prompted, not generated). */
123
- export function writeCloudMemorySecret(value, path = CLOUD_MEMORY_SECRET_PATH) {
136
+ /**
137
+ * Atomic write 0600. Caller supplies the value (cloud secrets are
138
+ * operator-prompted, not generated). Always writes to canonical path.
139
+ */
140
+ export function writeCloudMemorySecret(value, path = CLOUD_MEMORY_SECRET_CANONICAL_PATH) {
124
141
  writeSecretAtPath(path, value);
125
142
  }
126
143
  /** Convenience: is a cloud secret on disk? */
@@ -1 +1 @@
1
- {"version":3,"file":"memory-secret.js","sourceRoot":"","sources":["../../src/lib/memory-secret.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-secret.js","sourceRoot":"","sources":["../../src/lib/memory-secret.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * upgrade-check.ts — non-blocking upgrade-available banner for olam CLI.
3
+ *
4
+ * On each `olam <command>` invocation, asynchronously checks whether a newer
5
+ * version of `@pleri/olam-cli` is available on npm. The result is cached for
6
+ * 24 hours in `~/.olam/cli-upgrade-cache.json` so the npm check fires at most
7
+ * once per day. Any network or FS failure silently skips the banner.
8
+ *
9
+ * Suppression rules (any match → silent):
10
+ * - OLAM_DISABLE_UPGRADE_BANNER=1
11
+ * - --json flag present in argv
12
+ * - stdout is not a TTY
13
+ */
14
+ export declare const UPGRADE_CACHE_TTL_MS: number;
15
+ export declare const NPM_CHECK_TIMEOUT_MS = 2000;
16
+ export declare const DEFAULT_CACHE_PATH: string;
17
+ /**
18
+ * Fetch the latest published version of @pleri/olam-cli from the npm registry.
19
+ * Returns null on any network error or timeout.
20
+ */
21
+ export declare function fetchLatestVersion(timeoutMs?: number): Promise<string | null>;
22
+ /**
23
+ * Determine the latest version, using cache when fresh.
24
+ * Always returns without throwing. Returns null when unavailable.
25
+ */
26
+ export declare function resolveLatestVersion(opts: {
27
+ cachePath?: string;
28
+ now?: number;
29
+ fetchFn?: (timeout: number) => Promise<string | null>;
30
+ }): Promise<string | null>;
31
+ /**
32
+ * Build the upgrade banner string (with box-drawing characters).
33
+ */
34
+ export declare function buildBanner(latestVersion: string, currentVersion: string): string;
35
+ /**
36
+ * Returns true when the upgrade banner should be suppressed.
37
+ */
38
+ export declare function shouldSuppressBanner(opts: {
39
+ env?: Record<string, string | undefined>;
40
+ argv?: readonly string[];
41
+ isTTY?: boolean;
42
+ }): boolean;
43
+ /**
44
+ * Fire-and-forget upgrade check. Resolves the latest version, prints a banner
45
+ * to stdout if a newer version is available, and swallows all errors.
46
+ *
47
+ * Call this BEFORE program.parseAsync(). The promise is not awaited — the
48
+ * banner races the command action. For short commands this means the banner
49
+ * arrives before exit; for fast commands that call process.exit explicitly
50
+ * (e.g. olam --version), the banner may be suppressed naturally.
51
+ */
52
+ export declare function scheduleUpgradeCheck(currentVersion: string, opts?: {
53
+ cachePath?: string;
54
+ fetchFn?: (timeout: number) => Promise<string | null>;
55
+ env?: Record<string, string | undefined>;
56
+ argv?: readonly string[];
57
+ isTTY?: boolean;
58
+ write?: (s: string) => void;
59
+ }): Promise<void>;
60
+ //# sourceMappingURL=upgrade-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-check.d.ts","sourceRoot":"","sources":["../../src/lib/upgrade-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AACxD,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAE1C,eAAO,MAAM,kBAAkB,QAA6D,CAAC;AAgD7F;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,SAAuB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuBxB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACvD,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBzB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAWjF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CASV;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * upgrade-check.ts — non-blocking upgrade-available banner for olam CLI.
3
+ *
4
+ * On each `olam <command>` invocation, asynchronously checks whether a newer
5
+ * version of `@pleri/olam-cli` is available on npm. The result is cached for
6
+ * 24 hours in `~/.olam/cli-upgrade-cache.json` so the npm check fires at most
7
+ * once per day. Any network or FS failure silently skips the banner.
8
+ *
9
+ * Suppression rules (any match → silent):
10
+ * - OLAM_DISABLE_UPGRADE_BANNER=1
11
+ * - --json flag present in argv
12
+ * - stdout is not a TTY
13
+ */
14
+ import * as fs from 'node:fs';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+ export const UPGRADE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
18
+ export const NPM_CHECK_TIMEOUT_MS = 2_000;
19
+ export const DEFAULT_CACHE_PATH = path.join(os.homedir(), '.olam', 'cli-upgrade-cache.json');
20
+ function parseVersion(v) {
21
+ const parts = v.replace(/^[^0-9]*/, '').split('.').map(Number);
22
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
23
+ }
24
+ function isNewer(latest, current) {
25
+ const [la, lb, lc] = parseVersion(latest);
26
+ const [ca, cb, cc] = parseVersion(current);
27
+ if (la !== ca)
28
+ return la > ca;
29
+ if (lb !== cb)
30
+ return lb > cb;
31
+ return lc > cc;
32
+ }
33
+ function readCache(cachePath) {
34
+ try {
35
+ const raw = fs.readFileSync(cachePath, 'utf-8');
36
+ const parsed = JSON.parse(raw);
37
+ if (typeof parsed === 'object' &&
38
+ parsed !== null &&
39
+ typeof parsed['checkedAt'] === 'number' &&
40
+ typeof parsed['latestVersion'] === 'string') {
41
+ return parsed;
42
+ }
43
+ }
44
+ catch {
45
+ // unreadable or missing — treat as no cache
46
+ }
47
+ return null;
48
+ }
49
+ function writeCache(cachePath, data) {
50
+ try {
51
+ const dir = path.dirname(cachePath);
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ fs.writeFileSync(cachePath, JSON.stringify(data), 'utf-8');
54
+ }
55
+ catch {
56
+ // non-fatal
57
+ }
58
+ }
59
+ /**
60
+ * Fetch the latest published version of @pleri/olam-cli from the npm registry.
61
+ * Returns null on any network error or timeout.
62
+ */
63
+ export async function fetchLatestVersion(timeoutMs = NPM_CHECK_TIMEOUT_MS) {
64
+ const ac = new AbortController();
65
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
66
+ try {
67
+ const res = await fetch('https://registry.npmjs.org/@pleri/olam-cli/latest', {
68
+ signal: ac.signal,
69
+ headers: { accept: 'application/json' },
70
+ });
71
+ if (!res.ok)
72
+ return null;
73
+ const json = (await res.json());
74
+ if (typeof json === 'object' &&
75
+ json !== null &&
76
+ typeof json['version'] === 'string') {
77
+ return json['version'];
78
+ }
79
+ return null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+ /**
89
+ * Determine the latest version, using cache when fresh.
90
+ * Always returns without throwing. Returns null when unavailable.
91
+ */
92
+ export async function resolveLatestVersion(opts) {
93
+ const cachePath = opts.cachePath ?? DEFAULT_CACHE_PATH;
94
+ const now = opts.now ?? Date.now();
95
+ const fetchFn = opts.fetchFn ?? fetchLatestVersion;
96
+ const cached = readCache(cachePath);
97
+ if (cached !== null && now - cached.checkedAt < UPGRADE_CACHE_TTL_MS) {
98
+ return cached.latestVersion;
99
+ }
100
+ let latest = null;
101
+ try {
102
+ latest = await fetchFn(NPM_CHECK_TIMEOUT_MS);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ if (latest !== null) {
108
+ writeCache(cachePath, { checkedAt: now, latestVersion: latest });
109
+ }
110
+ return latest;
111
+ }
112
+ /**
113
+ * Build the upgrade banner string (with box-drawing characters).
114
+ */
115
+ export function buildBanner(latestVersion, currentVersion) {
116
+ const line1 = ` olam v${latestVersion} available (you have v${currentVersion}) `;
117
+ const line2 = ` Update: npm install -g @pleri/olam-cli@latest `;
118
+ const width = Math.max(line1.length, line2.length);
119
+ const pad1 = line1.padEnd(width);
120
+ const pad2 = line2.padEnd(width);
121
+ const top = `╭${'─'.repeat(width)}╮`;
122
+ const mid1 = `│${pad1}│`;
123
+ const mid2 = `│${pad2}│`;
124
+ const bot = `╰${'─'.repeat(width)}╯`;
125
+ return [top, mid1, mid2, bot].join('\n') + '\n';
126
+ }
127
+ /**
128
+ * Returns true when the upgrade banner should be suppressed.
129
+ */
130
+ export function shouldSuppressBanner(opts) {
131
+ const env = opts.env ?? process.env;
132
+ const argv = opts.argv ?? process.argv;
133
+ const isTTY = opts.isTTY ?? process.stdout.isTTY;
134
+ if (env['OLAM_DISABLE_UPGRADE_BANNER'] === '1')
135
+ return true;
136
+ if (!isTTY)
137
+ return true;
138
+ if (argv.includes('--json'))
139
+ return true;
140
+ return false;
141
+ }
142
+ /**
143
+ * Fire-and-forget upgrade check. Resolves the latest version, prints a banner
144
+ * to stdout if a newer version is available, and swallows all errors.
145
+ *
146
+ * Call this BEFORE program.parseAsync(). The promise is not awaited — the
147
+ * banner races the command action. For short commands this means the banner
148
+ * arrives before exit; for fast commands that call process.exit explicitly
149
+ * (e.g. olam --version), the banner may be suppressed naturally.
150
+ */
151
+ export function scheduleUpgradeCheck(currentVersion, opts) {
152
+ if (shouldSuppressBanner(opts ?? {})) {
153
+ return Promise.resolve();
154
+ }
155
+ const write = opts?.write ?? ((s) => process.stdout.write(s));
156
+ return resolveLatestVersion({
157
+ cachePath: opts?.cachePath,
158
+ fetchFn: opts?.fetchFn,
159
+ })
160
+ .then((latest) => {
161
+ if (latest !== null && isNewer(latest, currentVersion)) {
162
+ write(buildBanner(latest, currentVersion));
163
+ }
164
+ })
165
+ .catch(() => {
166
+ // never surface
167
+ });
168
+ }
169
+ //# sourceMappingURL=upgrade-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-check.js","sourceRoot":"","sources":["../../src/lib/upgrade-check.ts"],"names":[],"mappings":"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"}
@@ -167,6 +167,23 @@ export interface UpgradeKubernetesResult {
167
167
  readonly exitCode: number;
168
168
  readonly summary: string;
169
169
  }
170
+ /**
171
+ * Early-exit probe for healthy-cluster re-runs.
172
+ *
173
+ * Reads the host-cp image spec from the on-disk 50-deployment.yaml, queries
174
+ * the running deployment's current image + readyReplicas, and returns true
175
+ * when the cluster is already at the desired state. When true the caller
176
+ * prints a single skip line and exits 0 without running any of the 8 steps.
177
+ *
178
+ * Fail-open: any read/parse/kubectl error returns false (run the full flow).
179
+ * Only triggers when --force-refresh-manifests is NOT set.
180
+ *
181
+ * Exported for unit testing (injectable deps).
182
+ */
183
+ export declare function probeClusterAlreadyAtDesiredState(context: string, manifestsDir: string, deps: UpgradeKubernetesDeps): Promise<{
184
+ atDesiredState: boolean;
185
+ desiredDigest: string | null;
186
+ }>;
170
187
  /**
171
188
  * Main entrypoint for the kubernetes upgrade path.
172
189
  *
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade-kubernetes.d.ts","sourceRoot":"","sources":["../../src/lib/upgrade-kubernetes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upgrade-kubernetes.d.ts","sourceRoot":"","sources":["../../src/lib/upgrade-kubernetes.ts"],"names":[],"mappings":"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"}
@@ -479,6 +479,67 @@ async function createGhcrPullSecret(context, deps, stderr) {
479
479
  // two-volume hostPath approach, which is fully retracted (Phase B B2).
480
480
  // host-cp now reaches docker via TCP through the docker-socket-proxy
481
481
  // ExternalName Service — no docker socket bind into k3d nodes at all.
482
+ /**
483
+ * Early-exit probe for healthy-cluster re-runs.
484
+ *
485
+ * Reads the host-cp image spec from the on-disk 50-deployment.yaml, queries
486
+ * the running deployment's current image + readyReplicas, and returns true
487
+ * when the cluster is already at the desired state. When true the caller
488
+ * prints a single skip line and exits 0 without running any of the 8 steps.
489
+ *
490
+ * Fail-open: any read/parse/kubectl error returns false (run the full flow).
491
+ * Only triggers when --force-refresh-manifests is NOT set.
492
+ *
493
+ * Exported for unit testing (injectable deps).
494
+ */
495
+ export async function probeClusterAlreadyAtDesiredState(context, manifestsDir, deps) {
496
+ const readFileSyncImpl = deps.readFileSyncImpl ?? fs.readFileSync;
497
+ const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
498
+ // 1. Read desired image from 50-deployment.yaml
499
+ const deploymentManifestPath = path.join(manifestsDir, '50-deployment.yaml');
500
+ let desiredImage = null;
501
+ try {
502
+ const raw = readFileSyncImpl(deploymentManifestPath, 'utf8');
503
+ // Match the main container image line: "image: <ref>@sha256:<digest>"
504
+ // The manifest uses `image: ghcr.io/pleri/olam-host-cp@sha256:<digest>`
505
+ const match = raw.match(/^\s+image:\s+(ghcr\.io\/pleri\/olam-host-cp@sha256:[a-f0-9]+)/m);
506
+ if (match?.[1]) {
507
+ desiredImage = match[1].trim();
508
+ }
509
+ }
510
+ catch {
511
+ return { atDesiredState: false, desiredDigest: null };
512
+ }
513
+ if (!desiredImage) {
514
+ // Could not parse desired image — fail open.
515
+ return { atDesiredState: false, desiredDigest: null };
516
+ }
517
+ // Extract just the digest portion for reporting.
518
+ const digestMatch = desiredImage.match(/sha256:[a-f0-9]+/);
519
+ const desiredDigest = digestMatch?.[0] ?? null;
520
+ // 2. Query running deployment image + readyReplicas
521
+ const imageResult = await wrap([
522
+ '--context', context,
523
+ 'get', 'deploy', HOST_CP_DEPLOYMENT_NAME,
524
+ '-n', K8S_NAMESPACE,
525
+ '-o', `jsonpath={.spec.template.spec.containers[?(@.name=="${HOST_CP_DEPLOYMENT_NAME}")].image}`,
526
+ ], { timeout: 10_000 });
527
+ if (!imageResult.ok || !imageResult.stdout.trim()) {
528
+ return { atDesiredState: false, desiredDigest };
529
+ }
530
+ const runningImage = imageResult.stdout.trim();
531
+ // 3. Check readyReplicas
532
+ const replicasResult = await wrap([
533
+ '--context', context,
534
+ 'get', 'deploy', HOST_CP_DEPLOYMENT_NAME,
535
+ '-n', K8S_NAMESPACE,
536
+ '-o', 'jsonpath={.status.readyReplicas}',
537
+ ], { timeout: 10_000 });
538
+ const readyReplicas = parseInt(replicasResult.stdout.trim() || '0', 10);
539
+ // 4. Cluster is at desired state iff images match AND deployment is healthy.
540
+ const atDesiredState = runningImage === desiredImage && readyReplicas >= 1;
541
+ return { atDesiredState, desiredDigest };
542
+ }
482
543
  /**
483
544
  * Main entrypoint for the kubernetes upgrade path.
484
545
  *
@@ -505,6 +566,43 @@ export async function runUpgradeKubernetes(opts = {}, deps = {}) {
505
566
  }
506
567
  // skipped: dir exists or bundle absent (dev context) — no output, continue.
507
568
  }
569
+ // ── Pre-step 0b: early-exit probe — skip all 8 steps on healthy re-run ──
570
+ // Reads desired host-cp image from 50-deployment.yaml, compares to running
571
+ // deployment. When they match AND readyReplicas >= 1, nothing has changed:
572
+ // all 8 steps are no-ops, so we skip them and report in <1s.
573
+ // Only active when --force-refresh-manifests is NOT set (that flag is the
574
+ // explicit "re-run everything" escape hatch).
575
+ if (!opts.forceRefreshManifests) {
576
+ // Need a context for the probe — resolve it first with a lightweight helper.
577
+ const earlyCtxResolved = resolveKubectlContext(deps.configPath);
578
+ let earlyCtx = null;
579
+ if (earlyCtxResolved.error === undefined) {
580
+ earlyCtx = earlyCtxResolved.context;
581
+ }
582
+ else {
583
+ // Try auto-pin (same logic as the main flow below).
584
+ const autoPin = (deps.autoPinImpl ?? autoPinKubectlContext)({ configPath: deps.configPath });
585
+ if (autoPin.context !== undefined) {
586
+ earlyCtx = autoPin.context;
587
+ }
588
+ }
589
+ if (earlyCtx !== null) {
590
+ const probe = await probeClusterAlreadyAtDesiredState(earlyCtx, manifestsDir, deps);
591
+ if (probe.atDesiredState) {
592
+ const digest = probe.desiredDigest ?? '(unknown)';
593
+ stdout.write(`${pc.green('✓')} cluster already at desired state — host-cp ${pc.dim(digest)} ready, skipping 8-step flow\n`);
594
+ // Still emit the instrumentation event so dashboards track re-runs.
595
+ const emitImpl = deps.emitImpl ?? emitUpgradeComplete;
596
+ emitImpl({
597
+ substrate: 'kubernetes',
598
+ duration_ms: Date.now() - startMs,
599
+ flavor: 'k3s',
600
+ skipped: true,
601
+ }, deps.emitOpts ?? {});
602
+ return { exitCode: 0, summary: `cluster already at desired state (host-cp ${digest})` };
603
+ }
604
+ }
605
+ }
508
606
  // ── Step 0.4: R3-C — create/update ghcr-pull imagePullSecret ────────
509
607
  // RELOCATED (R4-W2-A): this step previously ran before ensureK8sBootstrap,
510
608
  // but the `olam` namespace doesn't exist yet on a fresh cluster — `kubectl
@@ -607,6 +705,11 @@ export async function runUpgradeKubernetes(opts = {}, deps = {}) {
607
705
  rotateSecrets: opts.rotateSecrets === true,
608
706
  }, {
609
707
  kubectlWrapImpl: deps.kubectlWrapImpl,
708
+ // Route per-manifest progress through spinner.text so each file
709
+ // updates in-place instead of printing a new line (B4 spam fix).
710
+ onProgress: (label) => {
711
+ step05Spinner.text = `Bootstrapping olam namespace + RBAC + secrets (B4)${label}`;
712
+ },
610
713
  ...(deps.k8sBootstrapDeps ?? {}),
611
714
  });
612
715
  if (bootstrap.exitCode !== 0) {
@@ -868,7 +971,28 @@ export async function runUpgradeKubernetes(opts = {}, deps = {}) {
868
971
  }
869
972
  else {
870
973
  stderr.write(`${pc.red('error:')} host-cp did not report engine=kubernetes.\n` +
871
- ` Check that the deployment rolled out with the correct image.\n`);
974
+ ` The /health port-forward succeeded but no X-Olam-Engine header was returned —\n` +
975
+ ` usually means the pod isn't Ready yet, is crashlooping, or failed to pull its image.\n` +
976
+ ` Pod-state diagnostics follow (share the FULL block when asking for help):\n`);
977
+ // Surface the actual pod state. The two commands below are read-only +
978
+ // safe to run multiple times. Output is captured + redirected to stderr
979
+ // so the operator sees it inline with the failure.
980
+ const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
981
+ try {
982
+ const getPods = await wrap(['-n', 'olam', 'get', 'pods', '-l', 'app=olam-host-cp', '-o', 'wide'], { timeout: 5_000 });
983
+ stderr.write(`\n${pc.dim('--- kubectl -n olam get pods -l app=olam-host-cp ---')}\n` +
984
+ `${getPods.stdout || getPods.stderr || '(no output)'}\n`);
985
+ const describe = await wrap(['-n', 'olam', 'describe', 'pod', '-l', 'app=olam-host-cp'], { timeout: 5_000 });
986
+ // Tail the last 40 lines — Events block sits at the bottom and is
987
+ // the highest-signal slice (ImagePullBackOff / FailedScheduling /
988
+ // CrashLoopBackOff all surface there).
989
+ const tail = (describe.stdout || '').split('\n').slice(-40).join('\n');
990
+ stderr.write(`\n${pc.dim('--- kubectl -n olam describe pod (tail 40 lines, includes Events) ---')}\n` +
991
+ `${tail || '(no output)'}\n\n`);
992
+ }
993
+ catch (err) {
994
+ stderr.write(` ${pc.dim('(kubectl diagnostics unavailable: ' + err.message + ')')}\n`);
995
+ }
872
996
  }
873
997
  return { exitCode: 1, summary: 'health check failed — engine mismatch' };
874
998
  }