@ratio-app/cli 0.1.1

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 (182) hide show
  1. package/README.md +504 -0
  2. package/bin/run.mjs +5 -0
  3. package/dist/commands/__test__/throw.d.ts +25 -0
  4. package/dist/commands/__test__/throw.js +238 -0
  5. package/dist/commands/__test__/throw.js.map +1 -0
  6. package/dist/commands/app/create.d.ts +55 -0
  7. package/dist/commands/app/create.js +609 -0
  8. package/dist/commands/app/create.js.map +1 -0
  9. package/dist/commands/app/deploy.d.ts +14 -0
  10. package/dist/commands/app/deploy.js +179 -0
  11. package/dist/commands/app/deploy.js.map +1 -0
  12. package/dist/commands/app/dev.d.ts +28 -0
  13. package/dist/commands/app/dev.js +701 -0
  14. package/dist/commands/app/dev.js.map +1 -0
  15. package/dist/commands/app/generate.d.ts +14 -0
  16. package/dist/commands/app/generate.js +179 -0
  17. package/dist/commands/app/generate.js.map +1 -0
  18. package/dist/commands/app/info.d.ts +14 -0
  19. package/dist/commands/app/info.js +179 -0
  20. package/dist/commands/app/info.js.map +1 -0
  21. package/dist/commands/app/link.d.ts +118 -0
  22. package/dist/commands/app/link.js +1120 -0
  23. package/dist/commands/app/link.js.map +1 -0
  24. package/dist/commands/auth/login.d.ts +43 -0
  25. package/dist/commands/auth/login.js +987 -0
  26. package/dist/commands/auth/login.js.map +1 -0
  27. package/dist/commands/auth/logout.d.ts +15 -0
  28. package/dist/commands/auth/logout.js +486 -0
  29. package/dist/commands/auth/logout.js.map +1 -0
  30. package/dist/commands/auth/whoami.d.ts +16 -0
  31. package/dist/commands/auth/whoami.js +531 -0
  32. package/dist/commands/auth/whoami.js.map +1 -0
  33. package/dist/commands/dev/listen.d.ts +84 -0
  34. package/dist/commands/dev/listen.js +1187 -0
  35. package/dist/commands/dev/listen.js.map +1 -0
  36. package/dist/commands/dev/trigger.d.ts +88 -0
  37. package/dist/commands/dev/trigger.js +729 -0
  38. package/dist/commands/dev/trigger.js.map +1 -0
  39. package/dist/index.d.ts +14 -0
  40. package/dist/index.js +617 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/auth/authorize-code-exchange.d.ts +50 -0
  43. package/dist/lib/auth/authorize-code-exchange.js +114 -0
  44. package/dist/lib/auth/authorize-code-exchange.js.map +1 -0
  45. package/dist/lib/auth/authorize-url.d.ts +42 -0
  46. package/dist/lib/auth/authorize-url.js +35 -0
  47. package/dist/lib/auth/authorize-url.js.map +1 -0
  48. package/dist/lib/auth/browser.d.ts +16 -0
  49. package/dist/lib/auth/browser.js +48 -0
  50. package/dist/lib/auth/browser.js.map +1 -0
  51. package/dist/lib/auth/config.d.ts +41 -0
  52. package/dist/lib/auth/config.js +21 -0
  53. package/dist/lib/auth/config.js.map +1 -0
  54. package/dist/lib/auth/http-refresher.d.ts +30 -0
  55. package/dist/lib/auth/http-refresher.js +103 -0
  56. package/dist/lib/auth/http-refresher.js.map +1 -0
  57. package/dist/lib/auth/identity.d.ts +26 -0
  58. package/dist/lib/auth/identity.js +10 -0
  59. package/dist/lib/auth/identity.js.map +1 -0
  60. package/dist/lib/auth/index.d.ts +13 -0
  61. package/dist/lib/auth/index.js +527 -0
  62. package/dist/lib/auth/index.js.map +1 -0
  63. package/dist/lib/auth/loopback-server.d.ts +65 -0
  64. package/dist/lib/auth/loopback-server.js +245 -0
  65. package/dist/lib/auth/loopback-server.js.map +1 -0
  66. package/dist/lib/auth/manual-prompt.d.ts +17 -0
  67. package/dist/lib/auth/manual-prompt.js +20 -0
  68. package/dist/lib/auth/manual-prompt.js.map +1 -0
  69. package/dist/lib/auth/pkce.d.ts +24 -0
  70. package/dist/lib/auth/pkce.js +15 -0
  71. package/dist/lib/auth/pkce.js.map +1 -0
  72. package/dist/lib/auth/state.d.ts +20 -0
  73. package/dist/lib/auth/state.js +19 -0
  74. package/dist/lib/auth/state.js.map +1 -0
  75. package/dist/lib/auth/verbose-trace.d.ts +25 -0
  76. package/dist/lib/auth/verbose-trace.js +17 -0
  77. package/dist/lib/auth/verbose-trace.js.map +1 -0
  78. package/dist/lib/credentials/clock.d.ts +18 -0
  79. package/dist/lib/credentials/clock.js +10 -0
  80. package/dist/lib/credentials/clock.js.map +1 -0
  81. package/dist/lib/credentials/index.d.ts +5 -0
  82. package/dist/lib/credentials/index.js +340 -0
  83. package/dist/lib/credentials/index.js.map +1 -0
  84. package/dist/lib/credentials/path.d.ts +26 -0
  85. package/dist/lib/credentials/path.js +32 -0
  86. package/dist/lib/credentials/path.js.map +1 -0
  87. package/dist/lib/credentials/refresher.d.ts +26 -0
  88. package/dist/lib/credentials/refresher.js +34 -0
  89. package/dist/lib/credentials/refresher.js.map +1 -0
  90. package/dist/lib/credentials/schema.d.ts +138 -0
  91. package/dist/lib/credentials/schema.js +85 -0
  92. package/dist/lib/credentials/schema.js.map +1 -0
  93. package/dist/lib/credentials/store.d.ts +102 -0
  94. package/dist/lib/credentials/store.js +337 -0
  95. package/dist/lib/credentials/store.js.map +1 -0
  96. package/dist/lib/credentials/types.d.ts +65 -0
  97. package/dist/lib/credentials/types.js +1 -0
  98. package/dist/lib/credentials/types.js.map +1 -0
  99. package/dist/lib/dev-store-gate.d.ts +66 -0
  100. package/dist/lib/dev-store-gate.js +15 -0
  101. package/dist/lib/dev-store-gate.js.map +1 -0
  102. package/dist/lib/errors.d.ts +60 -0
  103. package/dist/lib/errors.js +101 -0
  104. package/dist/lib/errors.js.map +1 -0
  105. package/dist/lib/manifest/index.d.ts +2 -0
  106. package/dist/lib/manifest/index.js +144 -0
  107. package/dist/lib/manifest/index.js.map +1 -0
  108. package/dist/lib/manifest/read-manifest.d.ts +32 -0
  109. package/dist/lib/manifest/read-manifest.js +87 -0
  110. package/dist/lib/manifest/read-manifest.js.map +1 -0
  111. package/dist/lib/manifest/write-client-id.d.ts +33 -0
  112. package/dist/lib/manifest/write-client-id.js +94 -0
  113. package/dist/lib/manifest/write-client-id.js.map +1 -0
  114. package/dist/lib/messages.d.ts +15 -0
  115. package/dist/lib/messages.js +16 -0
  116. package/dist/lib/messages.js.map +1 -0
  117. package/dist/lib/orchestrator/child-runner.d.ts +140 -0
  118. package/dist/lib/orchestrator/child-runner.js +471 -0
  119. package/dist/lib/orchestrator/child-runner.js.map +1 -0
  120. package/dist/lib/preview/index.d.ts +11 -0
  121. package/dist/lib/preview/index.js +17 -0
  122. package/dist/lib/preview/index.js.map +1 -0
  123. package/dist/lib/preview/types.d.ts +16 -0
  124. package/dist/lib/preview/types.js +11 -0
  125. package/dist/lib/preview/types.js.map +1 -0
  126. package/dist/lib/ratio-command.d.ts +52 -0
  127. package/dist/lib/ratio-command.js +141 -0
  128. package/dist/lib/ratio-command.js.map +1 -0
  129. package/dist/lib/relay/index.d.ts +7 -0
  130. package/dist/lib/relay/index.js +362 -0
  131. package/dist/lib/relay/index.js.map +1 -0
  132. package/dist/lib/relay/relay-client.d.ts +91 -0
  133. package/dist/lib/relay/relay-client.js +362 -0
  134. package/dist/lib/relay/relay-client.js.map +1 -0
  135. package/dist/lib/relay/types.d.ts +71 -0
  136. package/dist/lib/relay/types.js +1 -0
  137. package/dist/lib/relay/types.js.map +1 -0
  138. package/dist/lib/render-error.d.ts +35 -0
  139. package/dist/lib/render-error.js +115 -0
  140. package/dist/lib/render-error.js.map +1 -0
  141. package/dist/lib/verbose-flag.d.ts +12 -0
  142. package/dist/lib/verbose-flag.js +17 -0
  143. package/dist/lib/verbose-flag.js.map +1 -0
  144. package/dist/render-CrHGqTPH.d.ts +115 -0
  145. package/dist/templates/.gitkeep +0 -0
  146. package/dist/templates/minimal/.env.example.tmpl +4 -0
  147. package/dist/templates/minimal/README.md.tmpl +26 -0
  148. package/dist/templates/minimal/gitignore +5 -0
  149. package/dist/templates/minimal/manifest.json +5 -0
  150. package/dist/templates/minimal/package.json.tmpl +25 -0
  151. package/dist/templates/minimal/ratio.config.jsonc.tmpl +17 -0
  152. package/dist/templates/minimal/src/index.ts.tmpl +26 -0
  153. package/dist/templates/minimal/src/install.ts.tmpl +37 -0
  154. package/dist/templates/minimal/src/webhooks.ts.tmpl +57 -0
  155. package/dist/templates/minimal/tsconfig.json +17 -0
  156. package/dist/templates/serverless/README.md.tmpl +28 -0
  157. package/dist/templates/serverless/gitignore +6 -0
  158. package/dist/templates/serverless/manifest.json +5 -0
  159. package/dist/templates/serverless/package.json.tmpl +22 -0
  160. package/dist/templates/serverless/ratio.config.jsonc.tmpl +17 -0
  161. package/dist/templates/serverless/src/index.ts.tmpl +20 -0
  162. package/dist/templates/serverless/src/install.ts.tmpl +20 -0
  163. package/dist/templates/serverless/src/webhooks.ts.tmpl +36 -0
  164. package/dist/templates/serverless/tsconfig.json +18 -0
  165. package/dist/templates/serverless/wrangler.toml.tmpl +9 -0
  166. package/dist/templates/with-admin-ui/.env.example.tmpl +4 -0
  167. package/dist/templates/with-admin-ui/README.md.tmpl +28 -0
  168. package/dist/templates/with-admin-ui/gitignore +6 -0
  169. package/dist/templates/with-admin-ui/manifest.json +5 -0
  170. package/dist/templates/with-admin-ui/package.json.tmpl +31 -0
  171. package/dist/templates/with-admin-ui/ratio.config.jsonc.tmpl +17 -0
  172. package/dist/templates/with-admin-ui/src/admin/app/globals.css +19 -0
  173. package/dist/templates/with-admin-ui/src/admin/app/layout.tsx.tmpl +13 -0
  174. package/dist/templates/with-admin-ui/src/admin/app/page.tsx.tmpl +8 -0
  175. package/dist/templates/with-admin-ui/src/admin/next-env.d.ts.tmpl +5 -0
  176. package/dist/templates/with-admin-ui/src/admin/next.config.mjs +3 -0
  177. package/dist/templates/with-admin-ui/src/admin/tsconfig.json +20 -0
  178. package/dist/templates/with-admin-ui/src/index.ts.tmpl +26 -0
  179. package/dist/templates/with-admin-ui/src/install.ts.tmpl +37 -0
  180. package/dist/templates/with-admin-ui/src/webhooks.ts.tmpl +57 -0
  181. package/dist/templates/with-admin-ui/tsconfig.json +17 -0
  182. package/package.json +78 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/manifest/write-client-id.ts","../../../src/lib/errors.ts","../../../src/lib/manifest/read-manifest.ts","../../../src/lib/orchestrator/child-runner.ts","../../../src/lib/preview/types.ts","../../../src/lib/preview/index.ts","../../../src/lib/ratio-command.ts","../../../src/lib/render-error.ts","../../../src/lib/verbose-flag.ts","../../../src/commands/app/dev.ts"],"sourcesContent":["/**\n * Atomically rewrite the top-level \"clientId\" value in a ratio.config.jsonc\n * file. Preserves every comment, whitespace token, and unknown key.\n *\n * The file is treated as an opaque UTF-8 string: no JSON/JSONC parser is\n * involved, so comments survive verbatim. Exactly one targeted regex is\n * applied to the `\"clientId\": \"<value>\"` line; everything else is copied\n * byte-for-byte.\n *\n * Throws ValidationError if the file is not shaped like a canonical\n * ratio.config.jsonc (no top-level \"clientId\" key on its own line).\n */\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { ValidationError } from '../errors.js';\n\n/** Manifest file name expected in the app repo root. */\nexport const MANIFEST_FILE_NAME = 'ratio.config.jsonc';\n\n/** Targeted single-key rewrite regex for the clientId value. */\nconst CLIENT_ID_ROW = /^(\\s*\"clientId\"\\s*:\\s*\")([^\"]*)(\"\\s*,?)/m;\n\n/** Read-only extraction regex for the appName echo. Never used to write. */\nconst APP_NAME_ROW = /^\\s*\"appName\"\\s*:\\s*\"([^\"]*)\"/m;\n\nexport interface WriteClientIdResult {\n /** false when the file already had the same clientId. */\n written: boolean;\n /** Absolute path to the manifest file. */\n path: string;\n /** Parsed from the manifest's appName row, when present. */\n appName?: string;\n /** The value that was replaced, when it was non-empty and different. */\n previousClientId?: string;\n}\n\n/**\n * Locate `ratio.config.jsonc` under `cwd`, rewrite ONLY the top-level\n * clientId value, and persist via atomic tmp+rename with 0600 discipline\n * (mirrors the credential store's write contract). Same-value re-runs are\n * a no-op (`written: false`, file untouched).\n */\nexport async function writeClientId(\n cwd: string,\n clientId: string,\n): Promise<WriteClientIdResult> {\n const filePath = path.join(cwd, MANIFEST_FILE_NAME);\n\n let content: string;\n try {\n content = fs.readFileSync(filePath, 'utf8');\n } catch {\n throw new ValidationError(\n `${MANIFEST_FILE_NAME} not found in ${cwd}.`,\n `Run this command from the app's repo root; ${MANIFEST_FILE_NAME} not found in ${cwd}.`,\n );\n }\n\n const appNameMatch = APP_NAME_ROW.exec(content);\n const appName = appNameMatch?.[1];\n\n const rowMatch = CLIENT_ID_ROW.exec(content);\n if (rowMatch === null) {\n throw new ValidationError(\n `${MANIFEST_FILE_NAME} has no top-level \"clientId\" entry.`,\n 'Restore the \"clientId\" line from the app template, then re-run.',\n );\n }\n\n const existing = rowMatch[2] ?? '';\n\n const base: WriteClientIdResult = {\n written: false,\n path: filePath,\n ...(appName !== undefined ? { appName } : {}),\n };\n\n if (existing === clientId) {\n return base;\n }\n\n // Function replacer: the new value is inserted verbatim, immune to `$`\n // pattern expansion in String.replace replacement strings.\n const updated = content.replace(\n CLIENT_ID_ROW,\n (_row, open: string, _old: string, close: string) =>\n `${open}${clientId}${close}`,\n );\n\n const tmpPath = `${filePath}.${process.pid}.${crypto\n .randomBytes(8)\n .toString('hex')}.tmp`;\n try {\n // mode at open time closes the create-then-chmod race window.\n fs.writeFileSync(tmpPath, updated, { mode: 0o600 });\n fs.renameSync(tmpPath, filePath);\n } catch (err) {\n try {\n fs.unlinkSync(tmpPath);\n } catch {\n // Best-effort cleanup: the tmp file may never have been created.\n }\n throw err;\n }\n if (process.platform !== 'win32') {\n // Belt-and-braces: rename may preserve a prior destination mode.\n fs.chmodSync(filePath, 0o600);\n }\n\n return {\n ...base,\n written: true,\n ...(existing !== '' ? { previousClientId: existing } : {}),\n };\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","/**\n * Read-only view over the app manifest (ratio.config.jsonc).\n *\n * The file is treated as an opaque UTF-8 string: no JSON/JSONC parser is\n * involved, so comments and unknown keys are irrelevant to the read and\n * survive untouched on disk. Each field is extracted with one targeted\n * regex, mirroring the write-side idiom in write-client-id.ts. This module\n * never writes the file.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { ValidationError } from '../errors.js';\nimport { MANIFEST_FILE_NAME } from './write-client-id.js';\n\n/** Read-only extraction regex for the dev command line. */\nconst DEV_COMMAND_ROW = /^\\s*\"devCommand\"\\s*:\\s*\"([^\"]*)\"/m;\n\n/** Read-only extraction regex for the dev-server port (integer, unquoted). */\nconst DEV_SERVER_PORT_ROW = /^\\s*\"devServerPort\"\\s*:\\s*(\\d+)/m;\n\n/** Key-presence probe: distinguishes \"absent\" from \"present but malformed\". */\nconst DEV_SERVER_PORT_KEY = /^\\s*\"devServerPort\"\\s*:/m;\n\n/** Read-only extraction regex for the appName echo. Never used to write. */\nconst APP_NAME_ROW = /^\\s*\"appName\"\\s*:\\s*\"([^\"]*)\"/m;\n\n/** Read-only extraction regex for the clientId echo. Never used to write. */\nconst CLIENT_ID_READ_ROW = /^\\s*\"clientId\"\\s*:\\s*\"([^\"]*)\"/m;\n\n/** Hint shown whenever the dev command entry is missing or unusable. */\nexport const DEV_COMMAND_HINT =\n 'Add a \"devCommand\" entry to ratio.config.jsonc (e.g. \"npm run dev\") and re-run.';\n\nexport interface ManifestView {\n /** Absolute path to the manifest file that was read. */\n path: string;\n /** The command used to start the app's own dev server. Always present. */\n devCommand: string;\n /** Optional local dev-server port (1-65535); undefined when absent. */\n devServerPort: number | undefined;\n /** Parsed from the manifest's appName row, when present. */\n appName: string | undefined;\n /** Parsed from the manifest's clientId row, when present. */\n clientId: string | undefined;\n}\n\n/**\n * Locate `ratio.config.jsonc` under `cwd` and extract the fields the dev\n * orchestrator needs. Fails fast with a typed error when the file is\n * missing, when `devCommand` is absent/empty, or when `devServerPort` is\n * present but not an integer in 1-65535.\n */\nexport function readManifest(cwd: string): ManifestView {\n const filePath = path.join(cwd, MANIFEST_FILE_NAME);\n\n let content: string;\n try {\n content = fs.readFileSync(filePath, 'utf8');\n } catch {\n throw new ValidationError(\n `${MANIFEST_FILE_NAME} not found in ${cwd}.`,\n `Run this command from the app's repo root; ${MANIFEST_FILE_NAME} not found in ${cwd}.`,\n );\n }\n\n const devCommand = DEV_COMMAND_ROW.exec(content)?.[1];\n if (devCommand === undefined || devCommand.trim() === '') {\n throw new ValidationError(\n `${MANIFEST_FILE_NAME} has no usable \"devCommand\" entry.`,\n DEV_COMMAND_HINT,\n );\n }\n\n let devServerPort: number | undefined;\n if (DEV_SERVER_PORT_KEY.test(content)) {\n const rawPort = DEV_SERVER_PORT_ROW.exec(content)?.[1];\n const port = rawPort === undefined ? Number.NaN : Number.parseInt(rawPort, 10);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new ValidationError(\n `${MANIFEST_FILE_NAME} has an invalid \"devServerPort\" value.`,\n '\"devServerPort\" must be an unquoted integer between 1 and 65535 (e.g. \"devServerPort\": 3001).',\n );\n }\n devServerPort = port;\n }\n\n return {\n path: filePath,\n devCommand,\n devServerPort,\n appName: APP_NAME_ROW.exec(content)?.[1],\n clientId: CLIENT_ID_READ_ROW.exec(content)?.[1],\n };\n}\n","/**\n * Child-process runner for `ratio app dev`.\n *\n * Spawns two children — the app's own dev command and the webhook\n * listener — multiplexes their output onto stdout with per-child line\n * prefixes, and arbitrates a single deterministic exit code out of child\n * crashes and delivery of SIGINT/SIGTERM to the orchestrator.\n *\n * Everything process-shaped is injectable (spawn functions, signal\n * source, output sinks, hard-exit) so the full lifecycle is testable\n * without real dev servers or real signals.\n */\n\nimport { spawn } from 'node:child_process';\nimport {\n OrchestratorDrainTimeoutError,\n OrchestratorSpawnError,\n ValidationError,\n} from '../errors.js';\nimport { DEV_COMMAND_HINT } from '../manifest/read-manifest.js';\nimport type { PreviewController } from '../preview/index.js';\n\n/** SIGTERM → SIGKILL grace window during shutdown. */\nexport const DRAIN_MS = 5_000;\n\n/**\n * Window within which \"both children crashed\" is arbitrated to the\n * first-observed exit. The arbiter is deterministic for every ordering\n * (first observed terminating cause always wins), which satisfies the\n * race rule for this window and beyond.\n */\nexport const RACE_WINDOW_MS = 200;\n\n/** A second interrupt inside this window forces an immediate exit (130). */\nexport const SIGNAL_FORCE_EXIT_WINDOW_MS = 2_000;\n\n/** Per-child line-buffer bound; overflow flushes a partial line. */\nexport const CHILD_BUFFER_MAX_BYTES = 64 * 1024;\n\n/** Hint shown when the dev command cannot be tokenized safely. */\nexport const DEV_COMMAND_TOKENIZE_HINT =\n 'devCommand must be a simple whitespace-separated command; complex shell pipelines are not supported.';\n\n/** Default forwarded-to port for the listener when the manifest has none. */\nexport const DEFAULT_FORWARD_PORT = 3001;\n\n// ── Tokenizer ──────────────────────────────────────────────────────────────\n\n/**\n * Split a dev command string into an argv array: whitespace-separated,\n * with single- or double-quoted segments kept together. Shell operators\n * (pipes, redirects, substitutions) are rejected outright — the command\n * is spawned without a shell, so they could never mean what the author\n * intended.\n */\nexport function tokenizeDevCommand(raw: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | undefined;\n let hasToken = false;\n\n for (let i = 0; i < raw.length; i += 1) {\n const ch = raw.charAt(i);\n if (quote !== undefined) {\n if (ch === quote) {\n quote = undefined;\n } else {\n current += ch;\n }\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch;\n hasToken = true;\n continue;\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n if (hasToken) {\n tokens.push(current);\n current = '';\n hasToken = false;\n }\n continue;\n }\n if (ch === '|' || ch === '&' || ch === ';' || ch === '<' || ch === '>' || ch === '`') {\n throw new ValidationError(\n `devCommand contains an unsupported shell operator: ${ch}`,\n DEV_COMMAND_TOKENIZE_HINT,\n );\n }\n if (ch === '$' && raw.charAt(i + 1) === '(') {\n throw new ValidationError(\n 'devCommand contains an unsupported shell substitution.',\n DEV_COMMAND_TOKENIZE_HINT,\n );\n }\n current += ch;\n hasToken = true;\n }\n\n if (quote !== undefined) {\n throw new ValidationError(\n 'devCommand has an unterminated quote.',\n DEV_COMMAND_TOKENIZE_HINT,\n );\n }\n if (hasToken) {\n tokens.push(current);\n }\n if (tokens.length === 0 || tokens[0] === '') {\n throw new ValidationError('devCommand is empty.', DEV_COMMAND_HINT);\n }\n return tokens;\n}\n\n// ── Log multiplexer ────────────────────────────────────────────────────────\n\n/**\n * Per-stream line buffer that emits `<prefix><line>\\n` only on newline\n * boundaries, so chunks split mid-line (or mid-UTF-8-character) never\n * corrupt the prefixed output. Bounded to CHILD_BUFFER_MAX_BYTES: on\n * overflow, the partial line is flushed with the prefix and a newline.\n */\nexport class LinePrefixer {\n private buffer: Buffer = Buffer.alloc(0);\n\n constructor(\n private readonly prefix: string,\n private readonly write: (chunk: string) => void,\n ) {}\n\n push(chunk: Buffer | string): void {\n const incoming = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;\n if (incoming.length === 0) {\n return;\n }\n this.buffer = this.buffer.length === 0 ? incoming : Buffer.concat([this.buffer, incoming]);\n this.drainCompleteLines();\n if (this.buffer.length > CHILD_BUFFER_MAX_BYTES) {\n this.flush();\n }\n }\n\n /** Emit any buffered partial line (with prefix + newline), then reset. */\n flush(): void {\n if (this.buffer.length > 0) {\n this.write(`${this.prefix}${this.buffer.toString('utf8')}\\n`);\n this.buffer = Buffer.alloc(0);\n }\n }\n\n private drainCompleteLines(): void {\n let newlineAt = this.buffer.indexOf(0x0a);\n while (newlineAt !== -1) {\n const line = this.buffer.subarray(0, newlineAt).toString('utf8');\n this.buffer = this.buffer.subarray(newlineAt + 1);\n this.write(`${this.prefix}${line}\\n`);\n newlineAt = this.buffer.indexOf(0x0a);\n }\n }\n}\n\n// ── Exit-code arbitration ──────────────────────────────────────────────────\n\n/** POSIX-conventional exit codes for signal-terminated children. */\nconst SIGNAL_EXIT_CODES: Readonly<Partial<Record<NodeJS.Signals, number>>> = {\n SIGHUP: 129,\n SIGINT: 130,\n SIGQUIT: 131,\n SIGKILL: 137,\n SIGTERM: 143,\n};\n\n/** Map a child's (code, signal) exit pair to a single numeric exit code. */\nexport function exitCodeFrom(\n code: number | null,\n signal: NodeJS.Signals | null,\n): number {\n if (code !== null) {\n return code;\n }\n if (signal !== null) {\n return SIGNAL_EXIT_CODES[signal] ?? 1;\n }\n return 1;\n}\n\n/**\n * Deterministic exit-code arbiter: the FIRST observed terminating cause\n * (a child exit, or a signal delivered to the orchestrator) decides the\n * final code. Exits observed after shutdown started are the expected\n * result of our own SIGTERM and never change the outcome.\n */\nexport class ExitArbiter {\n private decidingChildCode: number | undefined;\n private signalCode: number | undefined;\n\n get shutdownInitiated(): boolean {\n return this.decidingChildCode !== undefined || this.signalCode !== undefined;\n }\n\n observeChildExit(code: number | null, signal: NodeJS.Signals | null): void {\n if (this.shutdownInitiated) {\n return;\n }\n this.decidingChildCode = exitCodeFrom(code, signal);\n }\n\n observeSignal(sig: 'SIGINT' | 'SIGTERM'): void {\n if (this.shutdownInitiated) {\n return;\n }\n this.signalCode = sig === 'SIGINT' ? 130 : 143;\n }\n\n finalCode(): number {\n return this.signalCode ?? this.decidingChildCode ?? 0;\n }\n}\n\n// ── Child spawning ─────────────────────────────────────────────────────────\n\n/** The exact spawn options the orchestrator uses for both children. */\nexport interface SpawnChildOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n stdio: ['ignore', 'pipe', 'pipe'];\n shell: false;\n}\n\n/** Structural subset of ChildProcess the orchestrator relies on. */\nexport interface ChildLike {\n readonly pid?: number | undefined;\n readonly stdout: NodeJS.ReadableStream | null;\n readonly stderr: NodeJS.ReadableStream | null;\n kill(signal?: NodeJS.Signals): boolean;\n once(\n event: 'exit',\n listener: (code: number | null, signal: NodeJS.Signals | null) => void,\n ): unknown;\n once(event: 'error', listener: (err: Error) => void): unknown;\n}\n\nexport type SpawnFn = (\n command: string,\n args: readonly string[],\n options: SpawnChildOptions,\n) => ChildLike;\n\nconst defaultSpawn: SpawnFn = (command, args, options) =>\n spawn(command, [...args], {\n cwd: options.cwd,\n env: options.env,\n stdio: options.stdio,\n shell: options.shell,\n });\n\n/** Minimal process-signal surface, injectable so tests never send real signals. */\nexport interface SignalSource {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n removeListener(event: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n}\n\n/** How the listener child is invoked: always the current node executable. */\nexport interface ListenInvocation {\n command: string;\n args: string[];\n}\n\n/**\n * Build the listener child's invocation. By default the listener runs as\n * a sub-command of this same binary. The RATIO_DEV_LISTEN_BIN environment\n * variable substitutes a different script for the listener (a test seam;\n * unset in normal use, in which case the default binary path is used).\n */\nexport function buildListenInvocation(\n env: NodeJS.ProcessEnv,\n devServerPort: number | undefined,\n argv1: string | undefined,\n): ListenInvocation {\n const override = env.RATIO_DEV_LISTEN_BIN;\n const listenBin = override ?? argv1;\n if (listenBin === undefined || listenBin === '') {\n throw new OrchestratorSpawnError(\n 'Could not locate the CLI binary to start the webhook listener.',\n 'Re-install the CLI, or invoke it through its bin entry point.',\n );\n }\n const listenArgs = override\n ? []\n : [\n 'dev',\n 'listen',\n '--forward-to',\n `localhost:${devServerPort ?? DEFAULT_FORWARD_PORT}/webhooks`,\n ];\n return { command: process.execPath, args: [listenBin, ...listenArgs] };\n}\n\n// ── Orchestration ──────────────────────────────────────────────────────────\n\nexport interface OrchestratorIo {\n writeOut(chunk: string): void;\n writeErr(chunk: string): void;\n}\n\nexport interface OrchestrateOptions {\n /** Tokenized dev command; index 0 is the executable. */\n appArgv: string[];\n /** Optional port hint exposed to the app child as PORT. */\n devServerPort: number | undefined;\n cwd: string;\n env: NodeJS.ProcessEnv;\n preview: PreviewController;\n verbose: boolean;\n io: OrchestratorIo;\n signalSource: SignalSource;\n /** Immediate process exit for the second-interrupt fast path. */\n hardExit: (code: number) => void;\n /** Test seam: spawner for the app child. */\n spawnApp?: SpawnFn;\n /** Test seam: spawner for the listener child. */\n spawnListen?: SpawnFn;\n /** Test seam: shrink the SIGTERM → SIGKILL grace window. */\n drainMs?: number;\n /** Test seam: stand-in for the CLI's own script path. */\n argv1?: string;\n}\n\nfunction describeCause(cause: unknown): string {\n return cause instanceof Error ? cause.message : String(cause);\n}\n\n/**\n * Run both children to completion and resolve the final exit code.\n * Rejects with a typed error when a child cannot be spawned (exit 10) or\n * when an otherwise-clean shutdown needed a SIGKILL escalation (exit 11).\n */\nexport async function orchestrate(options: OrchestrateOptions): Promise<number> {\n const { io } = options;\n const drainMs = options.drainMs ?? DRAIN_MS;\n const spawnApp = options.spawnApp ?? defaultSpawn;\n const spawnListen = options.spawnListen ?? defaultSpawn;\n const trace = (line: string): void => {\n if (options.verbose) {\n io.writeErr(`[verbose] ${line}\\n`);\n }\n };\n\n const appCommand = options.appArgv[0];\n if (appCommand === undefined || appCommand === '') {\n throw new ValidationError('devCommand is empty.', DEV_COMMAND_HINT);\n }\n\n // Child A: the app's own dev command. PORT is set only when the\n // manifest declares a devServerPort.\n const appEnv: NodeJS.ProcessEnv = {\n ...options.env,\n ...(options.devServerPort !== undefined\n ? { PORT: String(options.devServerPort) }\n : {}),\n RATIO_ORCHESTRATED: '1',\n };\n let app: ChildLike;\n try {\n app = spawnApp(appCommand, options.appArgv.slice(1), {\n cwd: options.cwd,\n env: appEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: false,\n });\n } catch (cause) {\n throw new OrchestratorSpawnError(\n `Could not start the dev command (${appCommand}): ${describeCause(cause)}`,\n );\n }\n trace(`spawn app: ${appCommand} (${app.pid ?? '-'})`);\n\n // Child B: the webhook listener, run as a sub-command of this binary.\n const invocation = buildListenInvocation(\n options.env,\n options.devServerPort,\n options.argv1 ?? process.argv[1],\n );\n const listenEnv: NodeJS.ProcessEnv = { ...options.env, RATIO_ORCHESTRATED: '1' };\n let listen: ChildLike;\n try {\n listen = spawnListen(invocation.command, invocation.args, {\n cwd: options.cwd,\n env: listenEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: false,\n });\n } catch (cause) {\n app.kill('SIGTERM');\n throw new OrchestratorSpawnError(\n `Could not start the webhook listener: ${describeCause(cause)}`,\n 'The bundled webhook listener could not be started; re-install the CLI if this persists.',\n );\n }\n trace(`spawn listen: (${listen.pid ?? '-'})`);\n\n // Log multiplexer: per-stream line buffers, one prefix per child.\n const appOut = new LinePrefixer('[app] ', (chunk) => io.writeOut(chunk));\n const appErrOut = new LinePrefixer('[app] ', (chunk) => io.writeOut(chunk));\n const listenOut = new LinePrefixer('[listen] ', (chunk) => io.writeOut(chunk));\n const listenErrOut = new LinePrefixer('[listen] ', (chunk) => io.writeOut(chunk));\n const pipe = (stream: NodeJS.ReadableStream | null, prefixer: LinePrefixer): void => {\n if (stream !== null) {\n stream.on('data', (chunk: Buffer | string) => prefixer.push(chunk));\n }\n };\n pipe(app.stdout, appOut);\n pipe(app.stderr, appErrOut);\n pipe(listen.stdout, listenOut);\n pipe(listen.stderr, listenErrOut);\n\n const arbiter = new ExitArbiter();\n let appExited = false;\n let listenExited = false;\n let spawnFailure: OrchestratorSpawnError | undefined;\n let drainEscalated = false;\n let drainTimer: NodeJS.Timeout | undefined;\n let drainStartedAt = 0;\n let firstSignalAt = 0;\n\n return await new Promise<number>((resolve, reject) => {\n let settled = false;\n\n const cleanup = (): void => {\n options.signalSource.removeListener('SIGINT', onSigint);\n options.signalSource.removeListener('SIGTERM', onSigterm);\n if (drainTimer !== undefined) {\n clearTimeout(drainTimer);\n drainTimer = undefined;\n }\n appOut.flush();\n appErrOut.flush();\n listenOut.flush();\n listenErrOut.flush();\n };\n\n const settle = (code: number): void => {\n if (!settled) {\n settled = true;\n cleanup();\n resolve(code);\n }\n };\n const fail = (err: unknown): void => {\n if (!settled) {\n settled = true;\n cleanup();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n const startDrain = (): void => {\n if (drainTimer !== undefined) {\n return;\n }\n drainStartedAt = Date.now();\n drainTimer = setTimeout(() => {\n if (!appExited) {\n app.kill('SIGKILL');\n drainEscalated = true;\n }\n if (!listenExited) {\n listen.kill('SIGKILL');\n drainEscalated = true;\n }\n }, drainMs);\n };\n\n const finishIfDone = (): void => {\n if (!appExited || !listenExited) {\n return;\n }\n if (drainStartedAt > 0) {\n trace(`drain elapsed: ${Date.now() - drainStartedAt}ms`);\n }\n const code = arbiter.finalCode();\n void options.preview.onShutdown().then(\n () => {\n if (spawnFailure !== undefined) {\n fail(spawnFailure);\n return;\n }\n if (drainEscalated && code === 0) {\n fail(\n new OrchestratorDrainTimeoutError(\n 'A child process had to be forcibly terminated during shutdown.',\n ),\n );\n return;\n }\n settle(code);\n },\n (err: unknown) => fail(err),\n );\n };\n\n const onAppExit = (code: number | null, signal: NodeJS.Signals | null): void => {\n appExited = true;\n appOut.flush();\n appErrOut.flush();\n trace(`app exited: code=${code} signal=${signal}`);\n const alreadyShuttingDown = arbiter.shutdownInitiated;\n arbiter.observeChildExit(code, signal);\n if (!alreadyShuttingDown && !listenExited) {\n listen.kill('SIGTERM');\n startDrain();\n }\n finishIfDone();\n };\n\n const onListenExit = (code: number | null, signal: NodeJS.Signals | null): void => {\n listenExited = true;\n listenOut.flush();\n listenErrOut.flush();\n trace(`listen exited: code=${code} signal=${signal}`);\n const alreadyShuttingDown = arbiter.shutdownInitiated;\n arbiter.observeChildExit(code, signal);\n if (!alreadyShuttingDown && !appExited) {\n app.kill('SIGTERM');\n startDrain();\n }\n finishIfDone();\n };\n\n const onAppError = (err: Error): void => {\n appExited = true;\n if (spawnFailure === undefined) {\n spawnFailure = new OrchestratorSpawnError(\n `Could not start the dev command (${appCommand}): ${err.message}`,\n );\n }\n arbiter.observeChildExit(1, null);\n if (!listenExited) {\n listen.kill('SIGTERM');\n startDrain();\n }\n finishIfDone();\n };\n\n const onListenError = (err: Error): void => {\n listenExited = true;\n if (spawnFailure === undefined) {\n spawnFailure = new OrchestratorSpawnError(\n `Could not start the webhook listener: ${err.message}`,\n 'The bundled webhook listener could not be started; re-install the CLI if this persists.',\n );\n }\n arbiter.observeChildExit(1, null);\n if (!appExited) {\n app.kill('SIGTERM');\n startDrain();\n }\n finishIfDone();\n };\n\n const onSignal = (sig: 'SIGINT' | 'SIGTERM'): void => {\n trace(`signal received: ${sig}`);\n if (arbiter.shutdownInitiated) {\n if (firstSignalAt === 0) {\n // Shutdown was child-initiated; arm the force-exit window so a\n // follow-up interrupt can still cut the drain short.\n firstSignalAt = Date.now();\n return;\n }\n if (Date.now() - firstSignalAt <= SIGNAL_FORCE_EXIT_WINDOW_MS) {\n options.hardExit(130);\n }\n return;\n }\n firstSignalAt = Date.now();\n arbiter.observeSignal(sig);\n io.writeErr('shutting down: stopping the dev command and the webhook listener\\n');\n if (!appExited) {\n app.kill('SIGTERM');\n }\n if (!listenExited) {\n listen.kill('SIGTERM');\n }\n startDrain();\n };\n const onSigint = (): void => onSignal('SIGINT');\n const onSigterm = (): void => onSignal('SIGTERM');\n options.signalSource.on('SIGINT', onSigint);\n options.signalSource.on('SIGTERM', onSigterm);\n\n app.once('exit', onAppExit);\n app.once('error', onAppError);\n listen.once('exit', onListenExit);\n listen.once('error', onListenError);\n\n // Both children are up: notify the preview seam (a no-op in v1).\n trace(`preview: onDevServerReady(${options.devServerPort ?? 0})`);\n void options.preview\n .onDevServerReady(options.devServerPort ?? 0)\n .catch((err: unknown) => fail(err));\n });\n}\n","/**\n * Reserved seam for a future active-preview implementation. In v1 the\n * only implementation is a no-op; the interface exists so a future\n * change can bind an active implementation without editing the\n * orchestrator.\n */\nexport interface PreviewController {\n onDevServerReady(port: number): Promise<void>;\n onShutdown(): Promise<void>;\n}\n\nexport class NullPreviewController implements PreviewController {\n async onDevServerReady(_port: number): Promise<void> {}\n async onShutdown(): Promise<void> {}\n}\n","export { NullPreviewController } from './types.js';\nexport type { PreviewController } from './types.js';\n\n/**\n * v1: always returns NullPreviewController. The active-preview binding\n * is introduced by a future change; the orchestrator does not need to\n * change when that happens.\n */\nexport function createPreviewController(): PreviewController {\n return new NullPreviewController();\n}\n\nimport { NullPreviewController } from './types.js';\nimport type { PreviewController } from './types.js';\n","import { Command, Errors } from '@oclif/core';\nimport { renderError } from './render-error.js';\n\n/**\n * Base class for all Ratio CLI commands.\n *\n * Overrides catch() to intercept every error thrown from run(), render it\n * per the Ratio CLI error-UX spec (typed hierarchy -> structured stderr/stdout,\n * --verbose for stack traces, --json for machine-readable envelope), and then\n * suppress oclif's own double-render by setting skipOclifErrorHandling on the\n * re-thrown error.\n *\n * Path B: base-class override — no oclif.hooks wiring needed.\n * oclif v4 does not expose a command_error hook; the reliable interception\n * point is Command.catch(), which is called by Command._run() before the\n * error bubbles up to oclif's global handle() function.\n *\n * Double-render prevention:\n * oclif's handle() checks err.skipOclifErrorHandling before printing.\n * After rendering we set skipOclifErrorHandling = true so oclif's\n * pretty-print is skipped. We also set err.oclif.exit = N so handle()\n * calls process.exit(N) with the correct code.\n *\n * Exit-code routing:\n * - exitCode === 0 (ComingSoonError): throw ExitError(0) so oclif calls\n * process.exit(0) cleanly. ExitError has code 'EEXIT' and is treated as\n * a clean exit — shouldPrint is false in handle().\n * - exitCode > 0: mark err.skipOclifErrorHandling = true and set\n * err.oclif = { exit: N }, then re-throw so handle() exits N.\n *\n * Verbose/json detection strategy:\n * We scan this.argv directly. this.argv is set by oclif's Command before\n * run() is called and always contains the raw argument list. This approach\n * works both for:\n * (a) post-parse errors (thrown after this.parse() succeeds) — argv has the\n * flags that were successfully recognised.\n * (b) pre-parse errors (thrown BY this.parse(), e.g. unknown flag) — argv\n * still contains --verbose / --json even though parse() threw.\n * Simple string scan is sufficient since --verbose and --json are boolean\n * flags with no value argument.\n */\nexport abstract class RatioCommand extends Command {\n private _getRenderFlags(): { verbose: boolean; json: boolean } {\n const argv = this.argv as string[];\n return {\n verbose: argv.includes('--verbose'),\n json: argv.includes('--json'),\n };\n }\n\n override async catch(\n err: Error & {\n exitCode?: number;\n oclif?: { exit?: number };\n skipOclifErrorHandling?: boolean;\n },\n ): Promise<never> {\n const { verbose, json } = this._getRenderFlags();\n\n const output = renderError({ err, verbose, json });\n\n // Write rendered output before any exit/throw\n if (output.stdout) process.stdout.write(output.stdout);\n if (output.stderr) process.stderr.write(output.stderr);\n\n if (output.exitCode === 0) {\n // ComingSoonError path — ExitError(0) causes oclif to call process.exit(0)\n // cleanly. ExitError has code 'EEXIT'; handle() treats it as a clean exit\n // (shouldPrint is false) so no additional output is written.\n throw new Errors.ExitError(0);\n }\n\n // For non-zero exits: suppress oclif's pretty-print by marking the error,\n // then route the correct exit code via oclif.exit so handle() calls process.exit(N).\n err.skipOclifErrorHandling = true;\n err.oclif = { exit: output.exitCode };\n throw err;\n }\n}\n","import { isRatioCLIError, RatioCLIError, ValidationError } from './errors.js';\n\nexport interface RenderInput {\n err: unknown;\n verbose: boolean;\n json: boolean;\n}\n\nexport interface RenderOutput {\n /** Text to write to stdout. Empty string means nothing to write. */\n stdout: string;\n /** Text to write to stderr. Empty string means nothing to write. */\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Detects whether an error originates from the oclif argument/flag parser.\n * oclif's CLIParseError subclasses (NonExistentFlagsError, RequiredArgsError,\n * etc.) all carry a `parse` property set by CLIParseErrorOptions. This is the\n * stable structural signal — more reliable than checking the class name string.\n */\nfunction isOclifParserError(err: unknown): err is Error & { parse: unknown } {\n return (\n err instanceof Error &&\n 'parse' in err &&\n err.parse !== null &&\n err.parse !== undefined\n );\n}\n\n/**\n * Builds the JSON error envelope. Fields with undefined values are omitted\n * so the consumer never sees `\"hint\": null` or `\"docsUrl\": null`.\n */\nfunction buildErrorEnvelope(\n code: string,\n message: string,\n hint?: string,\n docsUrl?: string,\n): string {\n const obj: Record<string, unknown> = { ok: false, code, message };\n if (hint !== undefined) obj['hint'] = hint;\n if (docsUrl !== undefined) obj['docsUrl'] = docsUrl;\n return JSON.stringify(obj) + '\\n';\n}\n\n/**\n * Pure rendering function — no side effects, no process.exit, no I/O.\n * The caller (RatioCommand.catch) is responsible for writing output and exiting.\n *\n * Rendering rules (all branches):\n *\n * | Error shape | --json | --verbose | stdout | stderr | exit |\n * |----------------------|--------|-----------|--------------------------------|----------------------------------|------|\n * | ComingSoonError | false | any | err.message + '\\n' | '' | 0 |\n * | ComingSoonError | true | any | '{\"ok\":true,\"data\":null}\\n' | '' | 0 |\n * | RatioCLIError >0 | false | false | '' | code: msg [+ hint] [+ docsUrl] | N |\n * | RatioCLIError >0 | false | true | '' | above + stack | N |\n * | RatioCLIError >0 | true | false | JSON envelope | '' | N |\n * | RatioCLIError >0 | true | true | JSON envelope | stack only | N |\n * | oclif ParserError | false | any | '' | VALIDATION_ERROR: msg [+stack] | 8 |\n * | oclif ParserError | true | any | JSON VALIDATION_ERROR envelope | stack if verbose | 8 |\n * | Plain Error / other | false | false | '' | INTERNAL: msg\\n | 1 |\n * | Plain Error / other | false | true | '' | INTERNAL: msg\\n + stack\\n | 1 |\n * | Plain Error / other | true | any | JSON INTERNAL envelope | stack if verbose | 1 |\n */\nexport function renderError(input: RenderInput): RenderOutput {\n const { err, verbose, json } = input;\n\n // ── Branch 1: our own typed hierarchy ───────────────────────────────────\n if (isRatioCLIError(err)) {\n const ratioErr = err as RatioCLIError;\n\n // exitCode === 0 → coming-soon: render on stdout, no stderr, exit 0\n if (ratioErr.exitCode === 0) {\n if (json) {\n return {\n stdout: JSON.stringify({ ok: true, data: null }) + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n return {\n stdout: ratioErr.message + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n\n // exitCode > 0 → error render\n let stderrLines = `${ratioErr.code}: ${ratioErr.message}\\n`;\n if (ratioErr.hint !== undefined) stderrLines += `${ratioErr.hint}\\n`;\n if (ratioErr.docsUrl !== undefined) stderrLines += `${ratioErr.docsUrl}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),\n stderr: verbose && ratioErr.stack ? ratioErr.stack + '\\n' : '',\n exitCode: ratioErr.exitCode,\n };\n }\n\n if (verbose && ratioErr.stack) {\n stderrLines += ratioErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrLines, exitCode: ratioErr.exitCode };\n }\n\n // ── Branch 2: oclif parser error (unknown flag, missing required arg, etc.) ──\n if (isOclifParserError(err)) {\n const parserErr = err as Error & { parse: unknown };\n const msg = parserErr.message || 'Unknown parse error';\n\n // Strip oclif's \"Unexpected argument: --foo\" or similar prefix noise where\n // possible, but keep the core message as-is since it's already human-readable.\n const coercedErr = new ValidationError(msg);\n\n let stderrText = `${coercedErr.code}: ${msg}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(coercedErr.code, msg),\n stderr: verbose && parserErr.stack ? parserErr.stack + '\\n' : '',\n exitCode: 8,\n };\n }\n\n if (verbose && parserErr.stack) {\n stderrText += parserErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 8 };\n }\n\n // ── Branch 3: plain Error or non-Error throw ─────────────────────────────\n const message = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n const stderrPrefix = `INTERNAL: ${message}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope('INTERNAL', message),\n stderr: verbose && stack ? stack + '\\n' : '',\n exitCode: 1,\n };\n }\n\n let stderrText = stderrPrefix;\n if (verbose && stack) {\n stderrText += stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 1 };\n}\n","import { Flags } from '@oclif/core';\n\n/** Global verbose flag — every command imports and spreads this. */\nexport const verboseFlag = {\n verbose: Flags.boolean({\n description:\n 'Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors.',\n }),\n};\n\n/** JSON envelope flag — read-style commands (whoami, dev trigger --list) import. */\nexport const jsonFlag = {\n json: Flags.boolean({\n description: 'Emit machine-readable JSON on stdout.',\n }),\n};\n","import { readManifest } from '../../lib/manifest/index.js';\nimport {\n orchestrate,\n tokenizeDevCommand,\n} from '../../lib/orchestrator/child-runner.js';\nimport type { SignalSource, SpawnFn } from '../../lib/orchestrator/child-runner.js';\nimport { createPreviewController } from '../../lib/preview/index.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { verboseFlag } from '../../lib/verbose-flag.js';\n\nexport default class AppDev extends RatioCommand {\n static override description = `Run the current app locally: starts the manifest's dev command and a webhook listener side by side with prefixed logs from both.\n\nReads ratio.config.jsonc from the current directory (\"devCommand\" is required; \"devServerPort\" is optional and, when set, is passed to your dev command as PORT). If either process stops, the other is shut down and the failing exit code is propagated. Ctrl+C stops both cleanly.\n\nExit codes: 0 success; 3 not signed in; 5 network failure; 8 invalid configuration; 10 a child process could not be started; 11 a child had to be forcibly terminated during shutdown; 130/143 interrupted or terminated.`;\n\n static override examples = [\n '<%= config.bin %> app dev',\n '<%= config.bin %> app dev --verbose',\n ];\n\n static override flags = {\n ...verboseFlag,\n };\n\n // ── Seams: overridable in tests; defaults hit the real process. ──────────\n public writeOut: (chunk: string) => void = (chunk) => {\n process.stdout.write(chunk);\n };\n\n public writeErr: (chunk: string) => void = (chunk) => {\n process.stderr.write(chunk);\n };\n\n public signalSource: SignalSource = process;\n\n public hardExit: (code: number) => void = (code) => {\n process.exit(code);\n };\n\n public getCwd: () => string = () => process.cwd();\n\n public envReader: () => NodeJS.ProcessEnv = () => process.env;\n\n /** Test seam: spawner for the app child (real spawn when undefined). */\n public spawnApp: SpawnFn | undefined;\n\n /** Test seam: spawner for the listener child (real spawn when undefined). */\n public spawnListen: SpawnFn | undefined;\n\n /** Test seam: SIGTERM → SIGKILL grace override (default drain when undefined). */\n public drainMs: number | undefined;\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(AppDev);\n const cwd = this.getCwd();\n const env = this.envReader();\n\n const manifest = readManifest(cwd);\n const appArgv = tokenizeDevCommand(manifest.devCommand);\n const preview = createPreviewController();\n\n const code = await orchestrate({\n appArgv,\n devServerPort: manifest.devServerPort,\n cwd,\n env,\n preview,\n verbose: flags.verbose === true,\n io: {\n writeOut: (chunk) => this.writeOut(chunk),\n writeErr: (chunk) => this.writeErr(chunk),\n },\n signalSource: this.signalSource,\n hardExit: (exitCode) => this.hardExit(exitCode),\n ...(this.spawnApp !== undefined ? { spawnApp: this.spawnApp } : {}),\n ...(this.spawnListen !== undefined ? { spawnListen: this.spawnListen } : {}),\n ...(this.drainMs !== undefined ? { drainMs: this.drainMs } : {}),\n });\n\n if (code !== 0) {\n this.hardExit(code);\n }\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs","stderrText"]}
