diffsplain 0.5.0 → 0.6.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 (91) hide show
  1. package/README.md +17 -12
  2. package/dist/assets/{angular-html-CoQle5-T.js → angular-html-vBvPnd6a.js} +1 -1
  3. package/dist/assets/{angular-ts-BB_A1Lpy.js → angular-ts-CDzF1Z6a.js} +1 -1
  4. package/dist/assets/{apl-B7_4_FNq.js → apl-Bm9bxvP7.js} +1 -1
  5. package/dist/assets/{astro-CXVo4VE4.js → astro-RNrNWCYO.js} +1 -1
  6. package/dist/assets/{blade-BVX41dq7.js → blade-On-NBYUv.js} +1 -1
  7. package/dist/assets/{c-Dhyi8Nd2.js → c-hRmhFgD0.js} +1 -1
  8. package/dist/assets/{cobol-CU5oLeZj.js → cobol-CIqc3k2t.js} +1 -1
  9. package/dist/assets/{coffee-D_FU6gC_.js → coffee-Cd5EyvvS.js} +1 -1
  10. package/dist/assets/{cpp-CxoYLSi3.js → cpp-zXFAAp_S.js} +1 -1
  11. package/dist/assets/{crystal-BdgqBN3z.js → crystal-DRYY4cSw.js} +1 -1
  12. package/dist/assets/{css-wLk1vVwJ.js → css-C4op_MsD.js} +1 -1
  13. package/dist/assets/{edge-DMylWoGl.js → edge-d3rJkErf.js} +1 -1
  14. package/dist/assets/{elixir-Djmgto_Z.js → elixir-DPqlJhYM.js} +1 -1
  15. package/dist/assets/{elm-D7B-eBLA.js → elm-DgU37I8a.js} +1 -1
  16. package/dist/assets/{erb-g1sso1GW.js → erb-CZVB_ntC.js} +1 -1
  17. package/dist/assets/{git-rebase-DEAhxutZ.js → git-rebase-hUkltHZG.js} +1 -1
  18. package/dist/assets/{glimmer-js-CHvGFGMp.js → glimmer-js-5XOT9GQ8.js} +1 -1
  19. package/dist/assets/{glimmer-ts-DvhL705p.js → glimmer-ts-DT7D0Kfq.js} +1 -1
  20. package/dist/assets/{glsl-C8mS_t8I.js → glsl-Br4_Eh5O.js} +1 -1
  21. package/dist/assets/{graphql-DW2pnvqi.js → graphql--BmZ_AYP.js} +1 -1
  22. package/dist/assets/{hack-DYc1d3he.js → hack-CMdqGDoA.js} +1 -1
  23. package/dist/assets/{haml-_5OmzH31.js → haml-DLEOHGmg.js} +1 -1
  24. package/dist/assets/{handlebars-rN-CaXUf.js → handlebars-BfUftoV0.js} +1 -1
  25. package/dist/assets/{html-derivative-BovOubry.js → html-derivative-D5cZE6C4.js} +1 -1
  26. package/dist/assets/{html-DQnaJUjl.js → html-uOgx2iYC.js} +1 -1
  27. package/dist/assets/{http-BKTgwOYc.js → http-cW9c66J_.js} +1 -1
  28. package/dist/assets/{hurl-ChS3_IgD.js → hurl-BztURO2n.js} +1 -1
  29. package/dist/assets/{index-BLtRBc2n.css → index-C2p_1iAk.css} +1 -1
  30. package/dist/assets/{index-CEglkSs-.js → index-DZFM4-3L.js} +11 -11
  31. package/dist/assets/{java-Dgnyk--z.js → java-Cy4mnKaj.js} +1 -1
  32. package/dist/assets/{javascript-BfnrwkRN.js → javascript-_Z02t_9Q.js} +1 -1
  33. package/dist/assets/{jinja-CD2AIvOi.js → jinja-BppBm5gt.js} +1 -1
  34. package/dist/assets/{jison-DmKNw3Pl.js → jison-Dzns436M.js} +1 -1
  35. package/dist/assets/{json-CKB5a8aj.js → json-BYkfLOry.js} +1 -1
  36. package/dist/assets/{jsx-jJXrDRBM.js → jsx-DtAm8NUb.js} +1 -1
  37. package/dist/assets/{julia-G8vVBW-t.js → julia-CLIMRuh0.js} +1 -1
  38. package/dist/assets/{just-DMXLrLZL.js → just-DMg47an6.js} +1 -1
  39. package/dist/assets/{latex-DS3xuxld.js → latex-D05ztCOx.js} +1 -1
  40. package/dist/assets/{liquid-COQnD_C4.js → liquid-DpImQR4t.js} +1 -1
  41. package/dist/assets/{lua-BwMKwosd.js → lua--ZHUYQUU.js} +1 -1
  42. package/dist/assets/{marko-cQCK_WIk.js → marko-Dh3Xi08i.js} +1 -1
  43. package/dist/assets/{mdc-DQUjY13N.js → mdc-Br6Pt78u.js} +1 -1
  44. package/dist/assets/{nginx-C4Kv3DJi.js → nginx-CwSU6U6d.js} +1 -1
  45. package/dist/assets/{nim-ChzLzuYD.js → nim-CBbTwyeq.js} +1 -1
  46. package/dist/assets/{perl-z5xUiWAY.js → perl-D3jQu3LN.js} +1 -1
  47. package/dist/assets/{php-BHqm-Xeh.js → php-BMeXBzlU.js} +1 -1
  48. package/dist/assets/{pug-C9C_Llft.js → pug-BGdY1g5E.js} +1 -1
  49. package/dist/assets/{qml-dtpuwXJZ.js → qml-DsT5biza.js} +1 -1
  50. package/dist/assets/{r-BicQMs_c.js → r-CbS9PfN5.js} +1 -1
  51. package/dist/assets/{razor-D5h00YTZ.js → razor-Bwm8roHi.js} +1 -1
  52. package/dist/assets/{regexp-CTa8S86v.js → regexp-Bc_9DyAu.js} +1 -1
  53. package/dist/assets/{rst-Bg-b-7HK.js → rst-Cj1Aii91.js} +1 -1
  54. package/dist/assets/{ruby-BiWYm056.js → ruby-BPMtFZnc.js} +1 -1
  55. package/dist/assets/{sas-bJTdfgUz.js → sas-CoOau1t2.js} +1 -1
  56. package/dist/assets/{scss-0qkNyHr1.js → scss-CvfaI808.js} +1 -1
  57. package/dist/assets/{shellscript-3sDkDE9z.js → shellscript-B0C-TdLE.js} +1 -1
  58. package/dist/assets/{shellsession-D4EBhEmz.js → shellsession-UhmLiqcA.js} +1 -1
  59. package/dist/assets/{soy-DG9hUwLL.js → soy-D419u0RL.js} +1 -1
  60. package/dist/assets/{sql-B0UoKHkx.js → sql-Do9k0rRn.js} +1 -1
  61. package/dist/assets/{stata-CsQbuXQp.js → stata-x47J6BC4.js} +1 -1
  62. package/dist/assets/{surrealql-D63VLxos.js → surrealql-BERQ6pHu.js} +1 -1
  63. package/dist/assets/{svelte-BApdnAma.js → svelte-dRWkyqKW.js} +1 -1
  64. package/dist/assets/{templ-B64pN6Lz.js → templ-CTq-KoWN.js} +1 -1
  65. package/dist/assets/{tex-DR2UVIPF.js → tex-CdRGjyYI.js} +1 -1
  66. package/dist/assets/{ts-tags-IwhomvQv.js → ts-tags-CgaMqyVb.js} +1 -1
  67. package/dist/assets/{tsx-DROdxqzS.js → tsx-cpBQKcPG.js} +1 -1
  68. package/dist/assets/{twig-BtbUqpR4.js → twig-vbs_PM4-.js} +1 -1
  69. package/dist/assets/{typescript-CHpSjYZL.js → typescript-Cc--nwIo.js} +1 -1
  70. package/dist/assets/{vue-DCvEu_ug.js → vue-CgHwdBmB.js} +1 -1
  71. package/dist/assets/{vue-html-BgL4Rf-L.js → vue-html-BHfoMw47.js} +1 -1
  72. package/dist/assets/{vue-vine-CRNdhPTn.js → vue-vine-_lVRp_NK.js} +1 -1
  73. package/dist/assets/{xml-CkfPxZuz.js → xml-G4NVmTde.js} +1 -1
  74. package/dist/assets/{xsl-CteaoFSV.js → xsl-s0so2M1i.js} +1 -1
  75. package/dist/assets/{yaml-CUr3TDRN.js → yaml-CpsmW05t.js} +1 -1
  76. package/dist/index.html +2 -2
  77. package/package.json +49 -23
  78. package/scripts/access-token.mjs +21 -0
  79. package/scripts/build-diff-data.mjs +260 -39
  80. package/scripts/cache.mjs +301 -0
  81. package/scripts/cli-args.mjs +206 -57
  82. package/scripts/coding-agents.mjs +279 -162
  83. package/scripts/dev.mjs +218 -0
  84. package/scripts/doctor.mjs +265 -46
  85. package/scripts/generate-summaries.mjs +760 -260
  86. package/scripts/mock-agent.mjs +165 -0
  87. package/scripts/present.mjs +394 -85
  88. package/scripts/presenter-runtime.mjs +148 -0
  89. package/scripts/serve-built.mjs +343 -71
  90. package/scripts/summary-path.mjs +24 -7
  91. package/scripts/support-record.mjs +242 -0
