@ansible-flow/node 0.0.0-stage → 0.85.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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -2
  3. package/dist/ai/agents.d.ts +34 -0
  4. package/dist/ai/agents.js +16 -0
  5. package/dist/ai/agents.js.map +7 -0
  6. package/dist/ai/hostConfig.d.ts +44 -0
  7. package/dist/ai/hostConfig.js +120 -0
  8. package/dist/ai/hostConfig.js.map +7 -0
  9. package/dist/ai/hosts.d.ts +28 -0
  10. package/dist/ai/hosts.js +9 -0
  11. package/dist/ai/hosts.js.map +7 -0
  12. package/dist/ai/writers.d.ts +124 -0
  13. package/dist/ai/writers.js +75 -0
  14. package/dist/ai/writers.js.map +7 -0
  15. package/dist/autoLayout.d.ts +38 -0
  16. package/dist/autoLayout.js +145 -0
  17. package/dist/autoLayout.js.map +7 -0
  18. package/dist/awx/awxWorkflow.d.ts +47 -0
  19. package/dist/awx/awxWorkflow.js +17 -0
  20. package/dist/awx/awxWorkflow.js.map +7 -0
  21. package/dist/awx/client.d.ts +77 -0
  22. package/dist/awx/client.js +22 -0
  23. package/dist/awx/client.js.map +7 -0
  24. package/dist/catalog/bindingCheck.d.ts +4 -0
  25. package/dist/catalog/bindingCheck.js +69 -0
  26. package/dist/catalog/bindingCheck.js.map +7 -0
  27. package/dist/catalog/catalogIndex.d.ts +52 -0
  28. package/dist/catalog/catalogIndex.js +13 -0
  29. package/dist/catalog/catalogIndex.js.map +7 -0
  30. package/dist/catalog/catalogWorker.d.ts +17 -0
  31. package/dist/catalog/catalogWorker.js +38 -0
  32. package/dist/catalog/catalogWorker.js.map +7 -0
  33. package/dist/catalog/dumpFiles.d.ts +3 -0
  34. package/dist/catalog/dumpFiles.js +19 -0
  35. package/dist/catalog/dumpFiles.js.map +7 -0
  36. package/dist/chunks/chunk-2MZQHIXP.js +614 -0
  37. package/dist/chunks/chunk-2MZQHIXP.js.map +7 -0
  38. package/dist/chunks/chunk-4FHPTGI4.js +52 -0
  39. package/dist/chunks/chunk-4FHPTGI4.js.map +7 -0
  40. package/dist/chunks/chunk-A2NMQGQE.js +189 -0
  41. package/dist/chunks/chunk-A2NMQGQE.js.map +7 -0
  42. package/dist/chunks/chunk-EPJKJBNW.js +181 -0
  43. package/dist/chunks/chunk-EPJKJBNW.js.map +7 -0
  44. package/dist/chunks/chunk-FG53MLUX.js +136 -0
  45. package/dist/chunks/chunk-FG53MLUX.js.map +7 -0
  46. package/dist/chunks/chunk-GAL5LW6Y.js +269 -0
  47. package/dist/chunks/chunk-GAL5LW6Y.js.map +7 -0
  48. package/dist/chunks/chunk-HPVT6I36.js +371 -0
  49. package/dist/chunks/chunk-HPVT6I36.js.map +7 -0
  50. package/dist/chunks/chunk-I6FIOP7G.js +48 -0
  51. package/dist/chunks/chunk-I6FIOP7G.js.map +7 -0
  52. package/dist/chunks/chunk-IIFP6UQR.js +44 -0
  53. package/dist/chunks/chunk-IIFP6UQR.js.map +7 -0
  54. package/dist/chunks/chunk-JDPN3LTT.js +18 -0
  55. package/dist/chunks/chunk-JDPN3LTT.js.map +7 -0
  56. package/dist/chunks/chunk-LGUG2Q7Z.js +191 -0
  57. package/dist/chunks/chunk-LGUG2Q7Z.js.map +7 -0
  58. package/dist/chunks/chunk-NTZRNEZM.js +25 -0
  59. package/dist/chunks/chunk-NTZRNEZM.js.map +7 -0
  60. package/dist/chunks/chunk-RETJPSKL.js +123 -0
  61. package/dist/chunks/chunk-RETJPSKL.js.map +7 -0
  62. package/dist/chunks/chunk-SSZNBJZQ.js +52 -0
  63. package/dist/chunks/chunk-SSZNBJZQ.js.map +7 -0
  64. package/dist/chunks/chunk-SUV3VHKR.js +52 -0
  65. package/dist/chunks/chunk-SUV3VHKR.js.map +7 -0
  66. package/dist/chunks/chunk-THNSGAKN.js +133 -0
  67. package/dist/chunks/chunk-THNSGAKN.js.map +7 -0
  68. package/dist/chunks/chunk-UNCR5JOZ.js +85 -0
  69. package/dist/chunks/chunk-UNCR5JOZ.js.map +7 -0
  70. package/dist/env/probeClient.d.ts +77 -0
  71. package/dist/env/probeClient.js +244 -0
  72. package/dist/env/probeClient.js.map +7 -0
  73. package/dist/env/userSettings.d.ts +33 -0
  74. package/dist/env/userSettings.js +101 -0
  75. package/dist/env/userSettings.js.map +7 -0
  76. package/dist/run/audit.d.ts +2 -0
  77. package/dist/run/audit.js +10 -0
  78. package/dist/run/audit.js.map +7 -0
  79. package/dist/run/exec.d.ts +14 -0
  80. package/dist/run/exec.js +7 -0
  81. package/dist/run/exec.js.map +7 -0
  82. package/dist/run/facts.d.ts +39 -0
  83. package/dist/run/facts.js +96 -0
  84. package/dist/run/facts.js.map +7 -0
  85. package/dist/run/ids.d.ts +2 -0
  86. package/dist/run/ids.js +8 -0
  87. package/dist/run/ids.js.map +7 -0
  88. package/dist/run/pastRuns.d.ts +9 -0
  89. package/dist/run/pastRuns.js +13 -0
  90. package/dist/run/pastRuns.js.map +7 -0
  91. package/dist/run/runFlow.d.ts +78 -0
  92. package/dist/run/runFlow.js +20 -0
  93. package/dist/run/runFlow.js.map +7 -0
  94. package/dist/run/segments.d.ts +43 -0
  95. package/dist/run/segments.js +157 -0
  96. package/dist/run/segments.js.map +7 -0
  97. package/dist/run/stop.d.ts +3 -0
  98. package/dist/run/stop.js +7 -0
  99. package/dist/run/stop.js.map +7 -0
  100. package/dist/run/storage.d.ts +16 -0
  101. package/dist/run/storage.js +15 -0
  102. package/dist/run/storage.js.map +7 -0
  103. package/dist/run/tail.d.ts +5 -0
  104. package/dist/run/tail.js +7 -0
  105. package/dist/run/tail.js.map +7 -0
  106. package/dist/runner/client.d.ts +149 -0
  107. package/dist/runner/client.js +168 -0
  108. package/dist/runner/client.js.map +7 -0
  109. package/dist/runner/devBundle.d.ts +14 -0
  110. package/dist/runner/devBundle.js +78 -0
  111. package/dist/runner/devBundle.js.map +7 -0
  112. package/dist/scenario/cli.d.ts +39 -0
  113. package/dist/scenario/cli.js +125 -0
  114. package/dist/scenario/cli.js.map +7 -0
  115. package/dist/scenario/runner.d.ts +50 -0
  116. package/dist/scenario/runner.js +185 -0
  117. package/dist/scenario/runner.js.map +7 -0
  118. package/dist/sharedRefs.d.ts +22 -0
  119. package/dist/sharedRefs.js +114 -0
  120. package/dist/sharedRefs.js.map +7 -0
  121. package/dist/ssh/exec.d.ts +39 -0
  122. package/dist/ssh/exec.js +13 -0
  123. package/dist/ssh/exec.js.map +7 -0
  124. package/dist/ssh/remote.d.ts +40 -0
  125. package/dist/ssh/remote.js +25 -0
  126. package/dist/ssh/remote.js.map +7 -0
  127. package/dist/ssh/sshRun.d.ts +1 -0
  128. package/dist/ssh/sshRun.js +24 -0
  129. package/dist/ssh/sshRun.js.map +7 -0
  130. package/dist/ssh/sync.d.ts +22 -0
  131. package/dist/ssh/sync.js +12 -0
  132. package/dist/ssh/sync.js.map +7 -0
  133. package/dist/timeouts.d.ts +38 -0
  134. package/dist/timeouts.js +7 -0
  135. package/dist/timeouts.js.map +7 -0
  136. package/package.json +35 -4
