@rebasepro/cli 0.6.0 → 0.7.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 (51) hide show
  1. package/README.md +0 -1
  2. package/dist/commands/api-keys.d.ts +1 -0
  3. package/dist/commands/generate_sdk.d.ts +1 -1
  4. package/dist/commands/init.d.ts +13 -0
  5. package/dist/index.es.js +463 -105
  6. package/dist/index.es.js.map +1 -1
  7. package/package.json +22 -18
  8. package/templates/template/backend/package.json +0 -1
  9. package/templates/template/backend/src/index.ts +1 -1
  10. package/templates/template/config/collections/posts.ts +1 -3
  11. package/templates/template/config/collections/tags.ts +1 -3
  12. package/templates/template/config/collections/users.ts +1 -4
  13. package/templates/template/package.json +0 -1
  14. package/templates/template/scripts/example.ts +4 -1
  15. package/dist/index.cjs +0 -1948
  16. package/dist/index.cjs.map +0 -1
  17. package/skills/rebase-api/SKILL.md +0 -662
  18. package/skills/rebase-api/references/.gitkeep +0 -3
  19. package/skills/rebase-auth/SKILL.md +0 -1143
  20. package/skills/rebase-auth/references/.gitkeep +0 -3
  21. package/skills/rebase-backend-postgres/SKILL.md +0 -633
  22. package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
  23. package/skills/rebase-basics/SKILL.md +0 -749
  24. package/skills/rebase-basics/references/.gitkeep +0 -3
  25. package/skills/rebase-collections/SKILL.md +0 -1328
  26. package/skills/rebase-collections/references/.gitkeep +0 -3
  27. package/skills/rebase-cron-jobs/SKILL.md +0 -699
  28. package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
  29. package/skills/rebase-custom-functions/SKILL.md +0 -233
  30. package/skills/rebase-deployment/SKILL.md +0 -583
  31. package/skills/rebase-deployment/references/.gitkeep +0 -3
  32. package/skills/rebase-design-language/SKILL.md +0 -664
  33. package/skills/rebase-email/SKILL.md +0 -701
  34. package/skills/rebase-email/references/.gitkeep +0 -1
  35. package/skills/rebase-entity-history/SKILL.md +0 -485
  36. package/skills/rebase-entity-history/references/.gitkeep +0 -1
  37. package/skills/rebase-local-env-setup/SKILL.md +0 -189
  38. package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
  39. package/skills/rebase-realtime/SKILL.md +0 -755
  40. package/skills/rebase-realtime/references/.gitkeep +0 -3
  41. package/skills/rebase-sdk/SKILL.md +0 -594
  42. package/skills/rebase-sdk/references/.gitkeep +0 -0
  43. package/skills/rebase-storage/SKILL.md +0 -765
  44. package/skills/rebase-storage/references/.gitkeep +0 -3
  45. package/skills/rebase-studio/SKILL.md +0 -746
  46. package/skills/rebase-studio/references/.gitkeep +0 -3
  47. package/skills/rebase-ui-components/SKILL.md +0 -1411
  48. package/skills/rebase-ui-components/references/.gitkeep +0 -3
  49. package/skills/rebase-webhooks/SKILL.md +0 -623
  50. package/skills/rebase-webhooks/references/.gitkeep +0 -1
  51. package/templates/template/backend/drizzle.config.ts +0 -51
package/dist/index.es.js CHANGED
@@ -11,6 +11,7 @@ import { fileURLToPath } from "url";
11
11
  import crypto from "crypto";
12
12
  import { generateSDK } from "@rebasepro/sdk-generator";
13
13
  import { execSync, spawn } from "child_process";
14
+ import { createRequire } from "module";
14
15
  //#region src/utils/package-manager.ts
