arkaos 4.33.0 → 4.34.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/config/hooks/session-start.sh +1 -1
- package/config/statusline.sh +6 -1
- package/core/hooks/session_start.py +69 -28
- 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/zed/.rules +1 -1
- package/installer/autoupdate.js +249 -0
- package/installer/cli.js +7 -0
- package/installer/core-snapshot.js +5 -1
- package/installer/doctor.js +11 -0
- package/installer/index.js +11 -0
- package/installer/update.js +11 -0
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/auto-update.sh +188 -0
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.34.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.34.0
|
|
@@ -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
|
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
|
|
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.34.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.34.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.34.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.34.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.34.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.34.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.
|
package/harness/zed/.rules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.34.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background auto-update daemon for ArkaOS (Foundation PR-1).
|
|
3
|
+
*
|
|
4
|
+
* Users were found running v2 while v4.33 was live — the update path
|
|
5
|
+
* (`npx arkaos@latest update`) only fires when the user remembers to run
|
|
6
|
+
* it. This installs a per-OS scheduled unit that runs
|
|
7
|
+
* `scripts/auto-update.sh` daily (and at load): the script checks the npm
|
|
8
|
+
* registry, applies the core update headlessly, and notifies the user.
|
|
9
|
+
* Project sync stays supervised — the next Claude session picks it up via
|
|
10
|
+
* [arka:update-available].
|
|
11
|
+
*
|
|
12
|
+
* Opt-in by default on install/update (`ensureDefaultEnabled`), with a
|
|
13
|
+
* persisted opt-out marker so `npx arkaos autoupdate disable` survives
|
|
14
|
+
* future updates. ESM, Node + Bun, no interactive prompts. Unit
|
|
15
|
+
* generation (`unitFor`) is pure so it can be unit-tested without
|
|
16
|
+
* touching the OS (autostart.js precedent).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
export const AUTOUPDATE_LABEL = "io.wizardingcode.arkaos.updater";
|
|
26
|
+
const SERVICE_NAME = "arkaos-updater.service";
|
|
27
|
+
const TIMER_NAME = "arkaos-updater.timer";
|
|
28
|
+
|
|
29
|
+
function defaultRepoRoot() {
|
|
30
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Pure: the opt-out marker path — written by disable(), honored by
|
|
34
|
+
* ensureDefaultEnabled() and by scripts/auto-update.sh itself. */
|
|
35
|
+
export function optoutPath(home = homedir()) {
|
|
36
|
+
return join(home, ".arkaos", "autoupdate.optout");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Anchor the daemon at the stable ~/.arkaos/lib snapshot, never at the
|
|
40
|
+
* package root when it can be an npx cache: `npm cache clean` purges
|
|
41
|
+
* that location at any time, and a default-on daemon anchored there
|
|
42
|
+
* dies silently — with no surviving daemon to self-heal (QG blocker,
|
|
43
|
+
* Foundation PR-1). core-snapshot.js ships scripts/ into the snapshot;
|
|
44
|
+
* repoRoot is the fallback for dev checkouts before the first deploy. */
|
|
45
|
+
export function stableRoot({ home = homedir(), repoRoot = defaultRepoRoot() } = {}) {
|
|
46
|
+
const lib = join(home, ".arkaos", "lib");
|
|
47
|
+
return existsSync(join(lib, "scripts", "auto-update.sh")) ? lib : repoRoot;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Pure: the scheduled unit(s) for a given platform. Throws on
|
|
51
|
+
* unsupported OS. Returns { kind, files: [{ path, content }] } —
|
|
52
|
+
* systemd needs a service + timer pair, launchd a single plist. */
|
|
53
|
+
export function unitFor(os, { repoRoot, home }) {
|
|
54
|
+
const script = `${repoRoot}/scripts/auto-update.sh`;
|
|
55
|
+
const log = `${home}/.arkaos/logs/auto-update-daemon.log`;
|
|
56
|
+
|
|
57
|
+
if (os === "darwin") {
|
|
58
|
+
// launchd runs with a minimal PATH (/usr/bin:/bin:...) — node/npx
|
|
59
|
+
// live in /opt/homebrew/bin or /usr/local/bin, so the plist must
|
|
60
|
+
// carry a usable PATH or the daemon dies on `command -v npx`.
|
|
61
|
+
// StartCalendarInterval fires daily; RunAtLoad covers machines that
|
|
62
|
+
// were asleep/off at that hour (login catches up).
|
|
63
|
+
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
64
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
65
|
+
<plist version="1.0">
|
|
66
|
+
<dict>
|
|
67
|
+
<key>Label</key>
|
|
68
|
+
<string>${AUTOUPDATE_LABEL}</string>
|
|
69
|
+
<key>ProgramArguments</key>
|
|
70
|
+
<array>
|
|
71
|
+
<string>/bin/bash</string>
|
|
72
|
+
<string>${script}</string>
|
|
73
|
+
</array>
|
|
74
|
+
<key>EnvironmentVariables</key>
|
|
75
|
+
<dict>
|
|
76
|
+
<key>PATH</key>
|
|
77
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
78
|
+
</dict>
|
|
79
|
+
<key>StartCalendarInterval</key>
|
|
80
|
+
<dict>
|
|
81
|
+
<key>Hour</key>
|
|
82
|
+
<integer>10</integer>
|
|
83
|
+
<key>Minute</key>
|
|
84
|
+
<integer>30</integer>
|
|
85
|
+
</dict>
|
|
86
|
+
<key>RunAtLoad</key>
|
|
87
|
+
<true/>
|
|
88
|
+
<key>StandardOutPath</key>
|
|
89
|
+
<string>${log}</string>
|
|
90
|
+
<key>StandardErrorPath</key>
|
|
91
|
+
<string>${log}</string>
|
|
92
|
+
</dict>
|
|
93
|
+
</plist>
|
|
94
|
+
`;
|
|
95
|
+
return {
|
|
96
|
+
kind: "launchd",
|
|
97
|
+
files: [
|
|
98
|
+
{
|
|
99
|
+
path: join(home, "Library", "LaunchAgents", `${AUTOUPDATE_LABEL}.plist`),
|
|
100
|
+
content,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (os === "linux") {
|
|
107
|
+
const service = `[Unit]
|
|
108
|
+
Description=ArkaOS auto-update (npm registry watch + headless core update)
|
|
109
|
+
After=network-online.target
|
|
110
|
+
|
|
111
|
+
[Service]
|
|
112
|
+
Type=oneshot
|
|
113
|
+
ExecStart=/bin/bash ${script}
|
|
114
|
+
StandardOutput=append:${log}
|
|
115
|
+
StandardError=append:${log}
|
|
116
|
+
`;
|
|
117
|
+
// Persistent=true replays a missed daily run at next boot — the
|
|
118
|
+
// laptop-that-was-off case that left users on v2.
|
|
119
|
+
const timer = `[Unit]
|
|
120
|
+
Description=Daily ArkaOS auto-update check
|
|
121
|
+
|
|
122
|
+
[Timer]
|
|
123
|
+
OnCalendar=daily
|
|
124
|
+
Persistent=true
|
|
125
|
+
|
|
126
|
+
[Install]
|
|
127
|
+
WantedBy=timers.target
|
|
128
|
+
`;
|
|
129
|
+
const unitDir = join(home, ".config", "systemd", "user");
|
|
130
|
+
return {
|
|
131
|
+
kind: "systemd",
|
|
132
|
+
files: [
|
|
133
|
+
{ path: join(unitDir, SERVICE_NAME), content: service },
|
|
134
|
+
{ path: join(unitDir, TIMER_NAME), content: timer },
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw new Error(`unsupported platform for autoupdate: ${os}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _silent(cmd) {
|
|
143
|
+
try {
|
|
144
|
+
execSync(cmd, { stdio: "pipe" });
|
|
145
|
+
return true;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function enable({ repoRoot = defaultRepoRoot() } = {}) {
|
|
152
|
+
const root = stableRoot({ repoRoot });
|
|
153
|
+
const unit = unitFor(process.platform, { repoRoot: root, home: homedir() });
|
|
154
|
+
mkdirSync(join(homedir(), ".arkaos", "logs"), { recursive: true });
|
|
155
|
+
for (const f of unit.files) {
|
|
156
|
+
mkdirSync(dirname(f.path), { recursive: true });
|
|
157
|
+
writeFileSync(f.path, f.content, "utf8");
|
|
158
|
+
}
|
|
159
|
+
if (existsSync(optoutPath())) rmSync(optoutPath());
|
|
160
|
+
const mainPath = unit.files[0].path;
|
|
161
|
+
if (unit.kind === "launchd") {
|
|
162
|
+
_silent(`launchctl unload "${mainPath}"`);
|
|
163
|
+
if (!_silent(`launchctl load -w "${mainPath}"`)) {
|
|
164
|
+
return { ok: false, path: mainPath,
|
|
165
|
+
message: "Plist written but launchctl load failed — it will still run at next login." };
|
|
166
|
+
}
|
|
167
|
+
} else if (unit.kind === "systemd") {
|
|
168
|
+
_silent("systemctl --user daemon-reload");
|
|
169
|
+
if (!_silent(`systemctl --user enable --now ${TIMER_NAME}`)) {
|
|
170
|
+
return { ok: false, path: mainPath,
|
|
171
|
+
message: "Timer written but systemctl enable failed — check 'systemctl --user' availability." };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { ok: true, path: mainPath,
|
|
175
|
+
message: "Auto-update enabled. ArkaOS checks npm daily and updates itself." };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function disable() {
|
|
179
|
+
const unit = unitFor(process.platform, { repoRoot: defaultRepoRoot(), home: homedir() });
|
|
180
|
+
const mainPath = unit.files[0].path;
|
|
181
|
+
if (unit.kind === "launchd") _silent(`launchctl unload "${mainPath}"`);
|
|
182
|
+
else if (unit.kind === "systemd") _silent(`systemctl --user disable --now ${TIMER_NAME}`);
|
|
183
|
+
for (const f of unit.files) if (existsSync(f.path)) rmSync(f.path);
|
|
184
|
+
// Persisted opt-out: future `npx arkaos update` runs must not re-enable.
|
|
185
|
+
mkdirSync(join(homedir(), ".arkaos"), { recursive: true });
|
|
186
|
+
writeFileSync(optoutPath(), new Date().toISOString(), "utf8");
|
|
187
|
+
return { ok: true, path: mainPath, message: "Auto-update disabled (opt-out persisted)." };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function status() {
|
|
191
|
+
let unit;
|
|
192
|
+
try {
|
|
193
|
+
unit = unitFor(process.platform, { repoRoot: defaultRepoRoot(), home: homedir() });
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return { installed: false, supported: false, optout: false, message: err.message };
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
installed: unit.files.every((f) => existsSync(f.path)),
|
|
199
|
+
supported: true,
|
|
200
|
+
optout: existsSync(optoutPath()),
|
|
201
|
+
kind: unit.kind,
|
|
202
|
+
path: unit.files[0].path,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Default-on wiring for install/update flows: enable unless the user
|
|
207
|
+
* opted out or the platform is unsupported. Never throws. */
|
|
208
|
+
export function ensureDefaultEnabled({ repoRoot = defaultRepoRoot() } = {}) {
|
|
209
|
+
const s = status();
|
|
210
|
+
if (!s.supported) return { action: "unsupported" };
|
|
211
|
+
if (s.optout) return { action: "optout" };
|
|
212
|
+
if (s.installed) {
|
|
213
|
+
// Refresh unit content in place (paths/schedule may change between
|
|
214
|
+
// versions) but do not force a reload storm on every update.
|
|
215
|
+
try {
|
|
216
|
+
const root = stableRoot({ repoRoot });
|
|
217
|
+
const unit = unitFor(process.platform, { repoRoot: root, home: homedir() });
|
|
218
|
+
for (const f of unit.files) writeFileSync(f.path, f.content, "utf8");
|
|
219
|
+
} catch {}
|
|
220
|
+
return { action: "already-enabled" };
|
|
221
|
+
}
|
|
222
|
+
const r = enable({ repoRoot });
|
|
223
|
+
return { action: r.ok ? "enabled" : "partial", message: r.message };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function autoupdate(args = []) {
|
|
227
|
+
const action = (args[0] || "status").toLowerCase();
|
|
228
|
+
if (action === "enable") {
|
|
229
|
+
const r = enable();
|
|
230
|
+
console.log(` ${r.ok ? "✓" : "⚠"} ${r.message}\n ${r.path}`);
|
|
231
|
+
} else if (action === "disable") {
|
|
232
|
+
const r = disable();
|
|
233
|
+
console.log(` ✓ ${r.message}`);
|
|
234
|
+
} else if (action === "run") {
|
|
235
|
+
// Foreground one-shot check — same script (and anchor) the daemon runs.
|
|
236
|
+
const script = join(stableRoot(), "scripts", "auto-update.sh");
|
|
237
|
+
try {
|
|
238
|
+
execSync(`/bin/bash "${script}" --force`, { stdio: "inherit" });
|
|
239
|
+
} catch { process.exitCode = 1; }
|
|
240
|
+
} else if (action === "status") {
|
|
241
|
+
const s = status();
|
|
242
|
+
if (!s.supported) console.log(` Auto-update not supported here: ${s.message}`);
|
|
243
|
+
else if (s.optout) console.log(` Auto-update: DISABLED by user opt-out\n (re-enable: npx arkaos autoupdate enable)`);
|
|
244
|
+
else console.log(` Auto-update: ${s.installed ? "ENABLED" : "disabled"} (${s.kind})\n ${s.path}`);
|
|
245
|
+
} else {
|
|
246
|
+
console.error(` Unknown autoupdate action: ${action}. Use enable | disable | status | run.`);
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
package/installer/cli.js
CHANGED
|
@@ -63,6 +63,7 @@ Usage:
|
|
|
63
63
|
npx arkaos migrate-user-data Move user data (~/.claude/skills/arka/ → ~/.arkaos/)
|
|
64
64
|
npx arkaos dashboard Start monitoring dashboard
|
|
65
65
|
npx arkaos autostart <enable|disable|status> Start dashboard on boot
|
|
66
|
+
npx arkaos autoupdate <enable|disable|status|run> Keep ArkaOS updated automatically (daily check + notification)
|
|
66
67
|
npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
|
|
67
68
|
npx arkaos models Model Fabric: which model runs each role
|
|
68
69
|
npx arkaos models set <role> <provider>/<model> Re-route a role
|
|
@@ -134,6 +135,12 @@ async function main() {
|
|
|
134
135
|
break;
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
case "autoupdate": {
|
|
139
|
+
const { autoupdate } = await import("./autoupdate.js");
|
|
140
|
+
await autoupdate(positionals.slice(1));
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
137
144
|
case "uninstall":
|
|
138
145
|
const { uninstall } = await import("./uninstall.js");
|
|
139
146
|
await uninstall();
|
|
@@ -35,7 +35,11 @@ import { join, basename } from "node:path";
|
|
|
35
35
|
// resolve_arkaos_root() checks, so a crash mid-deploy on a fresh install
|
|
36
36
|
// leaves a snapshot that fails validation instead of a core-only root
|
|
37
37
|
// that validates but lacks config/departments/knowledge.
|
|
38
|
-
|
|
38
|
+
// scripts/ joined in Foundation PR-1: the auto-update daemon's launchd/
|
|
39
|
+
// systemd unit anchors at the snapshot (a unit pointing into the npx
|
|
40
|
+
// cache dies silently on `npm cache clean` — QG blocker). Under 0.5MB
|
|
41
|
+
// after the __pycache__ filter.
|
|
42
|
+
const SNAPSHOT_DIRS = ["config", "departments", "knowledge", "scripts", "core"];
|
|
39
43
|
|
|
40
44
|
export function deployCoreSnapshot(arkaosRoot, installDir) {
|
|
41
45
|
if (!existsSync(join(arkaosRoot, "core", "sync", "__init__.py"))) return false;
|
package/installer/doctor.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getArkaosPython, getVenvPython, canImportCore, getRepoRoot, diagnoseVen
|
|
|
6
6
|
import { IS_WINDOWS, HOOK_EXT, CMD_FINDER } from "./platform.js";
|
|
7
7
|
import { checkNode, checkObsidian, checkOllama } from "./system-tools.js";
|
|
8
8
|
import { graphifyDoctor } from "./graphify.js";
|
|
9
|
+
import { status as autoupdateStatus } from "./autoupdate.js";
|
|
9
10
|
|
|
10
11
|
const INSTALL_DIR = join(homedir(), ".arkaos");
|
|
11
12
|
|
|
@@ -563,6 +564,16 @@ export const checks = [
|
|
|
563
564
|
check: () => statuslineConfigured(),
|
|
564
565
|
fix: () => "Run: npx arkaos install --force (redeploys and wires the statusline)",
|
|
565
566
|
},
|
|
567
|
+
{
|
|
568
|
+
name: "autoupdate",
|
|
569
|
+
description: "Auto-update daemon installed (or explicit user opt-out)",
|
|
570
|
+
severity: "warn",
|
|
571
|
+
check: () => {
|
|
572
|
+
const s = autoupdateStatus();
|
|
573
|
+
return !s.supported || s.optout || s.installed;
|
|
574
|
+
},
|
|
575
|
+
fix: () => "Run: npx arkaos autoupdate enable",
|
|
576
|
+
},
|
|
566
577
|
{
|
|
567
578
|
name: "hooks-wired",
|
|
568
579
|
description: "Hook chain referenced by ~/.claude/settings.json (governance live)",
|
package/installer/index.js
CHANGED
|
@@ -324,6 +324,17 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
324
324
|
console.log(` Warning: could not seed config.json (${err.message})`);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// Foundation PR-1 — auto-update daemon, default-on (opt-out persists
|
|
328
|
+
// across installs via ~/.arkaos/autoupdate.optout).
|
|
329
|
+
try {
|
|
330
|
+
const { ensureDefaultEnabled } = await import("./autoupdate.js");
|
|
331
|
+
const au = ensureDefaultEnabled({ repoRoot: ARKAOS_ROOT });
|
|
332
|
+
if (au.action === "enabled") console.log(` Auto-update daemon enabled (npx arkaos autoupdate status).`);
|
|
333
|
+
else if (au.action === "optout") console.log(` Auto-update daemon: user opt-out respected.`);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
console.log(` Warning: auto-update daemon not enabled (${err.message})`);
|
|
336
|
+
}
|
|
337
|
+
|
|
327
338
|
// PR28 v2.47.0 — scaffold the user-mutable files the discipline-arc
|
|
328
339
|
// commands depend on: redaction-clients.json (leak scanner config)
|
|
329
340
|
// and reorganize-proposals/ (daily proposal output). Idempotent.
|
package/installer/update.js
CHANGED
|
@@ -534,6 +534,17 @@ export async function update({ skillsFlag = "" } = {}) {
|
|
|
534
534
|
console.log(` ⚠ Could not set up Graphify (${err.message})`);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
// ── 7b. Auto-update daemon (Foundation PR-1) — default-on, opt-out kept ──
|
|
538
|
+
try {
|
|
539
|
+
const { ensureDefaultEnabled } = await import("./autoupdate.js");
|
|
540
|
+
const au = ensureDefaultEnabled({ repoRoot: ARKAOS_ROOT });
|
|
541
|
+
if (au.action === "enabled") console.log(" ✓ Auto-update daemon enabled (daily npm check + notification)");
|
|
542
|
+
else if (au.action === "already-enabled") console.log(" ✓ Auto-update daemon active");
|
|
543
|
+
else if (au.action === "optout") console.log(" · Auto-update daemon: user opt-out respected");
|
|
544
|
+
} catch (err) {
|
|
545
|
+
console.log(` ⚠ Auto-update daemon not enabled (${err.message})`);
|
|
546
|
+
}
|
|
547
|
+
|
|
537
548
|
// ── 8. Update manifest ──
|
|
538
549
|
console.log(" [8/8] Finalizing...");
|
|
539
550
|
manifest.version = VERSION;
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# ArkaOS — background auto-update (Foundation PR-1)
|
|
4
|
+
#
|
|
5
|
+
# Invoked daily by the io.wizardingcode.arkaos.updater unit (see
|
|
6
|
+
# installer/autoupdate.js) or manually via `npx arkaos autoupdate run`.
|
|
7
|
+
#
|
|
8
|
+
# Flow: registry check (curl, short timeout) → compare with the installed
|
|
9
|
+
# version (~/.arkaos/install-manifest.json) → headless `npx -y
|
|
10
|
+
# arkaos@latest update` → OS notification with the outcome. Project sync
|
|
11
|
+
# stays supervised: update.js resets sync-state.json, so the next Claude
|
|
12
|
+
# session surfaces [arka:update-available] and /arka update runs there.
|
|
13
|
+
#
|
|
14
|
+
# Every failure path logs and exits 0 — a broken check must never surface
|
|
15
|
+
# as a crashing login item.
|
|
16
|
+
# ============================================================================
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
ARKA_HOME="${HOME}/.arkaos"
|
|
20
|
+
LOG_DIR="${ARKA_HOME}/logs"
|
|
21
|
+
LOG="${LOG_DIR}/auto-update.log"
|
|
22
|
+
LOCK_DIR="${ARKA_HOME}/auto-update.lock"
|
|
23
|
+
MANIFEST="${ARKA_HOME}/install-manifest.json"
|
|
24
|
+
OPTOUT="${ARKA_HOME}/autoupdate.optout"
|
|
25
|
+
PROFILE="${ARKA_HOME}/profile.json"
|
|
26
|
+
REGISTRY_URL="https://registry.npmjs.org/arkaos/latest"
|
|
27
|
+
|
|
28
|
+
FORCE=0
|
|
29
|
+
[ "${1:-}" = "--force" ] && FORCE=1
|
|
30
|
+
|
|
31
|
+
mkdir -p "$LOG_DIR"
|
|
32
|
+
|
|
33
|
+
log() {
|
|
34
|
+
printf '%s %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$1" >> "$LOG"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Keep the log bounded (~1MB cap, keep the newest half).
|
|
38
|
+
rotate_log() {
|
|
39
|
+
local size
|
|
40
|
+
size=$(wc -c < "$LOG" 2>/dev/null || echo 0)
|
|
41
|
+
if [ "${size:-0}" -gt 1048576 ]; then
|
|
42
|
+
tail -c 524288 "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# JSON field reader: venv python → python3 → sed (last resort).
|
|
47
|
+
json_field() {
|
|
48
|
+
local file="$1" field="$2"
|
|
49
|
+
local py="${ARKA_HOME}/venv/bin/python"
|
|
50
|
+
[ -x "$py" ] || py="$(command -v python3 || true)"
|
|
51
|
+
if [ -n "$py" ]; then
|
|
52
|
+
"$py" -c "import json,sys; print(json.load(open(sys.argv[1])).get(sys.argv[2],''))" \
|
|
53
|
+
"$file" "$field" 2>/dev/null && return 0
|
|
54
|
+
fi
|
|
55
|
+
sed -n "s/.*\"${field}\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$file" 2>/dev/null | head -1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
notify() {
|
|
59
|
+
local msg="$1"
|
|
60
|
+
log "notify: $msg"
|
|
61
|
+
case "$(uname -s)" in
|
|
62
|
+
Darwin)
|
|
63
|
+
command -v osascript >/dev/null 2>&1 && \
|
|
64
|
+
osascript -e "display notification \"${msg}\" with title \"ArkaOS\"" >/dev/null 2>&1
|
|
65
|
+
;;
|
|
66
|
+
Linux)
|
|
67
|
+
command -v notify-send >/dev/null 2>&1 && notify-send "ArkaOS" "$msg" >/dev/null 2>&1
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Notification copy follows the installed profile language (pt → pt-PT).
|
|
73
|
+
LANG_CODE=""
|
|
74
|
+
[ -f "$PROFILE" ] && LANG_CODE="$(json_field "$PROFILE" language)"
|
|
75
|
+
|
|
76
|
+
msg_updated() {
|
|
77
|
+
if [ "$LANG_CODE" = "pt" ]; then
|
|
78
|
+
echo "Atualizado para v$1. Os projetos sincronizam na próxima sessão Claude."
|
|
79
|
+
else
|
|
80
|
+
echo "Updated to v$1. Projects sync on your next Claude session."
|
|
81
|
+
fi
|
|
82
|
+
}
|
|
83
|
+
msg_failed() {
|
|
84
|
+
if [ "$LANG_CODE" = "pt" ]; then
|
|
85
|
+
echo "Falha no auto-update (v$1). Corre: npx arkaos@latest update"
|
|
86
|
+
else
|
|
87
|
+
echo "Auto-update failed (v$1). Run: npx arkaos@latest update"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
rotate_log
|
|
92
|
+
|
|
93
|
+
# ── Opt-out and install guards ─────────────────────────────────────────
|
|
94
|
+
if [ -f "$OPTOUT" ]; then
|
|
95
|
+
log "skip: user opt-out marker present"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
if [ ! -f "$MANIFEST" ]; then
|
|
99
|
+
log "skip: no install-manifest.json — ArkaOS not installed"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ── Lock (mkdir is atomic); reclaim stale locks older than 2h ──────────
|
|
104
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
105
|
+
if [ -n "$(find "$LOCK_DIR" -maxdepth 0 -mmin +120 2>/dev/null)" ]; then
|
|
106
|
+
log "reclaiming stale lock"
|
|
107
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
108
|
+
mkdir "$LOCK_DIR" 2>/dev/null || { log "skip: lock contention"; exit 0; }
|
|
109
|
+
else
|
|
110
|
+
log "skip: another auto-update run holds the lock"
|
|
111
|
+
exit 0
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT
|
|
115
|
+
|
|
116
|
+
# launchd/systemd environments miss the node toolchain locations; append
|
|
117
|
+
# (not prepend — explicit PATH entries, e.g. test stubs, must win).
|
|
118
|
+
export PATH="${PATH}:/opt/homebrew/bin:/usr/local/bin"
|
|
119
|
+
|
|
120
|
+
INSTALLED="$(json_field "$MANIFEST" version)"
|
|
121
|
+
if [ -z "$INSTALLED" ]; then
|
|
122
|
+
log "skip: could not read installed version from manifest"
|
|
123
|
+
exit 0
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
127
|
+
log "skip: curl unavailable"
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
LATEST_JSON="$(curl -sf --max-time 15 "$REGISTRY_URL" 2>/dev/null || true)"
|
|
131
|
+
if [ -z "$LATEST_JSON" ]; then
|
|
132
|
+
log "skip: registry unreachable (offline?)"
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
TMP_JSON="${ARKA_HOME}/.autoupdate-latest.json"
|
|
136
|
+
printf '%s' "$LATEST_JSON" > "$TMP_JSON"
|
|
137
|
+
LATEST="$(json_field "$TMP_JSON" version)"
|
|
138
|
+
rm -f "$TMP_JSON"
|
|
139
|
+
if [ -z "$LATEST" ]; then
|
|
140
|
+
log "skip: could not parse registry response"
|
|
141
|
+
exit 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Only ever move FORWARD: a dev/prerelease install newer than the
|
|
145
|
+
# registry `latest` must not be silently downgraded (QG, Francisca).
|
|
146
|
+
# Ordering compare via python; degraded fallback is plain inequality.
|
|
147
|
+
is_newer() { # is_newer LATEST INSTALLED → exit 0 when LATEST > INSTALLED
|
|
148
|
+
local py="${ARKA_HOME}/venv/bin/python"
|
|
149
|
+
[ -x "$py" ] || py="$(command -v python3 || true)"
|
|
150
|
+
if [ -n "$py" ]; then
|
|
151
|
+
"$py" -c '
|
|
152
|
+
import re, sys
|
|
153
|
+
def key(v):
|
|
154
|
+
core = re.split(r"[-+]", v, maxsplit=1)[0]
|
|
155
|
+
nums = [int(x) for x in re.findall(r"\d+", core)[:3]]
|
|
156
|
+
nums += [0] * (3 - len(nums))
|
|
157
|
+
return (nums, "-" not in v) # prerelease sorts below its release
|
|
158
|
+
sys.exit(0 if key(sys.argv[1]) > key(sys.argv[2]) else 1)' "$1" "$2" 2>/dev/null
|
|
159
|
+
return $?
|
|
160
|
+
fi
|
|
161
|
+
[ "$1" != "$2" ]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if [ "$FORCE" -ne 1 ]; then
|
|
165
|
+
if [ "$LATEST" = "$INSTALLED" ]; then
|
|
166
|
+
log "up to date (v${INSTALLED})"
|
|
167
|
+
exit 0
|
|
168
|
+
fi
|
|
169
|
+
if ! is_newer "$LATEST" "$INSTALLED"; then
|
|
170
|
+
log "installed v${INSTALLED} is ahead of registry v${LATEST} — skip"
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if ! command -v npx >/dev/null 2>&1; then
|
|
176
|
+
log "skip: npx unavailable — cannot apply v${LATEST}"
|
|
177
|
+
exit 0
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
log "updating v${INSTALLED} → v${LATEST}"
|
|
181
|
+
if npx -y arkaos@latest update >> "$LOG" 2>&1; then
|
|
182
|
+
log "update to v${LATEST} succeeded"
|
|
183
|
+
notify "$(msg_updated "$LATEST")"
|
|
184
|
+
else
|
|
185
|
+
log "update to v${LATEST} FAILED (see log above)"
|
|
186
|
+
notify "$(msg_failed "$LATEST")"
|
|
187
|
+
fi
|
|
188
|
+
exit 0
|