busa-cli 0.9.3 → 0.9.4

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];
@@ -1066,12 +1099,16 @@ Examples:
1066
1099
  return found;
1067
1100
  })
1068
1101
  );
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(
1102
+ 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(
1103
+ "--auto-merge",
1104
+ "skip review and create the Base immediately (default: propose a pending Change Request)"
1105
+ ).addHelpText(
1070
1106
  "after",
1071
1107
  `
1072
1108
  Examples:
1073
1109
  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`
1110
+ busabase-cli bases create --slug products --name "\u4EA7\u54C1\u76EE\u5F55 Products" --fields-json @fields.json
1111
+ busabase-cli bases create --slug products --name "Products" --auto-merge # skip review, create immediately`
1075
1112
  ).action(
1076
1113
  runAction(
1077
1114
  state,
@@ -1080,7 +1117,8 @@ Examples:
1080
1117
  name: opts.name,
1081
1118
  description: opts.description,
1082
1119
  parentNodeId: opts.parentNodeId,
1083
- fields: parseFieldDefinitions(opts)
1120
+ fields: parseFieldDefinitions(opts),
1121
+ ...opts.autoMerge ? { autoMerge: true } : {}
1084
1122
  })
1085
1123
  )
1086
1124
  );
@@ -1241,7 +1279,42 @@ Example:
1241
1279
 
1242
1280
  Use the JSON output directly in an attachment field value, e.g. {"cover_image":[<output>]}.`
1243
1281
  ).action(runAction(state, (_client, opts, config) => uploadAsset(config, opts)));
1244
- addUploadCommand(program.command("assets").description("Assets"));
1282
+ const assetsCommand = program.command("assets").description("Assets");
1283
+ addUploadCommand(assetsCommand);
1284
+ 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(
1285
+ "after",
1286
+ `
1287
+ Examples:
1288
+ busabase-cli assets put-text --asset-id ast_123 --file ./extracted.txt
1289
+ busabase-cli assets put-text --asset-id ast_123 --text "plain text"
1290
+ busabase-cli assets put-text --asset-id ast_123 --none`
1291
+ ).action(runAction(state, (client, opts) => putTextCommand(client, opts)));
1292
+ 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(
1293
+ runAction(
1294
+ state,
1295
+ (client, opts) => client.assets.grep({
1296
+ pattern: opts.pattern,
1297
+ flags: opts.flags,
1298
+ scope: opts.assetIds || opts.drivePath || opts.mimeTypes ? {
1299
+ assetIds: opts.assetIds,
1300
+ drivePath: opts.drivePath,
1301
+ mimeTypes: opts.mimeTypes
1302
+ } : void 0,
1303
+ maxMatches: opts.maxMatches,
1304
+ contextLines: opts.contextLines
1305
+ })
1306
+ )
1307
+ );
1308
+ 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(
1309
+ runAction(
1310
+ state,
1311
+ (client, opts) => client.assets.readTextLines({
1312
+ assetId: opts.assetId,
1313
+ startLine: opts.startLine,
1314
+ endLine: opts.endLine
1315
+ })
1316
+ )
1317
+ );
1245
1318
  addGlobalFlags(program.command("api")).description("Raw request to any /api/v1 endpoint").addOption(
1246
1319
  new Option("--method <get|post|put|delete>", "HTTP method").choices(["get", "post", "put", "delete"]).makeOptionMandatory()
1247
1320
  ).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-5P2ZNCL5.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-5P2ZNCL5.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.4",
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.4",
35
35
  "commander": "^14.0.0"
36
36
  },
37
37
  "engines": {