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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { listBundleFiles, readBundleFile } from "../harness-bundle.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OpenCode adapter (Foundation PR-6). Deploys the generated harness
|
|
7
|
+
* bundle onto OpenCode's native surfaces (opencode.ai/docs):
|
|
8
|
+
*
|
|
9
|
+
* AGENTS.md instructions contract (same body as codex)
|
|
10
|
+
* agents/*.md curated agent cut (C-suite + squad leads)
|
|
11
|
+
* commands/*.md one /arka-<dept> router command per department
|
|
12
|
+
* opencode.json arka-tools MCP — merged NON-destructively
|
|
13
|
+
*
|
|
14
|
+
* Merge contract: user keys ALWAYS win. An existing `$schema`, any
|
|
15
|
+
* user-defined `mcp` server, or a user-modified `arka-tools` entry is
|
|
16
|
+
* never overwritten; a corrupt opencode.json is never clobbered (we
|
|
17
|
+
* skip with a warning instead — config-seed.js posture).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pure, exported for tests. Returns:
|
|
22
|
+
* { config, changed } merged object + whether anything was added
|
|
23
|
+
* null leave the file alone (corrupt input)
|
|
24
|
+
*/
|
|
25
|
+
export function mergeOpencodeConfig(existingText, fragmentText) {
|
|
26
|
+
let fragment;
|
|
27
|
+
try {
|
|
28
|
+
fragment = JSON.parse(fragmentText);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
let existing = {};
|
|
33
|
+
const trimmed = (existingText ?? "").trim();
|
|
34
|
+
if (trimmed !== "") {
|
|
35
|
+
try {
|
|
36
|
+
existing = JSON.parse(trimmed);
|
|
37
|
+
} catch {
|
|
38
|
+
return null; // corrupt user config — never clobber
|
|
39
|
+
}
|
|
40
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const merged = { ...existing };
|
|
45
|
+
let changed = false;
|
|
46
|
+
if (!merged.$schema && fragment.$schema) {
|
|
47
|
+
merged.$schema = fragment.$schema;
|
|
48
|
+
changed = true;
|
|
49
|
+
}
|
|
50
|
+
const userMcp =
|
|
51
|
+
merged.mcp && typeof merged.mcp === "object" && !Array.isArray(merged.mcp)
|
|
52
|
+
? merged.mcp
|
|
53
|
+
: {};
|
|
54
|
+
const mcp = { ...userMcp };
|
|
55
|
+
for (const [name, server] of Object.entries(fragment.mcp || {})) {
|
|
56
|
+
if (!(name in mcp)) {
|
|
57
|
+
mcp[name] = server;
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (changed && Object.keys(mcp).length > 0) merged.mcp = mcp;
|
|
62
|
+
return { config: merged, changed };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
configureHooks(config) {
|
|
67
|
+
// OpenCode has no shell-hook chain; "configure" means deploying the
|
|
68
|
+
// instruction/agent/command surfaces + the MCP config merge.
|
|
69
|
+
const agentsMd = readBundleFile("opencode", "AGENTS.md");
|
|
70
|
+
if (!agentsMd) {
|
|
71
|
+
console.warn(
|
|
72
|
+
" OpenCode bundle missing (harness/opencode/) — " +
|
|
73
|
+
"run scripts/harness_gen.py; skipping."
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
mkdirSync(config.configDir, { recursive: true });
|
|
78
|
+
writeFileSync(join(config.configDir, "AGENTS.md"), agentsMd);
|
|
79
|
+
|
|
80
|
+
let deployed = 0;
|
|
81
|
+
for (const sub of ["agents", "commands"]) {
|
|
82
|
+
const names = listBundleFiles("opencode", sub);
|
|
83
|
+
if (names.length === 0) continue;
|
|
84
|
+
mkdirSync(join(config.configDir, sub), { recursive: true });
|
|
85
|
+
for (const name of names) {
|
|
86
|
+
const content = readBundleFile("opencode", `${sub}/${name}`);
|
|
87
|
+
if (content === null) continue;
|
|
88
|
+
writeFileSync(join(config.configDir, sub, name), content);
|
|
89
|
+
deployed += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fragment = readBundleFile("opencode", "opencode.json");
|
|
94
|
+
if (fragment) {
|
|
95
|
+
const existingText = existsSync(config.settingsFile)
|
|
96
|
+
? readFileSync(config.settingsFile, "utf-8")
|
|
97
|
+
: "";
|
|
98
|
+
const result = mergeOpencodeConfig(existingText, fragment);
|
|
99
|
+
if (result === null) {
|
|
100
|
+
console.warn(
|
|
101
|
+
" opencode.json unreadable — left untouched " +
|
|
102
|
+
"(the merge is never destructive)."
|
|
103
|
+
);
|
|
104
|
+
} else if (result.changed) {
|
|
105
|
+
writeFileSync(
|
|
106
|
+
config.settingsFile,
|
|
107
|
+
JSON.stringify(result.config, null, 2) + "\n"
|
|
108
|
+
);
|
|
109
|
+
console.log(
|
|
110
|
+
" opencode.json updated (arka-tools MCP registered, user keys preserved)."
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log(
|
|
115
|
+
` OpenCode configured (AGENTS.md + ${deployed} agent/command files).`
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
};
|
package/installer/cli.js
CHANGED
|
@@ -21,6 +21,10 @@ const { values, positionals } = parseArgs({
|
|
|
21
21
|
force: { type: "boolean", short: "f" },
|
|
22
22
|
"no-system": { type: "boolean" },
|
|
23
23
|
"with-ollama": { type: "boolean" },
|
|
24
|
+
// Foundation PR-3 — `npx arkaos install --profile <essential|complete|local-ai>`.
|
|
25
|
+
// Declared so it lands in `values.profile` instead of a free
|
|
26
|
+
// positional under strict:false (the documented --fix lesson).
|
|
27
|
+
profile: { type: "string" },
|
|
24
28
|
// PR3.5 v3.74.1 — declared so `npx arkaos doctor --fix` lands in
|
|
25
29
|
// `values.fix` rather than as a free positional under strict:false.
|
|
26
30
|
// Eliminates the dead-branch fallback flagged by Marta in PR2's QG.
|
|
@@ -57,6 +61,7 @@ ArkaOS v${VERSION} — The Operating System for AI Agent Teams
|
|
|
57
61
|
Usage:
|
|
58
62
|
npx arkaos install Install ArkaOS in current environment
|
|
59
63
|
npx arkaos install --runtime <runtime> Install for specific runtime
|
|
64
|
+
npx arkaos install --profile <profile> Pick the install profile non-interactively
|
|
60
65
|
npx arkaos init Initialize project config (.arkaos.json)
|
|
61
66
|
npx arkaos update Update to latest version
|
|
62
67
|
npx arkaos migrate Migrate from v1 to v2
|
|
@@ -64,6 +69,7 @@ Usage:
|
|
|
64
69
|
npx arkaos dashboard Start monitoring dashboard
|
|
65
70
|
npx arkaos autostart <enable|disable|status> Start dashboard on boot
|
|
66
71
|
npx arkaos autoupdate <enable|disable|status|run> Keep ArkaOS updated automatically (daily check + notification)
|
|
72
|
+
npx arkaos menubar <enable|disable|status> Menu bar launcher (macOS — ▲ quick actions)
|
|
67
73
|
npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
|
|
68
74
|
npx arkaos models Model Fabric: which model runs each role
|
|
69
75
|
npx arkaos models set <role> <provider>/<model> Re-route a role
|
|
@@ -77,6 +83,9 @@ Options:
|
|
|
77
83
|
-r, --runtime <name> Target runtime: claude-code, codex, gemini, cursor
|
|
78
84
|
-p, --path <dir> Installation directory (default: auto-detect)
|
|
79
85
|
-f, --force Force reinstall, overwriting existing files
|
|
86
|
+
--profile <name> Install profile: essential (venv, hooks, skills,
|
|
87
|
+
dashboard, MCPs), complete (+ litellm[proxy], ffmpeg),
|
|
88
|
+
local-ai (+ Ollama, local execution model)
|
|
80
89
|
-v, --version Show version
|
|
81
90
|
-h, --help Show this help
|
|
82
91
|
|
|
@@ -100,7 +109,20 @@ Examples:
|
|
|
100
109
|
|
|
101
110
|
async function main() {
|
|
102
111
|
switch (command) {
|
|
103
|
-
case "install":
|
|
112
|
+
case "install": {
|
|
113
|
+
// Validate --profile up front (before runtime detection) so a
|
|
114
|
+
// typo fails fast instead of silently falling back to the wizard.
|
|
115
|
+
let profileFlag;
|
|
116
|
+
if (values.profile !== undefined) {
|
|
117
|
+
const { normalizeProfileFlag, INSTALL_PROFILES } = await import("./profile.js");
|
|
118
|
+
profileFlag = normalizeProfileFlag(values.profile);
|
|
119
|
+
if (!profileFlag) {
|
|
120
|
+
console.error(
|
|
121
|
+
`Invalid --profile "${values.profile}". Valid values: ${INSTALL_PROFILES.join(", ")}`
|
|
122
|
+
);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
104
126
|
const runtime = values.runtime || await detectRuntime();
|
|
105
127
|
await install({
|
|
106
128
|
runtime,
|
|
@@ -108,8 +130,10 @@ async function main() {
|
|
|
108
130
|
force: values.force,
|
|
109
131
|
skipSystem: values["no-system"],
|
|
110
132
|
withOllama: values["with-ollama"],
|
|
133
|
+
profileFlag,
|
|
111
134
|
});
|
|
112
135
|
break;
|
|
136
|
+
}
|
|
113
137
|
|
|
114
138
|
case "init": {
|
|
115
139
|
const { init } = await import("./init.js");
|
|
@@ -141,6 +165,12 @@ async function main() {
|
|
|
141
165
|
break;
|
|
142
166
|
}
|
|
143
167
|
|
|
168
|
+
case "menubar": {
|
|
169
|
+
const { menubar } = await import("./menubar.js");
|
|
170
|
+
await menubar(positionals.slice(1));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
case "uninstall":
|
|
145
175
|
const { uninstall } = await import("./uninstall.js");
|
|
146
176
|
await uninstall();
|
|
@@ -69,6 +69,24 @@ const RUNTIMES = [
|
|
|
69
69
|
skillsDir: () => join(homedir(), ".cursor", "skills"),
|
|
70
70
|
settingsFile: () => join(homedir(), ".cursor", "settings.json"),
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
// Foundation PR-6 — OpenCode first-class. Listed AFTER the existing
|
|
74
|
+
// runtimes so multi-runtime machines keep their current default
|
|
75
|
+
// (detection picks the first match).
|
|
76
|
+
id: "opencode",
|
|
77
|
+
name: "OpenCode",
|
|
78
|
+
detect: () => {
|
|
79
|
+
try {
|
|
80
|
+
execSync("opencode --version", { stdio: "pipe" });
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return existsSync(join(homedir(), ".config", "opencode"));
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
configDir: () => join(homedir(), ".config", "opencode"),
|
|
87
|
+
skillsDir: () => join(homedir(), ".config", "opencode", "commands"),
|
|
88
|
+
settingsFile: () => join(homedir(), ".config", "opencode", "opencode.json"),
|
|
89
|
+
},
|
|
72
90
|
];
|
|
73
91
|
|
|
74
92
|
export async function detectRuntime() {
|
package/installer/doctor.js
CHANGED
|
@@ -7,9 +7,40 @@ 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
9
|
import { status as autoupdateStatus } from "./autoupdate.js";
|
|
10
|
+
import { normalizeProfileFlag, profileIncludes } from "./profile.js";
|
|
11
|
+
import {
|
|
12
|
+
loadProfilesManifest,
|
|
13
|
+
parseExecutionModel,
|
|
14
|
+
resolveServicesForProfile,
|
|
15
|
+
} from "./services.js";
|
|
16
|
+
import { menubarHealthy } from "./menubar.js";
|
|
10
17
|
|
|
11
18
|
const INSTALL_DIR = join(homedir(), ".arkaos");
|
|
12
19
|
|
|
20
|
+
// ─── Install-profile awareness (Foundation PR-4) ────────────────────────
|
|
21
|
+
// Checks may declare `minProfile`; when the machine's persisted profile
|
|
22
|
+
// sits below it on the ladder (essential ⊂ complete ⊂ local-ai) the
|
|
23
|
+
// check reports "skipped (not in <profile> profile)" instead of a
|
|
24
|
+
// misleading warn — an essential machine without Ollama is healthy.
|
|
25
|
+
|
|
26
|
+
export function currentInstallProfile(
|
|
27
|
+
profilePath = join(INSTALL_DIR, "profile.json")
|
|
28
|
+
) {
|
|
29
|
+
try {
|
|
30
|
+
const profile = JSON.parse(readFileSync(profilePath, "utf-8"));
|
|
31
|
+
return normalizeProfileFlag(profile.installProfile) || "essential";
|
|
32
|
+
} catch {
|
|
33
|
+
return "essential";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Null when the check applies; otherwise the human-readable skip reason. */
|
|
38
|
+
export function checkSkipReason(check, activeProfile) {
|
|
39
|
+
if (!check.minProfile) return null;
|
|
40
|
+
if (profileIncludes(activeProfile, check.minProfile)) return null;
|
|
41
|
+
return `not in ${activeProfile} profile`;
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
// Resolve a single command via the platform-native locator. Returns true
|
|
14
45
|
// when the command is discoverable on PATH, false otherwise. stderr is
|
|
15
46
|
// suppressed through Node's stdio option so the probe does not print
|
|
@@ -414,6 +445,7 @@ export const checks = [
|
|
|
414
445
|
name: "ollama",
|
|
415
446
|
description: "Ollama present (optional — cognitive layer LLM runtime)",
|
|
416
447
|
severity: "warn",
|
|
448
|
+
minProfile: "local-ai",
|
|
417
449
|
check: () => checkOllama().installed,
|
|
418
450
|
fix: () => {
|
|
419
451
|
const s = checkOllama();
|
|
@@ -500,6 +532,7 @@ export const checks = [
|
|
|
500
532
|
name: "ffmpeg",
|
|
501
533
|
description: "FFmpeg present (video encode/cut for Hyperframes + transcription workflows)",
|
|
502
534
|
severity: "warn",
|
|
535
|
+
minProfile: "complete",
|
|
503
536
|
check: () => commandExists("ffmpeg"),
|
|
504
537
|
fix: () => "Install FFmpeg: brew install ffmpeg (macOS) / apt install ffmpeg (Linux) / winget install Gyan.FFmpeg (Windows)",
|
|
505
538
|
},
|
|
@@ -625,6 +658,120 @@ export const checks = [
|
|
|
625
658
|
check: () => companionPluginsInstalled(),
|
|
626
659
|
fix: () => "claude plugin marketplace add obra/superpowers-marketplace && claude plugin install superpowers@superpowers-marketplace; claude plugin marketplace add thedotmack/claude-mem && claude plugin install claude-mem@thedotmack",
|
|
627
660
|
},
|
|
661
|
+
// ─── Install-profile checks (Foundation PR-4) — all warn-only ─────────
|
|
662
|
+
{
|
|
663
|
+
name: "install-profile",
|
|
664
|
+
description: "Install profile valid (profile.json + install-profiles manifest)",
|
|
665
|
+
severity: "warn",
|
|
666
|
+
check: () => {
|
|
667
|
+
// A profile.json that predates PR-3 (no installProfile key) is
|
|
668
|
+
// valid — it means essential. An explicitly invalid value or an
|
|
669
|
+
// unloadable/unresolvable manifest is the failure.
|
|
670
|
+
const profilePath = join(INSTALL_DIR, "profile.json");
|
|
671
|
+
if (existsSync(profilePath)) {
|
|
672
|
+
try {
|
|
673
|
+
const profile = JSON.parse(readFileSync(profilePath, "utf-8"));
|
|
674
|
+
if (
|
|
675
|
+
profile.installProfile !== undefined &&
|
|
676
|
+
!normalizeProfileFlag(profile.installProfile)
|
|
677
|
+
) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const repoRoot = getRepoRoot();
|
|
685
|
+
if (!repoRoot) return true; // no repo reference — not a profile problem
|
|
686
|
+
try {
|
|
687
|
+
const manifest = loadProfilesManifest(repoRoot);
|
|
688
|
+
resolveServicesForProfile(currentInstallProfile(), manifest);
|
|
689
|
+
return true;
|
|
690
|
+
} catch {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
fix: () =>
|
|
695
|
+
"profile.json installProfile must be one of essential|complete|local-ai; refresh the manifest via: npx arkaos@latest update",
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
name: "litellm-proxy",
|
|
699
|
+
description: "LiteLLM proxy installed (gateway prerequisite — complete profile)",
|
|
700
|
+
severity: "warn",
|
|
701
|
+
minProfile: "complete",
|
|
702
|
+
check: () => {
|
|
703
|
+
const py = getArkaosPython();
|
|
704
|
+
if (!py) return false;
|
|
705
|
+
try {
|
|
706
|
+
execSync(`"${py}" -c "import litellm"`, { stdio: "ignore", timeout: 30000 });
|
|
707
|
+
return true;
|
|
708
|
+
} catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
fix: () =>
|
|
713
|
+
"Run: ~/.arkaos/venv/bin/pip install 'litellm[proxy]' (or: npx arkaos doctor --fix)",
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
name: "whisper",
|
|
717
|
+
description: "Whisper transcription installed (faster-whisper — complete profile)",
|
|
718
|
+
severity: "warn",
|
|
719
|
+
minProfile: "complete",
|
|
720
|
+
check: () => {
|
|
721
|
+
const py = getArkaosPython();
|
|
722
|
+
if (!py) return false;
|
|
723
|
+
try {
|
|
724
|
+
execSync(`"${py}" -c "import faster_whisper"`, { stdio: "ignore", timeout: 30000 });
|
|
725
|
+
return true;
|
|
726
|
+
} catch {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
fix: () =>
|
|
731
|
+
"Run: ~/.arkaos/venv/bin/pip install faster-whisper (or: npx arkaos doctor --fix)",
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: "ollama-execution-model",
|
|
735
|
+
description: "Local execution model pulled (Model Fabric — local-ai profile)",
|
|
736
|
+
severity: "warn",
|
|
737
|
+
minProfile: "local-ai",
|
|
738
|
+
check: () => {
|
|
739
|
+
const resolved = parseExecutionModel();
|
|
740
|
+
// No ollama execution role in models.yaml — nothing to verify.
|
|
741
|
+
if (!resolved) return true;
|
|
742
|
+
try {
|
|
743
|
+
const out = execSync("ollama list", {
|
|
744
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
745
|
+
timeout: 10000,
|
|
746
|
+
}).toString();
|
|
747
|
+
return out.split(/\r?\n/).some((line) => {
|
|
748
|
+
const token = line.trim().split(/\s+/)[0];
|
|
749
|
+
return (
|
|
750
|
+
token === resolved.model ||
|
|
751
|
+
(!resolved.model.includes(":") && token.split(":")[0] === resolved.model)
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
} catch {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
fix: () => {
|
|
759
|
+
const resolved = parseExecutionModel();
|
|
760
|
+
return resolved
|
|
761
|
+
? `Run: ollama pull ${resolved.model} (or: npx arkaos doctor --fix)`
|
|
762
|
+
: "Configure: npx arkaos models set execution ollama/<model>";
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "menubar",
|
|
767
|
+
description: "Menu bar launcher running (macOS — ▲ quick actions)",
|
|
768
|
+
severity: "warn",
|
|
769
|
+
// Live probe, not file decoration: menubarHealthy composes file
|
|
770
|
+
// presence + opt-out semantics + `launchctl list` (QG M2). Every
|
|
771
|
+
// branch is unit-tested via the injectable exec in menubar.test.js.
|
|
772
|
+
check: () => menubarHealthy(),
|
|
773
|
+
fix: () => "Run: npx arkaos menubar enable (opt-out permanently: npx arkaos menubar disable)",
|
|
774
|
+
},
|
|
628
775
|
];
|
|
629
776
|
|
|
630
777
|
// ─── Windows-only checks ───────────────────────────────────────────────
|
|
@@ -709,14 +856,50 @@ export async function doctor(options = {}) {
|
|
|
709
856
|
console.log(` ✗ Venv repair failed (${result.reason})`);
|
|
710
857
|
}
|
|
711
858
|
}
|
|
859
|
+
|
|
860
|
+
// ─── --fix: reconcile the install profile's services (PR-4) ──────
|
|
861
|
+
// Non-interactive: consent-free services (pip into our venv, model
|
|
862
|
+
// pull) install; consent-gated ones print the exact command. Never
|
|
863
|
+
// blocks — a reconcile error degrades to a single warning line.
|
|
864
|
+
try {
|
|
865
|
+
const repoRoot = getRepoRoot();
|
|
866
|
+
if (repoRoot) {
|
|
867
|
+
const activeProfile = currentInstallProfile();
|
|
868
|
+
console.log(` → Reconciling ${activeProfile} profile services`);
|
|
869
|
+
const { reconcileServices } = await import("./services.js");
|
|
870
|
+
const results = await reconcileServices({
|
|
871
|
+
profile: activeProfile,
|
|
872
|
+
repoRoot,
|
|
873
|
+
interactive: false,
|
|
874
|
+
log: (msg) => console.log(" " + String(msg).trim()),
|
|
875
|
+
});
|
|
876
|
+
for (const r of results) {
|
|
877
|
+
if (r.status === "installed") console.log(` ✓ ${r.label} installed`);
|
|
878
|
+
else if (r.status === "failed") console.log(` ✗ ${r.label} failed${r.hint ? ` — ${r.hint}` : ""}`);
|
|
879
|
+
else if (r.status === "skipped") console.log(` · ${r.label} skipped${r.hint ? ` — ${r.hint}` : ""}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
} catch (err) {
|
|
883
|
+
console.log(` ⚠ Service reconciliation skipped (${err.message})`);
|
|
884
|
+
}
|
|
712
885
|
console.log("");
|
|
713
886
|
}
|
|
714
887
|
|
|
888
|
+
const activeProfile = currentInstallProfile();
|
|
715
889
|
let passed = 0;
|
|
716
890
|
let warned = 0;
|
|
717
891
|
let failed = 0;
|
|
892
|
+
let skipped = 0;
|
|
718
893
|
|
|
719
894
|
for (const check of checks) {
|
|
895
|
+
// Profile gate (PR-4): below-profile checks are informational
|
|
896
|
+
// skips, not warnings — see checkSkipReason.
|
|
897
|
+
const skipReason = checkSkipReason(check, activeProfile);
|
|
898
|
+
if (skipReason) {
|
|
899
|
+
console.log(` \x1b[90m-\x1b[0m ${check.description} — skipped (${skipReason})`);
|
|
900
|
+
skipped++;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
720
903
|
// A single check that throws must not crash the rest of the doctor.
|
|
721
904
|
// Treat the exception as "check failed" and record a short hint so
|
|
722
905
|
// the user can see what blew up. Also keep any stack-trace noise
|
|
@@ -752,7 +935,8 @@ export async function doctor(options = {}) {
|
|
|
752
935
|
}
|
|
753
936
|
}
|
|
754
937
|
|
|
755
|
-
|
|
938
|
+
const skippedSuffix = skipped > 0 ? `, ${skipped} skipped` : "";
|
|
939
|
+
console.log(`\n Results: ${passed} passed, ${warned} warnings, ${failed} failures${skippedSuffix}\n`);
|
|
756
940
|
await securityAdvisory();
|
|
757
941
|
if (failed > 0) process.exit(1);
|
|
758
942
|
}
|
|
@@ -763,10 +947,26 @@ export async function doctor(options = {}) {
|
|
|
763
947
|
// (core.governance.harness_scanner_cli).
|
|
764
948
|
function doctorJson() {
|
|
765
949
|
const results = [];
|
|
950
|
+
const activeProfile = currentInstallProfile();
|
|
766
951
|
let passed = 0;
|
|
767
952
|
let warned = 0;
|
|
768
953
|
let failed = 0;
|
|
954
|
+
let skipped = 0;
|
|
769
955
|
for (const check of checks) {
|
|
956
|
+
// Profile gate (PR-4) — same skip semantics as the human run.
|
|
957
|
+
const skipReason = checkSkipReason(check, activeProfile);
|
|
958
|
+
if (skipReason) {
|
|
959
|
+
skipped++;
|
|
960
|
+
results.push({
|
|
961
|
+
name: check.name,
|
|
962
|
+
status: "skipped",
|
|
963
|
+
severity: check.severity,
|
|
964
|
+
description: check.description,
|
|
965
|
+
fix: "",
|
|
966
|
+
skipReason,
|
|
967
|
+
});
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
770
970
|
let ok = false;
|
|
771
971
|
let error = null;
|
|
772
972
|
try {
|
|
@@ -792,7 +992,7 @@ function doctorJson() {
|
|
|
792
992
|
}
|
|
793
993
|
console.log(JSON.stringify({
|
|
794
994
|
checks: results,
|
|
795
|
-
summary: { passed, warned, failed, total: results.length },
|
|
995
|
+
summary: { passed, warned, failed, skipped, total: results.length },
|
|
796
996
|
}));
|
|
797
997
|
if (failed > 0) process.exit(1);
|
|
798
998
|
}
|