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
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v4.33.0 — 89 agents, 17 departments, 331 skills, 297 commands, 16 ADRs.
3
+ > v4.35.0 — 89 agents, 17 departments, 331 skills, 297 commands, 16 ADRs.
4
4
  > One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
5
5
 
6
6
  ## What it is
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.33.0
1
+ 4.35.0
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env python3
2
+ """ArkaOS menu bar launcher (Foundation PR-5).
3
+
4
+ A lightweight macOS menu bar app (rumps) that surfaces the ArkaOS
5
+ runtime state and one-click actions:
6
+
7
+ - Check for updates -> scripts/auto-update.sh --force (PR-1 daemon)
8
+ - Open Dashboard -> start-dashboard.sh ensure + open UI port
9
+ - Start Ollama -> open -a Ollama (fallback: ollama serve)
10
+ [local-ai profile only, when stopped]
11
+ - Doctor -> Terminal running `npx arkaos doctor`
12
+ - Auto-update on/off -> npx arkaos autoupdate enable|disable
13
+ - Quit
14
+
15
+ Posture (matches scripts/auto-update.sh): every failure path logs and
16
+ exits 0 — a broken login item must never crash. rumps import is guarded:
17
+ non-macOS, missing rumps, or headless -> clean exit 0 with a hint.
18
+
19
+ State model and menu-visibility logic are PURE functions so tests
20
+ exercise them via the introspection flags without rumps or a display:
21
+
22
+ arka-menubar.py --print-state JSON of read_state()
23
+ arka-menubar.py --print-menu JSON of visible menu item ids
24
+
25
+ Test hooks (env): ARKA_MENUBAR_HOME overrides the ~/.arkaos parent dir;
26
+ ARKA_MENUBAR_OLLAMA (absent|stopped|running) overrides the live probe.
27
+ The probe itself only runs on the local-ai profile (the only profile
28
+ that ever surfaces Start Ollama); other profiles never spawn ollama.
29
+ """
30
+
31
+ # PEP 604 annotations (`Path | None`) are evaluated at def time without
32
+ # this import and raise TypeError on Python < 3.10 — and the plist's
33
+ # last-resort interpreter is the macOS system /usr/bin/python3 (3.9).
34
+ # The future import turns them into strings, importable everywhere.
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ import os
39
+ import shutil
40
+ import subprocess
41
+ import sys
42
+ import threading
43
+ from pathlib import Path
44
+
45
+ VALID_PROFILES = ("essential", "complete", "local-ai")
46
+ REFRESH_SECONDS = 60
47
+ TITLE = "▲" # ▲ — brand wordmark glyph
48
+ TITLE_PENDING = "▲ •" # ▲ • — sync pending badge
49
+ # Contract with installer/menubar.js (optoutPath) and the PR-1 daemon
50
+ # (installer/autoupdate.js optoutPath). menubar.test.js locks parity
51
+ # against the real JS modules — never rename one side alone.
52
+ MENUBAR_OPTOUT_BASENAME = "menubar.optout"
53
+ AUTOUPDATE_OPTOUT_BASENAME = "autoupdate.optout"
54
+
55
+ USAGE = """arka-menubar.py — ArkaOS menu bar launcher (macOS)
56
+ (no args) run the menu bar app (exits 0 when unsupported)
57
+ --print-state JSON snapshot of the runtime state
58
+ --print-menu JSON list of visible menu item ids
59
+ --help this text
60
+ """
61
+
62
+
63
+ def arka_home() -> Path:
64
+ override = os.environ.get("ARKA_MENUBAR_HOME", "")
65
+ base = Path(override) if override else Path.home()
66
+ return base / ".arkaos"
67
+
68
+
69
+ # ── Pure state model ─────────────────────────────────────────────────────
70
+
71
+
72
+ def read_state(home: Path | None = None) -> dict:
73
+ """Read-only snapshot of the runtime state. Never throws."""
74
+ home = home or arka_home()
75
+ state = {
76
+ "version": None,
77
+ "sync_pending": False,
78
+ "profile": "essential",
79
+ "autoupdate_on": True,
80
+ }
81
+ try:
82
+ manifest = json.loads((home / "install-manifest.json").read_text())
83
+ state["version"] = manifest.get("version") or None
84
+ except Exception:
85
+ pass
86
+ try:
87
+ sync = json.loads((home / "sync-state.json").read_text())
88
+ state["sync_pending"] = sync.get("version") == "pending-sync"
89
+ except Exception:
90
+ pass
91
+ try:
92
+ profile = json.loads((home / "profile.json").read_text())
93
+ value = str(profile.get("installProfile", "essential")).strip().lower()
94
+ state["profile"] = value if value in VALID_PROFILES else "essential"
95
+ except Exception:
96
+ pass
97
+ state["autoupdate_on"] = not (home / AUTOUPDATE_OPTOUT_BASENAME).exists()
98
+ return state
99
+
100
+
101
+ def ollama_status() -> str:
102
+ """absent | stopped | running — read-only probes, short timeouts."""
103
+ override = os.environ.get("ARKA_MENUBAR_OLLAMA", "")
104
+ if override in ("absent", "stopped", "running"):
105
+ return override
106
+ if shutil.which("ollama") is None:
107
+ return "absent"
108
+ try:
109
+ subprocess.run(
110
+ ["ollama", "list"], capture_output=True, timeout=2, check=True
111
+ )
112
+ return "running"
113
+ except Exception:
114
+ return "stopped"
115
+
116
+
117
+ def ollama_status_for(state: dict) -> str:
118
+ """Gate the live probe: only the local-ai profile ever surfaces the
119
+ Start Ollama item, so every other profile skips the subprocess."""
120
+ if state.get("profile") != "local-ai":
121
+ return "absent"
122
+ return ollama_status()
123
+
124
+
125
+ def menu_items(state: dict, ollama: str) -> list:
126
+ """Pure: visible menu item ids for a given state."""
127
+ items = ["check_updates", "open_dashboard", "doctor"]
128
+ if state.get("profile") == "local-ai" and ollama == "stopped":
129
+ items.append("start_ollama")
130
+ items.append("autoupdate_toggle")
131
+ items.append("disable")
132
+ items.append("quit")
133
+ return items
134
+
135
+
136
+ def title_for(state: dict) -> str:
137
+ return TITLE_PENDING if state.get("sync_pending") else TITLE
138
+
139
+
140
+ def version_label(state: dict) -> str:
141
+ """User-visible copy — never renders 'vunknown'."""
142
+ version = state.get("version")
143
+ return f"ArkaOS v{version}" if version else "ArkaOS (version unknown)"
144
+
145
+
146
+ # ── Action helpers (subprocess, never blocking the UI thread) ────────────
147
+
148
+
149
+ def stable_script(name: str) -> Path | None:
150
+ """Resolve a scripts/ file: purge-proof ~/.arkaos/lib snapshot first,
151
+ then the .repo-path reference (autoupdate.js::stableRoot parity)."""
152
+ home = arka_home()
153
+ lib = home / "lib" / "scripts" / name
154
+ if lib.exists():
155
+ return lib
156
+ try:
157
+ repo = Path((home / ".repo-path").read_text().strip())
158
+ candidate = repo / "scripts" / name
159
+ if candidate.exists():
160
+ return candidate
161
+ except Exception:
162
+ pass
163
+ return None
164
+
165
+
166
+ def log_line(message: str) -> None:
167
+ try:
168
+ log_dir = arka_home() / "logs"
169
+ log_dir.mkdir(parents=True, exist_ok=True)
170
+ with open(log_dir / "menubar.log", "a", encoding="utf-8") as handle:
171
+ handle.write(message.rstrip() + "\n")
172
+ except Exception:
173
+ pass
174
+
175
+
176
+ def action_check_updates() -> None:
177
+ script = stable_script("auto-update.sh")
178
+ if script is None:
179
+ log_line("check_updates: auto-update.sh not found")
180
+ return
181
+ subprocess.Popen(["/bin/bash", str(script), "--force"])
182
+
183
+
184
+ def action_open_dashboard() -> None:
185
+ script = stable_script("start-dashboard.sh")
186
+ if script is not None:
187
+ try:
188
+ subprocess.run(["/bin/bash", str(script), "ensure"], timeout=120)
189
+ except Exception as err:
190
+ log_line(f"open_dashboard: ensure failed ({err})")
191
+ ui_port = ""
192
+ try:
193
+ for line in (arka_home() / "dashboard.ports").read_text().splitlines():
194
+ if line.startswith("UI_PORT="):
195
+ ui_port = line.split("=", 1)[1].strip()
196
+ except Exception:
197
+ pass
198
+ if ui_port.isdigit():
199
+ subprocess.run(["/usr/bin/open", f"http://localhost:{ui_port}"], timeout=15)
200
+ else:
201
+ log_line(
202
+ "open_dashboard: no UI_PORT after start-dashboard.sh ensure — "
203
+ "check ~/.arkaos/logs for the dashboard startup error"
204
+ )
205
+
206
+
207
+ def action_start_ollama() -> None:
208
+ # Operator decision (PR-5 Phase 0): the app first, `serve` as fallback.
209
+ result = subprocess.run(["/usr/bin/open", "-a", "Ollama"],
210
+ capture_output=True, timeout=15)
211
+ if result.returncode != 0:
212
+ if shutil.which("ollama"):
213
+ subprocess.Popen(
214
+ ["ollama", "serve"],
215
+ stdout=subprocess.DEVNULL,
216
+ stderr=subprocess.DEVNULL,
217
+ )
218
+ else:
219
+ log_line("start_ollama: neither Ollama.app nor ollama binary found")
220
+
221
+
222
+ def action_doctor() -> None:
223
+ subprocess.run([
224
+ "/usr/bin/osascript", "-e",
225
+ 'tell application "Terminal" to activate',
226
+ "-e",
227
+ 'tell application "Terminal" to do script "npx arkaos doctor"',
228
+ ], capture_output=True, timeout=15)
229
+
230
+
231
+ def action_autoupdate(enable: bool) -> None:
232
+ """Runs on a worker thread (npx resolves the registry — slow/offline
233
+ must degrade to a logged line, never a silent no-op)."""
234
+ verb = "enable" if enable else "disable"
235
+ if shutil.which("npx") is None:
236
+ log_line(
237
+ "autoupdate_toggle: npx not on the LaunchAgent PATH — "
238
+ f"run manually: npx arkaos autoupdate {verb}"
239
+ )
240
+ return
241
+ subprocess.run(["npx", "arkaos", "autoupdate", verb],
242
+ capture_output=True, timeout=180)
243
+
244
+
245
+ def action_disable_menubar() -> None:
246
+ """Permanent opt-out from the menu itself (QG M7): writes the marker
247
+ installer/menubar.js honors; the startup guard in run_app makes any
248
+ remaining RunAtLoad an instant no-op until `npx arkaos menubar enable`."""
249
+ marker = arka_home() / MENUBAR_OPTOUT_BASENAME
250
+ marker.parent.mkdir(parents=True, exist_ok=True)
251
+ marker.write_text("disabled from the menu bar\n", encoding="utf-8")
252
+ log_line("disable: opt-out marker written — re-enable: npx arkaos menubar enable")
253
+
254
+
255
+ # ── rumps app (guarded import — never a crashing login item) ─────────────
256
+
257
+
258
+ def run_app() -> int:
259
+ if sys.platform != "darwin":
260
+ print("arka-menubar: macOS only — nothing to do")
261
+ return 0
262
+ # Permanent opt-out (QG M7): the plist may still RunAtLoad until an
263
+ # update removes it — the marker makes that launch an instant no-op.
264
+ if (arka_home() / MENUBAR_OPTOUT_BASENAME).exists():
265
+ print("arka-menubar: user opt-out — exiting (re-enable: npx arkaos menubar enable)")
266
+ return 0
267
+ try:
268
+ import rumps
269
+ except Exception as err: # missing dep, headless session, SIP oddity
270
+ print(
271
+ "arka-menubar: rumps unavailable "
272
+ f"({err}) — install: ~/.arkaos/venv/bin/pip install rumps"
273
+ )
274
+ return 0
275
+
276
+ class ArkaMenuBar(rumps.App):
277
+ def __init__(self):
278
+ super().__init__(TITLE, quit_button=None)
279
+ self._rumps = rumps
280
+ self._pollers = set()
281
+ self.refresh(None)
282
+ self.timer = rumps.Timer(self.refresh, REFRESH_SECONDS)
283
+ self.timer.start()
284
+
285
+ def _spawn(self, work, refresh_after=False):
286
+ """Run side-effect work off the AppKit main thread (a blocking
287
+ subprocess in a rumps callback freezes the whole menu bar).
288
+ The worker only runs subprocesses and reads files — ALL rumps
289
+ interaction stays on the main thread: an optional 1s poll timer
290
+ (main thread) refreshes the menu once the worker finishes.
291
+ Worker bodies are exception-guarded to log_line — a missing
292
+ binary must never die silently (QG M5)."""
293
+ def guarded():
294
+ try:
295
+ work()
296
+ except Exception as err:
297
+ log_line(f"action: {err}")
298
+
299
+ worker = threading.Thread(target=guarded, daemon=True)
300
+ worker.start()
301
+ if not refresh_after:
302
+ return
303
+
304
+ def poll(timer):
305
+ if not worker.is_alive():
306
+ timer.stop()
307
+ self._pollers.discard(timer)
308
+ self.refresh(None)
309
+
310
+ poller = self._rumps.Timer(poll, 1)
311
+ self._pollers.add(poller)
312
+ poller.start()
313
+
314
+ def refresh(self, _sender):
315
+ state = read_state()
316
+ ollama = ollama_status_for(state)
317
+ self.title = title_for(state)
318
+ self.menu.clear()
319
+ info = rumps.MenuItem(version_label(state))
320
+ info.set_callback(None) # informational, not clickable
321
+ entries = [info]
322
+ if state["sync_pending"]:
323
+ pending = rumps.MenuItem("Sync pending — open a Claude session")
324
+ pending.set_callback(None)
325
+ entries.append(pending)
326
+ entries.append(rumps.separator)
327
+ visible = menu_items(state, ollama)
328
+ labels = {
329
+ "check_updates": ("Check for updates", self.on_check_updates),
330
+ "open_dashboard": ("Open Dashboard", self.on_open_dashboard),
331
+ "doctor": ("Run Doctor", self.on_doctor),
332
+ "start_ollama": ("Start Ollama", self.on_start_ollama),
333
+ "autoupdate_toggle": (
334
+ "Auto-update: on" if state["autoupdate_on"] else "Auto-update: off",
335
+ self.on_autoupdate_toggle,
336
+ ),
337
+ "disable": ("Disable menu bar (permanent)", self.on_disable),
338
+ "quit": ("Quit until next login", self.on_quit),
339
+ }
340
+ for item_id in visible:
341
+ label, callback = labels[item_id]
342
+ entry = rumps.MenuItem(label, callback=callback)
343
+ if item_id == "autoupdate_toggle":
344
+ entry.state = 1 if state["autoupdate_on"] else 0
345
+ if item_id == "disable":
346
+ entries.append(rumps.separator)
347
+ entries.append(entry)
348
+ self.menu.update(entries)
349
+
350
+ def on_check_updates(self, _):
351
+ # Popen is non-blocking, but a fork/exec OSError must not
352
+ # propagate into the rumps callback (QG r2 minor).
353
+ try:
354
+ action_check_updates()
355
+ except Exception as err:
356
+ log_line(f"check_updates: {err}")
357
+
358
+ def on_open_dashboard(self, _):
359
+ self._spawn(action_open_dashboard)
360
+
361
+ def on_doctor(self, _):
362
+ self._spawn(action_doctor)
363
+
364
+ def on_start_ollama(self, _):
365
+ self._spawn(action_start_ollama, refresh_after=True)
366
+
367
+ def on_autoupdate_toggle(self, _):
368
+ enable = not read_state()["autoupdate_on"]
369
+ self._spawn(lambda: action_autoupdate(enable=enable), refresh_after=True)
370
+
371
+ def on_disable(self, _):
372
+ try:
373
+ action_disable_menubar()
374
+ except Exception as err:
375
+ log_line(f"disable: {err}")
376
+ self._rumps.quit_application()
377
+
378
+ def on_quit(self, _):
379
+ self._rumps.quit_application()
380
+
381
+ try:
382
+ ArkaMenuBar().run()
383
+ except Exception as err:
384
+ log_line(f"fatal: {err}")
385
+ print(f"arka-menubar: exiting cleanly after error ({err})")
386
+ return 0
387
+
388
+
389
+ def main(argv: list) -> int:
390
+ if "--help" in argv or "-h" in argv:
391
+ print(USAGE)
392
+ return 0
393
+ if "--print-state" in argv:
394
+ print(json.dumps(read_state()))
395
+ return 0
396
+ if "--print-menu" in argv:
397
+ state = read_state()
398
+ print(json.dumps(menu_items(state, ollama_status_for(state))))
399
+ return 0
400
+ if argv and argv[0].startswith("-"):
401
+ print(f"arka-menubar: unknown option {argv[0]}\n\n{USAGE}")
402
+ return 2
403
+ return run_app()
404
+
405
+
406
+ if __name__ == "__main__":
407
+ sys.exit(main(sys.argv[1:]))
@@ -32,6 +32,6 @@ fi
32
32
 
