arkaos 4.33.0 → 4.35.0

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 (76) hide show
  1. package/THE-ARKAOS-GUIDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/bin/arka-menubar.py +407 -0
  4. package/config/hooks/session-start.sh +1 -1
  5. package/config/install-profiles.json +75 -0
  6. package/config/statusline.sh +6 -1
  7. package/core/hooks/session_start.py +69 -28
  8. package/core/runtime/opencode.py +113 -0
  9. package/core/runtime/registry.py +6 -1
  10. package/harness/codex/AGENTS.md +1 -1
  11. package/harness/copilot/copilot-instructions.md +1 -1
  12. package/harness/cursor/rules/arkaos.mdc +2 -2
  13. package/harness/gemini/GEMINI.md +1 -1
  14. package/harness/opencode/AGENTS.md +1 -1
  15. package/harness/opencode/agents/arka-architect-gabriel.md +12 -0
  16. package/harness/opencode/agents/arka-brand-director-valentina.md +12 -0
  17. package/harness/opencode/agents/arka-cfo-helena.md +12 -0
  18. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +12 -0
  19. package/harness/opencode/agents/arka-community-strategist-beatriz.md +12 -0
  20. package/harness/opencode/agents/arka-content-strategist-rafael.md +12 -0
  21. package/harness/opencode/agents/arka-conversion-strategist-ines.md +12 -0
  22. package/harness/opencode/agents/arka-coo-sofia.md +12 -0
  23. package/harness/opencode/agents/arka-copy-director-eduardo.md +12 -0
  24. package/harness/opencode/agents/arka-cqo-marta.md +12 -0
  25. package/harness/opencode/agents/arka-cto-marco.md +12 -0
  26. package/harness/opencode/agents/arka-design-ops-lead-iris.md +12 -0
  27. package/harness/opencode/agents/arka-ecom-director-ricardo.md +12 -0
  28. package/harness/opencode/agents/arka-knowledge-director-clara.md +12 -0
  29. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +12 -0
  30. package/harness/opencode/agents/arka-marketing-director-luna.md +12 -0
  31. package/harness/opencode/agents/arka-ops-lead-daniel.md +12 -0
  32. package/harness/opencode/agents/arka-pm-director-carolina.md +12 -0
  33. package/harness/opencode/agents/arka-revops-lead-vicente.md +12 -0
  34. package/harness/opencode/agents/arka-saas-strategist-tiago.md +12 -0
  35. package/harness/opencode/agents/arka-sales-director-miguel.md +12 -0
  36. package/harness/opencode/agents/arka-strategy-director-tomas.md +12 -0
  37. package/harness/opencode/agents/arka-tech-director-francisca.md +12 -0
  38. package/harness/opencode/agents/arka-tech-lead-paulo.md +12 -0
  39. package/harness/opencode/agents/arka-video-producer-simao.md +12 -0
  40. package/harness/opencode/commands/arka-brand.md +9 -0
  41. package/harness/opencode/commands/arka-community.md +9 -0
  42. package/harness/opencode/commands/arka-content.md +9 -0
  43. package/harness/opencode/commands/arka-dev.md +9 -0
  44. package/harness/opencode/commands/arka-ecom.md +9 -0
  45. package/harness/opencode/commands/arka-fin.md +9 -0
  46. package/harness/opencode/commands/arka-kb.md +9 -0
  47. package/harness/opencode/commands/arka-landing.md +9 -0
  48. package/harness/opencode/commands/arka-lead.md +9 -0
  49. package/harness/opencode/commands/arka-mkt.md +9 -0
  50. package/harness/opencode/commands/arka-ops.md +9 -0
  51. package/harness/opencode/commands/arka-org.md +9 -0
  52. package/harness/opencode/commands/arka-pm.md +9 -0
  53. package/harness/opencode/commands/arka-saas.md +9 -0
  54. package/harness/opencode/commands/arka-sales.md +9 -0
  55. package/harness/opencode/commands/arka-strat.md +9 -0
  56. package/harness/opencode/opencode.json +16 -0
  57. package/harness/zed/.rules +1 -1
  58. package/installer/adapters/opencode.js +118 -0
  59. package/installer/autoupdate.js +249 -0
  60. package/installer/cli.js +38 -1
  61. package/installer/core-snapshot.js +5 -1
  62. package/installer/detect-runtime.js +18 -0
  63. package/installer/doctor.js +213 -2
  64. package/installer/index.js +182 -85
  65. package/installer/menubar.js +262 -0
  66. package/installer/product-stats.js +93 -0
  67. package/installer/profile.js +75 -0
  68. package/installer/prompts.js +303 -41
  69. package/installer/services.js +487 -0
  70. package/installer/ui.js +190 -0
  71. package/installer/update.js +219 -91
  72. package/knowledge/skills-manifest.json +1 -1
  73. package/package.json +6 -1
  74. package/pyproject.toml +1 -1
  75. package/scripts/auto-update.sh +188 -0
  76. package/scripts/harness_gen.py +98 -1
