@arbidocs/cli 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,2252 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var commander = require('commander');
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+ var os = require('os');
8
+ require('fake-indexeddb/auto');
9
+ var sdk = require('@arbidocs/sdk');
10
+ var prompts = require('@inquirer/prompts');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
15
+ var path__default = /*#__PURE__*/_interopDefault(path);
16
+ var os__default = /*#__PURE__*/_interopDefault(os);
17
+
18
+ var CONFIG_DIR = path__default.default.join(os__default.default.homedir(), ".arbi");
19
+ var CONFIG_FILE = path__default.default.join(CONFIG_DIR, "config.json");
20
+ var CREDENTIALS_FILE = path__default.default.join(CONFIG_DIR, "credentials.json");
21
+ function ensureConfigDir() {
22
+ if (!fs__default.default.existsSync(CONFIG_DIR)) {
23
+ fs__default.default.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
24
+ }
25
+ }
26
+ function writeSecureFile(filePath, data) {
27
+ ensureConfigDir();
28
+ fs__default.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
29
+ }
30
+ function readJsonFile(filePath) {
31
+ try {
32
+ const content = fs__default.default.readFileSync(filePath, "utf-8");
33
+ return JSON.parse(content);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ function getConfig() {
39
+ return readJsonFile(CONFIG_FILE);
40
+ }
41
+ function saveConfig(config) {
42
+ writeSecureFile(CONFIG_FILE, config);
43
+ }
44
+ function updateConfig(updates) {
45
+ const existing = getConfig() || {};
46
+ saveConfig({ ...existing, ...updates });
47
+ }
48
+ function requireConfig() {
49
+ const config = getConfig();
50
+ if (!config?.baseUrl) {
51
+ console.error("Not configured. Run: arbi config set-url <url>");
52
+ process.exit(1);
53
+ }
54
+ return config;
55
+ }
56
+ function getCredentials() {
57
+ return readJsonFile(CREDENTIALS_FILE);
58
+ }
59
+ function saveCredentials(creds) {
60
+ writeSecureFile(CREDENTIALS_FILE, creds);
61
+ }
62
+ function deleteCredentials() {
63
+ try {
64
+ fs__default.default.unlinkSync(CREDENTIALS_FILE);
65
+ } catch {
66
+ }
67
+ }
68
+ function requireCredentials() {
69
+ const creds = getCredentials();
70
+ if (!creds) {
71
+ console.error("Not logged in. Run: arbi login");
72
+ process.exit(1);
73
+ }
74
+ return creds;
75
+ }
76
+ var SESSION_FILE = path__default.default.join(CONFIG_DIR, "session.json");
77
+ var DEFAULT_SESSION = {
78
+ lastMessageExtId: null
79
+ };
80
+ function getChatSession() {
81
+ return readJsonFile(SESSION_FILE) || { ...DEFAULT_SESSION };
82
+ }
83
+ function saveChatSession(session) {
84
+ writeSecureFile(SESSION_FILE, session);
85
+ }
86
+ function updateChatSession(updates) {
87
+ const existing = getChatSession();
88
+ saveChatSession({ ...existing, ...updates });
89
+ }
90
+ function clearChatSession() {
91
+ saveChatSession({ ...DEFAULT_SESSION });
92
+ }
93
+
94
+ // src/commands/config-cmd.ts
95
+ function registerConfigCommand(program2) {
96
+ const config = program2.command("config").description("Manage CLI configuration");
97
+ config.command("set-url <url>").description("Set the ARBI server URL").action((url) => {
98
+ try {
99
+ const parsed = new URL(url);
100
+ const deploymentDomain = parsed.hostname;
101
+ updateConfig({ baseUrl: url.replace(/\/+$/, ""), deploymentDomain });
102
+ console.log(`Server URL: ${url}`);
103
+ console.log(`Domain: ${deploymentDomain}`);
104
+ } catch {
105
+ console.error(`Invalid URL: ${url}`);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ }
110
+ async function promptSelect(message, choices) {
111
+ return prompts.select({ message, choices });
112
+ }
113
+ async function promptCheckbox(message, choices) {
114
+ return prompts.checkbox({ message, choices });
115
+ }
116
+ async function promptSearch(message, choices) {
117
+ return prompts.search({
118
+ message,
119
+ source: async (term) => {
120
+ if (!term) return choices;
121
+ const lower = term.toLowerCase();
122
+ return choices.filter((c) => c.name.toLowerCase().includes(lower));
123
+ }
124
+ });
125
+ }
126
+ async function promptInput(message, required = true) {
127
+ return prompts.input({
128
+ message,
129
+ validate: required ? (v) => v.trim() ? true : "Required" : void 0
130
+ });
131
+ }
132
+ async function promptPassword(message) {
133
+ return prompts.password({
134
+ message,
135
+ mask: "*",
136
+ validate: (v) => v ? true : "Required"
137
+ });
138
+ }
139
+ async function promptConfirm(message, defaultValue = true) {
140
+ return prompts.confirm({ message, default: defaultValue });
141
+ }
142
+
143
+ // src/commands/login.ts
144
+ function registerLoginCommand(program2) {
145
+ program2.command("login").description("Log in to ARBI").option("-e, --email <email>", "Email address (or ARBI_EMAIL env var)").option("-p, --password <password>", "Password (or ARBI_PASSWORD env var)").option("-w, --workspace <id>", "Workspace ID to select after login").action(async (opts) => {
146
+ const config = requireConfig();
147
+ const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
148
+ const pw = opts.password || process.env.ARBI_PASSWORD || await promptPassword("Password");
149
+ const arbi = sdk.createArbiClient({
150
+ baseUrl: config.baseUrl,
151
+ deploymentDomain: config.deploymentDomain,
152
+ credentials: "omit"
153
+ });
154
+ await arbi.crypto.initSodium();
155
+ try {
156
+ const result = await arbi.auth.login({ email, password: pw });
157
+ saveCredentials({
158
+ email,
159
+ signingPrivateKeyBase64: arbi.crypto.bytesToBase64(result.signingPrivateKey),
160
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(result.serverSessionKey)
161
+ });
162
+ const { data: workspaces } = await arbi.fetch.GET("/api/user/workspaces");
163
+ const wsList = workspaces || [];
164
+ console.log(`Logged in as ${email}`);
165
+ if (wsList.length === 0) {
166
+ console.log("No workspaces found.");
167
+ return;
168
+ }
169
+ if (opts.workspace) {
170
+ const ws2 = wsList.find((w) => w.external_id === opts.workspace);
171
+ if (!ws2) {
172
+ console.error(`Workspace ${opts.workspace} not found.`);
173
+ process.exit(1);
174
+ }
175
+ updateConfig({ selectedWorkspaceId: ws2.external_id });
176
+ console.log(`Workspace: ${ws2.name} (${ws2.external_id})`);
177
+ return;
178
+ }
179
+ if (wsList.length === 1) {
180
+ updateConfig({ selectedWorkspaceId: wsList[0].external_id });
181
+ console.log(`Workspace: ${wsList[0].name} (${wsList[0].external_id})`);
182
+ return;
183
+ }
184
+ const choices = wsList.map((ws2) => {
185
+ const totalDocs = ws2.shared_document_count + ws2.private_document_count;
186
+ return {
187
+ name: `${ws2.name} (${totalDocs} docs)`,
188
+ value: ws2.external_id,
189
+ description: ws2.external_id
190
+ };
191
+ });
192
+ const selected = await promptSelect("Select workspace", choices);
193
+ updateConfig({ selectedWorkspaceId: selected });
194
+ const ws = wsList.find((w) => w.external_id === selected);
195
+ console.log(`Workspace: ${ws.name} (${selected})`);
196
+ } catch (err) {
197
+ const msg = err instanceof Error ? err.message : String(err);
198
+ console.error(`Login failed: ${msg}`);
199
+ process.exit(1);
200
+ }
201
+ });
202
+ }
203
+ var CENTRAL_API_URL = "https://central.arbi.work";
204
+ async function getVerificationCode(email, apiKey) {
205
+ const params = new URLSearchParams({ email });
206
+ const res = await fetch(`${CENTRAL_API_URL}/license-management/verify-ci?${params.toString()}`, {
207
+ method: "GET",
208
+ headers: { "x-api-key": apiKey }
209
+ });
210
+ if (!res.ok) {
211
+ const body = await res.text().catch(() => "");
212
+ throw new Error(`Failed to get verification code: ${res.status} ${body}`);
213
+ }
214
+ const data = await res.json();
215
+ const words = data?.verification_words ?? data?.verification_code ?? null;
216
+ if (!words) throw new Error("No verification code in response");
217
+ return Array.isArray(words) ? words.join(" ") : String(words);
218
+ }
219
+ function registerRegisterCommand(program2) {
220
+ program2.command("register").description("Register a new ARBI account").option("--non-interactive", "CI/automation mode (requires SUPPORT_API_KEY env var)").option("-e, --email <email>", "Email address (or ARBI_EMAIL env var)").option("-p, --password <password>", "Password (or ARBI_PASSWORD env var)").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").action(
221
+ async (opts) => {
222
+ const config = requireConfig();
223
+ if (opts.nonInteractive) {
224
+ await nonInteractiveRegister(config, opts);
225
+ } else {
226
+ await interactiveRegister(config, opts);
227
+ }
228
+ }
229
+ );
230
+ }
231
+ async function interactiveRegister(config, opts) {
232
+ const email = opts.email || await promptInput("Email");
233
+ const arbi = sdk.createArbiClient({
234
+ baseUrl: config.baseUrl,
235
+ deploymentDomain: config.deploymentDomain,
236
+ credentials: "omit"
237
+ });
238
+ await arbi.crypto.initSodium();
239
+ const codeMethod = await promptSelect("Verification method", [
240
+ { name: "I have an invitation code", value: "code" },
241
+ { name: "Send me a verification email", value: "email" }
242
+ ]);
243
+ let verificationCode;
244
+ if (codeMethod === "code") {
245
+ verificationCode = await promptInput("Invitation code");
246
+ } else {
247
+ console.log("Sending verification email...");
248
+ const verifyResponse = await arbi.fetch.POST("/api/user/verify-email", {
249
+ body: { email }
250
+ });
251
+ if (verifyResponse.error) {
252
+ console.error(`Failed to send verification email: ${JSON.stringify(verifyResponse.error)}`);
253
+ process.exit(1);
254
+ }
255
+ console.log("Verification email sent. Check your inbox.");
256
+ verificationCode = await promptInput("Verification code");
257
+ }
258
+ const password2 = opts.password || await promptPassword("Password");
259
+ const confirmPw = await promptPassword("Confirm password");
260
+ if (password2 !== confirmPw) {
261
+ console.error("Passwords do not match.");
262
+ process.exit(1);
263
+ }
264
+ const firstName = opts.firstName || await promptInput("First name", false) || "User";
265
+ const lastName = opts.lastName || await promptInput("Last name", false) || "";
266
+ try {
267
+ await arbi.auth.register({
268
+ email,
269
+ password: password2,
270
+ verificationCode,
271
+ firstName,
272
+ lastName
273
+ });
274
+ console.log(`
275
+ Registered successfully as ${email}`);
276
+ } catch (err) {
277
+ const msg = err instanceof Error ? err.message : String(err);
278
+ console.error(`Registration failed: ${msg}`);
279
+ process.exit(1);
280
+ }
281
+ const doLogin = await promptConfirm("Log in now?");
282
+ if (doLogin) {
283
+ try {
284
+ const loginResult = await arbi.auth.login({ email, password: password2 });
285
+ saveCredentials({
286
+ email,
287
+ signingPrivateKeyBase64: arbi.crypto.bytesToBase64(loginResult.signingPrivateKey),
288
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey)
289
+ });
290
+ const { data: workspaces } = await arbi.fetch.GET("/api/user/workspaces");
291
+ const wsList = workspaces || [];
292
+ console.log(`Logged in as ${email}`);
293
+ if (wsList.length === 1) {
294
+ updateConfig({ selectedWorkspaceId: wsList[0].external_id });
295
+ console.log(`Workspace: ${wsList[0].name} (${wsList[0].external_id})`);
296
+ } else if (wsList.length > 1) {
297
+ const choices = wsList.map((ws2) => {
298
+ const totalDocs = ws2.shared_document_count + ws2.private_document_count;
299
+ return {
300
+ name: `${ws2.name} (${totalDocs} docs)`,
301
+ value: ws2.external_id,
302
+ description: ws2.external_id
303
+ };
304
+ });
305
+ const selected = await promptSelect("Select workspace", choices);
306
+ updateConfig({ selectedWorkspaceId: selected });
307
+ const ws = wsList.find((w) => w.external_id === selected);
308
+ console.log(`Workspace: ${ws.name} (${selected})`);
309
+ }
310
+ } catch (err) {
311
+ const msg = err instanceof Error ? err.message : String(err);
312
+ console.error(`Login failed: ${msg}`);
313
+ console.error("You can log in later with: arbi login");
314
+ }
315
+ }
316
+ }
317
+ async function nonInteractiveRegister(config, opts) {
318
+ const email = opts.email || process.env.ARBI_EMAIL;
319
+ const password2 = opts.password || process.env.ARBI_PASSWORD;
320
+ const supportApiKey = process.env.SUPPORT_API_KEY;
321
+ if (!email) {
322
+ console.error("Email required. Use --email <email> or set ARBI_EMAIL");
323
+ process.exit(1);
324
+ }
325
+ if (!password2) {
326
+ console.error("Password required. Use --password <password> or set ARBI_PASSWORD");
327
+ process.exit(1);
328
+ }
329
+ if (!supportApiKey) {
330
+ console.error("SUPPORT_API_KEY env var is required for --non-interactive registration");
331
+ process.exit(1);
332
+ }
333
+ const arbi = sdk.createArbiClient({
334
+ baseUrl: config.baseUrl,
335
+ deploymentDomain: config.deploymentDomain,
336
+ credentials: "omit"
337
+ });
338
+ await arbi.crypto.initSodium();
339
+ const verifyResponse = await arbi.fetch.POST("/api/user/verify-email", {
340
+ body: { email }
341
+ });
342
+ if (verifyResponse.error) {
343
+ console.error(`verify-email failed: ${JSON.stringify(verifyResponse.error)}`);
344
+ process.exit(1);
345
+ }
346
+ const verificationCode = await getVerificationCode(email, supportApiKey);
347
+ try {
348
+ await arbi.auth.register({
349
+ email,
350
+ password: password2,
351
+ verificationCode,
352
+ firstName: opts.firstName ?? "Test",
353
+ lastName: opts.lastName ?? "User"
354
+ });
355
+ console.log(`Registered: ${email}`);
356
+ } catch (err) {
357
+ const msg = err instanceof Error ? err.message : String(err);
358
+ console.error(`Registration failed: ${msg}`);
359
+ process.exit(1);
360
+ }
361
+ }
362
+
363
+ // src/commands/logout.ts
364
+ function registerLogoutCommand(program2) {
365
+ program2.command("logout").description("Log out of ARBI").action(() => {
366
+ deleteCredentials();
367
+ updateConfig({ selectedWorkspaceId: void 0 });
368
+ console.log("Logged out.");
369
+ });
370
+ }
371
+
372
+ // src/commands/status.ts
373
+ function registerStatusCommand(program2) {
374
+ program2.command("status").description("Show current configuration and login status").action(() => {
375
+ const config = getConfig();
376
+ const creds = getCredentials();
377
+ if (!config?.baseUrl) {
378
+ console.log("Not configured. Run: arbi config set-url <url>");
379
+ return;
380
+ }
381
+ console.log(`Server: ${config.baseUrl}`);
382
+ if (creds) {
383
+ console.log(`User: ${creds.email}`);
384
+ } else {
385
+ console.log("User: (not logged in)");
386
+ }
387
+ if (config.selectedWorkspaceId) {
388
+ console.log(`Workspace: ${config.selectedWorkspaceId}`);
389
+ } else {
390
+ console.log("Workspace: (none selected)");
391
+ }
392
+ });
393
+ }
394
+ async function createAuthenticatedClient() {
395
+ const config = requireConfig();
396
+ const creds = requireCredentials();
397
+ const arbi = sdk.createArbiClient({
398
+ baseUrl: config.baseUrl,
399
+ deploymentDomain: config.deploymentDomain,
400
+ credentials: "omit"
401
+ });
402
+ await arbi.crypto.initSodium();
403
+ const signingPrivateKey = sdk.base64ToBytes(creds.signingPrivateKeyBase64);
404
+ const loginResult = await arbi.auth.loginWithKey({
405
+ email: creds.email,
406
+ signingPrivateKey
407
+ });
408
+ saveCredentials({
409
+ ...creds,
410
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey)
411
+ });
412
+ return { arbi, loginResult };
413
+ }
414
+ async function selectWorkspace(arbi, workspaceId, wrappedKey, serverSessionKey) {
415
+ const creds = requireCredentials();
416
+ const signingPrivateKey = sdk.base64ToBytes(creds.signingPrivateKeyBase64);
417
+ const ed25519PublicKey = signingPrivateKey.slice(32, 64);
418
+ const encryptionKeyPair = sdk.deriveEncryptionKeypairFromSigning({
419
+ publicKey: ed25519PublicKey,
420
+ secretKey: signingPrivateKey
421
+ });
422
+ const workspaceKey = sdk.sealedBoxDecrypt(wrappedKey, encryptionKeyPair.secretKey);
423
+ const header = await sdk.createWorkspaceKeyHeader(workspaceKey, serverSessionKey);
424
+ arbi.session.setSelectedWorkspace(workspaceId);
425
+ arbi.session.setCachedWorkspaceHeader(workspaceId, header);
426
+ }
427
+ async function selectWorkspaceById(arbi, workspaceId, serverSessionKey) {
428
+ const { data: workspaces, error } = await arbi.fetch.GET("/api/user/workspaces");
429
+ if (error || !workspaces) {
430
+ throw new Error("Failed to fetch workspaces");
431
+ }
432
+ const ws = workspaces.find((w) => w.external_id === workspaceId);
433
+ if (!ws || !ws.wrapped_key) {
434
+ throw new Error(`Workspace ${workspaceId} not found or has no encryption key`);
435
+ }
436
+ await selectWorkspace(arbi, ws.external_id, ws.wrapped_key, serverSessionKey);
437
+ return { external_id: ws.external_id, name: ws.name, wrapped_key: ws.wrapped_key };
438
+ }
439
+
440
+ // src/helpers.ts
441
+ function runAction(fn) {
442
+ return async () => {
443
+ try {
444
+ await fn();
445
+ } catch (err) {
446
+ const msg = err instanceof Error ? err.message : String(err);
447
+ console.error(`Error: ${msg}`);
448
+ process.exit(1);
449
+ }
450
+ };
451
+ }
452
+ function requireData(result, message) {
453
+ if (result.error || !result.data) {
454
+ console.error(message);
455
+ process.exit(1);
456
+ }
457
+ return result.data;
458
+ }
459
+ function requireOk(result, message) {
460
+ if (result.error) {
461
+ console.error(message);
462
+ process.exit(1);
463
+ }
464
+ }
465
+ async function resolveAuth() {
466
+ const config = requireConfig();
467
+ const { arbi, loginResult } = await createAuthenticatedClient();
468
+ return { arbi, loginResult, config };
469
+ }
470
+ async function resolveWorkspace(workspaceOpt) {
471
+ const config = requireConfig();
472
+ const workspaceId = workspaceOpt || config.selectedWorkspaceId;
473
+ if (!workspaceId) {
474
+ console.error("No workspace selected. Run: arbi workspace select <id>");
475
+ process.exit(1);
476
+ }
477
+ const { arbi, loginResult } = await createAuthenticatedClient();
478
+ await selectWorkspaceById(arbi, workspaceId, loginResult.serverSessionKey);
479
+ const accessToken = arbi.session.getState().accessToken;
480
+ const workspaceKeyHeader = arbi.session.getWorkspaceKeyHeader();
481
+ if (!accessToken || !workspaceKeyHeader) {
482
+ console.error("Authentication error \u2014 missing token or workspace key");
483
+ process.exit(1);
484
+ }
485
+ return { arbi, loginResult, config, workspaceId, accessToken, workspaceKeyHeader };
486
+ }
487
+ function printTable(columns, rows) {
488
+ console.log(columns.map((c) => c.header.padEnd(c.width)).join(""));
489
+ for (const row of rows) {
490
+ console.log(
491
+ columns.map((c) => {
492
+ const val = c.value(row);
493
+ return val.slice(0, c.width - 2).padEnd(c.width);
494
+ }).join("")
495
+ );
496
+ }
497
+ }
498
+ function parseJsonArg(input2, example) {
499
+ try {
500
+ return JSON.parse(input2);
501
+ } catch {
502
+ console.error(`Invalid JSON. Example: ${example}`);
503
+ process.exit(1);
504
+ }
505
+ }
506
+
507
+ // src/commands/workspaces.ts
508
+ function registerWorkspacesCommand(program2) {
509
+ program2.command("workspaces").description("List workspaces").action(
510
+ runAction(async () => {
511
+ const { arbi } = await resolveAuth();
512
+ const data = requireData(
513
+ await arbi.fetch.GET("/api/user/workspaces"),
514
+ "Failed to fetch workspaces"
515
+ );
516
+ if (data.length === 0) {
517
+ console.log("No workspaces found.");
518
+ return;
519
+ }
520
+ printTable(
521
+ [
522
+ { header: "ID", width: 24, value: (r) => r.external_id },
523
+ { header: "NAME", width: 30, value: (r) => r.name },
524
+ {
525
+ header: "DOCS",
526
+ width: 6,
527
+ value: (r) => String(r.shared_document_count + r.private_document_count)
528
+ },
529
+ {
530
+ header: "ROLE",
531
+ width: 10,
532
+ value: (r) => r.users?.[0]?.role ?? ""
533
+ }
534
+ ],
535
+ data
536
+ );
537
+ })
538
+ );
539
+ const workspace = program2.command("workspace").description("Workspace management");
540
+ workspace.command("select [id]").description("Select active workspace (interactive picker if no ID given)").action(
541
+ (id) => runAction(async () => {
542
+ const { arbi } = await resolveAuth();
543
+ const data = requireData(
544
+ await arbi.fetch.GET("/api/user/workspaces"),
545
+ "Failed to fetch workspaces"
546
+ );
547
+ if (data.length === 0) {
548
+ console.log("No workspaces found.");
549
+ return;
550
+ }
551
+ let selectedId;
552
+ if (id) {
553
+ const ws2 = data.find((w) => w.external_id === id);
554
+ if (!ws2) {
555
+ console.error(`Workspace ${id} not found.`);
556
+ console.error("Available workspaces:");
557
+ for (const w of data) console.error(` ${w.external_id} ${w.name}`);
558
+ process.exit(1);
559
+ }
560
+ selectedId = id;
561
+ } else {
562
+ const choices = data.map((ws2) => {
563
+ const totalDocs = ws2.shared_document_count + ws2.private_document_count;
564
+ return {
565
+ name: `${ws2.name} (${totalDocs} docs)`,
566
+ value: ws2.external_id,
567
+ description: ws2.external_id
568
+ };
569
+ });
570
+ selectedId = await promptSelect("Select workspace", choices);
571
+ }
572
+ updateConfig({ selectedWorkspaceId: selectedId });
573
+ const ws = data.find((w) => w.external_id === selectedId);
574
+ console.log(`Selected: ${ws.name} (${selectedId})`);
575
+ })()
576
+ );
577
+ workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).action(
578
+ (name, opts) => runAction(async () => {
579
+ const { arbi } = await resolveAuth();
580
+ const data = requireData(
581
+ await arbi.fetch.POST("/api/workspace/create_protected", {
582
+ body: { name, description: opts.description ?? null, is_public: opts.public ?? false }
583
+ }),
584
+ "Failed to create workspace"
585
+ );
586
+ console.log(`Created: ${data.name} (${data.external_id})`);
587
+ })()
588
+ );
589
+ workspace.command("delete <id>").description("Delete a workspace").action(
590
+ (id) => runAction(async () => {
591
+ const { arbi } = await resolveAuth();
592
+ const data = requireData(
593
+ await arbi.fetch.DELETE("/api/workspace/{workspace_ext_id}", {
594
+ params: { path: { workspace_ext_id: id } }
595
+ }),
596
+ "Failed to delete workspace"
597
+ );
598
+ console.log(data?.detail ?? `Deleted workspace ${id}`);
599
+ })()
600
+ );
601
+ workspace.command("update <id> <json>").description("Update workspace properties (pass JSON)").action(
602
+ (id, json) => runAction(async () => {
603
+ const body = parseJsonArg(json, `arbi workspace update wrk-123 '{"name": "New Name"}'`);
604
+ const { arbi } = await resolveWorkspace(id);
605
+ const data = requireData(
606
+ await arbi.fetch.PATCH("/api/workspace/{workspace_ext_id}", {
607
+ params: { path: { workspace_ext_id: id } },
608
+ body
609
+ }),
610
+ "Failed to update workspace"
611
+ );
612
+ console.log(`Updated: ${data.name} (${data.external_id})`);
613
+ })()
614
+ );
615
+ workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
616
+ (opts) => runAction(async () => {
617
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
618
+ const data = requireData(
619
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/users", {
620
+ params: { path: { workspace_ext_id: workspaceId } }
621
+ }),
622
+ "Failed to fetch workspace users"
623
+ );
624
+ if (data.length === 0) {
625
+ console.log("No users found.");
626
+ return;
627
+ }
628
+ printTable(
629
+ [
630
+ {
631
+ header: "USER_ID",
632
+ width: 16,
633
+ value: (r) => r.user?.external_id ?? ""
634
+ },
635
+ {
636
+ header: "NAME",
637
+ width: 20,
638
+ value: (r) => {
639
+ const u = r.user;
640
+ return [u?.given_name, u?.family_name].filter(Boolean).join(" ") || "";
641
+ }
642
+ },
643
+ {
644
+ header: "EMAIL",
645
+ width: 30,
646
+ value: (r) => r.user?.email ?? ""
647
+ },
648
+ { header: "ROLE", width: 14, value: (r) => r.role },
649
+ { header: "DOCS", width: 6, value: (r) => String(r.document_count) },
650
+ { header: "CONVS", width: 6, value: (r) => String(r.conversation_count) }
651
+ ],
652
+ data
653
+ );
654
+ })()
655
+ );
656
+ workspace.command("add-user <emails...>").description("Add users to the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-r, --role <role>", "Role: owner, collaborator, guest", "collaborator").action(
657
+ (emails, opts) => runAction(async () => {
658
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
659
+ const role = opts.role ?? "collaborator";
660
+ const data = requireData(
661
+ await arbi.fetch.POST("/api/workspace/{workspace_ext_id}/users", {
662
+ params: { path: { workspace_ext_id: workspaceId } },
663
+ body: { emails, role }
664
+ }),
665
+ "Failed to add users"
666
+ );
667
+ for (const u of data) console.log(`Added: ${u.user.email} as ${u.role}`);
668
+ })()
669
+ );
670
+ workspace.command("remove-user <user-ids...>").description("Remove users from the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
671
+ (userIds, opts) => runAction(async () => {
672
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
673
+ requireOk(
674
+ await arbi.fetch.DELETE("/api/workspace/{workspace_ext_id}/users", {
675
+ params: { path: { workspace_ext_id: workspaceId } },
676
+ body: { users: userIds.map((id) => ({ user_ext_id: id })) }
677
+ }),
678
+ "Failed to remove users"
679
+ );
680
+ console.log(`Removed ${userIds.length} user(s).`);
681
+ })()
682
+ );
683
+ workspace.command("set-role <role> <user-ids...>").description("Update user roles (owner, collaborator, guest)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
684
+ (role, userIds, opts) => runAction(async () => {
685
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
686
+ const data = requireData(
687
+ await arbi.fetch.PATCH("/api/workspace/{workspace_ext_id}/users", {
688
+ params: { path: { workspace_ext_id: workspaceId } },
689
+ body: { user_ext_ids: userIds, role }
690
+ }),
691
+ "Failed to update roles"
692
+ );
693
+ for (const u of data) console.log(`Updated: ${u.user.email} \u2192 ${u.role}`);
694
+ })()
695
+ );
696
+ workspace.command("copy <target-workspace-id> <doc-ids...>").description("Copy documents to another workspace").option("-w, --workspace <id>", "Source workspace ID (defaults to selected workspace)").action(
697
+ (targetId, docIds, opts) => runAction(async () => {
698
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
699
+ const data = requireData(
700
+ await arbi.fetch.POST("/api/workspace/{workspace_ext_id}/copy", {
701
+ params: { path: { workspace_ext_id: workspaceId } },
702
+ body: { target_workspace_ext_id: targetId, items: docIds }
703
+ }),
704
+ "Failed to copy documents"
705
+ );
706
+ console.log(`${data.detail} (${data.documents_copied} document(s) copied)`);
707
+ })()
708
+ );
709
+ }
710
+
711
+ // src/commands/docs.ts
712
+ function formatSize(bytes) {
713
+ if (!bytes) return "-";
714
+ if (bytes < 1024) return `${bytes} B`;
715
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
716
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
717
+ }
718
+ async function fetchDocChoices(arbi, workspaceId) {
719
+ const data = requireData(
720
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/documents", {
721
+ params: { path: { workspace_ext_id: workspaceId } }
722
+ }),
723
+ "Failed to fetch documents"
724
+ );
725
+ if (data.length === 0) {
726
+ console.log("No documents found.");
727
+ process.exit(0);
728
+ }
729
+ return data.map((d) => ({
730
+ name: `${d.file_name ?? "Unnamed"} (${d.status})`,
731
+ value: d.external_id,
732
+ description: d.external_id
733
+ }));
734
+ }
735
+ function registerDocsCommand(program2) {
736
+ program2.command("docs").description("List documents in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
737
+ (opts) => runAction(async () => {
738
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
739
+ const data = requireData(
740
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/documents", {
741
+ params: { path: { workspace_ext_id: workspaceId } }
742
+ }),
743
+ "Failed to fetch documents"
744
+ );
745
+ if (data.length === 0) {
746
+ console.log("No documents found.");
747
+ return;
748
+ }
749
+ printTable(
750
+ [
751
+ { header: "ID", width: 24, value: (r) => r.external_id },
752
+ { header: "NAME", width: 36, value: (r) => r.file_name ?? "Unnamed" },
753
+ { header: "STATUS", width: 14, value: (r) => r.status ?? "" },
754
+ { header: "SIZE", width: 10, value: (r) => formatSize(r.file_size) }
755
+ ],
756
+ data
757
+ );
758
+ })()
759
+ );
760
+ const doc = program2.command("doc").description("Document management");
761
+ doc.command("get [ids...]").description("Get document details (interactive picker if no IDs given)").action(
762
+ (ids) => runAction(async () => {
763
+ const { arbi, workspaceId } = await resolveWorkspace();
764
+ let docIds = ids && ids.length > 0 ? ids : void 0;
765
+ if (!docIds) {
766
+ const choices = await fetchDocChoices(arbi, workspaceId);
767
+ const selected = await promptCheckbox("Select documents", choices);
768
+ if (selected.length === 0) return;
769
+ docIds = selected;
770
+ }
771
+ const data = requireData(
772
+ await arbi.fetch.GET("/api/document/", { params: { query: { external_ids: docIds } } }),
773
+ "Failed to fetch documents"
774
+ );
775
+ console.log(JSON.stringify(data, null, 2));
776
+ })()
777
+ );
778
+ doc.command("delete [ids...]").description("Delete documents (interactive picker if no IDs given)").action(
779
+ (ids) => runAction(async () => {
780
+ const { arbi, workspaceId } = await resolveWorkspace();
781
+ let docIds = ids && ids.length > 0 ? ids : void 0;
782
+ if (!docIds) {
783
+ const choices = await fetchDocChoices(arbi, workspaceId);
784
+ const selected = await promptCheckbox("Select documents to delete", choices);
785
+ if (selected.length === 0) return;
786
+ docIds = selected;
787
+ }
788
+ requireOk(
789
+ await arbi.fetch.DELETE("/api/document/", { body: { external_ids: docIds } }),
790
+ "Failed to delete documents"
791
+ );
792
+ console.log(`Deleted ${docIds.length} document(s).`);
793
+ })()
794
+ );
795
+ doc.command("update [json]").description("Update documents (interactive form if no JSON given)").action(
796
+ (json) => runAction(async () => {
797
+ if (json) {
798
+ const parsed = parseJsonArg(json, `arbi doc update '[{"external_id": "doc-123", "shared": true}]'`);
799
+ const body = Array.isArray(parsed) ? { documents: parsed } : parsed;
800
+ const { arbi } = await resolveWorkspace();
801
+ const data = requireData(
802
+ await arbi.fetch.PATCH("/api/document/", { body }),
803
+ "Failed to update documents"
804
+ );
805
+ console.log(`Updated ${data.length} document(s).`);
806
+ } else {
807
+ const { arbi, workspaceId } = await resolveWorkspace();
808
+ const choices = await fetchDocChoices(arbi, workspaceId);
809
+ const docId = await promptSearch("Select document to update", choices);
810
+ const field = await promptSelect("Field to update", [
811
+ { name: "Title", value: "title" },
812
+ { name: "Shared", value: "shared" },
813
+ { name: "Author", value: "author" },
814
+ { name: "Abstract", value: "abstract" },
815
+ { name: "Date", value: "date" }
816
+ ]);
817
+ let value;
818
+ if (field === "shared") {
819
+ value = await promptSelect("Shared?", [
820
+ { name: "Yes", value: true },
821
+ { name: "No", value: false }
822
+ ]);
823
+ } else if (field === "date") {
824
+ value = await promptInput("Date (YYYY-MM-DD)", false) || null;
825
+ } else {
826
+ value = await promptInput(`${field.charAt(0).toUpperCase() + field.slice(1)}`);
827
+ }
828
+ const data = requireData(
829
+ await arbi.fetch.PATCH("/api/document/", {
830
+ body: { documents: [{ external_id: docId, [field]: value }] }
831
+ }),
832
+ "Failed to update document"
833
+ );
834
+ console.log(`Updated ${data.length} document(s).`);
835
+ }
836
+ })()
837
+ );
838
+ 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(
839
+ (urls, opts) => runAction(async () => {
840
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
841
+ const data = requireData(
842
+ await arbi.fetch.POST("/api/document/upload-url", {
843
+ params: {
844
+ query: { urls, workspace_ext_id: workspaceId, shared: opts.shared ?? false }
845
+ }
846
+ }),
847
+ "Failed to upload from URLs"
848
+ );
849
+ console.log(`Uploaded: ${data.doc_ext_ids.join(", ")}`);
850
+ if (data.duplicates && data.duplicates.length > 0) {
851
+ console.log(`Duplicates: ${data.duplicates.join(", ")}`);
852
+ }
853
+ })()
854
+ );
855
+ doc.command("parsed [doc-id] [stage]").description("Get parsed document content (stage: marker, subchunk, final)").action(
856
+ (docId, stage) => runAction(async () => {
857
+ const { arbi, config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace();
858
+ if (!docId) {
859
+ const choices = await fetchDocChoices(arbi, workspaceId);
860
+ docId = await promptSearch("Select document", choices);
861
+ }
862
+ const validStages = ["marker", "subchunk", "final"];
863
+ if (!stage) {
864
+ stage = await promptSelect("Parsing stage", [
865
+ { name: "Final (processed)", value: "final" },
866
+ { name: "Subchunk", value: "subchunk" },
867
+ { name: "Marker (raw)", value: "marker" }
868
+ ]);
869
+ } else if (!validStages.includes(stage)) {
870
+ console.error(`Invalid stage: ${stage}. Must be one of: ${validStages.join(", ")}`);
871
+ process.exit(1);
872
+ }
873
+ const res = await fetch(`${config.baseUrl}/api/document/${docId}/parsed-${stage}`, {
874
+ headers: { Authorization: `Bearer ${accessToken}`, "workspace-key": workspaceKeyHeader }
875
+ });
876
+ if (!res.ok) {
877
+ const body = await res.text().catch(() => "");
878
+ console.error(`Failed: ${res.status} ${body}`);
879
+ process.exit(1);
880
+ }
881
+ console.log(JSON.stringify(await res.json(), null, 2));
882
+ })()
883
+ );
884
+ }
885
+ var AUTH_TIMEOUT_MS = 1e4;
886
+ function connectWebSocket(options) {
887
+ const { baseUrl, accessToken, onMessage, onClose } = options;
888
+ const url = sdk.buildWebSocketUrl(baseUrl);
889
+ return new Promise((resolve, reject) => {
890
+ const ws = new WebSocket(url);
891
+ let authenticated = false;
892
+ const timeout = setTimeout(() => {
893
+ if (!authenticated) {
894
+ ws.close();
895
+ reject(new Error("WebSocket auth timed out"));
896
+ }
897
+ }, AUTH_TIMEOUT_MS);
898
+ ws.addEventListener("open", () => {
899
+ ws.send(sdk.createAuthMessage(accessToken));
900
+ });
901
+ ws.addEventListener("message", (event) => {
902
+ const data = typeof event.data === "string" ? event.data : String(event.data);
903
+ const msg = sdk.parseServerMessage(data);
904
+ if (!msg) return;
905
+ if (!authenticated) {
906
+ if (sdk.isMessageType(msg, "auth_result")) {
907
+ clearTimeout(timeout);
908
+ if (msg.success) {
909
+ authenticated = true;
910
+ resolve({ close: () => ws.close() });
911
+ } else {
912
+ ws.close();
913
+ reject(new Error(`WebSocket auth failed: ${msg.reason || "unknown"}`));
914
+ }
915
+ }
916
+ return;
917
+ }
918
+ onMessage(msg);
919
+ });
920
+ ws.addEventListener("close", (event) => {
921
+ clearTimeout(timeout);
922
+ if (!authenticated) {
923
+ reject(new Error(`WebSocket closed before auth (code ${event.code})`));
924
+ return;
925
+ }
926
+ onClose?.(event.code, event.reason);
927
+ });
928
+ ws.addEventListener("error", () => {
929
+ });
930
+ });
931
+ }
932
+
933
+ // src/commands/upload.ts
934
+ function registerUploadCommand(program2) {
935
+ program2.command("upload <files...>").description("Upload documents to the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-W, --watch", "Watch document processing progress after upload").action(
936
+ (files, opts) => runAction(async () => {
937
+ for (const f of files) {
938
+ if (!fs__default.default.existsSync(f)) {
939
+ console.error(`File not found: ${f}`);
940
+ process.exit(1);
941
+ }
942
+ }
943
+ const { config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace(
944
+ opts.workspace
945
+ );
946
+ const uploadedDocs = /* @__PURE__ */ new Map();
947
+ for (const filePath of files) {
948
+ const fileBuffer = fs__default.default.readFileSync(filePath);
949
+ const fileName = path__default.default.basename(filePath);
950
+ const formData = new FormData();
951
+ formData.append("files", new Blob([fileBuffer]), fileName);
952
+ const res = await fetch(
953
+ `${config.baseUrl}/api/document/upload?workspace_ext_id=${workspaceId}`,
954
+ {
955
+ method: "POST",
956
+ headers: {
957
+ Authorization: `Bearer ${accessToken}`,
958
+ "workspace-key": workspaceKeyHeader
959
+ },
960
+ body: formData
961
+ }
962
+ );
963
+ if (!res.ok) {
964
+ const body = await res.text().catch(() => "");
965
+ console.error(`Upload failed for ${fileName}: ${res.status} ${body}`);
966
+ process.exit(1);
967
+ }
968
+ const result = await res.json();
969
+ console.log(`Uploaded: ${fileName} (${result.doc_ext_ids.join(", ")})`);
970
+ if (result.duplicates && result.duplicates.length > 0) {
971
+ console.log(` Duplicates: ${result.duplicates.join(", ")}`);
972
+ }
973
+ for (const id of result.doc_ext_ids) uploadedDocs.set(id, fileName);
974
+ }
975
+ if (opts.watch && uploadedDocs.size > 0) {
976
+ const pending = new Set(uploadedDocs.keys());
977
+ console.log(`
978
+ Watching ${pending.size} document(s)...`);
979
+ const conn = await connectWebSocket({
980
+ baseUrl: config.baseUrl,
981
+ accessToken,
982
+ onMessage: (msg) => {
983
+ if (sdk.isMessageType(msg, "task_update")) {
984
+ if (!pending.has(msg.doc_ext_id)) return;
985
+ console.log(
986
+ ` ${uploadedDocs.get(msg.doc_ext_id) || msg.file_name}: ${msg.status} (${msg.progress}%)`
987
+ );
988
+ if (msg.status === "completed" || msg.status === "failed") {
989
+ pending.delete(msg.doc_ext_id);
990
+ if (pending.size === 0) {
991
+ console.log("\nAll documents processed.");
992
+ conn.close();
993
+ }
994
+ }
995
+ }
996
+ },
997
+ onClose: () => {
998
+ if (pending.size > 0)
999
+ console.log(`
1000
+ Connection closed. ${pending.size} document(s) still processing.`);
1001
+ }
1002
+ });
1003
+ }
1004
+ })()
1005
+ );
1006
+ }
1007
+ function registerDownloadCommand(program2) {
1008
+ 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(
1009
+ (docId, opts) => runAction(async () => {
1010
+ const { arbi, config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace(opts.workspace);
1011
+ if (!docId) {
1012
+ const data = requireData(
1013
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/documents", {
1014
+ params: { path: { workspace_ext_id: workspaceId } }
1015
+ }),
1016
+ "Failed to fetch documents"
1017
+ );
1018
+ if (data.length === 0) {
1019
+ console.log("No documents found.");
1020
+ return;
1021
+ }
1022
+ const choices = data.map((d) => ({
1023
+ name: `${d.file_name ?? "Unnamed"} (${d.status})`,
1024
+ value: d.external_id,
1025
+ description: d.external_id
1026
+ }));
1027
+ docId = await promptSearch("Select document to download", choices);
1028
+ }
1029
+ const res = await fetch(`${config.baseUrl}/api/document/${docId}/download`, {
1030
+ headers: { Authorization: `Bearer ${accessToken}`, "workspace-key": workspaceKeyHeader }
1031
+ });
1032
+ if (!res.ok) {
1033
+ const body = await res.text().catch(() => "");
1034
+ console.error(`Download failed: ${res.status} ${body}`);
1035
+ process.exit(1);
1036
+ }
1037
+ let filename = `${docId}`;
1038
+ const disposition = res.headers.get("content-disposition");
1039
+ if (disposition) {
1040
+ const match = disposition.match(/filename[*]?=(?:UTF-8''|"?)([^";]+)/i);
1041
+ if (match) filename = decodeURIComponent(match[1].replace(/"/g, ""));
1042
+ }
1043
+ const outputPath = opts.output || path__default.default.join(process.cwd(), filename);
1044
+ const buffer = Buffer.from(await res.arrayBuffer());
1045
+ fs__default.default.writeFileSync(outputPath, buffer);
1046
+ console.log(
1047
+ `Downloaded: ${path__default.default.basename(outputPath)} (${(buffer.length / (1024 * 1024)).toFixed(1)} MB)`
1048
+ );
1049
+ })()
1050
+ );
1051
+ }
1052
+
1053
+ // src/commands/ask.ts
1054
+ function parseSSEEvents(chunk, buffer) {
1055
+ const combined = buffer + chunk;
1056
+ const events = [];
1057
+ const parts = combined.split("\n\n");
1058
+ const remaining = parts.pop() || "";
1059
+ for (const part of parts) {
1060
+ if (!part.trim()) continue;
1061
+ let eventType = "";
1062
+ let eventData = "";
1063
+ for (const line of part.split("\n")) {
1064
+ if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1065
+ else if (line.startsWith("data: ")) eventData = line.slice(6);
1066
+ }
1067
+ if (eventType && eventData) events.push({ event: eventType, data: eventData });
1068
+ }
1069
+ return { events, remaining };
1070
+ }
1071
+ function registerAskCommand(program2) {
1072
+ program2.command("ask <question>").description("Ask the RAG assistant a question").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-c, --config <id>", "Config ext_id to use (e.g. cfg-xxx)").option("-n, --new", "Start a new conversation (ignore previous context)").option("-v, --verbose", "Show agent steps and tool calls").action(
1073
+ (question, opts) => runAction(async () => {
1074
+ const { arbi, config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace(opts.workspace);
1075
+ const session = getChatSession();
1076
+ if (opts.new) {
1077
+ clearChatSession();
1078
+ session.lastMessageExtId = null;
1079
+ }
1080
+ const docs = requireData(
1081
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/documents", {
1082
+ params: { path: { workspace_ext_id: workspaceId } }
1083
+ }),
1084
+ "Failed to fetch workspace documents"
1085
+ );
1086
+ const docIds = docs.map((d) => d.external_id);
1087
+ const body = {
1088
+ content: question,
1089
+ workspace_ext_id: workspaceId,
1090
+ tools: {
1091
+ retrieval_chunk: {
1092
+ name: "retrieval_chunk",
1093
+ description: "retrieval chunk",
1094
+ tool_args: { doc_ext_ids: docIds },
1095
+ tool_responses: {}
1096
+ },
1097
+ retrieval_full_context: {
1098
+ name: "retrieval_full_context",
1099
+ description: "retrieval full_context",
1100
+ tool_args: { doc_ext_ids: [] },
1101
+ tool_responses: {}
1102
+ }
1103
+ }
1104
+ };
1105
+ if (session.lastMessageExtId) {
1106
+ body.parent_message_ext_id = session.lastMessageExtId;
1107
+ }
1108
+ if (opts.config) {
1109
+ body.config_ext_id = opts.config;
1110
+ }
1111
+ const res = await fetch(`${config.baseUrl}/api/assistant/query`, {
1112
+ method: "POST",
1113
+ headers: {
1114
+ Authorization: `Bearer ${accessToken}`,
1115
+ "workspace-key": workspaceKeyHeader,
1116
+ "Content-Type": "application/json"
1117
+ },
1118
+ body: JSON.stringify(body)
1119
+ });
1120
+ if (!res.ok) {
1121
+ if (res.status === 503)
1122
+ console.error(
1123
+ "The selected LLM is not responding. Please retry or select another model."
1124
+ );
1125
+ else if (res.status === 401) console.error("Token has expired. Please run: arbi login");
1126
+ else console.error(`Query failed: ${res.status} ${await res.text().catch(() => "")}`);
1127
+ process.exit(1);
1128
+ }
1129
+ if (!res.body) {
1130
+ console.error("No response body");
1131
+ process.exit(1);
1132
+ }
1133
+ const reader = res.body.getReader();
1134
+ const decoder = new TextDecoder("utf-8");
1135
+ let sseBuffer = "";
1136
+ let assistantMessageExtId = null;
1137
+ const processEvents = (events) => {
1138
+ for (const { event, data } of events) {
1139
+ try {
1140
+ if (event === "stream_start") {
1141
+ const parsed = JSON.parse(data);
1142
+ if (parsed.assistant_message_ext_id) {
1143
+ assistantMessageExtId = parsed.assistant_message_ext_id;
1144
+ }
1145
+ } else if (event === "agent_step") {
1146
+ if (opts.verbose) {
1147
+ const parsed = JSON.parse(data);
1148
+ const focus = parsed.focus || parsed.status || "";
1149
+ console.error(`
1150
+ [agent] ${focus}`);
1151
+ }
1152
+ } else if (event === "token") {
1153
+ const parsed = JSON.parse(data);
1154
+ if (parsed.content) process.stdout.write(parsed.content);
1155
+ } else if (event === "error") {
1156
+ const parsed = JSON.parse(data);
1157
+ console.error(`
1158
+ Error: ${parsed.message || "Unknown streaming error"}`);
1159
+ }
1160
+ } catch {
1161
+ }
1162
+ }
1163
+ };
1164
+ while (true) {
1165
+ const { done, value } = await reader.read();
1166
+ if (done) break;
1167
+ const { events, remaining } = parseSSEEvents(
1168
+ decoder.decode(value, { stream: true }),
1169
+ sseBuffer
1170
+ );
1171
+ sseBuffer = remaining;
1172
+ processEvents(events);
1173
+ }
1174
+ if (sseBuffer.trim()) {
1175
+ const { events } = parseSSEEvents(sseBuffer + "\n\n", "");
1176
+ processEvents(events);
1177
+ }
1178
+ process.stdout.write("\n");
1179
+ if (assistantMessageExtId) {
1180
+ updateChatSession({ lastMessageExtId: assistantMessageExtId });
1181
+ }
1182
+ })()
1183
+ );
1184
+ }
1185
+ function timestamp() {
1186
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
1187
+ }
1188
+ function registerWatchCommand(program2) {
1189
+ program2.command("watch").description("Watch workspace activity in real time").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
1190
+ (opts) => runAction(async () => {
1191
+ const { config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace);
1192
+ console.log(`Watching workspace ${workspaceId}... (Ctrl+C to stop)`);
1193
+ await connectWebSocket({
1194
+ baseUrl: config.baseUrl,
1195
+ accessToken,
1196
+ onMessage: (msg) => {
1197
+ if (sdk.isMessageType(msg, "task_update")) {
1198
+ console.log(
1199
+ `[${timestamp()}] task_update: ${msg.file_name} - ${msg.status} (${msg.progress}%)`
1200
+ );
1201
+ } else if (sdk.isMessageType(msg, "batch_complete")) {
1202
+ console.log(
1203
+ `[${timestamp()}] batch_complete: ${msg.batch_type} - ${msg.doc_ext_ids.length} docs`
1204
+ );
1205
+ } else if (sdk.isMessageType(msg, "error")) {
1206
+ console.log(`[${timestamp()}] error: ${msg.message}`);
1207
+ } else {
1208
+ console.log(`[${timestamp()}] ${msg.type}: ${JSON.stringify(msg)}`);
1209
+ }
1210
+ },
1211
+ onClose: (code, reason) => {
1212
+ console.log(`
1213
+ Connection closed (code ${code}${reason ? ": " + reason : ""})`);
1214
+ }
1215
+ });
1216
+ })()
1217
+ );
1218
+ }
1219
+
1220
+ // src/commands/contacts.ts
1221
+ function registerContactsCommand(program2) {
1222
+ const contacts = program2.command("contacts").description("Manage contacts");
1223
+ contacts.command("list").description("List all contacts").action(
1224
+ runAction(async () => {
1225
+ const { arbi } = await resolveAuth();
1226
+ const data = requireData(
1227
+ await arbi.fetch.GET("/api/user/contacts"),
1228
+ "Failed to fetch contacts"
1229
+ );
1230
+ if (data.length === 0) {
1231
+ console.log("No contacts found.");
1232
+ return;
1233
+ }
1234
+ printTable(
1235
+ [
1236
+ { header: "ID", width: 16, value: (r) => r.external_id },
1237
+ {
1238
+ header: "NAME",
1239
+ width: 20,
1240
+ value: (r) => {
1241
+ const u = r.user;
1242
+ return u ? [u.given_name, u.family_name].filter(Boolean).join(" ") : "";
1243
+ }
1244
+ },
1245
+ { header: "EMAIL", width: 30, value: (r) => r.email },
1246
+ { header: "STATUS", width: 18, value: (r) => r.status }
1247
+ ],
1248
+ data
1249
+ );
1250
+ })
1251
+ );
1252
+ contacts.command("add [emails...]").description("Add contacts by email (prompt if no emails given)").action(
1253
+ (emails) => runAction(async () => {
1254
+ const { arbi } = await resolveAuth();
1255
+ if (!emails || emails.length === 0) {
1256
+ const input2 = await promptInput("Email address(es), comma-separated");
1257
+ emails = input2.split(",").map((e) => e.trim()).filter(Boolean);
1258
+ if (emails.length === 0) return;
1259
+ }
1260
+ const data = requireData(
1261
+ await arbi.fetch.POST("/api/user/contacts", { body: { emails } }),
1262
+ "Failed to add contacts"
1263
+ );
1264
+ for (const c of data) {
1265
+ console.log(`Added: ${c.email} (${c.external_id}) \u2014 ${c.status}`);
1266
+ }
1267
+ })()
1268
+ );
1269
+ contacts.command("remove [ids...]").description("Remove contacts (interactive picker if no IDs given)").action(
1270
+ (ids) => runAction(async () => {
1271
+ const { arbi } = await resolveAuth();
1272
+ let contactIds = ids && ids.length > 0 ? ids : void 0;
1273
+ if (!contactIds) {
1274
+ const data = requireData(
1275
+ await arbi.fetch.GET("/api/user/contacts"),
1276
+ "Failed to fetch contacts"
1277
+ );
1278
+ if (data.length === 0) {
1279
+ console.log("No contacts found.");
1280
+ return;
1281
+ }
1282
+ contactIds = await promptCheckbox(
1283
+ "Select contacts to remove",
1284
+ data.map((c) => {
1285
+ const u = c.user;
1286
+ const name = u ? [u.given_name, u.family_name].filter(Boolean).join(" ") : "";
1287
+ return {
1288
+ name: name ? `${name} (${c.email})` : c.email,
1289
+ value: c.external_id
1290
+ };
1291
+ })
1292
+ );
1293
+ if (contactIds.length === 0) return;
1294
+ }
1295
+ requireOk(
1296
+ await arbi.fetch.DELETE("/api/user/contacts", { body: { contact_ids: contactIds } }),
1297
+ "Failed to remove contacts"
1298
+ );
1299
+ console.log(`Removed ${contactIds.length} contact(s).`);
1300
+ })()
1301
+ );
1302
+ contacts.action(async () => {
1303
+ await contacts.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
1304
+ });
1305
+ }
1306
+
1307
+ // src/commands/dm.ts
1308
+ function registerDmCommand(program2) {
1309
+ const dm = program2.command("dm").description("Direct messages");
1310
+ dm.command("list").description("List all DMs and notifications").action(
1311
+ runAction(async () => {
1312
+ const { arbi } = await resolveAuth();
1313
+ const data = requireData(
1314
+ await arbi.fetch.GET("/api/notifications/"),
1315
+ "Failed to fetch messages"
1316
+ );
1317
+ if (data.length === 0) {
1318
+ console.log("No messages found.");
1319
+ return;
1320
+ }
1321
+ printTable(
1322
+ [
1323
+ { header: "ID", width: 16, value: (r) => r.external_id },
1324
+ { header: "TYPE", width: 18, value: (r) => r.type },
1325
+ {
1326
+ header: "FROM",
1327
+ width: 22,
1328
+ value: (r) => {
1329
+ const s = r.sender;
1330
+ if (!s) return "";
1331
+ const name = [s.given_name, s.family_name].filter(Boolean).join(" ");
1332
+ return name || s.email || "";
1333
+ }
1334
+ },
1335
+ { header: "READ", width: 6, value: (r) => r.read ? "yes" : "no" },
1336
+ { header: "CONTENT", width: 40, value: (r) => r.content ?? "" }
1337
+ ],
1338
+ data
1339
+ );
1340
+ })
1341
+ );
1342
+ dm.command("send [recipient] [content]").description("Send a DM (interactive if no args; recipient can be email or user ext_id)").action(
1343
+ (recipient, content) => runAction(async () => {
1344
+ const { arbi } = await resolveAuth();
1345
+ if (!recipient) {
1346
+ const contacts = requireData(
1347
+ await arbi.fetch.GET("/api/user/contacts"),
1348
+ "Failed to fetch contacts"
1349
+ );
1350
+ if (contacts.length === 0) {
1351
+ console.error("No contacts found. Add contacts first: arbi contacts add <email>");
1352
+ process.exit(1);
1353
+ }
1354
+ recipient = await promptSelect(
1355
+ "Send to",
1356
+ contacts.map((c) => {
1357
+ const u = c.user;
1358
+ const name = u ? [u.given_name, u.family_name].filter(Boolean).join(" ") : "";
1359
+ return {
1360
+ name: name ? `${name} (${c.email})` : c.email,
1361
+ value: u?.external_id ?? c.external_id,
1362
+ description: c.email
1363
+ };
1364
+ })
1365
+ );
1366
+ }
1367
+ if (!content) {
1368
+ content = await promptInput("Message");
1369
+ }
1370
+ let recipientExtId = recipient;
1371
+ if (recipient.includes("@")) {
1372
+ const contacts = requireData(
1373
+ await arbi.fetch.GET("/api/user/contacts"),
1374
+ "Failed to fetch contacts"
1375
+ );
1376
+ const match = contacts.find((c) => c.email === recipient);
1377
+ if (!match) {
1378
+ console.error(`No contact found with email: ${recipient}`);
1379
+ console.error("Add them first: arbi contacts add " + recipient);
1380
+ process.exit(1);
1381
+ }
1382
+ recipientExtId = match.user?.external_id ?? match.external_id;
1383
+ }
1384
+ const data = requireData(
1385
+ await arbi.fetch.POST("/api/notifications/", {
1386
+ body: { messages: [{ recipient_ext_id: recipientExtId, content }] }
1387
+ }),
1388
+ "Failed to send message"
1389
+ );
1390
+ for (const n of data) {
1391
+ console.log(`Sent: ${n.external_id} \u2192 ${n.recipient.email}`);
1392
+ }
1393
+ })()
1394
+ );
1395
+ dm.command("read [ids...]").description("Mark messages as read (interactive picker if no IDs given)").action(
1396
+ (ids) => runAction(async () => {
1397
+ const { arbi } = await resolveAuth();
1398
+ let msgIds = ids && ids.length > 0 ? ids : void 0;
1399
+ if (!msgIds) {
1400
+ const data2 = requireData(
1401
+ await arbi.fetch.GET("/api/notifications/"),
1402
+ "Failed to fetch messages"
1403
+ );
1404
+ const unread = data2.filter((m) => !m.read);
1405
+ if (unread.length === 0) {
1406
+ console.log("No unread messages.");
1407
+ return;
1408
+ }
1409
+ msgIds = await promptCheckbox(
1410
+ "Select messages to mark as read",
1411
+ unread.map((m) => {
1412
+ const s = m.sender;
1413
+ const from = s ? [s.given_name, s.family_name].filter(Boolean).join(" ") || s.email || "" : "";
1414
+ return {
1415
+ name: `${from}: ${(m.content ?? "").slice(0, 50)}`,
1416
+ value: m.external_id
1417
+ };
1418
+ })
1419
+ );
1420
+ if (msgIds.length === 0) return;
1421
+ }
1422
+ const data = requireData(
1423
+ await arbi.fetch.PATCH("/api/notifications/", {
1424
+ body: { updates: msgIds.map((id) => ({ external_id: id, read: true })) }
1425
+ }),
1426
+ "Failed to update messages"
1427
+ );
1428
+ console.log(`Marked ${data.length} message(s) as read.`);
1429
+ })()
1430
+ );
1431
+ dm.command("delete [ids...]").description("Delete messages (interactive picker if no IDs given)").action(
1432
+ (ids) => runAction(async () => {
1433
+ const { arbi } = await resolveAuth();
1434
+ let msgIds = ids && ids.length > 0 ? ids : void 0;
1435
+ if (!msgIds) {
1436
+ const data = requireData(
1437
+ await arbi.fetch.GET("/api/notifications/"),
1438
+ "Failed to fetch messages"
1439
+ );
1440
+ if (data.length === 0) {
1441
+ console.log("No messages found.");
1442
+ return;
1443
+ }
1444
+ msgIds = await promptCheckbox(
1445
+ "Select messages to delete",
1446
+ data.map((m) => {
1447
+ const s = m.sender;
1448
+ const from = s ? [s.given_name, s.family_name].filter(Boolean).join(" ") || s.email || "" : "";
1449
+ return {
1450
+ name: `${from}: ${(m.content ?? "").slice(0, 50)}`,
1451
+ value: m.external_id
1452
+ };
1453
+ })
1454
+ );
1455
+ if (msgIds.length === 0) return;
1456
+ }
1457
+ requireOk(
1458
+ await arbi.fetch.DELETE("/api/notifications/", { body: { external_ids: msgIds } }),
1459
+ "Failed to delete messages"
1460
+ );
1461
+ console.log(`Deleted ${msgIds.length} message(s).`);
1462
+ })()
1463
+ );
1464
+ dm.action(async () => {
1465
+ await dm.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
1466
+ });
1467
+ }
1468
+
1469
+ // src/commands/tags.ts
1470
+ async function fetchTagChoices(arbi, workspaceId) {
1471
+ const data = requireData(
1472
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/tags", {
1473
+ params: { path: { workspace_ext_id: workspaceId } }
1474
+ }),
1475
+ "Failed to fetch tags"
1476
+ );
1477
+ if (data.length === 0) {
1478
+ console.log("No tags found.");
1479
+ process.exit(0);
1480
+ }
1481
+ return {
1482
+ data,
1483
+ choices: data.map((t) => ({
1484
+ name: `${t.name} (${t.tag_type?.type ?? "unknown"}, ${t.doctag_count} docs)`,
1485
+ value: t.external_id,
1486
+ description: t.external_id
1487
+ }))
1488
+ };
1489
+ }
1490
+ function registerTagsCommand(program2) {
1491
+ const tags = program2.command("tags").description("Manage tags");
1492
+ tags.command("list").description("List tags in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
1493
+ (opts) => runAction(async () => {
1494
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
1495
+ const data = requireData(
1496
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/tags", {
1497
+ params: { path: { workspace_ext_id: workspaceId } }
1498
+ }),
1499
+ "Failed to fetch tags"
1500
+ );
1501
+ if (data.length === 0) {
1502
+ console.log("No tags found.");
1503
+ return;
1504
+ }
1505
+ printTable(
1506
+ [
1507
+ { header: "ID", width: 24, value: (r) => r.external_id },
1508
+ { header: "NAME", width: 24, value: (r) => r.name },
1509
+ {
1510
+ header: "TYPE",
1511
+ width: 12,
1512
+ value: (r) => r.tag_type?.type ?? ""
1513
+ },
1514
+ { header: "DOCS", width: 6, value: (r) => String(r.doctag_count) },
1515
+ { header: "SHARED", width: 8, value: (r) => r.shared ? "yes" : "no" }
1516
+ ],
1517
+ data
1518
+ );
1519
+ })()
1520
+ );
1521
+ tags.command("create [name]").description("Create a new tag (interactive form if no name given)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option(
1522
+ "-t, --type <type>",
1523
+ "Tag type (checkbox, text, number, select, folder, search, date, assistant)",
1524
+ "checkbox"
1525
+ ).option("-i, --instruction <text>", "Tag instruction").option("--shared", "Make tag shared", false).action(
1526
+ (name, opts) => runAction(async () => {
1527
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
1528
+ const interactive = !name;
1529
+ if (!name) {
1530
+ name = await promptInput("Tag name");
1531
+ }
1532
+ const tagType = opts.type || await promptSelect("Tag type", [
1533
+ { name: "Checkbox", value: "checkbox" },
1534
+ { name: "Text", value: "text" },
1535
+ { name: "Number", value: "number" },
1536
+ { name: "Select", value: "select" },
1537
+ { name: "Date", value: "date" },
1538
+ { name: "Folder", value: "folder" },
1539
+ { name: "Search", value: "search" },
1540
+ { name: "Assistant", value: "assistant" }
1541
+ ]);
1542
+ const instruction = interactive ? opts.instruction ?? (await promptInput("Instruction (optional)", false) || null) : opts.instruction ?? null;
1543
+ const shared = interactive ? opts.shared || await promptConfirm("Shared?", false) : opts.shared ?? false;
1544
+ const data = requireData(
1545
+ await arbi.fetch.POST("/api/tag/", {
1546
+ body: {
1547
+ name,
1548
+ workspace_ext_id: workspaceId,
1549
+ tag_type: { type: tagType, options: [] },
1550
+ instruction,
1551
+ shared
1552
+ }
1553
+ }),
1554
+ "Failed to create tag"
1555
+ );
1556
+ console.log(`Created: ${data.name} (${data.external_id})`);
1557
+ })()
1558
+ );
1559
+ tags.command("delete [id]").description("Delete a tag (interactive picker if no ID given)").action(
1560
+ (id) => runAction(async () => {
1561
+ const { arbi, workspaceId } = await resolveWorkspace();
1562
+ if (!id) {
1563
+ const { choices } = await fetchTagChoices(arbi, workspaceId);
1564
+ id = await promptSelect("Select tag to delete", choices);
1565
+ }
1566
+ const data = requireData(
1567
+ await arbi.fetch.DELETE("/api/tag/{tag_ext_id}", {
1568
+ params: { path: { tag_ext_id: id } }
1569
+ }),
1570
+ "Failed to delete tag"
1571
+ );
1572
+ console.log(data?.detail ?? `Deleted tag ${id}`);
1573
+ })()
1574
+ );
1575
+ tags.command("update [id] [json]").description("Update a tag (interactive picker + form if no args)").action(
1576
+ (id, json) => runAction(async () => {
1577
+ const { arbi, workspaceId } = await resolveWorkspace();
1578
+ if (!id) {
1579
+ const { choices } = await fetchTagChoices(arbi, workspaceId);
1580
+ id = await promptSelect("Select tag to update", choices);
1581
+ }
1582
+ let body;
1583
+ if (json) {
1584
+ body = parseJsonArg(json, `arbi tags update tag-123 '{"name": "New Name"}'`);
1585
+ } else {
1586
+ const field = await promptSelect("Field to update", [
1587
+ { name: "Name", value: "name" },
1588
+ { name: "Instruction", value: "instruction" },
1589
+ { name: "Shared", value: "shared" }
1590
+ ]);
1591
+ if (field === "shared") {
1592
+ body = { shared: await promptConfirm("Shared?") };
1593
+ } else {
1594
+ body = { [field]: await promptInput(field.charAt(0).toUpperCase() + field.slice(1)) };
1595
+ }
1596
+ }
1597
+ const data = requireData(
1598
+ await arbi.fetch.PATCH("/api/tag/{tag_ext_id}", {
1599
+ params: { path: { tag_ext_id: id } },
1600
+ body
1601
+ }),
1602
+ "Failed to update tag"
1603
+ );
1604
+ console.log(`Updated: ${data.name} (${data.external_id})`);
1605
+ })()
1606
+ );
1607
+ tags.action(async (_opts, cmd) => {
1608
+ await cmd.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
1609
+ });
1610
+ }
1611
+
1612
+ // src/commands/doctags.ts
1613
+ async function pickTag(arbi, workspaceId, message = "Select tag") {
1614
+ const data = requireData(
1615
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/tags", {
1616
+ params: { path: { workspace_ext_id: workspaceId } }
1617
+ }),
1618
+ "Failed to fetch tags"
1619
+ );
1620
+ if (data.length === 0) {
1621
+ console.log("No tags found.");
1622
+ process.exit(0);
1623
+ }
1624
+ return promptSelect(
1625
+ message,
1626
+ data.map((t) => ({
1627
+ name: `${t.name} (${t.tag_type?.type ?? ""})`,
1628
+ value: t.external_id,
1629
+ description: t.external_id
1630
+ }))
1631
+ );
1632
+ }
1633
+ async function pickDocs(arbi, workspaceId, message = "Select documents") {
1634
+ const data = requireData(
1635
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/documents", {
1636
+ params: { path: { workspace_ext_id: workspaceId } }
1637
+ }),
1638
+ "Failed to fetch documents"
1639
+ );
1640
+ if (data.length === 0) {
1641
+ console.log("No documents found.");
1642
+ process.exit(0);
1643
+ }
1644
+ return promptCheckbox(
1645
+ message,
1646
+ data.map((d) => ({
1647
+ name: `${d.file_name ?? "Unnamed"} (${d.status})`,
1648
+ value: d.external_id
1649
+ }))
1650
+ );
1651
+ }
1652
+ function registerDoctagsCommand(program2) {
1653
+ const doctags = program2.command("doctags").description("Manage document tags (doctags)");
1654
+ doctags.command("create [tag-id] [doc-ids...]").description("Assign a tag to documents (interactive pickers if no args)").option("-n, --note <text>", "Note for the doctag").action(
1655
+ (tagId, docIds, opts) => runAction(async () => {
1656
+ const { arbi, workspaceId } = await resolveWorkspace();
1657
+ if (!tagId) tagId = await pickTag(arbi, workspaceId, "Select tag to assign");
1658
+ if (!docIds || docIds.length === 0)
1659
+ docIds = await pickDocs(arbi, workspaceId, "Select documents to tag");
1660
+ if (docIds.length === 0) return;
1661
+ const data = requireData(
1662
+ await arbi.fetch.POST("/api/document/doctag", {
1663
+ body: { tag_ext_id: tagId, doc_ext_ids: docIds, note: opts?.note ?? null }
1664
+ }),
1665
+ "Failed to create doctags"
1666
+ );
1667
+ console.log(`Created ${data.length} doctag(s) for tag ${tagId}.`);
1668
+ })()
1669
+ );
1670
+ doctags.command("delete [tag-id] [doc-ids...]").description("Remove a tag from documents (interactive pickers if no args)").action(
1671
+ (tagId, docIds) => runAction(async () => {
1672
+ const { arbi, workspaceId } = await resolveWorkspace();
1673
+ if (!tagId) tagId = await pickTag(arbi, workspaceId, "Select tag to remove");
1674
+ if (!docIds || docIds.length === 0)
1675
+ docIds = await pickDocs(arbi, workspaceId, "Select documents to untag");
1676
+ if (docIds.length === 0) return;
1677
+ requireOk(
1678
+ await arbi.fetch.DELETE("/api/document/doctag", {
1679
+ body: { tag_ext_id: tagId, doc_ext_ids: docIds }
1680
+ }),
1681
+ "Failed to delete doctags"
1682
+ );
1683
+ console.log(`Removed tag ${tagId} from ${docIds.length} document(s).`);
1684
+ })()
1685
+ );
1686
+ doctags.command("generate").description("AI-generate doctags (interactive pickers if no flags)").option("--tags <ids>", "Comma-separated tag IDs").option("--docs <ids>", "Comma-separated document IDs").action(
1687
+ (opts) => runAction(async () => {
1688
+ const { arbi, workspaceId } = await resolveWorkspace();
1689
+ let tagIds;
1690
+ if (opts.tags) {
1691
+ tagIds = opts.tags.split(",").map((s) => s.trim());
1692
+ } else {
1693
+ const data2 = requireData(
1694
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/tags", {
1695
+ params: { path: { workspace_ext_id: workspaceId } }
1696
+ }),
1697
+ "Failed to fetch tags"
1698
+ );
1699
+ if (data2.length === 0) {
1700
+ console.log("No tags found.");
1701
+ return;
1702
+ }
1703
+ tagIds = await promptCheckbox(
1704
+ "Select tags to generate",
1705
+ data2.map((t) => ({
1706
+ name: `${t.name} (${t.tag_type?.type ?? ""})`,
1707
+ value: t.external_id
1708
+ }))
1709
+ );
1710
+ if (tagIds.length === 0) return;
1711
+ }
1712
+ let docIds;
1713
+ if (opts.docs) {
1714
+ docIds = opts.docs.split(",").map((s) => s.trim());
1715
+ } else {
1716
+ docIds = await pickDocs(arbi, workspaceId, "Select documents for AI tagging");
1717
+ if (docIds.length === 0) return;
1718
+ }
1719
+ const data = requireData(
1720
+ await arbi.fetch.POST("/api/document/doctag/generate", {
1721
+ body: { tag_ext_ids: tagIds, doc_ext_ids: docIds }
1722
+ }),
1723
+ "Failed to generate doctags"
1724
+ );
1725
+ console.log(
1726
+ `Generating doctags for ${data.doc_ext_ids.length} doc(s) with ${data.tag_ext_ids.length} tag(s).`
1727
+ );
1728
+ })()
1729
+ );
1730
+ }
1731
+
1732
+ // src/commands/conversations.ts
1733
+ async function pickConversation(arbi, workspaceId, message = "Select conversation") {
1734
+ const data = requireData(
1735
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/conversations", {
1736
+ params: { path: { workspace_ext_id: workspaceId } }
1737
+ }),
1738
+ "Failed to fetch conversations"
1739
+ );
1740
+ if (data.length === 0) {
1741
+ console.log("No conversations found.");
1742
+ process.exit(0);
1743
+ }
1744
+ return promptSelect(
1745
+ message,
1746
+ data.map((c) => ({
1747
+ name: `${c.title ?? "Untitled"} (${c.message_count} msgs)`,
1748
+ value: c.external_id,
1749
+ description: c.external_id
1750
+ }))
1751
+ );
1752
+ }
1753
+ function registerConversationsCommand(program2) {
1754
+ const conv = program2.command("conversations").description("Manage conversations");
1755
+ conv.command("list").description("List conversations in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
1756
+ (opts) => runAction(async () => {
1757
+ const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
1758
+ const data = requireData(
1759
+ await arbi.fetch.GET("/api/workspace/{workspace_ext_id}/conversations", {
1760
+ params: { path: { workspace_ext_id: workspaceId } }
1761
+ }),
1762
+ "Failed to fetch conversations"
1763
+ );
1764
+ if (data.length === 0) {
1765
+ console.log("No conversations found.");
1766
+ return;
1767
+ }
1768
+ printTable(
1769
+ [
1770
+ { header: "ID", width: 24, value: (r) => r.external_id },
1771
+ { header: "TITLE", width: 36, value: (r) => r.title ?? "Untitled" },
1772
+ { header: "MSGS", width: 6, value: (r) => String(r.message_count) },
1773
+ { header: "SHARED", width: 8, value: (r) => r.is_shared ? "yes" : "no" }
1774
+ ],
1775
+ data
1776
+ );
1777
+ })()
1778
+ );
1779
+ conv.command("threads [conversation-id]").description("Show threads (interactive picker if no ID given)").action(
1780
+ (conversationId) => runAction(async () => {
1781
+ const { arbi, workspaceId } = await resolveWorkspace();
1782
+ if (!conversationId) conversationId = await pickConversation(arbi, workspaceId);
1783
+ const data = requireData(
1784
+ await arbi.fetch.GET("/api/conversation/{conversation_ext_id}/threads", {
1785
+ params: { path: { conversation_ext_id: conversationId } }
1786
+ }),
1787
+ "Failed to fetch threads"
1788
+ );
1789
+ console.log(JSON.stringify(data, null, 2));
1790
+ })()
1791
+ );
1792
+ conv.command("delete [conversation-id]").description("Delete a conversation (interactive picker if no ID given)").action(
1793
+ (conversationId) => runAction(async () => {
1794
+ const { arbi, workspaceId } = await resolveWorkspace();
1795
+ if (!conversationId)
1796
+ conversationId = await pickConversation(
1797
+ arbi,
1798
+ workspaceId,
1799
+ "Select conversation to delete"
1800
+ );
1801
+ const data = requireData(
1802
+ await arbi.fetch.DELETE("/api/conversation/{conversation_ext_id}", {
1803
+ params: { path: { conversation_ext_id: conversationId } }
1804
+ }),
1805
+ "Failed to delete conversation"
1806
+ );
1807
+ console.log(data?.detail ?? `Deleted conversation ${conversationId}`);
1808
+ })()
1809
+ );
1810
+ conv.command("share [conversation-id]").description("Share a conversation (interactive picker if no ID given)").action(
1811
+ (conversationId) => runAction(async () => {
1812
+ const { arbi, workspaceId } = await resolveWorkspace();
1813
+ if (!conversationId)
1814
+ conversationId = await pickConversation(arbi, workspaceId, "Select conversation to share");
1815
+ const data = requireData(
1816
+ await arbi.fetch.POST("/api/conversation/{conversation_ext_id}/share", {
1817
+ params: { path: { conversation_ext_id: conversationId } }
1818
+ }),
1819
+ "Failed to share conversation"
1820
+ );
1821
+ console.log(data?.detail ?? `Shared conversation ${conversationId}`);
1822
+ })()
1823
+ );
1824
+ conv.command("title [conversation-id] [title]").description("Update conversation title (interactive if no args)").action(
1825
+ (conversationId, title) => runAction(async () => {
1826
+ const { arbi, workspaceId } = await resolveWorkspace();
1827
+ if (!conversationId) conversationId = await pickConversation(arbi, workspaceId);
1828
+ if (!title) title = await promptInput("New title");
1829
+ const data = requireData(
1830
+ await arbi.fetch.PATCH("/api/conversation/{conversation_ext_id}/title", {
1831
+ params: { path: { conversation_ext_id: conversationId } },
1832
+ body: { title }
1833
+ }),
1834
+ "Failed to update title"
1835
+ );
1836
+ console.log(data?.detail ?? `Updated title to: ${title}`);
1837
+ })()
1838
+ );
1839
+ conv.command("add-user [conversation-id] [user-id]").description("Add a user to a conversation (interactive if no args)").action(
1840
+ (conversationId, userId) => runAction(async () => {
1841
+ const { arbi, workspaceId } = await resolveWorkspace();
1842
+ if (!conversationId) conversationId = await pickConversation(arbi, workspaceId);
1843
+ if (!userId) userId = await promptInput("User ext_id or email");
1844
+ const data = requireData(
1845
+ await arbi.fetch.POST("/api/conversation/{conversation_ext_id}/user", {
1846
+ params: { path: { conversation_ext_id: conversationId } },
1847
+ body: { user_ext_id: userId }
1848
+ }),
1849
+ "Failed to add user to conversation"
1850
+ );
1851
+ console.log(data?.detail ?? `Added user ${userId} to conversation`);
1852
+ })()
1853
+ );
1854
+ conv.command("remove-user [conversation-id] [user-id]").description("Remove a user from a conversation (interactive if no args)").action(
1855
+ (conversationId, userId) => runAction(async () => {
1856
+ const { arbi, workspaceId } = await resolveWorkspace();
1857
+ if (!conversationId) conversationId = await pickConversation(arbi, workspaceId);
1858
+ if (!userId) userId = await promptInput("User ext_id");
1859
+ const data = requireData(
1860
+ await arbi.fetch.DELETE("/api/conversation/{conversation_ext_id}/user", {
1861
+ params: { path: { conversation_ext_id: conversationId } },
1862
+ body: { user_ext_id: userId }
1863
+ }),
1864
+ "Failed to remove user from conversation"
1865
+ );
1866
+ console.log(data?.detail ?? `Removed user ${userId} from conversation`);
1867
+ })()
1868
+ );
1869
+ conv.command("message <message-id>").description("Get message details").action(
1870
+ (messageId) => runAction(async () => {
1871
+ const { arbi } = await resolveWorkspace();
1872
+ const data = requireData(
1873
+ await arbi.fetch.GET("/api/conversation/message/{message_ext_id}", {
1874
+ params: { path: { message_ext_id: messageId } }
1875
+ }),
1876
+ "Failed to fetch message"
1877
+ );
1878
+ console.log(JSON.stringify(data, null, 2));
1879
+ })()
1880
+ );
1881
+ conv.command("delete-message <message-id>").description("Delete a message and its descendants").action(
1882
+ (messageId) => runAction(async () => {
1883
+ const { arbi } = await resolveAuth();
1884
+ const data = requireData(
1885
+ await arbi.fetch.DELETE("/api/conversation/message/{message_ext_id}", {
1886
+ params: { path: { message_ext_id: messageId } }
1887
+ }),
1888
+ "Failed to delete message"
1889
+ );
1890
+ console.log(data?.detail ?? `Deleted message ${messageId}`);
1891
+ })()
1892
+ );
1893
+ conv.action(async (_opts, cmd) => {
1894
+ await cmd.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
1895
+ });
1896
+ }
1897
+
1898
+ // src/commands/settings.ts
1899
+ function registerSettingsCommand(program2) {
1900
+ const settings = program2.command("settings").description("Manage user settings");
1901
+ settings.command("get").description("Show current user settings").action(
1902
+ runAction(async () => {
1903
+ const { arbi } = await resolveAuth();
1904
+ const data = requireData(
1905
+ await arbi.fetch.GET("/api/user/settings"),
1906
+ "Failed to fetch settings"
1907
+ );
1908
+ console.log(JSON.stringify(data, null, 2));
1909
+ })
1910
+ );
1911
+ settings.command("set <json>").description("Update user settings (pass JSON object)").action(
1912
+ (json) => runAction(async () => {
1913
+ const body = parseJsonArg(json, `arbi settings set '{"hide_online_status": true}'`);
1914
+ const { arbi } = await resolveAuth();
1915
+ requireOk(
1916
+ await arbi.fetch.PATCH("/api/user/settings", { body }),
1917
+ "Failed to update settings"
1918
+ );
1919
+ console.log("Settings updated.");
1920
+ })()
1921
+ );
1922
+ settings.action(async () => {
1923
+ await settings.commands.find((c) => c.name() === "get").parseAsync([], { from: "user" });
1924
+ });
1925
+ }
1926
+
1927
+ // src/commands/agentconfig.ts
1928
+ var MODEL_SECTIONS = [
1929
+ "Agents",
1930
+ "QueryLLM",
1931
+ "EvaluatorLLM",
1932
+ "TitleLLM",
1933
+ "SummariseLLM",
1934
+ "DoctagLLM",
1935
+ "ArtifactLLM"
1936
+ ];
1937
+ async function pickConfig(arbi, message = "Select configuration") {
1938
+ const data = requireData(
1939
+ await arbi.fetch.GET("/api/configs/versions"),
1940
+ "Failed to fetch config versions"
1941
+ );
1942
+ if (data.versions.length === 0) {
1943
+ console.log("No saved configurations found.");
1944
+ process.exit(0);
1945
+ }
1946
+ return promptSelect(
1947
+ message,
1948
+ data.versions.map((v) => ({
1949
+ name: `${v.title || "(untitled)"} \u2014 ${new Date(v.created_at).toLocaleString()}`,
1950
+ value: v.external_id,
1951
+ description: v.external_id
1952
+ }))
1953
+ );
1954
+ }
1955
+ async function fetchModels(arbi) {
1956
+ const data = requireData(await arbi.fetch.GET("/api/health/models"), "Failed to fetch models");
1957
+ return data.models.map((m) => ({
1958
+ name: `${m.model_name} (${m.provider ?? m.api_type})`,
1959
+ value: m.model_name,
1960
+ description: m.api_type
1961
+ }));
1962
+ }
1963
+ async function buildConfigInteractively(arbi) {
1964
+ const body = {};
1965
+ const title = await promptInput("Config title (optional)", false);
1966
+ if (title) body.title = title;
1967
+ const section = await promptSelect("Section to configure", [
1968
+ { name: "Agents \u2014 main agent model, persona, tools", value: "Agents" },
1969
+ { name: "QueryLLM \u2014 answer review model", value: "QueryLLM" },
1970
+ { name: "EvaluatorLLM \u2014 chunk evaluation", value: "EvaluatorLLM" },
1971
+ { name: "TitleLLM \u2014 title generation", value: "TitleLLM" },
1972
+ { name: "SummariseLLM \u2014 conversation summariser", value: "SummariseLLM" },
1973
+ { name: "DoctagLLM \u2014 AI document tagging", value: "DoctagLLM" },
1974
+ { name: "ArtifactLLM \u2014 skill/memory synthesis", value: "ArtifactLLM" },
1975
+ { name: "Retriever \u2014 search parameters", value: "Retriever" },
1976
+ { name: "Reranker \u2014 reranking model", value: "Reranker" },
1977
+ { name: "ModelCitation \u2014 citation thresholds", value: "ModelCitation" },
1978
+ { name: "Chunker \u2014 chunk size", value: "Chunker" },
1979
+ { name: "Embedder \u2014 embedding model", value: "Embedder" },
1980
+ { name: "KeywordEmbedder \u2014 BM25 parameters", value: "KeywordEmbedder" }
1981
+ ]);
1982
+ const sectionConfig = {};
1983
+ if (MODEL_SECTIONS.includes(section)) {
1984
+ const models = await fetchModels(arbi);
1985
+ if (models.length > 0) {
1986
+ const changeModel = await promptConfirm("Change model?", true);
1987
+ if (changeModel) {
1988
+ const modelKey = section === "Agents" ? "AGENT_MODEL_NAME" : "MODEL_NAME";
1989
+ sectionConfig[modelKey] = await promptSelect("Model", models);
1990
+ if (section === "Agents") {
1991
+ sectionConfig.AGENT_API_TYPE = "remote";
1992
+ } else {
1993
+ sectionConfig.API_TYPE = "remote";
1994
+ }
1995
+ }
1996
+ }
1997
+ }
1998
+ if (section === "Agents") {
1999
+ const field = await promptSelect("What else to change?", [
2000
+ { name: "Nothing else", value: "done" },
2001
+ { name: "Temperature", value: "LLM_AGENT_TEMPERATURE" },
2002
+ { name: "Max tokens", value: "AGENT_MAX_TOKENS" },
2003
+ { name: "Max iterations", value: "AGENT_MAX_ITERATIONS" },
2004
+ { name: "Persona", value: "PERSONA" },
2005
+ { name: "Web search enabled", value: "WEB_SEARCH" },
2006
+ { name: "Human in the loop", value: "HUMAN_IN_THE_LOOP" }
2007
+ ]);
2008
+ if (field !== "done") {
2009
+ if (field === "WEB_SEARCH" || field === "HUMAN_IN_THE_LOOP") {
2010
+ sectionConfig[field] = await promptConfirm(field + "?");
2011
+ } else if (field === "LLM_AGENT_TEMPERATURE") {
2012
+ sectionConfig[field] = parseFloat(await promptInput("Temperature (0.0 - 2.0)"));
2013
+ } else if (field === "AGENT_MAX_TOKENS" || field === "AGENT_MAX_ITERATIONS") {
2014
+ sectionConfig[field] = parseInt(await promptInput(field), 10);
2015
+ } else {
2016
+ sectionConfig[field] = await promptInput(field);
2017
+ }
2018
+ }
2019
+ } else if (section === "Retriever") {
2020
+ const field = await promptSelect("Field to change", [
2021
+ { name: "Search mode", value: "SEARCH_MODE" },
2022
+ { name: "Min similarity score", value: "MIN_RETRIEVAL_SIM_SCORE" },
2023
+ { name: "Max chunks to retrieve", value: "MAX_TOTAL_CHUNKS_TO_RETRIEVE" },
2024
+ { name: "Max distinct documents", value: "MAX_DISTINCT_DOCUMENTS" },
2025
+ { name: "Hybrid dense weight", value: "HYBRID_DENSE_WEIGHT" },
2026
+ { name: "Hybrid sparse weight", value: "HYBRID_SPARSE_WEIGHT" }
2027
+ ]);
2028
+ if (field === "SEARCH_MODE") {
2029
+ sectionConfig[field] = await promptSelect("Search mode", [
2030
+ { name: "Semantic (dense)", value: "semantic" },
2031
+ { name: "Keyword (sparse/BM25)", value: "keyword" },
2032
+ { name: "Hybrid (dense + sparse)", value: "hybrid" }
2033
+ ]);
2034
+ } else {
2035
+ sectionConfig[field] = parseFloat(await promptInput(field));
2036
+ }
2037
+ } else if (section === "ModelCitation") {
2038
+ const field = await promptSelect("Field to change", [
2039
+ { name: "Similarity threshold", value: "SIM_THREASHOLD" },
2040
+ { name: "Min char size", value: "MIN_CHAR_SIZE_TO_ANSWER" },
2041
+ { name: "Max citations", value: "MAX_NUMB_CITATIONS" }
2042
+ ]);
2043
+ sectionConfig[field] = parseFloat(await promptInput(field));
2044
+ } else if (section === "Chunker") {
2045
+ sectionConfig.MAX_CHUNK_TOKENS = parseInt(await promptInput("Max chunk tokens"), 10);
2046
+ } else if (section === "Reranker") {
2047
+ const field = await promptSelect("Field to change", [
2048
+ { name: "Max chunks after rerank", value: "MAX_NUMB_OF_CHUNKS" },
2049
+ { name: "Model", value: "MODEL_NAME" }
2050
+ ]);
2051
+ if (field === "MODEL_NAME") {
2052
+ const models = await fetchModels(arbi);
2053
+ sectionConfig.MODEL_NAME = await promptSelect("Model", models);
2054
+ sectionConfig.API_TYPE = "remote";
2055
+ } else {
2056
+ sectionConfig[field] = parseInt(await promptInput(field), 10);
2057
+ }
2058
+ } else if (section === "QueryLLM" || section === "EvaluatorLLM" || section === "TitleLLM" || section === "SummariseLLM" || section === "DoctagLLM" || section === "ArtifactLLM") {
2059
+ const field = await promptSelect("What else to change?", [
2060
+ { name: "Nothing else", value: "done" },
2061
+ { name: "Temperature", value: "TEMPERATURE" },
2062
+ { name: "Max tokens", value: "MAX_TOKENS" }
2063
+ ]);
2064
+ if (field !== "done") {
2065
+ sectionConfig[field] = field === "TEMPERATURE" ? parseFloat(await promptInput("Temperature (0.0 - 2.0)")) : parseInt(await promptInput("Max tokens"), 10);
2066
+ }
2067
+ }
2068
+ if (Object.keys(sectionConfig).length > 0) {
2069
+ body[section] = sectionConfig;
2070
+ }
2071
+ return body;
2072
+ }
2073
+ function registerAgentconfigCommand(program2) {
2074
+ const ac = program2.command("agentconfig").description("Manage agent configurations");
2075
+ ac.command("list").description("List saved configuration versions").action(
2076
+ runAction(async () => {
2077
+ const { arbi } = await resolveAuth();
2078
+ const data = requireData(
2079
+ await arbi.fetch.GET("/api/configs/versions"),
2080
+ "Failed to fetch config versions"
2081
+ );
2082
+ if (data.versions.length === 0) {
2083
+ console.log("No saved configurations.");
2084
+ return;
2085
+ }
2086
+ printTable(
2087
+ [
2088
+ { header: "ID", width: 24, value: (r) => r.external_id },
2089
+ { header: "TITLE", width: 30, value: (r) => r.title ?? "(untitled)" },
2090
+ {
2091
+ header: "CREATED",
2092
+ width: 22,
2093
+ value: (r) => new Date(r.created_at).toLocaleString()
2094
+ }
2095
+ ],
2096
+ data.versions
2097
+ );
2098
+ })
2099
+ );
2100
+ ac.command("get [config-id]").description('Show configuration details (picker if no ID; use "default" for system defaults)').action(
2101
+ (configId) => runAction(async () => {
2102
+ const { arbi } = await resolveAuth();
2103
+ if (!configId) {
2104
+ configId = await promptSelect("Which configuration?", [
2105
+ { name: "System defaults", value: "default", description: "Built-in defaults" },
2106
+ { name: "Pick from saved versions...", value: "__pick__" }
2107
+ ]);
2108
+ if (configId === "__pick__") {
2109
+ configId = await pickConfig(arbi);
2110
+ }
2111
+ }
2112
+ const data = requireData(
2113
+ await arbi.fetch.GET("/api/configs/{config_ext_id}", {
2114
+ params: { path: { config_ext_id: configId } }
2115
+ }),
2116
+ "Failed to fetch configuration"
2117
+ );
2118
+ console.log(JSON.stringify(data, null, 2));
2119
+ })()
2120
+ );
2121
+ ac.command("save [json]").description("Save a new configuration (interactive if no JSON given)").option("-t, --title <title>", "Config title").option("--tag <tag-ext-id>", "Associate with an assistant tag").option("--message <msg-ext-id>", "Associate with a parent message").action(
2122
+ (json, opts) => runAction(async () => {
2123
+ const { arbi } = await resolveAuth();
2124
+ let body;
2125
+ if (json) {
2126
+ body = parseJsonArg(
2127
+ json,
2128
+ `arbi agentconfig save '{"Agents": {"AGENT_MODEL_NAME": "GPT4o@OpenAI"}, "title": "GPT-4o config"}'`
2129
+ );
2130
+ if (opts?.title) body.title = opts.title;
2131
+ } else {
2132
+ body = await buildConfigInteractively(arbi);
2133
+ }
2134
+ if (opts?.tag) body.tag_ext_id = opts.tag;
2135
+ if (opts?.message) body.parent_message_ext_id = opts.message;
2136
+ const data = requireData(
2137
+ await arbi.fetch.POST("/api/configs/", { body }),
2138
+ "Failed to save configuration"
2139
+ );
2140
+ console.log(`Saved: ${data.title || "(untitled)"} (${data.external_id})`);
2141
+ })()
2142
+ );
2143
+ ac.command("delete [config-id]").description("Delete a configuration (picker if no ID)").action(
2144
+ (configId) => runAction(async () => {
2145
+ const { arbi } = await resolveAuth();
2146
+ if (!configId) configId = await pickConfig(arbi, "Select configuration to delete");
2147
+ const data = requireData(
2148
+ await arbi.fetch.DELETE("/api/configs/{config_ext_id}", {
2149
+ params: { path: { config_ext_id: configId } }
2150
+ }),
2151
+ "Failed to delete configuration"
2152
+ );
2153
+ console.log(data.detail);
2154
+ })()
2155
+ );
2156
+ ac.command("schema").description("Show JSON schema for all configuration models").action(
2157
+ runAction(async () => {
2158
+ const { arbi } = await resolveAuth();
2159
+ const data = requireData(
2160
+ await arbi.fetch.GET("/api/configs/schema"),
2161
+ "Failed to fetch config schema"
2162
+ );
2163
+ console.log(JSON.stringify(data, null, 2));
2164
+ })
2165
+ );
2166
+ ac.action(async () => {
2167
+ await ac.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
2168
+ });
2169
+ }
2170
+
2171
+ // src/commands/health.ts
2172
+ function registerHealthCommand(program2) {
2173
+ program2.command("health").description("Show server health status").action(
2174
+ runAction(async () => {
2175
+ const { arbi } = await resolveAuth();
2176
+ const data = requireData(
2177
+ await arbi.fetch.GET("/api/health/"),
2178
+ "Failed to fetch health status"
2179
+ );
2180
+ console.log(`Status: ${data.status}`);
2181
+ if (data.backend_git_hash) console.log(`Backend: ${data.backend_git_hash}`);
2182
+ if (data.frontend_docker_version) console.log(`Frontend: ${data.frontend_docker_version}`);
2183
+ if (data.services.length > 0) {
2184
+ console.log("\nServices:");
2185
+ for (const s of data.services) {
2186
+ console.log(` ${s.name}: ${s.status}${s.detail ? ` \u2014 ${s.detail}` : ""}`);
2187
+ }
2188
+ }
2189
+ if (data.models_health) {
2190
+ console.log(`
2191
+ Models (${data.models_health.application}):`);
2192
+ for (const m of data.models_health.models) {
2193
+ console.log(` ${m.model}: ${m.status}${m.detail ? ` \u2014 ${m.detail}` : ""}`);
2194
+ }
2195
+ }
2196
+ })
2197
+ );
2198
+ program2.command("models").description("List available AI models").action(
2199
+ runAction(async () => {
2200
+ const { arbi } = await resolveAuth();
2201
+ const data = requireData(
2202
+ await arbi.fetch.GET("/api/health/models"),
2203
+ "Failed to fetch models"
2204
+ );
2205
+ if (data.models.length === 0) {
2206
+ console.log("No models available.");
2207
+ return;
2208
+ }
2209
+ printTable(
2210
+ [
2211
+ { header: "NAME", width: 30, value: (r) => r.model_name },
2212
+ { header: "PROVIDER", width: 20, value: (r) => r.provider ?? "" },
2213
+ { header: "API_TYPE", width: 16, value: (r) => r.api_type }
2214
+ ],
2215
+ data.models
2216
+ );
2217
+ })
2218
+ );
2219
+ }
2220
+
2221
+ // src/index.ts
2222
+ console.debug = () => {
2223
+ };
2224
+ var _origInfo = console.info;
2225
+ console.info = (...args) => {
2226
+ if (typeof args[0] === "string" && args[0].startsWith("[API]")) return;
2227
+ _origInfo(...args);
2228
+ };
2229
+ var program = new commander.Command();
2230
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.1.0");
2231
+ registerConfigCommand(program);
2232
+ registerLoginCommand(program);
2233
+ registerRegisterCommand(program);
2234
+ registerLogoutCommand(program);
2235
+ registerStatusCommand(program);
2236
+ registerWorkspacesCommand(program);
2237
+ registerDocsCommand(program);
2238
+ registerUploadCommand(program);
2239
+ registerDownloadCommand(program);
2240
+ registerAskCommand(program);
2241
+ registerWatchCommand(program);
2242
+ registerContactsCommand(program);
2243
+ registerDmCommand(program);
2244
+ registerTagsCommand(program);
2245
+ registerDoctagsCommand(program);
2246
+ registerConversationsCommand(program);
2247
+ registerSettingsCommand(program);
2248
+ registerAgentconfigCommand(program);
2249
+ registerHealthCommand(program);
2250
+ program.parse();
2251
+ //# sourceMappingURL=index.cjs.map
2252
+ //# sourceMappingURL=index.cjs.map