busa-cli 0.9.3 → 0.9.5

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.
@@ -792,6 +792,34 @@ async function uploadAsset(config, opts) {
792
792
  size: file.byteLength
793
793
  };
794
794
  }
795
+ var INLINE_TEXT_MAX_BYTES = 1024 * 1024;
796
+ async function putTextCommand(client, opts) {
797
+ const assetId = opts.assetId;
798
+ const text = opts.file ? readFileSync2(opts.file, "utf8") : opts.text;
799
+ if (opts.none) {
800
+ return client.assets.putText({ assetId, none: true });
801
+ }
802
+ if (text === void 0) {
803
+ throw new Error("Provide --text <string>, --file <path>, or --none.");
804
+ }
805
+ const sizeBytes = Buffer.byteLength(text, "utf8");
806
+ if (sizeBytes <= INLINE_TEXT_MAX_BYTES) {
807
+ return client.assets.putText({ assetId, text });
808
+ }
809
+ const upload = await client.assets.createTextUploadUrl({ assetId, sizeBytes });
810
+ const uploadResponse = await fetch(upload.uploadUrl, {
811
+ method: "PUT",
812
+ headers: { "content-type": "text/plain; charset=utf-8" },
813
+ body: text
814
+ });
815
+ if (!uploadResponse.ok) {
816
+ const body = await uploadResponse.text();
817
+ throw new Error(
818
+ `Text byte upload failed (${uploadResponse.status} ${uploadResponse.statusText})${body ? `: ${body}` : ""}`
819
+ );
820
+ }
821
+ return client.assets.putText({ assetId, storageKey: upload.storageKey });
822
+ }
795
823
  function runAction(state, handler) {
796
824
  return async (_opts, cmd) => {
797
825
  const opts = cmd.optsWithGlobals();
@@ -840,9 +868,12 @@ function classifyKind(inner) {
840
868
  return { kind: "json" };
841
869
  }
842
870
  function inputFields(inputSchema) {
871
+ if (inputSchema === void 0) return [];
843
872
  const { inner } = unwrapSchema(inputSchema);
844
873
  const shape = inner?.shape ?? (inner?.def?.type === "object" ? inner.def.shape : void 0);
845
- if (!shape || typeof shape !== "object") return [];
874
+ if (!shape || typeof shape !== "object") {
875
+ return [{ key: "input", kind: "json", required: true, root: true }];
876
+ }
846
877
  const fields = [];
847
878
  for (const [key, sub] of Object.entries(shape)) {
848
879
  const { inner: unwrapped, optional } = unwrapSchema(sub);
@@ -900,13 +931,15 @@ function registerGeneratedCommands(program, state) {
900
931
  OpenAPI: ${route.method} ${route.path}`);
901
932
  leaf.action(
902
933
  runAction(state, (client, opts) => {
903
- const input = {};
934
+ let input = {};
904
935
  for (const f of fields) {
905
936
  if (GLOBAL_LONG_FLAGS.has(`--${kebab(f.key)}`)) continue;
906
937
  const optKey = f.kind === "json" ? `${f.key}Json` : f.key;
907
938
  const value = opts[optKey];
908
939
  if (value === void 0) continue;
909
- input[f.key] = f.kind === "json" ? parseJsonValue(value, `${kebab(f.key)}-json`) : value;
940
+ const parsed = f.kind === "json" ? parseJsonValue(value, `${kebab(f.key)}-json`) : value;
941
+ if (f.root) input = parsed;
942
+ else input[f.key] = parsed;
910
943
  }
911
944
  let target = client;
912
945
  for (const segment of navPath) target = target[segment];
@@ -1056,6 +1089,28 @@ Examples:
1056
1089
  });
1057
1090
  })
1058
1091
  );
1092
+ 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(
1093
+ "--position <n>",
1094
+ "new position among the target parent's children",
1095
+ (v) => Number.parseInt(v, 10)
1096
+ ).option("--message <text>", "Change Request message").option("--submitted-by <name>", "producer label").addHelpText(
1097
+ "after",
1098
+ `
1099
+ Examples:
1100
+ busabase-cli nodes move --node-id nod_123 --position 0
1101
+ busabase-cli nodes move --node-id nod_123 --parent-node-id nod_456 --position 2`
1102
+ ).action(
1103
+ runAction(
1104
+ state,
1105
+ (client, opts) => client.nodes.move({
1106
+ nodeId: opts.nodeId,
1107
+ parentNodeId: opts.parentNodeId,
1108
+ position: opts.position,
1109
+ message: opts.message,
1110
+ submittedBy: opts.submittedBy
1111
+ })
1112
+ )
1113
+ );
1059
1114
  const bases = program.command("bases").description("Bases (structured tables)");
1060
1115
  addGlobalFlags(bases.command("list")).description("List Bases in the active space").action(runAction(state, (client) => client.bases.list()));
1061
1116
  addGlobalFlags(bases.command("get")).description("Get one Base by slug").requiredOption("--slug <slug>", "Base slug").action(
@@ -1066,12 +1121,16 @@ Examples:
1066
1121
  return found;
1067
1122
  })
1068
1123
  );
1069
- addGlobalFlags(bases.command("create")).description("Create a Base").requiredOption("--slug <slug>", "Base slug").requiredOption("--name <name>", "Base name").option("--field <slug:name:type...>", "field definition, repeatable").option("--fields-json <json|@file>", "field definitions as JSON array").option("--description <text>", "optional description").option("--parent-node-id <id>", "parent folder node id; omit for root").addHelpText(
1124
+ addGlobalFlags(bases.command("create")).description("Create a Base \u2014 review-first by default: returns a pending Change Request").requiredOption("--slug <slug>", "Base slug").requiredOption("--name <name>", "Base name").option("--field <slug:name:type...>", "field definition, repeatable").option("--fields-json <json|@file>", "field definitions as JSON array").option("--description <text>", "optional description").option("--parent-node-id <id>", "parent folder node id; omit for root").option(
1125
+ "--auto-merge",
1126
+ "skip review and create the Base immediately (default: propose a pending Change Request)"
1127
+ ).addHelpText(
1070
1128
  "after",
1071
1129
  `
1072
1130
  Examples:
1073
1131
  busabase-cli bases create --slug products --name "\u4EA7\u54C1\u76EE\u5F55 Products" --field product_name:"Product Name":text
1074
- busabase-cli bases create --slug products --name "\u4EA7\u54C1\u76EE\u5F55 Products" --fields-json @fields.json`
1132
+ busabase-cli bases create --slug products --name "\u4EA7\u54C1\u76EE\u5F55 Products" --fields-json @fields.json
1133
+ busabase-cli bases create --slug products --name "Products" --auto-merge # skip review, create immediately`
1075
1134
  ).action(
1076
1135
  runAction(
1077
1136
  state,
@@ -1080,7 +1139,8 @@ Examples:
1080
1139
  name: opts.name,
1081
1140
  description: opts.description,
1082
1141
  parentNodeId: opts.parentNodeId,
1083
- fields: parseFieldDefinitions(opts)
1142
+ fields: parseFieldDefinitions(opts),
1143
+ ...opts.autoMerge ? { autoMerge: true } : {}
1084
1144
  })
1085
1145
  )
1086
1146
  );
@@ -1233,6 +1293,37 @@ Examples:
1233
1293
  })