15
16
  /**
16
17
  * Package manager detection and command abstraction.
@@ -130,9 +131,9 @@ function getPMCommands(pm) {
130
131
  //#endregion
131
132
  //#region src/commands/init.ts
132
133
  var access = promisify(fs.access);
133
- var __filename$2 = fileURLToPath(import.meta.url);
134
- var __dirname$2 = path.dirname(__filename$2);
135
- function findParentDir$1(currentDir, targetName) {
134
+ var __filename$1 = fileURLToPath(import.meta.url);
135
+ var __dirname$1 = path.dirname(__filename$1);
136
+ function findParentDir(currentDir, targetName) {
136
137
  const root = path.parse(currentDir).root;
137
138
  while (currentDir && currentDir !== root) {
138
139
  if (path.basename(currentDir) === targetName) return currentDir;
@@ -140,7 +141,7 @@ function findParentDir$1(currentDir, targetName) {
140
141
  }
141
142
  return null;
142
143
  }
143
- var cliRoot = findParentDir$1(__dirname$2, "cli");
144
+ var cliRoot = findParentDir(__dirname$1, "cli");
144
145
  var PRESET_CHOICES = [
145
146
  {
146
147
  name: "Blog — Posts, Authors, Tags (with markdown editor)",
@@ -158,6 +159,63 @@ var PRESET_CHOICES = [
158
159
  short: "Blank"
159
160
  }
160
161
  ];
162
+ /**
163
+ * Builds the interactive prompt questions for `rebase init`.
164
+ * Exported for testability — all prompt `type` values must match
165
+ * types registered by the installed version of inquirer.
166
+ */
167
+ function buildInitQuestions(params) {
168
+ const { nameArg, templateArg, hasGitFlag, hasInstallFlag, pm } = params;
169
+ const questions = [];
170
+ if (!nameArg) questions.push({
171
+ type: "input",
172
+ name: "projectName",
173
+ message: "Project name:",
174
+ default: "my-rebase-app",
175
+ validate: (input) => {
176
+ if (!input.trim()) return "Project name is required";
177
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) return "Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores";
178
+ return true;
179
+ }
180
+ });
181
+ if (!templateArg) questions.push({
182
+ type: "select",
183
+ name: "preset",
184
+ message: "Choose a starter template:",
185
+ choices: PRESET_CHOICES,
186
+ default: "blog"
187
+ });
188
+ if (!hasGitFlag) questions.push({
189
+ type: "confirm",
190
+ name: "git",
191
+ message: "Initialize a git repository?",
192
+ default: true
193
+ });
194
+ if (!hasInstallFlag) questions.push({
195
+ type: "confirm",
196
+ name: "installDeps",
197
+ message: `Install dependencies with ${pm}?`,
198
+ default: true
199
+ });
200
+ questions.push({
201
+ type: "input",
202
+ name: "databaseUrl",
203
+ message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
204
+ default: "",
205
+ validate: (input) => {
206
+ if (input.trim() && /[\r\n]/.test(input)) return "Database URL cannot contain newline characters.";
207
+ return true;
208
+ }
209
+ });
210
+ questions.push({
211
+ type: "confirm",
212
+ name: "introspect",
213
+ message: "Would you like to introspect this database to automatically generate collections?",
214
+ default: true,
215
+ when: (answers) => !!answers.databaseUrl?.trim()
216
+ });
217
+ return questions;
218
+ }
161
219
  async function createRebaseApp(rawArgs) {
162
220
  console.log(`
163
221
  ${chalk.bold("Rebase")} — Create a new project 🚀
@@ -205,53 +263,12 @@ async function promptForOptions(rawArgs, pm) {
205
263
  pmCommands
206
264
  };
207
265
  }
208
- const questions = [];
209
- if (!nameArg) questions.push({
210
- type: "input",
211
- name: "projectName",
212
- message: "Project name:",
213
- default: "my-rebase-app",
214
- validate: (input) => {
215
- if (!input.trim()) return "Project name is required";
216
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) return "Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores";
217
- return true;
218
- }
219
- });
220
- if (!templateArg) questions.push({
221
- type: "list",
222
- name: "preset",
223
- message: "Choose a starter template:",
224
- choices: PRESET_CHOICES,
225
- default: "blog"
226
- });
227
- if (!args["--git"]) questions.push({
228
- type: "confirm",
229
- name: "git",
230
- message: "Initialize a git repository?",
231
- default: true
232
- });
233
- if (!args["--install"]) questions.push({
234
- type: "confirm",
235
- name: "installDeps",
236
- message: `Install dependencies with ${pm}?`,
237
- default: true
238
- });
239
- questions.push({
240
- type: "input",
241
- name: "databaseUrl",
242
- message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
243
- default: "",
244
- validate: (input) => {
245
- if (input.trim() && /[\r\n]/.test(input)) return "Database URL cannot contain newline characters.";
246
- return true;
247
- }
248
- });
249
- questions.push({
250
- type: "confirm",
251
- name: "introspect",
252
- message: "Would you like to introspect this database to automatically generate collections?",
253
- default: true,
254
- when: (answers) => !!answers.databaseUrl?.trim()
266
+ const questions = buildInitQuestions({
267
+ nameArg,
268
+ templateArg,
269
+ hasGitFlag: !!args["--git"],
270
+ hasInstallFlag: !!args["--install"],
271
+ pm
255
272
  });
256
273
  const answers = await inquirer.prompt(questions);
257
274
  const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
@@ -558,7 +575,7 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
558
575
  * CLI command: generate-sdk
559
576
  *
560
577
  * Reads collection definitions from a specified directory (default: ./config/collections),
561
- * generates a typed JS SDK, and writes it to the output directory (default: ./generated/sdk).
578
+ * generates a typed TypeScript SDK, and writes it to the output directory (default: ./generated/sdk).
562
579
  *
563
580
  * Uses jiti for dynamic TypeScript import of collection files.
564
581
  */
@@ -704,7 +721,7 @@ function findProjectRoot(startDir = process.cwd()) {
704
721
  try {
705
722
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
706
723
  if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
707
- if (pkg.workspaces.some((w) => w === "backend" || w.includes("backend"))) return dir;
724
+ if (pkg.workspaces.some((w) => w === "backend")) return dir;
708
725
  }
709
726
  } catch {}
710
727
  if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) return dir;
@@ -946,7 +963,6 @@ ${chalk.green.bold("Commands")}
946
963
  ${chalk.blue.bold("push")} Apply schema directly to database (development)
947
964
  ${chalk.blue.bold("generate")} Generate migration files
948
965
  ${chalk.blue.bold("migrate")} Run pending migrations
949
- ${chalk.blue.bold("studio")} Open Studio viewer
950
966
  ${chalk.blue.bold("branch")} Database branching (create, list, delete, info)
951
967
 
952
968
  ${chalk.green.bold("Examples")}
@@ -1415,19 +1431,90 @@ async function resetPassword(rawArgs) {
1415
1431
  process.exit(1);
1416
1432
  }
1417
1433
  const projectRoot = requireProjectRoot();
1434
+ let envServiceKey;
1435
+ const envFile = findEnvFile(projectRoot);
1436
+ if (envFile && fs.existsSync(envFile)) try {
1437
+ const match = fs.readFileSync(envFile, "utf8").match(/^\s*REBASE_SERVICE_KEY\s*=\s*['"]?(.*?)['"]?\s*$/m);
1438
+ if (match && match[1]) envServiceKey = match[1];
1439
+ } catch {}
1440
+ let baseUrl = process.env.REBASE_BASE_URL;
1441
+ let serviceKey = process.env.REBASE_SERVICE_KEY || envServiceKey;
1442
+ const statePath = path.join(projectRoot, ".rebase", "state.json");
1443
+ if (fs.existsSync(statePath)) try {
1444
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
1445
+ if (state && typeof state === "object") {
1446
+ if (typeof state.baseUrl === "string" && !baseUrl) baseUrl = state.baseUrl;
1447
+ if (typeof state.serviceKey === "string" && !serviceKey) serviceKey = state.serviceKey;
1448
+ }
1449
+ } catch {}
1450
+ const devUrlPath = path.join(projectRoot, ".rebase-dev-url");
1451
+ if (fs.existsSync(devUrlPath) && !baseUrl) try {
1452
+ baseUrl = fs.readFileSync(devUrlPath, "utf8").trim();
1453
+ } catch {}
1454
+ if (baseUrl && serviceKey) {
1455
+ console.log("Trying API-first reset via running backend...");
1456
+ try {
1457
+ const finalPass = newPassword || "NewPassword123!";
1458
+ const cleanBaseUrl = baseUrl.replace(/\/+$/, "");
1459
+ const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=1`;
1460
+ const searchRes = await fetch(searchUrl, { headers: {
1461
+ "Authorization": `Bearer ${serviceKey}`,
1462
+ "Accept": "application/json"
1463
+ } });
1464
+ if (!searchRes.ok) throw new Error(`Failed to list users: ${searchRes.statusText}`);
1465
+ const searchData = await searchRes.json();
1466
+ if (!searchData || typeof searchData !== "object") throw new Error("Invalid response format from user search API.");
1467
+ let userId;
1468
+ if (Array.isArray(searchData)) {
1469
+ const firstUser = searchData[0];
1470
+ if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
1471
+ else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
1472
+ } else if ("users" in searchData && Array.isArray(searchData.users)) {
1473
+ const firstUser = searchData.users[0];
1474
+ if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
1475
+ else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
1476
+ }
1477
+ if (!userId) throw new Error(`User not found with email: ${email}`);
1478
+ const resetUrl = `${cleanBaseUrl}/api/admin/users/${userId}/reset-password`;
1479
+ const resetRes = await fetch(resetUrl, {
1480
+ method: "POST",
1481
+ headers: {
1482
+ "Authorization": `Bearer ${serviceKey}`,
1483
+ "Content-Type": "application/json",
1484
+ "Accept": "application/json"
1485
+ },
1486
+ body: JSON.stringify({ password: finalPass })
1487
+ });
1488
+ if (!resetRes.ok) {
1489
+ const errText = await resetRes.text();
1490
+ throw new Error(`Password reset endpoint failed: ${errText || resetRes.statusText}`);
1491
+ }
1492
+ console.log("API reset successful.");
1493
+ console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (via API)"));
1494
+ console.log("");
1495
+ console.log(` ${chalk.gray("Email:")} ${email}`);
1496
+ console.log(` ${chalk.gray("Password:")} ${finalPass}`);
1497
+ console.log("");
1498
+ return;
1499
+ } catch (err) {
1500
+ const errMsg = err instanceof Error ? err.message : String(err);
1501
+ console.warn(chalk.yellow("API reset failed, falling back to direct database update..."));
1502
+ console.warn(chalk.gray(` Details: ${errMsg}`));
1503
+ }
1504
+ }
1418
1505
  const backendDir = requireBackendDir(projectRoot);
