@contentful/experience-design-system-cli 2.18.1-dev-build-180092e.0 → 2.18.1-dev-build-0f85b83.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 (59) hide show
  1. package/README.md +2 -1
  2. package/dist/package.json +2 -2
  3. package/dist/src/analyze/command.js +3 -3
  4. package/dist/src/analyze/composition/parse-map-edges.d.ts +1 -1
  5. package/dist/src/analyze/select/tui/App.js +4 -1
  6. package/dist/src/analyze/select-agent/command.js +5 -6
  7. package/dist/src/apply/api-client.d.ts +5 -2
  8. package/dist/src/apply/api-client.js +7 -4
  9. package/dist/src/apply/command.js +27 -10
  10. package/dist/src/apply/preview-utils.d.ts +5 -1
  11. package/dist/src/apply/preview-utils.js +5 -1
  12. package/dist/src/apply/tui/ServerApplyView.d.ts +6 -3
  13. package/dist/src/apply/tui/ServerApplyView.js +15 -6
  14. package/dist/src/apply/tui/ServerPreviewView.d.ts +2 -1
  15. package/dist/src/apply/tui/ServerPreviewView.js +3 -3
  16. package/dist/src/generate/agent-runner.d.ts +125 -0
  17. package/dist/src/generate/agent-runner.js +427 -0
  18. package/dist/src/generate/command.d.ts +6 -0
  19. package/dist/src/generate/command.js +17 -7
  20. package/dist/src/generate/progress.d.ts +9 -0
  21. package/dist/src/generate/progress.js +11 -0
  22. package/dist/src/generate/prompt-builder.d.ts +26 -0
  23. package/dist/src/generate/prompt-builder.js +213 -0
  24. package/dist/src/import/command.js +6 -1
  25. package/dist/src/import/orchestrator.d.ts +1 -0
  26. package/dist/src/import/orchestrator.js +2 -0
  27. package/dist/src/import/tui/WizardApp.d.ts +2 -1
  28. package/dist/src/import/tui/WizardApp.js +9 -9
  29. package/dist/src/import/tui/final-review-host.d.ts +2 -1
  30. package/dist/src/import/tui/final-review-host.js +2 -2
  31. package/dist/src/import/tui/runLivePreview.d.ts +3 -0
  32. package/dist/src/import/tui/runLivePreview.js +4 -1
  33. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +2 -1
  34. package/dist/src/import/tui/steps/GenerateReviewStep.js +3 -3
  35. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -2
  36. package/dist/src/import/tui/steps/WizardPreviewStep.js +12 -3
  37. package/dist/src/import/tui/useFinalizePreview.d.ts +2 -0
  38. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  39. package/dist/src/import/tui/useLivePreview.d.ts +2 -0
  40. package/dist/src/import/tui/useLivePreview.js +1 -0
  41. package/dist/src/lib/agent-model-options.js +1 -1
  42. package/dist/src/lib/agent-names.d.ts +4 -0
  43. package/dist/src/lib/agent-names.js +5 -0
  44. package/dist/src/lib/command-options.d.ts +1 -0
  45. package/dist/src/lib/command-options.js +3 -0
  46. package/dist/src/runs/modify-launcher.d.ts +2 -0
  47. package/dist/src/runs/modify-launcher.js +2 -0
  48. package/dist/src/runs/push-helpers.d.ts +1 -0
  49. package/dist/src/runs/push-helpers.js +3 -0
  50. package/dist/src/runs/replay-helpers.d.ts +4 -0
  51. package/dist/src/runs/replay-helpers.js +3 -0
  52. package/dist/src/session/cache-keys.d.ts +1 -1
  53. package/dist/src/session/cache-keys.js +1 -1
  54. package/dist/src/session/db.d.ts +1 -1
  55. package/dist/src/session/db.js +1 -1
  56. package/package.json +5 -5
  57. package/skills/generate-components.md +427 -0
  58. package/skills/generate-tokens.md +194 -0
  59. package/skills/select-components.md +224 -0
