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.
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/bin/arka-menubar.py +407 -0
- package/config/hooks/session-start.sh +1 -1
- package/config/install-profiles.json +75 -0
- package/config/statusline.sh +6 -1
- package/core/hooks/session_start.py +69 -28
- package/core/runtime/opencode.py +113 -0
- package/core/runtime/registry.py +6 -1
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- package/harness/opencode/agents/arka-architect-gabriel.md +12 -0
- package/harness/opencode/agents/arka-brand-director-valentina.md +12 -0
- package/harness/opencode/agents/arka-cfo-helena.md +12 -0
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +12 -0
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +12 -0
- package/harness/opencode/agents/arka-content-strategist-rafael.md +12 -0
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +12 -0
- package/harness/opencode/agents/arka-coo-sofia.md +12 -0
- package/harness/opencode/agents/arka-copy-director-eduardo.md +12 -0
- package/harness/opencode/agents/arka-cqo-marta.md +12 -0
- package/harness/opencode/agents/arka-cto-marco.md +12 -0
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +12 -0
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +12 -0
- package/harness/opencode/agents/arka-knowledge-director-clara.md +12 -0
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +12 -0
- package/harness/opencode/agents/arka-marketing-director-luna.md +12 -0
- package/harness/opencode/agents/arka-ops-lead-daniel.md +12 -0
- package/harness/opencode/agents/arka-pm-director-carolina.md +12 -0
- package/harness/opencode/agents/arka-revops-lead-vicente.md +12 -0
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +12 -0
- package/harness/opencode/agents/arka-sales-director-miguel.md +12 -0
- package/harness/opencode/agents/arka-strategy-director-tomas.md +12 -0
- package/harness/opencode/agents/arka-tech-director-francisca.md +12 -0
- package/harness/opencode/agents/arka-tech-lead-paulo.md +12 -0
- package/harness/opencode/agents/arka-video-producer-simao.md +12 -0
- package/harness/opencode/commands/arka-brand.md +9 -0
- package/harness/opencode/commands/arka-community.md +9 -0
- package/harness/opencode/commands/arka-content.md +9 -0
- package/harness/opencode/commands/arka-dev.md +9 -0
- package/harness/opencode/commands/arka-ecom.md +9 -0
- package/harness/opencode/commands/arka-fin.md +9 -0
- package/harness/opencode/commands/arka-kb.md +9 -0
- package/harness/opencode/commands/arka-landing.md +9 -0
- package/harness/opencode/commands/arka-lead.md +9 -0
- package/harness/opencode/commands/arka-mkt.md +9 -0
- package/harness/opencode/commands/arka-ops.md +9 -0
- package/harness/opencode/commands/arka-org.md +9 -0
- package/harness/opencode/commands/arka-pm.md +9 -0
- package/harness/opencode/commands/arka-saas.md +9 -0
- package/harness/opencode/commands/arka-sales.md +9 -0
- package/harness/opencode/commands/arka-strat.md +9 -0
- package/harness/opencode/opencode.json +16 -0
- package/harness/zed/.rules +1 -1
- package/installer/adapters/opencode.js +118 -0
- package/installer/autoupdate.js +249 -0
- package/installer/cli.js +38 -1
- package/installer/core-snapshot.js +5 -1
- package/installer/detect-runtime.js +18 -0
- package/installer/doctor.js +213 -2
- package/installer/index.js +182 -85
- package/installer/menubar.js +262 -0
- package/installer/product-stats.js +93 -0
- package/installer/profile.js +75 -0
- package/installer/prompts.js +303 -41
- package/installer/services.js +487 -0
- package/installer/ui.js +190 -0
- package/installer/update.js +219 -91
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +6 -1
- package/pyproject.toml +1 -1
- package/scripts/auto-update.sh +188 -0
- package/scripts/harness_gen.py +98 -1
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
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.
|
|
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
|
|
35
|
+
{"systemMessage": "\n ▲ A 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
|
+
}
|
package/config/statusline.sh
CHANGED
|
@@ -173,7 +173,12 @@ if [ -f "$ARKA_CONFIG" ]; then
|
|
|
173
173
|
fi
|
|
174
174
|
|
|
175
175
|
# ─── Build Line 1: Context bar ───────────────────────────────────────────
|
|
176
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
289
|
-
return
|
|
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
|
-
|
|
370
|
+
visible = build_visible(cwd)
|
|
340
371
|
except Exception: # absolute fail-open: static banner, exit 0
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|