@rebasepro/cli 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +2 -1
  2. package/bin/rebase.js +143 -4
  3. package/dist/{agent-setup-B5AKHOgt.js → agent-setup-D9mHRZ5R.js} +125 -3
  4. package/dist/agent-setup-D9mHRZ5R.js.map +1 -0
  5. package/dist/{api-keys-Tm7zvAPe.js → api-keys-CyJ26KEJ.js} +41 -10
  6. package/dist/api-keys-CyJ26KEJ.js.map +1 -0
  7. package/dist/{apps-BsU_ILbP.js → apps-CnLDV5b6.js} +6 -7
  8. package/dist/{apps-BsU_ILbP.js.map → apps-CnLDV5b6.js.map} +1 -1
  9. package/dist/{atlas-dev-database-OWtqnG7W.js → atlas-dev-database-BODqdCWt.js} +4 -4
  10. package/dist/{atlas-dev-database-OWtqnG7W.js.map → atlas-dev-database-BODqdCWt.js.map} +1 -1
  11. package/dist/{auth-Vl2hFKXP.js → auth-dII2jCO1.js} +10 -8
  12. package/dist/auth-dII2jCO1.js.map +1 -0
  13. package/dist/{branch-pointer-9DnwkFJJ.js → branch-pointer-BQUF6lYa.js} +1 -1
  14. package/dist/{build-BBgT8EJZ.js → build-Cza7ZQrp.js} +6 -7
  15. package/dist/{build-BBgT8EJZ.js.map → build-Cza7ZQrp.js.map} +1 -1
  16. package/dist/{bundle-DtCCCr4w.js → bundle-C6yEtMJ-.js} +53 -5
  17. package/dist/bundle-C6yEtMJ-.js.map +1 -0
  18. package/dist/bundle.d.ts +14 -0
  19. package/dist/{cloud-BpH0_bmV.js → cloud-BdnmqQaZ.js} +8528 -4342
  20. package/dist/cloud-BdnmqQaZ.js.map +1 -0
  21. package/dist/collections-dir-DlvDhime.js +69 -0
  22. package/dist/collections-dir-DlvDhime.js.map +1 -0
  23. package/dist/commands/agent-setup.d.ts +58 -0
  24. package/dist/commands/api-keys.d.ts +13 -0
  25. package/dist/commands/cloud/activity.d.ts +25 -0
  26. package/dist/commands/cloud/app-key.d.ts +18 -0
  27. package/dist/commands/cloud/auth.d.ts +1 -0
  28. package/dist/commands/cloud/billing-invoices.d.ts +39 -0
  29. package/dist/commands/cloud/claim.d.ts +7 -0
  30. package/dist/commands/cloud/context.d.ts +115 -6
  31. package/dist/commands/cloud/deploy-summary.d.ts +162 -0
  32. package/dist/commands/cloud/deploy.d.ts +64 -0
  33. package/dist/commands/cloud/env.d.ts +24 -0
  34. package/dist/commands/cloud/index.d.ts +13 -1
  35. package/dist/commands/cloud/init.d.ts +143 -0
  36. package/dist/commands/cloud/login-flow.d.ts +51 -0
  37. package/dist/commands/cloud/notifications.d.ts +28 -0
  38. package/dist/commands/cloud/orgs.d.ts +13 -0
  39. package/dist/commands/cloud/previews.d.ts +123 -0
  40. package/dist/commands/cloud/resources.d.ts +68 -14
  41. package/dist/commands/cloud/runtime-logs.d.ts +176 -0
  42. package/dist/commands/cloud/service-key.d.ts +15 -0
  43. package/dist/commands/cloud/token-capabilities.d.ts +61 -8
  44. package/dist/commands/cloud/tokens.d.ts +18 -3
  45. package/dist/commands/cloud/usage.d.ts +63 -0
  46. package/dist/commands/dev.d.ts +22 -0
  47. package/dist/commands/doctor.d.ts +1 -1
  48. package/dist/commands/generate-view.d.ts +94 -0
  49. package/dist/commands/generate_sdk.d.ts +19 -2
  50. package/dist/commands/init.d.ts +20 -0
  51. package/dist/commands/skills.d.ts +4 -3
  52. package/dist/commands/start.d.ts +10 -0
  53. package/dist/commands/storage.d.ts +4 -0
  54. package/dist/commands/upgrade.d.ts +12 -0
  55. package/dist/commands/verify.d.ts +96 -0
  56. package/dist/{consent-ClRHmcxf.js → consent-BD7F29T3.js} +2 -2
  57. package/dist/{consent-ClRHmcxf.js.map → consent-BD7F29T3.js.map} +1 -1
  58. package/dist/{console-CEGm0XNc.js → console-BXB6-8GT.js} +12 -6
  59. package/dist/console-BXB6-8GT.js.map +1 -0
  60. package/dist/{context-CyflUrKg.js → context-GUxkZm-S.js} +482 -26
  61. package/dist/context-GUxkZm-S.js.map +1 -0
  62. package/dist/{daemon-CknkDjd6.js → daemon-Cp7oNXth.js} +160 -5
  63. package/dist/daemon-Cp7oNXth.js.map +1 -0
  64. package/dist/{daemon-entry-CbLRrgzy.js → daemon-entry-zOC1revO.js} +6 -3
  65. package/dist/daemon-entry-zOC1revO.js.map +1 -0
  66. package/dist/{db-CRNpA5mH.js → db-KzlqddGD.js} +101 -54
  67. package/dist/db-KzlqddGD.js.map +1 -0
  68. package/dist/{derive-CsoFGAnK.js → derive-ae9UOoOB.js} +2 -2
  69. package/dist/{derive-CsoFGAnK.js.map → derive-ae9UOoOB.js.map} +1 -1
  70. package/dist/{dev-C0pbMmhS.js → dev-DTqVMgB3.js} +231 -44
  71. package/dist/dev-DTqVMgB3.js.map +1 -0
  72. package/dist/dev-db/daemon.d.ts +41 -0
  73. package/dist/dev-db/pull.d.ts +33 -5
  74. package/dist/dev-db/state.d.ts +9 -0
  75. package/dist/{dev-preflight-unHpiYO1.js → dev-preflight-Cxv3TiD1.js} +73 -27
  76. package/dist/dev-preflight-Cxv3TiD1.js.map +1 -0
  77. package/dist/{dev-server-B1UYifZJ.js → dev-server-OJsUGZEB.js} +14 -3
  78. package/dist/dev-server-OJsUGZEB.js.map +1 -0
  79. package/dist/{doctor-CATRdWm6.js → doctor-BRWOlqeG.js} +14 -12
  80. package/dist/doctor-BRWOlqeG.js.map +1 -0
  81. package/dist/{eject-DuasTr-F.js → eject-DABcDnPN.js} +3 -4
  82. package/dist/{eject-DuasTr-F.js.map → eject-DABcDnPN.js.map} +1 -1
  83. package/dist/{fold-static-hDHtDfTq.js → fold-static-BnAK-anQ.js} +3 -3
  84. package/dist/{fold-static-hDHtDfTq.js.map → fold-static-BnAK-anQ.js.map} +1 -1
  85. package/dist/generate-view-IFwCADvb.js +662 -0
  86. package/dist/generate-view-IFwCADvb.js.map +1 -0
  87. package/dist/{generate_sdk-Cvg5jjJF.js → generate_sdk-BvcWvoUh.js} +49 -13
  88. package/dist/generate_sdk-BvcWvoUh.js.map +1 -0
  89. package/dist/index.es.js +58 -40
  90. package/dist/index.es.js.map +1 -1
  91. package/dist/{init-BWAwHPtz.js → init-CaU6GhwI.js} +50 -62
  92. package/dist/init-CaU6GhwI.js.map +1 -0
  93. package/dist/invoked-command-CG_rIa_o.js +55 -0
  94. package/dist/invoked-command-CG_rIa_o.js.map +1 -0
  95. package/dist/last-dev-database-DpW5dfoX.js +2 -0
  96. package/dist/{manifest-DRp0UVTj.js → manifest-BtzDy7KQ.js} +36 -3
  97. package/dist/{manifest-DRp0UVTj.js.map → manifest-BtzDy7KQ.js.map} +1 -1
  98. package/dist/{normalize-imports-BoBVh5tU.js → normalize-imports-D0zXlM6g.js} +3 -3
  99. package/dist/{normalize-imports-BoBVh5tU.js.map → normalize-imports-D0zXlM6g.js.map} +1 -1
  100. package/dist/{path-flags-BFgG9V6y.js → path-flags-DNvtEi2-.js} +5 -4
  101. package/dist/path-flags-DNvtEi2-.js.map +1 -0
  102. package/dist/{prepare-DdnU70z8.js → prepare-CTw9NEkh.js} +5 -4
  103. package/dist/{prepare-DdnU70z8.js.map → prepare-CTw9NEkh.js.map} +1 -1
  104. package/dist/project-CasK56vN.js +701 -0
  105. package/dist/project-CasK56vN.js.map +1 -0
  106. package/dist/{pull-C3PXduzl.js → pull-DDMpsZ5r.js} +131 -24
  107. package/dist/pull-DDMpsZ5r.js.map +1 -0
  108. package/dist/refusal-ReeeGP8a.js +26 -0
  109. package/dist/refusal-ReeeGP8a.js.map +1 -0
  110. package/dist/resolve-D647F1LA.js +2 -0
  111. package/dist/{resources-BdNRDEAy.js → resources-CPSC5Kcu.js} +4 -5
  112. package/dist/{resources-BdNRDEAy.js.map → resources-CPSC5Kcu.js.map} +1 -1
  113. package/dist/{schema-B0-p6T1O.js → schema-BSXwZSiI.js} +9 -8
  114. package/dist/schema-BSXwZSiI.js.map +1 -0
  115. package/dist/{skills-DfnTeyRz.js → skills-BU2flMOy.js} +52 -8
  116. package/dist/skills-BU2flMOy.js.map +1 -0
  117. package/dist/{start-BmEZkS0n.js → start-mVb6upwY.js} +20 -7
  118. package/dist/start-mVb6upwY.js.map +1 -0
  119. package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
  120. package/dist/state-CYdBvnwA.js.map +1 -0
  121. package/dist/static-syntax-check.d.ts +64 -0
  122. package/dist/{status-BJ26yZOo.js → status-BREvtzJm.js} +8 -9
  123. package/dist/{status-BJ26yZOo.js.map → status-BREvtzJm.js.map} +1 -1
  124. package/dist/{storage-DKPUBTWG.js → storage-DtOXGflp.js} +159 -16
  125. package/dist/storage-DtOXGflp.js.map +1 -0
  126. package/dist/{telemetry-EMFRVRQi.js → telemetry-CgwJPVMO.js} +2 -2
  127. package/dist/{telemetry-EMFRVRQi.js.map → telemetry-CgwJPVMO.js.map} +1 -1
  128. package/dist/{telemetry-m9czTNGR.js → telemetry-DwK5RQJl.js} +4 -4
  129. package/dist/{telemetry-m9czTNGR.js.map → telemetry-DwK5RQJl.js.map} +1 -1
  130. package/dist/{upgrade-DjihWuZL.js → upgrade-B7sVwL8S.js} +26 -4
  131. package/dist/{upgrade-DjihWuZL.js.map → upgrade-B7sVwL8S.js.map} +1 -1
  132. package/dist/utils/args.d.ts +32 -3
  133. package/dist/utils/boot-retry.d.ts +41 -0
  134. package/dist/utils/collections-dir.d.ts +14 -0
  135. package/dist/utils/dev-preflight.d.ts +34 -1
  136. package/dist/utils/dev-server.d.ts +11 -0
  137. package/dist/utils/path-flags.d.ts +2 -1
  138. package/dist/utils/project.d.ts +5 -1
  139. package/dist/utils/tool-stdio.d.ts +1 -1
  140. package/dist/utils/unknown-command.d.ts +4 -0
  141. package/dist/verify-D7UsV_qe.js +730 -0
  142. package/dist/verify-D7UsV_qe.js.map +1 -0
  143. package/package.json +9 -7
  144. package/templates/eject/backend/src/index.ts +9 -1
  145. package/templates/generated-schemas/ecommerce.ts +3 -0
  146. package/templates/overlays/baas/ai-instructions.md +13 -0
  147. package/templates/resource-graphs/blank.json +25 -0
  148. package/templates/resource-graphs/blog.json +28 -0
  149. package/templates/resource-graphs/ecommerce.json +28 -0
  150. package/templates/resource-graphs/headless.json +22 -0
  151. package/templates/template/.claude/hooks/post-edit-collection.sh +99 -0
  152. package/templates/template/.claude/settings.json +17 -0
  153. package/templates/template/.env.example +15 -5
  154. package/templates/template/ai-instructions.md +21 -1
  155. package/templates/template/config/collections/presets/ecommerce/index.ts +23 -0
  156. package/templates/template/docker-compose.yml +4 -1
  157. package/templates/template/frontend/package.json +1 -0
  158. package/templates/template/frontend/src/App.tsx +10 -9
  159. package/templates/views/AppView.tsx.tmpl +138 -0
  160. package/templates/views/EntityView.tsx.tmpl +64 -0
  161. package/templates/views/index.ts.tmpl +8 -0
  162. package/dist/agent-setup-B5AKHOgt.js.map +0 -1
  163. package/dist/api-keys-Tm7zvAPe.js.map +0 -1
  164. package/dist/args-BX0YD14a.js +0 -266
  165. package/dist/args-BX0YD14a.js.map +0 -1
  166. package/dist/auth-Vl2hFKXP.js.map +0 -1
  167. package/dist/bundle-DtCCCr4w.js.map +0 -1
  168. package/dist/cloud-BpH0_bmV.js.map +0 -1
  169. package/dist/console-CEGm0XNc.js.map +0 -1
  170. package/dist/context-CyflUrKg.js.map +0 -1
  171. package/dist/daemon-CknkDjd6.js.map +0 -1
  172. package/dist/daemon-entry-CbLRrgzy.js.map +0 -1
  173. package/dist/db-CRNpA5mH.js.map +0 -1
  174. package/dist/dev-C0pbMmhS.js.map +0 -1
  175. package/dist/dev-preflight-unHpiYO1.js.map +0 -1
  176. package/dist/dev-server-B1UYifZJ.js.map +0 -1
  177. package/dist/doctor-CATRdWm6.js.map +0 -1
  178. package/dist/generate_sdk-Cvg5jjJF.js.map +0 -1
  179. package/dist/init-BWAwHPtz.js.map +0 -1
  180. package/dist/last-dev-database-c_dKz1S3.js +0 -2
  181. package/dist/path-flags-BFgG9V6y.js.map +0 -1
  182. package/dist/project-Wp2zoyeN.js +0 -349
  183. package/dist/project-Wp2zoyeN.js.map +0 -1
  184. package/dist/pull-C3PXduzl.js.map +0 -1
  185. package/dist/resolve-CNQjsatH.js +0 -2
  186. package/dist/schema-B0-p6T1O.js.map +0 -1
  187. package/dist/skills-DfnTeyRz.js.map +0 -1
  188. package/dist/start-BmEZkS0n.js.map +0 -1
  189. package/dist/state-CCB-FAQ9.js.map +0 -1
  190. package/dist/storage-DKPUBTWG.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-GUxkZm-S.js","names":[],"sources":["../src/commands/cloud/errors.ts","../src/commands/cloud/token-capabilities.ts","../src/commands/cloud/context.ts"],"sourcesContent":["/**\n * Turning a control-plane failure into something the caller can act on.\n *\n * The control plane talks to Kubernetes, and when Kubernetes refuses it, the\n * refusal travels back verbatim: a whole `Status` object, the request headers,\n * an `audit-id`, an `x-kubernetes-pf-flowschema-uid`. That reached the user's\n * terminal unedited. Two things are wrong with it beyond the noise.\n *\n * The first is that the one sentence that matters is buried in the middle of a\n * JSON blob, so the remedy — if there is one — is the hardest part to find.\n *\n * The second is worse, and is the reason this file exists rather than a\n * `slice(0, 200)`. A `403` naming a `system:serviceaccount:` is a statement\n * about the PLATFORM's own credentials: some role the control plane runs as is\n * missing a grant. Nothing in the user's project can change that — not the\n * collections, not `rebase.json`, not the deploy flags — and the error, printed\n * raw in the middle of `rebase cloud deploy`, reads exactly like a project\n * fault. Someone acting on that reading deletes working code looking for the\n * cause. (That is not hypothetical: three cron jobs were removed from a project\n * to test whether they caused a `cronjobs.batch` 403. They did not.)\n *\n * So this classifies before it summarises, and says whose problem it is.\n */\n\nimport { parseEnvBoolean } from \"@rebasepro/types\";\n\nimport { switchOn } from \"../../utils/args\";\n\n/** What a Kubernetes API refusal carries, once it is found. */\nexport interface KubernetesStatus {\n message?: string;\n reason?: string;\n code?: number;\n details?: { group?: string; kind?: string; name?: string };\n}\n\nexport interface ErrorSummary {\n /** One line, safe to print. Never the raw body. */\n message: string;\n /** A remedy, when one exists — or the reason there is none. */\n hint?: string;\n /** A stable code for `--json` callers to branch on. */\n code: string;\n /**\n * Whether the failure is the platform's rather than the project's.\n *\n * The whole point of the summary: a caller that retries or mutates the\n * project on a platform-side refusal makes things worse, and an agent\n * cannot tell from the prose.\n */\n platform: boolean;\n /** The untouched original, printed only under `--debug`. */\n raw: string;\n}\n\n/**\n * The service accounts a Kubernetes 403 can name, and what each means.\n *\n * `system:serviceaccount:` is the platform's own identity — the control plane's\n * pod, or a tenant's operator. A project cannot grant it anything, because the\n * grant lives in a cluster the project does not own. `system:anonymous` is the\n * same fact with the credential missing entirely.\n */\nconst PLATFORM_PRINCIPAL_RE = /system:(?:serviceaccount:|anonymous|node:)/;\n\n/**\n * A Kubernetes `Status` object embedded anywhere in a message.\n *\n * Scanned for rather than parsed off the front: the control plane wraps it\n * (\"Failed to create the tenant namespace: …\"), the client library appends\n * headers after it, and both halves are worth keeping out of the summary.\n * Balanced-brace scanning rather than a regex, because `details.causes` nests\n * and a lazy `\\{.*?\\}` truncates the object at the first inner brace — which\n * parses to nothing and silently falls through to the raw message.\n */\nexport function extractKubernetesStatus(text: string): KubernetesStatus | undefined {\n for (let start = text.indexOf(\"{\"); start !== -1; start = text.indexOf(\"{\", start + 1)) {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth !== 0) continue;\n const candidate = text.slice(start, i + 1);\n let parsed: unknown;\n try {\n parsed = JSON.parse(candidate);\n } catch {\n break; // Not JSON; try the next opening brace.\n }\n const obj = parsed as Record<string, unknown> & KubernetesStatus;\n // `kind: \"Status\"` is the marker the API server always sets on a\n // refusal. Requiring it means an ordinary JSON body in an error\n // message — a validation report, say — is left alone.\n if (obj?.kind === \"Status\" || (obj?.status === \"Failure\" && typeof obj.code === \"number\")) {\n return {\n message: typeof obj.message === \"string\" ? obj.message : undefined,\n reason: typeof obj.reason === \"string\" ? obj.reason : undefined,\n code: typeof obj.code === \"number\" ? obj.code : undefined,\n details: (obj.details as KubernetesStatus[\"details\"]) ?? undefined\n };\n }\n break;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Strip the transport noise a Kubernetes client appends to its own message.\n *\n * Only ever applied to the fallback path — when no `Status` was found, the\n * message is all there is, and cutting it at the first header keeps the\n * sentence while dropping the `audit-id` and the flowschema uid nobody outside\n * the cluster can use.\n */\nfunction trimTransportNoise(message: string): string {\n const cut = message.search(\n /\\s*(?:headers:|HTTP request failed|audit-id|x-kubernetes-pf-|\\{\"kind\":\"Status\")/i\n );\n const trimmed = (cut > 0 ? message.slice(0, cut) : message).trim();\n return trimmed.replace(/[\\s:,-]+$/, \"\");\n}\n\n/** The longest a summary may be before it stops being one. */\nconst MAX_SUMMARY = 300;\n\n/**\n * One actionable line (plus a hint) from whatever the control plane returned.\n *\n * `status` is the HTTP status of the control-plane call itself, which is a\n * different number from the `code` inside an embedded Kubernetes `Status` — the\n * control plane routinely answers 500 while the cluster answered 403, and it is\n * the inner one that says what happened.\n */\nexport function summarizeError(error: unknown, context: string): ErrorSummary {\n const err = error as { status?: number; message?: string; code?: string };\n const raw = err?.message ?? String(error);\n const k8s = extractKubernetesStatus(raw);\n\n if (k8s) {\n const inner = k8s.message?.trim() ?? \"\";\n const forbidden = k8s.code === 403 || k8s.reason === \"Forbidden\";\n const platform = forbidden && PLATFORM_PRINCIPAL_RE.test(inner);\n\n if (platform) {\n return {\n // Named as the platform's before the sentence that follows, so\n // the first thing read is whose problem it is.\n message: `${context}: the platform's own cluster credentials were refused by Kubernetes.`,\n hint:\n `${truncate(inner)}\\n`\n + \" This is a platform-side permission, not something your project can grant. \"\n + \"Nothing in your code, collections or deploy flags will change it — \"\n + \"report it with the message above rather than retrying or changing the project.\",\n code: \"platform_permission_denied\",\n platform: true,\n raw\n };\n }\n\n return {\n message: `${context}: ${truncate(inner || k8s.reason || \"the cluster refused the request\")}`,\n hint: k8s.code ? `Kubernetes answered ${k8s.code}${k8s.reason ? ` (${k8s.reason})` : \"\"}.` : undefined,\n code: k8s.reason ? `k8s_${k8s.reason.toLowerCase()}` : \"k8s_error\",\n platform: false,\n raw\n };\n }\n\n // Not a Kubernetes refusal. Still worth trimming: the same transport dumps\n // its headers onto plenty of errors that carry no `Status` at all.\n const trimmed = trimTransportNoise(raw);\n return {\n message: `${context}${err?.status ? ` (${err.status})` : \"\"}: ${truncate(trimmed || raw)}`,\n code: err?.code ?? (err?.status ? `http_${err.status}` : \"request_failed\"),\n platform: false,\n raw\n };\n}\n\nfunction truncate(text: string): string {\n const flat = text.replace(/\\s+/g, \" \").trim();\n return flat.length > MAX_SUMMARY ? `${flat.slice(0, MAX_SUMMARY - 1)}…` : flat;\n}\n\n/**\n * Whether the caller asked for the untouched body.\n *\n * `--debug` is already what `bin/rebase.js` prints after every failure as the\n * thing to add, so the raw payload hangs off the flag people are told to reach\n * for rather than off one invented here.\n *\n * The one reader of `REBASE_DEBUG` in the commands, spelled as the bin spells\n * it. It used to be `=== \"1\"` here while the quote fallbacks in `resources.ts`\n * tested the raw string for truthiness, so `=true` hid the body and `=0`\n * printed the fallback.\n */\nexport function wantsRawError(argv: readonly string[] = process.argv): boolean {\n return switchOn(argv, \"--debug\") || parseEnvBoolean(process.env.REBASE_DEBUG) === true;\n}\n","/**\n * What a Rebase Cloud token may do, named the way a person asks for it.\n *\n * A token is a personal API key on the control plane: it acts as the account\n * that created it, and holds only the scopes it was given. Nobody should have\n * to know that `rebase cloud deploy` reads the `deployments` collection, uploads\n * through the `deploy` function and needs `project:deploy` on one project — so\n * `rebase cloud tokens create --can deploy` and the console's token screen both\n * ask for a capability, and this module turns capabilities into scopes.\n *\n * One map, read by the CLI and by the console. Each capability covers exactly\n * the control-plane calls its commands make, traced from the commands\n * themselves and held there by `token-capabilities.test.ts`, which runs them\n * against a stand-in control plane that refuses everything a token does not\n * hold:\n *\n * - `deploy` — `rebase cloud deploy` (bundle and source paths, uploads, the\n * build follow, `--preview`), `rebase cloud deployments` and\n * `rebase cloud previews`.\n * - `logs` — `rebase cloud logs` (build, `--follow`, `--runtime`,\n * `--preview`),\n * `rebase cloud metrics`, `rebase cloud usage` and\n * `rebase cloud cron` — whose `runtime-token` call is how a\n * token reads the app's own cron history.\n * - `env` — `rebase cloud env list|set|unset|reveal|pull`, and the\n * same with `--preview` (and `env reset --preview`).\n * - `database` — `rebase cloud db list|info|connect`.\n * - `backups` — `rebase cloud db backup list|create|status|download` and\n * `db pitr status`. Restoring takes `project:restore`, which no\n * capability grants: putting old data over a live database is\n * for a signed-in person.\n *\n * Three more capabilities act on one ORGANIZATION rather than a project, and a\n * token holds one only when it is asked for ({@link ORG_CAPABILITIES}):\n *\n * - `billing` — `rebase cloud billing`, `billing invoices|upcoming`.\n * - `audit` — `rebase cloud activity`.\n * - `notifications` — `rebase cloud notifications` on the organization's\n * projects (adding and removing one still takes the\n * owner or admin role).\n *\n * Each holds its `org:*` scope narrowed to the organization\n * (`org:billing:<organizationId>`).\n *\n * Every project-level scope is narrowed to the one project the token is for\n * (`project:deploy:<projectId>`). The data and function scopes cannot be: they\n * name a collection or a function, not a row, and the control plane's\n * row-level security keeps the token to what its owner can see.\n *\n * Deliberately free of imports, so the control plane's console can read the\n * same file.\n *\n * @module\n */\n\n/** A capability a token can be given. */\nexport type TokenCapability = \"deploy\" | \"logs\" | \"env\" | \"database\" | \"backups\";\n\n/** Every capability, in the order they are offered. */\nexport const TOKEN_CAPABILITY_NAMES: readonly TokenCapability[] = [\"deploy\", \"logs\", \"env\", \"database\", \"backups\"];\n\n/**\n * Scopes every token holds, whatever it was created for: resolving a project\n * by its slug, reading the deployment a command follows (a production one, or\n * a preview's), the organization a project belongs to, and the control plane's\n * public description of itself.\n */\nexport const ALWAYS_SCOPES: string[] = [\n \"data:read:projects\",\n \"data:read:deployments\",\n \"data:read:preview-deployments\",\n \"data:read:organizations\",\n \"functions:invoke:platform-config\"\n];\n\n/** Each capability: what it lets a token do, and the scopes that does on one project. */\nexport const TOKEN_CAPABILITIES: Record<TokenCapability, { label: string; scopes: (projectId: string) => string[] }> = {\n deploy: {\n label: \"Deploy the project and follow its builds\",\n scopes: (projectId) => [\n `project:read:${projectId}`,\n `project:deploy:${projectId}`,\n \"functions:invoke:deploy\",\n // `deploy --preview` triggers, and `cloud previews` lists and\n // deletes, through the `previews` function.\n \"functions:invoke:previews\"\n ]\n },\n logs: {\n label: \"Read build and runtime logs, cron history, live metrics and usage\",\n scopes: (projectId) => [\n `project:read:${projectId}`,\n `project:logs:${projectId}`,\n \"functions:invoke:runtime-logs\",\n \"functions:invoke:metrics\",\n \"functions:invoke:runtime-token\",\n // `logs --preview <name>` finds the preview through `previews`.\n \"functions:invoke:previews\",\n // `rebase cloud usage`: the route asks `project:read` per project.\n \"functions:invoke:usage\"\n ]\n },\n env: {\n label: \"Read and change environment variables, secrets included\",\n scopes: (projectId) => [\n `project:env:${projectId}`,\n \"functions:invoke:env-vars\",\n // `env … --preview`: previews' own overrides live in `previews`.\n \"functions:invoke:previews\"\n ]\n },\n database: {\n label: \"Connect to the database and read its password\",\n scopes: (projectId) => [\n `project:read:${projectId}`,\n `project:database:${projectId}`,\n \"functions:invoke:db-info\",\n \"data:read:databases\"\n ]\n },\n backups: {\n label: \"List, create and download backups (restoring needs a signed-in person)\",\n scopes: (projectId) => [\n `project:read:${projectId}`,\n `project:backups:${projectId}`,\n \"functions:invoke:backup\"\n ]\n }\n};\n\n/** Whether a word names a capability. */\nexport function isTokenCapability(value: string): value is TokenCapability {\n return (TOKEN_CAPABILITY_NAMES as readonly string[]).includes(value);\n}\n\n/**\n * The scopes a token for `capabilities` on one project holds: {@link ALWAYS_SCOPES}\n * first, then each capability's, each scope once.\n *\n * Throws on a project id a scope cannot carry — empty, or with whitespace — and\n * on an unknown capability, both of which are a caller's mistake rather than\n * something to mint a token from.\n */\nexport function scopesForCapabilities(projectId: string, capabilities: readonly TokenCapability[]): string[] {\n if (projectId.length === 0 || /\\s/.test(projectId)) {\n throw new Error(`\"${projectId}\" is not a project id a scope can be narrowed to.`);\n }\n const scopes = new Set<string>(ALWAYS_SCOPES);\n for (const capability of capabilities) {\n if (!isTokenCapability(capability)) {\n throw new Error(`\"${String(capability)}\" is not a token capability. Capabilities: ${TOKEN_CAPABILITY_NAMES.join(\", \")}.`);\n }\n for (const scope of TOKEN_CAPABILITIES[capability].scopes(projectId)) scopes.add(scope);\n }\n return [...scopes];\n}\n\n/**\n * Read a token's scopes back as capabilities: the projects its project scopes\n * name, and, for each, the capabilities it holds every scope of.\n *\n * For showing a token — `rebase cloud tokens list`, `rebase cloud whoami` — so\n * a list of fifteen scope strings reads as \"deploy, logs on <project>\".\n */\nexport function capabilitiesInScopes(scopes: readonly string[]): Array<{ projectId: string; capabilities: TokenCapability[] }> {\n const held = new Set(scopes);\n const projectIds: string[] = [];\n for (const scope of scopes) {\n const match = /^project:[a-z][a-z0-9-]*:(.+)$/.exec(scope);\n if (match && !projectIds.includes(match[1])) projectIds.push(match[1]);\n }\n return projectIds.map(projectId => ({\n projectId,\n capabilities: TOKEN_CAPABILITY_NAMES.filter(capability =>\n TOKEN_CAPABILITIES[capability].scopes(projectId).every(scope => held.has(scope)))\n }));\n}\n\n/**\n * Whether a token's scopes reach one project: a project scope narrowed to it,\n * or an unnarrowed one, which reaches every project.\n *\n * What decides, in a directory `rebase cloud init --claim` linked, whether\n * `REBASE_TOKEN` or the token stored for that project acts on it: a\n * `REBASE_TOKEN` made for other projects would be refused on this one.\n */\nexport function scopesReachProject(scopes: readonly string[], projectId: string): boolean {\n return scopes.some(scope => {\n const match = /^project:[a-z][a-z0-9-]*(?::(.+))?$/.exec(scope);\n return match !== null && (match[1] === undefined || match[1] === projectId);\n });\n}\n\n/* ─── organization capabilities ──────────────────────────────────── */\n\n/** A capability on one organization. A token holds one only when asked for. */\nexport type OrgCapability = \"billing\" | \"audit\" | \"notifications\";\n\n/** Every organization capability, in the order they are offered. */\nexport const ORG_CAPABILITY_NAMES: readonly OrgCapability[] = [\"billing\", \"audit\", \"notifications\"];\n\n/** Each organization capability: what it lets a token do, and the scopes that does on one organization. */\nexport const ORG_CAPABILITIES: Record<OrgCapability, { label: string; scopes: (organizationId: string) => string[] }> = {\n billing: {\n label: \"Read the organization's billing: card on file, invoices and the next invoice (owners and admins)\",\n scopes: (organizationId) => [\n `org:billing:${organizationId}`,\n \"functions:invoke:stripe-billing\",\n // `rebase cloud billing` shows the billing account row.\n \"data:read:billing-accounts\"\n ]\n },\n audit: {\n label: \"Read the organization's activity log (owners and admins)\",\n scopes: (organizationId) => [\n `org:audit:${organizationId}`,\n \"functions:invoke:audit\"\n ]\n },\n notifications: {\n label: \"Manage notifications: your own email choices, and its projects' webhooks\",\n scopes: (organizationId) => [\n `org:notifications:${organizationId}`,\n \"functions:invoke:notifications\"\n ]\n }\n};\n\n/** Whether a word names an organization capability. */\nexport function isOrgCapability(value: string): value is OrgCapability {\n return (ORG_CAPABILITY_NAMES as readonly string[]).includes(value);\n}\n\n/**\n * The scopes `capabilities` hold on one organization, each once — added to a\n * token's project scopes, so not {@link ALWAYS_SCOPES}.\n *\n * Throws on an organization id a scope cannot carry, and on an unknown\n * capability.\n */\nexport function scopesForOrgCapabilities(organizationId: string, capabilities: readonly OrgCapability[]): string[] {\n if (organizationId.length === 0 || /\\s/.test(organizationId)) {\n throw new Error(`\"${organizationId}\" is not an organization id a scope can be narrowed to.`);\n }\n const scopes = new Set<string>();\n for (const capability of capabilities) {\n if (!isOrgCapability(capability)) {\n throw new Error(`\"${String(capability)}\" is not an organization capability. Capabilities: ${ORG_CAPABILITY_NAMES.join(\", \")}.`);\n }\n for (const scope of ORG_CAPABILITIES[capability].scopes(organizationId)) scopes.add(scope);\n }\n return [...scopes];\n}\n\n/**\n * Read a token's `org:*` scopes back as organization capabilities: the\n * organizations they name, and, for each, the capabilities it holds every\n * scope of.\n */\nexport function orgCapabilitiesInScopes(scopes: readonly string[]): Array<{ organizationId: string; capabilities: OrgCapability[] }> {\n const held = new Set(scopes);\n const organizationIds: string[] = [];\n for (const scope of scopes) {\n const match = /^org:[a-z][a-z0-9-]*:(.+)$/.exec(scope);\n if (match && !organizationIds.includes(match[1])) organizationIds.push(match[1]);\n }\n return organizationIds.map(organizationId => ({\n organizationId,\n capabilities: ORG_CAPABILITY_NAMES.filter(capability =>\n ORG_CAPABILITIES[capability].scopes(organizationId).every(scope => held.has(scope)))\n }));\n}\n","/**\n * Shared foundation for the `rebase cloud` command family.\n *\n * Everything cloud subcommands need in common lives here:\n * - credential storage (~/.rebase/credentials.json, keyed per control-plane host)\n * - project link file (.rebase/cloud.json in the project dir)\n * - control-plane URL resolution\n * - an authenticated `@rebasepro/client` instance (createCloudClient / requireClient)\n * - small output helpers shared across subcommands\n *\n * The control plane is itself a Rebase app, so we reuse the same SDK the web\n * console uses (`@rebasepro/client`). Auth, token refresh, the data REST client\n * and function invocation all come from the SDK — the CLI only supplies a\n * file-backed AuthStorage so a login persists across invocations.\n */\nimport fs from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport { spawn } from \"child_process\";\nimport chalk from \"chalk\";\nimport arg from \"arg\";\nimport inquirer from \"inquirer\";\nimport { createMemoryStorage, createRebaseClient, type AuthStorage } from \"@rebasepro/client\";\nimport { findProjectRoot } from \"../../utils/project\";\nimport { parseCommandArgs } from \"../../utils/args\";\nimport { cliUserAgent } from \"../../utils/version\";\nimport { summarizeError, wantsRawError } from \"./errors\";\nimport { parseEnvBoolean } from \"@rebasepro/types\";\nimport { scopesReachProject } from \"./token-capabilities\";\n\n/* ═══════════════════════════════════════════════════════════════\n Constants & paths\n ═══════════════════════════════════════════════════════════════ */\n\n/** Default hosted control plane (the Rebase Cloud console origin). */\nconst DEFAULT_CLOUD_URL = \"https://app.rebase.pro\";\n\n/** The storage key the SDK's auth module reads/writes the session under. */\nconst SDK_SESSION_KEY = \"rebase_auth\";\n\n/** ~/.rebase/credentials.json — one file, many hosts. */\nfunction credentialsPath(): string {\n return path.join(os.homedir(), \".rebase\", \"credentials.json\");\n}\n\n/** Project-local link file: <project>/.rebase/cloud.json */\n/**\n * The billing account an organization row points at, whichever key it arrived under.\n *\n * REST serves every column under its own property name, so the relation comes\n * back as `billingAccountId`. Both readers in this CLI checked\n * `billing_account_id` and `billingAccount` — neither is what arrives — so\n * `rebase cloud billing` answered \"no billing account\" for every organization,\n * and the deploy pre-check never saw an internal plan. The other two spellings\n * are still accepted: older servers and fixtures send them.\n */\nexport function billingAccountIdOf(org: unknown): string | number | undefined {\n if (typeof org !== \"object\" || org === null) return undefined;\n for (const key of [\"billingAccountId\", \"billing_account_id\", \"billingAccount\"]) {\n const value = (org as Record<string, unknown>)[key];\n if (typeof value === \"string\" || typeof value === \"number\") return value;\n }\n return undefined;\n}\n\nexport function projectLinkPath(cwd: string = process.cwd()): string {\n const root = findProjectRoot(cwd) || cwd;\n return path.join(root, \".rebase\", \"cloud.json\");\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Credentials file model\n ═══════════════════════════════════════════════════════════════\n\n {\n \"current\": \"https://app.rebase.pro\",\n \"contexts\": {\n \"https://app.rebase.pro\": { \"auth\": \"<sdk session json>\", \"org\": \"42\" }\n }\n }\n*/\n\ninterface CloudContextEntry {\n /** Raw JSON blob the SDK auth module persists (the RebaseSession). */\n auth?: string;\n /** Active organization id for this host, if the user selected one. */\n org?: string;\n /**\n * Unclaimed projects `rebase cloud init --claim` provisioned on this host,\n * keyed by project id: each one's token and claim link. See\n * {@link EphemeralGrant}.\n */\n ephemeral?: Record<string, EphemeralGrant>;\n}\n\n/**\n * What an account-less provision hands back, kept until it stops mattering.\n *\n * Both values are bearer secrets — the token deploys to the project, the claim\n * link moves it into the organization of whoever opens it — so they live here, in\n * the private credentials file, and never in the repository: not in `.env`,\n * where `REBASE_API_TOKEN` beside `REBASE_BASE_URL` is what the MCP server and\n * the SDK send to the APP (which knows nothing of a control-plane token; `init`\n * writes a key minted on the app there instead), and not in\n * `.rebase/cloud.json`, which a cloned repository supplies.\n */\nexport interface EphemeralGrant {\n token: string;\n claimUrl: string;\n /** ISO time the project, and the token with it, expires unless claimed. */\n expiresAt: string;\n}\n\ninterface CredentialsFile {\n current?: string;\n contexts: Record<string, CloudContextEntry>;\n}\n\nfunction readCredentials(): CredentialsFile {\n try {\n const raw = fs.readFileSync(credentialsPath(), \"utf-8\");\n const parsed = JSON.parse(raw) as CredentialsFile;\n if (!parsed.contexts) parsed.contexts = {};\n return parsed;\n } catch {\n return { contexts: {} };\n }\n}\n\n/**\n * Replace the credentials file with `data`: written beside it, then renamed\n * over it. Every command reads the whole file and writes it back, and several\n * may run at once (MCP runs cloud tools concurrently). Written in place, a\n * reader landing between the truncate and the write saw a half file, read it\n * as empty, and wrote back one without the login and the grants; a disk-full\n * error left it empty. A rename is atomic: a reader sees the old file or the\n * new one.\n */\nfunction writeCredentials(data: CredentialsFile): void {\n const file = credentialsPath();\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;\n try {\n // Written with private perms — this file holds refresh tokens.\n fs.writeFileSync(temporary, JSON.stringify(data, null, 2), { mode: 0o600 });\n try {\n fs.chmodSync(temporary, 0o600);\n } catch {\n // best effort on platforms without chmod semantics\n }\n fs.renameSync(temporary, file);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n throw error;\n }\n}\n\n/** Keep an unclaimed project's token and claim link for this host. */\nexport function saveEphemeralGrant(url: string, projectId: string, grant: EphemeralGrant): void {\n const creds = readCredentials();\n const entry = creds.contexts[url] || {};\n entry.ephemeral = { ...(entry.ephemeral ?? {}), [projectId]: grant };\n creds.contexts[url] = entry;\n writeCredentials(creds);\n}\n\n/** Drop a grant — once its project is claimed, or its token is refused. */\nexport function forgetEphemeralGrant(url: string, projectId: string): void {\n const creds = readCredentials();\n const entry = creds.contexts[url];\n if (!entry?.ephemeral?.[projectId]) return;\n delete entry.ephemeral[projectId];\n writeCredentials(creds);\n}\n\n/** The grant for a project on a host, while it has not expired. */\nexport function ephemeralGrantFor(url: string, projectId: string, now: number = Date.now()): EphemeralGrant | undefined {\n const grant = readCredentials().contexts[url]?.ephemeral?.[projectId];\n if (!grant || !(Date.parse(grant.expiresAt) > now)) return undefined;\n return grant;\n}\n\n/**\n * The grant as it is stored, expired or not: what a message about a refused\n * token can still show (the claim link) after the token itself stops counting.\n */\nexport function storedEphemeralGrant(url: string, projectId: string): EphemeralGrant | undefined {\n return readCredentials().contexts[url]?.ephemeral?.[projectId];\n}\n\n/**\n * The grant for the project this directory is linked to, when the command\n * targets the host it was provisioned on.\n */\nexport function linkedEphemeralGrant(url: string): { projectId: string; grant: EphemeralGrant } | undefined {\n const link = readLink();\n if (!link?.projectId || link.mode === \"direct\" || normalizeUrl(link.url) !== url) return undefined;\n const grant = ephemeralGrantFor(url, link.projectId);\n return grant ? { projectId: link.projectId, grant } : undefined;\n}\n\n/**\n * The unexpired grant for the project a link names, on the host it names: what\n * `status`, `unlink` and `init` read the claim URL back from. Undefined for a\n * claimed project (its grant is gone), a direct link, or no link.\n */\nexport function grantForLink(link: ProjectLink | null | undefined): EphemeralGrant | undefined {\n if (!link?.projectId || link.mode === \"direct\") return undefined;\n return ephemeralGrantFor(normalizeUrl(link.url), link.projectId);\n}\n\n/**\n * The grant a link names as it is stored, expired or not, with the host it is\n * kept under: what `rebase cloud claim` reads, so it can say \"expired\" rather\n * than \"never had one\".\n */\nexport function storedGrantForLink(link: ProjectLink | null | undefined): { url: string; grant: EphemeralGrant } | undefined {\n if (!link?.projectId || link.mode === \"direct\") return undefined;\n const url = normalizeUrl(link.url);\n const grant = storedEphemeralGrant(url, link.projectId);\n return grant ? { url, grant } : undefined;\n}\n\n/** Whether a `.env` sets a variable to something: an empty `KEY=` or `KEY=\"\"` does not. */\nexport function dotenvSets(file: string, key: string): boolean {\n if (!fs.existsSync(file)) return false;\n const pattern = new RegExp(`^\\\\s*(export\\\\s+)?${key}\\\\s*=(.*)$`);\n return fs.readFileSync(file, \"utf-8\").split(\"\\n\").some((l) => {\n const value = pattern.exec(l)?.[2]?.trim();\n return value !== undefined && value !== \"\" && value !== \"\\\"\\\"\" && value !== \"''\";\n });\n}\n\n/** The variable `init --claim` writes the app's key to, and the MCP server and the SDK read. */\nconst APP_KEY_ENV = \"REBASE_API_TOKEN\";\n\n/** Whether this project's `.env` (at its root, else here) holds an app key. */\nexport function projectDotenvSetsAppKey(): boolean {\n return dotenvSets(path.join(findProjectRoot() ?? process.cwd(), \".env\"), APP_KEY_ENV);\n}\n\n/**\n * When the app key `init --claim` minted stops working, and what to do about\n * it. It is minted to expire with the claim window; a claim hands it over to\n * the person who claims (the control plane lifts its expiry), so an unclaimed\n * project's key stops at the window's end and a claimed one's does not. The\n * claimer replaces it — the agent holds a copy — with `rebase cloud app-key\n * rotate`. Every command that knows the date says so.\n */\nexport function appKeyExpiryNote(expiresAt: string): string {\n return `${APP_KEY_ENV} in .env stops working at ${new Date(expiresAt).toLocaleString()} unless the project is claimed first: `\n + \"claiming hands the key to the person who claims it, and it stops expiring. \"\n + `They can replace it with \\`rebase cloud app-key rotate\\`.`;\n}\n\n/** Host that a bare `rebase cloud` command should target, if any. */\nfunction currentContextUrl(): string | undefined {\n return readCredentials().current;\n}\n\n/** Persist the active organization id for a host. */\nexport function setContextOrg(url: string, org: string | undefined): void {\n const creds = readCredentials();\n const entry = creds.contexts[url] || {};\n if (org) entry.org = org;\n else delete entry.org;\n creds.contexts[url] = entry;\n writeCredentials(creds);\n}\n\nexport function getContextOrg(url: string): string | undefined {\n return readCredentials().contexts[url]?.org;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n File-backed AuthStorage (per host)\n ═══════════════════════════════════════════════════════════════ */\n\nfunction createFileAuthStorage(url: string): AuthStorage {\n return {\n getItem(key) {\n if (key !== SDK_SESSION_KEY) return null;\n return readCredentials().contexts[url]?.auth ?? null;\n },\n setItem(key, value) {\n if (key !== SDK_SESSION_KEY) return;\n const creds = readCredentials();\n const entry = creds.contexts[url] || {};\n entry.auth = value;\n creds.contexts[url] = entry;\n if (!creds.current) creds.current = url;\n writeCredentials(creds);\n },\n removeItem(key) {\n if (key !== SDK_SESSION_KEY) return;\n const creds = readCredentials();\n if (creds.contexts[url]) {\n delete creds.contexts[url].auth;\n delete creds.contexts[url].org;\n }\n writeCredentials(creds);\n }\n };\n}\n\n/** Mark a host as the active context (called on login). */\nexport function setCurrentContext(url: string): void {\n const creds = readCredentials();\n creds.current = url;\n if (!creds.contexts[url]) creds.contexts[url] = {};\n writeCredentials(creds);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n URL resolution\n ═══════════════════════════════════════════════════════════════\n\n Priority: --url flag > REBASE_CLOUD_URL env > CLOUD link's url\n > stored current context > default hosted URL.\n\n A **direct** link is deliberately absent from that list; see below.\n*/\n\n/**\n * Where the control-plane URL came from. `link` is the one a repository chose\n * rather than the user — see {@link isKnownControlPlane}.\n */\nexport type CloudUrlSource = \"flag\" | \"env\" | \"link\" | \"context\" | \"default\";\n\nexport function resolveCloudUrl(rawArgs: string[]): string {\n return resolveCloudTarget(rawArgs).url;\n}\n\n/** The control-plane URL, and which rung of the ladder above supplied it. */\nexport function resolveCloudTarget(rawArgs: string[]): { url: string; source: CloudUrlSource } {\n const parsed = arg({ \"--url\": String }, { argv: rawArgs.slice(2),\npermissive: true });\n if (parsed[\"--url\"]) return { url: normalizeUrl(parsed[\"--url\"]),\nsource: \"flag\" };\n if (process.env.REBASE_CLOUD_URL) return { url: normalizeUrl(process.env.REBASE_CLOUD_URL),\nsource: \"env\" };\n\n const link = readLink();\n /*\n * A direct link is a tenant, not a control plane.\n *\n * `rebase cloud link <url>` points this checkout at ONE running Rebase API\n * — \"no control plane, no login\", as its own help page puts it — so its URL\n * is the customer's own backend. Returning it here made it the control\n * plane for the entire `cloud` family: `whoami` reported \"Not logged in to\n * https://api.example.com\", and following that printed remedy POSTed the\n * user's *control-plane* email and password to the tenant host.\n *\n * Commands that genuinely need a control plane refuse instead — see\n * `refuseDirectLink`, which every one of them reaches through\n * `requireClient` (and `login`, which builds its client directly).\n */\n if (link?.url && link.mode !== \"direct\") return { url: normalizeUrl(link.url),\nsource: \"link\" };\n\n const current = currentContextUrl();\n if (current) return { url: normalizeUrl(current),\nsource: \"context\" };\n\n return { url: DEFAULT_CLOUD_URL,\nsource: \"default\" };\n}\n\n/**\n * Whether a control-plane host is one the user chose: the platform's own, or a\n * host already in their credentials file because they signed in to it.\n *\n * A link file is part of the repository, and a cloned repository is not the\n * user's to trust with their password. `login` asks before sending one to a\n * linked host that is neither.\n */\nexport function isKnownControlPlane(url: string): boolean {\n return url === DEFAULT_CLOUD_URL || Object.hasOwn(readCredentials().contexts, url);\n}\n\n/**\n * Refuse to run a control-plane command in a directory linked straight at a\n * backend.\n *\n * The alternative — quietly using the default control plane — would be worse\n * than the bug it replaces: the user asked for `https://api.example.com` and\n * would get an answer about `app.rebase.pro` with nothing saying so. Naming the\n * link, and both ways out of it, is the whole message.\n *\n * `--project` and `--url` are the ways out, so a line carrying either passes\n * through: both say, explicitly, which control-plane subject the caller means.\n */\nexport function refuseDirectLink(rawArgs: string[]): void {\n const link = readLink();\n if (link?.mode !== \"direct\") return;\n\n const parsed = arg(\n { \"--project\": String,\n\"-p\": \"--project\",\n\"--url\": String },\n { argv: rawArgs.slice(2),\npermissive: true }\n );\n if (parsed[\"--project\"] || parsed[\"--url\"]) return;\n\n fail(\n `This directory is linked directly to ${chalk.cyan(link.url)} — `\n + `${chalk.bold(\"rebase cloud unlink\")} first, or pass ${chalk.bold(\"--project\")}.`,\n \"A direct link is one backend, not a control plane: it has no projects, no organizations and no session.\",\n \"direct_link\"\n );\n}\n\nfunction normalizeUrl(url: string): string {\n let u = url.trim().replace(/\\/+$/, \"\");\n if (!/^https?:\\/\\//.test(u)) u = `https://${u}`;\n return u;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Rebase client factory + auth guard\n ═══════════════════════════════════════════════════════════════ */\n\nexport type CloudClient = ReturnType<typeof createRebaseClient>;\n\n/**\n * Build an SDK client bound to a control-plane host, backed by the on-disk\n * credential store. `autoRefresh` is disabled so we never leave a dangling\n * setTimeout that keeps the CLI process alive; token refresh is done on demand\n * by `requireClient`.\n */\nexport function createCloudClient(url: string): CloudClient {\n return createRebaseClient({\n baseUrl: url,\n // No cloud subcommand needs a socket. The one that looks like it might —\n // `logs -f` — polls `deployments.findById` on a timer (see\n // `streamBuildLogs`), so nothing here ever opens a channel or an\n // `observe()`.\n //\n // This used to say `websocketUrl: \"\"` and mean the same thing. It stopped\n // meaning it when the client grew a diagnostic for the case it produces:\n // realtime is on unless you switch it off, so \"on, with no usable URL\"\n // reads as a misconfiguration and warns. The warning is right — and it\n // was firing on every single `rebase cloud` invocation, telling the user\n // to fix the CLI's own client construction, in prose they had no way to\n // act on. `realtime: false` is the escape it names: it is the difference\n // between \"no socket, because I never asked for one\" and \"no socket, and\n // I do not know why\".\n realtime: false,\n // Every control-plane request says which CLI is calling. The control\n // plane's wire format moves ahead of what is published to npm, so it\n // has to be able to answer an old client with `CLI_TOO_OLD` and the\n // minimum version — and it cannot do that for a caller that never\n // identified itself. Until this line, none of them did.\n headers: { \"User-Agent\": cliUserAgent() },\n auth: {\n storage: createFileAuthStorage(url),\n persistSession: true,\n autoRefresh: false\n }\n });\n}\n\n/**\n * The environment variable that authenticates the cloud family with a token\n * instead of a `cloud login` session — for CI and agents, which have no\n * terminal to log in at and should not hold a person's password.\n */\nexport const TOKEN_ENV = \"REBASE_TOKEN\";\n\n/** `REBASE_TOKEN`, trimmed, or undefined when it is unset or blank. */\nexport function envToken(): string | undefined {\n const value = process.env[TOKEN_ENV]?.trim();\n return value ? value : undefined;\n}\n\n/** The token each token-authenticated client was built with — see {@link freshAccessToken}. */\nconst STATIC_TOKENS = new WeakMap<object, string>();\n\n/** The project each client built from an `init --claim` grant acts for — see {@link grantProjectOf}. */\nconst GRANT_CLIENTS = new WeakMap<object, string>();\n\n/**\n * The project whose `rebase cloud init --claim` token a client holds, or\n * undefined for a session or `REBASE_TOKEN`: what `whoami` reports, since\n * {@link requireClient} may pick the grant over `REBASE_TOKEN`.\n */\nexport function grantProjectOf(client: object): string | undefined {\n return GRANT_CLIENTS.get(client);\n}\n\n/**\n * A client that sends `token` as its bearer and nothing else.\n *\n * No session: the token is not refreshed, and the credentials file is neither\n * read nor written — a CI job's token must not become, or replace, the login\n * of whoever owns the machine. A 401 is answered by {@link explainRefusedToken}\n * rather than by the SDK's session refresh, because there is no session to\n * refresh and \"log in again\" is the wrong remedy for a token.\n */\nexport function createTokenClient(url: string, token: string, ephemeralProjectId?: string): CloudClient {\n const client = createRebaseClient({\n baseUrl: url,\n realtime: false,\n headers: { \"User-Agent\": cliUserAgent() },\n // Watches for the control plane saying a token's request is deprecated.\n fetch: deprecationAwareFetch,\n token,\n auth: {\n storage: createMemoryStorage(),\n persistSession: false,\n autoRefresh: false\n }\n });\n client.setOnUnauthorized(() => ephemeralProjectId\n ? explainRefusedGrant(url, ephemeralProjectId, token)\n : explainRefusedToken(url, token));\n STATIC_TOKENS.set(client, token);\n if (ephemeralProjectId) GRANT_CLIENTS.set(client, ephemeralProjectId);\n return client;\n}\n\n/** The deprecation notices already printed, once each per process. */\nconst TOLD_DEPRECATED = new Set<string>();\n\n/**\n * Say, once, that the control plane marked a token's request as deprecated.\n *\n * The control plane answers a request it still serves, but will not in a later\n * release, with `Deprecation: true` (RFC 9745), a `Link` with\n * `rel=\"deprecation\"` to the page that explains it, and an\n * `X-Rebase-Deprecation` sentence saying what to do — a token from before\n * `org:billing` reading billing is the first. CI logs are where that is seen,\n * so it goes to stderr as a warning, once per notice per process, and never\n * into the `--json` value on stdout.\n *\n * Exported so its test can hand it a response.\n */\nexport function noticeDeprecation(response: Response): void {\n if (response.headers?.get?.(\"Deprecation\") == null) return;\n const notice = response.headers.get(\"X-Rebase-Deprecation\")?.trim()\n || \"The control plane marked a request this command made as deprecated: it may stop working in a later release.\";\n if (TOLD_DEPRECATED.has(notice)) return;\n TOLD_DEPRECATED.add(notice);\n const link = /<([^>]+)>\\s*;\\s*rel=\"?deprecation\"?/i.exec(response.headers.get(\"Link\") ?? \"\")?.[1];\n warn(notice, link ? `See ${link}` : undefined);\n}\n\n/**\n * `fetch` for a token client: the global one — read at call time, so a test's\n * stand-in is used — with every answer shown to {@link noticeDeprecation}.\n */\nconst deprecationAwareFetch: typeof globalThis.fetch = async (input, init) => {\n const response = await globalThis.fetch(input, init);\n noticeDeprecation(response);\n return response;\n};\n\n/** What `/api/auth/scopes` said each token holds, asked once per process. */\nconst TOKEN_SCOPES = new Map<string, Promise<string[] | undefined>>();\n\n/** The projects already told that `REBASE_TOKEN` was set aside for their own token. */\nconst TOLD_GRANT_PREFERRED = new Set<string>();\n\n/**\n * The scopes the control plane says a token holds — one `/api/auth/scopes`\n * request per token and host for the life of the process — or undefined when\n * it could not say: refused, unreachable, or an answer without `held`.\n */\nfunction tokenScopes(url: string, token: string): Promise<string[] | undefined> {\n const key = `${url}\\n${token}`;\n let pending = TOKEN_SCOPES.get(key);\n if (!pending) {\n pending = (async () => {\n try {\n const res = await fetch(`${url}/api/auth/scopes`, {\n headers: { Authorization: `Bearer ${token}`, \"User-Agent\": cliUserAgent() }\n });\n if (!res.ok) return undefined;\n const body: unknown = await res.json();\n const held = typeof body === \"object\" && body !== null ? (body as { held?: unknown }).held : undefined;\n return Array.isArray(held) && held.every(s => typeof s === \"string\") ? held : undefined;\n } catch {\n return undefined;\n }\n })();\n TOKEN_SCOPES.set(key, pending);\n }\n return pending;\n}\n\n/** Forget what {@link tokenScopes} asked and who was told — each test is a new process. */\nexport function resetTokenScopesForTest(): void {\n TOKEN_SCOPES.clear();\n TOLD_GRANT_PREFERRED.clear();\n TOLD_DEPRECATED.clear();\n}\n\n/**\n * Whether a command line names a project other than the linked one with\n * `--project`: the grant acts only on its own project, so such a command\n * keeps `REBASE_TOKEN` whatever it holds.\n */\nfunction namesAnotherProject(rawArgs: string[], projectId: string): boolean {\n const parsed = arg({ \"--project\": String, \"-p\": \"--project\" }, { argv: rawArgs.slice(2), permissive: true });\n const ref = parsed[\"--project\"];\n if (!ref) return false;\n const link = readLink();\n return ref !== projectId && ref !== link?.slug;\n}\n\n/**\n * Whether the control plane still accepts a token: one request to\n * `/api/auth/scopes`, the route every token may read. `refused` only when it\n * answered 401 or 403; `unknown` when it could not be asked, or answered\n * something else — which proves nothing about the token.\n */\nexport async function probeToken(url: string, token: string): Promise<\"accepted\" | \"refused\" | \"unknown\"> {\n try {\n const probe = await fetch(`${url}/api/auth/scopes`, {\n headers: { Authorization: `Bearer ${token}`, \"User-Agent\": cliUserAgent() }\n });\n if (probe.ok) return \"accepted\";\n return probe.status === 401 || probe.status === 403 ? \"refused\" : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/**\n * Why the control plane answered a token-authenticated request with 401, and\n * what to do about it. Never returns.\n *\n * Two different causes reach the same status. Usually the token itself was\n * refused — it expired, was revoked, or belongs to another control plane — and\n * the remedy is a new token. But a few endpoints take only a person's session\n * (`/auth/me` among them), and those refuse a perfectly good token too. One\n * request to `/api/auth/scopes`, which accepts a token, tells the two apart, so\n * neither is answered with the other's remedy.\n */\nasync function explainRefusedToken(url: string, token: string): Promise<never> {\n if (await probeToken(url, token) === \"accepted\") {\n fail(\n `This command needs a signed-in session, and ${TOKEN_ENV} is an API token.`,\n `Unset ${TOKEN_ENV} and run ${chalk.bold(\"rebase cloud login\")} to run it.`,\n \"session_required\"\n );\n }\n fail(\n `${TOKEN_ENV} was rejected by ${url}: it has expired, been revoked, or was not issued by this control plane.`,\n `Create a new one with ${chalk.bold(\"rebase cloud tokens create\")} and put it in ${TOKEN_ENV}.`,\n \"token_rejected\"\n );\n}\n\n/** Two minutes of head-room before a token is treated as expired. */\nconst EXPIRY_BUFFER_MS = 120_000;\n\n/** The half of a client that holds its session — all {@link freshAccessToken} reads. */\nexport interface SessionHolder {\n auth: {\n getSession(): { accessToken: string; expiresAt: number } | null;\n refreshSession(): Promise<{ accessToken: string }>;\n };\n}\n\n/**\n * An access token with time left on it, refreshed first if it is close to\n * expiry. Rejects when there is no session, or it cannot be refreshed.\n *\n * For the requests the SDK does not make itself. Its own calls refresh and\n * retry on a 401; a raw `fetch` or a tunnel handshake presents whatever token\n * it was handed, and `createCloudClient` never refreshes in the background. A\n * token read once at the start of a command that runs for longer than it\n * lives — a deploy's build, a `db connect` left open — is refused by the time\n * it is used, so it is read here at the moment it is sent.\n *\n * A client built from `REBASE_TOKEN` has no session; its token is returned\n * as it is, since there is nothing to refresh it with.\n */\nexport async function freshAccessToken(client: SessionHolder): Promise<string> {\n const fixed = STATIC_TOKENS.get(client);\n if (fixed) return fixed;\n const session = client.auth.getSession();\n if (session?.accessToken && session.expiresAt > Date.now() + EXPIRY_BUFFER_MS) return session.accessToken;\n return (await client.auth.refreshSession()).accessToken;\n}\n\n/**\n * A request made with an unclaimed project's token was refused. Never returns.\n *\n * Two causes reach that 401, as for {@link explainRefusedToken}. The project was\n * claimed (which revokes the token) or expired, and the token is dead. Or the\n * command called a route that takes only a person's session — `whoami` and\n * `projects create` read `/auth/me` — which refuses a perfectly good token too.\n * The grant holds the project's only token and the only stored copy of its\n * claim link, so it is forgotten only once `/api/auth/scopes` refuses the token\n * as well; and even then the claim link is printed before it goes.\n */\nasync function explainRefusedGrant(url: string, projectId: string, token: string): Promise<never> {\n const verdict = await probeToken(url, token);\n if (verdict === \"accepted\") {\n fail(\n `This command needs a signed-in session, and this directory uses the token \\`rebase cloud init --claim\\` stored for project ${projectId}.`,\n // As narrowed on the control plane: an unclaimed project's token\n // reads no secret back — no variable's value, no database\n // password — and opens no tunnel or preview.\n \"That token still deploys, reads logs and metrics, sets environment variables and runs `db info`, \"\n + \"but reads no secret back (no variable's value, no database password) and cannot open `db connect` or a preview; \"\n + `${chalk.bold(\"rebase cloud whoami\")} shows it, the claim URL and when it expires. `\n + `Run this command as a person once the project is claimed: ${chalk.bold(\"rebase cloud login\")}.`,\n \"session_required\"\n );\n }\n if (verdict === \"unknown\") {\n fail(\n `The token \\`rebase cloud init --claim\\` stored for project ${projectId} was refused by ${url}, and the control plane could not say why.`,\n `The token and the claim URL are kept. Check your connection and run the command again; ${chalk.bold(\"rebase cloud whoami\")} shows the claim URL.`,\n \"token_rejected\"\n );\n }\n const grant = storedEphemeralGrant(url, projectId);\n forgetEphemeralGrant(url, projectId);\n // The person who just claimed it has often just signed in, too. With the\n // grant gone the next run uses that login, so \"sign in\" would send them to\n // do what they have already done: tell them to run the command again.\n const keyNote = grant && projectDotenvSetsAppKey() ? ` ${appKeyExpiryNote(grant.expiresAt)}` : \"\";\n if (readCredentials().contexts[url]?.auth) {\n fail(\n `The token \\`rebase cloud init --claim\\` stored for project ${projectId} was refused by ${url}: the project was claimed, or it expired.`,\n `It is forgotten, and you are signed in to ${url}: run the command again and it uses your login.${keyNote}`,\n \"token_rejected\"\n );\n }\n const claim = grant\n ? ` If nobody has claimed it yet, its claim URL was ${chalk.bold(grant.claimUrl)} (expires ${new Date(grant.expiresAt).toLocaleString()}).`\n + keyNote\n : \"\";\n fail(\n `The token \\`rebase cloud init --claim\\` stored for project ${projectId} was refused by ${url}.`,\n \"Once a project is claimed its agent token stops working: sign in with \"\n + `${chalk.bold(\"rebase cloud login\")} as the account that claimed it, or create a token there with `\n + `${chalk.bold(\"rebase cloud tokens create\")}.${claim}`,\n \"token_rejected\"\n );\n}\n\n/**\n * Return an authenticated client for the resolved host.\n *\n * With `REBASE_TOKEN` set, that token is the credential for every request and\n * the stored login is not consulted at all — except in a directory linked to a\n * project `init --claim` provisioned, when `REBASE_TOKEN` holds no scope on that\n * project (one `/api/auth/scopes` call): the project's own token acts there\n * instead, and the command says so once. Next, a directory linked to a\n * project `rebase cloud init --claim` provisioned uses the token stored for it\n * (see {@link EphemeralGrant}) until it expires: nobody can sign in as that\n * project's owner, so a login could not reach it anyway. Otherwise it is the\n * `cloud login` session — see {@link requireSessionClient}.\n */\nexport async function requireClient(rawArgs: string[]): Promise<{ client: CloudClient; url: string }> {\n const token = envToken();\n if (!token) {\n const linked = linkedEphemeralGrant(resolveCloudUrl(rawArgs));\n if (!linked) return requireSessionClient(rawArgs);\n refuseDirectLink(rawArgs);\n const url = resolveCloudUrl(rawArgs);\n return { client: createTokenClient(url, linked.grant.token, linked.projectId), url };\n }\n // Before anything else: everything downstream of here is a control-plane\n // call, and a direct-linked directory has no control plane to make it\n // against.\n refuseDirectLink(rawArgs);\n const url = resolveCloudUrl(rawArgs);\n // REBASE_TOKEN wins, as documented — unless it holds no scope on the\n // project `init --claim` linked here, which it would only be refused on:\n // a CI token made for other projects, set in the shell. Then the\n // project's own token acts, and the command says so once. When the\n // control plane cannot say what REBASE_TOKEN holds, it still wins.\n const displaced = linkedEphemeralGrant(url);\n if (displaced) {\n if (!namesAnotherProject(rawArgs, displaced.projectId)) {\n const held = await tokenScopes(url, token);\n if (held && !scopesReachProject(held, displaced.projectId)) {\n if (!TOLD_GRANT_PREFERRED.has(displaced.projectId)) {\n TOLD_GRANT_PREFERRED.add(displaced.projectId);\n warn(\n `${TOKEN_ENV} holds no scope on project ${displaced.projectId}, so the token \\`rebase cloud init --claim\\` stored for it is used instead.`,\n `Pass --project to act on another project with ${TOKEN_ENV}, or give ${TOKEN_ENV} a capability on this one.`\n );\n }\n return { client: createTokenClient(url, displaced.grant.token, displaced.projectId), url };\n }\n }\n warn(\n `${TOKEN_ENV} is set, so it is used instead of the token \\`rebase cloud init --claim\\` stored for project ${displaced.projectId}.`,\n `Unset ${TOKEN_ENV} to act on that project with its own token.`\n );\n }\n return { client: createTokenClient(url, token), url };\n}\n\n/**\n * Return a client holding the `cloud login` session for the resolved host,\n * refreshing the access token if it is close to expiry, whether or not\n * `REBASE_TOKEN` is set. Exits with a helpful message when there is no usable\n * session (never logged in, or the refresh token was revoked).\n *\n * For the commands a token cannot run — minting and revoking tokens: a key that\n * could mint keys could mint its own successor, so the control plane refuses\n * every key on those routes. Inviting people is the other: it hands out access\n * to every project in an organization, which no token capability grants.\n * `action` names what the refusal says a token cannot do.\n */\nexport async function requireSessionClient(\n rawArgs: string[],\n action = \"manage tokens\"\n): Promise<{ client: CloudClient; url: string }> {\n // Before anything else: everything downstream of here is a control-plane\n // call, and a direct-linked directory has no control plane to make it\n // against.\n refuseDirectLink(rawArgs);\n const url = resolveCloudUrl(rawArgs);\n const client = createCloudClient(url);\n const session = client.auth.getSession();\n\n if (!session || !session.accessToken) {\n if (envToken()) {\n fail(\n `This command needs a signed-in session for ${chalk.cyan(url)}, and ${TOKEN_ENV} is an API token.`,\n `A token cannot ${action}. Run ${chalk.bold(\"rebase cloud login\")} first.`,\n \"session_required\"\n );\n }\n fail(\n `Not logged in to ${chalk.cyan(url)}.`,\n `Run ${chalk.bold(\"rebase cloud login\")} first.`,\n \"not_logged_in\"\n );\n }\n\n if (session.expiresAt <= Date.now() + EXPIRY_BUFFER_MS) {\n try {\n await client.auth.refreshSession();\n } catch {\n fail(\n `Your session for ${chalk.cyan(url)} has expired.`,\n `Run ${chalk.bold(\"rebase cloud login\")} to sign in again.`,\n // Distinct from `not_logged_in`: a caller retrying on a stale\n // session should re-authenticate, not conclude it was never set\n // up. Same remedy, different diagnosis.\n \"session_expired\"\n );\n }\n }\n\n return { client,\nurl };\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Platform configuration\n ═══════════════════════════════════════════════════════════════ */\n\n/** One place a deploy can actually land, as the control plane describes it. */\nexport interface DeployTarget {\n clusterId?: string | null;\n provider: string;\n region?: string;\n label?: string;\n baseDomain?: string;\n}\n\nexport interface PlatformConfig {\n tenantBaseDomain?: string;\n deployTargets?: DeployTarget[];\n}\n\n/**\n * The control plane's public, non-secret self-description (`platform-config`).\n *\n * None of it is knowable from the CLI side: it is per-deployment configuration —\n * production serves tenants at `rebase.website` on GKE, a dev control plane at\n * `localhost` on Docker. Guessing produced two separate lies: a congratulation\n * URL that resolved nowhere near the app, and a `provider` the project does not\n * run on (see `createProject`).\n *\n * Cached per host for the process: it is fixed for a control plane's lifetime,\n * and `projects list` formats one host per row off a single fetch.\n *\n * @returns the config, or `undefined` if the control plane doesn't serve\n * `platform-config` (an older deployment) or the request failed. A failure is\n * cached too — a short-lived CLI should not retry once per row. Note the\n * distinction callers rely on: `undefined` means \"this control plane cannot\n * tell us\", whereas `deployTargets: []` is a control plane stating that it has\n * no infrastructure configured.\n */\nconst platformConfigCache = new Map<string, Promise<PlatformConfig | undefined>>();\n\nexport function fetchPlatformConfig(client: CloudClient, url: string): Promise<PlatformConfig | undefined> {\n let pending = platformConfigCache.get(url);\n if (!pending) {\n pending = client.functions\n .invoke<PlatformConfig>(\"platform-config\", undefined, { method: \"GET\" })\n .then((cfg) => cfg ?? undefined)\n .catch(() => undefined);\n platformConfigCache.set(url, pending);\n }\n return pending;\n}\n\n/**\n * The base domain tenant projects are served at, derived from the same\n * TENANT_BASE_DOMAIN the ingress and the console read (see\n * saas/backend/src/utils/tenant-domain.ts).\n */\nexport function fetchTenantBaseDomain(client: CloudClient, url: string): Promise<string | undefined> {\n return fetchPlatformConfig(client, url).then((cfg) => cfg?.tenantBaseDomain?.trim() || undefined);\n}\n\n/**\n * The infrastructure a deploy for this control plane would ACTUALLY use, in the\n * resolver's own preference order (saas/backend/src/k8s/resolve.ts).\n *\n * @returns the targets, or `undefined` when the control plane cannot say.\n */\nexport function fetchDeployTargets(client: CloudClient, url: string): Promise<DeployTarget[] | undefined> {\n return fetchPlatformConfig(client, url).then((cfg) =>\n Array.isArray(cfg?.deployTargets) ? cfg.deployTargets : undefined\n );\n}\n\n/**\n * Public host for a project — `<subdomain>.<base>`, or the bare subdomain when\n * the base domain is unknown.\n *\n * It deliberately never falls back to a guessed domain. The user copies this\n * string into a browser, so a plausible-but-wrong hostname is worse than an\n * obviously incomplete one: `acme.rebase.pro` looks reachable and isn't, while\n * `acme` reads as \"the subdomain is acme\" and prompts no wasted debugging.\n */\nexport function formatTenantHost(\n subdomain: string | undefined,\n baseDomain: string | undefined\n): string | undefined {\n if (!subdomain) return undefined;\n return baseDomain ? `${subdomain}.${baseDomain}` : subdomain;\n}\n\n/** The fields of a project row this module needs to render a host. */\nexport interface HostableProject {\n subdomain?: string;\n /** Resolved server-side; absent on control planes older than the host hook. */\n host?: string;\n}\n\n/**\n * The host to display for a project.\n *\n * Prefers `host` off the record: the control plane resolves it through the same\n * `tenantHost()` the ingress uses, so it accounts for the project's *cluster*\n * base domain. The CLI cannot compute that itself — `clusters` is admin-only\n * under RLS, so a normal user's token cannot read `baseDomain`, and a project on\n * a second cluster is served somewhere the platform default does not name.\n *\n * `baseDomain` (from `platform-config`) remains the fallback for a control plane\n * that predates the hook — right for the single-cluster case, which is every\n * project today.\n */\nexport function projectHost(\n project: HostableProject,\n baseDomain: string | undefined\n): string | undefined {\n return project.host || formatTenantHost(project.subdomain, baseDomain);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Project link file (.rebase/cloud.json)\n ═══════════════════════════════════════════════════════════════ */\n\nexport interface ProjectLink {\n url: string;\n projectId: string;\n /** The project's subdomain — the slug users see in console URLs and type into --project. */\n slug?: string;\n projectName?: string;\n orgId?: string;\n /**\n * Base URL of the project's own API.\n *\n * For a cloud project this is a convenience derived from the subdomain. For\n * a **self-hosted** project it is the entire link: there is no control plane\n * to look anything up in, so `projectId` is empty and this is what commands\n * resolve against.\n *\n * Keeping both kinds of link in one file is deliberate. A second link file\n * for self-hosting would fork every command that reads one, and the tooling\n * would drift into being cloud-only by accident.\n */\n apiUrl?: string;\n /**\n * How this checkout is linked. Absent means `cloud` — which is every link\n * written before this field existed.\n */\n mode?: \"cloud\" | \"direct\";\n}\n\nexport function readLink(cwd: string = process.cwd()): ProjectLink | null {\n try {\n return JSON.parse(fs.readFileSync(projectLinkPath(cwd), \"utf-8\")) as ProjectLink;\n } catch {\n return null;\n }\n}\n\nexport function writeLink(link: ProjectLink, cwd: string = process.cwd()): void {\n const file = projectLinkPath(cwd);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(link, null, 2));\n}\n\nexport function removeLink(cwd: string = process.cwd()): boolean {\n const file = projectLinkPath(cwd);\n if (fs.existsSync(file)) {\n fs.rmSync(file);\n return true;\n }\n return false;\n}\n\n/**\n * Flags that may appear anywhere on a `rebase cloud` line, including *before*\n * the resource group.\n *\n * They have to be declared wherever positionals are resolved, because `arg`'s\n * `permissive: true` does not merely tolerate an undeclared flag — it pushes it\n * into `_` alongside the positionals, and for a flag that takes a value it\n * pushes the value in too. So `cloud --project acme storage create` parsed\n * without this spec yields `_` of `[\"--project\", \"acme\", \"storage\", \"create\"]`,\n * and the group reads as `\"acme\"`: a real project name, in the group position,\n * dispatching to nothing. Skipping tokens that start with `-` does not save you\n * there — the damage is the orphaned value, which looks exactly like a\n * positional.\n *\n * Only genuinely global flags belong here. Group-specific ones (`--bucket`,\n * `--region`, …) are declared by the handler that owns them. They can still\n * precede the action — or stand in for it, on a group with a default — so the\n * dispatcher also declares every one that takes a value: `CLOUD_VALUE_FLAGS`\n * in `index.ts`.\n *\n * `-p` is `--project` in eighteen places and `--password` in `login`. That\n * ambiguity does not matter to the one caller that reads this spec: it resolves\n * positionals and never looks at a flag's value, so all it needs to know is\n * that `-p` takes one. Anything that wants the value must keep declaring it\n * itself, with the meaning its own command gives it.\n */\nexport const GLOBAL_CLOUD_FLAGS = {\n \"--json\": Boolean,\n \"--yes\": Boolean,\n \"--help\": Boolean,\n \"--project\": String,\n /* `resolveCloudUrl` reads this off the raw line for EVERY command in the\n family — it is the first entry in the URL priority list — so a strict\n parse that did not declare it would reject a flag the CLI itself honours\n on the command being parsed. */\n \"--url\": String,\n \"-p\": \"--project\",\n \"-y\": \"--yes\",\n \"-h\": \"--help\"\n} as const;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * The raw project reference to operate on: explicit `--project` flag wins,\n * otherwise the linked project. Exits with guidance when neither is present.\n * The value is a slug (the project's subdomain, as shown in console URLs) or,\n * for old scripts and link files, a raw project UUID.\n */\nexport function requireProjectRef(rawArgs: string[]): string {\n const parsed = arg({ \"--project\": String,\n\"-p\": \"--project\" }, { argv: rawArgs.slice(2),\npermissive: true });\n if (parsed[\"--project\"]) return parsed[\"--project\"];\n const link = readLink();\n if (link?.projectId) return link.projectId;\n fail(\n \"No project specified and this directory is not linked.\",\n `Pass ${chalk.bold(\"--project <slug>\")} or run ${chalk.bold(\"rebase cloud link\")}.`,\n \"no_project\"\n );\n}\n\n/**\n * Resolve a project reference — slug or UUID — to the internal id the API\n * takes, or undefined when no such project is visible. Slugs cost one lookup;\n * UUIDs pass through untouched so linked directories and old scripts skip the\n * round-trip.\n */\nexport async function lookupProjectId(ref: string, client: CloudClient): Promise<string | undefined> {\n if (UUID_RE.test(ref)) return ref;\n const res = await client.data.collection(\"projects\").find({\n where: { subdomain: [\"==\", ref] },\n limit: 1\n });\n const row = res.data[0] as { id?: string | number } | undefined;\n return row?.id === undefined ? undefined : String(row.id);\n}\n\n/** Like `lookupProjectId`, but exits with guidance when the ref matches nothing. */\nexport async function resolveProjectRef(ref: string, client: CloudClient): Promise<string> {\n const id = await lookupProjectId(ref, client);\n if (id === undefined) {\n fail(\n `No project with slug ${chalk.bold(ref)}.`,\n `List yours with ${chalk.bold(\"rebase cloud projects\")}.`,\n \"project_not_found\"\n );\n }\n return id;\n}\n\n/** `requireProjectRef` + `resolveProjectRef` in one step. */\nexport async function requireProject(rawArgs: string[], client: CloudClient): Promise<string> {\n return resolveProjectRef(requireProjectRef(rawArgs), client);\n}\n\n/**\n * The project reference to SHOW: the slug the user typed or the linked slug.\n * Never resolves — for human output only. Old link files predate `slug` and\n * fall back to the stored id.\n */\nexport function displayProjectRef(rawArgs: string[]): string {\n const parsed = arg({ \"--project\": String,\n\"-p\": \"--project\" }, { argv: rawArgs.slice(2),\npermissive: true });\n if (parsed[\"--project\"]) return parsed[\"--project\"];\n const link = readLink();\n return link?.slug || link?.projectId || \"\";\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Machine-readable output mode\n ═══════════════════════════════════════════════════════════════\n\n Rebase is built for agents, and the CLI is their primary interface. An agent\n must never scrape a colorized table, so every cloud command can emit a single\n JSON value instead of human output.\n\n JSON mode is on when ANY of these hold:\n • `--json` was passed,\n • `REBASE_JSON=1` is set, or\n • stdout is not a TTY (piped/redirected — i.e. a program is reading it).\n\n One exception to the last rule: `cloud deploy` stays human when piped, and is\n JSON only on `--json` or `REBASE_JSON=1`. Its reader is nearly always a person,\n often watching through an agent that pipes everything — and what they need\n from it (the URL, whether the new version is live) was buried in an object\n they never asked for. See `initOutputMode`'s `whenPiped`.\n\n In JSON mode a command prints exactly one JSON value to stdout and nothing\n else; errors print `{\"error\":{...}}` and exit non-zero. The mode is a\n process-global set once, at dispatch, by `initOutputMode` — every helper here\n (fail, reportError, emit) reads it so the whole family is consistent.\n\n ── The stream contract ─────────────────────────────────────────────────────\n\n The mode decides the SHAPE of the result. It does not decide which stream\n anything lands on; that rule is fixed and holds in both modes:\n\n stdout the command's result, and nothing else — one human rendering or\n one JSON value. A caller may redirect it into a parser.\n stderr everything that is not the result: progress, advice, warnings,\n errors, and the interactive prompts.\n\n Nothing is written to both. That is the whole rule, and it is worth stating\n because the family drifted off it in a way that reads as a bug in the report\n itself: a warning from the SDK went to stderr while the report went to\n stdout, so `whoami 2>&1` interleaved two unrelated voices and the header\n looked like it had been printed twice.\n\n The practical consequence for a new command: the ONLY things that may call\n `console.log` are inside an `emit` human closure. Progress and next-step\n advice go through `note`, cautions through `warn`, outcomes through\n `success` — all of which write to stderr. `keyValues` prints result rows and\n so stays on stdout, which is why it belongs inside the `emit` closure too.\n*/\n\nlet JSON_MODE = false;\n\n/**\n * Resolve and latch the output mode for this invocation. Call once at the top of\n * `cloudCommand`, before anything can print or `fail`. Returns the resolved mode\n * (handy for tests, which otherwise leave it at its `false` default).\n */\nexport function initOutputMode(\n rawArgs: string[],\n opts: {\n /**\n * What a piped run with no explicit choice gets. `json` for the family.\n * `human` for a command whose output a person needs to read even when a\n * program is relaying it — `cloud deploy`, whose JSON told an agent's\n * user neither where the app was nor whether it was live.\n */\n whenPiped?: \"json\" | \"human\";\n } = {}\n): boolean {\n const parsed = arg({ \"--json\": Boolean }, { argv: rawArgs.slice(2),\npermissive: true });\n // Most explicit wins. `REBASE_JSON=0` is the half that was missing: the\n // \"stdout is not a TTY\" rule is right for a result, and wrong for the reader\n // who ran `rebase cloud env --help | less` and got a JSON object. There was\n // no way to say so — `--json` could only turn the mode ON, and `REBASE_JSON`\n // was tested against \"1\" alone, so any other value including \"0\" fell\n // through to the TTY test and set it anyway. Any spelling of yes or no now\n // decides; only an unset or unrecognised value leaves it to the TTY.\n if (parsed[\"--json\"]) JSON_MODE = true;\n else JSON_MODE = parseEnvBoolean(process.env.REBASE_JSON)\n ?? (opts.whenPiped === \"human\" ? false : process.stdout.isTTY !== true);\n return JSON_MODE;\n}\n\n/** Whether the current invocation is emitting machine-readable JSON. */\nexport function isJsonMode(): boolean {\n return JSON_MODE;\n}\n\n/** Force the mode (tests only — production latches it via `initOutputMode`). */\nexport function setJsonModeForTest(value: boolean): void {\n JSON_MODE = value;\n}\n\n/** Strip ANSI colour codes — JSON output must never carry terminal escapes. */\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\u001b\\[[0-9;]*m/g;\nfunction stripAnsi(s: string): string {\n return s.replace(ANSI_RE, \"\");\n}\n\n/**\n * Write one JSON value to stdout, followed by a newline.\n *\n * Indented, because the overwhelmingly common reader is a person or an agent\n * looking at a terminal — JSON mode is entered automatically whenever stdout is\n * not a TTY, so `rebase cloud deployments list` piped anywhere at all produced\n * a project's entire deployment history as one unwrapped line. `JSON.parse`\n * does not care about the whitespace; everything else does.\n */\nexport function printJson(value: unknown): void {\n process.stdout.write(JSON.stringify(value, null, 2) + \"\\n\");\n}\n\n/**\n * The one output primitive every new command uses: in JSON mode emit `json`\n * (and nothing else); otherwise run `human`. Keeping the two behind a single\n * call is what guarantees a command can never print a table AND a JSON blob.\n */\nexport function emit<T>(human: () => void, json: JsonArg<T>): void {\n if (JSON_MODE) printJson(json);\n else human();\n}\n\n/**\n * The JSON payload, which must be a value and not a producer of one.\n *\n * `json: unknown` accepted a function without complaint, and four call sites in\n * `resources.ts` passed `() => ({ … })` — mirroring the human argument beside\n * it, which *is* a thunk. In JSON mode `printJson` then stringified a function,\n * which is `undefined`. So `rebase cloud compute`, `compute set` and two\n * cluster commands printed the single word `undefined` on every piped or\n * `--json` run — and since this family forces JSON mode off a TTY, that is\n * every scripted use of them.\n *\n * Typed so the mistake cannot be made again: a function resolves the parameter\n * to `never`, and the call site fails to compile.\n */\nexport type JsonArg<T> = T extends (...args: never[]) => unknown ? never : T;\n\n/**\n * Print a help page — the human one, or a machine-readable description of the\n * same command in JSON mode.\n *\n * `--help` is the one place where \"stdout is not a TTY\" is a weak signal: a\n * person runs `rebase cloud db --help | less` and wants the page. But the rule\n * this family promises is that stdout carries one JSON value whenever it is not\n * a terminal, and a help page is the easiest possible thing to describe\n * structurally — so rather than carve out an exception, help answers the same\n * question in the reader's own language. For an agent, `--help` piped is then a\n * discovery call rather than 60 lines of ANSI to scrape.\n *\n * `env` shipped this shape first, alone; this generalises it so every group\n * answers the same way.\n */\nexport function emitHelp(\n command: string,\n actions: Array<string | HelpAction>,\n human: () => void,\n extra: Record<string, unknown> = {}\n): void {\n emit(human, { command,\nactions: actions.map(entry => describeAction(entry, command)),\n...extra });\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Group help pages\n ═══════════════════════════════════════════════════════════════ */\n\n/**\n * One action on a group's help page.\n *\n * The page used to exist twice: a hand-formatted template literal for a\n * terminal, and a bare list of action WORDS for everything else. So the two\n * answers to `--help` were not the same answer — piped, `rebase cloud env\n * --help` said `[\"list\",\"set\",\"unset\",\"reveal\",\"pull\"]` and not one description,\n * not one flag, and not the sentence about build-time variables that is the\n * whole reason the page exists. This family forces JSON mode off a TTY, so that\n * was every scripted and every agent-driven read of it.\n *\n * One description, rendered twice.\n */\nexport interface HelpAction {\n /** The action word: `set`, `backup restore`. */\n action: string;\n /** The usage tail after the word: `KEY=VALUE`, `<domain>`, `[-y]`. */\n args?: string;\n /** One line: what it does. */\n description: string;\n /** Flags this action takes of its own, `[flag, description]`. */\n flags?: Array<[string, string]>;\n /** The heading it sits under, on a page that groups its actions. */\n section?: string;\n}\n\n/** A whole group page — `rebase cloud env --help`. */\nexport interface GroupHelp {\n /** The command words, no leading `rebase`: `cloud env`, or `cloud` itself. */\n command: string;\n /** The title's tail: \"Environment variables\". */\n title: string;\n actions: HelpAction[];\n /** Options that belong to the group rather than to one action. */\n options?: Array<[string, string]>;\n /** Closing paragraphs — the things a reader gets wrong more than once. */\n notes?: string[];\n}\n\n/**\n * Normalise an action for the JSON form. Strings stay legal — the index page\n * lists group names, which are not actions of the command being described.\n *\n * `command` is the page's own command (`cloud env`), so the usage line a caller\n * reads is a line it can run: without it the answer said `rebase cloud set\n * KEY=VALUE`, which is a command that does not exist.\n */\nfunction describeAction(entry: string | HelpAction, command: string): Record<string, unknown> {\n if (typeof entry === \"string\") return { action: entry,\ndescription: \"\",\nflags: [] };\n return {\n action: entry.action,\n usage: `rebase ${command} ${entry.action}${entry.args ? ` ${entry.args}` : \"\"}`,\n description: entry.description,\n flags: (entry.flags ?? []).map(([flag, description]) => ({ flag,\ndescription }))\n };\n}\n\n/**\n * Flags every cloud command accepts, documented once.\n *\n * Lives here rather than in `action-help.ts` because both help renderers print\n * it and `action-help` imports this module, not the other way round.\n */\nexport const GLOBAL_HELP_FLAGS: Array<[string, string]> = [\n [\"--project, -p <slug>\", \"Operate on a project without linking this directory\"],\n [\"--json\", \"Machine-readable output (also when piped, or REBASE_JSON=1)\"],\n [\"--url <origin>\", \"Target a specific control plane (or REBASE_CLOUD_URL)\"],\n [\"--yes, -y\", \"Skip confirmation prompts\"],\n [\"--debug\", \"Print the untouched error body after a failure\"]\n];\n\n/**\n * Flag names `parseCloudArgs` adds to every command in the family.\n *\n * Derived from `GLOBAL_CLOUD_FLAGS` rather than listed again — a second copy is\n * how a global gains a spelling that the collision sweep does not know about.\n * `--debug` is the one addition: `bin/rebase.js` takes it off `process.argv`\n * itself, so no cloud spec declares it and every command still accepts it.\n */\nexport const GLOBAL_SPEC_KEYS = new Set([...Object.keys(GLOBAL_CLOUD_FLAGS), \"--debug\"]);\n\n/** Column width for an action word (+ its args) before its description. */\nconst HELP_COLUMN = 28;\n\n/**\n * The gap after `text` in a `width`-wide column, never less than one space.\n *\n * `padEnd` returns the string unchanged when it is already at the width, which\n * glues the description onto the flag: `--connection-string <url>The external…`.\n */\nfunction pad(text: string, width: number): string {\n return \" \".repeat(Math.max(1, width - text.length));\n}\n\n/**\n * Print a group's page — the human one, and the same content as JSON when piped.\n */\nexport function printGroupHelp(page: GroupHelp): void {\n emitHelp(\n page.command,\n page.actions,\n () => {\n console.log(\"\");\n console.log(`${chalk.bold(`rebase ${page.command}`)} — ${page.title}`);\n console.log(\"\");\n console.log(chalk.green.bold(\"Usage\"));\n console.log(` rebase ${page.command} ${chalk.blue(\"<action>\")} [options]`);\n\n let section: string | undefined;\n for (const entry of page.actions) {\n const heading = entry.section ?? \"Commands\";\n if (heading !== section) {\n section = heading;\n console.log(\"\");\n console.log(chalk.green.bold(heading));\n }\n const word = `${entry.action}${entry.args ? ` ${entry.args}` : \"\"}`;\n console.log(\n ` ${chalk.blue.bold(entry.action)}${entry.args ? ` ${chalk.gray(entry.args)}` : \"\"}`\n + `${pad(word, HELP_COLUMN)}${entry.description}`\n );\n for (const [flag, description] of entry.flags ?? []) {\n console.log(` ${chalk.blue(flag)}${pad(flag, HELP_COLUMN - 4)}${chalk.gray(description)}`);\n }\n }\n\n if (page.options?.length) {\n console.log(\"\");\n console.log(chalk.green.bold(\"Options\"));\n for (const [flag, description] of page.options) {\n console.log(` ${chalk.blue(flag)}${pad(flag, HELP_COLUMN)}${description}`);\n }\n }\n\n console.log(\"\");\n console.log(chalk.green.bold(\"Global options\"));\n for (const [flag, description] of GLOBAL_HELP_FLAGS) {\n console.log(` ${chalk.blue(flag)}${pad(flag, HELP_COLUMN)}${chalk.gray(description)}`);\n }\n\n if (page.notes?.length) {\n console.log(\"\");\n for (const note of page.notes) console.log(chalk.gray(` ${note}`));\n }\n console.log(\"\");\n },\n {\n usage: `rebase ${page.command} <action> [options]`,\n summary: page.title,\n options: (page.options ?? []).map(([flag, description]) => ({ flag,\ndescription })),\n globalFlags: GLOBAL_HELP_FLAGS.map(([flag, description]) => ({ flag,\ndescription })),\n notes: page.notes ?? []\n }\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Output helpers\n ═══════════════════════════════════════════════════════════════ */\n\n/**\n * Print a warning (+ optional hint) — in every output mode, always to stderr.\n *\n * `emit` is for a command's *result*, and JSON mode legitimately replaces the\n * human rendering of one. A warning is not a result: it says the command is\n * about to do something the caller may not have meant, and that is exactly as\n * true when the output is piped. Gating one on `!isJsonMode()` deleted it\n * precisely where nobody was watching the terminal — a `--source` deploy ejected\n * a live project off the managed runtime and said so only to a TTY that wasn't\n * there.\n *\n * stdout carries the JSON value and nothing else, so warnings go to stderr:\n * a machine parser reading stdout cannot be corrupted by one. Only the\n * *formatting* may depend on the mode — colour and indentation for a terminal,\n * plain ASCII otherwise. Whether a warning is emitted at all may not.\n *\n * Anything a caller might branch on belongs in the JSON payload as well; stderr\n * is for whoever reads the transcript afterwards.\n */\nexport function warn(message: string, hint?: string): void {\n if (JSON_MODE) {\n process.stderr.write(`warning: ${stripAnsi(message)}\\n`);\n if (hint) process.stderr.write(` ${stripAnsi(hint)}\\n`);\n return;\n }\n console.error(\"\");\n console.error(chalk.yellow(` ⚠ ${message}`));\n if (hint) console.error(chalk.gray(` ${hint}`));\n}\n\n/**\n * Print an error (+ optional hint) and exit non-zero. Never returns.\n *\n * `code` is the field a caller branches on, and it defaults to `\"error\"` rather\n * than `null`. An envelope whose only machine-readable field is null is not\n * machine-readable — `{\"error\":{\"message\":\"No project specified…\",\"code\":null}}`\n * forced the very substring-matching on `message` that the envelope exists to\n * make unnecessary, and `message` is the field most likely to be reworded.\n *\n * `\"error\"` is deliberately a poor code: it says \"this refusal has not been\n * classified yet\" without ever being absent. Anything a caller might plausibly\n * want to distinguish — `usage`, `not_found`, `unauthenticated` — passes a real\n * one. Codes are part of the CLI's contract once shipped; see\n * `cloud-reporting.test.ts`, which pins the ones commands are documented to\n * return.\n */\nexport function fail(message: string, hint?: string, code?: string): never {\n if (JSON_MODE) {\n printJson({ error: { message: stripAnsi(message),\ncode: code ?? \"error\",\nhint: hint ? stripAnsi(hint) : undefined } });\n process.exit(1);\n }\n console.error(\"\");\n console.error(chalk.red(` ✗ ${message}`));\n if (hint) console.error(chalk.gray(` ${hint}`));\n console.error(\"\");\n process.exit(1);\n}\n\n/**\n * Confirm a destructive/irreversible action, respecting non-interactive use.\n *\n * With `--yes`/`-y` it proceeds silently. In JSON mode or a non-TTY it REFUSES\n * to prompt — a prompt that can hang is a known repo landmine — and fails,\n * telling the caller to pass `--yes`. Only an interactive terminal gets a real\n * confirm prompt; declining there aborts cleanly (exit 0).\n */\nexport async function confirmDestructive(opts: { yes: boolean; prompt: string }): Promise<void> {\n if (opts.yes) return;\n if (JSON_MODE || process.stdin.isTTY !== true) {\n fail(\n \"This action is destructive and needs confirmation.\",\n `Re-run with ${chalk.bold(\"--yes\")} to proceed.`,\n \"confirmation_required\"\n );\n }\n const { confirmed } = (await inquirer.prompt([\n { type: \"confirm\",\nname: \"confirmed\",\ndefault: false,\nmessage: opts.prompt }\n ] as Parameters<typeof inquirer.prompt>[0])) as { confirmed: boolean };\n if (!confirmed) {\n // stderr: declining is not a result. Unreachable in JSON mode — the\n // guard above already refused rather than prompt — so stdout stays\n // empty and the exit code carries the outcome.\n console.error(chalk.gray(\" Aborted.\"));\n process.exit(0);\n }\n}\n\n/**\n * Refuse, rather than prompt, when there is nobody to answer.\n *\n * `confirmDestructive` has always done this for yes/no confirmations. The\n * *value* prompts had no such guard: `cloud login`, `cloud link`, `cloud use`,\n * `cloud orgs create` and `cloud db create` all called `inquirer.prompt`\n * unconditionally, so piping any of them — which is how an agent runs every\n * command in this family — parked the process on a prompt reading from a stdin\n * that was never going to produce a line. A hang is the worst failure mode\n * available here: no output, no exit code, nothing to retry on.\n *\n * @param what what the prompt would have asked for, e.g. \"an email and password\"\n * @param flags the flags that supply it non-interactively\n */\nexport function requireInteractive(what: string, flags: string): void {\n if (JSON_MODE || process.stdin.isTTY !== true) {\n fail(\n `This command needs ${what}, and there is no terminal to ask on.`,\n `Pass ${chalk.bold(flags)}.`,\n \"input_required\"\n );\n }\n}\n\n/**\n * Resolve one cloud command's flags and ARGUMENTS from the full `process.argv`.\n *\n * This replaces `cloudPositionals`, which was `rawArgs.slice(3).filter(a =>\n * !a.startsWith(\"-\"))`. Dropping `-`-prefixed tokens looks like it solves the\n * permissive-parse problem and does not: a flag that takes a VALUE leaves the\n * value behind, an ordinary word in the argument position that no filter can\n * tell from a real one. `--project` is the flag every one of these commands\n * documents, so the failure was reachable from the help page:\n *\n * rebase cloud env unset -p acme → removed the variable \"acme\"\n * rebase cloud env set KEY -p acme → stored the value \"acme\"\n * rebase cloud domains add -p acme → registered the domain \"acme\"\n * rebase cloud webhooks delete -p acme 42 → deleted webhook \"acme\", not 42\n * rebase cloud cancel -p acme → cancelled deployment id \"acme\"\n *\n * The filter's other half is quieter. A flag nobody declared *is* dropped by\n * it — but only from the operands, never from the run: nothing rejects it, so\n * the command proceeds with the argument missing or defaulted. `db backup\n * --dry-run` listed backups, `domains remove --dry-run` detached the domain,\n * and `env set KEY=v --secrett` stored the value as an ordinary variable that\n * `env reveal` will hand back. The one place an undeclared flag became the\n * argument outright is `projects info|delete`, which resolved its id through\n * `positionals()` instead — that skips only LEADING `-` tokens, so `projects\n * delete --force` looked up a project named \"--force\".\n *\n * So: parse the whole line strictly, through the same `parseCommandArgs` the\n * non-cloud commands use — `arg` then consumes each declared flag *with its\n * value* wherever it appears, and rejects the undeclared, leaving `_` holding\n * the command words followed by the real arguments. `commandWords` counts from\n * `cloud` itself (`cloud env set` ⇒ 3), and is applied to the parsed\n * positionals, so a flag written before the group shifts nothing.\n *\n * Two things this adds over calling `parseCommandArgs` directly, and the reason\n * it is worth a wrapper:\n *\n * - `GLOBAL_CLOUD_FLAGS` is merged in. `--json`, `--yes` and `--project` may\n * appear anywhere on a cloud line including before the group, so a strict\n * parse that did not declare them would reject the CLI's own documented\n * usage. (`parseCommandArgs` adds `--debug`/`--help` on top of that.)\n * - A parse error is reported through `fail`, not thrown. A throw reaches\n * `bin/rebase.js`, which prints `✗ …` to stderr — which is right for every\n * other command and wrong here: `rebase cloud` is in JSON mode whenever\n * stdout is not a TTY, i.e. always for the agents this family is built for,\n * and it promises them exactly one JSON value. `fail` keeps that promise,\n * with the same `usage` code the other refusals in this family use.\n */\nexport function parseCloudArgs<S extends arg.Spec>(opts: {\n spec: S;\n rawArgs: string[];\n commandWords: number;\n /** Names the command in errors, e.g. `cloud env set` (no leading `rebase`). */\n command: string;\n maxPositionals?: number;\n}): { flags: arg.Result<S & typeof GLOBAL_CLOUD_FLAGS>; positionals: string[] } {\n const spec = { ...GLOBAL_CLOUD_FLAGS,\n...opts.spec };\n try {\n const parsed = parseCommandArgs({ ...opts,\nspec });\n return { flags: parsed.flags,\npositionals: parsed.positionals };\n } catch (err) {\n fail(err instanceof Error ? err.message : String(err), undefined, \"usage\");\n }\n}\n\n/**\n * Refuse an action word its group does not dispatch. Returns for `undefined`,\n * which is every group's default action.\n *\n * The groups that switch on their action already do this in a `default:` case.\n * The groups written as a chain of `if (action === \"x\") return …` did not: a\n * word that matched nothing fell out of the chain into the *default* action, so\n * `rebase cloud storage creat` listed the buckets and exited 0, and\n * `rebase cloud billing usage` printed the account. Reporting a typo as a\n * successful run of a different command is the failure mode this family exists\n * to not have — an agent branching on the exit code learns nothing, and a person\n * reads the output of a command they did not ask for.\n *\n * One spelling, so the code is `unknown_command` everywhere rather than the\n * default `\"error\"` half of them used, and the hint always names the group's own\n * `--help` rather than the index page.\n */\nexport function requireKnownAction(\n group: string,\n action: string | undefined,\n known: readonly string[]\n): void {\n if (action === undefined || known.includes(action)) return;\n fail(\n `Unknown ${group} command: ${action}`,\n `Run \\`rebase cloud ${group} --help\\`. Actions: ${known.join(\", \")}.`,\n \"unknown_command\"\n );\n}\n\n/**\n * `--timeout <seconds>` as milliseconds, or `fallbackMs` when it was not given.\n *\n * One function rather than one per command, because two commands take this flag\n * and a second copy is where the two would come to disagree about what\n * `--timeout 0` means.\n *\n * A value this cannot read is a refusal, not a fall back to the default. The\n * whole reason a caller passes a timeout is that it has a deadline of its own;\n * quietly substituting a different one is how a fifteen-minute wait turns up\n * inside a five-minute CI step, having been asked for `--timeout 30s`.\n */\nexport function resolveTimeoutMs(\n value: string | undefined,\n opts: { fallbackMs: number; command: string }\n): number {\n if (value === undefined) return opts.fallbackMs;\n const seconds = Number(value);\n if (!Number.isFinite(seconds) || seconds <= 0) {\n fail(\n `--timeout takes a number of seconds (got \"${value}\").`,\n `Run \\`rebase ${opts.command} --help\\`.`,\n \"usage\"\n );\n }\n return seconds * 1000;\n}\n\n/**\n * Announce an outcome — \"Logged in as …\", \"Deleted project …\".\n *\n * On **stderr**, in both modes. It reads like a result and is not one: the\n * result is the JSON value (or the table) on stdout, and every JSON payload in\n * this family already carries `success: true`. Leaving this on stdout meant a\n * successful `rebase cloud link | jq` was handed a green tick followed by an\n * object — one stream, two syntaxes, and only the second parseable.\n *\n * It stays visible in JSON mode, unlike `note`: an agent that got a `success`\n * line on a command it expected to refuse has learned something.\n */\nexport function success(message: string): void {\n if (JSON_MODE) {\n process.stderr.write(`${stripAnsi(message)}\\n`);\n return;\n }\n console.error(\"\");\n console.error(chalk.bold.green(` ✓ ${message}`));\n console.error(\"\");\n}\n\n/**\n * Narrate progress, or point at the next step — \"Signing in to …\", \"Redeploy\n * for the tenant to pick this up\".\n *\n * stderr, and **suppressed entirely in JSON mode**. This is the one helper that\n * a mode may silence, and the distinction from `warn` is worth keeping sharp:\n *\n * - A warning is a *condition*. It is as true when piped as when watched, so\n * silencing it hides something the caller would want to know. `warn` never\n * silences.\n * - A note is *hand-holding*. \"Next: run `rebase generate-sdk`\" tells a person\n * what to type; the agent reading the JSON already has the same information\n * structurally, or does not need it. Printing it anyway is transcript noise.\n *\n * When in doubt it is a warning. The cost of a needless warning is a line; the\n * cost of a swallowed one is the deploy that ejected a project off the managed\n * runtime and said so only to a terminal nobody was looking at.\n */\nexport function note(message: string, indent = \" \"): void {\n if (JSON_MODE) return;\n console.error(`${indent}${message}`);\n}\n\n/** A blank spacer line on the narration stream. No-op in JSON mode. */\nexport function noteBlank(): void {\n if (JSON_MODE) return;\n console.error(\"\");\n}\n\n/** Colorize a deployment / resource status token. */\nexport function colorStatus(status: string | undefined): string {\n switch (status) {\n case \"active\":\n case \"success\":\n case \"connected\":\n return chalk.green(status);\n case \"deploying\":\n case \"provisioning\":\n case \"pending_billing\":\n case \"untested\":\n return chalk.yellow(status ?? \"\");\n case \"failed\":\n return chalk.red(status);\n case \"stopped\":\n return chalk.gray(status);\n default:\n return chalk.gray(status ?? \"unknown\");\n }\n}\n\n/**\n * Render a two-column key/value block with aligned keys. Empty rows are skipped\n * — including `null`, which the API sends for an unset column and which used to\n * print the literal string \"null\" (e.g. `Custom domain: null`).\n */\nexport function keyValues(rows: Array<[string, string | null | undefined]>): void {\n const width = Math.max(...rows.map(([k]) => k.length));\n for (const [k, v] of rows) {\n if (v === undefined || v === null || v === \"\") continue;\n console.log(` ${chalk.gray(`${k}:`.padEnd(width + 1))} ${v}`);\n }\n}\n\n/**\n * Surface an SDK/HTTP error consistently. The SDK throws RebaseApiError with\n * a `.status` and `.message`; anything else falls back to its string form.\n *\n * The message is summarised rather than printed — see `summarizeError`. What\n * arrives here is routinely a whole Kubernetes `Status` object with the request\n * headers appended, and the one sentence worth reading is inside it. The\n * untouched body is still available, behind `--debug`, on stderr where it\n * cannot corrupt the JSON value on stdout.\n */\nexport function reportError(e: unknown, context: string): never {\n const err = e as { status?: number; message?: string; code?: string };\n const summary = summarizeError(e, context);\n\n if (wantsRawError()) {\n process.stderr.write(`\\n${summary.raw}\\n\\n`);\n }\n\n if (JSON_MODE) {\n printJson({\n error: {\n message: stripAnsi(summary.message),\n // The SDK supplies a code for errors the API classified. When it\n // does not, the HTTP status still classifies it well enough to\n // branch on — `http_401` and `http_502` want very different\n // handling, and both used to arrive as `null`. A cluster\n // refusal classifies itself, and `platform` below says whether\n // acting on it is even the caller's business.\n code: summary.code,\n status: err?.status ?? null,\n hint: summary.hint ? stripAnsi(summary.hint) : undefined,\n platform: summary.platform,\n context\n }\n });\n process.exit(1);\n }\n fail(summary.message, summary.hint, summary.code);\n}\n\n/**\n * Open a URL in the user's default browser (best effort). Always announces the\n * URL first so it stays usable over SSH or when no browser is available.\n *\n * The announcement is narration, not the result — it goes to stderr, and in\n * JSON mode it is silent. Every caller `emit`s the same URL in its payload, so\n * a machine reader gets it from the one place it is guaranteed to be parseable\n * rather than from a line that happens to end in a URL.\n */\nexport function openUrl(target: string, label = \"Opening\"): void {\n noteBlank();\n note(`${label} ${chalk.cyan(target)}`);\n noteBlank();\n const opener =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n try {\n const child = spawn(opener, [target], {\n stdio: \"ignore\",\n detached: true,\n shell: process.platform === \"win32\"\n });\n child.on(\"error\", () => {\n /* URL already printed for manual copy */\n });\n child.unref();\n } catch {\n /* URL already printed */\n }\n}\n\n/**\n * The one thing every control-plane row is required to have.\n *\n * The subcommands each declare their own row shape — `OrgRow`, `ProjectRow`,\n * `DatabaseRow`, `DeploymentRow` — and every one of them is an `id` plus\n * fields that are all optional, because a control plane older than a given\n * column simply does not send it. The reads are written that way too\n * (`o.name ?? \"(unnamed)\"`, `row.slug ? … : \"\"`).\n */\nexport interface CloudRow {\n id: string | number;\n}\n\n/**\n * Read the control plane's rows as a subcommand's row shape.\n *\n * `client.data.collection(...)` is typed against the *generated* schema of the\n * project the SDK is pointed at, and the control-plane collections this CLI\n * reads are not in one — so every row arrives as an open\n * `Record<string, unknown>`. A declared `interface` gets no implicit index\n * signature, so it does not overlap that, and a direct `as` is refused: which\n * is how ten call sites came to write `as unknown as XRow[]`, an assertion\n * about a wire payload with nothing checking it in either direction.\n *\n * There is exactly one invariant to check and this checks it. Everything else\n * the shapes declare is optional and already read as such, so there is nothing\n * further to verify — but a row with no usable `id` is not a row any of these\n * commands can act on, and passing it through was how a listing came to print\n * `[undefined]` and a lookup came to compare against the string `\"undefined\"`.\n */\nexport function cloudRows<T extends CloudRow>(rows: readonly Record<string, unknown>[] | undefined): T[] {\n return (rows ?? []).filter((row): row is Record<string, unknown> & T =>\n typeof row?.id === \"string\" || typeof row?.id === \"number\");\n}\n\n/** {@link cloudRows} for an endpoint that returns a single row. */\nexport function cloudRow<T extends CloudRow>(row: Record<string, unknown> | undefined): T | undefined {\n return cloudRows<T>(row ? [row] : [])[0];\n}\n\n/**\n * {@link cloudRows} for a write that must have produced a row.\n *\n * `create()` returning something with no usable `id` means the control plane\n * accepted the write and then described it in a way this CLI cannot act on.\n * The callers all go straight on to use that id — `setContextOrg(url,\n * String(created.id))` — so asserting the shape, which is what stood here,\n * turned a control-plane fault into an organization whose active id is the\n * seven-letter string `\"undefined\"`, stored in the user's config file.\n */\nexport function requireCloudRow<T extends CloudRow>(\n row: Record<string, unknown> | undefined,\n what: string\n): T {\n const parsed = cloudRow<T>(row);\n if (!parsed) throw new Error(`The control plane accepted the ${what} but returned no id for it.`);\n return parsed;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,IAAM,wBAAwB;;;;;;;;;;;AAY9B,SAAgB,wBAAwB,MAA4C;CAChF,KAAK,IAAI,QAAQ,KAAK,QAAQ,GAAG,GAAG,UAAU,IAAI,QAAQ,KAAK,QAAQ,KAAK,QAAQ,CAAC,GAAG;EACpF,IAAI,QAAQ;EACZ,IAAI,WAAW;EACf,IAAI,UAAU;EACd,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,KAAK;GACtC,MAAM,KAAK,KAAK;GAChB,IAAI,SAAS;IACT,UAAU;IACV;GACJ;GACA,IAAI,OAAO,MAAM;IACb,UAAU;IACV;GACJ;GACA,IAAI,OAAO,MAAK;IACZ,WAAW,CAAC;IACZ;GACJ;GACA,IAAI,UAAU;GACd,IAAI,OAAO,KAAK;QACX,IAAI,OAAO,KAAK;IACjB;IACA,IAAI,UAAU,GAAG;IACjB,MAAM,YAAY,KAAK,MAAM,OAAO,IAAI,CAAC;IACzC,IAAI;IACJ,IAAI;KACA,SAAS,KAAK,MAAM,SAAS;IACjC,QAAQ;KACJ;IACJ;IACA,MAAM,MAAM;IAIZ,IAAI,KAAK,SAAS,YAAa,KAAK,WAAW,aAAa,OAAO,IAAI,SAAS,UAC5E,OAAO;KACH,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAA;KACzD,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,KAAA;KACtD,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,KAAA;KAChD,SAAU,IAAI,WAA2C,KAAA;IAC7D;IAEJ;GACJ;EACJ;CACJ;AAEJ;;;;;;;;;AAUA,SAAS,mBAAmB,SAAyB;CACjD,MAAM,MAAM,QAAQ,OAChB,kFACJ;CAEA,QADiB,MAAM,IAAI,QAAQ,MAAM,GAAG,GAAG,IAAI,QAAA,CAAS,KACrD,CAAA,CAAQ,QAAQ,aAAa,EAAE;AAC1C;;AAGA,IAAM,cAAc;;;;;;;;;AAUpB,SAAgB,eAAe,OAAgB,SAA+B;CAC1E,MAAM,MAAM;CACZ,MAAM,MAAM,KAAK,WAAW,OAAO,KAAK;CACxC,MAAM,MAAM,wBAAwB,GAAG;CAEvC,IAAI,KAAK;EACL,MAAM,QAAQ,IAAI,SAAS,KAAK,KAAK;EAIrC,KAHkB,IAAI,SAAS,OAAO,IAAI,WAAW,gBACvB,sBAAsB,KAAK,KAAK,GAG1D,OAAO;GAGH,SAAS,GAAG,QAAQ;GACpB,MACI,GAAG,SAAS,KAAK,EAAE;GAIvB,MAAM;GACN,UAAU;GACV;EACJ;EAGJ,OAAO;GACH,SAAS,GAAG,QAAQ,IAAI,SAAS,SAAS,IAAI,UAAU,iCAAiC;GACzF,MAAM,IAAI,OAAO,uBAAuB,IAAI,OAAO,IAAI,SAAS,KAAK,IAAI,OAAO,KAAK,GAAG,KAAK,KAAA;GAC7F,MAAM,IAAI,SAAS,OAAO,IAAI,OAAO,YAAY,MAAM;GACvD,UAAU;GACV;EACJ;CACJ;CAIA,MAAM,UAAU,mBAAmB,GAAG;CACtC,OAAO;EACH,SAAS,GAAG,UAAU,KAAK,SAAS,KAAK,IAAI,OAAO,KAAK,GAAG,IAAI,SAAS,WAAW,GAAG;EACvF,MAAM,KAAK,SAAS,KAAK,SAAS,QAAQ,IAAI,WAAW;EACzD,UAAU;EACV;CACJ;AACJ;AAEA,SAAS,SAAS,MAAsB;CACpC,MAAM,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;CAC5C,OAAO,KAAK,SAAS,cAAc,GAAG,KAAK,MAAM,GAAG,cAAc,CAAC,EAAE,KAAK;AAC9E;;;;;;;;;;;;;AAcA,SAAgB,cAAc,OAA0B,QAAQ,MAAe;CAC3E,OAAO,SAAS,MAAM,SAAS,KAAK,gBAAgB,QAAQ,IAAI,YAAY,MAAM;AACtF;;;;AC9JA,IAAa,yBAAqD;CAAC;CAAU;CAAQ;CAAO;CAAY;AAAS;;;;;;;AAQjH,IAAa,gBAA0B;CACnC;CACA;CACA;CACA;CACA;AACJ;;AAGA,IAAa,qBAA0G;CACnH,QAAQ;EACJ,OAAO;EACP,SAAS,cAAc;GACnB,gBAAgB;GAChB,kBAAkB;GAClB;GAGA;EACJ;CACJ;CACA,MAAM;EACF,OAAO;EACP,SAAS,cAAc;GACnB,gBAAgB;GAChB,gBAAgB;GAChB;GACA;GACA;GAEA;GAEA;EACJ;CACJ;CACA,KAAK;EACD,OAAO;EACP,SAAS,cAAc;GACnB,eAAe;GACf;GAEA;EACJ;CACJ;CACA,UAAU;EACN,OAAO;EACP,SAAS,cAAc;GACnB,gBAAgB;GAChB,oBAAoB;GACpB;GACA;EACJ;CACJ;CACA,SAAS;EACL,OAAO;EACP,SAAS,cAAc;GACnB,gBAAgB;GAChB,mBAAmB;GACnB;EACJ;CACJ;AACJ;;AAGA,SAAgB,kBAAkB,OAAyC;CACvE,OAAQ,uBAA6C,SAAS,KAAK;AACvE;;;;;;;;;AAUA,SAAgB,sBAAsB,WAAmB,cAAoD;CACzG,IAAI,UAAU,WAAW,KAAK,KAAK,KAAK,SAAS,GAC7C,MAAM,IAAI,MAAM,IAAI,UAAU,kDAAkD;CAEpF,MAAM,SAAS,IAAI,IAAY,aAAa;CAC5C,KAAK,MAAM,cAAc,cAAc;EACnC,IAAI,CAAC,kBAAkB,UAAU,GAC7B,MAAM,IAAI,MAAM,IAAI,OAAO,UAAU,EAAE,6CAA6C,uBAAuB,KAAK,IAAI,EAAE,EAAE;EAE5H,KAAK,MAAM,SAAS,mBAAmB,WAAW,CAAC,OAAO,SAAS,GAAG,OAAO,IAAI,KAAK;CAC1F;CACA,OAAO,CAAC,GAAG,MAAM;AACrB;;;;;;;;AASA,SAAgB,qBAAqB,QAA0F;CAC3H,MAAM,OAAO,IAAI,IAAI,MAAM;CAC3B,MAAM,aAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,QAAQ;EACxB,MAAM,QAAQ,iCAAiC,KAAK,KAAK;EACzD,IAAI,SAAS,CAAC,WAAW,SAAS,MAAM,EAAE,GAAG,WAAW,KAAK,MAAM,EAAE;CACzE;CACA,OAAO,WAAW,KAAI,eAAc;EAChC;EACA,cAAc,uBAAuB,QAAO,eACxC,mBAAmB,WAAW,CAAC,OAAO,SAAS,CAAC,CAAC,OAAM,UAAS,KAAK,IAAI,KAAK,CAAC,CAAC;CACxF,EAAE;AACN;;;;;;;;;AAUA,SAAgB,mBAAmB,QAA2B,WAA4B;CACtF,OAAO,OAAO,MAAK,UAAS;EACxB,MAAM,QAAQ,sCAAsC,KAAK,KAAK;EAC9D,OAAO,UAAU,SAAS,MAAM,OAAO,KAAA,KAAa,MAAM,OAAO;CACrE,CAAC;AACL;;AAQA,IAAa,uBAAiD;CAAC;CAAW;CAAS;AAAe;;AAGlG,IAAa,mBAA2G;CACpH,SAAS;EACL,OAAO;EACP,SAAS,mBAAmB;GACxB,eAAe;GACf;GAEA;EACJ;CACJ;CACA,OAAO;EACH,OAAO;EACP,SAAS,mBAAmB,CACxB,aAAa,kBACb,wBACJ;CACJ;CACA,eAAe;EACX,OAAO;EACP,SAAS,mBAAmB,CACxB,qBAAqB,kBACrB,gCACJ;CACJ;AACJ;;AAGA,SAAgB,gBAAgB,OAAuC;CACnE,OAAQ,qBAA2C,SAAS,KAAK;AACrE;;;;;;;;AASA,SAAgB,yBAAyB,gBAAwB,cAAkD;CAC/G,IAAI,eAAe,WAAW,KAAK,KAAK,KAAK,cAAc,GACvD,MAAM,IAAI,MAAM,IAAI,eAAe,wDAAwD;CAE/F,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,cAAc,cAAc;EACnC,IAAI,CAAC,gBAAgB,UAAU,GAC3B,MAAM,IAAI,MAAM,IAAI,OAAO,UAAU,EAAE,qDAAqD,qBAAqB,KAAK,IAAI,EAAE,EAAE;EAElI,KAAK,MAAM,SAAS,iBAAiB,WAAW,CAAC,OAAO,cAAc,GAAG,OAAO,IAAI,KAAK;CAC7F;CACA,OAAO,CAAC,GAAG,MAAM;AACrB;;;;;;AAOA,SAAgB,wBAAwB,QAA6F;CACjI,MAAM,OAAO,IAAI,IAAI,MAAM;CAC3B,MAAM,kBAA4B,CAAC;CACnC,KAAK,MAAM,SAAS,QAAQ;EACxB,MAAM,QAAQ,6BAA6B,KAAK,KAAK;EACrD,IAAI,SAAS,CAAC,gBAAgB,SAAS,MAAM,EAAE,GAAG,gBAAgB,KAAK,MAAM,EAAE;CACnF;CACA,OAAO,gBAAgB,KAAI,oBAAmB;EAC1C;EACA,cAAc,qBAAqB,QAAO,eACtC,iBAAiB,WAAW,CAAC,OAAO,cAAc,CAAC,CAAC,OAAM,UAAS,KAAK,IAAI,KAAK,CAAC,CAAC;CAC3F,EAAE;AACN;;;;;;;;;;;;;;;;;;;AC5OA,IAAM,oBAAoB;;AAG1B,IAAM,kBAAkB;;AAGxB,SAAS,kBAA0B;CAC/B,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,kBAAkB;AAChE;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,KAA2C;CAC1E,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO,KAAA;CACpD,KAAK,MAAM,OAAO;EAAC;EAAoB;EAAsB;CAAgB,GAAG;EAC5E,MAAM,QAAS,IAAgC;EAC/C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO;CACvE;AAEJ;AAEA,SAAgB,gBAAgB,MAAc,QAAQ,IAAI,GAAW;CACjE,MAAM,OAAO,gBAAgB,GAAG,KAAK;CACrC,OAAO,KAAK,KAAK,MAAM,WAAW,YAAY;AAClD;AAkDA,SAAS,kBAAmC;CACxC,IAAI;EACA,MAAM,MAAM,GAAG,aAAa,gBAAgB,GAAG,OAAO;EACtD,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,CAAC,OAAO,UAAU,OAAO,WAAW,CAAC;EACzC,OAAO;CACX,QAAQ;EACJ,OAAO,EAAE,UAAU,CAAC,EAAE;CAC1B;AACJ;;;;;;;;;;AAWA,SAAS,iBAAiB,MAA6B;CACnD,MAAM,OAAO,gBAAgB;CAC7B,GAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CACpD,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE;CACvD,IAAI;EAEA,GAAG,cAAc,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;EAC1E,IAAI;GACA,GAAG,UAAU,WAAW,GAAK;EACjC,QAAQ,CAER;EACA,GAAG,WAAW,WAAW,IAAI;CACjC,SAAS,OAAO;EACZ,GAAG,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;EACpC,MAAM;CACV;AACJ;;AAGA,SAAgB,mBAAmB,KAAa,WAAmB,OAA6B;CAC5F,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,QAAQ,MAAM,SAAS,QAAQ,CAAC;CACtC,MAAM,YAAY;EAAE,GAAI,MAAM,aAAa,CAAC;GAAK,YAAY;CAAM;CACnE,MAAM,SAAS,OAAO;CACtB,iBAAiB,KAAK;AAC1B;;AAGA,SAAgB,qBAAqB,KAAa,WAAyB;CACvE,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,QAAQ,MAAM,SAAS;CAC7B,IAAI,CAAC,OAAO,YAAY,YAAY;CACpC,OAAO,MAAM,UAAU;CACvB,iBAAiB,KAAK;AAC1B;;AAGA,SAAgB,kBAAkB,KAAa,WAAmB,MAAc,KAAK,IAAI,GAA+B;CACpH,MAAM,QAAQ,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,YAAY;CAC3D,IAAI,CAAC,SAAS,EAAE,KAAK,MAAM,MAAM,SAAS,IAAI,MAAM,OAAO,KAAA;CAC3D,OAAO;AACX;;;;;AAMA,SAAgB,qBAAqB,KAAa,WAA+C;CAC7F,OAAO,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,YAAY;AACxD;;;;;AAMA,SAAgB,qBAAqB,KAAuE;CACxG,MAAM,OAAO,SAAS;CACtB,IAAI,CAAC,MAAM,aAAa,KAAK,SAAS,YAAY,aAAa,KAAK,GAAG,MAAM,KAAK,OAAO,KAAA;CACzF,MAAM,QAAQ,kBAAkB,KAAK,KAAK,SAAS;CACnD,OAAO,QAAQ;EAAE,WAAW,KAAK;EAAW;CAAM,IAAI,KAAA;AAC1D;;;;;;AAOA,SAAgB,aAAa,MAAkE;CAC3F,IAAI,CAAC,MAAM,aAAa,KAAK,SAAS,UAAU,OAAO,KAAA;CACvD,OAAO,kBAAkB,aAAa,KAAK,GAAG,GAAG,KAAK,SAAS;AACnE;;;;;;AAOA,SAAgB,mBAAmB,MAA0F;CACzH,IAAI,CAAC,MAAM,aAAa,KAAK,SAAS,UAAU,OAAO,KAAA;CACvD,MAAM,MAAM,aAAa,KAAK,GAAG;CACjC,MAAM,QAAQ,qBAAqB,KAAK,KAAK,SAAS;CACtD,OAAO,QAAQ;EAAE;EAAK;CAAM,IAAI,KAAA;AACpC;;AAGA,SAAgB,WAAW,MAAc,KAAsB;CAC3D,IAAI,CAAC,GAAG,WAAW,IAAI,GAAG,OAAO;CACjC,MAAM,UAAU,IAAI,OAAO,qBAAqB,IAAI,WAAW;CAC/D,OAAO,GAAG,aAAa,MAAM,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,MAAM;EAC1D,MAAM,QAAQ,QAAQ,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK;EACzC,OAAO,UAAU,KAAA,KAAa,UAAU,MAAM,UAAU,UAAU,UAAU;CAChF,CAAC;AACL;;AAGA,IAAM,cAAc;;AAGpB,SAAgB,0BAAmC;CAC/C,OAAO,WAAW,KAAK,KAAK,gBAAgB,KAAK,QAAQ,IAAI,GAAG,MAAM,GAAG,WAAW;AACxF;;;;;;;;;AAUA,SAAgB,iBAAiB,WAA2B;CACxD,OAAO,GAAG,YAAY,4BAA4B,IAAI,KAAK,SAAS,CAAC,CAAC,eAAe,EAAE;AAG3F;;AAGA,SAAS,oBAAwC;CAC7C,OAAO,gBAAgB,CAAC,CAAC;AAC7B;;AAGA,SAAgB,cAAc,KAAa,KAA+B;CACtE,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,QAAQ,MAAM,SAAS,QAAQ,CAAC;CACtC,IAAI,KAAK,MAAM,MAAM;MAChB,OAAO,MAAM;CAClB,MAAM,SAAS,OAAO;CACtB,iBAAiB,KAAK;AAC1B;AAEA,SAAgB,cAAc,KAAiC;CAC3D,OAAO,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE;AAC5C;AAMA,SAAS,sBAAsB,KAA0B;CACrD,OAAO;EACH,QAAQ,KAAK;GACT,IAAI,QAAQ,iBAAiB,OAAO;GACpC,OAAO,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,QAAQ;EACpD;EACA,QAAQ,KAAK,OAAO;GAChB,IAAI,QAAQ,iBAAiB;GAC7B,MAAM,QAAQ,gBAAgB;GAC9B,MAAM,QAAQ,MAAM,SAAS,QAAQ,CAAC;GACtC,MAAM,OAAO;GACb,MAAM,SAAS,OAAO;GACtB,IAAI,CAAC,MAAM,SAAS,MAAM,UAAU;GACpC,iBAAiB,KAAK;EAC1B;EACA,WAAW,KAAK;GACZ,IAAI,QAAQ,iBAAiB;GAC7B,MAAM,QAAQ,gBAAgB;GAC9B,IAAI,MAAM,SAAS,MAAM;IACrB,OAAO,MAAM,SAAS,IAAI,CAAC;IAC3B,OAAO,MAAM,SAAS,IAAI,CAAC;GAC/B;GACA,iBAAiB,KAAK;EAC1B;CACJ;AACJ;;AAGA,SAAgB,kBAAkB,KAAmB;CACjD,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,UAAU;CAChB,IAAI,CAAC,MAAM,SAAS,MAAM,MAAM,SAAS,OAAO,CAAC;CACjD,iBAAiB,KAAK;AAC1B;AAkBA,SAAgB,gBAAgB,SAA2B;CACvD,OAAO,mBAAmB,OAAO,CAAC,CAAC;AACvC;;AAGA,SAAgB,mBAAmB,SAA4D;CAC3F,MAAM,SAAS,IAAI,EAAE,SAAS,OAAO,GAAG;EAAE,MAAM,QAAQ,MAAM,CAAC;EACnE,YAAY;CAAK,CAAC;CACd,IAAI,OAAO,UAAU,OAAO;EAAE,KAAK,aAAa,OAAO,QAAQ;EACnE,QAAQ;CAAO;CACX,IAAI,QAAQ,IAAI,kBAAkB,OAAO;EAAE,KAAK,aAAa,QAAQ,IAAI,gBAAgB;EAC7F,QAAQ;CAAM;CAEV,MAAM,OAAO,SAAS;CAetB,IAAI,MAAM,OAAO,KAAK,SAAS,UAAU,OAAO;EAAE,KAAK,aAAa,KAAK,GAAG;EAChF,QAAQ;CAAO;CAEX,MAAM,UAAU,kBAAkB;CAClC,IAAI,SAAS,OAAO;EAAE,KAAK,aAAa,OAAO;EACnD,QAAQ;CAAU;CAEd,OAAO;EAAE,KAAK;EAClB,QAAQ;CAAU;AAClB;;;;;;;;;AAUA,SAAgB,oBAAoB,KAAsB;CACtD,OAAO,QAAQ,qBAAqB,OAAO,OAAO,gBAAgB,CAAC,CAAC,UAAU,GAAG;AACrF;;;;;;;;;;;;;AAcA,SAAgB,iBAAiB,SAAyB;CACtD,MAAM,OAAO,SAAS;CACtB,IAAI,MAAM,SAAS,UAAU;CAE7B,MAAM,SAAS,IACX;EAAE,aAAa;EACvB,MAAM;EACN,SAAS;CAAO,GACR;EAAE,MAAM,QAAQ,MAAM,CAAC;EAC/B,YAAY;CAAK,CACb;CACA,IAAI,OAAO,gBAAgB,OAAO,UAAU;CAE5C,KACI,wCAAwC,MAAM,KAAK,KAAK,GAAG,EAAE,KACxD,MAAM,KAAK,qBAAqB,EAAE,kBAAkB,MAAM,KAAK,WAAW,EAAE,IACjF,2GACA,aACJ;AACJ;AAEA,SAAS,aAAa,KAAqB;CACvC,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC,QAAQ,QAAQ,EAAE;CACrC,IAAI,CAAC,eAAe,KAAK,CAAC,GAAG,IAAI,WAAW;CAC5C,OAAO;AACX;;;;;;;AAcA,SAAgB,kBAAkB,KAA0B;CACxD,OAAO,mBAAmB;EACtB,SAAS;EAeT,UAAU;EAMV,SAAS,EAAE,cAAc,aAAa,EAAE;EACxC,MAAM;GACF,SAAS,sBAAsB,GAAG;GAClC,gBAAgB;GAChB,aAAa;EACjB;CACJ,CAAC;AACL;;;;;;AAOA,IAAa,YAAY;;AAGzB,SAAgB,WAA+B;CAC3C,MAAM,QAAQ,QAAQ,IAAI,UAAU,EAAE,KAAK;CAC3C,OAAO,QAAQ,QAAQ,KAAA;AAC3B;;AAGA,IAAM,gCAAgB,IAAI,QAAwB;;AAGlD,IAAM,gCAAgB,IAAI,QAAwB;;;;;;AAOlD,SAAgB,eAAe,QAAoC;CAC/D,OAAO,cAAc,IAAI,MAAM;AACnC;;;;;;;;;;AAWA,SAAgB,kBAAkB,KAAa,OAAe,oBAA0C;CACpG,MAAM,SAAS,mBAAmB;EAC9B,SAAS;EACT,UAAU;EACV,SAAS,EAAE,cAAc,aAAa,EAAE;EAExC,OAAO;EACP;EACA,MAAM;GACF,SAAS,oBAAoB;GAC7B,gBAAgB;GAChB,aAAa;EACjB;CACJ,CAAC;CACD,OAAO,wBAAwB,qBACzB,oBAAoB,KAAK,oBAAoB,KAAK,IAClD,oBAAoB,KAAK,KAAK,CAAC;CACrC,cAAc,IAAI,QAAQ,KAAK;CAC/B,IAAI,oBAAoB,cAAc,IAAI,QAAQ,kBAAkB;CACpE,OAAO;AACX;;AAGA,IAAM,kCAAkB,IAAI,IAAY;;;;;;;;;;;;;;AAexC,SAAgB,kBAAkB,UAA0B;CACxD,IAAI,SAAS,SAAS,MAAM,aAAa,KAAK,MAAM;CACpD,MAAM,SAAS,SAAS,QAAQ,IAAI,sBAAsB,CAAC,EAAE,KAAK,KAC3D;CACP,IAAI,gBAAgB,IAAI,MAAM,GAAG;CACjC,gBAAgB,IAAI,MAAM;CAC1B,MAAM,OAAO,uCAAuC,KAAK,SAAS,QAAQ,IAAI,MAAM,KAAK,EAAE,CAAC,GAAG;CAC/F,KAAK,QAAQ,OAAO,OAAO,SAAS,KAAA,CAAS;AACjD;;;;;AAMA,IAAM,wBAAiD,OAAO,OAAO,SAAS;CAC1E,MAAM,WAAW,MAAM,WAAW,MAAM,OAAO,IAAI;CACnD,kBAAkB,QAAQ;CAC1B,OAAO;AACX;;AAGA,IAAM,+BAAe,IAAI,IAA2C;;AAGpE,IAAM,uCAAuB,IAAI,IAAY;;;;;;AAO7C,SAAS,YAAY,KAAa,OAA8C;CAC5E,MAAM,MAAM,GAAG,IAAI,IAAI;CACvB,IAAI,UAAU,aAAa,IAAI,GAAG;CAClC,IAAI,CAAC,SAAS;EACV,WAAW,YAAY;GACnB,IAAI;IACA,MAAM,MAAM,MAAM,MAAM,GAAG,IAAI,mBAAmB,EAC9C,SAAS;KAAE,eAAe,UAAU;KAAS,cAAc,aAAa;IAAE,EAC9E,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,KAAA;IACpB,MAAM,OAAgB,MAAM,IAAI,KAAK;IACrC,MAAM,OAAO,OAAO,SAAS,YAAY,SAAS,OAAQ,KAA4B,OAAO,KAAA;IAC7F,OAAO,MAAM,QAAQ,IAAI,KAAK,KAAK,OAAM,MAAK,OAAO,MAAM,QAAQ,IAAI,OAAO,KAAA;GAClF,QAAQ;IACJ;GACJ;EACJ,EAAA,CAAG;EACH,aAAa,IAAI,KAAK,OAAO;CACjC;CACA,OAAO;AACX;;;;;;AAcA,SAAS,oBAAoB,SAAmB,WAA4B;CAExE,MAAM,MADS,IAAI;EAAE,aAAa;EAAQ,MAAM;CAAY,GAAG;EAAE,MAAM,QAAQ,MAAM,CAAC;EAAG,YAAY;CAAK,CAC9F,CAAA,CAAO;CACnB,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,OAAO,SAAS;CACtB,OAAO,QAAQ,aAAa,QAAQ,MAAM;AAC9C;;;;;;;AAQA,eAAsB,WAAW,KAAa,OAA4D;CACtG,IAAI;EACA,MAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,mBAAmB,EAChD,SAAS;GAAE,eAAe,UAAU;GAAS,cAAc,aAAa;EAAE,EAC9E,CAAC;EACD,IAAI,MAAM,IAAI,OAAO;EACrB,OAAO,MAAM,WAAW,OAAO,MAAM,WAAW,MAAM,YAAY;CACtE,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;;AAaA,eAAe,oBAAoB,KAAa,OAA+B;CAC3E,IAAI,MAAM,WAAW,KAAK,KAAK,MAAM,YACjC,KACI,+CAA+C,UAAU,oBACzD,SAAS,UAAU,WAAW,MAAM,KAAK,oBAAoB,EAAE,cAC/D,kBACJ;CAEJ,KACI,GAAG,UAAU,mBAAmB,IAAI,2EACpC,yBAAyB,MAAM,KAAK,4BAA4B,EAAE,iBAAiB,UAAU,IAC7F,gBACJ;AACJ;;AAGA,IAAM,mBAAmB;;;;;;;;;;;;;;;AAwBzB,eAAsB,iBAAiB,QAAwC;CAC3E,MAAM,QAAQ,cAAc,IAAI,MAAM;CACtC,IAAI,OAAO,OAAO;CAClB,MAAM,UAAU,OAAO,KAAK,WAAW;CACvC,IAAI,SAAS,eAAe,QAAQ,YAAY,KAAK,IAAI,IAAI,kBAAkB,OAAO,QAAQ;CAC9F,QAAQ,MAAM,OAAO,KAAK,eAAe,EAAA,CAAG;AAChD;;;;;;;;;;;;AAaA,eAAe,oBAAoB,KAAa,WAAmB,OAA+B;CAC9F,MAAM,UAAU,MAAM,WAAW,KAAK,KAAK;CAC3C,IAAI,YAAY,YACZ,KACI,8HAA8H,UAAU,IAIxI,wNAES,MAAM,KAAK,qBAAqB,EAAE,0GACwB,MAAM,KAAK,oBAAoB,EAAE,IACpG,kBACJ;CAEJ,IAAI,YAAY,WACZ,KACI,8DAA8D,UAAU,kBAAkB,IAAI,6CAC9F,0FAA0F,MAAM,KAAK,qBAAqB,EAAE,wBAC5H,gBACJ;CAEJ,MAAM,QAAQ,qBAAqB,KAAK,SAAS;CACjD,qBAAqB,KAAK,SAAS;CAInC,MAAM,UAAU,SAAS,wBAAwB,IAAI,IAAI,iBAAiB,MAAM,SAAS,MAAM;CAC/F,IAAI,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,MACjC,KACI,8DAA8D,UAAU,kBAAkB,IAAI,4CAC9F,6CAA6C,IAAI,iDAAiD,WAClG,gBACJ;CAEJ,MAAM,QAAQ,QACR,oDAAoD,MAAM,KAAK,MAAM,QAAQ,EAAE,YAAY,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,eAAe,EAAE,MAClI,UACJ;CACN,KACI,8DAA8D,UAAU,kBAAkB,IAAI,IAC9F,yEACS,MAAM,KAAK,oBAAoB,EAAE,gEACjC,MAAM,KAAK,4BAA4B,EAAE,GAAG,SACrD,gBACJ;AACJ;;;;;;;;;;;;;;AAeA,eAAsB,cAAc,SAAkE;CAClG,MAAM,QAAQ,SAAS;CACvB,IAAI,CAAC,OAAO;EACR,MAAM,SAAS,qBAAqB,gBAAgB,OAAO,CAAC;EAC5D,IAAI,CAAC,QAAQ,OAAO,qBAAqB,OAAO;EAChD,iBAAiB,OAAO;EACxB,MAAM,MAAM,gBAAgB,OAAO;EACnC,OAAO;GAAE,QAAQ,kBAAkB,KAAK,OAAO,MAAM,OAAO,OAAO,SAAS;GAAG;EAAI;CACvF;CAIA,iBAAiB,OAAO;CACxB,MAAM,MAAM,gBAAgB,OAAO;CAMnC,MAAM,YAAY,qBAAqB,GAAG;CAC1C,IAAI,WAAW;EACX,IAAI,CAAC,oBAAoB,SAAS,UAAU,SAAS,GAAG;GACpD,MAAM,OAAO,MAAM,YAAY,KAAK,KAAK;GACzC,IAAI,QAAQ,CAAC,mBAAmB,MAAM,UAAU,SAAS,GAAG;IACxD,IAAI,CAAC,qBAAqB,IAAI,UAAU,SAAS,GAAG;KAChD,qBAAqB,IAAI,UAAU,SAAS;KAC5C,KACI,GAAG,UAAU,6BAA6B,UAAU,UAAU,8EAC9D,iDAAiD,UAAU,YAAY,UAAU,2BACrF;IACJ;IACA,OAAO;KAAE,QAAQ,kBAAkB,KAAK,UAAU,MAAM,OAAO,UAAU,SAAS;KAAG;IAAI;GAC7F;EACJ;EACA,KACI,GAAG,UAAU,+FAA+F,UAAU,UAAU,IAChI,SAAS,UAAU,4CACvB;CACJ;CACA,OAAO;EAAE,QAAQ,kBAAkB,KAAK,KAAK;EAAG;CAAI;AACxD;;;;;;;;;;;;;AAcA,eAAsB,qBAClB,SACA,SAAS,iBACoC;CAI7C,iBAAiB,OAAO;CACxB,MAAM,MAAM,gBAAgB,OAAO;CACnC,MAAM,SAAS,kBAAkB,GAAG;CACpC,MAAM,UAAU,OAAO,KAAK,WAAW;CAEvC,IAAI,CAAC,WAAW,CAAC,QAAQ,aAAa;EAClC,IAAI,SAAS,GACT,KACI,8CAA8C,MAAM,KAAK,GAAG,EAAE,QAAQ,UAAU,oBAChF,kBAAkB,OAAO,QAAQ,MAAM,KAAK,oBAAoB,EAAE,UAClE,kBACJ;EAEJ,KACI,oBAAoB,MAAM,KAAK,GAAG,EAAE,IACpC,OAAO,MAAM,KAAK,oBAAoB,EAAE,UACxC,eACJ;CACJ;CAEA,IAAI,QAAQ,aAAa,KAAK,IAAI,IAAI,kBAClC,IAAI;EACA,MAAM,OAAO,KAAK,eAAe;CACrC,QAAQ;EACJ,KACI,oBAAoB,MAAM,KAAK,GAAG,EAAE,gBACpC,OAAO,MAAM,KAAK,oBAAoB,EAAE,qBAIxC,iBACJ;CACJ;CAGJ,OAAO;EAAE;EACb;CAAI;AACJ;;;;;;;;;;;;;;;;;;;;AAuCA,IAAM,sCAAsB,IAAI,IAAiD;AAEjF,SAAgB,oBAAoB,QAAqB,KAAkD;CACvG,IAAI,UAAU,oBAAoB,IAAI,GAAG;CACzC,IAAI,CAAC,SAAS;EACV,UAAU,OAAO,UACZ,OAAuB,mBAAmB,KAAA,GAAW,EAAE,QAAQ,MAAM,CAAC,CAAC,CACvE,MAAM,QAAQ,OAAO,KAAA,CAAS,CAAC,CAC/B,YAAY,KAAA,CAAS;EAC1B,oBAAoB,IAAI,KAAK,OAAO;CACxC;CACA,OAAO;AACX;;;;;;AAOA,SAAgB,sBAAsB,QAAqB,KAA0C;CACjG,OAAO,oBAAoB,QAAQ,GAAG,CAAC,CAAC,MAAM,QAAQ,KAAK,kBAAkB,KAAK,KAAK,KAAA,CAAS;AACpG;;;;;;;AAQA,SAAgB,mBAAmB,QAAqB,KAAkD;CACtG,OAAO,oBAAoB,QAAQ,GAAG,CAAC,CAAC,MAAM,QAC1C,MAAM,QAAQ,KAAK,aAAa,IAAI,IAAI,gBAAgB,KAAA,CAC5D;AACJ;;;;;;;;;;AAWA,SAAgB,iBACZ,WACA,YACkB;CAClB,IAAI,CAAC,WAAW,OAAO,KAAA;CACvB,OAAO,aAAa,GAAG,UAAU,GAAG,eAAe;AACvD;;;;;;;;;;;;;;AAsBA,SAAgB,YACZ,SACA,YACkB;CAClB,OAAO,QAAQ,QAAQ,iBAAiB,QAAQ,WAAW,UAAU;AACzE;AAiCA,SAAgB,SAAS,MAAc,QAAQ,IAAI,GAAuB;CACtE,IAAI;EACA,OAAO,KAAK,MAAM,GAAG,aAAa,gBAAgB,GAAG,GAAG,OAAO,CAAC;CACpE,QAAQ;EACJ,OAAO;CACX;AACJ;AAEA,SAAgB,UAAU,MAAmB,MAAc,QAAQ,IAAI,GAAS;CAC5E,MAAM,OAAO,gBAAgB,GAAG;CAChC,GAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CACpD,GAAG,cAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACxD;AAEA,SAAgB,WAAW,MAAc,QAAQ,IAAI,GAAY;CAC7D,MAAM,OAAO,gBAAgB,GAAG;CAChC,IAAI,GAAG,WAAW,IAAI,GAAG;EACrB,GAAG,OAAO,IAAI;EACd,OAAO;CACX;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,qBAAqB;CAC9B,UAAU;CACV,SAAS;CACT,UAAU;CACV,aAAa;CAKb,SAAS;CACT,MAAM;CACN,MAAM;CACN,MAAM;AACV;AAEA,IAAM,UAAU;;;;;;;AAQhB,SAAgB,kBAAkB,SAA2B;CACzD,MAAM,SAAS,IAAI;EAAE,aAAa;EACtC,MAAM;CAAY,GAAG;EAAE,MAAM,QAAQ,MAAM,CAAC;EAC5C,YAAY;CAAK,CAAC;CACd,IAAI,OAAO,cAAc,OAAO,OAAO;CACvC,MAAM,OAAO,SAAS;CACtB,IAAI,MAAM,WAAW,OAAO,KAAK;CACjC,KACI,0DACA,QAAQ,MAAM,KAAK,kBAAkB,EAAE,UAAU,MAAM,KAAK,mBAAmB,EAAE,IACjF,YACJ;AACJ;;;;;;;AAQA,eAAsB,gBAAgB,KAAa,QAAkD;CACjG,IAAI,QAAQ,KAAK,GAAG,GAAG,OAAO;CAK9B,MAAM,OAAM,MAJM,OAAO,KAAK,WAAW,UAAU,CAAC,CAAC,KAAK;EACtD,OAAO,EAAE,WAAW,CAAC,MAAM,GAAG,EAAE;EAChC,OAAO;CACX,CAAC,EAAA,CACe,KAAK;CACrB,OAAO,KAAK,OAAO,KAAA,IAAY,KAAA,IAAY,OAAO,IAAI,EAAE;AAC5D;;AAGA,eAAsB,kBAAkB,KAAa,QAAsC;CACvF,MAAM,KAAK,MAAM,gBAAgB,KAAK,MAAM;CAC5C,IAAI,OAAO,KAAA,GACP,KACI,wBAAwB,MAAM,KAAK,GAAG,EAAE,IACxC,mBAAmB,MAAM,KAAK,uBAAuB,EAAE,IACvD,mBACJ;CAEJ,OAAO;AACX;;AAGA,eAAsB,eAAe,SAAmB,QAAsC;CAC1F,OAAO,kBAAkB,kBAAkB,OAAO,GAAG,MAAM;AAC/D;;;;;;AAOA,SAAgB,kBAAkB,SAA2B;CACzD,MAAM,SAAS,IAAI;EAAE,aAAa;EACtC,MAAM;CAAY,GAAG;EAAE,MAAM,QAAQ,MAAM,CAAC;EAC5C,YAAY;CAAK,CAAC;CACd,IAAI,OAAO,cAAc,OAAO,OAAO;CACvC,MAAM,OAAO,SAAS;CACtB,OAAO,MAAM,QAAQ,MAAM,aAAa;AAC5C;AAiDA,IAAI,YAAY;;;;;;AAOhB,SAAgB,eACZ,SACA,OAQI,CAAC,GACE;CAUP,IATe,IAAI,EAAE,UAAU,QAAQ,GAAG;EAAE,MAAM,QAAQ,MAAM,CAAC;EACrE,YAAY;CAAK,CAQT,CAAA,CAAO,WAAW,YAAY;MAC7B,YAAY,gBAAgB,QAAQ,IAAI,WAAW,MAChD,KAAK,cAAc,UAAU,QAAQ,QAAQ,OAAO,UAAU;CACtE,OAAO;AACX;;AAGA,SAAgB,aAAsB;CAClC,OAAO;AACX;;AASA,IAAM,UAAU;AAChB,SAAS,UAAU,GAAmB;CAClC,OAAO,EAAE,QAAQ,SAAS,EAAE;AAChC;;;;;;;;;;AAWA,SAAgB,UAAU,OAAsB;CAC5C,QAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,IAAI;AAC9D;;;;;;AAOA,SAAgB,KAAQ,OAAmB,MAAwB;CAC/D,IAAI,WAAW,UAAU,IAAI;MACxB,MAAM;AACf;;;;;;;;;;;;;;;;AAiCA,SAAgB,SACZ,SACA,SACA,OACA,QAAiC,CAAC,GAC9B;CACJ,KAAK,OAAO;EAAE;EAClB,SAAS,QAAQ,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;EAC5D,GAAG;CAAM,CAAC;AACV;;;;;;;;;AAqDA,SAAS,eAAe,OAA4B,SAA0C;CAC1F,IAAI,OAAO,UAAU,UAAU,OAAO;EAAE,QAAQ;EACpD,aAAa;EACb,OAAO,CAAC;CAAE;CACN,OAAO;EACH,QAAQ,MAAM;EACd,OAAO,UAAU,QAAQ,GAAG,MAAM,SAAS,MAAM,OAAO,IAAI,MAAM,SAAS;EAC3E,aAAa,MAAM;EACnB,QAAQ,MAAM,SAAS,CAAC,EAAA,CAAG,KAAK,CAAC,MAAM,kBAAkB;GAAE;GACnE;EAAY,EAAE;CACV;AACJ;;;;;;;AAQA,IAAa,oBAA6C;CACtD,CAAC,wBAAwB,qDAAqD;CAC9E,CAAC,UAAU,6DAA6D;CACxE,CAAC,kBAAkB,uDAAuD;CAC1E,CAAC,aAAa,2BAA2B;CACzC,CAAC,WAAW,gDAAgD;AAChE;AAUwC,CAAC,GAAG,OAAO,KAAK,kBAAkB,CAAY;;AAGtF,IAAM,cAAc;;;;;;;AAQpB,SAAS,IAAI,MAAc,OAAuB;CAC9C,OAAO,IAAI,OAAO,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,CAAC;AACtD;;;;AAKA,SAAgB,eAAe,MAAuB;CAClD,SACI,KAAK,SACL,KAAK,eACC;EACF,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,GAAG,MAAM,KAAK,UAAU,KAAK,SAAS,EAAE,KAAK,KAAK,OAAO;EACrE,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,MAAM,KAAK,OAAO,CAAC;EACrC,QAAQ,IAAI,YAAY,KAAK,QAAQ,GAAG,MAAM,KAAK,UAAU,EAAE,WAAW;EAE1E,IAAI;EACJ,KAAK,MAAM,SAAS,KAAK,SAAS;GAC9B,MAAM,UAAU,MAAM,WAAW;GACjC,IAAI,YAAY,SAAS;IACrB,UAAU;IACV,QAAQ,IAAI,EAAE;IACd,QAAQ,IAAI,MAAM,MAAM,KAAK,OAAO,CAAC;GACzC;GACA,MAAM,OAAO,GAAG,MAAM,SAAS,MAAM,OAAO,IAAI,MAAM,SAAS;GAC/D,QAAQ,IACJ,KAAK,MAAM,KAAK,KAAK,MAAM,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAC5E,IAAI,MAAM,WAAW,IAAI,MAAM,aACxC;GACA,KAAK,MAAM,CAAC,MAAM,gBAAgB,MAAM,SAAS,CAAC,GAC9C,QAAQ,IAAI,SAAS,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,cAAc,CAAC,IAAI,MAAM,KAAK,WAAW,GAAG;EAEtG;EAEA,IAAI,KAAK,SAAS,QAAQ;GACtB,QAAQ,IAAI,EAAE;GACd,QAAQ,IAAI,MAAM,MAAM,KAAK,SAAS,CAAC;GACvC,KAAK,MAAM,CAAC,MAAM,gBAAgB,KAAK,SACnC,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,WAAW,IAAI,aAAa;EAElF;EAEA,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,MAAM,KAAK,gBAAgB,CAAC;EAC9C,KAAK,MAAM,CAAC,MAAM,gBAAgB,mBAC9B,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,WAAW,IAAI,MAAM,KAAK,WAAW,GAAG;EAG1F,IAAI,KAAK,OAAO,QAAQ;GACpB,QAAQ,IAAI,EAAE;GACd,KAAK,MAAM,QAAQ,KAAK,OAAO,QAAQ,IAAI,MAAM,KAAK,KAAK,MAAM,CAAC;EACtE;EACA,QAAQ,IAAI,EAAE;CAClB,GACA;EACI,OAAO,UAAU,KAAK,QAAQ;EAC9B,SAAS,KAAK;EACd,UAAU,KAAK,WAAW,CAAC,EAAA,CAAG,KAAK,CAAC,MAAM,kBAAkB;GAAE;GAC1E;EAAY,EAAE;EACF,aAAa,kBAAkB,KAAK,CAAC,MAAM,kBAAkB;GAAE;GAC3E;EAAY,EAAE;EACF,OAAO,KAAK,SAAS,CAAC;CAC1B,CACJ;AACJ;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,KAAK,SAAiB,MAAqB;CACvD,IAAI,WAAW;EACX,QAAQ,OAAO,MAAM,YAAY,UAAU,OAAO,EAAE,GAAG;EACvD,IAAI,MAAM,QAAQ,OAAO,MAAM,KAAK,UAAU,IAAI,EAAE,GAAG;EACvD;CACJ;CACA,QAAQ,MAAM,EAAE;CAChB,QAAQ,MAAM,MAAM,OAAO,OAAO,SAAS,CAAC;CAC5C,IAAI,MAAM,QAAQ,MAAM,MAAM,KAAK,OAAO,MAAM,CAAC;AACrD;;;;;;;;;;;;;;;;;AAkBA,SAAgB,KAAK,SAAiB,MAAe,MAAsB;CACvE,IAAI,WAAW;EACX,UAAU,EAAE,OAAO;GAAE,SAAS,UAAU,OAAO;GACvD,MAAM,QAAQ;GACd,MAAM,OAAO,UAAU,IAAI,IAAI,KAAA;EAAU,EAAE,CAAC;EACpC,QAAQ,KAAK,CAAC;CAClB;CACA,QAAQ,MAAM,EAAE;CAChB,QAAQ,MAAM,MAAM,IAAI,OAAO,SAAS,CAAC;CACzC,IAAI,MAAM,QAAQ,MAAM,MAAM,KAAK,KAAK,MAAM,CAAC;CAC/C,QAAQ,MAAM,EAAE;CAChB,QAAQ,KAAK,CAAC;AAClB;;;;;;;;;AAUA,eAAsB,mBAAmB,MAAuD;CAC5F,IAAI,KAAK,KAAK;CACd,IAAI,aAAa,QAAQ,MAAM,UAAU,MACrC,KACI,sDACA,eAAe,MAAM,KAAK,OAAO,EAAE,eACnC,uBACJ;CAEJ,MAAM,EAAE,cAAe,MAAM,SAAS,OAAO,CACzC;EAAE,MAAM;EAChB,MAAM;EACN,SAAS;EACT,SAAS,KAAK;CAAO,CACjB,CAA0C;CAC1C,IAAI,CAAC,WAAW;EAIZ,QAAQ,MAAM,MAAM,KAAK,YAAY,CAAC;EACtC,QAAQ,KAAK,CAAC;CAClB;AACJ;;;;;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,MAAc,OAAqB;CAClE,IAAI,aAAa,QAAQ,MAAM,UAAU,MACrC,KACI,sBAAsB,KAAK,wCAC3B,QAAQ,MAAM,KAAK,KAAK,EAAE,IAC1B,gBACJ;AAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,eAAmC,MAO6B;CAC5E,MAAM,OAAO;EAAE,GAAG;EACtB,GAAG,KAAK;CAAK;CACT,IAAI;EACA,MAAM,SAAS,iBAAiB;GAAE,GAAG;GAC7C;EAAK,CAAC;EACE,OAAO;GAAE,OAAO,OAAO;GAC/B,aAAa,OAAO;EAAY;CAC5B,SAAS,KAAK;EACV,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAAG,KAAA,GAAW,OAAO;CAC7E;AACJ;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,mBACZ,OACA,QACA,OACI;CACJ,IAAI,WAAW,KAAA,KAAa,MAAM,SAAS,MAAM,GAAG;CACpD,KACI,WAAW,MAAM,YAAY,UAC7B,sBAAsB,MAAM,sBAAsB,MAAM,KAAK,IAAI,EAAE,IACnE,iBACJ;AACJ;;;;;;;;;;;;;AAcA,SAAgB,iBACZ,OACA,MACM;CACN,IAAI,UAAU,KAAA,GAAW,OAAO,KAAK;CACrC,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,GACxC,KACI,6CAA6C,MAAM,MACnD,gBAAgB,KAAK,QAAQ,aAC7B,OACJ;CAEJ,OAAO,UAAU;AACrB;;;;;;;;;;;;;AAcA,SAAgB,QAAQ,SAAuB;CAC3C,IAAI,WAAW;EACX,QAAQ,OAAO,MAAM,GAAG,UAAU,OAAO,EAAE,GAAG;EAC9C;CACJ;CACA,QAAQ,MAAM,EAAE;CAChB,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,SAAS,CAAC;CAChD,QAAQ,MAAM,EAAE;AACpB;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,KAAK,SAAiB,SAAS,MAAY;CACvD,IAAI,WAAW;CACf,QAAQ,MAAM,GAAG,SAAS,SAAS;AACvC;;AAGA,SAAgB,YAAkB;CAC9B,IAAI,WAAW;CACf,QAAQ,MAAM,EAAE;AACpB;;AAGA,SAAgB,YAAY,QAAoC;CAC5D,QAAQ,QAAR;EACI,KAAK;EACL,KAAK;EACL,KAAK,aACD,OAAO,MAAM,MAAM,MAAM;EAC7B,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACD,OAAO,MAAM,OAAO,UAAU,EAAE;EACpC,KAAK,UACD,OAAO,MAAM,IAAI,MAAM;EAC3B,KAAK,WACD,OAAO,MAAM,KAAK,MAAM;EAC5B,SACI,OAAO,MAAM,KAAK,UAAU,SAAS;CAC7C;AACJ;;;;;;AAOA,SAAgB,UAAU,MAAwD;CAC9E,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC;CACrD,KAAK,MAAM,CAAC,GAAG,MAAM,MAAM;EACvB,IAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,MAAM,IAAI;EAC/C,QAAQ,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,GAAG,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,GAAG;CACjE;AACJ;;;;;;;;;;;AAYA,SAAgB,YAAY,GAAY,SAAwB;CAC5D,MAAM,MAAM;CACZ,MAAM,UAAU,eAAe,GAAG,OAAO;CAEzC,IAAI,cAAc,GACd,QAAQ,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK;CAG/C,IAAI,WAAW;EACX,UAAU,EACN,OAAO;GACH,SAAS,UAAU,QAAQ,OAAO;GAOlC,MAAM,QAAQ;GACd,QAAQ,KAAK,UAAU;GACvB,MAAM,QAAQ,OAAO,UAAU,QAAQ,IAAI,IAAI,KAAA;GAC/C,UAAU,QAAQ;GAClB;EACJ,EACJ,CAAC;EACD,QAAQ,KAAK,CAAC;CAClB;CACA,KAAK,QAAQ,SAAS,QAAQ,MAAM,QAAQ,IAAI;AACpD;;;;;;;;;;AAWA,SAAgB,QAAQ,QAAgB,QAAQ,WAAiB;CAC7D,UAAU;CACV,KAAK,GAAG,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG;CACrC,UAAU;CACV,MAAM,SACF,QAAQ,aAAa,WAAW,SAAS,QAAQ,aAAa,UAAU,UAAU;CACtF,IAAI;EACA,MAAM,QAAQ,MAAM,QAAQ,CAAC,MAAM,GAAG;GAClC,OAAO;GACP,UAAU;GACV,OAAO,QAAQ,aAAa;EAChC,CAAC;EACD,MAAM,GAAG,eAAe,CAExB,CAAC;EACD,MAAM,MAAM;CAChB,QAAQ,CAER;AACJ;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,UAA8B,MAA2D;CACrG,QAAQ,QAAQ,CAAC,EAAA,CAAG,QAAQ,QACxB,OAAO,KAAK,OAAO,YAAY,OAAO,KAAK,OAAO,QAAQ;AAClE;;AAGA,SAAgB,SAA6B,KAAyD;CAClG,OAAO,UAAa,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAC1C;;;;;;;;;;;AAYA,SAAgB,gBACZ,KACA,MACC;CACD,MAAM,SAAS,SAAY,GAAG;CAC9B,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,kCAAkC,KAAK,4BAA4B;CAChG,OAAO;AACX"}