1234
1294
  )
1235
1295
  );
1296
+ addGlobalFlags(program.command("grep")).description(
1297
+ "Search files, Docs, and Base records with one pattern (unified grep; use `assets grep` for files-only)"
1298
+ ).requiredOption("--pattern <regex>", "literal or regex pattern").option("--flags <flags>", 'RegExp flags, e.g. "i" for case-insensitive').option(
1299
+ "--sources <sources...>",
1300
+ 'sources to scan: "files", "docs", and/or "records" (default: all three)'
1301
+ ).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(
1302
+ "--max-matches <n>",
1303
+ "max matches, shared across sources (default 100, cap 1000)",
1304
+ parsePositiveInt
1305
+ ).option("--context-lines <n>", "lines of before/after context (default 0, cap 10)", parseNum).action(
1306
+ runAction(state, (client, opts) => {
1307
+ const filesScope = opts.assetIds || opts.drivePath || opts.mimeTypes ? {
1308
+ assetIds: opts.assetIds,
1309
+ drivePath: opts.drivePath,
1310
+ mimeTypes: opts.mimeTypes
1311
+ } : void 0;
1312
+ const docsScope = opts.nodeIds ? { nodeIds: opts.nodeIds } : void 0;
1313
+ const recordsScope = opts.baseIds || opts.baseSlugs ? {
1314
+ baseIds: opts.baseIds,
1315
+ baseSlugs: opts.baseSlugs
1316
+ } : void 0;
1317
+ return client.grep({
1318
+ pattern: opts.pattern,
1319
+ flags: opts.flags,
1320
+ sources: opts.sources,
1321
+ scope: filesScope || docsScope || recordsScope ? { files: filesScope, docs: docsScope, records: recordsScope } : void 0,
1322
+ maxMatches: opts.maxMatches,
1323
+ contextLines: opts.contextLines
1324
+ });
1325
+ })
1326
+ );
1236
1327
  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(
1237
1328
  "after",
1238
1329
  `
@@ -1241,7 +1332,42 @@ Example:
1241
1332
 
1242
1333
  Use the JSON output directly in an attachment field value, e.g. {"cover_image":[<output>]}.`
1243
1334
  ).action(runAction(state, (_client, opts, config) => uploadAsset(config, opts)));
