@pithy-sh/cli 0.1.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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +72 -0
  3. package/scripts/templateManifest.ts +49 -0
  4. package/scripts/tsconfig.json +26 -0
  5. package/scripts/vendorTemplate.ts +84 -0
  6. package/scripts/verifyPack.ts +88 -0
  7. package/src/audit/cliAudit.ts +406 -0
  8. package/src/bin.ts +111 -0
  9. package/src/capabilities/add.ts +288 -0
  10. package/src/capabilities/addBootstrap.ts +275 -0
  11. package/src/capabilities/catalog.ts +175 -0
  12. package/src/capabilities/compose.ts +39 -0
  13. package/src/capabilities/configConstants.ts +74 -0
  14. package/src/capabilities/configImports.ts +397 -0
  15. package/src/capabilities/eject.ts +331 -0
  16. package/src/capabilities/emailProvisioner.ts +346 -0
  17. package/src/capabilities/entitlementGap.ts +70 -0
  18. package/src/capabilities/entryExports.ts +162 -0
  19. package/src/capabilities/flow.ts +550 -0
  20. package/src/capabilities/hostRegistry.ts +368 -0
  21. package/src/capabilities/loadFailure.ts +208 -0
  22. package/src/capabilities/manifests.ts +238 -0
  23. package/src/capabilities/mediaProvisioner.ts +471 -0
  24. package/src/capabilities/mintSecrets.ts +306 -0
  25. package/src/capabilities/paymentsProvisioner.ts +207 -0
  26. package/src/capabilities/prerequisites.ts +168 -0
  27. package/src/capabilities/r2Bucket.ts +113 -0
  28. package/src/capabilities/reconcile.ts +1483 -0
  29. package/src/capabilities/remove.ts +597 -0
  30. package/src/capabilities/requiredOptions.ts +92 -0
  31. package/src/capabilities/rotateSecrets.ts +305 -0
  32. package/src/capabilities/secrets.ts +178 -0
  33. package/src/capabilities/secretsDispatcher.ts +29 -0
  34. package/src/capabilities/secretsProvisioner.ts +389 -0
  35. package/src/capabilities/storageProvisioner.ts +414 -0
  36. package/src/capabilities/supportProvisioner.ts +515 -0
  37. package/src/capabilities/testersLoader.ts +52 -0
  38. package/src/capabilities/testersProvisioner.ts +236 -0
  39. package/src/capabilities/turnstileProvisioner.ts +347 -0
  40. package/src/capabilities/vectorProvisioner.ts +260 -0
  41. package/src/ci/fileModes.ts +223 -0
  42. package/src/ci/sourceFiles.ts +200 -0
  43. package/src/ci/workflowDrivers.ts +524 -0
  44. package/src/cloudflare/accountAnswer.ts +110 -0
  45. package/src/cloudflare/config.ts +685 -0
  46. package/src/cloudflare/storeId.ts +129 -0
  47. package/src/commands/add.ts +372 -0
  48. package/src/commands/alias.ts +205 -0
  49. package/src/commands/dashboard.ts +651 -0
  50. package/src/commands/deploy.ts +150 -0
  51. package/src/commands/dev.ts +37 -0
  52. package/src/commands/doctor.ts +2059 -0
  53. package/src/commands/email.ts +425 -0
  54. package/src/commands/env.ts +155 -0
  55. package/src/commands/feature.ts +359 -0
  56. package/src/commands/init.ts +538 -0
  57. package/src/commands/media.ts +303 -0
  58. package/src/commands/migrate.ts +129 -0
  59. package/src/commands/payments.ts +336 -0
  60. package/src/commands/provision.ts +368 -0
  61. package/src/commands/remove.ts +151 -0
  62. package/src/commands/secrets.ts +652 -0
  63. package/src/commands/seed.ts +229 -0
  64. package/src/commands/storage.ts +309 -0
  65. package/src/commands/support.ts +331 -0
  66. package/src/commands/testers.ts +1020 -0
  67. package/src/commands/token.ts +364 -0
  68. package/src/commands/turnstile.ts +271 -0
  69. package/src/commands/ui.ts +222 -0
  70. package/src/commands/upgrade.ts +517 -0
  71. package/src/commands/vector.ts +390 -0
  72. package/src/commands/worker.ts +295 -0
  73. package/src/dashboard/api.ts +323 -0
  74. package/src/dashboard/connect.ts +758 -0
  75. package/src/dashboard/contract.ts +289 -0
  76. package/src/dashboard/grant.ts +124 -0
  77. package/src/dashboard/registry.ts +519 -0
  78. package/src/dashboard/resolveTarget.ts +119 -0
  79. package/src/dev/delivery.ts +174 -0
  80. package/src/dev/devLogin.ts +155 -0
  81. package/src/dev/devLoginTargets.ts +91 -0
  82. package/src/dev/env.ts +206 -0
  83. package/src/dev/hostWorkers.ts +290 -0
  84. package/src/dev/keys.ts +111 -0
  85. package/src/dev/logging.ts +87 -0
  86. package/src/dev/openUrl.ts +75 -0
  87. package/src/dev/orchestrator.ts +1014 -0
  88. package/src/dev/ports.ts +220 -0
  89. package/src/dev/readyWatch.ts +142 -0
  90. package/src/dev/state.ts +90 -0
  91. package/src/devSecrets/bootstrapVars.ts +265 -0
  92. package/src/devSecrets/devVars.ts +240 -0
  93. package/src/devSecrets/edit.ts +256 -0
  94. package/src/devSecrets/file.ts +277 -0
  95. package/src/devSecrets/generate.ts +428 -0
  96. package/src/devSecrets/location.ts +80 -0
  97. package/src/devSecrets/mode.ts +71 -0
  98. package/src/devSecrets/records.ts +30 -0
  99. package/src/devSecrets/report.ts +99 -0
  100. package/src/devSecrets/seed.ts +344 -0
  101. package/src/devSecrets/store.ts +262 -0
  102. package/src/devSecrets/targets.ts +204 -0
  103. package/src/dispatch.ts +147 -0
  104. package/src/docs/catalog.ts +246 -0
  105. package/src/docs/writeCatalog.ts +45 -0
  106. package/src/doctor/cloudflare.ts +287 -0
  107. package/src/doctor/devPreferences.ts +155 -0
  108. package/src/doctor/devSecrets.ts +464 -0
  109. package/src/doctor/devVars.ts +414 -0
  110. package/src/doctor/devVarsLocal.ts +138 -0
  111. package/src/doctor/environments.ts +155 -0
  112. package/src/doctor/health.ts +354 -0
  113. package/src/doctor/localDelivery.ts +91 -0
  114. package/src/doctor/portsRegistry.ts +252 -0
  115. package/src/doctor/projectName.ts +584 -0
  116. package/src/doctor/secretBindings.ts +166 -0
  117. package/src/doctor/settings.ts +274 -0
  118. package/src/doctor/settingsSources.ts +202 -0
  119. package/src/doctor/workerName.ts +174 -0
  120. package/src/doctor/wranglerVars.ts +33 -0
  121. package/src/feature/bindings.ts +93 -0
  122. package/src/feature/create.ts +179 -0
  123. package/src/feature/destroy.ts +160 -0
  124. package/src/feature/devConfig.ts +201 -0
  125. package/src/feature/identity.ts +100 -0
  126. package/src/feature/manifest.ts +132 -0
  127. package/src/feature/ports.ts +615 -0
  128. package/src/feature/provision.ts +362 -0
  129. package/src/feature/sync.ts +148 -0
  130. package/src/feature/worktree.ts +282 -0
  131. package/src/help/groups.ts +47 -0
  132. package/src/help/rootUsage.ts +135 -0
  133. package/src/main.ts +73 -0
  134. package/src/migrations/ledger.ts +129 -0
  135. package/src/migrations/registry.ts +47 -0
  136. package/src/migrations/run.ts +1066 -0
  137. package/src/notifier/check.ts +129 -0
  138. package/src/notifier/installer.ts +48 -0
  139. package/src/notifier/notify.ts +152 -0
  140. package/src/notifier/state.ts +248 -0
  141. package/src/notifier/version.ts +59 -0
  142. package/src/platform/editor.ts +333 -0
  143. package/src/platform/rc.ts +118 -0
  144. package/src/platform/shell.ts +83 -0
  145. package/src/project/appBindings.ts +184 -0
  146. package/src/project/appWorkflows.ts +266 -0
  147. package/src/project/applyDomains.ts +166 -0
  148. package/src/project/askDomains.ts +220 -0
  149. package/src/project/atomic.ts +466 -0
  150. package/src/project/bindingEntries.ts +425 -0
  151. package/src/project/config.ts +701 -0
  152. package/src/project/dashboard.ts +118 -0
  153. package/src/project/deploy.ts +364 -0
  154. package/src/project/devVars.ts +113 -0
  155. package/src/project/domainPrompt.ts +191 -0
  156. package/src/project/domains.ts +386 -0
  157. package/src/project/envInventory.ts +356 -0
  158. package/src/project/environment.ts +125 -0
  159. package/src/project/extensions.ts +69 -0
  160. package/src/project/jsonc.ts +289 -0
  161. package/src/project/packageManager.ts +238 -0
  162. package/src/project/readOptionalFile.ts +342 -0
  163. package/src/project/rollback.ts +145 -0
  164. package/src/project/scaffold.ts +1088 -0
  165. package/src/project/templateFiles.ts +53 -0
  166. package/src/project/verifyDeploy.ts +230 -0
  167. package/src/project/versionMetadata.ts +77 -0
  168. package/src/project/workerAddress.ts +176 -0
  169. package/src/project/workerCommand.ts +564 -0
  170. package/src/project/workerIdentity.ts +50 -0
  171. package/src/project/workerManifest.ts +135 -0
  172. package/src/project/workerScaffold.ts +289 -0
  173. package/src/project/workerScope.ts +394 -0
  174. package/src/project/workers.ts +86 -0
  175. package/src/project/workflows.ts +281 -0
  176. package/src/project/wrangler.ts +168 -0
  177. package/src/provision/confirm.ts +86 -0
  178. package/src/provision/environment.ts +407 -0
  179. package/src/provision/featureConfig.ts +98 -0
  180. package/src/provision/mode.ts +62 -0
  181. package/src/provision/pendingSecrets.ts +96 -0
  182. package/src/provision/resources.ts +126 -0
  183. package/src/provision/secretBindings.ts +149 -0
  184. package/src/provision/store.ts +33 -0
  185. package/src/provision/unprovisioned.ts +114 -0
  186. package/src/provision/wranglerEnv.ts +220 -0
  187. package/src/rootFlags.ts +48 -0
  188. package/src/seed/drivers.ts +423 -0
  189. package/src/seed/media.ts +187 -0
  190. package/src/seed/plan.ts +137 -0
  191. package/src/seed/prepare.ts +224 -0
  192. package/src/seed/registry.ts +25 -0
  193. package/src/seed/run.ts +793 -0
  194. package/src/seed/safety.ts +206 -0
  195. package/src/terminal/logger.ts +42 -0
  196. package/src/terminal/output.ts +64 -0
  197. package/src/terminal/style.ts +132 -0
  198. package/src/test-utils/doctorHarness.ts +190 -0
  199. package/src/test-utils/migrateHarness.ts +126 -0
  200. package/src/test-utils/seedHarness.ts +173 -0
  201. package/src/test-utils/tempRepo.ts +45 -0
  202. package/src/tokens/config.ts +16 -0
  203. package/src/tokens/engine.ts +345 -0
  204. package/src/tokens/mintedTokens.ts +233 -0
  205. package/src/tokens/sinks.ts +84 -0
  206. package/src/ui/flow.ts +451 -0
  207. package/src/ui/react.ts +112 -0
  208. package/src/ui/routeAllowlist.ts +208 -0
  209. package/src/ui/scaffold.ts +113 -0
  210. package/src/ui/screenStyles.ts +127 -0
  211. package/src/ui/stubs.ts +135 -0
  212. package/src/ui/templates.ts +52 -0
  213. package/src/ui/wire.ts +311 -0
  214. package/src/ui/workerUi.ts +172 -0
  215. package/templates/starter/.dev.secrets.example.jsonc +43 -0
  216. package/templates/starter/.dev.vars.example +30 -0
  217. package/templates/starter/apps/api/package.json +22 -0
  218. package/templates/starter/apps/api/pithy.config.ts +65 -0
  219. package/templates/starter/apps/api/pithy.worker.jsonc +11 -0
  220. package/templates/starter/apps/api/src/bindings.workers.test.ts +18 -0
  221. package/templates/starter/apps/api/src/cloudflare-test.d.ts +11 -0
  222. package/templates/starter/apps/api/src/index.ts +8 -0
  223. package/templates/starter/apps/api/tsconfig.json +26 -0
  224. package/templates/starter/apps/api/wrangler.jsonc +68 -0
  225. package/templates/starter/biome.template.jsonc +75 -0
  226. package/templates/starter/gitignore +37 -0
  227. package/templates/starter/package.json +28 -0
  228. package/templates/starter/pithy.config.ts +67 -0
  229. package/templates/starter/plugins/no-console.grit +25 -0
  230. package/templates/starter/plugins/no-process-io.grit +25 -0
  231. package/templates/starter/tsconfig.json +14 -0
  232. package/templates/starter/tsconfig.tools.json +30 -0
  233. package/templates/starter/vitest.config.ts +124 -0
  234. package/templates/starter/vitest.workers.config.ts +26 -0
