openuispec 0.1.28 → 0.1.30

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/cli/index.ts CHANGED
@@ -4,11 +4,15 @@
4
4
  *
5
5
  * Usage:
6
6
  * openuispec init Create a new spec project
7
+ * openuispec init --defaults Scaffold non-interactively with unconfirmed defaults
8
+ * openuispec configure-target <t> Configure and confirm target stack choices
9
+ * openuispec configure-target <t> --list-options Print target stack prompt options as JSON
7
10
  * openuispec drift [--target <t>] Check for spec drift
8
11
  * openuispec drift --snapshot --target <t> Snapshot current state + git baseline
9
12
  * openuispec drift --target <t> --explain Explain semantic changes since baseline
10
- * openuispec prepare --target <t> Build an AI-ready target update bundle
13
+ * openuispec prepare --target <t> Build the target work bundle
11
14
  * openuispec status Show cross-target baseline/drift status
15
+ * openuispec check --target <t> [--json] Composite validation + prepare readiness
12
16
  * openuispec validate [group...] Validate spec files against schemas
13
17
  */
14
18
 
@@ -45,13 +49,19 @@ async function main(): Promise<void> {
45
49
 
46
50
  switch (command) {
47
51
  case "init":
48
- await init();
52
+ await init(rest);
49
53
  break;
50
54
 
51
55
  case "update-rules":
52
56
  updateRules();
53
57
  break;
54
58
 
59
+ case "configure-target": {
60
+ const { runConfigureTarget } = await import("./configure-target.js");
61
+ await runConfigureTarget(rest);
62
+ break;
63
+ }
64
+
55
65
  case "drift": {
56
66
  const { runDrift } = await import("../drift/index.js");
57
67
  runDrift(rest);
@@ -70,6 +80,12 @@ async function main(): Promise<void> {
70
80
  break;
71
81
  }
72
82
 
83
+ case "check": {
84
+ const { runCheck } = await import("../check/index.js");
85
+ runCheck(rest);
86
+ break;
87
+ }
88
+
73
89
  case "validate": {
74
90
  const { runValidate } = await import("../schema/validate.js");
75
91
  runValidate(rest);
@@ -85,16 +101,25 @@ OpenUISpec CLI v0.1
85
101
 
86
102
  Usage:
87
103
  openuispec init Create a new spec project
104
+ openuispec init --defaults Scaffold non-interactively with unconfirmed defaults
105
+ openuispec init --no-configure-targets Skip target stack setup during init
88
106
  openuispec update-rules Update AI rules to match installed version
107
+ openuispec configure-target <t> [--defaults] Configure target stack; --defaults stays unconfirmed
108
+ openuispec configure-target <t> --set k=v Set specific stack values (confirmed)
109
+ openuispec configure-target <t> --list-options Print target stack prompt options as JSON
89
110
  openuispec drift [--target <t>] Check for spec drift
90
111
  openuispec drift --snapshot --target <t> Snapshot current state + git baseline
91
112
  openuispec drift --target <t> --explain Explain semantic changes since baseline
92
- openuispec prepare --target <t> Build an AI-ready target update bundle
113
+ openuispec prepare --target <t> Build the target work bundle
93
114
  openuispec status Show cross-target baseline/drift status
94
- openuispec validate [group...] Validate spec files
115
+ openuispec check --target <t> [--json] Composite validation + prepare readiness
116
+ openuispec validate [group...] [--json] Validate spec files
117
+ openuispec validate semantic --json Semantic validation as JSON
95
118
 
96
119
  Validate groups: manifest, tokens, screens, flows, platform, locales, contracts, semantic
97
120
 
121
+ Exit codes: 0 = success, 1 = missing config/usage error, 2 = validation failure
122
+
98
123
  Docs: https://openuispec.rsteam.uz
99
124
  `);
100
125
  break;
@@ -106,4 +131,7 @@ Docs: https://openuispec.rsteam.uz
106
131
  }
107
132
  }
108
133
 
109
- main();
134
+ main().catch((err) => {
135
+ console.error(err instanceof Error ? err.message : String(err));
136
+ process.exit(1);
137
+ });
package/cli/init.ts CHANGED
@@ -15,12 +15,13 @@ import {
15
15
  existsSync,
16
16
  appendFileSync,
17
17
  } from "node:fs";
18
- import { join, relative, dirname } from "node:path";
18
+ import { join, relative, dirname, resolve } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
+ import { SUPPORTED_TARGETS, isSupportedTarget, type SupportedTarget } from "../drift/index.js";
20
21
 
21
22
  // ── prompts ──────────────────────────────────────────────────────────
22
23
 
23
- async function ask(
24
+ export async function ask(
24
25
  rl: ReturnType<typeof createInterface>,
25
26
  question: string,
26
27
  fallback?: string
@@ -47,21 +48,41 @@ async function askList(
47
48
  .filter((s) => options.includes(s));
48
49
  }
49
50
 
51
+ export async function askChoice(
52
+ rl: ReturnType<typeof createInterface>,
53
+ question: string,
54
+ options: string[],
55
+ fallback: string
56
+ ): Promise<string> {
57
+ const answer = (await rl.question(`${question} [${options.join(", ")}] (${fallback}): `))
58
+ .trim()
59
+ .toLowerCase();
60
+ if (!answer) return fallback;
61
+ return options.includes(answer) ? answer : fallback;
62
+ }
63
+
64
+ async function askYesNo(
65
+ rl: ReturnType<typeof createInterface>,
66
+ question: string,
67
+ fallback: boolean
68
+ ): Promise<boolean> {
69
+ const answer = await askChoice(rl, question, ["yes", "no"], fallback ? "yes" : "no");
70
+ return answer === "yes";
71
+ }
72
+
50
73
  // ── scaffold ─────────────────────────────────────────────────────────
51
74
 
52
75
  function ensureDir(path: string): void {
53
- if (!existsSync(path)) {
54
- mkdirSync(path, { recursive: true });
55
- }
76
+ mkdirSync(path, { recursive: true });
56
77
  }
57
78
 
58
- function writeIfMissing(path: string, content: string): boolean {
79
+ function writeIfMissing(path: string, content: string, quiet = false): boolean {
59
80
  if (existsSync(path)) {
60
- console.log(` skip ${relative(process.cwd(), path)} (exists)`);
81
+ if (!quiet) console.log(` skip ${relative(process.cwd(), path)} (exists)`);
61
82
  return false;
62
83
  }
63
84
  writeFileSync(path, content);
64
- console.log(` create ${relative(process.cwd(), path)}`);
85
+ if (!quiet) console.log(` create ${relative(process.cwd(), path)}`);
65
86
  return true;
66
87
  }
67
88
 
@@ -85,7 +106,7 @@ function getPackageVersion(): string {
85
106
  function manifestTemplate(
86
107
  name: string,
87
108
  targets: string[],
88
- specDir: string
109
+ options: { withApi: boolean; backendPath: string | null }
89
110
  ): string {
90
111
  const targetList = targets.join(", ");
91
112
  const outputLines = targets
@@ -126,7 +147,9 @@ generation:
126
147
  # ios: "../ios-app/" # relative to this file
127
148
  # android: "../android-app/"
128
149
  # web: "../web-ui/"
129
- output_format:
150
+ ${options.withApi ? ` code_roots:
151
+ backend: "${options.backendPath}" # Required when api.endpoints are declared
152
+ ` : ""} output_format:
130
153
  ${outputLines}
131
154
 
132
155
  data_model: {}
@@ -169,7 +192,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
169
192
  3. Online: \`https://openuispec.rsteam.uz/llms-full.txt\` (if not installed)
170
193
 
171
194
  **Reference files inside the package (read in this order):**
172
- 1. \`README.md\` — schema tables, file format reference, root keys
195
+ 1. \`README.md\` — schema tables, file format reference, root wrapper keys
173
196
  2. \`spec/openuispec-v0.1.md\` — full specification (contracts, layout, expressions, etc.)
174
197
  3. \`examples/taskflow/openuispec/\` — complete working example with all file types
175
198
  4. \`schema/\` — JSON Schemas for validation
@@ -180,12 +203,19 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
180
203
  openuispec validate # Validate spec files against schemas
181
204
  openuispec validate semantic # Run semantic cross-reference linting
182
205
  openuispec validate screens # Validate only screens
206
+ openuispec configure-target ${targets[0]} [--defaults] # Configure target stack; --defaults stays unconfirmed
183
207
  openuispec status # Show cross-target baseline/drift status
184
208
  openuispec drift --target ${targets[0]} --explain # Explain semantic spec drift
185
- openuispec prepare --target ${targets[0]} # Build an AI-ready target update bundle
209
+ openuispec prepare --target ${targets[0]} # Build the target work bundle
186
210
  openuispec drift --snapshot --target ${targets[0]} # Snapshot current state + git baseline after target output exists
187
211
  \`\`\`
188
212
 
213
+ The target work bundle has two modes:
214
+ - \`bootstrap\` when no snapshot exists yet, for first-time generation
215
+ - \`update\` after a snapshot exists, for drift-based target updates
216
+
217
+ If target stack values were written with \`--defaults\`, treat them as unconfirmed. Before generating code, ask the user to confirm or change the stack and run \`openuispec configure-target <target>\` without \`--defaults\`.
218
+
189
219
  ## Learn more
190
220
 
191
221
  Docs: https://openuispec.rsteam.uz
@@ -253,7 +283,7 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
253
283
  type: scroll_vertical
254
284
  \`\`\`
255
285
  4. **Extract tokens** — scan for colors, fonts, spacing and create files in \`${specDir}/tokens/\`.
256
- 5. **Update the manifest** — fill in \`data_model\` and \`api.endpoints\` in \`${specDir}/openuispec.yaml\`.
286
+ 5. **Update the manifest** — fill in \`data_model\`, \`api.endpoints\`, and \`generation.code_roots.backend\` in \`${specDir}/openuispec.yaml\`.
257
287
 
258
288
  ## OpenUISpec Source Of Truth
259
289
 
@@ -274,7 +304,8 @@ Spec-first workflow:
274
304
  4. Run \`openuispec validate\`.
275
305
  5. Run \`openuispec validate semantic\`.
276
306
  6. Run \`openuispec drift --target <target> --explain\` to inspect semantic changes since that target's baseline.
277
- 7. Run \`openuispec prepare --target <target>\` to build the AI/developer work bundle for that target.
307
+ 7. Run \`openuispec prepare --target <target>\` to build the target work bundle for that target. In \`bootstrap\` mode it provides first-generation constraints; in \`update\` mode it provides drift-based update scope.
308
+ If the target stack was filled from defaults, stop and ask the user to confirm or change it before implementation.
278
309
  8. Verify the affected UI targets build/run if possible.
279
310
  9. Only then run \`openuispec drift --snapshot --target <target>\` for affected targets, after that target output directory exists.
280
311
  10. Run \`openuispec drift --target <target> --explain\` again to confirm no spec changes remain for that target.
@@ -295,6 +326,7 @@ Platform-first workflow:
295
326
  - Do not treat \`openuispec drift\` as proof that generated UI matches the spec.
296
327
  - Do not skip \`--explain\` / \`prepare\` when another platform needs to catch up with shared spec changes.
297
328
  - Do not modify generated UI without checking whether the spec must change first.
329
+ - Do not use \`configure-target --defaults\` as silent approval for implementation. Ask the user to confirm the stack first.
298
330
 
299
331
  ## CLI commands
300
332
  - \`openuispec init\` — scaffold a new spec project
@@ -303,7 +335,7 @@ Platform-first workflow:
303
335
  - \`openuispec drift --target <t>\` — check for spec drift
304
336
  - \`openuispec drift --target <t> --explain\` — explain semantic spec drift since the target baseline
305
337
  - \`openuispec drift --snapshot --target <t>\` — snapshot current state after the target output exists
306
- - \`openuispec prepare --target <t>\` — build an AI-ready target update bundle
338
+ - \`openuispec prepare --target <t>\` — build the target work bundle and check whether stack confirmation is still pending
307
339
  - \`openuispec status\` — show cross-target baseline/drift status
308
340
  - \`openuispec update-rules\` — update AI rules to match installed package version
309
341
  - \`openuispec drift --all\` — include stubs in drift check
@@ -327,7 +359,7 @@ export function updateRules(): void {
327
359
  }
328
360
 
329
361
  // Detect targets from manifest
330
- let targets = ["ios", "android", "web"];
362
+ let targets = [...SUPPORTED_TARGETS];
331
363
  try {
332
364
  const manifest = readFileSync(
333
365
  join(cwd, specDir, "openuispec.yaml"),
@@ -400,43 +432,180 @@ export function extractRulesVersion(filePath: string): string | null {
400
432
 
401
433
  export { getPackageVersion };
402
434
 
403
- // ── main ─────────────────────────────────────────────────────────────
435
+ interface InitOptions {
436
+ defaults: boolean;
437
+ quiet: boolean;
438
+ name?: string;
439
+ specDir?: string;
440
+ targets?: string[];
441
+ withApi?: boolean;
442
+ backendPath?: string;
443
+ configureTargets?: boolean;
444
+ }
404
445
 
405
- export async function init(): Promise<void> {
406
- const rl = createInterface({ input: stdin, output: stdout });
446
+ interface InitAnswers {
447
+ name: string;
448
+ specDir: string;
449
+ targets: string[];
450
+ withApi: boolean;
451
+ backendPath: string | null;
452
+ configureTargets: boolean;
453
+ }
407
454
 
408
- console.log("\nOpenUISpec Project Setup\n");
455
+ function parseTargetsValue(raw: string): string[] {
456
+ return raw
457
+ .split(",")
458
+ .map((value) => value.trim().toLowerCase())
459
+ .filter((value): value is SupportedTarget => isSupportedTarget(value));
460
+ }
409
461
 
410
- try {
411
- // 1. Project name (display name in manifest, derived from current folder)
412
- const cwd = process.cwd();
413
- const defaultName = cwd.split("/").pop() || "MyApp";
414
- const name = await ask(rl, "Project name", defaultName);
415
-
416
- // 2. Spec directory
417
- const specDir = await ask(rl, "Spec directory", "openuispec");
418
-
419
- // 3. Platforms
420
- const allTargets = ["ios", "android", "web"];
421
- const targets = await askList(
422
- rl,
423
- "\nWhich platforms?",
424
- allTargets,
425
- allTargets
426
- );
462
+ function requireFlagValue(argv: string[], index: number, flag: string): string {
463
+ const value = argv[index + 1];
464
+ if (!value || value.startsWith("--")) {
465
+ console.error(`Error: ${flag} requires a value.`);
466
+ process.exit(1);
467
+ }
468
+ return value;
469
+ }
427
470
 
428
- if (targets.length === 0) {
429
- console.error("At least one target is required.");
430
- process.exit(1);
471
+ function parseInitArgs(argv: string[]): InitOptions {
472
+ const options: InitOptions = { defaults: argv.includes("--defaults"), quiet: argv.includes("--quiet") };
473
+
474
+ for (let index = 0; index < argv.length; index++) {
475
+ const arg = argv[index];
476
+ switch (arg) {
477
+ case "--defaults":
478
+ case "--quiet":
479
+ break;
480
+ case "--name":
481
+ options.name = requireFlagValue(argv, index, arg);
482
+ index++;
483
+ break;
484
+ case "--spec-dir":
485
+ options.specDir = requireFlagValue(argv, index, arg);
486
+ index++;
487
+ break;
488
+ case "--targets":
489
+ options.targets = parseTargetsValue(requireFlagValue(argv, index, arg));
490
+ index++;
491
+ break;
492
+ case "--backend":
493
+ options.backendPath = requireFlagValue(argv, index, arg);
494
+ index++;
495
+ break;
496
+ case "--with-api":
497
+ options.withApi = true;
498
+ break;
499
+ case "--no-api":
500
+ options.withApi = false;
501
+ break;
502
+ case "--configure-targets":
503
+ options.configureTargets = true;
504
+ break;
505
+ case "--no-configure-targets":
506
+ options.configureTargets = false;
507
+ break;
508
+ default:
509
+ if (arg.startsWith("--")) {
510
+ console.error(`Error: Unknown init option: ${arg}`);
511
+ process.exit(1);
512
+ }
431
513
  }
514
+ }
432
515
 
433
- rl.close();
516
+ return options;
517
+ }
518
+
519
+ function collectDefaults(): InitAnswers {
520
+ const cwd = process.cwd();
521
+ const defaultName = cwd.split("/").pop() || "MyApp";
522
+ return {
523
+ name: defaultName,
524
+ specDir: "openuispec",
525
+ targets: [...SUPPORTED_TARGETS],
526
+ withApi: true,
527
+ backendPath: "../backend/",
528
+ configureTargets: true,
529
+ };
530
+ }
531
+
532
+ async function collectInteractiveAnswers(rl: ReturnType<typeof createInterface>): Promise<InitAnswers> {
533
+ const defaults = collectDefaults();
534
+ const name = await ask(rl, "Project name", defaults.name);
535
+ const specDir = await ask(rl, "Spec directory", defaults.specDir);
536
+ const targets = await askList(rl, "\nWhich platforms?", [...SUPPORTED_TARGETS], defaults.targets);
537
+
538
+ if (targets.length === 0) {
539
+ console.error("At least one target is required.");
540
+ process.exit(1);
541
+ }
542
+
543
+ const withApi = await askYesNo(rl, "Will this spec declare API endpoints?", defaults.withApi);
544
+ const backendPath = withApi
545
+ ? await ask(rl, "Backend folder path relative to openuispec.yaml", defaults.backendPath ?? "../backend/")
546
+ : null;
547
+ const configureTargets = await askYesNo(rl, "Configure target stacks now?", defaults.configureTargets);
548
+
549
+ return {
550
+ name,
551
+ specDir,
552
+ targets,
553
+ withApi,
554
+ backendPath,
555
+ configureTargets,
556
+ };
557
+ }
558
+
559
+ function collectNonInteractiveAnswers(argv: string[]): InitAnswers {
560
+ const parsed = parseInitArgs(argv);
561
+ const defaults = collectDefaults();
562
+
563
+ if (!parsed.defaults && argv.filter((a) => a !== "--quiet").length === 0) {
564
+ console.error(
565
+ "Error: `openuispec init` needs a TTY for prompts.\n" +
566
+ "Run with `--defaults` or pass flags such as `--name`, `--targets`, `--with-api`, `--backend`, and `--configure-targets`."
567
+ );
568
+ process.exit(1);
569
+ }
570
+
571
+ const targets = parsed.targets && parsed.targets.length > 0 ? parsed.targets : defaults.targets;
572
+ if (targets.length === 0) {
573
+ console.error("Error: --targets must include at least one of ios, android, web.");
574
+ process.exit(1);
575
+ }
576
+
577
+ const withApi = parsed.withApi ?? defaults.withApi;
578
+ const backendPath = withApi ? parsed.backendPath ?? defaults.backendPath : null;
579
+
580
+ return {
581
+ name: parsed.name ?? defaults.name,
582
+ specDir: parsed.specDir ?? defaults.specDir,
583
+ targets,
584
+ withApi,
585
+ backendPath,
586
+ configureTargets: parsed.configureTargets ?? defaults.configureTargets,
587
+ };
588
+ }
589
+
590
+ // ── main ─────────────────────────────────────────────────────────────
591
+
592
+ export async function init(argv: string[] = []): Promise<void> {
593
+ const quiet = argv.includes("--quiet");
594
+ const interactive = stdin.isTTY && stdout.isTTY && !argv.includes("--defaults");
595
+ const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
596
+
597
+ if (!quiet) console.log("\nOpenUISpec — Project Setup\n");
598
+
599
+ try {
600
+ const cwd = process.cwd();
601
+ const answers = rl ? await collectInteractiveAnswers(rl) : collectNonInteractiveAnswers(argv);
602
+ rl?.close();
434
603
 
435
604
  // ── create folders ─────────────────────────────────────────────
436
605
 
437
- console.log("\nScaffolding...\n");
606
+ if (!quiet) console.log("\nScaffolding...\n");
438
607
 
439
- const root = join(cwd, specDir);
608
+ const root = join(cwd, answers.specDir);
440
609
  const dirs = [
441
610
  "tokens",
442
611
  "contracts",
@@ -455,14 +624,19 @@ export async function init(): Promise<void> {
455
624
 
456
625
  writeIfMissing(
457
626
  join(root, "openuispec.yaml"),
458
- manifestTemplate(name, targets, specDir)
627
+ manifestTemplate(answers.name, answers.targets, {
628
+ withApi: answers.withApi,
629
+ backendPath: answers.backendPath,
630
+ }),
631
+ quiet
459
632
  );
460
633
 
461
634
  // ── spec README ──────────────────────────────────────────────
462
635
 
463
636
  writeIfMissing(
464
637
  join(root, "README.md"),
465
- specReadmeTemplate(name, targets)
638
+ specReadmeTemplate(answers.name, answers.targets),
639
+ quiet
466
640
  );
467
641
 
468
642
  // ── .gitkeep for empty dirs ────────────────────────────────────
@@ -476,65 +650,78 @@ export async function init(): Promise<void> {
476
650
  const gk = join(dir, ".gitkeep");
477
651
  if (!existsSync(gk)) {
478
652
  writeFileSync(gk, "");
479
- console.log(` create ${relative(cwd, gk)}`);
653
+ if (!quiet) console.log(` create ${relative(cwd, gk)}`);
480
654
  }
481
655
  }
482
656
  }
483
657
 
484
658
  // ── AI assistant rules ─────────────────────────────────────────
485
659
 
486
- const rules = aiRulesBlock(specDir, targets);
660
+ const rules = aiRulesBlock(answers.specDir, answers.targets);
487
661
 
488
662
  for (const file of ["CLAUDE.md", "AGENTS.md"]) {
489
663
  const filePath = join(cwd, file);
490
664
  if (existsSync(filePath)) {
491
665
  const existing = readFileSync(filePath, "utf-8");
492
666
  if (existing.includes("OpenUISpec")) {
493
- console.log(` skip ${file} (already has OpenUISpec rules)`);
667
+ if (!quiet) console.log(` skip ${file} (already has OpenUISpec rules)`);
494
668
  continue;
495
669
  }
496
670
  appendFileSync(filePath, "\n" + rules);
497
- console.log(` update ${file} (appended rules)`);
671
+ if (!quiet) console.log(` update ${file} (appended rules)`);
498
672
  } else {
499
673
  writeFileSync(filePath, rules.trimStart());
500
- console.log(` create ${file}`);
674
+ if (!quiet) console.log(` create ${file}`);
675
+ }
676
+ }
677
+
678
+ if (answers.configureTargets) {
679
+ if (!quiet) console.log("\nConfiguring target stacks...\n");
680
+ const { runConfigureTarget } = await import("./configure-target.js");
681
+ for (const target of answers.targets) {
682
+ await runConfigureTarget([target, ...(interactive ? [] : ["--defaults"]), ...(quiet ? ["--silent"] : [])]);
501
683
  }
502
684
  }
503
685
 
504
686
  // ── done ───────────────────────────────────────────────────────
505
687
 
506
- console.log(`
507
- Done! Your spec project is ready at ./${specDir}/
688
+ if (quiet) {
689
+ console.log(`./${answers.specDir}/`);
690
+ } else {
691
+ console.log(`
692
+ Done! Your spec project is ready at ./${answers.specDir}/
508
693
 
509
694
  Getting started (new project):
510
- 1. Edit ${specDir}/openuispec.yaml — define your data model and API
511
- 2. Create screens in ${specDir}/screens/ (one YAML per screen)
512
- 3. Create flows in ${specDir}/flows/ (multi-step navigation)
695
+ 1. Edit ${answers.specDir}/openuispec.yaml — define your data model and API
696
+ 2. Create screens in ${answers.specDir}/screens/ (one YAML per screen)
697
+ 3. Create flows in ${answers.specDir}/flows/ (multi-step navigation)
513
698
  4. Ask AI to generate native code from the spec
514
- 5. Run \`openuispec drift --snapshot --target ${targets[0]}\` to baseline the first accepted target state after that target output directory exists
699
+ 5. Run \`openuispec drift --snapshot --target ${answers.targets[0]}\` to baseline the first accepted target state after that target output directory exists
515
700
 
516
701
  Getting started (existing project):
517
702
  1. Ask AI to read your existing UI code and generate spec files:
518
- "Read src/screens/HomeScreen.swift and create ${specDir}/screens/home.yaml as status: stub"
703
+ "Read src/screens/HomeScreen.swift and create ${answers.specDir}/screens/home.yaml as status: stub"
519
704
  2. Spec screens incrementally: stub → draft → ready
520
705
  3. Only ready/draft screens are tracked by drift detection
521
706
  4. Run \`openuispec validate\` to check specs against the schema
522
- 5. Use \`openuispec drift --target ${targets[0]} --explain\` and \`openuispec prepare --target ${targets[0]}\` before asking AI to update a target
707
+ 5. Use \`openuispec prepare --target ${answers.targets[0]}\` before first-time generation, then use \`openuispec drift --target ${answers.targets[0]} --explain\` and \`openuispec prepare --target ${answers.targets[0]}\` before asking AI to update a target
523
708
 
524
709
  Commands:
525
710
  openuispec validate Validate spec files
526
711
  openuispec validate semantic Check semantic cross-references
712
+ openuispec configure-target ios [--defaults] Configure target stack; --defaults stays unconfirmed
527
713
  openuispec status Show cross-target baseline/drift status
528
714
  openuispec drift --target ios --explain Explain semantic spec changes
529
- openuispec prepare --target ios Build an AI-ready target update bundle
715
+ openuispec prepare --target ios Build the target work bundle
530
716
  openuispec drift --snapshot --target ios Save current state + git baseline after target output exists
531
717
 
532
718
  AI rules have been added to CLAUDE.md and AGENTS.md.
533
719
 
534
720
  Docs: https://openuispec.rsteam.uz
535
721
  `);
722
+ }
536
723
  } catch (err) {
537
- rl.close();
724
+ rl?.close();
538
725
  throw err;
539
726
  }
540
727
  }