@hiai-gg/hiai-opencode 0.1.4 → 0.1.6

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.
Files changed (81) hide show
  1. package/.env.example +14 -8
  2. package/AGENTS.md +19 -8
  3. package/ARCHITECTURE.md +7 -6
  4. package/LICENSE.md +0 -1
  5. package/README.md +48 -17
  6. package/assets/cli/hiai-opencode.mjs +590 -7
  7. package/assets/mcp/mempalace.mjs +159 -25
  8. package/config/hiai-opencode.schema.json +82 -148
  9. package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
  10. package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
  11. package/dist/config/defaults.d.ts +1 -0
  12. package/dist/config/platform-schema.d.ts +275 -10
  13. package/dist/config/schema/categories.d.ts +2 -2
  14. package/dist/config/schema/commands.d.ts +1 -0
  15. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -3
  16. package/dist/config/types.d.ts +22 -5
  17. package/dist/create-tools.d.ts +2 -0
  18. package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
  19. package/dist/features/builtin-commands/types.d.ts +1 -1
  20. package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
  21. package/dist/features/builtin-skills/skills/index.d.ts +1 -0
  22. package/dist/index.js +870 -1711
  23. package/dist/mcp/types.d.ts +1 -1
  24. package/dist/plugin/tool-registry.d.ts +2 -0
  25. package/dist/shared/mcp-static-export.d.ts +22 -0
  26. package/dist/tools/ast-grep/constants.d.ts +1 -1
  27. package/dist/tools/ast-grep/environment-check.d.ts +1 -5
  28. package/dist/tools/ast-grep/language-support.d.ts +0 -1
  29. package/dist/tools/ast-grep/types.d.ts +1 -2
  30. package/dist/tools/skill-mcp/tools.d.ts +2 -0
  31. package/hiai-opencode.json +39 -171
  32. package/package.json +6 -4
  33. package/src/agents/bob/default.ts +6 -1
  34. package/src/agents/bob/gpt-pro.ts +1 -0
  35. package/src/agents/bob.ts +1 -0
  36. package/src/agents/coder/gpt-codex.ts +1 -0
  37. package/src/agents/coder/gpt-pro.ts +1 -0
  38. package/src/agents/coder/gpt.ts +1 -0
  39. package/src/agents/dynamic-agent-core-sections.ts +36 -0
  40. package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
  41. package/src/config/defaults.ts +171 -28
  42. package/src/config/loader.test.ts +16 -1
  43. package/src/config/loader.ts +4 -2
  44. package/src/config/model-slots-and-export.test.ts +55 -0
  45. package/src/config/platform-schema.ts +37 -5
  46. package/src/config/schema/commands.ts +1 -0
  47. package/src/config/schema/oh-my-opencode-config.ts +0 -3
  48. package/src/config/types.ts +34 -5
  49. package/src/create-tools.ts +4 -1
  50. package/src/features/builtin-commands/commands.ts +7 -0
  51. package/src/features/builtin-commands/templates/doctor.ts +43 -0
  52. package/src/features/builtin-commands/types.ts +1 -1
  53. package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
  54. package/src/features/builtin-skills/skills/index.ts +1 -0
  55. package/src/features/builtin-skills/skills.ts +10 -1
  56. package/src/index.ts +4 -38
  57. package/src/lsp/index.ts +1 -0
  58. package/src/mcp/registry.ts +6 -1
  59. package/src/plugin/tool-registry.ts +4 -0
  60. package/src/shared/mcp-static-export.ts +121 -0
  61. package/src/tools/ast-grep/constants.ts +1 -1
  62. package/src/tools/ast-grep/environment-check.ts +2 -32
  63. package/src/tools/ast-grep/language-support.ts +0 -3
  64. package/src/tools/ast-grep/types.ts +1 -2
  65. package/src/tools/skill-mcp/tools.test.ts +44 -0
  66. package/src/tools/skill-mcp/tools.ts +45 -7
  67. package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
  68. package/dist/config/loader.test.d.ts +0 -1
  69. package/dist/config/models.d.ts +0 -13
  70. package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
  71. package/dist/internals/plugins/websearch-cited/index.d.ts +0 -11
  72. package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
  73. package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
  74. package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
  75. package/src/internals/plugins/websearch-cited/LICENSE +0 -214
  76. package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
  77. package/src/internals/plugins/websearch-cited/google.ts +0 -749
  78. package/src/internals/plugins/websearch-cited/index.ts +0 -301
  79. package/src/internals/plugins/websearch-cited/openai.ts +0 -407
  80. package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
  81. package/src/internals/plugins/websearch-cited/types.ts +0 -7