33
33
  # ─── Degraded fallback: static banner, valid JSON, exit 0 ──────────────
34
34
  cat <<'EOF'
35
- {"systemMessage": "\n╔══════════════════════════════════════════════╗\n║ A R K A O S ║\nThe Operating System for AI Teams ║\n╚══════════════════════════════════════════════╝\nOlá, founder (WizardingCode)\nArkaOS (degraded: no usable interpreter — run npx arkaos doctor)"}
35
+ {"systemMessage": "\nA R K A O S\n The Operating System for AI Agent Teams\n\n Olá, founder\n degraded: no usable interpreter — run npx arkaos doctor"}
36
36
  EOF
37
37
  exit 0
@@ -0,0 +1,75 @@
1
+ {
2
+ "_meta": {
3
+ "description": "ArkaOS install profiles — data-driven profile→services manifest (Foundation PR-4)",
4
+ "note": "profiles.*.services reference ids under services; extends chains must be acyclic. Consumed by installer/services.js (reconciliation) and installer/doctor.js (per-profile checks). Essential mirrors what a fresh install already guarantees so reconciliation can repair it."
5
+ },
6
+ "profiles": {
7
+ "essential": {
8
+ "label": "Essential",
9
+ "services": ["venv-core", "dashboard-deps", "mcp-infrastructure", "graphify"]
10
+ },
11
+ "complete": {
12
+ "label": "Complete",
13
+ "extends": "essential",
14
+ "services": ["litellm-proxy", "ffmpeg", "whisper"]
15
+ },
16
+ "local-ai": {
17
+ "label": "Local AI",
18
+ "extends": "complete",
19
+ "services": ["ollama", "ollama-execution-model"]
20
+ }
21
+ },
22
+ "services": {
23
+ "venv-core": {
24
+ "label": "Python venv + core deps",
25
+ "kind": "pip",
26
+ "packages": "pyyaml pydantic rich click jinja2",
27
+ "modules": ["yaml", "pydantic"]
28
+ },
29
+ "dashboard-deps": {
30
+ "label": "Dashboard API deps (fastapi, uvicorn)",
31
+ "kind": "pip",
32
+ "packages": "fastapi uvicorn python-multipart",
33
+ "modules": ["fastapi", "uvicorn"]
34
+ },
35
+ "mcp-infrastructure": {
36
+ "label": "MCP infrastructure (registry + servers)",
37
+ "kind": "file",
38
+ "path": "~/.claude/skills/arka/mcps/registry.json",
39
+ "hint": "Run: npx arkaos@latest update (redeploys the MCP bundle)"
40
+ },
41
+ "graphify": {
42
+ "label": "Graphify grounding CLI",
43
+ "kind": "graphify"
44
+ },
45
+ "litellm-proxy": {
46
+ "label": "LiteLLM proxy (gateway prerequisite)",
47
+ "kind": "pip",
48
+ "packages": "litellm[proxy]",
49
+ "modules": ["litellm"]
50
+ },
51
+ "ffmpeg": {
52
+ "label": "FFmpeg (media encode / transcription)",
53
+ "kind": "system-package",
54
+ "consent": true,
55
+ "binary": "ffmpeg",
56
+ "packages": { "brew": "ffmpeg", "apt": "ffmpeg", "winget": "Gyan.FFmpeg", "choco": "ffmpeg" }
57
+ },
58
+ "whisper": {
59
+ "label": "Whisper transcription (faster-whisper)",
60
+ "kind": "pip",
61
+ "packages": "faster-whisper",
62
+ "modules": ["faster_whisper"]
63
+ },
64
+ "ollama": {
65
+ "label": "Ollama runtime (local LLM backend)",
66
+ "kind": "ollama",
67
+ "consent": true
68
+ },
69
+ "ollama-execution-model": {
70
+ "label": "Local execution model (Model Fabric)",
71
+ "kind": "ollama-model",
72
+ "requires": ["ollama"]
73
+ }
74
+ }
75
+ }
@@ -173,7 +173,12 @@ if [ -f "$ARKA_CONFIG" ]; then
173
173
  fi