1419
1506
  const tsxBin = resolveTsx(projectRoot);
1420
1507
  if (!tsxBin) {
1421
1508
  console.error(chalk.red("✗ Could not find tsx binary."));
1422
1509
  process.exit(1);
1423
1510
  }
1424
- const envFile = findEnvFile(projectRoot);
1425
- const env = { ...process.env };
1426
- if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1427
- env.REBASE_RESET_EMAIL = email;
1428
- env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1429
- env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
1430
- const scriptContent = `
1511
+ try {
1512
+ const env = { ...process.env };
1513
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1514
+ env.REBASE_RESET_EMAIL = email;
1515
+ env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1516
+ env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
1517
+ const scriptContent = `
1431
1518
  import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
1432
1519
  import { hashPassword } from "@rebasepro/server-core";
1433
1520
  import { eq } from "drizzle-orm";
@@ -1481,28 +1568,33 @@ async function resetPassword() {
1481
1568
 
1482
1569
  resetPassword().catch(console.error);
1483
1570
  `;
1484
- const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
1485
- fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
1486
- console.log("");
1487
- console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password"));
1488
- console.log("");
1489
- console.log(` ${chalk.gray("Email:")} ${email}`);
1490
- if (newPassword) console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
1491
- console.log("");
1492
- const child = spawn(tsxBin, [tmpScriptPath], {
1493
- cwd: backendDir,
1494
- stdio: "inherit",
1495
- env
1496
- });
1497
- return new Promise((resolve) => {
1498
- child.on("close", (code) => {
1499
- try {
1500
- fs.unlinkSync(tmpScriptPath);
1501
- } catch {}
1502
- if (code !== 0) process.exit(code ?? 1);
1503
- resolve();
1571
+ const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
1572
+ fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
1573
+ console.log("");
1574
+ console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (Direct DB Fallback)"));
1575
+ console.log("");
1576
+ console.log(` ${chalk.gray("Email:")} ${email}`);
1577
+ if (newPassword) console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
1578
+ console.log("");
1579
+ const child = spawn(tsxBin, [tmpScriptPath], {
1580
+ cwd: backendDir,
1581
+ stdio: "inherit",
1582
+ env
1504
1583
  });
1505
- });
1584
+ return new Promise((resolve) => {
1585
+ child.on("close", (code) => {
1586
+ try {
1587
+ fs.unlinkSync(tmpScriptPath);
1588
+ } catch {}
1589
+ if (code !== 0) process.exit(code ?? 1);
1590
+ resolve();
1591
+ });
1592
+ });
1593
+ } catch (err) {
1594
+ console.error(chalk.red("✗ Direct database update failed."));
1595
+ console.error(err instanceof Error ? err.message : String(err));
1596
+ process.exit(1);
1597
+ }
1506
1598
  }
1507
1599
  function printAuthHelp() {
1508
1600
  console.log(`
@@ -1571,8 +1663,7 @@ async function doctorCommand(rawArgs) {
1571
1663
  }
1572
1664
  //#endregion
1573
1665
  //#region src/commands/skills.ts
1574
- var __filename$1 = fileURLToPath(import.meta.url);
1575
- var __dirname$1 = path.dirname(__filename$1);
1666
+ var require = createRequire(import.meta.url);
1576
1667
  /** Supported agent environments and their target directories. */
1577
1668
  var AGENTS = {
1578
1669
  cursor: {
@@ -1616,24 +1707,16 @@ var AGENTS = {
1616
1707
  })
1617
1708
  }
1618
1709
  };
