@hiai-gg/hiai-opencode 0.1.5 → 0.1.7
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/.env.example +21 -8
- package/AGENTS.md +60 -6
- package/ARCHITECTURE.md +6 -3
- package/LICENSE.md +0 -1
- package/README.md +113 -33
- package/assets/cli/hiai-opencode.mjs +668 -7
- package/assets/mcp/mempalace.mjs +159 -25
- package/config/hiai-opencode.schema.json +29 -3
- package/dist/agents/agent-skills.d.ts +7 -0
- package/dist/agents/bob/default.d.ts +1 -0
- package/dist/agents/bob/gemini.d.ts +1 -0
- package/dist/agents/bob/gpt-pro.d.ts +1 -0
- package/dist/agents/brainstormer.d.ts +7 -0
- package/dist/agents/coder/gpt-codex.d.ts +1 -1
- package/dist/agents/coder/gpt-pro.d.ts +1 -0
- package/dist/agents/coder/gpt.d.ts +2 -1
- package/dist/agents/designer.d.ts +7 -0
- package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
- package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
- package/dist/agents/strategist/gemini.d.ts +1 -0
- package/dist/agents/strategist/gpt.d.ts +1 -0
- package/dist/agents/types.d.ts +3 -1
- package/dist/config/index.d.ts +0 -1
- package/dist/config/platform-schema.d.ts +34 -6
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/hooks.d.ts +0 -2
- package/dist/config/schema/index.d.ts +0 -2
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -9
- package/dist/config/types.d.ts +4 -4
- package/dist/create-hooks.d.ts +0 -2
- package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +2 -0
- package/dist/features/builtin-skills/skills/website-copywriting.d.ts +2 -0
- package/dist/hooks/agent-usage-reminder/constants.d.ts +1 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -1
- package/dist/index.js +7719 -153698
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -2
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -3
- package/dist/plugin/startup-diagnostics.d.ts +1 -0
- package/dist/shared/logger.d.ts +2 -0
- package/dist/shared/mcp-static-export.d.ts +22 -0
- package/dist/shared/mode-routing.d.ts +6 -0
- package/dist/tools/ast-grep/constants.d.ts +1 -1
- package/dist/tools/ast-grep/environment-check.d.ts +1 -5
- package/dist/tools/ast-grep/language-support.d.ts +0 -1
- package/dist/tools/ast-grep/types.d.ts +1 -2
- package/dist/tools/delegate-task/git-categories.d.ts +2 -0
- package/dist/tools/delegate-task/sub-agent.d.ts +2 -0
- package/dist/tools/skill-mcp/constants.d.ts +1 -1
- package/hiai-opencode.json +50 -19
- package/package.json +10 -5
- package/src/agents/agent-skills.ts +70 -0
- package/src/agents/bob/default.ts +7 -1
- package/src/agents/bob/gemini.ts +1 -0
- package/src/agents/bob/gpt-pro.ts +3 -1
- package/src/agents/bob.ts +3 -0
- package/src/agents/brainstormer.ts +72 -0
- package/src/agents/builtin-agents.ts +59 -3
- package/src/agents/coder/gpt-codex.ts +5 -3
- package/src/agents/coder/gpt-pro.ts +4 -2
- package/src/agents/coder/gpt.ts +3 -1
- package/src/agents/critic/agent.ts +1 -0
- package/src/agents/designer.ts +70 -0
- package/src/agents/dynamic-agent-category-skills-guide.ts +6 -0
- package/src/agents/dynamic-agent-core-sections.ts +36 -0
- package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
- package/src/agents/guard/default.ts +1 -0
- package/src/agents/guard/gemini.ts +1 -0
- package/src/agents/guard/gpt.ts +1 -0
- package/src/agents/platform-manager.ts +17 -1
- package/src/agents/prompt-library/platform.ts +34 -0
- package/src/agents/researcher.ts +1 -0
- package/src/agents/strategist/gemini.ts +1 -0
- package/src/agents/strategist/gpt.ts +1 -0
- package/src/agents/types.ts +4 -1
- package/src/agents/ui.ts +1 -0
- package/src/config/defaults.ts +45 -13
- package/src/config/index.ts +0 -1
- package/src/config/model-slots-and-export.test.ts +73 -0
- package/src/config/platform-schema.ts +3 -3
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/hooks.ts +0 -2
- package/src/config/schema/index.ts +0 -2
- package/src/config/schema/oh-my-opencode-config.ts +0 -5
- package/src/config/types.ts +4 -4
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/doctor.ts +43 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
- package/src/features/builtin-skills/skills/index.ts +2 -0
- package/src/features/builtin-skills/skills/website-copywriting.ts +41 -0
- package/src/features/builtin-skills/skills.test.ts +8 -0
- package/src/features/builtin-skills/skills.ts +12 -1
- package/src/features/skill-mcp-manager/AGENTS.md +1 -1
- package/src/hooks/agent-usage-reminder/constants.ts +4 -4
- package/src/hooks/index.ts +0 -2
- package/src/hooks/keyword-detector/ultrawork/default.ts +18 -18
- package/src/hooks/keyword-detector/ultrawork/gemini.ts +21 -21
- package/src/hooks/keyword-detector/ultrawork/gpt.ts +6 -8
- package/src/hooks/keyword-detector/ultrawork/planner.ts +5 -5
- package/src/index.ts +8 -78
- package/src/internals/plugins/subtask2/commands/manifest.ts +2 -6
- package/src/internals/plugins/subtask2/hooks/command-hooks.ts +2 -2
- package/src/internals/plugins/subtask2/hooks/message-hooks.ts +1 -1
- package/src/internals/plugins/subtask2/parsing/parallel.ts +13 -10
- package/src/mcp/index.ts +0 -1
- package/src/mcp/registry.ts +27 -0
- package/src/plugin/chat-message.ts +0 -2
- package/src/plugin/hooks/create-session-hooks.ts +0 -17
- package/src/plugin/startup-diagnostics.ts +27 -0
- package/src/plugin-handlers/agent-config-handler.ts +3 -2
- package/src/plugin-handlers/mcp-config-handler.test.ts +63 -0
- package/src/plugin-handlers/mcp-config-handler.ts +29 -14
- package/src/plugin-handlers/strategist-agent-config-builder.ts +1 -1
- package/src/shared/agent-display-names.test.ts +9 -0
- package/src/shared/agent-display-names.ts +5 -0
- package/src/shared/log-legacy-plugin-startup-warning.ts +6 -8
- package/src/shared/logger.ts +8 -0
- package/src/shared/mcp-static-export.ts +119 -0
- package/src/shared/migration/agent-names.ts +8 -0
- package/src/shared/migration/hook-names.ts +1 -1
- package/src/shared/mode-routing.test.ts +88 -0
- package/src/shared/mode-routing.ts +30 -0
- package/src/shared/startup-diagnostics.ts +6 -7
- package/src/tools/ast-grep/constants.ts +1 -1
- package/src/tools/ast-grep/environment-check.ts +2 -32
- package/src/tools/ast-grep/language-support.ts +0 -3
- package/src/tools/ast-grep/types.ts +1 -2
- package/src/tools/call-omo-agent/tools.ts +11 -4
- package/src/tools/delegate-task/anthropic-categories.ts +3 -3
- package/src/tools/delegate-task/builtin-categories.ts +2 -0
- package/src/tools/delegate-task/categories.test.ts +87 -0
- package/src/tools/delegate-task/category-resolver.ts +8 -9
- package/src/tools/delegate-task/git-categories.ts +30 -0
- package/src/tools/delegate-task/model-string-parser.test.ts +90 -0
- package/src/tools/delegate-task/openai-categories.ts +26 -22
- package/src/tools/delegate-task/sub-agent.ts +10 -0
- package/src/tools/delegate-task/subagent-discovery.test.ts +123 -0
- package/src/tools/delegate-task/subagent-resolver.ts +18 -1
- package/src/tools/skill-mcp/constants.ts +1 -1
- package/src/tools/skill-mcp/tools.test.ts +44 -0
- package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
- package/dist/config/loader.test.d.ts +0 -1
- package/dist/config/models.d.ts +0 -13
- package/dist/config/schema/websearch.d.ts +0 -13
- package/dist/hooks/no-bob-gpt/hook.d.ts +0 -16
- package/dist/hooks/no-bob-gpt/index.d.ts +0 -1
- package/dist/hooks/no-coder-non-gpt/hook.d.ts +0 -20
- package/dist/hooks/no-coder-non-gpt/index.d.ts +0 -1
- package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
- package/dist/internals/plugins/websearch-cited/index.d.ts +0 -17
- package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
- package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
- package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
- package/dist/mcp/grep-app.d.ts +0 -6
- package/dist/mcp/omo-mcp-index.d.ts +0 -10
- package/dist/mcp/websearch.d.ts +0 -11
- package/src/config/schema/websearch.ts +0 -15
- package/src/hooks/no-bob-gpt/hook.ts +0 -56
- package/src/hooks/no-bob-gpt/index.ts +0 -1
- package/src/hooks/no-coder-non-gpt/hook.ts +0 -67
- package/src/hooks/no-coder-non-gpt/index.ts +0 -1
- package/src/internals/plugins/websearch-cited/LICENSE +0 -214
- package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
- package/src/internals/plugins/websearch-cited/google.ts +0 -749
- package/src/internals/plugins/websearch-cited/index.ts +0 -306
- package/src/internals/plugins/websearch-cited/openai.ts +0 -407
- package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
- package/src/internals/plugins/websearch-cited/types.ts +0 -7
- package/src/mcp/grep-app.ts +0 -6
- package/src/mcp/omo-mcp-index.ts +0 -30
- package/src/mcp/websearch.ts +0 -44
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
5
5
|
import { dirname, join } from "node:path"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
6
7
|
import { homedir } from "node:os"
|
|
7
8
|
import { parse } from "jsonc-parser"
|
|
9
|
+
import { createHash } from "node:crypto"
|
|
10
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
|
11
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
8
12
|
|
|
9
13
|
const DEFAULT_RAG_URL = "http://localhost:9002/tools/search"
|
|
14
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..")
|
|
15
|
+
const MCP_EXPORT_MARKER = "hiai-opencode"
|
|
10
16
|
|
|
11
17
|
const MCP_REGISTRY = {
|
|
12
18
|
playwright: {
|
|
@@ -52,10 +58,16 @@ function usage() {
|
|
|
52
58
|
console.log(`hiai-opencode
|
|
53
59
|
|
|
54
60
|
Usage:
|
|
61
|
+
hiai-opencode doctor
|
|
55
62
|
hiai-opencode mcp-status
|
|
63
|
+
hiai-opencode export-mcp [path]
|
|
64
|
+
hiai-opencode diagnose [path]
|
|
56
65
|
|
|
57
66
|
Commands:
|
|
67
|
+
doctor Full install/runtime diagnostic: MCP status + static export freshness + provider/skills/agents/LSP checks + MCP tool probes.
|
|
58
68
|
mcp-status Check hiai-opencode MCP configuration, keys, and local runtimes.
|
|
69
|
+
export-mcp Write a static .mcp.json for hosts whose mcp list ignores plugin runtime MCP.
|
|
70
|
+
diagnose Collect full diagnostic bundle to file (local only, no remote sending).
|
|
59
71
|
`)
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -106,6 +118,254 @@ function loadConfig() {
|
|
|
106
118
|
return { path: null, config: {} }
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
function candidateOpenCodeConfigPaths() {
|
|
122
|
+
const cwd = process.cwd()
|
|
123
|
+
const paths = [
|
|
124
|
+
join(cwd, ".opencode", "opencode.json"),
|
|
125
|
+
join(cwd, ".opencode", "opencode.jsonc"),
|
|
126
|
+
join(homedir(), ".config", "opencode", "opencode.json"),
|
|
127
|
+
join(homedir(), ".config", "opencode", "opencode.jsonc"),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
131
|
+
paths.push(join(process.env.APPDATA, "opencode", "opencode.json"))
|
|
132
|
+
paths.push(join(process.env.APPDATA, "opencode", "opencode.jsonc"))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return paths
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function checkOpenCodePluginRegistration() {
|
|
139
|
+
for (const path of candidateOpenCodeConfigPaths()) {
|
|
140
|
+
if (!existsSync(path)) continue
|
|
141
|
+
try {
|
|
142
|
+
const parsed = parse(readFileSync(path, "utf-8")) ?? {}
|
|
143
|
+
const pluginEntries = Array.isArray(parsed?.plugin) ? parsed.plugin : []
|
|
144
|
+
const plugins = pluginEntries
|
|
145
|
+
.map((entry) => typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : "")
|
|
146
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
|
147
|
+
|
|
148
|
+
if (plugins.includes("list")) {
|
|
149
|
+
return {
|
|
150
|
+
level: "warn",
|
|
151
|
+
detail: `${path} contains plugin: [\"list\"] which can block MCP loading. Replace with [\"@hiai-gg/hiai-opencode\"].`,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (plugins.includes("@hiai-gg/hiai-opencode")) {
|
|
156
|
+
return {
|
|
157
|
+
level: "ok",
|
|
158
|
+
detail: `plugin registered in ${path}`,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
level: "warn",
|
|
164
|
+
detail: `${path} found but @hiai-gg/hiai-opencode is not registered`,
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
level: "warn",
|
|
169
|
+
detail: `failed to parse ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
level: "warn",
|
|
176
|
+
detail: "no opencode.json/opencode.jsonc found in project or global config paths",
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function enabled(config, name) {
|
|
181
|
+
return config?.mcp?.[name]?.enabled ?? MCP_REGISTRY[name]?.defaultEnabled ?? true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function assetPath(...segments) {
|
|
185
|
+
return join(PACKAGE_ROOT, "assets", ...segments)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function createMcpExport(config) {
|
|
189
|
+
const servers = {}
|
|
190
|
+
|
|
191
|
+
if (enabled(config, "playwright")) {
|
|
192
|
+
servers.playwright = {
|
|
193
|
+
command: "node",
|
|
194
|
+
args: [assetPath("mcp", "playwright.mjs")],
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (enabled(config, "stitch")) {
|
|
199
|
+
servers.stitch = {
|
|
200
|
+
type: "http",
|
|
201
|
+
url: "https://stitch.googleapis.com/mcp",
|
|
202
|
+
headers: {
|
|
203
|
+
"X-Goog-Api-Key": config?.auth?.stitch || "${STITCH_AI_API_KEY}",
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (enabled(config, "sequential-thinking")) {
|
|
209
|
+
servers["sequential-thinking"] = {
|
|
210
|
+
command: "node",
|
|
211
|
+
args: [
|
|
212
|
+
assetPath("runtime", "npm-package-runner.mjs"),
|
|
213
|
+
"@modelcontextprotocol/server-sequential-thinking",
|
|
214
|
+
],
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (enabled(config, "firecrawl")) {
|
|
219
|
+
servers.firecrawl = {
|
|
220
|
+
command: "node",
|
|
221
|
+
args: [assetPath("runtime", "npm-package-runner.mjs"), "firecrawl-mcp"],
|
|
222
|
+
env: {
|
|
223
|
+
FIRECRAWL_API_KEY: config?.auth?.firecrawl || "${FIRECRAWL_API_KEY}",
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (enabled(config, "rag")) {
|
|
229
|
+
servers.rag = {
|
|
230
|
+
command: "node",
|
|
231
|
+
args: [assetPath("mcp", "rag.mjs")],
|
|
232
|
+
env: {
|
|
233
|
+
OPENCODE_RAG_URL:
|
|
234
|
+
process.env.OPENCODE_RAG_URL
|
|
235
|
+
|| resolveEnvTemplate(config?.mcp?.rag?.environment?.OPENCODE_RAG_URL)
|
|
236
|
+
|| DEFAULT_RAG_URL,
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (enabled(config, "mempalace")) {
|
|
242
|
+
const mempalacePython =
|
|
243
|
+
process.env.MEMPALACE_PYTHON?.trim()
|
|
244
|
+
|| resolveEnvTemplate(config?.mcp?.mempalace?.pythonPath?.trim())
|
|
245
|
+
|
|
246
|
+
servers.mempalace = {
|
|
247
|
+
command: "node",
|
|
248
|
+
args: [assetPath("mcp", "mempalace.mjs"), "--palace", "./.opencode/palace"],
|
|
249
|
+
env: mempalacePython
|
|
250
|
+
? { MEMPALACE_PYTHON: mempalacePython }
|
|
251
|
+
: undefined,
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (enabled(config, "context7")) {
|
|
256
|
+
servers.context7 = {
|
|
257
|
+
type: "http",
|
|
258
|
+
url: "https://mcp.context7.com/mcp",
|
|
259
|
+
headers: config?.auth?.context7 || process.env.CONTEXT7_API_KEY
|
|
260
|
+
? { "X-API-KEY": config?.auth?.context7 || "${CONTEXT7_API_KEY}" }
|
|
261
|
+
: undefined,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
_meta: {
|
|
267
|
+
generatedBy: MCP_EXPORT_MARKER,
|
|
268
|
+
version: 1,
|
|
269
|
+
generatedAt: new Date().toISOString(),
|
|
270
|
+
},
|
|
271
|
+
mcpServers: Object.fromEntries(
|
|
272
|
+
Object.entries(servers).map(([name, value]) => [
|
|
273
|
+
name,
|
|
274
|
+
Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined)),
|
|
275
|
+
]),
|
|
276
|
+
),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isManagedStaticMcpFile(path) {
|
|
281
|
+
if (!existsSync(path)) return false
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"))
|
|
285
|
+
return parsed?._meta?.generatedBy === MCP_EXPORT_MARKER
|
|
286
|
+
} catch {
|
|
287
|
+
return false
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function stableHash(value) {
|
|
292
|
+
return createHash("sha256").update(value).digest("hex")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function readExistingStaticMcp(path) {
|
|
296
|
+
if (!existsSync(path)) return null
|
|
297
|
+
try {
|
|
298
|
+
return JSON.parse(readFileSync(path, "utf-8"))
|
|
299
|
+
} catch {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function checkStaticMcpFreshness(outputPath, config) {
|
|
305
|
+
const expected = createMcpExport(config)
|
|
306
|
+
const existing = readExistingStaticMcp(outputPath)
|
|
307
|
+
if (!existing) {
|
|
308
|
+
return { status: "missing", detail: `${outputPath} is missing` }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const expectedNormalized = {
|
|
312
|
+
...expected,
|
|
313
|
+
_meta: { ...expected._meta, generatedAt: "<normalized>" },
|
|
314
|
+
}
|
|
315
|
+
const existingNormalized = {
|
|
316
|
+
...existing,
|
|
317
|
+
_meta: existing._meta ? { ...existing._meta, generatedAt: "<normalized>" } : undefined,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const expectedHash = stableHash(JSON.stringify(expectedNormalized))
|
|
321
|
+
const existingHash = stableHash(JSON.stringify(existingNormalized))
|
|
322
|
+
const managed = existing?._meta?.generatedBy === MCP_EXPORT_MARKER
|
|
323
|
+
|
|
324
|
+
if (expectedHash === existingHash) {
|
|
325
|
+
return {
|
|
326
|
+
status: "fresh",
|
|
327
|
+
detail: `${outputPath} is up to date${managed ? " (managed)" : ""}`,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!managed) {
|
|
332
|
+
return {
|
|
333
|
+
status: "drift-unmanaged",
|
|
334
|
+
detail: `${outputPath} differs and is not managed by ${MCP_EXPORT_MARKER}`,
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
status: "stale",
|
|
340
|
+
detail: `${outputPath} is stale (run: hiai-opencode export-mcp ${outputPath})`,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function exportMcp(outputPath = join(process.cwd(), ".mcp.json")) {
|
|
345
|
+
const { path, config, error } = loadConfig()
|
|
346
|
+
if (error) {
|
|
347
|
+
console.error(`Config parse warning: ${error}`)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const output = createMcpExport(config)
|
|
351
|
+
const mode = process.env.HIAI_OPENCODE_EXPORT_MCP_MODE?.trim().toLowerCase() || "safe"
|
|
352
|
+
const force = mode === "force"
|
|
353
|
+
|
|
354
|
+
if (existsSync(outputPath) && !isManagedStaticMcpFile(outputPath) && !force) {
|
|
355
|
+
console.error(
|
|
356
|
+
`Refusing to overwrite non-managed ${outputPath}. ` +
|
|
357
|
+
`Set HIAI_OPENCODE_EXPORT_MCP_MODE=force to override.`,
|
|
358
|
+
)
|
|
359
|
+
process.exit(1)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
mkdirSync(dirname(outputPath), { recursive: true })
|
|
363
|
+
writeFileSync(outputPath, `${JSON.stringify(output, null, 2)}\n`)
|
|
364
|
+
console.log(`Wrote ${outputPath}`)
|
|
365
|
+
console.log(`Source config: ${path ?? "defaults"}`)
|
|
366
|
+
console.log(`Servers: ${Object.keys(output.mcpServers).join(", ") || "(none)"}`)
|
|
367
|
+
}
|
|
368
|
+
|
|
109
369
|
function hasCommand(command, args = ["--version"]) {
|
|
110
370
|
const result = spawnSync(command, args, {
|
|
111
371
|
stdio: "ignore",
|
|
@@ -133,10 +393,41 @@ function checkNodeNpx() {
|
|
|
133
393
|
}
|
|
134
394
|
}
|
|
135
395
|
|
|
136
|
-
function
|
|
396
|
+
function resolveVenvPythonCandidates(basePath) {
|
|
397
|
+
if (!basePath) return []
|
|
398
|
+
if (process.platform === "win32") {
|
|
399
|
+
return [
|
|
400
|
+
join(basePath, ".venv", "Scripts", "python.exe"),
|
|
401
|
+
join(basePath, "venv", "Scripts", "python.exe"),
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return [
|
|
406
|
+
join(basePath, ".venv", "bin", "python"),
|
|
407
|
+
join(basePath, ".venv", "bin", "python3"),
|
|
408
|
+
join(basePath, "venv", "bin", "python"),
|
|
409
|
+
join(basePath, "venv", "bin", "python3"),
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function pythonCandidates(config) {
|
|
137
414
|
const configured = process.env.MEMPALACE_PYTHON?.trim()
|
|
415
|
+
|| resolveEnvTemplate(config?.mcp?.mempalace?.pythonPath?.trim())
|
|
138
416
|
const candidates = []
|
|
139
417
|
if (configured) candidates.push(configured)
|
|
418
|
+
|
|
419
|
+
for (const candidate of resolveVenvPythonCandidates(process.cwd())) {
|
|
420
|
+
if (existsSync(candidate)) candidates.push(candidate)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (const candidate of resolveVenvPythonCandidates(PACKAGE_ROOT)) {
|
|
424
|
+
if (existsSync(candidate)) candidates.push(candidate)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const candidate of resolveVenvPythonCandidates(join(homedir(), ".config", "opencode", "plugins", "hiai-opencode"))) {
|
|
428
|
+
if (existsSync(candidate)) candidates.push(candidate)
|
|
429
|
+
}
|
|
430
|
+
|
|
140
431
|
if (process.platform === "win32") {
|
|
141
432
|
candidates.push("py", "python", "python3")
|
|
142
433
|
} else {
|
|
@@ -158,18 +449,18 @@ function canImportMempalace(command) {
|
|
|
158
449
|
return result.status === 0
|
|
159
450
|
}
|
|
160
451
|
|
|
161
|
-
function checkMempalace() {
|
|
452
|
+
function checkMempalace(config) {
|
|
162
453
|
if (hasCommand(process.platform === "win32" ? "uv.exe" : "uv")) {
|
|
163
454
|
return { level: "ok", detail: "uv available" }
|
|
164
455
|
}
|
|
165
456
|
|
|
166
|
-
for (const candidate of pythonCandidates()) {
|
|
457
|
+
for (const candidate of pythonCandidates(config)) {
|
|
167
458
|
if (canImportMempalace(candidate)) {
|
|
168
459
|
return { level: "ok", detail: `${candidate} with mempalace available` }
|
|
169
460
|
}
|
|
170
461
|
}
|
|
171
462
|
|
|
172
|
-
const hasPython = pythonCandidates().some((candidate) =>
|
|
463
|
+
const hasPython = pythonCandidates(config).some((candidate) =>
|
|
173
464
|
hasCommand(candidate, candidate === "py" ? ["-3", "--version"] : ["--version"]),
|
|
174
465
|
)
|
|
175
466
|
|
|
@@ -211,6 +502,247 @@ function checkRemoteOptionalKey(_config, name) {
|
|
|
211
502
|
return { level: "ok", detail: "remote endpoint configured" }
|
|
212
503
|
}
|
|
213
504
|
|
|
505
|
+
function detectMempalacePythonSource(config) {
|
|
506
|
+
const envPython = process.env.MEMPALACE_PYTHON?.trim()
|
|
507
|
+
if (envPython) {
|
|
508
|
+
return { source: "env:MEMPALACE_PYTHON", value: envPython }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const cfgPython = resolveEnvTemplate(config?.mcp?.mempalace?.pythonPath?.trim())
|
|
512
|
+
if (cfgPython) {
|
|
513
|
+
return { source: "config:mcp.mempalace.pythonPath", value: cfgPython }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const candidates = pythonCandidates(config)
|
|
517
|
+
if (candidates.length > 0) {
|
|
518
|
+
const resolved = candidates.find((candidate) =>
|
|
519
|
+
hasCommand(candidate, candidate === "py" ? ["-3", "--version"] : ["--version"]),
|
|
520
|
+
)
|
|
521
|
+
if (resolved) {
|
|
522
|
+
return { source: "auto-detect", value: resolved }
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return { source: "none", value: "" }
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function parseProviderFromModelId(modelId) {
|
|
530
|
+
if (!modelId || typeof modelId !== "string") return null
|
|
531
|
+
const normalized = modelId.trim()
|
|
532
|
+
if (!normalized) return null
|
|
533
|
+
const directProvider = normalized.split("/")[0]
|
|
534
|
+
if (directProvider === "openrouter") {
|
|
535
|
+
const routedProvider = normalized.split("/")[1]
|
|
536
|
+
return routedProvider || "openrouter"
|
|
537
|
+
}
|
|
538
|
+
return directProvider
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function collectConfiguredProviders(config) {
|
|
542
|
+
const providerSet = new Set()
|
|
543
|
+
const modelEntries = Object.values(config?.models ?? {})
|
|
544
|
+
for (const entry of modelEntries) {
|
|
545
|
+
const modelId = typeof entry === "string" ? entry : entry?.model
|
|
546
|
+
const provider = parseProviderFromModelId(modelId)
|
|
547
|
+
if (provider) providerSet.add(provider)
|
|
548
|
+
}
|
|
549
|
+
return [...providerSet].sort()
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function checkOpenCodeConnectVisibility(config) {
|
|
553
|
+
const providers = collectConfiguredProviders(config)
|
|
554
|
+
const opencodeBinary = process.platform === "win32" ? "opencode.cmd" : "opencode"
|
|
555
|
+
const opencodeAvailable = hasCommand(opencodeBinary, ["--version"])
|
|
556
|
+
if (!opencodeAvailable) {
|
|
557
|
+
return {
|
|
558
|
+
level: "warn",
|
|
559
|
+
detail: `opencode binary not available in PATH; cannot inspect Connect providers. Configured model providers: ${providers.join(", ") || "(none)"}`,
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const commands = [
|
|
564
|
+
[opencodeBinary, ["connect", "list", "--json"]],
|
|
565
|
+
[opencodeBinary, ["connect", "list"]],
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
for (const [binary, args] of commands) {
|
|
569
|
+
const result = spawnSync(binary, args, { encoding: "utf-8", timeout: 15000, shell: process.platform === "win32" })
|
|
570
|
+
if (result.status === 0) {
|
|
571
|
+
const out = (result.stdout || "").trim()
|
|
572
|
+
const summary = out ? out.split("\n").slice(0, 3).join(" | ") : "connect list ok"
|
|
573
|
+
return {
|
|
574
|
+
level: "ok",
|
|
575
|
+
detail: `OpenCode Connect visible. Configured model providers: ${providers.join(", ") || "(none)"}; connect summary: ${summary}`,
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
level: "warn",
|
|
582
|
+
detail: `Could not read OpenCode Connect state. Configured model providers: ${providers.join(", ") || "(none)"}`,
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function getSkillRegistryPath() {
|
|
587
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
588
|
+
return join(process.env.APPDATA, "opencode", ".hiai", "skill-registry.json")
|
|
589
|
+
}
|
|
590
|
+
return join(homedir(), ".config", "opencode", ".hiai", "skill-registry.json")
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function checkSkillMaterialization() {
|
|
594
|
+
const registryPath = getSkillRegistryPath()
|
|
595
|
+
if (!existsSync(registryPath)) {
|
|
596
|
+
return { level: "warn", detail: `skill registry missing: ${registryPath}` }
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const parsed = JSON.parse(readFileSync(registryPath, "utf-8"))
|
|
601
|
+
const summary = parsed?.summary
|
|
602
|
+
const total = summary?.total ?? 0
|
|
603
|
+
const builtin = summary?.builtin ?? 0
|
|
604
|
+
const plugin = summary?.plugin ?? 0
|
|
605
|
+
return {
|
|
606
|
+
level: "ok",
|
|
607
|
+
detail: `materialized skills: total=${total}, builtin=${builtin}, plugin=${plugin}`,
|
|
608
|
+
}
|
|
609
|
+
} catch (error) {
|
|
610
|
+
return {
|
|
611
|
+
level: "warn",
|
|
612
|
+
detail: `failed to parse skill registry: ${error instanceof Error ? error.message : String(error)}`,
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function getAgentSummary(config) {
|
|
618
|
+
const visible = [
|
|
619
|
+
"Bob",
|
|
620
|
+
"Coder",
|
|
621
|
+
"Strategist",
|
|
622
|
+
"Guard",
|
|
623
|
+
"Critic",
|
|
624
|
+
"Designer",
|
|
625
|
+
"Researcher",
|
|
626
|
+
"Manager",
|
|
627
|
+
"Brainstormer",
|
|
628
|
+
"Vision",
|
|
629
|
+
]
|
|
630
|
+
const hidden = ["Agent Skills", "Sub", "build", "plan"]
|
|
631
|
+
const modelCount = Object.keys(config?.models ?? {}).length
|
|
632
|
+
return {
|
|
633
|
+
level: modelCount >= 10 ? "ok" : "warn",
|
|
634
|
+
detail: `visible=${visible.length} [${visible.join(", ")}]; hidden=${hidden.length} [${hidden.join(", ")}]; model slots configured=${modelCount}/10`,
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function getLspDefaults() {
|
|
639
|
+
return {
|
|
640
|
+
typescript: ["typescript-language-server", "--stdio"],
|
|
641
|
+
svelte: ["svelteserver", "--stdio"],
|
|
642
|
+
eslint: ["node", join(PACKAGE_ROOT, "assets", "runtime", "npm-package-runner.mjs"), "eslint-lsp", "--stdio"],
|
|
643
|
+
bash: ["node", join(PACKAGE_ROOT, "assets", "runtime", "npm-package-runner.mjs"), "bash-language-server", "start"],
|
|
644
|
+
pyright: ["pyright-langserver", "--stdio"],
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function checkLspAvailability(config) {
|
|
649
|
+
const defaults = getLspDefaults()
|
|
650
|
+
const results = []
|
|
651
|
+
|
|
652
|
+
for (const [name, command] of Object.entries(defaults)) {
|
|
653
|
+
const enabled = config?.lsp?.[name]?.enabled ?? true
|
|
654
|
+
if (!enabled) {
|
|
655
|
+
results.push(`⚪ ${name}: disabled`)
|
|
656
|
+
continue
|
|
657
|
+
}
|
|
658
|
+
const binary = command[0]
|
|
659
|
+
const args = binary === "node" ? ["--version"] : ["--version"]
|
|
660
|
+
const ok = hasCommand(binary, args)
|
|
661
|
+
results.push(`${ok ? "✅" : "⚠️ "} ${name}: ${ok ? "runtime available" : `${binary} not found`}`)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
level: results.some((line) => line.startsWith("⚠️")) ? "warn" : "ok",
|
|
666
|
+
detail: results.join(" | "),
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function probeStdioMcp(serverName, serverConfig) {
|
|
671
|
+
const command = serverConfig?.command
|
|
672
|
+
const args = serverConfig?.args ?? []
|
|
673
|
+
if (!command) {
|
|
674
|
+
return { level: "warn", detail: `${serverName}: missing command` }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const env = { ...process.env, ...(serverConfig?.env ?? {}) }
|
|
678
|
+
const client = new Client(
|
|
679
|
+
{ name: "hiai-opencode-doctor", version: "0.1.0" },
|
|
680
|
+
{ capabilities: { tools: {}, prompts: {}, resources: {} } },
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
const transport = new StdioClientTransport({
|
|
684
|
+
command,
|
|
685
|
+
args,
|
|
686
|
+
env,
|
|
687
|
+
stderr: "pipe",
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
const timeoutMs = 12000
|
|
691
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeoutMs))
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
await Promise.race([client.connect(transport), timeout])
|
|
695
|
+
const toolsResponse = await Promise.race([client.listTools(), timeout])
|
|
696
|
+
const count = toolsResponse?.tools?.length ?? 0
|
|
697
|
+
await client.close()
|
|
698
|
+
return { level: "ok", detail: `${serverName}: reachable, tools=${count}` }
|
|
699
|
+
} catch (error) {
|
|
700
|
+
try { await client.close() } catch {}
|
|
701
|
+
return {
|
|
702
|
+
level: "warn",
|
|
703
|
+
detail: `${serverName}: probe failed (${error instanceof Error ? error.message : String(error)})`,
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function probeRemoteMcp(serverName, serverConfig) {
|
|
709
|
+
const url = serverConfig?.url
|
|
710
|
+
if (!url) return { level: "warn", detail: `${serverName}: missing url` }
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const controller = new AbortController()
|
|
714
|
+
const timeout = setTimeout(() => controller.abort(), 4000)
|
|
715
|
+
const response = await fetch(url, { method: "GET", signal: controller.signal })
|
|
716
|
+
clearTimeout(timeout)
|
|
717
|
+
if (response.status < 500) {
|
|
718
|
+
return { level: "ok", detail: `${serverName}: endpoint reachable (${response.status})` }
|
|
719
|
+
}
|
|
720
|
+
return { level: "warn", detail: `${serverName}: endpoint returned ${response.status}` }
|
|
721
|
+
} catch (error) {
|
|
722
|
+
return {
|
|
723
|
+
level: "warn",
|
|
724
|
+
detail: `${serverName}: endpoint probe failed (${error instanceof Error ? error.message : String(error)})`,
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function probeMcpServers(config) {
|
|
730
|
+
const payload = createMcpExport(config)
|
|
731
|
+
const results = []
|
|
732
|
+
for (const [name, server] of Object.entries(payload.mcpServers ?? {})) {
|
|
733
|
+
if (server.command) {
|
|
734
|
+
results.push(await probeStdioMcp(name, server))
|
|
735
|
+
continue
|
|
736
|
+
}
|
|
737
|
+
if (server.type === "http") {
|
|
738
|
+
results.push(await probeRemoteMcp(name, server))
|
|
739
|
+
continue
|
|
740
|
+
}
|
|
741
|
+
results.push({ level: "warn", detail: `${name}: unsupported server shape` })
|
|
742
|
+
}
|
|
743
|
+
return results
|
|
744
|
+
}
|
|
745
|
+
|
|
214
746
|
function hasEnvOrAuth(config, envName, authKey) {
|
|
215
747
|
if (process.env[envName]?.trim()) return true
|
|
216
748
|
if (authKey && config?.auth?.[authKey]?.trim()) return true
|
|
@@ -223,11 +755,13 @@ function statusIcon(level) {
|
|
|
223
755
|
return "❌"
|
|
224
756
|
}
|
|
225
757
|
|
|
226
|
-
async function mcpStatus() {
|
|
758
|
+
async function mcpStatus(options = {}) {
|
|
227
759
|
const { path, config, error } = loadConfig()
|
|
228
|
-
|
|
760
|
+
const staticMcpPath = join(process.cwd(), ".mcp.json")
|
|
761
|
+
console.log(options.doctor ? "hiai-opencode doctor" : "hiai-opencode mcp-status")
|
|
229
762
|
console.log(`Config: ${path ?? "not found; using defaults"}`)
|
|
230
763
|
if (error) console.log(`Config parse warning: ${error}`)
|
|
764
|
+
console.log(`Static MCP export: ${staticMcpPath}`)
|
|
231
765
|
console.log("")
|
|
232
766
|
console.log("MCP Servers:")
|
|
233
767
|
|
|
@@ -251,6 +785,118 @@ async function mcpStatus() {
|
|
|
251
785
|
const result = await entry.check(config, name)
|
|
252
786
|
console.log(`${statusIcon(result.level)} ${name.padEnd(20)} - ${result.detail}`)
|
|
253
787
|
}
|
|
788
|
+
|
|
789
|
+
if (options.doctor) {
|
|
790
|
+
console.log("")
|
|
791
|
+
console.log("Doctor Checks:")
|
|
792
|
+
|
|
793
|
+
const freshness = checkStaticMcpFreshness(staticMcpPath, config)
|
|
794
|
+
const freshIcon = freshness.status === "fresh" ? "✅" : freshness.status === "missing" ? "⚠️ " : "❌"
|
|
795
|
+
console.log(`${freshIcon} static .mcp.json freshness - ${freshness.detail}`)
|
|
796
|
+
|
|
797
|
+
const connect = checkOpenCodeConnectVisibility(config)
|
|
798
|
+
console.log(`${statusIcon(connect.level)} OpenCode Connect visibility - ${connect.detail}`)
|
|
799
|
+
|
|
800
|
+
const pluginRegistration = checkOpenCodePluginRegistration()
|
|
801
|
+
console.log(`${statusIcon(pluginRegistration.level)} OpenCode plugin registration - ${pluginRegistration.detail}`)
|
|
802
|
+
|
|
803
|
+
const skills = checkSkillMaterialization()
|
|
804
|
+
console.log(`${statusIcon(skills.level)} Skill materialization - ${skills.detail}`)
|
|
805
|
+
|
|
806
|
+
const agents = getAgentSummary(config)
|
|
807
|
+
console.log(`${statusIcon(agents.level)} Agent count and naming - ${agents.detail}`)
|
|
808
|
+
|
|
809
|
+
const lsp = checkLspAvailability(config)
|
|
810
|
+
console.log(`${statusIcon(lsp.level)} LSP runtime availability - ${lsp.detail}`)
|
|
811
|
+
|
|
812
|
+
const mempalacePython = detectMempalacePythonSource(config)
|
|
813
|
+
const mempalacePythonIcon = mempalacePython.value ? "✅" : "⚠️ "
|
|
814
|
+
console.log(`${mempalacePythonIcon} MemPalace python selection - ${mempalacePython.source}${mempalacePython.value ? ` (${mempalacePython.value})` : ""}`)
|
|
815
|
+
|
|
816
|
+
console.log("")
|
|
817
|
+
console.log("MCP Tool Probes:")
|
|
818
|
+
const probeResults = await probeMcpServers(config)
|
|
819
|
+
for (const probe of probeResults) {
|
|
820
|
+
console.log(`${statusIcon(probe.level)} ${probe.detail}`)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
console.log("")
|
|
824
|
+
console.log("Recommended follow-ups:")
|
|
825
|
+
console.log(" - hiai-opencode export-mcp .mcp.json")
|
|
826
|
+
console.log(" - opencode debug config")
|
|
827
|
+
console.log(" - opencode mcp list --print-logs --log-level INFO")
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function runDiagnose(outputPath) {
|
|
832
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
833
|
+
const defaultPath = outputPath
|
|
834
|
+
|| join(process.cwd(), `hiai-diagnose-${timestamp}.txt`)
|
|
835
|
+
const { path: configPath, config, error } = loadConfig()
|
|
836
|
+
const sections = []
|
|
837
|
+
|
|
838
|
+
sections.push("=".repeat(60))
|
|
839
|
+
sections.push(`hiai-opencode diagnose - ${timestamp}`)
|
|
840
|
+
sections.push("=".repeat(60))
|
|
841
|
+
sections.push("")
|
|
842
|
+
|
|
843
|
+
sections.push("ENVIRONMENT (keys only, no values):")
|
|
844
|
+
const envKeys = [
|
|
845
|
+
"FIRECRAWL_API_KEY", "STITCH_AI_API_KEY", "CONTEXT7_API_KEY",
|
|
846
|
+
"EXA_API_KEY", "TAVILY_API_KEY", "OPENCODE_RAG_URL",
|
|
847
|
+
"MEMPALACE_PYTHON", "HIAI_PLAYWRIGHT_INSTALL_BROWSERS", "HIAI_MCP_AUTO_INSTALL",
|
|
848
|
+
]
|
|
849
|
+
for (const key of envKeys) {
|
|
850
|
+
const hasValue = !!process.env[key]?.trim()
|
|
851
|
+
sections.push(` ${key}: ${hasValue ? "(set)" : "(not set)"}`)
|
|
852
|
+
}
|
|
853
|
+
sections.push("")
|
|
854
|
+
|
|
855
|
+
sections.push("CONFIGURATION:")
|
|
856
|
+
sections.push(` Config path: ${configPath ?? "(defaults)"}`)
|
|
857
|
+
if (error) sections.push(` Config parse warning: ${error}`)
|
|
858
|
+
const modelKeys = Object.keys(config?.models ?? {})
|
|
859
|
+
const mcpKeys = Object.keys(config?.mcp ?? {})
|
|
860
|
+
sections.push(` models configured: ${modelKeys.length} [${modelKeys.join(", ") || "none"}]`)
|
|
861
|
+
sections.push(` mcp servers in config: ${mcpKeys.length} [${mcpKeys.join(", ") || "none"}]`)
|
|
862
|
+
sections.push("")
|
|
863
|
+
|
|
864
|
+
sections.push("TOOLS REGISTERED:")
|
|
865
|
+
const toolCount = 26
|
|
866
|
+
sections.push(` ~${toolCount} tools (from tool-registry.ts)`)
|
|
867
|
+
sections.push("")
|
|
868
|
+
|
|
869
|
+
sections.push("AGENTS:")
|
|
870
|
+
const agents = ["bob", "coder", "strategist", "guard", "critic", "designer", "researcher", "manager", "brainstormer", "vision"]
|
|
871
|
+
for (const agent of agents) {
|
|
872
|
+
const model = config?.models?.[agent]?.model
|
|
873
|
+
sections.push(` ${agent}: ${model ? `model=${model}` : "(default)"}`)
|
|
874
|
+
}
|
|
875
|
+
sections.push("")
|
|
876
|
+
|
|
877
|
+
sections.push("MCP SERVERS:")
|
|
878
|
+
for (const [name, entry] of Object.entries(MCP_REGISTRY)) {
|
|
879
|
+
const userEntry = config?.mcp?.[name]
|
|
880
|
+
const enabled = userEntry?.enabled ?? entry.defaultEnabled
|
|
881
|
+
sections.push(` ${name}: ${enabled ? "enabled" : "disabled"}`)
|
|
882
|
+
}
|
|
883
|
+
sections.push("")
|
|
884
|
+
|
|
885
|
+
sections.push("FILE PATHS:")
|
|
886
|
+
sections.push(` CWD: ${process.cwd()}`)
|
|
887
|
+
sections.push(` Package root: ${PACKAGE_ROOT}`)
|
|
888
|
+
sections.push(` Config: ${configPath ?? "(none)"}`)
|
|
889
|
+
sections.push(` Static MCP: ${join(process.cwd(), ".mcp.json")}`)
|
|
890
|
+
sections.push("")
|
|
891
|
+
|
|
892
|
+
sections.push("=".repeat(60))
|
|
893
|
+
sections.push("Diagnose complete. File written to: " + defaultPath)
|
|
894
|
+
sections.push("NO secrets or API keys are included in this output.")
|
|
895
|
+
sections.push("=".repeat(60))
|
|
896
|
+
|
|
897
|
+
mkdirSync(dirname(defaultPath), { recursive: true })
|
|
898
|
+
writeFileSync(defaultPath, sections.join("\n") + "\n")
|
|
899
|
+
console.log(`Diagnose written to: ${defaultPath}`)
|
|
254
900
|
}
|
|
255
901
|
|
|
256
902
|
async function main() {
|
|
@@ -265,6 +911,21 @@ async function main() {
|
|
|
265
911
|
return
|
|
266
912
|
}
|
|
267
913
|
|
|
914
|
+
if (command === "doctor") {
|
|
915
|
+
await mcpStatus({ doctor: true })
|
|
916
|
+
return
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (command === "export-mcp") {
|
|
920
|
+
exportMcp(process.argv[3])
|
|
921
|
+
return
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (command === "diagnose") {
|
|
925
|
+
await runDiagnose(process.argv[3])
|
|
926
|
+
return
|
|
927
|
+
}
|
|
928
|
+
|
|
268
929
|
console.error(`Unknown command: ${command}`)
|
|
269
930
|
usage()
|
|
270
931
|
process.exit(1)
|