@contentful/experience-design-system-generation 2.34.2-dev-build-3cd48dd.0 → 2.34.2-dev-build-f52da46.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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-generation",
3
- "version": "2.34.2-dev-build-3cd48dd.0",
3
+ "version": "2.34.2-dev-build-f52da46.0",
4
4
  "description": "Agent-invocation and skill-prompt engine for the Contentful Experience Design System SDK",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -113,6 +113,18 @@ export declare function agentSupportsBedrock(agent: AgentName): boolean;
113
113
  */
114
114
  export declare function resolveAgentModel(agent: AgentName, explicit?: string, bedrock?: boolean): string | undefined;
115
115
  export type AgentDebugEvent = (name: string, payload?: Record<string, unknown>) => void;
116
+ export declare function agentSupportsStdinPrompt(agent: AgentName): boolean;
117
+ /**
118
+ * Decide how to deliver the prompt.
119
+ *
120
+ * Prefer stdin whenever the agent supports it and the prompt is large enough for
121
+ * argv limits to matter. This is what makes `generate components` work on Windows,
122
+ * and it removes a latent ARG_MAX failure on macOS as the skills grow.
123
+ *
124
+ * Exported so callers can detect the case that has no answer: a large prompt for an
125
+ * agent that can only accept argv.
126
+ */
127
+ export declare function shouldUseStdinPrompt(agent: AgentName, prompt: string): boolean;
116
128
  export declare function buildArgs(agent: AgentName, prompt: string, model?: string, promptViaStdin?: boolean, bedrock?: boolean): string[];
