@hiai-gg/hiai-opencode 0.1.2 → 0.1.4

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 (58) hide show
  1. package/.env.example +28 -21
  2. package/AGENTS.md +183 -28
  3. package/ARCHITECTURE.md +17 -20
  4. package/LICENSE.md +1 -0
  5. package/README.md +269 -66
  6. package/assets/cli/hiai-opencode.mjs +276 -0
  7. package/assets/mcp/mempalace.mjs +47 -4
  8. package/assets/mcp/playwright.mjs +83 -0
  9. package/config/hiai-opencode.schema.json +113 -1
  10. package/dist/config/index.d.ts +0 -1
  11. package/dist/config/platform-schema.d.ts +72 -0
  12. package/dist/config/schema/agent-overrides.d.ts +256 -0
  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/index.d.ts +2 -0
  16. package/dist/config/schema/oh-my-opencode-config.d.ts +267 -0
  17. package/dist/config/schema/skill-discovery.d.ts +11 -0
  18. package/dist/config/types.d.ts +12 -1
  19. package/dist/features/builtin-commands/templates/mcp-status.d.ts +1 -0
  20. package/dist/features/builtin-commands/types.d.ts +1 -1
  21. package/dist/features/opencode-skill-loader/loader.d.ts +2 -0
  22. package/dist/index.js +617 -421
  23. package/dist/mcp/registry.d.ts +14 -0
  24. package/dist/mcp/types.d.ts +6 -0
  25. package/dist/plugin/skill-discovery-config.d.ts +4 -0
  26. package/dist/shared/startup-diagnostics.d.ts +6 -0
  27. package/hiai-opencode.json +192 -36
  28. package/package.json +4 -1
  29. package/src/agents/AGENTS.md +3 -4
  30. package/src/config/defaults.ts +55 -133
  31. package/src/config/index.ts +0 -1
  32. package/src/config/loader.ts +4 -1
  33. package/src/config/platform-schema.ts +18 -2
  34. package/src/config/schema/agent-overrides.ts +2 -0
  35. package/src/config/schema/commands.ts +1 -0
  36. package/src/config/schema/fast-apply.ts +4 -4
  37. package/src/config/schema/index.ts +2 -0
  38. package/src/config/schema/oh-my-opencode-config.ts +3 -0
  39. package/src/config/schema/skill-discovery.ts +25 -0
  40. package/src/config/types.ts +16 -0
  41. package/src/features/builtin-commands/commands.ts +7 -0
  42. package/src/features/builtin-commands/templates/mcp-status.ts +36 -0
  43. package/src/features/builtin-commands/types.ts +1 -1
  44. package/src/features/builtin-skills/skills/playwright.ts +24 -2
  45. package/src/features/opencode-skill-loader/loader.ts +11 -0
  46. package/src/index.ts +15 -13
  47. package/src/mcp/index.ts +0 -33
  48. package/src/mcp/omo-mcp-index.ts +0 -5
  49. package/src/mcp/registry.ts +132 -0
  50. package/src/mcp/types.ts +11 -1
  51. package/src/plugin/hooks/create-tool-guard-hooks.ts +1 -1
  52. package/src/plugin/skill-context.ts +31 -13
  53. package/src/plugin/skill-discovery-config.ts +32 -0
  54. package/src/plugin-handlers/agent-config-handler.ts +20 -13
  55. package/src/plugin-handlers/command-config-handler.ts +22 -12
  56. package/src/shared/migration/agent-names.ts +5 -5
  57. package/src/shared/startup-diagnostics.ts +77 -0
  58. package/src/config/models.ts +0 -32
@@ -3,10 +3,10 @@ import { z } from "zod"
3
3
  export const FastApplyConfigSchema = z.object({
4
4
  /** Enable fast-apply via Ollama (default: false) */
5
5
  enabled: z.boolean().optional().default(false),
6
- /** Ollama API URL (default: http://localhost:11434) */
7
- ollama_url: z.string().optional().default("http://localhost:11434"),
8
- /** Model name for fast-apply (default: qwen3.5:9b) */
9
- model: z.string().optional().default("qwen3.5:9b"),
6
+ /** Ollama API URL. Configure in hiai-opencode.json. */
7
+ ollama_url: z.string().optional().default(""),
8
+ /** Model name for fast-apply. Configure in hiai-opencode.json. */
9
+ model: z.string().optional().default(""),
10
10
  /** Timeout in milliseconds for Ollama request (default: 30000) */
11
11
  timeout: z.number().int().positive().optional().default(30000),
12
12
  })