@@ -0,0 +1,213 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const SKILL_FILES = {
6
+ components: 'generate-components.md',
7
+ tokens: 'generate-tokens.md',
8
+ select: 'select-components.md',
9
+ };
10
+ const OUTPUT_FILES = {
11
+ components: 'components.json',
12
+ tokens: 'tokens.json',
13
+ select: 'select.json',
14
+ };
15
+ export async function buildPrompt(options) {
16
+ const skillContent = await readSkillFile(options.skill, options.skillPathOverride);
17
+ const preamble = buildPreamble(options);
18
+ return `${preamble}\n\nSkill instructions follow:\n---\n${skillContent}`;
19
+ }
20
+ export function resolveSkillPath(skill) {
21
+ if (!(skill in SKILL_FILES))
22
+ throw new Error(`Invalid skill: ${skill}`);
23
+ // Walk up until we find the skills/ directory (works from both src/ and dist/src/ contexts)
24
+ const thisDir = dirname(fileURLToPath(import.meta.url));
25
+ let dir = thisDir;
26
+ for (;;) {
27
+ const candidate = join(dir, 'skills');
28
+ if (existsSync(candidate))
29
+ return join(candidate, SKILL_FILES[skill]);
30
+ const parent = resolve(dir, '..');
31
+ if (parent === dir) {
32
+ throw new Error(`skill file missing from CLI installation (could not locate skills/ directory from: ${thisDir})`);
33
+ }
34
+ dir = parent;
35
+ }
36
+ }
37
+ async function readSkillFile(skill, override) {
38
+ if (override) {
39
+ const skillPath = resolve(override);
40
+ try {
41
+ return await readFile(skillPath, 'utf8');
42
+ }
43
+ catch {
44
+ throw new Error(`custom prompt file not found (skill: ${skill}, path: ${skillPath})`);
45
+ }
46
+ }
47
+ const skillPath = resolveSkillPath(skill);
48
+ try {
49
+ return await readFile(skillPath, 'utf8');
50
+ }
51
+ catch {
52
+ throw new Error(`skill file missing from CLI installation — try reinstalling the CLI (looked for: ${skillPath})`);
53
+ }
54
+ }
55
+ function inferFenceLang(filename) {
56
+ if (!filename)
57
+ return 'json';
58
+ const ext = filename.split('.').pop()?.toLowerCase() ?? '';
59
+ const map = {
60
+ js: 'js',
61
+ mjs: 'js',
62
+ cjs: 'js',
63
+ ts: 'ts',
64
+ mts: 'ts',
65
+ cts: 'ts',
66
+ scss: 'scss',
67
+ sass: 'scss',
68
+ css: 'css',
69
+ json: 'json',
70
+ json5: 'json',
71
+ };
72
+ return map[ext] ?? 'text';
73
+ }
74
+ function buildPreamble(options) {
75
+ const { skill, mode, rawComponentsInline, rawTokensInline, rawTokensFilename, tokensInline, tokenMapInline, outDir } = options;
76
+ const outputFile = OUTPUT_FILES[skill];
77
+ const outputPath = join(resolve(outDir), outputFile);
78
+ const sections = [];
79
+ if (rawComponentsInline) {
80
+ sections.push(`Raw component data (JSON):\n\`\`\`json\n${rawComponentsInline}\n\`\`\``);
81
+ }
82
+ if (rawTokensInline) {
83
+ const lang = inferFenceLang(rawTokensFilename);
84
+ const label = rawTokensFilename ? `Raw token source (${rawTokensFilename})` : 'Raw token source';
85
+ sections.push(`${label}:\n\`\`\`${lang}\n${rawTokensInline}\n\`\`\``);
86
+ }
87
+ if (tokensInline) {
88
+ sections.push(`DTCG token data (for token kind lookups):\n\`\`\`json\n${tokensInline}\n\`\`\``);
89
+ }
90
+ if (tokenMapInline) {
91
+ sections.push(`Token-name sidecar (raw name → DTCG path):\n\`\`\`json\n${tokenMapInline}\n\`\`\``);
92
+ }
93
+ const inputBlock = sections.length > 0 ? `\n\n${sections.join('\n\n')}` : '';
94
+ if (mode === 'autonomous') {
95
+ if (skill === 'components') {
96
+ return buildComponentsAutonomousPreamble(inputBlock);
97
+ }
98
+ if (skill === 'select') {
99
+ return buildSelectAutonomousPreamble(inputBlock);
100
+ }
101
+ return buildTokensAutonomousPreamble(inputBlock);
102
+ }
103
+ else {
104
+ return `You are running as part of the experience-design-system-cli generate pipeline in INTERACTIVE mode. The developer is present and will answer your questions.
105
+
106
+ Your task: follow the skill instructions below, working collaboratively with the developer. Ask for confirmation on category corrections, prop exclusions, and grouping choices as the skill instructs.
107
+
108
+ All input data is provided inline below — do not read any additional files.${inputBlock}
109
+
110
+ Output:
111
+ Write the final ${outputFile} directly to: ${outputPath}`;
112
+ }
113
+ }
114
+ function buildComponentsAutonomousPreamble(inputBlock) {
115
+ return `You are running as part of the experience-design-system-cli generate pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
116
+
117
+ Context: You are classifying a React component for **Contentful Experience Orchestration**. The result is a Component Type — a schema that tells Contentful what a marketer can configure. Properties fall into three categories:
118
+ - **design**: controls how the component looks (variant, size, color, layout toggles)
119
+ - **content**: the data a content editor fills in (text, images, URLs, rich text)
120
+ - **state**: runtime behavioral flags (disabled, loading, expanded, identifiers)
121
+
122
+ For props with complex TypeScript types (named types, enums): reason from the prop name and type name to classify them. Do not automatically exclude a prop just because its type is a named reference — infer the likely values and classify it as enum if it controls appearance.
123
+
124
+ Your task: classify every prop and slot in the component below. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a "description" field on each tool call to document your reasoning so the developer can review it afterward.
125
+
126
+ All input data is provided inline below — do not read any additional files.${inputBlock}
127
+
128
+ ## Output protocol
129
+
130
+ Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each classification decision. The CLI reads your stdout line by line and writes each decision directly to the pipeline database.
131
+
132
+ The four tool calls you may emit are:
133
+
134
+ \`\`\`
135
+ {"tool":"classify_component","description":"<optional component-level description>","rationale":{"description":"<why this component is classified the way it is>","props":"<why these props were chosen>","slots":"<why these slots were chosen>"}}
136
+
137
+ {"tool":"classify_prop","prop":"<propName>","cdf_type":"<type>","cdf_category":"<category>","required":<bool>,"description":"<short customer-facing description>","reason":"<full internal rationale; not customer-facing>","values":["a","b"],"token_kind":"color","default":"<value>"}
138
+
139
+ {"tool":"exclude_prop","prop":"<propName>","reason":"<why excluded>"}
140
+
141
+ {"tool":"classify_slot","slot":"<slotName>","required":<bool>,"allowed_components":["ComponentName"],"description":"<reason>","rationale":"<why this slot was kept in the catalog>"}
142
+ \`\`\`
143
+
144
+ Rules:
145
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences around the lines.
146
+ - Every prop in the input must have exactly one call: either classify_prop or exclude_prop.
147
+ - Every slot in the input must have exactly one classify_slot call.
148
+ - Valid cdf_type values: string, richtext, media, enum, token, boolean
149
+ - Valid cdf_category values: content, design, state
150
+ - For enum type, always include "values" (non-empty string array).
151
+ - For token type, always include "token_kind" (DTCG \$type, e.g. "color").
152
+ - href and URL props → cdf_type "string", cdf_category "content". Do NOT use cdf_type "link" — it is not valid.
153
+ - Framework internals (ref, event handlers, test IDs) → exclude_prop.
154
+ - CSS design props (className, style, styles, positional/geometric props: top, bottom, left, right, rotation, offset, etc.) → classify_prop, cdf_type: "string", cdf_category: "design".
155
+ - On classify_component, "rationale" fields are operator-facing (read-only) but may surface in customer-facing exports. The "rationale.description" field is subject to the description content rules in the skill prompt (no internal initiative names). "rationale.props" and "rationale.slots" describe your reasoning about scope; "classify_slot.rationale" explains why each slot was kept.
156
+ - On classify_prop, "reason" is REQUIRED and is the LLM's internal rationale — shown to the developer reviewing the import, never to end-users. "description" is the customer-facing copy and is subject to the description content rules in the skill prompt. Keep them distinct: "description" is short and customer-facing; "reason" explains your reasoning in detail.
157
+ - You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
158
+ }
159
+ function buildSelectAutonomousPreamble(inputBlock) {
160
+ return `You are running as part of the experience-design-system-cli import pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
161
+
162
+ Your task: review the components provided below and decide whether each belongs in Contentful Experience Orchestration as a Component Type. The input is a JSON array — you may receive 1–N components in a single message. Emit one tool call per input component, named after the component. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a brief "reason" to document your reasoning for each decision.
163
+
164
+ Key rule: accept any component that renders visible UI — atoms, molecules, and organisms are all valid Component Types in Contentful Experience Orchestration. Reject only components that produce zero visual output: React hooks, pure context providers, A/B testing or variant-routing wrappers, analytics trackers, and security utilities. Do NOT reject a component because it has few props, is low-level, or has some A/B testing or personalization-related props mixed in — those props are handled in the generate step.
165
+
166
+ All input data is provided inline below — do not read any additional files.${inputBlock}
167
+
168
+ ## Output protocol
169
+
170
+ Do NOT write any files or emit any JSON blobs. Instead, emit JSON tool calls one per line to stdout. The CLI reads your stdout line by line.
171
+
172
+ The two tool calls — emit exactly one per input component:
173
+
174
+ \`\`\`
175
+ {"tool":"select_component","name":"<ComponentName>","reason":"<brief reason>"}
176
+
177
+ {"tool":"reject_component","name":"<ComponentName>","reason":"<brief reason>"}
178
+ \`\`\`
179
+
180
+ Rules:
181
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
182
+ - Emit exactly one tool call per input component. The "name" field must match a component name from the input array exactly. Tool calls may appear in any order.
183
+ - You may emit prose lines (not starting with {) to reason before each tool call — they are ignored by the parser.`;
184
+ }
185
+ function buildTokensAutonomousPreamble(inputBlock) {
186
+ return `You are running as part of the experience-design-system-cli generate pipeline in AUTONOMOUS mode. The developer is not present to answer questions.
187
+
188
+ Your task: classify every raw token from the input below into a DTCG token tree. Apply all judgment calls yourself — do not pause to ask for confirmation. Include a "description" field on each set_token call to document your reasoning.
189
+
190
+ All input data is provided inline below — do not read any additional files.${inputBlock}
191
+
192
+ ## Output protocol
193
+
194
+ Do NOT write any files or emit any JSON blobs. Instead, emit one JSON object per line to stdout for each token or group. The CLI reads your stdout line by line and writes each entry directly to the pipeline database.
195
+
196
+ The two tool calls you may emit are:
197
+
198
+ \`\`\`
199
+ {"tool":"set_group","path":"<dot.notation.path>","description":"<optional group description>"}
200
+
201
+ {"tool":"set_token","path":"<dot.notation.path>","type":"<DTCG type>","value":<value>,"description":"<reason>"}
202
+ \`\`\`
203
+
204
+ Rules:
205
+ - Emit exactly one JSON object per line. No multi-line JSON. No markdown fences.
206
+ - Emit a set_group call for every intermediate group node in the tree.
207
+ - Emit a set_token call for every leaf token.
208
+ - "path" is dot-notation, e.g. "colors.brand.primary" — no leading dots or slashes.
209
+ - "type" must be one of the 13 valid DTCG types: color, dimension, fontFamily, fontWeight, duration, cubicBezier, number, strokeStyle, border, transition, shadow, gradient, typography.
210
+ - "value" must be valid JSON (string, number, array, or object depending on the type). Do NOT wrap it in quotes if it is a complex type.
211
+ - Emit set_group calls before the set_token calls that fall under them.
212
+ - You may emit prose lines (not starting with {) anywhere — they are ignored by the parser and serve as your reasoning log.`;
213
+ }
@@ -4,7 +4,7 @@ import { resolveAutoFilter } from './auto-filter-resolve.js';
4
4
  import { resolveAgent, resolveModel } from './agent-model-resolve.js';
