@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
@@ -7,6 +7,7 @@ import { AGENT_NAME_MAP } from "../shared/migration";
7
7
  import { registerAgentName } from "../features/claude-code-session-state";
8
8
  import {
9
9
  discoverConfigSourceSkills,
10
+ discoverManagedPluginSkills,
10
11
  deduplicateSkillsByName,
11
12
  discoverGlobalAgentsSkills,
12
13
  discoverOpencodeGlobalSkills,
@@ -31,6 +32,7 @@ import {
31
32
  filterProtectedAgentOverrides,
32
33
  } from "./agent-override-protection";
33
34
  import { buildStrategistAgentConfig } from "./strategist-agent-config-builder";
35
+ import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
34
36
 
35
37
  type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
36
38
  build?: Record<string, unknown>;
@@ -130,8 +132,9 @@ export async function applyAgentConfig(params: {
130
132
  },
131
133
  ) as typeof params.pluginConfig.disabled_agents;
132
134
 
133
- const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true;
135
+ const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
134
136
  const [
137
+ discoveredManagedPluginSkills,
135
138
  discoveredConfigSourceSkills,
136
139
  discoveredUserSkills,
137
140
  discoveredProjectSkills,
@@ -140,23 +143,27 @@ export async function applyAgentConfig(params: {
140
143
  discoveredOpencodeProjectSkills,
141
144
  discoveredGlobalAgentsSkills,
142
145
  ] = await Promise.all([
143
- discoverConfigSourceSkills({
144
- config: params.pluginConfig.skills,
145
- configDir: params.ctx.directory,
146
- }),
147
- includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]),
148
- includeClaudeSkillsForAwareness
149
- ? discoverProjectClaudeSkills(params.ctx.directory)
150
- : Promise.resolve([]),
151
- includeClaudeSkillsForAwareness
146
+ discoverManagedPluginSkills(),
147
+ discovery.config_sources
148
+ ? discoverConfigSourceSkills({
149
+ config: params.pluginConfig.skills,
150
+ configDir: params.ctx.directory,
151
+ })
152
+ : Promise.resolve([]),
153
+ discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
154
+ discovery.project_claude
155
+ ? discoverProjectClaudeSkills(params.ctx.directory)
156
+ : Promise.resolve([]),
157
+ discovery.project_agents
152
158
  ? discoverProjectAgentsSkills(params.ctx.directory)
153
159
  : Promise.resolve([]),
154
- discoverOpencodeGlobalSkills(),
155
- discoverOpencodeProjectSkills(params.ctx.directory),
156
- includeClaudeSkillsForAwareness ? discoverGlobalAgentsSkills() : Promise.resolve([]),
160
+ discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
161
+ discovery.project_opencode ? discoverOpencodeProjectSkills(params.ctx.directory) : Promise.resolve([]),
162
+ discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
157
163
  ]);
158
164
 
159
165
  const allDiscoveredSkills = [
166
+ ...discoveredManagedPluginSkills,
160
167
  ...discoveredConfigSourceSkills,
161
168
  ...discoveredOpencodeProjectSkills,
162
169
  ...discoveredProjectSkills,
@@ -12,6 +12,7 @@ import {
12
12
  import { loadBuiltinCommands } from "../features/builtin-commands";
13
13
  import {
14
14
  discoverConfigSourceSkills,
15
+ loadManagedPluginSkills,
15
16
  loadGlobalAgentsSkills,
16
17
  loadProjectAgentsSkills,
17
18
  loadUserSkills,
@@ -26,6 +27,7 @@ import {
26
27
  log,
27
28
  } from "../shared";
28
29
  import type { PluginComponents } from "./plugin-components-loader";
30
+ import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
29
31
 
30
32
  export async function applyCommandConfig(params: {
31
33
  config: Record<string, unknown>;
@@ -39,10 +41,13 @@ export async function applyCommandConfig(params: {
39
41
  const systemCommands = (params.config.command as Record<string, unknown>) ?? {};
40
42
 
41
43
  const includeClaudeCommands = params.pluginConfig.claude_code?.commands ?? true;
42
- const includeClaudeSkills = params.pluginConfig.claude_code?.skills ?? true;
44
+ const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
43
45
 
44
46
  const externalSkillPlugin = detectExternalSkillPlugin(params.ctx.directory);
45
- if (includeClaudeSkills && externalSkillPlugin.detected) {
47
+ if (
48
+ (discovery.project_claude || discovery.global_claude || discovery.global_opencode) &&
49
+ externalSkillPlugin.detected
50
+ ) {
46
51
  log(getSkillPluginConflictWarning(externalSkillPlugin.pluginName!));
47
52
  }
48
53
 
@@ -52,6 +57,7 @@ export async function applyCommandConfig(params: {
52
57
  projectCommands,
53
58
  opencodeGlobalCommands,
54
59
  opencodeProjectCommands,
60
+ managedPluginSkills,
55
61
  userSkills,
56
62
  globalAgentsSkills,
57
63
  projectSkills,
@@ -59,24 +65,28 @@ export async function applyCommandConfig(params: {
59
65
  opencodeGlobalSkills,
60
66
  opencodeProjectSkills,
61
67
  ] = await Promise.all([
62
- discoverConfigSourceSkills({
63
- config: params.pluginConfig.skills,
64
- configDir: params.ctx.directory,
65
- }),
68
+ discovery.config_sources
69
+ ? discoverConfigSourceSkills({
70
+ config: params.pluginConfig.skills,
71
+ configDir: params.ctx.directory,
72
+ })
73
+ : Promise.resolve([]),
66
74
  includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
67
75
  includeClaudeCommands ? loadProjectCommands(params.ctx.directory) : Promise.resolve({}),
68
76
  loadOpencodeGlobalCommands(),
69
77
  loadOpencodeProjectCommands(params.ctx.directory),
70
- includeClaudeSkills ? loadUserSkills() : Promise.resolve({}),
71
- includeClaudeSkills ? loadGlobalAgentsSkills() : Promise.resolve({}),
72
- includeClaudeSkills ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}),
73
- includeClaudeSkills ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}),
74
- loadOpencodeGlobalSkills(),
75
- loadOpencodeProjectSkills(params.ctx.directory),
78
+ loadManagedPluginSkills(),
79
+ discovery.global_claude ? loadUserSkills() : Promise.resolve({}),
80
+ discovery.global_agents ? loadGlobalAgentsSkills() : Promise.resolve({}),
81
+ discovery.project_claude ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}),
82
+ discovery.project_agents ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}),
83
+ discovery.global_opencode ? loadOpencodeGlobalSkills() : Promise.resolve({}),
84
+ discovery.project_opencode ? loadOpencodeProjectSkills(params.ctx.directory) : Promise.resolve({}),
76
85
  ]);
77
86
 
78
87
  params.config.command = {
79
88
  ...builtinCommands,
89
+ ...managedPluginSkills,
80
90
  ...skillsToCommandDefinitionRecord(configSourceSkills),
81
91
  ...userCommands,
82
92
  ...userSkills,
@@ -64,10 +64,10 @@ export const AGENT_NAME_MAP: Record<string, string> = {
64
64
  // Designer
65
65
  designer: "designer",
66
66
 
67
- // Multimodal (runtime key remains "ui" for compatibility)
68
- ui: "ui",
69
- vision: "ui",
70
- multimodal: "ui",
67
+ // Multimodal / Vision
68
+ ui: "multimodal",
69
+ vision: "multimodal",
70
+ multimodal: "multimodal",
71
71
  }
72
72
 
73
73
  export const BUILTIN_AGENT_NAMES = new Set([
@@ -77,7 +77,7 @@ export const BUILTIN_AGENT_NAMES = new Set([
77
77
  "critic",
78
78
  "designer",
79
79
  "researcher",
80
- "ui",
80
+ "multimodal",
81
81
  "platform-manager",
82
82
  "guard",
83
83
  ])
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+
4
+ import type { HiaiOpenCodeConfig, HiaiOpencodeConfig } from "../config"
5
+ import { HIAI_MCP_REGISTRY } from "../mcp/registry"
6
+ import { parseJsoncSafe } from "./jsonc-parser"
7
+ import { getOpenCodeConfigPaths } from "./opencode-config-dir"
8
+ import { PLUGIN_NAME } from "./plugin-identity"
9
+
10
+ interface OpenCodeConfig {
11
+ plugin?: Array<string | [string, ...unknown[]]>
12
+ }
13
+
14
+ function readPlugins(configPath: string): string[] {
15
+ if (!existsSync(configPath)) return []
16
+
17
+ try {
18
+ const content = readFileSync(configPath, "utf-8")
19
+ const parsed = parseJsoncSafe<OpenCodeConfig>(content)
20
+ return (parsed.data?.plugin ?? [])
21
+ .map((entry) => typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : "")
22
+ .filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
23
+ } catch {
24
+ return []
25
+ }
26
+ }
27
+
28
+ export function warnIfListPluginEntry(directory: string): void {
29
+ const globalPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
30
+ const candidates = [
31
+ join(directory, ".opencode", "opencode.json"),
32
+ join(directory, ".opencode", "opencode.jsonc"),
33
+ globalPaths.configJson,
34
+ globalPaths.configJsonc,
35
+ ]
36
+
37
+ for (const configPath of candidates) {
38
+ const plugins = readPlugins(configPath)
39
+ if (!plugins.includes("list")) continue
40
+
41
+ console.warn(`[hiai-opencode] WARNING: ${configPath} contains plugin: ["list"].`)
42
+ console.warn("[hiai-opencode] This can prevent hiai-opencode MCP servers from loading from that config scope.")
43
+ console.warn(`[hiai-opencode] Update it to: plugin: ["${PLUGIN_NAME}"]`)
44
+ }
45
+ }
46
+
47
+ function hasConfigAuthFallback(pluginConfig: HiaiOpenCodeConfig, envName: string): boolean {
48
+ if (envName === "FIRECRAWL_API_KEY") return !!pluginConfig.auth?.firecrawl?.trim()
49
+ if (envName === "STITCH_AI_API_KEY") return !!pluginConfig.auth?.stitch?.trim()
50
+ if (envName === "CONTEXT7_API_KEY") return !!pluginConfig.auth?.context7?.trim()
51
+ return false
52
+ }
53
+
54
+ export function warnMissingRequiredMcpEnv(args: {
55
+ pluginConfig: HiaiOpenCodeConfig
56
+ platformConfig: HiaiOpencodeConfig
57
+ }): void {
58
+ const disabled = new Set(args.pluginConfig.disabled_mcps ?? [])
59
+ const mcpConfig = args.platformConfig.mcp ?? {}
60
+
61
+ for (const [name, entry] of Object.entries(HIAI_MCP_REGISTRY)) {
62
+ if (disabled.has(name)) continue
63
+ if (mcpConfig[name]?.enabled === false) continue
64
+ if (!entry.requiredEnv || entry.requiredEnv.length === 0) continue
65
+
66
+ const missing = entry.requiredEnv.filter((envName) =>
67
+ !process.env[envName]?.trim() && !hasConfigAuthFallback(args.pluginConfig, envName)
68
+ )
69
+
70
+ if (missing.length === 0) continue
71
+
72
+ console.warn(
73
+ `[hiai-opencode] MCP "${name}" is enabled but missing required env: ${missing.join(", ")}.`
74
+ + " The plugin will continue to load; set the key or disable this MCP in hiai-opencode.json.",
75
+ )
76
+ }
77
+ }
@@ -1,32 +0,0 @@
1
- export const MODEL_ROLE_GUIDE = [
2
- "fast: cheap/default for bounded helpers, researcher-style scans, platform chores",
3
- "mid: balanced default for steady execution/review work",
4
- "high: stronger general-purpose model for primary implementation and planning",
5
- "ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions",
6
- "vision: preferred for UI/media/multimodal interpretation and visual work",
7
- "reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable",
8
- ] as const;
9
-
10
- export const PROVIDER_MODEL_RULES = [
11
- "openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`",
12
- "anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`",
13
- "deepseek: use `deepseek/<model>` when connected directly",
14
- "glm: use `z-ai/<model>` or the provider id exposed by your gateway/client",
15
- "minimax: use `minimax/<model>`",
16
- "qwen: use `qwen/<model>`",
17
- "ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`",
18
- "openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`",
19
- "rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids",
20
- ] as const;
21
-
22
- export const MODEL_PRESETS = {
23
- fast: "openrouter/google/gemini-2.0-flash",
24
- mid: "openrouter/anthropic/claude-3.5-sonnet",
25
- high: "openrouter/anthropic/claude-3.5-opus",
26
- ultrahigh: "openrouter/openai/gpt-4o",
27
- vision: "openrouter/google/gemini-2.0-pro-exp-02-05",
28
- reasoning: "openrouter/openai/o1",
29
- strategist: "openrouter/z-ai/glm-5.1",
30
- critic: "openrouter/qwen/qwen2.5-72b-instruct",
31
- writing: "openrouter/kimi/kimi-latest",
32
- } as const;