@@ -0,0 +1,69 @@
1
+ import type { BuiltinSkill } from "../types"
2
+
3
+ export const hiaiOpencodeSetupSkill: BuiltinSkill = {
4
+ name: "hiai-opencode-setup",
5
+ description:
6
+ "Use when install/setup/onboarding or MCP debug mentions: install, setup, bootstrap, doctor, mcp-status, MCP not found, mcp list empty, MemPalace, RAG, firecrawl, stitch, sequential-thinking, playwright, DCP, agents, skills, or LSP.",
7
+ template: `# hiai-opencode Setup And Runtime Operations
8
+
9
+ Use this skill for hiai-opencode installation, diagnostics, and integration repair.
10
+
11
+ ## Architecture
12
+
13
+ - hiai-opencode is an OpenCode plugin, not a standalone app.
14
+ - MCP servers are external upstream tools launched by hiai-opencode wiring.
15
+ - The user-facing config is \`hiai-opencode.json\` or \`.opencode/hiai-opencode.json\`.
16
+ - Model provider credentials belong to OpenCode Connect. Do not ask for \`OPENROUTER_API_KEY\`, \`OPENAI_API_KEY\`, or \`ANTHROPIC_API_KEY\` for normal model usage.
17
+ - Service credentials are separate: \`FIRECRAWL_API_KEY\`, \`STITCH_AI_API_KEY\`, \`CONTEXT7_API_KEY\`.
18
+
19
+ ## First Diagnostic Commands
20
+
21
+ \`\`\`bash
22
+ hiai-opencode doctor
23
+ hiai-opencode mcp-status
24
+ opencode debug config
25
+ \`\`\`
26
+
27
+ If \`opencode mcp list\` is empty but doctor/mcp-status sees servers, explain that OpenCode's list may read only static \`.mcp.json\`. Refresh static visibility:
28
+
29
+ \`\`\`bash
30
+ hiai-opencode export-mcp .mcp.json
31
+ opencode mcp list --print-logs --log-level INFO
32
+ \`\`\`
33
+
34
+ ## Plugin vs MCP
35
+
36
+ Install OpenCode plugins with:
37
+
38
+ \`\`\`bash
39
+ opencode plugin @hiai-gg/hiai-opencode@latest --global
40
+ opencode plugin @tarquinen/opencode-dcp@latest --global
41
+ \`\`\`
42
+
43
+ Do not add MCP packages to the OpenCode plugin array. MCP packages are launched through \`hiai-opencode.json\` and helper scripts.
44
+
45
+ ## MCP Runtime Notes
46
+
47
+ - \`playwright\`: node/npx; browser binaries may need \`HIAI_PLAYWRIGHT_INSTALL_BROWSERS=1\`; Linux system deps may require admin rights.
48
+ - \`sequential-thinking\`: node/npx; use for complex planning, revision, and branching.
49
+ - \`firecrawl\`: requires \`FIRECRAWL_API_KEY\`.
50
+ - \`mempalace\`: prefers \`uv\`; otherwise Python 3.9+ with \`mempalace\`. Use \`mempalace_status\` first, search before answering memory questions, and never invent memories.
51
+ - \`rag\`: requires \`OPENCODE_RAG_URL\` or a reachable default \`http://localhost:9002/tools/search\`.
52
+ - \`stitch\`: requires \`STITCH_AI_API_KEY\`.
53
+ - \`context7\`: remote docs/search; key optional but recommended for limits.
54
+
55
+ ## Calling MCP
56
+
57
+ - Use native MCP tools if OpenCode exposes them.
58
+ - Use \`skill_mcp\` for skill-embedded MCP or enabled hiai-opencode MCP.
59
+ - If \`skill_mcp\` says a server is not found, check whether the skill is loaded, whether the MCP is enabled in \`hiai-opencode.json\`, and whether \`.mcp.json\` needs export.
60
+
61
+ ## Safety Rules
62
+
63
+ - Report missing keys by env var name only. Never print key values.
64
+ - Prefer project-local or user-level installs. Do not use sudo/admin rights unless the user explicitly asks.
65
+ - Do not edit unrelated OpenCode/Claude/Agents global skill folders unless the user opts in.
66
+ - Keep DCP separate: it is an optional OpenCode plugin, not part of the hiai-opencode package.
67
+ `,
68
+ allowedTools: ["Bash(*)", "Read(*)", "Edit(*)", "Glob(*)", "Grep(*)", "skill_mcp(*)"],
69
+ }
@@ -5,3 +5,4 @@ export { gitMasterSkill } from "./git-master"
5
5
  export { devBrowserSkill } from "./dev-browser"