5
5
  import { addAgentModelOptions } from '../lib/agent-model-options.js';
6
6
  import { resolveCompositionMode } from '../lib/composition-mode.js';
7
- import { addCompositionOptions } from '../lib/command-options.js';
7
+ import { addAllowDeletionsOption, addCompositionOptions } from '../lib/command-options.js';
8
8
  import { isConflictMode } from '../runs/save-path-resolver.js';
9
9
  import { readExperiencesCredentials } from '../credentials-store.js';
10
10
  import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
@@ -46,6 +46,7 @@ export function registerImportCommand(program) {
46
46
  .option('--print-prompt', 'Print the generate components prompt without invoking the agent. Replaces the legacy --dry-run prompt-print behaviour on this command.')
47
47
  .option('--auto-accept-scope', 'Accept all extracted components without prompting (for scripted/non-TTY callers)');
48
48
  addCompositionOptions(cmd);
49
+ addAllowDeletionsOption(cmd);
49
50
  cmd
50
51
  .option('--composition-map <path>', 'Consume a hand-authored parent→children interchange map (implies --composite)')
51
52
  .option('--composition-agent', 'Opt into agentic mapping resolution when deterministic sources find no groups (implies --composite)')
@@ -136,6 +137,7 @@ export function registerImportCommand(program) {
136
137
  ...(opts.host ? { host: opts.host } : {}),
137
138
  interactive: interactiveTerminalSupported,
138
139
  ...(opts.force ? { force: true } : {}),
140
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
139
141
  });
