@ken-jo/agent-connector 0.1.0

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 (203) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +348 -0
  4. package/dist/amp-QSFH2V7Y.js +217 -0
  5. package/dist/amp-QSFH2V7Y.js.map +1 -0
  6. package/dist/amp-TULUPXXL.js +237 -0
  7. package/dist/amp-TULUPXXL.js.map +1 -0
  8. package/dist/antigravity-CX2YLSY7.js +17 -0
  9. package/dist/antigravity-CX2YLSY7.js.map +1 -0
  10. package/dist/antigravity-STHZWMP3.js +171 -0
  11. package/dist/antigravity-STHZWMP3.js.map +1 -0
  12. package/dist/antigravity-cli-P6HADOAO.js +121 -0
  13. package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
  14. package/dist/antigravity-cli-YQDFVV4U.js +86 -0
  15. package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
  16. package/dist/chunk-54EMKTTX.js +191 -0
  17. package/dist/chunk-54EMKTTX.js.map +1 -0
  18. package/dist/chunk-7L6ITC4T.js +54 -0
  19. package/dist/chunk-7L6ITC4T.js.map +1 -0
  20. package/dist/chunk-7MSDGHJW.js +316 -0
  21. package/dist/chunk-7MSDGHJW.js.map +1 -0
  22. package/dist/chunk-BCHWFTV4.js +196 -0
  23. package/dist/chunk-BCHWFTV4.js.map +1 -0
  24. package/dist/chunk-DBQMX75U.js +36 -0
  25. package/dist/chunk-DBQMX75U.js.map +1 -0
  26. package/dist/chunk-EXLS7BSI.js +29 -0
  27. package/dist/chunk-EXLS7BSI.js.map +1 -0
  28. package/dist/chunk-JNM3TPLX.js +593 -0
  29. package/dist/chunk-JNM3TPLX.js.map +1 -0
  30. package/dist/chunk-JWNT5KW7.js +197 -0
  31. package/dist/chunk-JWNT5KW7.js.map +1 -0
  32. package/dist/chunk-KHO5WNTP.js +30 -0
  33. package/dist/chunk-KHO5WNTP.js.map +1 -0
  34. package/dist/chunk-LTT56GI3.js +155 -0
  35. package/dist/chunk-LTT56GI3.js.map +1 -0
  36. package/dist/chunk-MEH7GC7R.js +111 -0
  37. package/dist/chunk-MEH7GC7R.js.map +1 -0
  38. package/dist/chunk-MQPNU2CY.js +64 -0
  39. package/dist/chunk-MQPNU2CY.js.map +1 -0
  40. package/dist/chunk-OPXAP3AC.js +12 -0
  41. package/dist/chunk-OPXAP3AC.js.map +1 -0
  42. package/dist/chunk-RK6VBLXP.js +50 -0
  43. package/dist/chunk-RK6VBLXP.js.map +1 -0
  44. package/dist/chunk-TIIP3RG2.js +420 -0
  45. package/dist/chunk-TIIP3RG2.js.map +1 -0
  46. package/dist/chunk-TRKWUNO4.js +702 -0
  47. package/dist/chunk-TRKWUNO4.js.map +1 -0
  48. package/dist/chunk-TRQKHP7D.js +93 -0
  49. package/dist/chunk-TRQKHP7D.js.map +1 -0
  50. package/dist/chunk-WCETQWWK.js +59 -0
  51. package/dist/chunk-WCETQWWK.js.map +1 -0
  52. package/dist/chunk-WV63X7IK.js +357 -0
  53. package/dist/chunk-WV63X7IK.js.map +1 -0
  54. package/dist/chunk-XD7MY6HB.js +343 -0
  55. package/dist/chunk-XD7MY6HB.js.map +1 -0
  56. package/dist/chunk-XNAM7F35.js +276 -0
  57. package/dist/chunk-XNAM7F35.js.map +1 -0
  58. package/dist/chunk-XUCWLJY5.js +238 -0
  59. package/dist/chunk-XUCWLJY5.js.map +1 -0
  60. package/dist/chunk-YNZU6PAV.js +800 -0
  61. package/dist/chunk-YNZU6PAV.js.map +1 -0
  62. package/dist/chunk-YT43MUSJ.js +146 -0
  63. package/dist/chunk-YT43MUSJ.js.map +1 -0
  64. package/dist/claude-code-HKDV7T32.js +637 -0
  65. package/dist/claude-code-HKDV7T32.js.map +1 -0
  66. package/dist/claude-code-W74MNTAC.js +652 -0
  67. package/dist/claude-code-W74MNTAC.js.map +1 -0
  68. package/dist/cli/sdk.d.ts +54 -0
  69. package/dist/cli/sdk.js +82 -0
  70. package/dist/cli/sdk.js.map +1 -0
  71. package/dist/cli.d.ts +2 -0
  72. package/dist/cli.js +15 -0
  73. package/dist/cli.js.map +1 -0
  74. package/dist/codebuff-3PMZJJHP.js +307 -0
  75. package/dist/codebuff-3PMZJJHP.js.map +1 -0
  76. package/dist/codebuff-QX57FJDN.js +220 -0
  77. package/dist/codebuff-QX57FJDN.js.map +1 -0
  78. package/dist/codex-JFQTS6LU.js +593 -0
  79. package/dist/codex-JFQTS6LU.js.map +1 -0
  80. package/dist/codex-Q3ATRRX3.js +364 -0
  81. package/dist/codex-Q3ATRRX3.js.map +1 -0
  82. package/dist/copilot-cli-MTCGHZ2T.js +622 -0
  83. package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
  84. package/dist/copilot-cli-SJGIXVSD.js +369 -0
  85. package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
  86. package/dist/crush-I7PK3FGI.js +360 -0
  87. package/dist/crush-I7PK3FGI.js.map +1 -0
  88. package/dist/crush-LN5QFFR2.js +183 -0
  89. package/dist/crush-LN5QFFR2.js.map +1 -0
  90. package/dist/cursor-4TMXKJOZ.js +603 -0
  91. package/dist/cursor-4TMXKJOZ.js.map +1 -0
  92. package/dist/cursor-QZ6MTO74.js +194 -0
  93. package/dist/cursor-QZ6MTO74.js.map +1 -0
  94. package/dist/detect-WIAGVYS4.js +68 -0
  95. package/dist/detect-WIAGVYS4.js.map +1 -0
  96. package/dist/doctor-EZW2K4NI.js +372 -0
  97. package/dist/doctor-EZW2K4NI.js.map +1 -0
  98. package/dist/droid-KN6VRMMT.js +424 -0
  99. package/dist/droid-KN6VRMMT.js.map +1 -0
  100. package/dist/droid-W5WF5XEX.js +224 -0
  101. package/dist/droid-W5WF5XEX.js.map +1 -0
  102. package/dist/gemini-cli-CIRW37TU.js +369 -0
  103. package/dist/gemini-cli-CIRW37TU.js.map +1 -0
  104. package/dist/gemini-cli-Y35FFK4M.js +705 -0
  105. package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
  106. package/dist/goose-A3M5GODH.js +212 -0
  107. package/dist/goose-A3M5GODH.js.map +1 -0
  108. package/dist/goose-ZTGTSUFJ.js +480 -0
  109. package/dist/goose-ZTGTSUFJ.js.map +1 -0
  110. package/dist/hermes-H2KV44GK.js +470 -0
  111. package/dist/hermes-H2KV44GK.js.map +1 -0
  112. package/dist/hermes-ZYLMOFLZ.js +196 -0
  113. package/dist/hermes-ZYLMOFLZ.js.map +1 -0
  114. package/dist/hook-YVDILAMZ.js +75 -0
  115. package/dist/hook-YVDILAMZ.js.map +1 -0
  116. package/dist/index.d.ts +22 -0
  117. package/dist/index.js +10 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/install-HQRYZOQ2.js +60 -0
  120. package/dist/install-HQRYZOQ2.js.map +1 -0
  121. package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
  122. package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
  123. package/dist/kilo-NSWAVP6P.js +235 -0
  124. package/dist/kilo-NSWAVP6P.js.map +1 -0
  125. package/dist/kilo-QXJRCSLT.js +384 -0
  126. package/dist/kilo-QXJRCSLT.js.map +1 -0
  127. package/dist/kilo-cli-EAQTHXUW.js +587 -0
  128. package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
  129. package/dist/kilo-cli-EVBKUODA.js +160 -0
  130. package/dist/kilo-cli-EVBKUODA.js.map +1 -0
  131. package/dist/kimi-EI3UM3ZV.js +155 -0
  132. package/dist/kimi-EI3UM3ZV.js.map +1 -0
  133. package/dist/kimi-GQ7VXNYT.js +423 -0
  134. package/dist/kimi-GQ7VXNYT.js.map +1 -0
  135. package/dist/kiro-UCLSLBW2.js +217 -0
  136. package/dist/kiro-UCLSLBW2.js.map +1 -0
  137. package/dist/kiro-VQTT6LO2.js +451 -0
  138. package/dist/kiro-VQTT6LO2.js.map +1 -0
  139. package/dist/leaderboard-K6ON3SWW.js +137 -0
  140. package/dist/leaderboard-K6ON3SWW.js.map +1 -0
  141. package/dist/mux-T2TV4CIQ.js +211 -0
  142. package/dist/mux-T2TV4CIQ.js.map +1 -0
  143. package/dist/mux-ZMLVAXOO.js +96 -0
  144. package/dist/mux-ZMLVAXOO.js.map +1 -0
  145. package/dist/omp-PB4VDKS6.js +586 -0
  146. package/dist/omp-PB4VDKS6.js.map +1 -0
  147. package/dist/openclaw-ETN2M63O.js +751 -0
  148. package/dist/openclaw-ETN2M63O.js.map +1 -0
  149. package/dist/openclaw-IYA7ASL6.js +150 -0
  150. package/dist/openclaw-IYA7ASL6.js.map +1 -0
  151. package/dist/opencode-FZ33JKUZ.js +734 -0
  152. package/dist/opencode-FZ33JKUZ.js.map +1 -0
  153. package/dist/opencode-HWT3RSMN.js +216 -0
  154. package/dist/opencode-HWT3RSMN.js.map +1 -0
  155. package/dist/package-DLOVE2E4.js +1132 -0
  156. package/dist/package-DLOVE2E4.js.map +1 -0
  157. package/dist/pi-CIU2QHT4.js +208 -0
  158. package/dist/pi-CIU2QHT4.js.map +1 -0
  159. package/dist/pi-UFX2OYFV.js +139 -0
  160. package/dist/pi-UFX2OYFV.js.map +1 -0
  161. package/dist/qwen-code-5POCNU6P.js +573 -0
  162. package/dist/qwen-code-5POCNU6P.js.map +1 -0
  163. package/dist/qwen-code-W7NUCDEN.js +134 -0
  164. package/dist/qwen-code-W7NUCDEN.js.map +1 -0
  165. package/dist/roo-code-IOIUXRDE.js +242 -0
  166. package/dist/roo-code-IOIUXRDE.js.map +1 -0
  167. package/dist/roo-code-ZLKH4A7N.js +229 -0
  168. package/dist/roo-code-ZLKH4A7N.js.map +1 -0
  169. package/dist/runtime/index.d.ts +157 -0
  170. package/dist/runtime/index.js +20 -0
  171. package/dist/runtime/index.js.map +1 -0
  172. package/dist/serve-HVWWY4B6.js +65 -0
  173. package/dist/serve-HVWWY4B6.js.map +1 -0
  174. package/dist/status-FE7TECZD.js +128 -0
  175. package/dist/status-FE7TECZD.js.map +1 -0
  176. package/dist/synthetic-UKR6SMGS.js +178 -0
  177. package/dist/synthetic-UKR6SMGS.js.map +1 -0
  178. package/dist/telemetry-36GESKQQ.js +349 -0
  179. package/dist/telemetry-36GESKQQ.js.map +1 -0
  180. package/dist/trae-JSNDZFKL.js +204 -0
  181. package/dist/trae-JSNDZFKL.js.map +1 -0
  182. package/dist/trae-PA26DPBW.js +157 -0
  183. package/dist/trae-PA26DPBW.js.map +1 -0
  184. package/dist/types-CYUB1OTr.d.ts +427 -0
  185. package/dist/uninstall-IGCKNOSF.js +72 -0
  186. package/dist/uninstall-IGCKNOSF.js.map +1 -0
  187. package/dist/upgrade-JV2VK242.js +104 -0
  188. package/dist/upgrade-JV2VK242.js.map +1 -0
  189. package/dist/usage-6ER6EMEY.js +324 -0
  190. package/dist/usage-6ER6EMEY.js.map +1 -0
  191. package/dist/usage-event-JKBI6XCF.js +48 -0
  192. package/dist/usage-event-JKBI6XCF.js.map +1 -0
  193. package/dist/vscode-copilot-CK3NO54M.js +715 -0
  194. package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
  195. package/dist/warp-JNQA7PBU.js +126 -0
  196. package/dist/warp-JNQA7PBU.js.map +1 -0
  197. package/dist/warp-QNVDXOG4.js +199 -0
  198. package/dist/warp-QNVDXOG4.js.map +1 -0
  199. package/dist/zed-NNNX5MNT.js +272 -0
  200. package/dist/zed-NNNX5MNT.js.map +1 -0
  201. package/dist/zed-VRJKWYUR.js +209 -0
  202. package/dist/zed-VRJKWYUR.js.map +1 -0
  203. package/package.json +79 -0