1244
- addUploadCommand(program.command("assets").description("Assets"));
1335
+ const assetsCommand = program.command("assets").description("Assets");
1336
+ addUploadCommand(assetsCommand);
1337
+ addGlobalFlags(assetsCommand.command("put-text")).description("Write (or mark none) an asset's Drive Grep Retrieval text slot").requiredOption("--asset-id <id>", "asset id").option("--text <string>", "inline text (\u22641MB)").option("--file <path>", "read text from a local file instead of --text").option("--none", "mark as having no extractable text (e.g. a scanned, image-only PDF)").addHelpText(
1338
+ "after",
1339
+ `
1340
+ Examples:
1341
+ busabase-cli assets put-text --asset-id ast_123 --file ./extracted.txt
1342
+ busabase-cli assets put-text --asset-id ast_123 --text "plain text"
1343
+ busabase-cli assets put-text --asset-id ast_123 --none`
1344
+ ).action(runAction(state, (client, opts) => putTextCommand(client, opts)));
1345
+ addGlobalFlags(assetsCommand.command("grep")).description("Search every text-bearing asset in scope").requiredOption("--pattern <regex>", "literal or regex pattern").option("--flags <flags>", 'RegExp flags, e.g. "i" for case-insensitive').option("--asset-ids <ids...>", "scope: specific asset ids").option("--drive-path <path>", "scope: Drive/Skill mounted path prefix").option("--mime-types <types...>", "scope: MIME types").option("--max-matches <n>", "max matches (default 100, cap 1000)", parsePositiveInt).option("--context-lines <n>", "lines of before/after context (default 0, cap 10)", parseNum).action(
1346
+ runAction(
1347
+ state,
1348
+ (client, opts) => client.assets.grep({
1349
+ pattern: opts.pattern,
1350
+ flags: opts.flags,
1351
+ scope: opts.assetIds || opts.drivePath || opts.mimeTypes ? {
1352
+ assetIds: opts.assetIds,
1353
+ drivePath: opts.drivePath,
1354
+ mimeTypes: opts.mimeTypes
1355
+ } : void 0,
1356
+ maxMatches: opts.maxMatches,
1357
+ contextLines: opts.contextLines
1358
+ })
1359
+ )
1360
+ );
1361
+ addGlobalFlags(assetsCommand.command("read-lines")).description("Read an exact line range from an asset's text (range capped at 2000 lines)").requiredOption("--asset-id <id>", "asset id").requiredOption("--start-line <n>", "first line (1-based)", parsePositiveInt).requiredOption("--end-line <n>", "last line (1-based)", parsePositiveInt).action(
1362
+ runAction(
1363
+ state,
1364
+ (client, opts) => client.assets.readTextLines({
1365
+ assetId: opts.assetId,
1366
+ startLine: opts.startLine,
1367
+ endLine: opts.endLine
1368
+ })
1369
+ )
1370
+ );
1245
1371
  addGlobalFlags(program.command("api")).description("Raw request to any /api/v1 endpoint").addOption(
1246
1372
  new Option("--method <get|post|put|delete>", "HTTP method").choices(["get", "post", "put", "delete"]).makeOptionMandatory()
1247
1373
  ).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(
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-C5BGRNMU.js";
4
+ } from "./chunk-N6NMNVQT.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-C5BGRNMU.js";
5
+ } from "./chunk-N6NMNVQT.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.3",
3
+ "version": "0.9.5",
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.3",
34
+ "busabase-sdk": "0.9.5",
35
35
  "commander": "^14.0.0"
36
36
  },
37
37
  "engines": {