@@ -1,11 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { r as runningBackendUrl } from "./dev-server-B1UYifZJ.js";
3
- import { a as dataDir, c as pidRunning, d as releaseStartLock, f as stateFile, i as clearState, l as readState, o as devDbDir, r as chooseManagedPort, t as acquireStartLock } from "./state-CCB-FAQ9.js";
2
+ import { n as cliVersion } from "./version-Buyyo1ut.js";
3
+ import { i as runningBackendUrl } from "./dev-server-OJsUGZEB.js";
4
+ import { a as dataDir, c as pidRunning, d as releaseStartLock, f as stateFile, i as clearState, l as readState, o as devDbDir, r as chooseManagedPort, t as acquireStartLock } from "./state-CYdBvnwA.js";
4
5
  import fs from "fs";
5
6
  import path from "path";
6
7
  import { randomBytes } from "crypto";
7
8
  import { fileURLToPath } from "url";
8
- import { spawn } from "child_process";
9
+ import { execFile, spawn } from "child_process";
9
10
  import net from "net";
10
11
  //#region src/dev-db/daemon.ts
11
12
  /**
@@ -25,10 +26,14 @@ import net from "net";
25
26
  * daemon to identify itself instead.
26
27
  */
27
28
  var daemon_exports = /* @__PURE__ */ __exportAll({
29
+ acceptedConnections: () => acceptedConnections,
30
+ connectedClients: () => connectedClients,
31
+ daemonOutdated: () => daemonOutdated,
28
32
  ensureManagedDatabase: () => ensureManagedDatabase,
29
33
  findRunningDaemon: () => findRunningDaemon,
30
34
  isDaemonAlive: () => isDaemonAlive,
31
35
  managedUrl: () => managedUrl,
36
+ outdatedDaemonNotice: () => outdatedDaemonNotice,
32
37
  pgliteLogTail: () => pgliteLogTail,
33
38
  resetManagedDatabase: () => resetManagedDatabase,
34
39
  resolveCliEntry: () => resolveCliEntry,
@@ -119,6 +124,156 @@ function isDaemonAlive(state) {
119
124
  socket.connect(state.identityPort, "127.0.0.1");
120
125
  });
121
126
  }
