@contentful/experience-design-system-cli 2.12.0 → 2.12.1-dev-build-589b615.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 (142) hide show
  1. package/README.md +214 -357
  2. package/dist/package.json +1 -1
  3. package/dist/src/analyze/extract/astro.js +9 -0
  4. package/dist/src/analyze/extract/react.js +23 -0
  5. package/dist/src/analyze/extract/stencil.js +3 -0
  6. package/dist/src/analyze/extract/vue.js +6 -0
  7. package/dist/src/analyze/extract/web-components.js +10 -0
  8. package/dist/src/analyze/pre-classify.d.ts +1 -1
  9. package/dist/src/analyze/pre-classify.js +3 -3
  10. package/dist/src/analyze/select/persistence.d.ts +13 -0
  11. package/dist/src/analyze/select/persistence.js +38 -0
  12. package/dist/src/analyze/select/preview-annotations.d.ts +26 -0
  13. package/dist/src/analyze/select/preview-annotations.js +62 -0
  14. package/dist/src/analyze/select/tui/App.js +5 -2
  15. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +30 -0
  16. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +137 -0
  17. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +58 -1
  18. package/dist/src/analyze/select/tui/components/FieldEditor.js +637 -161
  19. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +1 -1
  20. package/dist/src/analyze/select/tui/components/JsonPanel.js +5 -1
  21. package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +34 -0
  22. package/dist/src/analyze/select/tui/components/RationalePanel.js +92 -0
  23. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +13 -1
  24. package/dist/src/analyze/select/tui/components/Sidebar.js +27 -3
  25. package/dist/src/analyze/select/tui/hooks/scroll-offset.d.ts +29 -0
  26. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +26 -0
  27. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  28. package/dist/src/analyze/select-agent/command.js +259 -45
  29. package/dist/src/analyze/select-agent/show-rationale.d.ts +35 -0
  30. package/dist/src/analyze/select-agent/show-rationale.js +92 -0
  31. package/dist/src/apply/command.js +7 -3
  32. package/dist/src/apply/tui/ServerApplyView.d.ts +3 -1
  33. package/dist/src/apply/tui/ServerApplyView.js +3 -2
  34. package/dist/src/credentials-store.d.ts +5 -0
  35. package/dist/src/credentials-store.js +7 -1
  36. package/dist/src/generate/agent-runner.d.ts +14 -0
  37. package/dist/src/generate/agent-runner.js +16 -0
  38. package/dist/src/generate/command.d.ts +6 -0
  39. package/dist/src/generate/command.js +45 -10
  40. package/dist/src/generate/progress.d.ts +9 -0
  41. package/dist/src/generate/progress.js +11 -0
  42. package/dist/src/generate/prompt-builder.d.ts +8 -0
  43. package/dist/src/generate/prompt-builder.js +22 -11
  44. package/dist/src/import/agent-model-resolve.d.ts +23 -0
  45. package/dist/src/import/agent-model-resolve.js +35 -0
  46. package/dist/src/import/auto-filter-resolve.d.ts +12 -0
  47. package/dist/src/import/auto-filter-resolve.js +16 -0
  48. package/dist/src/import/command.js +248 -8
  49. package/dist/src/import/orchestrator.d.ts +2 -0
  50. package/dist/src/import/orchestrator.js +11 -3
  51. package/dist/src/import/print-prompt.d.ts +35 -0
  52. package/dist/src/import/print-prompt.js +31 -0
  53. package/dist/src/import/tui/CustomPromptBanner.d.ts +11 -0
  54. package/dist/src/import/tui/CustomPromptBanner.js +7 -0
  55. package/dist/src/import/tui/WizardApp.d.ts +112 -8
  56. package/dist/src/import/tui/WizardApp.js +814 -333
  57. package/dist/src/import/tui/auto-filter-error.d.ts +2 -0
  58. package/dist/src/import/tui/auto-filter-error.js +29 -0
  59. package/dist/src/import/tui/final-review-host.d.ts +15 -0
  60. package/dist/src/import/tui/final-review-host.js +20 -0
  61. package/dist/src/import/tui/push-decision-gate-helpers.d.ts +8 -0
  62. package/dist/src/import/tui/push-decision-gate-helpers.js +8 -0
  63. package/dist/src/import/tui/push-progress.d.ts +23 -0
  64. package/dist/src/import/tui/push-progress.js +14 -0
  65. package/dist/src/import/tui/run-print-files-helpers.d.ts +26 -0
  66. package/dist/src/import/tui/run-print-files-helpers.js +8 -0
  67. package/dist/src/import/tui/run-teaser.d.ts +5 -0
  68. package/dist/src/import/tui/run-teaser.js +9 -0
  69. package/dist/src/import/tui/runLivePreview.d.ts +35 -0
  70. package/dist/src/import/tui/runLivePreview.js +73 -0
  71. package/dist/src/import/tui/runScopeGate.d.ts +22 -0
  72. package/dist/src/import/tui/runScopeGate.js +28 -0
  73. package/dist/src/import/tui/scope-gate-host.d.ts +21 -0
  74. package/dist/src/import/tui/scope-gate-host.js +20 -0
  75. package/dist/src/import/tui/spawn-generate.d.ts +33 -0
  76. package/dist/src/import/tui/spawn-generate.js +53 -0
  77. package/dist/src/import/tui/steps/CredentialsStep.d.ts +25 -1
  78. package/dist/src/import/tui/steps/CredentialsStep.js +31 -3
  79. package/dist/src/import/tui/steps/DoneStep.d.ts +6 -1
  80. package/dist/src/import/tui/steps/DoneStep.js +10 -4
  81. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +29 -2
  82. package/dist/src/import/tui/steps/GenerateReviewStep.js +412 -48
  83. package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +19 -0
  84. package/dist/src/import/tui/steps/PushDecisionGateStep.js +76 -0
  85. package/dist/src/import/tui/steps/PushingStep.d.ts +10 -0
  86. package/dist/src/import/tui/steps/PushingStep.js +40 -0
  87. package/dist/src/import/tui/steps/ScopeGateStep.d.ts +23 -0
  88. package/dist/src/import/tui/steps/ScopeGateStep.js +243 -0
  89. package/dist/src/import/tui/steps/TokenInputStep.js +20 -3
  90. package/dist/src/import/tui/useLivePreview.d.ts +35 -0
  91. package/dist/src/import/tui/useLivePreview.js +120 -0
  92. package/dist/src/import/tui/wizard-generate-progress.d.ts +21 -0
  93. package/dist/src/import/tui/wizard-generate-progress.js +25 -0
  94. package/dist/src/import/tui/wizard-save-flow.d.ts +37 -0
  95. package/dist/src/import/tui/wizard-save-flow.js +20 -0
  96. package/dist/src/import/tui/wizard-state-transitions.d.ts +80 -0
  97. package/dist/src/import/tui/wizard-state-transitions.js +79 -0
  98. package/dist/src/lib/contentful-urls.d.ts +34 -0
  99. package/dist/src/lib/contentful-urls.js +41 -0
  100. package/dist/src/program.js +2 -0
  101. package/dist/src/runs/export-helpers.d.ts +30 -0
  102. package/dist/src/runs/export-helpers.js +59 -0
  103. package/dist/src/runs/fingerprint.d.ts +47 -0
  104. package/dist/src/runs/fingerprint.js +75 -0
  105. package/dist/src/runs/ls-command.d.ts +12 -0
  106. package/dist/src/runs/ls-command.js +137 -0
  107. package/dist/src/runs/modify-launcher.d.ts +24 -0
  108. package/dist/src/runs/modify-launcher.js +36 -0
  109. package/dist/src/runs/path-prompt.d.ts +8 -0
  110. package/dist/src/runs/path-prompt.js +72 -0
  111. package/dist/src/runs/push-creds-prompt.d.ts +25 -0
  112. package/dist/src/runs/push-creds-prompt.js +39 -0
  113. package/dist/src/runs/push-helpers.d.ts +29 -0
  114. package/dist/src/runs/push-helpers.js +69 -0
  115. package/dist/src/runs/replay-helpers.d.ts +67 -0
  116. package/dist/src/runs/replay-helpers.js +148 -0
  117. package/dist/src/runs/resolve-run-target.d.ts +12 -0
  118. package/dist/src/runs/resolve-run-target.js +45 -0
  119. package/dist/src/runs/run-picker-mount.d.ts +46 -0
  120. package/dist/src/runs/run-picker-mount.js +72 -0
  121. package/dist/src/runs/run-picker.d.ts +17 -0
  122. package/dist/src/runs/run-picker.js +145 -0
  123. package/dist/src/runs/save-conflict.d.ts +8 -0
  124. package/dist/src/runs/save-conflict.js +56 -0
  125. package/dist/src/runs/save-path-resolver.d.ts +55 -0
  126. package/dist/src/runs/save-path-resolver.js +71 -0
  127. package/dist/src/runs/staleness.d.ts +38 -0
  128. package/dist/src/runs/staleness.js +140 -0
  129. package/dist/src/runs/store.d.ts +78 -0
  130. package/dist/src/runs/store.js +136 -0
  131. package/dist/src/session/cache-keys.d.ts +15 -0
  132. package/dist/src/session/cache-keys.js +38 -0
  133. package/dist/src/session/db.d.ts +89 -3
  134. package/dist/src/session/db.js +447 -104
  135. package/dist/src/setup/auto-filter-prompt.d.ts +13 -0
  136. package/dist/src/setup/auto-filter-prompt.js +24 -0
  137. package/dist/src/setup/command.d.ts +7 -0
  138. package/dist/src/setup/command.js +58 -2
  139. package/dist/src/types.d.ts +6 -0
  140. package/package.json +2 -2
  141. package/skills/generate-components.md +54 -6
  142. package/skills/select-components.md +13 -4
