@hiai-gg/hiai-opencode 0.1.0 → 0.1.2
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/AGENTS.md +0 -1
- package/ARCHITECTURE.md +0 -1
- package/dist/index.js +3 -3
- package/package.json +1 -6
- package/src/config/defaults.ts +1 -1
- package/dist/ast-grep-napi.linux-x64-gnu-d8zfa2q0.node +0 -0
- package/dist/ast-grep-napi.linux-x64-musl-0wywtr8y.node +0 -0
- package/dist/prompt-snapshots/bob.default.md +0 -514
- package/dist/prompt-snapshots/bob.gemini.md +0 -725
- package/dist/prompt-snapshots/bob.gpt-pro.md +0 -514
- package/dist/prompt-snapshots/coder.gpt-codex.md +0 -299
- package/dist/prompt-snapshots/coder.gpt-pro.md +0 -315
- package/dist/prompt-snapshots/coder.gpt.md +0 -315
- package/dist/prompt-snapshots/critic.md +0 -68
- package/dist/prompt-snapshots/guard.md +0 -599
- package/dist/prompt-snapshots/multimodal.md +0 -39
- package/dist/prompt-snapshots/platform-manager.md +0 -222
- package/dist/prompt-snapshots/quality-guardian.md +0 -32
- package/dist/prompt-snapshots/researcher.md +0 -29
- package/dist/prompt-snapshots/strategist.md +0 -573
- package/dist/prompt-snapshots/sub.md +0 -105
- package/scripts/check_docs.ts +0 -129
- package/scripts/doctor.ts +0 -522
- package/scripts/measure_prompts.ts +0 -193
- package/scripts/test_routing.ts +0 -294
package/scripts/check_docs.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { join, resolve, dirname } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const THIS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const ROOT = resolve(THIS_DIR, "..");
|
|
7
|
-
|
|
8
|
-
const PUBLIC_FILES = [
|
|
9
|
-
"README.md",
|
|
10
|
-
"AGENTS.md",
|
|
11
|
-
"ARCHITECTURE.md",
|
|
12
|
-
"LICENSE.md",
|
|
13
|
-
"hiai-opencode.json",
|
|
14
|
-
".env.example",
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const BLOCKED_PATTERNS = [
|
|
18
|
-
{ pattern: /[\u0400-\u04FF]/, label: "Cyrillic text" },
|
|
19
|
-
{ pattern: /C:\\hiai/i, label: "Private path C:\\hiai" },
|
|
20
|
-
{ pattern: /\/mnt\/ai_data/i, label: "Private path /mnt/ai_data" },
|
|
21
|
-
{ pattern: /\.claude(?!\w)/i, label: "Private .claude path" },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const DELETED_DOCS = [
|
|
25
|
-
"start.md",
|
|
26
|
-
"REGISTRY.md",
|
|
27
|
-
"AGENTS_INFO.md",
|
|
28
|
-
"docs/phase8-prompt-diet-report.md",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const BLOCKED_IN_CONFIG = [
|
|
32
|
-
{ pattern: /(?<![a-z0-9-])hiai-fast(?![a-z0-9-])/i, label: "Stale model alias: hiai-fast" },
|
|
33
|
-
{ pattern: /(?<![a-z0-9-])sonnet(?![a-z0-9-])(?!-)/i, label: "Stale model alias: sonnet (use claude-3.5-sonnet)" },
|
|
34
|
-
{ pattern: /(?<![a-z0-9-])haiku(?![a-z0-9-])/i, label: "Stale model alias: haiku" },
|
|
35
|
-
{ pattern: /claudeModelAliases/, label: "Stale config property: claudeModelAliases" },
|
|
36
|
-
{ pattern: /(?<![a-z0-9-])quality-guardian(?![a-z0-9-])/i, label: "Use public key: quality-guardian → critic" },
|
|
37
|
-
{ pattern: /(?<![a-z0-9-])platform-manager(?![a-z0-9-])/i, label: "Use public key: platform-manager → manager" },
|
|
38
|
-
{ pattern: /(?<![a-z0-9-])multimodal(?![a-z0-9-])/i, label: "Use public key: multimodal → vision" },
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
interface Violation {
|
|
42
|
-
file: string;
|
|
43
|
-
line: number;
|
|
44
|
-
text: string;
|
|
45
|
-
label: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function findViolations(filePath: string, content: string): Violation[] {
|
|
49
|
-
const violations: Violation[] = [];
|
|
50
|
-
try {
|
|
51
|
-
const content = readFileSync(filePath, "utf-8");
|
|
52
|
-
const lines = content.split("\n");
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < lines.length; i++) {
|
|
55
|
-
const line = lines[i];
|
|
56
|
-
|
|
57
|
-
for (const { pattern, label } of BLOCKED_PATTERNS) {
|
|
58
|
-
if (pattern.test(line)) {
|
|
59
|
-
violations.push({
|
|
60
|
-
file: filePath,
|
|
61
|
-
line: i + 1,
|
|
62
|
-
text: line.trim().slice(0, 120),
|
|
63
|
-
label,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const doc of DELETED_DOCS) {
|
|
70
|
-
if (content.includes(doc)) {
|
|
71
|
-
violations.push({
|
|
72
|
-
file: filePath,
|
|
73
|
-
line: 0,
|
|
74
|
-
text: `References deleted doc: ${doc}`,
|
|
75
|
-
label: "Deleted doc reference",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
// skip unreadable files
|
|
81
|
-
}
|
|
82
|
-
return violations;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function checkConfigPatterns(content: string, filePath: string): Violation[] {
|
|
86
|
-
const violations: Violation[] = [];
|
|
87
|
-
for (const item of BLOCKED_IN_CONFIG) {
|
|
88
|
-
if (item.pattern.test(content)) {
|
|
89
|
-
violations.push({
|
|
90
|
-
file: filePath,
|
|
91
|
-
line: 0,
|
|
92
|
-
text: `Contains blocked pattern: ${item.label}`,
|
|
93
|
-
label: "Stale config key",
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return violations;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const allViolations: Violation[] = [];
|
|
101
|
-
|
|
102
|
-
for (const fileName of PUBLIC_FILES) {
|
|
103
|
-
const filePath = join(ROOT, fileName);
|
|
104
|
-
try {
|
|
105
|
-
statSync(filePath);
|
|
106
|
-
const content = readFileSync(filePath, "utf-8");
|
|
107
|
-
const violations = fileName.endsWith(".json")
|
|
108
|
-
? [...findViolations(filePath, content), ...checkConfigPatterns(content, filePath)]
|
|
109
|
-
: findViolations(filePath, content);
|
|
110
|
-
allViolations.push(...violations);
|
|
111
|
-
} catch {
|
|
112
|
-
// file doesn't exist, skip
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (allViolations.length > 0) {
|
|
117
|
-
console.log("DOCS HYGIENE VIOLATIONS FOUND:\n");
|
|
118
|
-
for (const v of allViolations) {
|
|
119
|
-
const loc = v.line > 0 ? `:${v.line}` : "";
|
|
120
|
-
console.log(` [${v.label}] ${v.file}${loc}`);
|
|
121
|
-
console.log(` > ${v.text}`);
|
|
122
|
-
console.log();
|
|
123
|
-
}
|
|
124
|
-
console.log(`${allViolations.length} violation(s) found.`);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
} else {
|
|
127
|
-
console.log("DOCS HYGIENE CHECK PASSED — no violations found.");
|
|
128
|
-
process.exit(0);
|
|
129
|
-
}
|
package/scripts/doctor.ts
DELETED
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* hiai-opencode doctor
|
|
4
|
-
* Diagnostic command for open-source users to verify their local environment.
|
|
5
|
-
* Run: bun run doctor
|
|
6
|
-
*
|
|
7
|
-
* Exit codes:
|
|
8
|
-
* 0 = all required checks pass (optional warnings OK)
|
|
9
|
-
* 1 = required check failed
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { existsSync } from "node:fs";
|
|
13
|
-
import { statSync } from "node:fs";
|
|
14
|
-
import { readdirSync } from "node:fs";
|
|
15
|
-
import { execSync } from "node:child_process";
|
|
16
|
-
import { join, dirname } from "node:path";
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
18
|
-
|
|
19
|
-
const IS_WIN = process.platform === "win32";
|
|
20
|
-
|
|
21
|
-
const RED = "\x1b[31m";
|
|
22
|
-
const GREEN = "\x1b[32m";
|
|
23
|
-
const YELLOW = "\x1b[33m";
|
|
24
|
-
const BLUE = "\x1b[34m";
|
|
25
|
-
const DIM = "\x1b[2m";
|
|
26
|
-
const RESET = "\x1b[0m";
|
|
27
|
-
|
|
28
|
-
type CheckResult = "pass" | "warn" | "fail";
|
|
29
|
-
|
|
30
|
-
interface Check {
|
|
31
|
-
name: string;
|
|
32
|
-
result: CheckResult;
|
|
33
|
-
detail: string;
|
|
34
|
-
fix?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function green(s: string) { return `${GREEN}${s}${RESET}`; }
|
|
38
|
-
function yellow(s: string) { return `${YELLOW}${s}${RESET}`; }
|
|
39
|
-
function red(s: string) { return `${RED}${s}${RESET}`; }
|
|
40
|
-
function blue(s: string) { return `${BLUE}${s}${RESET}`; }
|
|
41
|
-
function dim(s: string) { return `${DIM}${s}${RESET}`; }
|
|
42
|
-
|
|
43
|
-
function execQuiet(cmd: string): string {
|
|
44
|
-
try {
|
|
45
|
-
const out = execSync(cmd, { encoding: "utf-8", timeout: 10000 } as Parameters<typeof execSync>[1]);
|
|
46
|
-
return String(out).trim();
|
|
47
|
-
} catch {
|
|
48
|
-
return "";
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function findBinary(name: string): string {
|
|
53
|
-
if (IS_WIN) {
|
|
54
|
-
const out = execQuiet(`where ${name}`);
|
|
55
|
-
if (out) return out.split("\n")[0].trim();
|
|
56
|
-
return "";
|
|
57
|
-
}
|
|
58
|
-
const out = execQuiet(`command -v ${name} || which ${name} 2>/dev/null || true`);
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function checkNode(): Check {
|
|
63
|
-
const version = execQuiet("node --version");
|
|
64
|
-
if (!version) {
|
|
65
|
-
return {
|
|
66
|
-
name: "Node.js",
|
|
67
|
-
result: "warn",
|
|
68
|
-
detail: "not found in PATH",
|
|
69
|
-
fix: "Install Node.js >=18 if you use npx-backed MCP/LSP helpers: https://nodejs.org/",
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const major = parseInt(version.replace("v", "").split(".")[0]);
|
|
73
|
-
if (major < 18) {
|
|
74
|
-
return {
|
|
75
|
-
name: "Node.js",
|
|
76
|
-
result: "warn",
|
|
77
|
-
detail: `found ${version}, requires >=18`,
|
|
78
|
-
fix: "Upgrade Node.js to >=18: https://nodejs.org/",
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
return { name: "Node.js", result: "pass", detail: `v${version} (>=18 OK)` };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function checkBun(): Check {
|
|
85
|
-
const currentBun = (process.versions as { bun?: string }).bun;
|
|
86
|
-
if (currentBun) {
|
|
87
|
-
const parts = currentBun.replace("v", "").split(".").map(Number);
|
|
88
|
-
const major = parts[0] ?? 0;
|
|
89
|
-
const minor = parts[1] ?? 0;
|
|
90
|
-
if (major > 1 || (major === 1 && minor >= 1)) {
|
|
91
|
-
return { name: "Bun", result: "pass", detail: `v${currentBun} (current runtime, >=1.1.0 OK)` };
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
name: "Bun",
|
|
95
|
-
result: "fail",
|
|
96
|
-
detail: `current runtime is ${currentBun}, requires >=1.1.0`,
|
|
97
|
-
fix: "Upgrade Bun: https://bun.sh/",
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const version = execQuiet("bun --version");
|
|
102
|
-
if (!version) {
|
|
103
|
-
return {
|
|
104
|
-
name: "Bun",
|
|
105
|
-
result: "fail",
|
|
106
|
-
detail: "not found in PATH",
|
|
107
|
-
fix: "Install Bun >=1.1.0: https://bun.sh/",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const parts = version.replace("v", "").split(".").map(Number);
|
|
111
|
-
const major = parts[0] ?? 0;
|
|
112
|
-
const minor = parts[1] ?? 0;
|
|
113
|
-
if (major < 1 || (major === 1 && minor < 1)) {
|
|
114
|
-
return {
|
|
115
|
-
name: "Bun",
|
|
116
|
-
result: "fail",
|
|
117
|
-
detail: `found ${version}, requires >=1.1.0`,
|
|
118
|
-
fix: "Upgrade Bun: https://bun.sh/",
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
return { name: "Bun", result: "pass", detail: `v${version} (>=1.1.0 OK)` };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function checkOpenCodeBinary(): Check {
|
|
125
|
-
const path = findBinary("opencode");
|
|
126
|
-
if (!path) {
|
|
127
|
-
return {
|
|
128
|
-
name: "OpenCode binary",
|
|
129
|
-
result: "warn",
|
|
130
|
-
detail: "not found in PATH",
|
|
131
|
-
fix: "Install OpenCode before runtime verification: https://opencode.ai/",
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
const version = execQuiet("opencode --version");
|
|
135
|
-
return {
|
|
136
|
-
name: "OpenCode binary",
|
|
137
|
-
result: "pass",
|
|
138
|
-
detail: `found at ${path}${version ? `, version ${version}` : ""}`,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function getScriptDir(): string {
|
|
143
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function checkBuild(): Check {
|
|
147
|
-
const distDir = join(getScriptDir(), "..", "dist");
|
|
148
|
-
if (!existsSync(distDir)) {
|
|
149
|
-
return {
|
|
150
|
-
name: "Plugin build",
|
|
151
|
-
result: "fail",
|
|
152
|
-
detail: "dist/ not found — run 'bun run build' first",
|
|
153
|
-
fix: "Run: bun run build",
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
const stat = statSync(distDir);
|
|
158
|
-
if (!stat.isDirectory()) {
|
|
159
|
-
return {
|
|
160
|
-
name: "Plugin build",
|
|
161
|
-
result: "fail",
|
|
162
|
-
detail: "dist/ exists but is not a directory",
|
|
163
|
-
fix: "Run: bun run build",
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const files = readdirSync(distDir);
|
|
167
|
-
if (!files.includes("index.js")) {
|
|
168
|
-
return {
|
|
169
|
-
name: "Plugin build",
|
|
170
|
-
result: "fail",
|
|
171
|
-
detail: "dist/ exists but index.js missing",
|
|
172
|
-
fix: "Run: bun run build",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
} catch {
|
|
176
|
-
return {
|
|
177
|
-
name: "Plugin build",
|
|
178
|
-
result: "fail",
|
|
179
|
-
detail: "dist/ exists but not readable",
|
|
180
|
-
fix: "Run: bun run build",
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
return { name: "Plugin build", result: "pass", detail: "dist/index.js present" };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function checkOpenCodePluginLoad(): Check {
|
|
187
|
-
if (!findBinary("opencode")) {
|
|
188
|
-
return {
|
|
189
|
-
name: "OpenCode plugin load",
|
|
190
|
-
result: "warn",
|
|
191
|
-
detail: "opencode binary not available — cannot verify plugin loading",
|
|
192
|
-
fix: "Install OpenCode to verify plugin loading",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
const out = execQuiet("opencode debug config 2>&1");
|
|
197
|
-
if (out.includes("@hiai-gg/hiai-opencode") || out.includes("hiai-opencode") || out.includes("HiaiOpenCode")) {
|
|
198
|
-
return {
|
|
199
|
-
name: "OpenCode plugin load",
|
|
200
|
-
result: "pass",
|
|
201
|
-
detail: "@hiai-gg/hiai-opencode plugin detected in OpenCode config",
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
name: "OpenCode plugin load",
|
|
206
|
-
result: "warn",
|
|
207
|
-
detail: "@hiai-gg/hiai-opencode plugin not detected in 'opencode debug config' output",
|
|
208
|
-
fix: "Add '@hiai-gg/hiai-opencode' to the plugins array in your OpenCode config; OpenCode installs npm plugins automatically at startup",
|
|
209
|
-
};
|
|
210
|
-
} catch (e: unknown) {
|
|
211
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
212
|
-
return {
|
|
213
|
-
name: "OpenCode plugin load",
|
|
214
|
-
result: "warn",
|
|
215
|
-
detail: `could not verify plugin load: ${errMsg.slice(0, 100)}`,
|
|
216
|
-
fix: "Ensure OpenCode config has '@hiai-gg/hiai-opencode' in plugins array",
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function checkExpectedAgents(): Check {
|
|
222
|
-
if (!findBinary("opencode")) {
|
|
223
|
-
return {
|
|
224
|
-
name: "Expected agent names",
|
|
225
|
-
result: "warn",
|
|
226
|
-
detail: "opencode not available — skipping agent name check",
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
const out = execQuiet("opencode debug config 2>&1");
|
|
231
|
-
const expected = ["Bob", "Coder", "Strategist", "Guard", "Critic", "Designer", "Researcher", "Manager", "Brainstormer", "Vision"];
|
|
232
|
-
const missing: string[] = [];
|
|
233
|
-
for (const agent of expected) {
|
|
234
|
-
if (!out.includes(agent)) missing.push(agent);
|
|
235
|
-
}
|
|
236
|
-
if (missing.length === 0) {
|
|
237
|
-
return {
|
|
238
|
-
name: "Expected agent names",
|
|
239
|
-
result: "pass",
|
|
240
|
-
detail: `all 10 visible agents present: ${expected.join(", ")}`,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
name: "Expected agent names",
|
|
245
|
-
result: "warn",
|
|
246
|
-
detail: `missing from debug output: ${missing.join(", ")}`,
|
|
247
|
-
fix: "Run 'opencode debug config' to diagnose",
|
|
248
|
-
};
|
|
249
|
-
} catch {
|
|
250
|
-
return {
|
|
251
|
-
name: "Expected agent names",
|
|
252
|
-
result: "warn",
|
|
253
|
-
detail: "could not verify agents — opencode debug config failed",
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function checkHiddenAgents(): Check {
|
|
259
|
-
if (!findBinary("opencode")) {
|
|
260
|
-
return {
|
|
261
|
-
name: "Hidden/system agents",
|
|
262
|
-
result: "warn",
|
|
263
|
-
detail: "opencode not available — skipping hidden agent check",
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
const out = execQuiet("opencode debug config 2>&1");
|
|
268
|
-
const shouldBeHidden = ["Sub", "Agent Skills"];
|
|
269
|
-
const exposed: string[] = [];
|
|
270
|
-
for (const agent of shouldBeHidden) {
|
|
271
|
-
if (out.includes(`"${agent}"`)) exposed.push(agent);
|
|
272
|
-
}
|
|
273
|
-
if (exposed.length === 0) {
|
|
274
|
-
return {
|
|
275
|
-
name: "Hidden/system agents",
|
|
276
|
-
result: "pass",
|
|
277
|
-
detail: `${shouldBeHidden.join(", ")} not shown as primary agents`,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
name: "Hidden/system agents",
|
|
282
|
-
result: "warn",
|
|
283
|
-
detail: `potentially visible as primary: ${exposed.join(", ")}`,
|
|
284
|
-
fix: "Check agent visibility in src/plugin-handlers/agent-config-handler.ts",
|
|
285
|
-
};
|
|
286
|
-
} catch {
|
|
287
|
-
return {
|
|
288
|
-
name: "Hidden/system agents",
|
|
289
|
-
result: "warn",
|
|
290
|
-
detail: "could not verify — opencode debug config failed",
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function checkMCPNames(): Check {
|
|
296
|
-
if (!findBinary("opencode")) {
|
|
297
|
-
return {
|
|
298
|
-
name: "Expected MCP names",
|
|
299
|
-
result: "warn",
|
|
300
|
-
detail: "opencode not available — skipping MCP name check",
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
try {
|
|
304
|
-
const out = execQuiet("opencode debug config 2>&1");
|
|
305
|
-
const expectedMCP = ["playwright", "stitch", "sequential-thinking", "firecrawl", "rag", "mempalace", "context7"];
|
|
306
|
-
const missing: string[] = [];
|
|
307
|
-
for (const mcp of expectedMCP) {
|
|
308
|
-
if (!out.includes(mcp)) missing.push(mcp);
|
|
309
|
-
}
|
|
310
|
-
if (missing.length === 0) {
|
|
311
|
-
return {
|
|
312
|
-
name: "Expected MCP names",
|
|
313
|
-
result: "pass",
|
|
314
|
-
detail: `all ${expectedMCP.length} MCPs registered: ${expectedMCP.join(", ")}`,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
name: "Expected MCP names",
|
|
319
|
-
result: "warn",
|
|
320
|
-
detail: `MCPs not detected in config: ${missing.join(", ")}`,
|
|
321
|
-
};
|
|
322
|
-
} catch {
|
|
323
|
-
return {
|
|
324
|
-
name: "Expected MCP names",
|
|
325
|
-
result: "warn",
|
|
326
|
-
detail: "could not verify MCP names",
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function checkEnvVars(): Check[] {
|
|
332
|
-
const checks: Check[] = [];
|
|
333
|
-
const modelProviders = [
|
|
334
|
-
"OPENROUTER_API_KEY",
|
|
335
|
-
"OPENAI_API_KEY",
|
|
336
|
-
"ANTHROPIC_API_KEY",
|
|
337
|
-
"DEEPSEEK_API_KEY",
|
|
338
|
-
"GLM_API_KEY",
|
|
339
|
-
"MINIMAX_API_KEY",
|
|
340
|
-
"QWEN_API_KEY",
|
|
341
|
-
];
|
|
342
|
-
const optional: [string, string][] = [
|
|
343
|
-
["OPENROUTER_API_KEY", "OpenRouter API key (recommended - plugin defaults use OpenRouter)"],
|
|
344
|
-
["OPENAI_API_KEY", "OpenAI API key (optional — for direct OpenAI models)"],
|
|
345
|
-
["ANTHROPIC_API_KEY", "Anthropic API key (optional — for direct Anthropic models)"],
|
|
346
|
-
["DEEPSEEK_API_KEY", "DeepSeek API key (optional)"],
|
|
347
|
-
["GLM_API_KEY", "GLM API key (optional)"],
|
|
348
|
-
["MINIMAX_API_KEY", "MiniMax API key (optional)"],
|
|
349
|
-
["QWEN_API_KEY", "Qwen API key (optional)"],
|
|
350
|
-
["STITCH_AI_API_KEY", "Stitch AI API key (optional — for Stitch MCP)"],
|
|
351
|
-
["FIRECRAWL_API_KEY", "Firecrawl API key (optional — for Firecrawl MCP web scraping)"],
|
|
352
|
-
["CONTEXT7_API_KEY", "Context7 API key (optional — for Context7 MCP code search)"],
|
|
353
|
-
["OLLAMA_BASE_URL", "Ollama base URL (optional — for local models)"],
|
|
354
|
-
["OLLAMA_MODEL", "Ollama model ID (optional — e.g. qwen3.5:4b)"],
|
|
355
|
-
["MEMPALACE_PYTHON", "Python command for MemPalace MCP (optional — e.g. python3 or python)"],
|
|
356
|
-
];
|
|
357
|
-
|
|
358
|
-
if (modelProviders.some((key) => Boolean(process.env[key]))) {
|
|
359
|
-
checks.push({
|
|
360
|
-
name: "ENV: model provider",
|
|
361
|
-
result: "pass",
|
|
362
|
-
detail: "at least one model provider key is set",
|
|
363
|
-
});
|
|
364
|
-
} else {
|
|
365
|
-
checks.push({
|
|
366
|
-
name: "ENV: model provider",
|
|
367
|
-
result: "warn",
|
|
368
|
-
detail: "no model provider keys detected",
|
|
369
|
-
fix: "Set OPENROUTER_API_KEY for default presets, or configure another provider/model in hiai-opencode.json",
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
for (const [key, desc] of optional) {
|
|
374
|
-
if (process.env[key]) {
|
|
375
|
-
checks.push({ name: `ENV: ${key}`, result: "pass", detail: `set` });
|
|
376
|
-
} else {
|
|
377
|
-
checks.push({
|
|
378
|
-
name: `ENV: ${key}`,
|
|
379
|
-
result: "warn",
|
|
380
|
-
detail: `${desc} — not set (optional)`,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return checks;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function checkLSPTools(): Check[] {
|
|
389
|
-
const checks: Check[] = [];
|
|
390
|
-
const tools: [string, string][] = [
|
|
391
|
-
["typescript-language-server", "TypeScript LSP (optional)"],
|
|
392
|
-
["pyright-langserver", "Python LSP (optional)"],
|
|
393
|
-
["svelteserver", "Svelte LSP (optional)"],
|
|
394
|
-
["bash-language-server", "Bash LSP (optional)"],
|
|
395
|
-
["eslint-lsp", "ESLint LSP (optional)"],
|
|
396
|
-
];
|
|
397
|
-
|
|
398
|
-
for (const [bin, desc] of tools) {
|
|
399
|
-
const path = findBinary(bin);
|
|
400
|
-
if (path) {
|
|
401
|
-
checks.push({ name: `LSP: ${bin}`, result: "pass", detail: `found` });
|
|
402
|
-
} else {
|
|
403
|
-
checks.push({
|
|
404
|
-
name: `LSP: ${bin}`,
|
|
405
|
-
result: "warn",
|
|
406
|
-
detail: `${desc} — not found`,
|
|
407
|
-
fix: `Install ${bin} or configure path in hiai-opencode.json lsp section`,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return checks;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function printResult(c: Check) {
|
|
416
|
-
const icon = c.result === "pass" ? green("✓") : c.result === "warn" ? yellow("⚠") : red("✗");
|
|
417
|
-
console.log(` ${icon} ${blue(c.name.padEnd(28))} ${c.detail}`);
|
|
418
|
-
if (c.fix) {
|
|
419
|
-
console.log(` ${dim("→")} ${c.fix}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function printTable(checks: Check[]) {
|
|
424
|
-
for (const c of checks) {
|
|
425
|
-
printResult(c);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function main() {
|
|
430
|
-
console.log();
|
|
431
|
-
console.log(blue("═".repeat(60)));
|
|
432
|
-
console.log(blue(" hiai-opencode doctor"));
|
|
433
|
-
console.log(blue("═".repeat(60)));
|
|
434
|
-
console.log();
|
|
435
|
-
|
|
436
|
-
let pass = 0, warn = 0, fail = 0;
|
|
437
|
-
|
|
438
|
-
console.log(dim("── Runtime ─────────────────────────────────────────────"));
|
|
439
|
-
const node = checkNode();
|
|
440
|
-
node.result === "pass" ? pass++ : node.result === "warn" ? warn++ : fail++;
|
|
441
|
-
printResult(node);
|
|
442
|
-
|
|
443
|
-
const bun = checkBun();
|
|
444
|
-
bun.result === "pass" ? pass++ : bun.result === "warn" ? warn++ : fail++;
|
|
445
|
-
printResult(bun);
|
|
446
|
-
|
|
447
|
-
const opencode = checkOpenCodeBinary();
|
|
448
|
-
opencode.result === "pass" ? pass++ : opencode.result === "warn" ? warn++ : fail++;
|
|
449
|
-
printResult(opencode);
|
|
450
|
-
|
|
451
|
-
console.log();
|
|
452
|
-
console.log(dim("── Build ────────────────────────────────────────────────"));
|
|
453
|
-
const build = checkBuild();
|
|
454
|
-
build.result === "pass" ? pass++ : build.result === "warn" ? warn++ : fail++;
|
|
455
|
-
printResult(build);
|
|
456
|
-
|
|
457
|
-
console.log();
|
|
458
|
-
console.log(dim("── Plugin Loading ────────────────────────────────────────"));
|
|
459
|
-
const plugin = checkOpenCodePluginLoad();
|
|
460
|
-
plugin.result === "pass" ? pass++ : plugin.result === "warn" ? warn++ : fail++;
|
|
461
|
-
printResult(plugin);
|
|
462
|
-
|
|
463
|
-
console.log();
|
|
464
|
-
console.log(dim("── Agent Visibility ───────────────────────────────────────"));
|
|
465
|
-
const agents = checkExpectedAgents();
|
|
466
|
-
agents.result === "pass" ? pass++ : agents.result === "warn" ? warn++ : fail++;
|
|
467
|
-
printResult(agents);
|
|
468
|
-
|
|
469
|
-
const hidden = checkHiddenAgents();
|
|
470
|
-
hidden.result === "pass" ? pass++ : hidden.result === "warn" ? warn++ : fail++;
|
|
471
|
-
printResult(hidden);
|
|
472
|
-
|
|
473
|
-
console.log();
|
|
474
|
-
console.log(dim("── MCP Registration ───────────────────────────────────────"));
|
|
475
|
-
const mcp = checkMCPNames();
|
|
476
|
-
mcp.result === "pass" ? pass++ : mcp.result === "warn" ? warn++ : fail++;
|
|
477
|
-
printResult(mcp);
|
|
478
|
-
|
|
479
|
-
console.log();
|
|
480
|
-
console.log(dim("── Environment Variables ──────────────────────────────────"));
|
|
481
|
-
const envChecks = checkEnvVars();
|
|
482
|
-
for (const c of envChecks) {
|
|
483
|
-
c.result === "pass" ? pass++ : c.result === "warn" ? warn++ : fail++;
|
|
484
|
-
printResult(c);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
console.log();
|
|
488
|
-
console.log(dim("── LSP Tools (optional) ───────────────────────────────────"));
|
|
489
|
-
const lspChecks = checkLSPTools();
|
|
490
|
-
for (const c of lspChecks) {
|
|
491
|
-
c.result === "pass" ? pass++ : c.result === "warn" ? warn++ : fail++;
|
|
492
|
-
printResult(c);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
console.log();
|
|
496
|
-
console.log(blue("─".repeat(60)));
|
|
497
|
-
const total = pass + warn + fail;
|
|
498
|
-
console.log(
|
|
499
|
-
` Results: ${green(`${pass} pass`)} ${yellow(`${warn} warn`)} ${red(`${fail} fail`)} (total ${total})`
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
if (fail > 0) {
|
|
503
|
-
console.log();
|
|
504
|
-
console.log(red(` Status: FAIL — ${fail} required check(s) must be fixed`));
|
|
505
|
-
console.log();
|
|
506
|
-
process.exit(1);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (warn > 0) {
|
|
510
|
-
console.log();
|
|
511
|
-
console.log(yellow(` Status: WARN — ${warn} optional check(s) missing (core OK)`));
|
|
512
|
-
console.log();
|
|
513
|
-
process.exit(0);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
console.log();
|
|
517
|
-
console.log(green(` Status: PASS — all checks OK`));
|
|
518
|
-
console.log();
|
|
519
|
-
process.exit(0);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
main();
|