busa-cli 0.9.8 → 0.9.10
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/dist/{chunk-37OV7S7G.js → chunk-IO4ARUEI.js} +1340 -564
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -2
|
@@ -120,7 +120,7 @@ function nodeIcon(type) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// src/run.ts
|
|
123
|
-
import { createHash as
|
|
123
|
+
import { createHash as createHash6 } from "crypto";
|
|
124
124
|
import { readFileSync as readFileSync2 } from "fs";
|
|
125
125
|
import { basename, extname as extname2 } from "path";
|
|
126
126
|
import {
|
|
@@ -137,6 +137,566 @@ import {
|
|
|
137
137
|
Option
|
|
138
138
|
} from "commander";
|
|
139
139
|
|
|
140
|
+
// src/backup/export/full-exporter.ts
|
|
141
|
+
import { createHash as createHash2 } from "crypto";
|
|
142
|
+
|
|
143
|
+
// ../../packages/busabase-contract/src/domains/dump/types.ts
|
|
144
|
+
import { z } from "zod";
|
|
145
|
+
var DumpTableSchema = z.enum([
|
|
146
|
+
"nodes",
|
|
147
|
+
/** Node-level access grants (permissions). Not secret — restore must keep them
|
|
148
|
+
* so a backed-up space's sharing/permission config survives a full restore. */
|
|
149
|
+
"nodePrincipals",
|
|
150
|
+
"bases",
|
|
151
|
+
"baseFields",
|
|
152
|
+
"views",
|
|
153
|
+
"records",
|
|
154
|
+
"fieldValues",
|
|
155
|
+
"recordLinks",
|
|
156
|
+
/** The physical bytes registry (open-domains/attachments) an Asset's `attachmentId` FKs into. */
|
|
157
|
+
"attachments",
|
|
158
|
+
"assets",
|
|
159
|
+
"assetUsages",
|
|
160
|
+
"assetTexts",
|
|
161
|
+
"commits",
|
|
162
|
+
"changeRequests",
|
|
163
|
+
"operations",
|
|
164
|
+
"comments",
|
|
165
|
+
"reviews",
|
|
166
|
+
"auditEvents"
|
|
167
|
+
]);
|
|
168
|
+
var ExportTablesInputSchema = z.object({
|
|
169
|
+
table: DumpTableSchema,
|
|
170
|
+
/** Opaque pagination cursor from a previous page's `nextCursor`; omit for the first page. */
|
|
171
|
+
cursor: z.string().optional(),
|
|
172
|
+
/** Page size, default 500, capped at 2000. */
|
|
173
|
+
limit: z.number().int().positive().max(2e3).optional().default(500)
|
|
174
|
+
});
|
|
175
|
+
var ExportTablesVOSchema = z.object({
|
|
176
|
+
rows: z.array(z.record(z.string(), z.unknown())),
|
|
177
|
+
nextCursor: z.string().nullable()
|
|
178
|
+
});
|
|
179
|
+
var ExportAssetTextInputSchema = z.object({ assetId: z.string() });
|
|
180
|
+
var ExportAssetTextVOSchema = z.object({
|
|
181
|
+
assetId: z.string(),
|
|
182
|
+
/** The row's `text_storage_key` — the exact key a restore must write back to. */
|
|
183
|
+
textStorageKey: z.string(),
|
|
184
|
+
/**
|
|
185
|
+
* `null` when this row owns no separate text object and therefore needs no
|
|
186
|
+
* blob in the archive:
|
|
187
|
+
* - auto-registered text-kind rows (`writtenBy: "auto"`), whose
|
|
188
|
+
* `textStorageKey` IS the owning attachment's own key — those bytes are
|
|
189
|
+
* already archived by the attachment-blob pass, and re-archiving them
|
|
190
|
+
* would duplicate bytes and collide on restore;
|
|
191
|
+
* - `status: "none"` rows (no extractable text), whose key is `""`.
|
|
192
|
+
*/
|
|
193
|
+
downloadUrl: z.string().nullable(),
|
|
194
|
+
/** `sha256:<hex>` of the text bytes, so a backup can verify what it downloaded. */
|
|
195
|
+
textContentHash: z.string().nullable(),
|
|
196
|
+
byteCount: z.number().int().nonnegative()
|
|
197
|
+
});
|
|
198
|
+
var ImportBeginVOSchema = z.object({
|
|
199
|
+
sessionId: z.string()
|
|
200
|
+
});
|
|
201
|
+
var ImportTablesInputSchema = z.object({
|
|
202
|
+
sessionId: z.string(),
|
|
203
|
+
table: z.union([
|
|
204
|
+
DumpTableSchema,
|
|
205
|
+
z.literal("docBodies"),
|
|
206
|
+
z.literal("attachmentBlobs"),
|
|
207
|
+
z.literal("assetTextBlobs")
|
|
208
|
+
]),
|
|
209
|
+
rows: z.array(z.record(z.string(), z.unknown()))
|
|
210
|
+
});
|
|
211
|
+
var ImportTablesVOSchema = z.object({
|
|
212
|
+
inserted: z.number().int().nonnegative()
|
|
213
|
+
});
|
|
214
|
+
var ImportSessionInputSchema = z.object({
|
|
215
|
+
sessionId: z.string()
|
|
216
|
+
});
|
|
217
|
+
var ImportCommitVOSchema = z.object({
|
|
218
|
+
ok: z.boolean(),
|
|
219
|
+
warnings: z.array(z.string())
|
|
220
|
+
});
|
|
221
|
+
var ImportAbortVOSchema = z.object({
|
|
222
|
+
ok: z.boolean()
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// src/backup/format/archive-writer.ts
|
|
226
|
+
import { createHash } from "crypto";
|
|
227
|
+
import { createWriteStream } from "fs";
|
|
228
|
+
import { PassThrough } from "stream";
|
|
229
|
+
import { pipeline } from "stream/promises";
|
|
230
|
+
import { ZstdCompress } from "zlib";
|
|
231
|
+
import * as tar from "tar-stream";
|
|
232
|
+
|
|
233
|
+
// src/backup/format/manifest.ts
|
|
234
|
+
import { z as z2 } from "zod";
|
|
235
|
+
var FORMAT_VERSION = 1;
|
|
236
|
+
var ManifestSchema = z2.object({
|
|
237
|
+
formatVersion: z2.literal(FORMAT_VERSION),
|
|
238
|
+
toolVersion: z2.string(),
|
|
239
|
+
exportedAt: z2.string(),
|
|
240
|
+
spaceId: z2.string(),
|
|
241
|
+
sourceHost: z2.string(),
|
|
242
|
+
fidelity: z2.enum(["full", "state"]),
|
|
243
|
+
/** Vault items and webhook signing secrets are never written to the archive. */
|
|
244
|
+
excludesSecrets: z2.literal(true),
|
|
245
|
+
tables: z2.record(z2.string(), z2.number().int().nonnegative()),
|
|
246
|
+
blobCount: z2.number().int().nonnegative(),
|
|
247
|
+
blobBytes: z2.number().int().nonnegative(),
|
|
248
|
+
/**
|
|
249
|
+
* Extracted-text ("Drive Grep Retrieval") blobs, counted separately from the
|
|
250
|
+
* attachment blobs above because they are a different object class — derived
|
|
251
|
+
* text, content-addressed under `asset-texts/blobs/sha256/…`, one per
|
|
252
|
+
* `busabase_asset_texts` row that owns its own object.
|
|
253
|
+
*
|
|
254
|
+
* `.default(0)` rather than required: archives written before asset-text
|
|
255
|
+
* blobs were captured at all carry no such field, and those are exactly the
|
|
256
|
+
* archives an operator most needs to be able to still open — the import's
|
|
257
|
+
* integrity pass is what tells them the text is missing, which it can only
|
|
258
|
+
* do if the manifest parses in the first place.
|
|
259
|
+
*/
|
|
260
|
+
textBlobCount: z2.number().int().nonnegative().optional().default(0),
|
|
261
|
+
textBlobBytes: z2.number().int().nonnegative().optional().default(0),
|
|
262
|
+
/** sha256 hex digest — see the doc comment above for exactly what it covers. */
|
|
263
|
+
checksum: z2.string()
|
|
264
|
+
});
|
|
265
|
+
function canonicalizeEntryChecksums(entries) {
|
|
266
|
+
return entries.slice().sort((a, b) => a.path.localeCompare(b.path)).map((e) => `${e.path}:${e.sha256}`).join("\n");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/backup/format/archive-writer.ts
|
|
270
|
+
var ArchiveWriter = class _ArchiveWriter {
|
|
271
|
+
pack = tar.pack();
|
|
272
|
+
entryChecksums = [];
|
|
273
|
+
donePromise;
|
|
274
|
+
constructor(outPath) {
|
|
275
|
+
const zstd = new ZstdCompress();
|
|
276
|
+
this.donePromise = pipeline(this.pack, zstd, createWriteStream(outPath));
|
|
277
|
+
}
|
|
278
|
+
static create(outPath) {
|
|
279
|
+
return new _ArchiveWriter(outPath);
|
|
280
|
+
}
|
|
281
|
+
/** Add one archive entry from an in-memory buffer/string (small entries: manifest, base.json, etc). */
|
|
282
|
+
async addBuffer(path, data) {
|
|
283
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
284
|
+
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
285
|
+
this.entryChecksums.push({ path, sha256 });
|
|
286
|
+
await new Promise((resolvePromise, reject) => {
|
|
287
|
+
this.pack.entry(
|
|
288
|
+
{ name: path, size: buf.length },
|
|
289
|
+
buf,
|
|
290
|
+
(err) => err ? reject(err) : resolvePromise()
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/** Add one archive entry streamed from a Readable (large entries: records.ndjson, blobs). */
|
|
295
|
+
async addStream(path, size, source) {
|
|
296
|
+
const hash = createHash("sha256");
|
|
297
|
+
const entry = this.pack.entry({ name: path, size });
|
|
298
|
+
const tee = new PassThrough();
|
|
299
|
+
tee.on("data", (chunk) => hash.update(chunk));
|
|
300
|
+
await pipeline(source, tee, entry);
|
|
301
|
+
this.entryChecksums.push({ path, sha256: hash.digest("hex") });
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Write `manifest.json` (with the top-level checksum over every other
|
|
305
|
+
* entry) and close the archive. Must be called exactly once, after every
|
|
306
|
+
* other entry has been added.
|
|
307
|
+
*/
|
|
308
|
+
async finalize(manifestWithoutChecksum) {
|
|
309
|
+
const checksum = createHash("sha256").update(canonicalizeEntryChecksums(this.entryChecksums)).digest("hex");
|
|
310
|
+
const manifest = { ...manifestWithoutChecksum, checksum };
|
|
311
|
+
const buf = Buffer.from(JSON.stringify(manifest, null, 2), "utf8");
|
|
312
|
+
await new Promise((resolvePromise, reject) => {
|
|
313
|
+
this.pack.entry(
|
|
314
|
+
{ name: "manifest.json", size: buf.length },
|
|
315
|
+
buf,
|
|
316
|
+
(err) => err ? reject(err) : resolvePromise()
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
this.pack.finalize();
|
|
320
|
+
await this.donePromise;
|
|
321
|
+
return manifest;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/backup/export/full-exporter.ts
|
|
326
|
+
var EXPORT_TABLES = [...DumpTableSchema.options];
|
|
327
|
+
var HISTORY_TABLES = [
|
|
328
|
+
"commits",
|
|
329
|
+
"changeRequests",
|
|
330
|
+
"operations",
|
|
331
|
+
"comments",
|
|
332
|
+
"reviews",
|
|
333
|
+
"auditEvents"
|
|
334
|
+
];
|
|
335
|
+
async function exportFull(options) {
|
|
336
|
+
const { client, outPath, spaceId, sourceHost, includeHistory, toolVersion, onProgress } = options;
|
|
337
|
+
const pageLimit = options.pageLimit ?? 500;
|
|
338
|
+
const log = onProgress ?? (() => {
|
|
339
|
+
});
|
|
340
|
+
const writer = ArchiveWriter.create(outPath);
|
|
341
|
+
const tableCounts = {};
|
|
342
|
+
const tableRows = {};
|
|
343
|
+
const tables = includeHistory ? EXPORT_TABLES : EXPORT_TABLES.filter((t) => !HISTORY_TABLES.includes(t));
|
|
344
|
+
for (const table of tables) {
|
|
345
|
+
const rows = [];
|
|
346
|
+
let cursor;
|
|
347
|
+
do {
|
|
348
|
+
const page = await client.dump.exportTables({ table, cursor, limit: pageLimit });
|
|
349
|
+
rows.push(...page.rows);
|
|
350
|
+
cursor = page.nextCursor ?? void 0;
|
|
351
|
+
} while (cursor);
|
|
352
|
+
tableRows[table] = rows;
|
|
353
|
+
tableCounts[table] = rows.length;
|
|
354
|
+
const ndjson = `${rows.map((row) => JSON.stringify(row)).join("\n")}${rows.length ? "\n" : ""}`;
|
|
355
|
+
await writer.addBuffer(`tree/${table}.ndjson`, ndjson);
|
|
356
|
+
log(`exported ${table}: ${rows.length} rows`);
|
|
357
|
+
}
|
|
358
|
+
const docNodes = (tableRows.nodes ?? []).filter((n) => n.type === "doc");
|
|
359
|
+
let docBodyCount = 0;
|
|
360
|
+
for (const node of docNodes) {
|
|
361
|
+
const nodeId = node.id;
|
|
362
|
+
const doc = await client.docs.get({ nodeId });
|
|
363
|
+
await writer.addBuffer(`docs/${nodeId}.md`, doc.body ?? "");
|
|
364
|
+
docBodyCount += 1;
|
|
365
|
+
}
|
|
366
|
+
log(`exported ${docBodyCount} doc bodies`);
|
|
367
|
+
const attachmentsById = new Map((tableRows.attachments ?? []).map((a) => [a.id, a]));
|
|
368
|
+
const attachmentBlobRows = [];
|
|
369
|
+
let blobCount = 0;
|
|
370
|
+
let blobBytes = 0;
|
|
371
|
+
const skippedAssets = [];
|
|
372
|
+
for (const asset of tableRows.assets ?? []) {
|
|
373
|
+
const assetId = asset.id;
|
|
374
|
+
const attachment = attachmentsById.get(asset.attachmentId);
|
|
375
|
+
try {
|
|
376
|
+
if (!attachment) throw new Error("no matching attachments row (orphaned asset)");
|
|
377
|
+
const download = await client.assets.download({ assetId });
|
|
378
|
+
const resolvedUrl = new URL(download.downloadUrl, sourceHost).toString();
|
|
379
|
+
const res = await fetch(resolvedUrl);
|
|
380
|
+
if (!res.ok || !res.body) {
|
|
381
|
+
throw new Error(`HTTP ${res.status}`);
|
|
382
|
+
}
|
|
383
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
384
|
+
attachmentBlobRows.push({
|
|
385
|
+
storageKey: attachment.storageKey,
|
|
386
|
+
mimeType: download.mimeType,
|
|
387
|
+
base64: buf.toString("base64")
|
|
388
|
+
});
|
|
389
|
+
blobCount += 1;
|
|
390
|
+
blobBytes += buf.length;
|
|
391
|
+
} catch (error) {
|
|
392
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
393
|
+
skippedAssets.push(assetId);
|
|
394
|
+
log(` WARNING: could not download asset ${assetId}, skipping its blob (${message})`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const blobNdjson = `${attachmentBlobRows.map((row) => JSON.stringify(row)).join("\n")}${attachmentBlobRows.length ? "\n" : ""}`;
|
|
398
|
+
await writer.addBuffer("tree/attachmentBlobs.ndjson", blobNdjson);
|
|
399
|
+
log(
|
|
400
|
+
`exported ${blobCount} attachment blobs (${blobBytes} bytes), skipped ${skippedAssets.length}`
|
|
401
|
+
);
|
|
402
|
+
const textBlobRows = [];
|
|
403
|
+
const seenTextKeys = /* @__PURE__ */ new Set();
|
|
404
|
+
let textBlobCount = 0;
|
|
405
|
+
let textBlobBytes = 0;
|
|
406
|
+
const skippedAssetTexts = [];
|
|
407
|
+
for (const assetText of tableRows.assetTexts ?? []) {
|
|
408
|
+
const assetId = assetText.assetId;
|
|
409
|
+
try {
|
|
410
|
+
const info = await client.dump.exportAssetText({ assetId });
|
|
411
|
+
if (!info.downloadUrl) continue;
|
|
412
|
+
if (seenTextKeys.has(info.textStorageKey)) continue;
|
|
413
|
+
const resolvedUrl = new URL(info.downloadUrl, sourceHost).toString();
|
|
414
|
+
const res = await fetch(resolvedUrl);
|
|
415
|
+
if (!res.ok || !res.body) {
|
|
416
|
+
throw new Error(`HTTP ${res.status}`);
|
|
417
|
+
}
|
|
418
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
419
|
+
if (info.textContentHash) {
|
|
420
|
+
const actual = `sha256:${createHash2("sha256").update(buf).digest("hex")}`;
|
|
421
|
+
if (actual !== info.textContentHash) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`content hash mismatch (expected ${info.textContentHash}, downloaded ${actual})`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
seenTextKeys.add(info.textStorageKey);
|
|
428
|
+
textBlobRows.push({ textStorageKey: info.textStorageKey, base64: buf.toString("base64") });
|
|
429
|
+
textBlobCount += 1;
|
|
430
|
+
textBlobBytes += buf.length;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
433
|
+
skippedAssetTexts.push(assetId);
|
|
434
|
+
log(
|
|
435
|
+
` WARNING: could not download text for asset ${assetId}, skipping its blob (${message})`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const textBlobNdjson = `${textBlobRows.map((row) => JSON.stringify(row)).join("\n")}${textBlobRows.length ? "\n" : ""}`;
|
|
440
|
+
await writer.addBuffer("tree/assetTextBlobs.ndjson", textBlobNdjson);
|
|
441
|
+
log(
|
|
442
|
+
`exported ${textBlobCount} asset-text blobs (${textBlobBytes} bytes), skipped ${skippedAssetTexts.length}`
|
|
443
|
+
);
|
|
444
|
+
const manifestWithoutChecksum = {
|
|
445
|
+
formatVersion: FORMAT_VERSION,
|
|
446
|
+
toolVersion,
|
|
447
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
448
|
+
spaceId,
|
|
449
|
+
sourceHost,
|
|
450
|
+
fidelity: "full",
|
|
451
|
+
excludesSecrets: true,
|
|
452
|
+
tables: tableCounts,
|
|
453
|
+
blobCount,
|
|
454
|
+
blobBytes,
|
|
455
|
+
textBlobCount,
|
|
456
|
+
textBlobBytes
|
|
457
|
+
};
|
|
458
|
+
return writer.finalize(manifestWithoutChecksum);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/backup/format/archive-reader.ts
|
|
462
|
+
import { createHash as createHash3 } from "crypto";
|
|
463
|
+
import { createReadStream } from "fs";
|
|
464
|
+
import { ZstdDecompress } from "zlib";
|
|
465
|
+
import * as tar2 from "tar-stream";
|
|
466
|
+
var ZSTD_MAGIC = Buffer.from([40, 181, 47, 253]);
|
|
467
|
+
var GZIP_MAGIC = Buffer.from([31, 139]);
|
|
468
|
+
async function detectCompression(path) {
|
|
469
|
+
return new Promise((resolvePromise, reject) => {
|
|
470
|
+
const stream = createReadStream(path, { start: 0, end: 3 });
|
|
471
|
+
const chunks = [];
|
|
472
|
+
stream.on("data", (c2) => chunks.push(c2));
|
|
473
|
+
stream.on("error", reject);
|
|
474
|
+
stream.on("end", () => {
|
|
475
|
+
const head = Buffer.concat(chunks);
|
|
476
|
+
if (head.subarray(0, 4).equals(ZSTD_MAGIC)) resolvePromise("zstd");
|
|
477
|
+
else if (head.subarray(0, 2).equals(GZIP_MAGIC)) resolvePromise("gzip");
|
|
478
|
+
else reject(new Error("Unrecognized .bbdump compression (not zstd or gzip magic bytes)."));
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
async function readArchive(path) {
|
|
483
|
+
const compression = await detectCompression(path);
|
|
484
|
+
if (compression === "gzip") {
|
|
485
|
+
throw new Error(
|
|
486
|
+
"This .bbdump archive is gzip-compressed; `busabase-cli restore` only reads zstd archives in v1."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const extract2 = tar2.extract();
|
|
490
|
+
const entries = /* @__PURE__ */ new Map();
|
|
491
|
+
const checksums = [];
|
|
492
|
+
let manifestBuf;
|
|
493
|
+
await new Promise((resolvePromise, reject) => {
|
|
494
|
+
extract2.on("entry", (header, stream, next) => {
|
|
495
|
+
const chunks = [];
|
|
496
|
+
stream.on("data", (c2) => chunks.push(c2));
|
|
497
|
+
stream.on("end", () => {
|
|
498
|
+
const buf = Buffer.concat(chunks);
|
|
499
|
+
if (header.name === "manifest.json") {
|
|
500
|
+
manifestBuf = buf;
|
|
501
|
+
} else {
|
|
502
|
+
const sha256 = createHash3("sha256").update(buf).digest("hex");
|
|
503
|
+
checksums.push({ path: header.name, sha256 });
|
|
504
|
+
entries.set(header.name, buf);
|
|
505
|
+
}
|
|
506
|
+
next();
|
|
507
|
+
});
|
|
508
|
+
stream.on("error", reject);
|
|
509
|
+
stream.resume();
|
|
510
|
+
});
|
|
511
|
+
extract2.on("finish", resolvePromise);
|
|
512
|
+
extract2.on("error", reject);
|
|
513
|
+
createReadStream(path).pipe(new ZstdDecompress()).on("error", reject).pipe(extract2);
|
|
514
|
+
});
|
|
515
|
+
if (!manifestBuf) {
|
|
516
|
+
throw new Error("Archive is missing manifest.json.");
|
|
517
|
+
}
|
|
518
|
+
const manifest = ManifestSchema.parse(JSON.parse(manifestBuf.toString("utf8")));
|
|
519
|
+
const recomputed = createHash3("sha256").update(canonicalizeEntryChecksums(checksums)).digest("hex");
|
|
520
|
+
if (recomputed !== manifest.checksum) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`Archive checksum mismatch \u2014 file may be truncated or corrupted (expected ${manifest.checksum}, got ${recomputed}).`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
return { manifest, entries };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/backup/import/full-importer.ts
|
|
529
|
+
var IMPORT_ORDER = [
|
|
530
|
+
"nodes",
|
|
531
|
+
// `nodeId` / `sourceNodeId` FK into busabase_nodes — must follow "nodes".
|
|
532
|
+
"nodePrincipals",
|
|
533
|
+
"bases",
|
|
534
|
+
"baseFields",
|
|
535
|
+
"views",
|
|
536
|
+
"commits",
|
|
537
|
+
"changeRequests",
|
|
538
|
+
"operations",
|
|
539
|
+
"records",
|
|
540
|
+
"fieldValues",
|
|
541
|
+
"recordLinks",
|
|
542
|
+
"attachments",
|
|
543
|
+
"assets",
|
|
544
|
+
"assetUsages",
|
|
545
|
+
"assetTexts",
|
|
546
|
+
"comments",
|
|
547
|
+
"reviews",
|
|
548
|
+
"auditEvents"
|
|
549
|
+
];
|
|
550
|
+
var BATCH_SIZE = 200;
|
|
551
|
+
async function importFull(options) {
|
|
552
|
+
const { client, archive, onProgress } = options;
|
|
553
|
+
const log = onProgress ?? (() => {
|
|
554
|
+
});
|
|
555
|
+
const { sessionId } = await client.dump.importBegin();
|
|
556
|
+
try {
|
|
557
|
+
const blobBuf = archive.entries.get("tree/attachmentBlobs.ndjson");
|
|
558
|
+
if (blobBuf) {
|
|
559
|
+
const blobRows = blobBuf.toString("utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
560
|
+
for (let i = 0; i < blobRows.length; i += BATCH_SIZE) {
|
|
561
|
+
const batch = blobRows.slice(i, i + BATCH_SIZE);
|
|
562
|
+
await client.dump.importTables({ sessionId, table: "attachmentBlobs", rows: batch });
|
|
563
|
+
}
|
|
564
|
+
log(`imported ${blobRows.length} attachment blobs`);
|
|
565
|
+
}
|
|
566
|
+
const textBlobBuf = archive.entries.get("tree/assetTextBlobs.ndjson");
|
|
567
|
+
if (textBlobBuf) {
|
|
568
|
+
const textBlobRows = textBlobBuf.toString("utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
569
|
+
for (let i = 0; i < textBlobRows.length; i += BATCH_SIZE) {
|
|
570
|
+
const batch = textBlobRows.slice(i, i + BATCH_SIZE);
|
|
571
|
+
await client.dump.importTables({ sessionId, table: "assetTextBlobs", rows: batch });
|
|
572
|
+
}
|
|
573
|
+
log(`imported ${textBlobRows.length} asset-text blobs`);
|
|
574
|
+
}
|
|
575
|
+
for (const table of IMPORT_ORDER) {
|
|
576
|
+
const buf = archive.entries.get(`tree/${table}.ndjson`);
|
|
577
|
+
if (!buf) continue;
|
|
578
|
+
const rows = buf.toString("utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
579
|
+
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
|
580
|
+
const batch = rows.slice(i, i + BATCH_SIZE);
|
|
581
|
+
await client.dump.importTables({ sessionId, table, rows: batch });
|
|
582
|
+
}
|
|
583
|
+
log(`imported ${table}: ${rows.length} rows`);
|
|
584
|
+
}
|
|
585
|
+
const docBodyRows = [...archive.entries.entries()].filter(([path]) => path.startsWith("docs/") && path.endsWith(".md")).map(([path, buf]) => ({
|
|
586
|
+
nodeId: path.slice("docs/".length, -".md".length),
|
|
587
|
+
markdown: buf.toString("utf8")
|
|
588
|
+
}));
|
|
589
|
+
if (docBodyRows.length > 0) {
|
|
590
|
+
await client.dump.importTables({ sessionId, table: "docBodies", rows: docBodyRows });
|
|
591
|
+
log(`imported ${docBodyRows.length} doc bodies`);
|
|
592
|
+
}
|
|
593
|
+
const result = await client.dump.importCommit({ sessionId });
|
|
594
|
+
return result;
|
|
595
|
+
} catch (error) {
|
|
596
|
+
await client.dump.importAbort({ sessionId }).catch(() => void 0);
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/backup/commands.ts
|
|
602
|
+
var reportProgress = (message) => {
|
|
603
|
+
console.error(message);
|
|
604
|
+
};
|
|
605
|
+
var STATE_ONLY_BACKUP_MESSAGE = [
|
|
606
|
+
"--state-only backup is not implemented, and will not be \u2014 it is superseded by",
|
|
607
|
+
"`busabase-cli publish`, which writes the same state-level content as a readable,",
|
|
608
|
+
"diffable directory you can push to GitHub:",
|
|
609
|
+
"",
|
|
610
|
+
" busabase-cli publish <node-slug> -o ./my-package",
|
|
611
|
+
" busabase-cli install https://github.com/<you>/<repo>",
|
|
612
|
+
"",
|
|
613
|
+
"`busabase-cli backup` stays the full-fidelity backup path (raw rows, original ids,",
|
|
614
|
+
"history) \u2014 run it without --state-only. The two formats are deliberately separate:",
|
|
615
|
+
"a backup carries the audit events, comments, reviews and ACLs that a published",
|
|
616
|
+
"package must never leak."
|
|
617
|
+
].join("\n");
|
|
618
|
+
var STATE_ONLY_RESTORE_MESSAGE = [
|
|
619
|
+
"state-only archive restore is not implemented, and will not be \u2014 it is superseded",
|
|
620
|
+
"by `busabase-cli install`, which installs state-level content into an EXISTING",
|
|
621
|
+
"space as change requests you review before they go live:",
|
|
622
|
+
"",
|
|
623
|
+
" busabase-cli install https://github.com/<you>/<repo> --into-folder <name>",
|
|
624
|
+
"",
|
|
625
|
+
"`busabase-cli restore` stays the full-fidelity restore path (original ids and",
|
|
626
|
+
"history, into an empty space)."
|
|
627
|
+
].join("\n");
|
|
628
|
+
var runBackup = async (client, options) => {
|
|
629
|
+
if (options.stateOnly) throw new Error(STATE_ONLY_BACKUP_MESSAGE);
|
|
630
|
+
const { spaceId } = options;
|
|
631
|
+
if (!spaceId) {
|
|
632
|
+
throw new Error(
|
|
633
|
+
"No space configured. Pass --space-id <id>, export BUSABASE_SPACE_ID, or run `busabase-cli login`."
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
reportProgress(
|
|
637
|
+
`Backing up space ${spaceId} \u2192 ${options.outFile} (full fidelity, history=${options.includeHistory})`
|
|
638
|
+
);
|
|
639
|
+
const manifest = await exportFull({
|
|
640
|
+
client,
|
|
641
|
+
outPath: options.outFile,
|
|
642
|
+
spaceId,
|
|
643
|
+
sourceHost: options.baseUrl,
|
|
644
|
+
includeHistory: options.includeHistory,
|
|
645
|
+
toolVersion: options.toolVersion,
|
|
646
|
+
onProgress: (message) => reportProgress(` ${message}`)
|
|
647
|
+
});
|
|
648
|
+
const rows = Object.values(manifest.tables).reduce((total, count) => total + count, 0);
|
|
649
|
+
if (options.json) {
|
|
650
|
+
return {
|
|
651
|
+
file: options.outFile,
|
|
652
|
+
rows,
|
|
653
|
+
blobCount: manifest.blobCount,
|
|
654
|
+
blobBytes: manifest.blobBytes,
|
|
655
|
+
manifest
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return [
|
|
659
|
+
`Done. ${rows} rows, ${manifest.blobCount} blobs (${manifest.blobBytes} bytes) \u2192 ${options.outFile}`,
|
|
660
|
+
"",
|
|
661
|
+
"Restore it into an empty space with:",
|
|
662
|
+
` busabase-cli restore ${options.outFile}`
|
|
663
|
+
].join("\n");
|
|
664
|
+
};
|
|
665
|
+
var runRestore = async (client, options) => {
|
|
666
|
+
if (options.resume) {
|
|
667
|
+
throw new Error("--resume is not implemented yet in this version.");
|
|
668
|
+
}
|
|
669
|
+
reportProgress(`Reading ${options.file} \u2026`);
|
|
670
|
+
const archive = await readArchive(options.file);
|
|
671
|
+
reportProgress(
|
|
672
|
+
`Archive: fidelity=${archive.manifest.fidelity} space=${archive.manifest.spaceId} exportedAt=${archive.manifest.exportedAt}`
|
|
673
|
+
);
|
|
674
|
+
if (archive.manifest.fidelity !== "full") throw new Error(STATE_ONLY_RESTORE_MESSAGE);
|
|
675
|
+
if (options.intoFolder) {
|
|
676
|
+
throw new Error("--into-folder only applies to state-only archives.");
|
|
677
|
+
}
|
|
678
|
+
const result = await importFull({
|
|
679
|
+
client,
|
|
680
|
+
archive,
|
|
681
|
+
onProgress: (message) => reportProgress(` ${message}`)
|
|
682
|
+
});
|
|
683
|
+
if (options.json) {
|
|
684
|
+
return {
|
|
685
|
+
restored: result.ok,
|
|
686
|
+
file: options.file,
|
|
687
|
+
spaceId: archive.manifest.spaceId,
|
|
688
|
+
warnings: result.warnings
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (!result.ok || result.warnings.length > 0) {
|
|
692
|
+
return [
|
|
693
|
+
"Restore completed with warnings:",
|
|
694
|
+
...result.warnings.map((warning) => ` \u2022 ${warning}`)
|
|
695
|
+
].join("\n");
|
|
696
|
+
}
|
|
697
|
+
return "Restore completed successfully.";
|
|
698
|
+
};
|
|
699
|
+
|
|
140
700
|
// src/banner.ts
|
|
141
701
|
var useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
142
702
|
var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
@@ -206,15 +766,19 @@ function writeDotEnvFile(updates) {
|
|
|
206
766
|
|
|
207
767
|
// src/login.ts
|
|
208
768
|
import { spawn } from "child_process";
|
|
209
|
-
import { createHash, randomBytes } from "crypto";
|
|
769
|
+
import { createHash as createHash4, randomBytes } from "crypto";
|
|
210
770
|
import { createServer } from "http";
|
|
211
771
|
import { createInterface } from "readline/promises";
|
|
772
|
+
|
|
773
|
+
// ../../packages/busabase-contract/src/auth/device-authorization.ts
|
|
774
|
+
var BUSABASE_CLI_CLIENT_ID = "busabase-cli";
|
|
775
|
+
|
|
776
|
+
// src/login.ts
|
|
212
777
|
import { DEFAULT_BASE_URL, normalizeBaseUrl } from "busabase-sdk";
|
|
213
|
-
var CLI_CLIENT_ID = "busabase-cli";
|
|
214
778
|
var CLI_CLIENT_PLATFORM = "cli";
|
|
215
779
|
var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
216
780
|
var EXPIRES_AT_KEY = "BUSABASE_TOKEN_EXPIRES_AT";
|
|
217
|
-
var
|
|
781
|
+
var LEGACY_SESSION_TOKEN_PREFIX = "bss_";
|
|
218
782
|
var AUTO_REFRESH_THRESHOLD_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
219
783
|
var DEFAULT_LOCAL_URL = "http://localhost:15419";
|
|
220
784
|
var base64url = (buffer) => buffer.toString("base64url");
|
|
@@ -272,9 +836,9 @@ async function verifyAuth(baseUrl, token) {
|
|
|
272
836
|
}
|
|
273
837
|
return await res.json();
|
|
274
838
|
}
|
|
275
|
-
async function
|
|
839
|
+
async function loopbackOauthLogin(baseUrl, useBrowser) {
|
|
276
840
|
const codeVerifier = base64url(randomBytes(32));
|
|
277
|
-
const codeChallenge = base64url(
|
|
841
|
+
const codeChallenge = base64url(createHash4("sha256").update(codeVerifier).digest());
|
|
278
842
|
const state = base64url(randomBytes(16));
|
|
279
843
|
const { code, redirectUri } = await new Promise(
|
|
280
844
|
(resolve, reject) => {
|
|
@@ -327,7 +891,7 @@ async function oauthLogin(baseUrl, useBrowser) {
|
|
|
327
891
|
const redirect = `http://127.0.0.1:${port}/callback`;
|
|
328
892
|
const authorizeUrl = new URL(`${baseUrl}/api/oauth/authorize`);
|
|
329
893
|
authorizeUrl.searchParams.set("response_type", "code");
|
|
330
|
-
authorizeUrl.searchParams.set("client_id",
|
|
894
|
+
authorizeUrl.searchParams.set("client_id", BUSABASE_CLI_CLIENT_ID);
|
|
331
895
|
authorizeUrl.searchParams.set("client_platform", CLI_CLIENT_PLATFORM);
|
|
332
896
|
authorizeUrl.searchParams.set("code_challenge", codeChallenge);
|
|
333
897
|
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
@@ -352,7 +916,7 @@ async function oauthLogin(baseUrl, useBrowser) {
|
|
|
352
916
|
headers: { "content-type": "application/json" },
|
|
353
917
|
body: JSON.stringify({
|
|
354
918
|
grant_type: "authorization_code",
|
|
355
|
-
client_id:
|
|
919
|
+
client_id: BUSABASE_CLI_CLIENT_ID,
|
|
356
920
|
client_platform: CLI_CLIENT_PLATFORM,
|
|
357
921
|
code,
|
|
358
922
|
code_verifier: codeVerifier,
|
|
@@ -367,7 +931,110 @@ async function oauthLogin(baseUrl, useBrowser) {
|
|
|
367
931
|
const payload = await tokenRes.json();
|
|
368
932
|
const token = payload.apiKey ?? payload.token ?? payload.accessToken;
|
|
369
933
|
if (!token) throw new Error("Token exchange returned no credential.");
|
|
370
|
-
return { token, expiresAt: payload.expiresAt ?? void 0 };
|
|
934
|
+
return payload.apiKey ? { token, apiKeyExpiresAt: payload.expiresAt ?? void 0 } : { token, expiresAt: payload.expiresAt ?? void 0 };
|
|
935
|
+
}
|
|
936
|
+
var delay = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
937
|
+
async function readDeviceResponse(response) {
|
|
938
|
+
return await response.json().catch(() => ({}));
|
|
939
|
+
}
|
|
940
|
+
async function deviceLogin(baseUrl, useBrowser) {
|
|
941
|
+
const origin = new URL(baseUrl).origin;
|
|
942
|
+
let codeResponse;
|
|
943
|
+
try {
|
|
944
|
+
codeResponse = await fetch(`${baseUrl}/api/auth/device/code`, {
|
|
945
|
+
method: "POST",
|
|
946
|
+
headers: { "content-type": "application/json", origin },
|
|
947
|
+
body: JSON.stringify({
|
|
948
|
+
client_id: BUSABASE_CLI_CLIENT_ID,
|
|
949
|
+
scope: "openid profile email"
|
|
950
|
+
})
|
|
951
|
+
});
|
|
952
|
+
} catch {
|
|
953
|
+
throw new Error(`Could not start device sign-in because ${baseUrl} could not be reached.`);
|
|
954
|
+
}
|
|
955
|
+
const code = await codeResponse.json().catch(() => ({}));
|
|
956
|
+
if (!codeResponse.ok) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`Could not start device sign-in (HTTP ${codeResponse.status})${code.error_description ? `: ${code.error_description}` : "."}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
if (!code.device_code || !code.user_code || !code.verification_uri) {
|
|
962
|
+
throw new Error("Device sign-in returned an incomplete authorization response.");
|
|
963
|
+
}
|
|
964
|
+
const verificationUrl = code.verification_uri_complete ?? code.verification_uri;
|
|
965
|
+
say("");
|
|
966
|
+
say("Authorize Busabase CLI in any browser:");
|
|
967
|
+
say(` ${code.verification_uri}`);
|
|
968
|
+
say(` Code: ${code.user_code}`);
|
|
969
|
+
say("");
|
|
970
|
+
if (useBrowser) openBrowser(verificationUrl);
|
|
971
|
+
say("Waiting for authorization\u2026");
|
|
972
|
+
const expiresInSeconds = Math.max(1, code.expires_in ?? 15 * 60);
|
|
973
|
+
const deadline = Date.now() + expiresInSeconds * 1e3;
|
|
974
|
+
let intervalSeconds = Math.max(1, code.interval ?? 5);
|
|
975
|
+
let consecutiveNetworkFailures = 0;
|
|
976
|
+
while (Date.now() < deadline) {
|
|
977
|
+
await delay(intervalSeconds * 1e3);
|
|
978
|
+
let tokenResponse;
|
|
979
|
+
try {
|
|
980
|
+
tokenResponse = await fetch(`${baseUrl}/api/auth/device/token`, {
|
|
981
|
+
method: "POST",
|
|
982
|
+
headers: { "content-type": "application/json", origin },
|
|
983
|
+
body: JSON.stringify({
|
|
984
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
985
|
+
device_code: code.device_code,
|
|
986
|
+
client_id: BUSABASE_CLI_CLIENT_ID
|
|
987
|
+
})
|
|
988
|
+
});
|
|
989
|
+
consecutiveNetworkFailures = 0;
|
|
990
|
+
} catch {
|
|
991
|
+
consecutiveNetworkFailures += 1;
|
|
992
|
+
if (consecutiveNetworkFailures >= 3) {
|
|
993
|
+
throw new Error(
|
|
994
|
+
"Device sign-in lost its network connection. Check connectivity and run `busabase-cli login` again."
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const payload = await readDeviceResponse(tokenResponse);
|
|
1000
|
+
if (tokenResponse.ok && payload.access_token) {
|
|
1001
|
+
const finalizeResponse = await fetch(`${baseUrl}/api/v1/device/finalize`, {
|
|
1002
|
+
method: "POST",
|
|
1003
|
+
headers: {
|
|
1004
|
+
authorization: `Bearer ${payload.access_token}`,
|
|
1005
|
+
"content-type": "application/json"
|
|
1006
|
+
},
|
|
1007
|
+
body: JSON.stringify({ deviceCode: code.device_code })
|
|
1008
|
+
});
|
|
1009
|
+
const finalized = await finalizeResponse.json().catch(() => ({}));
|
|
1010
|
+
if (!finalizeResponse.ok || !finalized.apiKey || finalized.credentialType !== "api_key") {
|
|
1011
|
+
const finalizeError = typeof finalized.error === "string" ? finalized.error : finalized.error?.message ?? finalized.message;
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
`Device sign-in could not finalize the selected API key${finalizeError ? `: ${finalizeError}` : ` (HTTP ${finalizeResponse.status})`}.`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
token: finalized.apiKey,
|
|
1018
|
+
apiKeyExpiresAt: finalized.expiresAt ?? void 0
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
switch (payload.error) {
|
|
1022
|
+
case "authorization_pending":
|
|
1023
|
+
continue;
|
|
1024
|
+
case "slow_down":
|
|
1025
|
+
intervalSeconds += 5;
|
|
1026
|
+
continue;
|
|
1027
|
+
case "access_denied":
|
|
1028
|
+
throw new Error("Device sign-in was denied. No credential was saved.");
|
|
1029
|
+
case "expired_token":
|
|
1030
|
+
throw new Error("The device code expired. Run `busabase-cli login` to request a new one.");
|
|
1031
|
+
default:
|
|
1032
|
+
throw new Error(
|
|
1033
|
+
`Device sign-in failed${payload.error_description ? `: ${payload.error_description}` : ` (HTTP ${tokenResponse.status})`}.`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error("The device code expired. Run `busabase-cli login` to request a new one.");
|
|
371
1038
|
}
|
|
372
1039
|
async function refreshToken(baseUrl, token) {
|
|
373
1040
|
const res = await fetch(`${baseUrl}/api/oauth/refresh`, {
|
|
@@ -412,12 +1079,12 @@ async function chooseTarget() {
|
|
|
412
1079
|
say("How should this CLI connect?");
|
|
413
1080
|
say(" 1. Local/Desktop on this computer \u2014 no account, no login");
|
|
414
1081
|
say(" Use when you run `busabase server` or the Busabase Desktop app locally.");
|
|
415
|
-
say(" 2. Busabase Cloud \u2014
|
|
416
|
-
say("
|
|
1082
|
+
say(" 2. Busabase Cloud \u2014 device sign-in (recommended)");
|
|
1083
|
+
say(" Works locally, over SSH, and in containers; approve from any browser.");
|
|
417
1084
|
say(" 3. Busabase Cloud \u2014 paste an API key");
|
|
418
1085
|
say(" Best for CI, servers, or agents where a browser is not available.");
|
|
419
|
-
say(" 4. Self-hosted Busabase \u2014
|
|
420
|
-
say(" Use your team's Busabase URL when it supports
|
|
1086
|
+
say(" 4. Self-hosted Busabase \u2014 device sign-in");
|
|
1087
|
+
say(" Use your team's Busabase URL when it supports device authorization.");
|
|
421
1088
|
say(" 5. Self-hosted Busabase \u2014 paste an API key");
|
|
422
1089
|
say(" Use your team's Busabase URL with a long-lived key for automation.");
|
|
423
1090
|
const choice = await ask("Choose 1-5 [2]: ");
|
|
@@ -434,11 +1101,11 @@ async function chooseTarget() {
|
|
|
434
1101
|
case "3":
|
|
435
1102
|
return { baseUrl: cloud, method: "api-key" };
|
|
436
1103
|
case "4":
|
|
437
|
-
return { baseUrl: await askSelfHostedUrl(), method: "
|
|
1104
|
+
return { baseUrl: await askSelfHostedUrl(), method: "device" };
|
|
438
1105
|
case "5":
|
|
439
1106
|
return { baseUrl: await askSelfHostedUrl(), method: "api-key" };
|
|
440
1107
|
default:
|
|
441
|
-
return { baseUrl: cloud, method: "
|
|
1108
|
+
return { baseUrl: cloud, method: "device" };
|
|
442
1109
|
}
|
|
443
1110
|
}
|
|
444
1111
|
async function runLogin(options) {
|
|
@@ -447,14 +1114,16 @@ async function runLogin(options) {
|
|
|
447
1114
|
let method;
|
|
448
1115
|
if (apiKey) {
|
|
449
1116
|
method = "api-key";
|
|
1117
|
+
} else if (options.deviceCode) {
|
|
1118
|
+
method = "device";
|
|
450
1119
|
} else if (options.oauth) {
|
|
451
|
-
method = "oauth";
|
|
1120
|
+
method = "loopback-oauth";
|
|
452
1121
|
} else if (isInteractive()) {
|
|
453
1122
|
const target = await chooseTarget();
|
|
454
1123
|
baseUrl = normalizeBaseUrl(target.baseUrl);
|
|
455
1124
|
method = target.method;
|
|
456
1125
|
} else {
|
|
457
|
-
method = isLocalHost(baseUrl) ? "none" : "
|
|
1126
|
+
method = isLocalHost(baseUrl) ? "none" : "device";
|
|
458
1127
|
}
|
|
459
1128
|
if (method === "none") {
|
|
460
1129
|
if (await probeAuthRequired(baseUrl)) {
|
|
@@ -474,10 +1143,16 @@ async function runLogin(options) {
|
|
|
474
1143
|
}
|
|
475
1144
|
let token;
|
|
476
1145
|
let expiresAt;
|
|
477
|
-
|
|
478
|
-
|
|
1146
|
+
let apiKeyExpiresAt;
|
|
1147
|
+
if (method === "device") {
|
|
1148
|
+
const result = await deviceLogin(baseUrl, options.browser);
|
|
1149
|
+
token = result.token;
|
|
1150
|
+
apiKeyExpiresAt = result.apiKeyExpiresAt;
|
|
1151
|
+
} else if (method === "loopback-oauth") {
|
|
1152
|
+
const result = await loopbackOauthLogin(baseUrl, options.browser);
|
|
479
1153
|
token = result.token;
|
|
480
1154
|
expiresAt = result.expiresAt;
|
|
1155
|
+
apiKeyExpiresAt = result.apiKeyExpiresAt;
|
|
481
1156
|
} else {
|
|
482
1157
|
if (!apiKey) {
|
|
483
1158
|
say("Create a key in the dashboard \u2192 Settings \u2192 API Keys (shown once).");
|
|
@@ -500,10 +1175,13 @@ async function runLogin(options) {
|
|
|
500
1175
|
say(`\u2713 Signed in and saved to ${dotEnvPath()}`);
|
|
501
1176
|
return {
|
|
502
1177
|
status: "signed in",
|
|
503
|
-
method,
|
|
1178
|
+
method: method === "loopback-oauth" ? "oauth" : method,
|
|
1179
|
+
credentialType: method === "device" ? "api_key" : expiresAt ? "session" : "api_key",
|
|
504
1180
|
user: verify.user?.email ?? verify.user?.name ?? verify.user?.id ?? "(unknown)",
|
|
505
1181
|
space: spaceId ?? "(server default)",
|
|
506
|
-
|
|
1182
|
+
createdSpace: String(Boolean(verify.createdSpace)),
|
|
1183
|
+
bootstrapRequired: String(Boolean(verify.bootstrapRequired)),
|
|
1184
|
+
expiresAt: expiresAt ?? apiKeyExpiresAt ?? "(no expiry \u2014 API key)",
|
|
507
1185
|
baseUrl,
|
|
508
1186
|
config: dotEnvPath()
|
|
509
1187
|
};
|
|
@@ -512,10 +1190,12 @@ async function runRefresh(options) {
|
|
|
512
1190
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
513
1191
|
const token = options.apiKey;
|
|
514
1192
|
if (!token) throw new Error("Not signed in \u2014 run `busabase-cli login` first.");
|
|
515
|
-
|
|
1193
|
+
const file = loadDotEnvFile();
|
|
1194
|
+
const isSavedExpiringSession = file.BUSABASE_API_KEY === token && Boolean(file.BUSABASE_TOKEN_EXPIRES_AT);
|
|
1195
|
+
if (!token.startsWith(LEGACY_SESSION_TOKEN_PREFIX) && !isSavedExpiringSession) {
|
|
516
1196
|
return {
|
|
517
1197
|
status: "nothing to refresh",
|
|
518
|
-
detail: "This credential is an API key
|
|
1198
|
+
detail: "This credential is an API key and cannot be refreshed. Only OAuth sessions refresh."
|
|
519
1199
|
};
|
|
520
1200
|
}
|
|
521
1201
|
let refreshed;
|
|
@@ -541,13 +1221,14 @@ async function runRefresh(options) {
|
|
|
541
1221
|
};
|
|
542
1222
|
}
|
|
543
1223
|
async function maybeAutoRefresh(baseUrl, apiKey) {
|
|
544
|
-
if (!apiKey
|
|
1224
|
+
if (!apiKey) return;
|
|
545
1225
|
const file = loadDotEnvFile();
|
|
546
1226
|
if (file.BUSABASE_API_KEY !== apiKey) return;
|
|
547
1227
|
const expiresRaw = file[EXPIRES_AT_KEY];
|
|
548
1228
|
if (!expiresRaw) return;
|
|
549
1229
|
const expiresAt = Date.parse(expiresRaw);
|
|
550
1230
|
if (Number.isNaN(expiresAt)) return;
|
|
1231
|
+
if (expiresAt <= Date.now()) return;
|
|
551
1232
|
if (expiresAt - Date.now() > AUTO_REFRESH_THRESHOLD_MS) return;
|
|
552
1233
|
try {
|
|
553
1234
|
const refreshed = await refreshToken(normalizeBaseUrl(baseUrl), apiKey);
|
|
@@ -558,11 +1239,25 @@ async function maybeAutoRefresh(baseUrl, apiKey) {
|
|
|
558
1239
|
} catch {
|
|
559
1240
|
}
|
|
560
1241
|
}
|
|
1242
|
+
function assertCredentialNotExpired(apiKey) {
|
|
1243
|
+
if (!apiKey) return;
|
|
1244
|
+
const file = loadDotEnvFile();
|
|
1245
|
+
if (file.BUSABASE_API_KEY !== apiKey) return;
|
|
1246
|
+
const expiresRaw = file[EXPIRES_AT_KEY];
|
|
1247
|
+
if (!expiresRaw) return;
|
|
1248
|
+
const expiresAt = Date.parse(expiresRaw);
|
|
1249
|
+
if (!Number.isNaN(expiresAt) && expiresAt <= Date.now()) {
|
|
1250
|
+
throw new Error(
|
|
1251
|
+
"Your saved device login has expired. Run `busabase-cli login --device-code` to authorize this CLI again."
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
561
1255
|
async function runLogout(options) {
|
|
562
1256
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
563
1257
|
const token = options.apiKey;
|
|
564
1258
|
let revoked = "no session to revoke";
|
|
565
|
-
|
|
1259
|
+
const file = loadDotEnvFile();
|
|
1260
|
+
if (token && (token.startsWith(LEGACY_SESSION_TOKEN_PREFIX) || file.BUSABASE_API_KEY === token && file[EXPIRES_AT_KEY])) {
|
|
566
1261
|
try {
|
|
567
1262
|
const res = await fetch(`${baseUrl}/api/oauth/revoke`, {
|
|
568
1263
|
method: "POST",
|
|
@@ -585,34 +1280,34 @@ import { readFile } from "fs/promises";
|
|
|
585
1280
|
import { join as join3 } from "path";
|
|
586
1281
|
|
|
587
1282
|
// ../../packages/busabase-contract/src/domains/package/types.ts
|
|
588
|
-
import { z as
|
|
1283
|
+
import { z as z8 } from "zod";
|
|
589
1284
|
|
|
590
1285
|
// ../../packages/openlib/i18n/i-string.ts
|
|
591
|
-
import { z as
|
|
1286
|
+
import { z as z4 } from "zod";
|
|
592
1287
|
|
|
593
1288
|
// ../../packages/openlib/i18n/i18n.ts
|
|
594
|
-
import { z } from "zod";
|
|
1289
|
+
import { z as z3 } from "zod";
|
|
595
1290
|
var i18n = {
|
|
596
1291
|
defaultLocale: "en",
|
|
597
1292
|
locales: ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr", "es", "pt"],
|
|
598
1293
|
extendLocales: ["en", "zh-CN", "zh-TW", "ja", "ko", "fr", "de", "es", "ru", "it", "vi", "pt"]
|
|
599
1294
|
};
|
|
600
|
-
var LocaleSchema =
|
|
1295
|
+
var LocaleSchema = z3.enum(i18n.locales);
|
|
601
1296
|
|
|
602
1297
|
// ../../packages/openlib/i18n/i-string.ts
|
|
603
|
-
var iStringRecordSchema =
|
|
604
|
-
var iStringSchema =
|
|
1298
|
+
var iStringRecordSchema = z4.partialRecord(LocaleSchema, z4.string());
|
|
1299
|
+
var iStringSchema = z4.union([z4.string(), iStringRecordSchema]).describe("i18n string");
|
|
605
1300
|
|
|
606
1301
|
// ../../packages/busabase-contract/src/domains/base/contract/base-schemas.ts
|
|
607
|
-
import { z as
|
|
608
|
-
var fieldNameSchema =
|
|
609
|
-
|
|
1302
|
+
import { z as z5 } from "zod";
|
|
1303
|
+
var fieldNameSchema = z5.union([
|
|
1304
|
+
z5.string().min(1),
|
|
610
1305
|
iStringRecordSchema.refine(
|
|
611
1306
|
(record) => Object.values(record).some((value) => value && value.trim().length > 0),
|
|
612
1307
|
"field name must have at least one non-empty locale value"
|
|
613
1308
|
)
|
|
614
1309
|
]);
|
|
615
|
-
var fieldTypeSchema =
|
|
1310
|
+
var fieldTypeSchema = z5.enum([
|
|
616
1311
|
"text",
|
|
617
1312
|
"longtext",
|
|
618
1313
|
"markdown",
|
|
@@ -639,85 +1334,85 @@ var fieldTypeSchema = z3.enum([
|
|
|
639
1334
|
"json",
|
|
640
1335
|
"yaml"
|
|
641
1336
|
]);
|
|
642
|
-
var fieldOptionsSchema =
|
|
643
|
-
ai:
|
|
644
|
-
model:
|
|
645
|
-
prompt:
|
|
646
|
-
reviewRequired:
|
|
647
|
-
sourceFieldIds:
|
|
1337
|
+
var fieldOptionsSchema = z5.object({
|
|
1338
|
+
ai: z5.object({
|
|
1339
|
+
model: z5.string().optional(),
|
|
1340
|
+
prompt: z5.string().optional(),
|
|
1341
|
+
reviewRequired: z5.boolean().optional(),
|
|
1342
|
+
sourceFieldIds: z5.array(z5.string()).optional()
|
|
648
1343
|
}).optional(),
|
|
649
1344
|
// Per-field config for `attachment` columns (all optional; logic enforces a
|
|
650
1345
|
// 25MB ceiling regardless).
|
|
651
|
-
attachment:
|
|
652
|
-
maxFiles:
|
|
653
|
-
allowedMimeTypes:
|
|
654
|
-
maxFileSize:
|
|
1346
|
+
attachment: z5.object({
|
|
1347
|
+
maxFiles: z5.number().int().positive().optional(),
|
|
1348
|
+
allowedMimeTypes: z5.array(z5.string()).optional(),
|
|
1349
|
+
maxFileSize: z5.number().int().positive().optional()
|
|
655
1350
|
}).optional(),
|
|
656
|
-
choices:
|
|
657
|
-
|
|
658
|
-
color:
|
|
659
|
-
id:
|
|
660
|
-
name:
|
|
1351
|
+
choices: z5.array(
|
|
1352
|
+
z5.object({
|
|
1353
|
+
color: z5.string().optional(),
|
|
1354
|
+
id: z5.string(),
|
|
1355
|
+
name: z5.string()
|
|
661
1356
|
})
|
|
662
1357
|
).optional(),
|
|
663
|
-
code:
|
|
664
|
-
language:
|
|
1358
|
+
code: z5.object({
|
|
1359
|
+
language: z5.string().optional()
|
|
665
1360
|
}).optional(),
|
|
666
|
-
embed:
|
|
667
|
-
aspectRatio:
|
|
668
|
-
height:
|
|
669
|
-
providers:
|
|
1361
|
+
embed: z5.object({
|
|
1362
|
+
aspectRatio: z5.enum(["16:9", "4:3", "1:1"]).optional(),
|
|
1363
|
+
height: z5.number().int().positive().max(1200).optional(),
|
|
1364
|
+
providers: z5.array(z5.string()).optional()
|
|
670
1365
|
}).optional(),
|
|
671
|
-
inverseFieldId:
|
|
672
|
-
multiple:
|
|
1366
|
+
inverseFieldId: z5.string().optional(),
|
|
1367
|
+
multiple: z5.boolean().optional(),
|
|
673
1368
|
// Display formatting for `number` columns (Notion-style: one number type,
|
|
674
1369
|
// a format option). `currency` renders via Intl.NumberFormat.
|
|
675
|
-
number:
|
|
676
|
-
format:
|
|
677
|
-
currency:
|
|
678
|
-
locale:
|
|
1370
|
+
number: z5.object({
|
|
1371
|
+
format: z5.enum(["plain", "currency"]).optional(),
|
|
1372
|
+
currency: z5.string().optional(),
|
|
1373
|
+
locale: z5.string().optional()
|
|
679
1374
|
}).optional(),
|
|
680
|
-
targetBaseId:
|
|
681
|
-
targetBaseSlug:
|
|
1375
|
+
targetBaseId: z5.string().optional().describe("Relation target Base id (bse_\u2026). Or pass targetBaseSlug to name it by slug."),
|
|
1376
|
+
targetBaseSlug: z5.string().optional().describe(
|
|
682
1377
|
"Relation target Base by slug \u2014 a convenience alias for targetBaseId, resolved server-side (active bases in the current space). If both are given, targetBaseId wins."
|
|
683
1378
|
)
|
|
684
1379
|
}).default({});
|
|
685
|
-
var baseFieldSchema =
|
|
686
|
-
id:
|
|
687
|
-
baseId:
|
|
688
|
-
slug:
|
|
1380
|
+
var baseFieldSchema = z5.object({
|
|
1381
|
+
id: z5.string(),
|
|
1382
|
+
baseId: z5.string(),
|
|
1383
|
+
slug: z5.string(),
|
|
689
1384
|
name: fieldNameSchema,
|
|
690
1385
|
type: fieldTypeSchema,
|
|
691
|
-
required:
|
|
692
|
-
position:
|
|
1386
|
+
required: z5.boolean(),
|
|
1387
|
+
position: z5.number(),
|
|
693
1388
|
options: fieldOptionsSchema
|
|
694
1389
|
});
|
|
695
|
-
var baseSchema =
|
|
696
|
-
id:
|
|
697
|
-
nodeId:
|
|
698
|
-
slug:
|
|
699
|
-
name:
|
|
700
|
-
description:
|
|
701
|
-
reviewPolicy:
|
|
702
|
-
kind:
|
|
703
|
-
requiredApprovals:
|
|
1390
|
+
var baseSchema = z5.object({
|
|
1391
|
+
id: z5.string(),
|
|
1392
|
+
nodeId: z5.string(),
|
|
1393
|
+
slug: z5.string(),
|
|
1394
|
+
name: z5.string(),
|
|
1395
|
+
description: z5.string(),
|
|
1396
|
+
reviewPolicy: z5.object({
|
|
1397
|
+
kind: z5.literal("single"),
|
|
1398
|
+
requiredApprovals: z5.number()
|
|
704
1399
|
}),
|
|
705
|
-
createdAt:
|
|
706
|
-
fields:
|
|
1400
|
+
createdAt: z5.string(),
|
|
1401
|
+
fields: z5.array(baseFieldSchema)
|
|
707
1402
|
});
|
|
708
|
-
var createBaseInputSchema =
|
|
709
|
-
parentNodeId:
|
|
1403
|
+
var createBaseInputSchema = z5.object({
|
|
1404
|
+
parentNodeId: z5.string().optional().describe(
|
|
710
1405
|
"Parent node id. Must be a folder or the space root; container-incapable node types (Base, Doc, AirApp, etc.) cannot hold children."
|
|
711
1406
|
),
|
|
712
|
-
slug:
|
|
713
|
-
name:
|
|
714
|
-
description:
|
|
715
|
-
fields:
|
|
716
|
-
|
|
717
|
-
slug:
|
|
1407
|
+
slug: z5.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1408
|
+
name: z5.string().min(1),
|
|
1409
|
+
description: z5.string().optional().default(""),
|
|
1410
|
+
fields: z5.array(
|
|
1411
|
+
z5.object({
|
|
1412
|
+
slug: z5.string().min(1),
|
|
718
1413
|
name: fieldNameSchema,
|
|
719
1414
|
type: fieldTypeSchema.default("text"),
|
|
720
|
-
required:
|
|
1415
|
+
required: z5.boolean().default(false),
|
|
721
1416
|
options: fieldOptionsSchema.optional().default({})
|
|
722
1417
|
})
|
|
723
1418
|
).default([]),
|
|
@@ -725,78 +1420,78 @@ var createBaseInputSchema = z3.object({
|
|
|
725
1420
|
// as a pending ChangeRequest (status "in_review") instead of creating it
|
|
726
1421
|
// immediately. Pass `autoMerge: true` only for callers that don't need human
|
|
727
1422
|
// review (seed/migration scripts, an explicit no-review agent task).
|
|
728
|
-
autoMerge:
|
|
1423
|
+
autoMerge: z5.boolean().optional().default(false)
|
|
729
1424
|
});
|
|
730
|
-
var createBaseFieldInputSchema =
|
|
1425
|
+
var createBaseFieldInputSchema = z5.object({
|
|
731
1426
|
name: fieldNameSchema,
|
|
732
1427
|
// Field slugs are snake_case identifiers (e.g. `cover_image`, `publish_date`) — the
|
|
733
1428
|
// seed and the inline-fields path on `POST /bases` already allow underscores, so the
|
|
734
1429
|
// add-field endpoint must too. (Base/folder/view slugs stay kebab-case: they go in URLs.)
|
|
735
|
-
slug:
|
|
1430
|
+
slug: z5.string().min(1).regex(/^[a-z0-9_-]+$/),
|
|
736
1431
|
type: fieldTypeSchema.default("text"),
|
|
737
|
-
required:
|
|
1432
|
+
required: z5.boolean().optional().default(false),
|
|
738
1433
|
options: fieldOptionsSchema.optional().default({})
|
|
739
1434
|
});
|
|
740
1435
|
var createFieldChangeRequestInputSchema = createBaseFieldInputSchema.extend({
|
|
741
|
-
message:
|
|
742
|
-
submittedBy:
|
|
1436
|
+
message: z5.string().optional().default("Add field"),
|
|
1437
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
743
1438
|
});
|
|
744
|
-
var deleteFieldChangeRequestInputSchema =
|
|
745
|
-
fieldId:
|
|
746
|
-
message:
|
|
747
|
-
submittedBy:
|
|
1439
|
+
var deleteFieldChangeRequestInputSchema = z5.object({
|
|
1440
|
+
fieldId: z5.string().min(1),
|
|
1441
|
+
message: z5.string().optional(),
|
|
1442
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
748
1443
|
});
|
|
749
|
-
var updateFieldChangeRequestInputSchema =
|
|
750
|
-
fieldId:
|
|
751
|
-
patch:
|
|
1444
|
+
var updateFieldChangeRequestInputSchema = z5.object({
|
|
1445
|
+
fieldId: z5.string().min(1),
|
|
1446
|
+
patch: z5.object({
|
|
752
1447
|
name: fieldNameSchema.optional(),
|
|
753
|
-
required:
|
|
1448
|
+
required: z5.boolean().optional(),
|
|
754
1449
|
options: fieldOptionsSchema.optional()
|
|
755
1450
|
}),
|
|
756
|
-
message:
|
|
757
|
-
submittedBy:
|
|
1451
|
+
message: z5.string().optional(),
|
|
1452
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
758
1453
|
});
|
|
759
|
-
var previewFieldConversionInputSchema =
|
|
760
|
-
fieldId:
|
|
1454
|
+
var previewFieldConversionInputSchema = z5.object({
|
|
1455
|
+
fieldId: z5.string().min(1),
|
|
761
1456
|
newType: fieldTypeSchema
|
|
762
1457
|
});
|
|
763
|
-
var previewFieldConversionOutputSchema =
|
|
764
|
-
totalCount:
|
|
765
|
-
convertibleCount:
|
|
766
|
-
nullCount:
|
|
767
|
-
conflicts:
|
|
1458
|
+
var previewFieldConversionOutputSchema = z5.object({
|
|
1459
|
+
totalCount: z5.number(),
|
|
1460
|
+
convertibleCount: z5.number(),
|
|
1461
|
+
nullCount: z5.number(),
|
|
1462
|
+
conflicts: z5.array(z5.object({ recordId: z5.string(), currentValue: z5.unknown() }))
|
|
768
1463
|
});
|
|
769
|
-
var convertFieldChangeRequestInputSchema =
|
|
770
|
-
fieldId:
|
|
1464
|
+
var convertFieldChangeRequestInputSchema = z5.object({
|
|
1465
|
+
fieldId: z5.string().min(1),
|
|
771
1466
|
newType: fieldTypeSchema,
|
|
772
|
-
selectChoiceMode:
|
|
773
|
-
message:
|
|
774
|
-
submittedBy:
|
|
1467
|
+
selectChoiceMode: z5.enum(["auto_create", "null_on_missing"]).default("null_on_missing"),
|
|
1468
|
+
message: z5.string().optional(),
|
|
1469
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
775
1470
|
});
|
|
776
|
-
var reorderFieldsChangeRequestInputSchema =
|
|
777
|
-
fieldIds:
|
|
778
|
-
message:
|
|
779
|
-
submittedBy:
|
|
1471
|
+
var reorderFieldsChangeRequestInputSchema = z5.object({
|
|
1472
|
+
fieldIds: z5.array(z5.string()).min(1),
|
|
1473
|
+
message: z5.string().optional(),
|
|
1474
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
780
1475
|
});
|
|
781
|
-
var archiveBaseInputSchema =
|
|
782
|
-
message:
|
|
783
|
-
submittedBy:
|
|
1476
|
+
var archiveBaseInputSchema = z5.object({
|
|
1477
|
+
message: z5.string().optional(),
|
|
1478
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
784
1479
|
});
|
|
785
|
-
var restoreBaseInputSchema =
|
|
786
|
-
message:
|
|
787
|
-
submittedBy:
|
|
1480
|
+
var restoreBaseInputSchema = z5.object({
|
|
1481
|
+
message: z5.string().optional(),
|
|
1482
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
788
1483
|
});
|
|
789
|
-
var restoreFieldChangeRequestInputSchema =
|
|
790
|
-
fieldId:
|
|
791
|
-
message:
|
|
792
|
-
submittedBy:
|
|
1484
|
+
var restoreFieldChangeRequestInputSchema = z5.object({
|
|
1485
|
+
fieldId: z5.string().min(1),
|
|
1486
|
+
message: z5.string().optional(),
|
|
1487
|
+
submittedBy: z5.string().optional().default("local-editor")
|
|
793
1488
|
});
|
|
794
1489
|
|
|
795
1490
|
// ../../packages/busabase-contract/src/domains/base/contract/view-schemas.ts
|
|
796
|
-
import { z as
|
|
1491
|
+
import { z as z7 } from "zod";
|
|
797
1492
|
|
|
798
1493
|
// ../../packages/busabase-contract/src/contract/schemas.ts
|
|
799
|
-
import { z as
|
|
1494
|
+
import { z as z6 } from "zod";
|
|
800
1495
|
|
|
801
1496
|
// ../../packages/busabase-contract/src/domains/filetree/definition.ts
|
|
802
1497
|
var fileTreeOperations = (type) => [
|
|
@@ -1025,128 +1720,144 @@ var OPERATION_META = Object.fromEntries(
|
|
|
1025
1720
|
);
|
|
1026
1721
|
|
|
1027
1722
|
// ../../packages/busabase-contract/src/contract/schemas.ts
|
|
1028
|
-
var nodeSchema =
|
|
1029
|
-
() =>
|
|
1030
|
-
id:
|
|
1031
|
-
parentId:
|
|
1032
|
-
type:
|
|
1033
|
-
slug:
|
|
1034
|
-
name:
|
|
1035
|
-
description:
|
|
1036
|
-
metadata:
|
|
1037
|
-
entryFile:
|
|
1038
|
-
visibility:
|
|
1039
|
-
version:
|
|
1040
|
-
assetId:
|
|
1041
|
-
}).default({}),
|
|
1042
|
-
position:
|
|
1043
|
-
createdAt:
|
|
1044
|
-
updatedAt:
|
|
1045
|
-
baseId:
|
|
1046
|
-
children:
|
|
1047
|
-
hasChildren:
|
|
1723
|
+
var nodeSchema = z6.lazy(
|
|
1724
|
+
() => z6.object({
|
|
1725
|
+
id: z6.string(),
|
|
1726
|
+
parentId: z6.string().nullable(),
|
|
1727
|
+
type: z6.enum(NODE_TYPES),
|
|
1728
|
+
slug: z6.string(),
|
|
1729
|
+
name: z6.string(),
|
|
1730
|
+
description: z6.string(),
|
|
1731
|
+
metadata: z6.object({
|
|
1732
|
+
entryFile: z6.string().optional(),
|
|
1733
|
+
visibility: z6.enum(["private", "workspace", "public"]).optional(),
|
|
1734
|
+
version: z6.string().optional(),
|
|
1735
|
+
assetId: z6.string().optional()
|
|
1736
|
+
}).catchall(z6.unknown()).default({}),
|
|
1737
|
+
position: z6.number(),
|
|
1738
|
+
createdAt: z6.string(),
|
|
1739
|
+
updatedAt: z6.string(),
|
|
1740
|
+
baseId: z6.string().nullable(),
|
|
1741
|
+
children: z6.array(nodeSchema),
|
|
1742
|
+
hasChildren: z6.boolean().optional()
|
|
1048
1743
|
})
|
|
1049
1744
|
);
|
|
1050
|
-
var nodePrincipalSchema =
|
|
1051
|
-
id:
|
|
1052
|
-
nodeId:
|
|
1053
|
-
principalType:
|
|
1054
|
-
principalId:
|
|
1055
|
-
role:
|
|
1056
|
-
grantedBy:
|
|
1057
|
-
createdAt:
|
|
1058
|
-
updatedAt:
|
|
1745
|
+
var nodePrincipalSchema = z6.object({
|
|
1746
|
+
id: z6.string(),
|
|
1747
|
+
nodeId: z6.string(),
|
|
1748
|
+
principalType: z6.enum(["user", "team", "space"]),
|
|
1749
|
+
principalId: z6.string(),
|
|
1750
|
+
role: z6.enum(["read", "changeRequest", "write", "manage"]),
|
|
1751
|
+
grantedBy: z6.string(),
|
|
1752
|
+
createdAt: z6.string(),
|
|
1753
|
+
updatedAt: z6.string()
|
|
1059
1754
|
});
|
|
1060
|
-
var listNodesInputSchema =
|
|
1061
|
-
parentId:
|
|
1062
|
-
depth:
|
|
1755
|
+
var listNodesInputSchema = z6.object({
|
|
1756
|
+
parentId: z6.string().nullable().optional().describe("Node to start from. Omit or null to start from the space root."),
|
|
1757
|
+
depth: z6.coerce.number().int().min(1).max(5).optional().describe(
|
|
1063
1758
|
"How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5."
|
|
1064
1759
|
)
|
|
1065
1760
|
}).optional();
|
|
1066
|
-
var isDescendantInputSchema =
|
|
1067
|
-
nodeId:
|
|
1068
|
-
potentialAncestorId:
|
|
1761
|
+
var isDescendantInputSchema = z6.object({
|
|
1762
|
+
nodeId: z6.string(),
|
|
1763
|
+
potentialAncestorId: z6.string()
|
|
1764
|
+
});
|
|
1765
|
+
var isDescendantOutputSchema = z6.object({
|
|
1766
|
+
isDescendant: z6.boolean()
|
|
1767
|
+
});
|
|
1768
|
+
var updateNodeMetadataInputSchema = z6.object({
|
|
1769
|
+
nodeId: z6.string(),
|
|
1770
|
+
metadata: z6.record(z6.string(), z6.unknown())
|
|
1069
1771
|
});
|
|
1070
|
-
var
|
|
1071
|
-
|
|
1772
|
+
var searchNodesByNameInputSchema = z6.object({
|
|
1773
|
+
query: z6.string().min(1),
|
|
1774
|
+
limit: z6.number().int().min(1).max(50).optional().default(20)
|
|
1072
1775
|
});
|
|
1073
|
-
var
|
|
1074
|
-
id:
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1776
|
+
var nodeSearchResultSchema = z6.object({
|
|
1777
|
+
id: z6.string(),
|
|
1778
|
+
type: z6.enum(NODE_TYPES),
|
|
1779
|
+
name: z6.string(),
|
|
1780
|
+
slug: z6.string(),
|
|
1781
|
+
path: z6.string(),
|
|
1782
|
+
updatedAt: z6.string()
|
|
1079
1783
|
});
|
|
1080
|
-
var
|
|
1081
|
-
id:
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1784
|
+
var userRefSchema = z6.object({
|
|
1785
|
+
id: z6.string(),
|
|
1786
|
+
name: z6.string().nullable(),
|
|
1787
|
+
email: z6.string().nullable(),
|
|
1788
|
+
image: z6.string().nullable(),
|
|
1789
|
+
role: z6.string().nullable().optional()
|
|
1790
|
+
});
|
|
1791
|
+
var commitSchema = z6.object({
|
|
1792
|
+
id: z6.string(),
|
|
1793
|
+
baseId: z6.string().nullable(),
|
|
1794
|
+
targetType: z6.enum(["base", "node"]),
|
|
1795
|
+
nodeId: z6.string().nullable(),
|
|
1796
|
+
operationId: z6.string().nullable(),
|
|
1797
|
+
parentCommitId: z6.string().nullable(),
|
|
1798
|
+
fields: z6.record(z6.string(), z6.unknown()),
|
|
1799
|
+
operation: z6.enum(OPERATION_KINDS),
|
|
1800
|
+
message: z6.string(),
|
|
1801
|
+
author: z6.string(),
|
|
1091
1802
|
authorUser: userRefSchema.nullable().optional().default(null),
|
|
1092
|
-
createdAt:
|
|
1803
|
+
createdAt: z6.string()
|
|
1093
1804
|
});
|
|
1094
|
-
var operationSchema =
|
|
1095
|
-
id:
|
|
1096
|
-
changeRequestId:
|
|
1097
|
-
baseId:
|
|
1098
|
-
targetType:
|
|
1099
|
-
nodeId:
|
|
1100
|
-
operation:
|
|
1101
|
-
status:
|
|
1102
|
-
targetRecordId:
|
|
1103
|
-
targetViewId:
|
|
1104
|
-
filePath:
|
|
1105
|
-
sourceRecordId:
|
|
1106
|
-
sourceCommitId:
|
|
1107
|
-
baseCommitId:
|
|
1108
|
-
headCommitId:
|
|
1109
|
-
deleteMode:
|
|
1110
|
-
mergedRecordId:
|
|
1111
|
-
mergedViewId:
|
|
1112
|
-
position:
|
|
1113
|
-
createdAt:
|
|
1114
|
-
updatedAt:
|
|
1805
|
+
var operationSchema = z6.object({
|
|
1806
|
+
id: z6.string(),
|
|
1807
|
+
changeRequestId: z6.string(),
|
|
1808
|
+
baseId: z6.string().nullable(),
|
|
1809
|
+
targetType: z6.enum(["base", "node"]),
|
|
1810
|
+
nodeId: z6.string().nullable(),
|
|
1811
|
+
operation: z6.enum(OPERATION_KINDS),
|
|
1812
|
+
status: z6.enum(["pending", "merged", "archived", "failed"]),
|
|
1813
|
+
targetRecordId: z6.string().nullable(),
|
|
1814
|
+
targetViewId: z6.string().nullable(),
|
|
1815
|
+
filePath: z6.string().nullable(),
|
|
1816
|
+
sourceRecordId: z6.string().nullable(),
|
|
1817
|
+
sourceCommitId: z6.string().nullable(),
|
|
1818
|
+
baseCommitId: z6.string().nullable(),
|
|
1819
|
+
headCommitId: z6.string(),
|
|
1820
|
+
deleteMode: z6.enum(["archive"]),
|
|
1821
|
+
mergedRecordId: z6.string().nullable(),
|
|
1822
|
+
mergedViewId: z6.string().nullable(),
|
|
1823
|
+
position: z6.number(),
|
|
1824
|
+
createdAt: z6.string(),
|
|
1825
|
+
updatedAt: z6.string(),
|
|
1115
1826
|
headCommit: commitSchema,
|
|
1116
1827
|
// Resolved canonical "before" values for the operation's target, so the UI can
|
|
1117
1828
|
// render a true before → after field diff. Null for creations (no prior state)
|
|
1118
1829
|
// and for kinds whose prior state isn't a field map (e.g. skill files, whose
|
|
1119
1830
|
// previous content lives in storage). Records resolve from the base commit;
|
|
1120
1831
|
// views resolve from the current view row ({ name, description, config }).
|
|
1121
|
-
baseFields:
|
|
1832
|
+
baseFields: z6.record(z6.string(), z6.unknown()).nullable()
|
|
1122
1833
|
});
|
|
1123
|
-
var reviewSchema =
|
|
1124
|
-
id:
|
|
1125
|
-
changeRequestId:
|
|
1126
|
-
reviewerId:
|
|
1834
|
+
var reviewSchema = z6.object({
|
|
1835
|
+
id: z6.string(),
|
|
1836
|
+
changeRequestId: z6.string(),
|
|
1837
|
+
reviewerId: z6.string(),
|
|
1127
1838
|
reviewer: userRefSchema.nullable().optional().default(null),
|
|
1128
|
-
verdict:
|
|
1129
|
-
reason:
|
|
1130
|
-
visibleOperationHeads:
|
|
1131
|
-
createdAt:
|
|
1839
|
+
verdict: z6.enum(["approved", "rejected"]),
|
|
1840
|
+
reason: z6.string().nullable(),
|
|
1841
|
+
visibleOperationHeads: z6.record(z6.string(), z6.string()),
|
|
1842
|
+
createdAt: z6.string()
|
|
1132
1843
|
});
|
|
1133
|
-
var commentSubjectTypeSchema =
|
|
1134
|
-
var commentSchema =
|
|
1135
|
-
id:
|
|
1844
|
+
var commentSubjectTypeSchema = z6.enum(["record", "change_request", "operation", "commit"]);
|
|
1845
|
+
var commentSchema = z6.object({
|
|
1846
|
+
id: z6.string(),
|
|
1136
1847
|
subjectType: commentSubjectTypeSchema,
|
|
1137
|
-
subjectId:
|
|
1138
|
-
recordId:
|
|
1139
|
-
changeRequestId:
|
|
1140
|
-
operationId:
|
|
1141
|
-
commitId:
|
|
1142
|
-
authorId:
|
|
1848
|
+
subjectId: z6.string(),
|
|
1849
|
+
recordId: z6.string().nullable(),
|
|
1850
|
+
changeRequestId: z6.string().nullable(),
|
|
1851
|
+
operationId: z6.string().nullable(),
|
|
1852
|
+
commitId: z6.string().nullable(),
|
|
1853
|
+
authorId: z6.string(),
|
|
1143
1854
|
author: userRefSchema.nullable().optional().default(null),
|
|
1144
|
-
body:
|
|
1145
|
-
mentionsAi:
|
|
1146
|
-
createdAt:
|
|
1147
|
-
updatedAt:
|
|
1855
|
+
body: z6.string(),
|
|
1856
|
+
mentionsAi: z6.boolean(),
|
|
1857
|
+
createdAt: z6.string(),
|
|
1858
|
+
updatedAt: z6.string()
|
|
1148
1859
|
});
|
|
1149
|
-
var changeRequestStatusSchema =
|
|
1860
|
+
var changeRequestStatusSchema = z6.enum([
|
|
1150
1861
|
"in_review",
|
|
1151
1862
|
"changes_requested",
|
|
1152
1863
|
"approved",
|
|
@@ -1155,53 +1866,53 @@ var changeRequestStatusSchema = z4.enum([
|
|
|
1155
1866
|
"abandoned",
|
|
1156
1867
|
"conflict"
|
|
1157
1868
|
]);
|
|
1158
|
-
var changeRequestSchema =
|
|
1159
|
-
id:
|
|
1160
|
-
baseId:
|
|
1161
|
-
targetType:
|
|
1162
|
-
nodeId:
|
|
1869
|
+
var changeRequestSchema = z6.object({
|
|
1870
|
+
id: z6.string(),
|
|
1871
|
+
baseId: z6.string().nullable(),
|
|
1872
|
+
targetType: z6.enum(["base", "node"]),
|
|
1873
|
+
nodeId: z6.string().nullable(),
|
|
1163
1874
|
status: changeRequestStatusSchema,
|
|
1164
|
-
submittedBy:
|
|
1875
|
+
submittedBy: z6.string(),
|
|
1165
1876
|
submittedByUser: userRefSchema.nullable().optional().default(null),
|
|
1166
|
-
sourceMeta:
|
|
1167
|
-
reviewPolicySnapshot:
|
|
1168
|
-
mergeSummary:
|
|
1169
|
-
rejectedReason:
|
|
1170
|
-
reviewedAt:
|
|
1171
|
-
mergedAt:
|
|
1172
|
-
createdAt:
|
|
1173
|
-
updatedAt:
|
|
1877
|
+
sourceMeta: z6.record(z6.string(), z6.unknown()),
|
|
1878
|
+
reviewPolicySnapshot: z6.record(z6.string(), z6.unknown()),
|
|
1879
|
+
mergeSummary: z6.record(z6.string(), z6.unknown()),
|
|
1880
|
+
rejectedReason: z6.string().nullable(),
|
|
1881
|
+
reviewedAt: z6.string().nullable(),
|
|
1882
|
+
mergedAt: z6.string().nullable(),
|
|
1883
|
+
createdAt: z6.string(),
|
|
1884
|
+
updatedAt: z6.string(),
|
|
1174
1885
|
base: baseSchema.nullable(),
|
|
1175
1886
|
node: nodeSchema.nullable(),
|
|
1176
|
-
operations:
|
|
1887
|
+
operations: z6.array(operationSchema),
|
|
1177
1888
|
primaryOperation: operationSchema.nullable(),
|
|
1178
|
-
operationCount:
|
|
1179
|
-
reviews:
|
|
1889
|
+
operationCount: z6.number(),
|
|
1890
|
+
reviews: z6.array(reviewSchema)
|
|
1180
1891
|
});
|
|
1181
|
-
var agentTaskSchema =
|
|
1892
|
+
var agentTaskSchema = z6.object({
|
|
1182
1893
|
changeRequest: changeRequestSchema,
|
|
1183
|
-
trigger:
|
|
1184
|
-
reviewReason:
|
|
1185
|
-
aiComments:
|
|
1894
|
+
trigger: z6.enum(["changes_requested", "ai_mention"]),
|
|
1895
|
+
reviewReason: z6.string().nullable(),
|
|
1896
|
+
aiComments: z6.array(commentSchema)
|
|
1186
1897
|
});
|
|
1187
|
-
var searchResultSchema =
|
|
1188
|
-
id:
|
|
1189
|
-
kind:
|
|
1190
|
-
title:
|
|
1191
|
-
body:
|
|
1192
|
-
eyebrow:
|
|
1193
|
-
href:
|
|
1194
|
-
updatedAt:
|
|
1898
|
+
var searchResultSchema = z6.object({
|
|
1899
|
+
id: z6.string(),
|
|
1900
|
+
kind: z6.enum(["record", "change_request", "base", "file"]),
|
|
1901
|
+
title: z6.string(),
|
|
1902
|
+
body: z6.string(),
|
|
1903
|
+
eyebrow: z6.string(),
|
|
1904
|
+
href: z6.string(),
|
|
1905
|
+
updatedAt: z6.string().nullable()
|
|
1195
1906
|
});
|
|
1196
|
-
var searchResponseSchema =
|
|
1197
|
-
query:
|
|
1198
|
-
limit:
|
|
1199
|
-
offset:
|
|
1200
|
-
hasMore:
|
|
1201
|
-
results:
|
|
1907
|
+
var searchResponseSchema = z6.object({
|
|
1908
|
+
query: z6.string(),
|
|
1909
|
+
limit: z6.number(),
|
|
1910
|
+
offset: z6.number(),
|
|
1911
|
+
hasMore: z6.boolean(),
|
|
1912
|
+
results: z6.array(searchResultSchema)
|
|
1202
1913
|
});
|
|
1203
|
-
var liveEventSchema =
|
|
1204
|
-
kind:
|
|
1914
|
+
var liveEventSchema = z6.object({
|
|
1915
|
+
kind: z6.enum([
|
|
1205
1916
|
"change_request.created",
|
|
1206
1917
|
"change_request.updated",
|
|
1207
1918
|
"change_request.deleted",
|
|
@@ -1215,16 +1926,16 @@ var liveEventSchema = z4.object({
|
|
|
1215
1926
|
// busabase-cloud's host hook to persist an inbox notification row.
|
|
1216
1927
|
"change_request.pending_review"
|
|
1217
1928
|
]),
|
|
1218
|
-
spaceId:
|
|
1219
|
-
actorId:
|
|
1220
|
-
changeRequestId:
|
|
1221
|
-
baseId:
|
|
1222
|
-
nodeIds:
|
|
1223
|
-
recordIds:
|
|
1224
|
-
viewIds:
|
|
1225
|
-
operationCount:
|
|
1929
|
+
spaceId: z6.string(),
|
|
1930
|
+
actorId: z6.string(),
|
|
1931
|
+
changeRequestId: z6.string(),
|
|
1932
|
+
baseId: z6.string().nullable(),
|
|
1933
|
+
nodeIds: z6.array(z6.string()),
|
|
1934
|
+
recordIds: z6.array(z6.string()),
|
|
1935
|
+
viewIds: z6.array(z6.string()),
|
|
1936
|
+
operationCount: z6.number()
|
|
1226
1937
|
});
|
|
1227
|
-
var auditActionSchema =
|
|
1938
|
+
var auditActionSchema = z6.enum([
|
|
1228
1939
|
"record.viewed",
|
|
1229
1940
|
"change_request.created",
|
|
1230
1941
|
"change_request.updated",
|
|
@@ -1246,157 +1957,158 @@ var auditActionSchema = z4.enum([
|
|
|
1246
1957
|
"asset.metadata_updated",
|
|
1247
1958
|
"asset.text_written",
|
|
1248
1959
|
"asset.text_marked_none",
|
|
1960
|
+
"node.metadata_updated",
|
|
1249
1961
|
"node.purged"
|
|
1250
1962
|
]);
|
|
1251
|
-
var auditEventSchema =
|
|
1252
|
-
id:
|
|
1963
|
+
var auditEventSchema = z6.object({
|
|
1964
|
+
id: z6.string(),
|
|
1253
1965
|
action: auditActionSchema,
|
|
1254
|
-
actorId:
|
|
1966
|
+
actorId: z6.string(),
|
|
1255
1967
|
actor: userRefSchema.nullable().optional().default(null),
|
|
1256
|
-
baseId:
|
|
1257
|
-
recordId:
|
|
1258
|
-
changeRequestId:
|
|
1259
|
-
operationId:
|
|
1260
|
-
commitId:
|
|
1261
|
-
metadata:
|
|
1262
|
-
createdAt:
|
|
1968
|
+
baseId: z6.string().nullable(),
|
|
1969
|
+
recordId: z6.string().nullable(),
|
|
1970
|
+
changeRequestId: z6.string().nullable(),
|
|
1971
|
+
operationId: z6.string().nullable(),
|
|
1972
|
+
commitId: z6.string().nullable(),
|
|
1973
|
+
metadata: z6.record(z6.string(), z6.unknown()),
|
|
1974
|
+
createdAt: z6.string()
|
|
1263
1975
|
});
|
|
1264
|
-
var createAuditEventInputSchema =
|
|
1976
|
+
var createAuditEventInputSchema = z6.object({
|
|
1265
1977
|
action: auditActionSchema,
|
|
1266
|
-
actorId:
|
|
1267
|
-
baseId:
|
|
1268
|
-
recordId:
|
|
1269
|
-
changeRequestId:
|
|
1270
|
-
operationId:
|
|
1271
|
-
commitId:
|
|
1272
|
-
metadata:
|
|
1978
|
+
actorId: z6.string().optional().default("local-viewer"),
|
|
1979
|
+
baseId: z6.string().optional().nullable(),
|
|
1980
|
+
recordId: z6.string().optional().nullable(),
|
|
1981
|
+
changeRequestId: z6.string().optional().nullable(),
|
|
1982
|
+
operationId: z6.string().optional().nullable(),
|
|
1983
|
+
commitId: z6.string().optional().nullable(),
|
|
1984
|
+
metadata: z6.record(z6.string(), z6.unknown()).optional().default({})
|
|
1273
1985
|
});
|
|
1274
|
-
var nodeOperationInputSchema =
|
|
1275
|
-
|
|
1276
|
-
kind:
|
|
1277
|
-
ref:
|
|
1986
|
+
var nodeOperationInputSchema = z6.discriminatedUnion("kind", [
|
|
1987
|
+
z6.object({
|
|
1988
|
+
kind: z6.literal("create"),
|
|
1989
|
+
ref: z6.string().min(1).optional().describe(
|
|
1278
1990
|
'Optional in-change-request temp id for this node. A later operation can set parentNodeRef to this value to nest under it \u2014 e.g. create a folder with ref "growth", then create Bases with parentNodeRef "growth", all in one change request.'
|
|
1279
1991
|
),
|
|
1280
|
-
parentNodeId:
|
|
1281
|
-
parentNodeRef:
|
|
1992
|
+
parentNodeId: z6.string().optional(),
|
|
1993
|
+
parentNodeRef: z6.string().min(1).optional().describe(
|
|
1282
1994
|
"Parent this node under a node an EARLIER operation in the same change request created (matched by its ref). Mutually exclusive with parentNodeId."
|
|
1283
1995
|
),
|
|
1284
|
-
nodeType:
|
|
1285
|
-
slug:
|
|
1286
|
-
name:
|
|
1287
|
-
description:
|
|
1288
|
-
metadata:
|
|
1996
|
+
nodeType: z6.enum(CREATABLE_NODE_TYPES),
|
|
1997
|
+
slug: z6.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1998
|
+
name: z6.string().min(1),
|
|
1999
|
+
description: z6.string().optional().default(""),
|
|
2000
|
+
metadata: z6.record(z6.string(), z6.unknown()).optional().default({}),
|
|
1289
2001
|
// Base fields (nodeType "base" only); a base needs at least one field.
|
|
1290
|
-
fields:
|
|
1291
|
-
|
|
1292
|
-
slug:
|
|
2002
|
+
fields: z6.array(
|
|
2003
|
+
z6.object({
|
|
2004
|
+
slug: z6.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1293
2005
|
name: fieldNameSchema,
|
|
1294
2006
|
type: fieldTypeSchema.default("text"),
|
|
1295
|
-
required:
|
|
2007
|
+
required: z6.boolean().optional().default(false),
|
|
1296
2008
|
options: fieldOptionsSchema.optional().default({})
|
|
1297
2009
|
})
|
|
1298
2010
|
).optional()
|
|
1299
2011
|
}),
|
|
1300
|
-
|
|
1301
|
-
kind:
|
|
1302
|
-
nodeId:
|
|
1303
|
-
slug:
|
|
1304
|
-
name:
|
|
1305
|
-
description:
|
|
2012
|
+
z6.object({
|
|
2013
|
+
kind: z6.literal("rename"),
|
|
2014
|
+
nodeId: z6.string(),
|
|
2015
|
+
slug: z6.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
|
|
2016
|
+
name: z6.string().min(1).optional(),
|
|
2017
|
+
description: z6.string().optional()
|
|
1306
2018
|
}),
|
|
1307
|
-
|
|
1308
|
-
kind:
|
|
1309
|
-
nodeId:
|
|
2019
|
+
z6.object({
|
|
2020
|
+
kind: z6.literal("delete"),
|
|
2021
|
+
nodeId: z6.string()
|
|
1310
2022
|
}),
|
|
1311
|
-
|
|
1312
|
-
kind:
|
|
1313
|
-
nodeId:
|
|
2023
|
+
z6.object({
|
|
2024
|
+
kind: z6.literal("restore"),
|
|
2025
|
+
nodeId: z6.string()
|
|
1314
2026
|
}),
|
|
1315
|
-
|
|
1316
|
-
kind:
|
|
1317
|
-
nodeId:
|
|
2027
|
+
z6.object({
|
|
2028
|
+
kind: z6.literal("move"),
|
|
2029
|
+
nodeId: z6.string(),
|
|
1318
2030
|
// Exactly one of parentNodeId / parentNodeRef (a ref created earlier in this CR).
|
|
1319
|
-
parentNodeId:
|
|
1320
|
-
parentNodeRef:
|
|
1321
|
-
position:
|
|
2031
|
+
parentNodeId: z6.string().optional(),
|
|
2032
|
+
parentNodeRef: z6.string().min(1).optional(),
|
|
2033
|
+
position: z6.number().int().optional()
|
|
1322
2034
|
})
|
|
1323
2035
|
]);
|
|
1324
|
-
var createNodeChangeRequestInputSchema =
|
|
1325
|
-
message:
|
|
2036
|
+
var createNodeChangeRequestInputSchema = z6.object({
|
|
2037
|
+
message: z6.string().optional().default("Update node tree").describe(
|
|
1326
2038
|
'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Reorganize marketing docs under a Campaigns folder".'
|
|
1327
2039
|
),
|
|
1328
|
-
submittedBy:
|
|
1329
|
-
autoMerge:
|
|
2040
|
+
submittedBy: z6.string().optional().default("local-producer"),
|
|
2041
|
+
autoMerge: z6.boolean().optional().default(false).describe(
|
|
1330
2042
|
"Whether to approve and merge this structural node change immediately. Defaults to false so node Change Request creation is review-first unless explicitly requested."
|
|
1331
2043
|
),
|
|
1332
|
-
operations:
|
|
2044
|
+
operations: z6.array(nodeOperationInputSchema).min(1)
|
|
1333
2045
|
});
|
|
1334
|
-
var moveNodeInputSchema =
|
|
1335
|
-
nodeId:
|
|
1336
|
-
parentNodeId:
|
|
1337
|
-
position:
|
|
1338
|
-
message:
|
|
1339
|
-
submittedBy:
|
|
2046
|
+
var moveNodeInputSchema = z6.object({
|
|
2047
|
+
nodeId: z6.string(),
|
|
2048
|
+
parentNodeId: z6.string().optional().describe("New parent folder node id. Omit to keep the current parent and only reorder."),
|
|
2049
|
+
position: z6.number().int().optional().describe("New position among the target parent's children."),
|
|
2050
|
+
message: z6.string().optional().describe("Reviewer-facing Change Request message."),
|
|
2051
|
+
submittedBy: z6.string().optional()
|
|
1340
2052
|
});
|
|
1341
|
-
var createDeleteChangeRequestInputSchema =
|
|
1342
|
-
message:
|
|
2053
|
+
var createDeleteChangeRequestInputSchema = z6.object({
|
|
2054
|
+
message: z6.string().optional().default("Delete record").describe(
|
|
1343
2055
|
'Explanation shown to the human reviewer. Say what is being removed and why, e.g. "Archive duplicate contact \u2014 merged into Acme Corp".'
|
|
1344
2056
|
),
|
|
1345
|
-
submittedBy:
|
|
2057
|
+
submittedBy: z6.string().optional().default("local-producer"),
|
|
1346
2058
|
// Only "archive" is supported — hard delete after retention was never
|
|
1347
2059
|
// implemented, so the API no longer accepts it (breaking change).
|
|
1348
|
-
deleteMode:
|
|
2060
|
+
deleteMode: z6.enum(["archive"]).optional().default("archive")
|
|
1349
2061
|
});
|
|
1350
|
-
var reviseOperationInputSchema =
|
|
1351
|
-
fields:
|
|
2062
|
+
var reviseOperationInputSchema = z6.object({
|
|
2063
|
+
fields: z6.record(z6.string(), z6.unknown()).describe(
|
|
1352
2064
|
"Updated field values keyed by field slug. If you set the base's PRIMARY field (its first field), keep it a short human-readable name \u2014 it is the record's display title everywhere."
|
|
1353
2065
|
),
|
|
1354
|
-
message:
|
|
2066
|
+
message: z6.string().optional().default("Revise operation").describe(
|
|
1355
2067
|
'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Update deal stage to qualified \u2014 demo booked for July 8".'
|
|
1356
2068
|
),
|
|
1357
|
-
author:
|
|
1358
|
-
baseCommitId:
|
|
2069
|
+
author: z6.string().optional().default("local-producer"),
|
|
2070
|
+
baseCommitId: z6.string().optional()
|
|
1359
2071
|
});
|
|
1360
|
-
var reviewChangeRequestInputSchema =
|
|
1361
|
-
verdict:
|
|
1362
|
-
reason:
|
|
2072
|
+
var reviewChangeRequestInputSchema = z6.object({
|
|
2073
|
+
verdict: z6.enum(["approved", "rejected"]),
|
|
2074
|
+
reason: z6.string().optional()
|
|
1363
2075
|
});
|
|
1364
|
-
var commentSubjectInputSchema =
|
|
2076
|
+
var commentSubjectInputSchema = z6.object({
|
|
1365
2077
|
subjectType: commentSubjectTypeSchema,
|
|
1366
|
-
subjectId:
|
|
2078
|
+
subjectId: z6.string().min(1)
|
|
1367
2079
|
});
|
|
1368
2080
|
var createCommentInputSchema = commentSubjectInputSchema.extend({
|
|
1369
|
-
authorId:
|
|
1370
|
-
body:
|
|
1371
|
-
mentionsAi:
|
|
2081
|
+
authorId: z6.string().optional().default("local-admin"),
|
|
2082
|
+
body: z6.string().trim().min(1),
|
|
2083
|
+
mentionsAi: z6.boolean().optional().default(false)
|
|
1372
2084
|
});
|
|
1373
|
-
var listInputSchema =
|
|
1374
|
-
limit:
|
|
2085
|
+
var listInputSchema = z6.object({
|
|
2086
|
+
limit: z6.coerce.number().int().min(1).max(100).optional().default(50)
|
|
1375
2087
|
}).optional().default({ limit: 50 });
|
|
1376
|
-
var listChangeRequestsPagedInputSchema =
|
|
1377
|
-
limit:
|
|
2088
|
+
var listChangeRequestsPagedInputSchema = z6.object({
|
|
2089
|
+
limit: z6.coerce.number().int().min(1).max(100).optional().default(50),
|
|
1378
2090
|
/** Opaque base64 cursor (`createdAt|id`) for keyset pagination. */
|
|
1379
|
-
cursor:
|
|
1380
|
-
status:
|
|
1381
|
-
mine:
|
|
2091
|
+
cursor: z6.string().optional(),
|
|
2092
|
+
status: z6.array(changeRequestStatusSchema).optional(),
|
|
2093
|
+
mine: z6.boolean().optional()
|
|
1382
2094
|
}).optional().default({ limit: 50 });
|
|
1383
|
-
var listChangeRequestsResponseSchema =
|
|
1384
|
-
changeRequests:
|
|
1385
|
-
nextCursor:
|
|
2095
|
+
var listChangeRequestsResponseSchema = z6.object({
|
|
2096
|
+
changeRequests: z6.array(changeRequestSchema),
|
|
2097
|
+
nextCursor: z6.string().nullable()
|
|
1386
2098
|
});
|
|
1387
|
-
var changeRequestCountsSchema =
|
|
1388
|
-
review:
|
|
1389
|
-
changes:
|
|
1390
|
-
created:
|
|
1391
|
-
approved:
|
|
1392
|
-
merged:
|
|
1393
|
-
rejected:
|
|
2099
|
+
var changeRequestCountsSchema = z6.object({
|
|
2100
|
+
review: z6.number().int().nonnegative(),
|
|
2101
|
+
changes: z6.number().int().nonnegative(),
|
|
2102
|
+
created: z6.number().int().nonnegative(),
|
|
2103
|
+
approved: z6.number().int().nonnegative(),
|
|
2104
|
+
merged: z6.number().int().nonnegative(),
|
|
2105
|
+
rejected: z6.number().int().nonnegative()
|
|
1394
2106
|
});
|
|
1395
2107
|
var SEARCH_SOURCES = ["records", "files", "names"];
|
|
1396
|
-
var searchInputSchema =
|
|
1397
|
-
query:
|
|
1398
|
-
limit:
|
|
1399
|
-
offset:
|
|
2108
|
+
var searchInputSchema = z6.object({
|
|
2109
|
+
query: z6.string().default(""),
|
|
2110
|
+
limit: z6.coerce.number().int().min(1).max(100).optional().default(20),
|
|
2111
|
+
offset: z6.coerce.number().int().min(0).optional().default(0),
|
|
1400
2112
|
/**
|
|
1401
2113
|
* Restrict which content this call searches. Omitted means all three
|
|
1402
2114
|
* (unchanged behavior for every caller before this parameter existed).
|
|
@@ -1406,26 +2118,26 @@ var searchInputSchema = z4.object({
|
|
|
1406
2118
|
* (`?sources=records&sources=files`) becomes an array. Accept both shapes
|
|
1407
2119
|
* and normalize to an array.
|
|
1408
2120
|
*/
|
|
1409
|
-
sources:
|
|
2121
|
+
sources: z6.union([z6.array(z6.enum(SEARCH_SOURCES)), z6.enum(SEARCH_SOURCES)]).transform((value) => Array.isArray(value) ? value : [value]).optional()
|
|
1410
2122
|
});
|
|
1411
|
-
var authSpaceSchema =
|
|
1412
|
-
id:
|
|
1413
|
-
name:
|
|
1414
|
-
slug:
|
|
1415
|
-
plan:
|
|
2123
|
+
var authSpaceSchema = z6.object({
|
|
2124
|
+
id: z6.string(),
|
|
2125
|
+
name: z6.string(),
|
|
2126
|
+
slug: z6.string().nullable(),
|
|
2127
|
+
plan: z6.string().nullable()
|
|
1416
2128
|
});
|
|
1417
|
-
var authUserSchema =
|
|
1418
|
-
id:
|
|
1419
|
-
name:
|
|
1420
|
-
email:
|
|
1421
|
-
image:
|
|
2129
|
+
var authUserSchema = z6.object({
|
|
2130
|
+
id: z6.string(),
|
|
2131
|
+
name: z6.string(),
|
|
2132
|
+
email: z6.string().nullable(),
|
|
2133
|
+
image: z6.string().nullable()
|
|
1422
2134
|
});
|
|
1423
|
-
var authMemberSchema =
|
|
1424
|
-
userId:
|
|
1425
|
-
spaceId:
|
|
1426
|
-
role:
|
|
2135
|
+
var authMemberSchema = z6.object({
|
|
2136
|
+
userId: z6.string(),
|
|
2137
|
+
spaceId: z6.string(),
|
|
2138
|
+
role: z6.string()
|
|
1427
2139
|
});
|
|
1428
|
-
var authInfoSchema =
|
|
2140
|
+
var authInfoSchema = z6.object({
|
|
1429
2141
|
/** The space this request is scoped to (explicit `x-busabase-space` header, else the default). */
|
|
1430
2142
|
space: authSpaceSchema,
|
|
1431
2143
|
user: authUserSchema,
|
|
@@ -1435,11 +2147,15 @@ var authInfoSchema = z4.object({
|
|
|
1435
2147
|
* space-scoped — when this has more than one entry, callers should confirm the
|
|
1436
2148
|
* intended space and target it explicitly via the `x-busabase-space` header.
|
|
1437
2149
|
*/
|
|
1438
|
-
spaces:
|
|
2150
|
+
spaces: z6.array(authSpaceSchema),
|
|
2151
|
+
/** Cloud only: the server created this user's first Space during this exact request. */
|
|
2152
|
+
createdSpace: z6.boolean().optional(),
|
|
2153
|
+
/** Cloud only: this auto-created Space still needs its idempotent starter initialization. */
|
|
2154
|
+
bootstrapRequired: z6.boolean().optional()
|
|
1439
2155
|
});
|
|
1440
2156
|
|
|
1441
2157
|
// ../../packages/busabase-contract/src/domains/base/contract/view-schemas.ts
|
|
1442
|
-
var viewFilterOperatorSchema =
|
|
2158
|
+
var viewFilterOperatorSchema = z7.enum([
|
|
1443
2159
|
"contains",
|
|
1444
2160
|
"equals",
|
|
1445
2161
|
"not_empty",
|
|
@@ -1447,82 +2163,82 @@ var viewFilterOperatorSchema = z5.enum([
|
|
|
1447
2163
|
"is_true",
|
|
1448
2164
|
"is_false"
|
|
1449
2165
|
]);
|
|
1450
|
-
var viewFilterSchema =
|
|
1451
|
-
fieldSlug:
|
|
2166
|
+
var viewFilterSchema = z7.object({
|
|
2167
|
+
fieldSlug: z7.string(),
|
|
1452
2168
|
// Stable field identity — survives slug reuse; populated on merge.
|
|
1453
|
-
fieldId:
|
|
2169
|
+
fieldId: z7.string().optional(),
|
|
1454
2170
|
operator: viewFilterOperatorSchema,
|
|
1455
|
-
value:
|
|
2171
|
+
value: z7.unknown().optional()
|
|
1456
2172
|
});
|
|
1457
|
-
var viewSortSchema =
|
|
1458
|
-
direction:
|
|
1459
|
-
fieldSlug:
|
|
1460
|
-
fieldId:
|
|
2173
|
+
var viewSortSchema = z7.object({
|
|
2174
|
+
direction: z7.enum(["asc", "desc"]),
|
|
2175
|
+
fieldSlug: z7.string(),
|
|
2176
|
+
fieldId: z7.string().optional()
|
|
1461
2177
|
});
|
|
1462
|
-
var viewTypeSchema =
|
|
1463
|
-
var ganttScaleSchema =
|
|
1464
|
-
var galleryCoverFitSchema =
|
|
1465
|
-
var galleryCardSizeSchema =
|
|
1466
|
-
var viewConfigSchema =
|
|
1467
|
-
filters:
|
|
1468
|
-
sorts:
|
|
1469
|
-
visibleFieldSlugs:
|
|
2178
|
+
var viewTypeSchema = z7.enum(["table", "gallery", "kanban", "calendar", "gantt"]);
|
|
2179
|
+
var ganttScaleSchema = z7.enum(["week", "month"]);
|
|
2180
|
+
var galleryCoverFitSchema = z7.enum(["cover", "fit"]);
|
|
2181
|
+
var galleryCardSizeSchema = z7.enum(["small", "medium", "large"]);
|
|
2182
|
+
var viewConfigSchema = z7.object({
|
|
2183
|
+
filters: z7.array(viewFilterSchema).default([]),
|
|
2184
|
+
sorts: z7.array(viewSortSchema).default([]),
|
|
2185
|
+
visibleFieldSlugs: z7.array(z7.string()).nullable().optional(),
|
|
1470
2186
|
// ── Gallery-only presentation config (ignored by table views) ──
|
|
1471
2187
|
// Which attachment field supplies the cover image (null = no cover).
|
|
1472
|
-
coverFieldSlug:
|
|
2188
|
+
coverFieldSlug: z7.string().nullable().optional(),
|
|
1473
2189
|
coverFit: galleryCoverFitSchema.optional(),
|
|
1474
2190
|
cardSize: galleryCardSizeSchema.optional(),
|
|
1475
2191
|
// Whether to render the field label above each value on a card.
|
|
1476
|
-
showFieldLabels:
|
|
2192
|
+
showFieldLabels: z7.boolean().optional(),
|
|
1477
2193
|
// ── Kanban-only: which single-select field stacks records into columns.
|
|
1478
|
-
stackByFieldSlug:
|
|
2194
|
+
stackByFieldSlug: z7.string().nullable().optional(),
|
|
1479
2195
|
// ── Calendar-only: which date field positions records on the month grid.
|
|
1480
|
-
dateFieldSlug:
|
|
2196
|
+
dateFieldSlug: z7.string().nullable().optional(),
|
|
1481
2197
|
// ── Gantt-only: the start/end date fields bounding each record's bar, plus
|
|
1482
2198
|
// the time-axis granularity.
|
|
1483
|
-
startFieldSlug:
|
|
1484
|
-
endFieldSlug:
|
|
2199
|
+
startFieldSlug: z7.string().nullable().optional(),
|
|
2200
|
+
endFieldSlug: z7.string().nullable().optional(),
|
|
1485
2201
|
ganttScale: ganttScaleSchema.optional()
|
|
1486
2202
|
});
|
|
1487
|
-
var viewSchema =
|
|
1488
|
-
id:
|
|
1489
|
-
baseId:
|
|
1490
|
-
slug:
|
|
1491
|
-
name:
|
|
1492
|
-
description:
|
|
2203
|
+
var viewSchema = z7.object({
|
|
2204
|
+
id: z7.string(),
|
|
2205
|
+
baseId: z7.string(),
|
|
2206
|
+
slug: z7.string(),
|
|
2207
|
+
name: z7.string(),
|
|
2208
|
+
description: z7.string(),
|
|
1493
2209
|
type: viewTypeSchema,
|
|
1494
2210
|
config: viewConfigSchema,
|
|
1495
|
-
status:
|
|
1496
|
-
createdBy:
|
|
2211
|
+
status: z7.enum(["active", "archived"]),
|
|
2212
|
+
createdBy: z7.string(),
|
|
1497
2213
|
createdByUser: userRefSchema.nullable().optional().default(null),
|
|
1498
|
-
archivedAt:
|
|
1499
|
-
createdAt:
|
|
1500
|
-
updatedAt:
|
|
2214
|
+
archivedAt: z7.string().nullable(),
|
|
2215
|
+
createdAt: z7.string(),
|
|
2216
|
+
updatedAt: z7.string()
|
|
1501
2217
|
});
|
|
1502
|
-
var createViewInputSchema =
|
|
2218
|
+
var createViewInputSchema = z7.object({
|
|
1503
2219
|
config: viewConfigSchema.optional().default({ filters: [], sorts: [] }),
|
|
1504
|
-
description:
|
|
1505
|
-
message:
|
|
1506
|
-
name:
|
|
2220
|
+
description: z7.string().optional().default(""),
|
|
2221
|
+
message: z7.string().optional().default("Create view"),
|
|
2222
|
+
name: z7.string().min(1),
|
|
1507
2223
|
type: viewTypeSchema.optional().default("table"),
|
|
1508
|
-
slug:
|
|
1509
|
-
submittedBy:
|
|
2224
|
+
slug: z7.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
|
|
2225
|
+
submittedBy: z7.string().optional().default("local-producer")
|
|
1510
2226
|
});
|
|
1511
|
-
var updateViewInputSchema =
|
|
2227
|
+
var updateViewInputSchema = z7.object({
|
|
1512
2228
|
config: viewConfigSchema.optional(),
|
|
1513
|
-
description:
|
|
1514
|
-
message:
|
|
1515
|
-
name:
|
|
2229
|
+
description: z7.string().optional(),
|
|
2230
|
+
message: z7.string().optional().default("Update view"),
|
|
2231
|
+
name: z7.string().min(1).optional(),
|
|
1516
2232
|
type: viewTypeSchema.optional(),
|
|
1517
|
-
submittedBy:
|
|
2233
|
+
submittedBy: z7.string().optional().default("local-producer")
|
|
1518
2234
|
});
|
|
1519
|
-
var deleteViewInputSchema =
|
|
1520
|
-
message:
|
|
1521
|
-
submittedBy:
|
|
2235
|
+
var deleteViewInputSchema = z7.object({
|
|
2236
|
+
message: z7.string().optional().default("Delete view"),
|
|
2237
|
+
submittedBy: z7.string().optional().default("local-producer")
|
|
1522
2238
|
});
|
|
1523
|
-
var restoreViewInputSchema =
|
|
1524
|
-
message:
|
|
1525
|
-
submittedBy:
|
|
2239
|
+
var restoreViewInputSchema = z7.object({
|
|
2240
|
+
message: z7.string().optional().default("Restore view"),
|
|
2241
|
+
submittedBy: z7.string().optional().default("local-producer")
|
|
1526
2242
|
});
|
|
1527
2243
|
|
|
1528
2244
|
// ../../packages/busabase-contract/src/domains/package/types.ts
|
|
@@ -1552,131 +2268,131 @@ var PACKAGE_MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
|
1552
2268
|
var PACKAGE_MAX_TOTAL_BYTES = 250 * 1024 * 1024;
|
|
1553
2269
|
var PACKAGE_MAX_FILE_COUNT = 5e3;
|
|
1554
2270
|
var PACKAGE_MAX_RECORDS_PER_BASE = 5e4;
|
|
1555
|
-
var PackageManifestSchema =
|
|
1556
|
-
format:
|
|
2271
|
+
var PackageManifestSchema = z8.object({
|
|
2272
|
+
format: z8.literal(PACKAGE_FORMAT),
|
|
1557
2273
|
/** Default `--into-folder` name. Not the repo name — a repo can host many packages. */
|
|
1558
|
-
name:
|
|
1559
|
-
description:
|
|
2274
|
+
name: z8.string().min(1),
|
|
2275
|
+
description: z8.string().default(""),
|
|
1560
2276
|
/**
|
|
1561
2277
|
* Informational / reserved for a future marketplace index. The real version pin
|
|
1562
2278
|
* is always the git ref in the install URL.
|
|
1563
2279
|
*/
|
|
1564
|
-
version:
|
|
1565
|
-
author:
|
|
1566
|
-
license:
|
|
1567
|
-
homepage:
|
|
1568
|
-
tags:
|
|
2280
|
+
version: z8.string().optional(),
|
|
2281
|
+
author: z8.string().optional(),
|
|
2282
|
+
license: z8.string().optional(),
|
|
2283
|
+
homepage: z8.string().url().optional(),
|
|
2284
|
+
tags: z8.array(z8.string()).default([])
|
|
1569
2285
|
});
|
|
1570
|
-
var PackageFileTreeNodeMetaSchema =
|
|
1571
|
-
type:
|
|
1572
|
-
name:
|
|
1573
|
-
description:
|
|
1574
|
-
position:
|
|
2286
|
+
var PackageFileTreeNodeMetaSchema = z8.object({
|
|
2287
|
+
type: z8.enum(PACKAGE_FILE_TREE_NODE_TYPES),
|
|
2288
|
+
name: z8.string().min(1),
|
|
2289
|
+
description: z8.string().default(""),
|
|
2290
|
+
position: z8.number().int().optional()
|
|
1575
2291
|
});
|
|
1576
|
-
var PackageFolderMetaSchema =
|
|
2292
|
+
var PackageFolderMetaSchema = z8.object({
|
|
1577
2293
|
/** Defaults to the humanized slug when absent. */
|
|
1578
|
-
name:
|
|
1579
|
-
description:
|
|
1580
|
-
position:
|
|
2294
|
+
name: z8.string().optional(),
|
|
2295
|
+
description: z8.string().default(""),
|
|
2296
|
+
position: z8.number().int().optional()
|
|
1581
2297
|
});
|
|
1582
2298
|
var PackageFileNodeMetaSchema = PackageFolderMetaSchema;
|
|
1583
|
-
var PackageDocFrontmatterSchema =
|
|
1584
|
-
name:
|
|
1585
|
-
description:
|
|
1586
|
-
position:
|
|
2299
|
+
var PackageDocFrontmatterSchema = z8.object({
|
|
2300
|
+
name: z8.string().min(1),
|
|
2301
|
+
description: z8.string().default(""),
|
|
2302
|
+
position: z8.number().int().optional()
|
|
1587
2303
|
});
|
|
1588
|
-
var PackageFieldOptionsSchema =
|
|
1589
|
-
ai:
|
|
1590
|
-
model:
|
|
1591
|
-
prompt:
|
|
1592
|
-
reviewRequired:
|
|
2304
|
+
var PackageFieldOptionsSchema = z8.object({
|
|
2305
|
+
ai: z8.object({
|
|
2306
|
+
model: z8.string().optional(),
|
|
2307
|
+
prompt: z8.string().optional(),
|
|
2308
|
+
reviewRequired: z8.boolean().optional(),
|
|
1593
2309
|
/** Package-only — replaces `ai.sourceFieldIds`. */
|
|
1594
|
-
sourceFieldSlugs:
|
|
2310
|
+
sourceFieldSlugs: z8.array(z8.string()).optional()
|
|
1595
2311
|
}).optional(),
|
|
1596
|
-
attachment:
|
|
1597
|
-
maxFiles:
|
|
1598
|
-
allowedMimeTypes:
|
|
1599
|
-
maxFileSize:
|
|
2312
|
+
attachment: z8.object({
|
|
2313
|
+
maxFiles: z8.number().int().positive().optional(),
|
|
2314
|
+
allowedMimeTypes: z8.array(z8.string()).optional(),
|
|
2315
|
+
maxFileSize: z8.number().int().positive().optional()
|
|
1600
2316
|
}).optional(),
|
|
1601
|
-
choices:
|
|
1602
|
-
|
|
1603
|
-
color:
|
|
1604
|
-
id:
|
|
1605
|
-
name:
|
|
2317
|
+
choices: z8.array(
|
|
2318
|
+
z8.object({
|
|
2319
|
+
color: z8.string().optional(),
|
|
2320
|
+
id: z8.string(),
|
|
2321
|
+
name: z8.string()
|
|
1606
2322
|
})
|
|
1607
2323
|
).optional(),
|
|
1608
|
-
code:
|
|
1609
|
-
language:
|
|
2324
|
+
code: z8.object({
|
|
2325
|
+
language: z8.string().optional()
|
|
1610
2326
|
}).optional(),
|
|
1611
|
-
embed:
|
|
1612
|
-
aspectRatio:
|
|
1613
|
-
height:
|
|
1614
|
-
providers:
|
|
2327
|
+
embed: z8.object({
|
|
2328
|
+
aspectRatio: z8.enum(["16:9", "4:3", "1:1"]).optional(),
|
|
2329
|
+
height: z8.number().int().positive().max(1200).optional(),
|
|
2330
|
+
providers: z8.array(z8.string()).optional()
|
|
1615
2331
|
}).optional(),
|
|
1616
2332
|
/** Package-only — replaces `inverseFieldId`. */
|
|
1617
|
-
inverseFieldSlug:
|
|
1618
|
-
multiple:
|
|
1619
|
-
number:
|
|
1620
|
-
format:
|
|
1621
|
-
currency:
|
|
1622
|
-
locale:
|
|
2333
|
+
inverseFieldSlug: z8.string().optional(),
|
|
2334
|
+
multiple: z8.boolean().optional(),
|
|
2335
|
+
number: z8.object({
|
|
2336
|
+
format: z8.enum(["plain", "currency"]).optional(),
|
|
2337
|
+
currency: z8.string().optional(),
|
|
2338
|
+
locale: z8.string().optional()
|
|
1623
2339
|
}).optional(),
|
|
1624
2340
|
/** Native API alias — the server resolves it to `targetBaseId`. */
|
|
1625
|
-
targetBaseSlug:
|
|
2341
|
+
targetBaseSlug: z8.string().optional()
|
|
1626
2342
|
}).default({});
|
|
1627
|
-
var PackageBaseFieldSchema =
|
|
1628
|
-
slug:
|
|
2343
|
+
var PackageBaseFieldSchema = z8.object({
|
|
2344
|
+
slug: z8.string().min(1),
|
|
1629
2345
|
name: fieldNameSchema,
|
|
1630
2346
|
type: fieldTypeSchema,
|
|
1631
|
-
required:
|
|
1632
|
-
position:
|
|
2347
|
+
required: z8.boolean().default(false),
|
|
2348
|
+
position: z8.number().int(),
|
|
1633
2349
|
options: PackageFieldOptionsSchema
|
|
1634
2350
|
});
|
|
1635
|
-
var PackageViewFilterSchema =
|
|
1636
|
-
fieldSlug:
|
|
2351
|
+
var PackageViewFilterSchema = z8.object({
|
|
2352
|
+
fieldSlug: z8.string().min(1),
|
|
1637
2353
|
operator: viewFilterOperatorSchema,
|
|
1638
|
-
value:
|
|
2354
|
+
value: z8.unknown().optional()
|
|
1639
2355
|
});
|
|
1640
|
-
var PackageViewSortSchema =
|
|
1641
|
-
direction:
|
|
1642
|
-
fieldSlug:
|
|
2356
|
+
var PackageViewSortSchema = z8.object({
|
|
2357
|
+
direction: z8.enum(["asc", "desc"]),
|
|
2358
|
+
fieldSlug: z8.string().min(1)
|
|
1643
2359
|
});
|
|
1644
|
-
var PackageViewConfigSchema =
|
|
1645
|
-
filters:
|
|
1646
|
-
sorts:
|
|
1647
|
-
visibleFieldSlugs:
|
|
2360
|
+
var PackageViewConfigSchema = z8.object({
|
|
2361
|
+
filters: z8.array(PackageViewFilterSchema).default([]),
|
|
2362
|
+
sorts: z8.array(PackageViewSortSchema).default([]),
|
|
2363
|
+
visibleFieldSlugs: z8.array(z8.string()).nullable().optional()
|
|
1648
2364
|
});
|
|
1649
|
-
var PackageViewSchema =
|
|
1650
|
-
slug:
|
|
1651
|
-
name:
|
|
1652
|
-
description:
|
|
1653
|
-
type:
|
|
2365
|
+
var PackageViewSchema = z8.object({
|
|
2366
|
+
slug: z8.string().min(1),
|
|
2367
|
+
name: z8.string().min(1),
|
|
2368
|
+
description: z8.string().default(""),
|
|
2369
|
+
type: z8.literal("table").default("table"),
|
|
1654
2370
|
config: PackageViewConfigSchema.default({ filters: [], sorts: [] })
|
|
1655
2371
|
});
|
|
1656
|
-
var PackageBaseSchema =
|
|
1657
|
-
name:
|
|
1658
|
-
description:
|
|
1659
|
-
position:
|
|
2372
|
+
var PackageBaseSchema = z8.object({
|
|
2373
|
+
name: z8.string().min(1),
|
|
2374
|
+
description: z8.string().default(""),
|
|
2375
|
+
position: z8.number().int().optional(),
|
|
1660
2376
|
/**
|
|
1661
2377
|
* Informational only — `reviewPolicy` is NOT settable via `bases.create`, so
|
|
1662
2378
|
* install cannot apply it and warns when it differs from the target's default.
|
|
1663
2379
|
*/
|
|
1664
|
-
reviewPolicy:
|
|
1665
|
-
kind:
|
|
1666
|
-
requiredApprovals:
|
|
2380
|
+
reviewPolicy: z8.object({
|
|
2381
|
+
kind: z8.literal("single"),
|
|
2382
|
+
requiredApprovals: z8.number()
|
|
1667
2383
|
}).optional(),
|
|
1668
|
-
fields:
|
|
1669
|
-
views:
|
|
2384
|
+
fields: z8.array(PackageBaseFieldSchema).default([]),
|
|
2385
|
+
views: z8.array(PackageViewSchema).default([])
|
|
1670
2386
|
});
|
|
1671
|
-
var PackageRecordLineSchema =
|
|
1672
|
-
key:
|
|
1673
|
-
fields:
|
|
2387
|
+
var PackageRecordLineSchema = z8.object({
|
|
2388
|
+
key: z8.string().min(1),
|
|
2389
|
+
fields: z8.record(z8.string(), z8.unknown()).default({})
|
|
1674
2390
|
});
|
|
1675
2391
|
|
|
1676
|
-
//
|
|
1677
|
-
import { createHash as
|
|
2392
|
+
// ../../packages/busabase-package/src/apply.ts
|
|
2393
|
+
import { createHash as createHash5 } from "crypto";
|
|
1678
2394
|
|
|
1679
|
-
//
|
|
2395
|
+
// ../../packages/busabase-package/src/tree.ts
|
|
1680
2396
|
import { extname } from "path";
|
|
1681
2397
|
var NODE_SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
1682
2398
|
var assertSafeNodeSlug = (slug, context) => {
|
|
@@ -1777,7 +2493,7 @@ var walkNodes = function* (nodes) {
|
|
|
1777
2493
|
};
|
|
1778
2494
|
var collectBaseNodes = (nodes) => [...walkNodes(nodes)].filter((node) => node.type === "base");
|
|
1779
2495
|
|
|
1780
|
-
//
|
|
2496
|
+
// ../../packages/busabase-package/src/apply.ts
|
|
1781
2497
|
var RECORD_BATCH_SIZE = 200;
|
|
1782
2498
|
var fieldKey = (baseSlug, fieldSlug) => `${baseSlug}\0${fieldSlug}`;
|
|
1783
2499
|
var applyInstall = async (client, plan, options) => {
|
|
@@ -2047,7 +2763,7 @@ var createFileTreeNode = async (client, node, parentNodeId, options, state) => {
|
|
|
2047
2763
|
var UploadTargetUnsupportedError = class extends Error {
|
|
2048
2764
|
};
|
|
2049
2765
|
var uploadAsset = async (client, fileName, mimeType, bytes) => {
|
|
2050
|
-
const contentHash = `sha256:${
|
|
2766
|
+
const contentHash = `sha256:${createHash5("sha256").update(bytes).digest("hex")}`;
|
|
2051
2767
|
const requested = await client.assets.createUploadUrl({
|
|
2052
2768
|
fileName,
|
|
2053
2769
|
mimeType,
|
|
@@ -2221,7 +2937,7 @@ var approveAndMerge = async (client, changeRequestId) => {
|
|
|
2221
2937
|
return client.changeRequests.merge({ changeRequestId });
|
|
2222
2938
|
};
|
|
2223
2939
|
|
|
2224
|
-
//
|
|
2940
|
+
// ../../packages/busabase-package/src/collect.ts
|
|
2225
2941
|
var findSourceNode = (nodes, slugOrId) => {
|
|
2226
2942
|
const stack = [...nodes];
|
|
2227
2943
|
while (stack.length > 0) {
|
|
@@ -2512,7 +3228,7 @@ var walk = function* (nodes) {
|
|
|
2512
3228
|
}
|
|
2513
3229
|
};
|
|
2514
3230
|
|
|
2515
|
-
//
|
|
3231
|
+
// ../../packages/busabase-package/src/github.ts
|
|
2516
3232
|
import { Uint8ArrayReader, ZipReader } from "@zip.js/zip.js";
|
|
2517
3233
|
var GITHUB_ALLOWED_HOSTS = [
|
|
2518
3234
|
"github.com",
|
|
@@ -2671,7 +3387,7 @@ var fetchGithubPackageFiles = async (repoUrl, options = {}) => {
|
|
|
2671
3387
|
return { source, files };
|
|
2672
3388
|
};
|
|
2673
3389
|
|
|
2674
|
-
//
|
|
3390
|
+
// ../../packages/busabase-package/src/frontmatter.ts
|
|
2675
3391
|
import { parse, stringify } from "yaml";
|
|
2676
3392
|
var DELIMITER = "---";
|
|
2677
3393
|
var normalizeDocBody = (body) => body.replaceAll("\r\n", "\n").replace(/^\n+/, "").replace(/\n+$/, "");
|
|
@@ -2715,7 +3431,7 @@ ${normalizeDocBody(body)}
|
|
|
2715
3431
|
`;
|
|
2716
3432
|
};
|
|
2717
3433
|
|
|
2718
|
-
//
|
|
3434
|
+
// ../../packages/busabase-package/src/layout-read.ts
|
|
2719
3435
|
var decodeText = (bytes, filePath) => {
|
|
2720
3436
|
const text = bytes.toString("utf8");
|
|
2721
3437
|
if (text.includes("\uFFFD")) {
|
|
@@ -2933,7 +3649,7 @@ var stripExtension = (fileName) => {
|
|
|
2933
3649
|
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
|
2934
3650
|
};
|
|
2935
3651
|
|
|
2936
|
-
//
|
|
3652
|
+
// ../../packages/busabase-package/src/layout-write.ts
|
|
2937
3653
|
import { mkdir, rm, writeFile } from "fs/promises";
|
|
2938
3654
|
import { dirname, join as join2 } from "path";
|
|
2939
3655
|
var sortKeysDeep = (value) => {
|
|
@@ -3124,9 +3840,20 @@ var writePackageFiles = async (files, outDir, options = {}) => {
|
|
|
3124
3840
|
}
|
|
3125
3841
|
};
|
|
3126
3842
|
|
|
3127
|
-
//
|
|
3843
|
+
// ../../packages/busabase-package/src/plan.ts
|
|
3844
|
+
var findTargetFolder = (nodes, slug) => {
|
|
3845
|
+
const roots = nodes.length === 1 && nodes[0].children ? nodes[0].children : nodes;
|
|
3846
|
+
return roots.find((node) => node.slug === slug && node.type === "folder");
|
|
3847
|
+
};
|
|
3848
|
+
var resolveTargetState = async (client, targetFolderSlug) => {
|
|
3849
|
+
const [nodes, bases] = await Promise.all([client.nodes.list(), client.bases.list()]);
|
|
3850
|
+
return {
|
|
3851
|
+
targetFolder: findTargetFolder(nodes, targetFolderSlug),
|
|
3852
|
+
existingBaseSlugs: new Set(bases.map((base) => base.slug))
|
|
3853
|
+
};
|
|
3854
|
+
};
|
|
3128
3855
|
var buildInstallPlan = (tree, target, options = {}) => {
|
|
3129
|
-
const targetFolderSlug = options.intoFolder ?? tree.manifest.name;
|
|
3856
|
+
const targetFolderSlug = options.intoFolder ?? suggestSlug(tree.manifest.name);
|
|
3130
3857
|
const warnings = [];
|
|
3131
3858
|
const existingChildSlugs = new Set(
|
|
3132
3859
|
(target.targetFolder?.children ?? []).map((child) => child.slug)
|
|
@@ -3338,26 +4065,21 @@ Nothing was created. Re-run with --rename to install the colliding items under s
|
|
|
3338
4065
|
};
|
|
3339
4066
|
|
|
3340
4067
|
// src/package/commands.ts
|
|
3341
|
-
var
|
|
4068
|
+
var reportProgress2 = (message) => {
|
|
3342
4069
|
console.error(message);
|
|
3343
4070
|
};
|
|
3344
4071
|
var runInstall = async (client, repoUrl, options) => {
|
|
3345
|
-
|
|
4072
|
+
reportProgress2(`Fetching ${repoUrl} \u2026`);
|
|
3346
4073
|
const { source, files } = await fetchGithubPackageFiles(repoUrl, {
|
|
3347
4074
|
githubToken: options.githubToken
|
|
3348
4075
|
});
|
|
3349
|
-
|
|
4076
|
+
reportProgress2(`Downloaded ${files.size} file(s) from ${source.owner}/${source.repo}.`);
|
|
3350
4077
|
const tree = readPackageTree(files);
|
|
3351
|
-
const
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
targetFolder: findTargetFolder(nodes, targetFolderSlug),
|
|
3357
|
-
existingBaseSlugs: new Set(bases.map((base) => base.slug))
|
|
3358
|
-
},
|
|
3359
|
-
{ intoFolder: options.intoFolder, rename: options.rename }
|
|
3360
|
-
);
|
|
4078
|
+
const targetFolderSlug = options.intoFolder ?? suggestSlug(tree.manifest.name);
|
|
4079
|
+
const plan = buildInstallPlan(tree, await resolveTargetState(client, targetFolderSlug), {
|
|
4080
|
+
intoFolder: options.intoFolder,
|
|
4081
|
+
rename: options.rename
|
|
4082
|
+
});
|
|
3361
4083
|
if (options.dryRun) {
|
|
3362
4084
|
const report = `${renderPlan(plan)}
|
|
3363
4085
|
|
|
@@ -3368,15 +4090,11 @@ Dry run \u2014 nothing was created.`;
|
|
|
3368
4090
|
const result = await applyInstall(client, plan, {
|
|
3369
4091
|
autoMerge: Boolean(options.autoMerge),
|
|
3370
4092
|
submittedBy: `busabase-cli install (${tree.manifest.name})`,
|
|
3371
|
-
onProgress:
|
|
4093
|
+
onProgress: reportProgress2
|
|
3372
4094
|
});
|
|
3373
4095
|
if (options.json) return { installed: true, ...result };
|
|
3374
4096
|
return renderInstallReport(plan.targetFolderSlug, result);
|
|
3375
4097
|
};
|
|
3376
|
-
var findTargetFolder = (nodes, slug) => {
|
|
3377
|
-
const roots = nodes.length === 1 && nodes[0].children ? nodes[0].children : nodes;
|
|
3378
|
-
return roots.find((node) => node.slug === slug && node.type === "folder");
|
|
3379
|
-
};
|
|
3380
4098
|
var toPlanSummary = (plan) => ({
|
|
3381
4099
|
targetFolder: plan.targetFolderSlug,
|
|
3382
4100
|
package: plan.tree.manifest.name,
|
|
@@ -3412,7 +4130,7 @@ var runPublish = async (client, nodeSlugOrId, options) => {
|
|
|
3412
4130
|
const source = findSourceNode(nodes, nodeSlugOrId);
|
|
3413
4131
|
const warnings = [];
|
|
3414
4132
|
const manifest = await resolveManifest(options.outDir, options.name, source.name, source.slug);
|
|
3415
|
-
|
|
4133
|
+
reportProgress2(`Reading "${source.slug}" \u2026`);
|
|
3416
4134
|
const tree = await collectPackageTree(client, source, {
|
|
3417
4135
|
manifest,
|
|
3418
4136
|
warn: (message) => warnings.push(message),
|
|
@@ -3666,7 +4384,7 @@ async function uploadAsset2(config, opts) {
|
|
|
3666
4384
|
const fileName = opts.fileName ?? basename(filePath);
|
|
3667
4385
|
const mimeType = opts.mimeType ?? guessMimeType2(filePath);
|
|
3668
4386
|
const context = opts.context ?? "record-field";
|
|
3669
|
-
const contentHash = `sha256:${
|
|
4387
|
+
const contentHash = `sha256:${createHash6("sha256").update(file).digest("hex")}`;
|
|
3670
4388
|
const requested = await rawRequest(config, "POST", "/api/v1/assets/upload-urls", {
|
|
3671
4389
|
fileName,
|
|
3672
4390
|
mimeType,
|
|
@@ -3752,6 +4470,7 @@ function runAction(state, handler) {
|
|
|
3752
4470
|
const opts = cmd.optsWithGlobals();
|
|
3753
4471
|
const config = resolveConfig(opts);
|
|
3754
4472
|
state.config = config;
|
|
4473
|
+
assertCredentialNotExpired(config.apiKey);
|
|
3755
4474
|
await maybeAutoRefresh(config.baseUrl, config.apiKey);
|
|
3756
4475
|
const client = createBusabaseClient(config);
|
|
3757
4476
|
const result = await handler(client, opts, config);
|
|
@@ -3763,6 +4482,7 @@ function runArgAction(state, handler) {
|
|
|
3763
4482
|
const opts = cmd.optsWithGlobals();
|
|
3764
4483
|
const config = resolveConfig(opts);
|
|
3765
4484
|
state.config = config;
|
|
4485
|
+
assertCredentialNotExpired(config.apiKey);
|
|
3766
4486
|
await maybeAutoRefresh(config.baseUrl, config.apiKey);
|
|
3767
4487
|
const client = createBusabaseClient(config);
|
|
3768
4488
|
const result = await handler(arg, client, opts, config);
|
|
@@ -3949,7 +4669,7 @@ function buildProgram(state = {}) {
|
|
|
3949
4669
|
runAction(state, (_client, _opts, config) => rawFetch(config, "GET", "/api/v1/openapi.json"))
|
|
3950
4670
|
);
|
|
3951
4671
|
addGlobalFlags(program.command("whoami")).description("Active space, user, and membership").action(runAction(state, (client) => client.auth.verify()));
|
|
3952
|
-
addGlobalFlags(program.command("login")).description("Connect the CLI to Busabase \u2014 Personal/local, Cloud, or self-hosted").option("--
|
|
4672
|
+
addGlobalFlags(program.command("login")).description("Connect the CLI to Busabase \u2014 Personal/local, Cloud, or self-hosted").option("--device-code", "force device authorization and skip the method prompt").option("--oauth", "legacy same-machine OAuth callback flow").option("--no-browser", "print the sign-in URL instead of opening a browser").option("--refresh", "slide the saved OAuth session forward (no browser, no re-login)").addHelpText(
|
|
3953
4673
|
"after",
|
|
3954
4674
|
`
|
|
3955
4675
|
Busabase is an approval-first database and knowledge base for AI agents: agents
|
|
@@ -3958,14 +4678,15 @@ propose changes, humans review and merge what becomes trusted data.
|
|
|
3958
4678
|
Run interactively, login explains the connection choices and writes the selected
|
|
3959
4679
|
Busabase instance to ~/.busabase/.env:
|
|
3960
4680
|
1. Local/Desktop on this computer \u2014 no account, no login
|
|
3961
|
-
2. Busabase Cloud \u2014
|
|
4681
|
+
2. Busabase Cloud \u2014 device sign-in (recommended; works over SSH)
|
|
3962
4682
|
3. Busabase Cloud \u2014 paste an API key
|
|
3963
|
-
4. Self-hosted Busabase \u2014
|
|
4683
|
+
4. Self-hosted Busabase \u2014 device sign-in
|
|
3964
4684
|
5. Self-hosted Busabase \u2014 paste an API key
|
|
3965
4685
|
The flags below skip the menu (handy for scripts / CI):
|
|
3966
4686
|
|
|
3967
4687
|
busabase-cli login # pick from the menu
|
|
3968
|
-
busabase-cli login --
|
|
4688
|
+
busabase-cli login --device-code # Cloud device sign-in
|
|
4689
|
+
busabase-cli login --oauth # legacy same-machine callback
|
|
3969
4690
|
busabase-cli login --api-key sk_\u2026 # Cloud API key (headless/CI)
|
|
3970
4691
|
busabase-cli login --base-url http://localhost:15419 # connect to a local server (no auth)
|
|
3971
4692
|
busabase-cli login --refresh # extend the current session (auto-runs too)`
|
|
@@ -3977,6 +4698,7 @@ The flags below skip the menu (handy for scripts / CI):
|
|
|
3977
4698
|
baseUrl: config.baseUrl,
|
|
3978
4699
|
apiKey: opts.apiKey,
|
|
3979
4700
|
spaceId: config.spaceId,
|
|
4701
|
+
deviceCode: Boolean(opts.deviceCode),
|
|
3980
4702
|
oauth: Boolean(opts.oauth),
|
|
3981
4703
|
browser: opts.browser !== false
|
|
3982
4704
|
});
|
|
@@ -4496,6 +5218,60 @@ Examples:
|
|
|
4496
5218
|
})
|
|
4497
5219
|
)
|
|
4498
5220
|
);
|
|
5221
|
+
addGlobalFlags(program.command("backup")).description("Back up the active space to a full-fidelity .bbdump archive").option("-o, --out-file <file>", "output archive path", "space.bbdump").option(
|
|
5222
|
+
"--no-history",
|
|
5223
|
+
"full-fidelity rows, but omit the history tables (commits, change requests, operations, comments, reviews, audit events)"
|
|
5224
|
+
).option("--state-only", "superseded by `busabase-cli publish` \u2014 see the error text").addHelpText(
|
|
5225
|
+
"after",
|
|
5226
|
+
`
|
|
5227
|
+
A backup is full fidelity: raw rows with their original ids, every doc body, and
|
|
5228
|
+
every attachment's bytes, in one integrity-checked archive. Vault items and
|
|
5229
|
+
webhook signing secrets are never written to it.
|
|
5230
|
+
|
|
5231
|
+
Examples:
|
|
5232
|
+
busabase-cli backup # \u2192 ./space.bbdump
|
|
5233
|
+
busabase-cli backup -o ./backups/acme-2026-07-19.bbdump
|
|
5234
|
+
busabase-cli backup --no-history # smaller: current state only
|
|
5235
|
+
|
|
5236
|
+
To hand content to someone else rather than back it up, use \`busabase-cli publish\`,
|
|
5237
|
+
which writes a readable, diffable package directory you can push to GitHub.`
|
|
5238
|
+
).action(
|
|
5239
|
+
runAction(
|
|
5240
|
+
state,
|
|
5241
|
+
(client, opts, config) => runBackup(client, {
|
|
5242
|
+
outFile: opts.outFile,
|
|
5243
|
+
includeHistory: opts.history !== false,
|
|
5244
|
+
stateOnly: Boolean(opts.stateOnly),
|
|
5245
|
+
json: config.output === "json",
|
|
5246
|
+
baseUrl: config.baseUrl,
|
|
5247
|
+
spaceId: config.spaceId,
|
|
5248
|
+
toolVersion: pkgVersion()
|
|
5249
|
+
})
|
|
5250
|
+
)
|
|
5251
|
+
);
|
|
5252
|
+
addGlobalFlags(program.command("restore")).description("Restore a .bbdump archive into an EMPTY space, preserving original ids").argument("<file>", "path to the .bbdump archive").option("--resume", "resume a previously interrupted restore (not yet implemented)").option("--into-folder <name>", "state-only archives only \u2014 see the error text").addHelpText(
|
|
5253
|
+
"after",
|
|
5254
|
+
`
|
|
5255
|
+
The archive's checksum is verified before a single row is written, and the target
|
|
5256
|
+
space must be EMPTY \u2014 a restore replays original ids, so it cannot merge into a
|
|
5257
|
+
space that already holds content.
|
|
5258
|
+
|
|
5259
|
+
Examples:
|
|
5260
|
+
busabase-cli restore ./space.bbdump
|
|
5261
|
+
busabase-cli restore ./backups/acme-2026-07-19.bbdump --space-id spc_123
|
|
5262
|
+
|
|
5263
|
+
To add content to a space that is already in use, use \`busabase-cli install\` instead.`
|
|
5264
|
+
).action(
|
|
5265
|
+
runArgAction(
|
|
5266
|
+
state,
|
|
5267
|
+
(file, client, opts, config) => runRestore(client, {
|
|
5268
|
+
file,
|
|
5269
|
+
resume: Boolean(opts.resume),
|
|
5270
|
+
intoFolder: opts.intoFolder,
|
|
5271
|
+
json: config.output === "json"
|
|
5272
|
+
})
|
|
5273
|
+
)
|
|
5274
|
+
);
|
|
4499
5275
|
addGlobalFlags(program.command("api")).description("Raw request to any /api/v1 endpoint").addOption(
|
|
4500
5276
|
new Option("--method <get|post|put|delete>", "HTTP method").choices(["get", "post", "put", "delete"]).makeOptionMandatory()
|
|
4501
5277
|
).requiredOption("--path <p>", "path under /api/v1, e.g. /bases").option("--query <k=v...>", "query-string param, repeatable").option("--body-json <json>", "JSON request body").action(
|