@@ -0,0 +1,614 @@
1
+ // src/node/ai/writers.ts
2
+ import { execFile } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { promises as fs } from "node:fs";
5
+ import { dirname, isAbsolute, join, relative } from "node:path";
6
+ import {
7
+ applyEdits,
8
+ findNodeAtLocation,
9
+ modify,
10
+ parse as parseJsonc,
11
+ parseTree
12
+ } from "jsonc-parser";
13
+ import { TOOL_SPECS } from "@ansible-flow/core/ai/specs";
14
+ var AGENTS = [
15
+ "claude",
16
+ "codex",
17
+ "gemini",
18
+ "opencode",
19
+ "zcode",
20
+ "copilot",
21
+ "cursor",
22
+ // AFD-173: the agents built into VS Code forks, by the files they read (AFD-172 probes).
23
+ "kiro",
24
+ "antigravity",
25
+ "devin"
26
+ ];
27
+ var SERVER_NAME = "ansible-flow";
28
+ var SKILL_NAME = "ansible-flow";
29
+ var MANIFEST = ".afd-skill.json";
30
+ var BEGIN = `# >>> ${SERVER_NAME} (managed by Ansible Flow Designer; Remove AI Assistant Setup deletes it)`;
31
+ var END = `# <<< ${SERVER_NAME}`;
32
+ var hash = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
33
+ async function readText(path) {
34
+ try {
35
+ return await fs.readFile(path, "utf8");
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ async function exists(path) {
41
+ try {
42
+ await fs.stat(path);
43
+ return true;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ async function backup(path) {
49
+ const copy = `${path}.afd-backup`;
50
+ if (await exists(path) && !await exists(copy)) await fs.copyFile(path, copy);
51
+ }
52
+ var SKILL_HOME = {
53
+ claude: { repo: ".claude", user: ".claude" },
54
+ codex: { repo: ".agents", user: ".agents" },
55
+ gemini: { repo: ".agents", user: ".gemini" },
56
+ opencode: { repo: ".agents", user: ".agents" },
57
+ zcode: { repo: ".agents", user: ".agents" },
58
+ copilot: { repo: ".github", user: ".copilot" },
59
+ cursor: { repo: ".claude", user: ".claude" },
60
+ kiro: { repo: ".kiro", user: ".kiro" },
61
+ // Antigravity's customization roots: .agents in a repository, ~/.gemini/config for the user.
62
+ antigravity: { repo: ".agents", user: ".gemini/config" },
63
+ // Devin reads ~/.codeium/windsurf/skills and ~/.agents/skills, not ~/.devin/skills.
64
+ devin: { repo: ".agents", user: ".codeium/windsurf" }
65
+ };
66
+ function skillDirs(agent, scope, env) {
67
+ const base = scope === "repo" ? env.repo : env.home;
68
+ if (!base) return [];
69
+ return [join(base, SKILL_HOME[agent][scope], "skills", SKILL_NAME)];
70
+ }
71
+ async function readManifest(dir) {
72
+ const raw = await readText(join(dir, MANIFEST));
73
+ if (raw === void 0) return void 0;
74
+ let data;
75
+ try {
76
+ data = JSON.parse(raw);
77
+ } catch {
78
+ return void 0;
79
+ }
80
+ if (!data || typeof data !== "object") return void 0;
81
+ const { version, files } = data;
82
+ if (!files || typeof files !== "object" || Array.isArray(files)) return void 0;
83
+ const kept = {};
84
+ for (const [name, h] of Object.entries(files))
85
+ if (typeof h === "string" && SKILL_FILE.test(name) && name !== MANIFEST) kept[name] = h;
86
+ return { version: typeof version === "string" ? version : "", files: kept };
87
+ }
88
+ var SKILL_FILE = /^(?!.*(?:^|\/)\.\.?(?:\/|$))[\w.-]+(?:\/[\w.-]+)*$/;
89
+ var insideDir = (root, path) => {
90
+ const rel = relative(root, path);
91
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
92
+ };
93
+ async function nearestReal(path) {
94
+ for (let p = path; ; p = dirname(p)) {
95
+ try {
96
+ return await fs.realpath(p);
97
+ } catch {
98
+ if (dirname(p) === p) return p;
99
+ }
100
+ }
101
+ }
102
+ async function skillPath(dir, name) {
103
+ if (!SKILL_FILE.test(name)) return void 0;
104
+ const path = join(dir, ...name.split("/"));
105
+ const [root, parent] = await Promise.all([nearestReal(dir), nearestReal(dirname(path))]);
106
+ return insideDir(root, parent) ? path : void 0;
107
+ }
108
+ async function skillState(dir) {
109
+ if (!await exists(dir)) return void 0;
110
+ const link = await fs.lstat(dir).then(
111
+ (s) => s.isSymbolicLink(),
112
+ () => false
113
+ );
114
+ const manifest = link ? void 0 : await readManifest(dir);
115
+ if (!manifest) return { dir, foreign: true, edited: [] };
116
+ const edited = [];
117
+ for (const [name, h] of Object.entries(manifest.files)) {
118
+ const path = await skillPath(dir, name);
119
+ const text = path && await readText(path);
120
+ if (text !== void 0 && hash(text) !== h) edited.push(name);
121
+ }
122
+ return { dir, foreign: false, version: manifest.version, edited };
123
+ }
124
+ function newerVersion(a, b) {
125
+ const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
126
+ const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
127
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
128
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
129
+ if (d !== 0) return d > 0;
130
+ }
131
+ return false;
132
+ }
133
+ async function planSkill(dir, skill) {
134
+ const state = await skillState(dir);
135
+ if (!state) return "create";
136
+ if (state.foreign) return "skip";
137
+ if (state.version && newerVersion(state.version, skill.version)) return "unchanged";
138
+ for (const [name, text] of Object.entries(skill.files)) {
139
+ const path = await skillPath(dir, name);
140
+ if (!path || await readText(path) !== text) return "update";
141
+ }
142
+ return state.version === skill.version ? "unchanged" : "update";
143
+ }
144
+ async function writeSkill(dir, skill) {
145
+ const before = await skillState(dir);
146
+ if (before?.foreign) throw new Error(`${dir} exists and is not ours`);
147
+ const old = before ? await readManifest(dir) : void 0;
148
+ for (const name of Object.keys(old?.files ?? {})) {
149
+ if (name in skill.files) continue;
150
+ const path = await skillPath(dir, name);
151
+ if (path) await fs.rm(path, { force: true });
152
+ }
153
+ for (const [name, text] of Object.entries(skill.files)) {
154
+ const path = await skillPath(dir, name);
155
+ if (!path) throw new Error(`${name} would be written outside ${dir}`);
156
+ await fs.mkdir(dirname(path), { recursive: true });
157
+ await fs.writeFile(path, text, "utf8");
158
+ }
159
+ const manifest = {
160
+ version: skill.version,
161
+ files: Object.fromEntries(Object.entries(skill.files).map(([n, t]) => [n, hash(t)]))
162
+ };
163
+ await fs.writeFile(join(dir, MANIFEST), `${JSON.stringify(manifest, null, 2)}
164
+ `, "utf8");
165
+ }
166
+ async function removeSkill(dir) {
167
+ const state = await skillState(dir);
168
+ if (!state || state.foreign) return false;
169
+ const manifest = await readManifest(dir);
170
+ const names = Object.keys(manifest?.files ?? {});
171
+ for (const name of names) {
172
+ const path = await skillPath(dir, name);
173
+ if (path) await fs.rm(path, { force: true });
174
+ }
175
+ await fs.rm(join(dir, MANIFEST), { force: true });
176
+ const prune = async (d) => {
177
+ if (!insideDir(dir, d)) return;
178
+ try {
179
+ if ((await fs.readdir(d)).length === 0) {
180
+ await fs.rmdir(d);
181
+ if (d !== dir) await prune(dirname(d));
182
+ }
183
+ } catch {
184
+ }
185
+ };
186
+ for (const name of names) await prune(dirname(join(dir, ...name.split("/"))));
187
+ await prune(dir);
188
+ return true;
189
+ }
190
+ var codexConfig = (env) => join(env.home, ".codex", "config.toml");
191
+ var tomlString = (s) => JSON.stringify(s);
192
+ function codexBlock(launch) {
193
+ const lines = [
194
+ BEGIN,
195
+ `[mcp_servers.${SERVER_NAME}]`,
196
+ `command = ${tomlString(launch.command)}`,
197
+ `args = [${launch.args.map(tomlString).join(", ")}]`
198
+ ];
199
+ if (launch.env && Object.keys(launch.env).length)
200
+ lines.push(
201
+ `env = { ${Object.entries(launch.env).map(([k, v]) => `${k} = ${tomlString(v)}`).join(", ")} }`
202
+ );
203
+ lines.push(END);
204
+ return lines.join("\n");
205
+ }
206
+ function splitBlock(text) {
207
+ const start = text.indexOf(BEGIN);
208
+ const end = start < 0 ? -1 : text.indexOf(END, start);
209
+ if (start < 0 || end < 0) return { before: text, after: "" };
210
+ return {
211
+ before: text.slice(0, start),
212
+ block: text.slice(start, end + END.length),
213
+ after: text.slice(end + END.length)
214
+ };
215
+ }
216
+ var handWritten = (text) => new RegExp(`^\\s*\\[mcp_servers\\.(${SERVER_NAME}|"${SERVER_NAME}")\\]`, "m").test(
217
+ splitBlock(text).before + splitBlock(text).after
218
+ );
219
+ async function planCodex(env, launch) {
220
+ const target = codexConfig(env);
221
+ const text = await readText(target) ?? "";
222
+ const item = { agent: "codex", what: "mcp", target };
223
+ if (handWritten(text))
224
+ return {
225
+ ...item,
226
+ action: "skip",
227
+ note: `[mcp_servers.${SERVER_NAME}] is written by hand there; left as it is`
228
+ };
229
+ const { block } = splitBlock(text);
230
+ if (block === void 0) return { ...item, action: "create" };
231
+ return { ...item, action: block === codexBlock(launch) ? "unchanged" : "update" };
232
+ }
233
+ async function writeCodex(env, launch) {
234
+ const target = codexConfig(env);
235
+ const text = await readText(target) ?? "";
236
+ if (handWritten(text)) return;
237
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
238
+ const block = codexBlock(launch).replaceAll("\n", eol);
239
+ const parts = splitBlock(text);
240
+ const next = parts.block === void 0 ? `${text}${text && !text.endsWith("\n") ? eol : ""}${text ? eol : ""}${block}${eol}` : `${parts.before}${block}${parts.after}`;
241
+ if (next === text) return;
242
+ await fs.mkdir(dirname(target), { recursive: true });
243
+ await backup(target);
244
+ await fs.writeFile(target, next, "utf8");
245
+ }
246
+ async function removeCodex(env) {
247
+ const target = codexConfig(env);
248
+ const text = await readText(target);
249
+ if (text === void 0) return false;
250
+ const parts = splitBlock(text);
251
+ if (parts.block === void 0) return false;
252
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
253
+ const after = parts.after.startsWith(eol) ? parts.after.slice(eol.length) : parts.after;
254
+ await fs.writeFile(
255
+ target,
256
+ `${parts.before.replace(/(\r?\n)+$/, eol)}${after}`.replace(/^\r?\n$/, ""),
257
+ "utf8"
258
+ );
259
+ return true;
260
+ }
261
+ var stdio = (l) => ({
262
+ command: l.command,
263
+ args: l.args,
264
+ ...l.env ? { env: l.env } : {}
265
+ });
266
+ async function opencodeConfig(env) {
267
+ const dir = join(env.home, ".config", "opencode");
268
+ const jsonc = join(dir, "opencode.jsonc");
269
+ return await exists(jsonc) ? jsonc : join(dir, "opencode.json");
270
+ }
271
+ var STDIO_KEYS = ["command", "args", "env"];
272
+ var READ_ONLY_TOOLS = TOOL_SPECS.filter((s) => s.readOnly).map((s) => s.name);
273
+ var CURSOR_WORKSPACE = "${workspaceFolder}";
274
+ var JSON_MCP = {
275
+ gemini: {
276
+ file: async (env) => join(env.home, ".gemini", "settings.json"),
277
+ path: ["mcpServers", SERVER_NAME],
278
+ entry: (l) => ({
279
+ ...stdio(l),
280
+ description: "Ansible Flow Designer: read and edit *.flow.yaml flows by key"
281
+ }),
282
+ managed: [...STDIO_KEYS, "description"]
283
+ },
284
+ opencode: {
285
+ file: opencodeConfig,
286
+ path: ["mcp", SERVER_NAME],
287
+ entry: (l) => ({
288
+ type: "local",
289
+ command: [l.command, ...l.args],
290
+ ...l.env ? { environment: l.env } : {},
291
+ enabled: true
292
+ }),
293
+ managed: ["type", "command", "environment"]
294
+ },
295
+ // ZCode reads ~/.agents/mcp.json (besides ~/.zcode/config.json): a file other agents share.
296
+ zcode: {
297
+ file: async (env) => join(env.home, ".agents", "mcp.json"),
298
+ path: ["mcpServers", SERVER_NAME],
299
+ entry: stdio,
300
+ managed: STDIO_KEYS
301
+ },
302
+ copilot: {
303
+ file: async (env) => join(env.home, ".copilot", "mcp-config.json"),
304
+ path: ["mcpServers", SERVER_NAME],
305
+ entry: (l) => ({ type: "local", ...stdio(l), tools: ["*"] }),
306
+ managed: ["type", ...STDIO_KEYS]
307
+ },
308
+ cursor: {
309
+ file: async (env) => join(env.home, ".cursor", "mcp.json"),
310
+ path: ["mcpServers", SERVER_NAME],
311
+ // AFD-173: Cursor starts global servers in the home folder and sends no roots; the workspace
312
+ // comes as a variable (the server leaves it out when Cursor does not resolve it).
313
+ entry: (l) => ({
314
+ ...stdio(l),
315
+ env: { ...l.env ?? {}, AFD_MCP_ROOTS: CURSOR_WORKSPACE }
316
+ }),
317
+ managed: STDIO_KEYS
318
+ },
319
+ kiro: {
320
+ file: async (env) => join(env.home, ".kiro", "settings", "mcp.json"),
321
+ path: ["mcpServers", SERVER_NAME],
322
+ // AFD-174: Kiro asks before every call; the tools that only read are approved when the entry
323
+ // is first written. Not managed: what the user approves or takes back later stays.
324
+ entry: (l) => ({ ...stdio(l), autoApprove: READ_ONLY_TOOLS }),
325
+ managed: STDIO_KEYS
326
+ },
327
+ // Antigravity IDE's global customization root; its own plugin folders do not load MCP servers.
328
+ antigravity: {
329
+ file: async (env) => join(env.home, ".gemini", "config", "mcp_config.json"),
330
+ path: ["mcpServers", SERVER_NAME],
331
+ entry: stdio,
332
+ managed: STDIO_KEYS
333
+ },
334
+ devin: {
335
+ file: async (env) => devinMcpFile(env),
336
+ path: ["mcpServers", SERVER_NAME],
337
+ entry: stdio,
338
+ managed: STDIO_KEYS
339
+ }
340
+ };
341
+ function devinMcpFile(env) {
342
+ const platform = env.platform ?? process.platform;
343
+ return platform === "win32" ? join(env.home, "AppData", "Roaming", "devin", "mcp_config.json") : join(env.xdgConfig || join(env.home, ".config"), "devin", "mcp_config.json");
344
+ }
345
+ async function mcpTarget(agent, env, launch) {
346
+ if (agent === "claude") return launch ? claudeCommandLine(launch) : "claude mcp";
347
+ if (agent === "codex") return codexConfig(env);
348
+ return JSON_MCP[agent].file(env);
349
+ }
350
+ function formatting(text) {
351
+ const indent = /\n([ \t]+)"/.exec(text)?.[1] ?? " ";
352
+ return {
353
+ insertSpaces: !indent.includes(" "),
354
+ tabSize: indent.includes(" ") ? 1 : indent.length,
355
+ eol: text.includes("\r\n") ? "\r\n" : "\n"
356
+ };
357
+ }
358
+ function current(text, path) {
359
+ const tree = parseTree(text);
360
+ const node = tree && findNodeAtLocation(tree, path);
361
+ return node ? parseJsonc(text.slice(node.offset, node.offset + node.length)) : void 0;
362
+ }
363
+ function broken(text) {
364
+ const errors = [];
365
+ parseJsonc(text, errors, { allowTrailingComma: true });
366
+ return errors.length > 0;
367
+ }
368
+ async function planJson(agent, env, launch) {
369
+ const spec = JSON_MCP[agent];
370
+ const target = await spec.file(env);
371
+ const text = await readText(target) ?? "";
372
+ const item = { agent, what: "mcp", target };
373
+ if (text.trim() && broken(text))
374
+ return { ...item, action: "skip", note: "it does not parse as JSON; left as it is" };
375
+ const now = text.trim() ? current(text, spec.path) : void 0;
376
+ if (now === void 0) return { ...item, action: "create" };
377
+ const want = spec.entry(launch);
378
+ const have = now && typeof now === "object" ? now : {};
379
+ const same = spec.managed.every((k) => JSON.stringify(have[k]) === JSON.stringify(want[k]));
380
+ return { ...item, action: same ? "unchanged" : "update" };
381
+ }
382
+ async function writeJson(agent, env, launch) {
383
+ const spec = JSON_MCP[agent];
384
+ const target = await spec.file(env);
385
+ const text = await readText(target) ?? "";
386
+ if (text.trim() && broken(text)) return;
387
+ const base = text.trim() ? text : "{}\n";
388
+ const want = spec.entry(launch);
389
+ const now = current(base, spec.path);
390
+ let next = base;
391
+ if (now === void 0 || !now || typeof now !== "object" || Array.isArray(now))
392
+ next = applyEdits(next, modify(next, spec.path, want, { formattingOptions: formatting(next) }));
393
+ else
394
+ for (const key of spec.managed)
395
+ next = applyEdits(
396
+ next,
397
+ modify(next, [...spec.path, key], want[key], { formattingOptions: formatting(next) })
398
+ );
399
+ if (next === text) return;
400
+ await fs.mkdir(dirname(target), { recursive: true });
401
+ await backup(target);
402
+ await fs.writeFile(target, next, "utf8");
403
+ }
404
+ async function removeJson(agent, env) {
405
+ const spec = JSON_MCP[agent];
406
+ const target = await spec.file(env);
407
+ const text = await readText(target);
408
+ if (!text || broken(text) || current(text, spec.path) === void 0) return false;
409
+ await fs.writeFile(
410
+ target,
411
+ applyEdits(text, modify(text, spec.path, void 0, { formattingOptions: formatting(text) })),
412
+ "utf8"
413
+ );
414
+ return true;
415
+ }
416
+ function claudeAddArgs(launch) {
417
+ const env = Object.entries(launch.env ?? {}).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
418
+ return ["mcp", "add", "-s", "user", SERVER_NAME, ...env, "--", launch.command, ...launch.args];
419
+ }
420
+ function claudeCommandLine(launch) {
421
+ const q = (a) => /[\s"]/.test(a) ? `"${a.replaceAll('"', '\\"')}"` : a;
422
+ return ["claude", ...claudeAddArgs(launch)].map(q).join(" ");
423
+ }
424
+ async function planClaude(env, launch) {
425
+ const item = {
426
+ agent: "claude",
427
+ what: "mcp",
428
+ target: claudeCommandLine(launch)
429
+ };
430
+ if (!env.claude)
431
+ return {
432
+ ...item,
433
+ action: "skip",
434
+ note: "claude is not on PATH: run this command once it is installed"
435
+ };
436
+ const got = await env.claude(["mcp", "get", SERVER_NAME]);
437
+ if (got.code !== 0) return { ...item, action: "create" };
438
+ const same = got.out.includes(launch.command) && launch.args.every((a) => got.out.includes(a));
439
+ return { ...item, action: same ? "unchanged" : "update" };
440
+ }
441
+ async function writeClaude(env, launch) {
442
+ if (!env.claude) return;
443
+ if ((await env.claude(["mcp", "get", SERVER_NAME])).code === 0)
444
+ await env.claude(["mcp", "remove", SERVER_NAME, "-s", "user"]);
445
+ const r = await env.claude(claudeAddArgs(launch));
446
+ if (r.code !== 0) throw new Error(`claude mcp add failed: ${r.out.trim()}`);
447
+ }
448
+ async function removeClaude(env) {
449
+ if (!env.claude || (await env.claude(["mcp", "get", SERVER_NAME])).code !== 0) return false;
450
+ return (await env.claude(["mcp", "remove", SERVER_NAME, "-s", "user"])).code === 0;
451
+ }
452
+ function claudeRunner(path) {
453
+ const shim = /\.(cmd|bat)$/i.test(path);
454
+ return (args) => new Promise((done) => {
455
+ const quoted = shim ? args.map((a) => `"${a.replaceAll('"', '""')}"`) : args;
456
+ execFile(
457
+ shim ? `"${path}"` : path,
458
+ quoted,
459
+ { timeout: 3e4, windowsHide: true, shell: shim },
460
+ (err, stdout, stderr) => done({
461
+ code: err ? err.code ?? 1 : 0,
462
+ out: `${stdout}${stderr}`
463
+ })
464
+ );
465
+ });
466
+ }
467
+ function planMcp(agent, env, launch) {
468
+ return agent === "claude" ? planClaude(env, launch) : agent === "codex" ? planCodex(env, launch) : planJson(agent, env, launch);
469
+ }
470
+ async function planSetup(agents, scope, env, launch, skill) {
471
+ const items = [];
472
+ const seen = /* @__PURE__ */ new Set();
473
+ for (const agent of agents) {
474
+ for (const dir of skillDirs(agent, scope, env)) {
475
+ if (seen.has(dir)) continue;
476
+ seen.add(dir);
477
+ const action = await planSkill(dir, skill);
478
+ const state = action === "update" ? await skillState(dir) : void 0;
479
+ items.push({
480
+ agent,
481
+ what: "skill",
482
+ target: dir,
483
+ action,
484
+ ...action === "skip" ? { note: "a folder of that name exists and is not ours; left as it is" } : {},
485
+ ...state?.edited.length ? { note: `you changed ${state.edited.join(", ")}; it will be replaced` } : {}
486
+ });
487
+ }
488
+ items.push(await planMcp(agent, env, launch));
489
+ }
490
+ return items;
491
+ }
492
+ async function applySetup(items, env, launch, skill) {
493
+ for (const item of items) {
494
+ if (item.action !== "create" && item.action !== "update") continue;
495
+ if (item.what === "skill") await writeSkill(item.target, skill);
496
+ else if (item.agent === "claude") await writeClaude(env, launch);
497
+ else if (item.agent === "codex") await writeCodex(env, launch);
498
+ else await writeJson(item.agent, env, launch);
499
+ }
500
+ }
501
+ async function removeSetup(agents, env) {
502
+ const removed = [];
503
+ const seen = /* @__PURE__ */ new Set();
504
+ for (const agent of agents) {
505
+ for (const scope of ["repo", "user"])
506
+ for (const dir of skillDirs(agent, scope, env)) {
507
+ if (seen.has(dir)) continue;
508
+ seen.add(dir);
509
+ if (await removeSkill(dir))
510
+ removed.push({ agent, what: "skill", target: dir, action: "remove" });
511
+ }
512
+ const gone = agent === "claude" ? await removeClaude(env) : agent === "codex" ? await removeCodex(env) : await removeJson(agent, env);
513
+ if (gone)
514
+ removed.push({ agent, what: "mcp", target: await mcpTarget(agent, env), action: "remove" });
515
+ }
516
+ return removed;
517
+ }
518
+ var claudeConfig = (env) => join(env.home, ".claude.json");
519
+ var isServer = (a) => typeof a === "string" && /(^|[\\/])mcp\.js$/i.test(a);
520
+ function entryServer(entry) {
521
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return {};
522
+ const e = entry;
523
+ const words = Array.isArray(e.command) ? e.command : [e.command, ...Array.isArray(e.args) ? e.args : []];
524
+ const [command] = words;
525
+ const server = words.slice(1).find(isServer);
526
+ return {
527
+ ...typeof command === "string" ? { command } : {},
528
+ ...server ? { server } : {}
529
+ };
530
+ }
531
+ var parseOr = (text) => {
532
+ try {
533
+ return text === void 0 ? void 0 : JSON.parse(text);
534
+ } catch {
535
+ return void 0;
536
+ }
537
+ };
538
+ async function registration(agent, env) {
539
+ const file = agent === "claude" ? claudeConfig(env) : await mcpTarget(agent, env);
540
+ const text = await readText(file);
541
+ if (!text?.includes(SERVER_NAME)) return void 0;
542
+ if (agent === "codex") {
543
+ if (handWritten(text)) return { agent, file, hand: true };
544
+ const { block } = splitBlock(text);
545
+ if (block === void 0) return void 0;
546
+ const command = parseOr(/^command = (".*")\s*$/m.exec(block)?.[1]);
547
+ const args = parseOr(/^args = (\[.*\])\s*$/m.exec(block)?.[1]);
548
+ return { agent, file, ...entryServer({ command, args }) };
549
+ }
550
+ let entry;
551
+ if (agent === "claude") {
552
+ const data = parseOr(text);
553
+ entry = data?.mcpServers?.[SERVER_NAME];
554
+ } else {
555
+ if (broken(text)) return void 0;
556
+ entry = current(text, JSON_MCP[agent].path);
557
+ }
558
+ return entry === void 0 ? void 0 : { agent, file, ...entryServer(entry) };
559
+ }
560
+ function storageId(server) {
561
+ return /[\\/]globalStorage[\\/]([^\\/]+)[\\/]mcp[\\/]mcp\.js$/i.exec(server)?.[1]?.toLowerCase();
562
+ }
563
+ async function staleRegistrations(agents, env, server) {
564
+ const ours = storageId(server);
565
+ const found = [];
566
+ for (const agent of agents) {
567
+ const r = await registration(agent, env);
568
+ if (!r?.server || r.hand) continue;
569
+ const id = storageId(r.server);
570
+ if (!id) continue;
571
+ if (!await exists(r.server)) found.push({ ...r, server: r.server, why: "missing" });
572
+ else if (ours && id !== ours) found.push({ ...r, server: r.server, why: "elsewhere" });
573
+ }
574
+ return found;
575
+ }
576
+
577
+ export {
578
+ AGENTS,
579
+ SERVER_NAME,
580
+ SKILL_NAME,
581
+ skillDirs,
582
+ skillState,
583
+ newerVersion,
584
+ planSkill,
585
+ writeSkill,
586
+ removeSkill,
587
+ codexConfig,
588
+ codexBlock,
589
+ planCodex,
590
+ writeCodex,
591
+ removeCodex,
592
+ CURSOR_WORKSPACE,
593
+ devinMcpFile,
594
+ mcpTarget,
595
+ planJson,
596
+ writeJson,
597
+ removeJson,
598
+ claudeAddArgs,
599
+ claudeCommandLine,
600
+ planClaude,
601
+ writeClaude,
602
+ removeClaude,
603
+ claudeRunner,
604
+ planMcp,
605
+ planSetup,
606
+ applySetup,
607
+ removeSetup,
608
+ claudeConfig,
609
+ entryServer,
610
+ registration,
611
+ storageId,
612
+ staleRegistrations
613
+ };
614
+ //# sourceMappingURL=chunk-2MZQHIXP.js.map