@@ -0,0 +1,734 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveEnvRefsDeep
4
+ } from "./chunk-DBQMX75U.js";
5
+ import {
6
+ BaseAdapter
7
+ } from "./chunk-TIIP3RG2.js";
8
+ import "./chunk-7L6ITC4T.js";
9
+ import {
10
+ buildServeWrapperCommand,
11
+ shouldWrapForTelemetry
12
+ } from "./chunk-MEH7GC7R.js";
13
+ import "./chunk-TRQKHP7D.js";
14
+
15
+ // src/adapters/opencode/index.ts
16
+ import {
17
+ chmodSync,
18
+ existsSync,
19
+ mkdirSync,
20
+ readFileSync,
21
+ rmSync,
22
+ writeFileSync
23
+ } from "fs";
24
+ import { homedir } from "os";
25
+ import { dirname, join } from "path";
26
+ var HOST = "opencode";
27
+ var MCP_ROOT_KEY = "mcp";
28
+ var EVENT_TO_OPENCODE = {
29
+ PreToolUse: "tool.execute.before",
30
+ PostToolUse: "tool.execute.after",
31
+ // OpenCode lacks a real SessionStart hook (#14808 / #5409). The verified
32
+ // surrogate is experimental.chat.system.transform, which receives the system
33
+ // prompt array; we inject additionalContext there.
34
+ SessionStart: "experimental.chat.system.transform"
35
+ };
36
+ var OpenCodeAdapter = class extends BaseAdapter {
37
+ id = HOST;
38
+ name = "OpenCode";
39
+ paradigm = "ts-plugin";
40
+ capabilities = {
41
+ preToolUse: true,
42
+ postToolUse: true,
43
+ // OpenCode's compaction hook is experimental and not wired here.
44
+ preCompact: false,
45
+ sessionStart: true,
46
+ sessionEnd: false,
47
+ userPromptSubmit: false,
48
+ stop: false,
49
+ notification: false,
50
+ // tool.execute.before mutates output.args → input rewrite supported.
51
+ canModifyArgs: true,
52
+ // tool.execute.after mutates output.output → output rewrite supported.
53
+ canModifyOutput: true,
54
+ // injected via experimental.chat.system.transform at session start.
55
+ canInjectSessionContext: true,
56
+ transports: ["stdio", "sse", "http"],
57
+ // Content surfaces: OpenCode authors all three natively under <ocDir>.
58
+ supportsCommands: true,
59
+ supportsSkills: true,
60
+ supportsSubagents: true
61
+ };
62
+ // ── Detection ──────────────────────────────────────────────────────────
63
+ detectInstalled(projectDir) {
64
+ const userConfigDir = join(homedir(), ".config", "opencode");
65
+ const userConfig = join(userConfigDir, "opencode.json");
66
+ const projectConfig = join(projectDir, "opencode.json");
67
+ const userInstalled = existsSync(userConfigDir) || existsSync(userConfig);
68
+ const projInstalled = existsSync(projectConfig);
69
+ const installed = userInstalled || projInstalled;
70
+ const scope = projInstalled && !userInstalled ? "project" : "user";
71
+ const configPath = scope === "project" ? projectConfig : userConfig;
72
+ return {
73
+ id: this.id,
74
+ name: this.name,
75
+ installed,
76
+ paradigm: this.paradigm,
77
+ capabilities: this.capabilities,
78
+ configPath,
79
+ scope,
80
+ reason: installed ? scope === "project" ? `found project OpenCode config at ${projectConfig}` : `found OpenCode config under ${userConfigDir}` : `no OpenCode config at ${userConfigDir}`,
81
+ confidence: installed ? "high" : "low"
82
+ };
83
+ }
84
+ // ── Native paths ───────────────────────────────────────────────────────
85
+ getConfigDir(ctx) {
86
+ return ctx.scope === "project" ? ctx.projectDir : join(homedir(), ".config", "opencode");
87
+ }
88
+ getServerConfigPath(ctx) {
89
+ return join(this.getConfigDir(ctx), "opencode.json");
90
+ }
91
+ /**
92
+ * For ts-plugin hosts the "hook config path" is the generated plugin FILE.
93
+ * OpenCode auto-loads every file in the plugin dir; writing this file IS the
94
+ * registration.
95
+ */
96
+ getHookConfigPath(ctx) {
97
+ return join(this.pluginDir(ctx), this.pluginFileName(ctx));
98
+ }
99
+ /** Plugin directory OpenCode auto-loads modules from, per scope. */
100
+ pluginDir(ctx) {
101
+ return ctx.scope === "project" ? join(ctx.projectDir, ".opencode", "plugin") : join(homedir(), ".config", "opencode", "plugin");
102
+ }
103
+ /** Plugin module file name (one per connector, kebab-case id). */
104
+ pluginFileName(ctx) {
105
+ return `${ctx.connector.id}.js`;
106
+ }
107
+ // ── MCP server install / uninstall ─────────────────────────────────────
108
+ installServer(ctx) {
109
+ const server = this.effectiveServer(ctx);
110
+ const serverPath = this.getServerConfigPath(ctx);
111
+ if (!server) {
112
+ return [
113
+ {
114
+ platform: this.id,
115
+ action: "skip",
116
+ path: serverPath,
117
+ detail: ctx.connector.server ? "server registration disabled for opencode" : "connector declares no MCP server"
118
+ }
119
+ ];
120
+ }
121
+ const entry = this.renderServerEntry(ctx, server);
122
+ return [
123
+ this.upsertServerInJson(
124
+ serverPath,
125
+ MCP_ROOT_KEY,
126
+ ctx.connector.id,
127
+ entry,
128
+ ctx.dryRun
129
+ )
130
+ ];
131
+ }
132
+ uninstallServer(ctx) {
133
+ const serverPath = this.getServerConfigPath(ctx);
134
+ return [
135
+ this.removeServerFromJson(
136
+ serverPath,
137
+ MCP_ROOT_KEY,
138
+ ctx.connector.id,
139
+ ctx.dryRun
140
+ )
141
+ ];
142
+ }
143
+ /** Resolve the per-platform server override into an effective ServerDef. */
144
+ effectiveServer(ctx) {
145
+ const override = ctx.connector.platforms[HOST]?.server;
146
+ if (override === false) return void 0;
147
+ const base = ctx.connector.server;
148
+ if (!base) return void 0;
149
+ return override && typeof override === "object" ? { ...base, ...override } : base;
150
+ }
151
+ /**
152
+ * Render a normalized ServerDef into OpenCode's native "mcp" entry.
153
+ *
154
+ * stdio → { type: "local", command: [exe, ...args], environment }
155
+ * remote → { type: "remote", url }
156
+ *
157
+ * OpenCode flattens command+args into a single ARRAY. When telemetry wrapping
158
+ * applies, the wrapper's command+args (which already include the real command
159
+ * tail after `--`) become the head of that array.
160
+ */
161
+ renderServerEntry(ctx, server) {
162
+ const transport = server.transport;
163
+ if (transport === "stdio") {
164
+ let command = server.command ?? "";
165
+ let args = [...server.args ?? []];
166
+ if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
167
+ const wrapped = buildServeWrapperCommand(
168
+ ctx.homeBinPath,
169
+ ctx.connector.id,
170
+ command,
171
+ args,
172
+ ctx.scope,
173
+ this.id
174
+ );
175
+ command = wrapped.command;
176
+ args = wrapped.args;
177
+ }
178
+ const commandArray = resolveEnvRefsDeep([command, ...args]).filter(
179
+ (s) => s !== ""
180
+ );
181
+ const entry2 = {
182
+ type: "local",
183
+ command: commandArray
184
+ };
185
+ const environment = this.renderEnv(server.env);
186
+ if (environment) entry2.environment = environment;
187
+ if (server.enabled === false) entry2.enabled = false;
188
+ return entry2;
189
+ }
190
+ const entry = {
191
+ type: "remote",
192
+ url: resolveEnvRefsDeep(server.url ?? "")
193
+ };
194
+ if (server.enabled === false) entry.enabled = false;
195
+ return entry;
196
+ }
197
+ /**
198
+ * Render env values. OpenCode documents no native interpolation token, so
199
+ * resolve every ${env:VAR} reference to a literal at install time.
200
+ */
201
+ renderEnv(env) {
202
+ if (!env || Object.keys(env).length === 0) return void 0;
203
+ return resolveEnvRefsDeep({ ...env });
204
+ }
205
+ // ── Hook install / uninstall (ts-plugin) ───────────────────────────────
206
+ installHooks(ctx) {
207
+ const pluginPath = this.getHookConfigPath(ctx);
208
+ if (ctx.connector.platforms[HOST]?.hooks === false) {
209
+ return [
210
+ {
211
+ platform: this.id,
212
+ action: "skip",
213
+ path: pluginPath,
214
+ detail: "hooks disabled for opencode"
215
+ }
216
+ ];
217
+ }
218
+ if (ctx.connector.hookEvents.length === 0) {
219
+ return [
220
+ {
221
+ platform: this.id,
222
+ action: "skip",
223
+ path: pluginPath,
224
+ detail: "connector declares no hooks"
225
+ }
226
+ ];
227
+ }
228
+ const files = this.synthesizePlugin(ctx);
229
+ const changes = [];
230
+ for (const file of files) {
231
+ const before = existsSync(file.path) ? this.safeRead(file.path) : void 0;
232
+ let action;
233
+ if (before === void 0) action = "create";
234
+ else if (before === file.contents) action = "skip";
235
+ else action = "update";
236
+ if (action !== "skip" && !ctx.dryRun) {
237
+ ensureDir(dirname(file.path));
238
+ writeFileSync(file.path, file.contents, "utf8");
239
+ chmodSync(file.path, file.executable ? 493 : 420);
240
+ }
241
+ changes.push({
242
+ platform: this.id,
243
+ action,
244
+ path: file.path,
245
+ detail: `opencode plugin module (${this.hookDetail(ctx)})`
246
+ });
247
+ }
248
+ return changes;
249
+ }
250
+ /**
251
+ * Human-facing summary of which declared events the synthesized module ACTUALLY
252
+ * wires. Only events present in EVENT_TO_OPENCODE are mapped/wired; any declared
253
+ * event with no OpenCode mapping (e.g. UserPromptSubmit) is reported separately
254
+ * as "unsupported here" so the detail never overstates coverage.
255
+ */
256
+ hookDetail(ctx) {
257
+ const declared = ctx.connector.hookEvents;
258
+ const mapped = declared.filter((e) => EVENT_TO_OPENCODE[e] !== void 0);
259
+ const unsupported = declared.filter((e) => EVENT_TO_OPENCODE[e] === void 0);
260
+ const base = mapped.join(",");
261
+ return unsupported.length > 0 ? `${base}; unsupported here: ${unsupported.join(",")}` : base;
262
+ }
263
+ uninstallHooks(ctx) {
264
+ const pluginPath = this.getHookConfigPath(ctx);
265
+ if (!existsSync(pluginPath)) {
266
+ return [
267
+ {
268
+ platform: this.id,
269
+ action: "skip",
270
+ path: pluginPath,
271
+ detail: "no opencode plugin module present"
272
+ }
273
+ ];
274
+ }
275
+ if (!ctx.dryRun) rmSync(pluginPath, { force: true });
276
+ return [
277
+ {
278
+ platform: this.id,
279
+ action: "remove",
280
+ path: pluginPath,
281
+ detail: "opencode plugin module"
282
+ }
283
+ ];
284
+ }
285
+ // ── Content surfaces: commands / skills / subagents ──────────────────────
286
+ // CONTENT-ONLY: pure native-file writers under <ocDir>/{commands,skills,
287
+ // agent}. No runtime dispatch, no home-bin pointer, no telemetry wrap. Each
288
+ // method is idempotent (byte-identical → skip) via BaseAdapter.writeContentFile
289
+ // and reversible via removeContentFile. Honors platforms["opencode"]
290
+ // per-surface false to skip. <ocDir> is reused from getConfigDir (user
291
+ // ~/.config/opencode, project <projectDir>/.opencode).
292
+ //
293
+ // NOTE on dirs: commands live in commands/<name>.md and skills in
294
+ // skills/<name>/SKILL.md, but subagents use the SINGULAR "agent/" dir
295
+ // (agent/<name>.md) — the OpenCode loader/CLI historically uses singular
296
+ // (anomalyco/opencode#14410); singular is the safe choice.
297
+ commandsDir(ctx) {
298
+ return join(this.getConfigDir(ctx), "commands");
299
+ }
300
+ skillsDir(ctx) {
301
+ return join(this.getConfigDir(ctx), "skills");
302
+ }
303
+ agentDir(ctx) {
304
+ return join(this.getConfigDir(ctx), "agent");
305
+ }
306
+ /** Native command file path: <ocDir>/commands/<name>.md. */
307
+ commandPath(ctx, name) {
308
+ return join(this.commandsDir(ctx), `${name}.md`);
309
+ }
310
+ /** Native skill dir: <ocDir>/skills/<name>. */
311
+ skillDir(ctx, name) {
312
+ return join(this.skillsDir(ctx), name);
313
+ }
314
+ /** Native subagent file path: <ocDir>/agent/<name>.md (SINGULAR dir). */
315
+ subagentPath(ctx, name) {
316
+ return join(this.agentDir(ctx), `${name}.md`);
317
+ }
318
+ // ── Commands ──────────────────────────────────────────────────────────────
319
+ installCommands(ctx) {
320
+ const { connector } = ctx;
321
+ if (connector.platforms[HOST]?.commands === false) {
322
+ return [{ platform: this.id, action: "skip", detail: "commands disabled for opencode" }];
323
+ }
324
+ if (connector.commands.length === 0) {
325
+ return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
326
+ }
327
+ return connector.commands.map(
328
+ (cmd) => this.writeContentFile(
329
+ this.commandPath(ctx, cmd.name),
330
+ this.renderCommand(cmd),
331
+ ctx.dryRun
332
+ )
333
+ );
334
+ }
335
+ uninstallCommands(ctx) {
336
+ const { connector } = ctx;
337
+ if (connector.commands.length === 0) {
338
+ return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
339
+ }
340
+ return connector.commands.map(
341
+ (cmd) => this.removeContentFile(this.commandPath(ctx, cmd.name), ctx.dryRun)
342
+ );
343
+ }
344
+ /** Render a command to md+frontmatter (description, agent, model, subtask). */
345
+ renderCommand(cmd) {
346
+ const frontmatter = {};
347
+ if (cmd.description !== void 0) frontmatter.description = cmd.description;
348
+ if (cmd.model !== void 0) frontmatter.model = cmd.model;
349
+ if (cmd.subtask !== void 0) frontmatter.subtask = cmd.subtask;
350
+ if (cmd.extra) Object.assign(frontmatter, cmd.extra);
351
+ return this.renderFrontmatterMd(frontmatter, cmd.prompt);
352
+ }
353
+ // ── Skills ────────────────────────────────────────────────────────────────
354
+ installSkills(ctx) {
355
+ const { connector } = ctx;
356
+ if (connector.platforms[HOST]?.skills === false) {
357
+ return [{ platform: this.id, action: "skip", detail: "skills disabled for opencode" }];
358
+ }
359
+ if (connector.skills.length === 0) {
360
+ return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
361
+ }
362
+ const changes = [];
363
+ for (const skill of connector.skills) {
364
+ const dir = this.skillDir(ctx, skill.name);
365
+ changes.push(
366
+ this.writeContentFile(join(dir, "SKILL.md"), this.renderSkill(skill), ctx.dryRun)
367
+ );
368
+ for (const [rel, contents] of Object.entries(skill.resources ?? {})) {
369
+ const target = this.resolveWithin(dir, rel);
370
+ if (target === null) {
371
+ changes.push({
372
+ platform: this.id,
373
+ action: "warn",
374
+ detail: `skill resource "${rel}" escapes the skill dir; skipped`
375
+ });
376
+ continue;
377
+ }
378
+ changes.push(this.writeContentFile(target, contents, ctx.dryRun));
379
+ }
380
+ }
381
+ return changes;
382
+ }
383
+ uninstallSkills(ctx) {
384
+ const { connector } = ctx;
385
+ if (connector.skills.length === 0) {
386
+ return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
387
+ }
388
+ const changes = [];
389
+ for (const skill of connector.skills) {
390
+ const dir = this.skillDir(ctx, skill.name);
391
+ changes.push(this.removeContentFile(join(dir, "SKILL.md"), ctx.dryRun));
392
+ for (const rel of Object.keys(skill.resources ?? {})) {
393
+ const target = this.resolveWithin(dir, rel);
394
+ if (target === null) continue;
395
+ changes.push(this.removeContentFile(target, ctx.dryRun));
396
+ }
397
+ changes.push(this.removeDirIfEmpty(dir, ctx.dryRun));
398
+ }
399
+ return changes;
400
+ }
401
+ /**
402
+ * Render a skill's SKILL.md (UNIFORM across platforms): frontmatter
403
+ * (name, description + optional model, allowed-tools, disable-model-invocation)
404
+ * + markdown body. Only the parent dir differs per platform.
405
+ */
406
+ renderSkill(skill) {
407
+ const frontmatter = {
408
+ name: skill.name,
409
+ description: skill.description
410
+ };
411
+ if (skill.model !== void 0) frontmatter.model = skill.model;
412
+ const allow = skill.tools?.allow;
413
+ if (allow && allow.length > 0) frontmatter["allowed-tools"] = allow.join(", ");
414
+ if (skill.disableModelInvocation !== void 0) {
415
+ frontmatter["disable-model-invocation"] = skill.disableModelInvocation;
416
+ }
417
+ if (skill.extra) Object.assign(frontmatter, skill.extra);
418
+ return this.renderFrontmatterMd(frontmatter, skill.body);
419
+ }
420
+ // ── Subagents ───────────────────────────────────────────────────────────────
421
+ installSubagents(ctx) {
422
+ const { connector } = ctx;
423
+ if (connector.platforms[HOST]?.subagents === false) {
424
+ return [{ platform: this.id, action: "skip", detail: "subagents disabled for opencode" }];
425
+ }
426
+ if (connector.subagents.length === 0) {
427
+ return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
428
+ }
429
+ return connector.subagents.map(
430
+ (agent) => this.writeContentFile(
431
+ this.subagentPath(ctx, agent.name),
432
+ this.renderSubagent(agent),
433
+ ctx.dryRun
434
+ )
435
+ );
436
+ }
437
+ uninstallSubagents(ctx) {
438
+ const { connector } = ctx;
439
+ if (connector.subagents.length === 0) {
440
+ return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
441
+ }
442
+ return connector.subagents.map(
443
+ (agent) => this.removeContentFile(this.subagentPath(ctx, agent.name), ctx.dryRun)
444
+ );
445
+ }
446
+ /**
447
+ * Render a subagent to md+frontmatter. OpenCode's shape is
448
+ * (description, mode:"subagent", model, permission) with the system prompt as
449
+ * the body. `name` is NOT a frontmatter field — it comes from the filename.
450
+ * `permission` is derived from the coarse `readonly` knob: a readonly agent
451
+ * gets a per-tool deny map (edit/bash) so it cannot mutate the workspace.
452
+ */
453
+ renderSubagent(agent) {
454
+ const frontmatter = {
455
+ description: agent.description,
456
+ mode: "subagent"
457
+ };
458
+ if (agent.model !== void 0) frontmatter.model = agent.model;
459
+ if (agent.readonly === true) {
460
+ frontmatter.permission = { edit: "deny", bash: "deny" };
461
+ }
462
+ if (agent.extra) Object.assign(frontmatter, agent.extra);
463
+ return this.renderFrontmatterMd(frontmatter, agent.prompt);
464
+ }
465
+ // ── ts-plugin synthesis ────────────────────────────────────────────────
466
+ /**
467
+ * Build ONE self-contained ESM bridge module for OpenCode.
468
+ *
469
+ * The module imports nothing from agent-connector. It embeds two constants
470
+ * (the absolute home-bin path and the connector id) and a `bridge()` helper
471
+ * that shells out to the universal hook entrypoint via execFileSync, feeding
472
+ * the OpenCode payload on stdin and JSON.parsing the normalized HookResponse
473
+ * back from stdout (fail-open: any error → null). Its default export is the
474
+ * OpenCode plugin factory returning an object keyed only by the OpenCode event
475
+ * names for the events this connector actually declares.
476
+ */
477
+ synthesizePlugin(ctx) {
478
+ const path = this.getHookConfigPath(ctx);
479
+ const contents = this.buildPluginSource(ctx);
480
+ return [{ path, contents, executable: false }];
481
+ }
482
+ /** Compose the generated plugin source with plain string concatenation. */
483
+ buildPluginSource(ctx) {
484
+ const homeBin = JSON.stringify(ctx.homeBinPath);
485
+ const connectorId = JSON.stringify(ctx.connector.id);
486
+ const events = ctx.connector.hookEvents.filter(
487
+ (e) => EVENT_TO_OPENCODE[e] !== void 0
488
+ );
489
+ const has = (e) => events.includes(e);
490
+ const header = `/**
491
+ * AUTO-GENERATED by agent-connector \u2014 DO NOT EDIT.
492
+ *
493
+ * Self-contained OpenCode plugin bridge for connector ${ctx.connector.id}.
494
+ * It imports nothing from agent-connector: every hook invocation shells out to
495
+ * the stable home binary's universal entrypoint and JSON-parses the normalized
496
+ * response. Fail-open: any bridge error degrades to "allow".
497
+ */
498
+ import { execFileSync, execSync } from "node:child_process";
499
+
500
+ const HOME_BIN = ${homeBin};
501
+ const CONNECTOR_ID = ${connectorId};
502
+
503
+ /**
504
+ * Invoke the universal hook entrypoint for one event.
505
+ * @param {string} event canonical event name (PreToolUse|PostToolUse|SessionStart)
506
+ * @param {object} payload OpenCode-shaped payload posted on stdin
507
+ * @returns {object|null} normalized HookResponse, or null on any failure
508
+ */
509
+ function bridge(event, payload) {
510
+ try {
511
+ // On Windows HOME_BIN is the agent-connector.cmd launcher: Node cannot
512
+ // execFile a batch file, and shell+args is deprecated (DEP0190), so run one
513
+ // quoted command line via a shell. POSIX keeps the direct execFile (no shell).
514
+ const args = ["hook", "opencode", event, "--connector", CONNECTOR_ID];
515
+ const opts = { input: JSON.stringify(payload), encoding: "utf8" };
516
+ const stdout =
517
+ process.platform === "win32"
518
+ ? execSync([HOME_BIN, ...args].map((a) => '"' + a + '"').join(" "), opts)
519
+ : execFileSync(HOME_BIN, args, opts);
520
+ const text = (stdout || "").trim();
521
+ if (text === "") return { decision: "allow" };
522
+ return JSON.parse(text);
523
+ } catch {
524
+ // Fail-open \u2014 never wedge a tool call on a bridge error.
525
+ return null;
526
+ }
527
+ }
528
+ `;
529
+ const handlers = [];
530
+ if (has("PreToolUse")) {
531
+ handlers.push(` // PreToolUse \u2192 block (throw) / rewrite args (mutate output.args).
532
+ "tool.execute.before": async (input, output) => {
533
+ const payload = {
534
+ toolName: input.tool ?? "",
535
+ toolInput: (output && output.args) ?? {},
536
+ sessionId: input.sessionID ?? "",
537
+ projectDir: PROJECT_DIR,
538
+ };
539
+ const res = bridge("PreToolUse", payload);
540
+ if (!res) return;
541
+ // OpenCode has no "ask" gate \u2014 degrade "ask" to a block (safe direction).
542
+ if (res.decision === "deny" || res.decision === "ask") {
543
+ throw new Error(res.reason || "Blocked by ${ctx.connector.id}");
544
+ }
545
+ if (res.updatedInput && output && output.args) {
546
+ Object.assign(output.args, res.updatedInput);
547
+ }
548
+ },`);
549
+ }
550
+ if (has("PostToolUse")) {
551
+ handlers.push(` // PostToolUse \u2192 observe / rewrite tool output (mutate output.output).
552
+ "tool.execute.after": async (input, output) => {
553
+ const payload = {
554
+ toolName: input.tool ?? "",
555
+ toolInput: input.args ?? {},
556
+ toolOutput: output ? output.output : undefined,
557
+ sessionId: input.sessionID ?? "",
558
+ projectDir: PROJECT_DIR,
559
+ };
560
+ const res = bridge("PostToolUse", payload);
561
+ if (!res) return;
562
+ if (typeof res.updatedOutput === "string" && output) {
563
+ output.output = res.updatedOutput;
564
+ }
565
+ },`);
566
+ }
567
+ if (has("SessionStart")) {
568
+ handlers.push(` // SessionStart surrogate \u2192 inject context into the system prompt.
569
+ // OpenCode has no real SessionStart hook (#14808 / #5409); the
570
+ // experimental.chat.system.transform hook is the verified injection point.
571
+ "experimental.chat.system.transform": async (input, output) => {
572
+ const payload = {
573
+ sessionId: (input && input.sessionID) ?? "",
574
+ projectDir: PROJECT_DIR,
575
+ };
576
+ const res = bridge("SessionStart", payload);
577
+ if (!res) return;
578
+ if (res.additionalContext && output && Array.isArray(output.system)) {
579
+ // Insert at index 1 (after the header) to preserve OpenCode's
580
+ // prompt-cache fold (header must remain system[0]).
581
+ output.system.splice(1, 0, res.additionalContext);
582
+ }
583
+ },`);
584
+ }
585
+ const factory = `
586
+ export default async function (ctx) {
587
+ // ctx.directory is the OpenCode project root; fall back to cwd.
588
+ const PROJECT_DIR =
589
+ (ctx && (ctx.directory || (ctx.worktree && ctx.worktree.path))) ||
590
+ process.cwd();
591
+
592
+ return {
593
+ ${handlers.join("\n")}
594
+ };
595
+ }
596
+ `;
597
+ return header + factory;
598
+ }
599
+ // ── Runtime: parse OUR bridge payload → normalized event ───────────────
600
+ /**
601
+ * `raw` is the payload OUR generated plugin posts (NOT a host-native shape):
602
+ * { toolName, toolInput, toolOutput?, isError?, sessionId, projectDir }
603
+ * so this maps straight through.
604
+ */
605
+ parseEvent(event, raw) {
606
+ const input = raw ?? {};
607
+ const base = {
608
+ hostPlatform: HOST,
609
+ connectorId: "",
610
+ sessionId: typeof input.sessionId === "string" ? input.sessionId : "",
611
+ raw,
612
+ ...typeof input.projectDir === "string" ? { projectDir: input.projectDir } : {}
613
+ };
614
+ switch (event) {
615
+ case "PreToolUse": {
616
+ const ev = {
617
+ ...base,
618
+ toolName: input.toolName ?? "",
619
+ toolInput: input.toolInput ?? {}
620
+ };
621
+ return ev;
622
+ }
623
+ case "PostToolUse": {
624
+ const ev = {
625
+ ...base,
626
+ toolName: input.toolName ?? "",
627
+ toolInput: input.toolInput ?? {},
628
+ ...typeof input.toolOutput === "string" ? { toolOutput: input.toolOutput } : {},
629
+ ...typeof input.isError === "boolean" ? { isError: input.isError } : {}
630
+ };
631
+ return ev;
632
+ }
633
+ case "SessionStart": {
634
+ const ev = { ...base, source: "startup" };
635
+ return ev;
636
+ }
637
+ default:
638
+ return { ...base, source: "startup" };
639
+ }
640
+ }
641
+ // ── Runtime: normalized response → reply the generated bridge parses ───
642
+ /**
643
+ * Unlike json-stdio hosts (whose reply is the host's NATIVE control payload),
644
+ * OUR generated bridge consumes this stdout directly. So the reply body is the
645
+ * NORMALIZED HookResponse itself — the bridge JSON.parses it and reads
646
+ * decision / updatedInput / updatedOutput / additionalContext.
647
+ */
648
+ formatReply(_event, response) {
649
+ return {
650
+ exitCode: 0,
651
+ stdout: JSON.stringify(response ?? { decision: "allow" })
652
+ };
653
+ }
654
+ // ── Diagnostics ────────────────────────────────────────────────────────
655
+ getHealthChecks(ctx) {
656
+ const serverPath = this.getServerConfigPath(ctx);
657
+ const pluginPath = this.getHookConfigPath(ctx);
658
+ const connectorId = ctx.connector.id;
659
+ const hasHooks = ctx.connector.hookEvents.length > 0;
660
+ const checks = [
661
+ {
662
+ name: `${this.name}: opencode.json present`,
663
+ check: () => existsSync(serverPath) ? { status: "OK", detail: serverPath } : { status: "FAIL", detail: `not found: ${serverPath}` }
664
+ },
665
+ {
666
+ name: `${this.name}: mcp.${connectorId} registered`,
667
+ check: () => {
668
+ if (!ctx.connector.server) {
669
+ return { status: "OK", detail: "no MCP server declared" };
670
+ }
671
+ const cfg = this.readJson(
672
+ serverPath
673
+ );
674
+ const bucket = cfg?.[MCP_ROOT_KEY];
675
+ if (!cfg || !bucket) {
676
+ return { status: "FAIL", detail: `no ${MCP_ROOT_KEY} in ${serverPath}` };
677
+ }
678
+ return connectorId in bucket ? { status: "OK", detail: `${MCP_ROOT_KEY}.${connectorId} present` } : {
679
+ status: "FAIL",
680
+ detail: `no ${MCP_ROOT_KEY}.${connectorId} in ${serverPath}`
681
+ };
682
+ }
683
+ },
684
+ {
685
+ name: `${this.name}: plugin module present`,
686
+ check: () => {
687
+ if (!hasHooks) return { status: "OK", detail: "no hooks declared" };
688
+ return existsSync(pluginPath) ? { status: "OK", detail: pluginPath } : { status: "FAIL", detail: `not found: ${pluginPath}` };
689
+ }
690
+ }
691
+ ];
692
+ for (const cmd of ctx.connector.commands) {
693
+ const p = this.commandPath(ctx, cmd.name);
694
+ checks.push({
695
+ name: `${this.name}: command ${cmd.name} present`,
696
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
697
+ });
698
+ }
699
+ for (const skill of ctx.connector.skills) {
700
+ const p = join(this.skillDir(ctx, skill.name), "SKILL.md");
701
+ checks.push({
702
+ name: `${this.name}: skill ${skill.name} present`,
703
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
704
+ });
705
+ }
706
+ for (const agent of ctx.connector.subagents) {
707
+ const p = this.subagentPath(ctx, agent.name);
708
+ checks.push({
709
+ name: `${this.name}: subagent ${agent.name} present`,
710
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
711
+ });
712
+ }
713
+ return checks;
714
+ }
715
+ /** Read a file, returning undefined on any error (idempotency compare). */
716
+ safeRead(path) {
717
+ try {
718
+ return readFileSync(path, "utf8");
719
+ } catch {
720
+ return void 0;
721
+ }
722
+ }
723
+ };
724
+ function ensureDir(dir) {
725
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
726
+ }
727
+ var adapter = new OpenCodeAdapter();
728
+ var opencode_default = adapter;
729
+ export {
730
+ OpenCodeAdapter,
731
+ adapter,
732
+ opencode_default as default
733
+ };
734
+ //# sourceMappingURL=opencode-FZ33JKUZ.js.map