@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.
- package/README.md +0 -1
- package/dist/commands/api-keys.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/commands/init.d.ts +13 -0
- package/dist/index.es.js +463 -105
- package/dist/index.es.js.map +1 -1
- package/package.json +22 -18
- package/templates/template/backend/package.json +0 -1
- package/templates/template/backend/src/index.ts +1 -1
- package/templates/template/config/collections/posts.ts +1 -3
- package/templates/template/config/collections/tags.ts +1 -3
- package/templates/template/config/collections/users.ts +1 -4
- package/templates/template/package.json +0 -1
- package/templates/template/scripts/example.ts +4 -1
- package/dist/index.cjs +0 -1948
- package/dist/index.cjs.map +0 -1
- package/skills/rebase-api/SKILL.md +0 -662
- package/skills/rebase-api/references/.gitkeep +0 -3
- package/skills/rebase-auth/SKILL.md +0 -1143
- package/skills/rebase-auth/references/.gitkeep +0 -3
- package/skills/rebase-backend-postgres/SKILL.md +0 -633
- package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
- package/skills/rebase-basics/SKILL.md +0 -749
- package/skills/rebase-basics/references/.gitkeep +0 -3
- package/skills/rebase-collections/SKILL.md +0 -1328
- package/skills/rebase-collections/references/.gitkeep +0 -3
- package/skills/rebase-cron-jobs/SKILL.md +0 -699
- package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
- package/skills/rebase-custom-functions/SKILL.md +0 -233
- package/skills/rebase-deployment/SKILL.md +0 -583
- package/skills/rebase-deployment/references/.gitkeep +0 -3
- package/skills/rebase-design-language/SKILL.md +0 -664
- package/skills/rebase-email/SKILL.md +0 -701
- package/skills/rebase-email/references/.gitkeep +0 -1
- package/skills/rebase-entity-history/SKILL.md +0 -485
- package/skills/rebase-entity-history/references/.gitkeep +0 -1
- package/skills/rebase-local-env-setup/SKILL.md +0 -189
- package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
- package/skills/rebase-realtime/SKILL.md +0 -755
- package/skills/rebase-realtime/references/.gitkeep +0 -3
- package/skills/rebase-sdk/SKILL.md +0 -594
- package/skills/rebase-sdk/references/.gitkeep +0 -0
- package/skills/rebase-storage/SKILL.md +0 -765
- package/skills/rebase-storage/references/.gitkeep +0 -3
- package/skills/rebase-studio/SKILL.md +0 -746
- package/skills/rebase-studio/references/.gitkeep +0 -3
- package/skills/rebase-ui-components/SKILL.md +0 -1411
- package/skills/rebase-ui-components/references/.gitkeep +0 -3
- package/skills/rebase-webhooks/SKILL.md +0 -623
- package/skills/rebase-webhooks/references/.gitkeep +0 -1
- 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$
|
|
134
|
-
var __dirname$
|
|
135
|
-
function findParentDir
|
|
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
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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"
|
|
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
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
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
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
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
|
|
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
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
|
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
|