@pleri/olam-cli 0.1.195 → 0.1.198
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 +52 -0
- package/dist/ask/knowledge-pack.generated.d.ts.map +1 -1
- package/dist/ask/knowledge-pack.generated.js +12 -8
- package/dist/ask/knowledge-pack.generated.js.map +1 -1
- package/dist/commands/auth-list-json.d.ts +34 -0
- package/dist/commands/auth-list-json.d.ts.map +1 -1
- package/dist/commands/auth-list-json.js +24 -0
- package/dist/commands/auth-list-json.js.map +1 -1
- package/dist/commands/auth-migrate.d.ts +212 -0
- package/dist/commands/auth-migrate.d.ts.map +1 -0
- package/dist/commands/auth-migrate.js +465 -0
- package/dist/commands/auth-migrate.js.map +1 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +239 -184
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/bootstrap.d.ts +4 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +6 -0
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/dispatch.js +11 -1
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +299 -12
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/kg-mirror.d.ts +18 -2
- package/dist/commands/kg-mirror.d.ts.map +1 -1
- package/dist/commands/kg-mirror.js +78 -3
- package/dist/commands/kg-mirror.js.map +1 -1
- package/dist/commands/mcp/complete.d.ts +36 -0
- package/dist/commands/mcp/complete.d.ts.map +1 -0
- package/dist/commands/mcp/complete.js +66 -0
- package/dist/commands/mcp/complete.js.map +1 -0
- package/dist/commands/mcp/index.d.ts +1 -1
- package/dist/commands/mcp/index.d.ts.map +1 -1
- package/dist/commands/mcp/index.js +3 -1
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/memory/bridge.d.ts +1 -1
- package/dist/commands/memory/bridge.d.ts.map +1 -1
- package/dist/commands/memory/bridge.js +2 -6
- package/dist/commands/memory/bridge.js.map +1 -1
- package/dist/commands/memory/secret.d.ts.map +1 -1
- package/dist/commands/memory/secret.js +4 -3
- package/dist/commands/memory/secret.js.map +1 -1
- package/dist/commands/observe.d.ts +3 -3
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +11 -8
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/runbooks.d.ts.map +1 -1
- package/dist/commands/runbooks.js +77 -10
- package/dist/commands/runbooks.js.map +1 -1
- package/dist/commands/services-tls.d.ts.map +1 -1
- package/dist/commands/services-tls.js +65 -10
- package/dist/commands/services-tls.js.map +1 -1
- package/dist/commands/services.d.ts +35 -1
- package/dist/commands/services.d.ts.map +1 -1
- package/dist/commands/services.js +153 -32
- package/dist/commands/services.js.map +1 -1
- package/dist/commands/setup-phase-8-kg-hook.d.ts +48 -0
- package/dist/commands/setup-phase-8-kg-hook.d.ts.map +1 -0
- package/dist/commands/setup-phase-8-kg-hook.js +93 -0
- package/dist/commands/setup-phase-8-kg-hook.js.map +1 -0
- package/dist/commands/setup-phase-9-memory-bridge.d.ts +36 -0
- package/dist/commands/setup-phase-9-memory-bridge.d.ts.map +1 -0
- package/dist/commands/setup-phase-9-memory-bridge.js +59 -0
- package/dist/commands/setup-phase-9-memory-bridge.js.map +1 -0
- package/dist/commands/setup.d.ts +34 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +372 -32
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +70 -1
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/update.d.ts +24 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +53 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +31 -8
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/image-digests.json +8 -8
- package/dist/index.js +4487 -2451
- package/dist/lib/auth-backend.d.ts +168 -0
- package/dist/lib/auth-backend.d.ts.map +1 -0
- package/dist/lib/auth-backend.js +172 -0
- package/dist/lib/auth-backend.js.map +1 -0
- package/dist/lib/auth-list-cache.d.ts +67 -0
- package/dist/lib/auth-list-cache.d.ts.map +1 -0
- package/dist/lib/auth-list-cache.js +84 -0
- package/dist/lib/auth-list-cache.js.map +1 -0
- package/dist/lib/auth-list.d.ts +107 -0
- package/dist/lib/auth-list.d.ts.map +1 -0
- package/dist/lib/auth-list.js +123 -0
- package/dist/lib/auth-list.js.map +1 -0
- package/dist/lib/auth-login.d.ts +92 -0
- package/dist/lib/auth-login.d.ts.map +1 -0
- package/dist/lib/auth-login.js +124 -0
- package/dist/lib/auth-login.js.map +1 -0
- package/dist/lib/auth-mutator-backend.d.ts +54 -0
- package/dist/lib/auth-mutator-backend.d.ts.map +1 -0
- package/dist/lib/auth-mutator-backend.js +62 -0
- package/dist/lib/auth-mutator-backend.js.map +1 -0
- package/dist/lib/auth-remote.d.ts +50 -0
- package/dist/lib/auth-remote.d.ts.map +1 -1
- package/dist/lib/auth-remote.js +84 -2
- package/dist/lib/auth-remote.js.map +1 -1
- package/dist/lib/bootstrap-kubernetes.d.ts +69 -10
- package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
- package/dist/lib/bootstrap-kubernetes.js +264 -46
- package/dist/lib/bootstrap-kubernetes.js.map +1 -1
- package/dist/lib/config.d.ts +35 -4
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +82 -11
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/health-probes.d.ts +0 -22
- package/dist/lib/health-probes.d.ts.map +1 -1
- package/dist/lib/health-probes.js +57 -0
- package/dist/lib/health-probes.js.map +1 -1
- package/dist/lib/peripheral-registry.d.ts +11 -0
- package/dist/lib/peripheral-registry.d.ts.map +1 -1
- package/dist/lib/peripheral-registry.js +5 -0
- package/dist/lib/peripheral-registry.js.map +1 -1
- package/dist/lib/plans-client.d.ts.map +1 -1
- package/dist/lib/plans-client.js +6 -3
- package/dist/lib/plans-client.js.map +1 -1
- package/dist/mcp-server.js +138 -6
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/30-configmap.yaml +4 -0
- package/host-cp/k8s/manifests/50-deployment.yaml +13 -1
- package/host-cp/k8s/manifests/65-tls-secret-template.yaml.tmpl +35 -0
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/src/dispatch-persister.mjs +157 -0
- package/host-cp/src/pr-nanny.mjs +7 -0
- package/host-cp/src/server.mjs +175 -3
- package/host-cp/src/world-watchdog-pid-lookup.mjs +119 -0
- package/host-cp/src/world-watchdog-probes.mjs +271 -0
- package/host-cp/src/world-watchdog-recovery.mjs +192 -0
- package/host-cp/src/world-watchdog.mjs +313 -0
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* Phase 3.5: (no-op for docker)
|
|
29
29
|
* Phase 4: Shell init (idempotent append of completion eval-line)
|
|
30
30
|
* Phase 5: Init global config (ensure ~/.olam/config.json has host.substrate set)
|
|
31
|
-
* Phase 5a: Skill source picker
|
|
31
|
+
* Phase 5a: Skill source picker (runs unconditionally; auto-skips when sources already registered)
|
|
32
32
|
* Phase 5b: Project sweep
|
|
33
33
|
* Phase 6: Auth prompt (offer interactive `olam auth login`)
|
|
34
34
|
* Phase 7: Final verify (subprocess: olam doctor; halt on non-zero)
|
|
@@ -65,8 +65,11 @@ import { ensureTlsInstalled } from './services-tls.js';
|
|
|
65
65
|
import { printError, printHeader, printInfo, printSuccess, printWarning } from '../output.js';
|
|
66
66
|
import { pickSkillSourcePhase, formatPostSetupSuggestion, } from './setup-phase-5a-skill-source.js';
|
|
67
67
|
import { runProjectSweepPhase } from './setup-phase-5b-project-sweep.js';
|
|
68
|
+
import { runKgHookPhase } from './setup-phase-8-kg-hook.js';
|
|
69
|
+
import { runMemoryBridgePhase } from './setup-phase-9-memory-bridge.js';
|
|
68
70
|
import { OLAM_CONFIG_PATH, writeConfig } from '../lib/config.js';
|
|
69
71
|
import { migrateSecretIfNeeded, KNOWN_SECRET_NAMES } from '@olam/core/src/secrets/paths.js';
|
|
72
|
+
import { resolveKubectlContext } from '../lib/kubectl-context.js';
|
|
70
73
|
const REQUIRED_NODE_MAJOR = 20;
|
|
71
74
|
/** k3d cluster name provisioned by olam setup --substrate=kubernetes. */
|
|
72
75
|
export const SETUP_K3D_CLUSTER_NAME = 'olam-dev';
|
|
@@ -182,7 +185,200 @@ function resolveSubstrate(opts, deps) {
|
|
|
182
185
|
// Fresh install (no config file, or unrecognised substrate) → new default
|
|
183
186
|
return 'kubernetes';
|
|
184
187
|
}
|
|
185
|
-
|
|
188
|
+
const SUBSTRATE_CHOICES = [
|
|
189
|
+
{
|
|
190
|
+
label: 'Docker Desktop (recommended)',
|
|
191
|
+
substrate: 'docker',
|
|
192
|
+
preferredRuntime: 'docker-desktop',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
label: 'Colima',
|
|
196
|
+
substrate: 'docker',
|
|
197
|
+
preferredRuntime: 'colima',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: 'Kubernetes (k3d on Docker Desktop)',
|
|
201
|
+
substrate: 'kubernetes',
|
|
202
|
+
preferredRuntime: 'docker-desktop',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
label: 'Kubernetes (k3d on Colima)',
|
|
206
|
+
substrate: 'kubernetes',
|
|
207
|
+
preferredRuntime: 'colima',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
label: 'Kubernetes (external context already pinned)',
|
|
211
|
+
substrate: 'kubernetes',
|
|
212
|
+
preferredRuntime: 'external-k8s',
|
|
213
|
+
requirePinnedContext: true,
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
/** Default (first) choice — Docker Desktop. */
|
|
217
|
+
const DEFAULT_SUBSTRATE_CHOICE = SUBSTRATE_CHOICES[0];
|
|
218
|
+
/**
|
|
219
|
+
* Narrows the picker-internal `SetupSubstrate` to the config-schema `Substrate`.
|
|
220
|
+
* `'docker'` in the picker maps to `'compose'` in the config (they name the same
|
|
221
|
+
* non-kubernetes stack; the config schema predates the picker rename).
|
|
222
|
+
*/
|
|
223
|
+
function toConfigSubstrate(s) {
|
|
224
|
+
return s === 'docker' ? 'compose' : 'kubernetes';
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Phase 0 — Substrate picker.
|
|
228
|
+
*
|
|
229
|
+
* Asks the operator which container runtime + substrate they want to use.
|
|
230
|
+
* Writes `host.substrate` and `host.preferred_runtime` to ~/.olam/config.json.
|
|
231
|
+
*
|
|
232
|
+
* Skip conditions (all silent — return { ok: true, skipped: true }):
|
|
233
|
+
* - opts.substrate is already set → "--substrate=<value> passed"
|
|
234
|
+
* - opts.skipSubstratePicker → "--skip-substrate-picker"
|
|
235
|
+
* - config already has both fields → "already configured (<runtime>)"
|
|
236
|
+
* - !process.stdin.isTTY && !opts.yes → uses docker-desktop default
|
|
237
|
+
* - opts.yes → uses docker-desktop default
|
|
238
|
+
*/
|
|
239
|
+
/**
|
|
240
|
+
* Return a comma-joined list of up to 5 available kubectl context names.
|
|
241
|
+
* Used to populate the error message when the operator enters an unknown context.
|
|
242
|
+
*/
|
|
243
|
+
function listAvailableContexts() {
|
|
244
|
+
const r = spawnSync('kubectl', ['config', 'get-contexts', '-o', 'name'], {
|
|
245
|
+
encoding: 'utf-8',
|
|
246
|
+
stdio: 'pipe',
|
|
247
|
+
});
|
|
248
|
+
if (r.status !== 0 || !r.stdout.trim())
|
|
249
|
+
return '(none found)';
|
|
250
|
+
const names = r.stdout.trim().split('\n').map((n) => n.trim()).filter(Boolean);
|
|
251
|
+
const preview = names.slice(0, 5);
|
|
252
|
+
return preview.join(', ') + (names.length > 5 ? `, … (+${names.length - 5} more)` : '');
|
|
253
|
+
}
|
|
254
|
+
export async function phase0SubstratePicker(opts, deps) {
|
|
255
|
+
// Skip: explicit --substrate= on the command line
|
|
256
|
+
if (opts.substrate !== undefined) {
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
skipped: true,
|
|
260
|
+
message: `skipped — --substrate=${opts.substrate} passed`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Skip: explicit --skip-substrate-picker
|
|
264
|
+
if (opts.skipSubstratePicker) {
|
|
265
|
+
return { ok: true, skipped: true, message: 'skipped via --skip-substrate-picker' };
|
|
266
|
+
}
|
|
267
|
+
// Skip: already configured in config file
|
|
268
|
+
const configPath = deps.configPath ?? OLAM_CONFIG_PATH;
|
|
269
|
+
if (existsSync(configPath)) {
|
|
270
|
+
try {
|
|
271
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
272
|
+
const parsed = JSON.parse(raw);
|
|
273
|
+
const host = parsed.host;
|
|
274
|
+
if (host?.substrate !== undefined
|
|
275
|
+
&& host?.preferred_runtime !== undefined) {
|
|
276
|
+
return {
|
|
277
|
+
ok: true,
|
|
278
|
+
skipped: true,
|
|
279
|
+
message: `skipped — substrate already configured (${host.preferred_runtime})`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// malformed config — proceed to picker
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Non-TTY without --yes → use default silently
|
|
288
|
+
if (!process.stdin.isTTY && !opts.yes) {
|
|
289
|
+
writeConfig({ host: { substrate: toConfigSubstrate(DEFAULT_SUBSTRATE_CHOICE.substrate), preferred_runtime: DEFAULT_SUBSTRATE_CHOICE.preferredRuntime } }, { configPath: deps.configPath });
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
skipped: true,
|
|
293
|
+
message: 'skipped (non-TTY); using docker-desktop default',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// --yes mode → use default silently (write config, print summary)
|
|
297
|
+
if (opts.yes) {
|
|
298
|
+
writeConfig({ host: { substrate: toConfigSubstrate(DEFAULT_SUBSTRATE_CHOICE.substrate), preferred_runtime: DEFAULT_SUBSTRATE_CHOICE.preferredRuntime } }, { configPath: deps.configPath });
|
|
299
|
+
process.stdout.write(` ✓ Phase 0: substrate=${DEFAULT_SUBSTRATE_CHOICE.substrate} runtime=${DEFAULT_SUBSTRATE_CHOICE.preferredRuntime}\n`);
|
|
300
|
+
return {
|
|
301
|
+
ok: true,
|
|
302
|
+
message: `substrate=${DEFAULT_SUBSTRATE_CHOICE.substrate} runtime=${DEFAULT_SUBSTRATE_CHOICE.preferredRuntime}`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
// Interactive picker via @inquirer/prompts select (same pattern as Phase 5a).
|
|
306
|
+
// Lazy-load only when deps don't inject the prompt functions — keeps the
|
|
307
|
+
// import out of the test path and avoids the eager-import overhead.
|
|
308
|
+
const resolvedSelectFn = deps.selectPromptFn
|
|
309
|
+
?? (await import('@inquirer/prompts')).select;
|
|
310
|
+
const choices = SUBSTRATE_CHOICES.map((c, i) => ({
|
|
311
|
+
value: String(i),
|
|
312
|
+
name: c.label,
|
|
313
|
+
}));
|
|
314
|
+
const pickedIndex = await resolvedSelectFn({
|
|
315
|
+
message: 'Choose your container runtime + substrate:',
|
|
316
|
+
choices,
|
|
317
|
+
});
|
|
318
|
+
const choice = SUBSTRATE_CHOICES[Number(pickedIndex)] ?? DEFAULT_SUBSTRATE_CHOICE;
|
|
319
|
+
// Special handling: external context requires kubectl_context_pinned to be set
|
|
320
|
+
if (choice.requirePinnedContext) {
|
|
321
|
+
// Check whether the config already has it
|
|
322
|
+
let hasPinnedContext = false;
|
|
323
|
+
if (existsSync(configPath)) {
|
|
324
|
+
try {
|
|
325
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
326
|
+
const parsed = JSON.parse(raw);
|
|
327
|
+
const host = parsed.host;
|
|
328
|
+
hasPinnedContext = typeof host?.kubectl_context_pinned === 'string'
|
|
329
|
+
&& host.kubectl_context_pinned.length > 0;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// ignore
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!hasPinnedContext) {
|
|
336
|
+
// Prompt for context name + validate
|
|
337
|
+
const resolvedInputFn = deps.inputPromptFn
|
|
338
|
+
?? (await import('@inquirer/prompts')).input;
|
|
339
|
+
const contextName = await resolvedInputFn({
|
|
340
|
+
message: 'Enter your kubectl context name (e.g. lima-k3s, gke_proj_region_cluster):',
|
|
341
|
+
validate: (v) => {
|
|
342
|
+
if (v.trim().length === 0)
|
|
343
|
+
return 'Context name must not be empty';
|
|
344
|
+
const name = v.trim();
|
|
345
|
+
// Sanitize to prevent jsonpath injection via embedded quotes.
|
|
346
|
+
const escaped = name.replace(/"/g, '\\"');
|
|
347
|
+
// Use jsonpath — `get-contexts <name>` exits 0 even for missing contexts
|
|
348
|
+
// (it just prints an empty table). jsonpath prints the name when found,
|
|
349
|
+
// empty string when not found.
|
|
350
|
+
const r = spawnSync('kubectl', ['config', 'view', '-o', `jsonpath={.contexts[?(@.name=="${escaped}")].name}`], { stdio: 'pipe', encoding: 'utf-8' });
|
|
351
|
+
if (r.status !== 0) {
|
|
352
|
+
return 'kubectl not available — verify kubectl is installed and re-run setup';
|
|
353
|
+
}
|
|
354
|
+
if (r.stdout.trim() !== name) {
|
|
355
|
+
return (`Context "${name}" not found in kubeconfig. `
|
|
356
|
+
+ `Available: ${listAvailableContexts()}`);
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
writeConfig({
|
|
362
|
+
host: {
|
|
363
|
+
substrate: toConfigSubstrate(choice.substrate),
|
|
364
|
+
preferred_runtime: choice.preferredRuntime,
|
|
365
|
+
kubectl_context_pinned: contextName.trim(),
|
|
366
|
+
},
|
|
367
|
+
}, { configPath: deps.configPath });
|
|
368
|
+
process.stdout.write(` ✓ Phase 0: substrate=${choice.substrate} runtime=${choice.preferredRuntime} context=${contextName.trim()}\n`);
|
|
369
|
+
return {
|
|
370
|
+
ok: true,
|
|
371
|
+
message: `substrate=${choice.substrate} runtime=${choice.preferredRuntime} context=${contextName.trim()}`,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
writeConfig({ host: { substrate: toConfigSubstrate(choice.substrate), preferred_runtime: choice.preferredRuntime } }, { configPath: deps.configPath });
|
|
376
|
+
process.stdout.write(` ✓ Phase 0: substrate=${choice.substrate} runtime=${choice.preferredRuntime}\n`);
|
|
377
|
+
return {
|
|
378
|
+
ok: true,
|
|
379
|
+
message: `substrate=${choice.substrate} runtime=${choice.preferredRuntime}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
186
382
|
/**
|
|
187
383
|
* Phase 0.5 — Detect existing reachable kubeconfig contexts (ADR 021).
|
|
188
384
|
*
|
|
@@ -270,15 +466,35 @@ async function phase1SystemCheck(substrate, deps) {
|
|
|
270
466
|
if (!kubectlProbe.ok) {
|
|
271
467
|
return phaseFromProbe(kubectlProbe);
|
|
272
468
|
}
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
469
|
+
// docker is only required when the cluster is k3d-managed (k3d runs its node
|
|
470
|
+
// containers on the operator's local docker daemon). External kubernetes clusters
|
|
471
|
+
// (K3s-on-Lima, Kind on a remote VM, GKE, AKS, kubeadm, etc.) reach the
|
|
472
|
+
// apiserver directly via kubectl and don't need a local docker daemon.
|
|
473
|
+
//
|
|
474
|
+
// Heuristic: kubectl context names that START with `k3d-` are k3d-managed
|
|
475
|
+
// (e.g. `k3d-olam-dev`, `k3d-olam-host`). Anything else (`lima-k3s`,
|
|
476
|
+
// `kind-foo`, `gke_proj_region_cluster`, custom names) is treated as external
|
|
477
|
+
// — docker is optional.
|
|
478
|
+
const ctx = resolveKubectlContext(deps.configPath);
|
|
479
|
+
const isK3dManaged = ctx.context?.startsWith('k3d-') ?? false;
|
|
480
|
+
if (isK3dManaged) {
|
|
481
|
+
const dockerProbe = await probeDockerDaemon(deps.dockerExec);
|
|
482
|
+
if (!dockerProbe.ok) {
|
|
483
|
+
return {
|
|
484
|
+
ok: false,
|
|
485
|
+
message: 'docker daemon required for k3d-managed cluster: ' + dockerProbe.message,
|
|
486
|
+
remedy: dockerProbe.remedy,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else if (ctx.context !== undefined) {
|
|
491
|
+
// Surface that we deliberately skipped the docker check so the operator
|
|
492
|
+
// sees the decision in the phase log.
|
|
493
|
+
process.stdout.write(` ℹ skipped docker check — kubectl context "${ctx.context}" is not k3d-managed\n`);
|
|
281
494
|
}
|
|
495
|
+
// If ctx.context is undefined (no context pinned yet), the kubectlProbe above
|
|
496
|
+
// already confirms kubectl is present; the cluster-provision phases that follow
|
|
497
|
+
// will surface the missing context error — don't double-fail here.
|
|
282
498
|
if (!nodeOk) {
|
|
283
499
|
return {
|
|
284
500
|
ok: false,
|
|
@@ -296,7 +512,8 @@ async function phase1SystemCheck(substrate, deps) {
|
|
|
296
512
|
const lintWithWarn = colimaLint;
|
|
297
513
|
printWarning(` ⚠ ${lintWithWarn.remedy}`);
|
|
298
514
|
}
|
|
299
|
-
|
|
515
|
+
const dockerNote = isK3dManaged ? '; docker ready' : '';
|
|
516
|
+
return { ok: true, message: `kubectl on PATH${dockerNote}; node ${nodeVersion}` };
|
|
300
517
|
}
|
|
301
518
|
// Docker substrate (default)
|
|
302
519
|
const dockerProbe = await probeDockerDaemon(deps.dockerExec);
|
|
@@ -344,6 +561,19 @@ async function phase1_5InstallSubstrate(substrate, opts, deps, reuseContext) {
|
|
|
344
561
|
message: `skipped k3d install (reusing context ${reuseContext})`,
|
|
345
562
|
};
|
|
346
563
|
}
|
|
564
|
+
// k3d only runs on k3d-managed clusters (context name starts with `k3d-`).
|
|
565
|
+
// External clusters (lima-k3s, kind-*, gke_*, custom names) already have their
|
|
566
|
+
// own k8s distribution installed — k3d is irrelevant and would be misleading to
|
|
567
|
+
// install. Mirror the same heuristic used by phase1SystemCheck for consistency.
|
|
568
|
+
const ctx = resolveKubectlContext(deps.configPath);
|
|
569
|
+
const isK3dManaged = ctx.context?.startsWith('k3d-') ?? false;
|
|
570
|
+
if (!isK3dManaged && ctx.context !== undefined) {
|
|
571
|
+
return {
|
|
572
|
+
ok: true,
|
|
573
|
+
skipped: true,
|
|
574
|
+
message: `skipped k3d install — kubectl context "${ctx.context}" is not k3d-managed`,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
347
577
|
const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
|
|
348
578
|
const promptFn = deps.prompt ?? defaultPrompt;
|
|
349
579
|
// k3d works on both darwin and linux; one path for both.
|
|
@@ -637,6 +867,13 @@ async function phase3Bootstrap(substrate, deps, opts) {
|
|
|
637
867
|
if (substrate === 'kubernetes' && opts.hostCpDevPath) {
|
|
638
868
|
bootstrapArgs.push('--host-cp-dev-path', opts.hostCpDevPath);
|
|
639
869
|
}
|
|
870
|
+
// Forward output + pre-pull preferences to the k3s bootstrap subprocess.
|
|
871
|
+
if (substrate === 'kubernetes' && opts.verbose) {
|
|
872
|
+
bootstrapArgs.push('--verbose');
|
|
873
|
+
}
|
|
874
|
+
if (substrate === 'kubernetes' && opts.skipPrepull) {
|
|
875
|
+
bootstrapArgs.push('--skip-prepull');
|
|
876
|
+
}
|
|
640
877
|
const r = await spawnFn('olam', bootstrapArgs);
|
|
641
878
|
if (r.status === 0) {
|
|
642
879
|
return { ok: true, message: `olam bootstrap succeeded (substrate: ${substrate})` };
|
|
@@ -790,11 +1027,10 @@ async function phase5aSkillSource(opts, deps) {
|
|
|
790
1027
|
if (opts.skipSkillSource) {
|
|
791
1028
|
return { ok: true, skipped: true, message: 'skipped via --skip-skill-source' };
|
|
792
1029
|
}
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
1030
|
+
// Run unconditionally. pickSkillSourcePhase auto-detects existing skill sources
|
|
1031
|
+
// (skips silently when ≥1 are registered) and handles the interactive TUI,
|
|
1032
|
+
// non-interactive --skill-source flag, and non-TTY fallback internally.
|
|
1033
|
+
// Use --skip-skill-source to opt out entirely (headless CI, manual setup later).
|
|
798
1034
|
return pickSkillSourcePhase(opts, deps);
|
|
799
1035
|
}
|
|
800
1036
|
async function phase5bProjectSweep(opts, deps) {
|
|
@@ -846,8 +1082,21 @@ async function phase7Verify(opts, deps) {
|
|
|
846
1082
|
remedy: 'Inspect doctor output above; the specific failing probe names its remedy.',
|
|
847
1083
|
};
|
|
848
1084
|
}
|
|
1085
|
+
async function phase8KgHook(opts, deps) {
|
|
1086
|
+
if (opts.skipKgHook) {
|
|
1087
|
+
return { ok: true, skipped: true, message: 'skipped via --skip-kg-hook' };
|
|
1088
|
+
}
|
|
1089
|
+
return runKgHookPhase(opts, deps);
|
|
1090
|
+
}
|
|
1091
|
+
async function phase9MemoryBridge(opts, deps) {
|
|
1092
|
+
if (opts.skipMemoryBridge) {
|
|
1093
|
+
return { ok: true, skipped: true, message: 'skipped via --skip-memory-bridge' };
|
|
1094
|
+
}
|
|
1095
|
+
return runMemoryBridgePhase(opts, deps);
|
|
1096
|
+
}
|
|
849
1097
|
// ── Orchestrator ──────────────────────────────────────────────────────
|
|
850
1098
|
const PHASE_TITLES = [
|
|
1099
|
+
'Phase 0: Substrate picker',
|
|
851
1100
|
'Phase 0.5: Detect existing k8s context',
|
|
852
1101
|
'Phase 1: System check',
|
|
853
1102
|
'Phase 1.5: Substrate tools install',
|
|
@@ -862,11 +1111,89 @@ const PHASE_TITLES = [
|
|
|
862
1111
|
'Phase 5b: Project sweep',
|
|
863
1112
|
'Phase 6: Auth prompt',
|
|
864
1113
|
'Phase 7: Final verification',
|
|
1114
|
+
'Phase 8: KG hook install',
|
|
1115
|
+
'Phase 9: Agent memory bridge install',
|
|
865
1116
|
];
|
|
866
1117
|
export async function runSetup(opts, deps = {}) {
|
|
1118
|
+
// ── --continue mode: skip Phases 1-7, run only Phase 8 + Phase 9 ──────────
|
|
1119
|
+
if (opts.continueMode) {
|
|
1120
|
+
printHeader('olam setup --continue — Phase 8 + Phase 9 only');
|
|
1121
|
+
const results = [];
|
|
1122
|
+
let failureAt = null;
|
|
1123
|
+
// Migrate secrets idempotently (same as full run).
|
|
1124
|
+
for (const name of KNOWN_SECRET_NAMES) {
|
|
1125
|
+
migrateSecretIfNeeded(name);
|
|
1126
|
+
}
|
|
1127
|
+
// Phase 8 title is at PHASE_TITLES index 15 (0=0, 0.5=1, 1=2, … 7=14, 8=15).
|
|
1128
|
+
const p8Title = PHASE_TITLES[15];
|
|
1129
|
+
const p9Title = PHASE_TITLES[16];
|
|
1130
|
+
process.stdout.write(`\n${p8Title}\n`);
|
|
1131
|
+
const p8Result = await phase8KgHook(opts, deps);
|
|
1132
|
+
results.push({ name: p8Title, result: p8Result });
|
|
1133
|
+
if (p8Result.ok && p8Result.skipped) {
|
|
1134
|
+
printWarning(` ⊘ ${p8Result.message}`);
|
|
1135
|
+
}
|
|
1136
|
+
else if (p8Result.ok) {
|
|
1137
|
+
printSuccess(` ✓ ${p8Result.message}`);
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
printError(` ✗ ${p8Result.message}`);
|
|
1141
|
+
if (p8Result.remedy)
|
|
1142
|
+
printWarning(` remedy: ${p8Result.remedy}`);
|
|
1143
|
+
failureAt = 8;
|
|
1144
|
+
}
|
|
1145
|
+
if (failureAt === null) {
|
|
1146
|
+
process.stdout.write(`\n${p9Title}\n`);
|
|
1147
|
+
const p9Result = await phase9MemoryBridge(opts, deps);
|
|
1148
|
+
results.push({ name: p9Title, result: p9Result });
|
|
1149
|
+
if (p9Result.ok && p9Result.skipped) {
|
|
1150
|
+
printWarning(` ⊘ ${p9Result.message}`);
|
|
1151
|
+
}
|
|
1152
|
+
else if (p9Result.ok) {
|
|
1153
|
+
printSuccess(` ✓ ${p9Result.message}`);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
printError(` ✗ ${p9Result.message}`);
|
|
1157
|
+
if (p9Result.remedy)
|
|
1158
|
+
printWarning(` remedy: ${p9Result.remedy}`);
|
|
1159
|
+
failureAt = 9;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
process.stdout.write('\n');
|
|
1163
|
+
if (failureAt !== null) {
|
|
1164
|
+
printError(`Setup FAILED at Phase ${failureAt}`);
|
|
1165
|
+
return { phases: results, failureAt, exitCode: 1 };
|
|
1166
|
+
}
|
|
1167
|
+
printSuccess('Setup --continue complete.');
|
|
1168
|
+
return { phases: results, failureAt: null, exitCode: 0 };
|
|
1169
|
+
}
|
|
1170
|
+
// ── Full setup (Phases 0 through 9) ───────────────────────────────────────
|
|
1171
|
+
// Phase 0 (substrate picker) runs eagerly BEFORE resolveSubstrate so it can
|
|
1172
|
+
// write host.substrate to config — resolveSubstrate then reads the written value.
|
|
1173
|
+
// We still push the result into the phases array for the SetupReport.
|
|
1174
|
+
const results = [];
|
|
1175
|
+
let failureAt = null;
|
|
1176
|
+
const phase0Title = PHASE_TITLES[0];
|
|
1177
|
+
process.stdout.write(`\n${phase0Title}\n`);
|
|
1178
|
+
const phase0Result = await phase0SubstratePicker(opts, deps);
|
|
1179
|
+
results.push({ name: phase0Title, result: phase0Result });
|
|
1180
|
+
if (phase0Result.ok && phase0Result.skipped) {
|
|
1181
|
+
printWarning(` ⊘ ${phase0Result.message}`);
|
|
1182
|
+
}
|
|
1183
|
+
else if (phase0Result.ok) {
|
|
1184
|
+
// Summary line already printed inside phase0SubstratePicker for non-skip path.
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
printError(` ✗ ${phase0Result.message}`);
|
|
1188
|
+
if (phase0Result.remedy)
|
|
1189
|
+
printWarning(` remedy: ${phase0Result.remedy}`);
|
|
1190
|
+
process.stdout.write('\n');
|
|
1191
|
+
printError('Setup FAILED at Phase 0');
|
|
1192
|
+
return { phases: results, failureAt: 0, exitCode: 1 };
|
|
1193
|
+
}
|
|
867
1194
|
const substrate = resolveSubstrate(opts, deps);
|
|
868
1195
|
const clusterName = resolveClusterName(opts);
|
|
869
|
-
//
|
|
1196
|
+
// Cluster-name flag validation (fail fast before any I/O).
|
|
870
1197
|
// Only validate when the kubernetes substrate is in play — the flag is a
|
|
871
1198
|
// no-op for docker and should not block docker-only operators who pass it
|
|
872
1199
|
// accidentally, but we still validate the format to surface typos.
|
|
@@ -899,9 +1226,7 @@ export async function runSetup(opts, deps = {}) {
|
|
|
899
1226
|
// Phase 0.5: detect existing k8s context. Run eagerly (not in phaseFns) so
|
|
900
1227
|
// we can capture reuseContext and thread it through Phases 1.5, 2.5, 2.6.
|
|
901
1228
|
// We still push the result into the phases array for the SetupReport.
|
|
902
|
-
const
|
|
903
|
-
let failureAt = null;
|
|
904
|
-
const phase0_5Title = PHASE_TITLES[0];
|
|
1229
|
+
const phase0_5Title = PHASE_TITLES[1];
|
|
905
1230
|
process.stdout.write(`\n${phase0_5Title}\n`);
|
|
906
1231
|
const phase0_5Result = await phase0_5DetectExistingContext(substrate, opts, deps);
|
|
907
1232
|
results.push({ name: phase0_5Title, result: phase0_5Result });
|
|
@@ -934,11 +1259,13 @@ export async function runSetup(opts, deps = {}) {
|
|
|
934
1259
|
() => phase5bProjectSweep(opts, deps),
|
|
935
1260
|
() => phase6Auth(opts, deps),
|
|
936
1261
|
() => phase7Verify(opts, deps),
|
|
1262
|
+
() => phase8KgHook(opts, deps),
|
|
1263
|
+
() => phase9MemoryBridge(opts, deps),
|
|
937
1264
|
];
|
|
938
|
-
// results + failureAt were initialised above (before Phase 0
|
|
939
|
-
// phaseFns indices correspond to PHASE_TITLES[
|
|
1265
|
+
// results + failureAt were initialised above (before Phase 0).
|
|
1266
|
+
// phaseFns indices correspond to PHASE_TITLES[2..] (Phase 0 = [0], Phase 0.5 = [1]).
|
|
940
1267
|
for (let i = 0; i < phaseFns.length; i += 1) {
|
|
941
|
-
const name = PHASE_TITLES[i +
|
|
1268
|
+
const name = PHASE_TITLES[i + 2];
|
|
942
1269
|
process.stdout.write(`\n${name}\n`);
|
|
943
1270
|
const result = await phaseFns[i]();
|
|
944
1271
|
results.push({ name, result });
|
|
@@ -952,7 +1279,7 @@ export async function runSetup(opts, deps = {}) {
|
|
|
952
1279
|
printError(` ✗ ${result.message}`);
|
|
953
1280
|
if (result.remedy)
|
|
954
1281
|
printWarning(` remedy: ${result.remedy}`);
|
|
955
|
-
failureAt = i + 2; //
|
|
1282
|
+
failureAt = i + 2; // phaseFns[0] lands at results[2] — Phase 0 + Phase 0.5 occupy [0] + [1]
|
|
956
1283
|
break;
|
|
957
1284
|
}
|
|
958
1285
|
}
|
|
@@ -967,10 +1294,12 @@ export async function runSetup(opts, deps = {}) {
|
|
|
967
1294
|
for (const line of nextStepsDocs) {
|
|
968
1295
|
printInfo('docs', line);
|
|
969
1296
|
}
|
|
970
|
-
// Print the skill-source suggestion when
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
|
|
1297
|
+
// Print the skill-source suggestion only when the operator explicitly opted out
|
|
1298
|
+
// via --skip-skill-source (they asked us not to run Phase 5a at all).
|
|
1299
|
+
// When Phase 5a ran unconditionally (the default), it either registered a source,
|
|
1300
|
+
// found existing ones, or the operator chose "skip for now" in the interactive picker
|
|
1301
|
+
// — no post-setup hint needed because the phase itself communicated the outcome.
|
|
1302
|
+
if (opts.skipSkillSource) {
|
|
974
1303
|
process.stdout.write(formatPostSetupSuggestion());
|
|
975
1304
|
}
|
|
976
1305
|
else {
|
|
@@ -982,6 +1311,8 @@ export function registerSetup(program) {
|
|
|
982
1311
|
program
|
|
983
1312
|
.command('setup')
|
|
984
1313
|
.description('Fresh-host onboarding wizard (k3d cluster + services, idempotent)')
|
|
1314
|
+
.option('--skip-substrate-picker', 'Skip Phase 0 substrate picker (uses existing config or default). '
|
|
1315
|
+
+ 'For non-interactive setups where host.substrate is managed manually.')
|
|
985
1316
|
.option('--substrate <substrate>', 'Target substrate: kubernetes (default, alias: k3s) or docker. '
|
|
986
1317
|
+ 'Auto-detected from ~/.olam/config.json when not specified.')
|
|
987
1318
|
.option('--cluster-name <name>', 'k3d cluster name to create/use (kubernetes substrate only; default: olam-dev). '
|
|
@@ -996,8 +1327,8 @@ export function registerSetup(program) {
|
|
|
996
1327
|
.option('--skip-https-bootstrap', 'Skip Phase 3.5 (do not install mkcert or provision TLS Secret; run `olam services tls-install` later)')
|
|
997
1328
|
.option('--skip-shell-init', 'Skip Phase 4 (do not append to ~/.zshrc / ~/.bashrc)')
|
|
998
1329
|
.option('--skip-auth', 'Skip Phase 6 (do not prompt for `olam auth login`)')
|
|
999
|
-
.option('--skip-skill-source', 'Skip Phase 5a (
|
|
1000
|
-
.option('--skill-source <id-or-url>', 'Non-interactive Phase 5a: curated name (e.g. atlas-toolbox) or git URL')
|
|
1330
|
+
.option('--skip-skill-source', 'Skip Phase 5a entirely (run `olam skills source add` later; default: Phase 5a runs and auto-skips when sources already registered)')
|
|
1331
|
+
.option('--skill-source <id-or-url>', 'Non-interactive Phase 5a: curated name (e.g. atlas-toolbox) or git URL (skips interactive picker)')
|
|
1001
1332
|
.option('--skip-project-sweep', 'Skip Phase 5b (do not walk projects directory for repo discovery)')
|
|
1002
1333
|
.option('--projects <path>', 'Phase 5b: project root path to walk (default: ~/Projects)')
|
|
1003
1334
|
.option('--dry-run', 'Phase 5b: print matches without registering or building (no side effects)')
|
|
@@ -1008,6 +1339,12 @@ export function registerSetup(program) {
|
|
|
1008
1339
|
+ '/host/olam so host-cp reads live source. Regular operators DO NOT '
|
|
1009
1340
|
+ 'use this — the published image is self-contained. Absolute path required.')
|
|
1010
1341
|
.option('-y, --yes', 'Auto-affirm every prompt (non-interactive)')
|
|
1342
|
+
.option('--continue', 'Skip Phases 1-7 (initial setup) and run Phase 8 (KG hook) + Phase 9 '
|
|
1343
|
+
+ '(memory bridge) only. Use after `olam setup` has run once successfully.')
|
|
1344
|
+
.option('--skip-kg-hook', 'Skip Phase 8 (do not install KG hook; run `olam kg install-hook --scope user` later)')
|
|
1345
|
+
.option('--skip-memory-bridge', 'Skip Phase 9 (do not register agentmemory MCP; run `olam memory install` later)')
|
|
1346
|
+
.option('--verbose', '[k3s] stream all bootstrap phase output sequentially instead of collapsing to live spinner lines')
|
|
1347
|
+
.option('--skip-prepull', '[k3s] skip pre-pulling olam-* images into the k3d node (pods cold-pull on demand)')
|
|
1011
1348
|
.action(async (rawOpts) => {
|
|
1012
1349
|
// Normalise the --substrate flag: 'k3s' is an alias for 'kubernetes'.
|
|
1013
1350
|
let substrate;
|
|
@@ -1024,9 +1361,12 @@ export function registerSetup(program) {
|
|
|
1024
1361
|
process.exitCode = 1;
|
|
1025
1362
|
return;
|
|
1026
1363
|
}
|
|
1027
|
-
|
|
1364
|
+
// commander maps `--continue` to rawOpts.continue (reserved word in JS,
|
|
1365
|
+
// but valid as an object property). Map it to continueMode to avoid
|
|
1366
|
+
// shadowing the `continue` keyword in the surrounding scope.
|
|
1367
|
+
const { substrate: _drop, continue: continueFlag, ...restOpts } = rawOpts;
|
|
1028
1368
|
void _drop;
|
|
1029
|
-
const report = await runSetup({ ...restOpts, substrate });
|
|
1369
|
+
const report = await runSetup({ ...restOpts, substrate, continueMode: continueFlag });
|
|
1030
1370
|
if (report.exitCode !== 0)
|
|
1031
1371
|
process.exitCode = report.exitCode;
|
|
1032
1372
|
});
|