@arbidocs/cli 0.3.10 → 0.3.13

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.js CHANGED
@@ -5,8 +5,8 @@ var commander = require('commander');
5
5
  var fs = require('fs');
6
6
  var os = require('os');
7
7
  var path = require('path');
8
+ var chalk2 = require('chalk');
8
9
  var sdk = require('@arbidocs/sdk');
9
- var chalk = require('chalk');
10
10
  var prompts = require('@inquirer/prompts');
11
11
  var child_process = require('child_process');
12
12
  var client = require('@arbidocs/client');
@@ -18,7 +18,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
18
  var fs__default = /*#__PURE__*/_interopDefault(fs);
19
19
  var os__default = /*#__PURE__*/_interopDefault(os);
20
20
  var path__default = /*#__PURE__*/_interopDefault(path);
21
- var chalk__default = /*#__PURE__*/_interopDefault(chalk);
21
+ var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
22
22
 
23
23
  var store = new sdk.FileConfigStore();
24
24
  function getConfig() {
@@ -38,6 +38,13 @@ function requireConfig() {
38
38
  throw err;
39
39
  }
40
40
  }
41
+ function resolveConfig() {
42
+ const { config, source } = store.resolveConfigWithFallbacks();
43
+ if (source !== "config") {
44
+ console.error(chalk2__default.default.dim(`Using server URL from ${source}: ${config.baseUrl}`));
45
+ }
46
+ return config;
47
+ }
41
48
  function getCredentials() {
42
49
  return store.getCredentials();
43
50
  }
@@ -54,32 +61,32 @@ function clearChatSession() {
54
61
  store.clearChatSession();
55
62
  }
56
63
  function success(msg) {
57
- console.log(chalk__default.default.green(msg));
64
+ console.log(chalk2__default.default.green(msg));
58
65
  }
59
66
  function error(msg) {
60
- console.error(chalk__default.default.red(msg));
67
+ console.error(chalk2__default.default.red(msg));
61
68
  }
62
69
  function warn(msg) {
63
- console.error(chalk__default.default.yellow(msg));
70
+ console.error(chalk2__default.default.yellow(msg));
64
71
  }
65
72
  function label(key, value) {
66
- console.log(`${chalk__default.default.bold(key)} ${value}`);
73
+ console.log(`${chalk2__default.default.bold(key)} ${value}`);
67
74
  }
68
75
  function dim(msg) {
69
- console.log(chalk__default.default.dim(msg));
76
+ console.log(chalk2__default.default.dim(msg));
70
77
  }
71
78
  function bold(msg) {
72
- console.log(chalk__default.default.bold(msg));
79
+ console.log(chalk2__default.default.bold(msg));
73
80
  }
74
81
  function status(s) {
75
82
  const lower = s.toLowerCase();
76
- if (["healthy", "completed", "available", "online", "on"].includes(lower)) return chalk__default.default.green(s);
77
- if (["failed", "error", "unavailable", "offline"].includes(lower)) return chalk__default.default.red(s);
78
- if (["processing", "pending", "degraded", "warning"].includes(lower)) return chalk__default.default.yellow(s);
83
+ if (["healthy", "completed", "available", "online", "on"].includes(lower)) return chalk2__default.default.green(s);
84
+ if (["failed", "error", "unavailable", "offline"].includes(lower)) return chalk2__default.default.red(s);
85
+ if (["processing", "pending", "degraded", "warning"].includes(lower)) return chalk2__default.default.yellow(s);
79
86
  return s;
80
87
  }
81
88
  function ref(s) {
82
- return chalk__default.default.cyan(s);
89
+ return chalk2__default.default.cyan(s);
83
90
  }
84
91
 
85
92
  // src/commands/config-cmd.ts
@@ -3502,7 +3509,7 @@ function getLatestVersion(skipCache = false) {
3502
3509
  }
3503
3510
  }
3504
3511
  function getCurrentVersion() {
3505
- return "0.3.10";
3512
+ return "0.3.13";
3506
3513
  }