@@ -1,7 +1,12 @@
1
1
  import { resolve, join } from 'node:path';
2
2
  import { runPipeline } from './orchestrator.js';
3
+ import { resolveAutoFilter } from './auto-filter-resolve.js';
4
+ import { resolveAgent, resolveModel } from './agent-model-resolve.js';
3
5
  import { readExperiencesCredentials } from '../credentials-store.js';
4
6
  import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
7
+ import { replayRun, modifyRun } from '../runs/replay-helpers.js';
8
+ import { resolvePromptFlags } from './print-prompt.js';
9
+ import { shouldShowRunPicker } from '../runs/run-picker-mount.js';
5
10
  export function registerImportCommand(program) {
6
11
  program
7
12
  .command('import')
@@ -11,9 +16,10 @@ export function registerImportCommand(program) {
11
16
  .option('--cma-token <token>', 'CMA personal access token (or set CONTENTFUL_MANAGEMENT_TOKEN)')
12
17
  .option('--project <path>', 'Path to the project root to analyze', '.')
13
18
  .option('--out <path>', 'Output directory for pipeline artifacts')
14
- .option('--agent <name>', 'Agent to use for generate components', 'claude')
19
+ .option('--agent <name>', 'Agent to use for generate components (overrides credentials.json; falls back to "claude")')
15
20
  .option('--model <name>', 'Model to use for generate components (defaults to a small/fast model per agent)')
16
21
  .option('--tokens <path>', 'Path to a DTCG tokens.json file to push alongside generated components')
22
+ .option('--raw-tokens <path>', 'Path to a raw token source file (SCSS, CSS variables, JS/TS, Style Dictionary, etc.) to classify and import alongside components. Bypasses the interactive token prompt.')
17
23
  .option('--select-all', 'Select all extracted components for generation (default)')
18
24
  .option('--select <pattern>', 'Select components matching pattern (repeatable)', (v, a) => [...a, v], [])
19
25
  .option('--deselect <pattern>', 'Deselect components matching pattern (repeatable)', (v, a) => [...a, v], [])
@@ -27,8 +33,153 @@ export function registerImportCommand(program) {
27
33
  .option('--exclude-invalid', 'Automatically reject components with validation errors (empty names, collisions)')
28
34
  .option('--viewports <path>', 'JSON file with viewport array (passed to apply push)')
29
35
  .option('--host <url>', 'Override API base URL (passed to apply push)')
30
- .option('--dry-run', 'Print generate components prompt without invoking the agent')
36
+ .option('--dry-run', "(deprecated, will change semantics in a future release) Print generate components prompt without invoking the agent. Use --print-prompt for the same behaviour explicitly, or '--dry-run --no-push' for the upcoming manifest-preview semantics.")
37
+ .option('--print-prompt', 'Print the generate components prompt without invoking the agent. Replaces the legacy --dry-run prompt-print behaviour on this command.')
38
+ .option('--auto-accept-scope', 'Accept all extracted components without prompting (for scripted/non-TTY callers)')
39
+ .option('--auto-filter', 'Force the AI auto-filter ON (overrides the credentials.json autoFilter preference)')
40
+ .option('--no-auto-filter', 'Skip the automatic AI pre-filter; jump straight to manual scope-gate (overrides the credentials.json autoFilter preference; no-op when paired with --auto-accept-scope)')
41
+ .option('--no-live-preview', 'Skip the automatic preview re-run after each FieldEditor save (no-op when paired with --auto-accept-scope)')
42
+ .option('--no-push', 'Run extract → scope-gate → generate → final-review and exit without pushing to Contentful (no credentials prompt; live preview disabled)')
43
+ .option('--no-save', 'Push without writing components.json / tokens.json to disk (default: save AND push)')
44
+ .option('--out-dir <path>', 'Save components.json / tokens.json to this directory; bypasses the inline save-path prompt')
45
+ .option('--on-conflict <mode>', "How to handle existing components.json / tokens.json at the save path: 'overwrite' replaces files, 'skip' writes to a timestamped subdirectory, 'fail' exits non-zero. Skips the wizard's interactive conflict gate when set.", (value) => {
46
+ if (value !== 'overwrite' && value !== 'skip' && value !== 'fail') {
47
+ process.stderr.write(`Error: invalid --on-conflict value '${value}'. Use one of: overwrite, skip, fail.\n`);
48
+ process.exit(1);
49
+ }
50
+ return value;
51
+ })
52
+ .option('--select-prompt-path <path>', 'Path to a custom .md skill prompt for analyze select-agent (bypasses bundled invariants)')
53
+ .option('--generate-prompt-path <path>', 'Path to a custom .md skill prompt for generate components (bypasses bundled invariants)')
54
+ .option('--push-from-run <id-or-path>', "Push a prior run's recorded pipeline.db session to Contentful WITHOUT writing components.json / tokens.json to disk. Accepts a run-id or filesystem path that matches a recorded savePath. Credentials are resolved from flags, then the run record, then 'experiences setup', then (in a TTY) an interactive prompt.")
55
+ .option('--modify <id-or-path>', 'Re-open the wizard at final-review with a prior run pre-populated for field edits. Accepts a run-id or filesystem path. Pair with --overwrite or --save-as-new to pick the save mode.')
56
+ .option('--overwrite', "Only valid with --modify: save back to the run's recorded savePath")
57
+ .option('--save-as-new', 'Only valid with --modify: always save to a new path (prompts for one)')
58
+ .option('--force', 'Bypass staleness checks when paired with --push-from-run or --modify.')
31
59
  .action(async (opts) => {
60
+ // ── --push-from-run handling ───────────────────────────────────────
61
+ // Push-only replay of a prior run. Mutex checks happen *before* any
62
+ // side effects (no DB reads, no wizard render).
63
+ if (opts.pushFromRun !== undefined) {
64
+ if (opts.modify !== undefined) {
65
+ process.stderr.write('Error: --push-from-run and --modify are mutually exclusive. --push-from-run pushes the recorded session; --modify re-opens the wizard for edits.\n');
66
+ process.exit(1);
67
+ return;
68
+ }
69
+ if (opts.project !== '.') {
70
+ process.stderr.write('Error: --push-from-run and --project are mutually exclusive. The project path is read from the recorded run.\n');
71
+ process.exit(1);
72
+ return;
73
+ }
74
+ if (opts.save === false) {
75
+ process.stderr.write('Error: --push-from-run and --no-save are mutually exclusive. --push-from-run never writes to disk.\n');
76
+ process.exit(1);
77
+ return;
78
+ }
79
+ if (opts.push === false) {
80
+ process.stderr.write('Error: --push-from-run and --no-push are mutually exclusive. Pushing is the whole point of --push-from-run.\n');
81
+ process.exit(1);
82
+ return;
83
+ }
84
+ if (opts.overwrite || opts.saveAsNew) {
85
+ process.stderr.write('Error: --overwrite and --save-as-new only apply with --modify.\n');
86
+ process.exit(1);
87
+ return;
88
+ }
89
+ try {
90
+ await replayRun({
91
+ runIdOrPath: opts.pushFromRun,
92
+ ...(opts.spaceId ? { spaceId: opts.spaceId } : {}),
93
+ ...(opts.environmentId ? { environmentId: opts.environmentId } : {}),
94
+ ...(opts.cmaToken ? { cmaToken: opts.cmaToken } : {}),
95
+ ...(opts.host ? { host: opts.host } : {}),
96
+ interactive: !!process.stdout.isTTY,
97
+ ...(opts.force ? { force: true } : {}),
98
+ });
99
+ return;
100
+ }
101
+ catch (err) {
102
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
103
+ process.exit(1);
104
+ return;
105
+ }
106
+ }
107
+ // ── --modify handling ──────────────────────────────────────────────
108
+ if (opts.modify !== undefined) {
109
+ if (opts.project !== '.') {
110
+ process.stderr.write('Error: --modify and --project are mutually exclusive. The project path is read from the recorded run.\n');
111
+ process.exit(1);
112
+ return;
113
+ }
114
+ if (opts.overwrite && opts.saveAsNew) {
115
+ process.stderr.write('Error: --overwrite and --save-as-new are mutually exclusive.\n');
116
+ process.exit(1);
117
+ return;
118
+ }
119
+ try {
120
+ await modifyRun({
121
+ runIdOrPath: opts.modify,
122
+ ...(opts.overwrite ? { overwrite: true } : {}),
123
+ ...(opts.saveAsNew ? { saveAsNew: true } : {}),
124
+ ...(opts.outDir ? { outDir: opts.outDir } : {}),
125
+ ...(opts.force ? { force: true } : {}),
126
+ });
127
+ return;
128
+ }
129
+ catch (err) {
130
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
131
+ process.exit(1);
132
+ return;
133
+ }
134
+ }
135
+ if (opts.overwrite || opts.saveAsNew) {
136
+ process.stderr.write('Error: --overwrite and --save-as-new require --modify.\n');
137
+ process.exit(1);
138
+ return;
139
+ }
140
+ if (opts.save === false && opts.push === false) {
141
+ process.stderr.write('Error: --no-save and --no-push together would do nothing. Pick one or neither.\n');
142
+ process.exit(1);
143
+ return;
144
+ }
145
+ if (opts.save === false && opts.outDir) {
146
+ process.stderr.write('Error: --no-save and --out-dir are mutually exclusive. --no-save disables disk writes; --out-dir picks a directory for them.\n');
147
+ process.exit(1);
148
+ return;
149
+ }
150
+ if (opts.save === false && opts.onConflict) {
151
+ process.stderr.write('Error: --no-save and --on-conflict are mutually exclusive. --no-save disables disk writes; --on-conflict only applies when files are being written.\n');
152
+ process.exit(1);
153
+ return;
154
+ }
155
+ // ── --raw-tokens validation ─────────────────────────────────────────
156
+ // The raw-tokens path is a source file the wizard hands to
157
+ // `generate tokens --raw-tokens <path>`. Validate at parse time so
158
+ // operators get an immediate error before the wizard renders.
159
+ if (opts.rawTokens !== undefined) {
160
+ if (opts.tokens !== undefined) {
161
+ process.stderr.write('Error: --raw-tokens and --tokens are mutually exclusive: --raw-tokens is the source file to classify, --tokens is a pre-classified DTCG sidecar.\n');
162
+ process.exit(1);
163
+ return;
164
+ }
165
+ const { access } = await import('node:fs/promises');
166
+ try {
167
+ await access(opts.rawTokens);
168
+ }
169
+ catch {
170
+ process.stderr.write(`Error: --raw-tokens: file not found: ${opts.rawTokens}\n`);
171
+ process.exit(1);
172
+ return;
173
+ }
174
+ }
175
+ const promptFlags = resolvePromptFlags({
176
+ ...(opts.dryRun !== undefined ? { dryRun: opts.dryRun } : {}),
177
+ ...(opts.printPrompt !== undefined ? { printPrompt: opts.printPrompt } : {}),
178
+ });
179
+ if (promptFlags.deprecationNotice) {
180
+ process.stderr.write(promptFlags.deprecationNotice);
181
+ }
182
+ const dryRunForward = promptFlags.forwardDryRun;
32
183
  const isHeadless = opts.skipAnalyze ||
33
184
  opts.skipGenerate ||
34
185
  opts.skipApply ||
@@ -36,23 +187,107 @@ export function registerImportCommand(program) {
36
187
  !!opts.environmentId ||
37
188
  !!opts.cmaToken ||
38
189
  opts.yes ||
39
- opts.dryRun ||
190
+ dryRunForward ||
40
191
  false;
192
+ const autoAcceptScope = opts.autoAcceptScope ?? false;
193
+ if (!process.stdout.isTTY && !isHeadless && !autoAcceptScope) {
194
+ process.stderr.write('Error: experiences import is interactive. Pass --auto-accept-scope, or use a headless mode by providing credentials (--space-id, --environment-id, --cma-token) or one of --skip-analyze, --skip-generate, --skip-apply, --yes, --dry-run, --print-prompt.\n');
195
+ process.exit(1);
196
+ return;
197
+ }
41
198
  if (process.stdout.isTTY && !isHeadless) {
42
199
  const { render } = await import('ink');
43
200
  const { createElement } = await import('react');
44
201
  const { WizardApp } = await import('./tui/WizardApp.js');
45
202
  const creds = await readExperiencesCredentials();
203
+ // Parity-audit Q4: resolve --agent / --model overrides against the
204
+ // stored credentials.json so both flags are functional for the
205
+ // wizard path. Flag wins, then stored value, then default.
206
+ const resolvedAgent = resolveAgent(opts.agent, creds.agent);
207
+ const resolvedModel = resolveModel(opts.model, creds.agentModel);
208
+ // ── Run picker decision ─────────────────────────────────────────
209
+ // When runs.json has prior entries and the operator didn't ask for
210
+ // a specific entry point, open the wizard with the run picker. The
211
+ // helper enforces every gate (TTY, conflicting flags, file state).
212
+ const pickerDecision = await shouldShowRunPicker({
213
+ flags: {
214
+ ...(opts.pushFromRun !== undefined ? { pushFromRun: opts.pushFromRun } : {}),
215
+ ...(opts.modify !== undefined ? { modify: opts.modify } : {}),
216
+ ...(opts.project !== '.' ? { project: opts.project } : {}),
217
+ ...(opts.autoAcceptScope ? { autoAcceptScope: true } : {}),
218
+ ...(opts.dryRun ? { dryRun: true } : {}),
219
+ },
220
+ isTTY: !!process.stdin.isTTY,
221
+ });
222
+ let pickerSelection = null;
223
+ const pickerProps = {};
224
+ if (pickerDecision.shouldShow) {
225
+ pickerProps.initialRuns = pickerDecision.runs;
226
+ pickerProps.onRunPicked = (selection) => {
227
+ pickerSelection = selection;
228
+ setImmediate(() => process.exit(0));
229
+ };
230
+ }
46
231
  const { waitUntilExit } = render(createElement(WizardApp, {
47
232
  initialSpaceId: creds.spaceId,
48
233
  initialEnvironmentId: creds.environmentId || 'master',
49
234
  initialCmaToken: creds.cmaToken,
50
235
  initialHost: toConfiguredHost(opts.host ?? creds.host) ?? DEFAULT_CONFIGURED_HOST,
51
- initialAgent: opts.agent !== 'claude' ? opts.agent : undefined,
236
+ initialAgent: resolvedAgent,
237
+ ...(resolvedModel ? { initialModel: resolvedModel } : {}),
52
238
  initialProjectPath: opts.project !== '.' ? resolve(opts.project) : undefined,
53
239
  host: opts.host,
240
+ autoAcceptScope,
241
+ noCache: opts.cache === false,
242
+ autoFilter: resolveAutoFilter({ autoFilter: opts.autoFilter }, creds.autoFilter),
243
+ livePreview: opts.livePreview !== false,
244
+ noPush: opts.push === false,
245
+ noSave: opts.save === false,
246
+ ...(opts.outDir ? { outDirOverride: resolve(opts.outDir) } : {}),
247
+ ...(opts.onConflict ? { onConflictMode: opts.onConflict } : {}),
248
+ selectPromptPath: opts.selectPromptPath ?? creds.selectPromptPath,
249
+ generatePromptPath: opts.generatePromptPath ?? creds.generatePromptPath,
250
+ ...(opts.rawTokens ? { initialRawTokensPath: resolve(opts.rawTokens) } : {}),
251
+ ...pickerProps,
54
252
  }));
55
- await waitUntilExit();
253
+ try {
254
+ await waitUntilExit();
255
+ }
256
+ catch {
257
+ /* Ink throws on process.exit; swallow so picker dispatch can run. */
258
+ }
259
+ // ── Picker dispatch ─────────────────────────────────────────────
260
+ // If the operator picked a run, route into the existing entry
261
+ // points so credential resolution and mutex checks stay in one
262
+ // place. The --modify path resolves the run record via
263
+ // resolveRunTarget and threads its session IDs through
264
+ // launchModifyWizard (see runs/replay-helpers.ts).
265
+ if (pickerSelection) {
266
+ const sel = pickerSelection;
267
+ if (sel.action === 'push' && sel.runId) {
268
+ await replayRun({
269
+ runIdOrPath: sel.runId,
270
+ ...(opts.spaceId ? { spaceId: opts.spaceId } : {}),
271
+ ...(opts.environmentId ? { environmentId: opts.environmentId } : {}),
272
+ ...(opts.cmaToken ? { cmaToken: opts.cmaToken } : {}),
273
+ ...(opts.host ? { host: opts.host } : {}),
274
+ interactive: !!process.stdout.isTTY,
275
+ ...(opts.force ? { force: true } : {}),
276
+ });
277
+ return;
278
+ }
279
+ if (sel.action === 'modify' && sel.runId) {
280
+ await modifyRun({
281
+ runIdOrPath: sel.runId,
282
+ ...(opts.outDir ? { outDir: opts.outDir } : {}),
283
+ ...(opts.overwrite ? { overwrite: true } : {}),
284
+ ...(opts.saveAsNew ? { saveAsNew: true } : {}),
285
+ ...(opts.force ? { force: true } : {}),
286
+ });
287
+ return;
288
+ }
289
+ // action === 'new' falls through — the wizard already advanced.
290
+ }
56
291
  return;
57
292
  }
58
293
  const skipApply = opts.skipApply ?? false;
@@ -66,14 +301,18 @@ export function registerImportCommand(program) {
66
301
  }
67
302
  const projectRoot = resolve(opts.project);
68
303
  const outDir = opts.out ? resolve(opts.out) : join(projectRoot, '.contentful');
304
+ // Parity-audit Q4: also honor stored agent/model in headless mode.
305
+ const headlessCreds = await readExperiencesCredentials();
306
+ const headlessAgent = resolveAgent(opts.agent, headlessCreds.agent);
307
+ const headlessModel = resolveModel(opts.model, headlessCreds.agentModel);
69
308
  const result = await runPipeline({
70
309
  project: projectRoot,
71
310
  out: outDir,
72
311
  spaceId,
73
312
  environmentId,
74
313
  cmaToken,
75
- agent: opts.agent,
76
- model: opts.model,
314
+ agent: headlessAgent,
315
+ model: headlessModel,
77
316
  tokens: opts.tokens,
78
317
  selectAll: opts.selectAll,
79
318
  select: opts.select.length > 0 ? opts.select : undefined,
@@ -88,7 +327,8 @@ export function registerImportCommand(program) {
88
327
  excludeInvalid: opts.excludeInvalid ?? false,
89
328
  viewports: opts.viewports,
90
329
  host: opts.host,
91
- dryRun: opts.dryRun,
330
+ dryRun: dryRunForward,
331
+ selectPromptPath: opts.selectPromptPath,
92
332
  }, (line) => process.stderr.write(line + '\n'));
93
333
  const hasFailed = result.steps.some((s) => s.status === 'failed');
94
334
  if (!process.stdout.isTTY) {
@@ -21,6 +21,8 @@ export interface PipelineOptions {
21
21
  selectAll?: boolean;
22
22
  select?: string[];
23
23
  deselect?: string[];
24
+ /** Forwarded to the spawned `analyze select-agent` subprocess. */
25
+ selectPromptPath?: string;
24
26
  }
25
27
  export interface StepResult {
26
28
  step: string;
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { execFile } from 'node:child_process';
5
5
  import { openPipelineDb, getOrCreateSession, createStep, updateStep, findLatestSessionForCommand, } from '../session/db.js';
6
6
  import { PREVIEW_ERROR_PREFIX, VALIDATION_FAILED_CODE, parsePreviewValidationErrors } from '../apply/api-client.js';
7
+ import { buildPostPushUrl } from '../lib/contentful-urls.js';
7
8
  function findCliPath() {
8
9
  return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'bin', 'cli.js');
9
10
  }
@@ -174,6 +175,10 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
174
175
  editArgs.push('--model', opts.model);
175
176
  if (opts.excludeInvalid)
176
177
  editArgs.push('--exclude-invalid');
178
+ if (opts.noCache)
179
+ editArgs.push('--no-cache');
180
+ if (opts.selectPromptPath)
181
+ editArgs.push('--select-prompt-path', opts.selectPromptPath);
177
182
  }
178
183
  else {
179
184
  editArgs = ['analyze', 'select', '--session', extractSessionId];
@@ -449,10 +454,13 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
449
454
  progressWriter('');
450
455
  progressWriter(`Pipeline complete. Session: ${sessionId}`);
451
456
  if (opts.spaceId && opts.environmentId && !opts.skipApply) {
452
- const apiHost = opts.host ?? 'api.contentful.com';
453
- const appHost = apiHost.replace(/^api\./, 'app.');
457
+ const viewUrl = buildPostPushUrl({
458
+ host: opts.host ?? 'api.contentful.com',
459
+ spaceId: opts.spaceId,
460
+ environmentId: opts.environmentId,
461
+ });
454
462
  progressWriter('');
455
- progressWriter(`View your design system: https://${appHost}/spaces/${opts.spaceId}/environments/${opts.environmentId}/components`);
463
+ progressWriter(`View your design system: ${viewUrl}`);
456
464
  }
457
465
  db.close();
458
466
  return { session: sessionId, project: projectRoot, steps };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `experiences import --print-prompt` / `--dry-run` — prompt-print helpers.
3
+ *
4
+ * Both flags currently surface the same behaviour: print the generate-prompt
5
+ * (delegated downstream by the orchestrator via `generate components --dry-run`)
6
+ * and exit before invoking the agent. `--print-prompt` is the new, explicit
7
+ * name. `--dry-run` is preserved for one deprecation cycle: it keeps working
8
+ * but emits a stderr notice signalling that its semantics will change in a
9
+ * future release (to "manifest preview, no push", matching `apply push`'s
10
+ * `--dry-run`).
11
+ *
12
+ * Keeping the routing in a small pure module lets the command surface stay
13
+ * declarative and lets the deprecation notice be asserted from tests without
14
+ * spawning the full pipeline.
15
+ */
16
+ export declare const DRY_RUN_DEPRECATION_NOTICE: string;
17
+ export type PromptFlags = {
18
+ /** True when the user passed --dry-run. */
19
+ dryRun?: boolean;
20
+ /** True when the user passed --print-prompt. */
21
+ printPrompt?: boolean;
22
+ };
23
+ export type PromptFlagsResolution = {
24
+ /** Whether to forward `--dry-run` semantics to the downstream generate step. */
25
+ forwardDryRun: boolean;
26
+ /** Stderr message to emit, or null when the flags need no warning. */
27
+ deprecationNotice: string | null;
28
+ };
29
+ /**
30
+ * Resolve the user's `--dry-run` / `--print-prompt` choice into the
31
+ * downstream-forwarded dry-run boolean plus an optional deprecation message.
32
+ * Pure (no IO) so it's trivially unit-testable; the caller writes the notice
33
+ * to stderr.
34
+ */
35
+ export declare function resolvePromptFlags(flags: PromptFlags): PromptFlagsResolution;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `experiences import --print-prompt` / `--dry-run` — prompt-print helpers.
3
+ *
4
+ * Both flags currently surface the same behaviour: print the generate-prompt
5
+ * (delegated downstream by the orchestrator via `generate components --dry-run`)
6
+ * and exit before invoking the agent. `--print-prompt` is the new, explicit
7
+ * name. `--dry-run` is preserved for one deprecation cycle: it keeps working
8
+ * but emits a stderr notice signalling that its semantics will change in a
9
+ * future release (to "manifest preview, no push", matching `apply push`'s
10
+ * `--dry-run`).
11
+ *
12
+ * Keeping the routing in a small pure module lets the command surface stay
13
+ * declarative and lets the deprecation notice be asserted from tests without
14
+ * spawning the full pipeline.
15
+ */
16
+ export const DRY_RUN_DEPRECATION_NOTICE = "Warning: '--dry-run' on 'experiences import' will change semantics in a future release; " +
17
+ "use '--print-prompt' to print the prompt or '--dry-run --no-push' to preview the manifest.\n";
18
+ /**
19
+ * Resolve the user's `--dry-run` / `--print-prompt` choice into the
20
+ * downstream-forwarded dry-run boolean plus an optional deprecation message.
21
+ * Pure (no IO) so it's trivially unit-testable; the caller writes the notice
22
+ * to stderr.
23
+ */
24
+ export function resolvePromptFlags(flags) {
25
+ const forwardDryRun = !!(flags.dryRun || flags.printPrompt);
26
+ // Only the bare `--dry-run` path emits the deprecation notice; pairing it
27
+ // with `--print-prompt` (or using `--print-prompt` alone) opts the operator
28
+ // into the new explicit name, so we stay quiet.
29
+ const deprecationNotice = flags.dryRun && !flags.printPrompt ? DRY_RUN_DEPRECATION_NOTICE : null;
30
+ return { forwardDryRun, deprecationNotice };
31
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ /**
3
+ * Feature 8: top-of-wizard banner shown while any custom skill prompt is
4
+ * active. Persists at every step so the operator cannot miss that bundled
5
+ * invariants are bypassed.
6
+ */
7
+ export interface CustomPromptBannerProps {
8
+ selectPromptPath?: string;
9
+ generatePromptPath?: string;
10
+ }
11
+ export declare function CustomPromptBanner({ selectPromptPath, generatePromptPath, }: CustomPromptBannerProps): React.ReactElement | null;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function CustomPromptBanner({ selectPromptPath, generatePromptPath, }) {
4
+ if (!selectPromptPath && !generatePromptPath)
5
+ return null;
6
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "WARNING: Custom prompt active" }), selectPromptPath ? _jsxs(Text, { color: "yellow", children: ["select: ", selectPromptPath] }) : null, generatePromptPath ? _jsxs(Text, { color: "yellow", children: ["components: ", generatePromptPath] }) : null, _jsx(Text, { color: "yellow", dimColor: true, children: "Bundled invariants (utility-wrapper rejection, description content rules) do NOT apply." })] }));
7
+ }
@@ -1,24 +1,128 @@
1
1
  import React from 'react';
2
- export declare function buildAnalyzeSelectArgs(opts: {
2
+ import { type RunPickerSelection } from '../../runs/run-picker.js';
3
+ import type { RunRecord } from '../../runs/store.js';
4
+ import { type OnConflictMode } from '../../runs/save-path-resolver.js';
5
+ export declare function buildSelectAgentArgs(opts: {
3
6
  sessionId: string;
4
- acceptAll: boolean;
7
+ agent: string;
8
+ /** Parity-audit Q4: forward `--model` override from `experiences import`. */
9
+ model?: string;
10
+ /** Feature 8: forward to the spawned select-agent subprocess. */
11
+ selectPromptPath?: string;
12
+ /**
13
+ * Forward the operator's `experiences import --no-cache` through to the
14
+ * spawned `analyze select-agent` so PR #59's per-component select-cache is
15
+ * bypassed on this run. Default (omitted/false) preserves cache behavior.
16
+ */
17
+ noCache?: boolean;
18
+ }): string[];
19
+ export type AutoFilterProgress = {
20
+ n: number;
21
+ total: number;
22
+ decision: 'accepted' | 'rejected';
23
+ name: string;
24
+ reason: string;
25
+ };
26
+ export declare function parseAutoFilterProgressLine(line: string): AutoFilterProgress | null;
27
+ export declare function buildGenerateComponentsArgs(opts: {
28
+ sessionId: string;
29
+ tokensPath?: string;
30
+ agent: string;
31
+ /** Parity-audit Q4: forward `--model` override from `experiences import`. */
32
+ model?: string;
33
+ noCache?: boolean;
34
+ /** Feature 8: forward to the spawned generate components subprocess. */
35
+ generatePromptPath?: string;
5
36
  }): string[];
6
37
  export declare function formatAcceptanceSummary(opts: {
7
38
  accepted: number;
8
39
  autoRejected: number;
9
40
  }): string;
10
- export declare function formatGeneratedSummary(opts: {
11
- generated: number;
12
- renamedSlots: number;
13
- autoRejected: number;
14
- }): string;
41
+ /**
42
+ * Parse the `wrote tokens.json (N tokens)` confirmation line emitted by
43
+ * `experiences print tokens`. Returns 0 when the line cannot be parsed —
44
+ * the wizard still records the run; downstream consumers should treat 0 as
45
+ * "unknown" rather than "no tokens".
46
+ */
47
+ export declare function parsePrintTokensCount(stdout: string): number;
15
48
  export type WizardAppProps = {
16
49
  initialSpaceId?: string;
17
50
  initialEnvironmentId?: string;
18
51
  initialCmaToken?: string;
19
52
  initialHost?: string;
20
53
  initialAgent?: string;
54
+ /** Parity-audit Q4: resolved model override (flag || stored value). */
55
+ initialModel?: string;
21
56
  initialProjectPath?: string;
22
57
  host?: string;
58
+ autoAcceptScope?: boolean;
59
+ noCache?: boolean;
60
+ autoFilter?: boolean;
61
+ livePreview?: boolean;
62
+ noPush?: boolean;
63
+ noSave?: boolean;
64
+ /** Task 4 — `--out-dir <path>` flag. Bypasses the inline save-path prompt. */
65
+ outDirOverride?: string;
66
+ /**
67
+ * Task 5 — `--on-conflict <overwrite|skip|fail>` flag. When supplied along
68
+ * with `outDirOverride`, the wizard skips the SaveConflictGate and applies
69
+ * the chosen mode automatically via `resolveSavePath`.
70
+ */
71
+ onConflictMode?: OnConflictMode;
72
+ /**
73
+ * Feature 8: custom prompt path overrides forwarded to the spawned
74
+ * `analyze select-agent` and `generate components` subprocesses. When set,
75
+ * the wizard also renders a persistent top-of-screen banner so the operator
76
+ * cannot miss that bundled invariants are bypassed.
77
+ */
78
+ selectPromptPath?: string;
79
+ generatePromptPath?: string;
80
+ /**
81
+ * Modify-entry: when set, the wizard treats extract as already-run and
82
+ * seeds `state.extractSessionId` from this value. Combined with
83
+ * `initialStep: 'final-review'`, the wizard skips welcome → token-input →
84
+ * checking-claude-auth → extracting → scope-gate and lands directly on
85
+ * the post-generate review screen using DB-backed reads.
86
+ */
87
+ seedExtractSessionId?: string;
88
+ /**
89
+ * Modify-entry: when set, the wizard treats generate as already-run and
90
+ * seeds `state.generateSessionId`. Required (together with
91
+ * `seedExtractSessionId`) for the final-review short-circuit to render
92
+ * meaningful data.
93
+ */
94
+ seedGenerateSessionId?: string;
95
+ /**
96
+ * Modify-entry: when set, the wizard treats the tokens step as already-run
97
+ * and seeds `state.tokenSessionId`. Without this, the modify entry would
98
+ * leave tokens unaddressable — push would skip them and `runPrintFiles`
99
+ * would never re-emit `tokens.json` for the modified save path.
100
+ */
101
+ seedTokenSessionId?: string;
102
+ /**
103
+ * Modify-entry: overrides the wizard's initial step. When set, the wizard
104
+ * bypasses its normal welcome/token-input bootstrap. Currently only
105
+ * `'final-review'` is plumbed end-to-end; `'scope-gate'` is accepted for
106
+ * future use but falls through to standard behavior.
107
+ */
108
+ initialStep?: 'scope-gate' | 'final-review';
109
+ /**
110
+ * Headless raw-token source path. When set (and the modify-entry props
111
+ * are not), the wizard seeds `state.rawTokensPath` and lands directly on
112
+ * the `generating-tokens` step, skipping welcome + token-input. The
113
+ * existing `generating-tokens` effect then drives token classification
114
+ * via `generate tokens --raw-tokens <path>` just as if the operator had
115
+ * submitted the interactive `TokenInputStep`.
116
+ */
117
+ initialRawTokensPath?: string;
118
+ /**
119
+ * When set with a non-empty array, the wizard opens with the run picker
120
+ * instead of the welcome step. The picker invokes `onRunPicked` with the
121
+ * operator's selection so the CLI surface can route into
122
+ * `--push-from-run` / `--modify` entry points (or fall through to the
123
+ * normal welcome step on 'new').
124
+ */
125
+ initialRuns?: RunRecord[];
126
+ onRunPicked?: (selection: RunPickerSelection) => void;
23
127
  };
24
- export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialProjectPath, host, }?: WizardAppProps): React.ReactElement;
128
+ export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope, noCache, autoFilter, livePreview, noPush, noSave, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, initialStep, initialRawTokensPath, initialRuns, onRunPicked, }?: WizardAppProps): React.ReactElement;