arkaos 4.34.0 → 4.36.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 +460 -0
  4. package/config/install-profiles.json +75 -0
  5. package/core/runtime/opencode.py +252 -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
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdir
2
2
  import { join, dirname, resolve } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { execSync } from "node:child_process";
5
- import { ensureVenv, ensureVenvHealthy, getArkaosPython, pipInstall } from "./python-resolver.js";
5
+ import { ensureVenvHealthy, getArkaosPython, pipInstall } from "./python-resolver.js";
6
6
  import { copyHookLib, copyHookAssets } from "./hook-lib.js";
7
7
  import { deploySkills } from "./skill-deploy.js";
8
8
  import { deprecationNotice, resolveSkillsMode } from "./skills-mode.js";
@@ -12,13 +12,41 @@ import { loadAdapter } from "./index.js";
12
12
  import { migrateUserData, printMigrationReport } from "./migrate-user-data.js";
13
13
  import { resolveFile } from "./path-resolver.js";
14
14
  import { IS_WINDOWS, HOOK_EXT } from "./platform.js";
15
+ import { getUi } from "./ui.js";
15
16
  import { fileURLToPath } from "node:url";
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
19
  const ARKAOS_ROOT = resolve(__dirname, "..");
19
20
  const VERSION = JSON.parse(readFileSync(join(ARKAOS_ROOT, "package.json"), "utf-8")).version;
20
21
 
