@mostra/cli 0.0.0-stage → 0.2.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 (196) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +18 -2
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +79 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/bootstrap-DfEe_yPr.js +113 -0
  7. package/dist/bootstrap-DfEe_yPr.js.map +1 -0
  8. package/dist/exit-CBIf8nAU.js +79 -0
  9. package/dist/exit-CBIf8nAU.js.map +1 -0
  10. package/dist/flush-BCuWs0Zh.js +718 -0
  11. package/dist/flush-BCuWs0Zh.js.map +1 -0
  12. package/dist/index.d.ts +230 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +3 -0
  15. package/dist/reference/cli.md +977 -0
  16. package/dist/reference/diagnostics.json +1264 -0
  17. package/dist/reference/errors/MOSTRA_A11Y_MINOR.md +23 -0
  18. package/dist/reference/errors/MOSTRA_A11Y_SERIOUS.md +23 -0
  19. package/dist/reference/errors/MOSTRA_ACTION_CANCELLED.md +23 -0
  20. package/dist/reference/errors/MOSTRA_ACTION_EVENT_INVALID.md +23 -0
  21. package/dist/reference/errors/MOSTRA_ACTION_EXPORT_INVALID.md +23 -0
  22. package/dist/reference/errors/MOSTRA_ACTION_INTERRUPTED.md +23 -0
  23. package/dist/reference/errors/MOSTRA_ACTION_RUN_NOT_FOUND.md +23 -0
  24. package/dist/reference/errors/MOSTRA_ACTION_THREW.md +23 -0
  25. package/dist/reference/errors/MOSTRA_ACTION_TOO_LARGE.md +23 -0
  26. package/dist/reference/errors/MOSTRA_ACTION_UNKNOWN_FUNCTION.md +23 -0
  27. package/dist/reference/errors/MOSTRA_ACTION_UNSERIALIZABLE.md +23 -0
  28. package/dist/reference/errors/MOSTRA_ARTIFACT_DIR_NOT_EMPTY.md +23 -0
  29. package/dist/reference/errors/MOSTRA_ARTIFACT_ENTRY_AMBIGUOUS.md +23 -0
  30. package/dist/reference/errors/MOSTRA_ARTIFACT_NOT_FOUND.md +23 -0
  31. package/dist/reference/errors/MOSTRA_ARTIFACT_NO_ENTRY.md +23 -0
  32. package/dist/reference/errors/MOSTRA_ARTIFACT_NO_PAGE.md +23 -0
  33. package/dist/reference/errors/MOSTRA_ARTIFACT_SOURCE_MISSING.md +23 -0
  34. package/dist/reference/errors/MOSTRA_ARTIFACT_SOURCE_UNREADABLE.md +23 -0
  35. package/dist/reference/errors/MOSTRA_ARTIFACT_TEMPLATE_UNKNOWN.md +23 -0
  36. package/dist/reference/errors/MOSTRA_ARTIFACT_VERSION_COLLECTED.md +23 -0
  37. package/dist/reference/errors/MOSTRA_ARTIFACT_VERSION_NOT_FOUND.md +23 -0
  38. package/dist/reference/errors/MOSTRA_BUILD_CRASHED.md +23 -0
  39. package/dist/reference/errors/MOSTRA_BUILD_FAILED.md +23 -0
  40. package/dist/reference/errors/MOSTRA_BUILD_TIMEOUT.md +23 -0
  41. package/dist/reference/errors/MOSTRA_BUILD_UNRESOLVED.md +23 -0
  42. package/dist/reference/errors/MOSTRA_CLI_CONFIG_UNWRITABLE.md +23 -0
  43. package/dist/reference/errors/MOSTRA_CLI_UNKNOWN_COMMAND.md +23 -0
  44. package/dist/reference/errors/MOSTRA_CLI_USAGE.md +23 -0
  45. package/dist/reference/errors/MOSTRA_CONSOLE_ERROR.md +23 -0
  46. package/dist/reference/errors/MOSTRA_CONSOLE_WARNING.md +23 -0
  47. package/dist/reference/errors/MOSTRA_DEP_INSTALL_FAILED.md +23 -0
  48. package/dist/reference/errors/MOSTRA_DEP_MISSING.md +23 -0
  49. package/dist/reference/errors/MOSTRA_DEP_NOT_FOUND.md +23 -0
  50. package/dist/reference/errors/MOSTRA_DEP_OFFLINE.md +23 -0
  51. package/dist/reference/errors/MOSTRA_DEP_SPEC_INVALID.md +23 -0
  52. package/dist/reference/errors/MOSTRA_DEP_TIMEOUT.md +23 -0
  53. package/dist/reference/errors/MOSTRA_DEP_WARM_CONFLICT.md +23 -0
  54. package/dist/reference/errors/MOSTRA_ENV_BIND_FAILED.md +23 -0
  55. package/dist/reference/errors/MOSTRA_ENV_BROWSER_MISSING.md +23 -0
  56. package/dist/reference/errors/MOSTRA_ENV_BUNDLE_DOWNLOAD_FAILED.md +23 -0
  57. package/dist/reference/errors/MOSTRA_ENV_BUNDLE_INVALID.md +23 -0
  58. package/dist/reference/errors/MOSTRA_ENV_BUNDLE_UNAVAILABLE.md +23 -0
  59. package/dist/reference/errors/MOSTRA_ENV_NODE_UNSUPPORTED.md +23 -0
  60. package/dist/reference/errors/MOSTRA_ENV_PORT_TAKEN.md +23 -0
  61. package/dist/reference/errors/MOSTRA_ENV_RUNTIME_MISSING.md +23 -0
  62. package/dist/reference/errors/MOSTRA_ENV_SERVICE_FAILED.md +23 -0
  63. package/dist/reference/errors/MOSTRA_ENV_SHELL_MISSING.md +23 -0
  64. package/dist/reference/errors/MOSTRA_ENV_WARM_MISSING.md +23 -0
  65. package/dist/reference/errors/MOSTRA_FIELD_CONFLICT.md +23 -0
  66. package/dist/reference/errors/MOSTRA_FIELD_DUPLICATE_ID.md +23 -0
  67. package/dist/reference/errors/MOSTRA_FIELD_ID_INVALID.md +23 -0
  68. package/dist/reference/errors/MOSTRA_FIELD_ID_UNSTABLE.md +23 -0
  69. package/dist/reference/errors/MOSTRA_FIELD_MUTATION_ID_REUSED.md +23 -0
  70. package/dist/reference/errors/MOSTRA_FIELD_SHARED.md +23 -0
  71. package/dist/reference/errors/MOSTRA_FIELD_TOO_LARGE.md +23 -0
  72. package/dist/reference/errors/MOSTRA_FIELD_UNBOUND.md +23 -0
  73. package/dist/reference/errors/MOSTRA_HOME_DISK_FULL.md +23 -0
  74. package/dist/reference/errors/MOSTRA_HOME_GC_INCOMPLETE.md +23 -0
  75. package/dist/reference/errors/MOSTRA_HOME_GONE.md +23 -0
  76. package/dist/reference/errors/MOSTRA_HOME_INTEGRITY.md +23 -0
  77. package/dist/reference/errors/MOSTRA_HOME_LEDGER_UNREADABLE.md +23 -0
  78. package/dist/reference/errors/MOSTRA_HOME_LOCKED.md +23 -0
  79. package/dist/reference/errors/MOSTRA_HOME_MISMATCH.md +23 -0
  80. package/dist/reference/errors/MOSTRA_HOME_NOT_ALWAYS_ON.md +23 -0
  81. package/dist/reference/errors/MOSTRA_HOME_NOT_OWNED.md +23 -0
  82. package/dist/reference/errors/MOSTRA_HOME_SOCKET_TOO_LONG.md +23 -0
  83. package/dist/reference/errors/MOSTRA_HOME_TOO_NEW.md +23 -0
  84. package/dist/reference/errors/MOSTRA_HOST_CONNECTION_CLOSED.md +23 -0
  85. package/dist/reference/errors/MOSTRA_HOST_CURSOR_COMPACTED.md +23 -0
  86. package/dist/reference/errors/MOSTRA_HOST_IDENTITY_UNAVAILABLE.md +23 -0
  87. package/dist/reference/errors/MOSTRA_HOST_INITIALIZE_ORDER.md +23 -0
  88. package/dist/reference/errors/MOSTRA_HOST_INTERNAL_ERROR.md +23 -0
  89. package/dist/reference/errors/MOSTRA_HOST_INVALID_PARAMS.md +23 -0
  90. package/dist/reference/errors/MOSTRA_HOST_MALFORMED.md +23 -0
  91. package/dist/reference/errors/MOSTRA_HOST_MESSAGE_TOO_LARGE.md +23 -0
  92. package/dist/reference/errors/MOSTRA_HOST_METHOD_UNKNOWN.md +23 -0
  93. package/dist/reference/errors/MOSTRA_HOST_NOTIFICATION_UNSUPPORTED.md +23 -0
  94. package/dist/reference/errors/MOSTRA_HOST_PORT_UNSUPPORTED.md +23 -0
  95. package/dist/reference/errors/MOSTRA_HOST_REQUEST_ID_REUSED.md +23 -0
  96. package/dist/reference/errors/MOSTRA_HOST_REVISION_UNSUPPORTED.md +23 -0
  97. package/dist/reference/errors/MOSTRA_HOST_SHUTDOWN_REFUSED.md +23 -0
  98. package/dist/reference/errors/MOSTRA_HOST_SUBSCRIPTION_UNKNOWN.md +23 -0
  99. package/dist/reference/errors/MOSTRA_HOST_TIMEOUT.md +23 -0
  100. package/dist/reference/errors/MOSTRA_HYGIENE_CONTENT_TYPE.md +23 -0
  101. package/dist/reference/errors/MOSTRA_HYGIENE_CSRF_MISSING.md +23 -0
  102. package/dist/reference/errors/MOSTRA_HYGIENE_HEADERS_TOO_LARGE.md +23 -0
  103. package/dist/reference/errors/MOSTRA_HYGIENE_HOST_REFUSED.md +23 -0
  104. package/dist/reference/errors/MOSTRA_HYGIENE_NOT_SERVED.md +23 -0
  105. package/dist/reference/errors/MOSTRA_HYGIENE_ORIGIN_REFUSED.md +23 -0
  106. package/dist/reference/errors/MOSTRA_HYGIENE_RATE_LIMITED.md +23 -0
  107. package/dist/reference/errors/MOSTRA_HYGIENE_REQUEST_MALFORMED.md +23 -0
  108. package/dist/reference/errors/MOSTRA_HYGIENE_SCOPE_DENIED.md +23 -0
  109. package/dist/reference/errors/MOSTRA_HYGIENE_SESSION_CONFLICT.md +23 -0
  110. package/dist/reference/errors/MOSTRA_HYGIENE_SESSION_MISSING.md +23 -0
  111. package/dist/reference/errors/MOSTRA_HYGIENE_TOKEN_INVALID.md +23 -0
  112. package/dist/reference/errors/MOSTRA_HYGIENE_TOKEN_REVOKED.md +23 -0
  113. package/dist/reference/errors/MOSTRA_LIVE_SOURCE_FAILED.md +23 -0
  114. package/dist/reference/errors/MOSTRA_LIVE_TOO_LARGE.md +23 -0
  115. package/dist/reference/errors/MOSTRA_MANIFEST_INVALID.md +23 -0
  116. package/dist/reference/errors/MOSTRA_MANIFEST_NOT_LITERAL.md +23 -0
  117. package/dist/reference/errors/MOSTRA_MANIFEST_UNKNOWN_KEY.md +23 -0
  118. package/dist/reference/errors/MOSTRA_NETWORK_EXTERNAL_FAILED.md +23 -0
  119. package/dist/reference/errors/MOSTRA_NETWORK_REQUEST_FAILED.md +23 -0
  120. package/dist/reference/errors/MOSTRA_PLATFORM_UNSUPPORTED.md +23 -0
  121. package/dist/reference/errors/MOSTRA_PUBLISH_ID_CONFLICT.md +23 -0
  122. package/dist/reference/errors/MOSTRA_PUBLISH_SOURCE_CHANGED.md +23 -0
  123. package/dist/reference/errors/MOSTRA_PUBLISH_VERIFY_FAILED.md +23 -0
  124. package/dist/reference/errors/MOSTRA_REGRESSION_ACTION_REMOVED.md +23 -0
  125. package/dist/reference/errors/MOSTRA_REGRESSION_ARIA.md +23 -0
  126. package/dist/reference/errors/MOSTRA_REGRESSION_FIELD_REMOVED.md +23 -0
  127. package/dist/reference/errors/MOSTRA_REGRESSION_LIVE_REMOVED.md +23 -0
  128. package/dist/reference/errors/MOSTRA_REGRESSION_PAGE_REMOVED.md +23 -0
  129. package/dist/reference/errors/MOSTRA_REGRESSION_PIXELS.md +23 -0
  130. package/dist/reference/errors/MOSTRA_RUNTIME_BLANK.md +23 -0
  131. package/dist/reference/errors/MOSTRA_RUNTIME_NOT_READY.md +23 -0
  132. package/dist/reference/errors/MOSTRA_RUNTIME_PAGE_ERROR.md +23 -0
  133. package/dist/reference/errors/MOSTRA_RUNTIME_WORKER_CRASHED.md +23 -0
  134. package/dist/reference/errors/MOSTRA_RUNTIME_WORKER_PARKED.md +23 -0
  135. package/dist/reference/errors/MOSTRA_SECRET_MISSING.md +23 -0
  136. package/dist/reference/errors/MOSTRA_SMOKE_BUDGET.md +23 -0
  137. package/dist/reference/errors/MOSTRA_SMOKE_NO_EFFECT.md +23 -0
  138. package/dist/reference/errors/MOSTRA_SUBMIT_ALREADY.md +23 -0
  139. package/dist/reference/errors/MOSTRA_SUBMIT_BLOCKED.md +23 -0
  140. package/dist/reference/errors/MOSTRA_SUBMIT_CHANNEL_DENIED.md +23 -0
  141. package/dist/reference/errors/MOSTRA_SUBMIT_NOT_FOUND.md +23 -0
  142. package/dist/reference/errors/MOSTRA_SUBMIT_POLICY_NONE.md +23 -0
  143. package/dist/reference/errors/MOSTRA_SUBMIT_REVISION_MOVED.md +23 -0
  144. package/dist/reference/errors/MOSTRA_SUBMIT_SIGNAL_INVALID.md +23 -0
  145. package/dist/reference/errors/MOSTRA_SUBMIT_VIEW_INVALID.md +23 -0
  146. package/dist/reference/errors/MOSTRA_TARGET_UNAVAILABLE.md +23 -0
  147. package/dist/reference/errors/MOSTRA_TYPE_ERROR.md +23 -0
  148. package/dist/reference/errors/MOSTRA_TYPE_UNTYPED_PACKAGE.md +23 -0
  149. package/dist/reference/errors/MOSTRA_VERIFY_ACTION_SKIPPED.md +23 -0
  150. package/dist/reference/errors/MOSTRA_VERIFY_BASELINE_MISSING.md +23 -0
  151. package/dist/reference/errors/MOSTRA_VERIFY_CRASHED.md +23 -0
  152. package/dist/reference/errors/MOSTRA_VERIFY_LIVE_FAILED.md +23 -0
  153. package/dist/reference/errors/MOSTRA_VERIFY_NO_BASELINE.md +23 -0
  154. package/dist/reference/errors/MOSTRA_VERIFY_SECRET_MISSING.md +23 -0
  155. package/dist/reference/errors/MOSTRA_VERIFY_SKIP_CONSEQUENCE.md +23 -0
  156. package/dist/reference/errors/MOSTRA_VERIFY_TIMEOUT.md +23 -0
  157. package/dist/reference/telemetry.md +82 -0
  158. package/dist/run-BJaRnz-Q.js +3824 -0
  159. package/dist/run-BJaRnz-Q.js.map +1 -0
  160. package/dist/run-CMyjOm0Q.js +2 -0
  161. package/dist/telemetry/flush-main.d.ts +1 -0
  162. package/dist/telemetry/flush-main.js +14 -0
  163. package/dist/telemetry/flush-main.js.map +1 -0
  164. package/package.json +62 -3
  165. package/src/bin.ts +70 -0
  166. package/src/bootstrap.ts +156 -0
  167. package/src/browser.ts +186 -0
  168. package/src/client.ts +313 -0
  169. package/src/command.ts +147 -0
  170. package/src/commands/admin.ts +791 -0
  171. package/src/commands/authoring.ts +244 -0
  172. package/src/commands/dev.ts +106 -0
  173. package/src/commands/index.ts +71 -0
  174. package/src/commands/interaction.ts +288 -0
  175. package/src/commands/shared.ts +71 -0
  176. package/src/commands/viewing.ts +153 -0
  177. package/src/docs.ts +111 -0
  178. package/src/exit.ts +89 -0
  179. package/src/help.ts +170 -0
  180. package/src/home.ts +111 -0
  181. package/src/index.ts +23 -0
  182. package/src/node-gate.ts +15 -0
  183. package/src/parse.ts +246 -0
  184. package/src/run.ts +387 -0
  185. package/src/telemetry/decision.ts +101 -0
  186. package/src/telemetry/events.ts +147 -0
  187. package/src/telemetry/flush-main.ts +13 -0
  188. package/src/telemetry/flush.ts +143 -0
  189. package/src/telemetry/harness.ts +39 -0
  190. package/src/telemetry/index.ts +318 -0
  191. package/src/telemetry/store.ts +379 -0
  192. package/src/telemetry/timezone.ts +72 -0
  193. package/src/telemetry/words.ts +87 -0
  194. package/src/telemetry/zone-sets.ts +182 -0
  195. package/src/telemetry/zones.generated.ts +720 -0
  196. package/src/version.ts +43 -0