117
129
  export declare function runAgent(options: {
118
130
  agent: AgentName;
@@ -123,9 +135,12 @@ export declare function runAgent(options: {
123
135
  bedrock?: boolean;
124
136
  onOutput?: (chunk: string) => void;
125
137
  /**
126
- * Deliver the prompt on stdin instead of as an argv positional. Required for
127
- * large prompts (e.g. the composition resolver inlining candidate files),
128
- * which overflow ARG_MAX when passed as an argument.
138
+ * Force the prompt onto stdin instead of an argv positional.
139
+ *
140
+ * Usually unnecessary: a prompt large enough for argv limits to matter is sent
141
+ * on stdin automatically (see shouldUseStdinPrompt). Set this to opt in for a
142
+ * small prompt too. It cannot force stdin for an agent that has no stdin path —
143
+ * copilot requires the prompt as its `-p` value.
129
144
  */
130
145
  promptViaStdin?: boolean;
131
146
  /** Optional debug-event sink; callers own how/where events get logged. */
@@ -1,4 +1,4 @@
1
- import { spawn } from 'node:child_process';
1
+ import { findBinary, spawnBinary } from './lib/binary-launch.js';
2
2
  export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
3
3
  const VALID_SELECT_TOOL_NAMES = new Set(['select_component', 'reject_component']);
4
4
  function findJsonObjectEnd(line) {
@@ -355,15 +355,55 @@ function codexBedrockConfigArgs() {
355
355
  const region = process.env.AWS_REGION?.trim() || process.env.AWS_DEFAULT_REGION?.trim() || DEFAULT_CODEX_BEDROCK_REGION;
356
356
  return ['-c', 'model_provider=amazon-bedrock', '-c', `model_providers.amazon-bedrock.region=${region}`];
357
357
  }
358
+ /**
359
+ * Agents that can read the prompt from stdin instead of argv.
360
+ *
361
+ * copilot cannot: its `-p` flag requires the prompt as the flag's value (see
362
+ * buildArgs), so there is nowhere for stdin to go.
363
+ */
364
+ const STDIN_CAPABLE_AGENTS = new Set(['claude', 'codex', 'opencode', 'cursor']);
365
+ export function agentSupportsStdinPrompt(agent) {
366
+ return STDIN_CAPABLE_AGENTS.has(agent);
367
+ }
368
+ /**
369
+ * Windows caps a command line at 8191 characters through cmd.exe, and 32767 in
370
+ * CreateProcess. Our skill prompts are far larger than both — generate-components
371
+ * alone is ~50KB — so passing one as an argv positional cannot work there. POSIX
372
+ * is far more generous (ARG_MAX ~1MB on macOS) but not unlimited.
373
+ *
374
+ * Kept deliberately low: stdin is the better path for any prompt of real size, and
375
+ * there is no benefit to argv beyond the few agents that require it.
376
+ */
377
+ const ARGV_PROMPT_LIMIT = 4096;
378
+ /**
379
+ * Decide how to deliver the prompt.
380
+ *
381
+ * Prefer stdin whenever the agent supports it and the prompt is large enough for
382
+ * argv limits to matter. This is what makes `generate components` work on Windows,
383
+ * and it removes a latent ARG_MAX failure on macOS as the skills grow.
384
+ *
385
+ * Exported so callers can detect the case that has no answer: a large prompt for an
386
+ * agent that can only accept argv.
387
+ */
388
+ export function shouldUseStdinPrompt(agent, prompt) {
389
+ return agentSupportsStdinPrompt(agent) && prompt.length > ARGV_PROMPT_LIMIT;
390
+ }
358
391
  export function buildArgs(agent, prompt, model, promptViaStdin = false, bedrock = false) {
359
392
  // codex with no configured model resolves to undefined — omit --model
360
393
  // entirely so the CLI picks its own account-compatible default.
361
394
  const resolvedModel = resolveAgentModel(agent, model, bedrock);
362
395
  const modelArg = resolvedModel ? ['--model', resolvedModel] : [];
363
- // When the prompt is delivered on stdin, omit it from argv — a large prompt
364
- // as a command-line argument overflows ARG_MAX (spawn E2BIG). All four CLIs
365
- // read the prompt from stdin when it isn't passed positionally.
366
- const promptArg = promptViaStdin ? [] : [prompt];
396
+ // Ignore a stdin request for an agent that has no stdin path. copilot's `-p`
397
+ // takes the prompt as its value, so honouring the request would emit a bare
398
+ // `-p` and silently drop the prompt. runAgent already avoids asking, but keep
399
+ // the guard here so the function can't be misused.
400
+ const useStdin = promptViaStdin && agentSupportsStdinPrompt(agent);
401
+ // When the prompt is delivered on stdin, omit it from argv — a large prompt as
402
+ // a command-line argument overflows ARG_MAX on POSIX (E2BIG) and cannot be
403
+ // passed at all on Windows, whose command line caps at 8191 characters through
404
+ // cmd.exe. These CLIs read the prompt from stdin when it isn't passed
405
+ // positionally.
406
+ const promptArg = useStdin ? [] : [prompt];
367
407
  switch (agent) {
368
408
  case 'claude':
369
409
  return ['--print', ...modelArg, ...promptArg];
@@ -406,7 +446,11 @@ export async function runAgent(options) {
406
446
  // call sites that forget to thread the flag through by hand.
407
447
  const bedrock = options.bedrock ?? process.env.EDS_BEDROCK === '1';
408
448
  const binary = resolveBinary(agent);
409
- const useStdin = !!promptViaStdin;
449
+ // Send a large prompt on stdin even when the caller didn't ask: a ~50KB skill
450
+ // prompt exceeds every Windows command-line limit, so argv simply cannot carry
451
+ // it. Honour an explicit promptViaStdin for smaller prompts, but never route to
452
+ // stdin for an agent that can't read it (copilot).
453
+ const useStdin = agentSupportsStdinPrompt(agent) && (!!promptViaStdin || shouldUseStdinPrompt(agent, prompt));
410
454
  const args = buildArgs(agent, prompt, model, useStdin, bedrock);
411
455
  const startedAt = Date.now();
412
456
  onDebugEvent?.('run.start', {
@@ -420,7 +464,7 @@ export async function runAgent(options) {
420
464
  });
421
465
  return new Promise((resolve) => {
422
466
  const bedrockEnv = bedrock ? BEDROCK_ENV_BY_AGENT[agent] : undefined;
423
- const child = spawn(binary, args, {
467
+ const child = spawnBinary(binary, args, {
424
468
  stdio: ['pipe', 'pipe', 'pipe'],
425
469
  ...(bedrockEnv ? { env: { ...process.env, ...bedrockEnv } } : {}),
426
470
  });
@@ -477,19 +521,10 @@ export async function runAgent(options) {
477
521
  export async function checkAgentAuth(agent) {
478
522
  const binary = resolveBinary(agent);
479
523
  // Verify the selected agent's binary exists first — for EVERY agent, not
480
- // just claude. When `binary` is an absolute path (e.g. set via
481
- // EDS_AGENT_BINARY_<AGENT>=/opt/custom/bin), `which` on some shells doesn't
482
- // resolve it — check the filesystem directly for absolute paths, and fall
483
- // back to `which` for bare names on $PATH.
484
- const binaryExists = await new Promise((resolve) => {
485
- if (binary.startsWith('/')) {
486
- import('node:fs/promises').then((fs) => fs.access(binary).then(() => resolve(true), () => resolve(false)));
487
- return;
488
- }
489
- const child = spawn('which', [binary], { stdio: 'ignore' });
490
- child.on('close', (code) => resolve(code === 0));
491
- });
492
- if (!binaryExists)
524
+ // just claude. Handles an absolute EDS_AGENT_BINARY_<AGENT> override as well as
525
+ // a bare name on PATH.
526
+ const resolvedBinary = findBinary(binary);
527
+ if (!resolvedBinary)
493
528
  return 'not-found';
494
529
  // Only Claude exposes `auth status --json`. Non-Claude agents are considered
495
530
  // authenticated once their binary is present — never gate them on claude
@@ -499,7 +534,7 @@ export async function checkAgentAuth(agent) {
499
534
  // Use `claude auth status` — fast, no API call, works regardless of which
500
535
  // auth provider (direct, Bedrock, Vertex) or whether AWS_PROFILE is set.
501
536
  return new Promise((resolve) => {
502
- const child = spawn(binary, ['auth', 'status', '--json'], {
537
+ const child = spawnBinary(resolvedBinary, ['auth', 'status', '--json'], {
503
538
  stdio: ['ignore', 'pipe', 'pipe'],
504
539
  });
505
540
  let stdout = '';
@@ -1,9 +1,10 @@
1
1
  export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
2
2
  export type { AgentName } from './agent-names.js';
3
- export { agentSupportsBedrock, buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseMapTokenPropToolCallLines, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, } from './agent-runner.js';
3
+ export { agentSupportsBedrock, agentSupportsStdinPrompt, buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseMapTokenPropToolCallLines, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, shouldUseStdinPrompt, } from './agent-runner.js';
4
4
  export type { AgentAuthStatus, AgentDebugEvent, AgentRunResult, ClassifyComponentCall, ClassifyPropCall, ClassifySlotCall, ExcludePropCall, MapTokenPropCall, ParsedMapTokenPropToolCalls, ParsedSelectToolCalls, ParsedTokenToolCalls, ParsedToolCalls, RejectComponentCall, SelectComponentCall, SelectToolCall, SetGroupCall, SetTokenCall, ToolCall, TokenToolCall, } from './agent-runner.js';
5
5
  export { createLocalCliAgentInvoker } from './agent-invoker.js';
6
6
  export type { AgentInvoker, CreateLocalCliAgentInvokerOptions, InvokeAgentOptions } from './agent-invoker.js';
7
7
  export { buildPrompt, formatCustomPromptBanner, resolveSkillPath } from './prompt-builder.js';
8
8
  export type { ComponentSourceRef, GeneratedCdf, Mode, PromptOptions, Skill } from './prompt-builder.js';
9
9
  export { formatGenerateProgressLine } from './progress.js';
10
+ export { binaryExists, findBinary, spawnBinary } from './lib/binary-launch.js';
package/dist/src/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  // Agent identity
2
2
  export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
3
3
  // Agent invocation (low-level)
4
- export { agentSupportsBedrock, buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseMapTokenPropToolCallLines, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, } from './agent-runner.js';
4
+ export { agentSupportsBedrock, agentSupportsStdinPrompt, buildArgs, checkAgentAuth, describeAgentFailure, extractSentinelOutput, parseMapTokenPropToolCallLines, parseSelectToolCallLines, parseTokenToolCallLines, parseToolCallLines, resolveAgentModel, resolveBinary, runAgent, shouldUseStdinPrompt, } from './agent-runner.js';
5
5
  // Agent invocation (interface)
6
6
  export { createLocalCliAgentInvoker } from './agent-invoker.js';
7
7
  // Prompt building
8
8
  export { buildPrompt, formatCustomPromptBanner, resolveSkillPath } from './prompt-builder.js';
9
9
  // Progress reporting
10
10
  export { formatGenerateProgressLine } from './progress.js';
11
+ // Cross-platform binary lookup and launch (Windows .cmd shims, PATHEXT)
12
+ export { binaryExists, findBinary, spawnBinary } from './lib/binary-launch.js';
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Cross-platform binary lookup and launch.
3
+ *
4
+ * Two separate Windows problems live here, and they need different fixes:
5
+ *
6
+ * 1. LOOKUP. libuv's spawn only tries `.com` and `.exe` when resolving a bare
7
+ * command name against PATH — it ignores PATHEXT. Every tool installed via
8
+ * npm (`claude`, `codex`, `pnpm`) is a `.cmd` shim on Windows, so a bare
9
+ * name resolves to nothing and spawn fails with ENOENT. `findBinary` walks
10
+ * PATH itself and tries the Windows executable extensions.
11
+ *
12
+ * 2. LAUNCH. Even with the full path, Node refuses to start a `.bat`/`.cmd`
13
+ * directly — it throws EINVAL (a deliberate guard added for CVE-2024-27980).
14
+ * Such a file has to run through `cmd.exe /d /s /c`. `spawnSpec` builds that
15
+ * argv.
16
+ *
17
+ * Both take `platform` as an argument so tests can assert Windows behaviour
18
+ * while running on macOS or Linux.
19
+ */
20
+ import { type ChildProcess, type SpawnOptions } from 'node:child_process';
21
+ /** Resolve a command to an absolute path, or null when it isn't on PATH. */
22
+ export declare function findBinary(binary: string, platform?: NodeJS.Platform): string | null;
23
+ /** True when `binary` resolves to something executable. */
24
+ export declare function binaryExists(binary: string, platform?: NodeJS.Platform): boolean;
25
+ export type SpawnSpec = {
26
+ command: string;
27
+ args: string[];
28
+ /** Set when `args` are pre-quoted and Node must pass them through untouched. */
29
+ windowsVerbatimArguments?: boolean;
30
+ };
31
+ /**
32
+ * Build the argv for launching an already-resolved executable. A real `.exe`, and
33
+ * anything off Windows, passes straight through.
34
+ *
35
+ * The quoting is deliberately simple, and is only safe because the arguments
36
+ * reaching it are our own flags and model names — prompts travel over stdin and
37
+ * must keep doing so. If user-controlled text ever needs to go through argv here,
38
+ * switch to `cross-spawn` rather than extending this: it implements the full
39
+ * cmd.exe rules, including the double-escaping `node_modules/.bin` shims need.
40
+ */
41
+ export declare function spawnSpec(resolved: string, args: string[], platform?: NodeJS.Platform): SpawnSpec;
42
+ /**
43
+ * Resolve a command and build its launch spec in one step. Returns null when the
44
+ * command isn't installed, so callers can report that distinctly from a failure
45
+ * to run.
46
+ */
47
+ export declare function resolveSpawn(binary: string, args: string[], platform?: NodeJS.Platform): SpawnSpec | null;
48
+ /**
49
+ * `child_process.spawn`, with Windows shim resolution applied.
50
+ *
51
+ * Use this instead of `spawn` for anything installed by a package manager. When
52
+ * the command can't be resolved it is passed through unchanged, so a missing
53
+ * binary still surfaces as the caller's usual `error` event rather than throwing
54
+ * from here.
55
+ */
56
+ export declare function spawnBinary(command: string, args: string[], options?: SpawnOptions): ChildProcess;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Cross-platform binary lookup and launch.
3
+ *
4
+ * Two separate Windows problems live here, and they need different fixes:
5
+ *
6
+ * 1. LOOKUP. libuv's spawn only tries `.com` and `.exe` when resolving a bare
7
+ * command name against PATH — it ignores PATHEXT. Every tool installed via
8
+ * npm (`claude`, `codex`, `pnpm`) is a `.cmd` shim on Windows, so a bare
9
+ * name resolves to nothing and spawn fails with ENOENT. `findBinary` walks
10
+ * PATH itself and tries the Windows executable extensions.
11
+ *
12
+ * 2. LAUNCH. Even with the full path, Node refuses to start a `.bat`/`.cmd`
13
+ * directly — it throws EINVAL (a deliberate guard added for CVE-2024-27980).
14
+ * Such a file has to run through `cmd.exe /d /s /c`. `spawnSpec` builds that
15
+ * argv.
16
+ *
17
+ * Both take `platform` as an argument so tests can assert Windows behaviour
18
+ * while running on macOS or Linux.
19
+ */
20
+ import { spawn } from 'node:child_process';
21
+ import { accessSync, constants } from 'node:fs';
22
+ import { delimiter, isAbsolute, join } from 'node:path';
23
+ /** Suffixes to try for a bare command name; POSIX names carry their own. */
24
+ function executableExtensions(platform) {
25
+ return platform === 'win32' ? ['.exe', '.cmd', '.bat', '.com'] : [''];
26
+ }
27
+ /** Resolve a command to an absolute path, or null when it isn't on PATH. */
28
+ export function findBinary(binary, platform = process.platform) {
29
+ const extensions = executableExtensions(platform);
30
+ if (isAbsolute(binary)) {
31
+ // An explicit path may already carry its extension, or (on Windows) omit it.
32
+ for (const extension of ['', ...extensions]) {
33
+ try {
34
+ accessSync(binary + extension, constants.F_OK);
35
+ return binary + extension;
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ for (const directory of (process.env['PATH'] ?? '').split(delimiter)) {
44
+ // Skip relative entries: resolving against the cwd would make the result
45
+ // depend on where the CLI was run from.
46
+ if (!directory || !isAbsolute(directory))
47
+ continue;
48
+ for (const extension of extensions) {
49
+ const candidate = join(directory, binary + extension);
50
+ try {
51
+ accessSync(candidate, constants.X_OK);
52
+ return candidate;
53
+ }
54
+ catch {
55
+ continue;
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /** True when `binary` resolves to something executable. */
62
+ export function binaryExists(binary, platform = process.platform) {
63
+ return findBinary(binary, platform) !== null;
64
+ }
65
+ /**
66
+ * Build the argv for launching an already-resolved executable. A real `.exe`, and
67
+ * anything off Windows, passes straight through.
68
+ *
69
+ * The quoting is deliberately simple, and is only safe because the arguments
70
+ * reaching it are our own flags and model names — prompts travel over stdin and
71
+ * must keep doing so. If user-controlled text ever needs to go through argv here,
72
+ * switch to `cross-spawn` rather than extending this: it implements the full
73
+ * cmd.exe rules, including the double-escaping `node_modules/.bin` shims need.
74
+ */
75
+ export function spawnSpec(resolved, args, platform = process.platform) {
76
+ if (platform !== 'win32' || /\.(exe|com)$/i.test(resolved)) {
77
+ return { command: resolved, args };
78
+ }
79
+ // `||` not `??`: an empty ComSpec is as unusable as an absent one.
80
+ const shell = process.env['ComSpec'] || 'cmd.exe';
81
+ const quote = (value) => `"${value.replace(/"/g, '""')}"`;
82
+ const commandLine = [resolved, ...args].map(quote).join(' ');
83
+ return {
84
+ command: shell,
85
+ // /d skips AutoRun scripts, /s keeps the outer quotes intact, /c runs and exits.
86
+ args: ['/d', '/s', '/c', `"${commandLine}"`],
87
+ windowsVerbatimArguments: true,
88
+ };
89
+ }
90
+ /**
91
+ * Resolve a command and build its launch spec in one step. Returns null when the
92
+ * command isn't installed, so callers can report that distinctly from a failure
93
+ * to run.
94
+ */
95
+ export function resolveSpawn(binary, args, platform = process.platform) {
96
+ const resolved = findBinary(binary, platform);
97
+ if (!resolved)
98
+ return null;
99
+ return spawnSpec(resolved, args, platform);
100
+ }
101
+ /**
102
+ * `child_process.spawn`, with Windows shim resolution applied.
103
+ *
104
+ * Use this instead of `spawn` for anything installed by a package manager. When
105
+ * the command can't be resolved it is passed through unchanged, so a missing
106
+ * binary still surfaces as the caller's usual `error` event rather than throwing
107
+ * from here.
108
+ */
109
+ export function spawnBinary(command, args, options = {}) {
110
+ const launch = resolveSpawn(command, args) ?? { command, args };
111
+ return spawn(launch.command, launch.args, {
112
+ ...options,
113
+ ...(launch.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
114
+ });
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-generation",
3
- "version": "2.34.2-dev-build-3cd48dd.0",
3
+ "version": "2.34.2-dev-build-f52da46.0",
4
4
  "description": "Agent-invocation and skill-prompt engine for the Contentful Experience Design System SDK",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "skills/"
23
23
  ],
24
24
  "dependencies": {
25
- "@contentful/experience-design-system-types": "2.34.2-dev-build-3cd48dd.0"
25
+ "@contentful/experience-design-system-types": "2.34.2-dev-build-f52da46.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@tsconfig/node24": "^24.0.4",