@ken-jo/agent-connector 0.1.0 → 0.2.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 (157) hide show
  1. package/README.md +176 -24
  2. package/dist/{amp-QSFH2V7Y.js → amp-YGV66GJT.js} +9 -2
  3. package/dist/amp-YGV66GJT.js.map +1 -0
  4. package/dist/{antigravity-CX2YLSY7.js → antigravity-7IBYFO6P.js} +4 -3
  5. package/dist/{antigravity-cli-YQDFVV4U.js → antigravity-cli-JEOUP2VN.js} +4 -3
  6. package/dist/{antigravity-cli-YQDFVV4U.js.map → antigravity-cli-JEOUP2VN.js.map} +1 -1
  7. package/dist/{chunk-LTT56GI3.js → chunk-3JCRUROU.js} +18 -5
  8. package/dist/chunk-3JCRUROU.js.map +1 -0
  9. package/dist/{chunk-YNZU6PAV.js → chunk-4ML7SZ7R.js} +69 -16
  10. package/dist/chunk-4ML7SZ7R.js.map +1 -0
  11. package/dist/{chunk-54EMKTTX.js → chunk-AKJGNTU3.js} +17 -17
  12. package/dist/chunk-AKJGNTU3.js.map +1 -0
  13. package/dist/chunk-GSGDG2BV.js +568 -0
  14. package/dist/chunk-GSGDG2BV.js.map +1 -0
  15. package/dist/{chunk-TRKWUNO4.js → chunk-KQSNDFBN.js} +13 -2
  16. package/dist/chunk-KQSNDFBN.js.map +1 -0
  17. package/dist/chunk-N6HH7K6P.js +494 -0
  18. package/dist/chunk-N6HH7K6P.js.map +1 -0
  19. package/dist/{chunk-XUCWLJY5.js → chunk-O2SBA5KR.js} +30 -30
  20. package/dist/{chunk-TIIP3RG2.js → chunk-PD4VFEBY.js} +342 -2
  21. package/dist/chunk-PD4VFEBY.js.map +1 -0
  22. package/dist/{chunk-WV63X7IK.js → chunk-SCVBYSF7.js} +2 -2
  23. package/dist/chunk-SCVBYSF7.js.map +1 -0
  24. package/dist/{chunk-7MSDGHJW.js → chunk-Z2BVYKZL.js} +60 -7
  25. package/dist/chunk-Z2BVYKZL.js.map +1 -0
  26. package/dist/claude-code-4275HQU4.js +1432 -0
  27. package/dist/claude-code-4275HQU4.js.map +1 -0
  28. package/dist/cli/sdk.js +4 -3
  29. package/dist/cli/sdk.js.map +1 -1
  30. package/dist/cli.js +1 -1
  31. package/dist/{codebuff-QX57FJDN.js → codebuff-LE3KBZGM.js} +9 -2
  32. package/dist/codebuff-LE3KBZGM.js.map +1 -0
  33. package/dist/{codex-JFQTS6LU.js → codex-4BY2ZJIW.js} +148 -7
  34. package/dist/codex-4BY2ZJIW.js.map +1 -0
  35. package/dist/{copilot-cli-MTCGHZ2T.js → copilot-cli-IPISKO3V.js} +109 -2
  36. package/dist/copilot-cli-IPISKO3V.js.map +1 -0
  37. package/dist/{crush-I7PK3FGI.js → crush-X3E25EQG.js} +31 -3
  38. package/dist/crush-X3E25EQG.js.map +1 -0
  39. package/dist/{cursor-4TMXKJOZ.js → cursor-UH3W622V.js} +66 -5
  40. package/dist/cursor-UH3W622V.js.map +1 -0
  41. package/dist/{detect-WIAGVYS4.js → detect-JK7T46N6.js} +7 -3
  42. package/dist/detect-JK7T46N6.js.map +1 -0
  43. package/dist/{doctor-EZW2K4NI.js → doctor-62BHIXOR.js} +13 -5
  44. package/dist/doctor-62BHIXOR.js.map +1 -0
  45. package/dist/{droid-KN6VRMMT.js → droid-T5VBRXZF.js} +32 -3
  46. package/dist/droid-T5VBRXZF.js.map +1 -0
  47. package/dist/{gemini-cli-Y35FFK4M.js → gemini-cli-ZSUK55UA.js} +51 -3
  48. package/dist/gemini-cli-ZSUK55UA.js.map +1 -0
  49. package/dist/{goose-ZTGTSUFJ.js → goose-M3RR3CZG.js} +63 -4
  50. package/dist/goose-M3RR3CZG.js.map +1 -0
  51. package/dist/{hermes-H2KV44GK.js → hermes-CUNKIAR5.js} +76 -3
  52. package/dist/hermes-CUNKIAR5.js.map +1 -0
  53. package/dist/{hook-YVDILAMZ.js → hook-AC7APC66.js} +26 -11
  54. package/dist/hook-AC7APC66.js.map +1 -0
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +3 -1
  57. package/dist/{install-HQRYZOQ2.js → install-DMW2C7SV.js} +12 -7
  58. package/dist/install-DMW2C7SV.js.map +1 -0
  59. package/dist/{jetbrains-copilot-HWA3DMH7.js → jetbrains-copilot-C3HDGGML.js} +16 -2
  60. package/dist/jetbrains-copilot-C3HDGGML.js.map +1 -0
  61. package/dist/{kilo-QXJRCSLT.js → kilo-ISR5FBBJ.js} +26 -2
  62. package/dist/kilo-ISR5FBBJ.js.map +1 -0
  63. package/dist/{kilo-cli-EAQTHXUW.js → kilo-cli-2RNODHWY.js} +30 -2
  64. package/dist/kilo-cli-2RNODHWY.js.map +1 -0
  65. package/dist/{kimi-GQ7VXNYT.js → kimi-Q5RG7CYT.js} +89 -6
  66. package/dist/kimi-Q5RG7CYT.js.map +1 -0
  67. package/dist/{kiro-VQTT6LO2.js → kiro-EKTRW424.js} +31 -2
  68. package/dist/kiro-EKTRW424.js.map +1 -0
  69. package/dist/{leaderboard-K6ON3SWW.js → leaderboard-R5HRJHNU.js} +3 -3
  70. package/dist/{mux-T2TV4CIQ.js → mux-5CI5FP2W.js} +9 -2
  71. package/dist/mux-5CI5FP2W.js.map +1 -0
  72. package/dist/{omp-PB4VDKS6.js → omp-AXA7XY22.js} +13 -2
  73. package/dist/omp-AXA7XY22.js.map +1 -0
  74. package/dist/{openclaw-ETN2M63O.js → openclaw-UZ5BMCGQ.js} +101 -4
  75. package/dist/openclaw-UZ5BMCGQ.js.map +1 -0
  76. package/dist/{opencode-FZ33JKUZ.js → opencode-NZBRLJN3.js} +53 -7
  77. package/dist/opencode-NZBRLJN3.js.map +1 -0
  78. package/dist/{package-DLOVE2E4.js → package-O63K4S7T.js} +10 -5
  79. package/dist/package-O63K4S7T.js.map +1 -0
  80. package/dist/{pi-CIU2QHT4.js → pi-SJPIL4VE.js} +6 -2
  81. package/dist/pi-SJPIL4VE.js.map +1 -0
  82. package/dist/{qwen-code-5POCNU6P.js → qwen-code-IFCM6BJY.js} +110 -2
  83. package/dist/qwen-code-IFCM6BJY.js.map +1 -0
  84. package/dist/{roo-code-IOIUXRDE.js → roo-code-NI5UU6JB.js} +26 -2
  85. package/dist/roo-code-NI5UU6JB.js.map +1 -0
  86. package/dist/runtime/index.d.ts +38 -4
  87. package/dist/runtime/index.js +9 -4
  88. package/dist/{serve-HVWWY4B6.js → serve-LFOUSZAD.js} +7 -6
  89. package/dist/{serve-HVWWY4B6.js.map → serve-LFOUSZAD.js.map} +1 -1
  90. package/dist/{status-FE7TECZD.js → status-M7JLZVLC.js} +6 -5
  91. package/dist/{status-FE7TECZD.js.map → status-M7JLZVLC.js.map} +1 -1
  92. package/dist/{telemetry-36GESKQQ.js → telemetry-3NVEXPYX.js} +5 -4
  93. package/dist/{telemetry-36GESKQQ.js.map → telemetry-3NVEXPYX.js.map} +1 -1
  94. package/dist/{trae-JSNDZFKL.js → trae-CQC72RHH.js} +9 -2
  95. package/dist/trae-CQC72RHH.js.map +1 -0
  96. package/dist/types-CNigfEuR.d.ts +757 -0
  97. package/dist/{uninstall-IGCKNOSF.js → uninstall-H6FGMIHN.js} +7 -6
  98. package/dist/{uninstall-IGCKNOSF.js.map → uninstall-H6FGMIHN.js.map} +1 -1
  99. package/dist/{upgrade-JV2VK242.js → upgrade-76BQZBMA.js} +7 -6
  100. package/dist/{upgrade-JV2VK242.js.map → upgrade-76BQZBMA.js.map} +1 -1
  101. package/dist/{usage-6ER6EMEY.js → usage-G3GSCX23.js} +3 -3
  102. package/dist/{usage-event-JKBI6XCF.js → usage-event-QUTMNBS5.js} +6 -5
  103. package/dist/{usage-event-JKBI6XCF.js.map → usage-event-QUTMNBS5.js.map} +1 -1
  104. package/dist/{vscode-copilot-CK3NO54M.js → vscode-copilot-4RVRTCKV.js} +53 -4
  105. package/dist/vscode-copilot-4RVRTCKV.js.map +1 -0
  106. package/dist/{warp-QNVDXOG4.js → warp-LLOSXR7A.js} +30 -2
  107. package/dist/warp-LLOSXR7A.js.map +1 -0
  108. package/dist/{zed-VRJKWYUR.js → zed-HYOITK7F.js} +53 -3
  109. package/dist/zed-HYOITK7F.js.map +1 -0
  110. package/package.json +4 -3
  111. package/dist/amp-QSFH2V7Y.js.map +0 -1
  112. package/dist/chunk-54EMKTTX.js.map +0 -1
  113. package/dist/chunk-7MSDGHJW.js.map +0 -1
  114. package/dist/chunk-LTT56GI3.js.map +0 -1
  115. package/dist/chunk-TIIP3RG2.js.map +0 -1
  116. package/dist/chunk-TRKWUNO4.js.map +0 -1
  117. package/dist/chunk-WV63X7IK.js.map +0 -1
  118. package/dist/chunk-XNAM7F35.js +0 -276
  119. package/dist/chunk-XNAM7F35.js.map +0 -1
  120. package/dist/chunk-YNZU6PAV.js.map +0 -1
  121. package/dist/claude-code-HKDV7T32.js +0 -637
  122. package/dist/claude-code-HKDV7T32.js.map +0 -1
  123. package/dist/codebuff-QX57FJDN.js.map +0 -1
  124. package/dist/codex-JFQTS6LU.js.map +0 -1
  125. package/dist/copilot-cli-MTCGHZ2T.js.map +0 -1
  126. package/dist/crush-I7PK3FGI.js.map +0 -1
  127. package/dist/cursor-4TMXKJOZ.js.map +0 -1
  128. package/dist/detect-WIAGVYS4.js.map +0 -1
  129. package/dist/doctor-EZW2K4NI.js.map +0 -1
  130. package/dist/droid-KN6VRMMT.js.map +0 -1
  131. package/dist/gemini-cli-Y35FFK4M.js.map +0 -1
  132. package/dist/goose-ZTGTSUFJ.js.map +0 -1
  133. package/dist/hermes-H2KV44GK.js.map +0 -1
  134. package/dist/hook-YVDILAMZ.js.map +0 -1
  135. package/dist/install-HQRYZOQ2.js.map +0 -1
  136. package/dist/jetbrains-copilot-HWA3DMH7.js.map +0 -1
  137. package/dist/kilo-QXJRCSLT.js.map +0 -1
  138. package/dist/kilo-cli-EAQTHXUW.js.map +0 -1
  139. package/dist/kimi-GQ7VXNYT.js.map +0 -1
  140. package/dist/kiro-VQTT6LO2.js.map +0 -1
  141. package/dist/mux-T2TV4CIQ.js.map +0 -1
  142. package/dist/omp-PB4VDKS6.js.map +0 -1
  143. package/dist/openclaw-ETN2M63O.js.map +0 -1
  144. package/dist/opencode-FZ33JKUZ.js.map +0 -1
  145. package/dist/package-DLOVE2E4.js.map +0 -1
  146. package/dist/pi-CIU2QHT4.js.map +0 -1
  147. package/dist/qwen-code-5POCNU6P.js.map +0 -1
  148. package/dist/roo-code-IOIUXRDE.js.map +0 -1
  149. package/dist/trae-JSNDZFKL.js.map +0 -1
  150. package/dist/types-CYUB1OTr.d.ts +0 -427
  151. package/dist/vscode-copilot-CK3NO54M.js.map +0 -1
  152. package/dist/warp-QNVDXOG4.js.map +0 -1
  153. package/dist/zed-VRJKWYUR.js.map +0 -1
  154. /package/dist/{antigravity-CX2YLSY7.js.map → antigravity-7IBYFO6P.js.map} +0 -0
  155. /package/dist/{chunk-XUCWLJY5.js.map → chunk-O2SBA5KR.js.map} +0 -0
  156. /package/dist/{leaderboard-K6ON3SWW.js.map → leaderboard-R5HRJHNU.js.map} +0 -0
  157. /package/dist/{usage-6ER6EMEY.js.map → usage-G3GSCX23.js.map} +0 -0