174
174
 
175
175
  # ─── Build Line 1: Context bar ───────────────────────────────────────────
176
- LINE1="${C_CYAN}▲ARKA${C_RESET} ${C_WHITE}${DIR_NAME}${C_RESET}"
176
+ # Version from the stable snapshot (one cat, no spawn storm on re-render).
177
+ ARKA_VER=""
178
+ if [ -f "$HOME/.arkaos/lib/VERSION" ]; then
179
+ ARKA_VER=$(tr -d '[:space:]' < "$HOME/.arkaos/lib/VERSION" 2>/dev/null)
180
+ fi
181
+ LINE1="${C_CYAN}▲ARKA${C_RESET}${ARKA_VER:+ ${C_DIM}v${ARKA_VER}${C_RESET}} ${C_WHITE}${DIR_NAME}${C_RESET}"
177
182
 
178
183
  # Git branch (hidden on main/master to reduce noise)
179
184
  if [ -n "$BRANCH" ] && [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
@@ -7,13 +7,15 @@ print) with ONE python process that emits the full ``systemMessage``.
7
7
  Measured baseline before this consolidation: 251ms p50 (isolated floor,
8
8
  benchmarks/results.md); each spawn costs ~20-80ms.
9
9
 
10
- Section order preserved exactly from the shell version:
11
- banner+greeting workflow line version+forge+drift →
12
- evidence-flow contract meta-tag contract Model Fabric directive
13
- [SESSION] rehydrator resume → [SESSION-MEMORY] recap JSON out.
10
+ Presentation contract (Foundation PR-2): the user sees a compact branded
11
+ greeting ``systemMessage`` carries ONLY banner+greeting, workflow/forge
12
+ state, and the drift warning. Everything the MODEL needs but the user
13
+ should not scroll through (evidence-flow contract, meta-tag contract,
14
+ authority brief, Model Fabric directive, [SESSION] resume,
15
+ [SESSION-MEMORY] recap) ships via ``hookSpecificOutput.
16
+ additionalContext`` — same enforcement, zero wall of text.
14
17
  Background side effects (reorganizer trigger, dashboard ensure) stay