140
142
  return;
141
143
  }
@@ -166,6 +168,7 @@ export function registerImportCommand(program) {
166
168
  ...(opts.saveAsNew ? { saveAsNew: true } : {}),
167
169
  ...(opts.outDir ? { outDir: opts.outDir } : {}),
168
170
  ...(opts.force ? { force: true } : {}),
171
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
169
172
  });
170
173
  return;
171
174
  }
@@ -297,6 +300,7 @@ export function registerImportCommand(program) {
297
300
  selectPromptPath: opts.selectPromptPath ?? creds.selectPromptPath,
298
301
  generatePromptPath: opts.generatePromptPath ?? creds.generatePromptPath,
299
302
  ...(opts.rawTokens ? { initialRawTokensPath: resolve(opts.rawTokens) } : {}),
303
+ allowDeletions: opts.allowDeletions === true,
300
304
  ...pickerProps,
301
305
  }));
302
306
  unmountInk = unmount;
@@ -357,6 +361,7 @@ export function registerImportCommand(program) {
357
361
  dryRun: dryRunForward,
358
362
  selectPromptPath: opts.selectPromptPath,
359
363
  autoRejectCycles: opts.autoRejectCycles ?? false,
364
+ allowDeletions: opts.allowDeletions ?? false,
360
365
  compositionMode: headlessCompositionMode,
361
366
  ...(opts.compositionMap ? { compositionMap: opts.compositionMap } : {}),
362
367
  ...(opts.compositionAgent ? { compositionAgent: true } : {}),
@@ -26,6 +26,7 @@ export interface PipelineOptions {
26
26
  selectPromptPath?: string;
27
27
  /** When true, auto-reject cycle participants and retry push instead of surfacing an error. */
28
28
  autoRejectCycles?: boolean;
29
+ allowDeletions?: boolean;
29
30
  compositionMode?: CompositionMode;
30
31
  compositionMap?: string;
31
32
  compositionAgent?: boolean;
@@ -418,6 +418,8 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
418
418
  pushArgs.push('--host', opts.host);
419
419
  if (opts.verbose)
420
420
  pushArgs.push('--verbose');
421
+ if (opts.allowDeletions)
422
+ pushArgs.push('--allow-deletions');
421
423
  pushArgs.push('--yes');
422
424
  const pushStepId = createStep(db, sessionId, 'apply push', {
423
425
  components: componentsPath,
@@ -66,5 +66,6 @@ export type WizardAppProps = {
66
66
  initialRawTokensPath?: string;
67
67
  initialRuns?: RunRecord[];
68
68
  onRunPicked?: (selection: RunPickerSelection) => void;
69
+ allowDeletions?: boolean;
69
70
  };
70
- export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope, autoRejectCycles, compositionMode, compositionMap, compositionAgent, compositionAgentMode, compositionRefresh, generateMap, promptOverrides, noCache, autoFilter, livePreview, noPush, noSave, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, }?: WizardAppProps): React.ReactElement;
71
+ export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope, autoRejectCycles, compositionMode, compositionMap, compositionAgent, compositionAgentMode, compositionRefresh, generateMap, promptOverrides, noCache, autoFilter, livePreview, noPush, noSave, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, allowDeletions, }?: WizardAppProps): React.ReactElement;
@@ -50,7 +50,7 @@ import { mergeAiDecisions } from './merge-ai-decisions.js';
50
50
  import { FinalReviewHost } from './final-review-host.js';
