@arbidocs/cli 0.3.15 → 0.3.17

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
@@ -2,9 +2,9 @@
2
2
  'use strict';
3
3
 
4
4
  var commander = require('commander');
5
- var fs = require('fs');
6
- var os = require('os');
5
+ var fs2 = require('fs');
7
6
  var path = require('path');
7
+ var os = require('os');
8
8
  var chalk2 = require('chalk');
9
9
  var sdk = require('@arbidocs/sdk');
10
10
  var prompts = require('@inquirer/prompts');
@@ -15,11 +15,104 @@ var module$1 = require('module');
15
15
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
16
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
17
 
18
- var fs__default = /*#__PURE__*/_interopDefault(fs);
19
- var os__default = /*#__PURE__*/_interopDefault(os);
18
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
20
19
  var path__default = /*#__PURE__*/_interopDefault(path);
20
+ var os__default = /*#__PURE__*/_interopDefault(os);
21
21
  var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
22
22
 
23
+ function getCacheFile() {
24
+ const configDir = process.env.ARBI_CONFIG_DIR ?? path__default.default.join(os__default.default.homedir(), ".arbi");
25
+ return path__default.default.join(configDir, "completions.json");
26
+ }
27
+ function ensureDir(filePath) {
28
+ const dir = path__default.default.dirname(filePath);
29
+ if (!fs2__default.default.existsSync(dir)) {
30
+ fs2__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
31
+ }
32
+ }
33
+ function getCachedWorkspaceIds() {
34
+ try {
35
+ const content = fs2__default.default.readFileSync(getCacheFile(), "utf-8");
36
+ const cache = JSON.parse(content);
37
+ return cache.workspaces.map((w) => w.id);
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+ function updateCompletionCache(workspaces3) {
43
+ const cache = {
44
+ workspaces: workspaces3.filter((w) => w.external_id).map((w) => ({ id: w.external_id, name: w.name ?? "" })),
45
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
46
+ };
47
+ const filePath = getCacheFile();
48
+ ensureDir(filePath);
49
+ fs2__default.default.writeFileSync(filePath, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
50
+ }
51
+
52
+ // src/completion.ts
53
+ var WORKSPACE_ID_OPTIONS = /* @__PURE__ */ new Set(["-w", "--workspace"]);
54
+ function getOptionFlags(cmd) {
55
+ const flags = [];
56
+ for (const opt of cmd.options) {
57
+ if (opt.short) flags.push(opt.short);
58
+ if (opt.long) flags.push(opt.long);
59
+ }
60
+ return flags;
61
+ }
62
+ function findOption(cmd, flag) {
63
+ return cmd.options.find((o) => o.short === flag || o.long === flag);
64
+ }
65
+ function getCompletions(program2, line) {
66
+ const parts = line.trim().split(/\s+/);
67
+ if (parts.length > 0) {
68
+ const first = parts[0];
69
+ if (first === program2.name() || first.endsWith("/" + program2.name())) {
70
+ parts.shift();
71
+ }
72
+ }
73
+ const endsWithSpace = line.endsWith(" ");
74
+ let current = program2;
75
+ let consumed = 0;
76
+ for (let i = 0; i < parts.length; i++) {
77
+ const word = parts[i];
78
+ const sub = current.commands.find(
79
+ (c) => c.name() === word || c.aliases().includes(word)
80
+ );
81
+ if (sub) {
82
+ current = sub;
83
+ consumed = i + 1;
84
+ } else {
85
+ break;
86
+ }
87
+ }
88
+ const remaining = parts.slice(consumed);
89
+ const lastWord = remaining.length > 0 ? remaining[remaining.length - 1] : "";
90
+ const prevWord = endsWithSpace ? remaining[remaining.length - 1] : remaining[remaining.length - 2];
91
+ if (prevWord && WORKSPACE_ID_OPTIONS.has(prevWord)) {
92
+ return getCachedWorkspaceIds();
93
+ }
94
+ const subcommands = current.commands.map((c) => c.name());
95
+ const options = getOptionFlags(current);
96
+ const allCandidates = [...subcommands, ...options];
97
+ if (remaining.length === 0 || endsWithSpace && remaining.length >= 0) {
98
+ if (endsWithSpace) {
99
+ const used = /* @__PURE__ */ new Set();
100
+ for (const part of remaining) {
101
+ if (part.startsWith("-")) {
102
+ used.add(part);
103
+ const opt = findOption(current, part);
104
+ if (opt) {
105
+ if (opt.short) used.add(opt.short);
106
+ if (opt.long) used.add(opt.long);
107
+ }
108
+ }
109
+ }
110
+ return allCandidates.filter((c) => !used.has(c));
111
+ }
112
+ return allCandidates;
113
+ }
114
+ return allCandidates.filter((c) => c.startsWith(lastWord));
115
+ }
23
116
  var store = new sdk.FileConfigStore();
24
117
  function getConfig() {
25
118
  return store.getConfig();
@@ -51,6 +144,12 @@ function getCredentials() {
51
144
  function deleteCredentials() {
52
145
  store.deleteCredentials();
53
146
  }
147
+ function saveLastMetadata(metadata) {
148
+ store.saveLastMetadata?.(metadata);
149
+ }
150
+ function loadLastMetadata() {
151
+ return store.loadLastMetadata?.() ?? null;
152
+ }
54
153
  function getChatSession() {
55
154
  return store.getChatSession();
56
155
  }
@@ -98,8 +197,8 @@ function getShellRcPath() {
98
197
  return path.join(os.homedir(), ".bashrc");
99
198
  }
100
199
  function isAliasInstalled(rcPath) {
101
- if (!fs.existsSync(rcPath)) return false;
102
- const content = fs.readFileSync(rcPath, "utf-8");
200
+ if (!fs2.existsSync(rcPath)) return false;
201
+ const content = fs2.readFileSync(rcPath, "utf-8");
103
202
  return content.includes(ALIAS_LINE) || content.includes(ALIAS_MARKER);
104
203
  }
105
204
  function registerConfigCommand(program2) {
@@ -178,7 +277,7 @@ function registerConfigCommand(program2) {
178
277
  dim("Usage: A what is the meaning of life");
179
278
  return;
180
279
  }
181
- fs.appendFileSync(rcPath, `
280
+ fs2.appendFileSync(rcPath, `
182
281
  ${ALIAS_MARKER}
183
282
  ${ALIAS_LINE}
184
283
  `);
@@ -3504,7 +3603,7 @@ var CACHE_FILE = path__default.default.join(os__default.default.homedir(), ".arb
3504
3603
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
3505
3604
  function readCache() {
3506
3605
  try {
3507
- const data = JSON.parse(fs__default.default.readFileSync(CACHE_FILE, "utf8"));
3606
+ const data = JSON.parse(fs2__default.default.readFileSync(CACHE_FILE, "utf8"));
3508
3607
  if (data.latest && data.checkedAt && Date.now() - data.checkedAt < CACHE_TTL_MS) {
3509
3608
  return data;
3510
3609
  }
@@ -3515,8 +3614,8 @@ function readCache() {
3515
3614
  function writeCache(latest) {
3516
3615
  try {
3517
3616
  const dir = path__default.default.dirname(CACHE_FILE);
3518
- if (!fs__default.default.existsSync(dir)) fs__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
3519
- fs__default.default.writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }) + "\n");
3617
+ if (!fs2__default.default.existsSync(dir)) fs2__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
3618
+ fs2__default.default.writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }) + "\n");
3520
3619
  } catch {
3521
3620
  }
3522
3621
  }
@@ -3537,13 +3636,13 @@ function getLatestVersion(skipCache = false) {
3537
3636
  }
3538
3637
  }
3539
3638
  function getCurrentVersion() {
3540
- return "0.3.15";
3639
+ return "0.3.17";
3541
3640
  }
3542
3641
  function readChangelog(fromVersion, toVersion) {
3543
3642
  try {
3544
3643
  const globalRoot = child_process.execSync("npm root -g", { encoding: "utf8", timeout: 5e3 }).trim();
3545
3644
  const changelogPath = path__default.default.join(globalRoot, "@arbidocs", "cli", "CHANGELOG.md");
3546
- const text = fs__default.default.readFileSync(changelogPath, "utf8");
3645
+ const text = fs2__default.default.readFileSync(changelogPath, "utf8");
3547
3646
  return extractSections(text, fromVersion, toVersion);
3548
3647
  } catch {
3549
3648
  return null;
@@ -3590,17 +3689,17 @@ function showChangelog(fromVersion, toVersion) {
3590
3689
  async function checkForUpdates(autoUpdate) {
3591
3690
  try {
3592
3691
  const latest = getLatestVersion();
3593
- if (!latest || latest === "0.3.15") return;
3692
+ if (!latest || latest === "0.3.17") return;
3594
3693
  if (autoUpdate) {
3595
3694
  warn(`
3596
- Your arbi version is out of date (${"0.3.15"} \u2192 ${latest}). Updating...`);
3695
+ Your arbi version is out of date (${"0.3.17"} \u2192 ${latest}). Updating...`);
3597
3696
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3598
- showChangelog("0.3.15", latest);
3697
+ showChangelog("0.3.17", latest);
3599
3698
  console.log(`Updated to ${latest}.`);
3600
3699
  } else {
3601
3700
  warn(
3602
3701
  `
3603
- Your arbi version is out of date (${"0.3.15"} \u2192 ${latest}).
3702
+ Your arbi version is out of date (${"0.3.17"} \u2192 ${latest}).
3604
3703
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3605
3704
  );
3606
3705
  }
@@ -3610,150 +3709,359 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3610
3709
  function hintUpdateOnError() {
3611
3710
  try {
3612
3711
  const cached = readCache();
3613
- if (cached && cached.latest !== "0.3.15") {
3712
+ if (cached && cached.latest !== "0.3.17") {
3614
3713
  warn(
3615
- `Your arbi version is out of date (${"0.3.15"} \u2192 ${cached.latest}). Run "arbi update".`
3714
+ `Your arbi version is out of date (${"0.3.17"} \u2192 ${cached.latest}). Run "arbi update".`
3616
3715
  );
3617
3716
  }
3618
3717
  } catch {
3619
3718
  }
3620
3719
  }
3720
+ var MAX_TASKS = 50;
3721
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3722
+ function getTasksFile() {
3723
+ const configDir = process.env.ARBI_CONFIG_DIR ?? path__default.default.join(os__default.default.homedir(), ".arbi");
3724
+ return path__default.default.join(configDir, "tasks.json");
3725
+ }
3726
+ function ensureDir2(filePath) {
3727
+ const dir = path__default.default.dirname(filePath);
3728
+ if (!fs2__default.default.existsSync(dir)) {
3729
+ fs2__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
3730
+ }
3731
+ }
3732
+ function readTasks() {
3733
+ try {
3734
+ const content = fs2__default.default.readFileSync(getTasksFile(), "utf-8");
3735
+ return JSON.parse(content);
3736
+ } catch {
3737
+ return [];
3738
+ }
3739
+ }
3740
+ function writeTasks(tasks) {
3741
+ const filePath = getTasksFile();
3742
+ ensureDir2(filePath);
3743
+ fs2__default.default.writeFileSync(filePath, JSON.stringify(tasks, null, 2) + "\n", { mode: 384 });
3744
+ }
3745
+ function getTasks() {
3746
+ const now = Date.now();
3747
+ const tasks = readTasks().filter((t) => now - new Date(t.submittedAt).getTime() < MAX_AGE_MS);
3748
+ return tasks;
3749
+ }
3750
+ function addTask(task) {
3751
+ const tasks = [task, ...getTasks().filter((t) => t.id !== task.id)].slice(0, MAX_TASKS);
3752
+ writeTasks(tasks);
3753
+ }
3754
+ function updateTaskStatus(id, status2) {
3755
+ const tasks = readTasks();
3756
+ const task = tasks.find((t) => t.id === id);
3757
+ if (task) {
3758
+ task.status = status2;
3759
+ writeTasks(tasks);
3760
+ }
3761
+ }
3762
+ function getLatestTask() {
3763
+ const tasks = getTasks();
3764
+ return tasks[0] ?? null;
3765
+ }
3621
3766
 
3622
- // src/commands/login.ts
3623
- function registerLoginCommand(program2) {
3624
- 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) => {
3625
- const config = store.requireConfig();
3626
- const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
3627
- const pw = opts.password || process.env.ARBI_PASSWORD || await promptPassword("Password");
3628
- try {
3629
- const { arbi } = await sdk.performPasswordLogin(config, email, pw, store);
3630
- clearChatSession();
3631
- const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
3632
- const wsList = workspaces2 || [];
3633
- const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
3634
- if (memberWorkspaces.length === 0) {
3635
- console.log("No workspaces found. Create one with: arbi workspace create <name>");
3636
- return;
3637
- }
3638
- if (opts.workspace) {
3639
- const ws2 = memberWorkspaces.find((w) => w.external_id === opts.workspace);
3640
- if (!ws2) {
3641
- error(`Workspace ${opts.workspace} not found or you don't have access.`);
3642
- process.exit(1);
3767
+ // src/notifications.ts
3768
+ var activeConnection = null;
3769
+ function timestamp() {
3770
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
3771
+ }
3772
+ function colorize(level, text) {
3773
+ if (level === "success") return chalk2__default.default.green(text);
3774
+ if (level === "error") return chalk2__default.default.red(text);
3775
+ if (level === "warning") return chalk2__default.default.yellow(text);
3776
+ return text;
3777
+ }
3778
+ async function startBackgroundNotifications(baseUrl, accessToken) {
3779
+ if (activeConnection) return;
3780
+ try {
3781
+ activeConnection = await sdk.connectWithReconnect({
3782
+ baseUrl,
3783
+ accessToken,
3784
+ onMessage: (msg) => {
3785
+ if (client.isMessageType(msg, "response_complete")) {
3786
+ updateTaskStatus(msg.response_id, msg.status);
3643
3787
  }
3644
- updateConfig({ selectedWorkspaceId: ws2.external_id });
3645
- success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
3646
- return;
3647
- }
3648
- if (memberWorkspaces.length === 1) {
3649
- updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
3650
- success(
3651
- `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
3788
+ const { text, level } = sdk.formatWsMessage(msg);
3789
+ process.stderr.write(`
3790
+ ${colorize(level, `[${timestamp()}] ${text}`)}
3791
+ `);
3792
+ },
3793
+ onClose: () => {
3794
+ activeConnection = null;
3795
+ },
3796
+ onReconnecting: (attempt, maxRetries) => {
3797
+ process.stderr.write(
3798
+ `
3799
+ ${chalk2__default.default.yellow(`[${timestamp()}] Reconnecting... (${attempt}/${maxRetries})`)}
3800
+ `
3652
3801
  );
3653
- return;
3802
+ },
3803
+ onReconnected: () => {
3804
+ process.stderr.write(`
3805
+ ${chalk2__default.default.green(`[${timestamp()}] Reconnected`)}
3806
+ `);
3807
+ },
3808
+ onReconnectFailed: () => {
3809
+ process.stderr.write(`
3810
+ ${chalk2__default.default.red(`[${timestamp()}] Reconnection failed`)}
3811
+ `);
3654
3812
  }
3655
- const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
3656
- const selected = await promptSelect("Select workspace", choices);
3657
- updateConfig({ selectedWorkspaceId: selected });
3658
- const ws = memberWorkspaces.find((w) => w.external_id === selected);
3659
- success(`Workspace: ${ws.name} (${ref(selected)})`);
3660
- dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
3661
- } catch (err) {
3662
- error(`Login failed: ${sdk.getErrorMessage(err)}`);
3663
- process.exit(1);
3664
- } finally {
3665
- await checkForUpdates(getConfig()?.autoUpdate);
3666
- }
3667
- });
3668
- }
3669
- var CENTRAL_API_URL = "https://central.arbi.work";
3670
- async function getVerificationCode(email, apiKey) {
3671
- const params = new URLSearchParams({ email });
3672
- const res = await fetch(`${CENTRAL_API_URL}/license-management/verify-ci?${params.toString()}`, {
3673
- method: "GET",
3674
- headers: { "x-api-key": apiKey }
3675
- });
3676
- if (!res.ok) {
3677
- const body = await res.text().catch(() => "");
3678
- throw new Error(`Failed to get verification code: ${res.status} ${body}`);
3813
+ });
3814
+ } catch {
3679
3815
  }
3680
- const data = await res.json();
3681
- const words = data?.verification_words ?? data?.verification_code ?? null;
3682
- if (!words) throw new Error("No verification code in response");
3683
- return Array.isArray(words) ? words.join(" ") : String(words);
3684
3816
  }
3685
- function registerRegisterCommand(program2) {
3686
- 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("-c, --verification-code <code>", "Verification code (skip prompt)").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").action(async (opts) => {
3687
- const config = requireConfig();
3688
- if (opts.nonInteractive) {
3689
- await nonInteractiveRegister(config, opts);
3690
- } else {
3691
- await smartRegister(config, opts);
3692
- }
3693
- });
3817
+ function stopBackgroundNotifications() {
3818
+ activeConnection?.close();
3819
+ activeConnection = null;
3694
3820
  }
3695
- async function smartRegister(config, opts) {
3696
- let email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
3697
- if ((opts.email || process.env.ARBI_EMAIL) && !email.includes("@")) {
3698
- email = `${email}@${config.deploymentDomain}`;
3699
- console.log(`Using email: ${email}`);
3821
+ process.on("exit", stopBackgroundNotifications);
3822
+ process.on("SIGINT", () => {
3823
+ stopBackgroundNotifications();
3824
+ process.exit(0);
3825
+ });
3826
+
3827
+ // src/helpers.ts
3828
+ var CONNECTION_ERROR_HINTS = {
3829
+ ECONNREFUSED: "Connection refused. Is the backend running?",
3830
+ ECONNRESET: "Connection reset by server. The backend may have restarted.",
3831
+ ENOTFOUND: "DNS resolution failed. Check the server URL.",
3832
+ ETIMEDOUT: "Connection timed out. Check network connectivity.",
3833
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "TLS certificate cannot be verified. The cert may be expired or self-signed.",
3834
+ CERT_HAS_EXPIRED: "TLS certificate has expired. Renew with manage-deployment.",
3835
+ ERR_TLS_CERT_ALTNAME_INVALID: "TLS certificate hostname mismatch. Check the server URL.",
3836
+ DEPTH_ZERO_SELF_SIGNED_CERT: "Self-signed TLS certificate. The cert may need to be renewed.",
3837
+ SELF_SIGNED_CERT_IN_CHAIN: "Self-signed certificate in chain. The cert may need to be renewed."
3838
+ };
3839
+ function diagnoseConnectionError(err) {
3840
+ const code = sdk.getErrorCode(err);
3841
+ if (code && code in CONNECTION_ERROR_HINTS) {
3842
+ return CONNECTION_ERROR_HINTS[code];
3700
3843
  }
3701
- const arbi = client.createArbiClient({
3702
- baseUrl: config.baseUrl,
3703
- deploymentDomain: config.deploymentDomain,
3704
- credentials: "omit"
3705
- });
3706
- await arbi.crypto.initSodium();
3707
- let verificationCode;
3708
- if (opts.verificationCode) {
3709
- verificationCode = opts.verificationCode;
3710
- } else {
3711
- const codeMethod = await promptSelect("Verification method", [
3712
- { name: "I have an invitation code", value: "code" },
3713
- { name: "Send me a verification email", value: "email" }
3714
- ]);
3715
- if (codeMethod === "code") {
3716
- verificationCode = await promptInput("Invitation code");
3717
- } else {
3718
- console.log("Sending verification email...");
3719
- const verifyResponse = await arbi.fetch.POST("/v1/user/verify-email", {
3720
- body: { email }
3721
- });
3722
- if (verifyResponse.error) {
3723
- error(`Failed to send verification email: ${JSON.stringify(verifyResponse.error)}`);
3724
- process.exit(1);
3725
- }
3726
- success("Verification email sent. Check your inbox.");
3727
- verificationCode = await promptInput("Verification code");
3844
+ const msg = err instanceof Error ? err.message : "";
3845
+ if (msg === "fetch failed" || msg.includes("fetch failed")) {
3846
+ return "Network error connecting to the server. Run `arbi health` to diagnose.";
3847
+ }
3848
+ return void 0;
3849
+ }
3850
+ function formatCliError(err) {
3851
+ const connectionHint = diagnoseConnectionError(err);
3852
+ if (connectionHint) return connectionHint;
3853
+ if (err instanceof sdk.ArbiApiError && err.apiError && typeof err.apiError === "object") {
3854
+ const base = err.message;
3855
+ const apiErr = err.apiError;
3856
+ const detail = apiErr.detail ?? apiErr.message ?? apiErr.error;
3857
+ if (typeof detail === "string" && detail && !base.includes(detail)) {
3858
+ return `${base} \u2014 ${detail}`;
3728
3859
  }
3860
+ return base;
3729
3861
  }
3730
- let pw;
3731
- const flagOrEnvPassword = opts.password || process.env.ARBI_PASSWORD;
3732
- if (flagOrEnvPassword) {
3733
- pw = flagOrEnvPassword;
3734
- } else {
3735
- pw = await promptPassword("Password");
3736
- const confirmPw = await promptPassword("Confirm password");
3737
- if (pw !== confirmPw) {
3738
- error("Passwords do not match.");
3862
+ return sdk.getErrorMessage(err);
3863
+ }
3864
+ function runAction(fn) {
3865
+ return async () => {
3866
+ try {
3867
+ await fn();
3868
+ process.exit(0);
3869
+ } catch (err) {
3870
+ error(`Error: ${formatCliError(err)}`);
3871
+ hintUpdateOnError();
3739
3872
  process.exit(1);
3740
3873
  }
3741
- }
3742
- const hasAllCoreFlags = !!(opts.email || process.env.ARBI_EMAIL) && !!(opts.password || process.env.ARBI_PASSWORD) && !!opts.verificationCode;
3743
- const firstName = opts.firstName || (hasAllCoreFlags ? "User" : await promptInput("First name", false) || "User");
3744
- const lastName = opts.lastName || (hasAllCoreFlags ? "" : await promptInput("Last name", false) || "");
3874
+ };
3875
+ }
3876
+ async function resolveAuth() {
3745
3877
  try {
3746
- await arbi.auth.register({
3747
- email,
3748
- password: pw,
3749
- verificationCode,
3750
- firstName,
3878
+ resolveConfig();
3879
+ return await sdk.resolveAuth(store);
3880
+ } catch (err) {
3881
+ if (err instanceof sdk.ArbiError) {
3882
+ error(err.message);
3883
+ process.exit(1);
3884
+ }
3885
+ throw err;
3886
+ }
3887
+ }
3888
+ async function resolveWorkspace(workspaceOpt) {
3889
+ try {
3890
+ resolveConfig();
3891
+ const ctx = await sdk.resolveWorkspace(store, workspaceOpt);
3892
+ if (getConfig()?.notifications !== false) {
3893
+ startBackgroundNotifications(ctx.config.baseUrl, ctx.accessToken).catch(() => {
3894
+ });
3895
+ }
3896
+ return ctx;
3897
+ } catch (err) {
3898
+ if (err instanceof sdk.ArbiError) {
3899
+ error(err.message);
3900
+ process.exit(1);
3901
+ }
3902
+ throw err;
3903
+ }
3904
+ }
3905
+ function printTable(columns, rows) {
3906
+ console.log(chalk2__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
3907
+ for (const row of rows) {
3908
+ console.log(
3909
+ columns.map((c) => {
3910
+ const val = c.value(row);
3911
+ return val.slice(0, c.width - 2).padEnd(c.width);
3912
+ }).join("")
3913
+ );
3914
+ }
3915
+ }
3916
+ function parseJsonArg(input2, example) {
3917
+ try {
3918
+ return JSON.parse(input2);
3919
+ } catch {
3920
+ error(`Invalid JSON. Example: ${example}`);
3921
+ process.exit(1);
3922
+ }
3923
+ }
3924
+
3925
+ // src/commands/login.ts
3926
+ function registerLoginCommand(program2) {
3927
+ 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(
3928
+ (opts) => runAction(async () => {
3929
+ const config = store.requireConfig();
3930
+ const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
3931
+ const pw = opts.password || process.env.ARBI_PASSWORD || await promptPassword("Password");
3932
+ try {
3933
+ const { arbi } = await sdk.performPasswordLogin(config, email, pw, store);
3934
+ clearChatSession();
3935
+ const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
3936
+ const wsList = workspaces3 || [];
3937
+ updateCompletionCache(wsList);
3938
+ const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
3939
+ if (memberWorkspaces.length === 0) {
3940
+ console.log("No workspaces found. Create one with: arbi workspace create <name>");
3941
+ return;
3942
+ }
3943
+ if (opts.workspace) {
3944
+ const ws2 = memberWorkspaces.find((w) => w.external_id === opts.workspace);
3945
+ if (!ws2) {
3946
+ error(`Workspace ${opts.workspace} not found or you don't have access.`);
3947
+ process.exit(1);
3948
+ }
3949
+ updateConfig({ selectedWorkspaceId: ws2.external_id });
3950
+ success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
3951
+ return;
3952
+ }
3953
+ if (memberWorkspaces.length === 1) {
3954
+ updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
3955
+ success(
3956
+ `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
3957
+ );
3958
+ return;
3959
+ }
3960
+ const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
3961
+ const selected = await promptSelect("Select workspace", choices);
3962
+ updateConfig({ selectedWorkspaceId: selected });
3963
+ const ws = memberWorkspaces.find((w) => w.external_id === selected);
3964
+ success(`Workspace: ${ws.name} (${ref(selected)})`);
3965
+ dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
3966
+ } catch (err) {
3967
+ error(`Login failed: ${formatCliError(err)}`);
3968
+ process.exit(1);
3969
+ } finally {
3970
+ await checkForUpdates(getConfig()?.autoUpdate);
3971
+ }
3972
+ })()
3973
+ );
3974
+ }
3975
+ var CENTRAL_API_URL = "https://central.arbi.work";
3976
+ async function getVerificationCode(email, apiKey) {
3977
+ const params = new URLSearchParams({ email });
3978
+ const res = await fetch(`${CENTRAL_API_URL}/license-management/verify-ci?${params.toString()}`, {
3979
+ method: "GET",
3980
+ headers: { "x-api-key": apiKey }
3981
+ });
3982
+ if (!res.ok) {
3983
+ const body = await res.text().catch(() => "");
3984
+ throw new Error(`Failed to get verification code: ${res.status} ${body}`);
3985
+ }
3986
+ const data = await res.json();
3987
+ const words = data?.verification_words ?? data?.verification_code ?? null;
3988
+ if (!words) throw new Error("No verification code in response");
3989
+ return Array.isArray(words) ? words.join(" ") : String(words);
3990
+ }
3991
+ function registerRegisterCommand(program2) {
3992
+ 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("-c, --verification-code <code>", "Verification code (skip prompt)").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").action(
3993
+ (opts) => runAction(async () => {
3994
+ const config = requireConfig();
3995
+ if (opts.nonInteractive) {
3996
+ await nonInteractiveRegister(config, opts);
3997
+ } else {
3998
+ await smartRegister(config, opts);
3999
+ }
4000
+ })()
4001
+ );
4002
+ }
4003
+ async function smartRegister(config, opts) {
4004
+ let email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
4005
+ if ((opts.email || process.env.ARBI_EMAIL) && !email.includes("@")) {
4006
+ email = `${email}@${config.deploymentDomain}`;
4007
+ console.log(`Using email: ${email}`);
4008
+ }
4009
+ const arbi = client.createArbiClient({
4010
+ baseUrl: config.baseUrl,
4011
+ deploymentDomain: config.deploymentDomain,
4012
+ credentials: "omit"
4013
+ });
4014
+ await arbi.crypto.initSodium();
4015
+ let verificationCode;
4016
+ if (opts.verificationCode) {
4017
+ verificationCode = opts.verificationCode;
4018
+ } else {
4019
+ const codeMethod = await promptSelect("Verification method", [
4020
+ { name: "I have an invitation code", value: "code" },
4021
+ { name: "Send me a verification email", value: "email" }
4022
+ ]);
4023
+ if (codeMethod === "code") {
4024
+ verificationCode = await promptInput("Invitation code");
4025
+ } else {
4026
+ console.log("Sending verification email...");
4027
+ const verifyResponse = await arbi.fetch.POST("/v1/user/verify-email", {
4028
+ body: { email }
4029
+ });
4030
+ if (verifyResponse.error) {
4031
+ error(`Failed to send verification email: ${JSON.stringify(verifyResponse.error)}`);
4032
+ process.exit(1);
4033
+ }
4034
+ success("Verification email sent. Check your inbox.");
4035
+ verificationCode = await promptInput("Verification code");
4036
+ }
4037
+ }
4038
+ let pw;
4039
+ const flagOrEnvPassword = opts.password || process.env.ARBI_PASSWORD;
4040
+ if (flagOrEnvPassword) {
4041
+ pw = flagOrEnvPassword;
4042
+ } else {
4043
+ pw = await promptPassword("Password");
4044
+ const confirmPw = await promptPassword("Confirm password");
4045
+ if (pw !== confirmPw) {
4046
+ error("Passwords do not match.");
4047
+ process.exit(1);
4048
+ }
4049
+ }
4050
+ const hasAllCoreFlags = !!(opts.email || process.env.ARBI_EMAIL) && !!(opts.password || process.env.ARBI_PASSWORD) && !!opts.verificationCode;
4051
+ const firstName = opts.firstName || (hasAllCoreFlags ? "User" : await promptInput("First name", false) || "User");
4052
+ const lastName = opts.lastName || (hasAllCoreFlags ? "" : await promptInput("Last name", false) || "");
4053
+ try {
4054
+ await arbi.auth.register({
4055
+ email,
4056
+ password: pw,
4057
+ verificationCode,
4058
+ firstName,
3751
4059
  lastName
3752
4060
  });
3753
4061
  success(`
3754
4062
  Registered successfully as ${email}`);
3755
4063
  } catch (err) {
3756
- error(`Registration failed: ${sdk.getErrorMessage(err)}`);
4064
+ error(`Registration failed: ${formatCliError(err)}`);
3757
4065
  process.exit(1);
3758
4066
  }
3759
4067
  const allFlagsProvided = !!(opts.email || process.env.ARBI_EMAIL) && !!(opts.password || process.env.ARBI_PASSWORD) && !!opts.verificationCode;
@@ -3813,7 +4121,7 @@ async function nonInteractiveRegister(config, opts) {
3813
4121
  });
3814
4122
  success(`Registered: ${email}`);
3815
4123
  } catch (err) {
3816
- error(`Registration failed: ${sdk.getErrorMessage(err)}`);
4124
+ error(`Registration failed: ${formatCliError(err)}`);
3817
4125
  process.exit(1);
3818
4126
  }
3819
4127
  await loginAfterRegister(config, email, password2);
@@ -3822,8 +4130,9 @@ async function loginAfterRegister(config, email, password2) {
3822
4130
  try {
3823
4131
  const { arbi } = await sdk.performPasswordLogin(config, email, password2, store);
3824
4132
  success(`Logged in as ${email}`);
3825
- const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
3826
- const wsList = workspaces2 || [];
4133
+ const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
4134
+ const wsList = workspaces3 || [];
4135
+ updateCompletionCache(wsList);
3827
4136
  const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
3828
4137
  if (memberWorkspaces.length === 0) {
3829
4138
  console.log("Creating your first workspace...");
@@ -3843,7 +4152,7 @@ async function loginAfterRegister(config, email, password2) {
3843
4152
  const ws = memberWorkspaces.find((w) => w.external_id === selected);
3844
4153
  success(`Workspace: ${ws.name} (${ref(selected)})`);
3845
4154
  } catch (err) {
3846
- error(`Login failed: ${sdk.getErrorMessage(err)}`);
4155
+ error(`Login failed: ${formatCliError(err)}`);
3847
4156
  error("You can log in later with: arbi login");
3848
4157
  }
3849
4158
  }
@@ -3880,217 +4189,12 @@ function registerStatusCommand(program2) {
3880
4189
  }
3881
4190
  });
3882
4191
  }
3883
- var MAX_TASKS = 50;
3884
- var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3885
- function getTasksFile() {
3886
- const configDir = process.env.ARBI_CONFIG_DIR ?? path__default.default.join(os__default.default.homedir(), ".arbi");
3887
- return path__default.default.join(configDir, "tasks.json");
3888
- }
3889
- function ensureDir(filePath) {
3890
- const dir = path__default.default.dirname(filePath);
3891
- if (!fs__default.default.existsSync(dir)) {
3892
- fs__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
3893
- }
3894
- }
3895
- function readTasks() {
3896
- try {
3897
- const content = fs__default.default.readFileSync(getTasksFile(), "utf-8");
3898
- return JSON.parse(content);
3899
- } catch {
3900
- return [];
3901
- }
3902
- }
3903
- function writeTasks(tasks) {
3904
- const filePath = getTasksFile();
3905
- ensureDir(filePath);
3906
- fs__default.default.writeFileSync(filePath, JSON.stringify(tasks, null, 2) + "\n", { mode: 384 });
3907
- }
3908
- function getTasks() {
3909
- const now = Date.now();
3910
- const tasks = readTasks().filter((t) => now - new Date(t.submittedAt).getTime() < MAX_AGE_MS);
3911
- return tasks;
3912
- }
3913
- function addTask(task) {
3914
- const tasks = [task, ...getTasks().filter((t) => t.id !== task.id)].slice(0, MAX_TASKS);
3915
- writeTasks(tasks);
3916
- }
3917
- function updateTaskStatus(id, status2) {
3918
- const tasks = readTasks();
3919
- const task = tasks.find((t) => t.id === id);
3920
- if (task) {
3921
- task.status = status2;
3922
- writeTasks(tasks);
3923
- }
3924
- }
3925
- function getLatestTask() {
3926
- const tasks = getTasks();
3927
- return tasks[0] ?? null;
3928
- }
3929
-
3930
- // src/notifications.ts
3931
- var activeConnection = null;
3932
- function timestamp() {
3933
- return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
3934
- }
3935
- function colorize(level, text) {
3936
- if (level === "success") return chalk2__default.default.green(text);
3937
- if (level === "error") return chalk2__default.default.red(text);
3938
- if (level === "warning") return chalk2__default.default.yellow(text);
3939
- return text;
3940
- }
3941
- async function startBackgroundNotifications(baseUrl, accessToken) {
3942
- if (activeConnection) return;
3943
- try {
3944
- activeConnection = await sdk.connectWithReconnect({
3945
- baseUrl,
3946
- accessToken,
3947
- onMessage: (msg) => {
3948
- if (client.isMessageType(msg, "response_complete")) {
3949
- updateTaskStatus(msg.response_id, msg.status);
3950
- }
3951
- const { text, level } = sdk.formatWsMessage(msg);
3952
- process.stderr.write(`
3953
- ${colorize(level, `[${timestamp()}] ${text}`)}
3954
- `);
3955
- },
3956
- onClose: () => {
3957
- activeConnection = null;
3958
- },
3959
- onReconnecting: (attempt, maxRetries) => {
3960
- process.stderr.write(
3961
- `
3962
- ${chalk2__default.default.yellow(`[${timestamp()}] Reconnecting... (${attempt}/${maxRetries})`)}
3963
- `
3964
- );
3965
- },
3966
- onReconnected: () => {
3967
- process.stderr.write(`
3968
- ${chalk2__default.default.green(`[${timestamp()}] Reconnected`)}
3969
- `);
3970
- },
3971
- onReconnectFailed: () => {
3972
- process.stderr.write(`
3973
- ${chalk2__default.default.red(`[${timestamp()}] Reconnection failed`)}
3974
- `);
3975
- }
3976
- });
3977
- } catch {
3978
- }
3979
- }
3980
- function stopBackgroundNotifications() {
3981
- activeConnection?.close();
3982
- activeConnection = null;
3983
- }
3984
- process.on("exit", stopBackgroundNotifications);
3985
- process.on("SIGINT", () => {
3986
- stopBackgroundNotifications();
3987
- process.exit(0);
3988
- });
3989
-
3990
- // src/helpers.ts
3991
- var CONNECTION_ERROR_HINTS = {
3992
- ECONNREFUSED: "Connection refused. Is the backend running?",
3993
- ECONNRESET: "Connection reset by server. The backend may have restarted.",
3994
- ENOTFOUND: "DNS resolution failed. Check the server URL.",
3995
- ETIMEDOUT: "Connection timed out. Check network connectivity.",
3996
- UNABLE_TO_VERIFY_LEAF_SIGNATURE: "TLS certificate cannot be verified. The cert may be expired or self-signed.",
3997
- CERT_HAS_EXPIRED: "TLS certificate has expired. Renew with manage-deployment.",
3998
- ERR_TLS_CERT_ALTNAME_INVALID: "TLS certificate hostname mismatch. Check the server URL.",
3999
- DEPTH_ZERO_SELF_SIGNED_CERT: "Self-signed TLS certificate. The cert may need to be renewed.",
4000
- SELF_SIGNED_CERT_IN_CHAIN: "Self-signed certificate in chain. The cert may need to be renewed."
4001
- };
4002
- function diagnoseConnectionError(err) {
4003
- const code = sdk.getErrorCode(err);
4004
- if (code && code in CONNECTION_ERROR_HINTS) {
4005
- return CONNECTION_ERROR_HINTS[code];
4006
- }
4007
- const msg = err instanceof Error ? err.message : "";
4008
- if (msg === "fetch failed" || msg.includes("fetch failed")) {
4009
- return "Network error connecting to the server. Run `arbi health` to diagnose.";
4010
- }
4011
- return void 0;
4012
- }
4013
- function formatCliError(err) {
4014
- const connectionHint = diagnoseConnectionError(err);
4015
- if (connectionHint) return connectionHint;
4016
- if (err instanceof sdk.ArbiApiError && err.apiError && typeof err.apiError === "object") {
4017
- const base = err.message;
4018
- const apiErr = err.apiError;
4019
- const detail = apiErr.detail ?? apiErr.message ?? apiErr.error;
4020
- if (typeof detail === "string" && detail && !base.includes(detail)) {
4021
- return `${base} \u2014 ${detail}`;
4022
- }
4023
- return base;
4024
- }
4025
- return sdk.getErrorMessage(err);
4026
- }
4027
- function runAction(fn) {
4028
- return async () => {
4029
- try {
4030
- await fn();
4031
- process.exit(0);
4032
- } catch (err) {
4033
- error(`Error: ${formatCliError(err)}`);
4034
- hintUpdateOnError();
4035
- process.exit(1);
4036
- }
4037
- };
4038
- }
4039
- async function resolveAuth() {
4040
- try {
4041
- resolveConfig();
4042
- return await sdk.resolveAuth(store);
4043
- } catch (err) {
4044
- if (err instanceof sdk.ArbiError) {
4045
- error(err.message);
4046
- process.exit(1);
4047
- }
4048
- throw err;
4049
- }
4050
- }
4051
- async function resolveWorkspace(workspaceOpt) {
4052
- try {
4053
- resolveConfig();
4054
- const ctx = await sdk.resolveWorkspace(store, workspaceOpt);
4055
- if (getConfig()?.notifications !== false) {
4056
- startBackgroundNotifications(ctx.config.baseUrl, ctx.accessToken).catch(() => {
4057
- });
4058
- }
4059
- return ctx;
4060
- } catch (err) {
4061
- if (err instanceof sdk.ArbiError) {
4062
- error(err.message);
4063
- process.exit(1);
4064
- }
4065
- throw err;
4066
- }
4067
- }
4068
- function printTable(columns, rows) {
4069
- console.log(chalk2__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
4070
- for (const row of rows) {
4071
- console.log(
4072
- columns.map((c) => {
4073
- const val = c.value(row);
4074
- return val.slice(0, c.width - 2).padEnd(c.width);
4075
- }).join("")
4076
- );
4077
- }
4078
- }
4079
- function parseJsonArg(input2, example) {
4080
- try {
4081
- return JSON.parse(input2);
4082
- } catch {
4083
- error(`Invalid JSON. Example: ${example}`);
4084
- process.exit(1);
4085
- }
4086
- }
4087
-
4088
- // src/commands/workspaces.ts
4089
4192
  function registerWorkspacesCommand(program2) {
4090
4193
  program2.command("workspaces").description("List workspaces").action(
4091
4194
  runAction(async () => {
4092
4195
  const { arbi } = await resolveAuth();
4093
4196
  const data = await sdk.workspaces.listWorkspaces(arbi);
4197
+ updateCompletionCache(data);
4094
4198
  if (data.length === 0) {
4095
4199
  console.log("No workspaces found.");
4096
4200
  return;
@@ -4119,6 +4223,7 @@ function registerWorkspacesCommand(program2) {
4119
4223
  (id) => runAction(async () => {
4120
4224
  const { arbi } = await resolveAuth();
4121
4225
  const data = await sdk.workspaces.listWorkspaces(arbi);
4226
+ updateCompletionCache(data);
4122
4227
  if (data.length === 0) {
4123
4228
  console.log("No workspaces found.");
4124
4229
  return;
@@ -4171,17 +4276,17 @@ function registerWorkspacesCommand(program2) {
4171
4276
  success(`Deleted workspace ${targetId}`);
4172
4277
  })()
4173
4278
  );
4174
- workspace.command("update <id> <json>").description("Update workspace properties (pass JSON)").action(
4175
- (id, json) => runAction(async () => {
4176
- const body = parseJsonArg(json, `arbi workspace update wrk-123 '{"name": "New Name"}'`);
4177
- const { arbi } = await resolveWorkspace(id);
4279
+ workspace.command("update <json>").description("Update workspace properties (pass JSON)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4280
+ (json, opts) => runAction(async () => {
4281
+ const body = parseJsonArg(json, `arbi workspace update '{"name": "New Name"}'`);
4282
+ const { arbi } = await resolveWorkspace(opts.workspace);
4178
4283
  const data = await sdk.workspaces.updateWorkspace(arbi, body);
4179
4284
  success(`Updated: ${data.name} (${ref(data.external_id)})`);
4180
4285
  })()
4181
4286
  );
4182
4287
  workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4183
4288
  (opts) => runAction(async () => {
4184
- const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4289
+ const { arbi } = await resolveWorkspace(opts.workspace);
4185
4290
  const data = await sdk.workspaces.listWorkspaceUsers(arbi);
4186
4291
  if (data.length === 0) {
4187
4292
  console.log("No users found.");
@@ -4217,7 +4322,7 @@ function registerWorkspacesCommand(program2) {
4217
4322
  );
4218
4323
  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(
4219
4324
  (emails, opts) => runAction(async () => {
4220
- const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4325
+ const { arbi } = await resolveWorkspace(opts.workspace);
4221
4326
  const role = opts.role ?? "collaborator";
4222
4327
  const data = await sdk.workspaces.addWorkspaceUsers(arbi, emails, role);
4223
4328
  for (const u of data) success(`Added: ${u.user.email} as ${u.role}`);
@@ -4225,14 +4330,14 @@ function registerWorkspacesCommand(program2) {
4225
4330
  );
4226
4331
  workspace.command("remove-user <user-ids...>").description("Remove users from the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4227
4332
  (userIds, opts) => runAction(async () => {
4228
- const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4333
+ const { arbi } = await resolveWorkspace(opts.workspace);
4229
4334
  await sdk.workspaces.removeWorkspaceUsers(arbi, userIds);
4230
4335
  success(`Removed ${userIds.length} user(s).`);
4231
4336
  })()
4232
4337
  );
4233
4338
  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(
4234
4339
  (role, userIds, opts) => runAction(async () => {
4235
- const { arbi, workspaceId } = await resolveWorkspace(opts.workspace);
4340
+ const { arbi } = await resolveWorkspace(opts.workspace);
4236
4341
  const data = await sdk.workspaces.setUserRole(
4237
4342
  arbi,
4238
4343
  userIds,
@@ -4243,14 +4348,15 @@ function registerWorkspacesCommand(program2) {
4243
4348
  );
4244
4349
  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(
4245
4350
  (targetId, docIds, opts) => runAction(async () => {
4246
- const { arbi, loginResult } = await resolveWorkspace(opts.workspace);
4247
- const signingPrivateKeyBase64 = arbi.crypto.bytesToBase64(loginResult.signingPrivateKey);
4248
- const wsList = await sdk.workspaces.listWorkspaces(arbi);
4351
+ const { arbi: userArbi } = await resolveAuth();
4352
+ const wsList = await sdk.workspaces.listWorkspaces(userArbi);
4249
4353
  const targetWs = wsList.find((w) => w.external_id === targetId);
4250
4354
  if (!targetWs || !targetWs.wrapped_key) {
4251
4355
  error(`Target workspace ${targetId} not found or has no encryption key`);
4252
4356
  process.exit(1);
4253
4357
  }
4358
+ const { arbi, loginResult } = await resolveWorkspace(opts.workspace);
4359
+ const signingPrivateKeyBase64 = arbi.crypto.bytesToBase64(loginResult.signingPrivateKey);
4254
4360
  const targetKey = await sdk.generateEncryptedWorkspaceKey(
4255
4361
  arbi,
4256
4362
  targetWs.wrapped_key,
@@ -4449,7 +4555,7 @@ function registerUploadCommand(program2) {
4449
4555
  program2.command("add <paths...>").alias("upload").description("Add 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").option("--no-watch", "Skip watching document processing").action(
4450
4556
  (paths, opts) => runAction(async () => {
4451
4557
  for (const p of paths) {
4452
- if (!fs__default.default.existsSync(p)) {
4558
+ if (!fs2__default.default.existsSync(p)) {
4453
4559
  error(`Path not found: ${p}`);
4454
4560
  process.exit(1);
4455
4561
  }
@@ -4460,7 +4566,7 @@ function registerUploadCommand(program2) {
4460
4566
  const uploadedDocs = /* @__PURE__ */ new Map();
4461
4567
  const auth = { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader };
4462
4568
  for (const filePath of paths) {
4463
- const stat = fs__default.default.statSync(filePath);
4569
+ const stat = fs2__default.default.statSync(filePath);
4464
4570
  if (stat.isDirectory()) {
4465
4571
  const result = await sdk.documentsNode.uploadDirectory(auth, workspaceId, filePath);
4466
4572
  if (result.doc_ext_ids.length === 0) {
@@ -4596,7 +4702,7 @@ function registerDownloadCommand(program2) {
4596
4702
  }
4597
4703
  const outputPath = opts.output || path__default.default.join(process.cwd(), filename);
4598
4704
  const buffer = Buffer.from(await res.arrayBuffer());
4599
- fs__default.default.writeFileSync(outputPath, buffer);
4705
+ fs2__default.default.writeFileSync(outputPath, buffer);
4600
4706
  success(
4601
4707
  `Downloaded: ${path__default.default.basename(outputPath)} (${(buffer.length / (1024 * 1024)).toFixed(1)} MB)`
4602
4708
  );
@@ -4682,13 +4788,22 @@ function registerAskCommand(program2) {
4682
4788
  }
4683
4789
  const verbose = opts.quiet === true ? false : getConfig()?.verbose !== false;
4684
4790
  let elapsedTime = null;
4791
+ let firstToken = true;
4685
4792
  const result = await sdk.streamSSE(res, {
4686
- onToken: (content) => process.stdout.write(content),
4793
+ onToken: (content) => {
4794
+ if (firstToken) {
4795
+ process.stderr.write(chalk2__default.default.dim("[ARBI] "));
4796
+ firstToken = false;
4797
+ }
4798
+ process.stdout.write(content);
4799
+ },
4687
4800
  onAgentStep: (data) => {
4688
4801
  if (verbose) {
4689
4802
  const label2 = sdk.formatAgentStepLabel(data);
4690
- if (label2) console.error(chalk2__default.default.dim(`
4691
- [agent] ${label2}`));
4803
+ if (label2 && data.step !== "answering") {
4804
+ console.error(chalk2__default.default.dim(`
4805
+ [ARBI] ${label2}`));
4806
+ }
4692
4807
  }
4693
4808
  },
4694
4809
  onElapsedTime: (t) => {
@@ -4698,29 +4813,23 @@ function registerAskCommand(program2) {
4698
4813
  Error: ${message}`))
4699
4814
  });
4700
4815
  process.stdout.write("\n");
4701
- const parts = [];
4702
- if (result.agentSteps.length > 0) {
4703
- let stepLabel = `${result.agentSteps.length} step${result.agentSteps.length === 1 ? "" : "s"}`;
4704
- if (result.toolCallCount > 0) {
4705
- stepLabel += ` (${result.toolCallCount} tool call${result.toolCallCount === 1 ? "" : "s"})`;
4706
- }
4707
- parts.push(stepLabel);
4708
- }
4709
- if (result.usage) {
4710
- parts.push(`${result.usage.total_tokens.toLocaleString()} tokens`);
4711
- }
4712
- if (result.context && result.context.context_window > 0) {
4713
- const used = result.context.all_llm_calls?.last_input_tokens ?? result.context.total_input;
4714
- parts.push(
4715
- `${used.toLocaleString()}/${result.context.context_window.toLocaleString()} context`
4816
+ if (result.metadata) {
4817
+ saveLastMetadata(result.metadata);
4818
+ }
4819
+ const refs = sdk.countCitations(result.metadata);
4820
+ const summary = opts.quiet !== true ? sdk.formatStreamSummary(result, elapsedTime) : "";
4821
+ if (summary) {
4822
+ const refSuffix = refs > 0 ? ` \xB7 ${refs} ref${refs === 1 ? "" : "s"}` : "";
4823
+ const text = `[${summary}${refSuffix}]`;
4824
+ const cols = process.stderr.columns || 80;
4825
+ const pad = Math.max(0, cols - text.length);
4826
+ console.error(chalk2__default.default.dim(" ".repeat(pad) + text));
4827
+ }
4828
+ if (refs > 0 && opts.quiet !== true) {
4829
+ console.error(
4830
+ chalk2__default.default.dim(`[${refs} citation${refs === 1 ? "" : "s"} \u2014 arbi cite to browse]`)
4716
4831
  );
4717
4832
  }
4718
- if (elapsedTime != null) {
4719
- parts.push(`${elapsedTime.toFixed(1)}s`);
4720
- }
4721
- if (parts.length > 0) {
4722
- console.error(chalk2__default.default.dim(`[${parts.join(" \xB7 ")}]`));
4723
- }
4724
4833
  if (result.assistantMessageExtId) {
4725
4834
  const updates = {
4726
4835
  lastMessageExtId: result.assistantMessageExtId,
@@ -4735,6 +4844,87 @@ Error: ${message}`))
4735
4844
  })()
4736
4845
  );
4737
4846
  }
4847
+ var MAX_PASSAGE_LINES = 40;
4848
+ function registerCiteCommand(program2) {
4849
+ program2.command("cite [number]").description("Browse citations from the last response").option("-a, --all", "Show all citations with full passages").action(
4850
+ (number, opts) => runAction(async () => {
4851
+ const raw = loadLastMetadata();
4852
+ if (!raw) {
4853
+ console.error(chalk2__default.default.dim("No citation data available. Run `arbi ask` first."));
4854
+ return;
4855
+ }
4856
+ const metadata = raw;
4857
+ const count = sdk.countCitations(metadata);
4858
+ if (count === 0) {
4859
+ console.log(chalk2__default.default.dim("The last response contained no citations."));
4860
+ return;
4861
+ }
4862
+ const resolved = sdk.resolveCitations(metadata);
4863
+ if (opts.all) {
4864
+ for (const r of resolved) {
4865
+ printCitationDetail(r.citationNum, resolved);
4866
+ console.log();
4867
+ }
4868
+ return;
4869
+ }
4870
+ if (number) {
4871
+ printCitationDetail(number, resolved);
4872
+ return;
4873
+ }
4874
+ const summaries = sdk.summarizeCitations(resolved);
4875
+ console.log(chalk2__default.default.bold(`Citations (${summaries.length}):
4876
+ `));
4877
+ for (const s of summaries) {
4878
+ const page = s.pageNumber != null ? `, p${s.pageNumber}` : "";
4879
+ const chunks = s.chunkCount > 1 ? ` (${s.chunkCount} passages)` : "";
4880
+ console.log(
4881
+ ` ${chalk2__default.default.cyan(`[${s.citationNum}]`)} ${chalk2__default.default.bold(s.docTitle)}${page}${chunks}`
4882
+ );
4883
+ const truncated = s.statement.length > 120 ? s.statement.slice(0, 120).replace(/\n/g, " ").trim() + "..." : s.statement.replace(/\n/g, " ").trim();
4884
+ console.log(` ${chalk2__default.default.dim(truncated)}`);
4885
+ }
4886
+ console.log(chalk2__default.default.dim(`
4887
+ Use \`arbi cite <N>\` to view full passage.`));
4888
+ })()
4889
+ );
4890
+ }
4891
+ function printCitationDetail(num, resolved) {
4892
+ const citation = resolved.find((r) => r.citationNum === num);
4893
+ if (!citation) {
4894
+ console.error(chalk2__default.default.red(`Citation [${num}] not found.`));
4895
+ return;
4896
+ }
4897
+ const firstChunk = citation.chunks[0];
4898
+ const docTitle = firstChunk?.metadata?.doc_title ?? "Unknown document";
4899
+ const page = firstChunk?.metadata?.page_number;
4900
+ console.log(chalk2__default.default.bold.cyan(`[Citation ${citation.citationNum}]`) + " " + chalk2__default.default.bold(docTitle));
4901
+ if (page != null) {
4902
+ console.log(chalk2__default.default.dim(`Page ${page}`));
4903
+ }
4904
+ console.log();
4905
+ console.log(chalk2__default.default.bold("Statement:"));
4906
+ console.log(` ${citation.citationData.statement}`);
4907
+ console.log();
4908
+ for (let i = 0; i < citation.chunks.length; i++) {
4909
+ const chunk = citation.chunks[i];
4910
+ if (citation.chunks.length > 1) {
4911
+ console.log(chalk2__default.default.bold(`Passage ${i + 1}/${citation.chunks.length}:`));
4912
+ } else {
4913
+ console.log(chalk2__default.default.bold("Passage:"));
4914
+ }
4915
+ const lines = chunk.content.split("\n");
4916
+ if (lines.length > MAX_PASSAGE_LINES) {
4917
+ console.log(lines.slice(0, MAX_PASSAGE_LINES).join("\n"));
4918
+ console.log(chalk2__default.default.dim(` ... (${lines.length - MAX_PASSAGE_LINES} more lines)`));
4919
+ } else {
4920
+ console.log(chunk.content);
4921
+ }
4922
+ if (i < citation.chunks.length - 1) console.log();
4923
+ }
4924
+ if (citation.chunks.length === 0) {
4925
+ console.log(chalk2__default.default.dim(" (no passage data available)"));
4926
+ }
4927
+ }
4738
4928
  function chunkScore(chunk) {
4739
4929
  return chunk.metadata.rerank_score ?? chunk.metadata.score ?? 0;
4740
4930
  }
@@ -5819,8 +6009,8 @@ function registerQuickstartCommand(program2) {
5819
6009
  try {
5820
6010
  const { arbi } = await sdk.performPasswordLogin(config, email, password2, store);
5821
6011
  success(`Logged in as ${email}`);
5822
- const { data: workspaces2 } = await arbi.fetch.GET("/v1/user/workspaces");
5823
- const wsList = workspaces2 || [];
6012
+ const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
6013
+ const wsList = workspaces3 || [];
5824
6014
  const memberWorkspaces = wsList.filter((ws) => ws.users?.some((u) => u.email === email));
5825
6015
  let workspaceId;
5826
6016
  let workspaceName;
@@ -6052,6 +6242,85 @@ function registerTaskCommand(program2) {
6052
6242
  })()
6053
6243
  );
6054
6244
  }
6245
+ var MARKER_START = "# arbi-cli completion start";
6246
+ var MARKER_END = "# arbi-cli completion end";
6247
+ var BASH_COMPLETION = `
6248
+ ${MARKER_START}
6249
+ _arbi_completions() {
6250
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
6251
+ local candidates
6252
+ candidates=$(arbi --get-completions "\${COMP_LINE}" 2>/dev/null)
6253
+ COMPREPLY=($(compgen -W "$candidates" -- "$cur"))
6254
+ }
6255
+ complete -o default -F _arbi_completions arbi
6256
+ ${MARKER_END}`;
6257
+ var ZSH_COMPLETION = `
6258
+ ${MARKER_START}
6259
+ _arbi_completions() {
6260
+ local candidates
6261
+ candidates=(\${(f)"$(arbi --get-completions "\${words[*]}" 2>/dev/null)"})
6262
+ compadd -a candidates
6263
+ }
6264
+ compdef _arbi_completions arbi
6265
+ ${MARKER_END}`;
6266
+ function getShellRcPath2() {
6267
+ const shell = process.env.SHELL || "";
6268
+ if (shell.includes("zsh")) return path.join(os.homedir(), ".zshrc");
6269
+ return path.join(os.homedir(), ".bashrc");
6270
+ }
6271
+ function isZsh() {
6272
+ return (process.env.SHELL || "").includes("zsh");
6273
+ }
6274
+ function isCompletionInstalled(rcPath) {
6275
+ if (!fs2.existsSync(rcPath)) return false;
6276
+ const content = fs2.readFileSync(rcPath, "utf-8");
6277
+ return content.includes(MARKER_START);
6278
+ }
6279
+ function removeCompletionBlock(content) {
6280
+ const startIdx = content.indexOf(MARKER_START);
6281
+ const endIdx = content.indexOf(MARKER_END);
6282
+ if (startIdx === -1 || endIdx === -1) return content;
6283
+ const before = content.substring(0, startIdx).replace(/\n+$/, "");
6284
+ const after = content.substring(endIdx + MARKER_END.length);
6285
+ return before + after;
6286
+ }
6287
+ function registerCompletionCommand(program2) {
6288
+ const completion = program2.command("completion").description("Manage shell tab completion");
6289
+ completion.command("install").description("Install shell tab completion (bash/zsh)").action(() => {
6290
+ const rcPath = getShellRcPath2();
6291
+ if (isCompletionInstalled(rcPath)) {
6292
+ success(`Completion already installed in ${rcPath}`);
6293
+ dim("If it's not working, run: source " + rcPath);
6294
+ return;
6295
+ }
6296
+ const snippet = isZsh() ? ZSH_COMPLETION : BASH_COMPLETION;
6297
+ fs2.appendFileSync(rcPath, snippet + "\n");
6298
+ success(`Installed tab completion in ${rcPath}`);
6299
+ dim("");
6300
+ dim(`Run: source ${rcPath}`);
6301
+ dim("Then try: arbi <TAB>");
6302
+ });
6303
+ completion.command("uninstall").description("Remove shell tab completion").action(() => {
6304
+ const rcPath = getShellRcPath2();
6305
+ if (!isCompletionInstalled(rcPath)) {
6306
+ dim("Completion is not installed.");
6307
+ return;
6308
+ }
6309
+ const content = fs2.readFileSync(rcPath, "utf-8");
6310
+ const cleaned = removeCompletionBlock(content);
6311
+ fs2.writeFileSync(rcPath, cleaned);
6312
+ success(`Removed tab completion from ${rcPath}`);
6313
+ dim(`Run: source ${rcPath}`);
6314
+ });
6315
+ completion.command("refresh").description("Refresh cached workspace IDs for tab completion").action(
6316
+ runAction(async () => {
6317
+ const { arbi } = await resolveAuth();
6318
+ const data = await sdk.workspaces.listWorkspaces(arbi);
6319
+ updateCompletionCache(data);
6320
+ success(`Cached ${data.length} workspace(s) for tab completion.`);
6321
+ })
6322
+ );
6323
+ }
6055
6324
 
6056
6325
  // src/index.ts
6057
6326
  console.debug = () => {
@@ -6062,7 +6331,7 @@ console.info = (...args) => {
6062
6331
  _origInfo(...args);
6063
6332
  };
6064
6333
  var program = new commander.Command();
6065
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.15");
6334
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.17");
6066
6335
  registerConfigCommand(program);
6067
6336
  registerLoginCommand(program);
6068
6337
  registerRegisterCommand(program);
@@ -6073,6 +6342,7 @@ registerDocsCommand(program);
6073
6342
  registerUploadCommand(program);
6074
6343
  registerDownloadCommand(program);
6075
6344
  registerAskCommand(program);
6345
+ registerCiteCommand(program);
6076
6346
  registerFindCommand(program);
6077
6347
  registerWatchCommand(program);
6078
6348
  registerContactsCommand(program);
@@ -6088,6 +6358,14 @@ registerUpdateCommand(program);
6088
6358
  registerQuickstartCommand(program);
6089
6359
  registerAgentCreateCommand(program);
6090
6360
  registerTaskCommand(program);
6361
+ registerCompletionCommand(program);
6362
+ var completionIdx = process.argv.indexOf("--get-completions");
6363
+ if (completionIdx !== -1) {
6364
+ const line = process.argv[completionIdx + 1] ?? "";
6365
+ const candidates = getCompletions(program, line);
6366
+ if (candidates.length > 0) process.stdout.write(candidates.join("\n") + "\n");
6367
+ process.exit(0);
6368
+ }
6091
6369
  program.parse();
6092
6370
  //# sourceMappingURL=index.js.map
6093
6371
  //# sourceMappingURL=index.js.map