1619
- function findParentDir(currentDir, targetName) {
1620
- const root = path.parse(currentDir).root;
1621
- while (currentDir && currentDir !== root) {
1622
- if (path.basename(currentDir) === targetName) return currentDir;
1623
- currentDir = path.dirname(currentDir);
1624
- }
1625
- return null;
1626
- }
1627
- /** Resolve the path to the bundled skills directory. */
1710
+ /**
1711
+ * Resolve the path to the skills directory from @rebasepro/agent-skills.
1712
+ * Works in both workspace (symlink) and published (real files) layouts.
1713
+ */
1628
1714
  function getSkillsSourceDir() {
1629
- const cliRoot = findParentDir(__dirname$1, "cli");
1630
- if (cliRoot) {
1631
- const dir = path.join(cliRoot, "skills");
1632
- if (fs.existsSync(dir)) return dir;
1633
- }
1634
- const distSkills = path.resolve(__dirname$1, "../../skills");
1635
- if (fs.existsSync(distSkills)) return distSkills;
1636
- throw new Error("Could not find bundled skills directory. Make sure the CLI was built with `pnpm build`.");
1715
+ const pkgJsonPath = require.resolve("@rebasepro/agent-skills/package.json");
1716
+ const pkgRoot = path.dirname(pkgJsonPath);
1717
+ const skillsDir = path.join(pkgRoot, "skills");
1718
+ if (!fs.existsSync(skillsDir)) throw new Error(`Skills directory not found at ${skillsDir}. Make sure @rebasepro/agent-skills is installed.`);
1719
+ return skillsDir;
1637
1720
  }