51
51
  import { runScopeGate } from './runScopeGate.js';
52
52
  import { buildAutoFilterErrorTail } from './auto-filter-error.js';
53
- import { checkAgentAuth } from '@contentful/experience-design-system-generation';
53
+ import { checkAgentAuth } from '../../generate/agent-runner.js';
54
54
  import { normalizePath } from '../path-utils.js';
55
55
  import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../../host-utils.js';
56
56
  import { writeExperiencesCredentials } from '../../credentials-store.js';
@@ -138,7 +138,7 @@ function logStep(entry) {
138
138
  appendFileSync(WIZARD_LOG, line);
139
139
  getDebugLogger().event('wizard', 'step', entry);
140
140
  }
141
- export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, autoRejectCycles = false, compositionMode = 'atomic', compositionMap, compositionAgent = false, compositionAgentMode, compositionRefresh = false, generateMap, promptOverrides, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, } = {}) {
141
+ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, autoRejectCycles = false, compositionMode = 'atomic', compositionMap, compositionAgent = false, compositionAgentMode, compositionRefresh = false, generateMap, promptOverrides, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, allowDeletions = false, } = {}) {
142
142
  const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
143
143
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
144
144
  const { stdout } = useStdout();
@@ -826,7 +826,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
826
826
  tokens = await readTokensFromPath('tokens', tokensPath);
827
827
  }
828
828
  let manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
829
- let preview = await client.previewImport(manifest);
829
+ let preview = await client.previewImport(manifest, allowDeletions);
830
830
  if (extractSessionId) {
831
831
  let needsRepreview = false;
832
832
  const db = openPipelineDb();
@@ -848,7 +848,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
848
848
  if (needsRepreview) {
849
849
  components = loadCDFComponents(db, extractSessionId);
850
850
  manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
851
- preview = await client.previewImport(manifest);
851
+ preview = await client.previewImport(manifest, allowDeletions);
852
852
  }
853
853
  }
854
854
  finally {
@@ -942,7 +942,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
942
942
  });
943
943
  }
944
944
  };
945
- const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, preview) => {
945
+ const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, allowDeletions, preview) => {
946
946
  if (shouldRefusePush(state)) {
947
947
  update(buildSkippedPushTransition());
948
948
  return;
@@ -982,7 +982,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
982
982
  environmentId,
983
983
  host: resolvedHost,
984
984
  });