@@ -44,6 +44,8 @@ export { BobAgentConfigSchema } from "./bob-agent"
44
44
  export type { BobAgentConfig } from "./bob-agent"
45
45
  export { SkillsConfigSchema } from "./skills"
46
46
  export type { SkillsConfig, SkillDefinition } from "./skills"
47
+ export { SkillDiscoveryConfigSchema } from "./skill-discovery"
48
+ export type { SkillDiscoveryConfig } from "./skill-discovery"
47
49
  export { StartWorkConfigSchema } from "./start-work"
48
50
  export type { StartWorkConfig } from "./start-work"
49
51
  export { TmuxConfigSchema, TmuxLayoutSchema, TmuxIsolationSchema } from "./tmux"
@@ -17,6 +17,7 @@ import { ModelCapabilitiesConfigSchema } from "./model-capabilities"
17
17
  import { RalphLoopConfigSchema } from "./ralph-loop"
18
18
  import { RuntimeFallbackConfigSchema } from "./runtime-fallback"
19
19
  import { SkillsConfigSchema } from "./skills"
20
+ import { SkillDiscoveryConfigSchema } from "./skill-discovery"
20
21
  import { BobConfigSchema } from "./bob"
21
22
  import { BobAgentConfigSchema } from "./bob-agent"
22
23
  import { TmuxConfigSchema } from "./tmux"
@@ -30,6 +31,7 @@ const AuthConfigSchema = z.object({
30
31
  openrouter: z.string().optional(),
31
32
  stitch: z.string().optional(),
32
33
  firecrawl: z.string().optional(),
34
+ context7: z.string().optional(),
33
35
  }).optional()
34
36
 