@@ -0,0 +1,222 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { defineCommand } from "citty";
5
+ import { unpublishedKitNotice } from "../project/scaffold";
6
+ import { workerIdentity } from "../project/workerIdentity";
7
+ import { formatDone, formatJsonLine, formatList, withErrorReporting } from "../terminal/output";
8
+ import { dim } from "../terminal/style";
9
+ import { listStubs, runUiAdd, runUiSync, type UiScreenSet } from "../ui/flow";
10
+ import { targetWorker } from "./add";
11
+
12
+ /**
13
+ * `pithy ui` — one command scaffolds a front end into an existing worker and wires it end to end, so
14
+ * the SPA and the API deploy as one unit on one origin.
15
+ *
16
+ * There are deliberately **no provider flags**. The sign-in screen renders its social buttons from the
17
+ * target worker's own `pithy.config.ts` through `virtual:pithy/auth`; a `--google` here would be a
18
+ * second source of truth that drifts the moment someone edits config or runs `pithy add auth`.
19
+ */
20
+
21
+ /** What each capability's screen set is called at a prompt. */
22
+ const SCREEN_QUESTIONS: Record<UiScreenSet, string> = {
23
+ auth: "Scaffold the passwordless sign-in screens?",
24
+ payments: "Scaffold the paywall and subscription screens?",
25
+ };
26
+
27
+ /** Ask whether to scaffold one capability's screens. Attached only when a human can answer. */
28
+ async function promptScreens(request: { screens: UiScreenSet; suggestion: boolean }): Promise<boolean> {
29
+ const { confirm, isCancel } = await import("@clack/prompts");
30
+ const answer = await confirm({
31
+ message: SCREEN_QUESTIONS[request.screens],
32
+ initialValue: request.suggestion,
33
+ });
34
+ if (isCancel(answer)) {
35
+ process.stderr.write("Canceled.\n");
36
+ process.exit(1);
37
+ }
38
+ return answer;
39
+ }
40
+
41
+ /** `pithy ui add <framework>` — scaffold a front end into one worker and wire it. */
42
+ const add = defineCommand({
43
+ meta: {
44
+ name: "add",
45
+ description:
46
+ "Scaffold a front end into a worker and serve it from that worker (one origin, one deploy). No provider flags: the sign-in screen renders from that worker's pithy.config.ts",
47
+ },
48
+ args: {
49
+ framework: { type: "positional", required: true, description: "Framework stub, e.g. react. See pithy ui list" },
50
+ worker: { type: "string", description: "Which worker to scaffold into (apps/<name>)" },
51
+ // No default on either: undefined means "decide" — prompt a human, else follow whether the
52
+ // capability is composed on that worker.
53
+ auth: { type: "boolean", description: "Include the passwordless sign-in screens (--no-auth for the bare SPA)" },
54
+ payments: { type: "boolean", description: "Include the paywall and subscription screens (--no-payments to skip)" },
55
+ json: { type: "boolean", default: false, description: "Machine-readable output" },
56
+ },
57
+ run: ({ args }) =>
58
+ withErrorReporting(args.json, async () => {
59
+ const projectDir = process.cwd();
60
+ const interactive = !args.json && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
61
+ const target = await targetWorker({
62
+ projectDir,
63
+ interactive,
64
+ ...(args.worker === undefined ? {} : { worker: args.worker }),
65
+ });
66
+
67
+ const report = await runUiAdd({
68
+ projectDir,
69
+ workerDir: target.dir,
70
+ config: target.config,
71
+ framework: args.framework,
72
+ ...(args.auth === undefined ? {} : { auth: args.auth }),
73
+ ...(args.payments === undefined ? {} : { payments: args.payments }),
74
+ ...(interactive ? { prompt: promptScreens } : {}),
75
+ });
76
+
77
+ if (args.json) {
78
+ process.stdout.write(`${formatJsonLine({ command: "ui.add", ...report, ...workerIdentity(target) })}\n`);
79
+ return;
80
+ }
81
+ const screens = [report.auth ? "sign-in" : null, report.payments ? "paywall" : null].filter(
82
+ (name) => name !== null,
83
+ );
84
+ const template = screens.length === 0 ? "Bare template." : `Screens included: ${screens.join(", ")}.`;
85
+ process.stdout.write(`Scaffolded a ${report.framework} front end into ${report.worker}. ${template}\n`);
86
+ if (report.skipped.length > 0) {
87
+ process.stdout.write(`${report.created.length} files added. ${report.skipped.length} left as they were.\n`);
88
+ } else {
89
+ process.stdout.write(`${report.created.length} files created.\n`);
90
+ }
91
+ // The one seeded test, named because its whole value is in being noticed. It is the only file
92
+ // here whose subject is a *silence*: the action label drifting out of the projection is invisible
93
+ // in dev and staging by construction — Cloudflare's test keys answer with no action to compare
94
+ // (#374) — so the first environment that can tell is the one where it refuses every sign-in.
95
+ if (report.auth) {
96
+ process.stdout.write("src/turnstile.test.tsx is yours now. It goes red when the widget stops reading\n");
97
+ process.stdout.write("its action from config. Dev and staging cannot see that drift. Production can.\n");
98
+ }
99
+ // Written and styled are two claims. Saying only the first is what let a backfill report a
100
+ // sign-in screen as created while three of the classes it renders were defined nowhere.
101
+ if (report.unstyled.length > 0) {
102
+ process.stdout.write(`These screens render classes nothing here defines: ${report.unstyled.join(", ")}.\n`);
103
+ process.stdout.write("Define them in src/styles.css, or restore src/pithy-screens.css.\n");
104
+ // Said here because this line used to be the only time anyone was told (#401). It printed once,
105
+ // did not affect the exit, and `pithy ui sync` never asked again.
106
+ process.stdout.write("pithy ui sync --check asks this again, and fails on it.\n");
107
+ }
108
+ process.stdout.write(`Worker-first: ${report.runWorkerFirst.join(", ")}.\n`);
109
+ // Before the install line, because it is the instruction that would otherwise be wrong. While the
110
+ // scope is unpublished the stub's `@pithy-sh/vite` range is dropped, so the install below succeeds
111
+ // and the build then fails on a Vite plugin nothing declared. `pithy init` says this; so does
112
+ // `pithy worker add`; the wording is decided once, in `unpublishedKitNotice`.
113
+ for (const line of unpublishedKitNotice() ?? []) process.stdout.write(`${dim(line)}\n`);
114
+ process.stdout.write(`Install the packages: ${report.packageManager} install. Then pithy dev.\n`);
115
+ // Naming `pithy add` alone was the whole failure: it read as "capabilities change this list",
116
+ // and the routes an adopter writes themselves change it too, with no command in the loop.
117
+ process.stdout.write("That list covers the routes mounted now. Mount another — your own included —\n");
118
+ process.stdout.write("and run pithy ui sync. Gate it in CI with pithy ui sync --check.\n");
119
+ process.stdout.write(`${formatDone()}\n`);
120
+ }),
121
+ });
122
+
123
+ /** `pithy ui sync` — re-derive the worker's asset allowlist from its current route table. */
124
+ const sync = defineCommand({
125
+ meta: {
126
+ name: "sync",
127
+ description: "Re-derive a worker's assets.run_worker_first from its routes, after mounting one",
128
+ },
129
+ args: {
130
+ worker: { type: "string", description: "Which worker to re-derive (apps/<name>)" },
131
+ check: {
132
+ type: "boolean",
133
+ default: false,
134
+ description: "Report drift and write nothing; exits 1 on a shadowed route or an unstyled screen",
135
+ },
136
+ json: { type: "boolean", default: false, description: "Machine-readable output" },
137
+ },
138
+ run: ({ args }) =>
139
+ withErrorReporting(args.json, async () => {
140
+ const projectDir = process.cwd();
141
+ const interactive = !args.json && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
142
+ const target = await targetWorker({
143
+ projectDir,
144
+ interactive,
145
+ ...(args.worker === undefined ? {} : { worker: args.worker }),
146
+ });
147
+
148
+ const report = await runUiSync({
149
+ projectDir,
150
+ workerDir: target.dir,
151
+ config: target.config,
152
+ check: args.check,
153
+ });
154
+
155
+ // A shadowed route is a 200 with the wrong body, so the gate is the exit code, not the wording.
156
+ if (report.uncovered.length > 0) process.exitCode = 1;
157
+ // **And so is a screen with no rules (#401).** Same failure shape — a 200, the wrong output, no
158
+ // error — and it fails only under `--check`, because that is the flag that means "report, write
159
+ // nothing, and tell CI". A plain `pithy ui sync` is somebody fixing their allowlist; exiting 1 on
160
+ // it would break every script that runs the fix.
161
+ if (args.check && report.unstyled.length > 0) process.exitCode = 1;
162
+
163
+ if (args.json) {
164
+ process.stdout.write(`${formatJsonLine({ command: "ui.sync", ...report, ...workerIdentity(target) })}\n`);
165
+ return;
166
+ }
167
+ if (args.check) {
168
+ if (report.uncovered.length > 0) {
169
+ process.stdout.write(`${report.worker}: the SPA shell is answering these, not the worker.\n`);
170
+ for (const route of report.uncovered) process.stdout.write(` ${route}\n`);
171
+ process.stdout.write(`Run pithy ui sync --worker ${report.worker}.\n`);
172
+ }
173
+ if (report.unstyled.length > 0) {
174
+ process.stdout.write(`${report.worker}: these screens render classes nothing here defines.\n`);
175
+ for (const name of report.unstyled) process.stdout.write(` ${name}\n`);
176
+ process.stdout.write("Define them in src/styles.css, or restore src/pithy-screens.css.\n");
177
+ }
178
+ if (report.uncovered.length === 0 && report.unstyled.length === 0) {
179
+ process.stdout.write(`${report.worker}: every route reaches the worker, and every screen is styled.\n`);
180
+ process.stdout.write(`${formatDone()}\n`);
181
+ }
182
+ return;
183
+ }
184
+ if (report.unstyled.length > 0) {
185
+ process.stdout.write(`These screens render classes nothing here defines: ${report.unstyled.join(", ")}.\n`);
186
+ process.stdout.write("Define them in src/styles.css, or restore src/pithy-screens.css.\n");
187
+ }
188
+ if (!report.changed) {
189
+ process.stdout.write(`${report.worker} is already in sync.\n`);
190
+ process.stdout.write(`${formatDone()}\n`);
191
+ return;
192
+ }
193
+ const added = report.after.filter((path) => !report.before.includes(path));
194
+ const removed = report.before.filter((path) => !report.after.includes(path));
195
+ process.stdout.write(`Re-derived ${report.worker}'s worker-first paths.\n`);
196
+ if (added.length > 0) process.stdout.write(`Added: ${added.join(", ")}.\n`);
197
+ if (removed.length > 0) process.stdout.write(`Removed: ${removed.join(", ")}.\n`);
198
+ process.stdout.write(`${formatDone()}\n`);
199
+ }),
200
+ });
201
+
202
+ /** `pithy ui list` — the framework stubs `pithy ui add` can scaffold. */
203
+ const list = defineCommand({
204
+ meta: { name: "list", description: "List the front-end frameworks pithy ui add can scaffold" },
205
+ args: {
206
+ json: { type: "boolean", default: false, description: "Machine-readable output" },
207
+ },
208
+ run: ({ args }) =>
209
+ withErrorReporting(args.json, async () => {
210
+ const stubs = listStubs();
211
+ if (args.json) {
212
+ process.stdout.write(`${formatJsonLine({ command: "ui.list", stubs })}\n`);
213
+ return;
214
+ }
215
+ process.stdout.write(`${formatList(stubs.map((stub) => ({ name: stub.id, description: stub.description })))}\n`);
216
+ }),
217
+ });
218
+
219
+ export default defineCommand({
220
+ meta: { name: "ui", description: "Scaffold and wire a front end served by one of the project's Workers" },
221
+ subCommands: { add, sync, list },
222
+ });