@rebasepro/cli 0.25.1 → 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 (151) hide show
  1. package/README.md +2 -1
  2. package/bin/rebase.js +143 -4
  3. package/dist/{agent-setup-BYb2Tbdz.js → agent-setup-D9mHRZ5R.js} +125 -3
  4. package/dist/agent-setup-D9mHRZ5R.js.map +1 -0
  5. package/dist/{api-keys-CSMbsmAn.js → api-keys-CyJ26KEJ.js} +41 -9
  6. package/dist/api-keys-CyJ26KEJ.js.map +1 -0
  7. package/dist/{apps-iBXNZhVN.js → apps-CnLDV5b6.js} +6 -6
  8. package/dist/{apps-iBXNZhVN.js.map → apps-CnLDV5b6.js.map} +1 -1
  9. package/dist/{atlas-dev-database-BusbHP6n.js → atlas-dev-database-BODqdCWt.js} +2 -2
  10. package/dist/{atlas-dev-database-BusbHP6n.js.map → atlas-dev-database-BODqdCWt.js.map} +1 -1
  11. package/dist/{auth-DRsTWPky.js → auth-dII2jCO1.js} +10 -7
  12. package/dist/auth-dII2jCO1.js.map +1 -0
  13. package/dist/{branch-pointer-Bn_FjNld.js → branch-pointer-BQUF6lYa.js} +1 -1
  14. package/dist/{build-et7Rccxi.js → build-Cza7ZQrp.js} +6 -6
  15. package/dist/{build-et7Rccxi.js.map → build-Cza7ZQrp.js.map} +1 -1
  16. package/dist/{bundle-DlqkDYSe.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-B-ZIjHrA.js → cloud-BdnmqQaZ.js} +8527 -4340
  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 +10 -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 +15 -0
  50. package/dist/commands/skills.d.ts +4 -3
  51. package/dist/commands/storage.d.ts +4 -0
  52. package/dist/commands/verify.d.ts +96 -0
  53. package/dist/{consent-BaB-eQKG.js → consent-BD7F29T3.js} +2 -2
  54. package/dist/{consent-BaB-eQKG.js.map → consent-BD7F29T3.js.map} +1 -1
  55. package/dist/{console-BlD9kjFW.js → console-BXB6-8GT.js} +12 -6
  56. package/dist/console-BXB6-8GT.js.map +1 -0
  57. package/dist/{context-DHiiiJ5C.js → context-GUxkZm-S.js} +481 -24
  58. package/dist/context-GUxkZm-S.js.map +1 -0
  59. package/dist/{daemon-DDMRbz4T.js → daemon-Cp7oNXth.js} +2 -2
  60. package/dist/{daemon-DDMRbz4T.js.map → daemon-Cp7oNXth.js.map} +1 -1
  61. package/dist/{db-9P6MnmdC.js → db-KzlqddGD.js} +37 -30
  62. package/dist/db-KzlqddGD.js.map +1 -0
  63. package/dist/{derive-MTdClnkc.js → derive-ae9UOoOB.js} +2 -2
  64. package/dist/{derive-MTdClnkc.js.map → derive-ae9UOoOB.js.map} +1 -1
  65. package/dist/{dev-BDN_OKXA.js → dev-DTqVMgB3.js} +59 -32
  66. package/dist/dev-DTqVMgB3.js.map +1 -0
  67. package/dist/{dev-preflight-FT9GbJVj.js → dev-preflight-Cxv3TiD1.js} +2 -2
  68. package/dist/{dev-preflight-FT9GbJVj.js.map → dev-preflight-Cxv3TiD1.js.map} +1 -1
  69. package/dist/{dev-server-CIZwAfZa.js → dev-server-OJsUGZEB.js} +2 -2
  70. package/dist/{dev-server-CIZwAfZa.js.map → dev-server-OJsUGZEB.js.map} +1 -1
  71. package/dist/{doctor-D8YXz7df.js → doctor-BRWOlqeG.js} +13 -11
  72. package/dist/doctor-BRWOlqeG.js.map +1 -0
  73. package/dist/{eject-izG2KjMo.js → eject-DABcDnPN.js} +3 -3
  74. package/dist/{eject-izG2KjMo.js.map → eject-DABcDnPN.js.map} +1 -1
  75. package/dist/{fold-static-CybSay_W.js → fold-static-BnAK-anQ.js} +3 -3
  76. package/dist/{fold-static-CybSay_W.js.map → fold-static-BnAK-anQ.js.map} +1 -1
  77. package/dist/generate-view-IFwCADvb.js +662 -0
  78. package/dist/generate-view-IFwCADvb.js.map +1 -0
  79. package/dist/{generate_sdk-B4R65oUV.js → generate_sdk-BvcWvoUh.js} +33 -7
  80. package/dist/generate_sdk-BvcWvoUh.js.map +1 -0
  81. package/dist/index.es.js +57 -38
  82. package/dist/index.es.js.map +1 -1
  83. package/dist/{init-DYaeppEN.js → init-CaU6GhwI.js} +8 -8
  84. package/dist/{init-DYaeppEN.js.map → init-CaU6GhwI.js.map} +1 -1
  85. package/dist/last-dev-database-DpW5dfoX.js +2 -0
  86. package/dist/{manifest-nvPdLlJY.js → manifest-BtzDy7KQ.js} +36 -3
  87. package/dist/{manifest-nvPdLlJY.js.map → manifest-BtzDy7KQ.js.map} +1 -1
  88. package/dist/{normalize-imports-BYw8ad87.js → normalize-imports-D0zXlM6g.js} +3 -3
  89. package/dist/{normalize-imports-BYw8ad87.js.map → normalize-imports-D0zXlM6g.js.map} +1 -1
  90. package/dist/{path-flags-BFgG9V6y.js → path-flags-DNvtEi2-.js} +5 -4
  91. package/dist/path-flags-DNvtEi2-.js.map +1 -0
  92. package/dist/{prepare-1rVNd2PF.js → prepare-CTw9NEkh.js} +4 -4
  93. package/dist/{prepare-1rVNd2PF.js.map → prepare-CTw9NEkh.js.map} +1 -1
  94. package/dist/{project-g4K6kV1t.js → project-CasK56vN.js} +2 -2
  95. package/dist/{project-g4K6kV1t.js.map → project-CasK56vN.js.map} +1 -1
  96. package/dist/resolve-D647F1LA.js +2 -0
  97. package/dist/{resources-CovP8AZ-.js → resources-CPSC5Kcu.js} +4 -4
  98. package/dist/{resources-CovP8AZ-.js.map → resources-CPSC5Kcu.js.map} +1 -1
  99. package/dist/{schema-nuxvvc50.js → schema-BSXwZSiI.js} +9 -7
  100. package/dist/schema-BSXwZSiI.js.map +1 -0
  101. package/dist/{skills-DeUIsZcT.js → skills-BU2flMOy.js} +52 -7
  102. package/dist/skills-BU2flMOy.js.map +1 -0
  103. package/dist/{start-RzZqDroW.js → start-mVb6upwY.js} +5 -5
  104. package/dist/{start-RzZqDroW.js.map → start-mVb6upwY.js.map} +1 -1
  105. package/dist/static-syntax-check.d.ts +64 -0
  106. package/dist/{status-D9Zm88NZ.js → status-BREvtzJm.js} +7 -7
  107. package/dist/{status-D9Zm88NZ.js.map → status-BREvtzJm.js.map} +1 -1
  108. package/dist/{storage-BB3umRfw.js → storage-DtOXGflp.js} +159 -15
  109. package/dist/storage-DtOXGflp.js.map +1 -0
  110. package/dist/{telemetry-HfU5uAh1.js → telemetry-CgwJPVMO.js} +2 -2
  111. package/dist/{telemetry-HfU5uAh1.js.map → telemetry-CgwJPVMO.js.map} +1 -1
  112. package/dist/{telemetry-TseNLk6X.js → telemetry-DwK5RQJl.js} +4 -4
  113. package/dist/{telemetry-TseNLk6X.js.map → telemetry-DwK5RQJl.js.map} +1 -1
  114. package/dist/{upgrade-apiPKKE_.js → upgrade-B7sVwL8S.js} +4 -4
  115. package/dist/{upgrade-apiPKKE_.js.map → upgrade-B7sVwL8S.js.map} +1 -1
  116. package/dist/utils/collections-dir.d.ts +14 -0
  117. package/dist/utils/path-flags.d.ts +2 -1
  118. package/dist/utils/tool-stdio.d.ts +1 -1
  119. package/dist/verify-D7UsV_qe.js +730 -0
  120. package/dist/verify-D7UsV_qe.js.map +1 -0
  121. package/package.json +9 -7
  122. package/templates/eject/backend/src/index.ts +9 -1
  123. package/templates/generated-schemas/ecommerce.ts +3 -0
  124. package/templates/overlays/baas/ai-instructions.md +13 -0
  125. package/templates/template/.claude/hooks/post-edit-collection.sh +99 -0
  126. package/templates/template/.claude/settings.json +17 -0
  127. package/templates/template/.env.example +10 -1
  128. package/templates/template/ai-instructions.md +21 -1
  129. package/templates/template/config/collections/presets/ecommerce/index.ts +23 -0
  130. package/templates/template/docker-compose.yml +4 -1
  131. package/templates/template/frontend/package.json +1 -0
  132. package/templates/views/AppView.tsx.tmpl +138 -0
  133. package/templates/views/EntityView.tsx.tmpl +64 -0
  134. package/templates/views/index.ts.tmpl +8 -0
  135. package/dist/agent-setup-BYb2Tbdz.js.map +0 -1
  136. package/dist/api-keys-CSMbsmAn.js.map +0 -1
  137. package/dist/auth-DRsTWPky.js.map +0 -1
  138. package/dist/bundle-DlqkDYSe.js.map +0 -1
  139. package/dist/cloud-B-ZIjHrA.js.map +0 -1
  140. package/dist/console-BlD9kjFW.js.map +0 -1
  141. package/dist/context-DHiiiJ5C.js.map +0 -1
  142. package/dist/db-9P6MnmdC.js.map +0 -1
  143. package/dist/dev-BDN_OKXA.js.map +0 -1
  144. package/dist/doctor-D8YXz7df.js.map +0 -1
  145. package/dist/generate_sdk-B4R65oUV.js.map +0 -1
  146. package/dist/last-dev-database-B_75fxZC.js +0 -2
  147. package/dist/path-flags-BFgG9V6y.js.map +0 -1
  148. package/dist/resolve-BQUAC3Wi.js +0 -2
  149. package/dist/schema-nuxvvc50.js.map +0 -1
  150. package/dist/skills-DeUIsZcT.js.map +0 -1
  151. package/dist/storage-BB3umRfw.js.map +0 -1