127
+ /**
128
+ * Whether a daemon `started` by one CLI should give way to the CLI `running` now.
129
+ *
130
+ * The database proxy runs inside the daemon, and the daemon outlives every
131
+ * command — so the proxy a project gets is whichever CLI started the daemon
132
+ * last, not the one it upgraded to. True for a daemon from before daemons were
133
+ * stamped, for an older release, and for another build of the same release
134
+ * (two canaries, a canary and its release): canary hashes do not sort by date,
135
+ * and this CLI's proxy is the one it can vouch for. False for a newer release,
136
+ * which an older CLI leaves alone, and when this CLI cannot read its own
137
+ * version.
138
+ */
139
+ function daemonOutdated(started, running) {
140
+ const core = (version) => {
141
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
142
+ return match ? match.slice(1, 4).map(Number) : null;
143
+ };
144
+ const ours = core(running);
145
+ if (!ours || started === running) return false;
146
+ const theirs = started === void 0 ? null : core(started);
147
+ if (!theirs) return true;
148
+ for (let i = 0; i < 3; i++) if (theirs[i] !== ours[i]) return theirs[i] < ours[i];
149
+ return true;
150
+ }
151
+ /**
152
+ * Send the daemon one command over its identity socket, and get the reply line.
153
+ *
154
+ * `null` for anything that is not the daemon answering: no connection, another
155
+ * token, no reply in time. The identity line comes first, so a stranger on a
156
+ * recycled port is never mistaken for it.
157
+ */
158
+ function askDaemon(state, command, timeoutMs = IDENTITY_TIMEOUT_MS) {
159
+ return new Promise((resolve) => {
160
+ const socket = new net.Socket();
161
+ let buffered = "";
162
+ let identified = false;
163
+ const settle = (reply) => {
164
+ socket.removeAllListeners();
165
+ socket.destroy();
166
+ resolve(reply);
167
+ };
168
+ socket.setTimeout(timeoutMs);
169
+ socket.once("timeout", () => settle(null));
170
+ socket.once("error", () => settle(null));
171
+ socket.once("close", () => settle(null));
172
+ socket.on("data", (chunk) => {
173
+ buffered += chunk.toString("utf8");
174
+ let newline = buffered.indexOf("\n");
175
+ while (newline !== -1) {
176
+ const line = buffered.slice(0, newline).trim();
177
+ buffered = buffered.slice(newline + 1);
178
+ if (identified) return settle(line);
179
+ if (line !== `rebase-dev-db ${state.token}`) return settle(null);
180
+ identified = true;
181
+ socket.write(`${command}\n`);
182
+ newline = buffered.indexOf("\n");
183
+ }
184
+ });
185
+ socket.connect(state.identityPort, "127.0.0.1");
186
+ });
187
+ }
188
+ /**
189
+ * How many of `lsof -Fn`'s sockets are connections accepted on `ports`.
190
+ *
191
+ * Each socket is a line `n<local>-><remote>`. The daemon's accepted client
192
+ * connections have one of its public ports on the local side; its own
193
+ * connections to the socket server behind the proxy are on other ports.
194
+ */
195
+ function acceptedConnections(lsofOutput, ports) {
196
+ let count = 0;
197
+ for (const line of lsofOutput.split("\n")) {
198
+ if (!line.startsWith("n") || !line.includes("->")) continue;
199
+ const local = line.slice(1, line.indexOf("->"));
200
+ if (ports.includes(Number(local.slice(local.lastIndexOf(":") + 1)))) count++;
201
+ }
202
+ return count;
203
+ }
204
+ /**
205
+ * The connections the operating system says the daemon has accepted, or `null`
206
+ * when it cannot be asked.
207
+ *
208
+ * For a daemon from before the `clients` command: it cannot be asked itself.
209
+ * `lsof` ships with macOS and most Linux installs; where it is missing, the
210
+ * answer is "cannot tell", and the caller does not restart anything.
211
+ */
212
+ function osAcceptedConnections(state) {
213
+ const ports = [state.port, ...Object.values(state.databases ?? {}).map((database) => database.port)];
214
+ return new Promise((resolve) => {
215
+ execFile("lsof", [
216
+ "-nP",
217
+ "-a",
218
+ "-p",
219
+ String(state.pid),
220
+ `-iTCP:${ports.join(",")}`,
221
+ "-sTCP:ESTABLISHED",
222
+ "-Fn"
223
+ ], { timeout: 3e3 }, (error, stdout) => {
224
+ if (error && !(error.code === 1 && stdout.trim() === "")) return resolve(null);
225
+ resolve(acceptedConnections(stdout, ports));
226
+ });
227
+ });
228
+ }
229
+ /**
230
+ * How many clients are connected to the daemon, over all its databases, or
231
+ * `null` when that cannot be known.
232
+ *
233
+ * Asked of the daemon first (`clients`). A daemon from before that command
234
+ * answers `error unknown command`, and then the operating system is asked.
235
+ */
236
+ async function connectedClients(state) {
237
+ const reply = await askDaemon(state, "clients");
238
+ if (reply === null) return null;
239
+ if (reply.startsWith("ok ")) {
240
+ const count = Number(reply.slice(3));
241
+ return Number.isInteger(count) && count >= 0 ? count : null;
242
+ }
243
+ return osAcceptedConnections(state);
244
+ }
245
+ /**
246
+ * What to say about a daemon an older CLI started that cannot be restarted
247
+ * here, because something is connected to it — or nothing can say whether
248
+ * anything is.
249
+ */
250
+ function outdatedDaemonNotice(state, running, clients) {
251
+ return [`⚠ The development database was started by ${state.cliVersion ? `Rebase CLI ${state.cliVersion}` : "an earlier Rebase CLI"}, and ${clients === null ? "this CLI cannot tell whether anything is connected to it" : `${clients} client${clients === 1 ? " is" : "s are"} connected to it`}, so it keeps running with that CLI's database proxy rather than this one's (${running}).`, " Stop what uses it — `rebase dev`, `rebase console`, a database GUI — then run `rebase db stop`. The next command starts it again, on this CLI. Data is kept."];
252
+ }
253
+ /** Said once per daemon in a process: every command that needs the database asks again. */
254
+ var outdatedNoticeShown = /* @__PURE__ */ new Set();
255
+ /**
256
+ * Restart a daemon an older CLI started, when nothing is connected to it.
257
+ *
258
+ * Returns whether it was stopped — the caller then starts a fresh one. With a
259
+ * client connected, stopping would cut it off mid-session, so it says once
260
+ * what to do instead and leaves the daemon serving.
261
+ */
262
+ async function giveWayToThisCli(projectRoot, state, options) {
263
+ const running = cliVersion();
264
+ if (!daemonOutdated(state.cliVersion, running)) return false;
265
+ const clients = await connectedClients(state);
266
+ if (clients === 0) {
267
+ if (!options.quiet) options.onProgress?.(`Restarting the development database: ${state.cliVersion ? `Rebase CLI ${state.cliVersion}` : "an earlier Rebase CLI"} started it, and this is ${running}. Data is kept.`);
268
+ return stopManagedDatabase(projectRoot);
269
+ }
270
+ const said = `${path.resolve(projectRoot)}\0${state.pid}`;
271
+ if (!outdatedNoticeShown.has(said)) {
272
+ outdatedNoticeShown.add(said);
273
+ (options.onNotice ?? ((line) => console.error(line)))(outdatedDaemonNotice(state, running, clients).join("\n"));
274
+ }
275
+ return false;
276
+ }
122
277
  /** The running daemon for this project, or `null`. Never starts anything. */