15
- detached and are config-gated; the NEW ``cognition.reorganize_on_session``
16
- gate lands here (QG F2-1 follow-up, endorsed for this PR).
18
+ detached and are config-gated (``cognition.reorganize_on_session``).
17
19
 
18
20
  The shell wrapper only resolves the interpreter and ``exec``s this
19
21
  module; with no usable venv it emits a static banner (fail-open).
@@ -35,17 +37,21 @@ _BUDGET_MS = 300
35
37
  _RECAP_ITEMS = 3
36
38
  _SUMMARY_CHARS = 130
37
39
 
38
- _BANNER = (
39
- "\n╔══════════════════════════════════════════════╗\n"
40
- "║ ║\n"
41
- "A R K A O S ║\n"
42
- "║ ║\n"
43
- "║ The Operating System for AI Teams ║\n"
44
- "║ by WizardingCode ║\n"
45
- "║ ║\n"
46
- "╚══════════════════════════════════════════════╝\n"
40
+ # Compact wordmark (Levitation identity: flat apex + floating bar) — the
41
+ # box-drawing wall it replaces read as a default terminal artifact.
42
+ _FALLBACK_BANNER = (
43
+ "\n ▲ A R K A O S\n"
44
+ " The Operating System for AI Agent Teams\n"
47
45
  )
48
46
 
47
+
48
+ def _banner(version: str, name: str, company: str) -> str:
49
+ return (
50
+ f"\n ▲ A R K A O S — v{version}\n"
51
+ f" The Operating System for AI Agent Teams · {company}\n"
52
+ f"\n Olá, {name}\n"
53
+ )
54
+
49
55
  _EVIDENCE_CONTRACT = (
50
56
  "\n\n[ARKA:EVIDENCE-FLOW] NON-NEGOTIABLE. Every non-trivial request runs"
51
57
  " the 4-gate evidence flow (constitution rule evidence-flow; source"
@@ -266,27 +272,52 @@ def _authority_brief(cwd: str) -> str:
266
272
  return f"\n\n{brief}" if brief else ""
267
273
 
268
274
 
269
- def build_message(cwd: str) -> str:
275
+ def build_visible(cwd: str) -> str:
276
+ """User-facing ``systemMessage``: branded greeting + live state only.
277
+
278
+ Owns the background side effects (reorganizer, dashboard ensure) so
279
+ they fire exactly once per session regardless of which builder a
280
+ caller combines.
281
+ """
270
282
  repo = repo_path()
271
283
  config = _config()
272
284
  name, company = _profile()
273
285
  version = _version(repo)
274
- msg = _BANNER + f"\nOlá, {name} ({company})\n"
286
+ msg = _banner(version, name, company)
275
287
  msg += _workflow_line()
276
- msg += f"ArkaOS v{version}"
277
288
  msg += _forge_line()
278
289
  msg += _drift(version)
279
- msg += _EVIDENCE_CONTRACT
280
- msg += _META_TAG_CONTRACT
281
- msg += _authority_brief(cwd)
282
- msg += _model_fabric()
283
290
  _trigger_reorganizer(repo, config)
284
291
  _ensure_dashboard(repo, config)
285
- msg += _session_resume()
292
+ return msg
293
+
294
+
295
+ def build_context(cwd: str) -> str:
296
+ """Model-only ``additionalContext``: the operating contracts.
297
+
298
+ Same text that used to flood the visible banner — the enforcement
299
+ surfaces (PreToolUse gate, Stop hook) read the model's OUTPUT
300
+ markers, so moving the injected contracts off-screen changes nothing
301
+ about enforcement.
302
+ """
303
+ parts = [
304
+ _EVIDENCE_CONTRACT,
305
+ _META_TAG_CONTRACT,
306
+ _authority_brief(cwd),
307
+ _model_fabric(),
308
+ _session_resume(),
309
+ ]
286
310
  recap = build_recap(cwd)
287
311
  if recap:
288
- msg += f"\n\n{recap}"
289
- return msg
312
+ parts.append(f"\n\n{recap}")
313
+ return "".join(parts).lstrip("\n")
314
+
315
+
316
+ def build_message(cwd: str) -> str:
317
+ """Full text (visible + contracts) — legacy single-string view."""
318
+ visible = build_visible(cwd)
319
+ context = build_context(cwd)
320
+ return visible + (f"\n\n{context}" if context else "")
290
321
 
291
322
 
292
323
  def build_recap(cwd: str, budget_ms: int = _BUDGET_MS) -> str:
@@ -336,10 +367,20 @@ def main(stdin_json: dict | None = None) -> int:
336
367
  or os.getcwd()
337
368
  )
338
369
  try:
339
- message = build_message(cwd)
370
+ visible = build_visible(cwd)
340
371
  except Exception: # absolute fail-open: static banner, exit 0
341
- message = _BANNER + "\nOlá, founder (WizardingCode)\nArkaOS"
342
- print(json.dumps({"systemMessage": message}))
372
+ visible = _FALLBACK_BANNER + "\n Olá, founder\n"
373
+ try:
374
+ context = build_context(cwd)
375
+ except Exception: # contracts are best-effort; greeting never breaks
376
+ context = ""
377
+ payload: dict = {"systemMessage": visible}
378
+ if context:
379
+ payload["hookSpecificOutput"] = {
380
+ "hookEventName": "SessionStart",
381
+ "additionalContext": context,
382
+ }
383
+ print(json.dumps(payload))
343
384
  return 0
344
385
 
345
386