6
6
  export { reviewWorkSkill } from "./review-work"
7
7
  export { aiSlopRemoverSkill } from "./ai-slop-remover"
8
+ export { hiaiOpencodeSetupSkill } from "./hiai-opencode-setup"
@@ -10,6 +10,7 @@ import {
10
10
  devBrowserSkill,
11
11
  reviewWorkSkill,
12
12
  aiSlopRemoverSkill,
13
+ hiaiOpencodeSetupSkill,
13
14
  } from "./skills/index"
14
15
 
15
16
  export interface CreateBuiltinSkillsOptions {
@@ -29,7 +30,15 @@ export function createBuiltinSkills(options: CreateBuiltinSkillsOptions = {}): B
29
30
  browserSkill = playwrightSkill
30
31
  }
31
32
 
32
- const skills = [browserSkill, frontendUiUxSkill, gitMasterSkill, devBrowserSkill, reviewWorkSkill, aiSlopRemoverSkill]
33
+ const skills = [
34
+ browserSkill,
35
+ hiaiOpencodeSetupSkill,
36
+ frontendUiUxSkill,
37
+ gitMasterSkill,
38
+ devBrowserSkill,
39
+ reviewWorkSkill,
40
+ aiSlopRemoverSkill,
41
+ ]
33
42
 
34
43
  if (!disabledSkills) {
35
44
  return skills
package/src/index.ts CHANGED
@@ -18,18 +18,15 @@ import { injectServerAuthIntoClient, log } from "./shared"
18
18
  import { hydratePluginConfigWithPlatformDefaults } from "./shared/runtime-plugin-config"
19
19
  import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
20
20
  import { PLUGIN_NAME } from "./shared/plugin-identity"
21
+ import { autoExportStaticMcpJson } from "./shared/mcp-static-export"
21
22
  import { warnIfListPluginEntry, warnMissingRequiredMcpEnv } from "./shared/startup-diagnostics"
22
23
  import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
23
24
  import { lspManager } from "./tools/lsp/client"
24
25
 
25
- import { loadConfig, resolveEnvVars } from "./config/loader"
26
+ import { loadConfig } from "./config/loader"
26
27
  import type { HiaiOpencodeConfig } from "./config/types"
27
28
 
28
29
  import { createPlugin as createSubtask2Plugin } from "./internals/plugins/subtask2/core/plugin"
29
- import WebsearchCitedPlugin, {
30
- WebsearchCitedGooglePlugin,
31
- WebsearchCitedOpenAIPlugin
32
- } from "./internals/plugins/websearch-cited/index"
33
30
  import { createBuiltinSkills } from "./features/builtin-skills"
34
31
  import {
35
32
  materializeBuiltinSkills,
@@ -89,6 +86,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
89
86
  pluginConfig,
90
87
  platformConfig: internalConfig,
91
88
  })
89
+ autoExportStaticMcpJson(ctx.directory, internalConfig)
92
90
 