@@ -0,0 +1,14 @@
1
+ import * as _oclif_core_interfaces from '@oclif/core/interfaces';
2
+ import { RatioCommand } from '../../lib/ratio-command.js';
3
+ import '@oclif/core';
4
+
5
+ declare class AppGenerate extends RatioCommand {
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ verbose: _oclif_core_interfaces.BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
13
+
14
+ export { AppGenerate as default };
@@ -0,0 +1,179 @@
1
+ // src/lib/messages.ts
2
+ function comingSoonMessage(command) {
3
+ return `${command} \u2014 coming soon.
4
+ This command will be available in an upcoming Ratio CLI release.
5
+ Run 'ratio --help' after your next update to see what's new.`;
6
+ }
7
+
8
+ // src/lib/ratio-command.ts
9
+ import { Command, Errors } from "@oclif/core";
10
+
11
+ // src/lib/errors.ts
12
+ var RatioCLIError = class extends Error {
13
+ code;
14
+ exitCode;
15
+ hint;
16
+ docsUrl;
17
+ constructor(opts) {
18
+ super(opts.message);
19
+ this.name = new.target.name;
20
+ this.code = opts.code;
21
+ this.exitCode = opts.exitCode;
22
+ if (opts.hint !== void 0) this.hint = opts.hint;
23
+ if (opts.docsUrl !== void 0) this.docsUrl = opts.docsUrl;
24
+ }
25
+ };
26
+ var ComingSoonError = class extends RatioCLIError {
27
+ constructor(message) {
28
+ super({ code: "COMING_SOON", exitCode: 0, message });
29
+ }
30
+ };
31
+ var ValidationError = class extends RatioCLIError {
32
+ constructor(message, hint) {
33
+ const opts = {
34
+ code: "VALIDATION_ERROR",
35
+ exitCode: 8,
36
+ message
37
+ };
38
+ if (hint !== void 0) opts.hint = hint;
39
+ super(opts);
40
+ }
41
+ };
42
+ function isRatioCLIError(err) {
43
+ return err instanceof RatioCLIError;
44
+ }
45
+
46
+ // src/lib/render-error.ts
47
+ function isOclifParserError(err) {
48
+ return err instanceof Error && "parse" in err && err.parse !== null && err.parse !== void 0;
49
+ }
50
+ function buildErrorEnvelope(code, message, hint, docsUrl) {
51
+ const obj = { ok: false, code, message };
52
+ if (hint !== void 0) obj["hint"] = hint;
53
+ if (docsUrl !== void 0) obj["docsUrl"] = docsUrl;
54
+ return JSON.stringify(obj) + "\n";
55
+ }
56
+ function renderError(input) {
57
+ const { err, verbose, json } = input;
58
+ if (isRatioCLIError(err)) {
59
+ const ratioErr = err;
60
+ if (ratioErr.exitCode === 0) {
61
+ if (json) {
62
+ return {
63
+ stdout: JSON.stringify({ ok: true, data: null }) + "\n",
64
+ stderr: "",
65
+ exitCode: 0
66
+ };
67
+ }
68
+ return {
69
+ stdout: ratioErr.message + "\n",
70
+ stderr: "",
71
+ exitCode: 0
72
+ };
73
+ }
74
+ let stderrLines = `${ratioErr.code}: ${ratioErr.message}
75
+ `;
76
+ if (ratioErr.hint !== void 0) stderrLines += `${ratioErr.hint}
77
+ `;
78
+ if (ratioErr.docsUrl !== void 0) stderrLines += `${ratioErr.docsUrl}
79
+ `;
80
+ if (json) {
81
+ return {
82
+ stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),
83
+ stderr: verbose && ratioErr.stack ? ratioErr.stack + "\n" : "",
84
+ exitCode: ratioErr.exitCode
85
+ };
86
+ }
87
+ if (verbose && ratioErr.stack) {
88
+ stderrLines += ratioErr.stack + "\n";
89
+ }
90
+ return { stdout: "", stderr: stderrLines, exitCode: ratioErr.exitCode };
91
+ }
92
+ if (isOclifParserError(err)) {
93
+ const parserErr = err;
94
+ const msg = parserErr.message || "Unknown parse error";
95
+ const coercedErr = new ValidationError(msg);
96
+ let stderrText2 = `${coercedErr.code}: ${msg}
97
+ `;
98
+ if (json) {
99
+ return {
100
+ stdout: buildErrorEnvelope(coercedErr.code, msg),
101
+ stderr: verbose && parserErr.stack ? parserErr.stack + "\n" : "",
102
+ exitCode: 8
103
+ };
104
+ }
105
+ if (verbose && parserErr.stack) {
106
+ stderrText2 += parserErr.stack + "\n";
107
+ }
108
+ return { stdout: "", stderr: stderrText2, exitCode: 8 };
109
+ }
110
+ const message = err instanceof Error ? err.message : String(err);
111
+ const stack = err instanceof Error ? err.stack : void 0;
112
+ const stderrPrefix = `INTERNAL: ${message}
113
+ `;
114
+ if (json) {
115
+ return {
116
+ stdout: buildErrorEnvelope("INTERNAL", message),
117
+ stderr: verbose && stack ? stack + "\n" : "",
118
+ exitCode: 1
119
+ };
120
+ }
121
+ let stderrText = stderrPrefix;
122
+ if (verbose && stack) {
123
+ stderrText += stack + "\n";
124
+ }
125
+ return { stdout: "", stderr: stderrText, exitCode: 1 };
126
+ }
127
+
128
+ // src/lib/ratio-command.ts
129
+ var RatioCommand = class extends Command {
130
+ _getRenderFlags() {
131
+ const argv = this.argv;
132
+ return {
133
+ verbose: argv.includes("--verbose"),
134
+ json: argv.includes("--json")
135
+ };
136
+ }
137
+ async catch(err) {
138
+ const { verbose, json } = this._getRenderFlags();
139
+ const output = renderError({ err, verbose, json });
140
+ if (output.stdout) process.stdout.write(output.stdout);
141
+ if (output.stderr) process.stderr.write(output.stderr);
142
+ if (output.exitCode === 0) {
143
+ throw new Errors.ExitError(0);
144
+ }
145
+ err.skipOclifErrorHandling = true;
146
+ err.oclif = { exit: output.exitCode };
147
+ throw err;
148
+ }
149
+ };
150
+
151
+ // src/lib/verbose-flag.ts
152
+ import { Flags } from "@oclif/core";
153
+ var verboseFlag = {
154
+ verbose: Flags.boolean({
155
+ description: "Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors."
156
+ })
157
+ };
158
+ var jsonFlag = {
159
+ json: Flags.boolean({
160
+ description: "Emit machine-readable JSON on stdout."
161
+ })
162
+ };
163
+
164
+ // src/commands/app/generate.ts
165
+ var AppGenerate = class _AppGenerate extends RatioCommand {
166
+ static description = "Generate code inside the current app. (Coming soon.)";
167
+ static examples = ["<%= config.bin %> app generate"];
168
+ static flags = {
169
+ ...verboseFlag
170
+ };
171
+ async run() {
172
+ await this.parse(_AppGenerate);
173
+ throw new ComingSoonError(comingSoonMessage("ratio app generate"));
174
+ }
175
+ };
176
+ export {
177
+ AppGenerate as default
178
+ };
179
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/messages.ts","../../../src/lib/ratio-command.ts","../../../src/lib/errors.ts","../../../src/lib/render-error.ts","../../../src/lib/verbose-flag.ts","../../../src/commands/app/generate.ts"],"sourcesContent":["/**\n * User-facing placeholder-message helpers used by every command\n * whose real behaviour is not yet available. Kept in one module so\n * wording stays consistent and a future edit is one place.\n */\n\n/** Message for command classes registered as \"Coming soon\" stubs. */\nexport function comingSoonMessage(command: string): string {\n return (\n `${command} — coming soon.\\n` +\n `This command will be available in an upcoming Ratio CLI release.\\n` +\n `Run 'ratio --help' after your next update to see what's new.`\n );\n}\n\n/**\n * Message for real command surfaces whose behaviour is being built.\n * @param command display name, e.g. 'ratio auth login'\n * @param hint one short sentence naming what's being finalised\n */\nexport function notYetAvailableMessage(command: string, hint: string): string {\n return (\n `${command} — not yet available.\\n` +\n `${hint}\\n` +\n `Run 'ratio --help' after your next update to see when it lands.`\n );\n}\n","import { Command, Errors } from '@oclif/core';\nimport { renderError } from './render-error.js';\n\n/**\n * Base class for all Ratio CLI commands.\n *\n * Overrides catch() to intercept every error thrown from run(), render it\n * per the Ratio CLI error-UX spec (typed hierarchy -> structured stderr/stdout,\n * --verbose for stack traces, --json for machine-readable envelope), and then\n * suppress oclif's own double-render by setting skipOclifErrorHandling on the\n * re-thrown error.\n *\n * Path B: base-class override — no oclif.hooks wiring needed.\n * oclif v4 does not expose a command_error hook; the reliable interception\n * point is Command.catch(), which is called by Command._run() before the\n * error bubbles up to oclif's global handle() function.\n *\n * Double-render prevention:\n * oclif's handle() checks err.skipOclifErrorHandling before printing.\n * After rendering we set skipOclifErrorHandling = true so oclif's\n * pretty-print is skipped. We also set err.oclif.exit = N so handle()\n * calls process.exit(N) with the correct code.\n *\n * Exit-code routing:\n * - exitCode === 0 (ComingSoonError): throw ExitError(0) so oclif calls\n * process.exit(0) cleanly. ExitError has code 'EEXIT' and is treated as\n * a clean exit — shouldPrint is false in handle().\n * - exitCode > 0: mark err.skipOclifErrorHandling = true and set\n * err.oclif = { exit: N }, then re-throw so handle() exits N.\n *\n * Verbose/json detection strategy:\n * We scan this.argv directly. this.argv is set by oclif's Command before\n * run() is called and always contains the raw argument list. This approach\n * works both for:\n * (a) post-parse errors (thrown after this.parse() succeeds) — argv has the\n * flags that were successfully recognised.\n * (b) pre-parse errors (thrown BY this.parse(), e.g. unknown flag) — argv\n * still contains --verbose / --json even though parse() threw.\n * Simple string scan is sufficient since --verbose and --json are boolean\n * flags with no value argument.\n */\nexport abstract class RatioCommand extends Command {\n private _getRenderFlags(): { verbose: boolean; json: boolean } {\n const argv = this.argv as string[];\n return {\n verbose: argv.includes('--verbose'),\n json: argv.includes('--json'),\n };\n }\n\n override async catch(\n err: Error & {\n exitCode?: number;\n oclif?: { exit?: number };\n skipOclifErrorHandling?: boolean;\n },\n ): Promise<never> {\n const { verbose, json } = this._getRenderFlags();\n\n const output = renderError({ err, verbose, json });\n\n // Write rendered output before any exit/throw\n if (output.stdout) process.stdout.write(output.stdout);\n if (output.stderr) process.stderr.write(output.stderr);\n\n if (output.exitCode === 0) {\n // ComingSoonError path — ExitError(0) causes oclif to call process.exit(0)\n // cleanly. ExitError has code 'EEXIT'; handle() treats it as a clean exit\n // (shouldPrint is false) so no additional output is written.\n throw new Errors.ExitError(0);\n }\n\n // For non-zero exits: suppress oclif's pretty-print by marking the error,\n // then route the correct exit code via oclif.exit so handle() calls process.exit(N).\n err.skipOclifErrorHandling = true;\n err.oclif = { exit: output.exitCode };\n throw err;\n }\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","import { isRatioCLIError, RatioCLIError, ValidationError } from './errors.js';\n\nexport interface RenderInput {\n err: unknown;\n verbose: boolean;\n json: boolean;\n}\n\nexport interface RenderOutput {\n /** Text to write to stdout. Empty string means nothing to write. */\n stdout: string;\n /** Text to write to stderr. Empty string means nothing to write. */\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Detects whether an error originates from the oclif argument/flag parser.\n * oclif's CLIParseError subclasses (NonExistentFlagsError, RequiredArgsError,\n * etc.) all carry a `parse` property set by CLIParseErrorOptions. This is the\n * stable structural signal — more reliable than checking the class name string.\n */\nfunction isOclifParserError(err: unknown): err is Error & { parse: unknown } {\n return (\n err instanceof Error &&\n 'parse' in err &&\n err.parse !== null &&\n err.parse !== undefined\n );\n}\n\n/**\n * Builds the JSON error envelope. Fields with undefined values are omitted\n * so the consumer never sees `\"hint\": null` or `\"docsUrl\": null`.\n */\nfunction buildErrorEnvelope(\n code: string,\n message: string,\n hint?: string,\n docsUrl?: string,\n): string {\n const obj: Record<string, unknown> = { ok: false, code, message };\n if (hint !== undefined) obj['hint'] = hint;\n if (docsUrl !== undefined) obj['docsUrl'] = docsUrl;\n return JSON.stringify(obj) + '\\n';\n}\n\n/**\n * Pure rendering function — no side effects, no process.exit, no I/O.\n * The caller (RatioCommand.catch) is responsible for writing output and exiting.\n *\n * Rendering rules (all branches):\n *\n * | Error shape | --json | --verbose | stdout | stderr | exit |\n * |----------------------|--------|-----------|--------------------------------|----------------------------------|------|\n * | ComingSoonError | false | any | err.message + '\\n' | '' | 0 |\n * | ComingSoonError | true | any | '{\"ok\":true,\"data\":null}\\n' | '' | 0 |\n * | RatioCLIError >0 | false | false | '' | code: msg [+ hint] [+ docsUrl] | N |\n * | RatioCLIError >0 | false | true | '' | above + stack | N |\n * | RatioCLIError >0 | true | false | JSON envelope | '' | N |\n * | RatioCLIError >0 | true | true | JSON envelope | stack only | N |\n * | oclif ParserError | false | any | '' | VALIDATION_ERROR: msg [+stack] | 8 |\n * | oclif ParserError | true | any | JSON VALIDATION_ERROR envelope | stack if verbose | 8 |\n * | Plain Error / other | false | false | '' | INTERNAL: msg\\n | 1 |\n * | Plain Error / other | false | true | '' | INTERNAL: msg\\n + stack\\n | 1 |\n * | Plain Error / other | true | any | JSON INTERNAL envelope | stack if verbose | 1 |\n */\nexport function renderError(input: RenderInput): RenderOutput {\n const { err, verbose, json } = input;\n\n // ── Branch 1: our own typed hierarchy ───────────────────────────────────\n if (isRatioCLIError(err)) {\n const ratioErr = err as RatioCLIError;\n\n // exitCode === 0 → coming-soon: render on stdout, no stderr, exit 0\n if (ratioErr.exitCode === 0) {\n if (json) {\n return {\n stdout: JSON.stringify({ ok: true, data: null }) + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n return {\n stdout: ratioErr.message + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n\n // exitCode > 0 → error render\n let stderrLines = `${ratioErr.code}: ${ratioErr.message}\\n`;\n if (ratioErr.hint !== undefined) stderrLines += `${ratioErr.hint}\\n`;\n if (ratioErr.docsUrl !== undefined) stderrLines += `${ratioErr.docsUrl}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),\n stderr: verbose && ratioErr.stack ? ratioErr.stack + '\\n' : '',\n exitCode: ratioErr.exitCode,\n };\n }\n\n if (verbose && ratioErr.stack) {\n stderrLines += ratioErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrLines, exitCode: ratioErr.exitCode };\n }\n\n // ── Branch 2: oclif parser error (unknown flag, missing required arg, etc.) ──\n if (isOclifParserError(err)) {\n const parserErr = err as Error & { parse: unknown };\n const msg = parserErr.message || 'Unknown parse error';\n\n // Strip oclif's \"Unexpected argument: --foo\" or similar prefix noise where\n // possible, but keep the core message as-is since it's already human-readable.\n const coercedErr = new ValidationError(msg);\n\n let stderrText = `${coercedErr.code}: ${msg}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(coercedErr.code, msg),\n stderr: verbose && parserErr.stack ? parserErr.stack + '\\n' : '',\n exitCode: 8,\n };\n }\n\n if (verbose && parserErr.stack) {\n stderrText += parserErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 8 };\n }\n\n // ── Branch 3: plain Error or non-Error throw ─────────────────────────────\n const message = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n const stderrPrefix = `INTERNAL: ${message}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope('INTERNAL', message),\n stderr: verbose && stack ? stack + '\\n' : '',\n exitCode: 1,\n };\n }\n\n let stderrText = stderrPrefix;\n if (verbose && stack) {\n stderrText += stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 1 };\n}\n","import { Flags } from '@oclif/core';\n\n/** Global verbose flag — every command imports and spreads this. */\nexport const verboseFlag = {\n verbose: Flags.boolean({\n description:\n 'Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors.',\n }),\n};\n\n/** JSON envelope flag — read-style commands (whoami, dev trigger --list) import. */\nexport const jsonFlag = {\n json: Flags.boolean({\n description: 'Emit machine-readable JSON on stdout.',\n }),\n};\n","import { comingSoonMessage } from '../../lib/messages.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { verboseFlag } from '../../lib/verbose-flag.js';\nimport { ComingSoonError } from '../../lib/errors.js';\n\nexport default class AppGenerate extends RatioCommand {\n static override description = 'Generate code inside the current app. (Coming soon.)';\n\n static override examples = ['<%= config.bin %> app generate'];\n\n static override flags = {\n ...verboseFlag,\n };\n\n public async run(): Promise<void> {\n await this.parse(AppGenerate);\n throw new ComingSoonError(comingSoonMessage('ratio app generate'));\n }\n}\n"],"mappings":"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","names":["stderrText"]}
@@ -0,0 +1,14 @@
1
+ import * as _oclif_core_interfaces from '@oclif/core/interfaces';
2
+ import { RatioCommand } from '../../lib/ratio-command.js';
3
+ import '@oclif/core';
4
+
5
+ declare class AppInfo extends RatioCommand {
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ verbose: _oclif_core_interfaces.BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
13
+
14
+ export { AppInfo as default };
@@ -0,0 +1,179 @@
1
+ // src/lib/messages.ts
2
+ function comingSoonMessage(command) {
3
+ return `${command} \u2014 coming soon.
4
+ This command will be available in an upcoming Ratio CLI release.
5
+ Run 'ratio --help' after your next update to see what's new.`;
6
+ }
7
+
8
+ // src/lib/ratio-command.ts
9
+ import { Command, Errors } from "@oclif/core";
10
+
11
+ // src/lib/errors.ts
12
+ var RatioCLIError = class extends Error {
13
+ code;
14
+ exitCode;
15
+ hint;
16
+ docsUrl;
17
+ constructor(opts) {
18
+ super(opts.message);
19
+ this.name = new.target.name;
20
+ this.code = opts.code;
21
+ this.exitCode = opts.exitCode;
22
+ if (opts.hint !== void 0) this.hint = opts.hint;
23
+ if (opts.docsUrl !== void 0) this.docsUrl = opts.docsUrl;
24
+ }
25
+ };
26
+ var ComingSoonError = class extends RatioCLIError {
27
+ constructor(message) {
28
+ super({ code: "COMING_SOON", exitCode: 0, message });
29
+ }
30
+ };
31
+ var ValidationError = class extends RatioCLIError {
32
+ constructor(message, hint) {
33
+ const opts = {
34
+ code: "VALIDATION_ERROR",
35
+ exitCode: 8,
36
+ message
37
+ };
38
+ if (hint !== void 0) opts.hint = hint;
39
+ super(opts);
40
+ }
41
+ };
42
+ function isRatioCLIError(err) {
43
+ return err instanceof RatioCLIError;
44
+ }
45
+
46
+ // src/lib/render-error.ts
47
+ function isOclifParserError(err) {
48
+ return err instanceof Error && "parse" in err && err.parse !== null && err.parse !== void 0;
49
+ }
50
+ function buildErrorEnvelope(code, message, hint, docsUrl) {
51
+ const obj = { ok: false, code, message };
52
+ if (hint !== void 0) obj["hint"] = hint;
53
+ if (docsUrl !== void 0) obj["docsUrl"] = docsUrl;
54
+ return JSON.stringify(obj) + "\n";
55
+ }
56
+ function renderError(input) {
57
+ const { err, verbose, json } = input;
58
+ if (isRatioCLIError(err)) {
59
+ const ratioErr = err;
60
+ if (ratioErr.exitCode === 0) {
61
+ if (json) {
62
+ return {
63
+ stdout: JSON.stringify({ ok: true, data: null }) + "\n",
64
+ stderr: "",
65
+ exitCode: 0
66
+ };
67
+ }
68
+ return {
69
+ stdout: ratioErr.message + "\n",
70
+ stderr: "",
71
+ exitCode: 0
72
+ };
73
+ }
74
+ let stderrLines = `${ratioErr.code}: ${ratioErr.message}
75
+ `;
76
+ if (ratioErr.hint !== void 0) stderrLines += `${ratioErr.hint}
77
+ `;
78
+ if (ratioErr.docsUrl !== void 0) stderrLines += `${ratioErr.docsUrl}
79
+ `;
80
+ if (json) {
81
+ return {
82
+ stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),
83
+ stderr: verbose && ratioErr.stack ? ratioErr.stack + "\n" : "",
84
+ exitCode: ratioErr.exitCode
85
+ };
86
+ }
87
+ if (verbose && ratioErr.stack) {
88
+ stderrLines += ratioErr.stack + "\n";
89
+ }
90
+ return { stdout: "", stderr: stderrLines, exitCode: ratioErr.exitCode };
91
+ }
92
+ if (isOclifParserError(err)) {
93
+ const parserErr = err;
94
+ const msg = parserErr.message || "Unknown parse error";
95
+ const coercedErr = new ValidationError(msg);
96
+ let stderrText2 = `${coercedErr.code}: ${msg}
97
+ `;
98
+ if (json) {
99
+ return {
100
+ stdout: buildErrorEnvelope(coercedErr.code, msg),
101
+ stderr: verbose && parserErr.stack ? parserErr.stack + "\n" : "",
102
+ exitCode: 8
103
+ };
104
+ }
105
+ if (verbose && parserErr.stack) {
106
+ stderrText2 += parserErr.stack + "\n";
107
+ }
108
+ return { stdout: "", stderr: stderrText2, exitCode: 8 };
109
+ }
110
+ const message = err instanceof Error ? err.message : String(err);
111
+ const stack = err instanceof Error ? err.stack : void 0;
112
+ const stderrPrefix = `INTERNAL: ${message}
113
+ `;
114
+ if (json) {
115
+ return {
116
+ stdout: buildErrorEnvelope("INTERNAL", message),
117
+ stderr: verbose && stack ? stack + "\n" : "",
118
+ exitCode: 1
119
+ };
120
+ }
121
+ let stderrText = stderrPrefix;
122
+ if (verbose && stack) {
123
+ stderrText += stack + "\n";
124
+ }
125
+ return { stdout: "", stderr: stderrText, exitCode: 1 };
126
+ }
127
+
128
+ // src/lib/ratio-command.ts
129
+ var RatioCommand = class extends Command {
130
+ _getRenderFlags() {
131
+ const argv = this.argv;
132
+ return {
133
+ verbose: argv.includes("--verbose"),
134
+ json: argv.includes("--json")
135
+ };
136
+ }
137
+ async catch(err) {
138
+ const { verbose, json } = this._getRenderFlags();
139
+ const output = renderError({ err, verbose, json });
140
+ if (output.stdout) process.stdout.write(output.stdout);
141
+ if (output.stderr) process.stderr.write(output.stderr);
142
+ if (output.exitCode === 0) {
143
+ throw new Errors.ExitError(0);
144
+ }
145
+ err.skipOclifErrorHandling = true;
146
+ err.oclif = { exit: output.exitCode };
147
+ throw err;
148
+ }
149
+ };
150
+
151
+ // src/lib/verbose-flag.ts
152
+ import { Flags } from "@oclif/core";
153
+ var verboseFlag = {
154
+ verbose: Flags.boolean({
155
+ description: "Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors."
156
+ })
157
+ };
158
+ var jsonFlag = {
159
+ json: Flags.boolean({
160
+ description: "Emit machine-readable JSON on stdout."
161
+ })
162
+ };
163
+
164
+ // src/commands/app/info.ts
165
+ var AppInfo = class _AppInfo extends RatioCommand {
166
+ static description = "Show details about the current app. (Coming soon.)";
167
+ static examples = ["<%= config.bin %> app info"];
168
+ static flags = {
169
+ ...verboseFlag
170
+ };
171
+ async run() {
172
+ await this.parse(_AppInfo);
173
+ throw new ComingSoonError(comingSoonMessage("ratio app info"));
174
+ }
175
+ };
176
+ export {
177
+ AppInfo as default
178
+ };
179
+ //# sourceMappingURL=info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/messages.ts","../../../src/lib/ratio-command.ts","../../../src/lib/errors.ts","../../../src/lib/render-error.ts","../../../src/lib/verbose-flag.ts","../../../src/commands/app/info.ts"],"sourcesContent":["/**\n * User-facing placeholder-message helpers used by every command\n * whose real behaviour is not yet available. Kept in one module so\n * wording stays consistent and a future edit is one place.\n */\n\n/** Message for command classes registered as \"Coming soon\" stubs. */\nexport function comingSoonMessage(command: string): string {\n return (\n `${command} — coming soon.\\n` +\n `This command will be available in an upcoming Ratio CLI release.\\n` +\n `Run 'ratio --help' after your next update to see what's new.`\n );\n}\n\n/**\n * Message for real command surfaces whose behaviour is being built.\n * @param command display name, e.g. 'ratio auth login'\n * @param hint one short sentence naming what's being finalised\n */\nexport function notYetAvailableMessage(command: string, hint: string): string {\n return (\n `${command} — not yet available.\\n` +\n `${hint}\\n` +\n `Run 'ratio --help' after your next update to see when it lands.`\n );\n}\n","import { Command, Errors } from '@oclif/core';\nimport { renderError } from './render-error.js';\n\n/**\n * Base class for all Ratio CLI commands.\n *\n * Overrides catch() to intercept every error thrown from run(), render it\n * per the Ratio CLI error-UX spec (typed hierarchy -> structured stderr/stdout,\n * --verbose for stack traces, --json for machine-readable envelope), and then\n * suppress oclif's own double-render by setting skipOclifErrorHandling on the\n * re-thrown error.\n *\n * Path B: base-class override — no oclif.hooks wiring needed.\n * oclif v4 does not expose a command_error hook; the reliable interception\n * point is Command.catch(), which is called by Command._run() before the\n * error bubbles up to oclif's global handle() function.\n *\n * Double-render prevention:\n * oclif's handle() checks err.skipOclifErrorHandling before printing.\n * After rendering we set skipOclifErrorHandling = true so oclif's\n * pretty-print is skipped. We also set err.oclif.exit = N so handle()\n * calls process.exit(N) with the correct code.\n *\n * Exit-code routing:\n * - exitCode === 0 (ComingSoonError): throw ExitError(0) so oclif calls\n * process.exit(0) cleanly. ExitError has code 'EEXIT' and is treated as\n * a clean exit — shouldPrint is false in handle().\n * - exitCode > 0: mark err.skipOclifErrorHandling = true and set\n * err.oclif = { exit: N }, then re-throw so handle() exits N.\n *\n * Verbose/json detection strategy:\n * We scan this.argv directly. this.argv is set by oclif's Command before\n * run() is called and always contains the raw argument list. This approach\n * works both for:\n * (a) post-parse errors (thrown after this.parse() succeeds) — argv has the\n * flags that were successfully recognised.\n * (b) pre-parse errors (thrown BY this.parse(), e.g. unknown flag) — argv\n * still contains --verbose / --json even though parse() threw.\n * Simple string scan is sufficient since --verbose and --json are boolean\n * flags with no value argument.\n */\nexport abstract class RatioCommand extends Command {\n private _getRenderFlags(): { verbose: boolean; json: boolean } {\n const argv = this.argv as string[];\n return {\n verbose: argv.includes('--verbose'),\n json: argv.includes('--json'),\n };\n }\n\n override async catch(\n err: Error & {\n exitCode?: number;\n oclif?: { exit?: number };\n skipOclifErrorHandling?: boolean;\n },\n ): Promise<never> {\n const { verbose, json } = this._getRenderFlags();\n\n const output = renderError({ err, verbose, json });\n\n // Write rendered output before any exit/throw\n if (output.stdout) process.stdout.write(output.stdout);\n if (output.stderr) process.stderr.write(output.stderr);\n\n if (output.exitCode === 0) {\n // ComingSoonError path — ExitError(0) causes oclif to call process.exit(0)\n // cleanly. ExitError has code 'EEXIT'; handle() treats it as a clean exit\n // (shouldPrint is false) so no additional output is written.\n throw new Errors.ExitError(0);\n }\n\n // For non-zero exits: suppress oclif's pretty-print by marking the error,\n // then route the correct exit code via oclif.exit so handle() calls process.exit(N).\n err.skipOclifErrorHandling = true;\n err.oclif = { exit: output.exitCode };\n throw err;\n }\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","import { isRatioCLIError, RatioCLIError, ValidationError } from './errors.js';\n\nexport interface RenderInput {\n err: unknown;\n verbose: boolean;\n json: boolean;\n}\n\nexport interface RenderOutput {\n /** Text to write to stdout. Empty string means nothing to write. */\n stdout: string;\n /** Text to write to stderr. Empty string means nothing to write. */\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Detects whether an error originates from the oclif argument/flag parser.\n * oclif's CLIParseError subclasses (NonExistentFlagsError, RequiredArgsError,\n * etc.) all carry a `parse` property set by CLIParseErrorOptions. This is the\n * stable structural signal — more reliable than checking the class name string.\n */\nfunction isOclifParserError(err: unknown): err is Error & { parse: unknown } {\n return (\n err instanceof Error &&\n 'parse' in err &&\n err.parse !== null &&\n err.parse !== undefined\n );\n}\n\n/**\n * Builds the JSON error envelope. Fields with undefined values are omitted\n * so the consumer never sees `\"hint\": null` or `\"docsUrl\": null`.\n */\nfunction buildErrorEnvelope(\n code: string,\n message: string,\n hint?: string,\n docsUrl?: string,\n): string {\n const obj: Record<string, unknown> = { ok: false, code, message };\n if (hint !== undefined) obj['hint'] = hint;\n if (docsUrl !== undefined) obj['docsUrl'] = docsUrl;\n return JSON.stringify(obj) + '\\n';\n}\n\n/**\n * Pure rendering function — no side effects, no process.exit, no I/O.\n * The caller (RatioCommand.catch) is responsible for writing output and exiting.\n *\n * Rendering rules (all branches):\n *\n * | Error shape | --json | --verbose | stdout | stderr | exit |\n * |----------------------|--------|-----------|--------------------------------|----------------------------------|------|\n * | ComingSoonError | false | any | err.message + '\\n' | '' | 0 |\n * | ComingSoonError | true | any | '{\"ok\":true,\"data\":null}\\n' | '' | 0 |\n * | RatioCLIError >0 | false | false | '' | code: msg [+ hint] [+ docsUrl] | N |\n * | RatioCLIError >0 | false | true | '' | above + stack | N |\n * | RatioCLIError >0 | true | false | JSON envelope | '' | N |\n * | RatioCLIError >0 | true | true | JSON envelope | stack only | N |\n * | oclif ParserError | false | any | '' | VALIDATION_ERROR: msg [+stack] | 8 |\n * | oclif ParserError | true | any | JSON VALIDATION_ERROR envelope | stack if verbose | 8 |\n * | Plain Error / other | false | false | '' | INTERNAL: msg\\n | 1 |\n * | Plain Error / other | false | true | '' | INTERNAL: msg\\n + stack\\n | 1 |\n * | Plain Error / other | true | any | JSON INTERNAL envelope | stack if verbose | 1 |\n */\nexport function renderError(input: RenderInput): RenderOutput {\n const { err, verbose, json } = input;\n\n // ── Branch 1: our own typed hierarchy ───────────────────────────────────\n if (isRatioCLIError(err)) {\n const ratioErr = err as RatioCLIError;\n\n // exitCode === 0 → coming-soon: render on stdout, no stderr, exit 0\n if (ratioErr.exitCode === 0) {\n if (json) {\n return {\n stdout: JSON.stringify({ ok: true, data: null }) + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n return {\n stdout: ratioErr.message + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n\n // exitCode > 0 → error render\n let stderrLines = `${ratioErr.code}: ${ratioErr.message}\\n`;\n if (ratioErr.hint !== undefined) stderrLines += `${ratioErr.hint}\\n`;\n if (ratioErr.docsUrl !== undefined) stderrLines += `${ratioErr.docsUrl}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),\n stderr: verbose && ratioErr.stack ? ratioErr.stack + '\\n' : '',\n exitCode: ratioErr.exitCode,\n };\n }\n\n if (verbose && ratioErr.stack) {\n stderrLines += ratioErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrLines, exitCode: ratioErr.exitCode };\n }\n\n // ── Branch 2: oclif parser error (unknown flag, missing required arg, etc.) ──\n if (isOclifParserError(err)) {\n const parserErr = err as Error & { parse: unknown };\n const msg = parserErr.message || 'Unknown parse error';\n\n // Strip oclif's \"Unexpected argument: --foo\" or similar prefix noise where\n // possible, but keep the core message as-is since it's already human-readable.\n const coercedErr = new ValidationError(msg);\n\n let stderrText = `${coercedErr.code}: ${msg}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(coercedErr.code, msg),\n stderr: verbose && parserErr.stack ? parserErr.stack + '\\n' : '',\n exitCode: 8,\n };\n }\n\n if (verbose && parserErr.stack) {\n stderrText += parserErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 8 };\n }\n\n // ── Branch 3: plain Error or non-Error throw ─────────────────────────────\n const message = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n const stderrPrefix = `INTERNAL: ${message}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope('INTERNAL', message),\n stderr: verbose && stack ? stack + '\\n' : '',\n exitCode: 1,\n };\n }\n\n let stderrText = stderrPrefix;\n if (verbose && stack) {\n stderrText += stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 1 };\n}\n","import { Flags } from '@oclif/core';\n\n/** Global verbose flag — every command imports and spreads this. */\nexport const verboseFlag = {\n verbose: Flags.boolean({\n description:\n 'Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors.',\n }),\n};\n\n/** JSON envelope flag — read-style commands (whoami, dev trigger --list) import. */\nexport const jsonFlag = {\n json: Flags.boolean({\n description: 'Emit machine-readable JSON on stdout.',\n }),\n};\n","import { comingSoonMessage } from '../../lib/messages.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { verboseFlag } from '../../lib/verbose-flag.js';\nimport { ComingSoonError } from '../../lib/errors.js';\n\nexport default class AppInfo extends RatioCommand {\n static override description = 'Show details about the current app. (Coming soon.)';\n\n static override examples = ['<%= config.bin %> app info'];\n\n static override flags = {\n ...verboseFlag,\n };\n\n public async run(): Promise<void> {\n await this.parse(AppInfo);\n throw new ComingSoonError(comingSoonMessage('ratio app info'));\n }\n}\n"],"mappings":"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","names":["stderrText"]}