22
+ // UI facade — set at the top of update(). The helpers below delegate to
23
+ // it; the plain facade prints the historical byte-identical strings, so
24
+ // the auto-update daemon's headless logs remain stable. Update normally
25
+ // runs headless (non-TTY), so fancy mode only appears on manual runs.
26
+ let ui = null;
27
+
28
+ function section(n, total, msg) {
29
+ if (ui) return ui.section(n, total, msg);
30
+ console.log(` [${n}/${total}] ${msg}`);
31
+ }
32
+
33
+ function ok(msg) {
34
+ if (ui) return ui.ok(msg);
35
+ console.log(` ✓ ${msg}`);
36
+ }
37
+
38
+ function warn(msg) {
39
+ if (ui) return ui.warn(msg);
40
+ console.log(` ⚠ ${msg}`);
41
+ }
42
+
43
+ function detail(msg) {
44
+ if (ui) return ui.detail(msg);
45
+ console.log(msg);
46
+ }
47
+
21
48
  export async function update({ skillsFlag = "" } = {}) {
49
+ ui = await getUi();
22
50
  const installDir = join(homedir(), ".arkaos");
23
51
  const manifestPath = join(installDir, "install-manifest.json");
24
52
  const profilePath = join(installDir, "profile.json");
@@ -40,7 +68,7 @@ export async function update({ skillsFlag = "" } = {}) {
40
68
  try {
41
69
  migrateLegacyHookState(homedir(), installDir);
42
70
  } catch (err) {
43
- console.log(` \u26a0 Legacy hook state migration skipped: ${err.message}`);
71
+ warn(`Legacy hook state migration skipped: ${err.message}`);
44
72
  }
45
73
 
46
74
  // User-data separation (ADR 2026-04-17): move descriptors and ecosystems
@@ -48,7 +76,7 @@ export async function update({ skillsFlag = "" } = {}) {
48
76
  try {
49
77
  printMigrationReport(migrateUserData());
50
78
  } catch (err) {
51
- console.log(` \u26a0 User-data migration skipped: ${err.message}`);
79
+ warn(`User-data migration skipped: ${err.message}`);
52
80
  }
53
81
 
54
82
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -64,13 +92,20 @@ export async function update({ skillsFlag = "" } = {}) {
64
92
  }).toString().trim();
65
93
  } catch {}
66
94
 
67
- console.log(`
95
+ if (ui.isFancy()) {
96
+ ui.intro(`▲ ARKA OS v${VERSION} — Update`);
97
+ ui.clack.log.message(
98
+ `Installed: v${manifest.version}\nPackage: v${VERSION}\nLatest: v${latestVersion}`,
99
+ );
100
+ } else {
101
+ console.log(`
68
102
  ArkaOS Update
69
103
  ─────────────
70
104
  Installed: v${manifest.version}
71
105
  Package: v${VERSION}
72
106
  Latest: v${latestVersion}
73
107
  `);
108
+ }
74
109
 
75
110
  // Dev-checkout detection: when ArkaOS is being run from a local git
76
111
  // clone (as opposed to a published `npx arkaos@latest` install), the
@@ -94,38 +129,50 @@ export async function update({ skillsFlag = "" } = {}) {
94
129
  const forceRequested = process.argv.includes("--force");
95
130
 
96
131
  if (versionsMatch && !forceRequested && !isDevCheckout) {
97
- console.log(" ✓ Already up to date.\n");
132
+ if (ui.isFancy()) {
133
+ ui.outro("Already up to date.");
134
+ } else {
135
+ console.log(" ✓ Already up to date.\n");
136
+ }
98
137
  return;
99
138
  }
100
139
 
101
140
  if (isDevCheckout && versionsMatch && !forceRequested) {
102
- console.log(" ℹ Dev checkout detected — running update even though version matches.\n");
141
+ if (ui.isFancy()) {
142
+ ui.clack.log.info("Dev checkout detected — running update even though version matches.");
143
+ } else {
144
+ console.log(" ℹ Dev checkout detected — running update even though version matches.\n");
145
+ }
103
146
  }
104
147
 
105
- console.log(" Updating (keeping your configuration)...\n");
148
+ if (ui.isFancy()) {
149
+ ui.clack.log.message("Updating (keeping your configuration)...");
150
+ } else {
151
+ console.log(" Updating (keeping your configuration)...\n");
152
+ }
106
153
 
107
154
  // ── 1. Update Python deps (using venv) ──
108
- console.log(" [1/8] Updating Python dependencies...");
155
+ section(1, 9, "Updating Python dependencies...");
109
156
 
110
157
  // Ensure venv is healthy (creates, repairs broken symlinks, or no-ops).
111
158
  // PR2 v3.73.1 — previously a stale broken-symlink venv could pass the
112
159
  // existence check, and the dashboard would silently fall back to ambient
113
160
  // python3 without sqlite-vec/fastembed.
114
- const venvHealth = ensureVenvHealthy({ log: (msg) => console.log(msg) });
161
+ const venvHealth = ensureVenvHealthy({ log: (msg) => detail(msg) });
115
162
  if (!venvHealth.healthy) {
116
- console.log(` \u26a0 Venv unhealthy (${venvHealth.reason}) - falling back to system Python with PEP 668 handling`);
163
+ warn(`Venv unhealthy (${venvHealth.reason}) - falling back to system Python with PEP 668 handling`);
117
164
  } else if (venvHealth.repaired) {
118
- console.log(` \u2713 Venv repaired (${venvHealth.reason})`);
165
+ ok(`Venv repaired (${venvHealth.reason})`);
119
166
  }
120
167
 
121
168
  const pythonCmd = getArkaosPython();
122
- const log = (msg) => console.log(msg);
169
+ const log = (msg) => detail(msg);
123
170
 
124
171
  // Core deps (always upgrade)
125
172
  if (pipInstall("pyyaml pydantic rich click jinja2", { upgrade: true, log })) {
126
- console.log(" \u2713 Core deps updated");
173
+ ok("Core deps updated");
127
174
  } else {
128
- console.log(" \u26a0 Core deps update failed");
175
+ warn("Core deps update failed");
129
176
  }
130
177
 
131
178
  // Only update optional deps if they were installed before
@@ -139,42 +186,42 @@ export async function update({ skillsFlag = "" } = {}) {
139
186
  const fastembedOk = pipInstall("fastembed", { upgrade: true, log, timeout: 180000 });
140
187
  const sqliteVssOk = pipInstall("sqlite-vec", { upgrade: true, log, timeout: 180000 });
141
188
  if (fastembedOk && sqliteVssOk) {
142
- console.log(" \u2713 Knowledge deps updated (fastembed, sqlite-vec)");
189
+ ok("Knowledge deps updated (fastembed, sqlite-vec)");
143
190
  } else if (fastembedOk) {
144
- console.log(" \u26a0 fastembed upgraded but sqlite-vec failed \u2014 semantic search degraded");
191
+ warn("fastembed upgraded but sqlite-vec failed \u2014 semantic search degraded");
145
192
  } else if (sqliteVssOk) {
146
- console.log(" \u26a0 sqlite-vec upgraded but fastembed failed \u2014 embedding pipeline degraded");
193
+ warn("sqlite-vec upgraded but fastembed failed \u2014 embedding pipeline degraded");
147
194
  } else {
148
- console.log(" \u26a0 Knowledge deps upgrade failed \u2014 run: npx arkaos doctor");
195
+ warn("Knowledge deps upgrade failed \u2014 run: npx arkaos doctor");
149
196
  }
150
197
  }
151
198
 
152
199
  if (pyCheck("fastapi")) {
153
200
  if (pipInstall("fastapi uvicorn", { upgrade: true, log, timeout: 60000 })) {
154
- console.log(" \u2713 Dashboard deps updated");
201
+ ok("Dashboard deps updated");
155
202
  }
156
203
  }
157
204
 
158
205
  // Always install ArkaOS core engine
159
206
  if (pipInstall("", { editable: ARKAOS_ROOT, log, timeout: 60000 })) {
160
- console.log(" \u2713 ArkaOS core engine installed");
207
+ ok("ArkaOS core engine installed");
161
208
  } else {
162
- console.log(" \u26a0 Core engine install failed — run: npx arkaos doctor");
209
+ warn("Core engine install failed — run: npx arkaos doctor");
163
210
  }
164
211
 
165
212
  // ── 2. Update config files ──
166
- console.log(" [2/8] Updating configuration...");
213
+ section(2, 9, "Updating configuration...");
167
214
  const constitutionSrc = join(ARKAOS_ROOT, "config", "constitution.yaml");
168
215
  mkdirSync(join(installDir, "config"), { recursive: true });
169
216
  if (existsSync(constitutionSrc)) {
170
217
  copyFileSync(constitutionSrc, join(installDir, "config", "constitution.yaml"));
171
- console.log("Constitution updated");
218
+ ok("Constitution updated");
172
219
  }
173
220
  const statuslineFile = IS_WINDOWS ? "statusline.ps1" : "statusline.sh";
174
221
  const statuslineSrc = join(ARKAOS_ROOT, "config", statuslineFile);
175
222
  if (existsSync(statuslineSrc)) {
176
223
  copyFileSync(statuslineSrc, join(installDir, "config", statuslineFile));
177
- console.log("Statusline updated");
224
+ ok("Statusline updated");
178
225
  }
179
226
  // Interaction Reform PR1 — refresh the ArkaOS output style file and
180
227
  // seed the default (if-absent; explicit operator choice preserved).
@@ -184,20 +231,20 @@ export async function update({ skillsFlag = "" } = {}) {
184
231
  sourceDir: join(ARKAOS_ROOT, "config", "output-styles"),
185
232
  });
186
233
  if (styleResult.copied > 0) {
187
- console.log("Output style updated");
234
+ ok("Output style updated");
188
235
  }
189
236
  const seedResult = seedOutputStyleDefault({
190
237
  runtime: manifest.runtime || "claude-code",
191
238
  });
192
239
  if (!seedResult.skipped && seedResult.action === "created") {
193
- console.log(`outputStyle default set to "${seedResult.value}"`);
240
+ ok(`outputStyle default set to "${seedResult.value}"`);
194
241
  }
195
242
  } catch (err) {
196
- console.log(`Output style update failed (${err.message})`);
243
+ warn(`Output style update failed (${err.message})`);
197
244
  }
198
245
 
199
246
  // ── 3. Update hooks ──
200
- console.log(" [3/8] Updating hooks...");
247
+ section(3, 9, "Updating hooks...");
201
248
  // Keep this list in lockstep with installer/index.js::installHooks and
202
249
  // installer/adapters/claude-code.js::hookCommand. Platform-aware: .ps1
203
250
  // on Windows, .sh everywhere else.
@@ -240,21 +287,21 @@ export async function update({ skillsFlag = "" } = {}) {
240
287
  try { chmodSync(destPath, 0o755); } catch {}
241
288
  }
242
289
  }
243
- console.log("Hook scripts updated");
290
+ ok("Hook scripts updated");
244
291
 
245
292
  // Shared hook libraries (config/hooks/_lib/ — the Python interpreter
246
293
  // resolver lives here). The hooks deployed above source
247
294
  // _lib/arka_python.sh from the install dir, so skipping this copy
248
295
  // leaves them falling back to a bare `python3` without ArkaOS deps.
249
296
  if (copyHookLib(srcHooksDir, destHooksDir)) {
250
- console.log("Hook lib updated (_lib/)");
297
+ ok("Hook lib updated (_lib/)");
251
298
  }
252
299
 
253
300
  // F2-6 fast-path shims + gate manifest (same shared deploy as the
254
301
  // fresh-install path — single asset list in hook-lib.js).
255
302
  const assetCount = copyHookAssets(srcHooksDir, destHooksDir);
256
303
  if (assetCount > 0) {
257
- console.log(`Hook fast-path assets updated (${assetCount})`);
304
+ ok(`Hook fast-path assets updated (${assetCount})`);
258
305
  }
259
306
 
260
307
  // Re-register hooks in the runtime's settings file.
@@ -268,14 +315,14 @@ export async function update({ skillsFlag = "" } = {}) {
268
315
  if (runtimeConfig) {
269
316
  const adapter = await loadAdapter(runtimeId);
270
317
  adapter.configureHooks(runtimeConfig, installDir);
271
- console.log("Hooks re-registered in settings");
318
+ ok("Hooks re-registered in settings");
272
319
  }
273
320
  } catch (err) {
274
- console.log(`Could not re-register hooks: ${err.message}`);
321
+ warn(`Could not re-register hooks: ${err.message}`);
275
322
  }
276
323
 
277
324
  // ── 4. Update CLI wrapper + user CLAUDE.md ──
278
- console.log(" [4/8] Updating CLI wrapper and user instructions...");
325
+ section(4, 9, "Updating CLI wrapper and user instructions...");
279
326
  const binDir = join(installDir, "bin");
280
327
  mkdirSync(binDir, { recursive: true });
281
328
 
@@ -286,27 +333,27 @@ export async function update({ skillsFlag = "" } = {}) {
286
333
  if (existsSync(psSrc)) copyFileSync(psSrc, join(binDir, "arka-claude.ps1"));
287
334
  if (existsSync(cmdSrc)) copyFileSync(cmdSrc, join(binDir, "arka-claude.cmd"));
288
335
  if (existsSync(psSrc) || existsSync(cmdSrc)) {
289
- console.log("arka-claude wrapper updated (.cmd + .ps1)");
336
+ ok("arka-claude wrapper updated (.cmd + .ps1)");
290
337
  }
291
338
  const pyPsSrc = join(ARKAOS_ROOT, "bin", "arka-py.ps1");
292
339
  const pyCmdSrc = join(ARKAOS_ROOT, "bin", "arka-py.cmd");
293
340
  if (existsSync(pyPsSrc)) copyFileSync(pyPsSrc, join(binDir, "arka-py.ps1"));
294
341
  if (existsSync(pyCmdSrc)) copyFileSync(pyCmdSrc, join(binDir, "arka-py.cmd"));
295
342
  if (existsSync(pyPsSrc) || existsSync(pyCmdSrc)) {
296
- console.log("arka-py interpreter shim updated (.cmd + .ps1)");
343
+ ok("arka-py interpreter shim updated (.cmd + .ps1)");
297
344
  }
298
345
  } else {
299
346
  const wrapperSrc = join(ARKAOS_ROOT, "bin", "arka-claude");
300
347
  if (existsSync(wrapperSrc)) {
301
348
  copyFileSync(wrapperSrc, join(binDir, "arka-claude"));
302
349
  try { chmodSync(join(binDir, "arka-claude"), 0o755); } catch {}
303
- console.log("arka-claude wrapper updated");
350
+ ok("arka-claude wrapper updated");
304
351
  }
305
352
  const arkaPySrc = join(ARKAOS_ROOT, "bin", "arka-py");
306
353
  if (existsSync(arkaPySrc)) {
307
354
  copyFileSync(arkaPySrc, join(binDir, "arka-py"));
308
355
  try { chmodSync(join(binDir, "arka-py"), 0o755); } catch {}
309
- console.log("arka-py interpreter shim updated");
356
+ ok("arka-py interpreter shim updated");
310
357
  }
311
358
  }
312
359
  const userClaudeMd = join(homedir(), ".claude", "CLAUDE.md");
@@ -314,11 +361,11 @@ export async function update({ skillsFlag = "" } = {}) {
314
361
  if (existsSync(claudeMdSrc)) {
315
362
  mkdirSync(join(homedir(), ".claude"), { recursive: true });
316
363
  copyFileSync(claudeMdSrc, userClaudeMd);
317
- console.log("~/.claude/CLAUDE.md updated");
364
+ ok("~/.claude/CLAUDE.md updated");
318
365
  }
319
366
 
320
367
  // ── 5. Update Cognitive Scheduler ──
321
- console.log(" [5/8] Updating cognitive scheduler...");
368
+ section(5, 9, "Updating cognitive scheduler...");
322
369
  updateCognitiveScheduler(installDir, ARKAOS_ROOT);
323
370
 
324
371
  // ── 6. Update /arka skill + department skills + sub-skills + agents ──
@@ -332,7 +379,7 @@ export async function update({ skillsFlag = "" } = {}) {
332
379
  // skills on his WSL (deployed long ago by install.sh) vs 1 skill on
333
380
  // his Windows install (only the main /arka). The Node installer
334
381
  // never deployed anything else.
335
- console.log(" [6/8] Updating /arka skill...");
382
+ section(6, 9, "Updating /arka skill...");
336
383
  const skillsBase = join(homedir(), ".claude", "skills");
337
384
  const skillDest = join(skillsBase, "arka");
338
385
  // Single shared deployment (installer/skill-deploy.js) — identical
@@ -342,7 +389,7 @@ export async function update({ skillsFlag = "" } = {}) {
342
389
  // before F2-7c-pre) + agent personas.
343
390
  const skillsMode = resolveSkillsMode({ flag: skillsFlag, fresh: false });
344
391
  if (skillsMode.deprecated) {
345
- console.log(" " + deprecationNotice());
392
+ detail(" " + deprecationNotice());
346
393
  }
347
394
  const skillCounts = deploySkills({
348
395
  repoRoot: ARKAOS_ROOT,
@@ -351,19 +398,19 @@ export async function update({ skillsFlag = "" } = {}) {
351
398
  version: VERSION,
352
399
  mode: skillsMode.mode,
353
400
  });
354
- console.log(`skill set mode: ${skillsMode.mode}`);
355
- if (skillCounts.main) console.log("/arka skill updated");
401
+ ok(`skill set mode: ${skillsMode.mode}`);
402
+ if (skillCounts.main) ok("/arka skill updated");
356
403
  if (skillCounts.depts > 0) {
357
- console.log(` ✓ ${skillCounts.depts} department skills updated`);
404
+ ok(`${skillCounts.depts} department skills updated`);
358
405
  }
359
406
  if (skillCounts.subs > 0) {
360
- console.log(` ✓ ${skillCounts.subs} sub-skills updated`);
407
+ ok(`${skillCounts.subs} sub-skills updated`);
361
408
  }
362
409
  if (skillCounts.meta > 0) {
363
- console.log(` ✓ ${skillCounts.meta} meta skills updated`);
410
+ ok(`${skillCounts.meta} meta skills updated`);
364
411
  }
365
412
  if (skillCounts.agents > 0) {
366
- console.log(` ✓ ${skillCounts.agents} agent personas updated`);
413
+ ok(`${skillCounts.agents} agent personas updated`);
367
414
  }
368
415
 
369
416
  // MCP infrastructure: deploy mcps/ subdirectories, registry, and
@@ -412,11 +459,11 @@ export async function update({ skillsFlag = "" } = {}) {
412
459
  }
413
460
  }
414
461
 
415
- console.log("MCP infrastructure updated (profiles, stacks, scripts, arka-prompts + arka-tools servers)");
462
+ ok("MCP infrastructure updated (profiles, stacks, scripts, arka-prompts + arka-tools servers)");
416
463
 
417
464
  // Higgsfield MCP is registered but requires an account + API key to connect.
418
465
  // Non-blocking warning: the update succeeds even without a Higgsfield account.
419
- console.log("Higgsfield MCP in registry — requires a Higgsfield account + HIGGSFIELD_API_KEY to connect (https://higgsfield.ai). Add per-project: bash apply-mcps.sh --add higgsfield");
466
+ warn("Higgsfield MCP in registry — requires a Higgsfield account + HIGGSFIELD_API_KEY to connect (https://higgsfield.ai). Add per-project: bash apply-mcps.sh --add higgsfield");
420
467
  }
421
468
 
422
469
  // ── 6b. Copy feature registry for sync engine ──
@@ -429,7 +476,7 @@ export async function update({ skillsFlag = "" } = {}) {
429
476
  copyFileSync(join(featuresSource, file), join(featuresDest, file));
430
477
  }
431
478
  if (featureFiles.length > 0) {
432
- console.log(`Feature registry: ${featureFiles.length} features copied`);
479
+ ok(`Feature registry: ${featureFiles.length} features copied`);
433
480
  }
434
481
  }
435
482
 
@@ -449,7 +496,7 @@ export async function update({ skillsFlag = "" } = {}) {
449
496
  // flow; update.js previously only wrote the first, which is a
450
497
  // latent bug that surfaces any time a user runs update from a
451
498
  // different clone than the original install.
452
- console.log(" [7/8] Updating references...");
499
+ section(7, 9, "Updating references...");
453
500
  writeFileSync(join(installDir, ".repo-path"), ARKAOS_ROOT);
454
501
  // .repo-path points at the npx cache, which `npm cache clean` can purge;
455
502
  // refresh the ~/.arkaos/lib snapshot so arka-py and the Python hooks
@@ -458,19 +505,19 @@ export async function update({ skillsFlag = "" } = {}) {
458
505
  // .repo-path (and any previous snapshot is preserved by the safe swap).
459
506
  try {
460
507
  if (deployCoreSnapshot(ARKAOS_ROOT, installDir)) {
461
- console.log("Core snapshot refreshed in ~/.arkaos/lib");
508
+ ok("Core snapshot refreshed in ~/.arkaos/lib");
462
509
  }
463
510
  } catch (err) {
464
- console.log(`Core snapshot skipped (${err.message}) — arka-py falls back to .repo-path`);
511
+ warn(`Core snapshot skipped (${err.message}) — arka-py falls back to .repo-path`);
465
512
  }
466
513
  const skillsArkaosDir = join(homedir(), ".claude", "skills", "arkaos");
467
514
  if (existsSync(skillsArkaosDir)) {
468
515
  writeFileSync(join(skillsArkaosDir, ".arkaos-root"), ARKAOS_ROOT);
469
- console.log("Repo path + skills alias updated");
516
+ ok("Repo path + skills alias updated");
470
517
  } else {
471
518
  // Fresh install never ran (or user deleted the skills alias);
472
519
  // don't recreate it here — that's the install flow's job.
473
- console.log("Repo path updated (skills alias not present)");
520
+ ok("Repo path updated (skills alias not present)");
474
521
  }
475
522
 
476
523
  // ── 7b. Frontend UI/UX tooling + Claude plugins ──
@@ -485,27 +532,27 @@ export async function update({ skillsFlag = "" } = {}) {
485
532
  const pluginResult = installDefaultClaudePlugins({ runtime: toolingRuntime });
486
533
  if (!pluginResult.skipped) {
487
534
  for (const m of pluginResult.marketplaces || []) {
488
- if (m.action === "added") console.log(`marketplace ${m.marketplace} added`);
489
- else if (m.action === "failed") console.log(`marketplace ${m.marketplace} failed (${m.reason})`);
535
+ if (m.action === "added") ok(`marketplace ${m.marketplace} added`);
536
+ else if (m.action === "failed") warn(`marketplace ${m.marketplace} failed (${m.reason})`);
490
537
  }
491
538
  for (const r of pluginResult.results) {
492
- if (r.action === "installed") console.log(` ✓ ${r.plugin} installed`);
493
- else if (r.action === "failed") console.log(` ⚠ ${r.plugin} failed (${r.reason})`);
539
+ if (r.action === "installed") ok(`${r.plugin} installed`);
540
+ else if (r.action === "failed") warn(`${r.plugin} failed (${r.reason})`);
494
541
  }
495
542
  }
496
543
  } catch (err) {
497
- console.log(`Could not update Claude plugins (${err.message})`);
544
+ warn(`Could not update Claude plugins (${err.message})`);
498
545
  }
499
546
  try {
500
547
  const { setupFrontendTooling } = await import("./frontend-tooling.js");
501
548
  const ft = await setupFrontendTooling({ runtime: toolingRuntime });
502
- if (ft.magicMcp?.action === "registered") console.log("Magic MCP registered (user scope)");
503
- else if (ft.magicMcp?.action === "already-present") console.log("Magic MCP already registered");
504
- else if (ft.magicMcp?.action === "failed") console.log(`Magic MCP failed (${ft.magicMcp.reason})`);
505
- if (ft.motionKit?.action === "installed") console.log("Motion AI Kit installed");
506
- else if (ft.motionKit?.action === "failed") console.log(`Motion AI Kit failed (${ft.motionKit.reason})`);
549
+ if (ft.magicMcp?.action === "registered") ok("Magic MCP registered (user scope)");
550
+ else if (ft.magicMcp?.action === "already-present") ok("Magic MCP already registered");
551
+ else if (ft.magicMcp?.action === "failed") warn(`Magic MCP failed (${ft.magicMcp.reason})`);
552
+ if (ft.motionKit?.action === "installed") ok("Motion AI Kit installed");
553
+ else if (ft.motionKit?.action === "failed") warn(`Motion AI Kit failed (${ft.motionKit.reason})`);
507
554
  } catch (err) {
508
- console.log(`Could not set up frontend tooling (${err.message})`);
555
+ warn(`Could not set up frontend tooling (${err.message})`);
509
556
  }
510
557
 
511
558
  // Graphify grounding layer — same wiring as installer/index.js. Best
@@ -514,45 +561,69 @@ export async function update({ skillsFlag = "" } = {}) {
514
561
  const { ensureGraphify, configureGraphifyHttp } = await import("./graphify.js");
515
562
  const gf = ensureGraphify();
516
563
  if (gf.binary?.installed) {
517
- console.log(`Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}`);
564
+ ok(`Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}`);
518
565
  if (gf.skillInstall?.action === "installed") {
519
- console.log("Graphify skill registered (graphify install)");
566
+ ok("Graphify skill registered (graphify install)");
520
567
  } else if (gf.skillInstall?.action === "failed") {
521
- console.log(`Graphify skill registration failed (${gf.skillInstall.reason})`);
568
+ warn(`Graphify skill registration failed (${gf.skillInstall.reason})`);
522
569
  }
523
570
  } else if (gf.binary?.hint) {
524
- console.log(` ⚠ ${gf.binary.hint}`);
571
+ warn(`${gf.binary.hint}`);
525
572
  }
526
573
  // Graphify HTTP knowledge-graph MCP (user-scope, config-driven endpoint).
527
574
  const gh = await configureGraphifyHttp({ runtime: toolingRuntime });
528
575
  if (gh.action === "registered" || gh.action === "re-registered") {
529
- console.log(`Graphify knowledge-graph MCP ${gh.action} (user scope)`);
576
+ ok(`Graphify knowledge-graph MCP ${gh.action} (user scope)`);
530
577
  } else if (gh.action === "failed") {
531
- console.log(`Graphify knowledge-graph MCP not registered (${gh.reason})`);
578
+ warn(`Graphify knowledge-graph MCP not registered (${gh.reason})`);
532
579
  }
533
580
  } catch (err) {
534
- console.log(`Could not set up Graphify (${err.message})`);
581
+ warn(`Could not set up Graphify (${err.message})`);
535
582
  }
536
583
 
537
584
  // ── 7b. Auto-update daemon (Foundation PR-1) — default-on, opt-out kept ──
538
585
  try {
539
586
  const { ensureDefaultEnabled } = await import("./autoupdate.js");
540
587
  const au = ensureDefaultEnabled({ repoRoot: ARKAOS_ROOT });
541
- if (au.action === "enabled") console.log("Auto-update daemon enabled (daily npm check + notification)");
542
- else if (au.action === "already-enabled") console.log("Auto-update daemon active");
543
- else if (au.action === "optout") console.log(" · Auto-update daemon: user opt-out respected");
588
+ if (au.action === "enabled") ok("Auto-update daemon enabled (daily npm check + notification)");
589
+ else if (au.action === "already-enabled") ok("Auto-update daemon active");
590
+ else if (au.action === "optout") detail(" · Auto-update daemon: user opt-out respected");
544
591
  } catch (err) {
545
- console.log(`Auto-update daemon not enabled (${err.message})`);
592
+ warn(`Auto-update daemon not enabled (${err.message})`);
593
+ }
594
+
595
+ // ── 7c. Menu bar launcher (Foundation PR-5) — macOS, default-on ──
596
+ // Opt-out consulted BEFORE pip (QG M3): a disabled menu bar must not
597
+ // cost its dependency on every daemon update. CI runners never get
598
+ // LaunchAgents (QG M4).
599
+ if (process.platform === "darwin" && !process.env.CI) {
600
+ try {
601
+ const { ensureDefaultEnabled: ensureMenubar, status: menubarStatus } =
602
+ await import("./menubar.js");
603
+ if (menubarStatus().optout) {
604
+ detail(" · Menu bar launcher: user opt-out respected");
605
+ } else {
606
+ if (!pipInstall("rumps", { log, timeout: 120000 })) {
607
+ warn("rumps install failed — menu bar app will hint on first run");
608
+ }
609
+ const mb = ensureMenubar({ repoRoot: ARKAOS_ROOT });
610
+ if (mb.action === "enabled") ok("Menu bar launcher enabled (▲ in the macOS menu bar)");
611
+ else if (mb.action === "already-enabled") ok("Menu bar launcher active");
612
+ else if (mb.action === "partial") warn(`Menu bar launcher: ${mb.message}`);
613
+ }
614
+ } catch (err) {
615
+ warn(`Menu bar launcher not enabled (${err.message})`);
616
+ }
546
617
  }
547
618
 
548
619
  // ── 8. Update manifest ──
549
- console.log(" [8/8] Finalizing...");
620
+ section(8, 9, "Finalizing...");
550
621
  manifest.version = VERSION;
551
622
  manifest.repoRoot = ARKAOS_ROOT;
552
623
  manifest.pythonCmd = pythonCmd;
553
624
  manifest.updatedAt = new Date().toISOString();
554
625
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
555
- console.log("Manifest updated");
626
+ ok("Manifest updated");
556
627
 
557
628
  // Reset sync state to trigger /arka update on next session
558
629
  const syncStatePath = join(installDir, "sync-state.json");
@@ -566,9 +637,54 @@ export async function update({ skillsFlag = "" } = {}) {
566
637
  core_updated_at: new Date().toISOString()
567
638
  };
568
639
  writeFileSync(syncStatePath, JSON.stringify(syncState, null, 2));
569
- console.log("Sync state reset (auto-detected on next Claude session)");
570
-
571
- console.log(`
640
+ ok("Sync state reset (auto-detected on next Claude session)");
641
+
642
+ // ── 9. Reconcile profile services (Foundation PR-4) ──
643
+ // Headless by design (the auto-update daemon runs this): only
644
+ // consent-free services install; consent-gated ones (ffmpeg, ollama)
645
+ // report a copy-paste hint. A service failure warns and continues —
646
+ // reconciliation can NEVER block the update.
647
+ section(9, 9, "Reconciling profile services...");
648
+ try {
649
+ const { normalizeProfileFlag } = await import("./profile.js");
650
+ const { reconcileServices } = await import("./services.js");
651
+ const installProfile = normalizeProfileFlag(profile.installProfile) || "essential";
652
+ const results = await reconcileServices({
653
+ profile: installProfile,
654
+ repoRoot: ARKAOS_ROOT,
655
+ interactive: false,
656
+ log: (msg) => detail(msg),
657
+ });
658
+ ok(`profile: ${installProfile}`);
659
+ for (const r of results) {
660
+ if (r.status === "present") ok(`${r.label} present`);
661
+ else if (r.status === "installed") ok(`${r.label} installed`);
662
+ else if (r.status === "skipped") detail(` · ${r.label} skipped${r.hint ? ` — ${r.hint}` : ""}`);
663
+ else warn(`${r.label} failed${r.hint ? ` — ${r.hint}` : ""}`);
664
+ }
665
+ } catch (err) {
666
+ warn(`Profile service reconciliation skipped (${err.message})`);
667
+ }
668
+
669
+ if (ui.isFancy()) {
670
+ ui.stopSpinner();
671
+ const warnCount = ui.warnings().length;
672
+ ui.note(
673
+ [
674
+ `Language: ${profile.language || "not set"}`,
675
+ `Market: ${profile.market || "not set"}`,
676
+ `Projects: ${profile.projectsDir || "not set"}`,
677
+ `Vault: ${profile.vaultPath || "not set"}`,
678
+ `Profile: ${profile.installProfile || "essential"}`,
679
+ warnCount > 0 ? `Warnings: ${warnCount} (see above)` : null,
680
+ ]
681
+ .filter((l) => l !== null)
682
+ .join("\n"),
683
+ `ArkaOS updated to v${VERSION} — configuration preserved`,
684
+ );
685
+ ui.outro("Next Claude Code session auto-detects the update and syncs your projects.");
686
+ } else {
687
+ console.log(`
572
688
  ╔══════════════════════════════════════════╗
573
689
  ║ ArkaOS updated to v${VERSION} ║
574
690
  ╚══════════════════════════════════════════╝
@@ -582,6 +698,7 @@ export async function update({ skillsFlag = "" } = {}) {
582
698
  Next time you open Claude Code, ArkaOS will automatically
583
699
  detect the update and sync all your projects.
584
700
  `);
701
+ }
585
702
  }
