@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/commands/dev/listen.ts","../../../src/lib/auth/config.ts","../../../src/lib/credentials/clock.ts","../../../src/lib/credentials/path.ts","../../../src/lib/errors.ts","../../../src/lib/credentials/refresher.ts","../../../src/lib/credentials/store.ts","../../../src/lib/credentials/schema.ts","../../../src/lib/ratio-command.ts","../../../src/lib/render-error.ts","../../../src/lib/relay/relay-client.ts","../../../src/lib/verbose-flag.ts"],"sourcesContent":["import { Flags } from '@oclif/core';\nimport { z } from 'zod';\nimport { resolveServerUrl } from '../../lib/auth/config.js';\nimport { FileCredentialStore } from '../../lib/credentials/index.js';\nimport {\n RelayConnectError,\n RelayResumeGapError,\n ValidationError,\n} from '../../lib/errors.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { RelayClient } from '../../lib/relay/index.js';\nimport type { RelayClientOptions } from '../../lib/relay/index.js';\nimport { jsonFlag, verboseFlag } from '../../lib/verbose-flag.js';\n\n/** Best-effort pre-connect reachability probe budget for the local target. */\nexport const TARGET_PROBE_TIMEOUT_MS = 2_000;\n\n/** Per-event forward POST budget; slower targets get a timeout failure line. */\nexport const FORWARD_TIMEOUT_MS = 10_000;\n\n/** Upper bound on concurrently in-flight forward POSTs. */\nexport const FORWARD_MAX_INFLIGHT = 32;\n\n/** Hard cap on waiting for in-flight forwards once the stream has ended. */\nexport const SHUTDOWN_DRAIN_MS = 5_000;\n\n/** A second interrupt inside this window forces an immediate exit (130). */\nexport const SIGNAL_FORCE_EXIT_WINDOW_MS = 2_000;\n\n/** Failed forwards report as `err:<short-reason>` in the event log line. */\nconst FORWARD_ERR_PREFIX = 'err:';\n\n/**\n * Hosts a `--forward-to` target may use. This is an allow-list: anything\n * not named here is rejected, rather than trying to enumerate bad hosts.\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '::1',\n]);\n\nconst FORWARD_TO_HINT =\n 'Use a local address with an explicit port, e.g. --forward-to localhost:3000/webhooks.';\n\n/** Expected response body for the dev-session create call. */\nconst devSessionSchema = z.object({\n sessionId: z.string().min(1),\n wsUrl: z.string().min(1),\n expiresAt: z.string(),\n});\n\ntype DevSession = z.infer<typeof devSessionSchema>;\n\n/** Minimal process-signal surface, injectable so tests never send real signals. */\ninterface SignalSource {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n removeListener(event: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n}\n\n/**\n * Parse and validate a `--forward-to` value into a URL.\n *\n * Accepted: `localhost:3000`, `localhost:3000/webhooks`,\n * `http://localhost:3000/webhooks`, `127.0.0.1:3000`, `[::1]:3000`,\n * `::1:3000`. The host must be local (see LOOPBACK_HOSTS), the port must\n * be explicit, and only plain `http://` is supported. A missing scheme\n * defaults to `http://`.\n */\nexport function parseForwardTarget(raw: string): URL {\n const trimmed = raw.trim();\n if (trimmed === '') {\n throw new ValidationError('--forward-to must not be empty.', FORWARD_TO_HINT);\n }\n\n let candidate = trimmed;\n const hasScheme = /^[A-Za-z][A-Za-z0-9+.-]*:\\/\\//.test(candidate);\n if (!hasScheme && candidate.startsWith('::1')) {\n // Bare IPv6 loopback needs brackets before it can parse as a URL host.\n candidate = `[::1]${candidate.slice('::1'.length)}`;\n }\n if (!hasScheme) {\n candidate = `http://${candidate}`;\n }\n\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new ValidationError(\n `--forward-to is not a valid URL: ${raw}`,\n FORWARD_TO_HINT,\n );\n }\n\n if (url.protocol === 'https:') {\n throw new ValidationError(\n '--forward-to does not support https targets; local forwarding uses plain http.',\n FORWARD_TO_HINT,\n );\n }\n if (url.protocol !== 'http:') {\n throw new ValidationError(\n `--forward-to has an unsupported scheme: ${url.protocol}//`,\n FORWARD_TO_HINT,\n );\n }\n\n const hostname = url.hostname.replace(/^\\[/, '').replace(/\\]$/, '');\n if (!LOOPBACK_HOSTS.has(hostname)) {\n throw new ValidationError(\n `--forward-to host must be local; got: ${hostname}`,\n FORWARD_TO_HINT,\n );\n }\n\n if (url.port === '') {\n throw new ValidationError(\n '--forward-to must include an explicit port.',\n FORWARD_TO_HINT,\n );\n }\n\n return url;\n}\n\n/**\n * Parse a `--events` value into a set of topics. Returns undefined when the\n * flag was not provided (forward everything). A provided-but-empty value is\n * an error — it would silently forward nothing.\n */\nexport function parseEventsFilter(\n raw: string | undefined,\n): ReadonlySet<string> | undefined {\n if (raw === undefined) {\n return undefined;\n }\n const topics = raw\n .split(',')\n .map((token) => token.trim())\n .filter((token) => token !== '');\n if (topics.length === 0) {\n throw new ValidationError(\n '--events was provided but contains no topics.',\n 'Pass a comma-separated list, e.g. --events=orders.created,orders.updated.',\n );\n }\n return new Set(topics);\n}\n\n/**\n * Extract the topic string from a webhook event body. The `--events` filter\n * matches exactly on payload.topic; anything without a string topic is\n * treated as non-matching when a filter is active.\n */\nexport function extractTopic(payload: unknown): string | undefined {\n if (typeof payload !== 'object' || payload === null) {\n return undefined;\n }\n const topic = (payload as { topic?: unknown }).topic;\n return typeof topic === 'string' ? topic : undefined;\n}\n\n/**\n * Render one event log line. JSON mode produces a single NDJSON object per\n * event on its own line; KV mode produces a single `key=value` line.\n */\nexport function formatEventLine(\n mode: 'json' | 'kv',\n seq: number,\n timestamp: string,\n topic: string | undefined,\n status: string,\n): string {\n if (mode === 'json') {\n return (\n JSON.stringify({\n seq,\n t: timestamp,\n topic: topic ?? null,\n 'forward-status': status,\n }) + '\\n'\n );\n }\n return `seq=${seq} t=${timestamp} topic=${topic ?? '-'} forward-status=${status}\\n`;\n}\n\n/** Map a failed forward attempt to a short machine-stable reason token. */\nfunction shortReason(cause: unknown, aborted: boolean): string {\n if (aborted) {\n return 'timeout';\n }\n if (cause instanceof Error) {\n const code = (cause as { cause?: { code?: unknown } }).cause?.code;\n if (typeof code === 'string' && code.length > 0) {\n return code.toLowerCase();\n }\n }\n return 'network';\n}\n\n/**\n * Create a dev session on the platform and return its stream coordinates.\n *\n * @param serverUrl - Base server URL (trailing slashes are stripped).\n * @param token - Bearer access token for the authenticated developer.\n * @param appId - UUID of the developer-owned app to associate with the session.\n */\nasync function createDevSession(\n serverUrl: string,\n token: string,\n appId: string,\n): Promise<DevSession> {\n const base = serverUrl.replace(/\\/+$/, '');\n let response: Response;\n try {\n response = await fetch(`${base}/dev/sessions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ appId }),\n });\n } catch {\n throw new RelayConnectError('Could not reach the dev-session endpoint.');\n }\n\n if (response.status === 400) {\n throw new ValidationError(\n 'The server rejected the dev-session request.',\n 'You may have too many active dev sessions; wait for one to expire and retry.',\n );\n }\n if (!response.ok) {\n throw new RelayConnectError(\n `Failed to create dev session (status: ${response.status})`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new RelayConnectError('Dev-session response was not the expected shape.');\n }\n const parsed = devSessionSchema.safeParse(body);\n if (!parsed.success) {\n throw new RelayConnectError('Dev-session response was not the expected shape.');\n }\n return parsed.data;\n}\n\n/** Wait for all pending promises, but never longer than `capMs`. */\nasync function drainPending(\n pending: ReadonlySet<Promise<void>>,\n capMs: number,\n): Promise<void> {\n if (pending.size === 0) {\n return;\n }\n let capTimer: NodeJS.Timeout | undefined;\n const cap = new Promise<void>((resolve) => {\n capTimer = setTimeout(resolve, capMs);\n });\n try {\n await Promise.race([Promise.all([...pending]), cap]);\n } finally {\n if (capTimer !== undefined) {\n clearTimeout(capTimer);\n }\n }\n}\n\nexport default class DevListen extends RatioCommand {\n static override description =\n 'Stream webhook events from your dev session and forward them to a local HTTP server.';\n\n static override examples = [\n '<%= config.bin %> dev listen --forward-to localhost:3000/webhooks --app-id=4f9d2c31-8a6b-4f2e-9c1d-3e5a7b9d0f12',\n '<%= config.bin %> dev listen --forward-to http://127.0.0.1:3000/webhooks --events=orders.created,orders.updated --app-id=4f9d2c31-8a6b-4f2e-9c1d-3e5a7b9d0f12',\n '<%= config.bin %> dev listen --forward-to localhost:3000/webhooks --json',\n '<%= config.bin %> dev listen --forward-to localhost:3000/webhooks --app-id=4f9d2c31-8a6b-4f2e-9c1d-3e5a7b9d0f12 --verbose',\n ];\n\n static override flags = {\n ...verboseFlag,\n ...jsonFlag,\n 'forward-to': Flags.string({\n description:\n 'Local URL to forward events to (localhost only, explicit port required), e.g. localhost:3000/webhooks.',\n required: true,\n }),\n events: Flags.string({\n description: 'Comma-separated event topics to forward (default: all).',\n }),\n 'app-id': Flags.string({\n description:\n 'App id whose dev session to open (UUID). Required.',\n required: true,\n }),\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 makeRelayClient: (options: RelayClientOptions) => RelayClient = (\n options,\n ) => new RelayClient(options);\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(DevListen);\n const jsonMode = flags.json === true;\n const verbose = flags.verbose === true;\n const trace = (message: string): void => {\n if (verbose) {\n this.writeErr(`${message}\\n`);\n }\n };\n\n const target = parseForwardTarget(flags['forward-to']);\n const targetUrl = target.toString();\n const topicFilter = parseEventsFilter(flags.events);\n\n const appId = flags['app-id'];\n if (appId === '') {\n throw new ValidationError(\n 'A dev listen session requires an --app-id.',\n 'Pass --app-id=<uuid>.',\n );\n }\n\n const store = new FileCredentialStore();\n const token = await store.getValidAccessToken();\n\n const serverUrl = resolveServerUrl();\n const session = await createDevSession(serverUrl, token, appId);\n trace(`session created: ${session.sessionId} (expires ${session.expiresAt})`);\n\n await this.probeTarget(targetUrl, trace);\n\n const clientOptions: RelayClientOptions = {\n sessionId: session.sessionId,\n wsUrl: session.wsUrl,\n credentialStore: store,\n ...(verbose\n ? { tracer: (message: string): void => this.writeErr(`${message}\\n`) }\n : {}),\n };\n const client = this.makeRelayClient(clientOptions);\n\n const emitEventLine = (\n seq: number,\n timestamp: string,\n topic: string | undefined,\n status: string,\n ): void => {\n if (jsonMode) {\n this.writeOut(formatEventLine('json', seq, timestamp, topic, status));\n return;\n }\n this.writeErr(formatEventLine('kv', seq, timestamp, topic, status));\n };\n\n let inFlight = 0;\n const slotWaiters: Array<() => void> = [];\n const pending = new Set<Promise<void>>();\n const releaseSlot = (): void => {\n const wake = slotWaiters.shift();\n if (wake !== undefined) {\n wake();\n }\n };\n\n let shuttingDown = false;\n let firstSignalAt = 0;\n const onSignal = (): void => {\n if (shuttingDown) {\n if (Date.now() - firstSignalAt <= SIGNAL_FORCE_EXIT_WINDOW_MS) {\n this.hardExit(130);\n }\n return;\n }\n shuttingDown = true;\n firstSignalAt = Date.now();\n this.writeErr(\n 'shutting down: closing the event stream and draining in-flight forwards\\n',\n );\n void client.close();\n };\n this.signalSource.on('SIGINT', onSignal);\n this.signalSource.on('SIGTERM', onSignal);\n\n try {\n await client.connect();\n this.writeErr(\n `listening: forwarding webhook events to ${targetUrl}\\n`,\n );\n\n try {\n for await (const event of client.events()) {\n if (event.kind !== 'webhook') {\n emitEventLine(event.seq, event.timestamp, undefined, 'dropped:control');\n continue;\n }\n\n const topic = extractTopic(event.payload);\n if (\n topicFilter !== undefined &&\n (topic === undefined || !topicFilter.has(topic))\n ) {\n emitEventLine(event.seq, event.timestamp, topic, 'filtered');\n continue;\n }\n\n if (inFlight >= FORWARD_MAX_INFLIGHT) {\n trace('forward capacity reached; waiting for a slot');\n await new Promise<void>((resolve) => {\n slotWaiters.push(resolve);\n });\n }\n\n inFlight += 1;\n const task = this.forwardOnce(targetUrl, event.payload)\n .then((status) => {\n emitEventLine(event.seq, event.timestamp, topic, status);\n })\n .finally(() => {\n inFlight -= 1;\n pending.delete(task);\n releaseSlot();\n });\n pending.add(task);\n }\n } catch (streamErr) {\n if (streamErr instanceof RelayResumeGapError) {\n // Events were missed and can never be replayed; surface immediately\n // so the error renderer maps it to its documented exit code.\n throw streamErr;\n }\n if (streamErr instanceof RelayConnectError && /4401|auth/i.test(streamErr.message)) {\n // The relay rejected our token mid-stream; a fresh sign-in is the fix.\n throw new RelayConnectError(\n streamErr.message,\n \"Your session token was rejected. Run 'ratio auth login' to sign in again.\",\n );\n }\n throw streamErr;\n }\n\n // Stream ended cleanly (server close or shutdown signal) — give the\n // remaining forwards a bounded window to finish.\n await drainPending(pending, SHUTDOWN_DRAIN_MS);\n } finally {\n this.signalSource.removeListener('SIGINT', onSignal);\n this.signalSource.removeListener('SIGTERM', onSignal);\n await client.close().catch(() => undefined);\n }\n }\n\n /**\n * Best-effort reachability probe of the forward target. Never throws and\n * never blocks the session: a dead target only produces a warning, because\n * the user may start their local server after this command boots.\n */\n private async probeTarget(\n targetUrl: string,\n trace: (message: string) => void,\n ): Promise<void> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), TARGET_PROBE_TIMEOUT_MS);\n try {\n const response = await fetch(targetUrl, {\n method: 'HEAD',\n signal: controller.signal,\n });\n trace(`target probe: responded with status ${response.status}`);\n } catch {\n this.writeErr(\n `warning: target ${targetUrl} is not responding yet; events will still be forwarded once your server is up\\n`,\n );\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * POST one event body to the local target. Resolves to a status token —\n * `ok:<status>` or `err:<reason>` — and NEVER rejects: a failed forward\n * is logged and dropped so the stream keeps flowing.\n */\n private async forwardOnce(targetUrl: string, payload: unknown): Promise<string> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FORWARD_TIMEOUT_MS);\n try {\n const response = await fetch(targetUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload ?? null),\n signal: controller.signal,\n });\n // Read and discard the body so the connection is released promptly.\n await response.arrayBuffer().catch(() => undefined);\n if (response.ok) {\n return `ok:${response.status}`;\n }\n return `${FORWARD_ERR_PREFIX}http-${response.status}`;\n } catch (cause) {\n return `${FORWARD_ERR_PREFIX}${shortReason(cause, controller.signal.aborted)}`;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","/**\n * Server-URL resolution for the Ratio CLI auth flow.\n *\n * Resolution order (highest priority first):\n * 1. RATIO_SERVER_URL environment variable\n * 2. Credentials.server from the stored credential file (passed in as opts.credentials.server)\n * 3. The compiled-in default: https://gkx.ratio.win\n *\n * The default is the ONLY place this URL appears in non-config source files.\n * All other auth-library modules call resolveServerUrl() — never hardcode.\n *\n * A-28: the merchantId constant lives here so it appears in exactly ONE\n * production file and is grep-able.\n */\n\n/** Compiled-in default server URL. */\nexport const DEFAULT_SERVER_URL = 'https://gkx.ratio.win';\n\n/**\n * Synthetic merchantId sent in the authorize URL.\n * A-28 adjudication: backend auto-creates missing merchants via\n * ensureMerchantExists(); 'ratio-cli-dev' is the designated synthetic value.\n * Appears in exactly ONE production file (this one).\n */\nexport const RATIO_CLI_MERCHANT_ID = 'ratio-cli-dev';\n\n/** The fixed clientId for the ratio-cli public OAuth client. */\nexport const RATIO_CLI_CLIENT_ID = 'ratio-cli' as const;\n\nexport interface ServerConfig {\n readonly url: string;\n}\n\nexport interface ResolveServerUrlOptions {\n readonly credentials?: { readonly server?: string };\n readonly env?: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Resolve the backend server URL using the three-level precedence chain.\n * Injectable for tests: pass `env` to avoid reading `process.env` directly.\n */\nexport function resolveServerUrl(opts?: ResolveServerUrlOptions): string {\n const env = opts?.env ?? process.env;\n if (typeof env['RATIO_SERVER_URL'] === 'string' && env['RATIO_SERVER_URL'].length > 0) {\n return env['RATIO_SERVER_URL'];\n }\n if (typeof opts?.credentials?.server === 'string' && opts.credentials.server.length > 0) {\n return opts.credentials.server;\n }\n return DEFAULT_SERVER_URL;\n}\n","/**\n * Injectable wall-clock seam for the credential store.\n *\n * This is the ONLY file in the credentials module allowed to sample the\n * real wall clock. Every other file receives a {@link Clock} instance so\n * tests can drive expiry-window logic deterministically with a fake.\n */\n\n/** Injectable time source. */\nexport interface Clock {\n /** Milliseconds since Unix epoch. */\n now(): number;\n}\n\n/** Production {@link Clock} backed by the system wall clock. */\nexport class SystemClock implements Clock {\n now(): number {\n return Date.now();\n }\n}\n","/**\n * Pure path resolution for the credentials file.\n *\n * POSIX: `$XDG_CONFIG_HOME/ratio/credentials` when the override is a\n * non-empty string, otherwise `~/.config/ratio/credentials`.\n * Windows: `%APPDATA%\\ratio\\credentials`, falling back to\n * `<home>\\AppData\\Roaming\\ratio\\credentials` when APPDATA is unset.\n *\n * `path.win32.join` / `path.posix.join` are used explicitly (never bare\n * `path.join`) so the result is deterministic when a test running on a\n * POSIX host fakes `platform === 'win32'`.\n */\n\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** The subset of environment variables the path algorithm consults. */\nexport interface PathEnv {\n readonly XDG_CONFIG_HOME?: string;\n readonly APPDATA?: string;\n}\n\n/** Snapshot the two relevant variables from the real process env. */\nfunction snapshotPathEnv(): PathEnv {\n const snapshot: { XDG_CONFIG_HOME?: string; APPDATA?: string } = {};\n const xdgConfigHome = process.env['XDG_CONFIG_HOME'];\n if (xdgConfigHome !== undefined) {\n snapshot.XDG_CONFIG_HOME = xdgConfigHome;\n }\n const appData = process.env['APPDATA'];\n if (appData !== undefined) {\n snapshot.APPDATA = appData;\n }\n return snapshot;\n}\n\n/**\n * Pure function. Returns the on-disk path for the credentials file.\n * platform defaults to process.platform, homedir defaults to os.homedir(),\n * env defaults to a snapshot of {XDG_CONFIG_HOME, APPDATA} from process.env.\n * All three are injectable so unit tests fake Windows without a Windows host.\n */\nexport function defaultCredentialsPath(\n platform?: NodeJS.Platform,\n homedir?: string,\n env?: PathEnv,\n): string {\n const resolvedPlatform = platform ?? process.platform;\n const resolvedHome = homedir ?? os.homedir();\n const resolvedEnv = env ?? snapshotPathEnv();\n\n if (resolvedPlatform === 'win32') {\n const appData = resolvedEnv.APPDATA;\n const base =\n appData !== undefined && appData !== ''\n ? appData\n : path.win32.join(resolvedHome, 'AppData', 'Roaming');\n return path.win32.join(base, 'ratio', 'credentials');\n }\n\n const xdgConfigHome = resolvedEnv.XDG_CONFIG_HOME;\n const base =\n typeof xdgConfigHome === 'string' && xdgConfigHome !== ''\n ? xdgConfigHome\n : path.posix.join(resolvedHome, '.config');\n return path.posix.join(base, 'ratio', 'credentials');\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 * Injectable token-refresh seam for the credential store.\n *\n * The store never performs network I/O itself: when a stored token is\n * close to expiry the store calls the injected {@link TokenRefresher}\n * and persists whatever it returns. The concrete HTTP implementation is\n * wired in by the auth commands; this module only defines the seam plus\n * a throw-if-called placeholder used before sign-in support is wired.\n */\n\nimport { NotLoggedInError } from '../errors.js';\nimport type { TokenSet } from './types.js';\n\n/** Injectable seam that exchanges a refresh token for a fresh token set. */\nexport interface TokenRefresher {\n refresh(refreshToken: string): Promise<TokenSet>;\n}\n\n/**\n * v1 stub. Always rejects with a static-message NotLoggedInError; it\n * never inspects, echoes, or logs the token it was handed. The real\n * HTTP implementation is supplied by the auth commands.\n */\nexport class NotImplementedRefresher implements TokenRefresher {\n async refresh(refreshToken: string): Promise<TokenSet> {\n // Deliberately unused: this placeholder must never touch the token.\n void refreshToken;\n throw new NotLoggedInError(\n 'Token refresh is not available yet in this CLI version.',\n );\n }\n}\n","/**\n * File-backed credential store.\n *\n * Token resolution precedence (highest first):\n * 1. `RATIO_API_TOKEN` from the injected env reader — returned as-is\n * with ZERO filesystem access and ZERO refresher involvement.\n * 2. The requested profile (default: \"default\") from the credentials\n * file, silently refreshed via the injected TokenRefresher when it\n * is within 5 minutes of expiry.\n *\n * Write contract: atomic tmp + rename in the SAME directory, tmp created\n * with mode 0600 at open time (POSIX), parent directory created 0700,\n * belt-and-braces chmod 0600 after rename. On Windows no chmod / mode\n * checks are ever performed. Concurrent writers are last-writer-wins by\n * design — the atomic rename guarantees no half-written file, which is\n * sufficient for a single-developer CLI.\n *\n * No secret (access token, refresh token, raw file contents) is ever\n * included in an error message or a log line.\n */\n\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport { dirname } from 'node:path';\nimport {\n NotLoggedInError,\n TokenExpiredError,\n ValidationError,\n} from '../errors.js';\nimport { SystemClock } from './clock.js';\nimport type { Clock } from './clock.js';\nimport { defaultCredentialsPath } from './path.js';\nimport { NotImplementedRefresher } from './refresher.js';\nimport type { TokenRefresher } from './refresher.js';\nimport { parseCredentials } from './schema.js';\nimport type { Credentials, DeveloperSession, Profile, TokenSet } from './types.js';\n\n/** Milliseconds before expiry at which a silent refresh is triggered. */\nconst REFRESH_WINDOW_MS = 300_000;\n\n/** Env var that short-circuits all file access when set to a non-empty string. */\nconst ENV_TOKEN_VAR = 'RATIO_API_TOKEN';\n\n/**\n * Env var that short-circuits developer-session file access when set to a\n * non-empty string. Strictly separate from RATIO_API_TOKEN: this one carries\n * the developer sign-in bearer used for app-management calls, never the\n * OAuth access token.\n */\nconst ENV_DEVELOPER_TOKEN_VAR = 'RATIO_DEVELOPER_TOKEN';\n\nexport interface CredentialStoreOptions {\n /** Defaults to defaultCredentialsPath(). */\n readonly filePath?: string;\n /** Defaults to new SystemClock(). */\n readonly clock?: Clock;\n /** Defaults to new NotImplementedRefresher(). */\n readonly refresher?: TokenRefresher;\n /** Defaults to () => process.env — injected for env-precedence tests. */\n readonly envReader?: () => Readonly<Record<string, string | undefined>>;\n /** Defaults to process.platform. */\n readonly platform?: NodeJS.Platform;\n}\n\nexport interface CredentialStore {\n /** Reads the full credentials file. Throws NotLoggedInError if missing. */\n load(): Promise<Credentials>;\n /** Atomic write with 0600 on POSIX. tmp + rename in same dir. */\n save(creds: Credentials): Promise<void>;\n /** Deletes the credentials file. Idempotent. */\n clear(): Promise<void>;\n /**\n * Env precedence: if envReader().RATIO_API_TOKEN is set, returns it\n * WITHOUT reading the file, WITHOUT touching the refresher, WITHOUT\n * any fs I/O. This is the auditable \"no disk access\" path.\n *\n * Otherwise: reads the file, validates, computes\n * msUntilExpiry = new Date(profile.expiresAt).getTime() - clock.now()\n * If msUntilExpiry < 300_000 (5 min), calls refresher.refresh(...),\n * persists the new TokenSet atomically, returns the new accessToken.\n * Otherwise returns profile.accessToken.\n */\n getValidAccessToken(profileName?: string): Promise<string>;\n /**\n * Env precedence: RATIO_DEVELOPER_TOKEN if set (no fs I/O).\n * Otherwise: load(); if creds.developer is absent or within 5 minutes of\n * expiry, throws NotLoggedInError ('Developer signin required.').\n * Otherwise returns creds.developer.jwt.\n * NO refresh attempt — the caller drives the re-prompt UX.\n */\n getValidDeveloperToken(): Promise<string>;\n /**\n * Merge a DeveloperSession into the loaded credentials (creating the file\n * if absent), atomic tmp+rename, 0600.\n */\n saveDeveloperSession(session: DeveloperSession): Promise<void>;\n}\n\n/** Narrow an unknown thrown value to a Node errno error with the given code. */\nfunction hasErrnoCode(err: unknown, code: string): boolean {\n return (\n err instanceof Error &&\n (err as NodeJS.ErrnoException).code === code\n );\n}\n\nexport class FileCredentialStore implements CredentialStore {\n private readonly filePath: string;\n private readonly clock: Clock;\n private readonly refresher: TokenRefresher;\n private readonly envReader: () => Readonly<\n Record<string, string | undefined>\n >;\n private readonly platform: NodeJS.Platform;\n\n constructor(opts?: CredentialStoreOptions) {\n this.filePath = opts?.filePath ?? defaultCredentialsPath();\n this.clock = opts?.clock ?? new SystemClock();\n this.refresher = opts?.refresher ?? new NotImplementedRefresher();\n this.envReader = opts?.envReader ?? (() => process.env);\n this.platform = opts?.platform ?? process.platform;\n }\n\n async load(): Promise<Credentials> {\n this.reapplyFileModeIfDrifted();\n\n let raw: string;\n try {\n raw = fs.readFileSync(this.filePath, 'utf8');\n } catch (err) {\n if (hasErrnoCode(err, 'ENOENT')) {\n throw new NotLoggedInError('You are not signed in.');\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // Never echo the raw contents — they may hold a truncated token.\n throw new ValidationError(\n 'credentials file is not valid JSON — run \"ratio auth login\" to reset',\n );\n }\n\n return parseCredentials(parsed);\n }\n\n async save(creds: Credentials): Promise<void> {\n const dir = dirname(this.filePath);\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n if (this.platform !== 'win32') {\n // mkdirSync does not re-mode a pre-existing directory; re-apply.\n fs.chmodSync(dir, 0o700);\n }\n\n const tmpPath = `${this.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, JSON.stringify(creds, null, 2), {\n mode: 0o600,\n });\n fs.renameSync(tmpPath, this.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\n if (this.platform !== 'win32') {\n // Belt-and-braces: rename may preserve a prior destination mode.\n fs.chmodSync(this.filePath, 0o600);\n }\n }\n\n async clear(): Promise<void> {\n try {\n fs.unlinkSync(this.filePath);\n } catch (err) {\n if (hasErrnoCode(err, 'ENOENT')) {\n return; // Idempotent: already gone.\n }\n throw err;\n }\n }\n\n async getValidAccessToken(profileName?: string): Promise<string> {\n const envToken = this.envReader()[ENV_TOKEN_VAR];\n if (typeof envToken === 'string' && envToken.length > 0) {\n return envToken;\n }\n\n const creds = await this.load();\n const name = profileName ?? 'default';\n const profile = creds.profiles[name];\n if (profile === undefined) {\n throw new NotLoggedInError('You are not signed in.');\n }\n\n const msUntilExpiry =\n new Date(profile.expiresAt).getTime() - this.clock.now();\n if (msUntilExpiry < REFRESH_WINDOW_MS) {\n return this.refreshAndPersist(creds, name, profile);\n }\n\n return profile.accessToken;\n }\n\n async getValidDeveloperToken(): Promise<string> {\n const envToken = this.envReader()[ENV_DEVELOPER_TOKEN_VAR];\n if (typeof envToken === 'string' && envToken.length > 0) {\n return envToken;\n }\n\n let creds: Credentials;\n try {\n creds = await this.load();\n } catch (err) {\n if (err instanceof NotLoggedInError) {\n throw new NotLoggedInError('Developer signin required.');\n }\n throw err;\n }\n\n const session = creds.developer;\n if (session === undefined) {\n throw new NotLoggedInError('Developer signin required.');\n }\n\n const msUntilExpiry =\n new Date(session.expiresAt).getTime() - this.clock.now();\n if (msUntilExpiry < REFRESH_WINDOW_MS) {\n // No refresh endpoint exists for developer sessions — the caller\n // drives a fresh interactive sign-in instead.\n throw new NotLoggedInError('Developer signin required.');\n }\n\n return session.jwt;\n }\n\n async saveDeveloperSession(session: DeveloperSession): Promise<void> {\n let creds: Credentials;\n try {\n creds = await this.load();\n } catch (err) {\n if (err instanceof NotLoggedInError) {\n // Edge case: no prior credentials file. Create a fresh v1 shell so\n // the merged write below still validates on the next load().\n creds = { version: 1, server: '', clientId: '', profiles: {} };\n } else {\n throw err;\n }\n }\n\n const updated: Credentials = {\n ...creds,\n developer: session,\n };\n await this.save(updated);\n }\n\n /**\n * Exchange the profile's refresh token for a fresh token set, merge it\n * into the loaded credentials (preserving every other profile), persist\n * atomically, and return the new access token. A refresher failure is\n * wrapped in TokenExpiredError; the underlying message is deliberately\n * discarded so a secret can never leak through the error surface.\n */\n private async refreshAndPersist(\n creds: Credentials,\n name: string,\n profile: Profile,\n ): Promise<string> {\n let refreshed: TokenSet;\n try {\n refreshed = await this.refresher.refresh(profile.refreshToken);\n } catch {\n throw new TokenExpiredError(\n 'Your session has expired and could not be refreshed.',\n );\n }\n\n const updated: Credentials = {\n ...creds,\n profiles: {\n ...creds.profiles,\n [name]: {\n ...profile,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n },\n },\n };\n await this.save(updated);\n return refreshed.accessToken;\n }\n\n /**\n * POSIX-only startup permission check: if the file exists with a mode\n * other than 0600, re-apply 0600 and emit a warning to stderr. Never\n * runs on Windows (stat modes there do not map to POSIX bits and chmod\n * must not be called). Missing file is NOT an error here — load()\n * reports it as NotLoggedInError.\n */\n private reapplyFileModeIfDrifted(): void {\n if (this.platform === 'win32') {\n return;\n }\n\n let stat: fs.Stats;\n try {\n stat = fs.statSync(this.filePath);\n } catch (err) {\n if (hasErrnoCode(err, 'ENOENT')) {\n return; // load() turns the missing file into NotLoggedInError.\n }\n throw err;\n }\n\n const mode = stat.mode & 0o777;\n if (mode !== 0o600) {\n fs.chmodSync(this.filePath, 0o600);\n process.stderr.write(\n `[warn] credentials file permissions were 0o${mode.toString(8)}; corrected to 0600.\\n`,\n );\n }\n }\n}\n","/**\n * Runtime validation for the on-disk credentials file.\n *\n * The parser is deliberately strict: unknown top-level keys, missing\n * fields, non-ISO timestamps, and any `version` other than `1` are all\n * rejected with a typed ValidationError. Error messages NEVER embed the\n * raw file contents — a malformed file may contain a truncated token.\n */\n\nimport { z } from 'zod';\nimport { ValidationError } from '../errors.js';\nimport type { Credentials } from './types.js';\n\nconst identitySchema = z\n .object({\n developerId: z.string(),\n email: z.string(),\n // Token-derived fields captured at login. Optional (additive) so files\n // written before these fields existed still validate as version 1.\n merchantId: z.string().optional(),\n scopes: z.array(z.string()).optional(),\n })\n .strict();\n\nconst profileSchema = z\n .object({\n accessToken: z.string(),\n refreshToken: z.string(),\n expiresAt: z.string().datetime({ offset: true }),\n identity: identitySchema,\n obtainedAt: z.string().datetime({ offset: true }),\n })\n .strict();\n\nconst developerSessionSchema = z\n .object({\n jwt: z.string(),\n developerId: z.string(),\n email: z.string(),\n expiresAt: z.string().datetime({ offset: true }),\n obtainedAt: z.string().datetime({ offset: true }),\n })\n .strict();\n\n/** Strict zod schema for the version-1 credentials file. */\nexport const credentialsSchema = z\n .object({\n version: z.literal(1),\n server: z.string(),\n clientId: z.string(),\n profiles: z.record(z.string(), profileSchema),\n // Optional cached developer session. Additive so files written before\n // this field existed still validate as version 1.\n developer: developerSessionSchema.optional(),\n })\n .strict();\n\n/**\n * Validate an already-JSON-parsed value against the version-1 shape.\n *\n * Throws ValidationError on any mismatch. A recognisable-but-unsupported\n * `version` gets a dedicated upgrade message; the version label is only\n * echoed when it is a number so arbitrary file contents can never leak\n * into an error message.\n */\nexport function parseCredentials(input: unknown): Credentials {\n if (typeof input === 'object' && input !== null && 'version' in input) {\n const version = (input as { version: unknown }).version;\n if (version !== 1) {\n const label = typeof version === 'number' ? String(version) : 'unknown';\n throw new ValidationError(\n `credentials file version ${label} is unsupported by this CLI — upgrade the CLI or sign in again`,\n );\n }\n }\n\n const result = credentialsSchema.safeParse(input);\n if (!result.success) {\n throw new ValidationError(\n 'credentials file schema mismatch — expected v1 shape',\n );\n }\n return result.data as Credentials;\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","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","/**\n * WebSocket relay tunnel client.\n *\n * Responsibilities:\n * - Connect to the relay stream endpoint with `Authorization: Bearer`\n * at the HTTP handshake (the token never appears in the URL). The\n * server authenticates the upgrade by reading the `sessionId` query\n * parameter, so every connect URL carries `?sessionId=<id>`.\n * - Accept the server-issued stream URL, which may already carry the\n * canonical `?sessionId=` parameter; it must match the session the\n * client was constructed with, and no other query parameter is\n * allowed — the client owns the connect query string.\n * - Track the monotonic per-session sequence number; discard\n * duplicates delivered during a resume replay.\n * - Auto-reconnect on abnormal closes with exponential backoff\n * (capped) and multiplicative jitter, resuming from the last seen\n * sequence via the `resumeCursor` query parameter (sent alongside\n * `sessionId`).\n * - Surface a server-declared resume gap as a typed\n * RelayResumeGapError — recovery policy belongs to the caller.\n * - Deliver events through a pull-based AsyncIterable with a bounded\n * local buffer; a stalled consumer fails fast instead of growing\n * memory without limit.\n *\n * Uses the `ws` package (not the runtime's built-in WebSocket, which\n * is only available as a stable global from Node 22 — this package\n * supports Node 18+).\n */\n\nimport WebSocket from 'ws';\nimport {\n RelayConnectError,\n RelayResumeGapError,\n ValidationError,\n} from '../errors.js';\nimport type {\n RelayClientOptions,\n RelayEvent,\n RelayRandom,\n RelayScheduler,\n ResumeCursor,\n} from './types.js';\n\n/**\n * Maximum number of undelivered events held for a slow consumer.\n * Exceeding it rejects the consumer's next pull and closes the\n * connection — the client never buffers without bound.\n */\nexport const LOCAL_BUFFER_MAX = 1000;\n\n/** Base backoff unit; attempt n waits min(2^n * 100, 30_000) ms. */\nconst BACKOFF_BASE_MS = 100;\n/** Backoff ceiling: 30_000 ms (30 seconds). */\nconst BACKOFF_CAP_MS = 30_000;\n\n/** Close code the server uses to reject an unauthenticated socket. */\nconst CLOSE_CODE_AUTH_REJECTED = 4401;\n/** Normal-closure code — clean shutdown, never reconnected. */\nconst CLOSE_CODE_NORMAL = 1000;\n\nconst defaultScheduler: RelayScheduler = (fn, ms) => {\n const handle = setTimeout(fn, ms);\n return { cancel: () => clearTimeout(handle) };\n};\n\ninterface Waiter {\n resolve: (result: IteratorResult<RelayEvent>) => void;\n reject: (err: Error) => void;\n}\n\nexport class RelayClient {\n private readonly wsUrl: string;\n private readonly options: RelayClientOptions;\n private readonly scheduler: RelayScheduler;\n private readonly random: RelayRandom;\n private readonly trace: (msg: string) => void;\n\n private ws: WebSocket | null = null;\n private lastSeq: ResumeCursor = -1;\n private reconnectAttempt = 0;\n private reconnectTimer: { cancel: () => void } | null = null;\n\n private readonly queue: RelayEvent[] = [];\n private readonly waiters: Waiter[] = [];\n /** First fatal error; once set, every consumer pull rejects with it. */\n private failure: Error | null = null;\n /** Clean end of stream (normal close or caller-initiated close()). */\n private ended = false;\n private userClosed = false;\n private started = false;\n\n private pendingConnect: {\n resolve: () => void;\n reject: (err: Error) => void;\n } | null = null;\n\n constructor(options: RelayClientOptions) {\n if (options.wsUrl === '') {\n throw new ValidationError('Relay stream URL must not be empty.');\n }\n if (options.sessionId === '') {\n throw new ValidationError('Relay session id must not be empty.');\n }\n let parsed: URL;\n try {\n parsed = new URL(options.wsUrl);\n } catch {\n throw new ValidationError(\n 'Relay stream URL is not a valid URL.',\n 'The server returned a malformed stream endpoint — retry creating the session.',\n );\n }\n for (const [name, value] of parsed.searchParams.entries()) {\n if (name === 'sessionId') {\n if (value !== options.sessionId) {\n throw new ValidationError(\n 'Relay stream URL carries a session id that does not match the created session.',\n 'The server and client disagree on the session — retry creating the session.',\n );\n }\n continue;\n }\n throw new ValidationError(\n 'Relay stream URL must not carry query parameters other than the session id.',\n 'The client builds the connection query string itself.',\n );\n }\n this.options = options;\n // Canonical base: origin + path only. The connect query string\n // (sessionId, and resumeCursor on resume) is built per attempt.\n this.wsUrl = `${parsed.origin}${parsed.pathname}`;\n this.scheduler = options.scheduler ?? defaultScheduler;\n this.random = options.random ?? Math.random;\n this.trace = options.tracer ?? (() => {});\n }\n\n /**\n * Opens the first connection. Resolves when the socket is open;\n * rejects with a typed error on authentication rejection or when the\n * stream fails before ever becoming established. May only be called\n * once per client instance.\n */\n async connect(): Promise<void> {\n if (this.started) {\n throw new ValidationError('connect() may only be called once.');\n }\n this.started = true;\n await new Promise<void>((resolve, reject) => {\n this.pendingConnect = { resolve, reject };\n void this.openSocket();\n });\n }\n\n /**\n * Pull-based event stream. Single-consumer: the iterator shares the\n * client's internal queue. Ends when close() is called or the server\n * closes cleanly; rejects with a typed error on fatal conditions.\n */\n events(): AsyncIterable<RelayEvent> {\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => this.nextEvent(),\n return: async (): Promise<IteratorResult<RelayEvent>> => {\n await this.close();\n return { done: true, value: undefined };\n },\n }),\n };\n }\n\n /**\n * Graceful shutdown: cancels any pending reconnect, closes the\n * socket with a normal-closure code, and completes the iterator.\n */\n async close(): Promise<void> {\n this.userClosed = true;\n if (this.reconnectTimer !== null) {\n this.reconnectTimer.cancel();\n this.reconnectTimer = null;\n }\n const ws = this.ws;\n if (ws === null || ws.readyState === WebSocket.CLOSED) {\n this.endClean();\n return;\n }\n await new Promise<void>((resolve) => {\n ws.once('close', () => resolve());\n ws.close(CLOSE_CODE_NORMAL);\n });\n }\n\n // ── internal state machine ────────────────────────────────────────\n\n private async openSocket(): Promise<void> {\n let token: string;\n try {\n token = await this.options.credentialStore.getValidAccessToken(\n this.options.profileName,\n );\n } catch (err) {\n // Credential-store failures are already typed; surface as fatal.\n this.fail(err instanceof Error ? err : new RelayConnectError(\n 'Could not obtain an access token for the relay connection.',\n ));\n return;\n }\n\n // The server authenticates the upgrade via the sessionId query\n // parameter — every connect carries it; a resume adds the cursor.\n const query = new URLSearchParams({ sessionId: this.options.sessionId });\n if (this.lastSeq !== -1) {\n query.set('resumeCursor', String(this.lastSeq));\n }\n const url = `${this.wsUrl}?${query.toString()}`;\n\n this.trace(\n `relay connect: session=${this.options.sessionId} cursor=${this.lastSeq}`,\n );\n\n const ws = new WebSocket(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n this.ws = ws;\n\n ws.on('open', () => {\n this.reconnectAttempt = 0;\n this.trace('relay connected');\n if (this.pendingConnect !== null) {\n this.pendingConnect.resolve();\n this.pendingConnect = null;\n }\n });\n ws.on('message', (data) => {\n this.handleMessage(String(data));\n });\n ws.on('error', (err) => {\n // The matching 'close' event drives the state machine; this\n // listener only prevents an unhandled-error crash.\n this.trace(`relay socket error: ${err.message}`);\n });\n ws.on('close', (code) => {\n this.handleClose(code);\n });\n }\n\n private handleMessage(raw: string): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.trace('relay frame dropped: not valid JSON');\n return;\n }\n if (typeof parsed !== 'object' || parsed === null) {\n this.trace('relay frame dropped: not an object');\n return;\n }\n const frame = parsed as Record<string, unknown>;\n\n if (frame['kind'] === 'control') {\n if (frame['type'] === 'resume-gap') {\n this.trace('relay resume gap reported by server');\n this.fail(\n new RelayResumeGapError(\n 'The server could not replay all missed events: the resume cursor is older than the replay window.',\n ),\n );\n return;\n }\n this.trace('relay control frame ignored: unknown type');\n return;\n }\n\n if (frame['kind'] === 'webhook' && typeof frame['seq'] === 'number') {\n const seq = frame['seq'];\n if (seq <= this.lastSeq) {\n // Duplicate delivered during a resume replay — discard.\n return;\n }\n this.lastSeq = seq;\n this.deliver({\n kind: 'webhook',\n seq,\n timestamp:\n typeof frame['timestamp'] === 'number'\n ? new Date(frame['timestamp']).toISOString()\n : '',\n payload: frame['event'],\n });\n return;\n }\n\n this.trace('relay frame dropped: unrecognized envelope');\n }\n\n private deliver(event: RelayEvent): void {\n const waiter = this.waiters.shift();\n if (waiter !== undefined) {\n waiter.resolve({ done: false, value: event });\n return;\n }\n if (this.queue.length >= LOCAL_BUFFER_MAX) {\n this.trace('relay local buffer overflow — closing connection');\n this.fail(\n new RelayConnectError(\n 'Local buffer overflow — consumer stalled',\n 'Slow down the consumer or reduce upstream event rate.',\n ),\n );\n return;\n }\n this.queue.push(event);\n }\n\n private handleClose(code: number): void {\n this.ws = null;\n\n if (this.failure !== null) {\n // Already failed (resume gap, overflow, auth) — nothing to do.\n return;\n }\n\n if (this.userClosed || code === CLOSE_CODE_NORMAL) {\n this.trace(`relay closed cleanly (code ${code})`);\n this.endClean();\n return;\n }\n\n if (code === CLOSE_CODE_AUTH_REJECTED) {\n this.trace('relay rejected the connection: authentication failed');\n this.fail(\n new RelayConnectError(\n 'The relay rejected the connection: authentication failed (close code 4401).',\n \"Run 'ratio auth login' and retry.\",\n ),\n );\n return;\n }\n\n this.scheduleReconnect(code);\n }\n\n private scheduleReconnect(closeCode: number): void {\n this.reconnectAttempt += 1;\n const base = Math.min(\n 2 ** this.reconnectAttempt * BACKOFF_BASE_MS,\n BACKOFF_CAP_MS,\n );\n // Multiplicative jitter in [0.75, 1.25] avoids reconnect stampedes.\n const delay = base * (0.75 + this.random() * 0.5);\n this.trace(\n `relay reconnect attempt ${this.reconnectAttempt} in ${Math.round(delay)}ms (close code ${closeCode})`,\n );\n this.reconnectTimer = this.scheduler(() => {\n this.reconnectTimer = null;\n void this.openSocket();\n }, delay);\n }\n\n private fail(err: Error): void {\n if (this.failure !== null) {\n return;\n }\n this.failure = err;\n // A failed stream must never reconnect — drop any pending timer.\n if (this.reconnectTimer !== null) {\n this.reconnectTimer.cancel();\n this.reconnectTimer = null;\n }\n if (this.pendingConnect !== null) {\n this.pendingConnect.reject(err);\n this.pendingConnect = null;\n }\n let waiter = this.waiters.shift();\n while (waiter !== undefined) {\n waiter.reject(err);\n waiter = this.waiters.shift();\n }\n const ws = this.ws;\n if (ws !== null && ws.readyState !== WebSocket.CLOSED) {\n ws.close(CLOSE_CODE_NORMAL);\n }\n }\n\n private endClean(): void {\n if (this.ended) {\n return;\n }\n this.ended = true;\n if (this.pendingConnect !== null) {\n // Closed before ever opening — the stream never established.\n this.pendingConnect.reject(\n new RelayConnectError(\n 'The relay connection closed before it was established.',\n ),\n );\n this.pendingConnect = null;\n }\n let waiter = this.waiters.shift();\n while (waiter !== undefined) {\n waiter.resolve({ done: true, value: undefined });\n waiter = this.waiters.shift();\n }\n }\n\n private nextEvent(): Promise<IteratorResult<RelayEvent>> {\n if (this.failure !== null) {\n return Promise.reject(this.failure);\n }\n const queued = this.queue.shift();\n if (queued !== undefined) {\n return Promise.resolve({ done: false, value: queued });\n }\n if (this.ended) {\n return Promise.resolve({ done: true, value: undefined });\n }\n return new Promise<IteratorResult<RelayEvent>>((resolve, reject) => {\n this.waiters.push({ resolve, reject });\n });\n }\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"],"mappings":"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","names":["Flags","z","base","stderrText","z","Flags"]}
@@ -0,0 +1,88 @@
1
+ import * as _oclif_core_interfaces from '@oclif/core/interfaces';
2
+ import { z } from 'zod';
3
+ import { CredentialStore } from '../../lib/credentials/store.js';
4
+ import { RatioCommand } from '../../lib/ratio-command.js';
5
+ import '../../lib/credentials/clock.js';
6
+ import '../../lib/credentials/refresher.js';
7
+ import '../../lib/credentials/types.js';
8
+ import '@oclif/core';
9
+
10
+ /** Per-request budget for platform HTTP calls; slower responses time out. */
11
+ declare const REQUEST_TIMEOUT_MS = 10000;
12
+ /** Payload scenarios the flag parser accepts; the server owns the catalog. */
13
+ declare const SCENARIO_OPTIONS: readonly ["cod", "prepaid", "cancelled"];
14
+ /** Expected response body for a successful trigger call (202 Accepted). */
15
+ declare const triggerResponseSchema: z.ZodObject<{
16
+ deliveryId: z.ZodString;
17
+ }, "strip", z.ZodTypeAny, {
18
+ deliveryId: string;
19
+ }, {
20
+ deliveryId: string;
21
+ }>;
22
+ type TriggerResponse = z.infer<typeof triggerResponseSchema>;
23
+ /**
24
+ * One scenario-catalog entry as the server returns it. The catalog —
25
+ * including which alternate topic spellings are accepted — is owned
26
+ * entirely server-side; the CLI only renders what it receives and never
27
+ * rewrites the user's topic input.
28
+ */
29
+ declare const scenarioEntrySchema: z.ZodObject<{
30
+ topic: z.ZodString;
31
+ aliases: z.ZodArray<z.ZodString, "many">;
32
+ scenarios: z.ZodArray<z.ZodString, "many">;
33
+ sampleShape: z.ZodUnknown;
34
+ }, "strip", z.ZodTypeAny, {
35
+ aliases: string[];
36
+ topic: string;
37
+ scenarios: string[];
38
+ sampleShape?: unknown;
39
+ }, {
40
+ aliases: string[];
41
+ topic: string;
42
+ scenarios: string[];
43
+ sampleShape?: unknown;
44
+ }>;
45
+ type ScenarioEntry = z.infer<typeof scenarioEntrySchema>;
46
+ /** Request body for the trigger endpoint. */
47
+ interface TriggerRequestBody {
48
+ readonly topic: string;
49
+ readonly scenario: string;
50
+ readonly appId: string;
51
+ }
52
+ /**
53
+ * POST one synthetic-event trigger. The topic is sent exactly as the user
54
+ * typed it — the server resolves accepted spellings to its canonical form.
55
+ */
56
+ declare function postTrigger(serverUrl: string, token: string, body: TriggerRequestBody): Promise<TriggerResponse>;
57
+ /** GET the scenario catalog (a bare JSON array of entries). */
58
+ declare function getScenarios(serverUrl: string, token: string): Promise<ScenarioEntry[]>;
59
+ /** One-line summary of a sample payload: its top-level field names. */
60
+ declare function summarizeSampleShape(sample: unknown): string;
61
+ /**
62
+ * Render the human-readable topic x scenario table for `--list`.
63
+ * Alternate accepted spellings are shown as a hint line per topic; the
64
+ * server owns that mapping and the CLI treats it as display-only.
65
+ */
66
+ declare function renderScenarioTable(entries: readonly ScenarioEntry[]): string;
67
+ declare class DevTrigger extends RatioCommand {
68
+ static description: string;
69
+ static examples: string[];
70
+ static args: {
71
+ topic: _oclif_core_interfaces.Arg<string | undefined, Record<string, unknown>>;
72
+ };
73
+ static flags: {
74
+ scenario: _oclif_core_interfaces.OptionFlag<string | undefined, _oclif_core_interfaces.CustomOptions>;
75
+ list: _oclif_core_interfaces.BooleanFlag<boolean>;
76
+ 'app-id': _oclif_core_interfaces.OptionFlag<string | undefined, _oclif_core_interfaces.CustomOptions>;
77
+ json: _oclif_core_interfaces.BooleanFlag<boolean>;
78
+ verbose: _oclif_core_interfaces.BooleanFlag<boolean>;
79
+ };
80
+ writeOut: (chunk: string) => void;
81
+ writeErr: (chunk: string) => void;
82
+ makeCredentialStore: () => CredentialStore;
83
+ run(): Promise<void>;
84
+ /** Handle `--list`: fetch the catalog and render table or JSON envelope. */
85
+ private runList;
86
+ }
87
+
88
+ export { REQUEST_TIMEOUT_MS, SCENARIO_OPTIONS, type ScenarioEntry, type TriggerRequestBody, type TriggerResponse, DevTrigger as default, getScenarios, postTrigger, renderScenarioTable, summarizeSampleShape };