93
91
  materializeBuiltinSkills(
94
92
  createBuiltinSkills({
@@ -129,6 +127,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
129
127
  const toolsResult = await createTools({
130
128
  ctx,
131
129
  pluginConfig,
130
+ platformConfig: internalConfig,
132
131
  managers,
133
132
  })
134
133
 
@@ -169,10 +168,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
169
168
  } catch (err) {
170
169
  console.error("[hiai-opencode] PTYPlugin failed to load:", err);
171
170
  }
172
- const websearchResult = await WebsearchCitedPlugin(ctx)
173
- const websearchGoogleResult = await WebsearchCitedGooglePlugin(ctx)
174
- const websearchOpenAIResult = await WebsearchCitedOpenAIPlugin(ctx)
175
-
176
171
  const combinedResult = {
177
172
  name: PLUGIN_NAME,
178
173
  ...pluginInterface,
@@ -181,7 +176,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
181
176
  tool: {
182
177
  ...pluginInterface.tool,
183
178
  ...ptyResult.tool,
184
- ...websearchResult.tool,
185
179
  },
186
180
 
187
181
  // Chain hooks: command.execute.before
@@ -212,7 +206,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
212
206
  config: async (input: any) => {
213
207
  await pluginInterface.config?.(input);
214
208
  await (subtask2Result as any).config?.(input);
215
- await (websearchResult as any).config?.(input);
216
209
  },
217
210
 
218
211
  // Merge Events
@@ -222,33 +215,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
222
215
  await (ptyResult as any).event?.(input);
223
216
  },
224
217
 
225
- // Auth (Consolidated)
226
- auth: {
227
- provider: "hiai-opencode",
228
- methods: [
229
- { type: "api" as const, label: "Google Search API key" },
230
- ],
231
- loader: async (getAuth: any) => {
232
- const authData = await getAuth();
233
- const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
234
-
235
- const getConfiguredKey = (configKey?: string) => {
236
- if (configKey) return resolveEnvVars(configKey);
237
- return undefined;
238
- };
239
-
240
- const googleKey = authData["Google Search API key"] || getConfiguredKey(internalConfig.auth?.googleSearch);
241
- const openaiKey = getConfiguredKey(internalConfig.auth?.openai);
242
- const openRouterKey = getConfiguredKey(internalConfig.auth?.openrouter);
243
-
244
- if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve({ type: "api", key: googleKey }));
245
- if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openaiKey }));
246
- if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openRouterKey }));
247
-
248
- return {};
249
- },
250
- },
251
-
252
218
  "experimental.session.compacting": async (
253
219
  _input: { sessionID: string },
254
220
  output: { context: string[] },
package/src/lsp/index.ts CHANGED
@@ -4,6 +4,7 @@ export function buildLspConfig(lsp: Record<string, LspServerConfig>): Record<str
4
4
  const result: Record<string, unknown> = {};
5
5
 
6
6
  for (const [name, server] of Object.entries(lsp)) {
7
+ if (server.enabled === false) continue;
7
8
  result[name] = {
8
9
  command: server.command,
9
10
  extensions: server.extensions,
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path"
2
+ import { existsSync } from "node:fs"
2
3
  import type { McpServerConfig } from "../config/types.js"
3
4
 
4
5
  export type HiaiMcpName =
@@ -22,7 +23,11 @@ export interface HiaiMcpRegistryEntry {
22
23
  }
23
24
 
24
25
  function resolveAssetScript(...segments: string[]): string {
25
- return join(import.meta.dirname, "..", "assets", ...segments)
26
+ const candidates = [
27
+ join(import.meta.dirname, "..", "assets", ...segments),
28
+ join(import.meta.dirname, "..", "..", "assets", ...segments),
29
+ ]
30
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0]
26
31
  }
27
32
 
28
33
  function createNpmPackageCommand(pkg: string, ...args: string[]): string[] {
@@ -5,6 +5,7 @@ import type {
5
5
  AvailableCategory,
6
6
  } from "../agents/dynamic-agent-prompt-builder"
7
7
  import type { HiaiOpenCodeConfig } from "../config"
8
+ import type { McpServerConfig } from "../config/types"
8
9
  import { isInteractiveBashEnabled } from "../create-runtime-tmux-config"
9
10
  import type { PluginContext, ToolsRecord } from "./types"
10
11
 
@@ -146,6 +147,7 @@ export function createToolRegistry(args: {
146
147
  managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">
147
148
  skillContext: SkillContext
148
149
  availableCategories: AvailableCategory[]
150
+ builtinMcp?: Record<string, McpServerConfig>
149
151
  interactiveBashEnabled?: boolean
150
152
  toolFactories?: Partial<ToolRegistryFactories>
151
153
  }): ToolRegistryResult {
@@ -155,6 +157,7 @@ export function createToolRegistry(args: {
155
157
  managers,
156
158
  skillContext,
157
159
  availableCategories,
160
+ builtinMcp,
158
161
  interactiveBashEnabled = isInteractiveBashEnabled(),
159
162
  toolFactories,
160
163
  } = args
@@ -216,6 +219,7 @@ export function createToolRegistry(args: {
216
219
  manager: managers.skillMcpManager,
217
220
  getLoadedSkills: () => skillContext.mergedSkills,
218
221
  getSessionID: getSessionIDForMcp,
222
+ builtinMcp,
219
223
  })
220
224
 
221
225
  const commands = factories.discoverCommandsSync(ctx.directory, {
@@ -0,0 +1,121 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
2
+ import { dirname, join } from "node:path"
3
+
4
+ import type { HiaiOpencodeConfig, McpServerConfig } from "../config/types"
5
+ import { resolveEnvVars } from "../config/loader"
6
+ import { log } from "./logger"
7
+
8
+ type StaticMcpServer = {
9
+ type?: "http" | "stdio"
10
+ url?: string
11
+ command?: string
12
+ args?: string[]
13
+ env?: Record<string, string>
14
+ headers?: Record<string, string>
15
+ }
16
+
17
+ export const MCP_EXPORT_MARKER = "hiai-opencode"
18
+
19
+ type StaticMcpJsonPayload = {
20
+ mcpServers: Record<string, StaticMcpServer>
21
+ _meta?: {
22
+ generatedBy: typeof MCP_EXPORT_MARKER
23
+ version: 1
24
+ generatedAt: string
25
+ }
26
+ }
27
+
28
+ function toStaticMcpServer(config: McpServerConfig): StaticMcpServer | null {
29
+ if (config.enabled === false) return null
30
+
31
+ if (config.type === "remote") {
32
+ const headers = config.headers
33
+ ? Object.fromEntries(Object.entries(config.headers).map(([key, value]) => [key, resolveEnvVars(value)]))
34
+ : undefined
35
+
36
+ return {
37
+ type: "http",
38
+ url: config.url,
39
+ ...(headers ? { headers } : {}),
40
+ }
41
+ }
42
+
43
+ const [command, ...args] = config.command ?? []
44
+ if (!command) return null
45
+
46
+ const env = config.environment
47
+ ? Object.fromEntries(Object.entries(config.environment).map(([key, value]) => [key, resolveEnvVars(value)]))
48
+ : undefined
49
+
50
+ return {
51
+ command,
52
+ ...(args.length > 0 ? { args } : {}),
53
+ ...(env ? { env } : {}),
54
+ }
55
+ }
56
+
57
+ export function buildStaticMcpJson(config: HiaiOpencodeConfig): StaticMcpJsonPayload {
58
+ const mcpServers: Record<string, StaticMcpServer> = {}
59
+
60
+ for (const [name, serverConfig] of Object.entries(config.mcp ?? {})) {
61
+ const converted = toStaticMcpServer(serverConfig)
62
+ if (converted) {
63
+ mcpServers[name] = converted
64
+ }
65
+ }
66
+
67
+ return {
68
+ _meta: {
69
+ generatedBy: MCP_EXPORT_MARKER,
70
+ version: 1,
71
+ generatedAt: new Date().toISOString(),
72
+ },
73
+ mcpServers,
74
+ }
75
+ }
76
+
77
+ export function isManagedStaticMcpFile(path: string): boolean {
78
+ if (!existsSync(path)) return false
79
+
80
+ try {
81
+ const raw = readFileSync(path, "utf-8")
82
+ const parsed = JSON.parse(raw) as StaticMcpJsonPayload
83
+ return parsed?._meta?.generatedBy === MCP_EXPORT_MARKER
84
+ } catch {
85
+ return false
86
+ }
87
+ }
88
+
89
+ export function autoExportStaticMcpJson(directory: string, config: HiaiOpencodeConfig): void {
90
+ const mode = process.env.HIAI_OPENCODE_AUTO_EXPORT_MCP?.trim().toLowerCase() || "if-missing"
91
+ if (mode === "0" || mode === "false" || mode === "no" || mode === "off") {
92
+ return
93
+ }
94
+
95
+ const outputPath = process.env.HIAI_OPENCODE_MCP_EXPORT_PATH?.trim() || join(directory, ".mcp.json")
96
+ if (mode === "if-missing" && existsSync(outputPath)) {
97
+ return
98
+ }
99
+
100
+ const isForceMode = mode === "force"
101
+ if (mode === "always" && existsSync(outputPath) && !isManagedStaticMcpFile(outputPath) && !isForceMode) {
102
+ console.warn(
103
+ `[hiai-opencode] WARNING: refusing to overwrite non-managed static MCP config at ${outputPath}. `
104
+ + "Set HIAI_OPENCODE_AUTO_EXPORT_MCP=force to override.",
105
+ )
106
+ return
107
+ }
108
+
109
+ try {
110
+ mkdirSync(dirname(outputPath), { recursive: true })
111
+ const payload = buildStaticMcpJson(config)
112
+ writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`)
113
+ log("[hiai-opencode] exported static MCP config", {
114
+ outputPath,
115
+ servers: Object.keys(payload.mcpServers),
116
+ })
117
+ } catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error)
119
+ console.warn(`[hiai-opencode] WARNING: failed to export static MCP config to ${outputPath}: ${message}`)
120
+ }
121
+ }
@@ -1,5 +1,5 @@
1
1
  export type { EnvironmentCheckResult } from "./environment-check"
2
2
  export { checkEnvironment, formatEnvironmentCheck } from "./environment-check"
3
- export { CLI_LANGUAGES, NAPI_LANGUAGES, LANG_EXTENSIONS } from "./language-support"
3
+ export { CLI_LANGUAGES, LANG_EXTENSIONS } from "./language-support"
4
4
  export { DEFAULT_TIMEOUT_MS, DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_MAX_MATCHES } from "./language-support"
5
5
  export { findSgCliPathSync, getSgCliPath, setSgCliPath } from "./sg-cli-path"
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "fs"
2
2
 
3
- import { CLI_LANGUAGES, NAPI_LANGUAGES } from "./language-support"
3
+ import { CLI_LANGUAGES } from "./language-support"
4
4
  import { getSgCliPath } from "./sg-cli-path"
5
5
 
6
6
  export interface EnvironmentCheckResult {
@@ -9,14 +9,10 @@ export interface EnvironmentCheckResult {
9
9
  path: string
10
10
  error?: string
11
11
  }
12
- napi: {
13
- available: boolean
14
- error?: string
15
- }
16
12
  }
17
13
 
18
14
  /**
19
- * Check if ast-grep CLI and NAPI are available.
15
+ * Check if ast-grep CLI is available.
20
16
  * Call this at startup to provide early feedback about missing dependencies.
21
17
  */
22
18
  export function checkEnvironment(): EnvironmentCheckResult {
@@ -26,9 +22,6 @@ export function checkEnvironment(): EnvironmentCheckResult {
26
22
  available: false,
27
23
  path: cliPath ?? "not found",
28
24
  },
29
- napi: {
30
- available: false,
31
- },
32
25
  }
33
26
 
34
27
  if (cliPath && existsSync(cliPath)) {
@@ -39,17 +32,6 @@ export function checkEnvironment(): EnvironmentCheckResult {
39
32
  result.cli.error = `Binary not found: ${cliPath}`
40
33
  }
41
34
 
42
- // Check NAPI availability
43
- try {
44
- require("@ast-grep/napi")
45
- result.napi.available = true
46
- } catch (error) {
47
- result.napi.available = false
48
- result.napi.error = `@ast-grep/napi not installed: ${
49
- error instanceof Error ? error.message : String(error)
50
- }`
51
- }
52
-
53
35
  return result
54
36
  }
55
37
 
@@ -70,20 +52,8 @@ export function formatEnvironmentCheck(result: EnvironmentCheckResult): string {
70
52
  lines.push(" Install: bun add -D @ast-grep/cli")
71
53
  }
72
54
 
73
- // NAPI status
74
- if (result.napi.available) {
75
- lines.push("[OK] NAPI: Available")
76
- } else {
77
- lines.push("[X] NAPI: Not available")
78
- if (result.napi.error) {
79
- lines.push(` Error: ${result.napi.error}`)
80
- }
81
- lines.push(" Install: bun add -D @ast-grep/napi")
82
- }
83
-
84
55
  lines.push("")
85
56
  lines.push(`CLI supports ${CLI_LANGUAGES.length} languages`)
86
- lines.push(`NAPI supports ${NAPI_LANGUAGES.length} languages: ${NAPI_LANGUAGES.join(", ")}`)
87
57
 
88
58
  return lines.join("\n")
89
59
  }
@@ -27,9 +27,6 @@ export const CLI_LANGUAGES = [
27
27
  "yaml",
28
28
  ] as const
29
29
 
30
- // NAPI supported languages (5 total - native bindings)
31
- export const NAPI_LANGUAGES = ["html", "javascript", "tsx", "css", "typescript"] as const
32
-
33
30
  export const DEFAULT_TIMEOUT_MS = 300_000
34
31
  export const DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024
35
32
  export const DEFAULT_MAX_MATCHES = 500
@@ -1,7 +1,6 @@
1
- import type { CLI_LANGUAGES, NAPI_LANGUAGES } from "./constants"
1
+ import type { CLI_LANGUAGES } from "./constants"
2
2
 
3
3
  export type CliLanguage = (typeof CLI_LANGUAGES)[number]
4
- export type NapiLanguage = (typeof NAPI_LANGUAGES)[number]
5
4
 
6
5
  export interface Position {
7
6
  line: number
@@ -0,0 +1,44 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { ToolContext } from "@opencode-ai/plugin/tool"
3
+
4
+ import { createSkillMcpTool } from "./tools"
5
+
6
+ test("skill_mcp falls back to enabled builtin MCP when skill MCP is absent", async () => {
7
+ let callCaptured: { serverName: string; toolName: string } | null = null
8
+
9
+ const toolDef = createSkillMcpTool({
10
+ manager: {
11
+ async callTool(info, _context, toolName) {
12
+ callCaptured = { serverName: info.serverName, toolName }
13
+ return { ok: true }
14
+ },
15
+ async readResource() {
16
+ return { ok: true }
17
+ },
18
+ async getPrompt() {
19
+ return { ok: true }
20
+ },
21
+ } as any,
22
+ getLoadedSkills: () => [],
23
+ getSessionID: () => "session-test",
24
+ builtinMcp: {
25
+ firecrawl: {
26
+ enabled: true,
27
+ type: "local",
28
+ command: ["node", "firecrawl.mjs"],
29
+ },
30
+ },
31
+ })
32
+
33
+ const result = await toolDef.execute(
34
+ {
35
+ mcp_name: "firecrawl",
36
+ tool_name: "firecrawl_search",
37
+ arguments: { query: "test" },
38
+ } as any,
39
+ { sessionID: "session-test" } as ToolContext,
40
+ )
41
+
42
+ expect(callCaptured).toEqual({ serverName: "firecrawl", toolName: "firecrawl_search" })
43
+ expect(result).toContain("\"ok\": true")
44
+ })
@@ -1,14 +1,17 @@
1
1
  import { tool, type ToolDefinition } from "@opencode-ai/plugin"
2
2
  import type { ToolContext } from "@opencode-ai/plugin/tool"
3
3
  import { BUILTIN_MCP_TOOL_HINTS, SKILL_MCP_DESCRIPTION } from "./constants"
4
+ import type { McpServerConfig } from "../../config/types"
4
5
  import type { SkillMcpArgs } from "./types"
5
6
  import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
7
+ import type { ClaudeCodeMcpServer } from "../../features/claude-code-mcp-loader/types"
6
8
  import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
7
9
 
8
10
  interface SkillMcpToolOptions {
9
11
  manager: SkillMcpManager
10
12
  getLoadedSkills: () => LoadedSkill[]
11
13
  getSessionID?: () => string | undefined
14
+ builtinMcp?: Record<string, McpServerConfig>
12
15
  }
13
16
 
14
17
  type OperationType = { type: "tool" | "resource" | "prompt"; name: string }
@@ -60,6 +63,28 @@ function findMcpServer(
60
63
  return null
61
64
  }
62
65
 
66
+ function convertBuiltinMcpConfig(config: McpServerConfig): ClaudeCodeMcpServer | null {
67
+ if (config.enabled === false) return null
68
+
69
+ if (config.type === "remote") {
70
+ return {
71
+ type: "http",
72
+ url: config.url,
73
+ headers: config.headers,
74
+ }
75
+ }
76
+
77
+ const [command, ...args] = config.command ?? []
78
+ if (!command) return null
79
+
80
+ return {
81
+ type: "stdio",
82
+ command,
83
+ args,
84
+ env: config.environment,
85
+ }
86
+ }
87
+
63
88
  function formatAvailableMcps(skills: LoadedSkill[]): string {
64
89
  const mcps: string[] = []
65
90
  for (const skill of skills) {
@@ -72,6 +97,14 @@ function formatAvailableMcps(skills: LoadedSkill[]): string {
72
97
  return mcps.length > 0 ? mcps.join("\n") : " (none found)"
73
98
  }
74
99
 
100
+ function formatAvailableBuiltinMcps(builtinMcp: Record<string, McpServerConfig> | undefined): string {
101
+ const names = Object.entries(builtinMcp ?? {})
102
+ .filter(([, config]) => config.enabled !== false)
103
+ .map(([name]) => ` - "${name}" from hiai-opencode config`)
104
+
105
+ return names.length > 0 ? names.join("\n") : " (none found)"
106
+ }
107
+
75
108
  function formatBuiltinMcpHint(mcpName: string): string | null {
76
109
  const nativeTools = BUILTIN_MCP_TOOL_HINTS[mcpName]
77
110
  if (!nativeTools) return null
@@ -119,7 +152,7 @@ export function applyGrepFilter(output: string, pattern: string | undefined): st
119
152
  }
120
153
 
121
154
  export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition {
122
- const { manager, getLoadedSkills, getSessionID } = options
155
+ const { manager, getLoadedSkills, getSessionID, builtinMcp } = options
123
156
 
124
157
  return tool({
125
158
  description: SKILL_MCP_DESCRIPTION,
@@ -141,8 +174,10 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
141
174
  const operation = validateOperationParams(args)
142
175
  const skills = getLoadedSkills()
143
176
  const found = findMcpServer(args.mcp_name, skills)
177
+ const builtinConfig = builtinMcp?.[args.mcp_name]
178
+ const convertedBuiltinConfig = builtinConfig ? convertBuiltinMcpConfig(builtinConfig) : null
144
179
 
145
- if (!found) {
180
+ if (!found && !convertedBuiltinConfig) {
146
181
  const builtinHint = formatBuiltinMcpHint(args.mcp_name)
147
182
  if (builtinHint) {
148
183
  throw new Error(builtinHint)
@@ -153,7 +188,10 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
153
188
  `Available MCP servers in loaded skills:\n` +
154
189
  formatAvailableMcps(skills) +
155
190
  `\n\n` +
156
- `Hint: Load the skill first using the 'skill' tool, then call skill_mcp.`,
191
+ `Available MCP servers in hiai-opencode config:\n` +
192
+ formatAvailableBuiltinMcps(builtinMcp) +
193
+ `\n\n` +
194
+ `Hint: Load the skill first for skill-embedded MCPs. Builtin hiai-opencode MCPs can be called directly when enabled in hiai-opencode.json.`,
157
195
  )
158
196
  }
159
197
 
@@ -164,14 +202,14 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
164
202
 
165
203
  const info: SkillMcpClientInfo = {
166
204
  serverName: args.mcp_name,
167
- skillName: found.skill.name,
205
+ skillName: found?.skill.name ?? "hiai-opencode",
168
206
  sessionID,
169
- scope: found.skill.scope,
207
+ scope: found?.skill.scope ?? "user",
170
208
  }
171
209
 
172
210
  const context: SkillMcpServerContext = {
173
- config: found.config,
174
- skillName: found.skill.name,
211
+ config: found?.config ?? convertedBuiltinConfig!,
212
+ skillName: found?.skill.name ?? "hiai-opencode",
175
213
  }
176
214
 
177
215
  const parsedArgs = parseArguments(args.arguments)
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- export declare const MODEL_ROLE_GUIDE: readonly ["fast: cheap/default for bounded helpers, researcher-style scans, platform chores", "mid: balanced default for steady execution/review work", "high: stronger general-purpose model for primary implementation and planning", "ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions", "vision: preferred for UI/media/multimodal interpretation and visual work", "reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable"];
2
- export declare const PROVIDER_MODEL_RULES: readonly ["openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`", "anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`", "deepseek: use `deepseek/<model>` when connected directly", "glm: use `z-ai/<model>` or the provider id exposed by your gateway/client", "minimax: use `minimax/<model>`", "qwen: use `qwen/<model>`", "ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`", "openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`", "rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids"];
3
- export declare const MODEL_PRESETS: {
4
- readonly fast: "openrouter/google/gemini-2.0-flash";
5
- readonly mid: "openrouter/anthropic/claude-3.5-sonnet";
6
- readonly high: "openrouter/anthropic/claude-3.5-opus";
7
- readonly ultrahigh: "openrouter/openai/gpt-4o";
8
- readonly vision: "openrouter/google/gemini-2.0-pro-exp-02-05";
9
- readonly reasoning: "openrouter/openai/o1";
10
- readonly strategist: "openrouter/z-ai/glm-5.1";
11
- readonly critic: "openrouter/qwen/qwen2.5-72b-instruct";
12
- readonly writing: "openrouter/kimi/kimi-latest";
13
- };