@arbidocs/cli 0.3.40 β†’ 0.3.42

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.42
4
+
5
+ [compare changes](https://github.com/arbicity/ARBI-frontend/compare/v0.3.40...HEAD)
6
+
7
+ ### πŸš€ Enhancements
8
+
9
+ - **cli:** Workspace improvements β€” --json, workspace current, delete + copy fixes ([#623](https://github.com/arbicity/ARBI-frontend/pull/623))
10
+
3
11
  ## v0.3.40
4
12
 
5
13
  [compare changes](https://github.com/arbicity/ARBI-frontend/compare/v0.3.39...HEAD)
package/SKILL.md CHANGED
@@ -32,12 +32,12 @@ Documents, conversations, and tags are scoped per workspace. An active workspace
32
32
  Common moves:
33
33
 
34
34
  - List: `arbi workspaces` (human) Β· `arbi workspaces --json` (script) Β· `arbi workspaces --ids`
35
- - Active workspace ID: `arbi workspace current` (prints the ID, empty string if none)
36
- - Pick/switch: `arbi workspace select <id>` β€” clears the chat session; uploaded docs in other workspaces are unaffected
37
- - Create: `arbi workspace create "My WS"` β€” does NOT auto-select, follow with `workspace select`
38
- - Rename / edit: `arbi workspace update '{"name":"New Name"}'`
39
- - Delete: `arbi workspace delete [id]` β€” deletes immediately (no confirmation prompt); if you delete the active workspace, the selection is cleared
40
- - Share: `arbi workspace add-user <email> -r <role>` where role is `owner|collaborator|guest` (may be plan-limited)
35
+ - Active workspace ID: `arbi workspace current` (prints the ID, empty string if none) Β· `arbi workspace current --json` for full details
36
+ - Pick/switch: `arbi workspace select <id-or-name>` β€” accepts a workspace ID or a unique (case-insensitive) name; clears the chat session; uploaded docs in other workspaces are unaffected
37
+ - Create: `arbi workspace create "My WS"` β€” does NOT auto-select. Use `--select` to create + select in one go, `--json` for scripts
38
+ - Rename / edit: `arbi workspace update '{"name":"New Name"}'` (add `--json` for scripts)
39
+ - Delete: `arbi workspace delete [id-or-name]` β€” prompts for confirmation in interactive shells. In non-TTY shells you **must** pass `-y/--yes` (otherwise the command refuses to delete). If you delete the active workspace, the selection is cleared. `--json` outputs `{id, deleted}`
40
+ - Share: `arbi workspace add-user <email> -r <role>` where role is `owner|collaborator|guest` (plan-limited β€” free tier has 0 collaborator slots)
41
41
  - List members: `arbi workspace users [--json]`
42
42
  - Remove member: `arbi workspace remove-user <user-id>`
43
43
  - Copy docs between workspaces: `arbi workspace copy <target-ws-id> <doc-id>...` β€” only copies documents that have finished indexing; partial/failed copies exit non-zero
@@ -45,8 +45,10 @@ Common moves:
45
45
  Scripting notes:
46
46
 
47
47
  - The table output of `arbi workspaces` is NOT reliably parseable (names contain spaces, status cells may contain glyphs). Use `--json` or `--ids`.
48
+ - Switch-by-name in one line: `arbi workspace select "My WS"`. If the name is ambiguous, the CLI lists the matches and exits non-zero β€” use the ID.
48
49
  - Sessions persist between CLI invocations via the config file (default `~/.arbi/`, override with `ARBI_CONFIG_DIR`).
49
50
  - Per-agent isolation: run agents with their own `ARBI_CONFIG_DIR=<path>` (or `ARBI_ID=<name>`) to avoid stomping each other's active workspace.
51
+ - Agent accounts typically have 0 collaborator slots, so `workspace add-user` errors with "Collaborator limit reached" unless the plan is upgraded.
50
52
 
51
53
  ## Tips
52
54
 
@@ -54,7 +56,7 @@ Scripting notes:
54
56
  - If a command fails with "fetch failed", check connectivity with `arbi health`.
55
57
  - Use `arbi ask -n` to start a fresh conversation (forget prior context).
56
58
  - Use `arbi ask -v` for verbose output showing retrieval steps.
57
- - Use `arbi status --json` and `arbi workspace current` to read CLI state from scripts.
59
+ - Use `arbi status --json` and `arbi workspace current` to read CLI state from scripts. `arbi workspaces --json` lists every workspace with `is_selected` marked.
58
60
 
59
61
  ## For Automations / AI Agents
60
62
 
@@ -76,3 +78,19 @@ Scripting notes:
76
78
  - **No color leakage**: chalk auto-disables ANSI codes when stdout is not a TTY, so `arbi docs --json | jq`, `arbi workspaces --json | jq`, etc. work cleanly. Status messages and spinners go to stderr; stdout is safe to pipe.
77
79
  - **Typo suggestions**: `arbi updat` β†’ `error: unknown command 'updat' (Did you mean update?)` (exit 1). Works for top-level commands and nested subcommands.
78
80
  - **Completion**: `arbi completion install` (no `bash`/`zsh` arg β€” auto-detected from `$SHELL`).
81
+
82
+ ## Document Lifecycle (for agents)
83
+
84
+ All document list/search/meta commands support machine-readable output. Prefer `--json`.
85
+
86
+ - **Upload**: `arbi add <paths...> --json --no-watch` β€” prints a single JSON summary line with `docIds` and `skippedDetails`. Progress and reconnect noise are routed to stderr; stdout stays clean when `--json` is set.
87
+ - **List**: `arbi docs --json` returns an array (or `[]` when empty). `--ids` prints one doc id per line. `--csv` prints a CSV with header. All three work on an empty workspace without erroring.
88
+ - **Filter**: `arbi docs --status completed,failed`, `--folder <pattern>`, `--ext pdf,docx`, `--query <text>`. Combine with `--limit <n>` to stop early.
89
+ - **Lite mode**: `arbi docs --lite --json` skips per-doc decryption for speed, but `doctags` and `doc_metadata` come back empty/null. Use full mode (omit `--lite`) if you need those.
90
+ - **Per-doc**: `arbi doc get <id...>` returns full JSON (no flag needed). `arbi doc delete <id...>` and `arbi doc update <id> '<json>'` take IDs directly.
91
+ - **Upload by URL**: `arbi doc upload-url <url> --json`.
92
+ - **Download**: `arbi download <id> -o <path>` writes the file; `-o -` streams raw bytes to stdout; `--json` emits a `{doc_id, path, bytes, filename}` metadata line after writing.
93
+ - **Tags**: `arbi tags list --json`, `arbi tags create <name> -t <type>`, `arbi tags delete <id>`.
94
+ - **Doctags** (tag↔doc link): `arbi doctags create <tag-id> <doc-id...>` / `arbi doctags delete <tag-id> <doc-id...>`.
95
+ - **Retrieve only**: `arbi find <query> --json` returns raw retrieval results without running the LLM.
96
+ - **Exit codes**: 0 on success (including clean skips), 1 on error. Errors print to stderr.
package/dist/index.js CHANGED
@@ -3641,7 +3641,7 @@ function getLatestVersion(skipCache = false) {
3641
3641
  }
3642
3642
  }
3643
3643
  function getCurrentVersion() {
3644
- return "0.3.40";
3644
+ return "0.3.42";
3645
3645
  }
3646
3646
  function readChangelog(fromVersion, toVersion) {
3647
3647
  try {
@@ -3694,17 +3694,17 @@ function showChangelog(fromVersion, toVersion) {
3694
3694
  async function checkForUpdates(autoUpdate) {
3695
3695
  try {
3696
3696
  const latest = getLatestVersion();
3697
- if (!latest || latest === "0.3.40") return;
3697
+ if (!latest || latest === "0.3.42") return;
3698
3698
  if (autoUpdate) {
3699
3699
  warn(`
3700
- Your arbi version is out of date (${"0.3.40"} \u2192 ${latest}). Updating...`);
3700
+ Your arbi version is out of date (${"0.3.42"} \u2192 ${latest}). Updating...`);
3701
3701
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3702
- showChangelog("0.3.40", latest);
3702
+ showChangelog("0.3.42", latest);
3703
3703
  console.log(`Updated to ${latest}.`);
3704
3704
  } else {
3705
3705
  warn(
3706
3706
  `
3707
- Your arbi version is out of date (${"0.3.40"} \u2192 ${latest}).
3707
+ Your arbi version is out of date (${"0.3.42"} \u2192 ${latest}).
3708
3708
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3709
3709
  );
3710
3710
  }
@@ -3714,9 +3714,9 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3714
3714
  function hintUpdateOnError() {
3715
3715
  try {
3716
3716
  const cached = readCache();
3717
- if (cached && cached.latest !== "0.3.40") {
3717
+ if (cached && cached.latest !== "0.3.42") {
3718
3718
  warn(
3719
- `Your arbi version is out of date (${"0.3.40"} \u2192 ${cached.latest}). Run "arbi update".`
3719
+ `Your arbi version is out of date (${"0.3.42"} \u2192 ${cached.latest}). Run "arbi update".`
3720
3720
  );
3721
3721
  }
3722
3722
  } catch {
@@ -4417,12 +4417,49 @@ function registerStatusCommand(program2) {
4417
4417
  }
4418
4418
  });
4419
4419
  }
4420
+ function resolveWorkspaceSelector(list, selector) {
4421
+ const byId = list.find((w) => w.external_id === selector);
4422
+ if (byId) return { ok: true, id: byId.external_id, ws: byId };
4423
+ const lower = selector.toLowerCase();
4424
+ const byName = list.filter((w) => (w.name ?? "").toLowerCase() === lower);
4425
+ if (byName.length === 1) return { ok: true, id: byName[0].external_id, ws: byName[0] };
4426
+ if (byName.length > 1) return { ok: false, reason: "ambiguous", matches: byName };
4427
+ return { ok: false, reason: "not-found" };
4428
+ }
4429
+ function printResolveError(list, selector, res) {
4430
+ if (res.ok) return;
4431
+ if (res.reason === "ambiguous") {
4432
+ error(`Workspace name "${selector}" is ambiguous \u2014 multiple matches:`);
4433
+ for (const w of res.matches ?? []) error(` ${w.external_id} ${w.name}`);
4434
+ error("Use the workspace ID instead.");
4435
+ return;
4436
+ }
4437
+ error(`Workspace ${selector} not found.`);
4438
+ error("Available workspaces:");
4439
+ for (const w of list) error(` ${w.external_id} ${w.name}`);
4440
+ }
4420
4441
  function registerWorkspacesCommand(program2) {
4421
- program2.command("workspaces").description("List workspaces").action(
4422
- runAction(async () => {
4442
+ program2.command("workspaces").description("List workspaces").option("--json", "Output as JSON").option("--ids", "Output only workspace IDs (one per line)").action(
4443
+ (opts) => runAction(async () => {
4423
4444
  const { arbi } = await resolveAuth();
4424
4445
  const data = await sdk.workspaces.listWorkspaces(arbi);
4425
4446
  updateCompletionCache(data);
4447
+ if (opts.ids) {
4448
+ for (const w of data) console.log(w.external_id);
4449
+ return;
4450
+ }
4451
+ if (opts.json) {
4452
+ const selectedId = getConfig()?.selectedWorkspaceId ?? null;
4453
+ const out = data.map((w) => ({
4454
+ id: w.external_id,
4455
+ name: w.name,
4456
+ docs: w.shared_document_count + w.private_document_count,
4457
+ role: w.users?.[0]?.role ?? null,
4458
+ is_selected: w.external_id === selectedId
4459
+ }));
4460
+ console.log(JSON.stringify(out, null, 2));
4461
+ return;
4462
+ }
4426
4463
  if (data.length === 0) {
4427
4464
  console.log("No workspaces found.");
4428
4465
  return;
@@ -4444,11 +4481,51 @@ function registerWorkspacesCommand(program2) {
4444
4481
  ],
4445
4482
  data
4446
4483
  );
4447
- })
4484
+ })()
4448
4485
  );
4449
4486
  const workspace = program2.command("workspace").description("Workspace management");
4450
- workspace.command("select [id]").description("Select active workspace (interactive picker if no ID given)").action(
4451
- (id) => runAction(async () => {
4487
+ workspace.command("current").description("Print the currently selected workspace ID (empty string if none)").option("--json", "Output full details as JSON").action(
4488
+ (opts) => runAction(async () => {
4489
+ const selectedId = getConfig()?.selectedWorkspaceId ?? "";
4490
+ if (!opts.json) {
4491
+ console.log(selectedId);
4492
+ return;
4493
+ }
4494
+ if (!selectedId) {
4495
+ console.log(JSON.stringify({ id: null, name: null, docs: 0, role: null }, null, 2));
4496
+ return;
4497
+ }
4498
+ const { arbi } = await resolveAuth();
4499
+ const data = await sdk.workspaces.listWorkspaces(arbi);
4500
+ const ws = data.find((w) => w.external_id === selectedId);
4501
+ if (!ws) {
4502
+ console.log(
4503
+ JSON.stringify(
4504
+ { id: selectedId, name: null, docs: 0, role: null, stale: true },
4505
+ null,
4506
+ 2
4507
+ )
4508
+ );
4509
+ return;
4510
+ }
4511
+ console.log(
4512
+ JSON.stringify(
4513
+ {
4514
+ id: ws.external_id,
4515
+ name: ws.name,
4516
+ docs: ws.shared_document_count + ws.private_document_count,
4517
+ role: ws.users?.[0]?.role ?? null
4518
+ },
4519
+ null,
4520
+ 2
4521
+ )
4522
+ );
4523
+ })()
4524
+ );
4525
+ workspace.command("select [id-or-name]").description(
4526
+ "Select active workspace (accepts ID or unique name; interactive picker if nothing given)"
4527
+ ).action(
4528
+ (idOrName) => runAction(async () => {
4452
4529
  const { arbi } = await resolveAuth();
4453
4530
  const data = await sdk.workspaces.listWorkspaces(arbi);
4454
4531
  updateCompletionCache(data);
@@ -4457,15 +4534,13 @@ function registerWorkspacesCommand(program2) {
4457
4534
  return;
4458
4535
  }
4459
4536
  let selectedId;
4460
- if (id) {
4461
- const ws2 = data.find((w) => w.external_id === id);
4462
- if (!ws2) {
4463
- error(`Workspace ${id} not found.`);
4464
- error("Available workspaces:");
4465
- for (const w of data) error(` ${w.external_id} ${w.name}`);
4537
+ if (idOrName) {
4538
+ const res = resolveWorkspaceSelector(data, idOrName);
4539
+ if (!res.ok) {
4540
+ printResolveError(data, idOrName, res);
4466
4541
  process.exit(1);
4467
4542
  }
4468
- selectedId = id;
4543
+ selectedId = res.id;
4469
4544
  } else {
4470
4545
  const choices = sdk.formatWorkspaceChoices(data);
4471
4546
  selectedId = await promptSelect("Select workspace", choices);
@@ -4476,7 +4551,7 @@ function registerWorkspacesCommand(program2) {
4476
4551
  success(`Selected: ${ws.name} (${ref(selectedId)})`);
4477
4552
  })()
4478
4553
  );
4479
- workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).action(
4554
+ workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).option("--select", "Set the new workspace as the active selection", false).option("--json", "Output the new workspace as JSON").action(
4480
4555
  (name, opts) => runAction(async () => {
4481
4556
  const { arbi, loginResult } = await resolveAuth();
4482
4557
  const userProjects = await sdk.projects.listProjects(arbi);
@@ -4491,37 +4566,114 @@ function registerWorkspacesCommand(program2) {
4491
4566
  opts.description,
4492
4567
  opts.public ?? false
4493
4568
  );
4569
+ if (opts.select) {
4570
+ updateConfig({ selectedWorkspaceId: data.external_id });
4571
+ clearChatSession();
4572
+ }
4573
+ if (opts.json) {
4574
+ console.log(
4575
+ JSON.stringify(
4576
+ {
4577
+ id: data.external_id,
4578
+ name: data.name,
4579
+ selected: opts.select ?? false
4580
+ },
4581
+ null,
4582
+ 2
4583
+ )
4584
+ );
4585
+ return;
4586
+ }
4494
4587
  success(`Created: ${data.name} (${ref(data.external_id)})`);
4588
+ if (opts.select) success(`Selected: ${data.name} (${ref(data.external_id)})`);
4495
4589
  })()
4496
4590
  );
4497
- workspace.command("delete [id]").description("Delete a workspace (defaults to selected workspace)").action(
4498
- (id) => runAction(async () => {
4591
+ workspace.command("delete [id-or-name]").description("Delete a workspace (defaults to selected workspace)").option("-y, --yes", "Skip confirmation prompt (required in non-interactive shells)", false).option("--json", "Output the result as JSON").action(
4592
+ (idOrName, opts) => runAction(async () => {
4499
4593
  const { arbi } = await resolveAuth();
4500
- const targetId = id ?? getConfig()?.selectedWorkspaceId;
4501
- if (!targetId) {
4502
- error("No workspace ID given and no workspace selected. Run: arbi workspace select");
4503
- process.exit(1);
4594
+ const config = getConfig();
4595
+ let targetId;
4596
+ let targetName;
4597
+ if (idOrName) {
4598
+ const data = await sdk.workspaces.listWorkspaces(arbi);
4599
+ const res = resolveWorkspaceSelector(data, idOrName);
4600
+ if (!res.ok) {
4601
+ printResolveError(data, idOrName, res);
4602
+ process.exit(1);
4603
+ }
4604
+ targetId = res.id;
4605
+ targetName = res.ws.name;
4606
+ } else {
4607
+ targetId = config?.selectedWorkspaceId;
4608
+ if (!targetId) {
4609
+ error("No workspace ID given and no workspace selected. Run: arbi workspace select");
4610
+ process.exit(1);
4611
+ }
4612
+ }
4613
+ const isInteractive = process.stdin.isTTY === true && process.stdout.isTTY === true;
4614
+ if (!opts.yes) {
4615
+ if (!isInteractive) {
4616
+ error(
4617
+ `Refusing to delete ${targetId} without confirmation. Re-run with --yes (non-interactive shell).`
4618
+ );
4619
+ process.exit(1);
4620
+ }
4621
+ const label2 = targetName ? `"${targetName}" (${targetId})` : targetId;
4622
+ const confirmed = await promptConfirm(
4623
+ `Delete workspace ${label2}? This removes all documents, conversations, and tags in it.`,
4624
+ false
4625
+ );
4626
+ if (!confirmed) {
4627
+ console.log("Cancelled.");
4628
+ return;
4629
+ }
4504
4630
  }
4505
4631
  await sdk.workspaces.deleteWorkspaces(arbi, [targetId]);
4632
+ if (config?.selectedWorkspaceId === targetId) {
4633
+ updateConfig({ selectedWorkspaceId: void 0 });
4634
+ }
4506
4635
  const session = getChatSession();
4507
4636
  if (session.workspaceId === targetId) {
4508
4637
  clearChatSession();
4509
4638
  }
4639
+ if (opts.json) {
4640
+ console.log(JSON.stringify({ id: targetId, deleted: true }, null, 2));
4641
+ return;
4642
+ }
4510
4643
  success(`Deleted workspace ${targetId}`);
4511
4644
  })()
4512
4645
  );
4513
- workspace.command("update <json>").description("Update workspace properties (pass JSON)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4646
+ workspace.command("update <json>").description("Update workspace properties (pass JSON)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output the updated workspace as JSON").action(
4514
4647
  (json, opts) => runAction(async () => {
4515
4648
  const body = parseJsonArg(json, `arbi workspace update '{"name": "New Name"}'`);
4516
4649
  const { arbi } = await resolveWorkspace(opts.workspace);
4517
4650
  const data = await sdk.workspaces.updateWorkspace(arbi, body);
4651
+ if (opts.json) {
4652
+ console.log(JSON.stringify({ id: data.external_id, name: data.name }, null, 2));
4653
+ return;
4654
+ }
4518
4655
  success(`Updated: ${data.name} (${ref(data.external_id)})`);
4519
4656
  })()
4520
4657
  );
4521
- workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4658
+ workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
4522
4659
  (opts) => runAction(async () => {
4523
4660
  const { arbi } = await resolveWorkspace(opts.workspace);
4524
4661
  const data = await sdk.workspaces.listWorkspaceUsers(arbi);
4662
+ if (opts.json) {
4663
+ const out = data.map((r) => {
4664
+ const u = r.user ?? {};
4665
+ return {
4666
+ user_id: u.external_id ?? null,
4667
+ email: u.email ?? null,
4668
+ name: [u.given_name, u.family_name].filter(Boolean).join(" ") || null,
4669
+ role: r.role,
4670
+ document_count: r.document_count,
4671
+ conversation_count: r.conversation_count
4672
+ };
4673
+ });
4674
+ console.log(JSON.stringify(out, null, 2));
4675
+ return;
4676
+ }
4525
4677
  if (data.length === 0) {
4526
4678
  console.log("No users found.");
4527
4679
  return;
@@ -4598,7 +4750,18 @@ function registerWorkspacesCommand(program2) {
4598
4750
  signingPrivateKeyBase64
4599
4751
  );
4600
4752
  const data = await sdk.workspaces.copyDocuments(arbi, targetId, docIds, targetKey);
4601
- success(`${data.detail} (${data.documents_copied} document(s) copied)`);
4753
+ const copied = data.documents_copied ?? 0;
4754
+ const requested = docIds.length;
4755
+ if (copied === 0) {
4756
+ error(`Copied 0 of ${requested} document(s). ${data.detail ?? ""}`.trim());
4757
+ error("Hint: documents must be fully indexed before they can be copied.");
4758
+ process.exit(1);
4759
+ }
4760
+ if (copied < requested) {
4761
+ error(`Copied ${copied} of ${requested} document(s). ${data.detail ?? ""}`.trim());
4762
+ return;
4763
+ }
4764
+ success(`Copied ${copied} document(s) to ${targetId}`);
4602
4765
  })()
4603
4766
  );
4604
4767
  }
@@ -4756,7 +4919,18 @@ function registerDocsCommand(program2) {
4756
4919
  if (hardLimit !== void 0 && totalFetched >= hardLimit) break;
4757
4920
  }
4758
4921
  if (totalFetched === 0 && data.length === 0) {
4759
- console.log("No documents found.");
4922
+ if (opts.json) {
4923
+ if (opts.output) fs5.writeFileSync(opts.output, "[]\n");
4924
+ else console.log("[]");
4925
+ } else if (opts.ids) {
4926
+ if (opts.output) fs5.writeFileSync(opts.output, "");
4927
+ } else if (opts.csv) {
4928
+ if (opts.output) fs5.writeFileSync(opts.output, csvHeader + "\n");
4929
+ } else if (opts.count) {
4930
+ console.log("0");
4931
+ } else {
4932
+ console.log("No documents found.");
4933
+ }
4760
4934
  return;
4761
4935
  }
4762
4936
  if (!bufferAll && (opts.ids || opts.csv)) {
@@ -4967,10 +5141,19 @@ function registerDocsCommand(program2) {
4967
5141
  }
4968
5142
  })()
4969
5143
  );
4970
- doc.command("upload-url <urls...>").description("Upload documents from URLs").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--shared", "Make documents shared", false).action(
5144
+ doc.command("upload-url <urls...>").description("Upload documents from URLs").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--shared", "Make documents shared", false).option("--json", "Output results as JSON").action(
4971
5145
  (urls, opts) => runAction(async () => {
4972
5146
  const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4973
5147
  const data = await sdk.documents.uploadUrl(arbi, urls, workspaceId, opts.shared ?? false);
5148
+ if (opts.json) {
5149
+ console.log(
5150
+ JSON.stringify({
5151
+ docIds: data.doc_ext_ids ?? [],
5152
+ skipped: data.skipped ?? []
5153
+ })
5154
+ );
5155
+ return;
5156
+ }
4974
5157
  success(`Uploaded: ${(data.doc_ext_ids ?? []).join(", ")}`);
4975
5158
  if (data.skipped && data.skipped.length > 0) {
4976
5159
  warn(
@@ -5705,7 +5888,7 @@ Connection closed. ${pending.size} document(s) still processing.`);
5705
5888
  }
5706
5889
  });
5707
5890
  await done;
5708
- } else if (uploadedDocs.size > 0 && !willWatch) {
5891
+ } else if (uploadedDocs.size > 0 && !willWatch && !opts.json) {
5709
5892
  dim(
5710
5893
  'Tip: Use -W/--watch to monitor processing progress, or run "arbi docs" to check status.'
5711
5894
  );
@@ -6209,7 +6392,13 @@ Connection closed. ${pending.size} document(s) still processing.`);
6209
6392
  }
6210
6393
  }
6211
6394
  function registerDownloadCommand(program2) {
6212
- program2.command("download [doc-id]").description("Download a document (interactive picker if no ID given)").option("-o, --output <path>", "Output file path").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
6395
+ program2.command("download [doc-id]").description("Download a document (interactive picker if no ID given)").option(
6396
+ "-o, --output <path>",
6397
+ 'Output file path. Use "-" to stream raw bytes to stdout (for piping).'
6398
+ ).option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option(
6399
+ "--json",
6400
+ 'Emit a JSON metadata line to stdout after writing the file (doc_id, path, bytes, filename). Implies a file write \u2014 use "-o -" or pipe stdout to get bytes without JSON.'
6401
+ ).action(
6213
6402
  (docId, opts) => runAction(async () => {
6214
6403
  const { arbi, config, accessToken } = await resolveWorkspace(opts.workspace);
6215
6404
  if (!docId) {
@@ -6235,9 +6424,24 @@ function registerDownloadCommand(program2) {
6235
6424
  const match = disposition.match(/filename[*]?=(?:UTF-8''|"?)([^";]+)/i);
6236
6425
  if (match) filename = decodeURIComponent(match[1].replace(/"/g, ""));
6237
6426
  }
6238
- const outputPath = opts.output || path5__default.default.join(process.cwd(), filename);
6239
6427
  const buffer = Buffer.from(await res.arrayBuffer());
6428
+ if (opts.output === "-") {
6429
+ process.stdout.write(buffer);
6430
+ return;
6431
+ }
6432
+ const outputPath = opts.output || path5__default.default.join(process.cwd(), filename);
6240
6433
  fs5__default.default.writeFileSync(outputPath, buffer);
6434
+ if (opts.json) {
6435
+ console.log(
6436
+ JSON.stringify({
6437
+ doc_id: docId,
6438
+ path: outputPath,
6439
+ bytes: buffer.length,
6440
+ filename: path5__default.default.basename(outputPath)
6441
+ })
6442
+ );
6443
+ return;
6444
+ }
6241
6445
  success(
6242
6446
  `Downloaded: ${path5__default.default.basename(outputPath)} (${(buffer.length / (1024 * 1024)).toFixed(1)} MB)`
6243
6447
  );
@@ -8258,7 +8462,7 @@ console.info = (...args) => {
8258
8462
  _origInfo(...args);
8259
8463
  };
8260
8464
  var program = new commander.Command();
8261
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.40");
8465
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.42");
8262
8466
  registerConfigCommand(program);
8263
8467
  registerLoginCommand(program);
8264
8468
  registerRegisterCommand(program);