35
37
  export const HiaiOpenCodeConfigSchema = z.object({
@@ -60,6 +62,7 @@ export const HiaiOpenCodeConfigSchema = z.object({
60
62
  experimental: ExperimentalConfigSchema.optional(),
61
63
  auto_update: z.boolean().optional(),
62
64
  skills: SkillsConfigSchema.optional(),
65
+ skill_discovery: SkillDiscoveryConfigSchema.optional(),
63
66
  ralph_loop: RalphLoopConfigSchema.optional(),
64
67
  /**
65
68
  * Enable runtime fallback (default: false)
@@ -0,0 +1,25 @@
1
+ import { z } from "zod"
2
+
3
+ export const SkillDiscoveryConfigSchema = z.object({
4
+ /**
5
+ * Explicit skills.sources entries from hiai-opencode config.
6
+ * Safe by default because the user intentionally configured them.
7
+ */
8
+ config_sources: z.boolean().default(true),
9
+ /**
10
+ * Project-local OpenCode skills: .opencode/skills and .opencode/skill.
11
+ * Kept on by default because they are part of the current project contract.
12
+ */
13
+ project_opencode: z.boolean().default(true),
14
+ /**
15
+ * Global OpenCode skills from the user's OpenCode config directory.
16
+ * Off by default to keep clean installs deterministic.
17
+ */
18
+ global_opencode: z.boolean().default(false),
19
+ project_claude: z.boolean().default(false),
20
+ global_claude: z.boolean().default(false),
21
+ project_agents: z.boolean().default(false),
22
+ global_agents: z.boolean().default(false),
23
+ })
24
+
25
+ export type SkillDiscoveryConfig = z.infer<typeof SkillDiscoveryConfigSchema>
@@ -32,6 +32,8 @@ export const LEGACY_AGENT_ALIAS_NAMES = [
32
32
  "zoe",
33
33
  "build",
34
34
  "pre-plan",
35
+ "manager",
36
+ "vision",
35
37
  "logician",
36
38
  "librarian",
37
39
  "explore",
@@ -54,6 +56,8 @@ export const LEGACY_AGENT_ALIAS_TO_CANONICAL: Record<
54
56
  zoe: "bob",
55
57
  build: "bob",
56
58
  "pre-plan": "strategist",
59
+ manager: "platform-manager",
60
+ vision: "multimodal",
57
61
  logician: "strategist",
58
62
  librarian: "researcher",
59
63
  explore: "researcher",
@@ -135,6 +139,16 @@ export interface SkillsConfig {
135
139
  disabled?: string[];
136
140
  }
137
141
 
142
+ export interface SkillDiscoveryConfig {
143
+ config_sources?: boolean;
144
+ project_opencode?: boolean;
145
+ global_opencode?: boolean;
146
+ project_claude?: boolean;
147
+ global_claude?: boolean;
148
+ project_agents?: boolean;
149
+ global_agents?: boolean;
150
+ }
151
+
138
152
  export interface PermissionsConfig {
139
153
  read?: Record<string, string>;
140
154
  edit?: Record<string, string>;
@@ -148,6 +162,7 @@ export interface AuthKeys {
148
162
  openrouter?: string;
149
163
  stitch?: string;
150
164
  firecrawl?: string;
165
+ context7?: string;
151
166
  }
152
167
 
153
168
  export interface OllamaConfig {
@@ -168,6 +183,7 @@ export interface HiaiOpencodeConfig {
168
183
  lsp?: Record<string, LspServerConfig>;
169
184
  subtask2?: Subtask2Config;
170
185
  skills?: SkillsConfig;
186
+ skill_discovery?: SkillDiscoveryConfig;
171
187
  permissions?: PermissionsConfig;
172
188
  auth?: AuthKeys;
173
189
  ollama?: OllamaConfig;
@@ -8,6 +8,7 @@ import { REFACTOR_TEMPLATE } from "./templates/refactor"
8
8
  import { START_WORK_TEMPLATE } from "./templates/start-work"
9
9
  import { HANDOFF_TEMPLATE } from "./templates/handoff"
10
10
  import { REMOVE_AI_SLOPS_TEMPLATE } from "./templates/remove-ai-slops"
11
+ import { MCP_STATUS_TEMPLATE } from "./templates/mcp-status"
11
12
 
12
13
  interface LoadBuiltinCommandsOptions {
13
14
  useRegisteredAgents?: boolean
@@ -121,6 +122,12 @@ $ARGUMENTS
121
122
  </user-request>`,
122
123
  argumentHint: "[goal]",
123
124
  },
125
+ "mcp-status": {
126
+ description: "(builtin) Show hiai-opencode MCP server status, missing keys, and local runtime availability",
127
+ template: `<command-instruction>
128
+ ${MCP_STATUS_TEMPLATE}
129
+ </command-instruction>`,
130
+ },
124
131
  }
125
132
  }
126
133
 
@@ -0,0 +1,36 @@
1
+ export const MCP_STATUS_TEMPLATE = `# MCP Status Command
2
+
3
+ ## Purpose
4
+
5
+ Use /mcp-status to show the effective hiai-opencode MCP setup without relying on OpenCode's mcp list output.
6
+
7
+ ## Execute
8
+
9
+ Run:
10
+
11
+ \`\`\`bash
12
+ hiai-opencode mcp-status
13
+ \`\`\`
14
+
15
+ If the binary is not on PATH, try the package-local fallback:
16
+
17
+ \`\`\`bash
18
+ node ./node_modules/@hiai-gg/hiai-opencode/assets/cli/hiai-opencode.mjs mcp-status
19
+ \`\`\`
20
+
21
+ ## Report
22
+
23
+ Summarize the output in a compact status table:
24
+
25
+ - MCP server name
26
+ - status: ok, warning, error, disabled
27
+ - cause or next action
28
+
29
+ Rules:
30
+
31
+ - Do not print API key values.
32
+ - If a key is missing, name the env var only.
33
+ - If a runtime is missing, give the exact install hint from the command output or the shortest safe next command.
34
+ - Do not edit config unless the user explicitly asks.
35
+ - Do not run package installs unless the user explicitly asks.
36
+ `
@@ -1,6 +1,6 @@
1
1
  import type { CommandDefinition } from "../claude-code-command-loader"
2
2
 
3
- export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops"
3
+ export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops" | "mcp-status"
4
4
 
5
5
  export interface BuiltinCommandConfig {
6
6
  disabled_commands?: BuiltinCommandName[]
@@ -5,11 +5,33 @@ export const playwrightSkill: BuiltinSkill = {
5
5
  description: "MUST USE for any browser-related tasks. Browser automation via Playwright MCP - verification, browsing, information gathering, web scraping, testing, screenshots, and all browser interactions.",
6
6
  template: `# Playwright Browser Automation
7
7
 
8
- This skill provides browser automation capabilities via the Playwright MCP server.`,
8
+ This skill provides browser automation capabilities via the Playwright MCP server.
9
+
10
+ ## Required workflow
11
+
12
+ 1. Load this skill before calling \`skill_mcp\`.
13
+ 2. Use \`skill_mcp\` with \`mcp_name="playwright"\` for browser navigation, interaction, screenshots, and visual verification.
14
+ 3. If the host says \`MCP server "playwright" not found\`, do not conclude that Playwright is impossible. First report that the skill was not loaded or the Playwright MCP server was not registered in this session.
15
+ 4. If Chromium starts but fails with missing Linux libraries such as \`libnspr4\`, \`libnss3\`, \`libatk-bridge\`, \`libgtk-3\`, or similar, distinguish browser OS dependencies from MCP availability.
16
+
17
+ ## Linux dependency fallback
18
+
19
+ Playwright has two dependency layers:
20
+
21
+ - Browser binary: installable without sudo with \`npx playwright install chromium\` or by setting \`HIAI_PLAYWRIGHT_INSTALL_BROWSERS=1\` before OpenCode starts.
22
+ - System libraries: on minimal Linux images these usually require admin rights via \`sudo npx playwright install-deps chromium\` or OS package manager equivalents.
23
+
24
+ If sudo is unavailable, try these alternatives before falling back to curl-only checks:
25
+
26
+ - Use an already installed Chrome/Chromium/Edge by adding Playwright MCP args in \`hiai-opencode.json\`, for example \`--browser chrome\` or \`--browser msedge\`.
27
+ - Use a remote/browser service or CDP-backed browser when available.
28
+ - Switch the browser automation provider to \`agent-browser\` or \`playwright-cli\` if the workspace has those tools installed.
29
+
30
+ Only use \`curl\` as a final degraded check. Clearly say that HTTP checks do not replace interactive browser verification.`,
9
31
  mcpConfig: {
10
32
  playwright: {
11
33
  command: "npx",
12
- args: ["@playwright/mcp@latest"],
34
+ args: ["-y", "@playwright/mcp@latest"],
13
35
  },
14
36
  },
15
37
  }
@@ -62,6 +62,12 @@ export async function loadGlobalAgentsSkills(): Promise<Record<string, CommandDe
62
62
  return skillsToCommandDefinitionRecord(skills)
63
63
  }
64
64
 
65
+ export async function loadManagedPluginSkills(): Promise<Record<string, CommandDefinition>> {
66
+ const skillsDir = join(getOpenCodeConfigDir({ binary: "opencode" }), ".hiai", "skills", "plugin")
67
+ const skills = await loadSkillsFromDir({ skillsDir, scope: "builtin" })
68
+ return skillsToCommandDefinitionRecord(skills)
69
+ }
70
+
65
71
  export interface DiscoverSkillsOptions {
66
72
  includeClaudeCodePaths?: boolean
67
73
  directory?: string
@@ -170,3 +176,8 @@ export async function discoverGlobalAgentsSkills(): Promise<LoadedSkill[]> {
170
176
  const agentsGlobalDir = join(getAgentsConfigDir(), "skills")
171
177
  return loadSkillsFromDir({ skillsDir: agentsGlobalDir, scope: "user" })
172
178
  }
179
+
180
+ export async function discoverManagedPluginSkills(): Promise<LoadedSkill[]> {
181
+ const skillsDir = join(getOpenCodeConfigDir({ binary: "opencode" }), ".hiai", "skills", "plugin")
182
+ return loadSkillsFromDir({ skillsDir, scope: "builtin" })
183
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ 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 { warnIfListPluginEntry, warnMissingRequiredMcpEnv } from "./shared/startup-diagnostics"
21
22
  import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
22
23
  import { lspManager } from "./tools/lsp/client"
23
24
 
@@ -56,6 +57,7 @@ function configureBundledBunPtyLibrary(): void {
56
57
  const candidates = [
57
58
  join(import.meta.dirname, "..", "node_modules", "bun-pty", "rust-pty", "target", "release", libraryName),
58
59
  join(import.meta.dirname, "..", "..", "bun-pty", "rust-pty", "target", "release", libraryName),
60
+ join(import.meta.dirname, "..", "..", "..", "bun-pty", "rust-pty", "target", "release", libraryName),
59
61
  ]
60
62
 
61
63
  const resolved = candidates.find((candidate) => existsSync(candidate))
@@ -68,6 +70,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
68
70
  log("[HiaiOpenCodePlugin] ENTRY - plugin loading", {
69
71
  directory: ctx.directory,
70
72
  })
73
+ warnIfListPluginEntry(ctx.directory)
71
74
 
72
75
  const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
73
76
  if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
@@ -82,6 +85,10 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
82
85
  loadPluginConfig(ctx.directory, ctx),
83
86
  internalConfig,
84
87
  )
88
+ warnMissingRequiredMcpEnv({
89
+ pluginConfig,
90
+ platformConfig: internalConfig,
91
+ })
85
92
 
86
93
  materializeBuiltinSkills(
87
94
  createBuiltinSkills({
@@ -219,29 +226,24 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
219
226
  auth: {
220
227
  provider: "hiai-opencode",
221
228
  methods: [
222
- { type: "api" as const, label: "Google API key" },
223
- { type: "api" as const, label: "OpenAI API key" },
224
- { type: "api" as const, label: "OpenRouter API key" },
229
+ { type: "api" as const, label: "Google Search API key" },
225
230
  ],
226
231
  loader: async (getAuth: any) => {
227
232
  const authData = await getAuth();
228
233
  const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
229
234
 
230
- // Helper to get key from config or authData
231
- const getKey = (label: string, configKey?: string) => {
232
- const fromAuth = authData[label];
233
- if (fromAuth) return fromAuth;
235
+ const getConfiguredKey = (configKey?: string) => {
234
236
  if (configKey) return resolveEnvVars(configKey);
235
237
  return undefined;
236
238
  };
237
239
 
238
- const googleKey = getKey("Google API key", internalConfig.auth?.googleSearch);
239
- const openaiKey = getKey("OpenAI API key", internalConfig.auth?.openai);
240
- const openRouterKey = getKey("OpenRouter API key", internalConfig.auth?.openrouter);
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);
241
243
 
242
- if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve(googleKey));
243
- if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve(openaiKey));
244
- if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve(openRouterKey));
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 }));
245
247
 
246
248
  return {};
247
249
  },
package/src/mcp/index.ts CHANGED
@@ -1,45 +1,12 @@
1
1
  export { createBuiltinMcps } from "./omo-mcp-index"
2
2
  export { McpNameSchema, type McpName, type AnyMcpName } from "./types"
3
3
 
4
- import { existsSync } from "node:fs";
5
- import { join } from "node:path";
6
4
  import type { McpServerConfig } from "../config/types.js";
7
5
  import { resolveEnvVars } from "../config/loader.js";
8
6
 
9
- const ASSETS_DIR = join(import.meta.dirname || __dirname, "..", "assets", "mcp");
10
-
11
- function resolveBundledMcp(name: string): string {
12
- const script = join(ASSETS_DIR, name);
13
- if (existsSync(script)) return script;
14
- return "";
15
- }
16
-
17
7
  export function buildMcpConfig(mcp: Record<string, McpServerConfig>): Record<string, unknown> {
18
8
  const result: Record<string, unknown> = {};
19
9
 
20
- const defaults: Record<string, McpServerConfig> = {
21
- rag: {
22
- enabled: true,
23
- type: "local",
24
- command: ["node", resolveBundledMcp("rag.mjs")].filter(Boolean),
25
- environment: {
26
- OPENCODE_RAG_URL: "http://localhost:9002/tools/search",
27
- },
28
- },
29
- mempalace: {
30
- enabled: true,
31
- type: "local",
32
- command: ["node", resolveBundledMcp("mempalace.mjs"), "--palace", "./.opencode/palace"].filter(Boolean),
33
- timeout: 60000,
34
- },
35
- };
36
-
37
- for (const [name, server] of Object.entries(defaults)) {
38
- if (!(name in mcp) && server.command && server.command.length >= 2) {
39
- mcp[name] = server;
40
- }
41
- }
42
-
43
10
  for (const [name, server] of Object.entries(mcp)) {
44
11
  if (!server.enabled) continue;
45
12
 
@@ -1,5 +1,4 @@
1
1
  import { createWebsearchConfig } from "./websearch"
2
- import { context7 } from "./context7"
3
2
  import { grep_app } from "./grep-app"
4
3
  import type { HiaiOpenCodeConfig } from "../config/schema"
5
4
 
@@ -23,10 +22,6 @@ export function createBuiltinMcps(disabledMcps: string[] = [], config?: HiaiOpen
23
22
  }
24
23
  }
25
24
 
26
- if (!disabledMcps.includes("context7")) {
27
- mcps.context7 = context7
28
- }
29
-
30
25
  if (!disabledMcps.includes("grep_app")) {
31
26
  mcps.grep_app = grep_app
32
27
  }
@@ -0,0 +1,132 @@
1
+ import { join } from "node:path"
2
+ import type { McpServerConfig } from "../config/types.js"
3
+
4
+ export type HiaiMcpName =
5
+ | "playwright"
6
+ | "stitch"
7
+ | "sequential-thinking"
8
+ | "firecrawl"
9
+ | "rag"
10
+ | "context7"
11
+ | "mempalace"
12
+
13
+ export type HiaiMcpInstallKind = "bundled" | "npm" | "python" | "remote" | "user-service"
14
+
15
+ export interface HiaiMcpRegistryEntry {
16
+ name: HiaiMcpName
17
+ enabledByDefault: boolean
18
+ install: HiaiMcpInstallKind
19
+ requiredEnv?: string[]
20
+ optionalEnv?: string[]
21
+ config: McpServerConfig
22
+ }
23
+
24
+ function resolveAssetScript(...segments: string[]): string {
25
+ return join(import.meta.dirname, "..", "assets", ...segments)
26
+ }
27
+
28
+ function createNpmPackageCommand(pkg: string, ...args: string[]): string[] {
29
+ return ["node", resolveAssetScript("runtime", "npm-package-runner.mjs"), pkg, ...args]
30
+ }
31
+
32
+ export const HIAI_MCP_REGISTRY: Record<HiaiMcpName, HiaiMcpRegistryEntry> = {
33
+ playwright: {
34
+ name: "playwright",
35
+ enabledByDefault: true,
36
+ install: "npm",
37
+ optionalEnv: ["HIAI_PLAYWRIGHT_INSTALL_BROWSERS"],
38
+ config: {
39
+ enabled: true,
40
+ command: ["node", resolveAssetScript("mcp", "playwright.mjs")],
41
+ timeout: 600000,
42
+ },
43
+ },
44
+ stitch: {
45
+ name: "stitch",
46
+ enabledByDefault: true,
47
+ install: "remote",
48
+ requiredEnv: ["STITCH_AI_API_KEY"],
49
+ config: {
50
+ enabled: true,
51
+ type: "remote",
52
+ url: "https://stitch.googleapis.com/mcp",
53
+ headers: { "X-Goog-Api-Key": "{env:STITCH_AI_API_KEY}" },
54
+ timeout: 600000,
55
+ },
56
+ },
57
+ "sequential-thinking": {
58
+ name: "sequential-thinking",
59
+ enabledByDefault: true,
60
+ install: "npm",
61
+ config: {
62
+ enabled: true,
63
+ command: createNpmPackageCommand("@modelcontextprotocol/server-sequential-thinking"),
64
+ timeout: 600000,
65
+ },
66
+ },
67
+ firecrawl: {
68
+ name: "firecrawl",
69
+ enabledByDefault: true,
70
+ install: "npm",
71
+ requiredEnv: ["FIRECRAWL_API_KEY"],
72
+ config: {
73
+ enabled: true,
74
+ command: createNpmPackageCommand("firecrawl-mcp"),
75
+ timeout: 600000,
76
+ environment: { FIRECRAWL_API_KEY: "{env:FIRECRAWL_API_KEY}" },
77
+ },
78
+ },
79
+ rag: {
80
+ name: "rag",
81
+ enabledByDefault: true,
82
+ install: "user-service",
83
+ optionalEnv: ["OPENCODE_RAG_URL"],
84
+ config: {
85
+ enabled: true,
86
+ type: "local",
87
+ command: ["node", resolveAssetScript("mcp", "rag.mjs")],
88
+ environment: {
89
+ OPENCODE_RAG_URL: "{env:OPENCODE_RAG_URL:-http://localhost:9002/tools/search}",
90
+ },
91
+ timeout: 600000,
92
+ },
93
+ },
94
+ context7: {
95
+ name: "context7",
96
+ enabledByDefault: true,
97
+ install: "remote",
98
+ optionalEnv: ["CONTEXT7_API_KEY"],
99
+ config: {
100
+ enabled: true,
101
+ type: "remote",
102
+ url: "https://mcp.context7.com/mcp",
103
+ headers: { "X-API-KEY": "{env:CONTEXT7_API_KEY}" },
104
+ timeout: 600000,
105
+ },
106
+ },
107
+ mempalace: {
108
+ name: "mempalace",
109
+ enabledByDefault: true,
110
+ install: "python",
111
+ optionalEnv: ["MEMPALACE_PYTHON", "MEMPALACE_PALACE_PATH", "HIAI_MCP_AUTO_INSTALL"],
112
+ config: {
113
+ enabled: true,
114
+ type: "local",
115
+ command: ["node", resolveAssetScript("mcp", "mempalace.mjs"), "--palace", "./.opencode/palace"],
116
+ timeout: 600000,
117
+ },
118
+ },
119
+ }
120
+
121
+ export function createDefaultMcpConfig(): Record<HiaiMcpName, McpServerConfig> {
122
+ return Object.fromEntries(
123
+ Object.entries(HIAI_MCP_REGISTRY).map(([name, entry]) => [
124
+ name,
125
+ { ...entry.config, enabled: entry.enabledByDefault },
126
+ ]),
127
+ ) as Record<HiaiMcpName, McpServerConfig>
128
+ }
129
+
130
+ export function getKnownMcpNames(): HiaiMcpName[] {
131
+ return Object.keys(HIAI_MCP_REGISTRY) as HiaiMcpName[]
132
+ }
package/src/mcp/types.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import { z } from "zod"
2
2
 
3
- export const McpNameSchema = z.enum(["websearch", "context7", "grep_app"])
3
+ export const McpNameSchema = z.enum([
4
+ "playwright",
5
+ "stitch",
6
+ "sequential-thinking",
7
+ "firecrawl",
8
+ "rag",
9
+ "context7",
10
+ "mempalace",
11
+ "websearch",
12
+ "grep_app",
13
+ ])
4
14
 
5
15
  export type McpName = z.infer<typeof McpNameSchema>
6
16
 
@@ -136,7 +136,7 @@ export function createToolGuardHooks(args: {
136
136
  : null
137
137
 
138
138
  const fastApply = isHookEnabled("fast-apply")
139
- ? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "http://localhost:11434", model: "qwen3.5:9b", timeout: 30000 }))
139
+ ? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "", model: "", timeout: 30000 }))
140
140
  : null
141
141
 
142
142
  return {
@@ -8,6 +8,7 @@ import type {
8
8
 
9
9
  import {
10
10
  discoverConfigSourceSkills,
11
+ discoverManagedPluginSkills,
11
12
  discoverUserClaudeSkills,
12
13
  discoverProjectClaudeSkills,
13
14
  discoverOpencodeGlobalSkills,
@@ -18,6 +19,7 @@ import {
18
19
  } from "../features/opencode-skill-loader"
19
20
  import { createBuiltinSkills } from "../features/builtin-skills"
20
21
  import { getSystemMcpServerNames } from "../features/claude-code-mcp-loader"
22
+ import { resolveSkillDiscoveryConfig } from "./skill-discovery-config"
21
23
 
22
24
  export type SkillContext = {
23
25
  mergedSkills: LoadedSkill[]
@@ -71,21 +73,37 @@ export async function createSkillContext(args: {
71
73
  return true
72
74
  })
73
75
 
74
- const includeClaudeSkills = pluginConfig.claude_code?.skills !== false
75
- const [configSourceSkills, userSkills, globalSkills, projectSkills, opencodeProjectSkills, agentsProjectSkills, agentsGlobalSkills] =
76
+ const discovery = resolveSkillDiscoveryConfig(pluginConfig)
77
+ const [
78
+ managedPluginSkills,
79
+ configSourceSkills,
80
+ userSkills,
81
+ globalSkills,
82
+ projectSkills,
83
+ opencodeProjectSkills,
84
+ agentsProjectSkills,
85
+ agentsGlobalSkills,
86
+ ] =
76
87
  await Promise.all([
77
- discoverConfigSourceSkills({
78
- config: pluginConfig.skills,
79
- configDir: directory,
80
- }),
81
- includeClaudeSkills ? discoverUserClaudeSkills() : Promise.resolve([]),
82
- discoverOpencodeGlobalSkills(),
83
- includeClaudeSkills ? discoverProjectClaudeSkills(directory) : Promise.resolve([]),
84
- discoverOpencodeProjectSkills(directory),
85
- discoverProjectAgentsSkills(directory),
86
- discoverGlobalAgentsSkills(),
88
+ discoverManagedPluginSkills(),
89
+ discovery.config_sources
90
+ ? discoverConfigSourceSkills({
91
+ config: pluginConfig.skills,
92
+ configDir: directory,
93
+ })
94
+ : Promise.resolve([]),
95
+ discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
96
+ discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
97
+ discovery.project_claude ? discoverProjectClaudeSkills(directory) : Promise.resolve([]),
98
+ discovery.project_opencode ? discoverOpencodeProjectSkills(directory) : Promise.resolve([]),
99
+ discovery.project_agents ? discoverProjectAgentsSkills(directory) : Promise.resolve([]),
100
+ discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
87
101
  ])
88
102
 
103
+ const filteredManagedPluginSkills = filterProviderGatedSkills(
104
+ managedPluginSkills,
105
+ browserProvider,
106
+ )
89
107
  const filteredConfigSourceSkills = filterProviderGatedSkills(
90
108
  configSourceSkills,
91
109
  browserProvider,
@@ -109,7 +127,7 @@ export async function createSkillContext(args: {
109
127
  const mergedSkills = mergeSkills(
110
128
  builtinSkills,
111
129
  pluginConfig.skills,
112
- filteredConfigSourceSkills,
130
+ [...filteredManagedPluginSkills, ...filteredConfigSourceSkills],
113
131
  [...filteredUserSkills, ...filteredAgentsGlobalSkills],
114
132
  filteredGlobalSkills,
115
133
  [...filteredProjectSkills, ...filteredAgentsProjectSkills],
@@ -0,0 +1,32 @@
1
+ import type { HiaiOpenCodeConfig } from "../config"
2
+ import type { SkillDiscoveryConfig } from "../config/schema"
3
+
4
+ export type ResolvedSkillDiscoveryConfig = Required<SkillDiscoveryConfig>
5
+
6
+ const DEFAULT_SKILL_DISCOVERY: ResolvedSkillDiscoveryConfig = {
7
+ config_sources: true,
8
+ project_opencode: true,
9
+ global_opencode: false,
10
+ project_claude: false,
11
+ global_claude: false,
12
+ project_agents: false,
13
+ global_agents: false,
14
+ }
15
+
16
+ export function resolveSkillDiscoveryConfig(
17
+ pluginConfig: HiaiOpenCodeConfig,
18
+ ): ResolvedSkillDiscoveryConfig {
19
+ const resolved: ResolvedSkillDiscoveryConfig = {
20
+ ...DEFAULT_SKILL_DISCOVERY,
21
+ ...(pluginConfig.skill_discovery ?? {}),
22
+ }
23
+
24
+ // Compatibility switch: historically this only controlled Claude-style skills.
25
+ // Keep that meaning, but make it stronger for those two sources.
26
+ if (pluginConfig.claude_code?.skills === false) {
27
+ resolved.project_claude = false
28
+ resolved.global_claude = false
29
+ }
30
+
31
+ return resolved
32
+ }