@@ -0,0 +1,1432 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readRegisteredMeta
4
+ } from "./chunk-3JCRUROU.js";
5
+ import {
6
+ addLedgerOwner,
7
+ configPatchManualEdit,
8
+ configPatchNamespaceViolation,
9
+ createLedgerEntry,
10
+ describeJsonValue,
11
+ dropLedgerEntry,
12
+ findLedgerEntry,
13
+ hashJsonValue,
14
+ isValidConfigPatchKey,
15
+ jsonDeepEquals,
16
+ ledgerEntriesOwnedBy,
17
+ loadConfigPatchLedger,
18
+ removeLedgerOwner,
19
+ saveConfigPatchLedger
20
+ } from "./chunk-GSGDG2BV.js";
21
+ import {
22
+ resolveEnvRefsDeep,
23
+ rewriteEnvRefs
24
+ } from "./chunk-DBQMX75U.js";
25
+ import {
26
+ BaseAdapter
27
+ } from "./chunk-PD4VFEBY.js";
28
+ import {
29
+ renderCommandMd,
30
+ renderSkillMd,
31
+ renderSubagentMd
32
+ } from "./chunk-7L6ITC4T.js";
33
+ import {
34
+ buildHomeBinHookCommand,
35
+ buildServeWrapperCommand,
36
+ isHomeBinHookCommand,
37
+ shouldWrapForTelemetry
38
+ } from "./chunk-MEH7GC7R.js";
39
+ import {
40
+ linesOutsideFences,
41
+ listManagedBlocks,
42
+ loadMemoryLedger,
43
+ recordMemoryTarget,
44
+ removeBlocksFromText,
45
+ removeManagedBlocksFile,
46
+ saveMemoryLedger,
47
+ upsertManagedBlockFile
48
+ } from "./chunk-N6HH7K6P.js";
49
+ import {
50
+ backupsDir,
51
+ ensureDir
52
+ } from "./chunk-TRQKHP7D.js";
53
+
54
+ // src/adapters/claude-code/index.ts
55
+ import { copyFileSync, existsSync, lstatSync, readFileSync, realpathSync } from "fs";
56
+ import { homedir } from "os";
57
+ import { basename, dirname, join } from "path";
58
+
59
+ // src/adapters/claude-code/wire.ts
60
+ function extractSessionId(input) {
61
+ if (typeof input.transcript_path === "string") {
62
+ const m = input.transcript_path.match(/([a-f0-9-]{36})\.jsonl$/);
63
+ if (m && m[1]) return m[1];
64
+ }
65
+ if (typeof input.session_id === "string" && input.session_id !== "") {
66
+ return input.session_id;
67
+ }
68
+ return "";
69
+ }
70
+ function toolResponseToString(value) {
71
+ if (value == null) return void 0;
72
+ if (typeof value === "string") return value;
73
+ try {
74
+ return JSON.stringify(value);
75
+ } catch {
76
+ return String(value);
77
+ }
78
+ }
79
+
80
+ // src/adapters/claude-code/index.ts
81
+ var HOST = "claude-code";
82
+ var MCP_ROOT_KEY = "mcpServers";
83
+ var CLAUDE_AGENTS_IMPORT_BLOCK_ID = "_shared/claude-agents-import";
84
+ var ClaudeCodeAdapter = class extends BaseAdapter {
85
+ id = HOST;
86
+ name = "Claude Code";
87
+ paradigm = "json-stdio";
88
+ capabilities = {
89
+ preToolUse: true,
90
+ postToolUse: true,
91
+ preCompact: true,
92
+ sessionStart: true,
93
+ sessionEnd: true,
94
+ userPromptSubmit: true,
95
+ stop: true,
96
+ notification: true,
97
+ // Newer events — Claude Code is the reference host for all four.
98
+ permissionRequest: true,
99
+ postToolUseFailure: true,
100
+ subagentStart: true,
101
+ subagentStop: true,
102
+ // Claude Code can rewrite PreToolUse input (updatedInput) but does NOT let a
103
+ // PostToolUse hook rewrite already-emitted tool output.
104
+ canModifyArgs: true,
105
+ canModifyOutput: false,
106
+ canInjectSessionContext: true,
107
+ // Native passthrough hooks: settings.json hook keys are free-form event
108
+ // names, so any host event (TaskCreated, TeammateIdle, WorktreeCreate, …)
109
+ // declared under platforms["claude-code"].nativeHooks installs verbatim.
110
+ supportsNativeHooks: true,
111
+ // Declarative host-config key patches: claude-code is the ONLY v1 host
112
+ // (set-if-absent leaf keys in settings.json, refcounted ownership ledger,
113
+ // sensitive-key denylist — see SENSITIVE_KEY_RULES below).
114
+ supportsConfigPatch: true,
115
+ transports: ["stdio", "http"],
116
+ // Content surfaces: Claude Code is the reference implementation for all three.
117
+ supportsCommands: true,
118
+ supportsSkills: true,
119
+ supportsSubagents: true,
120
+ // Memory surface — EXCEPTION host: Claude Code reads CLAUDE.md, NOT
121
+ // AGENTS.md (docs are explicit; no AGENTS.md support through v2.1.172), so
122
+ // memoryTargets/installMemory below override the base AGENTS.md default.
123
+ supportsMemory: true
124
+ };
125
+ // ── Detection ────────────────────────────────────────────────────────────
126
+ detectInstalled(projectDir) {
127
+ const userDir = join(homedir(), ".claude");
128
+ const userSettings = join(userDir, "settings.json");
129
+ const userServers = join(homedir(), ".claude.json");
130
+ const projectServers = join(projectDir, ".mcp.json");
131
+ const userInstalled = existsSync(userDir) || existsSync(userSettings) || existsSync(userServers);
132
+ const projInstalled = existsSync(projectServers);
133
+ const installed = userInstalled || projInstalled;
134
+ const scope = projInstalled && !userInstalled ? "project" : "user";
135
+ const configPath = scope === "project" ? projectServers : userSettings;
136
+ return {
137
+ id: this.id,
138
+ name: this.name,
139
+ installed,
140
+ paradigm: this.paradigm,
141
+ capabilities: this.capabilities,
142
+ configPath,
143
+ scope,
144
+ reason: installed ? scope === "project" ? `found project Claude Code config at ${projectServers}` : `found Claude Code config under ${userDir}` : `no Claude Code config at ${userDir}`,
145
+ confidence: installed ? "high" : "low"
146
+ };
147
+ }
148
+ // ── Native paths ─────────────────────────────────────────────────────────
149
+ getConfigDir(ctx) {
150
+ return ctx.scope === "project" ? join(ctx.projectDir, ".claude") : join(homedir(), ".claude");
151
+ }
152
+ getServerConfigPath(ctx) {
153
+ return ctx.scope === "project" ? join(ctx.projectDir, ".mcp.json") : join(homedir(), ".claude.json");
154
+ }
155
+ getHookConfigPath(ctx) {
156
+ return join(this.getConfigDir(ctx), "settings.json");
157
+ }
158
+ // ── MCP server install / uninstall ───────────────────────────────────────
159
+ installServer(ctx) {
160
+ const { connector } = ctx;
161
+ const override = connector.platforms[HOST]?.server;
162
+ if (!connector.server || override === false) {
163
+ return [
164
+ {
165
+ platform: this.id,
166
+ action: "skip",
167
+ detail: connector.server ? "server registration disabled for claude-code" : "connector declares no MCP server"
168
+ }
169
+ ];
170
+ }
171
+ const server = override && typeof override === "object" ? { ...connector.server, ...override } : connector.server;
172
+ const serverPath = this.getServerConfigPath(ctx);
173
+ const entry = this.renderServerEntry(ctx, server);
174
+ return [
175
+ this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun)
176
+ ];
177
+ }
178
+ uninstallServer(ctx) {
179
+ const serverPath = this.getServerConfigPath(ctx);
180
+ return [
181
+ this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun)
182
+ ];
183
+ }
184
+ /** Render a normalized ServerDef into Claude Code's native mcpServers entry. */
185
+ renderServerEntry(ctx, server) {
186
+ const transport = server.transport;
187
+ if (transport === "stdio") {
188
+ let command = server.command ?? "";
189
+ let args = [...server.args ?? []];
190
+ if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
191
+ const wrapped = buildServeWrapperCommand(
192
+ ctx.homeBinPath,
193
+ ctx.connector.id,
194
+ command,
195
+ args,
196
+ ctx.scope,
197
+ this.id
198
+ );
199
+ command = wrapped.command;
200
+ args = wrapped.args;
201
+ }
202
+ const entry2 = { type: "stdio", command, args };
203
+ const env = this.renderEnv(server.env);
204
+ if (env) entry2.env = env;
205
+ if (server.cwd) entry2.cwd = resolveEnvRefsDeep(server.cwd);
206
+ return entry2;
207
+ }
208
+ const entry = {
209
+ type: "http",
210
+ url: rewriteEnvRefs(server.url ?? "", claudeEnvToken)
211
+ };
212
+ if (server.headers) {
213
+ entry.headers = this.renderEnv(server.headers) ?? {};
214
+ }
215
+ return entry;
216
+ }
217
+ /**
218
+ * Render env/header values. Claude Code supports its own `${VAR}` native
219
+ * interpolation, so translate `${env:VAR}` refs to that native token rather
220
+ * than baking secrets into the file. Literals pass through unchanged.
221
+ */
222
+ renderEnv(env) {
223
+ if (!env || Object.keys(env).length === 0) return void 0;
224
+ const out = {};
225
+ for (const [k, v] of Object.entries(env)) {
226
+ out[k] = rewriteEnvRefs(v, claudeEnvToken);
227
+ }
228
+ return out;
229
+ }
230
+ // ── Hook install / uninstall ─────────────────────────────────────────────
231
+ installHooks(ctx) {
232
+ const { connector } = ctx;
233
+ const override = connector.platforms[HOST];
234
+ const normalizedEvents = override?.hooks === false ? [] : connector.hookEvents;
235
+ const nativeHooks = override?.nativeHooks ?? {};
236
+ const nativeEvents = Object.keys(nativeHooks);
237
+ if (normalizedEvents.length === 0 && nativeEvents.length === 0) {
238
+ return [
239
+ {
240
+ platform: this.id,
241
+ action: "skip",
242
+ detail: override?.hooks === false ? "hooks disabled for claude-code" : "connector declares no hooks"
243
+ }
244
+ ];
245
+ }
246
+ const settingsPath = this.getHookConfigPath(ctx);
247
+ const settings = this.readJson(settingsPath) ?? {};
248
+ const hooks = settings.hooks ??= {};
249
+ const changes = [];
250
+ let mutated = false;
251
+ const pending = [
252
+ ...normalizedEvents.map((event) => ({
253
+ event,
254
+ matcher: connector.hooks[event]?.matcher ?? ""
255
+ })),
256
+ ...nativeEvents.map((event) => ({
257
+ event,
258
+ matcher: nativeHooks[event]?.matcher ?? ""
259
+ }))
260
+ ];
261
+ for (const { event, matcher } of pending) {
262
+ const command = buildHomeBinHookCommand(ctx.homeBinPath, HOST, event, connector.id);
263
+ const entry = {
264
+ matcher,
265
+ hooks: [{ type: "command", command }]
266
+ };
267
+ const bucket = hooks[event] ??= [];
268
+ const existingIdx = bucket.findIndex((e) => this.entryHasCommand(e, command));
269
+ if (existingIdx >= 0) {
270
+ if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {
271
+ changes.push({
272
+ platform: this.id,
273
+ action: "skip",
274
+ path: settingsPath,
275
+ detail: `hooks.${event} already registered`
276
+ });
277
+ continue;
278
+ }
279
+ bucket[existingIdx] = entry;
280
+ changes.push({
281
+ platform: this.id,
282
+ action: "update",
283
+ path: settingsPath,
284
+ detail: `hooks.${event}`
285
+ });
286
+ } else {
287
+ bucket.push(entry);
288
+ changes.push({
289
+ platform: this.id,
290
+ action: "create",
291
+ path: settingsPath,
292
+ detail: `hooks.${event}`
293
+ });
294
+ }
295
+ mutated = true;
296
+ }
297
+ if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);
298
+ return changes;
299
+ }
300
+ uninstallHooks(ctx) {
301
+ const settingsPath = this.getHookConfigPath(ctx);
302
+ const settings = this.readJson(settingsPath);
303
+ const hooks = settings?.hooks;
304
+ if (!settings || !hooks) {
305
+ return [
306
+ {
307
+ platform: this.id,
308
+ action: "skip",
309
+ path: settingsPath,
310
+ detail: "no hooks section present"
311
+ }
312
+ ];
313
+ }
314
+ const changes = [];
315
+ let mutated = false;
316
+ for (const event of Object.keys(hooks)) {
317
+ const bucket = hooks[event];
318
+ if (!Array.isArray(bucket)) continue;
319
+ const next = [];
320
+ let removed = 0;
321
+ for (const e of bucket) {
322
+ const innerBefore = e.hooks?.length ?? 0;
323
+ const inner = (e.hooks ?? []).filter(
324
+ (h) => !this.isOurCommand(h.command, ctx)
325
+ );
326
+ removed += innerBefore - inner.length;
327
+ if (inner.length > 0) next.push({ matcher: e.matcher ?? "", hooks: inner });
328
+ }
329
+ if (removed > 0) {
330
+ if (next.length > 0) hooks[event] = next;
331
+ else delete hooks[event];
332
+ changes.push({
333
+ platform: this.id,
334
+ action: "remove",
335
+ path: settingsPath,
336
+ detail: `hooks.${event} (${removed})`
337
+ });
338
+ mutated = true;
339
+ }
340
+ }
341
+ if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);
342
+ if (changes.length === 0) {
343
+ changes.push({
344
+ platform: this.id,
345
+ action: "skip",
346
+ path: settingsPath,
347
+ detail: "no matching hook entries"
348
+ });
349
+ }
350
+ return changes;
351
+ }
352
+ entryHasCommand(entry, command) {
353
+ return (entry.hooks ?? []).some((h) => h.command === command);
354
+ }
355
+ // ── Declarative host-config key patches (configPatch) ────────────────────
356
+ // FIXED semantics (docs/ARCHITECTURE.md §4): set-if-absent on a single
357
+ // dotted LEAF key of settings.json, skip-warn on ANY conflict (present key,
358
+ // non-object intermediate, drift), refcounted ownership in the persisted
359
+ // ledger at <dataRoot>/state/config-patches.json. Reuses the adapter's
360
+ // existing whole-file settings.json JSON IO (the same handling the hook
361
+ // install performs on this exact file) — Claude Code itself rewrites this
362
+ // file, so format preservation is a non-issue here. NOTE for future hosts:
363
+ // VS Code JSONC needs jsonc-parser modify/applyEdits and Codex TOML needs an
364
+ // anchored edit — core/toml.ts's round-trip is BANNED for configPatch.
365
+ /** The ONLY file configPatch may touch on claude-code: settings.json at scope. */
366
+ getPatchableConfigPath(ctx) {
367
+ return join(this.getConfigDir(ctx), "settings.json");
368
+ }
369
+ installConfigPatches(ctx) {
370
+ const { connector } = ctx;
371
+ const patches = connector.platforms[HOST]?.configPatch ?? [];
372
+ if (patches.length === 0) {
373
+ return [
374
+ { platform: this.id, action: "skip", detail: "connector declares no configPatch entries" }
375
+ ];
376
+ }
377
+ const filePath = this.getPatchableConfigPath(ctx);
378
+ if (this.isPresentButUnparseable(filePath)) {
379
+ return [
380
+ {
381
+ platform: this.id,
382
+ action: "warn",
383
+ path: filePath,
384
+ detail: `existing ${filePath} is not parseable; configPatch left unapplied (back it up / fix it, then re-run)`
385
+ }
386
+ ];
387
+ }
388
+ const settings = this.readJson(filePath) ?? {};
389
+ if (typeof settings !== "object" || Array.isArray(settings)) {
390
+ return [
391
+ {
392
+ platform: this.id,
393
+ action: "warn",
394
+ path: filePath,
395
+ detail: `existing ${filePath} is not a JSON object; configPatch left unapplied`
396
+ }
397
+ ];
398
+ }
399
+ const ledger = loadConfigPatchLedger(ctx.dataRoot);
400
+ const changes = [];
401
+ let fileMutated = false;
402
+ let ledgerMutated = false;
403
+ for (const patch of patches) {
404
+ if (!isValidConfigPatchKey(patch.key)) {
405
+ changes.push({
406
+ platform: this.id,
407
+ action: "warn",
408
+ detail: `configPatch ${JSON.stringify(patch.key)} refused: not a dotted leaf path (segments must match [A-Za-z0-9_-]+; no array indices)`
409
+ });
410
+ continue;
411
+ }
412
+ const namespace = configPatchNamespaceViolation(patch.key);
413
+ if (namespace) {
414
+ changes.push({
415
+ platform: this.id,
416
+ action: "warn",
417
+ detail: `configPatch refused: ${namespace}`
418
+ });
419
+ continue;
420
+ }
421
+ const sensitive = claudeSensitiveKeyViolation(patch.key);
422
+ if (sensitive) {
423
+ changes.push({
424
+ platform: this.id,
425
+ action: "warn",
426
+ detail: `configPatch ${patch.key} refused: matches the claude-code sensitive-key denylist (${sensitive}); security-relevant keys are never patched`
427
+ });
428
+ continue;
429
+ }
430
+ const desired = resolveEnvRefsDeep(patch.value);
431
+ const segments = patch.key.split(".");
432
+ const leaf = readJsonLeaf(settings, segments);
433
+ const entry = findLedgerEntry(ledger, HOST, filePath, patch.key);
434
+ if (leaf.kind === "blocked") {
435
+ changes.push({
436
+ platform: this.id,
437
+ action: "warn",
438
+ path: filePath,
439
+ detail: `configPatch ${patch.key} skipped: "${leaf.atPath}" exists but is not an object \u2014 ${configPatchManualEdit(patch)}`
440
+ });
441
+ continue;
442
+ }
443
+ if (leaf.kind === "absent") {
444
+ writeJsonLeaf(settings, segments, desired);
445
+ fileMutated = true;
446
+ if (entry) {
447
+ entry.writtenValue = desired;
448
+ entry.writtenValueHash = hashJsonValue(desired);
449
+ addLedgerOwner(entry, connector.id, connector.version);
450
+ } else {
451
+ createLedgerEntry(ledger, {
452
+ platform: HOST,
453
+ file: filePath,
454
+ key: patch.key,
455
+ value: desired,
456
+ connectorId: connector.id,
457
+ connectorVersion: connector.version
458
+ });
459
+ }
460
+ ledgerMutated = true;
461
+ changes.push({
462
+ platform: this.id,
463
+ action: "create",
464
+ path: filePath,
465
+ detail: `configPatch ${patch.key}: <absent> \u2192 ${describeJsonValue(desired)} (${patch.reason})`
466
+ });
467
+ continue;
468
+ }
469
+ if (!entry) {
470
+ changes.push({
471
+ platform: this.id,
472
+ action: "warn",
473
+ path: filePath,
474
+ detail: `configPatch ${patch.key} skipped: already set to ${describeJsonValue(leaf.value)} (not created by agent-connector; desired ${describeJsonValue(desired)}) \u2014 ` + configPatchManualEdit(patch)
475
+ });
476
+ continue;
477
+ }
478
+ if (!jsonDeepEquals(leaf.value, entry.writtenValue)) {
479
+ changes.push({
480
+ platform: this.id,
481
+ action: "warn",
482
+ path: filePath,
483
+ detail: `configPatch ${patch.key}: value changed since install (current ${describeJsonValue(leaf.value)}, wrote ${describeJsonValue(entry.writtenValue)}); leaving in place \u2014 ${configPatchManualEdit(patch)}`
484
+ });
485
+ continue;
486
+ }
487
+ if (jsonDeepEquals(desired, leaf.value)) {
488
+ const owners = entry.owners.map((o) => o.connectorId);
489
+ if (addLedgerOwner(entry, connector.id, connector.version)) {
490
+ ledgerMutated = true;
491
+ changes.push({
492
+ platform: this.id,
493
+ action: "skip",
494
+ path: filePath,
495
+ detail: `configPatch ${patch.key} already installed; registered as co-owner (co-owned with ${owners.join(", ")})`
496
+ });
497
+ } else {
498
+ changes.push({
499
+ platform: this.id,
500
+ action: "skip",
501
+ path: filePath,
502
+ detail: `configPatch ${patch.key} already installed`
503
+ });
504
+ }
505
+ continue;
506
+ }
507
+ changes.push({
508
+ platform: this.id,
509
+ action: "warn",
510
+ path: filePath,
511
+ detail: `configPatch ${patch.key} skipped: already owned by ${entry.owners.map((o) => o.connectorId).join(", ")} with a different value (current ${describeJsonValue(leaf.value)}, desired ${describeJsonValue(desired)}) \u2014 ` + configPatchManualEdit(patch)
512
+ });
513
+ }
514
+ if (fileMutated) this.writeJson(filePath, settings, ctx.dryRun);
515
+ if (ledgerMutated && !ctx.dryRun) saveConfigPatchLedger(ctx.dataRoot, ledger);
516
+ return changes;
517
+ }
518
+ uninstallConfigPatches(ctx) {
519
+ const connectorId = ctx.connector.id;
520
+ const ledger = loadConfigPatchLedger(ctx.dataRoot);
521
+ const owned = ledgerEntriesOwnedBy(ledger, HOST, connectorId);
522
+ const declared = ctx.connector.platforms[HOST]?.configPatch ?? [];
523
+ const changes = [];
524
+ const ownedKeys = new Set(owned.map((e) => e.key));
525
+ for (const patch of declared) {
526
+ if (!ownedKeys.has(patch.key)) {
527
+ changes.push({
528
+ platform: this.id,
529
+ action: "skip",
530
+ detail: `configPatch ${patch.key}: no ownership recorded; left untouched`
531
+ });
532
+ }
533
+ }
534
+ if (owned.length === 0) return changes;
535
+ let ledgerMutated = false;
536
+ const backedUp = /* @__PURE__ */ new Set();
537
+ const byFile = /* @__PURE__ */ new Map();
538
+ for (const entry of owned) {
539
+ const bucket = byFile.get(entry.file) ?? [];
540
+ bucket.push(entry);
541
+ byFile.set(entry.file, bucket);
542
+ }
543
+ for (const [filePath, entries] of byFile) {
544
+ const unparseable = this.isPresentButUnparseable(filePath);
545
+ const settings = unparseable ? null : this.readJson(filePath);
546
+ let fileMutated = false;
547
+ for (const entry of entries) {
548
+ const { lastOwner } = removeLedgerOwner(entry, connectorId);
549
+ ledgerMutated = true;
550
+ if (!lastOwner) {
551
+ changes.push({
552
+ platform: this.id,
553
+ action: "skip",
554
+ path: filePath,
555
+ detail: `configPatch ${entry.key} retained: still owned by ${entry.owners.map((o) => o.connectorId).join(", ")}`
556
+ });
557
+ continue;
558
+ }
559
+ dropLedgerEntry(ledger, entry);
560
+ if (unparseable) {
561
+ changes.push({
562
+ platform: this.id,
563
+ action: "warn",
564
+ path: filePath,
565
+ detail: `configPatch ${entry.key}: ${filePath} is not parseable; key left in place (ownership released)`
566
+ });
567
+ continue;
568
+ }
569
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
570
+ changes.push({
571
+ platform: this.id,
572
+ action: "skip",
573
+ path: filePath,
574
+ detail: `configPatch ${entry.key} already absent (no settings file); ownership record dropped`
575
+ });
576
+ continue;
577
+ }
578
+ const leaf = readJsonLeaf(settings, entry.key.split("."));
579
+ if (leaf.kind !== "present") {
580
+ changes.push({
581
+ platform: this.id,
582
+ action: "skip",
583
+ path: filePath,
584
+ detail: `configPatch ${entry.key} already absent; ownership record dropped`
585
+ });
586
+ continue;
587
+ }
588
+ if (entry.prior?.present !== false || !jsonDeepEquals(leaf.value, entry.writtenValue)) {
589
+ changes.push({
590
+ platform: this.id,
591
+ action: "warn",
592
+ path: filePath,
593
+ detail: `configPatch ${entry.key}: value changed since install (current ${describeJsonValue(leaf.value)}, wrote ${describeJsonValue(entry.writtenValue)}); left in place`
594
+ });
595
+ continue;
596
+ }
597
+ if (!ctx.dryRun && !backedUp.has(filePath) && existsSync(filePath)) {
598
+ const backup = this.backupFileForConfigPatch(filePath);
599
+ backedUp.add(filePath);
600
+ if (backup) {
601
+ changes.push({
602
+ platform: this.id,
603
+ action: "create",
604
+ path: backup,
605
+ detail: "backed up settings before configPatch removal"
606
+ });
607
+ }
608
+ }
609
+ deleteJsonLeaf(settings, entry.key.split("."));
610
+ fileMutated = true;
611
+ changes.push({
612
+ platform: this.id,
613
+ action: "remove",
614
+ path: filePath,
615
+ detail: `configPatch ${entry.key} removed (was ${describeJsonValue(entry.writtenValue)})`
616
+ });
617
+ }
618
+ if (fileMutated && settings) this.writeJson(filePath, settings, ctx.dryRun);
619
+ }
620
+ if (ledgerMutated && !ctx.dryRun) saveConfigPatchLedger(ctx.dataRoot, ledger);
621
+ return changes;
622
+ }
623
+ /**
624
+ * Back up ONE exact file (the ledger-recorded patch target, which may belong
625
+ * to a different scope than the current ctx) into the standard backups dir.
626
+ * Best-effort: a failed copy returns null and the caller proceeds — the
627
+ * removal is already value-verified.
628
+ */
629
+ backupFileForConfigPatch(filePath) {
630
+ try {
631
+ ensureDir(backupsDir());
632
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
633
+ const dest = join(backupsDir(), `${this.id}-${stamp}-${basename(filePath)}`);
634
+ copyFileSync(filePath, dest);
635
+ return dest;
636
+ } catch {
637
+ return null;
638
+ }
639
+ }
640
+ /**
641
+ * Doctor: per-ledger-entry configPatch state for this connector —
642
+ * ok → key present and still exactly what we wrote;
643
+ * drifted → user changed the value (we print the manual edit, NEVER
644
+ * auto-fix — sync re-asserts only absent keys);
645
+ * missing → key deleted by the user; sync will re-assert it;
646
+ * orphaned → ledger row whose owners' connector records are all gone.
647
+ * Plus a re-print of the manual edit for declared patches that hold no
648
+ * ownership (skipped at install).
649
+ */
650
+ configPatchDiagnostics(ctx) {
651
+ const results = [];
652
+ const ledger = loadConfigPatchLedger(ctx.dataRoot);
653
+ const connectorId = ctx.connector.id;
654
+ const platformEntries = ledger.entries.filter((e) => e.platform === this.id);
655
+ for (const entry of platformEntries) {
656
+ const ownedByThis = entry.owners.some((o) => o.connectorId === connectorId);
657
+ if (!ownedByThis) {
658
+ const orphaned = entry.owners.length === 0 || entry.owners.every((o) => readRegisteredMeta(o.connectorId) === null);
659
+ if (orphaned) {
660
+ results.push({
661
+ check: `${this.name}: configPatch ${entry.key}`,
662
+ status: "warn",
663
+ message: `orphaned ledger entry in ${entry.file}: owning connector record(s) gone (${entry.owners.map((o) => o.connectorId).join(", ") || "none"})`,
664
+ fix: `re-run \`agent-connector uninstall <owner-id>\` to release it, or remove ${entry.key} from ${entry.file} manually`
665
+ });
666
+ }
667
+ continue;
668
+ }
669
+ const state = this.configPatchState(entry);
670
+ if (state.kind === "ok") {
671
+ results.push({
672
+ check: `${this.name}: configPatch ${entry.key}`,
673
+ status: "pass",
674
+ message: `ok \u2014 ${entry.key} = ${describeJsonValue(entry.writtenValue)} (${entry.file})`
675
+ });
676
+ } else if (state.kind === "missing") {
677
+ results.push({
678
+ check: `${this.name}: configPatch ${entry.key}`,
679
+ status: "warn",
680
+ message: `missing \u2014 key deleted from ${entry.file} since install`,
681
+ fix: `agent-connector install (sync) will re-assert it`
682
+ });
683
+ } else {
684
+ results.push({
685
+ check: `${this.name}: configPatch ${entry.key}`,
686
+ status: "warn",
687
+ message: `drifted \u2014 value changed since install (current ${describeJsonValue(state.current)}, wrote ${describeJsonValue(entry.writtenValue)}); never auto-fixed`,
688
+ fix: `manual edit if wanted: set ${entry.key} = ${describeJsonValue(entry.writtenValue)} in ${entry.file}`
689
+ });
690
+ }
691
+ }
692
+ const declared = ctx.connector.platforms[HOST]?.configPatch ?? [];
693
+ const ownedKeys = new Set(
694
+ ledgerEntriesOwnedBy(ledger, HOST, connectorId).map((e) => e.key)
695
+ );
696
+ for (const patch of declared) {
697
+ if (ownedKeys.has(patch.key)) continue;
698
+ results.push({
699
+ check: `${this.name}: configPatch ${patch.key}`,
700
+ status: "warn",
701
+ message: `declared but not owned (skipped at install or not yet installed)`,
702
+ fix: configPatchManualEdit(patch)
703
+ });
704
+ }
705
+ return results;
706
+ }
707
+ /** Current on-disk state of one owned ledger entry. */
708
+ configPatchState(entry) {
709
+ const settings = this.readJson(entry.file);
710
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
711
+ return { kind: "missing" };
712
+ }
713
+ const leaf = readJsonLeaf(settings, entry.key.split("."));
714
+ if (leaf.kind !== "present") return { kind: "missing" };
715
+ if (jsonDeepEquals(leaf.value, entry.writtenValue)) return { kind: "ok" };
716
+ return { kind: "drifted", current: leaf.value };
717
+ }
718
+ doctor(ctx) {
719
+ const results = super.doctor(ctx);
720
+ results.push(...this.configPatchDiagnostics(ctx));
721
+ return results;
722
+ }
723
+ // ── Content surfaces: commands / skills / subagents ──────────────────────
724
+ // CONTENT-ONLY: pure native-file writers under <configDir>/{commands,skills,
725
+ // agents}. No runtime dispatch, no home-bin pointer, no telemetry wrap. Each
726
+ // method is idempotent (byte-identical → skip) via BaseAdapter.writeContentFile
727
+ // and reversible via removeContentFile. Honors platforms["claude-code"]
728
+ // per-surface false to skip.
729
+ commandsDir(ctx) {
730
+ return join(this.getConfigDir(ctx), "commands");
731
+ }
732
+ skillsDir(ctx) {
733
+ return join(this.getConfigDir(ctx), "skills");
734
+ }
735
+ agentsDir(ctx) {
736
+ return join(this.getConfigDir(ctx), "agents");
737
+ }
738
+ /** Native command file path: <configDir>/commands/<name>.md. */
739
+ commandPath(ctx, name) {
740
+ return join(this.commandsDir(ctx), `${name}.md`);
741
+ }
742
+ /** Native skill dir: <configDir>/skills/<name>. */
743
+ skillDir(ctx, name) {
744
+ return join(this.skillsDir(ctx), name);
745
+ }
746
+ /** Native subagent file path: <configDir>/agents/<name>.md. */
747
+ subagentPath(ctx, name) {
748
+ return join(this.agentsDir(ctx), `${name}.md`);
749
+ }
750
+ // ── Commands ──────────────────────────────────────────────────────────────
751
+ installCommands(ctx) {
752
+ const { connector } = ctx;
753
+ if (connector.platforms[HOST]?.commands === false) {
754
+ return [{ platform: this.id, action: "skip", detail: "commands disabled for claude-code" }];
755
+ }
756
+ if (connector.commands.length === 0) {
757
+ return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
758
+ }
759
+ return connector.commands.map(
760
+ (cmd) => this.writeContentFile(
761
+ this.commandPath(ctx, cmd.name),
762
+ this.renderCommand(cmd),
763
+ ctx.dryRun
764
+ )
765
+ );
766
+ }
767
+ uninstallCommands(ctx) {
768
+ const { connector } = ctx;
769
+ if (connector.commands.length === 0) {
770
+ return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
771
+ }
772
+ return connector.commands.map(
773
+ (cmd) => this.removeContentFile(this.commandPath(ctx, cmd.name), ctx.dryRun)
774
+ );
775
+ }
776
+ /** Render a command to md+frontmatter (delegates to the shared renderer). */
777
+ renderCommand(cmd) {
778
+ return renderCommandMd(cmd);
779
+ }
780
+ // ── Skills ────────────────────────────────────────────────────────────────
781
+ installSkills(ctx) {
782
+ const { connector } = ctx;
783
+ if (connector.platforms[HOST]?.skills === false) {
784
+ return [{ platform: this.id, action: "skip", detail: "skills disabled for claude-code" }];
785
+ }
786
+ if (connector.skills.length === 0) {
787
+ return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
788
+ }
789
+ const changes = [];
790
+ for (const skill of connector.skills) {
791
+ const dir = this.skillDir(ctx, skill.name);
792
+ changes.push(
793
+ this.writeContentFile(join(dir, "SKILL.md"), this.renderSkill(skill), ctx.dryRun)
794
+ );
795
+ for (const [rel, contents] of Object.entries(skill.resources ?? {})) {
796
+ const target = this.resolveWithin(dir, rel);
797
+ if (target === null) {
798
+ changes.push({
799
+ platform: this.id,
800
+ action: "warn",
801
+ detail: `skill resource "${rel}" escapes the skill dir; skipped`
802
+ });
803
+ continue;
804
+ }
805
+ changes.push(this.writeContentFile(target, contents, ctx.dryRun));
806
+ }
807
+ }
808
+ return changes;
809
+ }
810
+ uninstallSkills(ctx) {
811
+ const { connector } = ctx;
812
+ if (connector.skills.length === 0) {
813
+ return [{ platform: this.id, action: "skip", detail: "connector declares no skills" }];
814
+ }
815
+ const changes = [];
816
+ for (const skill of connector.skills) {
817
+ const dir = this.skillDir(ctx, skill.name);
818
+ changes.push(this.removeContentFile(join(dir, "SKILL.md"), ctx.dryRun));
819
+ for (const rel of Object.keys(skill.resources ?? {})) {
820
+ const target = this.resolveWithin(dir, rel);
821
+ if (target === null) continue;
822
+ changes.push(this.removeContentFile(target, ctx.dryRun));
823
+ }
824
+ changes.push(this.removeDirIfEmpty(dir, ctx.dryRun));
825
+ }
826
+ return changes;
827
+ }
828
+ /** Render a skill's SKILL.md (delegates to the shared renderer). */
829
+ renderSkill(skill) {
830
+ return renderSkillMd(skill);
831
+ }
832
+ // ── Subagents ───────────────────────────────────────────────────────────────
833
+ installSubagents(ctx) {
834
+ const { connector } = ctx;
835
+ if (connector.platforms[HOST]?.subagents === false) {
836
+ return [{ platform: this.id, action: "skip", detail: "subagents disabled for claude-code" }];
837
+ }
838
+ if (connector.subagents.length === 0) {
839
+ return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
840
+ }
841
+ return connector.subagents.map(
842
+ (agent) => this.writeContentFile(
843
+ this.subagentPath(ctx, agent.name),
844
+ this.renderSubagent(agent),
845
+ ctx.dryRun
846
+ )
847
+ );
848
+ }
849
+ uninstallSubagents(ctx) {
850
+ const { connector } = ctx;
851
+ if (connector.subagents.length === 0) {
852
+ return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
853
+ }
854
+ return connector.subagents.map(
855
+ (agent) => this.removeContentFile(this.subagentPath(ctx, agent.name), ctx.dryRun)
856
+ );
857
+ }
858
+ /** Render a subagent to md+frontmatter (delegates to the shared renderer). */
859
+ renderSubagent(agent) {
860
+ return renderSubagentMd(agent);
861
+ }
862
+ // ── Memory surface: CLAUDE.md managed block + AGENTS.md interop ────────────
863
+ // EXCEPTION host. Claude Code reads CLAUDE.md, NOT AGENTS.md (docs verbatim:
864
+ // "Claude Code reads CLAUDE.md, not AGENTS.md"; no AGENTS.md support through
865
+ // v2.1.172), so the base AGENTS.md-first default is overridden:
866
+ // - mode "block" (default): the managed block goes straight into CLAUDE.md.
867
+ // HTML-comment markers are CORRECT here — Claude strips HTML comments
868
+ // from CLAUDE.md before context injection, so the markers and the
869
+ // do-not-edit notice are INVISIBLE to the model while remaining fully
870
+ // parseable by us for sync/doctor/uninstall.
871
+ // - mode "agents-import" (opt-in via platforms["claude-code"].memory.mode):
872
+ // canonical block goes into AGENTS.md, plus a SHARED `@AGENTS.md` import
873
+ // bridge block in CLAUDE.md (Anthropic's documented interop). Opt-in
874
+ // because the import makes Claude read the ENTIRE AGENTS.md — a behavior
875
+ // change agent-connector must not make silently.
876
+ // - AUTO interop: when CLAUDE.md ALREADY imports `@AGENTS.md` (a line
877
+ // outside code fences and outside our own blocks) or IS a symlink
878
+ // resolving to AGENTS.md, the canonical block goes to AGENTS.md and
879
+ // CLAUDE.md is NOT touched — the pre-existing user wiring is never
880
+ // claimed as agent-connector-managed (prevents double-reads and writes
881
+ // "through" a symlink). Order-independent: keyed off the import/symlink
882
+ // existing, never off whether the AGENTS.md block was written yet.
883
+ /** CLAUDE.md path: project `<projectDir>/CLAUDE.md`, user `~/.claude/CLAUDE.md`. */
884
+ claudeMdPath(ctx) {
885
+ return ctx.scope === "project" ? join(ctx.projectDir, "CLAUDE.md") : join(homedir(), ".claude", "CLAUDE.md");
886
+ }
887
+ /** Sibling AGENTS.md at the same scope (agents-import / auto-interop target). */
888
+ agentsMdPath(ctx) {
889
+ return ctx.scope === "project" ? join(ctx.projectDir, "AGENTS.md") : join(homedir(), ".claude", "AGENTS.md");
890
+ }
891
+ /**
892
+ * True when the user ALREADY wired CLAUDE.md to AGENTS.md themselves:
893
+ * an `@AGENTS.md` import line outside code fences and outside agent-connector
894
+ * managed blocks (our own bridge must not count), or CLAUDE.md being a
895
+ * symlink that resolves to the sibling AGENTS.md.
896
+ */
897
+ claudeMdWiredToAgentsMd(ctx) {
898
+ const claudeMd = this.claudeMdPath(ctx);
899
+ if (!existsSync(claudeMd)) return false;
900
+ try {
901
+ if (lstatSync(claudeMd).isSymbolicLink()) {
902
+ const agentsMd = this.agentsMdPath(ctx);
903
+ const resolved = realpathSync(claudeMd);
904
+ const agentsResolved = existsSync(agentsMd) ? realpathSync(agentsMd) : agentsMd;
905
+ if (resolved === agentsResolved) return true;
906
+ }
907
+ } catch {
908
+ }
909
+ let raw;
910
+ try {
911
+ raw = readFileSync(claudeMd, "utf8");
912
+ } catch {
913
+ return false;
914
+ }
915
+ const withoutBlocks = removeBlocksFromText(raw, { blockIdPrefix: "" }).text;
916
+ const importRe = /(^|\s)@(?:AGENTS\.md|~\/\.claude\/AGENTS\.md)(\s|$)/;
917
+ return linesOutsideFences(withoutBlocks).some((line) => importRe.test(line));
918
+ }
919
+ /**
920
+ * Effective memory mode for this install:
921
+ * "user-import" — the user already wired CLAUDE.md→AGENTS.md (wins over
922
+ * everything; we never write a duplicate or a second import);
923
+ * "agents-import" — explicit opt-in via platforms["claude-code"].memory.mode;
924
+ * "block" — the default (managed block directly in CLAUDE.md).
925
+ */
926
+ effectiveMemoryMode(ctx) {
927
+ if (this.claudeMdWiredToAgentsMd(ctx)) return "user-import";
928
+ return this.memoryOverride(ctx)?.mode === "agents-import" ? "agents-import" : "block";
929
+ }
930
+ memoryTargets(ctx) {
931
+ if (this.memoryOverride(ctx)?.path) return super.memoryTargets(ctx);
932
+ if (ctx.scope !== "project" && ctx.scope !== "user") return [];
933
+ const mode = this.effectiveMemoryMode(ctx);
934
+ if (mode === "block") {
935
+ return [
936
+ {
937
+ path: this.claudeMdPath(ctx),
938
+ reason: "CLAUDE.md (Claude Code reads CLAUDE.md, not AGENTS.md; HTML-comment markers are stripped from the model's context)"
939
+ }
940
+ ];
941
+ }
942
+ return [
943
+ {
944
+ path: this.agentsMdPath(ctx),
945
+ reason: mode === "user-import" ? "AGENTS.md (CLAUDE.md already imports/symlinks it \u2014 user wiring respected)" : "AGENTS.md (agents-import mode; @AGENTS.md bridge managed in CLAUDE.md)"
946
+ }
947
+ ];
948
+ }
949
+ installMemory(ctx) {
950
+ const changes = super.installMemory(ctx);
951
+ const { connector } = ctx;
952
+ if (connector.platforms[HOST]?.memory === false || (connector.memory ?? []).length === 0) {
953
+ return changes;
954
+ }
955
+ if (ctx.scope !== "project" && ctx.scope !== "user") return changes;
956
+ if (this.memoryOverride(ctx)?.path) return changes;
957
+ const mode = this.effectiveMemoryMode(ctx);
958
+ if (mode === "user-import") {
959
+ changes.push({
960
+ platform: this.id,
961
+ action: "skip",
962
+ path: this.claudeMdPath(ctx),
963
+ detail: "memory: CLAUDE.md already imports AGENTS.md; canonical block in AGENTS.md suffices (pre-existing user import never claimed as managed)"
964
+ });
965
+ return changes;
966
+ }
967
+ if (mode !== "agents-import") return changes;
968
+ const claudeMd = this.claudeMdPath(ctx);
969
+ const importLine = ctx.scope === "project" ? "@AGENTS.md" : "@~/.claude/AGENTS.md";
970
+ const res = upsertManagedBlockFile(claudeMd, {
971
+ blockId: CLAUDE_AGENTS_IMPORT_BLOCK_ID,
972
+ connectorId: "_shared",
973
+ content: importLine,
974
+ notice: "Managed by agent-connector (shared bridge). This import makes Claude Code read AGENTS.md; it is removed automatically when the last agent-connector block leaves AGENTS.md.",
975
+ force: ctx.force ?? false,
976
+ dryRun: ctx.dryRun
977
+ });
978
+ changes.push({
979
+ platform: this.id,
980
+ action: res.action,
981
+ path: claudeMd,
982
+ detail: `memory: ${res.detail} \u2014 @AGENTS.md import bridge (agents-import mode)`
983
+ });
984
+ if (res.backupPath) {
985
+ changes.push({
986
+ platform: this.id,
987
+ action: "create",
988
+ path: res.backupPath,
989
+ detail: "backed up CLAUDE.md before destructive change"
990
+ });
991
+ }
992
+ if (res.action !== "warn" && !ctx.dryRun) {
993
+ const ledger = loadMemoryLedger(connector.id);
994
+ recordMemoryTarget(ledger, {
995
+ platform: this.id,
996
+ scope: ctx.scope,
997
+ path: claudeMd,
998
+ blockId: CLAUDE_AGENTS_IMPORT_BLOCK_ID,
999
+ createdFile: res.createdFile,
1000
+ hash: res.hash
1001
+ });
1002
+ saveMemoryLedger(connector.id, ledger);
1003
+ }
1004
+ return changes;
1005
+ }
1006
+ uninstallMemory(ctx) {
1007
+ const priorRows = loadMemoryLedger(ctx.connector.id).targets.filter(
1008
+ (t) => t.platform === this.id && t.blockId === CLAUDE_AGENTS_IMPORT_BLOCK_ID
1009
+ );
1010
+ const changes = super.uninstallMemory(ctx);
1011
+ const pairs = /* @__PURE__ */ new Map();
1012
+ if (ctx.scope === "project" || ctx.scope === "user") {
1013
+ pairs.set(this.claudeMdPath(ctx), {
1014
+ agentsMd: this.agentsMdPath(ctx),
1015
+ createdFile: false
1016
+ });
1017
+ }
1018
+ for (const row of priorRows) {
1019
+ const existing = pairs.get(row.path);
1020
+ pairs.set(row.path, {
1021
+ agentsMd: existing?.agentsMd ?? join(dirname(row.path), "AGENTS.md"),
1022
+ createdFile: (existing?.createdFile ?? false) || row.createdFile
1023
+ });
1024
+ }
1025
+ for (const [claudeMd, { agentsMd, createdFile }] of pairs) {
1026
+ changes.push(...this.releaseAgentsImportBridge(ctx, claudeMd, agentsMd, createdFile));
1027
+ }
1028
+ return changes;
1029
+ }
1030
+ /**
1031
+ * Refcounted bridge release: remove the `_shared/claude-agents-import` block
1032
+ * from CLAUDE.md ONLY when the sibling AGENTS.md holds zero agent-connector
1033
+ * blocks from OTHER connectors (namespace-wide count, excluding this
1034
+ * connector's own blocks so the answer is identical on dry-run and real run).
1035
+ * A user-authored `@AGENTS.md` import (no markers) is NEVER touched.
1036
+ */
1037
+ releaseAgentsImportBridge(ctx, claudeMd, agentsMd, createdFile) {
1038
+ if (!existsSync(claudeMd)) return [];
1039
+ let raw;
1040
+ try {
1041
+ raw = readFileSync(claudeMd, "utf8");
1042
+ } catch {
1043
+ return [];
1044
+ }
1045
+ if (!listManagedBlocks(raw).some((b) => b.blockId === CLAUDE_AGENTS_IMPORT_BLOCK_ID)) {
1046
+ return [];
1047
+ }
1048
+ let remaining = 0;
1049
+ if (existsSync(agentsMd)) {
1050
+ try {
1051
+ remaining = listManagedBlocks(readFileSync(agentsMd, "utf8")).filter(
1052
+ (b) => !b.blockId.startsWith(`${ctx.connector.id}/`)
1053
+ ).length;
1054
+ } catch {
1055
+ remaining = 1;
1056
+ }
1057
+ }
1058
+ if (remaining > 0) {
1059
+ return [
1060
+ {
1061
+ platform: this.id,
1062
+ action: "skip",
1063
+ path: claudeMd,
1064
+ detail: `memory: @AGENTS.md import bridge retained \u2014 ${remaining} agent-connector block(s) from other connectors remain in ${agentsMd}`
1065
+ }
1066
+ ];
1067
+ }
1068
+ return removeManagedBlocksFile(
1069
+ claudeMd,
1070
+ { blockId: CLAUDE_AGENTS_IMPORT_BLOCK_ID },
1071
+ { dryRun: ctx.dryRun, deleteFileIfCreated: createdFile }
1072
+ ).map((r) => ({
1073
+ platform: this.id,
1074
+ action: r.action,
1075
+ path: r.path,
1076
+ detail: `memory: ${r.detail}`
1077
+ }));
1078
+ }
1079
+ /** True when a hook command references our home binary AND this connector id
1080
+ * (anchored so a shared-prefix id can't collide — see isHomeBinHookCommand). */
1081
+ isOurCommand(command, ctx) {
1082
+ return isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id);
1083
+ }
1084
+ // ── Diagnostics ──────────────────────────────────────────────────────────
1085
+ getHealthChecks(ctx) {
1086
+ const settingsPath = this.getHookConfigPath(ctx);
1087
+ const connectorId = ctx.connector.id;
1088
+ const homeBin = ctx.homeBinPath;
1089
+ const hookEvents = ctx.connector.hookEvents;
1090
+ const declaredHookCount = hookEvents.length + Object.keys(ctx.connector.platforms[HOST]?.nativeHooks ?? {}).length;
1091
+ const checks = [
1092
+ {
1093
+ name: `${this.name}: settings.json present`,
1094
+ check: () => {
1095
+ if (declaredHookCount === 0) {
1096
+ return { status: "OK", detail: "no hooks declared" };
1097
+ }
1098
+ return existsSync(settingsPath) ? { status: "OK", detail: settingsPath } : { status: "FAIL", detail: `not found: ${settingsPath}` };
1099
+ }
1100
+ },
1101
+ {
1102
+ name: `${this.name}: hook command registered`,
1103
+ check: () => {
1104
+ if (declaredHookCount === 0) {
1105
+ return { status: "OK", detail: "no hooks declared" };
1106
+ }
1107
+ const settings = this.readJson(
1108
+ settingsPath
1109
+ );
1110
+ if (!settings) return { status: "FAIL", detail: `cannot read ${settingsPath}` };
1111
+ const hooks = settings.hooks ?? {};
1112
+ const registered = Object.values(hooks).some(
1113
+ (entries) => (entries ?? []).some(
1114
+ (e) => (e.hooks ?? []).some(
1115
+ (h) => isHomeBinHookCommand(h.command, homeBin, connectorId)
1116
+ )
1117
+ )
1118
+ );
1119
+ return registered ? { status: "OK", detail: "hook command present" } : {
1120
+ status: "FAIL",
1121
+ detail: `no hook for ${connectorId} in ${settingsPath}`
1122
+ };
1123
+ }
1124
+ }
1125
+ ];
1126
+ for (const cmd of ctx.connector.commands) {
1127
+ const p = this.commandPath(ctx, cmd.name);
1128
+ checks.push({
1129
+ name: `${this.name}: command ${cmd.name} present`,
1130
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
1131
+ });
1132
+ }
1133
+ for (const skill of ctx.connector.skills) {
1134
+ const p = join(this.skillDir(ctx, skill.name), "SKILL.md");
1135
+ checks.push({
1136
+ name: `${this.name}: skill ${skill.name} present`,
1137
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
1138
+ });
1139
+ }
1140
+ for (const agent of ctx.connector.subagents) {
1141
+ const p = this.subagentPath(ctx, agent.name);
1142
+ checks.push({
1143
+ name: `${this.name}: subagent ${agent.name} present`,
1144
+ check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
1145
+ });
1146
+ }
1147
+ return checks;
1148
+ }
1149
+ // ── Runtime: parse Claude stdin JSON → normalized event ──────────────────
1150
+ parseEvent(event, raw) {
1151
+ const input = raw ?? {};
1152
+ const connectorId = typeof input.connector === "string" ? input.connector : "";
1153
+ const sessionId = extractSessionId(input);
1154
+ const projectDir = typeof input.cwd === "string" ? input.cwd : void 0;
1155
+ const base = {
1156
+ hostPlatform: HOST,
1157
+ connectorId,
1158
+ sessionId,
1159
+ raw,
1160
+ ...projectDir !== void 0 ? { projectDir } : {}
1161
+ };
1162
+ switch (event) {
1163
+ case "PreToolUse": {
1164
+ const ev = {
1165
+ ...base,
1166
+ toolName: input.tool_name ?? "",
1167
+ toolInput: input.tool_input ?? {}
1168
+ };
1169
+ return ev;
1170
+ }
1171
+ case "PostToolUse": {
1172
+ const ev = {
1173
+ ...base,
1174
+ toolName: input.tool_name ?? "",
1175
+ toolInput: input.tool_input ?? {},
1176
+ ...toolResponseToString(input.tool_response) !== void 0 ? { toolOutput: toolResponseToString(input.tool_response) } : {}
1177
+ };
1178
+ return ev;
1179
+ }
1180
+ case "SessionStart": {
1181
+ const ev = {
1182
+ ...base,
1183
+ source: normalizeSessionSource(input.source)
1184
+ };
1185
+ return ev;
1186
+ }
1187
+ case "SessionEnd": {
1188
+ const ev = {
1189
+ ...base,
1190
+ ...typeof input.reason === "string" ? { reason: input.reason } : {}
1191
+ };
1192
+ return ev;
1193
+ }
1194
+ case "UserPromptSubmit": {
1195
+ const ev = {
1196
+ ...base,
1197
+ prompt: typeof input.prompt === "string" ? input.prompt : ""
1198
+ };
1199
+ return ev;
1200
+ }
1201
+ case "PreCompact": {
1202
+ const ev = {
1203
+ ...base,
1204
+ ...input.trigger === "auto" || input.trigger === "manual" ? { trigger: input.trigger } : {}
1205
+ };
1206
+ return ev;
1207
+ }
1208
+ case "Stop": {
1209
+ const ev = {
1210
+ ...base,
1211
+ ...typeof input.stop_hook_active === "boolean" ? { stopHookActive: input.stop_hook_active } : {}
1212
+ };
1213
+ return ev;
1214
+ }
1215
+ case "Notification": {
1216
+ const ev = {
1217
+ ...base,
1218
+ message: typeof input.message === "string" ? input.message : ""
1219
+ };
1220
+ return ev;
1221
+ }
1222
+ case "PermissionRequest": {
1223
+ const ev = {
1224
+ ...base,
1225
+ toolName: input.tool_name ?? "",
1226
+ toolInput: input.tool_input ?? {},
1227
+ ...Array.isArray(input.permission_suggestions) ? { permissionSuggestions: input.permission_suggestions } : {}
1228
+ };
1229
+ return ev;
1230
+ }
1231
+ case "PostToolUseFailure": {
1232
+ const ev = {
1233
+ ...base,
1234
+ toolName: input.tool_name ?? "",
1235
+ toolInput: input.tool_input ?? {},
1236
+ error: typeof input.error === "string" ? input.error : "",
1237
+ ...typeof input.tool_use_id === "string" ? { toolUseId: input.tool_use_id } : {},
1238
+ ...typeof input.is_interrupt === "boolean" ? { isInterrupt: input.is_interrupt } : {},
1239
+ ...typeof input.duration_ms === "number" ? { durationMs: input.duration_ms } : {}
1240
+ };
1241
+ return ev;
1242
+ }
1243
+ case "SubagentStart": {
1244
+ const ev = {
1245
+ ...base,
1246
+ ...typeof input.agent_id === "string" ? { agentId: input.agent_id } : {},
1247
+ ...typeof input.agent_type === "string" ? { agentType: input.agent_type } : {}
1248
+ };
1249
+ return ev;
1250
+ }
1251
+ case "SubagentStop": {
1252
+ const ev = {
1253
+ ...base,
1254
+ ...typeof input.agent_id === "string" ? { agentId: input.agent_id } : {},
1255
+ ...typeof input.agent_type === "string" ? { agentType: input.agent_type } : {},
1256
+ ...typeof input.agent_transcript_path === "string" ? { agentTranscriptPath: input.agent_transcript_path } : {},
1257
+ ...typeof input.last_assistant_message === "string" ? { lastAssistantMessage: input.last_assistant_message } : {},
1258
+ ...typeof input.stop_hook_active === "boolean" ? { stopHookActive: input.stop_hook_active } : {}
1259
+ };
1260
+ return ev;
1261
+ }
1262
+ default: {
1263
+ const _never = event;
1264
+ throw new Error(`unsupported claude-code hook event: ${String(_never)}`);
1265
+ }
1266
+ }
1267
+ }
1268
+ // ── Runtime: normalized response → Claude native hook reply ──────────────
1269
+ formatReply(event, response) {
1270
+ const hookEventName = event;
1271
+ const decision = response.decision ?? "allow";
1272
+ if (event === "PermissionRequest") {
1273
+ if (response.decision === "deny") {
1274
+ return this.stdout({
1275
+ hookSpecificOutput: {
1276
+ hookEventName,
1277
+ decision: {
1278
+ behavior: "deny",
1279
+ message: response.reason ?? "Blocked by hook"
1280
+ }
1281
+ }
1282
+ });
1283
+ }
1284
+ if (response.decision === "allow" || response.decision === "modify" && response.updatedInput) {
1285
+ return this.stdout({
1286
+ hookSpecificOutput: {
1287
+ hookEventName,
1288
+ decision: {
1289
+ behavior: "allow",
1290
+ ...response.updatedInput ? { updatedInput: response.updatedInput } : {}
1291
+ }
1292
+ }
1293
+ });
1294
+ }
1295
+ return { exitCode: 0 };
1296
+ }
1297
+ if (event === "PostToolUseFailure" || event === "SubagentStart") {
1298
+ const context = decision === "context" ? response.additionalContext : decision === "deny" ? response.reason ?? response.additionalContext : void 0;
1299
+ if (context) {
1300
+ return this.stdout({
1301
+ hookSpecificOutput: { hookEventName, additionalContext: context }
1302
+ });
1303
+ }
1304
+ return { exitCode: 0 };
1305
+ }
1306
+ if (decision === "deny") {
1307
+ if (event === "Stop" || event === "SubagentStop" || event === "UserPromptSubmit" || event === "PostToolUse") {
1308
+ return this.stdout({
1309
+ decision: "block",
1310
+ reason: response.reason ?? "Blocked by hook"
1311
+ });
1312
+ }
1313
+ return this.stdout({
1314
+ hookSpecificOutput: {
1315
+ hookEventName,
1316
+ permissionDecision: "deny",
1317
+ permissionDecisionReason: response.reason ?? "Blocked by hook"
1318
+ }
1319
+ });
1320
+ }
1321
+ if (decision === "ask") {
1322
+ return this.stdout({
1323
+ hookSpecificOutput: {
1324
+ hookEventName,
1325
+ permissionDecision: "ask",
1326
+ permissionDecisionReason: response.reason ?? "Confirmation required by hook"
1327
+ }
1328
+ });
1329
+ }
1330
+ if (decision === "modify") {
1331
+ if (event === "PreToolUse" && response.updatedInput) {
1332
+ return this.stdout({
1333
+ hookSpecificOutput: { hookEventName, updatedInput: response.updatedInput }
1334
+ });
1335
+ }
1336
+ }
1337
+ if (decision === "context" && response.additionalContext) {
1338
+ return this.stdout({
1339
+ hookSpecificOutput: { hookEventName, additionalContext: response.additionalContext }
1340
+ });
1341
+ }
1342
+ return { exitCode: 0 };
1343
+ }
1344
+ stdout(payload) {
1345
+ return { exitCode: 0, stdout: JSON.stringify(payload) };
1346
+ }
1347
+ };
1348
+ var SENSITIVE_KEY_RULES = [
1349
+ { re: /^permissions(\.|$)/, label: "permissions" },
1350
+ { re: /^allowedTools(\.|$)/, label: "allowedTools" },
1351
+ { re: /^disallowedTools(\.|$)/, label: "disallowedTools" },
1352
+ { re: /^apiKey/i, label: "apiKey*" },
1353
+ { re: /^awsAuthRefresh$/, label: "awsAuthRefresh" },
1354
+ { re: /^awsCredentialExport$/, label: "awsCredentialExport" },
1355
+ { re: /^forceLoginMethod$/, label: "forceLoginMethod" },
1356
+ { re: /^forceLoginOrgUUID$/, label: "forceLoginOrgUUID" },
1357
+ { re: /^otelHeadersHelper$/, label: "otelHeadersHelper" },
1358
+ { re: /^env\.ANTHROPIC_/, label: "env.ANTHROPIC_*" },
1359
+ { re: /^env\.AWS_/, label: "env.AWS_*" },
1360
+ { re: /^env\.[^.]*_PROXY(\.|$)/i, label: "env.*_PROXY" },
1361
+ { re: /^env\.[^.]*TOKEN/i, label: "env.*TOKEN*" },
1362
+ { re: /^env\.[^.]*KEY/i, label: "env.*KEY*" },
1363
+ { re: /^env\.[^.]*SECRET/i, label: "env.*SECRET*" }
1364
+ ];
1365
+ function claudeSensitiveKeyViolation(key) {
1366
+ for (const rule of SENSITIVE_KEY_RULES) {
1367
+ if (rule.re.test(key)) return rule.label;
1368
+ }
1369
+ return null;
1370
+ }
1371
+ function readJsonLeaf(root, segments) {
1372
+ let node = root;
1373
+ for (let i = 0; i < segments.length - 1; i++) {
1374
+ const next = node[segments[i]];
1375
+ if (next === void 0) return { kind: "absent" };
1376
+ if (next === null || typeof next !== "object" || Array.isArray(next)) {
1377
+ return { kind: "blocked", atPath: segments.slice(0, i + 1).join(".") };
1378
+ }
1379
+ node = next;
1380
+ }
1381
+ const leaf = node[segments[segments.length - 1]];
1382
+ if (leaf === void 0) return { kind: "absent" };
1383
+ return { kind: "present", value: leaf };
1384
+ }
1385
+ function writeJsonLeaf(root, segments, value) {
1386
+ let node = root;
1387
+ for (let i = 0; i < segments.length - 1; i++) {
1388
+ const seg = segments[i];
1389
+ const next = node[seg];
1390
+ if (next === void 0) {
1391
+ const created = {};
1392
+ node[seg] = created;
1393
+ node = created;
1394
+ } else {
1395
+ node = next;
1396
+ }
1397
+ }
1398
+ node[segments[segments.length - 1]] = value;
1399
+ }
1400
+ function deleteJsonLeaf(root, segments) {
1401
+ let node = root;
1402
+ for (let i = 0; i < segments.length - 1; i++) {
1403
+ const next = node[segments[i]];
1404
+ if (next === null || typeof next !== "object" || Array.isArray(next)) return;
1405
+ node = next;
1406
+ }
1407
+ delete node[segments[segments.length - 1]];
1408
+ }
1409
+ function claudeEnvToken(name) {
1410
+ return `\${${name}}`;
1411
+ }
1412
+ function normalizeSessionSource(source) {
1413
+ switch (source) {
1414
+ case "compact":
1415
+ return "compact";
1416
+ case "resume":
1417
+ return "resume";
1418
+ case "clear":
1419
+ return "clear";
1420
+ default:
1421
+ return "startup";
1422
+ }
1423
+ }
1424
+ var adapter = new ClaudeCodeAdapter();
1425
+ var claude_code_default = adapter;
1426
+ export {
1427
+ ClaudeCodeAdapter,
1428
+ adapter,
1429
+ claudeSensitiveKeyViolation,
1430
+ claude_code_default as default
1431
+ };
1432
+ //# sourceMappingURL=claude-code-4275HQU4.js.map