1638
1721
  /** Read all skill directories and return their names + content. */
1639
1722
  function loadSkills(skillsDir) {
@@ -1748,6 +1831,272 @@ ${chalk.green.bold("Examples")}
1748
1831
  `);
1749
1832
  }
1750
1833
  //#endregion
1834
+ //#region src/commands/api-keys.ts
1835
+ /**
1836
+ * CLI command: rebase api-keys <action>
1837
+ *
1838
+ * Subcommands:
1839
+ * list — List all API keys (masked)
1840
+ * create — Create a new API key
1841
+ * revoke — Revoke an existing API key
1842
+ */
1843
+ function loadEnv(projectRoot) {
1844
+ const envFile = findEnvFile(projectRoot);
1845
+ const env = {};
1846
+ if (envFile && fs.existsSync(envFile)) {
1847
+ const content = fs.readFileSync(envFile, "utf-8");
1848
+ for (const line of content.split("\n")) {
1849
+ const trimmed = line.trim();
1850
+ if (!trimmed || trimmed.startsWith("#")) continue;
1851
+ const idx = trimmed.indexOf("=");
1852
+ if (idx > 0) {
1853
+ const key = trimmed.slice(0, idx).trim();
1854
+ let value = trimmed.slice(idx + 1).trim();
1855
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1856
+ env[key] = value;
1857
+ }
1858
+ }
1859
+ }
1860
+ return env;
1861
+ }
1862
+ function resolveBaseUrl(env) {
1863
+ const port = env.PORT || env.REBASE_PORT || "3001";
1864
+ return env.REBASE_BASE_URL || `http://localhost:${port}`;
1865
+ }
1866
+ async function apiKeysCommand(subcommand, rawArgs) {
1867
+ if (!subcommand || subcommand === "--help") {
1868
+ printApiKeysHelp();
1869
+ return;
1870
+ }
1871
+ switch (subcommand) {
1872
+ case "list":
1873
+ await listKeys(rawArgs);
1874
+ break;
1875
+ case "create":
1876
+ await createKey(rawArgs);
1877
+ break;
1878
+ case "revoke":
1879
+ await revokeKey(rawArgs);
1880
+ break;
1881
+ default:
1882
+ console.error(chalk.red(`Unknown api-keys command: ${subcommand}`));
1883
+ console.log("");
1884
+ printApiKeysHelp();
1885
+ process.exit(1);
1886
+ }
1887
+ }
1888
+ async function listKeys(_rawArgs) {
1889
+ const env = loadEnv(requireProjectRoot());
1890
+ const baseUrl = resolveBaseUrl(env);
1891
+ const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
1892
+ if (!serviceKey) {
1893
+ console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
1894
+ process.exit(1);
1895
+ }
1896
+ try {
1897
+ const res = await fetch(`${baseUrl}/api/admin/api-keys`, { headers: { Authorization: `Bearer ${serviceKey}` } });
1898
+ if (!res.ok) {
1899
+ const body = await res.text();
1900
+ console.error(chalk.red(`✗ Failed to list API keys: ${res.status} ${body}`));
1901
+ process.exit(1);
1902
+ }
1903
+ const { keys } = await res.json();
1904
+ console.log("");
1905
+ console.log(chalk.bold(" 🔑 API Keys"));
1906
+ console.log("");
1907
+ if (keys.length === 0) {
1908
+ console.log(chalk.gray(" No API keys found."));
1909
+ console.log("");
1910
+ return;
1911
+ }
1912
+ for (const key of keys) {
1913
+ const status = key.revoked_at ? chalk.red("revoked") : key.expires_at && new Date(key.expires_at) < /* @__PURE__ */ new Date() ? chalk.yellow("expired") : chalk.green("active");
1914
+ const perms = key.permissions.map((p) => `${p.collection}(${p.operations.join(",")})`).join(", ");
1915
+ console.log(` ${chalk.bold(key.name)} ${chalk.gray(`[${key.key_prefix}•••]`)} ${status}`);
1916
+ console.log(` ${chalk.gray("ID:")} ${key.id}`);
1917
+ console.log(` ${chalk.gray("Permissions:")} ${perms || "none"}`);
1918
+ console.log(` ${chalk.gray("Created:")} ${new Date(key.created_at).toLocaleDateString()}`);
1919
+ if (key.last_used_at) console.log(` ${chalk.gray("Last used:")} ${new Date(key.last_used_at).toLocaleDateString()}`);
1920
+ console.log("");
1921
+ }
1922
+ } catch (e) {
1923
+ console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
1924
+ console.error(chalk.gray(" Is the Rebase server running?"));
1925
+ process.exit(1);
1926
+ }
1927
+ }
1928
+ async function createKey(rawArgs) {
1929
+ const args = arg({
1930
+ "--name": String,
1931
+ "--permissions": String,
1932
+ "--admin": Boolean,
1933
+ "--rate-limit": Number,
1934
+ "--expires": String,
1935
+ "-n": "--name"
1936
+ }, {
1937
+ argv: rawArgs.slice(4),
1938
+ permissive: true
1939
+ });
1940
+ const name = args["--name"] || args._[0];
1941
+ const permissionsRaw = args["--permissions"];
1942
+ if (!name) {
1943
+ console.error(chalk.red("✗ Name is required."));
1944
+ console.log("");
1945
+ console.log(chalk.gray(" Usage: rebase api-keys create --name \"My Key\" --permissions '[{\"collection\":\"*\",\"operations\":[\"read\"]}]'"));
1946
+ process.exit(1);
1947
+ }
1948
+ let permissions;
1949
+ if (permissionsRaw) try {
1950
+ permissions = JSON.parse(permissionsRaw);
1951
+ } catch {
1952
+ console.error(chalk.red("✗ Invalid --permissions JSON."));
1953
+ console.log(chalk.gray(" Example: '[{\"collection\":\"*\",\"operations\":[\"read\",\"write\"]}]'"));
1954
+ process.exit(1);
1955
+ return;
1956
+ }
1957
+ else permissions = [{
1958
+ collection: "*",
1959
+ operations: [
1960
+ "read",
1961
+ "write",
1962
+ "delete"
1963
+ ]
1964
+ }];
1965
+ let expires_at = null;
1966
+ const expiresFlag = args["--expires"];
1967
+ if (expiresFlag) {
1968
+ const days = {
1969
+ "7d": 7,
1970
+ "30d": 30,
1971
+ "90d": 90,
1972
+ "1y": 365
1973
+ };
1974
+ if (days[expiresFlag]) expires_at = new Date(Date.now() + days[expiresFlag] * 864e5).toISOString();
1975
+ else {
1976
+ const parsed = new Date(expiresFlag);
1977
+ if (isNaN(parsed.getTime())) {
1978
+ console.error(chalk.red("✗ Invalid --expires value. Use 7d, 30d, 90d, 1y, or an ISO date."));
1979
+ process.exit(1);
1980
+ }
1981
+ expires_at = parsed.toISOString();
1982
+ }
1983
+ }
1984
+ const env = loadEnv(requireProjectRoot());
1985
+ const baseUrl = resolveBaseUrl(env);
1986
+ const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
1987
+ if (!serviceKey) {
1988
+ console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
1989
+ process.exit(1);
1990
+ }
1991
+ try {
1992
+ const body = {
1993
+ name,
1994
+ permissions,
1995
+ admin: args["--admin"] ?? false,
1996
+ rate_limit: args["--rate-limit"] ?? null,
1997
+ expires_at
1998
+ };
1999
+ const res = await fetch(`${baseUrl}/api/admin/api-keys`, {
2000
+ method: "POST",
2001
+ headers: {
2002
+ Authorization: `Bearer ${serviceKey}`,
2003
+ "Content-Type": "application/json"
2004
+ },
2005
+ body: JSON.stringify(body)
2006
+ });
2007
+ if (!res.ok) {
2008
+ const errBody = await res.text();
2009
+ console.error(chalk.red(`✗ Failed to create API key: ${res.status} ${errBody}`));
2010
+ process.exit(1);
2011
+ }
2012
+ const { key } = await res.json();
2013
+ console.log("");
2014
+ console.log(chalk.bold.green(" ✓ API key created successfully"));
2015
+ console.log("");
2016
+ console.log(` ${chalk.gray("Name:")} ${key.name}`);
2017
+ console.log(` ${chalk.gray("ID:")} ${key.id}`);
2018
+ console.log(` ${chalk.gray("Prefix:")} ${key.key_prefix}`);
2019
+ console.log("");
2020
+ console.log(chalk.bold.yellow(" ⚠ Copy your key now — it won't be shown again:"));
2021
+ console.log("");
2022
+ console.log(` ${chalk.cyan(key.key)}`);
2023
+ console.log("");
2024
+ } catch (e) {
2025
+ console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
2026
+ console.error(chalk.gray(" Is the Rebase server running?"));
2027
+ process.exit(1);
2028
+ }
2029
+ }
2030
+ async function revokeKey(rawArgs) {
2031
+ const args = arg({ "--id": String }, {
2032
+ argv: rawArgs.slice(4),
2033
+ permissive: true
2034
+ });
2035
+ const id = args["--id"] || args._[0];
2036
+ if (!id) {
2037
+ console.error(chalk.red("✗ Key ID is required."));
2038
+ console.log("");
2039
+ console.log(chalk.gray(" Usage: rebase api-keys revoke <key-id>"));
2040
+ process.exit(1);
2041
+ }
2042
+ const env = loadEnv(requireProjectRoot());
2043
+ const baseUrl = resolveBaseUrl(env);
2044
+ const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
2045
+ if (!serviceKey) {
2046
+ console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
2047
+ process.exit(1);
2048
+ }
2049
+ try {
2050
+ const res = await fetch(`${baseUrl}/api/admin/api-keys/${encodeURIComponent(id)}`, {
2051
+ method: "DELETE",
2052
+ headers: { Authorization: `Bearer ${serviceKey}` }
2053
+ });
2054
+ if (!res.ok) {
2055
+ const errBody = await res.text();
2056
+ console.error(chalk.red(`✗ Failed to revoke API key: ${res.status} ${errBody}`));
2057
+ process.exit(1);
2058
+ }
2059
+ console.log("");
2060
+ console.log(chalk.bold.green(" ✓ API key revoked successfully"));
2061
+ console.log(` ${chalk.gray("ID:")} ${id}`);
2062
+ console.log("");
2063
+ } catch (e) {
2064
+ console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
2065
+ console.error(chalk.gray(" Is the Rebase server running?"));
2066
+ process.exit(1);
2067
+ }
2068
+ }
2069
+ function printApiKeysHelp() {
2070
+ console.log(`
2071
+ ${chalk.bold("rebase api-keys")} — Manage Service API Keys
2072
+
2073
+ ${chalk.green.bold("Usage")}
2074
+ rebase api-keys ${chalk.blue("<command>")} [options]
2075
+
2076
+ ${chalk.green.bold("Commands")}
2077
+ ${chalk.blue.bold("list")} List all API keys
2078
+ ${chalk.blue.bold("create")} Create a new API key
2079
+ ${chalk.blue.bold("revoke")} Revoke an API key
2080
+
2081
+ ${chalk.green.bold("create Options")}
2082
+ ${chalk.blue("--name, -n")} Key name ${chalk.gray("(required)")}
2083
+ ${chalk.blue("--permissions")} JSON array of permissions
2084
+ ${chalk.gray("Default: [{\"collection\":\"*\",\"operations\":[\"read\",\"write\",\"delete\"]}]")}
2085
+ ${chalk.blue("--admin")} Grant admin role (access to admin routes)
2086
+ ${chalk.blue("--rate-limit")} Requests per 15-min window ${chalk.gray("(default: 1000)")}
2087
+ ${chalk.blue("--expires")} Expiration: 7d, 30d, 90d, 1y, or ISO date
2088
+
2089
+ ${chalk.green.bold("revoke Options")}
2090
+ ${chalk.blue("--id")} API key ID to revoke ${chalk.gray("(or positional arg)")}
2091
+
2092
+ ${chalk.green.bold("Examples")}
2093
+ rebase api-keys list
2094
+ rebase api-keys create --name "Analytics" --permissions '[{"collection":"events","operations":["read"]}]'
2095
+ rebase api-keys create -n "Full Access" --expires 90d
2096
+ rebase api-keys revoke abc123-def456
2097
+ `);
2098
+ }
2099
+ //#endregion
1751
2100
  //#region src/cli.ts