@@ -0,0 +1,138 @@
1
+ /**
2
+ * __LABEL__ — a screen of the admin panel, at /__SLUG__.
3
+ *
4
+ * Scaffolded by `rebase generate view __SLUG__` and listed in `./index.ts`.
5
+ * It renders inside the admin shell, so the signed-in user, the sidebar, the
6
+ * router and the data layer are already there: read and write through
7
+ * `useData()` and the collection's security rules and callbacks apply.
8
+ *
9
+ * Replace the body with your screen. The `rebase-custom-views` agent skill
10
+ * documents the contract and the design tokens.
11
+ */
12
+ import { useCallback, useEffect, useState } from "react";
13
+ import type { Entity } from "@rebasepro/types";
14
+ import { useAuthController, useData, useSnackbarController } from "@rebasepro/app";
15
+ import {
16
+ Button,
17
+ Card,
18
+ Chip,
19
+ CircularProgressCenter,
20
+ Container,
21
+ Sheet,
22
+ Tab,
23
+ Table,
24
+ TableBody,
25
+ TableCell,
26
+ TableHeader,
27
+ TableRow,
28
+ Tabs,
29
+ Typography
30
+ } from "@rebasepro/ui";
31
+
32
+ /** The collection this screen reads. Change it to the one you need. */
33
+ const COLLECTION = "__COLLECTION__";
34
+
35
+ export function __COMPONENT__() {
36
+ const data = useData();
37
+ const { user } = useAuthController();
38
+ const snackbar = useSnackbarController();
39
+
40
+ const [rows, setRows] = useState<Entity[] | undefined>();
41
+ const [error, setError] = useState<string | undefined>();
42
+ const [tab, setTab] = useState("rows");
43
+ const [selected, setSelected] = useState<Entity | undefined>();
44
+
45
+ const load = useCallback(async () => {
46
+ try {
47
+ const { data: found } = await data.collection(COLLECTION).find({ limit: 20 });
48
+ setRows(found);
49
+ setError(undefined);
50
+ } catch (e: unknown) {
51
+ setError(e instanceof Error ? e.message : String(e));
52
+ }
53
+ }, [data]);
54
+
55
+ useEffect(() => {
56
+ void load();
57
+ }, [load]);
58
+
59
+ const columns = rows?.[0] ? Object.keys(rows[0].values).slice(0, 3) : [];
60
+
61
+ return (
62
+ <Container maxWidth="6xl" className="py-8 flex flex-col gap-4">
63
+ <div className="flex items-start justify-between gap-4">
64
+ <div className="flex flex-col gap-1">
65
+ <Typography variant="h5">__LABEL_JSX__</Typography>
66
+ <Typography variant="body2" color="secondary">
67
+ Signed in as {user?.displayName ?? user?.email ?? "an anonymous user"}
68
+ </Typography>
69
+ </div>
70
+ <Button color="primary" onClick={() => load().then(() => snackbar.open({ type: "success", message: "Refreshed" }))}>
71
+ Refresh
72
+ </Button>
73
+ </div>
74
+
75
+ <Tabs value={tab} onValueChange={setTab}>
76
+ <Tab value="rows">Rows</Tab>
77
+ <Tab value="summary">Summary</Tab>
78
+ </Tabs>
79
+
80
+ {error && <Typography color="error">{error}</Typography>}
81
+ {!error && !rows && <CircularProgressCenter/>}
82
+
83
+ {rows && tab === "rows" && (
84
+ <Card className="overflow-hidden">
85
+ <Table>
86
+ <TableHeader>
87
+ <TableCell>id</TableCell>
88
+ {columns.map(column => <TableCell key={column}>{column}</TableCell>)}
89
+ </TableHeader>
90
+ <TableBody>
91
+ {rows.map(row => (
92
+ <TableRow key={String(row.id)} onClick={() => setSelected(row)}>
93
+ <TableCell>{String(row.id)}</TableCell>
94
+ {columns.map(column => <TableCell key={column}>{display(row.values[column])}</TableCell>)}
95
+ </TableRow>
96
+ ))}
97
+ </TableBody>
98
+ </Table>
99
+ </Card>
100
+ )}
101
+
102
+ {rows && tab === "summary" && (
103
+ <div className="grid grid-cols-2 gap-4">
104
+ <Card className="p-4 flex flex-col gap-1">
105
+ <Typography variant="caption" color="secondary">Rows loaded</Typography>
106
+ <Typography variant="stat">{rows.length}</Typography>
107
+ </Card>
108
+ <Card className="p-4 flex flex-col gap-2 items-start">
109
+ <Typography variant="caption" color="secondary">Collection</Typography>
110
+ <Chip size="small">{COLLECTION}</Chip>
111
+ </Card>
112
+ </div>
113
+ )}
114
+
115
+ <Sheet open={selected !== undefined} onOpenChange={open => !open && setSelected(undefined)} title="Row">
116
+ <div className="w-[28rem] max-w-full h-full overflow-auto p-6 flex flex-col gap-3">
117
+ <Typography variant="h6">{selected ? String(selected.id) : ""}</Typography>
118
+ {selected && Object.entries(selected.values).map(([key, value]) => (
119
+ <div key={key} className="flex flex-col gap-0.5">
120
+ <Typography variant="caption" color="secondary">{key}</Typography>
121
+ <Typography variant="body2">{display(value)}</Typography>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ </Sheet>
126
+ </Container>
127
+ );
128
+ }
129
+
130
+ /** A cell's text. Dates in `values` are `Date` objects in the admin. */
131
+ function display(value: unknown): string {
132
+ if (value === null || value === undefined) return "—";
133
+ if (value instanceof Date) return value.toLocaleString();
134
+ if (typeof value === "object") return JSON.stringify(value);
135
+ return String(value);
136
+ }
137
+
138
+ export default __COMPONENT__;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * __LABEL__ — a tab on every record of the `__COLLECTION__` collection.
3
+ *
4
+ * Scaffolded by `rebase generate view __SLUG__ --collection __COLLECTION__` and
5
+ * listed in that collection's `admin.entityViews` by path, so the backend,
6
+ * which reads the same collection file, never loads this one. The admin
7
+ * imports it lazily and renders the default export.
8
+ *
9
+ * The admin hands the component `EntityCustomViewParams`: the collection, the
10
+ * record (`entity`, absent while it is being created), its unsaved edits
11
+ * (`modifiedValues`) and the form (`formContext`). The `rebase-custom-views`
12
+ * agent skill documents the contract and the design tokens.
13
+ */
14
+ import type { EntityCustomViewParams } from "@rebasepro/cms-types";
15
+ import { useSnackbarController } from "@rebasepro/app";
16
+ import { Button, Card, Chip, Typography } from "@rebasepro/ui";
17
+
18
+ export function __COMPONENT__({ collection, entity, modifiedValues, formContext }: EntityCustomViewParams) {
19
+ const snackbar = useSnackbarController();
20
+ const values = modifiedValues ?? entity?.values ?? {};
21
+
22
+ if (!entity) {
23
+ return (
24
+ <div className="p-6">
25
+ <Typography variant="body2" color="secondary">
26
+ Save this {collection.singularName ?? collection.name} to see __label_jsx__.
27
+ </Typography>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ const copy = () => navigator.clipboard.writeText(JSON.stringify(values, null, 2))
33
+ .then(() => snackbar.open({ type: "success", message: "Copied" }));
34
+
35
+ return (
36
+ <div className="p-6 flex flex-col gap-4">
37
+ <div className="flex items-center justify-between gap-2">
38
+ <Typography variant="h6">__LABEL_JSX__</Typography>
39
+ {formContext.readOnly && <Chip size="small">Read only</Chip>}
40
+ </div>
41
+ <Card className="p-4 flex flex-col gap-3">
42
+ {Object.entries(values).map(([key, value]) => (
43
+ <div key={key} className="flex flex-col gap-0.5">
44
+ <Typography variant="caption" color="secondary">{key}</Typography>
45
+ <Typography variant="body2">{display(value)}</Typography>
46
+ </div>
47
+ ))}
48
+ </Card>
49
+ <div>
50
+ <Button color="neutral" variant="outlined" onClick={copy}>Copy as JSON</Button>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
55
+
56
+ /** A field's text. Dates in `values` are `Date` objects in the admin. */
57
+ function display(value: unknown): string {
58
+ if (value === null || value === undefined) return "—";
59
+ if (value instanceof Date) return value.toLocaleString();
60
+ if (typeof value === "object") return JSON.stringify(value);
61
+ return String(value);
62
+ }
63
+
64
+ export default __COMPONENT__;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The admin's own screens, handed to `<RebaseCMS views>` in `App.tsx`.
3
+ *
4
+ * `rebase generate view <slug>` adds a component file beside this one and an
5
+ * entry here. Edit the entries freely: `name` is the sidebar label, `icon` a
6
+ * Lucide name, `group` a sidebar section, `roles` who may see the screen.
7
+ */
8
+ import type { AppView } from "@rebasepro/cms-types";
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-setup-BYb2Tbdz.js","names":[],"sources":["../src/commands/agent-setup.ts"],"sourcesContent":["/**\n * Setting a project up for the AI coding assistants a developer uses: the\n * Rebase skills, and the Rebase MCP server in each assistant's own config.\n *\n * `rebase init` used to end by *printing* `rebase skills install`, and nothing\n * ran it — so a generated project had pointer files telling every assistant to\n * read skills that were not there, and an MCP server only Claude Code knew\n * about. This asks once, while the project is being made, pre-ticking the\n * assistants installed on the machine.\n */\nimport chalk from \"chalk\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport inquirer from \"inquirer\";\nimport {\n AGENT_KEYS,\n AGENTS,\n type AgentKey,\n detectInstalledAgents,\n installSkills,\n type LoadedSkill,\n loadBundledSkills\n} from \"./skills\";\nimport { detectPackageManager, type PackageManager } from \"../utils/package-manager\";\nimport { cliVersion } from \"../utils/version\";\n\n/** The name the server is registered under, in every config. */\nexport const MCP_SERVER_NAME = \"rebase\";\n\n/** The MCP server's package: a devDependency of the project, pinned with the rest. */\nexport const MCP_PACKAGE = \"@rebasepro/mcp\";\n\n/** The bin {@link MCP_PACKAGE} declares. */\nexport const MCP_BIN = \"rebase-mcp\";\n\n/** The command an assistant spawns, and its arguments. */\nexport interface McpLaunch {\n command: string;\n args: string[];\n}\n\n/**\n * How an assistant starts the Rebase MCP server: the project's own copy, through\n * the project's package manager.\n *\n * Not `npx -y @rebasepro/mcp`, which fetched the newest release on every first\n * start. The server drives the project's own CLI (`pnpm exec rebase …`) and calls\n * the project's own backend (`/api/admin/schema/plan`, …), whose versions the\n * lockfile pins — so a server from the registry ran ahead of both, at every\n * start, and downloaded before it could answer. `@rebasepro/mcp` is a\n * devDependency of the scaffold, pinned to the CLI's version like every other\n * `@rebasepro` package, and this runs that copy.\n *\n * `npx --no`, not `npx`: an assistant spawns the server with no TTY, and npx\n * then assumes \"yes\" and installs a missing package from the registry under the\n * name it was given. `--no` makes a missing install an error instead.\n */\nexport function mcpLaunch(pm: PackageManager): McpLaunch {\n return pm === \"npm\"\n ? { command: \"npx\", args: [\"--no\", MCP_BIN] }\n : { command: \"pnpm\", args: [\"exec\", MCP_BIN] };\n}\n\n/** What writing one MCP config did. */\nexport type McpWriteStatus =\n /** The file was created, or the server was added beside the ones in it. */\n | \"added\"\n /** A `rebase` server was already there. It is left exactly as it was. */\n | \"present\"\n /** The file exists but is not something this can edit without losing data. */\n | \"unreadable\";\n\nexport interface McpWriteResult {\n /** Project-relative path of the config file. */\n file: string;\n status: McpWriteStatus;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Add the server to a JSON config under `key`, keeping everything else in it.\n *\n * A file that does not parse as plain JSON is left alone: VS Code and Gemini\n * both accept comments in these files, and rewriting one through\n * `JSON.stringify` would delete them. Refusing costs one line of hand-editing;\n * the alternative silently costs the developer theirs.\n */\nfunction addJsonServer(filePath: string, key: string, entry: Record<string, unknown>): McpWriteStatus {\n let doc: Record<string, unknown> = {};\n if (fs.existsSync(filePath)) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filePath, \"utf-8\"));\n } catch {\n return \"unreadable\";\n }\n if (!isRecord(parsed)) return \"unreadable\";\n doc = parsed;\n }\n\n const servers = doc[key] ?? {};\n if (!isRecord(servers)) return \"unreadable\";\n if (MCP_SERVER_NAME in servers) return \"present\";\n\n doc[key] = { ...servers, [MCP_SERVER_NAME]: entry };\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, `${JSON.stringify(doc, null, 2)}\\n`, \"utf-8\");\n return \"added\";\n}\n\n/**\n * The Codex block, as TOML. Appended rather than merged: there is no TOML\n * parser here, and an append keeps every byte already in the file.\n */\nexport function renderCodexServer(projectDir: string, launch: McpLaunch): string {\n // A JSON string literal is a valid TOML basic string for these values.\n const str = (v: string) => JSON.stringify(v);\n return [\n `[mcp_servers.${MCP_SERVER_NAME}]`,\n `command = ${str(launch.command)}`,\n `args = [${launch.args.map(str).join(\", \")}]`,\n \"\",\n `[mcp_servers.${MCP_SERVER_NAME}.env]`,\n `REBASE_PROJECT_DIR = ${str(projectDir)}`,\n \"\"\n ].join(\"\\n\");\n}\n\nfunction addCodexServer(filePath: string, projectDir: string, launch: McpLaunch): McpWriteStatus {\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, \"utf-8\") : \"\";\n const table = new RegExp(`^\\\\s*\\\\[mcp_servers\\\\.(?:${MCP_SERVER_NAME}|\"${MCP_SERVER_NAME}\")\\\\]`, \"m\");\n if (table.test(existing)) return \"present\";\n\n const separator = existing === \"\" ? \"\" : existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, `${existing}${separator}${renderCodexServer(projectDir, launch)}`, \"utf-8\");\n return \"added\";\n}\n\n/**\n * Register the Rebase MCP server in the agent's project-level config, started\n * through the project's package manager (see {@link mcpLaunch}).\n *\n * Returns null for an agent with no project-level config to write.\n */\nexport function writeMcpConfig(\n agentKey: AgentKey,\n projectDir: string,\n pm: PackageManager = detectPackageManager(projectDir)\n): McpWriteResult | null {\n const mcp = AGENTS[agentKey].mcp;\n if (!mcp) return null;\n\n const filePath = path.join(projectDir, mcp.file);\n const env = { REBASE_PROJECT_DIR: mcp.projectDir };\n const { command, args } = mcpLaunch(pm);\n const write = (): McpWriteStatus => {\n switch (mcp.format) {\n case \"mcpServers\":\n return addJsonServer(filePath, \"mcpServers\", { command, args, env });\n case \"vscode\":\n // VS Code's own shape: `servers`, and an explicit transport.\n return addJsonServer(filePath, \"servers\", { type: \"stdio\", command, args, env });\n case \"codexToml\":\n return addCodexServer(filePath, mcp.projectDir, { command, args });\n }\n };\n return { file: mcp.file, status: write() };\n}\n\n/** What making sure the project depends on the MCP server did. */\nexport type McpDependencyStatus =\n /** Added to `devDependencies`, at this CLI's version: an install is due. */\n | \"added\"\n /** Already a dependency. Left at the version the project chose. */\n | \"present\"\n /** No `package.json`, or one that is not plain JSON: left alone. */\n | \"unreadable\";\n\n/**\n * Make the project depend on {@link MCP_PACKAGE}, which the config written by\n * {@link writeMcpConfig} runs from the project's own install.\n *\n * Pinned to this CLI's version, as `rebase init` pins every `@rebasepro`\n * package; a project that already lists it keeps its own choice.\n */\nexport function ensureMcpDependency(projectDir: string): McpDependencyStatus {\n const manifestPath = path.join(projectDir, \"package.json\");\n if (!fs.existsSync(manifestPath)) return \"unreadable\";\n const text = fs.readFileSync(manifestPath, \"utf-8\");\n let manifest: unknown;\n try {\n manifest = JSON.parse(text);\n } catch {\n return \"unreadable\";\n }\n if (!isRecord(manifest)) return \"unreadable\";\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = manifest[field];\n if (isRecord(deps) && MCP_PACKAGE in deps) return \"present\";\n }\n const devDependencies = isRecord(manifest.devDependencies) ? manifest.devDependencies : {};\n const version = cliVersion();\n manifest.devDependencies = { ...devDependencies, [MCP_PACKAGE]: version === \"unknown\" ? \"latest\" : version };\n // The file's own indentation, so the change is one line in a diff.\n const indent = /\\n([ \\t]+)\"/.exec(text)?.[1] ?? \" \";\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, indent)}\\n`, \"utf-8\");\n return \"added\";\n}\n\n/**\n * Point the scaffold's `.mcp.json` at the project's package manager.\n *\n * The template ships the pnpm spelling (`pnpm exec rebase-mcp`); an npm project\n * has no pnpm to run it with. Only the entry the template wrote is rewritten.\n */\nexport function retargetScaffoldMcpConfig(projectDir: string, pm: PackageManager): void {\n const filePath = path.join(projectDir, \".mcp.json\");\n if (!fs.existsSync(filePath)) return;\n let doc: unknown;\n try {\n doc = JSON.parse(fs.readFileSync(filePath, \"utf-8\"));\n } catch {\n return;\n }\n const servers = isRecord(doc) ? doc.mcpServers : undefined;\n const entry = isRecord(servers) ? servers[MCP_SERVER_NAME] : undefined;\n const shipped = mcpLaunch(\"pnpm\");\n if (!isRecord(doc) || !isRecord(servers) || !isRecord(entry)) return;\n if (entry.command !== shipped.command || JSON.stringify(entry.args) !== JSON.stringify(shipped.args)) return;\n const wanted = mcpLaunch(pm);\n if (wanted.command === shipped.command && JSON.stringify(wanted.args) === JSON.stringify(shipped.args)) return;\n servers[MCP_SERVER_NAME] = { ...entry, command: wanted.command, args: wanted.args };\n fs.writeFileSync(filePath, `${JSON.stringify(doc, null, 4)}\\n`, \"utf-8\");\n}\n\n/**\n * Whether the agent's project-level config already registers the Rebase\n * server: `null` for an agent that has no such config, `false` when the file\n * is missing or does not name it. Reads only — for the hint `rebase skills\n * install` prints when it was not asked to write one.\n */\nexport function mcpServerRegistered(agentKey: AgentKey, projectDir: string): boolean | null {\n const mcp = AGENTS[agentKey].mcp;\n if (!mcp) return null;\n const filePath = path.join(projectDir, mcp.file);\n if (!fs.existsSync(filePath)) return false;\n const text = fs.readFileSync(filePath, \"utf-8\");\n if (mcp.format === \"codexToml\") {\n return new RegExp(`^\\\\s*\\\\[mcp_servers\\\\.(?:${MCP_SERVER_NAME}|\"${MCP_SERVER_NAME}\")\\\\]`, \"m\").test(text);\n }\n try {\n const doc: unknown = JSON.parse(text);\n const servers = isRecord(doc) ? doc[mcp.format === \"vscode\" ? \"servers\" : \"mcpServers\"] : undefined;\n return isRecord(servers) && MCP_SERVER_NAME in servers;\n } catch {\n // Not plain JSON (comments): cannot tell, so do not nag.\n return null;\n }\n}\n\n/** What `configureAgents` did for one agent. */\nexport interface AgentSetupResult {\n agent: AgentKey;\n /** Skills written, or null when the bundle could not be loaded. */\n skills: number | null;\n /** An earlier agent in the same run that already wrote the skills directory. */\n sharedWith?: AgentKey;\n mcp: McpWriteResult | null;\n}\n\n/**\n * Install the skills and register the MCP server for each agent.\n *\n * Never throws for a missing skills bundle — the scaffold it runs inside must\n * not fail over its assistants — and reports it as `skills: null` instead.\n */\nexport function configureAgents(\n agents: readonly AgentKey[],\n projectDir: string,\n load: () => LoadedSkill[] = loadBundledSkills,\n pm: PackageManager = detectPackageManager(projectDir)\n): { results: AgentSetupResult[]; skillsError?: string; mcpDependency?: McpDependencyStatus } {\n let skillsError: string | undefined;\n let installed: ReturnType<typeof installSkills> = [];\n try {\n installed = installSkills(agents, load(), projectDir);\n } catch (err) {\n skillsError = err instanceof Error ? err.message : String(err);\n }\n\n const results = agents.map((agent): AgentSetupResult => {\n const skills = installed.find(r => r.agent === agent);\n return {\n agent,\n skills: skills ? skills.skills : null,\n sharedWith: skills?.sharedWith,\n mcp: writeMcpConfig(agent, projectDir, pm)\n };\n });\n // A config that runs the project's copy of the server needs the project to\n // have one.\n const registered = results.some(r => r.mcp?.status === \"added\" || r.mcp?.status === \"present\");\n return { results, skillsError, ...(registered && { mcpDependency: ensureMcpDependency(projectDir) }) };\n}\n\n/** The line to print when {@link ensureMcpDependency} changed or could not change the manifest. */\nexport function mcpDependencyNote(status: McpDependencyStatus | undefined, pm: PackageManager): string | undefined {\n if (status === \"added\") {\n return `Added ${MCP_PACKAGE} to devDependencies — run \\`${pm} install\\` before your assistant starts it.`;\n }\n if (status === \"unreadable\") {\n return `Could not add ${MCP_PACKAGE} to package.json — add it to devDependencies by hand; the MCP config runs the project's own copy.`;\n }\n return undefined;\n}\n\n/** One line per agent, for the scaffold's output. */\nexport function printAgentSetup(\n { results, skillsError, mcpDependency }: ReturnType<typeof configureAgents>,\n pm: PackageManager = \"pnpm\"\n): void {\n for (const result of results) {\n const agent = AGENTS[result.agent];\n const parts: string[] = [];\n\n if (result.sharedWith) {\n parts.push(`skills in ${chalk.gray(agent.targetDir)} ${chalk.gray(`(shared with ${AGENTS[result.sharedWith].label})`)}`);\n } else if (result.skills !== null) {\n parts.push(`${result.skills} skills in ${chalk.gray(agent.targetDir)}`);\n }\n\n if (result.mcp?.status === \"added\") {\n parts.push(`MCP server in ${chalk.gray(result.mcp.file)}`);\n } else if (result.mcp?.status === \"present\") {\n parts.push(`MCP server already in ${chalk.gray(result.mcp.file)}`);\n }\n\n // A tick is a claim that something was written; with nothing, it isn't.\n const mark = parts.length ? chalk.green(\"✓\") : chalk.yellow(\"!\");\n console.log(` ${mark} ${chalk.bold(agent.label)}${parts.length ? ` — ${parts.join(\", \")}` : \"\"}`);\n\n if (result.mcp?.status === \"unreadable\") {\n console.log(chalk.yellow(` ${result.mcp.file} is not plain JSON, so it was left alone — add the \"${MCP_SERVER_NAME}\" server by hand.`));\n } else if (!result.mcp) {\n console.log(chalk.gray(` ${agent.label} has no project-level MCP config — add @rebasepro/mcp in its MCP settings.`));\n }\n }\n\n const dependencyNote = mcpDependencyNote(mcpDependency, pm);\n if (dependencyNote) console.log(chalk.gray(` ${dependencyNote}`));\n\n if (skillsError) {\n console.log(chalk.yellow(` Could not install the Rebase skills: ${skillsError}`));\n console.log(chalk.yellow(\" Run `rebase skills install` in the project once dependencies are installed.\"));\n }\n}\n\n/**\n * Ask whether to set up AI agents, then which, pre-ticking what is installed.\n *\n * Interactive only; the caller has already refused a non-TTY. Returns [] when\n * the developer says no or unticks everything.\n */\nexport async function promptForAgents(home?: string): Promise<AgentKey[]> {\n const { configure } = await inquirer.prompt([{\n type: \"confirm\",\n name: \"configure\",\n message: \"Set up Rebase skills and the Rebase MCP server for your AI coding agent(s)?\",\n default: true\n }]);\n if (!configure) return [];\n\n // Installed first, so the pre-ticked boxes are the ones under the cursor.\n const detected = detectInstalledAgents(home);\n const installed = [...detected.filter(a => a.found), ...detected.filter(a => !a.found)];\n console.log(\"\");\n console.log(\"Detecting installed AI coding agents...\");\n for (const { label, marker, found } of installed) {\n if (found) console.log(` ${chalk.green(\"✓\")} ${label} — ${marker}`);\n else if (marker) console.log(chalk.gray(` ✗ ${label} — ${marker} (not found)`));\n else console.log(chalk.gray(` · ${label} — can't be detected; tick it below if you use it`));\n }\n console.log(\"\");\n\n const { selectedAgents } = await inquirer.prompt([{\n type: \"checkbox\",\n name: \"selectedAgents\",\n message: \"Select agents to configure:\",\n choices: installed.map(({ key, label, found }) => {\n const { targetDir, mcp } = AGENTS[key];\n const writes = mcp ? `${targetDir} + ${mcp.file}` : targetDir;\n return { name: `${label} ${chalk.gray(`— ${writes}`)}`, short: label, value: key, checked: found };\n })\n }]);\n\n return AGENT_KEYS.filter(key => selectedAgents.includes(key));\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"api-keys-CSMbsmAn.js","names":[],"sources":["../src/commands/api-keys.ts"],"sourcesContent":["/**\n * CLI command: rebase api-keys <action>\n *\n * The project's service keys, through `/api/admin/api-keys`, authenticated with\n * the service key from `.env`.\n *\n * Subcommands:\n * list — List the service keys (masked): kind, scopes, roles\n * get — Show one key\n * create — Create a service key holding the scopes named on the line\n * revoke — Revoke a key\n * scopes — List the scopes this backend knows, and which the caller holds\n */\nimport chalk from \"chalk\";\nimport type arg from \"arg\";\nimport { createRebaseClient, RebaseApiError } from \"@rebasepro/client\";\nimport {\n ADMIN_SCOPES,\n DATA_PLANE_SCOPES,\n KEY_MANAGEMENT_SCOPES,\n parseScope,\n type ApiKeyMasked,\n type ApiKeyWithSecret,\n type CreateApiKeyRequest,\n type ScopeSummary\n} from \"@rebasepro/types\";\nimport {\n requireProjectRoot,\n readEnvFile\n} from \"../utils/project\";\nimport { parseCommandArgs, UsageError, wantsHelp } from \"../utils/args\";\nimport { unknownCommand } from \"../utils/unknown-command\";\nimport { cliUserAgent } from \"../utils/version\";\nimport { serverUnreachableHint } from \"../utils/dev-server\";\nimport fs from \"fs\";\nimport path from \"path\";\n\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const API_KEYS_SUBCOMMANDS = [\"list\", \"get\", \"create\", \"revoke\", \"scopes\"] as const;\n\n/* ═══════════════════════════════════════════════════════════════\n Env helper — reads SERVICE_KEY and PORT from .env\n ═══════════════════════════════════════════════════════════════ */\n\n/**\n * Was a hand-rolled `indexOf(\"=\")` loop. It keyed `export KEY=value` as\n * `export KEY` and carried a trailing `# comment` into the value — so a key\n * that was present read as absent, or reached an `Authorization` header with a\n * comment attached and came back 401. See `readEnvFile`.\n */\nconst loadEnv = readEnvFile;\n\n/**\n * Where this project's backend is — or why the CLI will not say.\n *\n * `configured` is an address the developer named; `running` is the one this\n * project's backend recorded when it bound, and is still serving on. There is\n * no third, guessed, answer: the request carries the project's service key —\n * the `admin` role, every scope — and an address the CLI only guessed is one it\n * would hand that key to without knowing whose server answers.\n */\nexport type BackendAddress =\n | { url: string; source: \"configured\" | \"running\" }\n | { url: null; reason: string };\n\n/** Whether a process with this pid is alive and ours to signal. */\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n // ESRCH: gone. EPERM: alive, but another user's — not this project's backend.\n return false;\n }\n}\n\n/**\n * The address the service key may be sent to. Shared with `rebase storage`.\n *\n * In order:\n *\n * 1. `REBASE_BASE_URL`, in the shell or in `.env` — the developer said where.\n * 2. `.rebase/state.json`, which this project's backend writes when it binds\n * and removes when it stops — and only while the pid it names is alive,\n * because a backend that was killed leaves the file behind.\n *\n * Nothing else. `.env`'s `PORT` was the fallback, and every scaffold writes\n * `PORT=3001`: with `rebase dev` stopped, `rebase api-keys list` sent this\n * project's service key to whatever listened on 3001 — on the tester's machine,\n * another project's server. `.rebase-dev-url` is not enough on its own either:\n * a `rebase dev` that was killed leaves it behind, pointing at a port some\n * other server may have taken since.\n */\nexport function resolveBackendAddress(env: Record<string, string>, projectRoot: string): BackendAddress {\n const configured = process.env.REBASE_BASE_URL?.trim() || env.REBASE_BASE_URL?.trim();\n if (configured) return { url: configured, source: \"configured\" };\n\n const state = readBackendState(projectRoot);\n if (state && isAlive(state.pid)) return { url: state.url, source: \"running\" };\n\n const last = state ? ` The last one recorded here, at ${state.url} (pid ${state.pid}), has stopped.` : \"\";\n return {\n url: null,\n reason: `This project's server is not running, so the service key was not sent anywhere.${last} ` +\n \"Start it with `rebase dev`. If it runs some other way — `rebase start`, a container — \" +\n \"set REBASE_BASE_URL in .env to its address.\"\n };\n}\n\n/** What `.rebase/state.json` says about the backend that wrote it, or null. */\nfunction readBackendState(projectRoot: string): { url: string; pid: number } | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(path.join(projectRoot, \".rebase\", \"state.json\"), \"utf-8\"));\n } catch {\n return null;\n }\n if (!isRecord(parsed) || typeof parsed.pid !== \"number\" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;\n const url = typeof parsed.baseUrl === \"string\" && parsed.baseUrl.trim()\n ? parsed.baseUrl.trim()\n : typeof parsed.port === \"number\" ? `http://localhost:${parsed.port}` : null;\n\n return url ? { url, pid: parsed.pid } : null;\n}\n\n/**\n * The address to send the service key to, or the end of the command.\n *\n * Exits before any request is made: the refusal is the point.\n */\nexport function requireBackendUrl(env: Record<string, string>, projectRoot: string): string {\n const address = resolveBackendAddress(env, projectRoot);\n if (address.url !== null) return address.url;\n\n console.error(chalk.red(\"✗ No server to send the request to.\"));\n console.error(chalk.gray(` ${address.reason}`));\n process.exit(1);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n The backend, through the SDK\n ═══════════════════════════════════════════════════════════════ */\n\ntype ProjectClient = ReturnType<typeof createRebaseClient>;\n\ninterface Connection {\n client: ProjectClient;\n baseUrl: string;\n /** The variable the service key was read from, for a hint that names it. */\n keyVariable: ServiceKeyVariable;\n}\n\n/** Where a service key is read from: the name a scaffold writes, or the older one. */\ntype ServiceKeyVariable = \"REBASE_SERVICE_KEY\" | \"SERVICE_KEY\";\n\n/**\n * An SDK client for this project's backend, holding the service key.\n *\n * The service key is the project's own machine identity — the `admin` role,\n * every scope — so it is what may list, mint and revoke service keys. It is a\n * static bearer token: no session to restore or refresh, and no socket.\n */\nfunction connect(): Connection {\n const projectRoot = requireProjectRoot();\n const env = loadEnv(projectRoot);\n const keyVariable: ServiceKeyVariable = env.SERVICE_KEY ? \"SERVICE_KEY\" : \"REBASE_SERVICE_KEY\";\n const serviceKey = env[keyVariable];\n\n if (!serviceKey) {\n console.error(chalk.red(\"✗ REBASE_SERVICE_KEY not found in .env — required to manage API keys.\"));\n process.exit(1);\n }\n const baseUrl = requireBackendUrl(env, projectRoot);\n\n const client = createRebaseClient({\n baseUrl,\n token: serviceKey,\n realtime: false,\n headers: { \"User-Agent\": cliUserAgent() },\n auth: { persistSession: false, autoRefresh: false }\n });\n return { client, baseUrl, keyVariable };\n}\n\n/**\n * What to print when a request fails: the server's error code and message,\n * which is what says why — `SCOPE_EXCEEDS_CREATOR`, `INVALID_SCOPES`,\n * `ROLE_EXCEEDS_CREATOR`, `KEY_MANAGEMENT_SCOPE` — and a hint where one helps.\n *\n * Exported so its tests can read the lines without a server.\n */\nexport function describeFailure(\n action: string,\n error: unknown,\n baseUrl: string,\n keyVariable: ServiceKeyVariable = \"REBASE_SERVICE_KEY\"\n): string[] {\n if (!(error instanceof RebaseApiError)) {\n return [`✗ ${action}: ${error instanceof Error ? error.message : String(error)}`];\n }\n if (error.status === 0 || error.code === \"NETWORK_ERROR\") {\n return [`✗ ${action}: ${error.message}`, ...serverUnreachableHint(baseUrl)];\n }\n\n const label = [error.code, error.status ? `(${error.status})` : undefined].filter(Boolean).join(\" \");\n const lines = [`✗ ${action}${label ? ` — ${label}` : \"\"}: ${error.message}`];\n\n if (error.status === 401) {\n lines.push(` The server at ${baseUrl} did not accept ${keyVariable} from .env — is it this project's server?`);\n }\n // The message names the target that is not there; what *is* there rides\n // in `details`, and is the thing to pick from.\n if (error.code === \"UNKNOWN_SCOPE_TARGET\" && isRecord(error.details)) {\n for (const [kind, title] of [[\"collections\", \"Collections\"], [\"buckets\", \"Buckets\"], [\"functions\", \"Functions\"]] as const) {\n const names = error.details[kind];\n if (Array.isArray(names) && names.length > 0) {\n lines.push(` ${title}: ${names.map(String).join(\", \")}`);\n }\n }\n }\n return lines;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fail(action: string, error: unknown, connection: Connection): never {\n for (const line of describeFailure(action, error, connection.baseUrl, connection.keyVariable)) {\n console.error(line.startsWith(\"✗\") ? chalk.red(line) : chalk.gray(line));\n }\n process.exit(1);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Entry\n ═══════════════════════════════════════════════════════════════ */\n\nexport async function apiKeysCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void> {\n // `--help` is answered before dispatch, never by a handler. `cli.ts` only\n // rewrites the subcommand to `\"--help\"` when none was named, so\n // `rebase api-keys list --help` used to *list the keys* and\n // `rebase api-keys revoke --help` sent `DELETE /api/admin/api-keys/--help`.\n if (!subcommand || subcommand === \"--help\" || wantsHelp(rawArgs)) {\n printApiKeysHelp();\n return;\n }\n\n switch (subcommand) {\n case \"list\":\n await listKeys(rawArgs);\n break;\n case \"get\":\n await getKey(rawArgs);\n break;\n case \"create\":\n await createKey(rawArgs);\n break;\n case \"revoke\":\n await revokeKey(rawArgs);\n break;\n case \"scopes\":\n await listScopes(rawArgs);\n break;\n default:\n // The shape every other family uses. This one printed a bare line\n // to stderr *and* the whole help page to stdout, so a typo wrote\n // twenty lines to a stream a `--json` caller reads, with no `✗`, no\n // pointer at `--help`, and no did-you-mean — `craete` is one\n // transposition from `create`.\n unknownCommand(subcommand, API_KEYS_SUBCOMMANDS, \"api-keys\");\n }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Showing a key\n ═══════════════════════════════════════════════════════════════ */\n\nfunction keyStatus(key: ApiKeyMasked, now: Date): string {\n if (key.revoked_at) return chalk.red(\"revoked\");\n if (key.expires_at && new Date(key.expires_at) < now) return chalk.yellow(\"expired\");\n return chalk.green(\"active\");\n}\n\n/**\n * The lines that describe one key: what it is, what it may do and whom it\n * runs as.\n *\n * A service key runs as the RLS role `service` plus its own `roles`; a\n * personal key runs as its owner's roles as they are when it is used, so it\n * has none of its own to show.\n *\n * Exported so its tests can read the lines without a server.\n */\nexport function describeKey(key: ApiKeyMasked, now: Date = new Date()): string[] {\n const field = (label: string, value: string) => ` ${chalk.gray(`${label}:`.padEnd(12))}${value}`;\n const roles = key.kind === \"personal\"\n ? `its owner's (${key.owner_uid ?? \"unknown\"})`\n : [\"service\", ...key.roles].join(\", \");\n\n const lines = [\n ` ${chalk.bold(key.name)} ${chalk.gray(`[${key.key_prefix}•••]`)} ${keyStatus(key, now)}`,\n field(\"ID\", key.id),\n field(\"Kind\", key.kind),\n field(\"Scopes\", key.scopes.length > 0 ? key.scopes.join(\", \") : chalk.gray(\"none\")),\n field(\"Roles\", roles)\n ];\n if (key.rate_limit !== null) lines.push(field(\"Rate limit\", `${key.rate_limit} requests / 15 min`));\n if (key.expires_at) lines.push(field(\"Expires\", new Date(key.expires_at).toLocaleDateString()));\n lines.push(field(\"Created\", new Date(key.created_at).toLocaleDateString()));\n if (key.last_used_at) lines.push(field(\"Last used\", new Date(key.last_used_at).toLocaleDateString()));\n return lines;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n list\n ═══════════════════════════════════════════════════════════════ */\n\n/** The flags `rebase api-keys list` takes: none of its own. */\nexport const LIST_KEYS_FLAGS = {} as const;\n\nasync function listKeys(rawArgs: string[]): Promise<void> {\n parseCommandArgs({\n spec: LIST_KEYS_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"api-keys list\",\n maxPositionals: 0\n });\n\n const connection = connect();\n const { client } = connection;\n let keys: ApiKeyMasked[];\n try {\n ({ keys } = await client.apiKeys.listKeys());\n } catch (e: unknown) {\n fail(\"Failed to list API keys\", e, connection);\n }\n\n console.log(\"\");\n console.log(chalk.bold(\" 🔑 API Keys\"));\n console.log(\"\");\n\n if (keys.length === 0) {\n console.log(chalk.gray(\" No API keys found.\"));\n console.log(\"\");\n return;\n }\n\n const now = new Date();\n for (const key of keys) {\n for (const line of describeKey(key, now)) console.log(line);\n console.log(\"\");\n }\n}\n\n/* ═══════════════════════════════════════════════════════════════\n get\n ═══════════════════════════════════════════════════════════════ */\n\n/** The flags `rebase api-keys get` takes. */\nexport const GET_KEY_FLAGS = {\n \"--id\": String\n} as const;\n\n/**\n * Which key this invocation names, as a positional or as `--id`.\n *\n * Exported so its tests can drive the real parser rather than a copy of it.\n */\nexport function resolveGetKeyArgs(rawArgs: string[]): { id?: string } {\n const { flags, positionals } = parseCommandArgs({\n spec: GET_KEY_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"api-keys get\",\n maxPositionals: 1\n });\n\n return { id: flags[\"--id\"] || positionals[0] };\n}\n\nasync function getKey(rawArgs: string[]): Promise<void> {\n const { id } = resolveGetKeyArgs(rawArgs);\n if (!id) {\n throw new UsageError(\"Key ID is required: rebase api-keys get <key-id> (`rebase api-keys list` shows them).\");\n }\n\n const connection = connect();\n const { client } = connection;\n let key: ApiKeyMasked;\n try {\n ({ key } = await client.apiKeys.getKey(id));\n } catch (e: unknown) {\n fail(\"Failed to read API key\", e, connection);\n }\n\n console.log(\"\");\n for (const line of describeKey(key)) console.log(line);\n console.log(\"\");\n}\n\n/* ═══════════════════════════════════════════════════════════════\n create\n ═══════════════════════════════════════════════════════════════ */\n\n/** The flags `rebase api-keys create` takes. */\nexport const CREATE_KEY_FLAGS = {\n \"--name\": String,\n \"--scopes\": [String],\n \"--full-access\": Boolean,\n \"--roles\": [String],\n \"--rate-limit\": Number,\n \"--expires-in\": String,\n \"--expires-at\": String,\n \"-n\": \"--name\"\n} satisfies arg.Spec;\n\n/**\n * Options `create` refuses by name, each with the flag that does that job.\n *\n * Checked before the line is parsed: the strict parser would answer them\n * \"unknown option\", which does not say what to type instead.\n */\nexport const RETIRED_CREATE_OPTIONS: Readonly<Record<string, string>> = {\n \"--permissions\":\n \"--permissions is gone: name what the key may do with --scopes, e.g. \" +\n \"--scopes data:read:posts,data:write:posts (`rebase api-keys scopes` lists them).\",\n \"--admin\":\n \"--admin is gone: give the key the RLS role with --roles admin, and the admin-plane scopes it needs \" +\n \"with --scopes, e.g. --scopes users:read,schema:read.\",\n \"--expires\":\n \"--expires is gone: use --expires-in <days> (e.g. --expires-in 90) or --expires-at <ISO date>.\"\n};\n\n/** Refuse a retired option, wherever on the line and however it is spelled. */\nfunction refuseRetiredOptions(rawArgs: string[]): void {\n for (const token of rawArgs.slice(2)) {\n // Past `--` everything is a value, never an option.\n if (token === \"--\") return;\n const name = token.split(\"=\", 1)[0];\n const replacement = Object.prototype.hasOwnProperty.call(RETIRED_CREATE_OPTIONS, name)\n ? RETIRED_CREATE_OPTIONS[name]\n : undefined;\n if (replacement) throw new UsageError(replacement);\n }\n}\n\n/**\n * A list flag's values, whether repeated (`--scopes a --scopes b`) or\n * comma-separated (`--scopes a,b`) — or both — trimmed and deduplicated.\n * Whitespace separates too: no scope or role name contains any.\n */\nfunction splitList(values: readonly string[] | undefined): string[] {\n const out = new Set<string>();\n for (const value of values ?? []) {\n for (const entry of value.split(/[\\s,]+/)) {\n if (entry) out.add(entry);\n }\n }\n return [...out];\n}\n\nconst DAY_MS = 86_400_000;\n\nfunction resolveExpiry(expiresIn: string | undefined, expiresAt: string | undefined, now: Date): string | null {\n if (expiresIn !== undefined && expiresAt !== undefined) {\n throw new UsageError(\"Pass --expires-in or --expires-at, not both.\");\n }\n if (expiresIn !== undefined) {\n const days = /^\\d+$/.test(expiresIn.trim()) ? Number(expiresIn.trim()) : NaN;\n if (!Number.isSafeInteger(days) || days < 1) {\n throw new UsageError(`--expires-in takes a whole number of days, e.g. --expires-in 90 (got \"${expiresIn}\").`);\n }\n return new Date(now.getTime() + days * DAY_MS).toISOString();\n }\n if (expiresAt !== undefined) {\n const parsed = new Date(expiresAt);\n if (isNaN(parsed.getTime())) {\n throw new UsageError(`--expires-at takes an ISO date, e.g. --expires-at 2027-01-31 (got \"${expiresAt}\").`);\n }\n if (parsed.getTime() <= now.getTime()) {\n throw new UsageError(`--expires-at must be in the future (got ${parsed.toISOString()}).`);\n }\n return parsed.toISOString();\n }\n return null;\n}\n\n/** What `rebase api-keys create` was asked to mint. */\nexport interface CreateKeyPlan {\n name: string;\n /** The scopes named with `--scopes`, or `\"held\"` for `--full-access`. */\n scopes: string[] | \"held\";\n /** RLS roles beside `service`. */\n roles: string[];\n rate_limit: number | null;\n expires_at: string | null;\n}\n\n/**\n * What this invocation asks to be created.\n *\n * The name may be given either way — `--name \"My Key\"` or as the single\n * positional — and under the old permissive parse an undeclared flag became\n * that positional: `rebase api-keys create --debug --full-access` created a\n * key called `--debug` with access to everything, and `--debug` is what the\n * CLI prints after every failure as the thing to re-run with. Strict parsing\n * makes the flag an error instead of a name.\n *\n * What the key may do has to be asked for by name — `--scopes` or\n * `--full-access` — never defaulted: a forgotten flag must not mint a key that\n * reaches everything.\n *\n * Exported so its tests can drive the real parser rather than a copy of it.\n */\nexport function resolveCreateKeyArgs(rawArgs: string[], now: Date = new Date()): CreateKeyPlan {\n refuseRetiredOptions(rawArgs);\n\n const { flags, positionals } = parseCommandArgs({\n spec: CREATE_KEY_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"api-keys create\",\n maxPositionals: 1\n });\n\n const name = (flags[\"--name\"] || positionals[0] || \"\").trim();\n if (!name) {\n throw new UsageError('A key needs a name: rebase api-keys create \"CI\" --scopes data:read');\n }\n\n const named = splitList(flags[\"--scopes\"]);\n const fullAccess = flags[\"--full-access\"] === true;\n if (fullAccess && named.length > 0) {\n throw new UsageError(\"Pass --scopes or --full-access, not both: --full-access already means every scope you hold.\");\n }\n if (!fullAccess && named.length === 0) {\n throw new UsageError(\n \"Name what the key may do: --scopes data:read:posts,data:write:posts, or --full-access for every \" +\n \"scope you hold. `rebase api-keys scopes` lists them.\"\n );\n }\n\n const rateLimit = flags[\"--rate-limit\"];\n if (rateLimit !== undefined && (!Number.isSafeInteger(rateLimit) || rateLimit < 1)) {\n throw new UsageError(\"--rate-limit takes a whole number of requests per 15-minute window, e.g. --rate-limit 500.\");\n }\n\n return {\n name,\n scopes: fullAccess ? \"held\" : named,\n roles: splitList(flags[\"--roles\"]),\n rate_limit: rateLimit ?? null,\n expires_at: resolveExpiry(flags[\"--expires-in\"], flags[\"--expires-at\"], now)\n };\n}\n\n/**\n * The scopes `--full-access` puts on a key: every one the caller holds, less\n * key management, which no key may hold.\n *\n * Exported so its tests can check the subtraction without a server.\n */\nexport function grantableScopes(held: readonly string[]): string[] {\n const management: readonly string[] = KEY_MANAGEMENT_SCOPES;\n return held.filter(entry => !management.includes(parseScope(entry)?.scope ?? entry));\n}\n\nasync function createKey(rawArgs: string[]): Promise<void> {\n const plan = resolveCreateKeyArgs(rawArgs);\n const connection = connect();\n const { client } = connection;\n\n let scopes: string[];\n if (plan.scopes === \"held\") {\n let held: string[];\n try {\n ({ held } = await client.personalKeys.listScopes());\n } catch (e: unknown) {\n fail(\"Failed to read the scopes you hold\", e, connection);\n }\n scopes = grantableScopes(held);\n if (scopes.length === 0) {\n console.error(chalk.red(\"✗ You hold no scope a key may carry, so --full-access has nothing to grant.\"));\n process.exit(1);\n }\n } else {\n scopes = plan.scopes;\n }\n\n const request: CreateApiKeyRequest = {\n name: plan.name,\n scopes,\n ...(plan.roles.length > 0 ? { roles: plan.roles } : {}),\n ...(plan.rate_limit !== null ? { rate_limit: plan.rate_limit } : {}),\n ...(plan.expires_at !== null ? { expires_at: plan.expires_at } : {})\n };\n\n let created: ApiKeyWithSecret;\n try {\n ({ key: created } = await client.apiKeys.createKey(request));\n } catch (e: unknown) {\n fail(\"Failed to create API key\", e, connection);\n }\n\n console.log(\"\");\n console.log(chalk.bold.green(\" ✓ API key created\"));\n console.log(\"\");\n for (const line of describeKey(created)) console.log(line);\n console.log(\"\");\n console.log(chalk.bold.yellow(\" ⚠ Copy your key now — it won't be shown again:\"));\n console.log(\"\");\n console.log(` ${chalk.cyan(created.key)}`);\n console.log(\"\");\n}\n\n/* ═══════════════════════════════════════════════════════════════\n revoke\n ═══════════════════════════════════════════════════════════════ */\n\n/** The flags `rebase api-keys revoke` takes. */\nexport const REVOKE_KEY_FLAGS = {\n \"--id\": String\n} as const;\n\n/**\n * Which key this invocation names.\n *\n * The id is a positional, so the permissive parse handed one straight to the\n * DELETE: `rebase api-keys revoke --foo` sent\n * `DELETE /api/admin/api-keys/--foo`, and `rebase --debug api-keys revoke <id>`\n * shifted the words along and revoked the key named `revoke`.\n *\n * Exported so its tests can drive the real parser rather than a copy of it.\n */\nexport function resolveRevokeKeyArgs(rawArgs: string[]): { id?: string } {\n const { flags, positionals } = parseCommandArgs({\n spec: REVOKE_KEY_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"api-keys revoke\",\n maxPositionals: 1\n });\n\n return { id: flags[\"--id\"] || positionals[0] };\n}\n\nasync function revokeKey(rawArgs: string[]): Promise<void> {\n const { id } = resolveRevokeKeyArgs(rawArgs);\n\n if (!id) {\n throw new UsageError(\"Key ID is required: rebase api-keys revoke <key-id> (`rebase api-keys list` shows them).\");\n }\n\n const connection = connect();\n const { client } = connection;\n try {\n await client.apiKeys.revokeKey(id);\n } catch (e: unknown) {\n fail(\"Failed to revoke API key\", e, connection);\n }\n\n console.log(\"\");\n console.log(chalk.bold.green(\" ✓ API key revoked\"));\n console.log(` ${chalk.gray(\"ID:\")} ${id}`);\n console.log(\"\");\n}\n\n/* ═══════════════════════════════════════════════════════════════\n scopes\n ═══════════════════════════════════════════════════════════════ */\n\n/** The flags `rebase api-keys scopes` takes: none of its own. */\nexport const SCOPES_LIST_FLAGS = {} as const;\n\nconst PLANES: ReadonlyArray<{ plane: ScopeSummary[\"plane\"]; title: string; note: string }> = [\n { plane: \"data\", title: \"Data\", note: \"narrow what a key reaches; row-level security still decides the rows\" },\n { plane: \"admin\", title: \"Admin\", note: \"nobody holds these unless a role grants them; the admin role holds all\" },\n { plane: \"app\", title: \"App\", note: \"declared by this app under auth.scopes on the users collection\" }\n];\n\n/**\n * The scope catalogue as printed lines: one section per plane, each scope with\n * its label and what its target names, marked when the caller holds it.\n *\n * Exported so its tests can read the lines without a server.\n */\nexport function describeScopes(scopes: readonly ScopeSummary[], held: readonly string[]): string[] {\n const management: readonly string[] = KEY_MANAGEMENT_SCOPES;\n const width = Math.max(0, ...scopes.map(s => s.scope.length)) + 2;\n const labelWidth = Math.max(0, ...scopes.map(s => s.label.length)) + 2;\n const lines: string[] = [];\n for (const { plane, title, note } of PLANES) {\n const inPlane = scopes.filter(s => s.plane === plane);\n if (inPlane.length === 0) continue;\n lines.push(` ${chalk.bold(title)} ${chalk.gray(`— ${note}`)}`);\n for (const s of inPlane) {\n const mark = held.includes(s.scope) ? chalk.green(\"✓\") : \" \";\n const extra = management.includes(s.scope)\n ? chalk.gray(\"never on a key\")\n : s.target ? chalk.gray(`:<${s.target}>`) : \"\";\n lines.push(` ${mark} ${s.scope.padEnd(width)}${extra ? s.label.padEnd(labelWidth) : s.label}${extra}`);\n }\n lines.push(\"\");\n }\n return lines;\n}\n\nasync function listScopes(rawArgs: string[]): Promise<void> {\n parseCommandArgs({\n spec: SCOPES_LIST_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"api-keys scopes\",\n maxPositionals: 0\n });\n\n const connection = connect();\n const { client } = connection;\n let catalogue: { scopes: ScopeSummary[]; held: string[] };\n try {\n catalogue = await client.personalKeys.listScopes();\n } catch (e: unknown) {\n fail(\"Failed to read the scopes\", e, connection);\n }\n\n console.log(\"\");\n console.log(chalk.bold(\" Scopes\") + chalk.gray(\" (✓ = you hold it; :<target> narrows it to one)\"));\n console.log(\"\");\n for (const line of describeScopes(catalogue.scopes, catalogue.held)) console.log(line);\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Help\n ═══════════════════════════════════════════════════════════════ */\n\nfunction printApiKeysHelp() {\n const keyable = ADMIN_SCOPES.filter(scope => !(KEY_MANAGEMENT_SCOPES as readonly string[]).includes(scope));\n console.log(`\n${chalk.bold(\"rebase api-keys\")} — Manage service API keys\n\n${chalk.green.bold(\"Usage\")}\n rebase api-keys ${chalk.blue(\"<command>\")} [options]\n\n${chalk.green.bold(\"Commands\")}\n ${chalk.blue.bold(\"list\")} List the service keys: kind, scopes, roles\n ${chalk.blue.bold(\"get\")} ${chalk.gray(\"<id>\")} Show one key\n ${chalk.blue.bold(\"create\")} Create a service key\n ${chalk.blue.bold(\"revoke\")} ${chalk.gray(\"<id>\")} Revoke a key\n ${chalk.blue.bold(\"scopes\")} List the scopes this backend knows, and which you hold\n\n${chalk.green.bold(\"create Options\")}\n ${chalk.blue(\"--name, -n\")} Key name ${chalk.gray(\"(required; or the first argument)\")}\n ${chalk.blue(\"--scopes\")} What the key may do, comma-separated or repeated\n ${chalk.gray(\"(required unless --full-access)\")}\n ${chalk.blue(\"--full-access\")} Every scope you hold, less keys:read and keys:write\n ${chalk.blue(\"--roles\")} RLS roles the key runs as beside ${chalk.gray(\"service\")}, e.g. admin\n ${chalk.blue(\"--rate-limit\")} Requests per 15-min window ${chalk.gray(\"(default: the server's API-key limit)\")}\n ${chalk.blue(\"--expires-in\")} Days until the key expires\n ${chalk.blue(\"--expires-at\")} Expiry as an ISO date, e.g. 2027-01-31\n\n${chalk.green.bold(\"get / revoke Options\")}\n ${chalk.blue(\"--id\")} API key ID ${chalk.gray(\"(or the first argument)\")}\n\n${chalk.green.bold(\"Scopes\")}\n A scope is ${chalk.blue(\"resource:action\")}, optionally narrowed to one target:\n ${chalk.blue(\"data:read:posts\")} reads the posts collection and nothing else.\n Data ${DATA_PLANE_SCOPES.join(\", \")}\n ${chalk.gray(\"target: a collection slug, a storage source id (default), a function name\")}\n Admin ${keyable.join(\", \")}\n App whatever the app declares under auth.scopes ${chalk.gray(\"(rebase api-keys scopes)\")}\n\n${chalk.green.bold(\"Examples\")}\n rebase api-keys list\n rebase api-keys create \"Analytics\" --scopes data:read:events\n rebase api-keys create -n \"Blog CI\" --scopes data:read:posts,data:write:posts --expires-in 90\n rebase api-keys create -n \"Mailer\" --scopes \"functions:invoke:send-email,storage:read:(default)\"\n rebase api-keys create -n \"Ops\" --full-access --roles admin --expires-at 2027-01-31\n rebase api-keys get abc123-def456\n rebase api-keys revoke abc123-def456\n`);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-DRsTWPky.js","names":[],"sources":["../src/commands/auth.ts"],"sourcesContent":["/**\n * CLI command: rebase auth <action>\n *\n * Subcommands:\n * reset-password — Reset a user's password\n */\nimport chalk from \"chalk\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport { spawn } from \"child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n requireProjectRoot,\n requireBackendDir,\n findEnvFile,\n readEnvFile,\n resolveTsx,\n exitDependenciesNotInstalled\n} from \"../utils/project\";\nimport { parseEnvBoolean } from \"@rebasepro/types\";\nimport { unknownCommand } from \"../utils/unknown-command\";\nimport { redactConnectionPasswords } from \"../utils/redact\";\nimport type { PreparedDatabase } from \"../dev-db/prepare\";\nimport type { DevDatabaseSource } from \"../dev-db/resolve\";\n\n/** The backend was reached and refused: not a reason to fall back to the database. */\nclass ApiAnswered extends Error {}\n\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const AUTH_SUBCOMMANDS = [\"reset-password\"] as const;\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { resolveBackendAddress } from \"./api-keys\";\n\n/** A user as the admin API returns it, reduced to what this command needs. */\nexport interface ResolvedUser {\n id: string;\n email: string;\n}\n\n/**\n * Pick the user with exactly this email out of a search response.\n *\n * `/api/admin/users?search=` is an `ILIKE '%…%'` over email **or display\n * name**, ordered by role count descending. This used to take row `[0]` and\n * reset it, then print the email it had been *given* as confirmation — so two\n * ordinary situations ended in a successful-looking reset of somebody else's\n * account:\n *\n * - a substring collision: `bob@example.com` also matches\n * `robert.bob@example.com`;\n * - a display name, which is user-controlled and accepted up to 255\n * characters with no constraint on its content, containing an address\n * belonging to someone else.\n *\n * The ordering makes it worse rather than better — `array_length(roles) DESC\n * NULLS LAST` puts the most privileged match first, so the account most likely\n * to be reset by mistake is an admin's.\n *\n * Returns `undefined` when nothing matched exactly, which the caller reports\n * rather than falling through to a guess. The direct-database fallback below\n * has always matched with `eq(usersTable.email, email)`; this is the same\n * definition, so the command no longer resets different accounts depending on\n * whether the backend happened to be running.\n */\nexport function selectUserForEmail(payload: unknown, email: string): ResolvedUser | undefined {\n const wanted = email.trim().toLowerCase();\n if (!wanted) return undefined;\n\n const rows: unknown[] = Array.isArray(payload)\n ? payload\n : (payload && typeof payload === \"object\" && Array.isArray((payload as { users?: unknown }).users)\n ? (payload as { users: unknown[] }).users\n : []);\n\n for (const row of rows) {\n if (!row || typeof row !== \"object\") continue;\n const record = row as Record<string, unknown>;\n const rowEmail = typeof record.email === \"string\" ? record.email.trim().toLowerCase() : undefined;\n if (!rowEmail || rowEmail !== wanted) continue;\n\n const id = typeof record.id === \"string\"\n ? record.id\n : (typeof record.uid === \"string\" ? record.uid : undefined);\n if (!id) continue;\n\n return { id, email: record.email as string };\n }\n\n return undefined;\n}\n\nexport async function authCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void> {\n // `--help` is answered here and never reaches a handler. `cli.ts` only\n // rewrites the subcommand to `\"--help\"` when no subcommand was named, so\n // `rebase auth reset-password --help` used to *run the reset*: `--help`\n // became the email, the backend was contacted, `.tmp-reset-password.ts` was\n // written into the user's `backend/`, and a database UPDATE ran for a user\n // named `--help`. A flag whose whole job is to print text cannot be allowed\n // to reach code that writes.\n if (!subcommand || subcommand === \"--help\" || wantsHelp(rawArgs)) {\n printAuthHelp();\n return;\n }\n\n switch (subcommand) {\n case \"reset-password\":\n await resetPassword(rawArgs);\n break;\n default:\n unknownCommand(subcommand, AUTH_SUBCOMMANDS, \"auth\");\n }\n}\n\n/**\n * The flags `rebase auth reset-password` takes.\n *\n * `-p` was advertised in this command's own help and never declared here, so\n * `arg` — running permissively — pushed it into the positionals and the value\n * *after* it shifted out of reach: anyone following the help set the account's\n * password to the two-character string `-p`. Declared now, and `auth.test.ts`\n * asserts that the help and this spec list the same aliases.\n */\nexport const RESET_PASSWORD_FLAGS = {\n \"--email\": String,\n \"--password\": String,\n \"-e\": \"--email\",\n \"-p\": \"--password\"\n} as const;\n\n/**\n * Which account, and which password, this invocation names.\n *\n * Both may still be absent — the caller reports a missing email — but neither\n * can be a flag. `parseCommandArgs` parses the whole line strictly, so an\n * undeclared flag is an error rather than a positional. That is what stops\n * `rebase auth reset-password bob@example.com --debug` from setting Bob's\n * password to `--debug`, which is the flag the CLI itself prints after every\n * failure as the thing to re-run with.\n *\n * Exported so its tests can drive the real parser rather than a copy of it.\n */\nexport function resolveResetPasswordArgs(rawArgs: string[]): {\n email?: string;\n password?: string;\n} {\n const { flags, positionals } = parseCommandArgs({\n spec: RESET_PASSWORD_FLAGS,\n rawArgs,\n commandWords: 2,\n command: \"auth reset-password\",\n maxPositionals: 2\n });\n\n // Both spellings are supported: `<email> [password]` and `--email/--password`.\n return {\n email: flags[\"--email\"] || positionals[0],\n password: flags[\"--password\"] || positionals[1]\n };\n}\n\n/**\n * A password for a reset that was not given one.\n *\n * This used to be a constant, and `--help` printed it as the default. Reset is\n * the documented way back into an account nobody can sign in to — an admin,\n * usually — so the recovery path set every such account to a fixed string that\n * ships inside a public repository and a published npm package, and left it\n * there until somebody remembered to change it.\n *\n * base64url of 18 random bytes: 24 characters, ~144 bits, no shell-quoting\n * hazard, and nothing that reads like a placeholder somebody might keep.\n */\nexport function generatePassword(): string {\n return randomBytes(18).toString(\"base64url\");\n}\n\n/**\n * What a refusing backend said, from its `{ error: { message, code } }` body.\n *\n * The body used to be printed as it came, so a weak password read\n * `{\"error\":{\"message\":\"…\",\"code\":\"…\"}}`. A body that is not the envelope is\n * still shown, since it is the only account of the refusal there is.\n */\nasync function refusalReason(response: Response): Promise<string> {\n const text = await response.text().catch(() => \"\");\n try {\n const parsed: unknown = JSON.parse(text);\n const error = parsed && typeof parsed === \"object\" && \"error\" in parsed ? parsed.error : undefined;\n if (error && typeof error === \"object\" && \"message\" in error && typeof error.message === \"string\") {\n const code = \"code\" in error && typeof error.code === \"string\" ? ` (${error.code})` : \"\";\n return `${error.message}${code}`;\n }\n } catch {\n // Not JSON: the text is the answer.\n }\n return text.trim() || `${response.status} ${response.statusText}`.trim();\n}\n\nasync function resetPassword(rawArgs: string[]): Promise<void> {\n const { email, password: providedPassword } = resolveResetPasswordArgs(rawArgs);\n // Generated once, so both reset paths set and report the same thing.\n const wasGenerated = !providedPassword;\n const newPassword = providedPassword || generatePassword();\n\n if (!email) {\n console.error(chalk.red(\"✗ Email is required.\"));\n console.log(\"\");\n console.log(chalk.gray(\" Usage: rebase auth reset-password <email> [new-password]\"));\n console.log(chalk.gray(\" rebase auth reset-password --email user@example.com --password NewPass123!\"));\n process.exit(1);\n }\n\n const projectRoot = requireProjectRoot();\n\n // 1. Try API-first reset\n // Was a single-key regex, which did not match `export REBASE_SERVICE_KEY=…`\n // and kept a trailing `# comment` in the value. The command then fell\n // through to the direct-database path as though no key were configured.\n const envFile = findEnvFile(projectRoot);\n const envServiceKey: string | undefined = readEnvFile(projectRoot).REBASE_SERVICE_KEY;\n\n let serviceKey = process.env.REBASE_SERVICE_KEY || envServiceKey;\n\n const statePath = path.join(projectRoot, \".rebase\", \"state.json\");\n if (fs.existsSync(statePath)) {\n try {\n const state = JSON.parse(fs.readFileSync(statePath, \"utf8\")) as Record<string, unknown>;\n if (state && typeof state === \"object\" && typeof state.serviceKey === \"string\" && !serviceKey) {\n serviceKey = state.serviceKey;\n }\n } catch {\n // Ignore\n }\n }\n\n // Where the service key may go: a REBASE_BASE_URL the developer named, or\n // this project's backend while the pid that recorded it is alive — see\n // `resolveBackendAddress`. `.rebase/state.json`'s address on its own, and\n // `.rebase-dev-url`, outlive a killed `rebase dev`, and the port they name\n // may be another server's by now. With neither, the reset goes to the\n // database directly.\n const baseUrl = resolveBackendAddress(readEnvFile(projectRoot), projectRoot).url;\n\n if (baseUrl && serviceKey) {\n console.log(\"Trying API-first reset via running backend...\");\n try {\n const finalPass = newPassword;\n const cleanBaseUrl = baseUrl.replace(/\\/+$/, \"\");\n // Not `limit=1`: the search is fuzzy and ordered by role count, so\n // the exact match is not necessarily first — asking for one row can\n // make it unreachable. Ask for a page, then match exactly.\n const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=50`;\n const searchRes = await fetch(searchUrl, {\n headers: {\n \"Authorization\": `Bearer ${serviceKey}`,\n \"Accept\": \"application/json\"\n }\n });\n if (!searchRes.ok) {\n throw new ApiAnswered(`Failed to list users: ${searchRes.status} ${searchRes.statusText}`);\n }\n const searchData = await searchRes.json() as unknown;\n if (!searchData || typeof searchData !== \"object\") {\n throw new Error(\"Invalid response format from user search API.\");\n }\n\n const matched = selectUserForEmail(searchData, email);\n if (!matched) {\n throw new ApiAnswered(`No user has the email ${email}.`);\n }\n\n const resetUrl = `${cleanBaseUrl}/api/admin/users/${matched.id}/reset-password`;\n const resetRes = await fetch(resetUrl, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${serviceKey}`,\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\"\n },\n body: JSON.stringify({ password: finalPass })\n });\n\n if (!resetRes.ok) {\n throw new ApiAnswered(`Password reset refused: ${await refusalReason(resetRes)}`);\n }\n\n console.log(\"API reset successful.\");\n console.log(chalk.bold(\" 🔑 Rebase Auth — Reset Password (via API)\"));\n console.log(\"\");\n // The address as stored, not as typed — they differ in case often\n // enough that echoing the input hides which account was touched.\n console.log(` ${chalk.gray(\"Email:\")} ${matched.email}`);\n // Echoed only when we invented it. A password the operator typed\n // is already theirs; printing it again only adds scrollback.\n console.log(` ${chalk.gray(\"Password:\")} ${wasGenerated ? finalPass : \"*\".repeat(finalPass.length)}`);\n console.log(\"\");\n return;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n // The backend answered and said no — a password too weak, a user\n // it does not have. That answer stands: writing the hash straight\n // into the database instead skipped the strength rule it had just\n // applied, and ended no sessions. Only a backend that could not be\n // reached is a reason to go round it.\n if (err instanceof ApiAnswered) {\n console.error(chalk.red(`✗ ${errMsg}`));\n process.exitCode = 1;\n return;\n }\n console.warn(chalk.yellow(\"API reset failed, falling back to direct database update...\"));\n console.warn(chalk.gray(` Details: ${errMsg}`));\n }\n }\n\n // 2. Direct-DB Fallback\n const backendDir = requireBackendDir(projectRoot);\n const tsxBin = resolveTsx(projectRoot);\n\n if (!tsxBin) {\n exitDependenciesNotInstalled(projectRoot);\n }\n\n // The database the `db` family would use, resolved — and, for a remembered\n // compose container that is stopped, started — the same way. A stock\n // scaffold leaves DATABASE_URL commented out and runs on the managed\n // database, which only exists in a child's environment when something puts\n // it there — without this the script connected with libpq's defaults,\n // localhost:5432 as the OS user. An external DATABASE_URL adds nothing\n // here: the child already reads it.\n let prepared: PreparedDatabase;\n try {\n const { ensureDockerDatabase } = await import(\"./db\");\n await ensureDockerDatabase(projectRoot, rawArgs);\n const { prepareDatabaseEnv } = await import(\"../dev-db/prepare\");\n prepared = await prepareDatabaseEnv(projectRoot, {\n onProgress: (message) => console.log(chalk.gray(` ${message}`))\n });\n } catch (err) {\n console.error(chalk.red(\"✗ Direct database update failed.\"));\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n\n // Opened here first, so a database that does not answer gets the banner\n // every other command prints — not the script's raw error and stack — and\n // no script is written for it.\n const { databaseUrlOf } = await import(\"./db\");\n const url = databaseUrlOf(prepared, readEnvFile(projectRoot));\n if (url) {\n const failure = await databaseOpenFailure(url, prepared.database.source);\n if (failure) {\n for (const line of failure) console.error(line);\n process.exit(1);\n }\n }\n\n try {\n const env: Record<string, string> = { ...process.env as Record<string, string> };\n if (envFile) {\n env.DOTENV_CONFIG_PATH = envFile;\n }\n env.REBASE_RESET_EMAIL = email;\n env.REBASE_RESET_PASSWORD = newPassword;\n env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, \".env\");\n Object.assign(env, prepared.env);\n\n const scriptContent = resetPasswordScript(wasGenerated, parseEnvBoolean(process.env.REBASE_DEBUG) === true);\n\n const tmpScriptPath = path.join(backendDir, \".tmp-reset-password.ts\");\n fs.writeFileSync(tmpScriptPath, scriptContent, \"utf-8\");\n\n console.log(\"\");\n console.log(chalk.bold(\" 🔑 Rebase Auth — Reset Password (Direct DB Fallback)\"));\n console.log(\"\");\n console.log(` ${chalk.gray(\"Email:\")} ${email}`);\n console.log(` ${chalk.gray(\"Database:\")} ${prepared.description}`);\n if (!wasGenerated) {\n console.log(` ${chalk.gray(\"Password:\")} ${\"*\".repeat(newPassword.length)}`);\n }\n console.log(\"\");\n\n const child = spawn(tsxBin, [tmpScriptPath], {\n cwd: backendDir,\n stdio: \"inherit\",\n env\n });\n\n // The script is written into the user's backend directory, so every\n // exit has to remove it. Without an `error` handler a failed spawn\n // raises an unhandled event, the process dies before `close`, and\n // `.tmp-reset-password.ts` is left behind to be committed.\n const cleanup = () => {\n try { fs.unlinkSync(tmpScriptPath); } catch { /* already gone */ }\n };\n\n return new Promise((resolve) => {\n child.on(\"error\", (err) => {\n cleanup();\n console.error(chalk.red(\"✗ Could not run the reset script.\"));\n console.error(chalk.gray(` ${err.message}`));\n process.exit(1);\n });\n child.on(\"close\", (code) => {\n cleanup();\n if (code !== 0) {\n process.exit(code ?? 1);\n }\n resolve();\n });\n });\n } catch (err) {\n console.error(chalk.red(\"✗ Direct database update failed.\"));\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\n/**\n * Why the database the fallback would reset in cannot be opened, as the lines\n * to print — or null when it opens.\n *\n * A stopped database, refused credentials and a database name the server does\n * not have get the boxed banner the other commands print; anything else is one\n * sentence. Either way the password was not reset and no script was written.\n */\nasync function databaseOpenFailure(url: string, source: DevDatabaseSource): Promise<string[] | null> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString: url, connectionTimeoutMillis: 10_000 });\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n await client.query(\"SELECT 1\");\n return null;\n } catch (error) {\n const { connectionErrorText, connectionFailureBanner, connectionRefusedFixes } =\n await import(\"../dev-db/connection-banner\");\n const consequence = \"The password was not reset.\";\n return connectionFailureBanner(url, error, { consequence, fixes: connectionRefusedFixes(source) }) ?? [\n chalk.red(`✗ Could not open the database: ${redactConnectionPasswords(connectionErrorText(error))}`),\n chalk.gray(` ${consequence}`)\n ];\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\n/**\n * The script the direct-database fallback runs under the project's own tsx, so\n * it resolves the project's `@rebasepro/server-postgres` and schema.\n *\n * It ends the account's sessions in the transaction that sets the password,\n * as every door through the API does (`replaceUserPassword`): the refresh\n * tokens and outstanding reset links go, and `tokens_valid_after` voids the\n * access tokens already handed out. An operator resetting a phished account\n * with the backend down is the case this exists for, and a reset that left the\n * attacker's refresh token minting access tokens did not recover anything.\n *\n * It exits 0 only when a row was updated: a missing user and a thrown error are\n * both exit 1. `echoPassword` prints the new password, for a generated one;\n * `showStack` prints a failure's stack rather than its sentence (`--debug`).\n *\n * Exported so its tests can run it.\n */\nexport function resetPasswordScript(echoPassword: boolean, showStack = false): string {\n return `\nimport { createPostgresDatabaseConnection } from \"@rebasepro/server-postgres\";\nimport { hashPassword } from \"@rebasepro/server\";\nimport { getTableConfig } from \"drizzle-orm/pg-core\";\nimport * as dotenv from \"dotenv\";\nimport path from \"path\";\nimport fs from \"fs\";\n\ndotenv.config({ path: process.env.REBASE_ENV_FILE_PATH, quiet: true });\n\n// Lowercased, as every address is stored (\\`normalizeEmail\\`): matched as\n// typed, \"Ops@Acme.test\" found no row.\nconst email = process.env.REBASE_RESET_EMAIL!.trim().toLowerCase();\nconst newPassword = process.env.REBASE_RESET_PASSWORD!;\n\nconst quote = (identifier: string) => '\"' + identifier.replace(/\"/g, '\"\"') + '\"';\n\nasync function resetPassword() {\n const { pool } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);\n const hash = await hashPassword(newPassword);\n\n let usersTable;\n try {\n const schemaPath = path.resolve(\"./src/schema.generated.ts\");\n if (fs.existsSync(schemaPath)) {\n const schema = await import(\"file://\" + schemaPath);\n usersTable = schema.users || schema.tables?.users;\n }\n } catch (e) {\n // ignore and fallback\n }\n\n if (!usersTable) {\n const pgServer = await import(\"@rebasepro/server-postgres\");\n usersTable = pgServer.users;\n }\n\n // The physical names, from the table object: the collection may map its\n // properties to other columns, and the statements below are plain SQL.\n const { name: tableName, schema: tableSchema } = getTableConfig(usersTable);\n const usersSchema = tableSchema || \"public\";\n const users = quote(usersSchema) + \".\" + quote(tableName);\n const idColumn = quote(usersTable.id.name);\n const emailColumn = quote(usersTable.email.name);\n const passwordColumn = quote((usersTable.passwordHash ?? usersTable.password_hash).name);\n // Where \\`ensureAuthTablesExist\\` puts the tables that hang off the users\n // table: beside it, or in \\`rebase\\` when it lives in \\`public\\`.\n const authSchema = usersSchema === \"public\" ? \"rebase\" : usersSchema;\n\n const client = await pool.connect();\n try {\n // A database nothing has booted against — or a managed one \\`db reset\\`\n // emptied — has no users table, and so no account to reset. Said as\n // that, not as pg's \\`relation … does not exist\\`.\n const usersTable = await client.query(\"SELECT to_regclass($1) IS NOT NULL AS present\", [users]);\n if (!usersTable.rows[0]?.present) {\n console.error(\"✗ \" + users + \" does not exist in this database, so it holds no account to reset.\");\n console.error(\" Nothing was changed. \\`rebase dev\\` creates it the first time it boots against this database.\");\n process.exit(1);\n }\n\n await client.query(\"BEGIN\");\n const updated = await client.query(\n \"UPDATE \" + users + \" SET \" + passwordColumn + \" = $1 WHERE \" + emailColumn + \" = $2 \" +\n \"RETURNING \" + idColumn + \" AS id, \" + emailColumn + \" AS email\",\n [hash, email]\n );\n if (updated.rows.length === 0) {\n await client.query(\"ROLLBACK\");\n // Nothing was updated, so nothing was reset. Exiting 0 here reported\n // success for a no-op, which is what a script would have believed.\n console.error(\"✗ User not found: \" + email);\n process.exit(1);\n }\n const uid = updated.rows[0].id;\n\n // A table or column the server has not created yet (it never booted\n // against this database) holds no session to end.\n const watermark = await client.query(\n \"SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = 'tokens_valid_after'\",\n [usersSchema, tableName]\n );\n if (watermark.rows.length > 0) {\n await client.query(\"UPDATE \" + users + \" SET tokens_valid_after = NOW() WHERE \" + idColumn + \" = $1\", [uid]);\n }\n for (const table of [\"refresh_tokens\", \"password_reset_tokens\"]) {\n const qualified = quote(authSchema) + \".\" + quote(table);\n const present = await client.query(\"SELECT to_regclass($1) IS NOT NULL AS present\", [qualified]);\n if (present.rows[0]?.present) {\n await client.query(\"DELETE FROM \" + qualified + \" WHERE uid = $1\", [uid]);\n }\n }\n await client.query(\"COMMIT\");\n\n console.log(\"✅ Password reset for: \" + updated.rows[0].email);\n console.log(\" Every session of this account has ended.\");\n ${echoPassword ? 'console.log(\" Password: \" + newPassword);' : \"\"}\n process.exit(0);\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => undefined);\n throw error;\n } finally {\n client.release();\n }\n}\n\n// A thrown error is a failed reset, and exits like one — a refused connection\n// that exits 0 reads as a reset to anything checking the exit code. Said as a\n// sentence: the stack is the script's own, and only --debug asks for it.\nconst describe = (error: unknown): string => {\n if (error instanceof AggregateError && !error.message && error.errors.length > 0) return describe(error.errors[0]);\n const message = error instanceof Error ? error.message : String(error);\n const code = error && typeof error === \"object\" && \"code\" in error && typeof error.code === \"string\" ? error.code : \"\";\n return code && !message.includes(code) ? message + \" (\" + code + \")\" : message;\n};\nresetPassword().catch((error) => {\n ${showStack ? \"console.error(error);\" : \"console.error(\\\"✗ The password was not reset: \\\" + describe(error));\"}\n process.exit(1);\n});\n`;\n}\n\nfunction printAuthHelp() {\n console.log(`\n${chalk.bold(\"rebase auth\")} — Authentication management commands\n\n${chalk.green.bold(\"Usage\")}\n rebase auth ${chalk.blue(\"<command>\")} [options]\n\n${chalk.green.bold(\"Commands\")}\n ${chalk.blue.bold(\"reset-password\")} Reset a user's password\n\n${chalk.green.bold(\"reset-password Options\")}\n ${chalk.blue(\"--email, -e\")} User's email address\n ${chalk.blue(\"--password, -p\")} New password (default: one is generated and printed)\n\n${chalk.green.bold(\"Examples\")}\n rebase auth reset-password user@example.com\n rebase auth reset-password --email user@example.com --password MyNewPass!\n`);\n}\n"],"mappings":"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"}