586
703
 
587
704
  function ensureDir(dir) {
@@ -670,8 +787,8 @@ function migrateLegacyHookState(homeDir, installDir) {
670
787
  }
671
788
 
672
789
  if (migrated > 0) {
673
- console.log(` \u2713 Migrated ${migrated} legacy hook state file(s) from ~/.arka-os/`);
674
- console.log(` (original files left in place for v1 coexistence)`);
790
+ ok(`Migrated ${migrated} legacy hook state file(s) from ~/.arka-os/`);
791
+ detail(` (original files left in place for v1 coexistence)`);
675
792
  }
676
793
  }
677
794
 
@@ -682,7 +799,7 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
682
799
  const schedSrc = join(arkaosRoot, "config", "cognition", "schedules.yaml");
683
800
  if (existsSync(schedSrc)) {
684
801
  copyFileSync(schedSrc, join(installDir, "schedules.yaml"));
685
- console.log(" \u2713 Schedule config updated");
802
+ ok("Schedule config updated");
686
803
  }
687
804
 
688
805
  // 2. Update prompt files
@@ -699,7 +816,7 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
699
816
  copyFileSync(src, dst);
700
817
  }
701
818
  }
702
- console.log(" \u2713 Cognitive prompts updated");
819
+ ok("Cognitive prompts updated");
703
820
  }
