@nbt-dev/nbt 0.1.1 → 0.1.3
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 +7 -6
- package/dist/nbt.js +195 -422
- package/package.json +8 -4
- package/stdlib/auth/schema.nbt +6 -3
- package/stdlib/dns/schema.nbt +1 -1
- package/stdlib/ingest/schema.nbt +1 -1
- package/stdlib/phone/schema.nbt +1 -1
- package/stdlib/registry/schema.nbt +1 -1
- package/stdlib/secrets/schema.nbt +16 -0
- package/stdlib/workflows/schema.nbt +42 -0
- package/vendor/linux-x64/cartridges/auth/schema.nbt +6 -3
- package/vendor/linux-x64/cartridges/dns/schema.nbt +1 -1
- package/vendor/linux-x64/cartridges/ingest/schema.nbt +1 -1
- package/vendor/linux-x64/cartridges/phone/schema.nbt +1 -1
- package/vendor/linux-x64/cartridges/registry/schema.nbt +1 -1
- package/vendor/linux-x64/cartridges/secrets/schema.nbt +16 -0
- package/vendor/linux-x64/cartridges/workflows/schema.nbt +42 -0
- package/vendor/linux-x64/console +0 -0
- package/vendor/linux-x64/esbuild +0 -0
- package/vendor/linux-x64/nbt +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nbt-dev/nbt
|
|
2
2
|
|
|
3
|
-
The `nbt` CLI and
|
|
3
|
+
The `nbt` CLI and runtime for nbt-dev — a schema-first backend platform for web development.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -16,12 +16,13 @@ npx nbt list # list available standard modules
|
|
|
16
16
|
npx nbt add crm email # vendor standard cartridges into nbt/
|
|
17
17
|
npx nbt update crm email # refresh vendored modules without deleting migrations
|
|
18
18
|
# ...edit nbt/<cart>/schema.nbt (entities, indexes, @route decls)…
|
|
19
|
-
npx nbt dev # boot the
|
|
20
|
-
npx nbt migrate create billing --baseline # first migration from the schema
|
|
19
|
+
npx nbt dev # boot the runtime + live-reload local carts on save
|
|
20
|
+
npx nbt migrate create billing -m "initial" --baseline # first migration from the schema
|
|
21
|
+
npx nbt status # what persistent changes are unrecorded?
|
|
21
22
|
npx nbt generate # typed TS clients for the local cartridge set
|
|
22
23
|
|
|
23
|
-
npx nbt
|
|
24
|
-
npx nbt
|
|
24
|
+
npx nbt deploy # build + ship the whole project as one release
|
|
25
|
+
npx nbt deploy --env prod # target another configured environment
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
Commands read `nbt.json` for paths and the target instance; `--host`/`--port`/`--root`/`--out`
|
|
@@ -29,7 +30,7 @@ flags override it. Run from anywhere inside the project — `nbt` walks up to fi
|
|
|
29
30
|
|
|
30
31
|
Standard cartridges use a shadcn-style ownership model: `nbt add <name>` copies their source into
|
|
31
32
|
your configured carts directory. The local copy is visible and editable, `nbt generate` follows
|
|
32
|
-
that local cartridge set, and `nbt
|
|
33
|
+
that local cartridge set, and `nbt deploy` ships it to the configured runtime as one release.
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
36
|
npx nbt up # boot using nbt.json dev port
|
package/dist/nbt.js
CHANGED
|
@@ -3134,9 +3134,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3134
3134
|
* @param {string} [path]
|
|
3135
3135
|
* @return {(string|null|Command)}
|
|
3136
3136
|
*/
|
|
3137
|
-
executableDir(
|
|
3138
|
-
if (
|
|
3139
|
-
this._executableDir =
|
|
3137
|
+
executableDir(path3) {
|
|
3138
|
+
if (path3 === void 0) return this._executableDir;
|
|
3139
|
+
this._executableDir = path3;
|
|
3140
3140
|
return this;
|
|
3141
3141
|
}
|
|
3142
3142
|
/**
|
|
@@ -3393,48 +3393,33 @@ var program = new Command();
|
|
|
3393
3393
|
|
|
3394
3394
|
// src/nbt.ts
|
|
3395
3395
|
var import_node_child_process2 = require("node:child_process");
|
|
3396
|
-
var net = __toESM(require("node:net"));
|
|
3397
|
-
var path3 = __toESM(require("node:path"));
|
|
3398
|
-
var fs2 = __toESM(require("node:fs"));
|
|
3399
|
-
var readline = __toESM(require("node:readline/promises"));
|
|
3400
|
-
|
|
3401
|
-
// src/bundler.ts
|
|
3402
|
-
var import_esbuild = __toESM(require("esbuild"));
|
|
3403
3396
|
var path2 = __toESM(require("node:path"));
|
|
3404
|
-
var
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
delete g.__nbt_bundler__;
|
|
3415
|
-
await import((0, import_node_url.pathToFileURL)(path2.resolve(options.outfile)).href);
|
|
3416
|
-
const bundler = g.__nbt_bundler__;
|
|
3417
|
-
console.log(bundler.workflows, bundler.steps, bundler.actions);
|
|
3418
|
-
}
|
|
3419
|
-
|
|
3420
|
-
// src/nbt.ts
|
|
3421
|
-
if (process.platform !== "linux" || process.arch !== "x64") {
|
|
3397
|
+
var fs2 = __toESM(require("node:fs"));
|
|
3398
|
+
var os = __toESM(require("node:os"));
|
|
3399
|
+
var VENDOR_DIRS = {
|
|
3400
|
+
"linux-x64": "linux-x64",
|
|
3401
|
+
"darwin-arm64": "darwin-arm64",
|
|
3402
|
+
"darwin-x64": "darwin-x64"
|
|
3403
|
+
};
|
|
3404
|
+
var PLATFORM_KEY = `${process.platform}-${process.arch}`;
|
|
3405
|
+
var VENDOR_SUBDIR = VENDOR_DIRS[PLATFORM_KEY];
|
|
3406
|
+
if (!VENDOR_SUBDIR) {
|
|
3422
3407
|
console.error(
|
|
3423
|
-
`@nbt-dev/nbt
|
|
3408
|
+
`@nbt-dev/nbt has no prebuilt binaries for ${PLATFORM_KEY} (supported: ${Object.keys(VENDOR_DIRS).join(", ")}).`
|
|
3424
3409
|
);
|
|
3425
3410
|
process.exit(1);
|
|
3426
3411
|
}
|
|
3427
|
-
var
|
|
3428
|
-
var
|
|
3429
|
-
var NBT_BIN =
|
|
3430
|
-
var CONSOLE_BIN =
|
|
3412
|
+
var IS_MAC = process.platform === "darwin";
|
|
3413
|
+
var VENDOR = path2.join(__dirname, "..", "vendor", VENDOR_SUBDIR);
|
|
3414
|
+
var NBT_BIN = path2.join(VENDOR, "nbt");
|
|
3415
|
+
var CONSOLE_BIN = path2.join(VENDOR, "console");
|
|
3431
3416
|
function nativeEnv() {
|
|
3432
3417
|
return { ...process.env };
|
|
3433
3418
|
}
|
|
3434
3419
|
function corsOriginsFromNbtJson() {
|
|
3435
3420
|
let dir = process.cwd();
|
|
3436
3421
|
for (let i = 0; i < 32; i++) {
|
|
3437
|
-
const candidate =
|
|
3422
|
+
const candidate = path2.join(dir, "nbt.json");
|
|
3438
3423
|
if (fs2.existsSync(candidate)) {
|
|
3439
3424
|
try {
|
|
3440
3425
|
const cfg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
@@ -3446,7 +3431,7 @@ function corsOriginsFromNbtJson() {
|
|
|
3446
3431
|
}
|
|
3447
3432
|
return [];
|
|
3448
3433
|
}
|
|
3449
|
-
const parent =
|
|
3434
|
+
const parent = path2.dirname(dir);
|
|
3450
3435
|
if (parent === dir) break;
|
|
3451
3436
|
dir = parent;
|
|
3452
3437
|
}
|
|
@@ -3460,15 +3445,49 @@ function consoleEnv() {
|
|
|
3460
3445
|
}
|
|
3461
3446
|
return env;
|
|
3462
3447
|
}
|
|
3448
|
+
function projectUpPort() {
|
|
3449
|
+
let dir = process.cwd();
|
|
3450
|
+
for (let i = 0; i < 32; i++) {
|
|
3451
|
+
const candidate = path2.join(dir, "nbt.json");
|
|
3452
|
+
if (fs2.existsSync(candidate)) {
|
|
3453
|
+
try {
|
|
3454
|
+
const cfg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
3455
|
+
return Number(cfg?.environments?.dev?.port) || 8080;
|
|
3456
|
+
} catch {
|
|
3457
|
+
break;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
const parent = path2.dirname(dir);
|
|
3461
|
+
if (parent === dir) break;
|
|
3462
|
+
dir = parent;
|
|
3463
|
+
}
|
|
3464
|
+
return 8080;
|
|
3465
|
+
}
|
|
3466
|
+
function withProjectUpDefaults(argv2) {
|
|
3467
|
+
const hasPort = argv2.some(
|
|
3468
|
+
(arg) => arg === "--port" || arg === "-p" || arg.startsWith("--port=")
|
|
3469
|
+
);
|
|
3470
|
+
if (hasPort) return argv2;
|
|
3471
|
+
return [...argv2, "--port", String(projectUpPort())];
|
|
3472
|
+
}
|
|
3463
3473
|
function forwardToNative(argv2) {
|
|
3464
|
-
const
|
|
3474
|
+
const cmd = argv2[0];
|
|
3475
|
+
const isConsole = consoleCommands.has(cmd);
|
|
3465
3476
|
const bin = isConsole ? CONSOLE_BIN : NBT_BIN;
|
|
3466
|
-
const
|
|
3467
|
-
const
|
|
3477
|
+
const bootsDaemon = isConsole || cmd === "dev";
|
|
3478
|
+
const env = bootsDaemon ? consoleEnv() : nativeEnv();
|
|
3479
|
+
const nativeArgv = cmd === "up" ? withProjectUpDefaults(argv2) : argv2;
|
|
3468
3480
|
if (!fs2.existsSync(bin)) {
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3481
|
+
if (isConsole && IS_MAC) {
|
|
3482
|
+
console.error(
|
|
3483
|
+
`@nbt-dev/nbt: \`${cmd}\` runs the console daemon, which is Linux-only on macOS.
|
|
3484
|
+
Run it in Docker, then point the CLI at it with NBT_CONSOLE_URL.`
|
|
3485
|
+
);
|
|
3486
|
+
} else {
|
|
3487
|
+
console.error(
|
|
3488
|
+
`@nbt-dev/nbt: bundled binary not found at ${bin} (broken install?)`
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3472
3491
|
process.exit(1);
|
|
3473
3492
|
}
|
|
3474
3493
|
try {
|
|
@@ -3485,417 +3504,167 @@ function forwardToNative(argv2) {
|
|
|
3485
3504
|
}
|
|
3486
3505
|
process.exit(r.status === null ? 1 : r.status);
|
|
3487
3506
|
}
|
|
3488
|
-
function
|
|
3507
|
+
function resolveAgentEntry() {
|
|
3508
|
+
const rel = path2.join(
|
|
3509
|
+
"packages",
|
|
3510
|
+
"coding-agent",
|
|
3511
|
+
"dist",
|
|
3512
|
+
"cli.js"
|
|
3513
|
+
);
|
|
3514
|
+
const candidates = [
|
|
3515
|
+
path2.join(__dirname, "..", "agent", rel),
|
|
3516
|
+
// published: agent vendored under this package
|
|
3517
|
+
path2.join(__dirname, "..", "..", "agent", rel)
|
|
3518
|
+
// dev monorepo: npm/agent
|
|
3519
|
+
];
|
|
3520
|
+
for (const c of candidates) if (fs2.existsSync(c)) return c;
|
|
3489
3521
|
try {
|
|
3490
|
-
|
|
3491
|
-
fs2.readFileSync(path3.join(__dirname, "..", "package.json"), "utf8")
|
|
3492
|
-
);
|
|
3493
|
-
return typeof pkg?.version === "string" ? pkg.version : "unknown";
|
|
3522
|
+
return require.resolve("@nbt-dev/agent/dist/cli.js");
|
|
3494
3523
|
} catch {
|
|
3495
|
-
return
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
var program2 = new Command();
|
|
3499
|
-
program2.name("nbt").description("nbt.dev CLI").version(packageVersion(), "-v, --version", "Print the @nbt-dev/nbt version");
|
|
3500
|
-
var NBT = [
|
|
3501
|
-
["install", "Install a cartridge on a live instance"],
|
|
3502
|
-
["generate", "Generate a typed client from contracts"],
|
|
3503
|
-
["migrate", "Create migrations and deploy local cartridges"],
|
|
3504
|
-
["lsp", "Run the NBT language server over stdio (editors)"],
|
|
3505
|
-
["editor", "Install the NBT VS Code extension (editor install)"]
|
|
3506
|
-
];
|
|
3507
|
-
var CONSOLE = [
|
|
3508
|
-
["up", "Boot the console daemon"],
|
|
3509
|
-
["gen-cluster-key", "Generate a cluster signing key (copy to every node)"]
|
|
3510
|
-
];
|
|
3511
|
-
var consoleCommands = new Set(CONSOLE.map(([n]) => n));
|
|
3512
|
-
for (const [name, summary] of [...NBT, ...CONSOLE]) {
|
|
3513
|
-
program2.command(name).description(`${summary} (native)`);
|
|
3514
|
-
}
|
|
3515
|
-
var customCommands = /* @__PURE__ */ new Set();
|
|
3516
|
-
customCommands.add("init");
|
|
3517
|
-
program2.command("init").description("Scaffold an nbt project (nbt.json, nbt/, generated/)").argument(
|
|
3518
|
-
"[dir]",
|
|
3519
|
-
"directory to create the project in (default: current directory)"
|
|
3520
|
-
).option("-y, --yes", "skip prompts; include the hello-world example").option("--no-example", "skip the hello-world example cartridge").action(
|
|
3521
|
-
(dir, opts) => {
|
|
3522
|
-
runInit(dir, opts).catch((e) => {
|
|
3523
|
-
console.error(`nbt init: ${e?.message ?? e}`);
|
|
3524
|
-
process.exit(1);
|
|
3525
|
-
});
|
|
3526
|
-
}
|
|
3527
|
-
);
|
|
3528
|
-
var NBT_JSON_TEMPLATE = `{
|
|
3529
|
-
"carts": "nbt",
|
|
3530
|
-
"generated": "generated",
|
|
3531
|
-
"environments": {
|
|
3532
|
-
"dev": { "host": "127.0.0.1", "port": 8080 }
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
`;
|
|
3536
|
-
var HELLO_SCHEMA = `# A minimal entity. CRUD routes are generated automatically:
|
|
3537
|
-
# GET /api/hello/note list
|
|
3538
|
-
# POST /api/hello/note create
|
|
3539
|
-
# GET /api/hello/note/:id get
|
|
3540
|
-
# PUT /api/hello/note/:id update
|
|
3541
|
-
# DELETE /api/hello/note/:id delete
|
|
3542
|
-
entity Note {
|
|
3543
|
-
id: ulid
|
|
3544
|
-
createdAt: DateTime @default(now())
|
|
3545
|
-
updatedAt: DateTime @updatedAt
|
|
3546
|
-
title: string
|
|
3547
|
-
body: string
|
|
3548
|
-
|
|
3549
|
-
@@index([title])
|
|
3550
|
-
}
|
|
3551
|
-
`;
|
|
3552
|
-
async function runInit(dir, opts) {
|
|
3553
|
-
const root = dir ? path3.resolve(process.cwd(), dir) : process.cwd();
|
|
3554
|
-
if (dir) fs2.mkdirSync(root, { recursive: true });
|
|
3555
|
-
if (fs2.existsSync(path3.join(root, "nbt.json"))) {
|
|
3556
|
-
throw new Error(
|
|
3557
|
-
`nbt.json already exists ${dir ? `in ${dir}` : "here"} \u2014 this is already an nbt project`
|
|
3558
|
-
);
|
|
3559
|
-
}
|
|
3560
|
-
if (!fs2.existsSync(path3.join(STDLIB, "auth", "schema.nbt"))) {
|
|
3561
|
-
throw new Error("bundled auth cartridge is missing (broken install)");
|
|
3562
|
-
}
|
|
3563
|
-
if (fs2.existsSync(path3.join(root, "nbt", "auth"))) {
|
|
3564
|
-
throw new Error(
|
|
3565
|
-
"nbt/auth already exists; refusing to overwrite it during init"
|
|
3566
|
-
);
|
|
3567
|
-
}
|
|
3568
|
-
fs2.writeFileSync(path3.join(root, "nbt.json"), NBT_JSON_TEMPLATE);
|
|
3569
|
-
fs2.mkdirSync(path3.join(root, "nbt"), { recursive: true });
|
|
3570
|
-
fs2.mkdirSync(path3.join(root, "generated"), { recursive: true });
|
|
3571
|
-
addStandardCartridge("auth", path3.join(root, "nbt"), false);
|
|
3572
|
-
console.log("Created project:");
|
|
3573
|
-
console.log(" nbt.json (dev environment configured)");
|
|
3574
|
-
console.log(" nbt/auth/ (local core auth cartridge)");
|
|
3575
|
-
console.log(" generated/ (typed client output \u2014 nbt generate)");
|
|
3576
|
-
const example = opts.example !== false && (opts.yes === true || await wantExample());
|
|
3577
|
-
if (example) {
|
|
3578
|
-
scaffoldHelloCart(path3.join(root, "nbt", "hello"));
|
|
3579
|
-
console.log("\nCreated example cartridge nbt/hello/ (entity Note).");
|
|
3580
|
-
console.log("\nNext:");
|
|
3581
|
-
if (dir) console.log(` cd ${dir}`);
|
|
3582
|
-
console.log(
|
|
3583
|
-
" nbt dev # boot the console + live-reload on save"
|
|
3584
|
-
);
|
|
3585
|
-
} else {
|
|
3586
|
-
if (dir)
|
|
3587
|
-
console.log(
|
|
3588
|
-
`
|
|
3589
|
-
Next: cd ${dir}, then run \`nbt dev\` or add a standard cartridge with \`nbt add <name>\`.`
|
|
3590
|
-
);
|
|
3591
|
-
else
|
|
3592
|
-
console.log(
|
|
3593
|
-
"\nNext: run `nbt dev`, or add another standard cartridge with `nbt add <name>`."
|
|
3594
|
-
);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
async function wantExample() {
|
|
3598
|
-
if (!process.stdin.isTTY) return true;
|
|
3599
|
-
const rl = readline.createInterface({
|
|
3600
|
-
input: process.stdin,
|
|
3601
|
-
output: process.stdout
|
|
3602
|
-
});
|
|
3603
|
-
try {
|
|
3604
|
-
const ans = (await rl.question("Add a hello-world example cartridge? [Y/n] ")).trim().toLowerCase();
|
|
3605
|
-
return ans === "" || ans === "y" || ans === "yes";
|
|
3606
|
-
} finally {
|
|
3607
|
-
rl.close();
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
function scaffoldHelloCart(dir) {
|
|
3611
|
-
fs2.mkdirSync(path3.join(dir, "migrations"), { recursive: true });
|
|
3612
|
-
fs2.writeFileSync(path3.join(dir, "schema.nbt"), HELLO_SCHEMA);
|
|
3613
|
-
}
|
|
3614
|
-
customCommands.add("add");
|
|
3615
|
-
program2.command("add").description("Vendor standard-library cartridges into this project").argument("<cartridge...>", "standard cartridge name(s)").option("--overwrite", "replace an existing local cartridge").action((cartridges, opts) => {
|
|
3616
|
-
try {
|
|
3617
|
-
const proj = loadNbtProject();
|
|
3618
|
-
for (const name of cartridges) {
|
|
3619
|
-
const dest = addStandardCartridge(
|
|
3620
|
-
name,
|
|
3621
|
-
proj.cartsDir,
|
|
3622
|
-
opts.overwrite === true
|
|
3623
|
-
);
|
|
3624
|
-
console.log(`Added ${name} -> ${path3.relative(proj.rootDir, dest)}`);
|
|
3625
|
-
}
|
|
3626
|
-
console.log(
|
|
3627
|
-
"Run `nbt migrate deploy` to deploy the local cartridge set."
|
|
3628
|
-
);
|
|
3629
|
-
} catch (e) {
|
|
3630
|
-
console.error(`nbt add: ${e?.message ?? e}`);
|
|
3631
|
-
process.exit(1);
|
|
3632
|
-
}
|
|
3633
|
-
});
|
|
3634
|
-
function addStandardCartridge(name, cartsDir, overwrite) {
|
|
3635
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/.test(name)) {
|
|
3636
|
-
throw new Error(`invalid cartridge name '${name}'`);
|
|
3637
|
-
}
|
|
3638
|
-
const src = path3.join(STDLIB, name);
|
|
3639
|
-
if (!fs2.existsSync(path3.join(src, "schema.nbt"))) {
|
|
3640
|
-
const available = fs2.existsSync(STDLIB) ? fs2.readdirSync(STDLIB, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort().join(", ") : "";
|
|
3641
|
-
throw new Error(
|
|
3642
|
-
`unknown standard cartridge '${name}'${available ? ` (available: ${available})` : " (broken install: stdlib missing)"}`
|
|
3643
|
-
);
|
|
3644
|
-
}
|
|
3645
|
-
const dest = path3.join(cartsDir, name);
|
|
3646
|
-
if (fs2.existsSync(dest)) {
|
|
3647
|
-
if (!overwrite)
|
|
3648
|
-
throw new Error(`${dest} already exists; use --overwrite to replace it`);
|
|
3649
|
-
fs2.rmSync(dest, { recursive: true, force: true });
|
|
3524
|
+
return null;
|
|
3650
3525
|
}
|
|
3651
|
-
fs2.mkdirSync(cartsDir, { recursive: true });
|
|
3652
|
-
fs2.cpSync(src, dest, { recursive: true });
|
|
3653
|
-
return dest;
|
|
3654
3526
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
|
-
program2.command("update").description(
|
|
3672
|
-
"Update vendored standard-library modules from the bundled stdlib"
|
|
3673
|
-
).argument("<module...>", "standard module name(s)").action((modules) => {
|
|
3674
|
-
try {
|
|
3675
|
-
const proj = loadNbtProject();
|
|
3676
|
-
for (const name of modules) {
|
|
3677
|
-
const src = path3.join(STDLIB, name);
|
|
3678
|
-
if (!fs2.existsSync(path3.join(src, "schema.nbt"))) {
|
|
3679
|
-
throw new Error(`unknown standard module '${name}' (see: nbt list)`);
|
|
3680
|
-
}
|
|
3681
|
-
const dest = path3.join(proj.cartsDir, name);
|
|
3682
|
-
if (!fs2.existsSync(path3.join(dest, "schema.nbt"))) {
|
|
3683
|
-
throw new Error(
|
|
3684
|
-
`'${name}' is not installed in this project \u2014 run: nbt add ${name}`
|
|
3685
|
-
);
|
|
3527
|
+
function projectConsoleUrl() {
|
|
3528
|
+
if (process.env.NBT_CONSOLE_URL) return process.env.NBT_CONSOLE_URL;
|
|
3529
|
+
let dir = process.cwd();
|
|
3530
|
+
for (let i = 0; i < 32; i++) {
|
|
3531
|
+
const candidate = path2.join(dir, "nbt.json");
|
|
3532
|
+
if (fs2.existsSync(candidate)) {
|
|
3533
|
+
try {
|
|
3534
|
+
const cfg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
3535
|
+
const dev = cfg?.environments?.dev ?? {};
|
|
3536
|
+
const explicit = cfg?.agent?.consoleUrl ?? dev?.consoleUrl;
|
|
3537
|
+
if (typeof explicit === "string" && explicit) return explicit;
|
|
3538
|
+
const host = typeof dev?.host === "string" && dev.host ? dev.host : "127.0.0.1";
|
|
3539
|
+
const port = Number(dev?.port) || 8080;
|
|
3540
|
+
return `http://${host}:${port}`;
|
|
3541
|
+
} catch {
|
|
3542
|
+
break;
|
|
3686
3543
|
}
|
|
3687
|
-
const written = copyStdlibOver(src, dest);
|
|
3688
|
-
for (const f of written)
|
|
3689
|
-
console.log(` wrote ${path3.relative(proj.rootDir, f)}`);
|
|
3690
|
-
console.log(`Updated ${name}.`);
|
|
3691
3544
|
}
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
} catch (e) {
|
|
3696
|
-
console.error(`nbt update: ${e?.message ?? e}`);
|
|
3697
|
-
process.exit(1);
|
|
3545
|
+
const parent = path2.dirname(dir);
|
|
3546
|
+
if (parent === dir) break;
|
|
3547
|
+
dir = parent;
|
|
3698
3548
|
}
|
|
3699
|
-
|
|
3700
|
-
function copyStdlibOver(src, dest) {
|
|
3701
|
-
const written = [];
|
|
3702
|
-
const walk = (s, d) => {
|
|
3703
|
-
fs2.mkdirSync(d, { recursive: true });
|
|
3704
|
-
for (const e of fs2.readdirSync(s, { withFileTypes: true })) {
|
|
3705
|
-
const sp = path3.join(s, e.name);
|
|
3706
|
-
const dp = path3.join(d, e.name);
|
|
3707
|
-
if (e.isDirectory()) walk(sp, dp);
|
|
3708
|
-
else {
|
|
3709
|
-
fs2.copyFileSync(sp, dp);
|
|
3710
|
-
written.push(dp);
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
};
|
|
3714
|
-
walk(src, dest);
|
|
3715
|
-
return written;
|
|
3549
|
+
return "http://127.0.0.1:8080";
|
|
3716
3550
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
"-p, --port <port>",
|
|
3720
|
-
"console HTTP port (default: nbt.json dev env, else 8080)"
|
|
3721
|
-
).option("-d, --data-dir <dir>", "console data dir (default: ./data)").action((opts) => {
|
|
3722
|
-
runDev(opts).catch((e) => {
|
|
3723
|
-
console.error(`nbt dev: ${e?.message ?? e}`);
|
|
3724
|
-
process.exit(1);
|
|
3725
|
-
});
|
|
3726
|
-
});
|
|
3727
|
-
function loadNbtProject() {
|
|
3551
|
+
function projectLlmUrl() {
|
|
3552
|
+
if (process.env.NBT_LLM_URL) return process.env.NBT_LLM_URL;
|
|
3728
3553
|
let dir = process.cwd();
|
|
3729
3554
|
for (let i = 0; i < 32; i++) {
|
|
3730
|
-
const candidate =
|
|
3555
|
+
const candidate = path2.join(dir, "nbt.json");
|
|
3731
3556
|
if (fs2.existsSync(candidate)) {
|
|
3732
3557
|
try {
|
|
3733
3558
|
const cfg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
3734
|
-
const
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3559
|
+
const dev = cfg?.environments?.dev ?? {};
|
|
3560
|
+
const explicit = cfg?.agent?.llmUrl ?? dev?.llmUrl;
|
|
3561
|
+
if (typeof explicit === "string" && explicit) return explicit;
|
|
3737
3562
|
} catch {
|
|
3738
3563
|
break;
|
|
3739
3564
|
}
|
|
3565
|
+
return void 0;
|
|
3740
3566
|
}
|
|
3741
|
-
const parent =
|
|
3567
|
+
const parent = path2.dirname(dir);
|
|
3742
3568
|
if (parent === dir) break;
|
|
3743
3569
|
dir = parent;
|
|
3744
3570
|
}
|
|
3745
|
-
return
|
|
3746
|
-
rootDir: process.cwd(),
|
|
3747
|
-
cartsDir: path3.resolve(process.cwd(), "nbt"),
|
|
3748
|
-
port: 8080
|
|
3749
|
-
};
|
|
3750
|
-
}
|
|
3751
|
-
function withProjectUpDefaults(argv2) {
|
|
3752
|
-
const hasPort = argv2.some(
|
|
3753
|
-
(arg) => arg === "--port" || arg === "-p" || arg.startsWith("--port=")
|
|
3754
|
-
);
|
|
3755
|
-
if (hasPort) return argv2;
|
|
3756
|
-
return [...argv2, "--port", String(loadNbtProject().port)];
|
|
3757
|
-
}
|
|
3758
|
-
function discoverCarts(cartsDir) {
|
|
3759
|
-
if (!fs2.existsSync(cartsDir)) return [];
|
|
3760
|
-
return fs2.readdirSync(cartsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path3.join(cartsDir, e.name)).filter((d) => fs2.existsSync(path3.join(d, "schema.nbt")));
|
|
3571
|
+
return void 0;
|
|
3761
3572
|
}
|
|
3762
|
-
function
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
if (e.name !== ".dist" && e.name !== "node_modules") walk(p);
|
|
3775
|
-
} else if (e.name.endsWith(".nbt")) {
|
|
3776
|
-
try {
|
|
3777
|
-
const m = fs2.statSync(p).mtimeMs;
|
|
3778
|
-
if (m > newest) newest = m;
|
|
3779
|
-
} catch {
|
|
3780
|
-
}
|
|
3573
|
+
function projectBranchId() {
|
|
3574
|
+
if (process.env.NBT_BRANCH_ID) return process.env.NBT_BRANCH_ID;
|
|
3575
|
+
let dir = process.cwd();
|
|
3576
|
+
for (let i = 0; i < 32; i++) {
|
|
3577
|
+
const candidate = path2.join(dir, "nbt.json");
|
|
3578
|
+
if (fs2.existsSync(candidate)) {
|
|
3579
|
+
try {
|
|
3580
|
+
const cfg = JSON.parse(fs2.readFileSync(candidate, "utf8"));
|
|
3581
|
+
const b = cfg?.branch;
|
|
3582
|
+
if (typeof b === "string" && b) return b;
|
|
3583
|
+
} catch {
|
|
3584
|
+
break;
|
|
3781
3585
|
}
|
|
3586
|
+
return void 0;
|
|
3782
3587
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
}
|
|
3787
|
-
|
|
3788
|
-
return new Promise((resolve3) => {
|
|
3789
|
-
const sock = net.connect({ port, host });
|
|
3790
|
-
sock.once("connect", () => {
|
|
3791
|
-
sock.destroy();
|
|
3792
|
-
resolve3(true);
|
|
3793
|
-
});
|
|
3794
|
-
sock.once("error", () => resolve3(false));
|
|
3795
|
-
sock.setTimeout(500, () => {
|
|
3796
|
-
sock.destroy();
|
|
3797
|
-
resolve3(false);
|
|
3798
|
-
});
|
|
3799
|
-
});
|
|
3588
|
+
const parent = path2.dirname(dir);
|
|
3589
|
+
if (parent === dir) break;
|
|
3590
|
+
dir = parent;
|
|
3591
|
+
}
|
|
3592
|
+
return void 0;
|
|
3800
3593
|
}
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
);
|
|
3811
|
-
return r.status === 0;
|
|
3594
|
+
function storedApiKey() {
|
|
3595
|
+
try {
|
|
3596
|
+
const p = path2.join(os.homedir(), ".nbt", "credentials.json");
|
|
3597
|
+
const creds = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
3598
|
+
const key = creds?.apiKey;
|
|
3599
|
+
return typeof key === "string" && key.length > 0 ? key : void 0;
|
|
3600
|
+
} catch {
|
|
3601
|
+
return void 0;
|
|
3602
|
+
}
|
|
3812
3603
|
}
|
|
3813
|
-
|
|
3814
|
-
const
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
const upArgs = ["up", "--port", String(port)];
|
|
3819
|
-
if (opts.dataDir) upArgs.push("--data-dir", opts.dataDir);
|
|
3820
|
-
console.log(`nbt dev: starting console on :${port}`);
|
|
3821
|
-
const daemon = (0, import_node_child_process2.spawn)(CONSOLE_BIN, upArgs, {
|
|
3822
|
-
stdio: "inherit",
|
|
3823
|
-
env: consoleEnv()
|
|
3824
|
-
});
|
|
3825
|
-
let shuttingDown = false;
|
|
3826
|
-
const shutdown = (code = 0) => {
|
|
3827
|
-
if (shuttingDown) return;
|
|
3828
|
-
shuttingDown = true;
|
|
3829
|
-
try {
|
|
3830
|
-
daemon.kill("SIGTERM");
|
|
3831
|
-
} catch {
|
|
3832
|
-
}
|
|
3833
|
-
process.exit(code);
|
|
3834
|
-
};
|
|
3835
|
-
process.on("SIGINT", () => shutdown(0));
|
|
3836
|
-
process.on("SIGTERM", () => shutdown(0));
|
|
3837
|
-
daemon.on("exit", (code) => {
|
|
3838
|
-
if (!shuttingDown) {
|
|
3839
|
-
console.error(`nbt dev: console exited (${code ?? "signal"})`);
|
|
3840
|
-
process.exit(code ?? 1);
|
|
3841
|
-
}
|
|
3842
|
-
});
|
|
3843
|
-
let ready = false;
|
|
3844
|
-
for (let i = 0; i < 60; i++) {
|
|
3845
|
-
if (await tcpUp(port)) {
|
|
3846
|
-
ready = true;
|
|
3847
|
-
break;
|
|
3848
|
-
}
|
|
3849
|
-
await sleep(250);
|
|
3850
|
-
}
|
|
3851
|
-
if (!ready) {
|
|
3852
|
-
console.error("nbt dev: console did not come up in time");
|
|
3853
|
-
return shutdown(1);
|
|
3854
|
-
}
|
|
3855
|
-
const carts = discoverCarts(proj.cartsDir);
|
|
3856
|
-
if (carts.length === 0) {
|
|
3857
|
-
console.warn(
|
|
3858
|
-
`nbt dev: no carts found under ${proj.cartsDir} (nothing to watch)`
|
|
3604
|
+
function forwardToAgent(args) {
|
|
3605
|
+
const entry = resolveAgentEntry();
|
|
3606
|
+
if (!entry) {
|
|
3607
|
+
console.error(
|
|
3608
|
+
"@nbt-dev/nbt: coding agent not found (expected agent/packages/coding-agent/dist/cli.js). Build it with `npm run build` in npm/agent."
|
|
3859
3609
|
);
|
|
3610
|
+
process.exit(1);
|
|
3860
3611
|
}
|
|
3861
|
-
const
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3612
|
+
const env = nativeEnv();
|
|
3613
|
+
env.NBT_CONSOLE_URL = projectConsoleUrl();
|
|
3614
|
+
const llmUrl = projectLlmUrl();
|
|
3615
|
+
if (llmUrl) env.NBT_LLM_URL = llmUrl;
|
|
3616
|
+
const branchId = projectBranchId();
|
|
3617
|
+
if (branchId) env.NBT_BRANCH_ID = branchId;
|
|
3618
|
+
if (!env.NBT_API_KEY) {
|
|
3619
|
+
const key = storedApiKey();
|
|
3620
|
+
if (key) env.NBT_API_KEY = key;
|
|
3621
|
+
}
|
|
3622
|
+
const r = (0, import_node_child_process2.spawnSync)(process.execPath, [entry, ...args], {
|
|
3623
|
+
stdio: "inherit",
|
|
3624
|
+
env
|
|
3625
|
+
});
|
|
3626
|
+
if (r.error) {
|
|
3627
|
+
console.error(`@nbt-dev/nbt: failed to launch agent: ${r.error.message}`);
|
|
3628
|
+
process.exit(1);
|
|
3866
3629
|
}
|
|
3867
|
-
|
|
3868
|
-
`nbt dev: watching ${proj.cartsDir} \u2014 edit .nbt to reload (Ctrl-C to stop)`
|
|
3869
|
-
);
|
|
3870
|
-
let busy = false;
|
|
3871
|
-
setInterval(async () => {
|
|
3872
|
-
if (busy || shuttingDown) return;
|
|
3873
|
-
busy = true;
|
|
3874
|
-
try {
|
|
3875
|
-
for (const c of discoverCarts(proj.cartsDir)) {
|
|
3876
|
-
const m = newestNbtMtime(c);
|
|
3877
|
-
const prev = applied.get(c) ?? 0;
|
|
3878
|
-
if (m > prev) {
|
|
3879
|
-
await sleep(150);
|
|
3880
|
-
const settled = newestNbtMtime(c);
|
|
3881
|
-
console.log(`nbt dev: reload ${path3.basename(c)}`);
|
|
3882
|
-
installCart(c, port);
|
|
3883
|
-
applied.set(c, settled);
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
} finally {
|
|
3887
|
-
busy = false;
|
|
3888
|
-
}
|
|
3889
|
-
}, 400);
|
|
3630
|
+
process.exit(r.status === null ? 1 : r.status);
|
|
3890
3631
|
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3632
|
+
function packageVersion() {
|
|
3633
|
+
try {
|
|
3634
|
+
const pkg = JSON.parse(
|
|
3635
|
+
fs2.readFileSync(path2.join(__dirname, "..", "package.json"), "utf8")
|
|
3636
|
+
);
|
|
3637
|
+
return typeof pkg?.version === "string" ? pkg.version : "unknown";
|
|
3638
|
+
} catch {
|
|
3639
|
+
return "unknown";
|
|
3896
3640
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3641
|
+
}
|
|
3642
|
+
var program2 = new Command();
|
|
3643
|
+
program2.name("nbt").description("nbt.dev CLI").version(packageVersion(), "-v, --version", "Print the @nbt-dev/nbt version");
|
|
3644
|
+
var NBT = [
|
|
3645
|
+
["init", "Scaffold an nbt project"],
|
|
3646
|
+
["add", "Vendor standard cartridges into this project"],
|
|
3647
|
+
["list", "List available standard-library cartridges"],
|
|
3648
|
+
["update", "Refresh vendored cartridges from the stdlib"],
|
|
3649
|
+
["dev", "Boot the NBT runtime with live reload"],
|
|
3650
|
+
["build", "Build the frontend (esbuild SPA + SSR)"],
|
|
3651
|
+
["install", "Install a cartridge on a live instance"],
|
|
3652
|
+
["generate", "Generate a typed client from contracts"],
|
|
3653
|
+
["routes", "Generate file-based routes from app/"],
|
|
3654
|
+
["migrate", "Create migrations and deploy local cartridges"],
|
|
3655
|
+
["lsp", "Run the NBT language server over stdio (editors)"],
|
|
3656
|
+
["editor", "Install the NBT VS Code extension (editor install)"]
|
|
3657
|
+
];
|
|
3658
|
+
var CONSOLE = [
|
|
3659
|
+
["up", "Boot the NBT runtime"],
|
|
3660
|
+
["gen-cluster-key", "Generate a cluster signing key (copy to every node)"]
|
|
3661
|
+
];
|
|
3662
|
+
var consoleCommands = new Set(CONSOLE.map(([n]) => n));
|
|
3663
|
+
for (const [name, summary] of [...NBT, ...CONSOLE]) {
|
|
3664
|
+
program2.command(name).description(`${summary} (native)`);
|
|
3665
|
+
}
|
|
3666
|
+
program2.command("agent").description("Launch the nbt coding agent (defaults to the nbt provider)");
|
|
3667
|
+
var customCommands = /* @__PURE__ */ new Set();
|
|
3899
3668
|
customCommands.add("version");
|
|
3900
3669
|
program2.command("version").description("Print the @nbt-dev/nbt version").action(() => {
|
|
3901
3670
|
process.stdout.write(packageVersion() + "\n");
|
|
@@ -3942,6 +3711,7 @@ function runCompletion(words) {
|
|
|
3942
3711
|
if (words.length <= 1) {
|
|
3943
3712
|
const prefix = words[0] ?? "";
|
|
3944
3713
|
const names = [
|
|
3714
|
+
"agent",
|
|
3945
3715
|
...NBT.map(([n]) => n),
|
|
3946
3716
|
...CONSOLE.map(([n]) => n),
|
|
3947
3717
|
...customCommands
|
|
@@ -3966,6 +3736,9 @@ var first = argv[0];
|
|
|
3966
3736
|
if (first === "__complete__") {
|
|
3967
3737
|
runCompletion(argv.slice(1));
|
|
3968
3738
|
}
|
|
3739
|
+
if (first === "agent") {
|
|
3740
|
+
forwardToAgent(argv.slice(1));
|
|
3741
|
+
}
|
|
3969
3742
|
if (!first || first === "-h" || first === "--help" || first === "help") {
|
|
3970
3743
|
program2.outputHelp();
|
|
3971
3744
|
process.exit(first ? 0 : 1);
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbt-dev/nbt",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "The nbt CLI and
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "The nbt CLI and runtime for nbt-dev — a schema-first backend platform for web development. Linux/x64 + macOS prebuilt binaries.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nbt": "dist/nbt.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"dist/",
|
|
10
10
|
"vendor/linux-x64/",
|
|
11
|
+
"vendor/darwin-arm64/",
|
|
12
|
+
"vendor/darwin-x64/",
|
|
11
13
|
"stdlib/",
|
|
12
14
|
"README.md",
|
|
13
15
|
"LICENSE",
|
|
@@ -19,10 +21,12 @@
|
|
|
19
21
|
"prepack": "node scripts/build.mjs"
|
|
20
22
|
},
|
|
21
23
|
"os": [
|
|
22
|
-
"linux"
|
|
24
|
+
"linux",
|
|
25
|
+
"darwin"
|
|
23
26
|
],
|
|
24
27
|
"cpu": [
|
|
25
|
-
"x64"
|
|
28
|
+
"x64",
|
|
29
|
+
"arm64"
|
|
26
30
|
],
|
|
27
31
|
"engines": {
|
|
28
32
|
"node": ">=16"
|
package/stdlib/auth/schema.nbt
CHANGED
|
@@ -67,9 +67,9 @@ entity Account {
|
|
|
67
67
|
userId: string
|
|
68
68
|
providerId: string
|
|
69
69
|
password: string
|
|
70
|
-
accessToken: string
|
|
71
|
-
refreshToken: string
|
|
72
|
-
idToken: string
|
|
70
|
+
accessToken: string @secret
|
|
71
|
+
refreshToken: string @secret
|
|
72
|
+
idToken: string @secret
|
|
73
73
|
accessTokenExpiresAt: DateTime
|
|
74
74
|
refreshTokenExpiresAt: DateTime
|
|
75
75
|
scope: string
|
|
@@ -99,6 +99,9 @@ entity ApiKey {
|
|
|
99
99
|
key: string
|
|
100
100
|
permissions: string
|
|
101
101
|
roles: string
|
|
102
|
+
|
|
103
|
+
@@index([key])
|
|
104
|
+
@@index([projectId])
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
# Console-operator SSH public keys. The console daemon authenticates SSH
|
package/stdlib/dns/schema.nbt
CHANGED
|
@@ -21,7 +21,7 @@ entity DnsProvider {
|
|
|
21
21
|
updatedAt: DateTime @updatedAt
|
|
22
22
|
type: string # "cloudflare"
|
|
23
23
|
label: string # user-facing name ("my CF account")
|
|
24
|
-
apiToken: string
|
|
24
|
+
apiToken: string @secret # provider API token (sealed at rest)
|
|
25
25
|
accountId: string # CF account_id, needed for add_zone + registrar calls (optional)
|
|
26
26
|
status: string # "ACTIVE" | "INVALID" | "DISCONNECTED"
|
|
27
27
|
lastCheckedAt: u64 # ms epoch — updated on connect + test
|
package/stdlib/ingest/schema.nbt
CHANGED
package/stdlib/phone/schema.nbt
CHANGED
|
@@ -24,7 +24,7 @@ entity TwilioAccount {
|
|
|
24
24
|
updatedAt: DateTime @updatedAt
|
|
25
25
|
name: string # operator-facing label
|
|
26
26
|
subAccountSid: string # AC... — populated by provision()
|
|
27
|
-
authToken: string
|
|
27
|
+
authToken: string @secret # subaccount auth token (sealed at rest)
|
|
28
28
|
publicUrlBase: string # e.g. "https://acme.console.app"
|
|
29
29
|
status: string # PENDING | ACTIVE | SUSPENDED | CLOSED
|
|
30
30
|
retentionDays: s32 # 0 = keep message bodies forever; N = null body after N days
|
|
@@ -31,7 +31,7 @@ entity GsheetSync {
|
|
|
31
31
|
createdAt: DateTime @default(now())
|
|
32
32
|
updatedAt: DateTime @updatedAt
|
|
33
33
|
spreadsheetId: string # parsed from the operator-provided sheet URL
|
|
34
|
-
serviceAccountJson: string # SECRET — service-account key JSON
|
|
34
|
+
serviceAccountJson: string @secret # SECRET — service-account key JSON (sealed at rest)
|
|
35
35
|
saEmail: string # service-account client_email (shown in devtools)
|
|
36
36
|
intervalSeconds: u32 # poll cadence
|
|
37
37
|
enabled: bool # sync on/off
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Secrets cart — storage substrate for named, per-environment config secrets
|
|
2
|
+
# (STRIPE_KEY, OPENAI_KEY, ...). The console daemon is the single writer
|
|
3
|
+
# (modules/api/secrets.jai); the generated CRUD is not the intended interface —
|
|
4
|
+
# operators set values via POST /_console/secrets (or `nbt secret set`).
|
|
5
|
+
#
|
|
6
|
+
# `value` is @secret: sealed (AEAD) at rest, replicated as ciphertext, and never
|
|
7
|
+
# returned by any read path. The row id is "<env>:<name>" so set/get are
|
|
8
|
+
# idempotent without a secondary index.
|
|
9
|
+
entity Secret {
|
|
10
|
+
id: string @id # "<env>:<name>"
|
|
11
|
+
createdAt: DateTime @default(now())
|
|
12
|
+
updatedAt: DateTime @updatedAt
|
|
13
|
+
name: string # slot name, e.g. STRIPE_KEY
|
|
14
|
+
env: string # "dev" | "staging" | "prod"
|
|
15
|
+
value: string @secret # sealed config value
|
|
16
|
+
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# The latest deployed read-only VIEW bundle (the JS artifact serving GET /view/*).
|
|
2
|
+
# Unlike WorkflowBundle there is no per-instance version pinning — views are
|
|
3
|
+
# stateless on-read reductions, so only the newest row matters. It is persisted so
|
|
4
|
+
# a deploy-activated view bundle survives a daemon restart (reloaded by
|
|
5
|
+
# view_serve_init); `version` is a monotonic deploy counter, `contentHash` dedups
|
|
6
|
+
# a redeploy of identical bytes.
|
|
7
|
+
entity ViewBundle {
|
|
8
|
+
id: ulid
|
|
9
|
+
createdAt: DateTime @default(now())
|
|
10
|
+
updatedAt: DateTime @updatedAt
|
|
11
|
+
version: u32
|
|
12
|
+
source: string
|
|
13
|
+
contentHash: string
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
# An immutable, versioned workflow bundle (the JS artifact). Deploying new code
|
|
2
17
|
# registers a new version; in-flight executions keep replaying against the version
|
|
3
18
|
# they started on (the journal-header pin), so a code change can never corrupt a
|
|
@@ -22,14 +37,21 @@ entity WorkflowExecution {
|
|
|
22
37
|
createdAt: DateTime @default(now())
|
|
23
38
|
updatedAt: DateTime @updatedAt
|
|
24
39
|
status: string # RUNNING (incl. async host call in flight) | SUSPENDED | COMPLETED | FAILED | CANCELLED
|
|
40
|
+
workflowId?: string # OPTIONAL caller-chosen id (Phase 9b) for start dedup; the PK stays an auto-ULID (FK-safe). Unset on cron/event/most starts.
|
|
25
41
|
workflowName: string
|
|
26
42
|
args?: string # JSON arguments passed to runWorkflow
|
|
27
43
|
result?: string # JSON return value (COMPLETED)
|
|
28
44
|
error?: string # message + stack (FAILED)
|
|
29
45
|
cursor: u32
|
|
46
|
+
wakeAt: u64 # durable timer (Phase 9a): ms-epoch when a parked sleep is due (0 = not sleeping). The leader reconcile resumes the instance once wall-clock passes it.
|
|
47
|
+
parentExec?: string # child workflow (Phase 9e): the parent execution id to resume with this child's result on completion
|
|
48
|
+
parentSeq?: u32 # the parent's host-call ordinal awaiting this child's result
|
|
30
49
|
lane?: string # fairness lane; per-lane concurrency is capped so a burst of one lane can't starve others (default "default")
|
|
31
50
|
deployVersion: u32 # the bundle version this instance pins for its entire life (journal-header pin); replay always runs against this exact version
|
|
32
51
|
events: WorkflowExecutionEvent[]
|
|
52
|
+
signals: WorkflowSignal[] # buffered signals awaiting delivery (Phase 9d)
|
|
53
|
+
@@unique([workflowId]) # start dedup: a provided workflowId is unique (NULLs distinct, so unset starts never collide)
|
|
54
|
+
@@index([workflowId])
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
# A recurring trigger: fire `workflowName` whenever the cron expression matches.
|
|
@@ -62,6 +84,26 @@ entity WorkflowEvent {
|
|
|
62
84
|
processed: bool # set true once the leader has fired this emission's triggers
|
|
63
85
|
}
|
|
64
86
|
|
|
87
|
+
# A durable, buffered signal for an execution (Phase 9d). A signal that arrives
|
|
88
|
+
# before the workflow reaches the matching wait() — or for a name other than the
|
|
89
|
+
# one currently awaited — is stored here instead of being rejected; when the
|
|
90
|
+
# workflow parks on wait(name) it drains the oldest unconsumed matching signal and
|
|
91
|
+
# delivers it as the wait's result. id is a ULID so unconsumed signals drain in
|
|
92
|
+
# arrival order. A signal that arrives WHILE parked on the matching wait is
|
|
93
|
+
# delivered immediately (fast path) and never buffered.
|
|
94
|
+
entity WorkflowSignal {
|
|
95
|
+
id: ulid
|
|
96
|
+
createdAt: DateTime @default(now())
|
|
97
|
+
updatedAt: DateTime @updatedAt
|
|
98
|
+
execution: WorkflowExecution @relation(onDelete: Cascade) # the target instance
|
|
99
|
+
name: string # signal name, matched against wait(name)
|
|
100
|
+
payload?: string # JSON value delivered as the wait result
|
|
101
|
+
idempotencyKey?: string # optional caller dedup key; a re-POST with the same key is a no-op
|
|
102
|
+
consumed: bool # set true once delivered to a wait
|
|
103
|
+
@@index([execution])
|
|
104
|
+
@@unique([idempotencyKey]) # NULLs distinct (null-skip), so unkeyed signals never collide
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
enum WorkflowExecutionEventKind {
|
|
66
108
|
HOST_CALL_INTENT # recorded BEFORE a side effect is performed (durability)
|
|
67
109
|
HOST_CALL_RESULT # the resolved value of a host command (replay source)
|
|
@@ -67,9 +67,9 @@ entity Account {
|
|
|
67
67
|
userId: string
|
|
68
68
|
providerId: string
|
|
69
69
|
password: string
|
|
70
|
-
accessToken: string
|
|
71
|
-
refreshToken: string
|
|
72
|
-
idToken: string
|
|
70
|
+
accessToken: string @secret
|
|
71
|
+
refreshToken: string @secret
|
|
72
|
+
idToken: string @secret
|
|
73
73
|
accessTokenExpiresAt: DateTime
|
|
74
74
|
refreshTokenExpiresAt: DateTime
|
|
75
75
|
scope: string
|
|
@@ -99,6 +99,9 @@ entity ApiKey {
|
|
|
99
99
|
key: string
|
|
100
100
|
permissions: string
|
|
101
101
|
roles: string
|
|
102
|
+
|
|
103
|
+
@@index([key])
|
|
104
|
+
@@index([projectId])
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
# Console-operator SSH public keys. The console daemon authenticates SSH
|
|
@@ -21,7 +21,7 @@ entity DnsProvider {
|
|
|
21
21
|
updatedAt: DateTime @updatedAt
|
|
22
22
|
type: string # "cloudflare"
|
|
23
23
|
label: string # user-facing name ("my CF account")
|
|
24
|
-
apiToken: string
|
|
24
|
+
apiToken: string @secret # provider API token (sealed at rest)
|
|
25
25
|
accountId: string # CF account_id, needed for add_zone + registrar calls (optional)
|
|
26
26
|
status: string # "ACTIVE" | "INVALID" | "DISCONNECTED"
|
|
27
27
|
lastCheckedAt: u64 # ms epoch — updated on connect + test
|
|
@@ -24,7 +24,7 @@ entity TwilioAccount {
|
|
|
24
24
|
updatedAt: DateTime @updatedAt
|
|
25
25
|
name: string # operator-facing label
|
|
26
26
|
subAccountSid: string # AC... — populated by provision()
|
|
27
|
-
authToken: string
|
|
27
|
+
authToken: string @secret # subaccount auth token (sealed at rest)
|
|
28
28
|
publicUrlBase: string # e.g. "https://acme.console.app"
|
|
29
29
|
status: string # PENDING | ACTIVE | SUSPENDED | CLOSED
|
|
30
30
|
retentionDays: s32 # 0 = keep message bodies forever; N = null body after N days
|
|
@@ -31,7 +31,7 @@ entity GsheetSync {
|
|
|
31
31
|
createdAt: DateTime @default(now())
|
|
32
32
|
updatedAt: DateTime @updatedAt
|
|
33
33
|
spreadsheetId: string # parsed from the operator-provided sheet URL
|
|
34
|
-
serviceAccountJson: string # SECRET — service-account key JSON
|
|
34
|
+
serviceAccountJson: string @secret # SECRET — service-account key JSON (sealed at rest)
|
|
35
35
|
saEmail: string # service-account client_email (shown in devtools)
|
|
36
36
|
intervalSeconds: u32 # poll cadence
|
|
37
37
|
enabled: bool # sync on/off
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Secrets cart — storage substrate for named, per-environment config secrets
|
|
2
|
+
# (STRIPE_KEY, OPENAI_KEY, ...). The console daemon is the single writer
|
|
3
|
+
# (modules/api/secrets.jai); the generated CRUD is not the intended interface —
|
|
4
|
+
# operators set values via POST /_console/secrets (or `nbt secret set`).
|
|
5
|
+
#
|
|
6
|
+
# `value` is @secret: sealed (AEAD) at rest, replicated as ciphertext, and never
|
|
7
|
+
# returned by any read path. The row id is "<env>:<name>" so set/get are
|
|
8
|
+
# idempotent without a secondary index.
|
|
9
|
+
entity Secret {
|
|
10
|
+
id: string @id # "<env>:<name>"
|
|
11
|
+
createdAt: DateTime @default(now())
|
|
12
|
+
updatedAt: DateTime @updatedAt
|
|
13
|
+
name: string # slot name, e.g. STRIPE_KEY
|
|
14
|
+
env: string # "dev" | "staging" | "prod"
|
|
15
|
+
value: string @secret # sealed config value
|
|
16
|
+
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# The latest deployed read-only VIEW bundle (the JS artifact serving GET /view/*).
|
|
2
|
+
# Unlike WorkflowBundle there is no per-instance version pinning — views are
|
|
3
|
+
# stateless on-read reductions, so only the newest row matters. It is persisted so
|
|
4
|
+
# a deploy-activated view bundle survives a daemon restart (reloaded by
|
|
5
|
+
# view_serve_init); `version` is a monotonic deploy counter, `contentHash` dedups
|
|
6
|
+
# a redeploy of identical bytes.
|
|
7
|
+
entity ViewBundle {
|
|
8
|
+
id: ulid
|
|
9
|
+
createdAt: DateTime @default(now())
|
|
10
|
+
updatedAt: DateTime @updatedAt
|
|
11
|
+
version: u32
|
|
12
|
+
source: string
|
|
13
|
+
contentHash: string
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
# An immutable, versioned workflow bundle (the JS artifact). Deploying new code
|
|
2
17
|
# registers a new version; in-flight executions keep replaying against the version
|
|
3
18
|
# they started on (the journal-header pin), so a code change can never corrupt a
|
|
@@ -22,14 +37,21 @@ entity WorkflowExecution {
|
|
|
22
37
|
createdAt: DateTime @default(now())
|
|
23
38
|
updatedAt: DateTime @updatedAt
|
|
24
39
|
status: string # RUNNING (incl. async host call in flight) | SUSPENDED | COMPLETED | FAILED | CANCELLED
|
|
40
|
+
workflowId?: string # OPTIONAL caller-chosen id (Phase 9b) for start dedup; the PK stays an auto-ULID (FK-safe). Unset on cron/event/most starts.
|
|
25
41
|
workflowName: string
|
|
26
42
|
args?: string # JSON arguments passed to runWorkflow
|
|
27
43
|
result?: string # JSON return value (COMPLETED)
|
|
28
44
|
error?: string # message + stack (FAILED)
|
|
29
45
|
cursor: u32
|
|
46
|
+
wakeAt: u64 # durable timer (Phase 9a): ms-epoch when a parked sleep is due (0 = not sleeping). The leader reconcile resumes the instance once wall-clock passes it.
|
|
47
|
+
parentExec?: string # child workflow (Phase 9e): the parent execution id to resume with this child's result on completion
|
|
48
|
+
parentSeq?: u32 # the parent's host-call ordinal awaiting this child's result
|
|
30
49
|
lane?: string # fairness lane; per-lane concurrency is capped so a burst of one lane can't starve others (default "default")
|
|
31
50
|
deployVersion: u32 # the bundle version this instance pins for its entire life (journal-header pin); replay always runs against this exact version
|
|
32
51
|
events: WorkflowExecutionEvent[]
|
|
52
|
+
signals: WorkflowSignal[] # buffered signals awaiting delivery (Phase 9d)
|
|
53
|
+
@@unique([workflowId]) # start dedup: a provided workflowId is unique (NULLs distinct, so unset starts never collide)
|
|
54
|
+
@@index([workflowId])
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
# A recurring trigger: fire `workflowName` whenever the cron expression matches.
|
|
@@ -62,6 +84,26 @@ entity WorkflowEvent {
|
|
|
62
84
|
processed: bool # set true once the leader has fired this emission's triggers
|
|
63
85
|
}
|
|
64
86
|
|
|
87
|
+
# A durable, buffered signal for an execution (Phase 9d). A signal that arrives
|
|
88
|
+
# before the workflow reaches the matching wait() — or for a name other than the
|
|
89
|
+
# one currently awaited — is stored here instead of being rejected; when the
|
|
90
|
+
# workflow parks on wait(name) it drains the oldest unconsumed matching signal and
|
|
91
|
+
# delivers it as the wait's result. id is a ULID so unconsumed signals drain in
|
|
92
|
+
# arrival order. A signal that arrives WHILE parked on the matching wait is
|
|
93
|
+
# delivered immediately (fast path) and never buffered.
|
|
94
|
+
entity WorkflowSignal {
|
|
95
|
+
id: ulid
|
|
96
|
+
createdAt: DateTime @default(now())
|
|
97
|
+
updatedAt: DateTime @updatedAt
|
|
98
|
+
execution: WorkflowExecution @relation(onDelete: Cascade) # the target instance
|
|
99
|
+
name: string # signal name, matched against wait(name)
|
|
100
|
+
payload?: string # JSON value delivered as the wait result
|
|
101
|
+
idempotencyKey?: string # optional caller dedup key; a re-POST with the same key is a no-op
|
|
102
|
+
consumed: bool # set true once delivered to a wait
|
|
103
|
+
@@index([execution])
|
|
104
|
+
@@unique([idempotencyKey]) # NULLs distinct (null-skip), so unkeyed signals never collide
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
enum WorkflowExecutionEventKind {
|
|
66
108
|
HOST_CALL_INTENT # recorded BEFORE a side effect is performed (durability)
|
|
67
109
|
HOST_CALL_RESULT # the resolved value of a host command (replay source)
|
package/vendor/linux-x64/console
CHANGED
|
Binary file
|
|
Binary file
|
package/vendor/linux-x64/nbt
CHANGED
|
Binary file
|