arkaos 4.34.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 (70) 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/install-profiles.json +75 -0
  5. package/core/runtime/opencode.py +113 -0
  6. package/core/runtime/registry.py +6 -1
  7. package/harness/codex/AGENTS.md +1 -1
  8. package/harness/copilot/copilot-instructions.md +1 -1
  9. package/harness/cursor/rules/arkaos.mdc +2 -2
  10. package/harness/gemini/GEMINI.md +1 -1
  11. package/harness/opencode/AGENTS.md +1 -1
  12. package/harness/opencode/agents/arka-architect-gabriel.md +12 -0
  13. package/harness/opencode/agents/arka-brand-director-valentina.md +12 -0
  14. package/harness/opencode/agents/arka-cfo-helena.md +12 -0
  15. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +12 -0
  16. package/harness/opencode/agents/arka-community-strategist-beatriz.md +12 -0
  17. package/harness/opencode/agents/arka-content-strategist-rafael.md +12 -0
  18. package/harness/opencode/agents/arka-conversion-strategist-ines.md +12 -0
  19. package/harness/opencode/agents/arka-coo-sofia.md +12 -0
  20. package/harness/opencode/agents/arka-copy-director-eduardo.md +12 -0
  21. package/harness/opencode/agents/arka-cqo-marta.md +12 -0
  22. package/harness/opencode/agents/arka-cto-marco.md +12 -0
  23. package/harness/opencode/agents/arka-design-ops-lead-iris.md +12 -0
  24. package/harness/opencode/agents/arka-ecom-director-ricardo.md +12 -0
  25. package/harness/opencode/agents/arka-knowledge-director-clara.md +12 -0
  26. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +12 -0
  27. package/harness/opencode/agents/arka-marketing-director-luna.md +12 -0
  28. package/harness/opencode/agents/arka-ops-lead-daniel.md +12 -0
  29. package/harness/opencode/agents/arka-pm-director-carolina.md +12 -0
  30. package/harness/opencode/agents/arka-revops-lead-vicente.md +12 -0
  31. package/harness/opencode/agents/arka-saas-strategist-tiago.md +12 -0
  32. package/harness/opencode/agents/arka-sales-director-miguel.md +12 -0
  33. package/harness/opencode/agents/arka-strategy-director-tomas.md +12 -0
  34. package/harness/opencode/agents/arka-tech-director-francisca.md +12 -0
  35. package/harness/opencode/agents/arka-tech-lead-paulo.md +12 -0
  36. package/harness/opencode/agents/arka-video-producer-simao.md +12 -0
  37. package/harness/opencode/commands/arka-brand.md +9 -0
  38. package/harness/opencode/commands/arka-community.md +9 -0
  39. package/harness/opencode/commands/arka-content.md +9 -0
  40. package/harness/opencode/commands/arka-dev.md +9 -0
  41. package/harness/opencode/commands/arka-ecom.md +9 -0
  42. package/harness/opencode/commands/arka-fin.md +9 -0
  43. package/harness/opencode/commands/arka-kb.md +9 -0
  44. package/harness/opencode/commands/arka-landing.md +9 -0
  45. package/harness/opencode/commands/arka-lead.md +9 -0
  46. package/harness/opencode/commands/arka-mkt.md +9 -0
  47. package/harness/opencode/commands/arka-ops.md +9 -0
  48. package/harness/opencode/commands/arka-org.md +9 -0
  49. package/harness/opencode/commands/arka-pm.md +9 -0
  50. package/harness/opencode/commands/arka-saas.md +9 -0
  51. package/harness/opencode/commands/arka-sales.md +9 -0
  52. package/harness/opencode/commands/arka-strat.md +9 -0
  53. package/harness/opencode/opencode.json +16 -0
  54. package/harness/zed/.rules +1 -1
  55. package/installer/adapters/opencode.js +118 -0
  56. package/installer/cli.js +31 -1
  57. package/installer/detect-runtime.js +18 -0
  58. package/installer/doctor.js +202 -2
  59. package/installer/index.js +174 -88
  60. package/installer/menubar.js +262 -0
  61. package/installer/product-stats.js +93 -0
  62. package/installer/profile.js +75 -0
  63. package/installer/prompts.js +303 -41
  64. package/installer/services.js +487 -0
  65. package/installer/ui.js +190 -0
  66. package/installer/update.js +212 -95
  67. package/knowledge/skills-manifest.json +1 -1
  68. package/package.json +6 -1
  69. package/pyproject.toml +1 -1
  70. 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,16 @@ 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})`);
325
340
  }
326
341
 
327
342
  // Foundation PR-1 — auto-update daemon, default-on (opt-out persists
@@ -329,10 +344,41 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
329
344
  try {
330
345
  const { ensureDefaultEnabled } = await import("./autoupdate.js");
331
346
  const au = ensureDefaultEnabled({ repoRoot: ARKAOS_ROOT });
332
- if (au.action === "enabled") console.log(` Auto-update daemon enabled (npx arkaos autoupdate status).`);
333
- else if (au.action === "optout") console.log(` Auto-update daemon: user opt-out respected.`);
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.`);
334
349
  } catch (err) {
335
- console.log(` Warning: auto-update daemon not enabled (${err.message})`);
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
+ }
336
382
  }