704
821
 
705
822
  // 3. Update daemon script and core modules
@@ -709,7 +826,7 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
709
826
  if (existsSync(daemonSrc)) {
710
827
  copyFileSync(daemonSrc, join(binDir, "scheduler-daemon.py"));
711
828
  try { chmodSync(join(binDir, "scheduler-daemon.py"), 0o755); } catch {}
712
- console.log(" \u2713 Scheduler daemon updated");
829
+ ok("Scheduler daemon updated");
713
830
  }
714
831
 
715
832
  // 3b. Update scheduler core modules (daemon imports these at runtime)
@@ -734,7 +851,7 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
734
851
  ensureDir(dirname(dest));
735
852
  writeFileSync(dest, '"""ArkaOS — deployed subset for scheduler."""\n');
736
853
  }
737
- console.log(" \u2713 Scheduler core modules updated");
854
+ ok("Scheduler core modules updated");
738
855
 
739
856
  // 4. Ensure log directories
740
857
  ensureDir(join(installDir, "logs", "dreaming"));
@@ -749,9 +866,9 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
749
866
  try {
750
867
  execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "pipe" });
751
868
  execSync(`launchctl load "${plistPath}"`, { stdio: "pipe" });
752
- console.log(" \u2713 Scheduler service restarted (launchd)");
869
+ ok("Scheduler service restarted (launchd)");
753
870
  } catch {
754
- console.log(" \u26a0 Scheduler reload failed — restart manually");
871
+ warn("Scheduler reload failed — restart manually");
755
872
  }
756
873
  } else {
757
874
  // First time — install the service
@@ -800,17 +917,17 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
800
917
  writeFileSync(plistPath, plist);
801
918
  try {
802
919
  execSync(`launchctl load "${plistPath}"`, { stdio: "pipe" });
803
- console.log(" \u2713 Scheduler service installed and started (launchd)");
920
+ ok("Scheduler service installed and started (launchd)");
804
921
  } catch {
805
- console.log(" \u26a0 Scheduler plist written but load failed");
922
+ warn("Scheduler plist written but load failed");
806
923
  }
807
924
  }
808
925
  } else if (platform === "linux") {
809
926
  try {
810
927
  execSync("systemctl --user restart arkaos-scheduler.service 2>/dev/null", { stdio: "pipe" });
811
- console.log(" \u2713 Scheduler service restarted (systemd)");
928
+ ok("Scheduler service restarted (systemd)");
812
929
  } catch {
813
- console.log(" \u26a0 Scheduler not running — install with: npx arkaos install");
930
+ warn("Scheduler not running — install with: npx arkaos install");
814
931
  }
815
932
  }
816
933
  }