@arbidocs/cli 0.3.39 β†’ 0.3.41

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.41
4
+
5
+ [compare changes](https://github.com/arbicity/ARBI-frontend/compare/v0.3.40...HEAD)
6
+
7
+ ### 🏑 Chore
8
+
9
+ - Release v0.3.40 [skip ci] ([5e404545](https://github.com/arbicity/ARBI-frontend/commit/5e404545))
10
+
3
11
  ## v0.3.39
4
12
 
5
13
  [compare changes](https://github.com/arbicity/ARBI-frontend/compare/v0.3.38...HEAD)
package/SKILL.md CHANGED
@@ -6,15 +6,18 @@ ARBI is an enterprise document intelligence platform with E2E encrypted communic
6
6
 
7
7
  1. Install: `npm install -g @arbidocs/cli`
8
8
  2. Configure server: `arbi config set-url https://<deployment>.arbibox.com`
9
- 3. Get a claim code from your parent user (via ARBI web UI β†’ Settings β†’ Agents β†’ Authorize, or `arbi authorize agent`)
10
- 4. Connect: `arbi connect --code <claim-code>`
11
- 5. Verify: `arbi status`
9
+ 3. Log in (pick one):
10
+ - Interactive: `arbi quickstart` (wizard) or `arbi login -e <email> -p <password>`
11
+ - Scripted (humans with API access): `arbi register -e <email> -p <password> --verification-code <code>`
12
+ - Scripted (AI agents / bots, requires `SUPPORT_API_KEY`): `arbi agent-create --email agent-<name>@<domain> --workspace-name "<name>"`
13
+ - The email MUST start with `agent-`. This path creates the account, logs in, and selects the new workspace in one call.
14
+ 4. Verify: `arbi status` (add `--json` for machine-readable output)
12
15
 
13
- For multiple agents on the same machine, set `ARBI_ID=<name>` to isolate config in `~/.arbi/<name>/`.
16
+ For multiple agents on the same machine, set `ARBI_ID=<name>` (or `ARBI_CONFIG_DIR=<path>`) to isolate config in `~/.arbi/<name>/`.
14
17
 
15
18
  ## When to Use
16
19
 
17
- - **Document questions**: `arbi ask <question>` β€” performs RAG retrieval across workspace documents
20
+ - **Document questions**: `arbi ask <question>` β€” performs RAG retrieval across workspace documents (no quotes needed around the question)
18
21
  - **Document management**: `arbi docs`, `arbi upload`, `arbi download`, `arbi find`
19
22
  - **Local files**: `arbi local ls`, `arbi local find "**/*.pdf"`, `arbi local cat <file>`, `arbi local tree`
20
23
  - **Secure messaging**: `arbi dm send <recipient> <message>` β€” E2E encrypted
@@ -22,9 +25,70 @@ For multiple agents on the same machine, set `ARBI_ID=<name>` to isolate config
22
25
 
23
26
  Every command supports `--help`. Explore with `arbi --help` to discover all capabilities.
24
27
 
28
+ ## Workspaces
29
+
30
+ Documents, conversations, and tags are scoped per workspace. An active workspace is stored in the CLI config and used by `arbi docs`, `arbi upload`, `arbi ask`, etc.
31
+
32
+ Common moves:
33
+
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)
41
+ - List members: `arbi workspace users [--json]`
42
+ - Remove member: `arbi workspace remove-user <user-id>`
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
44
+
45
+ Scripting notes:
46
+
47
+ - The table output of `arbi workspaces` is NOT reliably parseable (names contain spaces, status cells may contain glyphs). Use `--json` or `--ids`.
48
+ - Sessions persist between CLI invocations via the config file (default `~/.arbi/`, override with `ARBI_CONFIG_DIR`).
49
+ - Per-agent isolation: run agents with their own `ARBI_CONFIG_DIR=<path>` (or `ARBI_ID=<name>`) to avoid stomping each other's active workspace.
50
+
25
51
  ## Tips
26
52
 
27
- - If not logged in, run `arbi connect --code <code>` with a new claim code from your parent user.
53
+ - Not logged in: run `arbi login` (interactive) or, with `SUPPORT_API_KEY` set, `arbi agent-create --email agent-<name>@<domain>`.
28
54
  - If a command fails with "fetch failed", check connectivity with `arbi health`.
29
55
  - Use `arbi ask -n` to start a fresh conversation (forget prior context).
30
56
  - 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.
58
+
59
+ ## For Automations / AI Agents
60
+
61
+ - **Structured output (`--json`)**: supported on `status`, `config show`, `health`, `models`, `workspaces`, `workspace users`, `docs`, `conversations` (or `conversations list`), `tags` (or `tags list`), `contacts` (or `contacts list`), `find`, `settings get`, `task list`, `ask -b`, `upload`, `watch`. Use it whenever parsing output. `docs` also supports `--csv`, `--ids`, and `--count`.
62
+ - **Human table output is lossy**: table rows may contain status glyphs (`●`, `🟩`, etc.) and emoji, and column widths truncate content. Never parse tables β€” always pass `--json`.
63
+ - **Exit codes**:
64
+ - `0` success
65
+ - `1` failure (bad args / auth / API / network / server error)
66
+ - `2` `arbi health --json` when the server reports unhealthy (the JSON body is still printed so you can introspect)
67
+ - `130` user-cancelled interactive prompt (Ctrl+C in `quickstart`)
68
+ - Always check `$?` β€” `arbi ask` while logged-out, typo commands, and network failures all return non-zero.
69
+ - **Avoid interactive prompts** β€” pass everything as flags/env vars:
70
+ - `arbi login -e $ARBI_EMAIL -p $ARBI_PASSWORD -w <workspace-id>` (or set `ARBI_EMAIL`/`ARBI_PASSWORD`).
71
+ - `arbi agent-create --email agent-<...> --workspace-name "<name>"` (requires `SUPPORT_API_KEY`).
72
+ - `arbi workspace select <id>` (ID directly, no picker).
73
+ - `arbi docs` auto-uses the selected workspace; pass `-w <id>` to override.
74
+ - Do **not** run `arbi quickstart` in non-interactive contexts β€” it is interactive-only.
75
+ - **Health probe**: `arbi health --json` returns the raw payload and exits `2` if the server is not healthy β€” useful for pre-flight checks.
76
+ - **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
+ - **Typo suggestions**: `arbi updat` β†’ `error: unknown command 'updat' (Did you mean update?)` (exit 1). Works for top-level commands and nested subcommands.
78
+ - **Completion**: `arbi completion install` (no `bash`/`zsh` arg β€” auto-detected from `$SHELL`).
79
+
80
+ ## Document Lifecycle (for agents)
81
+
82
+ All document list/search/meta commands support machine-readable output. Prefer `--json`.
83
+
84
+ - **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.
85
+ - **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.
86
+ - **Filter**: `arbi docs --status completed,failed`, `--folder <pattern>`, `--ext pdf,docx`, `--query <text>`. Combine with `--limit <n>` to stop early.
87
+ - **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.
88
+ - **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.
89
+ - **Upload by URL**: `arbi doc upload-url <url> --json`.
90
+ - **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.
91
+ - **Tags**: `arbi tags list --json`, `arbi tags create <name> -t <type>`, `arbi tags delete <id>`.
92
+ - **Doctags** (tag↔doc link): `arbi doctags create <tag-id> <doc-id...>` / `arbi doctags delete <tag-id> <doc-id...>`.
93
+ - **Retrieve only**: `arbi find <query> --json` returns raw retrieval results without running the LLM.
94
+ - **Exit codes**: 0 on success (including clean skips), 1 on error. Errors print to stderr.
package/dist/index.js CHANGED
@@ -216,8 +216,12 @@ function registerConfigCommand(program2) {
216
216
  process.exit(1);
217
217
  }
218
218
  });
219
- config.command("show").description("Show current configuration").action(() => {
219
+ config.command("show").description("Show current configuration").option("--json", "Output as JSON").action((opts) => {
220
220
  const cfg = getConfig();
221
+ if (opts.json) {
222
+ console.log(JSON.stringify(cfg ?? {}, null, 2));
223
+ return;
224
+ }
221
225
  if (!cfg) {
222
226
  dim("Not configured. Run: arbi config set-url <url>");
223
227
  return;
@@ -3637,7 +3641,7 @@ function getLatestVersion(skipCache = false) {
3637
3641
  }
3638
3642
  }
3639
3643
  function getCurrentVersion() {
3640
- return "0.3.39";
3644
+ return "0.3.41";
3641
3645
  }
3642
3646
  function readChangelog(fromVersion, toVersion) {
3643
3647
  try {
@@ -3690,17 +3694,17 @@ function showChangelog(fromVersion, toVersion) {
3690
3694
  async function checkForUpdates(autoUpdate) {
3691
3695
  try {
3692
3696
  const latest = getLatestVersion();
3693
- if (!latest || latest === "0.3.39") return;
3697
+ if (!latest || latest === "0.3.41") return;
3694
3698
  if (autoUpdate) {
3695
3699
  warn(`
3696
- Your arbi version is out of date (${"0.3.39"} \u2192 ${latest}). Updating...`);
3700
+ Your arbi version is out of date (${"0.3.41"} \u2192 ${latest}). Updating...`);
3697
3701
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3698
- showChangelog("0.3.39", latest);
3702
+ showChangelog("0.3.41", latest);
3699
3703
  console.log(`Updated to ${latest}.`);
3700
3704
  } else {
3701
3705
  warn(
3702
3706
  `
3703
- Your arbi version is out of date (${"0.3.39"} \u2192 ${latest}).
3707
+ Your arbi version is out of date (${"0.3.41"} \u2192 ${latest}).
3704
3708
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3705
3709
  );
3706
3710
  }
@@ -3710,9 +3714,9 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3710
3714
  function hintUpdateOnError() {
3711
3715
  try {
3712
3716
  const cached = readCache();
3713
- if (cached && cached.latest !== "0.3.39") {
3717
+ if (cached && cached.latest !== "0.3.41") {
3714
3718
  warn(
3715
- `Your arbi version is out of date (${"0.3.39"} \u2192 ${cached.latest}). Run "arbi update".`
3719
+ `Your arbi version is out of date (${"0.3.41"} \u2192 ${cached.latest}). Run "arbi update".`
3716
3720
  );
3717
3721
  }
3718
3722
  } catch {
@@ -4377,9 +4381,25 @@ function registerLogoutCommand(program2) {
4377
4381
 
4378
4382
  // src/commands/status.ts
4379
4383
  function registerStatusCommand(program2) {
4380
- program2.command("status").description("Show current configuration and login status").action(() => {
4384
+ program2.command("status").description("Show current configuration and login status").option("--json", "Output as JSON for scripting").action((opts) => {
4381
4385
  const config = getConfig();
4382
4386
  const creds = getCredentials();
4387
+ if (opts.json) {
4388
+ console.log(
4389
+ JSON.stringify(
4390
+ {
4391
+ configured: !!config?.baseUrl,
4392
+ server: config?.baseUrl ?? null,
4393
+ logged_in: !!creds,
4394
+ user: creds?.email ?? null,
4395
+ workspace: config?.selectedWorkspaceId ?? null
4396
+ },
4397
+ null,
4398
+ 2
4399
+ )
4400
+ );
4401
+ return;
4402
+ }
4383
4403
  if (!config?.baseUrl) {
4384
4404
  dim("Not configured. Run: arbi config set-url <url>");
4385
4405
  return;
@@ -4736,7 +4756,18 @@ function registerDocsCommand(program2) {
4736
4756
  if (hardLimit !== void 0 && totalFetched >= hardLimit) break;
4737
4757
  }
4738
4758
  if (totalFetched === 0 && data.length === 0) {
4739
- console.log("No documents found.");
4759
+ if (opts.json) {
4760
+ if (opts.output) fs5.writeFileSync(opts.output, "[]\n");
4761
+ else console.log("[]");
4762
+ } else if (opts.ids) {
4763
+ if (opts.output) fs5.writeFileSync(opts.output, "");
4764
+ } else if (opts.csv) {
4765
+ if (opts.output) fs5.writeFileSync(opts.output, csvHeader + "\n");
4766
+ } else if (opts.count) {
4767
+ console.log("0");
4768
+ } else {
4769
+ console.log("No documents found.");
4770
+ }
4740
4771
  return;
4741
4772
  }
4742
4773
  if (!bufferAll && (opts.ids || opts.csv)) {
@@ -4947,10 +4978,19 @@ function registerDocsCommand(program2) {
4947
4978
  }
4948
4979
  })()
4949
4980
  );
4950
- 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(
4981
+ 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(
4951
4982
  (urls, opts) => runAction(async () => {
4952
4983
  const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4953
4984
  const data = await sdk.documents.uploadUrl(arbi, urls, workspaceId, opts.shared ?? false);
4985
+ if (opts.json) {
4986
+ console.log(
4987
+ JSON.stringify({
4988
+ docIds: data.doc_ext_ids ?? [],
4989
+ skipped: data.skipped ?? []
4990
+ })
4991
+ );
4992
+ return;
4993
+ }
4954
4994
  success(`Uploaded: ${(data.doc_ext_ids ?? []).join(", ")}`);
4955
4995
  if (data.skipped && data.skipped.length > 0) {
4956
4996
  warn(
@@ -5685,7 +5725,7 @@ Connection closed. ${pending.size} document(s) still processing.`);
5685
5725
  }
5686
5726
  });
5687
5727
  await done;
5688
- } else if (uploadedDocs.size > 0 && !willWatch) {
5728
+ } else if (uploadedDocs.size > 0 && !willWatch && !opts.json) {
5689
5729
  dim(
5690
5730
  'Tip: Use -W/--watch to monitor processing progress, or run "arbi docs" to check status.'
5691
5731
  );
@@ -6189,7 +6229,13 @@ Connection closed. ${pending.size} document(s) still processing.`);
6189
6229
  }
6190
6230
  }
6191
6231
  function registerDownloadCommand(program2) {
6192
- 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(
6232
+ program2.command("download [doc-id]").description("Download a document (interactive picker if no ID given)").option(
6233
+ "-o, --output <path>",
6234
+ 'Output file path. Use "-" to stream raw bytes to stdout (for piping).'
6235
+ ).option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option(
6236
+ "--json",
6237
+ '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.'
6238
+ ).action(
6193
6239
  (docId, opts) => runAction(async () => {
6194
6240
  const { arbi, config, accessToken } = await resolveWorkspace(opts.workspace);
6195
6241
  if (!docId) {
@@ -6215,9 +6261,24 @@ function registerDownloadCommand(program2) {
6215
6261
  const match = disposition.match(/filename[*]?=(?:UTF-8''|"?)([^";]+)/i);
6216
6262
  if (match) filename = decodeURIComponent(match[1].replace(/"/g, ""));
6217
6263
  }
6218
- const outputPath = opts.output || path5__default.default.join(process.cwd(), filename);
6219
6264
  const buffer = Buffer.from(await res.arrayBuffer());
6265
+ if (opts.output === "-") {
6266
+ process.stdout.write(buffer);
6267
+ return;
6268
+ }
6269
+ const outputPath = opts.output || path5__default.default.join(process.cwd(), filename);
6220
6270
  fs5__default.default.writeFileSync(outputPath, buffer);
6271
+ if (opts.json) {
6272
+ console.log(
6273
+ JSON.stringify({
6274
+ doc_id: docId,
6275
+ path: outputPath,
6276
+ bytes: buffer.length,
6277
+ filename: path5__default.default.basename(outputPath)
6278
+ })
6279
+ );
6280
+ return;
6281
+ }
6221
6282
  success(
6222
6283
  `Downloaded: ${path5__default.default.basename(outputPath)} (${(buffer.length / (1024 * 1024)).toFixed(1)} MB)`
6223
6284
  );
@@ -6615,10 +6676,14 @@ Timeout (${timeoutSec}s), closing.`));
6615
6676
  }
6616
6677
  function registerContactsCommand(program2) {
6617
6678
  const contacts = program2.command("contacts").description("Manage contacts");
6618
- contacts.command("list").description("List all contacts").action(
6619
- runAction(async () => {
6679
+ contacts.command("list").description("List all contacts").option("--json", "Output as JSON").action(
6680
+ (opts) => runAction(async () => {
6620
6681
  const { arbi } = await resolveAuth();
6621
6682
  const data = await sdk.contacts.listContacts(arbi);
6683
+ if (opts.json) {
6684
+ console.log(JSON.stringify(data, null, 2));
6685
+ return;
6686
+ }
6622
6687
  if (data.length === 0) {
6623
6688
  console.log("No contacts found.");
6624
6689
  return;
@@ -6636,7 +6701,7 @@ function registerContactsCommand(program2) {
6636
6701
  ],
6637
6702
  data
6638
6703
  );
6639
- })
6704
+ })()
6640
6705
  );
6641
6706
  contacts.command("add [emails...]").description("Add contacts by email (prompt if no emails given)").action(
6642
6707
  (emails) => runAction(async () => {
@@ -6678,8 +6743,10 @@ function registerContactsCommand(program2) {
6678
6743
  success(`Removed ${contactIds.length} contact(s).`);
6679
6744
  })()
6680
6745
  );
6681
- contacts.action(async () => {
6682
- await contacts.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
6746
+ contacts.option("--json", "Output as JSON").action(async (opts) => {
6747
+ const args = [];
6748
+ if (opts.json) args.push("--json");
6749
+ await contacts.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
6683
6750
  });
6684
6751
  }
6685
6752
  function registerDmCommand(program2) {
@@ -6901,10 +6968,14 @@ async function fetchTagChoices(arbi) {
6901
6968
  }
6902
6969
  function registerTagsCommand(program2) {
6903
6970
  const tags = program2.command("tags").description("Manage tags");
6904
- tags.command("list").description("List tags in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
6971
+ tags.command("list").description("List tags in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
6905
6972
  (opts) => runAction(async () => {
6906
6973
  const { arbi } = await resolveWorkspace(opts.workspace);
6907
6974
  const data = await sdk.tags.listTags(arbi);
6975
+ if (opts.json) {
6976
+ console.log(JSON.stringify(data, null, 2));
6977
+ return;
6978
+ }
6908
6979
  if (data.length === 0) {
6909
6980
  console.log("No tags found.");
6910
6981
  return;
@@ -6997,8 +7068,11 @@ function registerTagsCommand(program2) {
6997
7068
  success(`Updated: ${data.name} (${ref(data.external_id)})`);
6998
7069
  })()
6999
7070
  );
7000
- tags.action(async (_opts, cmd) => {
7001
- await cmd.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
7071
+ tags.option("--json", "Output as JSON").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(async (opts, cmd) => {
7072
+ const args = [];
7073
+ if (opts.json) args.push("--json");
7074
+ if (opts.workspace) args.push("-w", opts.workspace);
7075
+ await cmd.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
7002
7076
  });
7003
7077
  }
7004
7078
  async function pickTag(arbi, workspaceId, message = "Select tag") {
@@ -7106,10 +7180,14 @@ async function pickConversation(arbi, workspaceId, message = "Select conversatio
7106
7180
  }
7107
7181
  function registerConversationsCommand(program2) {
7108
7182
  const conv = program2.command("conversations").description("Manage conversations");
7109
- conv.command("list").description("List conversations in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
7183
+ conv.command("list").description("List conversations in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
7110
7184
  (opts) => runAction(async () => {
7111
7185
  const { arbi } = await resolveWorkspace(opts.workspace);
7112
7186
  const data = await sdk.conversations.listConversations(arbi);
7187
+ if (opts.json) {
7188
+ console.log(JSON.stringify(data, null, 2));
7189
+ return;
7190
+ }
7113
7191
  if (data.length === 0) {
7114
7192
  console.log("No conversations found.");
7115
7193
  return;
@@ -7178,18 +7256,21 @@ function registerConversationsCommand(program2) {
7178
7256
  success(data?.detail ?? `Deleted message ${messageId}`);
7179
7257
  })()
7180
7258
  );
7181
- conv.action(async (_opts, cmd) => {
7182
- await cmd.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
7259
+ conv.option("--json", "Output as JSON").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(async (opts, cmd) => {
7260
+ const args = [];
7261
+ if (opts.json) args.push("--json");
7262
+ if (opts.workspace) args.push("-w", opts.workspace);
7263
+ await cmd.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
7183
7264
  });
7184
7265
  }
7185
7266
  function registerSettingsCommand(program2) {
7186
7267
  const settings = program2.command("settings").description("Manage user settings");
7187
- settings.command("get").description("Show current user settings").action(
7188
- runAction(async () => {
7268
+ settings.command("get").description("Show current user settings (always JSON; --json accepted for consistency)").option("--json", "Output as JSON (default \u2014 always JSON)").action(
7269
+ () => runAction(async () => {
7189
7270
  const { arbi } = await resolveAuth();
7190
7271
  const data = await sdk.settings.getSettings(arbi);
7191
7272
  console.log(JSON.stringify(data, null, 2));
7192
- })
7273
+ })()
7193
7274
  );
7194
7275
  settings.command("set <json>").description("Update user settings (pass JSON object)").action(
7195
7276
  (json) => runAction(async () => {
@@ -7424,10 +7505,17 @@ function registerAgentconfigCommand(program2) {
7424
7505
  });
7425
7506
  }
7426
7507
  function registerHealthCommand(program2) {
7427
- program2.command("health").description("Show server health status").action(
7428
- runAction(async () => {
7508
+ program2.command("health").description("Show server health status").option("--json", "Output raw JSON for scripting").action(
7509
+ (opts) => runAction(async () => {
7429
7510
  const { arbi } = await resolveAuth();
7430
7511
  const data = await sdk.health.getHealth(arbi);
7512
+ if (opts.json) {
7513
+ console.log(JSON.stringify(data, null, 2));
7514
+ if (data.status && data.status.toLowerCase() !== "healthy") {
7515
+ process.exit(2);
7516
+ }
7517
+ return;
7518
+ }
7431
7519
  label("Status:", status(data.status));
7432
7520
  if (data.backend_git_hash) label("Backend:", data.backend_git_hash);
7433
7521
  if (data.frontend_docker_version) label("Frontend:", data.frontend_docker_version);
@@ -7444,12 +7532,16 @@ Models (${data.models_health.application}):`);
7444
7532
  console.log(` ${m.model}: ${status(m.status)}${m.detail ? ` \u2014 ${m.detail}` : ""}`);
7445
7533
  }
7446
7534
  }
7447
- })
7535
+ })()
7448
7536
  );
7449
- program2.command("models").description("List available AI models").action(
7450
- runAction(async () => {
7537
+ program2.command("models").description("List available AI models").option("--json", "Output as JSON").action(
7538
+ (opts) => runAction(async () => {
7451
7539
  const { arbi } = await resolveAuth();
7452
7540
  const data = await sdk.health.getHealthModels(arbi);
7541
+ if (opts.json) {
7542
+ console.log(JSON.stringify(data.models, null, 2));
7543
+ return;
7544
+ }
7453
7545
  if (data.models.length === 0) {
7454
7546
  console.log("No models available.");
7455
7547
  return;
@@ -7462,7 +7554,7 @@ Models (${data.models_health.application}):`);
7462
7554
  ],
7463
7555
  data.models
7464
7556
  );
7465
- })
7557
+ })()
7466
7558
  );
7467
7559
  }
7468
7560
  function registerTuiCommand(program2) {
@@ -8031,7 +8123,7 @@ function removeCompletionBlock(content) {
8031
8123
  }
8032
8124
  function registerCompletionCommand(program2) {
8033
8125
  const completion = program2.command("completion").description("Manage shell tab completion");
8034
- completion.command("install").description("Install shell tab completion (bash/zsh)").action(() => {
8126
+ completion.command("install").description("Install shell tab completion (auto-detects bash or zsh \u2014 no shell arg needed)").action(() => {
8035
8127
  const rcPath = getShellRcPath2();
8036
8128
  if (isCompletionInstalled(rcPath)) {
8037
8129
  success(`Completion already installed in ${rcPath}`);
@@ -8207,7 +8299,7 @@ console.info = (...args) => {
8207
8299
  _origInfo(...args);
8208
8300
  };
8209
8301
  var program = new commander.Command();
8210
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.39");
8302
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.41");
8211
8303
  registerConfigCommand(program);
8212
8304
  registerLoginCommand(program);
8213
8305
  registerRegisterCommand(program);