123
278
  async function findRunningDaemon(projectRoot) {
124
279
  const state = readState(projectRoot);
@@ -226,7 +381,7 @@ async function adoptWithAdditional(state, started, keys) {
226
381
  async function ensureManagedDatabase(projectRoot, options = {}) {
227
382
  const keys = options.additionalKeys ?? [];
228
383
  const existing = await findRunningDaemon(projectRoot);
229
- if (existing) return adoptWithAdditional(existing, false, keys);
384
+ if (existing && !await giveWayToThisCli(projectRoot, existing, options)) return adoptWithAdditional(existing, false, keys);
230
385
  fs.mkdirSync(devDbDir(projectRoot), { recursive: true });
231
386
  ensureGitignore(projectRoot);
232
387
  if (!acquireStartLock(projectRoot, START_TIMEOUT_MS)) {
@@ -371,4 +526,4 @@ function ensureGitignore(projectRoot) {
371
526
  //#endregion
372
527
  export { resolveCliEntry as a, managedUrl as i, ensureManagedDatabase as n, resolveSpawn as o, findRunningDaemon as r, daemon_exports as t };
373
528
 
374
- //# sourceMappingURL=daemon-CknkDjd6.js.map
529
+ //# sourceMappingURL=daemon-Cp7oNXth.js.map