985
- let operation = await client.applyImport(manifest, acknowledgeBreakingChanges);
985
+ let operation = await client.applyImport(manifest, { acknowledgeBreakingChanges, allowDeletions });
986
986
  try {
987
987
  logStep({
988
988
  applyResponse: {
@@ -1433,7 +1433,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1433
1433
  return (_jsx(RunningStep, { stepNumber: stepNum, totalSteps: totalSteps, title: "Generating definitions", description: `${formatAcceptanceSummary({ accepted: state.acceptedCount, autoRejected: state.autoRejectedCount })} ${state.agent} is mapping your TypeScript types to Contentful's CDF format.${hasTokens ? ' Using your design tokens for prop resolution.' : ''}`, detail: progressDetail }));
1434
1434
  }
1435
1435
  case 'final-review': {
1436
- return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, compositionMode: compositionMode, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, onFinalize: (accepted, rejected, unresolved) => {
1436
+ return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, compositionMode: compositionMode, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, allowDeletions: allowDeletions, onFinalize: (accepted, rejected, unresolved) => {
1437
1437
  process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected} Unresolved: ${unresolved}\n`);
1438
1438
  let acceptedCount = accepted;
1439
1439
  const detectAcceptedCycles = () => {
@@ -1601,8 +1601,8 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1601
1601
  // present-but-empty (only $schema), so editing would dead-end on the
1602
1602
  // "No generated definitions found" screen.
1603
1603
  const editableComponentCount = Object.keys(state.manifest?.componentsManifest ?? {}).filter((k) => k !== '$schema').length;
1604
- return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, onConfirm: (acknowledge) => {
1605
- void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, state.serverPreview);
1604
+ return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, allowDeletions: allowDeletions, onConfirm: (acknowledge, deleteMissing) => {
1605
+ void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, deleteMissing, state.serverPreview);
1606
1606
  }, ...(editableComponentCount > 0 ? { onEdit: () => void runEditFromPreview() } : {}), onSaveFiles: () => {
1607
1607
  void startSaveFlow();
1608
1608
  }, onQuit: () => process.exit(0) }));
@@ -14,5 +14,6 @@ export type FinalReviewHostProps = {
14
14
  host?: string;
15
15
  tokensPath?: string;
16
16
  initialFinalizeError?: string | null;
17
+ allowDeletions?: boolean;
17
18
  };
18
- export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: FinalReviewHostProps): React.ReactElement;
19
+ export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }: FinalReviewHostProps): React.ReactElement;
@@ -4,7 +4,7 @@ import { PALETTE } from '../../analyze/select/tui/theme.js';
4
4
  import React from 'react';
5
5
  import { GenerateReviewStep } from './steps/GenerateReviewStep.js';
6
6
  import { AtomicGenerateReviewStep } from './steps/AtomicGenerateReviewStep.js';
7
- export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode = 'atomic', onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }) {
7
+ export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode = 'atomic', onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }) {
8
8
  if (!extractSessionId) {
9
9
  return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: PALETTE.error, children: "Error: no session ID \u2014 cannot load generated definitions." }) }));
10
10
  }
@@ -15,7 +15,7 @@ export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept,
15
15
  // passes projectSlotGraph to FieldEditor and never walks closures/cycles, so
16
16
  // slot-composition editing and every hierarchy affordance stay absent.
17
17
  const StepComponent = compositionMode === 'atomic' ? AtomicGenerateReviewStep : GenerateReviewStep;
18
- return (_jsx(StepComponent, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError }));
18
+ return (_jsx(StepComponent, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError, ...(compositionMode !== 'atomic' ? { allowDeletions } : {}) }));
19
19
  }
20
20
  function FinalReviewAutoAccept({ generatedCount, onFinalize, }) {
21
21
  React.useEffect(() => {
@@ -26,6 +26,9 @@ export type RunLivePreviewOptions = {
26
26
  * full delete. Lets the Finalize dialog show exactly what the accepted push
27
27
  * would delete, independent of the session's on-disk generated rows. */
28
28
  acceptedKeys?: ReadonlySet<string>;
29
+ /** Forwarded verbatim to `previewImport`. Governs whether the response
30
+ * includes removed entities or a suppressed-count summary instead. */
31
+ allowDeletions?: boolean;
29
32
  };
30
33
  /**
31
34
  * Pure async helper used by `useLivePreview` to re-fire `previewImport` after a
@@ -60,7 +60,10 @@ export async function runLivePreview(opts) {
60
60
  timeoutHandle = setTimeout(() => reject(new TimeoutError()), timeoutMs);
61
61
  });
62
62
  try {
63
- const response = (await Promise.race([client.previewImport(manifest), timeoutPromise]));
63
+ const response = (await Promise.race([
64
+ client.previewImport(manifest, opts.allowDeletions === true),
65
+ timeoutPromise,
66
+ ]));
64
67
  if (process.env['EDS_VERBOSE']) {
65
68
  const durationMs = Date.now() - startedAt;
66
69
  try {
@@ -11,6 +11,7 @@ type GenerateReviewStepProps = {
11
11
  host?: string;
12
12
  tokensPath?: string;
13
13
  initialFinalizeError?: string | null;
14
+ allowDeletions?: boolean;
14
15
  };
15
16
  export declare function sortComponentsForSidebar<T extends {
16
17
  key: string;
@@ -33,4 +34,4 @@ export interface BreakingRow {
33
34
  }
34
35
  export declare function buildBreakingRows(breakingChanges: BreakingComponent[]): BreakingRow[];
35
36
  export declare function deriveBreakingChanges(response: ServerPreviewResponse): BreakingComponent[];
36
- export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: GenerateReviewStepProps): React.ReactElement;
37
+ export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }: GenerateReviewStepProps): React.ReactElement;
@@ -174,7 +174,7 @@ export function deriveBreakingChanges(response) {
174
174
  }
175
175
  return out;
176
176
  }
177
- export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', initialFinalizeError = null, }) {
177
+ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', initialFinalizeError = null, allowDeletions = false, }) {
178
178
  const { stdout } = useStdout();
179
179
  const terminalWidth = stdout?.columns ?? 80;
180
180
  const [components, setComponents] = useState([]);
@@ -271,9 +271,8 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
271
271
  cmaToken,
272
272
  host,
273
273
  onResult: handleLivePreviewResult,
274
- // With nothing accepted, preview the delete-all diff so the review UI shows
275
- // which existing components a push would remove (instead of an empty preview).
276
274
  deleteAllComponents: acceptedCountForPreview === 0,
275
+ allowDeletions,
277
276
  });
278
277
  const SPINNER_FRAMES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
279
278
  const [spinnerTick, setSpinnerTick] = useState(0);
@@ -352,6 +351,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
352
351
  cmaToken,
353
352
  host,
354
353
  acceptedKeys: new Set(components.filter((c) => c.status === 'accepted').map((c) => c.key)),
354
+ allowDeletions,
355
355
  });
356
356
  const handleFinalizeConfirm = () => {
357
357
  const acceptedCount = components.filter((c) => c.status === 'accepted').length;
@@ -12,10 +12,14 @@ type WizardPreviewStepProps = {
12
12
  environmentId: string;
13
13
  stepNumber: number;
14
14
  totalSteps: number;
15
- onConfirm: (acknowledge: boolean) => void;
15
+ /** The value the preview was actually fetched with — not a hint. When
16
+ * `false`, the server never returned removed entities, so there is
17
+ * nothing to render item-by-item or toggle over. */
18
+ allowDeletions?: boolean;
19
+ onConfirm: (acknowledge: boolean, allowDeletions: boolean) => void;
16
20
  onEdit?: () => void;
17
21
  onSaveFiles?: () => void;
18
22
  onQuit: () => void;
19
23
  };
20
- export declare function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onEdit, onSaveFiles, onQuit, }: WizardPreviewStepProps): React.ReactElement;
24
+ export declare function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, allowDeletions: fetchedAllowDeletions, onConfirm, onEdit, onSaveFiles, onQuit, }: WizardPreviewStepProps): React.ReactElement;
21
25
  export {};
@@ -89,10 +89,14 @@ export function buildPreviewDiffLines(preview) {
89
89
  }
90
90
  return lines;
91
91
  }
92
- export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onEdit, onSaveFiles, onQuit, }) {
92
+ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, allowDeletions: fetchedAllowDeletions = false, onConfirm, onEdit, onSaveFiles, onQuit, }) {
93
93
  const breakingWithImpact = hasBreakingChangesWithImpact(preview);
94
94
  const [diffExpanded, setDiffExpanded] = useState(false);
95
95
  const [scrollOffset, setScrollOffset] = useState(0);
96
+ // Local state exists only to let the user opt OUT of a deletion the fetch
97
+ // already surfaced — it can never turn true when the fetch used false,
98
+ // because there's nothing in `preview` to reveal in that case.
99
+ const [allowDeletions, setAllowDeletions] = useState(fetchedAllowDeletions);
96
100
  const { stdout } = useStdout();
97
101
  const terminalRows = stdout?.rows ?? 40;
98
102
  const viewportHeight = Math.max(terminalRows - 14, 10);
@@ -105,9 +109,14 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
105
109
  }));
106
110
  }, [diffExpanded, preview]);
107
111
  const maxScroll = Math.max(0, allDiffLines.length - viewportHeight);
112
+ const removedCount = preview.components.removed.length + preview.tokens.removed.length;
108
113
  useImmediateInput((input, key) => {
109
114
  if (key.return) {
110
- onConfirm(breakingWithImpact);
115
+ onConfirm(breakingWithImpact, allowDeletions);
116
+ return;
117
+ }
118
+ if ((input === 'x' || input === 'X') && fetchedAllowDeletions && removedCount > 0) {
119
+ setAllowDeletions((prev) => !prev);
111
120
  return;
112
121
  }
113
122
  if (input === 'd' || input === 'D') {
@@ -156,5 +165,5 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
156
165
  })] })), components.changed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [components.changed.length, " will be updated"] })] }), components.changed.map((item, i) => {
157
166
  const isBreaking = item.changeClassification?.classification === 'breaking';
158
167
  return (_jsxs(Text, { color: isBreaking ? PALETTE.error : PALETTE.warning, children: [' ', isBreaking ? '⚠' : '~', " ", item.current.name] }, `chg-${i}`));
159
- })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [components.removed.length, " will be removed"] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: PALETTE.error, children: [' ', "\u2717 ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [tokens.removed.length, " will be removed"] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: PALETTE.error, bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
168
+ })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [components.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, dimColor: !allowDeletions, children: [' ', allowDeletions ? '✗' : '⊘', " ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [tokens.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), !fetchedAllowDeletions && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \u2298" }), _jsx(Text, { children: "Deletions are hidden by default \u2014 rerun with --allow-deletions to see them." })] })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: PALETTE.error, bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), allowDeletions && removedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: PALETTE.error, bold: true, children: ["\u26A0 ", removedCount, " missing ", removedCount === 1 ? 'entity' : 'entities', " will be permanently deleted. Press Enter to confirm."] }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), fetchedAllowDeletions && removedCount > 0 && (_jsxs(Text, { dimColor: true, children: ["[x] ", allowDeletions ? '[✓]' : '[ ]', " Also delete ", removedCount, " ", removedCount === 1 ? 'entity' : 'entities'] })), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
160
169
  }
@@ -11,6 +11,8 @@ export type UseFinalizePreviewOptions = {
11
11
  host: string;
12
12
  /** Component keys the operator has accepted; drives the scoped preview. */
13
13
  acceptedKeys: ReadonlySet<string>;
14
+ /** Forwarded verbatim to `runLivePreview`/`previewImport`. */
15
+ allowDeletions?: boolean;
14
16
  };
15
17
  export type UseFinalizePreviewReturn = {
16
18
  status: FinalizePreviewStatus;
@@ -12,6 +12,8 @@ export type UseLivePreviewOptions = {
12
12
  /** Preview an empty-but-present manifest (delete-all diff) when nothing is
13
13
  * accepted, so the final-review UI can show what a push would delete. */
14
14
  deleteAllComponents?: boolean;
15
+ /** Forwarded verbatim to `runLivePreview`/`previewImport`. */
16
+ allowDeletions?: boolean;
15
17
  };
16
18
  export type LivePreviewStatus = 'idle' | 'running';
17
19
  export type UseLivePreviewReturn = {
@@ -82,6 +82,7 @@ export function useLivePreview(opts) {
82
82
  host: current.host,
83
83
  generation,
84
84
  deleteAllComponents: current.deleteAllComponents === true,
85
+ allowDeletions: current.allowDeletions === true,
85
86
  });
86
87
  // Discard stale responses (generation tag).
87
88
  if (result.generation !== latestRef.current)
@@ -1,4 +1,4 @@
1
- import { AGENT_NAMES } from '@contentful/experience-design-system-generation';
1
+ import { AGENT_NAMES } from './agent-names.js';
2
2
  export const AGENT_DESCRIPTION = `Agent to use: ${AGENT_NAMES.join(', ')} (defaults to value saved by experiences setup)`;
3
3
  export const MODEL_DESCRIPTION = 'Model to use (defaults to a lightweight per-agent model; override with EDS_AGENT_MODEL_<AGENT>)';
4
4
  /**
@@ -0,0 +1,4 @@
1
+ export declare const AGENT_NAMES: readonly ["claude", "codex", "opencode", "cursor"];
2
+ export type AgentName = (typeof AGENT_NAMES)[number];
3
+ export declare const DEFAULT_AGENT_NAME: AgentName;
4
+ export declare function isAgentName(value: string): value is AgentName;
@@ -0,0 +1,5 @@
1
+ export const AGENT_NAMES = ['claude', 'codex', 'opencode', 'cursor'];
2
+ export const DEFAULT_AGENT_NAME = 'claude';
3
+ export function isAgentName(value) {
4
+ return AGENT_NAMES.includes(value);
5
+ }
@@ -3,3 +3,4 @@ export declare function addArtifactInputOptions(cmd: Command): Command;
3
3
  export declare function addContentfulTargetOptions(cmd: Command): Command;
4
4
  export declare function addCompositionOptions(cmd: Command): Command;
5
5
  export declare function addSelectionOptions(cmd: Command): Command;
6
+ export declare function addAllowDeletionsOption(cmd: Command): Command;
@@ -25,3 +25,6 @@ export function addSelectionOptions(cmd) {
25
25
  .option('--select <pattern>', 'Select entities by ID pattern (repeatable)', collectOptionValue, [])
26
26
  .option('--deselect <pattern>', 'Deselect entities by ID pattern (repeatable)', collectOptionValue, []);
27
27
  }
28
+ export function addAllowDeletionsOption(cmd) {
29
+ return cmd.option('--allow-deletions', 'Allow the push to delete remote ComponentTypes/DesignTokens missing from the manifest (default: skip them)');
30
+ }
@@ -26,5 +26,7 @@ export type ModifyLauncherInput = {
26
26
  initialHost?: string;
27
27
  /** Pre-fill CMA token (from credentials.json / env). */
28
28
  initialCmaToken?: string;
29
+ /** From `--allow-deletions` flag. Forwarded to wizard's push step. */
30
+ allowDeletions?: boolean;
29
31
  };
30
32
  export declare function launchModifyWizard(input: ModifyLauncherInput): Promise<void>;