@@ -10,18 +10,33 @@ import { copyHookLib, copyHookAssets } from "./hook-lib.js";
10
10
  import { deploySkills } from "./skill-deploy.js";
11
11
  import { resolveSkillsMode } from "./skills-mode.js";
12
12
  import { deployCoreSnapshot } from "./core-snapshot.js";
13
+ import { getUi } from "./ui.js";
14
+ import { buildProfileRecord } from "./profile.js";
15
+ import { readProductStats, productStatsLines } from "./product-stats.js";
13
16
 
14
17
  const __filename = fileURLToPath(import.meta.url);
15
18
  const __dirname = dirname(__filename);
16
19
  const ARKAOS_ROOT = resolve(__dirname, "..");
17
20
  const VERSION = JSON.parse(readFileSync(join(ARKAOS_ROOT, "package.json"), "utf-8")).version;
18
21
 
19
- export async function install({ runtime, path, force, skipSystem, withOllama }) {
22
+ // UI facade set at the top of install(). step/ok/warn/detail below
23
+ // delegate to it; the plain facade prints the historical byte-identical
24
+ // output, so headless logs and tests remain stable.
25
+ let ui = null;
26
+
27
+ export async function install({ runtime, path, force, skipSystem, withOllama, profileFlag }) {
20
28
  const startTime = Date.now();
21
29
  const config = getRuntimeConfig(runtime);
22
30
  const isUpgrade = existsSync(join(path || join(homedir(), ".arkaos"), "install-manifest.json"));
23
31
 
24
- console.log(`
32
+ ui = await getUi();
33
+ if (ui.isFancy()) {
34
+ ui.intro(`▲ ARKA OS v${VERSION}`);
35
+ ui.clack.log.message(
36
+ `Runtime: ${config.name}\nMode: ${isUpgrade ? "Upgrade" : "Fresh install"}`,
37
+ );
38
+ } else {
39
+ console.log(`
25
40
  ╔══════════════════════════════════════════════════════════╗
26
41
  ║ ArkaOS v${VERSION} — The Operating System for AI Agent Teams ║
27
42
  ╚══════════════════════════════════════════════════════════╝
@@ -29,10 +44,11 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
29
44
  Runtime: ${config.name}
30
45
  Mode: ${isUpgrade ? "Upgrade" : "Fresh install"}
31
46
  `);
47
+ }
32
48
 
33
49
  // ═══ Interactive Setup ═══
34
50
  const { runSetupPrompts } = await import("./prompts.js");
35
- const userConfig = await runSetupPrompts(isUpgrade);
51
+ const userConfig = await runSetupPrompts(isUpgrade, { profileFlag });
36
52
  const installDir = userConfig.installDir;
37
53
 
38
54
  // ═══ Step 1: Create directories ═══
@@ -71,7 +87,7 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
71
87
  const v1Found = v1Paths.find(p => existsSync(p));
72
88
  if (v1Found && !existsSync(join(installDir, "migrated-from-v1"))) {
73
89
  warn(`v1 detected at ${v1Found}`);
74
- console.log(" Run 'npx arkaos migrate' after install to migrate your data.");
90
+ detail(" Run 'npx arkaos migrate' after install to migrate your data.");
75
91
  } else {
76
92
  ok("No v1 installation found");
77
93
  }
@@ -80,13 +96,16 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
80
96
  // Wrapped in try/catch so a regression in system-tools.js cannot break a
81
97
  // fresh `npx arkaos install` for the ~20K user base. We degrade to a warn
82
98
  // and proceed to venv creation rather than exiting non-zero.
99
+ // Foundation PR-3: the "local-ai" profile implies Ollama for the
100
+ // existing system-tools check. Full profile provisioning is PR-4.
101
+ const wantsOllama = withOllama || userConfig.installProfile === "local-ai";
83
102
  if (!skipSystem) {
84
103
  try {
85
104
  const { ensureSystemTools } = await import("./system-tools.js");
86
105
  const { formatSudoInstructions } = await import("./package-manager.js");
87
- const sys = ensureSystemTools({ skipSystem: false, withOllama });
106
+ const sys = ensureSystemTools({ skipSystem: false, withOllama: wantsOllama });
88
107
  if (sys.sudoCommands && sys.sudoCommands.length > 0) {
89
- console.log(formatSudoInstructions(sys.sudoCommands));
108
+ detail(formatSudoInstructions(sys.sudoCommands));
90
109
  }
91
110
  for (const tool of [sys.obsidian, sys.node, sys.ollama]) {
92
111
  if (!tool) continue;
@@ -94,7 +113,7 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
94
113
  else if (tool.needsAction === "none") ok(`${tool.name} ready`);
95
114
  else warn(`${tool.name} ${tool.needsAction} — see commands above`);
96
115
  }
97
- if (withOllama && sys.ollama?.needsAction === "none") {
116
+ if (wantsOllama && sys.ollama?.needsAction === "none") {
98
117
  ok("Ollama backend ready — cognitive layer can use local LLM inference");
99
118
  }
100
119
  } catch (err) {
@@ -119,7 +138,7 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
119
138
  process.exit(1);
120
139
  }
121
140
  ok(`System Python found: ${systemPython}`);
122
- const venvCreated = ensureVenv((msg) => console.log(msg));
141
+ const venvCreated = ensureVenv((msg) => detail(msg));
123
142
  if (!venvCreated) {
124
143
  warn("Venv creation failed — falling back to system Python (PEP 668 may apply)");
125
144
  }
@@ -177,8 +196,8 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
177
196
  if (existsSync(pyPsSrc) || existsSync(pyCmdSrc)) ok("arka-py interpreter shim installed (.cmd + .ps1)");
178
197
  if (installed) {
179
198
  ok("arka-claude wrapper installed (.cmd + .ps1)");
180
- console.log(` Add to PATH: setx PATH "%PATH%;%USERPROFILE%\\.arkaos\\bin"`);
181
- console.log(` Then reopen any shell and run: arka-claude`);
199
+ detail(` Add to PATH: setx PATH "%PATH%;%USERPROFILE%\\.arkaos\\bin"`);
200
+ detail(` Then reopen any shell and run: arka-claude`);
182
201
  }
183
202
  } else {
184
203
  const wrapperSrc = join(ARKAOS_ROOT, "bin", "arka-claude");
@@ -186,8 +205,8 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
186
205
  copyFileSync(wrapperSrc, join(binDir, "arka-claude"));
187
206
  try { chmodSync(join(binDir, "arka-claude"), 0o755); } catch {}
188
207
  ok("arka-claude wrapper installed");
189
- console.log(` Add to PATH: export PATH="$HOME/.arkaos/bin:$PATH"`);
190
- console.log(` Optional alias: alias claude="arka-claude"`);
208
+ detail(` Add to PATH: export PATH="$HOME/.arkaos/bin:$PATH"`);
209
+ detail(` Optional alias: alias claude="arka-claude"`);
191
210
  }
192
211
  // arka-py — the ArkaOS Python entrypoint SKILL.md commands invoke so the
193
212
  // agent never runs a bare `python` that lacks ArkaOS deps (pyyaml, ...).
@@ -223,28 +242,24 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
223
242
  ok("Core snapshot deployed to ~/.arkaos/lib (survives npx cache purges)");
224
243
  }
225
244
  } catch (err) {
226
- console.log(` ⚠ Core snapshot skipped (${err.message}) — arka-py falls back to .repo-path`);
245
+ detail(` ⚠ Core snapshot skipped (${err.message}) — arka-py falls back to .repo-path`);
227
246
  }
228
247
  const skillsDir = join(config.skillsDir || join(homedir(), ".claude", "skills"), "arkaos");
229
248
  ensureDir(skillsDir);
230
249
  writeFileSync(join(skillsDir, ".arkaos-root"), ARKAOS_ROOT);
231
250
 
232
251
  const profilePath = join(installDir, "profile.json");
233
- const profile = {
234
- version: "2",
235
- language: userConfig.language,
236
- market: userConfig.market,
237
- role: userConfig.role,
238
- company: userConfig.company,
239
- projectsDir: userConfig.projectsDir,
240
- vaultPath: userConfig.vaultPath,
241
- created: existsSync(profilePath)
242
- ? JSON.parse(readFileSync(profilePath, "utf-8")).created
243
- : new Date().toISOString(),
244
- updated: new Date().toISOString(),
245
- };
252
+ // Previous record (upgrade) feeds `created` preservation and the
253
+ // installProfile fallback chain — see installer/profile.js.
254
+ let previousProfile = null;
255
+ if (existsSync(profilePath)) {
256
+ try {
257
+ previousProfile = JSON.parse(readFileSync(profilePath, "utf-8"));
258
+ } catch { /* corrupt previous profile — treated as fresh */ }
259
+ }
260
+ const profile = buildProfileRecord(userConfig, previousProfile);
246
261
  writeFileSync(profilePath, JSON.stringify(profile, null, 2));
247
- ok("Profile saved");
262
+ ok(`Profile saved (${profile.installProfile})`);
248
263
 
249
264
  // Save API keys if provided
250
265
  if (userConfig.openaiKey || userConfig.googleKey || userConfig.falKey) {
@@ -312,16 +327,58 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
312
327
  const { seedArkaosConfig } = await import("./config-seed.js");
313
328
  const seedResult = seedArkaosConfig({ home: homedir() });
314
329
  if (seedResult.action === "created") {
315
- console.log(` hardEnforcement enabled (default).`);
330
+ detail(` hardEnforcement enabled (default).`);
316
331
  } else if (seedResult.action === "added-key") {
317
- console.log(` hardEnforcement enabled (key was unset).`);
332
+ detail(` hardEnforcement enabled (key was unset).`);
318
333
  } else if (seedResult.action === "preserved-user-false") {
319
- console.log(` hardEnforcement is OFF (user-set, preserved).`);
334
+ detail(` hardEnforcement is OFF (user-set, preserved).`);
320
335
  } else if (seedResult.action === "rewrote-corrupt") {
321
- console.log(` config.json was corrupt — rewrote, backup at ${seedResult.backup}`);
336
+ detail(` config.json was corrupt — rewrote, backup at ${seedResult.backup}`);
322
337
  }
323
338
  } catch (err) {
324
- console.log(` Warning: could not seed config.json (${err.message})`);
339
+ detail(` Warning: could not seed config.json (${err.message})`);
340
+ }
341
+
342
+ // Foundation PR-1 — auto-update daemon, default-on (opt-out persists
343
+ // across installs via ~/.arkaos/autoupdate.optout).
344
+ try {
345
+ const { ensureDefaultEnabled } = await import("./autoupdate.js");
346
+ const au = ensureDefaultEnabled({ repoRoot: ARKAOS_ROOT });
347
+ if (au.action === "enabled") detail(` Auto-update daemon enabled (npx arkaos autoupdate status).`);
348
+ else if (au.action === "optout") detail(` Auto-update daemon: user opt-out respected.`);
349
+ } catch (err) {
350
+ detail(` Warning: auto-update daemon not enabled (${err.message})`);
351
+ }
352
+
353
+ // Foundation PR-5 — menu bar launcher (macOS only), default-on with
354
+ // persisted opt-out (npx arkaos menubar disable). rumps install and
355
+ // launchd wiring are best-effort: a failure can never break install.
356
+ // Guards (QG M3/M4): a LaunchAgent is system integration, so it
357
+ // honors --no-system and never touches CI runners; the opt-out is
358
+ // consulted BEFORE pip — a user who disabled the menu bar must not
359
+ // receive its dependency on every install.
360
+ if (process.platform === "darwin" && !skipSystem && !process.env.CI) {
361
+ try {
362
+ const { ensureDefaultEnabled: ensureMenubar, status: menubarStatus } =
363
+ await import("./menubar.js");
364
+ if (menubarStatus().optout) {
365
+ detail(" Menu bar launcher: user opt-out respected.");
366
+ } else {
367
+ if (pipInstall("rumps", { log: (m) => detail(m), timeout: 120000 })) {
368
+ ok("rumps installed (menu bar framework)");
369
+ } else {
370
+ warn("rumps install failed — menu bar app will hint on first run");
371
+ }
372
+ const mb = ensureMenubar({ repoRoot: ARKAOS_ROOT });
373
+ if (mb.action === "enabled") {
374
+ detail(" Menu bar launcher enabled (▲ in the macOS menu bar).");
375
+ } else if (mb.action === "partial") {
376
+ detail(` Menu bar launcher: ${mb.message}`);
377
+ }
378
+ }
379
+ } catch (err) {
380
+ detail(` Warning: could not enable menu bar launcher (${err.message})`);
381
+ }
325
382
  }
326
383
 
327
384
  // PR28 v2.47.0 — scaffold the user-mutable files the discipline-arc
@@ -331,13 +388,13 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
331
388
  const { scaffoldArkaosUserData } = await import("./user-data-scaffold.js");
332
389
  const scaffoldResult = scaffoldArkaosUserData({ home: homedir() });
333
390
  if (scaffoldResult.redaction.action === "created") {
334
- console.log(` redaction-clients.json scaffolded (empty list — populate to enable leak scanner).`);
391
+ detail(` redaction-clients.json scaffolded (empty list — populate to enable leak scanner).`);
335
392
  }
336
393
  if (scaffoldResult.proposals.action === "created") {
337
- console.log(` reorganize-proposals/ directory created.`);
394
+ detail(` reorganize-proposals/ directory created.`);
338
395
  }
339
396
  } catch (err) {
340
- console.log(` Warning: could not scaffold user-data (${err.message})`);
397
+ detail(` Warning: could not scaffold user-data (${err.message})`);
341
398
  }
342
399
 
343
400
  // PR45 v2.64.0 — seed autoMode.hard_deny defaults into
@@ -350,13 +407,13 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
350
407
  const denyResult = seedAutoModeHardDeny({ runtime });
351
408
  if (!denyResult.skipped) {
352
409
  if (denyResult.action === "created") {
353
- console.log(` autoMode.hard_deny created (${denyResult.count} rules).`);
410
+ detail(` autoMode.hard_deny created (${denyResult.count} rules).`);
354
411
  } else if (denyResult.action === "merged") {
355
- console.log(` autoMode.hard_deny merged (${denyResult.count} rules, operator entries preserved).`);
412
+ detail(` autoMode.hard_deny merged (${denyResult.count} rules, operator entries preserved).`);
356
413
  }
357
414
  }
358
415
  } catch (err) {
359
- console.log(` Warning: could not seed autoMode.hard_deny (${err.message})`);
416
+ detail(` Warning: could not seed autoMode.hard_deny (${err.message})`);
360
417
  }
361
418
 
362
419
  // PR48 v2.67.0 — seed worktree.baseRef = "head" so new Claude Code
@@ -366,12 +423,12 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
366
423
  const { seedWorktreeBaseRef } = await import("./worktree-baseref.js");
367
424
  const wtResult = seedWorktreeBaseRef({ runtime });
368
425
  if (!wtResult.skipped && wtResult.action === "created") {
369
- console.log(` worktree.baseRef set to "${wtResult.value}".`);
426
+ detail(` worktree.baseRef set to "${wtResult.value}".`);
370
427
  } else if (!wtResult.skipped && wtResult.action === "merged") {
371
- console.log(` worktree.baseRef merged ("${wtResult.value}").`);
428
+ detail(` worktree.baseRef merged ("${wtResult.value}").`);
372
429
  }
373
430
  } catch (err) {
374
- console.log(` Warning: could not seed worktree.baseRef (${err.message})`);
431
+ detail(` Warning: could not seed worktree.baseRef (${err.message})`);
375
432
  }
376
433
 
377
434
  // Interaction Reform PR1 — install the ArkaOS output style and seed
@@ -384,14 +441,14 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
384
441
  sourceDir: join(ARKAOS_ROOT, "config", "output-styles"),
385
442
  });
386
443
  if (styleResult.copied > 0) {
387
- console.log(` ${styleResult.copied} output style(s) installed (~/.claude/output-styles/).`);
444
+ detail(` ${styleResult.copied} output style(s) installed (~/.claude/output-styles/).`);
388
445
  }
389
446
  const seedResult = seedOutputStyleDefault({ runtime });
390
447
  if (!seedResult.skipped && seedResult.action === "created") {
391
- console.log(` outputStyle default set to "${seedResult.value}".`);
448
+ detail(` outputStyle default set to "${seedResult.value}".`);
392
449
  }
393
450
  } catch (err) {
394
- console.log(` Warning: could not install output style (${err.message})`);
451
+ detail(` Warning: could not install output style (${err.message})`);
395
452
  }
396
453
 
397
454
  // PR43 v2.62.0 — auto-install default Claude Code plugins. Only runs
@@ -403,23 +460,23 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
403
460
  if (!pluginResult.skipped) {
404
461
  for (const m of pluginResult.marketplaces || []) {
405
462
  if (m.action === "added") {
406
- console.log(` marketplace ${m.marketplace} added.`);
463
+ detail(` marketplace ${m.marketplace} added.`);
407
464
  } else if (m.action === "failed") {
408
- console.log(` marketplace ${m.marketplace} failed (${m.reason}).`);
465
+ detail(` marketplace ${m.marketplace} failed (${m.reason}).`);
409
466
  }
410
467
  }
411
468
  for (const r of pluginResult.results) {
412
469
  if (r.action === "installed") {
413
- console.log(` ${r.plugin} installed.`);
470
+ detail(` ${r.plugin} installed.`);
414
471
  } else if (r.action === "already-present") {
415
- console.log(` ${r.plugin} already installed (skipped).`);
472
+ detail(` ${r.plugin} already installed (skipped).`);
416
473
  } else if (r.action === "failed") {
417
- console.log(` ${r.plugin} install failed (${r.reason}).`);
474
+ detail(` ${r.plugin} install failed (${r.reason}).`);
418
475
  }
419
476
  }
420
477
  }
421
478
  } catch (err) {
422
- console.log(` Warning: could not install default Claude plugins (${err.message})`);
479
+ detail(` Warning: could not install default Claude plugins (${err.message})`);
423
480
  }
424
481
 
425
482
  // Frontend UI/UX tooling — Magic MCP (user scope, API-key gated) + Motion
@@ -428,19 +485,19 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
428
485
  const { setupFrontendTooling } = await import("./frontend-tooling.js");
429
486
  const ft = await setupFrontendTooling({ runtime });
430
487
  if (ft.magicMcp?.action === "registered") {
431
- console.log(" Magic MCP registered (user scope).");
488
+ detail(" Magic MCP registered (user scope).");
432
489
  } else if (ft.magicMcp?.action === "already-present") {
433
- console.log(" Magic MCP already registered (skipped).");
490
+ detail(" Magic MCP already registered (skipped).");
434
491
  } else if (ft.magicMcp?.action === "failed") {
435
- console.log(` Magic MCP registration failed (${ft.magicMcp.reason}).`);
492
+ detail(` Magic MCP registration failed (${ft.magicMcp.reason}).`);
436
493
  }
437
494
  if (ft.motionKit?.action === "installed") {
438
- console.log(" Motion AI Kit installed.");
495
+ detail(" Motion AI Kit installed.");
439
496
  } else if (ft.motionKit?.action === "failed") {
440
- console.log(` Motion AI Kit install failed (${ft.motionKit.reason}).`);
497
+ detail(` Motion AI Kit install failed (${ft.motionKit.reason}).`);
441
498
  }
442
499
  } catch (err) {
443
- console.log(` Warning: could not set up frontend tooling (${err.message})`);
500
+ detail(` Warning: could not set up frontend tooling (${err.message})`);
444
501
  }
445
502
 
446
503
  // Graphify grounding layer — code knowledge graphs (PyPI `graphifyy`,
@@ -450,24 +507,24 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
450
507
  const { ensureGraphify, configureGraphifyHttp } = await import("./graphify.js");
451
508
  const gf = ensureGraphify();
452
509
  if (gf.binary?.installed) {
453
- console.log(` Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}.`);
510
+ detail(` Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}.`);
454
511
  if (gf.skillInstall?.action === "installed") {
455
- console.log(" Graphify skill registered (graphify install).");
512
+ detail(" Graphify skill registered (graphify install).");
456
513
  } else if (gf.skillInstall?.action === "failed") {
457
- console.log(` Graphify skill registration failed (${gf.skillInstall.reason}).`);
514
+ detail(` Graphify skill registration failed (${gf.skillInstall.reason}).`);
458
515
  }
459
516
  } else if (gf.binary?.hint) {
460
- console.log(` ${gf.binary.hint}`);
517
+ detail(` ${gf.binary.hint}`);
461
518
  }
462
519
  // Graphify HTTP knowledge-graph MCP (user-scope, config-driven endpoint).
463
520
  const gh = await configureGraphifyHttp({ runtime });
464
521
  if (gh.action === "registered" || gh.action === "re-registered") {
465
- console.log(` Graphify knowledge-graph MCP ${gh.action} (user scope).`);
522
+ detail(` Graphify knowledge-graph MCP ${gh.action} (user scope).`);
466
523
  } else if (gh.action === "failed") {
467
- console.log(` Graphify knowledge-graph MCP not registered (${gh.reason}).`);
524
+ detail(` Graphify knowledge-graph MCP not registered (${gh.reason}).`);
468
525
  }
469
526
  } catch (err) {
470
- console.log(` Warning: could not set up Graphify (${err.message})`);
527
+ detail(` Warning: could not set up Graphify (${err.message})`);
471
528
  }
472
529
 
473
530
  const manifest = {
@@ -505,16 +562,41 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
505
562
 
506
563
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
507
564
 
508
- console.log(`
565
+ // Counts derived from the shipped artifacts — never hand-typed
566
+ // (docs-as-code). Null fields are omitted from the summary.
567
+ const stats = readProductStats(ARKAOS_ROOT);
568
+ const statLines = productStatsLines(stats);
569
+
570
+ if (ui.isFancy()) {
571
+ ui.stopSpinner();
572
+ const warnCount = ui.warnings().length;
573
+ ui.note(
574
+ [
575
+ `Runtime: ${config.name}`,
576
+ `Install dir: ${installDir}`,
577
+ `Profile: ${profile.installProfile}`,
578
+ ...statLines,
579
+ `Dashboard: npx arkaos dashboard`,
580
+ warnCount > 0 ? `Warnings: ${warnCount} (see above)` : null,
581
+ ``,
582
+ `Quick start: /arka · /do <description> · /dev feature`,
583
+ ]
584
+ .filter((l) => l !== null)
585
+ .join("\n"),
586
+ `ArkaOS v${VERSION} installed (${elapsed}s)`,
587
+ );
588
+ ui.outro("Open Claude Code and run /arka to get started.");
589
+ } else {
590
+ const plainStatLines = statLines.map((l) => ` ${l}`).join("\n");
591
+ console.log(`
509
592
  ╔══════════════════════════════════════════════════════════╗
510
593
  ║ ArkaOS v${VERSION} installed successfully! (${elapsed}s) ║
511
594
  ╚══════════════════════════════════════════════════════════╝
512
595
 
513
596
  Runtime: ${config.name}
514
597
  Install dir: ${installDir}
515
- Agents: 65 across 17 departments
516
- Skills: 244+ backed by enterprise frameworks
517
- Knowledge: Vector DB with semantic search
598
+ Profile: ${profile.installProfile}
599
+ ${plainStatLines ? plainStatLines + "\n" : ""} Knowledge: Vector DB with semantic search
518
600
  Dashboard: Run 'npx arkaos dashboard' to start
519
601
 
520
602
  Quick start:
@@ -530,22 +612,37 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
530
612
  npx arkaos keys — Configure API keys
531
613
  npx arkaos doctor — Run health checks
532
614
  `);
615
+ }
533
616
  }
