arkaos 4.34.0 → 4.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/bin/arka-menubar.py +407 -0
- package/config/install-profiles.json +75 -0
- 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/cli.js +31 -1
- package/installer/detect-runtime.js +18 -0
- package/installer/doctor.js +202 -2
- package/installer/index.js +174 -88
- 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 +212 -95
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +6 -1
- package/pyproject.toml +1 -1
- 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:]))
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""OpenCode runtime adapter (Foundation PR-6).
|
|
2
|
+
|
|
3
|
+
OpenCode (opencode.ai) — open-source terminal AI coding agent with
|
|
4
|
+
native agents (markdown, ``~/.config/opencode/agents/``), custom
|
|
5
|
+
commands (``commands/``), MCP servers (``opencode.json``), and AGENTS.md
|
|
6
|
+
instructions. The installer adapter (installer/adapters/opencode.js)
|
|
7
|
+
deploys the generated harness bundle onto those surfaces.
|
|
8
|
+
|
|
9
|
+
Honesty note: OpenCode ships a headless ``opencode run`` mode, but the
|
|
10
|
+
invocation/event shape has NOT been live-verified against a real binary
|
|
11
|
+
the way codex_cli.py documents — until that verification lands,
|
|
12
|
+
``headless_supported`` stays False (base-adapter conservatism; the
|
|
13
|
+
capabilities matrix must never overclaim). Registered follow-up.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from os.path import expanduser
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from core.runtime.base import RuntimeAdapter, RuntimeConfig, AgentContext, AgentResult
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from core.runtime.llm_provider import LLMResponse
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpenCodeAdapter(RuntimeAdapter):
|
|
27
|
+
"""Adapter for OpenCode (terminal AI coding agent)."""
|
|
28
|
+
|
|
29
|
+
def get_config(self) -> RuntimeConfig:
|
|
30
|
+
home = Path(expanduser("~"))
|
|
31
|
+
config_dir = home / ".config" / "opencode"
|
|
32
|
+
return RuntimeConfig(
|
|
33
|
+
id="opencode",
|
|
34
|
+
name="OpenCode",
|
|
35
|
+
config_dir=config_dir,
|
|
36
|
+
skills_dir=config_dir / "commands",
|
|
37
|
+
settings_file=config_dir / "opencode.json",
|
|
38
|
+
supports_hooks=False,
|
|
39
|
+
supports_subagents=True,
|
|
40
|
+
supports_mcp=True,
|
|
41
|
+
max_context_tokens=200_000,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def capabilities(self) -> dict[str, bool]:
|
|
45
|
+
return {
|
|
46
|
+
"agent_dispatch": False, # native agents exist; no ArkaOS wiring yet
|
|
47
|
+
"headless": False, # `opencode run` not live-verified yet
|
|
48
|
+
"file_ops": True, # terminal agent edits files natively
|
|
49
|
+
"hooks": False, # plugins exist; no PreToolUse/Stop parity
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def inject_context(self, layers: dict[str, str]) -> str:
|
|
53
|
+
"""OpenCode reads AGENTS.md and `instructions` config entries."""
|
|
54
|
+
parts = []
|
|
55
|
+
for name, content in layers.items():
|
|
56
|
+
parts.append(f"## {name}\n{content}")
|
|
57
|
+
return "\n\n".join(parts)
|
|
58
|
+
|
|
59
|
+
def dispatch_agent(self, context: AgentContext) -> AgentResult:
|
|
60
|
+
"""OpenCode selects its own (deployed arka-*) agents internally."""
|
|
61
|
+
return AgentResult(
|
|
62
|
+
agent_id=context.agent_id,
|
|
63
|
+
status="dispatched",
|
|
64
|
+
output=f"Agent {context.agent_id} dispatched via OpenCode",
|
|
65
|
+
metadata={"runtime": "opencode"},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def spawn_subagent(self, context: AgentContext) -> AgentResult:
|
|
69
|
+
"""Native subagents exist (mode: subagent) but ArkaOS cannot
|
|
70
|
+
target them programmatically yet — honest unsupported."""
|
|
71
|
+
return AgentResult(
|
|
72
|
+
agent_id=context.agent_id,
|
|
73
|
+
status="unsupported",
|
|
74
|
+
output=(
|
|
75
|
+
"OpenCode subagents are selected by OpenCode itself; "
|
|
76
|
+
"programmatic ArkaOS dispatch is not wired yet."
|
|
77
|
+
),
|
|
78
|
+
metadata={"runtime": "opencode", "fallback": "single-agent"},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def read_file(self, path: str) -> str:
|
|
82
|
+
raise NotImplementedError("Use OpenCode's native file read")
|
|
83
|
+
|
|
84
|
+
def write_file(self, path: str, content: str) -> None:
|
|
85
|
+
raise NotImplementedError("Use OpenCode's native file write")
|
|
86
|
+
|
|
87
|
+
def edit_file(self, path: str, old: str, new: str) -> None:
|
|
88
|
+
raise NotImplementedError("Use OpenCode's native file edit")
|
|
89
|
+
|
|
90
|
+
def execute_command(self, command: str, timeout: int = 120) -> tuple[str, int]:
|
|
91
|
+
raise NotImplementedError("Use OpenCode's native shell tool")
|
|
92
|
+
|
|
93
|
+
def search_files(self, pattern: str, path: str = ".") -> list[str]:
|
|
94
|
+
raise NotImplementedError("Use OpenCode's native file search")
|
|
95
|
+
|
|
96
|
+
def search_content(self, pattern: str, path: str = ".") -> list[str]:
|
|
97
|
+
raise NotImplementedError("Use OpenCode's native content search")
|
|
98
|
+
|
|
99
|
+
def headless_supported(self) -> bool:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def headless_complete(
|
|
103
|
+
self,
|
|
104
|
+
prompt: str,
|
|
105
|
+
*,
|
|
106
|
+
max_tokens: int = 2000,
|
|
107
|
+
system: str = "",
|
|
108
|
+
) -> "LLMResponse":
|
|
109
|
+
raise NotImplementedError(
|
|
110
|
+
"OpenCode `opencode run` exists but its invocation/output "
|
|
111
|
+
"shape is not live-verified yet (codex_cli.py precedent). "
|
|
112
|
+
"Fall back to AnthropicDirectProvider or StubProvider."
|
|
113
|
+
)
|
package/core/runtime/registry.py
CHANGED
|
@@ -5,12 +5,14 @@ from core.runtime.claude_code import ClaudeCodeAdapter
|
|
|
5
5
|
from core.runtime.codex_cli import CodexCliAdapter
|
|
6
6
|
from core.runtime.gemini_cli import GeminiCliAdapter
|
|
7
7
|
from core.runtime.cursor import CursorAdapter
|
|
8
|
+
from core.runtime.opencode import OpenCodeAdapter
|
|
8
9
|
|
|
9
10
|
_ADAPTERS: dict[str, type[RuntimeAdapter]] = {
|
|
10
11
|
"claude-code": ClaudeCodeAdapter,
|
|
11
12
|
"codex": CodexCliAdapter,
|
|
12
13
|
"gemini": GeminiCliAdapter,
|
|
13
14
|
"cursor": CursorAdapter,
|
|
15
|
+
"opencode": OpenCodeAdapter,
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
# Public read-only alias (PR-6) — used by core.runtime.capabilities_cli.
|
|
@@ -21,7 +23,8 @@ def get_adapter(runtime_id: str) -> RuntimeAdapter:
|
|
|
21
23
|
"""Get an adapter instance for the given runtime ID.
|
|
22
24
|
|
|
23
25
|
Args:
|
|
24
|
-
runtime_id: One of 'claude-code', 'codex', 'gemini', 'cursor'
|
|
26
|
+
runtime_id: One of 'claude-code', 'codex', 'gemini', 'cursor',
|
|
27
|
+
'opencode'
|
|
25
28
|
|
|
26
29
|
Returns:
|
|
27
30
|
Instantiated RuntimeAdapter for the runtime.
|
|
@@ -69,6 +72,8 @@ def detect_runtime() -> str:
|
|
|
69
72
|
return "gemini"
|
|
70
73
|
if (home / ".cursor").exists():
|
|
71
74
|
return "cursor"
|
|
75
|
+
if (home / ".config" / "opencode").exists():
|
|
76
|
+
return "opencode"
|
|
72
77
|
|
|
73
78
|
# Default to claude-code as primary runtime
|
|
74
79
|
return "claude-code"
|
package/harness/codex/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
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.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: ArkaOS v4.
|
|
2
|
+
description: ArkaOS v4.35.0 agent-team contract
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
7
7
|
|
|
8
|
-
> v4.
|
|
8
|
+
> v4.35.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
9
9
|
|
|
10
10
|
You are operating within ArkaOS. Every request routes through the
|
|
11
11
|
appropriate department squad — never respond as a generic assistant.
|
package/harness/gemini/GEMINI.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
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.
|