openuispec 0.1.29 → 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/README.md CHANGED
@@ -188,13 +188,15 @@ Use the commands like this:
188
188
  - `openuispec validate` checks schema correctness
189
189
  - `openuispec validate semantic` checks cross-references such as locale keys, formatters, mappers, contracts, icons, navigation targets, and API endpoints
190
190
  - `openuispec init --no-configure-targets` scaffolds the spec project without running the target-stack wizard
191
- - `openuispec configure-target <t>` records target stack choices in `platform/<target>.yaml` using preset defaults, while still allowing custom framework/library values when the project uses something outside the catalog
191
+ - `openuispec configure-target <t>` records and confirms target stack choices in `platform/<target>.yaml`, while still allowing custom framework/library values when the project uses something outside the catalog
192
192
  - `openuispec drift --target <t> --explain` explains semantic spec changes since that target's accepted baseline
193
193
  - `openuispec prepare --target <t>` builds the target work bundle for either first-time generation or drift-based updates
194
194
  - `openuispec status` shows every target's snapshot state, baseline commit, and whether that target is behind the current spec, still needs a baseline, or has not been generated yet
195
195
 
196
196
  In first-time generation mode, `prepare` also carries target-specific generation constraints such as native localization requirements, multi-file output rules, target folder layout expectations, and a requirement to refresh current platform/framework setup knowledge before code generation.
197
197
 
198
+ If stack choices were auto-applied via `configure-target --defaults` or `init --defaults`, they remain unconfirmed. `prepare` will block implementation readiness until the user explicitly confirms the target stack, and AI agents should ask the user to confirm or change those choices instead of silently proceeding to code generation.
199
+
198
200
  When target stack choices come from the preset catalog, `prepare --json` also exposes install-oriented refs for the selected options:
199
201
  - Android: Gradle plugin ids and library coordinates
200
202
  - Web: npm package specs
@@ -4,11 +4,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import YAML from "yaml";
7
- import { findProjectDir, readManifest } from "../drift/index.js";
7
+ import { findProjectDir, isSupportedTarget, readManifest, type SupportedTarget } from "../drift/index.js";
8
8
  import { ask, askChoice } from "./init.js";
9
9
 