1752
2101
  var __filename = fileURLToPath(import.meta.url);
1753
2102
  var __dirname = path.dirname(__filename);
@@ -1782,7 +2131,8 @@ async function entry(args) {
1782
2131
  "start",
1783
2132
  "auth",
1784
2133
  "doctor",
1785
- "skills"
2134
+ "skills",
2135
+ "api-keys"
1786
2136
  ].includes(command)) {
1787
2137
  printHelp();
1788
2138
  return;
@@ -1833,6 +2183,9 @@ async function entry(args) {
1833
2183
  case "skills":
1834
2184
  await skillsCommand(effectiveSubcommand, args);
1835
2185
  break;
2186
+ case "api-keys":
2187
+ await apiKeysCommand(effectiveSubcommand, args);
2188
+ break;
1836
2189
  default:
1837
2190
  console.log(chalk.red(`Unknown command: ${command}`));
1838
2191
  console.log("");
@@ -1861,11 +2214,10 @@ ${chalk.green.bold("Database")}
1861
2214
  ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
1862
2215
  ${chalk.blue.bold("db generate")} Generate SQL migration files
1863
2216
  ${chalk.blue.bold("db migrate")} Run pending migrations
1864
- ${chalk.blue.bold("db studio")} Open Drizzle Studio
1865
2217
  ${chalk.blue.bold("db")} ${chalk.gray("--help")} Show database command help
1866
2218
 
1867
2219
  ${chalk.green.bold("SDK")}
1868
- ${chalk.blue.bold("generate-sdk")} Generate a typed JS SDK from collections
2220
+ ${chalk.blue.bold("generate-sdk")} Generate a typed TypeScript SDK from collections
1869
2221
 
1870
2222
  ${chalk.green.bold("Auth")}
1871
2223
  ${chalk.blue.bold("auth reset-password")} Reset a user's password
@@ -1877,6 +2229,12 @@ ${chalk.green.bold("Diagnostics")}
1877
2229
  ${chalk.green.bold("AI Agent Skills")}
1878
2230
  ${chalk.blue.bold("skills install")} Install Rebase agent skills for your AI coding assistant
1879
2231
 
2232
+ ${chalk.green.bold("API Keys")}
2233
+ ${chalk.blue.bold("api-keys list")} List all service API keys
2234
+ ${chalk.blue.bold("api-keys create")} Create a new scoped API key
2235
+ ${chalk.blue.bold("api-keys revoke")} Revoke an existing API key
2236
+ ${chalk.blue.bold("api-keys")} ${chalk.gray("--help")} Show API key command help
2237
+
1880
2238
  ${chalk.green.bold("Options")}
1881
2239
  ${chalk.blue("--version, -v")} Show version number
1882
2240
  ${chalk.blue("--help, -h")} Show this help message
@@ -1885,6 +2243,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
1885
2243
  `);
1886
2244
  }
1887
2245
  //#endregion
1888
- export { authCommand, buildCommand, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, generateSdkCommand, getActiveBackendPlugin, getPMCommands, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand };
2246
+ export { authCommand, buildCommand, buildInitQuestions, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, generateSdkCommand, getActiveBackendPlugin, getPMCommands, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand };
1889
2247
 
1890
2248
  //# sourceMappingURL=index.es.js.map