337
383
 
338
384
  // PR28 v2.47.0 — scaffold the user-mutable files the discipline-arc
@@ -342,13 +388,13 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
342
388
  const { scaffoldArkaosUserData } = await import("./user-data-scaffold.js");
343
389
  const scaffoldResult = scaffoldArkaosUserData({ home: homedir() });
344
390
  if (scaffoldResult.redaction.action === "created") {
345
- 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).`);
346
392
  }
347
393
  if (scaffoldResult.proposals.action === "created") {
348
- console.log(` reorganize-proposals/ directory created.`);
394
+ detail(` reorganize-proposals/ directory created.`);
349
395
  }
350
396
  } catch (err) {
351
- console.log(` Warning: could not scaffold user-data (${err.message})`);
397
+ detail(` Warning: could not scaffold user-data (${err.message})`);
352
398
  }
353
399
 
354
400
  // PR45 v2.64.0 — seed autoMode.hard_deny defaults into
@@ -361,13 +407,13 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
361
407
  const denyResult = seedAutoModeHardDeny({ runtime });
362
408
  if (!denyResult.skipped) {
363
409
  if (denyResult.action === "created") {
364
- console.log(` autoMode.hard_deny created (${denyResult.count} rules).`);
410
+ detail(` autoMode.hard_deny created (${denyResult.count} rules).`);
365
411
  } else if (denyResult.action === "merged") {
366
- console.log(` autoMode.hard_deny merged (${denyResult.count} rules, operator entries preserved).`);
412
+ detail(` autoMode.hard_deny merged (${denyResult.count} rules, operator entries preserved).`);
367
413
  }
368
414
  }
369
415
  } catch (err) {
370
- console.log(` Warning: could not seed autoMode.hard_deny (${err.message})`);
416
+ detail(` Warning: could not seed autoMode.hard_deny (${err.message})`);
371
417
  }
372
418
 
373
419
  // PR48 v2.67.0 — seed worktree.baseRef = "head" so new Claude Code
@@ -377,12 +423,12 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
377
423
  const { seedWorktreeBaseRef } = await import("./worktree-baseref.js");
378
424
  const wtResult = seedWorktreeBaseRef({ runtime });
379
425
  if (!wtResult.skipped && wtResult.action === "created") {
380
- console.log(` worktree.baseRef set to "${wtResult.value}".`);
426
+ detail(` worktree.baseRef set to "${wtResult.value}".`);
381
427
  } else if (!wtResult.skipped && wtResult.action === "merged") {
382
- console.log(` worktree.baseRef merged ("${wtResult.value}").`);
428
+ detail(` worktree.baseRef merged ("${wtResult.value}").`);
383
429
  }
384
430
  } catch (err) {
385
- console.log(` Warning: could not seed worktree.baseRef (${err.message})`);
431
+ detail(` Warning: could not seed worktree.baseRef (${err.message})`);
386
432
  }
387
433
 
388
434
  // Interaction Reform PR1 — install the ArkaOS output style and seed
@@ -395,14 +441,14 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
395
441
  sourceDir: join(ARKAOS_ROOT, "config", "output-styles"),
396
442
  });
397
443
  if (styleResult.copied > 0) {
398
- console.log(` ${styleResult.copied} output style(s) installed (~/.claude/output-styles/).`);
444
+ detail(` ${styleResult.copied} output style(s) installed (~/.claude/output-styles/).`);
399
445
  }
400
446
  const seedResult = seedOutputStyleDefault({ runtime });
401
447
  if (!seedResult.skipped && seedResult.action === "created") {
402
- console.log(` outputStyle default set to "${seedResult.value}".`);
448
+ detail(` outputStyle default set to "${seedResult.value}".`);
403
449
  }
404
450
  } catch (err) {
405
- console.log(` Warning: could not install output style (${err.message})`);
451
+ detail(` Warning: could not install output style (${err.message})`);
406
452
  }
407
453
 
408
454
  // PR43 v2.62.0 — auto-install default Claude Code plugins. Only runs
@@ -414,23 +460,23 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
414
460
  if (!pluginResult.skipped) {
415
461
  for (const m of pluginResult.marketplaces || []) {
416
462
  if (m.action === "added") {
417
- console.log(` marketplace ${m.marketplace} added.`);
463
+ detail(` marketplace ${m.marketplace} added.`);
418
464
  } else if (m.action === "failed") {
419
- console.log(` marketplace ${m.marketplace} failed (${m.reason}).`);
465
+ detail(` marketplace ${m.marketplace} failed (${m.reason}).`);
420
466
  }
421
467
  }
422
468
  for (const r of pluginResult.results) {
423
469
  if (r.action === "installed") {
424
- console.log(` ${r.plugin} installed.`);
470
+ detail(` ${r.plugin} installed.`);
425
471
  } else if (r.action === "already-present") {
426
- console.log(` ${r.plugin} already installed (skipped).`);
472
+ detail(` ${r.plugin} already installed (skipped).`);
427
473
  } else if (r.action === "failed") {
428
- console.log(` ${r.plugin} install failed (${r.reason}).`);
474
+ detail(` ${r.plugin} install failed (${r.reason}).`);
429
475
  }
430
476
  }
431
477
  }
432
478
  } catch (err) {
433
- console.log(` Warning: could not install default Claude plugins (${err.message})`);
479
+ detail(` Warning: could not install default Claude plugins (${err.message})`);
434
480
  }
435
481
 
436
482
  // Frontend UI/UX tooling — Magic MCP (user scope, API-key gated) + Motion
@@ -439,19 +485,19 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
439
485
  const { setupFrontendTooling } = await import("./frontend-tooling.js");
440
486
  const ft = await setupFrontendTooling({ runtime });
441
487
  if (ft.magicMcp?.action === "registered") {
442
- console.log(" Magic MCP registered (user scope).");
488
+ detail(" Magic MCP registered (user scope).");
443
489
  } else if (ft.magicMcp?.action === "already-present") {
444
- console.log(" Magic MCP already registered (skipped).");
490
+ detail(" Magic MCP already registered (skipped).");
445
491
  } else if (ft.magicMcp?.action === "failed") {
446
- console.log(` Magic MCP registration failed (${ft.magicMcp.reason}).`);
492
+ detail(` Magic MCP registration failed (${ft.magicMcp.reason}).`);
447
493
  }
448
494
  if (ft.motionKit?.action === "installed") {
449
- console.log(" Motion AI Kit installed.");
495
+ detail(" Motion AI Kit installed.");
450
496
  } else if (ft.motionKit?.action === "failed") {
451
- console.log(` Motion AI Kit install failed (${ft.motionKit.reason}).`);
497
+ detail(` Motion AI Kit install failed (${ft.motionKit.reason}).`);
452
498
  }
453
499
  } catch (err) {
454
- console.log(` Warning: could not set up frontend tooling (${err.message})`);
500
+ detail(` Warning: could not set up frontend tooling (${err.message})`);
455
501
  }
456
502
 
457
503
  // Graphify grounding layer — code knowledge graphs (PyPI `graphifyy`,
@@ -461,24 +507,24 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
461
507
  const { ensureGraphify, configureGraphifyHttp } = await import("./graphify.js");
462
508
  const gf = ensureGraphify();
463
509
  if (gf.binary?.installed) {
464
- console.log(` Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}.`);
510
+ detail(` Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}.`);
465
511
  if (gf.skillInstall?.action === "installed") {
466
- console.log(" Graphify skill registered (graphify install).");
512
+ detail(" Graphify skill registered (graphify install).");
467
513
  } else if (gf.skillInstall?.action === "failed") {
468
- console.log(` Graphify skill registration failed (${gf.skillInstall.reason}).`);
514
+ detail(` Graphify skill registration failed (${gf.skillInstall.reason}).`);
469
515
  }
470
516
  } else if (gf.binary?.hint) {
471
- console.log(` ${gf.binary.hint}`);
517
+ detail(` ${gf.binary.hint}`);
472
518
  }
473
519
  // Graphify HTTP knowledge-graph MCP (user-scope, config-driven endpoint).
474
520
  const gh = await configureGraphifyHttp({ runtime });
475
521
  if (gh.action === "registered" || gh.action === "re-registered") {
476
- console.log(` Graphify knowledge-graph MCP ${gh.action} (user scope).`);
522
+ detail(` Graphify knowledge-graph MCP ${gh.action} (user scope).`);
477
523
  } else if (gh.action === "failed") {
478
- console.log(` Graphify knowledge-graph MCP not registered (${gh.reason}).`);
524
+ detail(` Graphify knowledge-graph MCP not registered (${gh.reason}).`);
479
525
  }
480
526
  } catch (err) {
481
- console.log(` Warning: could not set up Graphify (${err.message})`);
527
+ detail(` Warning: could not set up Graphify (${err.message})`);
482
528
  }
483
529
 
484
530
  const manifest = {
@@ -516,16 +562,41 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
516
562
 
517
563
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
518
564
 
519
- 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(`
520
592
  ╔══════════════════════════════════════════════════════════╗
