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
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /lead department (Rodrigo)"
3
+ ---
4
+
5
+ [arka:routing] lead -> Rodrigo
6
+
7
+ Handle the following as the ArkaOS /lead department, orchestrated by Rodrigo. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /mkt department (Luna)"
3
+ ---
4
+
5
+ [arka:routing] mkt -> Luna
6
+
7
+ Handle the following as the ArkaOS /mkt department, orchestrated by Luna. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /ops department (Daniel)"
3
+ ---
4
+
5
+ [arka:routing] ops -> Daniel
6
+
7
+ Handle the following as the ArkaOS /ops department, orchestrated by Daniel. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /org department (Sofia)"
3
+ ---
4
+
5
+ [arka:routing] org -> Sofia
6
+
7
+ Handle the following as the ArkaOS /org department, orchestrated by Sofia. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /pm department (Carolina)"
3
+ ---
4
+
5
+ [arka:routing] pm -> Carolina
6
+
7
+ Handle the following as the ArkaOS /pm department, orchestrated by Carolina. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /saas department (Tiago)"
3
+ ---
4
+
5
+ [arka:routing] saas -> Tiago
6
+
7
+ Handle the following as the ArkaOS /saas department, orchestrated by Tiago. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /sales department (Miguel)"
3
+ ---
4
+
5
+ [arka:routing] sales -> Miguel
6
+
7
+ Handle the following as the ArkaOS /sales department, orchestrated by Miguel. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "ArkaOS /strat department (Tomas)"
3
+ ---
4
+
5
+ [arka:routing] strat -> Tomas
6
+
7
+ Handle the following as the ArkaOS /strat department, orchestrated by Tomas. Follow the AGENTS.md contract (plan approval, evidence over narration, quality gate; generated by scripts/harness_gen.py):
8
+
9
+ $ARGUMENTS
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "mcp": {
4
+ "arka-tools": {
5
+ "type": "local",
6
+ "command": [
7
+ "npx",
8
+ "-y",
9
+ "arkaos",
10
+ "mcp",
11
+ "start"
12
+ ],
13
+ "enabled": true
14
+ }
15
+ }
16
+ }
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.33.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.35.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -0,0 +1,118 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { listBundleFiles, readBundleFile } from "../harness-bundle.js";
4
+
5
+ /**
6
+ * OpenCode adapter (Foundation PR-6). Deploys the generated harness
7
+ * bundle onto OpenCode's native surfaces (opencode.ai/docs):
8
+ *
9
+ * AGENTS.md instructions contract (same body as codex)
10
+ * agents/*.md curated agent cut (C-suite + squad leads)
11
+ * commands/*.md one /arka-<dept> router command per department
12
+ * opencode.json arka-tools MCP — merged NON-destructively
13
+ *
14
+ * Merge contract: user keys ALWAYS win. An existing `$schema`, any
15
+ * user-defined `mcp` server, or a user-modified `arka-tools` entry is
16
+ * never overwritten; a corrupt opencode.json is never clobbered (we
17
+ * skip with a warning instead — config-seed.js posture).
18
+ */
19
+
20
+ /**
21
+ * Pure, exported for tests. Returns:
22
+ * { config, changed } merged object + whether anything was added
23
+ * null leave the file alone (corrupt input)
24
+ */
25
+ export function mergeOpencodeConfig(existingText, fragmentText) {
26
+ let fragment;
27
+ try {
28
+ fragment = JSON.parse(fragmentText);
29
+ } catch {
30
+ return null;
31
+ }
32
+ let existing = {};
33
+ const trimmed = (existingText ?? "").trim();
34
+ if (trimmed !== "") {
35
+ try {
36
+ existing = JSON.parse(trimmed);
37
+ } catch {
38
+ return null; // corrupt user config — never clobber
39
+ }
40
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
41
+ return null;
42
+ }
43
+ }
44
+ const merged = { ...existing };
45
+ let changed = false;
46
+ if (!merged.$schema && fragment.$schema) {
47
+ merged.$schema = fragment.$schema;
48
+ changed = true;
49
+ }
50
+ const userMcp =
51
+ merged.mcp && typeof merged.mcp === "object" && !Array.isArray(merged.mcp)
52
+ ? merged.mcp
53
+ : {};
54
+ const mcp = { ...userMcp };
55
+ for (const [name, server] of Object.entries(fragment.mcp || {})) {
56
+ if (!(name in mcp)) {
57
+ mcp[name] = server;
58
+ changed = true;
59
+ }
60
+ }
61
+ if (changed && Object.keys(mcp).length > 0) merged.mcp = mcp;
62
+ return { config: merged, changed };
63
+ }
64
+
65
+ export default {
66
+ configureHooks(config) {
67
+ // OpenCode has no shell-hook chain; "configure" means deploying the
68
+ // instruction/agent/command surfaces + the MCP config merge.
69
+ const agentsMd = readBundleFile("opencode", "AGENTS.md");
70
+ if (!agentsMd) {
71
+ console.warn(
72
+ " OpenCode bundle missing (harness/opencode/) — " +
73
+ "run scripts/harness_gen.py; skipping."
74
+ );
75
+ return;
76
+ }
77
+ mkdirSync(config.configDir, { recursive: true });
78
+ writeFileSync(join(config.configDir, "AGENTS.md"), agentsMd);
79
+
80
+ let deployed = 0;
81
+ for (const sub of ["agents", "commands"]) {
82
+ const names = listBundleFiles("opencode", sub);
83
+ if (names.length === 0) continue;
84
+ mkdirSync(join(config.configDir, sub), { recursive: true });
85
+ for (const name of names) {
86
+ const content = readBundleFile("opencode", `${sub}/${name}`);
87
+ if (content === null) continue;
88
+ writeFileSync(join(config.configDir, sub, name), content);
89
+ deployed += 1;
90
+ }
91
+ }
92
+
93
+ const fragment = readBundleFile("opencode", "opencode.json");
94
+ if (fragment) {
95
+ const existingText = existsSync(config.settingsFile)
96
+ ? readFileSync(config.settingsFile, "utf-8")
97
+ : "";
98
+ const result = mergeOpencodeConfig(existingText, fragment);
99
+ if (result === null) {
100
+ console.warn(
101
+ " opencode.json unreadable — left untouched " +
102
+ "(the merge is never destructive)."
103
+ );
104
+ } else if (result.changed) {
105
+ writeFileSync(
106
+ config.settingsFile,
107
+ JSON.stringify(result.config, null, 2) + "\n"
108
+ );
109
+ console.log(
110
+ " opencode.json updated (arka-tools MCP registered, user keys preserved)."
111
+ );
112
+ }
113
+ }
114
+ console.log(
115
+ ` OpenCode configured (AGENTS.md + ${deployed} agent/command files).`
116
+ );
117
+ },
118
+ };
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Background auto-update daemon for ArkaOS (Foundation PR-1).
3
+ *
4
+ * Users were found running v2 while v4.33 was live — the update path
5
+ * (`npx arkaos@latest update`) only fires when the user remembers to run
6
+ * it. This installs a per-OS scheduled unit that runs
7
+ * `scripts/auto-update.sh` daily (and at load): the script checks the npm
8
+ * registry, applies the core update headlessly, and notifies the user.
9
+ * Project sync stays supervised — the next Claude session picks it up via
10
+ * [arka:update-available].
11
+ *
12
+ * Opt-in by default on install/update (`ensureDefaultEnabled`), with a
13
+ * persisted opt-out marker so `npx arkaos autoupdate disable` survives
14
+ * future updates. ESM, Node + Bun, no interactive prompts. Unit
15
+ * generation (`unitFor`) is pure so it can be unit-tested without
16
+ * touching the OS (autostart.js precedent).
17
+ */
18
+
19
+ import { execSync } from "node:child_process";
20
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
21
+ import { homedir } from "node:os";
22
+ import { dirname, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ export const AUTOUPDATE_LABEL = "io.wizardingcode.arkaos.updater";
26
+ const SERVICE_NAME = "arkaos-updater.service";
27
+ const TIMER_NAME = "arkaos-updater.timer";
28
+
29
+ function defaultRepoRoot() {
30
+ return join(dirname(fileURLToPath(import.meta.url)), "..");
31
+ }
32
+
33
+ /** Pure: the opt-out marker path — written by disable(), honored by
34
+ * ensureDefaultEnabled() and by scripts/auto-update.sh itself. */
35
+ export function optoutPath(home = homedir()) {
36
+ return join(home, ".arkaos", "autoupdate.optout");
37
+ }
38
+
39
+ /** Anchor the daemon at the stable ~/.arkaos/lib snapshot, never at the
40
+ * package root when it can be an npx cache: `npm cache clean` purges
41
+ * that location at any time, and a default-on daemon anchored there
42
+ * dies silently — with no surviving daemon to self-heal (QG blocker,
43
+ * Foundation PR-1). core-snapshot.js ships scripts/ into the snapshot;
44
+ * repoRoot is the fallback for dev checkouts before the first deploy. */
45
+ export function stableRoot({ home = homedir(), repoRoot = defaultRepoRoot() } = {}) {
46
+ const lib = join(home, ".arkaos", "lib");
47
+ return existsSync(join(lib, "scripts", "auto-update.sh")) ? lib : repoRoot;
48
+ }
49
+
50
+ /** Pure: the scheduled unit(s) for a given platform. Throws on
51
+ * unsupported OS. Returns { kind, files: [{ path, content }] } —
52
+ * systemd needs a service + timer pair, launchd a single plist. */
53
+ export function unitFor(os, { repoRoot, home }) {
54
+ const script = `${repoRoot}/scripts/auto-update.sh`;
55
+ const log = `${home}/.arkaos/logs/auto-update-daemon.log`;
56
+
57
+ if (os === "darwin") {
58
+ // launchd runs with a minimal PATH (/usr/bin:/bin:...) — node/npx
59
+ // live in /opt/homebrew/bin or /usr/local/bin, so the plist must
60
+ // carry a usable PATH or the daemon dies on `command -v npx`.
61
+ // StartCalendarInterval fires daily; RunAtLoad covers machines that
62
+ // were asleep/off at that hour (login catches up).
63
+ const content = `<?xml version="1.0" encoding="UTF-8"?>
64
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65
+ <plist version="1.0">
66
+ <dict>
67
+ <key>Label</key>
68
+ <string>${AUTOUPDATE_LABEL}</string>
69
+ <key>ProgramArguments</key>
70
+ <array>
71
+ <string>/bin/bash</string>
72
+ <string>${script}</string>
73
+ </array>
74
+ <key>EnvironmentVariables</key>
75
+ <dict>
76
+ <key>PATH</key>
77
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
78
+ </dict>
79
+ <key>StartCalendarInterval</key>
80
+ <dict>
81
+ <key>Hour</key>
82
+ <integer>10</integer>
83
+ <key>Minute</key>
84
+ <integer>30</integer>
85
+ </dict>
86
+ <key>RunAtLoad</key>
87
+ <true/>
88
+ <key>StandardOutPath</key>
89
+ <string>${log}</string>
90
+ <key>StandardErrorPath</key>
91
+ <string>${log}</string>
92
+ </dict>
93
+ </plist>
94
+ `;
95
+ return {
96
+ kind: "launchd",
97
+ files: [
98
+ {
99
+ path: join(home, "Library", "LaunchAgents", `${AUTOUPDATE_LABEL}.plist`),
100
+ content,
101
+ },
102
+ ],
103
+ };
104
+ }
105
+
106
+ if (os === "linux") {
107
+ const service = `[Unit]
108
+ Description=ArkaOS auto-update (npm registry watch + headless core update)
109
+ After=network-online.target
110
+
111
+ [Service]
112
+ Type=oneshot
113
+ ExecStart=/bin/bash ${script}
114
+ StandardOutput=append:${log}
115
+ StandardError=append:${log}
116
+ `;
117
+ // Persistent=true replays a missed daily run at next boot — the
118
+ // laptop-that-was-off case that left users on v2.
119
+ const timer = `[Unit]
120
+ Description=Daily ArkaOS auto-update check
121
+
122
+ [Timer]
123
+ OnCalendar=daily
124
+ Persistent=true
125
+
126
+ [Install]
127
+ WantedBy=timers.target
128
+ `;
129
+ const unitDir = join(home, ".config", "systemd", "user");
130
+ return {
131
+ kind: "systemd",
132
+ files: [
133
+ { path: join(unitDir, SERVICE_NAME), content: service },
134
+ { path: join(unitDir, TIMER_NAME), content: timer },
135
+ ],
136
+ };
137
+ }
138
+
139
+ throw new Error(`unsupported platform for autoupdate: ${os}`);
140
+ }
141
+
142
+ function _silent(cmd) {
143
+ try {
144
+ execSync(cmd, { stdio: "pipe" });
145
+ return true;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ export function enable({ repoRoot = defaultRepoRoot() } = {}) {
152
+ const root = stableRoot({ repoRoot });
153
+ const unit = unitFor(process.platform, { repoRoot: root, home: homedir() });
154
+ mkdirSync(join(homedir(), ".arkaos", "logs"), { recursive: true });
155
+ for (const f of unit.files) {
156
+ mkdirSync(dirname(f.path), { recursive: true });
157
+ writeFileSync(f.path, f.content, "utf8");
158
+ }
159
+ if (existsSync(optoutPath())) rmSync(optoutPath());
160
+ const mainPath = unit.files[0].path;
161
+ if (unit.kind === "launchd") {
162
+ _silent(`launchctl unload "${mainPath}"`);
163
+ if (!_silent(`launchctl load -w "${mainPath}"`)) {
164
+ return { ok: false, path: mainPath,
165
+ message: "Plist written but launchctl load failed — it will still run at next login." };
166
+ }
167
+ } else if (unit.kind === "systemd") {
168
+ _silent("systemctl --user daemon-reload");
169
+ if (!_silent(`systemctl --user enable --now ${TIMER_NAME}`)) {
170
+ return { ok: false, path: mainPath,
171
+ message: "Timer written but systemctl enable failed — check 'systemctl --user' availability." };
172
+ }
173
+ }
174
+ return { ok: true, path: mainPath,
175
+ message: "Auto-update enabled. ArkaOS checks npm daily and updates itself." };
176
+ }
177
+
178
+ export function disable() {
179
+ const unit = unitFor(process.platform, { repoRoot: defaultRepoRoot(), home: homedir() });
180
+ const mainPath = unit.files[0].path;
181
+ if (unit.kind === "launchd") _silent(`launchctl unload "${mainPath}"`);
182
+ else if (unit.kind === "systemd") _silent(`systemctl --user disable --now ${TIMER_NAME}`);
183
+ for (const f of unit.files) if (existsSync(f.path)) rmSync(f.path);
184
+ // Persisted opt-out: future `npx arkaos update` runs must not re-enable.
185
+ mkdirSync(join(homedir(), ".arkaos"), { recursive: true });
186
+ writeFileSync(optoutPath(), new Date().toISOString(), "utf8");
187
+ return { ok: true, path: mainPath, message: "Auto-update disabled (opt-out persisted)." };
188
+ }
189
+
190
+ export function status() {
191
+ let unit;
192
+ try {
193
+ unit = unitFor(process.platform, { repoRoot: defaultRepoRoot(), home: homedir() });
194
+ } catch (err) {
195
+ return { installed: false, supported: false, optout: false, message: err.message };
196
+ }
197
+ return {
198
+ installed: unit.files.every((f) => existsSync(f.path)),
199
+ supported: true,
200
+ optout: existsSync(optoutPath()),
201
+ kind: unit.kind,
202
+ path: unit.files[0].path,
203
+ };
204
+ }
205
+
206
+ /** Default-on wiring for install/update flows: enable unless the user
207
+ * opted out or the platform is unsupported. Never throws. */
208
+ export function ensureDefaultEnabled({ repoRoot = defaultRepoRoot() } = {}) {
209
+ const s = status();
210
+ if (!s.supported) return { action: "unsupported" };
211
+ if (s.optout) return { action: "optout" };
212
+ if (s.installed) {
213
+ // Refresh unit content in place (paths/schedule may change between
214
+ // versions) but do not force a reload storm on every update.
215
+ try {
216
+ const root = stableRoot({ repoRoot });
217
+ const unit = unitFor(process.platform, { repoRoot: root, home: homedir() });
218
+ for (const f of unit.files) writeFileSync(f.path, f.content, "utf8");
219
+ } catch {}
220
+ return { action: "already-enabled" };
221
+ }
222
+ const r = enable({ repoRoot });
223
+ return { action: r.ok ? "enabled" : "partial", message: r.message };
224
+ }
225
+
226
+ export async function autoupdate(args = []) {
227
+ const action = (args[0] || "status").toLowerCase();
228
+ if (action === "enable") {
229
+ const r = enable();
230
+ console.log(` ${r.ok ? "✓" : "⚠"} ${r.message}\n ${r.path}`);
231
+ } else if (action === "disable") {
232
+ const r = disable();
233
+ console.log(` ✓ ${r.message}`);
234
+ } else if (action === "run") {
235
+ // Foreground one-shot check — same script (and anchor) the daemon runs.
236
+ const script = join(stableRoot(), "scripts", "auto-update.sh");
237
+ try {
238
+ execSync(`/bin/bash "${script}" --force`, { stdio: "inherit" });
239
+ } catch { process.exitCode = 1; }
240
+ } else if (action === "status") {
241
+ const s = status();
242
+ if (!s.supported) console.log(` Auto-update not supported here: ${s.message}`);
243
+ else if (s.optout) console.log(` Auto-update: DISABLED by user opt-out\n (re-enable: npx arkaos autoupdate enable)`);
244
+ else console.log(` Auto-update: ${s.installed ? "ENABLED" : "disabled"} (${s.kind})\n ${s.path}`);
245
+ } else {
246
+ console.error(` Unknown autoupdate action: ${action}. Use enable | disable | status | run.`);
247
+ process.exitCode = 1;
248
+ }
249
+ }
package/installer/cli.js CHANGED
@@ -21,6 +21,10 @@ const { values, positionals } = parseArgs({
21
21
  force: { type: "boolean", short: "f" },
22
22
  "no-system": { type: "boolean" },
23
23
  "with-ollama": { type: "boolean" },
24
+ // Foundation PR-3 — `npx arkaos install --profile <essential|complete|local-ai>`.
25
+ // Declared so it lands in `values.profile` instead of a free
26
+ // positional under strict:false (the documented --fix lesson).
27
+ profile: { type: "string" },
24
28
  // PR3.5 v3.74.1 — declared so `npx arkaos doctor --fix` lands in
25
29
  // `values.fix` rather than as a free positional under strict:false.
26
30
  // Eliminates the dead-branch fallback flagged by Marta in PR2's QG.
@@ -57,12 +61,15 @@ ArkaOS v${VERSION} — The Operating System for AI Agent Teams
57
61
  Usage:
58
62
  npx arkaos install Install ArkaOS in current environment
59
63
  npx arkaos install --runtime <runtime> Install for specific runtime
64
+ npx arkaos install --profile <profile> Pick the install profile non-interactively
60
65
  npx arkaos init Initialize project config (.arkaos.json)
61
66
  npx arkaos update Update to latest version
62
67
  npx arkaos migrate Migrate from v1 to v2
63
68
  npx arkaos migrate-user-data Move user data (~/.claude/skills/arka/ → ~/.arkaos/)
64
69
  npx arkaos dashboard Start monitoring dashboard
65
70
  npx arkaos autostart <enable|disable|status> Start dashboard on boot
71
+ npx arkaos autoupdate <enable|disable|status|run> Keep ArkaOS updated automatically (daily check + notification)
72
+ npx arkaos menubar <enable|disable|status> Menu bar launcher (macOS — ▲ quick actions)
66
73
  npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
67
74
  npx arkaos models Model Fabric: which model runs each role
68
75
  npx arkaos models set <role> <provider>/<model> Re-route a role
@@ -76,6 +83,9 @@ Options:
76
83
  -r, --runtime <name> Target runtime: claude-code, codex, gemini, cursor
77
84
  -p, --path <dir> Installation directory (default: auto-detect)
78
85
  -f, --force Force reinstall, overwriting existing files
86
+ --profile <name> Install profile: essential (venv, hooks, skills,
87
+ dashboard, MCPs), complete (+ litellm[proxy], ffmpeg),
88
+ local-ai (+ Ollama, local execution model)
79
89
  -v, --version Show version
80
90
  -h, --help Show this help
81
91
 
@@ -99,7 +109,20 @@ Examples:
99
109
 
100
110
  async function main() {
101
111
  switch (command) {
102
- case "install":
112
+ case "install": {
113
+ // Validate --profile up front (before runtime detection) so a
114
+ // typo fails fast instead of silently falling back to the wizard.
115
+ let profileFlag;
116
+ if (values.profile !== undefined) {
117
+ const { normalizeProfileFlag, INSTALL_PROFILES } = await import("./profile.js");
118
+ profileFlag = normalizeProfileFlag(values.profile);
119
+ if (!profileFlag) {
120
+ console.error(
121
+ `Invalid --profile "${values.profile}". Valid values: ${INSTALL_PROFILES.join(", ")}`
122
+ );
123
+ process.exit(1);
124
+ }
125
+ }
103
126
  const runtime = values.runtime || await detectRuntime();
104
127
  await install({
105
128
  runtime,
@@ -107,8 +130,10 @@ async function main() {
107
130
  force: values.force,
108
131
  skipSystem: values["no-system"],
109
132
  withOllama: values["with-ollama"],
133
+ profileFlag,
110
134
  });
111
135
  break;
136
+ }
112
137
 
113
138
  case "init": {
114
139
  const { init } = await import("./init.js");
@@ -134,6 +159,18 @@ async function main() {
134
159
  break;
135
160
  }
136
161
 
162
+ case "autoupdate": {
163
+ const { autoupdate } = await import("./autoupdate.js");
164
+ await autoupdate(positionals.slice(1));
165
+ break;
166
+ }
167
+
168
+ case "menubar": {
169
+ const { menubar } = await import("./menubar.js");
170
+ await menubar(positionals.slice(1));
171
+ break;
172
+ }
173
+
137
174
  case "uninstall":
138
175
  const { uninstall } = await import("./uninstall.js");
139
176
  await uninstall();
@@ -35,7 +35,11 @@ import { join, basename } from "node:path";
35
35
  // resolve_arkaos_root() checks, so a crash mid-deploy on a fresh install
36
36
  // leaves a snapshot that fails validation instead of a core-only root
37
37
  // that validates but lacks config/departments/knowledge.
38
- const SNAPSHOT_DIRS = ["config", "departments", "knowledge", "core"];
38
+ // scripts/ joined in Foundation PR-1: the auto-update daemon's launchd/
39
+ // systemd unit anchors at the snapshot (a unit pointing into the npx
40
+ // cache dies silently on `npm cache clean` — QG blocker). Under 0.5MB
41
+ // after the __pycache__ filter.
42
+ const SNAPSHOT_DIRS = ["config", "departments", "knowledge", "scripts", "core"];
39
43
 
40
44
  export function deployCoreSnapshot(arkaosRoot, installDir) {
41
45
  if (!existsSync(join(arkaosRoot, "core", "sync", "__init__.py"))) return false;
@@ -69,6 +69,24 @@ const RUNTIMES = [
69
69
  skillsDir: () => join(homedir(), ".cursor", "skills"),
70
70
  settingsFile: () => join(homedir(), ".cursor", "settings.json"),
71
71
  },
72
+ {
73
+ // Foundation PR-6 — OpenCode first-class. Listed AFTER the existing
74
+ // runtimes so multi-runtime machines keep their current default
75
+ // (detection picks the first match).
76
+ id: "opencode",
77
+ name: "OpenCode",
78
+ detect: () => {
79
+ try {
80
+ execSync("opencode --version", { stdio: "pipe" });
81
+ return true;
82
+ } catch {
83
+ return existsSync(join(homedir(), ".config", "opencode"));
84
+ }
85
+ },
86
+ configDir: () => join(homedir(), ".config", "opencode"),
87
+ skillsDir: () => join(homedir(), ".config", "opencode", "commands"),
88
+ settingsFile: () => join(homedir(), ".config", "opencode", "opencode.json"),
89
+ },
72
90
  ];
73
91
 
74
92
  export async function detectRuntime() {