3507
3514
  function readChangelog(fromVersion, toVersion) {
3508
3515
  try {
@@ -3555,17 +3562,17 @@ function showChangelog(fromVersion, toVersion) {
3555
3562
  async function checkForUpdates(autoUpdate) {
3556
3563
  try {
3557
3564
  const latest = getLatestVersion();
3558
- if (!latest || latest === "0.3.10") return;
3565
+ if (!latest || latest === "0.3.13") return;
3559
3566
  if (autoUpdate) {
3560
3567
  warn(`
3561
- Your arbi version is out of date (${"0.3.10"} \u2192 ${latest}). Updating...`);
3568
+ Your arbi version is out of date (${"0.3.13"} \u2192 ${latest}). Updating...`);
3562
3569
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3563
- showChangelog("0.3.10", latest);
3570
+ showChangelog("0.3.13", latest);
3564
3571
  console.log(`Updated to ${latest}.`);
3565
3572
  } else {
3566
3573
  warn(
3567
3574
  `
3568
- Your arbi version is out of date (${"0.3.10"} \u2192 ${latest}).
3575
+ Your arbi version is out of date (${"0.3.13"} \u2192 ${latest}).
3569
3576
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3570
3577
  );
3571
3578
  }
@@ -3575,9 +3582,9 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3575
3582
  function hintUpdateOnError() {
3576
3583
  try {
3577
3584
  const cached = readCache();
3578
- if (cached && cached.latest !== "0.3.10") {
3585
+ if (cached && cached.latest !== "0.3.13") {
3579
3586
  warn(
3580
- `Your arbi version is out of date (${"0.3.10"} \u2192 ${cached.latest}). Run "arbi update".`
3587
+ `Your arbi version is out of date (${"0.3.13"} \u2192 ${cached.latest}). Run "arbi update".`
3581
3588
  );
3582
3589
  }
3583
3590
  } catch {
@@ -3595,30 +3602,32 @@ function registerLoginCommand(program2) {
3595
3602
  clearChatSession();
3596
3603
  const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
3597
3604
  const wsList = workspaces2 || [];
3598
- success(`Logged in as ${email}`);
3599
- if (wsList.length === 0) {
3600
- console.log("No workspaces found.");
3605
+ const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
3606
+ if (memberWorkspaces.length === 0) {
3607
+ console.log("No workspaces found. Create one with: arbi workspace create <name>");
3601
3608
  return;
3602
3609
  }
3603
3610
  if (opts.workspace) {
3604
- const ws2 = wsList.find((w) => w.external_id === opts.workspace);
3611
+ const ws2 = memberWorkspaces.find((w) => w.external_id === opts.workspace);
3605
3612
  if (!ws2) {
3606
- error(`Workspace ${opts.workspace} not found.`);
3613
+ error(`Workspace ${opts.workspace} not found or you don't have access.`);
3607
3614
  process.exit(1);
3608
3615
  }
3609
3616
  updateConfig({ selectedWorkspaceId: ws2.external_id });
3610
3617
  success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
3611
3618
  return;
3612
3619
  }
3613
- if (wsList.length === 1) {
3614
- updateConfig({ selectedWorkspaceId: wsList[0].external_id });
3615
- success(`Workspace: ${wsList[0].name} (${ref(wsList[0].external_id)})`);
3620
+ if (memberWorkspaces.length === 1) {
3621
+ updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
3622
+ success(
3623
+ `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
3624
+ );
3616
3625
  return;
3617
3626
  }
3618
- const choices = sdk.formatWorkspaceChoices(wsList);
3627
+ const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
3619
3628
  const selected = await promptSelect("Select workspace", choices);
3620
3629
  updateConfig({ selectedWorkspaceId: selected });
3621
- const ws = wsList.find((w) => w.external_id === selected);
3630
+ const ws = memberWorkspaces.find((w) => w.external_id === selected);
3622
3631
  success(`Workspace: ${ws.name} (${ref(selected)})`);
3623
3632
  dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
3624
3633
  } catch (err) {
@@ -3656,7 +3665,11 @@ function registerRegisterCommand(program2) {
3656
3665
  });
3657
3666
  }
3658
3667
  async function smartRegister(config, opts) {
3659
- const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
3668
+ let email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
3669
+ if ((opts.email || process.env.ARBI_EMAIL) && !email.includes("@")) {
3670
+ email = `${email}@${config.deploymentDomain}`;
3671
+ console.log(`Using email: ${email}`);
3672
+ }
3660
3673
  const arbi = client.createArbiClient({
3661
3674
  baseUrl: config.baseUrl,
3662
3675
  deploymentDomain: config.deploymentDomain,
@@ -3722,13 +3735,17 @@ Registered successfully as ${email}`);
3722
3735
  }
3723
3736
  }
3724
3737
  async function nonInteractiveRegister(config, opts) {
3725
- const email = opts.email || process.env.ARBI_EMAIL;
3738
+ let email = opts.email || process.env.ARBI_EMAIL;
3726
3739
  const password2 = opts.password || process.env.ARBI_PASSWORD;
3727
3740
  const supportApiKey = process.env.SUPPORT_API_KEY;
3728
3741
  if (!email) {
3729
3742
  error("Email required. Use --email <email> or set ARBI_EMAIL");
3730
3743
  process.exit(1);
3731
3744
  }
3745
+ if (!email.includes("@")) {
3746
+ email = `${email}@${config.deploymentDomain}`;
3747
+ console.log(`Using email: ${email}`);
3748
+ }
3732
3749
  if (!password2) {
3733
3750
  error("Password required. Use --password <password> or set ARBI_PASSWORD");
3734
3751
  process.exit(1);
@@ -3771,6 +3788,7 @@ async function nonInteractiveRegister(config, opts) {
3771
3788
  error(`Registration failed: ${sdk.getErrorMessage(err)}`);
3772
3789
  process.exit(1);
3773
3790
  }
3791
+ await loginAfterRegister(config, email, password2);
3774
3792
  }
3775
3793
  async function loginAfterRegister(config, email, password2) {
3776
3794
  try {
@@ -3778,22 +3796,23 @@ async function loginAfterRegister(config, email, password2) {
3778
3796
  success(`Logged in as ${email}`);
3779
3797
  const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
3780
3798
  const wsList = workspaces2 || [];
3781
- if (wsList.length === 0) {
3799
+ const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
3800
+ if (memberWorkspaces.length === 0) {
3782
3801
  console.log("Creating your first workspace...");
3783
3802
  const ws2 = await sdk.workspaces.createWorkspace(arbi, "My First Workspace");
3784
3803
  updateConfig({ selectedWorkspaceId: ws2.external_id });
3785
3804
  success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
3786
3805
  return;
3787
3806
  }
3788
- if (wsList.length === 1) {
3789
- updateConfig({ selectedWorkspaceId: wsList[0].external_id });
3790
- success(`Workspace: ${wsList[0].name} (${ref(wsList[0].external_id)})`);
3807
+ if (memberWorkspaces.length === 1) {
3808
+ updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
3809
+ success(`Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`);
3791
3810
  return;
3792
3811
  }
3793
- const choices = sdk.formatWorkspaceChoices(wsList);
3812
+ const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
3794
3813
  const selected = await promptSelect("Select workspace", choices);
3795
3814
  updateConfig({ selectedWorkspaceId: selected });
3796
- const ws = wsList.find((w) => w.external_id === selected);
3815
+ const ws = memberWorkspaces.find((w) => w.external_id === selected);
3797
3816
  success(`Workspace: ${ws.name} (${ref(selected)})`);
3798
3817
  } catch (err) {
3799
3818
  error(`Login failed: ${sdk.getErrorMessage(err)}`);
@@ -3838,9 +3857,9 @@ function timestamp() {
3838
3857
  return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
3839
3858
  }
3840
3859
  function colorize(level, text) {
3841
- if (level === "success") return chalk__default.default.green(text);
3842
- if (level === "error") return chalk__default.default.red(text);
3843
- if (level === "warning") return chalk__default.default.yellow(text);
3860
+ if (level === "success") return chalk2__default.default.green(text);
3861
+ if (level === "error") return chalk2__default.default.red(text);
3862
+ if (level === "warning") return chalk2__default.default.yellow(text);
3844
3863
  return text;
3845
3864
  }
3846
3865
  async function startBackgroundNotifications(baseUrl, accessToken) {
@@ -3861,18 +3880,18 @@ ${colorize(level, `[${timestamp()}] ${text}`)}
3861
3880
  onReconnecting: (attempt, maxRetries) => {
3862
3881
  process.stderr.write(
3863
3882
  `
3864
- ${chalk__default.default.yellow(`[${timestamp()}] Reconnecting... (${attempt}/${maxRetries})`)}
3883
+ ${chalk2__default.default.yellow(`[${timestamp()}] Reconnecting... (${attempt}/${maxRetries})`)}
3865
3884
  `
3866
3885
  );
3867
3886
  },
3868
3887
  onReconnected: () => {
3869
3888
  process.stderr.write(`
3870
- ${chalk__default.default.green(`[${timestamp()}] Reconnected`)}
3889
+ ${chalk2__default.default.green(`[${timestamp()}] Reconnected`)}
3871
3890
  `);
3872
3891
  },
3873
3892
  onReconnectFailed: () => {
3874
3893
  process.stderr.write(`
3875
- ${chalk__default.default.red(`[${timestamp()}] Reconnection failed`)}
3894
+ ${chalk2__default.default.red(`[${timestamp()}] Reconnection failed`)}
3876
3895
  `);
3877
3896
  }
3878
3897
  });
@@ -3890,13 +3909,49 @@ process.on("SIGINT", () => {
3890
3909
  });
3891
3910
 
3892
3911
  // src/helpers.ts
3912
+ var CONNECTION_ERROR_HINTS = {
3913
+ ECONNREFUSED: "Connection refused. Is the backend running?",
3914
+ ECONNRESET: "Connection reset by server. The backend may have restarted.",
3915
+ ENOTFOUND: "DNS resolution failed. Check the server URL.",
3916
+ ETIMEDOUT: "Connection timed out. Check network connectivity.",
3917
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "TLS certificate cannot be verified. The cert may be expired or self-signed.",
3918
+ CERT_HAS_EXPIRED: "TLS certificate has expired. Renew with manage-deployment.",
3919
+ ERR_TLS_CERT_ALTNAME_INVALID: "TLS certificate hostname mismatch. Check the server URL.",
3920
+ DEPTH_ZERO_SELF_SIGNED_CERT: "Self-signed TLS certificate. The cert may need to be renewed.",
3921
+ SELF_SIGNED_CERT_IN_CHAIN: "Self-signed certificate in chain. The cert may need to be renewed."
3922
+ };
3923
+ function diagnoseConnectionError(err) {
3924
+ const code = sdk.getErrorCode(err);
3925
+ if (code && code in CONNECTION_ERROR_HINTS) {
3926
+ return CONNECTION_ERROR_HINTS[code];
3927
+ }
3928
+ const msg = err instanceof Error ? err.message : "";
3929
+ if (msg === "fetch failed" || msg.includes("fetch failed")) {
3930
+ return "Network error connecting to the server. Run `arbi health` to diagnose.";
3931
+ }
3932
+ return void 0;
3933
+ }
3934
+ function formatCliError(err) {
3935
+ const connectionHint = diagnoseConnectionError(err);
3936
+ if (connectionHint) return connectionHint;
3937
+ if (err instanceof sdk.ArbiApiError && err.apiError && typeof err.apiError === "object") {
3938
+ const base = err.message;
3939
+ const apiErr = err.apiError;
3940
+ const detail = apiErr.detail ?? apiErr.message ?? apiErr.error;
3941
+ if (typeof detail === "string" && detail && !base.includes(detail)) {
3942
+ return `${base} \u2014 ${detail}`;
3943
+ }
3944
+ return base;
3945
+ }
3946
+ return sdk.getErrorMessage(err);
3947
+ }
3893
3948
  function runAction(fn) {
3894
3949
  return async () => {
3895
3950
  try {
3896
3951
  await fn();
3897
3952
  process.exit(0);
3898
3953
  } catch (err) {
3899
- error(`Error: ${sdk.getErrorMessage(err)}`);
3954
+ error(`Error: ${formatCliError(err)}`);
3900
3955
  hintUpdateOnError();
3901
3956
  process.exit(1);
3902
3957
  }
@@ -3904,6 +3959,7 @@ function runAction(fn) {
3904
3959
  }
3905
3960
  async function resolveAuth() {
3906
3961
  try {
3962
+ resolveConfig();
3907
3963
  return await sdk.resolveAuth(store);
3908
3964
  } catch (err) {
3909
3965
  if (err instanceof sdk.ArbiError) {
@@ -3915,6 +3971,7 @@ async function resolveAuth() {
3915
3971
  }
3916
3972
  async function resolveWorkspace(workspaceOpt) {
3917
3973
  try {
3974
+ resolveConfig();
3918
3975
  const ctx = await sdk.resolveWorkspace(store, workspaceOpt);
3919
3976
  if (getConfig()?.notifications) {
3920
3977
  startBackgroundNotifications(ctx.config.baseUrl, ctx.accessToken).catch(() => {
@@ -3930,7 +3987,7 @@ async function resolveWorkspace(workspaceOpt) {
3930
3987
  }
3931
3988
  }
3932
3989
  function printTable(columns, rows) {
3933
- console.log(chalk__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
3990
+ console.log(chalk2__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
3934
3991
  for (const row of rows) {
3935
3992
  console.log(
3936
3993
  columns.map((c) => {
@@ -4028,6 +4085,10 @@ function registerWorkspacesCommand(program2) {
4028
4085
  process.exit(1);
4029
4086
  }
4030
4087
  await sdk.workspaces.deleteWorkspaces(arbi, [targetId]);
4088
+ const session = getChatSession();
4089
+ if (session.workspaceId === targetId) {
4090
+ clearChatSession();
4091
+ }
4031
4092
  success(`Deleted workspace ${targetId}`);
4032
4093
  })()
4033
4094
  );
@@ -4207,6 +4268,23 @@ function registerDocsCommand(program2) {
4207
4268
  }
4208
4269
  const data = await sdk.documents.getDocuments(arbi, docIds);
4209
4270
  console.log(JSON.stringify(data, null, 2));
4271
+ const docs = Array.isArray(data) ? data : [data];
4272
+ for (const d of docs) {
4273
+ const raw = d;
4274
+ if (raw.status === "failed") {
4275
+ const reason = raw.error_reason || raw.status_details || raw.error || null;
4276
+ const docId = raw.external_id;
4277
+ if (reason) {
4278
+ console.error(chalk2__default.default.red(`
4279
+ \u26A0 ${docId} processing failed: ${reason}`));
4280
+ } else {
4281
+ console.error(
4282
+ chalk2__default.default.red(`
4283
+ \u26A0 ${docId} processing failed (no error details available)`)
4284
+ );
4285
+ }
4286
+ }
4287
+ }
4210
4288
  })()
4211
4289
  );
4212
4290
  doc.command("delete [ids...]").description("Delete documents (interactive picker if no IDs given)").action(
@@ -4270,39 +4348,30 @@ function registerDocsCommand(program2) {
4270
4348
  }
4271
4349
  })()
4272
4350
  );
4273
- doc.command("parsed [doc-id] [stage]").description("Get parsed document content (stage: marker, subchunk, final)").action(
4351
+ doc.command("parsed <doc-id> [stage]").description("Get parsed document content (stage: marker, subchunk, final; default: final)").action(
4274
4352
  (docId, stage) => runAction(async () => {
4275
- const { arbi, accessToken, workspaceKeyHeader, workspaceId, config } = await resolveWorkspace();
4276
- if (!docId) {
4277
- const choices = await fetchDocChoices(arbi, workspaceId);
4278
- docId = await promptSearch("Select document", choices);
4279
- }
4353
+ const { accessToken, workspaceKeyHeader, config } = await resolveWorkspace();
4280
4354
  const validStages = ["marker", "subchunk", "final"];
4281
- if (!stage) {
4282
- stage = await promptSelect("Parsing stage", [
4283
- { name: "Final (processed)", value: "final" },
4284
- { name: "Subchunk", value: "subchunk" },
4285
- { name: "Marker (raw)", value: "marker" }
4286
- ]);
4287
- } else if (!validStages.includes(stage)) {
4288
- error(`Invalid stage: ${stage}. Must be one of: ${validStages.join(", ")}`);
4355
+ const selectedStage = stage ?? "final";
4356
+ if (!validStages.includes(selectedStage)) {
4357
+ error(`Invalid stage: ${selectedStage}. Must be one of: ${validStages.join(", ")}`);
4289
4358
  process.exit(1);
4290
4359
  }
4291
4360
  const data = await sdk.documents.getParsedContent(
4292
4361
  { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader },
4293
4362
  docId,
4294
- stage
4363
+ selectedStage
4295
4364
  );
4296
4365
  console.log(JSON.stringify(data, null, 2));
4297
4366
  })()
4298
4367
  );
4299
4368
  }
4300
4369
  function registerUploadCommand(program2) {
4301
- 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(
4302
- (files, opts) => runAction(async () => {
4303
- for (const f of files) {
4304
- if (!fs__default.default.existsSync(f)) {
4305
- error(`File not found: ${f}`);
4370
+ program2.command("upload <paths...>").description("Upload files, directories, or zip archives to the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-W, --watch", "Watch document processing progress after upload").action(
4371
+ (paths, opts) => runAction(async () => {
4372
+ for (const p of paths) {
4373
+ if (!fs__default.default.existsSync(p)) {
4374
+ error(`Path not found: ${p}`);
4306
4375
  process.exit(1);
4307
4376
  }
4308
4377
  }
@@ -4311,42 +4380,109 @@ function registerUploadCommand(program2) {
4311
4380
  );
4312
4381
  const uploadedDocs = /* @__PURE__ */ new Map();
4313
4382
  const auth = { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader };
4314
- for (const filePath of files) {
4315
- const result = await sdk.documents.uploadLocalFile(auth, workspaceId, filePath);
4316
- success(`Uploaded: ${result.fileName} (${result.doc_ext_ids.join(", ")})`);
4317
- if (result.duplicates && result.duplicates.length > 0) {
4318
- warn(` Duplicates: ${result.duplicates.join(", ")}`);
4383
+ for (const filePath of paths) {
4384
+ const stat = fs__default.default.statSync(filePath);
4385
+ if (stat.isDirectory()) {
4386
+ const result = await sdk.documentsNode.uploadDirectory(auth, workspaceId, filePath);
4387
+ if (result.doc_ext_ids.length === 0) {
4388
+ warn(`No supported files found in directory: ${filePath}`);
4389
+ continue;
4390
+ }
4391
+ for (const [folder, info] of result.folders) {
4392
+ success(
4393
+ ` ${folder}: ${info.fileCount} file(s) \u2192 ${info.doc_ext_ids.length} uploaded`
4394
+ );
4395
+ if (info.duplicates.length > 0) {
4396
+ warn(` Duplicates: ${info.duplicates.join(", ")}`);
4397
+ }
4398
+ }
4399
+ success(
4400
+ `Uploaded directory: ${filePath} (${result.doc_ext_ids.length} document(s) total)`
4401
+ );
4402
+ for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
4403
+ } else if (filePath.toLowerCase().endsWith(".zip")) {
4404
+ const result = await sdk.documentsNode.uploadZip(auth, workspaceId, filePath);
4405
+ if (result.doc_ext_ids.length === 0) {
4406
+ warn(`No supported files found in zip: ${filePath}`);
4407
+ continue;
4408
+ }
4409
+ for (const [folder, info] of result.folders) {
4410
+ success(
4411
+ ` ${folder}: ${info.fileCount} file(s) \u2192 ${info.doc_ext_ids.length} uploaded`
4412
+ );
4413
+ if (info.duplicates.length > 0) {
4414
+ warn(` Duplicates: ${info.duplicates.join(", ")}`);
4415
+ }
4416
+ }
4417
+ success(`Uploaded zip: ${filePath} (${result.doc_ext_ids.length} document(s) total)`);
4418
+ for (const id of result.doc_ext_ids) uploadedDocs.set(id, filePath);
4419
+ } else {
4420
+ const result = await sdk.documentsNode.uploadLocalFile(auth, workspaceId, filePath);
4421
+ success(`Uploaded: ${result.fileName} (${result.doc_ext_ids.join(", ")})`);
4422
+ if (result.duplicates && result.duplicates.length > 0) {
4423
+ warn(` Duplicates: ${result.duplicates.join(", ")}`);
4424
+ }
4425
+ for (const id of result.doc_ext_ids) uploadedDocs.set(id, result.fileName);
4319
4426
  }
4320
- for (const id of result.doc_ext_ids) uploadedDocs.set(id, result.fileName);
4321
4427
  }
4322
- if (opts.watch && uploadedDocs.size > 0) {
4428
+ const isInteractive = process.stdout.isTTY === true;
4429
+ const shouldWatch = opts.watch === true || paths.length === 1 && isInteractive;
4430
+ if (shouldWatch && uploadedDocs.size > 0) {
4323
4431
  const pending = new Set(uploadedDocs.keys());
4432
+ const failed = /* @__PURE__ */ new Map();
4324
4433
  console.log(`
4325
4434
  Watching ${pending.size} document(s)...`);
4435
+ let onDone;
4436
+ const done = new Promise((r) => {
4437
+ onDone = r;
4438
+ });
4326
4439
  const conn = await sdk.connectWebSocket({
4327
4440
  baseUrl: config.baseUrl,
4328
4441
  accessToken,
4329
4442
  onMessage: (msg) => {
4330
4443
  if (client.isMessageType(msg, "task_update")) {
4331
4444
  if (!pending.has(msg.doc_ext_id)) return;
4332
- console.log(
4333
- ` ${uploadedDocs.get(msg.doc_ext_id) || msg.file_name}: ${status(msg.status)} (${msg.progress}%)`
4334
- );
4335
- if (msg.status === "completed" || msg.status === "failed") {
4445
+ const docName = uploadedDocs.get(msg.doc_ext_id) || msg.file_name;
4446
+ const extra = msg;
4447
+ if (msg.status === "failed") {
4448
+ const reason = extra.error_reason || extra.status_details || extra.detail || "Unknown error";
4449
+ failed.set(msg.doc_ext_id, reason);
4450
+ console.log(` ${docName}: ${status(msg.status)} \u2014 ${reason}`);
4336
4451
  pending.delete(msg.doc_ext_id);
4337
- if (pending.size === 0) {
4338
- success("\nAll documents processed.");
4339
- conn.close();
4452
+ } else {
4453
+ console.log(` ${docName}: ${status(msg.status)} (${msg.progress}%)`);
4454
+ if (msg.status === "completed") {
4455
+ pending.delete(msg.doc_ext_id);
4340
4456
  }
4341
4457
  }
4458
+ if (pending.size === 0) {
4459
+ conn.close();
4460
+ }
4342
4461
  }
4343
4462
  },
4344
4463
  onClose: () => {
4345
- if (pending.size > 0)
4464
+ if (failed.size > 0) {
4465
+ error(`
4466
+ ${failed.size} document(s) failed to process:`);
4467
+ for (const [docId, reason] of failed) {
4468
+ error(` ${uploadedDocs.get(docId) || docId}: ${reason}`);
4469
+ }
4470
+ }
4471
+ if (pending.size > 0) {
4346
4472
  warn(`
4347
4473
  Connection closed. ${pending.size} document(s) still processing.`);
4474
+ dim('Run "arbi watch" to continue monitoring, or "arbi docs" to check status.');
4475
+ } else if (failed.size === 0 && uploadedDocs.size > 0) {
4476
+ success("\nAll documents processed successfully.");
4477
+ }
4478
+ onDone();
4348
4479
  }
4349
4480
  });
4481
+ await done;
4482
+ } else if (uploadedDocs.size > 0 && !shouldWatch) {
4483
+ dim(
4484
+ 'Tip: Use -W/--watch to monitor processing progress, or run "arbi docs" to check status.'
4485
+ );
4350
4486
  }
4351
4487
  })()
4352
4488
  );
@@ -4393,9 +4529,14 @@ function registerAskCommand(program2) {
4393
4529
  const question = words.join(" ");
4394
4530
  const { arbi, accessToken, workspaceKeyHeader, workspaceId, config } = await resolveWorkspace(opts.workspace);
4395
4531
  const session = getChatSession();
4532
+ const workspaceChanged = session.lastMessageExtId && session.workspaceId && session.workspaceId !== workspaceId;
4533
+ let previousResponseId = null;
4396
4534
  if (opts.new) {
4397
4535
  clearChatSession();
4398
- session.lastMessageExtId = null;
4536
+ } else if (workspaceChanged) {
4537
+ clearChatSession();
4538
+ } else if (session.lastMessageExtId) {
4539
+ previousResponseId = session.lastMessageExtId;
4399
4540
  }
4400
4541
  const docs = await sdk.documents.listDocuments(arbi);
4401
4542
  const docIds = docs.map((d) => d.external_id);
@@ -4408,11 +4549,11 @@ function registerAskCommand(program2) {
4408
4549
  workspaceId,
4409
4550
  question,
4410
4551
  docIds,
4411
- previousResponseId: session.lastMessageExtId,
4552
+ previousResponseId,
4412
4553
  model: opts.config
4413
4554
  });
4414
4555
  } catch (err) {
4415
- const isStaleParent = session.lastMessageExtId && err instanceof Error && err.message.includes("Parent message not found");
4556
+ const isStaleParent = previousResponseId && err instanceof Error && (err.message.includes("404") || err.message.includes("Parent message not found"));
4416
4557
  if (!isStaleParent) throw err;
4417
4558
  clearChatSession();
4418
4559
  res = await sdk.assistant.queryAssistant({
@@ -4430,18 +4571,19 @@ function registerAskCommand(program2) {
4430
4571
  onToken: (content) => process.stdout.write(content),
4431
4572
  onAgentStep: (data) => {
4432
4573
  if (opts.verbose) {
4433
- const focus = data.focus || data.step || "";
4434
- console.error(chalk__default.default.dim(`
4435
- [agent] ${focus}`));
4574
+ const label2 = sdk.formatAgentStepLabel(data);
4575
+ if (label2) console.error(chalk2__default.default.dim(`
4576
+ [agent] ${label2}`));
4436
4577
  }
4437
4578
  },
4438
- onError: (message) => console.error(chalk__default.default.red(`
4579
+ onError: (message) => console.error(chalk2__default.default.red(`
4439
4580
  Error: ${message}`))
4440
4581
  });
4441
4582
  process.stdout.write("\n");
4442
4583
  if (result.assistantMessageExtId) {
4443
4584
  const updates = {
4444
- lastMessageExtId: result.assistantMessageExtId
4585
+ lastMessageExtId: result.assistantMessageExtId,
4586
+ workspaceId
4445
4587
  };
4446
4588
  const conversationExtId = result.userMessage?.conversation_ext_id ?? result.metadata?.conversation_ext_id;
4447
4589
  if (conversationExtId) {
@@ -4453,30 +4595,73 @@ Error: ${message}`))
4453
4595
  );
4454
4596
  }
4455
4597
  function colorize2(level, text) {
4456
- if (level === "success") return chalk__default.default.green(text);
4457
- if (level === "error") return chalk__default.default.red(text);
4458
- if (level === "warning") return chalk__default.default.yellow(text);
4598
+ if (level === "success") return chalk2__default.default.green(text);
4599
+ if (level === "error") return chalk2__default.default.red(text);
4600
+ if (level === "warning") return chalk2__default.default.yellow(text);
4459
4601
  return text;
4460
4602
  }
4461
4603
  function registerWatchCommand(program2) {
4462
- program2.command("watch").description("Watch workspace activity in real time").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4604
+ program2.command("watch").description("Watch workspace activity in real time").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-t, --timeout <seconds>", "Auto-close after N seconds").option("-n, --count <n>", "Stop after N messages").option("--json", "Output NDJSON (one JSON object per line)").action(
4463
4605
  (opts) => runAction(async () => {
4464
4606
  const { config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace);
4465
- console.log(`Watching workspace ${workspaceId}... (Ctrl+C to stop)`);
4466
- await sdk.connectWebSocket({
4607
+ const timeoutSec = opts.timeout ? parseInt(opts.timeout, 10) : void 0;
4608
+ const maxCount = opts.count ? parseInt(opts.count, 10) : void 0;
4609
+ const jsonMode = opts.json ?? false;
4610
+ if (!jsonMode) {
4611
+ const parts = [`Watching workspace ${workspaceId}...`];
4612
+ if (timeoutSec) parts.push(`(timeout: ${timeoutSec}s)`);
4613
+ if (maxCount) parts.push(`(max: ${maxCount} messages)`);
4614
+ parts.push("(Ctrl+C to stop)");
4615
+ console.log(parts.join(" "));
4616
+ }
4617
+ let messageCount = 0;
4618
+ let onDone;
4619
+ const done = new Promise((r) => {
4620
+ onDone = r;
4621
+ });
4622
+ const conn = await sdk.connectWebSocket({
4467
4623
  baseUrl: config.baseUrl,
4468
4624
  accessToken,
4469
4625
  onMessage: (msg) => {
4470
- const { text, level } = sdk.formatWsMessage(msg);
4471
- console.log(colorize2(level, text));
4626
+ messageCount++;
4627
+ if (jsonMode) {
4628
+ console.log(JSON.stringify(msg));
4629
+ } else {
4630
+ const { text, level } = sdk.formatWsMessage(msg);
4631
+ console.log(colorize2(level, text));
4632
+ }
4633
+ if (maxCount && messageCount >= maxCount) {
4634
+ if (!jsonMode) console.log(chalk2__default.default.dim(`
4635
+ Reached ${maxCount} messages, closing.`));
4636
+ conn.close();
4637
+ }
4472
4638
  },
4473
4639
  onClose: (code, reason) => {
4474
- console.log(
4475
- chalk__default.default.yellow(`
4640
+ if (!jsonMode) {
4641
+ console.log(
4642
+ chalk2__default.default.yellow(`
4476
4643
  Connection closed (code ${code}${reason ? ": " + reason : ""})`)
4477
- );
4644
+ );
4645
+ }
4646
+ onDone();
4478
4647
  }
4479
4648
  });
4649
+ let timer;
4650
+ if (timeoutSec) {
4651
+ timer = setTimeout(() => {
4652
+ if (!jsonMode) console.log(chalk2__default.default.dim(`
4653
+ Timeout (${timeoutSec}s), closing.`));
4654
+ conn.close();
4655
+ }, timeoutSec * 1e3);
4656
+ }
4657
+ const sigintHandler = () => {
4658
+ if (timer) clearTimeout(timer);
4659
+ conn.close();
4660
+ };
4661
+ process.on("SIGINT", sigintHandler);
4662
+ await done;
4663
+ if (timer) clearTimeout(timer);
4664
+ process.removeListener("SIGINT", sigintHandler);
4480
4665
  })()
4481
4666
  );
4482
4667
  }
@@ -5303,6 +5488,229 @@ Updated to ${latest}.`);
5303
5488
  success("Auto-update enabled. ARBI CLI will update automatically on login.");
5304
5489
  });
5305
5490
  }
5491
+ function registerQuickstartCommand(program2) {
5492
+ program2.command("quickstart").description("Interactive setup wizard \u2014 configure, register/login, and select a workspace").argument("[url]", "Server URL (auto-detected if omitted)").action(async (url) => {
5493
+ console.log("\nWelcome to ARBI CLI setup!\n");
5494
+ const { config, source } = store.resolveConfigWithFallbacks();
5495
+ if (url) {
5496
+ const domain = new URL(url).hostname;
5497
+ config.baseUrl = url.replace(/\/$/, "");
5498
+ config.deploymentDomain = domain;
5499
+ store.saveConfig(config);
5500
+ }
5501
+ const useDetected = await promptConfirm(`Use server ${config.baseUrl}?`, true);
5502
+ if (!useDetected) {
5503
+ const customUrl = await promptInput("Server URL");
5504
+ try {
5505
+ const domain = new URL(customUrl).hostname;
5506
+ config.baseUrl = customUrl.replace(/\/$/, "");
5507
+ config.deploymentDomain = domain;
5508
+ store.saveConfig(config);
5509
+ } catch {
5510
+ error("Invalid URL");
5511
+ process.exit(1);
5512
+ }
5513
+ } else if (source !== "config") {
5514
+ store.saveConfig(config);
5515
+ }
5516
+ dim(`Server: ${config.baseUrl}`);
5517
+ const action = await promptSelect("Do you have an account?", [
5518
+ { name: "Yes, log me in", value: "login" },
5519
+ { name: "No, register a new account", value: "register" }
5520
+ ]);
5521
+ const arbiClient = client.createArbiClient({
5522
+ baseUrl: config.baseUrl,
5523
+ deploymentDomain: config.deploymentDomain,
5524
+ credentials: "omit"
5525
+ });
5526
+ await arbiClient.crypto.initSodium();
5527
+ let email;
5528
+ let password2;
5529
+ if (action === "register") {
5530
+ email = await promptInput("Email");
5531
+ password2 = await promptPassword("Password");
5532
+ const confirmPw = await promptPassword("Confirm password");
5533
+ if (password2 !== confirmPw) {
5534
+ error("Passwords do not match.");
5535
+ process.exit(1);
5536
+ }
5537
+ const codeMethod = await promptSelect("Verification method", [
5538
+ { name: "I have an invitation code", value: "code" },
5539
+ { name: "Send me a verification email", value: "email" }
5540
+ ]);
5541
+ let verificationCode;
5542
+ if (codeMethod === "code") {
5543
+ verificationCode = await promptInput("Invitation code");
5544
+ } else {
5545
+ console.log("Sending verification email...");
5546
+ const verifyResponse = await arbiClient.fetch.POST("/v1/user/verify-email", {
5547
+ body: { email }
5548
+ });
5549
+ if (verifyResponse.error) {
5550
+ error(`Failed to send verification email: ${JSON.stringify(verifyResponse.error)}`);
5551
+ process.exit(1);
5552
+ }
5553
+ success("Verification email sent. Check your inbox.");
5554
+ verificationCode = await promptInput("Verification code");
5555
+ }
5556
+ try {
5557
+ const firstName = await promptInput("First name", false) || "User";
5558
+ const lastName = await promptInput("Last name", false) || "";
5559
+ await arbiClient.auth.register({
5560
+ email,
5561
+ password: password2,
5562
+ verificationCode,
5563
+ firstName,
5564
+ lastName
5565
+ });
5566
+ success(`Registered as ${email}`);
5567
+ } catch (err) {
5568
+ error(`Registration failed: ${sdk.getErrorMessage(err)}`);
5569
+ process.exit(1);
5570
+ }
5571
+ } else {
5572
+ email = await promptInput("Email");
5573
+ password2 = await promptPassword("Password");
5574
+ }
5575
+ try {
5576
+ const { arbi } = await sdk.performPasswordLogin(config, email, password2, store);
5577
+ success(`Logged in as ${email}`);
5578
+ const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
5579
+ const wsList = workspaces2 || [];
5580
+ const memberWorkspaces = wsList.filter((ws) => ws.users?.some((u) => u.email === email));
5581
+ let workspaceId;
5582
+ let workspaceName;
5583
+ if (memberWorkspaces.length === 0) {
5584
+ console.log("Creating your first workspace...");
5585
+ const ws = await sdk.workspaces.createWorkspace(arbi, "My Workspace");
5586
+ workspaceId = ws.external_id;
5587
+ workspaceName = ws.name;
5588
+ } else {
5589
+ const choices = [
5590
+ ...sdk.formatWorkspaceChoices(memberWorkspaces),
5591
+ { name: "+ Create new workspace", value: "__new__", description: "" }
5592
+ ];
5593
+ const selected = await promptSelect("Select workspace", choices);
5594
+ if (selected === "__new__") {
5595
+ const name = await promptInput("Workspace name", false) || "My Workspace";
5596
+ const ws = await sdk.workspaces.createWorkspace(arbi, name);
5597
+ workspaceId = ws.external_id;
5598
+ workspaceName = ws.name;
5599
+ } else {
5600
+ workspaceId = selected;
5601
+ workspaceName = memberWorkspaces.find((w) => w.external_id === selected)?.name || "";
5602
+ }
5603
+ }
5604
+ updateConfig({ selectedWorkspaceId: workspaceId });
5605
+ console.log("");
5606
+ success("Setup complete!");
5607
+ console.log("");
5608
+ console.log(` Server: ${ref(config.baseUrl)}`);
5609
+ console.log(` Account: ${ref(email)}`);
5610
+ console.log(` Workspace: ${workspaceName} (${ref(workspaceId)})`);
5611
+ console.log("");
5612
+ dim('Try: arbi ask "hello"');
5613
+ } catch (err) {
5614
+ error(`Login failed: ${sdk.getErrorMessage(err)}`);
5615
+ process.exit(1);
5616
+ }
5617
+ });
5618
+ }
5619
+ var CENTRAL_API_URL2 = "https://central.arbi.work";
5620
+ var DEFAULT_PASSWORD = "agent-dev-1234";
5621
+ async function getVerificationCode2(email, apiKey) {
5622
+ const params = new URLSearchParams({ email });
5623
+ const res = await fetch(`${CENTRAL_API_URL2}/license-management/verify-ci?${params.toString()}`, {
5624
+ method: "GET",
5625
+ headers: { "x-api-key": apiKey }
5626
+ });
5627
+ if (!res.ok) {
5628
+ const body = await res.text().catch(() => "");
5629
+ throw new Error(`Failed to get verification code: ${res.status} ${body}`);
5630
+ }
5631
+ const data = await res.json();
5632
+ const words = data?.verification_words ?? data?.verification_code ?? null;
5633
+ if (!words) throw new Error("No verification code in response");
5634
+ return Array.isArray(words) ? words.join(" ") : String(words);
5635
+ }
5636
+ function registerAgentCreateCommand(program2) {
5637
+ program2.command("agent-create").description("Create a bot/test account (requires SUPPORT_API_KEY)").argument("[url]", "Server URL (auto-detected if omitted)").option("-p, --password <password>", "Account password", DEFAULT_PASSWORD).option("--workspace-name <name>", "Workspace name", "Agent Workspace").option("--email <email>", "Custom email (default: agent-{timestamp}@{domain})").action(
5638
+ async (url, opts) => {
5639
+ const { config, source } = store.resolveConfigWithFallbacks();
5640
+ if (url) {
5641
+ const domain = new URL(url).hostname;
5642
+ config.baseUrl = url.replace(/\/$/, "");
5643
+ config.deploymentDomain = domain;
5644
+ store.saveConfig(config);
5645
+ dim(`Server: ${config.baseUrl}`);
5646
+ } else {
5647
+ dim(`Server: ${config.baseUrl} (from ${source})`);
5648
+ }
5649
+ const supportApiKey = process.env.SUPPORT_API_KEY;
5650
+ if (!supportApiKey) {
5651
+ error(
5652
+ "SUPPORT_API_KEY is required.\nSet it with: export SUPPORT_API_KEY=<key>\nOr source from .env: source .env && arbi agent-create"
5653
+ );
5654
+ process.exit(1);
5655
+ }
5656
+ const timestamp2 = Date.now();
5657
+ let email = opts.email || `agent-${timestamp2}@${config.deploymentDomain}`;
5658
+ if (!email.includes("@")) {
5659
+ email = `${email}@${config.deploymentDomain}`;
5660
+ }
5661
+ dim(`Email: ${email}`);
5662
+ const arbiClient = client.createArbiClient({
5663
+ baseUrl: config.baseUrl,
5664
+ deploymentDomain: config.deploymentDomain,
5665
+ credentials: "omit"
5666
+ });
5667
+ await arbiClient.crypto.initSodium();
5668
+ const verifyResponse = await arbiClient.fetch.POST("/v1/user/verify-email", {
5669
+ body: { email }
5670
+ });
5671
+ if (verifyResponse.error) {
5672
+ error(`verify-email failed: ${JSON.stringify(verifyResponse.error)}`);
5673
+ process.exit(1);
5674
+ }
5675
+ let verificationCode;
5676
+ try {
5677
+ verificationCode = await getVerificationCode2(email, supportApiKey);
5678
+ } catch (err) {
5679
+ error(`Failed to get verification code: ${sdk.getErrorMessage(err)}`);
5680
+ process.exit(1);
5681
+ }
5682
+ try {
5683
+ await arbiClient.auth.register({
5684
+ email,
5685
+ password: opts.password,
5686
+ verificationCode,
5687
+ firstName: "Agent",
5688
+ lastName: `${timestamp2}`
5689
+ });
5690
+ } catch (err) {
5691
+ error(`Registration failed: ${sdk.getErrorMessage(err)}`);
5692
+ process.exit(1);
5693
+ }
5694
+ try {
5695
+ const { arbi } = await sdk.performPasswordLogin(config, email, opts.password, store);
5696
+ const ws = await sdk.workspaces.createWorkspace(arbi, opts.workspaceName);
5697
+ updateConfig({ selectedWorkspaceId: ws.external_id });
5698
+ console.log("");
5699
+ success("Agent account created!");
5700
+ console.log("");
5701
+ console.log(` Email: ${ref(email)}`);
5702
+ console.log(` Password: ${ref(opts.password)}`);
5703
+ console.log(` Workspace: ${ws.name} (${ref(ws.external_id)})`);
5704
+ console.log(` Server: ${ref(config.baseUrl)}`);
5705
+ console.log("");
5706
+ dim('Ready to use: arbi ask "hello"');
5707
+ } catch (err) {
5708
+ error(`Post-registration setup failed: ${sdk.getErrorMessage(err)}`);
5709
+ process.exit(1);
5710
+ }
5711
+ }
5712
+ );
5713
+ }
5306
5714
 
5307
5715
  // src/index.ts
5308
5716
  console.debug = () => {
@@ -5313,7 +5721,7 @@ console.info = (...args) => {
5313
5721
  _origInfo(...args);
5314
5722
  };
5315
5723
  var program = new commander.Command();
5316
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.10");
5724
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.13");
5317
5725
  registerConfigCommand(program);
5318
5726
  registerLoginCommand(program);
5319
5727
  registerRegisterCommand(program);
@@ -5335,6 +5743,8 @@ registerAgentconfigCommand(program);
5335
5743
  registerHealthCommand(program);
5336
5744
  registerTuiCommand(program);
5337
5745
  registerUpdateCommand(program);
5746
+ registerQuickstartCommand(program);
5747
+ registerAgentCreateCommand(program);
5338
5748
  program.parse();
5339
5749
  //# sourceMappingURL=index.js.map
5340
5750
  //# sourceMappingURL=index.js.map