@@ -1,21 +1,77 @@
1
1
  import { constants } from 'node:fs';
2
2
  import { access } from 'node:fs/promises';
3
3
  import {
4
- basename,
5
4
  delimiter,
6
5
  dirname,
7
6
  isAbsolute,
8
7
  join,
9
8
  } from 'node:path';
10
9
 
11
- export const codingAgents = [
12
- 'codex',
13
- 'claude',
14
- 'copilot',
15
- 'cursor',
16
- 'opencode',
10
+ export const codingAgentCapabilities = {
11
+ codex: { binary: 'codex', model: true, reasoning: true },
12
+ claude: { binary: 'claude', model: true, reasoning: false },
13
+ copilot: { binary: 'copilot', model: true, reasoning: false },
14
+ cursor: { binary: 'cursor-agent', model: true, reasoning: false },
15
+ opencode: { binary: 'opencode', model: true, reasoning: true },
16
+ };
17
+
18
+ export const codingAgents = Object.keys(codingAgentCapabilities);
19
+
20
+ export function agentSupportsReasoning(agent) {
21
+ return codingAgentCapabilities[agent]?.reasoning === true;
22
+ }
23
+
24
+ export function assertReasoningSupported(agent, reasoning) {
25
+ if (reasoning && !agentSupportsReasoning(agent)) {
26
+ throw new Error(
27
+ `--reasoning is supported only by codex and opencode; ${agent} does not support it.`,
28
+ );
29
+ }
30
+ }
31
+
32
+ const summaryEnvironmentNames = [
33
+ 'CODEX_HOME',
34
+ 'COMSPEC',
35
+ 'HOME',
36
+ 'HOMEDRIVE',
37
+ 'HOMEPATH',
38
+ 'HTTP_PROXY',
39
+ 'HTTPS_PROXY',
40
+ 'LANG',
41
+ 'LC_ALL',
42
+ 'NODE_EXTRA_CA_CERTS',
43
+ 'NO_PROXY',
44
+ 'PATH',
45
+ 'SSL_CERT_FILE',
46
+ 'SystemRoot',
47
+ 'SYSTEMROOT',
48
+ 'TEMP',
49
+ 'TERM',
50
+ 'TMP',
51
+ 'TMPDIR',
52
+ 'USERPROFILE',
17
53
  ];
18
54
 
55
+ export function summaryAgentEnvironment(env = process.env) {
56
+ return Object.fromEntries(
57
+ summaryEnvironmentNames
58
+ .filter((name) => typeof env[name] === 'string')
59
+ .map((name) => [name, env[name]]),
60
+ );
61
+ }
62
+
63
+ const cursorDisabledReason =
64
+ 'Cursor review is disabled: Cursor Agent has no supported read-only, no-network, no-tool mode.';
65
+
66
+ export function agentDisabledReason(agent) {
67
+ if (agent === 'cursor') return cursorDisabledReason;
68
+ return undefined;
69
+ }
70
+
71
+ export const enabledCodingAgents = codingAgents.filter(
72
+ (agent) => !agentDisabledReason(agent),
73
+ );
74
+
19
75
  async function executable(path) {
20
76
  try {
21
77
  await access(
@@ -28,25 +84,15 @@ async function executable(path) {
28
84
  }
29
85
  }
30
86
 
31
- export async function findCommand(
32
- command,
33
- {
34
- env = process.env,
35
- platform = process.platform,
36
- } = {},
37
- ) {
38
- if (
39
- isAbsolute(command) ||
40
- command.includes('/') ||
41
- command.includes('\\')
42
- ) {
43
- return (await executable(command)) ? command : undefined;
44
- }
87
+ function commandExtensions(platform, env) {
88
+ return platform === 'win32'
89
+ ? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
90
+ : [''];
91
+ }
45
92
 
93
+ async function findOnPath(command, env, platform) {
46
94
  const extensions =
47
- platform === 'win32'
48
- ? (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')
49
- : [''];
95
+ commandExtensions(platform, env);
50
96
  const directories = (env.PATH || '').split(delimiter).filter(Boolean);
51
97
  for (const directory of directories) {
52
98
  for (const extension of extensions) {
@@ -58,10 +104,26 @@ export async function findCommand(
58
104
  return undefined;
59
105
  }
60
106
 
107
+ export async function findCommand(
108
+ command,
109
+ {
110
+ env = process.env,
111
+ platform = process.platform,
112
+ } = {},
113
+ ) {
114
+ const direct =
115
+ isAbsolute(command) ||
116
+ command.includes('/') ||
117
+ command.includes('\\');
118
+ if (direct) return (await executable(command)) ? command : undefined;
119
+ return findOnPath(command, env, platform);
120
+ }
121
+
61
122
  export async function commandAvailable(command, options) {
62
123
  return Boolean(await findCommand(command, options));
63
124
  }
64
125
 
126
+ // fallow-ignore-next-line complexity -- validation and fallback share one public selector.
65
127
  export async function selectCodingAgent(
66
128
  requested,
67
129
  available = commandAvailable,
@@ -69,23 +131,26 @@ export async function selectCodingAgent(
69
131
  if (requested) {
70
132
  if (!codingAgents.includes(requested)) {
71
133
  throw new Error(
72
- `Unsupported agent "${requested}". Choose ${codingAgents.join(', ')}.`,
134
+ `Unsupported agent "${requested}". Choose ${enabledCodingAgents.join(', ')}.`,
73
135
  );
74
136
  }
137
+ const disabled = agentDisabledReason(requested);
138
+ if (disabled) throw new Error(disabled);
75
139
  if (!(await available(requested))) {
76
140
  throw new Error(`Coding agent "${requested}" is not available.`);
77
141
  }
78
142
  return requested;
79
143
  }
80
144
 
81
- for (const agent of codingAgents) {
145
+ for (const agent of enabledCodingAgents) {
82
146
  if (await available(agent)) return agent;
83
147
  }
84
148
  throw new Error(
85
- `No coding agent is available. Install one of: ${codingAgents.join(', ')}.`,
149
+ `No coding agent is available. Install one of: ${enabledCodingAgents.join(', ')}. ${cursorDisabledReason}`,
86
150
  );
87
151
  }
88
152
 
153
+ // fallow-ignore-next-line complexity -- each supported agent has one override rule.
89
154
  export function codingAgentBinary(
90
155
  agent,
91
156
  {
@@ -94,8 +159,14 @@ export function codingAgentBinary(
94
159
  } = {},
95
160
  ) {
96
161
  if (agent === 'codex') return codexBin || env.CODEX_BIN || agent;
97
- if (agent === 'cursor') return env.CURSOR_BIN || 'cursor-agent';
98
- return env[`${agent.toUpperCase()}_BIN`] || agent;
162
+ if (agent === 'cursor') {
163
+ return env.CURSOR_BIN || codingAgentCapabilities.cursor.binary;
164
+ }
165
+ return (
166
+ env[`${agent.toUpperCase()}_BIN`] ||
167
+ codingAgentCapabilities[agent]?.binary ||
168
+ agent
169
+ );
99
170
  }
100
171
 
101
172
  function parseJsonText(text, agent) {
@@ -108,161 +179,179 @@ function parseJsonText(text, agent) {
108
179
  }
109
180
  }
110
181
 
111
- export function parseAgentResponse(agent, stdout) {
112
- if (agent === 'claude') {
113
- const envelope = parseJsonText(stdout, 'Claude');
114
- if (envelope?.structured_output) return envelope.structured_output;
115
- if (typeof envelope?.result === 'string') {
116
- return parseJsonText(envelope.result, 'Claude');
117
- }
118
- return envelope;
182
+ function parseClaudeResponse(stdout) {
183
+ const envelope = parseJsonText(stdout, 'Claude');
184
+ if (envelope?.structured_output) return envelope.structured_output;
185
+ if (typeof envelope?.result === 'string') {
186
+ return parseJsonText(envelope.result, 'Claude');
119
187
  }
188
+ return envelope;
189
+ }
120
190
 
121
- if (agent === 'opencode') {
122
- const events = stdout
123
- .split('\n')
124
- .filter(Boolean)
125
- .map((line) => {
126
- try {
127
- return JSON.parse(line);
128
- } catch {
129
- return undefined;
130
- }
131
- });
132
- const parts = events
133
- .filter((event) => event?.type === 'text' && event.part?.text)
134
- .map((event) => event.part.text);
135
- if (parts.length) return parseJsonText(parts.join(''), 'OpenCode');
136
- throw new Error('OpenCode did not return summary JSON');
191
+ function parseEvent(line) {
192
+ try {
193
+ return JSON.parse(line);
194
+ } catch {
195
+ return undefined;
137
196
  }
197
+ }
138
198
 
139
- if (agent === 'cursor') {
140
- const envelope = parseJsonText(stdout, 'Cursor');
141
- if (typeof envelope?.result === 'string') {
142
- return parseJsonText(envelope.result, 'Cursor');
143
- }
144
- return envelope;
199
+ function parseOpenCodeResponse(stdout) {
200
+ const parts = stdout
201
+ .split('\n')
202
+ .filter(Boolean)
203
+ .map(parseEvent)
204
+ .filter((event) => event?.type === 'text' && event.part?.text)
205
+ .map((event) => event.part.text);
206
+ if (!parts.length) throw new Error('OpenCode did not return summary JSON');
207
+ return parseJsonText(parts.join(''), 'OpenCode');
208
+ }
209
+
210
+ function parseCursorResponse(stdout) {
211
+ const envelope = parseJsonText(stdout, 'Cursor');
212
+ if (typeof envelope?.result === 'string') {
213
+ return parseJsonText(envelope.result, 'Cursor');
145
214
  }
215
+ return envelope;
216
+ }
146
217
 
218
+ export function parseAgentResponse(agent, stdout) {
219
+ if (agent === 'claude') return parseClaudeResponse(stdout);
220
+ if (agent === 'opencode') return parseOpenCodeResponse(stdout);
221
+ if (agent === 'cursor') return parseCursorResponse(stdout);
147
222
  const label = agent === 'copilot' ? 'Copilot' : 'Codex';
148
223
  return parseJsonText(stdout, label);
149
224
  }
150
225
 
151
- export function agentCommand({
152
- agent,
153
- binary = agent,
226
+ function codexCommand({
227
+ binary,
154
228
  model,
155
229
  reasoning,
156
230
  prompt,
157
- schema,
158
231
  schemaPath,
159
- inputPath,
160
- workingDirectory,
161
- env = process.env,
232
+ summaryDirectory,
233
+ summaryEnv,
162
234
  }) {
163
- if (agent === 'codex') {
164
- const args = [
165
- 'exec',
166
- '--ephemeral',
167
- '--sandbox',
168
- 'read-only',
169
- '--ignore-user-config',
170
- '--color',
171
- 'never',
172
- '--skip-git-repo-check',
173
- '-C',
174
- workingDirectory,
175
- '--output-schema',
176
- schemaPath,
177
- ];
178
- if (model) args.push('--model', model);
179
- if (reasoning) {
180
- args.push(
181
- '--config',
182
- `model_reasoning_effort=${JSON.stringify(reasoning)}`,
183
- );
184
- }
185
- args.push(prompt);
186
- return { command: binary, args, input: 'stdin' };
187
- }
188
-
189
- if (agent === 'claude') {
190
- const args = [
191
- '--print',
192
- '--output-format',
193
- 'json',
194
- '--json-schema',
195
- JSON.stringify(schema),
196
- '--tools',
197
- '',
198
- '--no-session-persistence',
199
- ];
200
- if (model) args.push('--model', model);
201
- args.push(prompt);
202
- return { command: binary, args, input: 'stdin' };
203
- }
204
-
205
- if (agent === 'copilot') {
206
- const schemaText = JSON.stringify(schema);
207
- const args = [
208
- '--silent',
209
- '--no-ask-user',
210
- '--no-color',
211
- '--no-custom-instructions',
212
- '--no-remote',
213
- '--no-remote-export',
214
- `--add-dir=${dirname(inputPath)}`,
215
- ];
216
- if (model) args.push('--model', model);
235
+ const args = [
236
+ 'exec',
237
+ '--ephemeral',
238
+ '--sandbox',
239
+ 'read-only',
240
+ '--ignore-user-config',
241
+ '--ignore-rules',
242
+ '--color',
243
+ 'never',
244
+ '--skip-git-repo-check',
245
+ '-C',
246
+ summaryDirectory,
247
+ '--output-schema',
248
+ schemaPath,
249
+ '--config',
250
+ 'mcp_servers={}',
251
+ '--config',
252
+ 'plugins={}',
253
+ '--config',
254
+ 'shell_environment_policy.inherit="none"',
255
+ '--config',
256
+ 'sandbox_workspace_write.network_access=false',
257
+ '--config',
258
+ 'web_search="disabled"',
259
+ ];
260
+ if (model) args.push('--model', model);
261
+ if (reasoning) {
217
262
  args.push(
218
- '--prompt',
219
- `${prompt}\n\nRead the snapshot from @${inputPath}. Return JSON that matches this schema:\n${schemaText}`,
263
+ '--config',
264
+ `model_reasoning_effort=${JSON.stringify(reasoning)}`,
220
265
  );
221
- return { command: binary, args, input: 'none' };
222
266
  }
267
+ args.push(prompt);
268
+ return {
269
+ command: binary,
270
+ args,
271
+ input: 'stdin',
272
+ cwd: summaryDirectory,
273
+ env: summaryEnv,
274
+ };
275
+ }
223
276
 
224
- if (agent === 'cursor') {
225
- const args = ['--print', '--trust', '--output-format', 'json'];
226
- if (model) args.push('--model', model);
227
- args.push(
228
- `${prompt}\n\nRead the snapshot from @${basename(inputPath)}. Return only JSON that matches this schema:\n${JSON.stringify(schema)}`,
229
- );
230
- return {
231
- command: binary,
232
- args,
233
- input: 'none',
234
- cwd: dirname(inputPath),
235
- };
236
- }
277
+ function claudeCommand({
278
+ binary,
279
+ model,
280
+ prompt,
281
+ schema,
282
+ summaryDirectory,
283
+ summaryEnv,
284
+ }) {
285
+ const args = [
286
+ '--print',
287
+ '--output-format',
288
+ 'json',
289
+ '--json-schema',
290
+ JSON.stringify(schema),
291
+ '--tools',
292
+ '',
293
+ '--no-session-persistence',
294
+ ];
295
+ if (model) args.push('--model', model);
296
+ args.push(prompt);
297
+ return {
298
+ command: binary,
299
+ args,
300
+ input: 'stdin',
301
+ cwd: summaryDirectory,
302
+ env: summaryEnv,
303
+ };
304
+ }
237
305
 
238
- let config = {};
239
- try {
240
- const parsed = JSON.parse(env.OPENCODE_CONFIG_CONTENT || '{}');
241
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
242
- config = parsed;
243
- }
244
- } catch {
245
- // Replace invalid inline config with the safe settings for this run.
246
- }
247
- const configuredAgents =
248
- config.agent &&
249
- typeof config.agent === 'object' &&
250
- !Array.isArray(config.agent)
251
- ? config.agent
252
- : {};
253
- const configuredBuild =
254
- configuredAgents.build &&
255
- typeof configuredAgents.build === 'object' &&
256
- !Array.isArray(configuredAgents.build)
257
- ? configuredAgents.build
258
- : {};
306
+ function copilotCommand({
307
+ binary,
308
+ inputPath,
309
+ model,
310
+ prompt,
311
+ schema,
312
+ summaryDirectory,
313
+ summaryEnv,
314
+ }) {
315
+ const schemaText = JSON.stringify(schema);
316
+ const args = [
317
+ '--silent',
318
+ '--no-ask-user',
319
+ '--no-color',
320
+ '--no-custom-instructions',
321
+ '--no-remote',
322
+ '--no-remote-export',
323
+ `--add-dir=${dirname(inputPath)}`,
324
+ ];
325
+ if (model) args.push('--model', model);
326
+ args.push(
327
+ '--prompt',
328
+ `${prompt}\n\nRead the snapshot from @${inputPath}. Return JSON that matches this schema:\n${schemaText}`,
329
+ );
330
+ return {
331
+ command: binary,
332
+ args,
333
+ input: 'none',
334
+ cwd: summaryDirectory,
335
+ env: summaryEnv,
336
+ };
337
+ }
338
+
339
+ function openCodeCommand({
340
+ binary,
341
+ model,
342
+ reasoning,
343
+ prompt,
344
+ schema,
345
+ summaryDirectory,
346
+ summaryEnv,
347
+ }) {
259
348
  const args = [
260
349
  'run',
261
350
  '--pure',
262
351
  '--format',
263
352
  'json',
264
353
  '--dir',
265
- dirname(inputPath),
354
+ summaryDirectory,
266
355
  '--agent',
267
356
  'build',
268
357
  ];
@@ -275,16 +364,14 @@ export function agentCommand({
275
364
  command: binary,
276
365
  args,
277
366
  input: 'stdin',
278
- cwd: dirname(inputPath),
367
+ cwd: summaryDirectory,
279
368
  env: {
369
+ ...summaryEnv,
280
370
  OPENCODE_DB: ':memory:',
281
371
  OPENCODE_CONFIG_CONTENT: JSON.stringify({
282
- ...config,
283
372
  permission: { '*': 'deny' },
284
373
  agent: {
285
- ...configuredAgents,
286
374
  build: {
287
- ...configuredBuild,
288
375
  permission: { '*': 'deny' },
289
376
  },
290
377
  },
@@ -292,3 +379,33 @@ export function agentCommand({
292
379
  },
293
380
  };
294
381
  }
382
+
383
+ export function agentCommand({
384
+ agent,
385
+ binary = agent,
386
+ model,
387
+ reasoning,
388
+ prompt,
389
+ schema,
390
+ schemaPath,
391
+ inputPath,
392
+ env = process.env,
393
+ }) {
394
+ const disabled = agentDisabledReason(agent);
395
+ if (disabled) throw new Error(disabled);
396
+ const options = {
397
+ binary,
398
+ inputPath,
399
+ model,
400
+ prompt,
401
+ reasoning,
402
+ schema,
403
+ schemaPath,
404
+ summaryDirectory: dirname(inputPath),
405
+ summaryEnv: summaryAgentEnvironment(env),
406
+ };
407
+ if (agent === 'codex') return codexCommand(options);
408
+ if (agent === 'claude') return claudeCommand(options);
409
+ if (agent === 'copilot') return copilotCommand(options);
410
+ return openCodeCommand(options);
411
+ }