busa-cli 0.9.4 → 0.9.6

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.
@@ -124,6 +124,7 @@ import { createHash as createHash2 } from "crypto";
124
124
  import { readFileSync as readFileSync2 } from "fs";
125
125
  import { basename, extname } from "path";
126
126
  import {
127
+ CREATABLE_NODE_TYPES,
127
128
  cloudContract,
128
129
  createBusabaseClient,
129
130
  DEFAULT_BASE_URL as DEFAULT_BASE_URL2,
@@ -363,9 +364,9 @@ async function oauthLogin(baseUrl, useBrowser) {
363
364
  throw new Error(`Token exchange failed (HTTP ${tokenRes.status})${text ? `: ${text}` : ""}`);
364
365
  }
365
366
  const payload = await tokenRes.json();
366
- const token = payload.token ?? payload.accessToken;
367
- if (!token) throw new Error("Token exchange returned no session token.");
368
- return { token, expiresAt: payload.expiresAt };
367
+ const token = payload.apiKey ?? payload.token ?? payload.accessToken;
368
+ if (!token) throw new Error("Token exchange returned no credential.");
369
+ return { token, expiresAt: payload.expiresAt ?? void 0 };
369
370
  }
370
371
  async function refreshToken(baseUrl, token) {
371
372
  const res = await fetch(`${baseUrl}/api/oauth/refresh`, {
@@ -889,8 +890,10 @@ var GENERATED_SKIP = /* @__PURE__ */ new Set([
889
890
  // `whoami`
890
891
  "records.search",
891
892
  // `records by-field-text`
892
- "records.listChangeRequests"
893
+ "records.listChangeRequests",
893
894
  // `records change-requests`
895
+ "airapps.listFiles"
896
+ // `airapps files`
894
897
  ]);
895
898
  function registerGeneratedCommands(program, state) {
896
899
  const getGroup = (displayName, tag) => program.commands.find((c2) => c2.name() === displayName) ?? program.command(displayName).description(tag ?? `${displayName} endpoints`);
@@ -1052,7 +1055,7 @@ The flags below skip the menu (handy for scripts / CI):
1052
1055
  const nodes = program.command("nodes").description("Workspace node tree");
1053
1056
  addGlobalFlags(nodes.command("list")).description("Workspace node tree").action(runAction(state, (client) => client.nodes.list()));
1054
1057
  addGlobalFlags(nodes.command("create-change-request")).description("Propose a new node via a Change Request").addOption(
1055
- new Option("--type <folder|base|skill|drive|file|doc>", "node type").choices(["folder", "base", "skill", "drive", "file", "doc"]).makeOptionMandatory()
1058
+ new Option(`--type <${CREATABLE_NODE_TYPES.join("|")}>`, "node type").choices(CREATABLE_NODE_TYPES).makeOptionMandatory()
1056
1059
  ).requiredOption("--slug <slug>", "node slug").requiredOption("--name <name>", "node name").option("--description <text>", "optional node description").option("--parent-node-id <id>", "parent folder node id; omit for root").option("--message <text>", "reviewer-facing Change Request message").option("--submitted-by <name>", "producer label").option("--field <slug:name:type...>", "base field, repeatable (for --type base)").option("--fields-json <json|@file>", "base fields as JSON array (for --type base)").option("--asset-id <id>", "backing Asset id (required for --type file)").addHelpText(
1057
1060
  "after",
1058
1061
  `
@@ -1089,6 +1092,28 @@ Examples:
1089
1092
  });
1090
1093
  })
1091
1094
  );
1095
+ addGlobalFlags(nodes.command("move")).description("Move or reorder a node (applied immediately, no review needed)").requiredOption("--node-id <id>", "node id to move").option("--parent-node-id <id>", "new parent folder node id; omit to keep the current parent").option(
1096
+ "--position <n>",
1097
+ "new position among the target parent's children",
1098
+ (v) => Number.parseInt(v, 10)
1099
+ ).option("--message <text>", "Change Request message").option("--submitted-by <name>", "producer label").addHelpText(
1100
+ "after",
1101
+ `
1102
+ Examples:
1103
+ busabase-cli nodes move --node-id nod_123 --position 0
1104
+ busabase-cli nodes move --node-id nod_123 --parent-node-id nod_456 --position 2`
1105
+ ).action(
1106
+ runAction(
1107
+ state,
1108
+ (client, opts) => client.nodes.move({
1109
+ nodeId: opts.nodeId,
1110
+ parentNodeId: opts.parentNodeId,
1111
+ position: opts.position,
1112
+ message: opts.message,
1113
+ submittedBy: opts.submittedBy
1114
+ })
1115
+ )
1116
+ );
1092
1117
  const bases = program.command("bases").description("Bases (structured tables)");
1093
1118
  addGlobalFlags(bases.command("list")).description("List Bases in the active space").action(runAction(state, (client) => client.bases.list()));
1094
1119
  addGlobalFlags(bases.command("get")).description("Get one Base by slug").requiredOption("--slug <slug>", "Base slug").action(
@@ -1190,6 +1215,95 @@ Examples:
1190
1215
  })
1191
1216
  )
1192
1217
  );
1218
+ const airapps = program.command("airapps").description("AirApps (sandboxed mini-apps)");
1219
+ addGlobalFlags(airapps.command("list")).description("List AirApps in the active space").action(runAction(state, (client) => client.airapps.list()));
1220
+ addGlobalFlags(airapps.command("get")).description("Get one AirApp by node id").requiredOption("--node-id <id>", "AirApp node id").action(
1221
+ runAction(state, (client, opts) => client.airapps.get({ nodeId: opts.nodeId }))
1222
+ );
1223
+ addGlobalFlags(airapps.command("create")).description("Create an AirApp \u2014 review-first by default: returns a pending Change Request").requiredOption("--slug <slug>", "AirApp slug").requiredOption("--name <name>", "AirApp name").option("--description <text>", "optional description").option("--parent-node-id <id>", "parent folder node id; omit for root").addOption(
1224
+ new Option(
1225
+ "--visibility <private|workspace|public>",
1226
+ "AirApp visibility (default private)"
1227
+ ).choices(["private", "workspace", "public"])
1228
+ ).option("--version <semver>", "AirApp version (default 0.1.0)").option(
1229
+ "--files-json <json|@file>",
1230
+ 'files as a JSON array, or @file.json \u2014 text files {"path","content","mimeType?"} or asset-backed files {"path","assetId","displayName?","mimeType?"}'
1231
+ ).addOption(
1232
+ new Option(
1233
+ "--merge-mode <merge|replace>",
1234
+ "how --files-json combines with the default scaffold (default merge)"
1235
+ ).choices(["merge", "replace"])
1236
+ ).option(
1237
+ "--auto-merge",
1238
+ "skip review and create the AirApp immediately (default: propose a pending Change Request)"
1239
+ ).addHelpText(
1240
+ "after",
1241
+ `
1242
+ mergeMode explains how --files-json combines with the AirApp's default scaffold:
1243
+ merge (default) \u2014 your files are layered on top of the default Hono-template
1244
+ scaffold by path; supply just a few files (e.g. one custom
1245
+ route) and still get the rest of the scaffold for any path
1246
+ you didn't provide yourself.
1247
+ replace \u2014 your files replace the scaffold entirely; use this when
1248
+ handing over a complete, self-contained project (e.g. a Vite
1249
+ app) so you don't end up with stray unrelated default files
1250
+ mixed in.
1251
+
1252
+ Examples:
1253
+ busabase-cli airapps create --slug hello-app --name "Hello App" --files-json @files.json
1254
+ busabase-cli airapps create --slug vite-app --name "Vite App" --files-json @files.json --merge-mode replace
1255
+ busabase-cli airapps create --slug hello-app --name "Hello App" --files-json @files.json --auto-merge # skip review, create immediately`
1256
+ ).action(
1257
+ runAction(
1258
+ state,
1259
+ (client, opts) => client.airapps.create({
1260
+ slug: opts.slug,
1261
+ name: opts.name,
1262
+ description: opts.description,
1263
+ parentNodeId: opts.parentNodeId,
1264
+ visibility: opts.visibility,
1265
+ version: opts.version,
1266
+ files: opts.filesJson ? parseJsonValue(opts.filesJson, "files-json") : void 0,
1267
+ mergeMode: opts.mergeMode,
1268
+ ...opts.autoMerge ? { autoMerge: true } : {}
1269
+ })
1270
+ )
1271
+ );
1272
+ addGlobalFlags(airapps.command("files")).description("List an AirApp's files").requiredOption("--node-id <id>", "AirApp node id").action(
1273
+ runAction(
1274
+ state,
1275
+ (client, opts) => client.airapps.listFiles({ nodeId: opts.nodeId })
1276
+ )
1277
+ );
1278
+ addGlobalFlags(airapps.command("read-file")).description("Read one AirApp file's content").requiredOption("--node-id <id>", "AirApp node id").requiredOption("--path <path>", "file path within the AirApp").action(
1279
+ runAction(
1280
+ state,
1281
+ (client, opts) => client.airapps.readFile({ nodeId: opts.nodeId, filePath: opts.path })
1282
+ )
1283
+ );
1284
+ addGlobalFlags(airapps.command("create-change-request")).description("Propose file changes to an existing AirApp via a Change Request").requiredOption("--node-id <id>", "AirApp node id").requiredOption(
1285
+ "--operations-json <json|@file>",
1286
+ 'file operations as a JSON array, or @file.json \u2014 "create"/"update" (text {"kind","path","content","mimeType?"} or asset-backed {"kind","path","assetId","displayName?","mimeType?"}), "delete" {"kind","path"}, or "metadata_update" {"kind","metadata":{"entryFile?","visibility?","version?"}}'
1287
+ ).option("--message <text>", "reviewer-facing Change Request message").option("--submitted-by <name>", "producer label").addHelpText(
1288
+ "after",
1289
+ `
1290
+ Examples:
1291
+ busabase-cli airapps create-change-request --node-id nod_123 --operations-json @operations.json
1292
+ busabase-cli airapps create-change-request --node-id nod_123 --operations-json '[{"kind":"update","path":"index.js","content":"..."}]' --message "Fix handler bug"`
1293
+ ).action(
1294
+ runAction(
1295
+ state,
1296
+ (client, opts) => client.airapps.createChangeRequest({
1297
+ nodeId: opts.nodeId,
1298
+ message: opts.message,
1299
+ submittedBy: opts.submittedBy,
1300
+ operations: parseJsonValue(
1301
+ opts.operationsJson,
1302
+ "operations-json"
1303
+ )
1304
+ })
1305
+ )
1306
+ );
1193
1307
  const records = program.command("records").description("Records");
1194
1308
  addGlobalFlags(records.command("list")).description("List records").option("--limit <n>", "max results (1-100)", parseLimit).option("--base-id <id>", "restrict to one Base").option("--cursor <cursor>", "opaque nextCursor from a previous page").action(
1195
1309
  runAction(state, (_client, opts, config) => {
@@ -1261,16 +1375,51 @@ Examples:
1261
1375
  (client, opts) => client.changeRequests.merge({ changeRequestId: opts.changeRequestId })
1262
1376
  )
1263
1377
  );
1264
- addGlobalFlags(program.command("search")).description("Full-text search").requiredOption("--query <q>", "search query").option("--limit <n>", "max results", parseNum).option("--offset <n>", "results offset", parseNum).action(
1378
+ addGlobalFlags(program.command("search")).description("Full-text search").requiredOption("--query <q>", "search query").option("--limit <n>", "max results", parseNum).option("--offset <n>", "results offset", parseNum).option(
1379
+ "--sources <sources...>",
1380
+ 'restrict which content to search: "records", "files", and/or "names" (default: all three)'
1381
+ ).action(
1265
1382
  runAction(
1266
1383
  state,
1267
1384
  (client, opts) => client.search({
1268
1385
  query: opts.query,
1269
1386
  limit: opts.limit,
1270
- offset: opts.offset
1387
+ offset: opts.offset,
1388
+ sources: opts.sources
1271
1389
  })
1272
1390
  )
1273
1391
  );
1392
+ addGlobalFlags(program.command("grep")).description(
1393
+ "Search files, Docs, and Base records with one pattern (unified grep; use `assets grep` for files-only)"
1394
+ ).requiredOption("--pattern <regex>", "literal or regex pattern").option("--flags <flags>", 'RegExp flags, e.g. "i" for case-insensitive').option(
1395
+ "--sources <sources...>",
1396
+ 'sources to scan: "files", "docs", and/or "records" (default: all three)'
1397
+ ).option("--asset-ids <ids...>", "files scope: specific asset ids").option("--drive-path <path>", "files scope: Drive/Skill mounted path prefix").option("--mime-types <types...>", "files scope: MIME types").option("--node-ids <ids...>", "docs scope: specific Doc node ids").option("--base-ids <ids...>", "records scope: specific Base ids").option("--base-slugs <slugs...>", "records scope: specific Base slugs").option(
1398
+ "--max-matches <n>",
1399
+ "max matches, shared across sources (default 100, cap 1000)",
1400
+ parsePositiveInt
1401
+ ).option("--context-lines <n>", "lines of before/after context (default 0, cap 10)", parseNum).action(
1402
+ runAction(state, (client, opts) => {
1403
+ const filesScope = opts.assetIds || opts.drivePath || opts.mimeTypes ? {
1404
+ assetIds: opts.assetIds,
1405
+ drivePath: opts.drivePath,
1406
+ mimeTypes: opts.mimeTypes
1407
+ } : void 0;
1408
+ const docsScope = opts.nodeIds ? { nodeIds: opts.nodeIds } : void 0;
1409
+ const recordsScope = opts.baseIds || opts.baseSlugs ? {
1410
+ baseIds: opts.baseIds,
1411
+ baseSlugs: opts.baseSlugs
1412
+ } : void 0;
1413
+ return client.grep({
1414
+ pattern: opts.pattern,
1415
+ flags: opts.flags,
1416
+ sources: opts.sources,
1417
+ scope: filesScope || docsScope || recordsScope ? { files: filesScope, docs: docsScope, records: recordsScope } : void 0,
1418
+ maxMatches: opts.maxMatches,
1419
+ contextLines: opts.contextLines
1420
+ });
1421
+ })
1422
+ );
1274
1423
  const addUploadCommand = (parent, hidden = false) => addGlobalFlags(parent.command("upload", { noHelp: hidden })).description("Upload a file and print an asset ref for record fields").requiredOption("--file <path>", "local file to upload").option("--file-name <name>", "stored file name (default: basename of --file)").option("--mime-type <mime>", "MIME type (default: inferred from extension)").option("--context <value>", "upload context (default record-field)").addHelpText(
1275
1424
  "after",
1276
1425
  `
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-5P2ZNCL5.js";
4
+ } from "./chunk-5ZYF74IE.js";
5
5
 
6
6
  // src/cli.ts
7
7
  process.exit(await runCli(process.argv.slice(2)));
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  HELP,
3
3
  render,
4
4
  runCli
5
- } from "./chunk-5P2ZNCL5.js";
5
+ } from "./chunk-5ZYF74IE.js";
6
6
 
7
7
  // src/index.ts
8
8
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busa-cli",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Command-line client for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server`. Short-name alias for busabase-cli.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-cli",
@@ -31,7 +31,7 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "busabase-sdk": "0.9.4",
34
+ "busabase-sdk": "0.9.6",
35
35
  "commander": "^14.0.0"
36
36
  },
37
37
  "engines": {