package/src/exit.ts ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The exit codes (INTERFACES §9, D140 item 16): one table keyed by diagnostic code, so every
3
+ * command ends the same way for the same failure. Each area has a code, and a few codes differ
4
+ * from their area; the table is total over the catalog by construction, and a test checks every
5
+ * catalog code against it.
6
+ */
7
+ import type { DiagnosticArea } from "@mostra/protocol";
8
+
9
+ /** The exit codes of INTERFACES §9. */
10
+ export const EXIT = {
11
+ /** Success, or `verify` passed. */
12
+ ok: 0,
13
+ /** Artifact failure: verify failed, publish refused, a request the artifact's state refused. */
14
+ failure: 1,
15
+ /** Usage error. */
16
+ usage: 2,
17
+ /** Environment error: no runtime, browser or socket; the home; an unsupported platform. */
18
+ environment: 3,
19
+ /** Timeout: `--timeout` passed before the instance answered. */
20
+ timeout: 4,
21
+ /** `wait` ended `still_waiting`: not an error, the status is in the result. */
22
+ stillWaiting: 5,
23
+ /** Conflict: `once` already submitted, a compare-and-set mismatch, a reused request ID. */
24
+ conflict: 6,
25
+ } as const;
26
+
27
+ /** One of {@link EXIT}'s codes. */
28
+ export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
29
+
30
+ /** Each area's exit code. Every area is here: a new one fails the type check until it is. */
31
+ const BY_AREA: Readonly<Record<DiagnosticArea, ExitCode>> = {
32
+ MANIFEST: EXIT.failure,
33
+ BUILD: EXIT.failure,
34
+ TYPE: EXIT.failure,
35
+ LINT: EXIT.failure,
36
+ DEP: EXIT.failure,
37
+ KIT: EXIT.failure,
38
+ FIELD: EXIT.failure,
39
+ ACTION: EXIT.failure,
40
+ LIVE: EXIT.failure,
41
+ RUNTIME: EXIT.failure,
42
+ CONSOLE: EXIT.failure,
43
+ NETWORK: EXIT.failure,
44
+ A11Y: EXIT.failure,
45
+ VISUAL: EXIT.failure,
46
+ SMOKE: EXIT.failure,
47
+ REGRESSION: EXIT.failure,
48
+ TERM: EXIT.failure,
49
+ PUBLISH: EXIT.failure,
50
+ SUBMIT: EXIT.failure,
51
+ HOST: EXIT.environment,
52
+ HOME: EXIT.environment,
53
+ HYGIENE: EXIT.failure,
54
+ SECRET: EXIT.failure,
55
+ SHARE: EXIT.environment,
56
+ VERIFY: EXIT.failure,
57
+ TARGET: EXIT.failure,
58
+ ENV: EXIT.environment,
59
+ PLATFORM: EXIT.environment,
60
+ ARTIFACT: EXIT.failure,
61
+ CLI: EXIT.usage,
62
+ };
63
+
64
+ /** Codes whose exit code isn't their area's. */
65
+ const BY_CODE: Readonly<Record<string, ExitCode>> = {
66
+ MOSTRA_HOST_TIMEOUT: EXIT.timeout,
67
+ // The CLI can't record a choice: its environment, not its command line.
68
+ MOSTRA_CLI_CONFIG_UNWRITABLE: EXIT.environment,
69
+ MOSTRA_HOST_INVALID_PARAMS: EXIT.usage,
70
+ MOSTRA_HOST_REQUEST_ID_REUSED: EXIT.conflict,
71
+ MOSTRA_HOST_CURSOR_COMPACTED: EXIT.failure,
72
+ MOSTRA_HOST_SUBSCRIPTION_UNKNOWN: EXIT.usage,
73
+ MOSTRA_SUBMIT_ALREADY: EXIT.conflict,
74
+ MOSTRA_SUBMIT_REVISION_MOVED: EXIT.conflict,
75
+ MOSTRA_FIELD_CONFLICT: EXIT.conflict,
76
+ MOSTRA_FIELD_MUTATION_ID_REUSED: EXIT.conflict,
77
+ MOSTRA_PUBLISH_ID_CONFLICT: EXIT.failure,
78
+ MOSTRA_HOME_INTEGRITY: EXIT.failure,
79
+ MOSTRA_HOME_NOT_ALWAYS_ON: EXIT.ok,
80
+ MOSTRA_VERIFY_CRASHED: EXIT.environment,
81
+ };
82
+
83
+ /** The exit code a diagnostic ends a command with. An unknown area is an environment error. */
84
+ export function exitCodeFor(code: string): ExitCode {
85
+ const exact = BY_CODE[code];
86
+ if (exact !== undefined) return exact;
87
+ const area = /^MOSTRA_([A-Z0-9]+)_/.exec(code)?.[1];
88
+ return (area !== undefined ? BY_AREA[area as DiagnosticArea] : undefined) ?? EXIT.environment;
89
+ }
package/src/help.ts ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Help, generated from the command definitions (D140 items 8 and 16): `--help` for people,
3
+ * `--help --json` for agents (the definition itself), and `docs/reference/cli.md`, which CI
4
+ * checks is current. The wording lives in the definitions; this module only lays it out.
5
+ */
6
+ import { type CommandDef, commandName, type FlagDef } from "./command.ts";
7
+ import { EXIT } from "./exit.ts";
8
+ import { GLOBAL_FLAGS, topWords } from "./parse.ts";
9
+
10
+ const EXIT_MEANING: Readonly<Record<number, string>> = {
11
+ [EXIT.ok]: "success",
12
+ [EXIT.failure]:
13
+ "artifact failure (verify failed, publish refused, the artifact's state refused the request)",
14
+ [EXIT.usage]: "usage error",
15
+ [EXIT.environment]:
16
+ "environment error (no runtime, browser or socket; the home; an unsupported platform)",
17
+ [EXIT.timeout]: "timeout (--timeout passed)",
18
+ [EXIT.stillWaiting]: "still waiting (the wait timed out; the result says where to resume)",
19
+ [EXIT.conflict]: "conflict (already submitted, a compare-and-set mismatch, a reused request ID)",
20
+ };
21
+
22
+ /** The usage line: `mostra open <artifact> [flags]`. */
23
+ export function usageLine(command: CommandDef): string {
24
+ const args = (command.args ?? []).map((arg) => {
25
+ const name = arg.variadic ? `<${arg.name}…>` : `<${arg.name}>`;
26
+ return arg.optional ? `[${name}]` : name;
27
+ });
28
+ return [
29
+ "mostra",
30
+ ...command.path,
31
+ ...args,
32
+ ...((command.flags ?? []).length > 0 ? ["[flags]"] : []),
33
+ ].join(" ");
34
+ }
35
+
36
+ function flagLine(flag: FlagDef): string {
37
+ const name = `--${flag.name}${flag.kind === "boolean" ? "" : ` ${flag.value ?? `<${flag.kind}>`}`}`;
38
+ const choices = flag.choices ? ` (${flag.choices.join(", ")})` : "";
39
+ const fallback = flag.default !== undefined ? ` Default: ${flag.default}.` : "";
40
+ return ` ${name.padEnd(28)} ${flag.summary}${choices}${fallback}`;
41
+ }
42
+
43
+ /** Commands that run until their connection or Ctrl-C ends them: `--timeout` doesn't bound them. */
44
+ export const UNBOUNDED: ReadonlySet<string> = new Set(["host", "dev"]);
45
+
46
+ /**
47
+ * The global flags a command doesn't define again (`wait --timeout` is the wait's own); a command
48
+ * `--timeout` doesn't bound has no `--timeout`.
49
+ */
50
+ function globalFlagsOf(command: CommandDef): FlagDef[] {
51
+ const own = new Set((command.flags ?? []).map((flag) => flag.name));
52
+ if (UNBOUNDED.has(command.path[0] ?? "")) own.add("timeout");
53
+ return GLOBAL_FLAGS.filter((flag) => !own.has(flag.name));
54
+ }
55
+
56
+ /** Every exit code a command can end with, sorted: `--timeout` can end any it bounds with 4. */
57
+ export function exitCodes(command: CommandDef): number[] {
58
+ const bounded = !UNBOUNDED.has(command.path[0] ?? "") && command.path[0] !== "wait";
59
+ return [
60
+ ...new Set([
61
+ EXIT.ok,
62
+ ...command.exits,
63
+ EXIT.usage,
64
+ EXIT.environment,
65
+ ...(bounded ? [EXIT.timeout] : []),
66
+ ]),
67
+ ].sort((a, b) => a - b);
68
+ }
69
+
70
+ /** One command's help, for people. */
71
+ export function commandHelp(command: CommandDef): string {
72
+ const lines = [command.summary, "", "Usage:", ` ${usageLine(command)}`];
73
+ if ((command.args ?? []).length > 0) {
74
+ lines.push("", "Arguments:");
75
+ for (const arg of command.args ?? [])
76
+ lines.push(` ${`<${arg.name}>`.padEnd(28)} ${arg.summary}`);
77
+ }
78
+ if ((command.flags ?? []).length > 0) {
79
+ lines.push("", "Flags:");
80
+ for (const flag of command.flags ?? []) lines.push(flagLine(flag));
81
+ }
82
+ lines.push("", "Global flags:");
83
+ for (const flag of globalFlagsOf(command)) lines.push(flagLine(flag));
84
+ lines.push("", `Output: ${command.output.type} (mostra docs ${command.output.topic})`);
85
+ lines.push("", "Exit codes:");
86
+ for (const code of exitCodes(command)) lines.push(` ${code} ${EXIT_MEANING[code] ?? ""}`);
87
+ lines.push("", "Example:", ` mostra ${command.example}`, "");
88
+ return lines.join("\n");
89
+ }
90
+
91
+ /** A group's help (`mostra daemon --help`): its commands. */
92
+ export function groupHelp(commands: readonly CommandDef[], group: string): string {
93
+ const members = commands.filter((command) => command.path[0] === group);
94
+ return [
95
+ `mostra ${group}: ${members.length} commands.`,
96
+ "",
97
+ ...members.map((command) => ` ${commandName(command).padEnd(22)} ${command.summary}`),
98
+ "",
99
+ `Run "mostra ${group} <command> --help" for one of them.`,
100
+ "",
101
+ ].join("\n");
102
+ }
103
+
104
+ /** The top-level help: every command. */
105
+ export function topHelp(commands: readonly CommandDef[]): string {
106
+ return [
107
+ "Mostra, the agent artifact runtime: write ordinary React, verify it, publish it, share the link, wait for answers.",
108
+ "",
109
+ "Usage:",
110
+ " mostra <command> [arguments] [flags]",
111
+ "",
112
+ "Commands:",
113
+ ...commands.map((command) => ` ${commandName(command).padEnd(22)} ${command.summary}`),
114
+ "",
115
+ "Global flags:",
116
+ ...GLOBAL_FLAGS.map(flagLine),
117
+ "",
118
+ 'Run "mostra <command> --help" for a command, and "mostra docs" for the reference.',
119
+ "",
120
+ ].join("\n");
121
+ }
122
+
123
+ /** One command's machine-readable definition (`--help --json`). */
124
+ export function commandDefinition(command: CommandDef): Record<string, unknown> {
125
+ return {
126
+ command: commandName(command),
127
+ summary: command.summary,
128
+ usage: usageLine(command),
129
+ args: (command.args ?? []).map((arg) => ({
130
+ name: arg.name,
131
+ summary: arg.summary,
132
+ optional: arg.optional === true,
133
+ variadic: arg.variadic === true,
134
+ })),
135
+ flags: [...(command.flags ?? []), ...globalFlagsOf(command)].map((flag) => ({
136
+ name: flag.name,
137
+ type: flag.kind,
138
+ summary: flag.summary,
139
+ ...(flag.choices ? { choices: flag.choices } : {}),
140
+ ...(flag.default !== undefined ? { default: flag.default } : {}),
141
+ global: GLOBAL_FLAGS.includes(flag),
142
+ })),
143
+ output: command.output,
144
+ exitCodes: exitCodes(command).map((code) => ({ code, meaning: EXIT_MEANING[code] ?? "" })),
145
+ stdin: typeof command.readsStdin === "function" ? "sometimes" : command.readsStdin === true,
146
+ streams: command.streams === true,
147
+ example: `mostra ${command.example}`,
148
+ };
149
+ }
150
+
151
+ /** `docs/reference/cli.md`: every command's help as one page. */
152
+ export function referenceMarkdown(commands: readonly CommandDef[]): string {
153
+ const sections = commands.map(
154
+ (command) =>
155
+ `## mostra ${commandName(command)}\n\n\`\`\`text\n${commandHelp(command).trimEnd()}\n\`\`\`\n`,
156
+ );
157
+ return [
158
+ "# The mostra CLI",
159
+ "",
160
+ "<!-- Generated from the command definitions in packages/cli/src/commands/. Don't edit; run `pnpm fix`. -->",
161
+ "",
162
+ 'Every command takes `--json`, which prints one envelope on stdout: `{ "ok": true, "result", "instance", "warnings" }` or `{ "ok": false, "error", "warnings" }`. Streaming commands print one envelope per line, the last one carrying `result` or `error`. Logs and progress go to stderr. Without a terminal, or with `--json`, no command asks a question.',
163
+ "",
164
+ `Commands: ${topWords(commands)
165
+ .map((word) => `\`${word}\``)
166
+ .join(", ")}.`,
167
+ "",
168
+ ...sections,
169
+ ].join("\n");
170
+ }
package/src/home.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Where the CLI's things are: the home (D84's order), the runtime it starts, the home's settings
3
+ * it reads, and the CLI's own config directory (D140 item 5). Resolving a path never touches the
4
+ * file system; the runtime owns everything inside the home.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { isAbsolute, join, resolve } from "node:path";
9
+ import { resolveRuntime } from "@mostra/sdk";
10
+
11
+ /** The environment the CLI reads. */
12
+ export type Environment = Readonly<Record<string, string | undefined>>;
13
+
14
+ const nonEmpty = (value: string | undefined): string | undefined =>
15
+ value === undefined || value === "" ? undefined : value;
16
+
17
+ /**
18
+ * The home, in D84's order: `--home`, then `MOSTRA_HOME`, then the platform default (`~/.mostra`
19
+ * on macOS, `${XDG_DATA_HOME:-~/.local/share}/mostra` on Linux), the same order the runtime uses.
20
+ */
21
+ export function resolveHome(options: {
22
+ readonly home?: string | undefined;
23
+ readonly env: Environment;
24
+ readonly cwd: string;
25
+ readonly platform?: string;
26
+ }): string {
27
+ const chosen = nonEmpty(options.home) ?? nonEmpty(options.env["MOSTRA_HOME"]);
28
+ if (chosen !== undefined) return resolve(options.cwd, chosen);
29
+ const user = nonEmpty(options.env["HOME"]) ?? homedir();
30
+ if ((options.platform ?? process.platform) === "darwin") return join(user, ".mostra");
31
+ const data = nonEmpty(options.env["XDG_DATA_HOME"]);
32
+ return join(
33
+ data !== undefined && isAbsolute(data) ? data : join(user, ".local", "share"),
34
+ "mostra",
35
+ );
36
+ }
37
+
38
+ /**
39
+ * The CLI's config directory (D140 item 5): `MOSTRA_CONFIG_DIR`, else
40
+ * `${XDG_CONFIG_HOME:-~/.config}/mostra`, on both platforms. It holds telemetry's choice and
41
+ * queue, per operating-system user, never per home.
42
+ */
43
+ export function configDir(env: Environment, cwd: string): string {
44
+ const override = nonEmpty(env["MOSTRA_CONFIG_DIR"]);
45
+ if (override !== undefined) return resolve(cwd, override);
46
+ const xdg = nonEmpty(env["XDG_CONFIG_HOME"]);
47
+ const base =
48
+ xdg !== undefined && isAbsolute(xdg)
49
+ ? xdg
50
+ : join(nonEmpty(env["HOME"]) ?? homedir(), ".config");
51
+ return join(base, "mostra");
52
+ }
53
+
54
+ /** The runtime the CLI starts: its Node.js and its `mostra-runtime` entry. */
55
+ export interface RuntimeLocation {
56
+ /** The Node.js to run it with: the runtime bundle's own, or this process's for a checkout. */
57
+ readonly node: string;
58
+ /** `mostra-runtime`'s entry. */
59
+ readonly entry: string;
60
+ }
61
+
62
+ /**
63
+ * The runtime the CLI starts (D85, RT-01), found as the SDK finds it: `MOSTRA_BUNDLE`,
64
+ * `MOSTRA_RUNTIME_BIN`, the runtime bundle this CLI sits in, or a development checkout's built
65
+ * runtime beside it. The bootstrap (`bootstrap.ts`) has already moved a CLI installed from npm
66
+ * into its pinned bundle, so this never downloads. Throws the SDK's diagnostic when there is none.
67
+ */
68
+ export async function locateRuntime(env: Environment): Promise<RuntimeLocation> {
69
+ const runtime = await resolveRuntime({ env });
70
+ return { node: runtime.node, entry: runtime.runtimeEntry };
71
+ }
72
+
73
+ /** What the CLI reads of a home's `config.json` (DATA_MODEL §1). */
74
+ export interface HomeMarks {
75
+ /** The first host that configured the home (D140 item 4), if any. */
76
+ readonly hostConfigured?: { readonly client: string; readonly at: string };
77
+ /** Whether the file couldn't be read as settings: the CLI then treats the home as a host's. */
78
+ readonly unreadable?: true;
79
+ }
80
+
81
+ /**
82
+ * Whether the CLI treats the home as a host's, which sends no telemetry (D140 item 4): a host
83
+ * marked it, or its `config.json` can't be read as settings (fail closed). One rule for the
84
+ * runner and for `mostra telemetry` (Opus review finding 6).
85
+ */
86
+ export function embeddedHome(marks: HomeMarks): boolean {
87
+ return marks.hostConfigured !== undefined || marks.unreadable === true;
88
+ }
89
+
90
+ /**
91
+ * The home's host mark, read from `config.json` without the instance, for commands that never
92
+ * reach one. A file that can't be read as JSON counts as a host's home, so telemetry fails closed.
93
+ */
94
+ export function readHomeMarks(home: string): HomeMarks {
95
+ let text: string;
96
+ try {
97
+ text = readFileSync(join(home, "config.json"), "utf8");
98
+ } catch (error) {
99
+ return (error as NodeJS.ErrnoException).code === "ENOENT" ? {} : { unreadable: true };
100
+ }
101
+ try {
102
+ const data = JSON.parse(text) as { hostConfigured?: { client?: unknown; at?: unknown } };
103
+ const mark = data.hostConfigured;
104
+ if (mark === undefined) return {};
105
+ return typeof mark.client === "string" && typeof mark.at === "string"
106
+ ? { hostConfigured: { client: mark.client, at: mark.at } }
107
+ : { unreadable: true };
108
+ } catch {
109
+ return { unreadable: true };
110
+ }
111
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@mostra/cli`: the `mostra` command (D60, INTERFACES §9), a thin client of the host protocol
3
+ * that agents and people run. Its binary is `mostra`; this entry exposes the runner and the
4
+ * command definitions, for the generated reference and for tools that embed the CLI's surface.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ export type { InstanceConnection, InstanceSummary } from "./client.ts";
10
+ export type {
11
+ ArgDef,
12
+ CommandContext,
13
+ CommandDef,
14
+ CommandOutcome,
15
+ FlagDef,
16
+ FlagKind,
17
+ TelemetryEventInput,
18
+ } from "./command.ts";
19
+ export { COMMANDS } from "./commands/index.ts";
20
+ export { EXIT, type ExitCode, exitCodeFor } from "./exit.ts";
21
+ export { commandDefinition, commandHelp, referenceMarkdown } from "./help.ts";
22
+ export type { Environment } from "./home.ts";
23
+ export { type RunnerIo, run } from "./run.ts";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The first thing `mostra` does (RT-01, D142): a copy installed from npm starts on the machine's
3
+ * Node.js, which must be 22.19 or later to find and start the runtime bundle (the SDK's download
4
+ * and extraction need it). An older one fails closed with `MOSTRA_ENV_NODE_UNSUPPORTED` before
5
+ * anything else loads, so nothing breaks half-way on an API it lacks. This module imports nothing.
6
+ */
7
+
8
+ /** The oldest Node.js `mostra` starts on, as `[major, minor]`. */
9
+ export const MINIMUM_NODE: readonly [number, number] = [22, 19];
10
+
11
+ /** Whether `version` (`process.versions.node`, `22.18.0`) is older than {@link MINIMUM_NODE}. */
12
+ export function nodeTooOld(version: string): boolean {
13
+ const [major = 0, minor = 0] = version.split(".").map((part) => Number.parseInt(part, 10));
14
+ return major < MINIMUM_NODE[0] || (major === MINIMUM_NODE[0] && minor < MINIMUM_NODE[1]);
15
+ }
package/src/parse.ts ADDED
@@ -0,0 +1,246 @@
1
+ /**
2
+ * The command line, read against the command definitions (D140 item 13): the global flags, the
3
+ * command path, its arguments and flags. Every failure is `MOSTRA_CLI_USAGE` or
4
+ * `MOSTRA_CLI_UNKNOWN_COMMAND`, so `--json` mode reports it like any other error.
5
+ */
6
+ import { parseArgs } from "node:util";
7
+ import { createDiagnostic, RpcError } from "@mostra/protocol";
8
+ import { type CommandDef, commandName, type FlagDef } from "./command.ts";
9
+
10
+ /** The flags every command takes (INTERFACES §9). */
11
+ export const GLOBAL_FLAGS: readonly FlagDef[] = [
12
+ {
13
+ name: "json",
14
+ kind: "boolean",
15
+ summary: "Print one JSON envelope on stdout (NDJSON for streams).",
16
+ },
17
+ {
18
+ name: "home",
19
+ kind: "string",
20
+ value: "<dir>",
21
+ summary: "Use this Mostra home instead of MOSTRA_HOME or the default.",
22
+ },
23
+ {
24
+ name: "cwd",
25
+ kind: "string",
26
+ value: "<dir>",
27
+ summary: "Resolve relative paths from this directory.",
28
+ },
29
+ {
30
+ name: "timeout",
31
+ kind: "duration",
32
+ value: "<d>",
33
+ summary: "Give up on the instance after this long (exit 4), such as 30s or 5m.",
34
+ },
35
+ {
36
+ name: "help",
37
+ kind: "boolean",
38
+ summary: "Show this command's help; with --json, its definition.",
39
+ },
40
+ ];
41
+
42
+ /** What the command line asks for. */
43
+ export type Parsed =
44
+ | { readonly kind: "version" }
45
+ | { readonly kind: "help"; readonly command?: CommandDef; readonly group?: string }
46
+ | {
47
+ readonly kind: "run";
48
+ readonly command: CommandDef;
49
+ readonly args: Record<string, string | readonly string[] | undefined>;
50
+ readonly flags: Record<string, string | number | boolean | readonly string[] | undefined>;
51
+ };
52
+
53
+ /** The global flags' values, read before anything else so a usage error still honors them. */
54
+ export interface GlobalFlags {
55
+ readonly json: boolean;
56
+ readonly home?: string;
57
+ readonly cwd?: string;
58
+ readonly timeoutMs?: number;
59
+ }
60
+
61
+ /** A usage error. */
62
+ export function usage(message: string): RpcError {
63
+ return new RpcError(createDiagnostic("MOSTRA_CLI_USAGE", { message }));
64
+ }
65
+
66
+ const DURATION = /^([1-9][0-9]*)(ms|s|m|h|d)$/;
67
+ const UNIT_MS: Readonly<Record<string, number>> = {
68
+ ms: 1,
69
+ s: 1000,
70
+ m: 60_000,
71
+ h: 3_600_000,
72
+ d: 86_400_000,
73
+ };
74
+
75
+ /** A duration flag's milliseconds: `30s`, `5m`, `2h`, `7d`, `500ms`. */
76
+ export function durationMs(value: string): number {
77
+ const match = DURATION.exec(value);
78
+ if (!match)
79
+ throw usage(`"${value}" isn't a duration; use a number with ms, s, m, h or d, such as 30s.`);
80
+ return Number(match[1]) * (UNIT_MS[match[2] as string] as number);
81
+ }
82
+
83
+ /**
84
+ * Reads the global flags wherever they stand, before the command is known: `--json` anywhere
85
+ * before `--` makes even a usage error a JSON envelope (D140 item 16).
86
+ */
87
+ export function globalFlags(argv: readonly string[]): GlobalFlags {
88
+ const end = argv.indexOf("--");
89
+ const words = end === -1 ? argv : argv.slice(0, end);
90
+ const value = (name: string): string | undefined => {
91
+ for (let index = 0; index < words.length; index += 1) {
92
+ const word = words[index] as string;
93
+ if (word === `--${name}`) return words[index + 1];
94
+ if (word.startsWith(`--${name}=`)) return word.slice(name.length + 3);
95
+ }
96
+ return undefined;
97
+ };
98
+ const timeout = value("timeout");
99
+ let timeoutMs: number | undefined;
100
+ try {
101
+ timeoutMs = timeout === undefined ? undefined : durationMs(timeout);
102
+ } catch {
103
+ timeoutMs = undefined;
104
+ }
105
+ const home = value("home");
106
+ const cwd = value("cwd");
107
+ return {
108
+ json: words.includes("--json"),
109
+ ...(home !== undefined ? { home } : {}),
110
+ ...(cwd !== undefined ? { cwd } : {}),
111
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
112
+ };
113
+ }
114
+
115
+ /** The command that names `words`' longest prefix. */
116
+ function findCommand(
117
+ commands: readonly CommandDef[],
118
+ words: readonly string[],
119
+ ): { command: CommandDef; used: number } | undefined {
120
+ let best: { command: CommandDef; used: number } | undefined;
121
+ for (const command of commands) {
122
+ const { path } = command;
123
+ if (path.length <= words.length && path.every((word, index) => words[index] === word)) {
124
+ if (!best || path.length > best.used) best = { command, used: path.length };
125
+ }
126
+ }
127
+ return best;
128
+ }
129
+
130
+ /** The first words of every command: the groups and the single-word commands. */
131
+ export function topWords(commands: readonly CommandDef[]): string[] {
132
+ return [...new Set(commands.map((command) => command.path[0] as string))].sort();
133
+ }
134
+
135
+ /** Parses the command line against the definitions. Throws a usage diagnostic. */
136
+ export function parseCommandLine(commands: readonly CommandDef[], argv: readonly string[]): Parsed {
137
+ const bare = argv.filter((word) => word !== "--json");
138
+ if (bare.length === 1 && (bare[0] === "--version" || bare[0] === "-v"))
139
+ return { kind: "version" };
140
+ // The command path is the leading words that aren't flags; global flags may stand before it.
141
+ const words: string[] = [];
142
+ const pathAt: number[] = [];
143
+ for (let index = 0; index < argv.length; index += 1) {
144
+ const word = argv[index] as string;
145
+ if (word === "--json" || word === "--help" || word === "-h") continue;
146
+ if (word === "--home" || word === "--cwd" || word === "--timeout") {
147
+ index += 1;
148
+ continue;
149
+ }
150
+ if (/^--(home|cwd|timeout)=/.test(word)) continue;
151
+ if (word.startsWith("-")) break;
152
+ words.push(word);
153
+ pathAt.push(index);
154
+ }
155
+ const helpAsked = argv.includes("--help") || argv.includes("-h");
156
+ if (words.length === 0) {
157
+ if (helpAsked || argv.every((word) => word === "--json")) return { kind: "help" };
158
+ throw usage(`Name a command first; ${argv[0]} isn't one.`);
159
+ }
160
+ const found = findCommand(commands, words);
161
+ if (!found) {
162
+ const group = commands.filter((command) => command.path[0] === words[0]);
163
+ if (group.length > 0) {
164
+ if (helpAsked) return { kind: "help", group: words[0] as string };
165
+ throw usage(
166
+ `"mostra ${words[0]}" needs one of: ${group.map((command) => command.path.slice(1).join(" ")).join(", ")}.`,
167
+ );
168
+ }
169
+ throw new RpcError(
170
+ createDiagnostic("MOSTRA_CLI_UNKNOWN_COMMAND", {
171
+ message: `"${words[0]}" isn't a mostra command. The commands are: ${topWords(commands).join(", ")}.`,
172
+ }),
173
+ );
174
+ }
175
+ const { command } = found;
176
+ if (helpAsked) return { kind: "help", command };
177
+ const used = new Set(pathAt.slice(0, found.used));
178
+ const rest = argv.filter((_, index) => !used.has(index));
179
+ const flagDefs = [...GLOBAL_FLAGS, ...(command.flags ?? [])];
180
+ let parsed: ReturnType<typeof parseArgs>;
181
+ try {
182
+ parsed = parseArgs({
183
+ args: [...rest],
184
+ allowPositionals: true,
185
+ strict: true,
186
+ options: Object.fromEntries(
187
+ flagDefs.map((flag) => [
188
+ flag.name,
189
+ { type: flag.kind === "boolean" ? "boolean" : "string" },
190
+ ]),
191
+ ) as Parameters<typeof parseArgs>[0] extends { options?: infer O } ? NonNullable<O> : never,
192
+ });
193
+ } catch (error) {
194
+ throw usage(
195
+ `${(error as Error).message.split(". To specify")[0]}. Run "mostra ${commandName(command)} --help".`,
196
+ );
197
+ }
198
+ const flags: Record<string, string | number | boolean | readonly string[] | undefined> = {};
199
+ for (const flag of command.flags ?? []) {
200
+ const raw = parsed.values[flag.name];
201
+ if (raw === undefined) continue;
202
+ if (flag.kind === "boolean") {
203
+ flags[flag.name] = raw === true;
204
+ continue;
205
+ }
206
+ const text = String(raw);
207
+ if (flag.choices && !flag.choices.includes(text)) {
208
+ throw usage(`--${flag.name} takes one of ${flag.choices.join(", ")}; "${text}" isn't one.`);
209
+ }
210
+ if (flag.kind === "number") {
211
+ const number = Number(text);
212
+ if (!Number.isInteger(number) || number < 0)
213
+ throw usage(`--${flag.name} takes a whole number; "${text}" isn't one.`);
214
+ flags[flag.name] = number;
215
+ } else if (flag.kind === "duration") {
216
+ durationMs(text);
217
+ flags[flag.name] = text;
218
+ } else if (flag.kind === "list") {
219
+ flags[flag.name] = text
220
+ .split(",")
221
+ .map((item) => item.trim())
222
+ .filter((item) => item.length > 0);
223
+ } else flags[flag.name] = text;
224
+ }
225
+ const positionals = [...parsed.positionals];
226
+ const args: Record<string, string | readonly string[] | undefined> = {};
227
+ for (const arg of command.args ?? []) {
228
+ if (arg.variadic) {
229
+ if (positionals.length === 0 && !arg.optional)
230
+ throw usage(`"mostra ${commandName(command)}" needs <${arg.name}…>.`);
231
+ args[arg.name] = positionals.splice(0);
232
+ continue;
233
+ }
234
+ const value = positionals.shift();
235
+ if (value === undefined && !arg.optional)
236
+ throw usage(`"mostra ${commandName(command)}" needs <${arg.name}>.`);
237
+ args[arg.name] = value;
238
+ }
239
+ if (positionals.length > 0) {
240
+ throw usage(
241
+ `"mostra ${commandName(command)}" takes no argument "${positionals[0]}". Run "mostra ${commandName(command)} --help".`,
242
+ );
243
+ }
244
+ if (typeof parsed.values["timeout"] === "string") durationMs(parsed.values["timeout"]);
245
+ return { kind: "run", command, args, flags };
246
+ }