10
- type SupportedTarget = "ios" | "android" | "web";
11
-
12
10
  type WizardOptionPreset = {
13
11
  value: string;
14
12
  generation_value?: string;
@@ -44,6 +42,27 @@ type TargetWizardPreset = {
44
42
  questions: ChoiceQuestionPreset[];
45
43
  };
46
44
 
45
+ export type TargetWizardOptionsResponse = {
46
+ target: SupportedTarget;
47
+ defaults_are_unconfirmed: true;
48
+ confirmation_required_before_implementation: true;
49
+ interactive_command: string;
50
+ defaults_command: string;
51
+ framework: {
52
+ prompt: string;
53
+ recommended: string;
54
+ options: string[];
55
+ custom_allowed: true;
56
+ };
57
+ questions: Array<{
58
+ key: string;
59
+ prompt: string;
60
+ recommended: string;
61
+ custom_allowed: true;
62
+ options: WizardOptionPreset[];
63
+ }>;
64
+ };
65
+
47
66
  function readWizardPresets(): Record<SupportedTarget, TargetWizardPreset> {
48
67
  const presetsPath = join(dirname(fileURLToPath(import.meta.url)), "target-presets.json");
49
68
  return JSON.parse(readFileSync(presetsPath, "utf-8")) as Record<SupportedTarget, TargetWizardPreset>;
@@ -51,6 +70,34 @@ function readWizardPresets(): Record<SupportedTarget, TargetWizardPreset> {
51
70
 
52
71
  const TARGET_WIZARDS = readWizardPresets();
53
72
 
73
+ function listFrameworkOptions(wizard: TargetWizardPreset): string[] {
74
+ return [...new Set([...(wizard.framework_options ?? [wizard.framework]), "other"])];
75
+ }
76
+
77
+ export function listTargetWizardOptions(target: SupportedTarget): TargetWizardOptionsResponse {
78
+ const wizard = TARGET_WIZARDS[target];
79
+ return {
80
+ target,
81
+ defaults_are_unconfirmed: true,
82
+ confirmation_required_before_implementation: true,
83
+ interactive_command: `openuispec configure-target ${target}`,
84
+ defaults_command: `openuispec configure-target ${target} --defaults`,
85
+ framework: {
86
+ prompt: wizard.framework_prompt ?? `${target} framework`,
87
+ recommended: wizard.framework,
88
+ options: listFrameworkOptions(wizard),
89
+ custom_allowed: true,
90
+ },
91
+ questions: wizard.questions.map((question) => ({
92
+ key: question.key,
93
+ prompt: question.prompt,
94
+ recommended: question.recommended,
95
+ custom_allowed: true,
96
+ options: question.options,
97
+ })),
98
+ };
99
+ }
100
+
54
101
  function filterOptionsForFramework(
55
102
  question: ChoiceQuestionPreset,
56
103
  framework: string
@@ -279,32 +326,72 @@ function buildGeneration(
279
326
  return generation;
280
327
  }
281
328
 
329
+ function stackConfirmation(useDefaults: boolean): Record<string, string> {
330
+ const now = new Date().toISOString();
331
+ return useDefaults
332
+ ? {
333
+ status: "pending_user_confirmation",
334
+ source: "defaults",
335
+ updated_at: now,
336
+ }
337
+ : {
338
+ status: "confirmed",
339
+ source: "user",
340
+ confirmed_at: now,
341
+ };
342
+ }
343
+
282
344
  function parseTarget(argv: string[]): SupportedTarget | null {
283
345
  const direct = argv[0];
284
- if (direct && ["ios", "android", "web"].includes(direct)) {
285
- return direct as SupportedTarget;
346
+ if (direct && isSupportedTarget(direct)) {
347
+ return direct;
286
348
  }
287
349
  const targetIdx = argv.indexOf("--target");
288
- if (targetIdx !== -1 && argv[targetIdx + 1] && ["ios", "android", "web"].includes(argv[targetIdx + 1])) {
289
- return argv[targetIdx + 1] as SupportedTarget;
350
+ if (targetIdx !== -1 && argv[targetIdx + 1] && isSupportedTarget(argv[targetIdx + 1])) {
351
+ return argv[targetIdx + 1];
290
352
  }
291
353
  return null;
292
354
  }
293
355
 
356
+ function parseSetPairs(argv: string[]): Record<string, string> {
357
+ const pairs: Record<string, string> = {};
358
+ for (let i = 0; i < argv.length; i++) {
359
+ if (argv[i] === "--set" && argv[i + 1]) {
360
+ const raw = argv[i + 1];
361
+ const eqIdx = raw.indexOf("=");
362
+ if (eqIdx > 0) {
363
+ pairs[raw.slice(0, eqIdx)] = raw.slice(eqIdx + 1);
364
+ }
365
+ i++;
366
+ }
367
+ }
368
+ return pairs;
369
+ }
370
+
294
371
  export async function runConfigureTarget(argv: string[]): Promise<void> {
295
372
  const target = parseTarget(argv);
373
+ const listOptions = argv.includes("--list-options");
296
374
  const useDefaults = argv.includes("--defaults");
297
- const interactive = stdin.isTTY && stdout.isTTY && !useDefaults;
375
+ const quiet = argv.includes("--quiet");
376
+ const setPairs = parseSetPairs(argv);
377
+ const hasSetPairs = Object.keys(setPairs).length > 0;
378
+ const interactive = stdin.isTTY && stdout.isTTY && !useDefaults && !hasSetPairs;
298
379
  if (!target) {
299
380
  console.error("Error: target is required for configure-target");
300
- console.error("Usage: openuispec configure-target <ios|android|web> [--defaults]");
381
+ console.error("Usage: openuispec configure-target <ios|android|web> [--defaults] [--list-options] [--set key=value]");
301
382
  process.exit(1);
302
383
  }
303
384
 
304
- if (!interactive && !useDefaults) {
385
+ if (listOptions) {
386
+ console.log(JSON.stringify(listTargetWizardOptions(target), null, 2));
387
+ return;
388
+ }
389
+
390
+ if (!interactive && !useDefaults && !hasSetPairs) {
305
391
  console.error(
306
392
  "Error: `openuispec configure-target` needs a TTY for prompts.\n" +
307
- "Run with `--defaults` in non-interactive environments."
393
+ "Preferred: ask the user to confirm the target stack, then run `openuispec configure-target <target>` in an interactive terminal.\n" +
394
+ "Fallback: run with `--defaults` only for unattended setup; those values remain unconfirmed and `prepare` will block implementation until the user confirms them."
308
395
  );
309
396
  process.exit(1);
310
397
  }
@@ -351,7 +438,16 @@ export async function runConfigureTarget(argv: string[]): Promise<void> {
351
438
 
352
439
  let answers = computeDefaultAnswers(framework);
353
440
 
354
- if (interactive) {
441
+ if (hasSetPairs) {
442
+ // Non-interactive --set path: merge provided values with existing/defaults
443
+ const knownKeys = new Set(wizard.questions.map((q) => q.key));
444
+ for (const [key, value] of Object.entries(setPairs)) {
445
+ if (!knownKeys.has(key)) {
446
+ console.error(`Warning: "${key}" does not match any wizard question; setting as custom value.`);
447
+ }
448
+ answers[key] = value;
449
+ }
450
+ } else if (interactive) {
355
451
  const rl = createInterface({ input: stdin, output: stdout });
356
452
 
357
453
  try {
@@ -393,10 +489,14 @@ export async function runConfigureTarget(argv: string[]): Promise<void> {
393
489
  }
394
490
  }
395
491
 
492
+ // --set implies confirmed (user explicitly chose values); --defaults without --set is pending
396
493
  const updatedPlatform: Record<string, any> = {
397
494
  ...existingPlatform,
398
495
  framework,
399
- generation: buildGeneration(wizard, answers, existingGeneration, framework),
496
+ generation: {
497
+ ...buildGeneration(wizard, answers, existingGeneration, framework),
498
+ stack_confirmation: stackConfirmation(useDefaults && !hasSetPairs),
499
+ },
400
500
  };
401
501
 
402
502
  if (wizard.language) updatedPlatform.language = wizard.language;
@@ -408,9 +508,16 @@ export async function runConfigureTarget(argv: string[]): Promise<void> {
408
508
 
409
509
  writeFileSync(platformPath, YAML.stringify({ [target]: updatedPlatform }));
410
510
 
411
- console.log(`\nSaved ${relative(process.cwd(), platformPath)}`);
412
- console.log("Configured values:");
413
- for (const [key, value] of Object.entries(answers)) {
414
- console.log(` - ${key}: ${value}`);
511
+ const savedPath = relative(process.cwd(), platformPath);
512
+ if (argv.includes("--silent")) {
513
+ // Called as subroutine (e.g. from init --quiet) — no output at all
514
+ } else if (quiet) {
515
+ console.log(savedPath);
516
+ } else {
517
+ console.log(`\nSaved ${savedPath}`);
518
+ console.log("Configured values:");
519
+ for (const [key, value] of Object.entries(answers)) {
520
+ console.log(` - ${key}: ${value}`);
521
+ }
415
522
  }
416
523
  }
package/cli/index.ts CHANGED
@@ -4,13 +4,15 @@
4
4
  *
5
5
  * Usage:
6
6
  * openuispec init Create a new spec project
7
- * openuispec init --defaults Scaffold non-interactively with defaults
8
- * openuispec configure-target <t> Configure target stack and managed dependencies
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
9
10
  * openuispec drift [--target <t>] Check for spec drift
10
11
  * openuispec drift --snapshot --target <t> Snapshot current state + git baseline
11
12
  * openuispec drift --target <t> --explain Explain semantic changes since baseline
12
13
  * openuispec prepare --target <t> Build the target work bundle
13
14
  * openuispec status Show cross-target baseline/drift status
15
+ * openuispec check --target <t> [--json] Composite validation + prepare readiness
14
16
  * openuispec validate [group...] Validate spec files against schemas
15
17
  */
16
18
 
@@ -78,6 +80,12 @@ async function main(): Promise<void> {
78
80
  break;
79
81
  }
80
82
 
83
+ case "check": {
84
+ const { runCheck } = await import("../check/index.js");
85
+ runCheck(rest);
86
+ break;
87
+ }
88
+
81
89
  case "validate": {
82
90
  const { runValidate } = await import("../schema/validate.js");
83
91
  runValidate(rest);
@@ -93,19 +101,25 @@ OpenUISpec CLI v0.1
93
101
 
94
102
  Usage:
95
103
  openuispec init Create a new spec project
96
- openuispec init --defaults Scaffold non-interactively with defaults
104
+ openuispec init --defaults Scaffold non-interactively with unconfirmed defaults
97
105
  openuispec init --no-configure-targets Skip target stack setup during init
98
106
  openuispec update-rules Update AI rules to match installed version
99
- openuispec configure-target <t> [--defaults] Configure target stack and managed dependencies
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
100
110
  openuispec drift [--target <t>] Check for spec drift
101
111
  openuispec drift --snapshot --target <t> Snapshot current state + git baseline
102
112
  openuispec drift --target <t> --explain Explain semantic changes since baseline
103
113
  openuispec prepare --target <t> Build the target work bundle
104
114
  openuispec status Show cross-target baseline/drift status
105
- 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
106
118
 
107
119
  Validate groups: manifest, tokens, screens, flows, platform, locales, contracts, semantic
108
120
 
121
+ Exit codes: 0 = success, 1 = missing config/usage error, 2 = validation failure
122
+
109
123
  Docs: https://openuispec.rsteam.uz
110
124
  `);
111
125
  break;
@@ -117,4 +131,7 @@ Docs: https://openuispec.rsteam.uz
117
131
  }
118
132
  }
119
133
 
120
- 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
@@ -17,6 +17,7 @@ import {
17
17
  } from "node:fs";
18
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
 
@@ -75,13 +76,13 @@ function ensureDir(path: string): void {
75
76
  mkdirSync(path, { recursive: true });
76
77
  }
77
78
 
78
- function writeIfMissing(path: string, content: string): boolean {
79
+ function writeIfMissing(path: string, content: string, quiet = false): boolean {
79
80
  if (existsSync(path)) {
80
- console.log(` skip ${relative(process.cwd(), path)} (exists)`);
81
+ if (!quiet) console.log(` skip ${relative(process.cwd(), path)} (exists)`);
81
82
  return false;
82
83
  }
83
84
  writeFileSync(path, content);
84
- console.log(` create ${relative(process.cwd(), path)}`);
85
+ if (!quiet) console.log(` create ${relative(process.cwd(), path)}`);
85
86
  return true;
86
87
  }
87
88
 
@@ -202,7 +203,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
202
203
  openuispec validate # Validate spec files against schemas
203
204
  openuispec validate semantic # Run semantic cross-reference linting
204
205
  openuispec validate screens # Validate only screens
205
- openuispec configure-target ${targets[0]} [--defaults] # Configure target stack defaults
206
+ openuispec configure-target ${targets[0]} [--defaults] # Configure target stack; --defaults stays unconfirmed
206
207
  openuispec status # Show cross-target baseline/drift status
207
208
  openuispec drift --target ${targets[0]} --explain # Explain semantic spec drift
208
209
  openuispec prepare --target ${targets[0]} # Build the target work bundle
@@ -213,6 +214,8 @@ The target work bundle has two modes:
213
214
  - \`bootstrap\` when no snapshot exists yet, for first-time generation
214
215
  - \`update\` after a snapshot exists, for drift-based target updates
215
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
+
216
219
  ## Learn more
217
220
 
218
221
  Docs: https://openuispec.rsteam.uz
@@ -302,6 +305,7 @@ Spec-first workflow:
302
305
  5. Run \`openuispec validate semantic\`.
303
306
  6. Run \`openuispec drift --target <target> --explain\` to inspect semantic changes since that target's baseline.
304
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.
305
309
  8. Verify the affected UI targets build/run if possible.
306
310
  9. Only then run \`openuispec drift --snapshot --target <target>\` for affected targets, after that target output directory exists.
307
311
  10. Run \`openuispec drift --target <target> --explain\` again to confirm no spec changes remain for that target.
@@ -322,6 +326,7 @@ Platform-first workflow:
322
326
  - Do not treat \`openuispec drift\` as proof that generated UI matches the spec.
323
327
  - Do not skip \`--explain\` / \`prepare\` when another platform needs to catch up with shared spec changes.
324
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.
325
330
 
326
331
  ## CLI commands
327
332
  - \`openuispec init\` — scaffold a new spec project
@@ -330,7 +335,7 @@ Platform-first workflow:
330
335
  - \`openuispec drift --target <t>\` — check for spec drift
331
336
  - \`openuispec drift --target <t> --explain\` — explain semantic spec drift since the target baseline
332
337
  - \`openuispec drift --snapshot --target <t>\` — snapshot current state after the target output exists
333
- - \`openuispec prepare --target <t>\` — build the target work bundle
338
+ - \`openuispec prepare --target <t>\` — build the target work bundle and check whether stack confirmation is still pending
334
339
  - \`openuispec status\` — show cross-target baseline/drift status
335
340
  - \`openuispec update-rules\` — update AI rules to match installed package version
336
341
  - \`openuispec drift --all\` — include stubs in drift check
@@ -354,7 +359,7 @@ export function updateRules(): void {
354
359
  }
355
360
 
356
361
  // Detect targets from manifest
357
- let targets = ["ios", "android", "web"];
362
+ let targets = [...SUPPORTED_TARGETS];
358
363
  try {
359
364
  const manifest = readFileSync(
360
365
  join(cwd, specDir, "openuispec.yaml"),
@@ -429,6 +434,7 @@ export { getPackageVersion };
429
434
 
430
435
  interface InitOptions {
431
436
  defaults: boolean;
437
+ quiet: boolean;
432
438
  name?: string;
433
439
  specDir?: string;
434
440
  targets?: string[];
@@ -447,11 +453,10 @@ interface InitAnswers {
447
453
  }
448
454
 
449
455
  function parseTargetsValue(raw: string): string[] {
450
- const allowed = new Set(["ios", "android", "web"]);
451
456
  return raw
452
457
  .split(",")
453
458
  .map((value) => value.trim().toLowerCase())
454
- .filter((value): value is string => allowed.has(value));
459
+ .filter((value): value is SupportedTarget => isSupportedTarget(value));
455
460
  }
456
461
 
457
462
  function requireFlagValue(argv: string[], index: number, flag: string): string {
@@ -464,12 +469,13 @@ function requireFlagValue(argv: string[], index: number, flag: string): string {
464
469
  }
465
470
 
466
471
  function parseInitArgs(argv: string[]): InitOptions {
467
- const options: InitOptions = { defaults: argv.includes("--defaults") };
472
+ const options: InitOptions = { defaults: argv.includes("--defaults"), quiet: argv.includes("--quiet") };
468
473
 
469
474
  for (let index = 0; index < argv.length; index++) {
470
475
  const arg = argv[index];
471
476
  switch (arg) {
472
477
  case "--defaults":
478
+ case "--quiet":
473
479
  break;
474
480
  case "--name":
475
481
  options.name = requireFlagValue(argv, index, arg);
@@ -516,7 +522,7 @@ function collectDefaults(): InitAnswers {
516
522
  return {
517
523
  name: defaultName,
518
524
  specDir: "openuispec",
519
- targets: ["ios", "android", "web"],
525
+ targets: [...SUPPORTED_TARGETS],
520
526
  withApi: true,
521
527
  backendPath: "../backend/",
522
528
  configureTargets: true,
@@ -527,7 +533,7 @@ async function collectInteractiveAnswers(rl: ReturnType<typeof createInterface>)
527
533
  const defaults = collectDefaults();
528
534
  const name = await ask(rl, "Project name", defaults.name);
529
535
  const specDir = await ask(rl, "Spec directory", defaults.specDir);
530
- const targets = await askList(rl, "\nWhich platforms?", ["ios", "android", "web"], defaults.targets);
536
+ const targets = await askList(rl, "\nWhich platforms?", [...SUPPORTED_TARGETS], defaults.targets);
531
537
 
532
538
  if (targets.length === 0) {
533
539
  console.error("At least one target is required.");
@@ -554,7 +560,7 @@ function collectNonInteractiveAnswers(argv: string[]): InitAnswers {
554
560
  const parsed = parseInitArgs(argv);
555
561
  const defaults = collectDefaults();
556
562
 
557
- if (!parsed.defaults && argv.length === 0) {
563
+ if (!parsed.defaults && argv.filter((a) => a !== "--quiet").length === 0) {
558
564
  console.error(
559
565
  "Error: `openuispec init` needs a TTY for prompts.\n" +
560
566
  "Run with `--defaults` or pass flags such as `--name`, `--targets`, `--with-api`, `--backend`, and `--configure-targets`."
@@ -584,10 +590,11 @@ function collectNonInteractiveAnswers(argv: string[]): InitAnswers {
584
590
  // ── main ─────────────────────────────────────────────────────────────
585
591
 
586
592
  export async function init(argv: string[] = []): Promise<void> {
593
+ const quiet = argv.includes("--quiet");
587
594
  const interactive = stdin.isTTY && stdout.isTTY && !argv.includes("--defaults");
588
595
  const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
589
596
 
590
- console.log("\nOpenUISpec — Project Setup\n");
597
+ if (!quiet) console.log("\nOpenUISpec — Project Setup\n");
591
598
 
592
599
  try {
593
600
  const cwd = process.cwd();
@@ -596,7 +603,7 @@ export async function init(argv: string[] = []): Promise<void> {
596
603
 
597
604
  // ── create folders ─────────────────────────────────────────────
598
605
 
599
- console.log("\nScaffolding...\n");
606
+ if (!quiet) console.log("\nScaffolding...\n");
600
607
 
601
608
  const root = join(cwd, answers.specDir);
602
609
  const dirs = [
@@ -620,14 +627,16 @@ export async function init(argv: string[] = []): Promise<void> {
620
627
  manifestTemplate(answers.name, answers.targets, {
621
628
  withApi: answers.withApi,
622
629
  backendPath: answers.backendPath,
623
- })
630
+ }),
631
+ quiet
624
632
  );
625
633
 
626
634
  // ── spec README ──────────────────────────────────────────────
627
635
 
628
636
  writeIfMissing(
629
637
  join(root, "README.md"),
630
- specReadmeTemplate(answers.name, answers.targets)
638
+ specReadmeTemplate(answers.name, answers.targets),
639
+ quiet
631
640
  );
632
641
 
633
642
  // ── .gitkeep for empty dirs ────────────────────────────────────
@@ -641,23 +650,11 @@ export async function init(argv: string[] = []): Promise<void> {
641
650
  const gk = join(dir, ".gitkeep");
642
651
  if (!existsSync(gk)) {
643
652
  writeFileSync(gk, "");
644
- console.log(` create ${relative(cwd, gk)}`);
653
+ if (!quiet) console.log(` create ${relative(cwd, gk)}`);
645
654
  }
646
655
  }
647
656
  }
648
657
 
649
- if (answers.withApi && answers.backendPath) {
650
- const backendDir = resolve(root, answers.backendPath);
651
- const backendExisted = existsSync(backendDir);
652
- ensureDir(backendDir);
653
- const backendEntries = readdirSync(backendDir).filter((entry) => entry !== ".gitkeep");
654
- const backendGitkeep = join(backendDir, ".gitkeep");
655
- if ((!backendExisted || backendEntries.length === 0) && !existsSync(backendGitkeep)) {
656
- writeFileSync(backendGitkeep, "");
657
- console.log(` create ${relative(cwd, backendGitkeep)}`);
658
- }
659
- }
660
-
661
658
  // ── AI assistant rules ─────────────────────────────────────────
662
659
 
663
660
  const rules = aiRulesBlock(answers.specDir, answers.targets);
@@ -667,28 +664,31 @@ export async function init(argv: string[] = []): Promise<void> {
667
664
  if (existsSync(filePath)) {
668
665
  const existing = readFileSync(filePath, "utf-8");
669
666
  if (existing.includes("OpenUISpec")) {
670
- console.log(` skip ${file} (already has OpenUISpec rules)`);
667
+ if (!quiet) console.log(` skip ${file} (already has OpenUISpec rules)`);
671
668
  continue;
672
669
  }
673
670
  appendFileSync(filePath, "\n" + rules);
674
- console.log(` update ${file} (appended rules)`);
671
+ if (!quiet) console.log(` update ${file} (appended rules)`);
675
672
  } else {
676
673
  writeFileSync(filePath, rules.trimStart());
677
- console.log(` create ${file}`);
674
+ if (!quiet) console.log(` create ${file}`);
678
675
  }
679
676
  }
680
677
 
681
678
  if (answers.configureTargets) {
682
- console.log("\nConfiguring target stacks...\n");
679
+ if (!quiet) console.log("\nConfiguring target stacks...\n");
683
680
  const { runConfigureTarget } = await import("./configure-target.js");
684
681
  for (const target of answers.targets) {
685
- await runConfigureTarget([target, ...(interactive ? [] : ["--defaults"])]);
682
+ await runConfigureTarget([target, ...(interactive ? [] : ["--defaults"]), ...(quiet ? ["--silent"] : [])]);
686
683
  }
687
684
  }
688
685
 
689
686
  // ── done ───────────────────────────────────────────────────────
690
687
 
691
- console.log(`
688
+ if (quiet) {
689
+ console.log(`./${answers.specDir}/`);
690
+ } else {
691
+ console.log(`
692
692
  Done! Your spec project is ready at ./${answers.specDir}/
693
693
 
694
694
  Getting started (new project):
@@ -709,7 +709,7 @@ Getting started (existing project):
709
709
  Commands:
710
710
  openuispec validate Validate spec files
711
711
  openuispec validate semantic Check semantic cross-references
712
- openuispec configure-target ios [--defaults] Configure target stack defaults
712
+ openuispec configure-target ios [--defaults] Configure target stack; --defaults stays unconfirmed
713
713
  openuispec status Show cross-target baseline/drift status
714
714
  openuispec drift --target ios --explain Explain semantic spec changes
715
715
  openuispec prepare --target ios Build the target work bundle
@@ -719,6 +719,7 @@ AI rules have been added to CLAUDE.md and AGENTS.md.
719
719
 
720
720
  Docs: https://openuispec.rsteam.uz
721
721
  `);
722
+ }
722
723
  } catch (err) {
723
724
  rl?.close();
724
725
  throw err;