534
617
 
535
618
  // ═══ Helper Functions ═══
619
+ // All four delegate to the UI facade when install() has resolved it;
620
+ // the plain facade (and the pre-resolution fallback) prints the exact
621
+ // historical strings, so non-fancy output is byte-identical.
536
622
 
537
623
  function step(n, total, msg) {
624
+ if (ui) return ui.step(n, total, msg);
538
625
  console.log(` [${n}/${total}] ${msg}`);
539
626
  }
540
627
 
541
628
  function ok(msg) {
629
+ if (ui) return ui.ok(msg);
542
630
  console.log(` ✓ ${msg}`);
543
631
  }
544
632
 
545
633
  function warn(msg) {
634
+ if (ui) return ui.warn(msg);
546
635
  console.log(` ⚠ ${msg}`);
547
636
  }
548
637
 
638
+ // Secondary info lines (pip progress, PATH hints, seed results). Fancy
639
+ // mode folds them into the active spinner; plain mode prints them
640
+ // unchanged.
641
+ function detail(msg) {
642
+ if (ui) return ui.detail(msg);
643
+ console.log(msg);
644
+ }
645
+
549
646
  function ensureDir(dir) {
550
647
  if (!existsSync(dir)) {
551
648
  mkdirSync(dir, { recursive: true });
@@ -553,7 +650,7 @@ function ensureDir(dir) {
553
650
  }
554
651
 
555
652
  function installAllPythonDeps(userConfig = {}) {
556
- const log = (msg) => console.log(msg);
653
+ const log = (msg) => detail(msg);
557
654
 
558
655
  // Core dependencies
559
656
  const coreDeps = "pyyaml pydantic rich click jinja2";
@@ -571,7 +668,7 @@ function installAllPythonDeps(userConfig = {}) {
571
668
  const transcriptionDeps = "faster-whisper";
572
669
 
573
670
  // Core deps (required)
574
- console.log(" Installing core dependencies...");
671
+ detail(" Installing core dependencies...");
575
672
  if (pipInstall(coreDeps, { log })) {
576
673
  ok("Core deps installed (pyyaml, pydantic, rich, click, jinja2)");
577
674
  } else {
@@ -583,7 +680,7 @@ function installAllPythonDeps(userConfig = {}) {
583
680
  // is reported accurately and the user knows which capability they
584
681
  // actually have.
585
682
  if (userConfig.installKnowledge !== false) {
586
- console.log(" Installing knowledge base dependencies...");
683
+ detail(" Installing knowledge base dependencies...");
587
684
  const fastembedOk = pipInstall("fastembed", { log, timeout: 180000 });
588
685
  const sqliteVssOk = pipInstall("sqlite-vec", { log, timeout: 180000 });
589
686
  if (fastembedOk && sqliteVssOk) {
@@ -598,7 +695,7 @@ function installAllPythonDeps(userConfig = {}) {
598
695
  }
599
696
 
600
697
  // Ingest deps
601
- console.log(" Installing content ingest dependencies...");
698
+ detail(" Installing content ingest dependencies...");
602
699
  if (pipInstall(ingestDeps, { log })) {
603
700
  ok("Ingest deps installed (yt-dlp, pdfplumber, beautifulsoup4)");
604
701
  } else {
@@ -607,7 +704,7 @@ function installAllPythonDeps(userConfig = {}) {
607
704
 
608
705
  // Dashboard deps (optional)
609
706
  if (userConfig.installDashboard !== false) {
610
- console.log(" Installing dashboard dependencies...");
707
+ detail(" Installing dashboard dependencies...");
611
708
  if (pipInstall(dashboardDeps, { log, timeout: 120000 })) {
612
709
  ok("Dashboard API installed (fastapi, uvicorn, python-multipart)");
613
710
  } else {
@@ -616,7 +713,7 @@ function installAllPythonDeps(userConfig = {}) {
616
713
  }
617
714
 
618
715
  // Transcription (optional, heavy)
619
- console.log(" Installing transcription engine...");
716
+ detail(" Installing transcription engine...");
620
717
  if (pipInstall(transcriptionDeps, { log, timeout: 300000 })) {
621
718
  ok("Whisper transcription installed");
622
719
  } else {
@@ -624,7 +721,7 @@ function installAllPythonDeps(userConfig = {}) {
624
721
  }
625
722
 
626
723
  // Install ArkaOS core package as editable
627
- console.log(" Installing ArkaOS core engine...");
724
+ detail(" Installing ArkaOS core engine...");
628
725
  if (pipInstall("", { editable: ARKAOS_ROOT, log, timeout: 60000 })) {
629
726
  ok("ArkaOS core engine installed");
630
727
  } else {
@@ -1045,13 +1142,13 @@ function printSchtasksAccessDeniedHelp(pythonPath, daemonPath) {
1045
1142
  // nice-to-have that keeps background tasks ticking. We print the
1046
1143
  // manual registration command so power users can elevate once and
1047
1144
  // run it, and move on.
1048
- console.log(" Access denied. Options:");
1049
- console.log(" 1) Re-run the installer from an elevated PowerShell (Run as Administrator)");
1050
- console.log(" 2) Or register the task manually from an elevated prompt:");
1051
- console.log(` schtasks /Create /F /TN "ArkaOS-Scheduler" /SC ONLOGON /TR "\"${pythonPath}\" \"${daemonPath}\""`);
1052
- console.log(" 3) Or skip the scheduler and launch the daemon manually when needed:");
1053
- console.log(` "${pythonPath}" "${daemonPath}"`);
1054
- console.log(" The rest of ArkaOS is installed and functional — this only affects background task automation.");
1145
+ detail(" Access denied. Options:");
1146
+ detail(" 1) Re-run the installer from an elevated PowerShell (Run as Administrator)");
1147
+ detail(" 2) Or register the task manually from an elevated prompt:");
1148
+ detail(` schtasks /Create /F /TN "ArkaOS-Scheduler" /SC ONLOGON /TR "\"${pythonPath}\" \"${daemonPath}\""`);
1149
+ detail(" 3) Or skip the scheduler and launch the daemon manually when needed:");
1150
+ detail(` "${pythonPath}" "${daemonPath}"`);
1151
+ detail(" The rest of ArkaOS is installed and functional — this only affects background task automation.");
1055
1152
  }
1056
1153
 
1057
1154
  function installSchtasksService(daemonPath) {
@@ -1108,7 +1205,7 @@ function installSchtasksService(daemonPath) {
1108
1205
  if (kind === "access-denied") {
1109
1206
  printSchtasksAccessDeniedHelp(pythonPath, daemonPath);
1110
1207
  } else {
1111
- console.log(" Try manually: schtasks /Create /F /TN \"ArkaOS-Scheduler\" /SC ONLOGON /TR \"" + pythonPath + " " + daemonPath + "\"");
1208
+ detail(" Try manually: schtasks /Create /F /TN \"ArkaOS-Scheduler\" /SC ONLOGON /TR \"" + pythonPath + " " + daemonPath + "\"");
1112
1209
  }
1113
1210
  return;
1114
1211
  }
@@ -1118,9 +1215,9 @@ function installSchtasksService(daemonPath) {
1118
1215
  const kind = classifySchtasksError(runResult.stderr);
1119
1216
  warn(`Scheduler: task created but /Run failed (${kind}): ${runResult.stderr.slice(0, 220)}`);
1120
1217
  if (kind === "access-denied") {
1121
- console.log(" Task is registered but couldn't be started now. It will run automatically at next logon.");
1218
+ detail(" Task is registered but couldn't be started now. It will run automatically at next logon.");
1122
1219
  } else {
1123
- console.log(" Task will start at next logon");
1220
+ detail(" Task will start at next logon");
1124
1221
  }
1125
1222
  return;
1126
1223
  }
@@ -1135,7 +1232,7 @@ export async function loadAdapter(runtime) {
1135
1232
  } catch {
1136
1233
  return {
1137
1234
  configureHooks(config, installDir) {
1138
- console.log(` Using generic configuration for ${runtime}`);
1235
+ detail(` Using generic configuration for ${runtime}`);
1139
1236
  },
1140
1237
  };
1141
1238
  }