521
593
  ║ ArkaOS v${VERSION} installed successfully! (${elapsed}s) ║
522
594
  ╚══════════════════════════════════════════════════════════╝
523
595
 
524
596
  Runtime: ${config.name}
525
597
  Install dir: ${installDir}
526
- Agents: 65 across 17 departments
527
- Skills: 244+ backed by enterprise frameworks
528
- Knowledge: Vector DB with semantic search
598
+ Profile: ${profile.installProfile}
599
+ ${plainStatLines ? plainStatLines + "\n" : ""} Knowledge: Vector DB with semantic search
529
600
  Dashboard: Run 'npx arkaos dashboard' to start
530
601
 
531
602
  Quick start:
@@ -541,22 +612,37 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
541
612
  npx arkaos keys — Configure API keys
542
613
  npx arkaos doctor — Run health checks
543
614
  `);
615
+ }
544
616
  }
545
617
 
546
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.
547
622
 
548
623
  function step(n, total, msg) {
624
+ if (ui) return ui.step(n, total, msg);
549
625
  console.log(` [${n}/${total}] ${msg}`);
550
626
  }
551
627
 
552
628
  function ok(msg) {
629
+ if (ui) return ui.ok(msg);
553
630
  console.log(` ✓ ${msg}`);
554
631
  }
555
632
 
556
633
  function warn(msg) {
634
+ if (ui) return ui.warn(msg);
557
635
  console.log(` ⚠ ${msg}`);
558
636
  }
559
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
+
560
646
  function ensureDir(dir) {
561
647
  if (!existsSync(dir)) {
562
648
  mkdirSync(dir, { recursive: true });
@@ -564,7 +650,7 @@ function ensureDir(dir) {
564
650
  }
565
651
 
566
652
  function installAllPythonDeps(userConfig = {}) {
567
- const log = (msg) => console.log(msg);
653
+ const log = (msg) => detail(msg);
568
654
 
569
655
  // Core dependencies
570
656
  const coreDeps = "pyyaml pydantic rich click jinja2";
@@ -582,7 +668,7 @@ function installAllPythonDeps(userConfig = {}) {
582
668
  const transcriptionDeps = "faster-whisper";
583
669
 
584
670
  // Core deps (required)
585
- console.log(" Installing core dependencies...");
671
+ detail(" Installing core dependencies...");
586
672
  if (pipInstall(coreDeps, { log })) {
587
673
  ok("Core deps installed (pyyaml, pydantic, rich, click, jinja2)");
588
674
  } else {
@@ -594,7 +680,7 @@ function installAllPythonDeps(userConfig = {}) {
594
680
  // is reported accurately and the user knows which capability they
595
681
  // actually have.
596
682
  if (userConfig.installKnowledge !== false) {
597
- console.log(" Installing knowledge base dependencies...");
683
+ detail(" Installing knowledge base dependencies...");
598
684
  const fastembedOk = pipInstall("fastembed", { log, timeout: 180000 });
599
685
  const sqliteVssOk = pipInstall("sqlite-vec", { log, timeout: 180000 });
600
686
  if (fastembedOk && sqliteVssOk) {
@@ -609,7 +695,7 @@ function installAllPythonDeps(userConfig = {}) {
609
695
  }
610
696
 
611
697
  // Ingest deps
612
- console.log(" Installing content ingest dependencies...");
698
+ detail(" Installing content ingest dependencies...");
613
699
  if (pipInstall(ingestDeps, { log })) {
614
700
  ok("Ingest deps installed (yt-dlp, pdfplumber, beautifulsoup4)");
615
701
  } else {
@@ -618,7 +704,7 @@ function installAllPythonDeps(userConfig = {}) {
618
704
 
619
705
  // Dashboard deps (optional)
620
706
  if (userConfig.installDashboard !== false) {
621
- console.log(" Installing dashboard dependencies...");
707
+ detail(" Installing dashboard dependencies...");
622
708
  if (pipInstall(dashboardDeps, { log, timeout: 120000 })) {
623
709
  ok("Dashboard API installed (fastapi, uvicorn, python-multipart)");
624
710
  } else {
@@ -627,7 +713,7 @@ function installAllPythonDeps(userConfig = {}) {
627
713
  }
628
714
 
629
715
  // Transcription (optional, heavy)
630
- console.log(" Installing transcription engine...");
716
+ detail(" Installing transcription engine...");
631
717
  if (pipInstall(transcriptionDeps, { log, timeout: 300000 })) {
632
718
  ok("Whisper transcription installed");
633
719
  } else {
@@ -635,7 +721,7 @@ function installAllPythonDeps(userConfig = {}) {
635
721
  }
636
722
 
637
723
  // Install ArkaOS core package as editable
638
- console.log(" Installing ArkaOS core engine...");
724
+ detail(" Installing ArkaOS core engine...");
639
725
  if (pipInstall("", { editable: ARKAOS_ROOT, log, timeout: 60000 })) {
640
726
  ok("ArkaOS core engine installed");
641
727
  } else {
@@ -1056,13 +1142,13 @@ function printSchtasksAccessDeniedHelp(pythonPath, daemonPath) {
1056
1142
  // nice-to-have that keeps background tasks ticking. We print the
1057
1143
  // manual registration command so power users can elevate once and
1058
1144
  // run it, and move on.
1059
- console.log(" Access denied. Options:");
1060
- console.log(" 1) Re-run the installer from an elevated PowerShell (Run as Administrator)");
1061
- console.log(" 2) Or register the task manually from an elevated prompt:");
1062
- console.log(` schtasks /Create /F /TN "ArkaOS-Scheduler" /SC ONLOGON /TR "\"${pythonPath}\" \"${daemonPath}\""`);
1063
- console.log(" 3) Or skip the scheduler and launch the daemon manually when needed:");
1064
- console.log(` "${pythonPath}" "${daemonPath}"`);
1065
- 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.");
1066
1152
  }
1067
1153
 
1068
1154
  function installSchtasksService(daemonPath) {
@@ -1119,7 +1205,7 @@ function installSchtasksService(daemonPath) {
1119
1205
  if (kind === "access-denied") {
1120
1206
  printSchtasksAccessDeniedHelp(pythonPath, daemonPath);
1121
1207
  } else {
1122
- 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 + "\"");
1123
1209
  }
1124
1210
  return;
1125
1211
  }
@@ -1129,9 +1215,9 @@ function installSchtasksService(daemonPath) {
1129
1215
  const kind = classifySchtasksError(runResult.stderr);
1130
1216
  warn(`Scheduler: task created but /Run failed (${kind}): ${runResult.stderr.slice(0, 220)}`);
1131
1217
  if (kind === "access-denied") {
1132
- 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.");
1133
1219
  } else {
1134
- console.log(" Task will start at next logon");
1220
+ detail(" Task will start at next logon");
1135
1221
  }
1136
1222
  return;
1137
1223
  }
@@ -1146,7 +1232,7 @@ export async function loadAdapter(runtime) {
1146
1232
  } catch {
1147
1233
  return {
1148
1234
  configureHooks(config, installDir) {
1149
- console.log(` Using generic configuration for ${runtime}`);
1235
+ detail(` Using generic configuration for ${runtime}`);
1150
1236
  },
1151
1237
  };
1152
1238
  }