@arbidocs/cli 0.3.19 → 0.3.20

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
@@ -10,6 +10,7 @@ var sdk = require('@arbidocs/sdk');
10
10
  var prompts = require('@inquirer/prompts');
11
11
  var child_process = require('child_process');
12
12
  var client = require('@arbidocs/client');
13
+ var url = require('url');
13
14
  var module$1 = require('module');
14
15
 
15
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -3250,7 +3251,7 @@ var waitForOthersClosedDelete = (databases, name, openDatabases, cb) => {
3250
3251
  };
3251
3252
  var deleteDatabase = (databases, connectionQueues, name, request, cb) => {
3252
3253
  const deleteDBTask = () => {
3253
- return new Promise((resolve) => {
3254
+ return new Promise((resolve3) => {
3254
3255
  const db = databases.get(name);
3255
3256
  const oldVersion = db !== void 0 ? db.version : 0;
3256
3257
  const onComplete = (err) => {
@@ -3261,7 +3262,7 @@ var deleteDatabase = (databases, connectionQueues, name, request, cb) => {
3261
3262
  cb(null, oldVersion);
3262
3263
  }
3263
3264
  } finally {
3264
- resolve();
3265
+ resolve3();
3265
3266
  }
3266
3267
  };
3267
3268
  try {
@@ -3409,7 +3410,7 @@ var runVersionchangeTransaction = (connection, version, request, cb) => {
3409
3410
  };
3410
3411
  var openDatabase = (databases, connectionQueues, name, version, request, cb) => {
3411
3412
  const openDBTask = () => {
3412
- return new Promise((resolve) => {
3413
+ return new Promise((resolve3) => {
3413
3414
  const onComplete = (err) => {
3414
3415
  try {
3415
3416
  if (err) {
@@ -3418,7 +3419,7 @@ var openDatabase = (databases, connectionQueues, name, version, request, cb) =>
3418
3419
  cb(null, connection);
3419
3420
  }
3420
3421
  } finally {
3421
- resolve();
3422
+ resolve3();
3422
3423
  }
3423
3424
  };
3424
3425
  let db = databases.get(name);
@@ -3636,7 +3637,7 @@ function getLatestVersion(skipCache = false) {
3636
3637
  }
3637
3638
  }
3638
3639
  function getCurrentVersion() {
3639
- return "0.3.19";
3640
+ return "0.3.20";
3640
3641
  }
3641
3642
  function readChangelog(fromVersion, toVersion) {
3642
3643
  try {
@@ -3689,17 +3690,17 @@ function showChangelog(fromVersion, toVersion) {
3689
3690
  async function checkForUpdates(autoUpdate) {
3690
3691
  try {
3691
3692
  const latest = getLatestVersion();
3692
- if (!latest || latest === "0.3.19") return;
3693
+ if (!latest || latest === "0.3.20") return;
3693
3694
  if (autoUpdate) {
3694
3695
  warn(`
3695
- Your arbi version is out of date (${"0.3.19"} \u2192 ${latest}). Updating...`);
3696
+ Your arbi version is out of date (${"0.3.20"} \u2192 ${latest}). Updating...`);
3696
3697
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3697
- showChangelog("0.3.19", latest);
3698
+ showChangelog("0.3.20", latest);
3698
3699
  console.log(`Updated to ${latest}.`);
3699
3700
  } else {
3700
3701
  warn(
3701
3702
  `
3702
- Your arbi version is out of date (${"0.3.19"} \u2192 ${latest}).
3703
+ Your arbi version is out of date (${"0.3.20"} \u2192 ${latest}).
3703
3704
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3704
3705
  );
3705
3706
  }
@@ -3709,9 +3710,9 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3709
3710
  function hintUpdateOnError() {
3710
3711
  try {
3711
3712
  const cached = readCache();
3712
- if (cached && cached.latest !== "0.3.19") {
3713
+ if (cached && cached.latest !== "0.3.20") {
3713
3714
  warn(
3714
- `Your arbi version is out of date (${"0.3.19"} \u2192 ${cached.latest}). Run "arbi update".`
3715
+ `Your arbi version is out of date (${"0.3.20"} \u2192 ${cached.latest}). Run "arbi update".`
3715
3716
  );
3716
3717
  }
3717
3718
  } catch {
@@ -3902,12 +3903,23 @@ async function resolveWorkspace(workspaceOpt) {
3902
3903
  throw err;
3903
3904
  }
3904
3905
  }
3906
+ async function resolveDmCrypto() {
3907
+ const authCtx = await resolveAuth();
3908
+ const { arbi, loginResult } = authCtx;
3909
+ const userExtId = arbi.session.getState().userExtId;
3910
+ if (!userExtId) {
3911
+ error("No user ID in session \u2014 cannot set up DM encryption.");
3912
+ process.exit(1);
3913
+ }
3914
+ const crypto = sdk.dm.createDmCryptoContext(arbi, loginResult.signingPrivateKey, userExtId);
3915
+ return { ...authCtx, crypto };
3916
+ }
3905
3917
  function printTable(columns, rows) {
3906
3918
  console.log(chalk2__default.default.bold(columns.map((c) => c.header.padEnd(c.width)).join("")));
3907
3919
  for (const row of rows) {
3908
3920
  console.log(
3909
3921
  columns.map((c) => {
3910
- const val = c.value(row);
3922
+ const val = c.value(row) ?? "";
3911
3923
  return val.slice(0, c.width - 2).padEnd(c.width);
3912
3924
  }).join("")
3913
3925
  );
@@ -3921,10 +3933,324 @@ function parseJsonArg(input2, example) {
3921
3933
  process.exit(1);
3922
3934
  }
3923
3935
  }
3936
+ var AGENT_BACKENDS = ["claude", "openclaw"];
3937
+ var AGENT_MIN_VERSIONS = {
3938
+ claude: {
3939
+ binary: "claude",
3940
+ minVersion: "2.0.0",
3941
+ installUrl: "https://docs.anthropic.com/en/docs/claude-code"
3942
+ },
3943
+ openclaw: {
3944
+ binary: "openclaw",
3945
+ minVersion: "2026.2.0",
3946
+ installUrl: "https://docs.openclaw.ai/install"
3947
+ }
3948
+ };
3949
+ function checkAgentDependency(backend) {
3950
+ const { binary, minVersion, installUrl } = AGENT_MIN_VERSIONS[backend];
3951
+ let version;
3952
+ try {
3953
+ version = child_process.execFileSync(binary, ["--version"], { encoding: "utf-8", timeout: 5e3 }).trim();
3954
+ } catch {
3955
+ error(
3956
+ `"${binary}" is not installed or not in PATH.
3957
+ The "${backend}" agent backend requires ${binary} >= ${minVersion}.
3958
+ Install: ${installUrl}`
3959
+ );
3960
+ process.exit(1);
3961
+ }
3962
+ const versionMatch = version.match(/(\d+\.\d+[\d.]*)/);
3963
+ if (!versionMatch) {
3964
+ dim(`Could not parse ${binary} version from: ${version}`);
3965
+ return;
3966
+ }
3967
+ const current = versionMatch[1];
3968
+ if (compareVersions(current, minVersion) < 0) {
3969
+ error(
3970
+ `"${binary}" version ${current} is too old (need >= ${minVersion}).
3971
+ Update: ${installUrl}`
3972
+ );
3973
+ process.exit(1);
3974
+ }
3975
+ dim(`${binary} ${current} \u2713`);
3976
+ }
3977
+ function compareVersions(a, b) {
3978
+ const pa = a.split(".").map(Number);
3979
+ const pb = b.split(".").map(Number);
3980
+ const len = Math.max(pa.length, pb.length);
3981
+ for (let i = 0; i < len; i++) {
3982
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
3983
+ if (diff !== 0) return diff;
3984
+ }
3985
+ return 0;
3986
+ }
3987
+ function loadSkillContent() {
3988
+ const cliRoot = path.resolve(path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)))), "..");
3989
+ try {
3990
+ return fs2.readFileSync(path.join(cliRoot, "SKILL.md"), "utf-8");
3991
+ } catch {
3992
+ return void 0;
3993
+ }
3994
+ }
3995
+ function createAgent(backend, sessionId) {
3996
+ switch (backend) {
3997
+ case "claude":
3998
+ return new sdk.ClaudeOrchestrator({ sessionId, skillPrompt: loadSkillContent() });
3999
+ case "openclaw":
4000
+ return new sdk.OpenClawOrchestrator({ sessionId });
4001
+ default:
4002
+ throw new Error(`Unknown agent backend: ${backend}`);
4003
+ }
4004
+ }
4005
+ function installSkill(backend) {
4006
+ const cliRoot = path.resolve(path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)))), "..");
4007
+ const skillPath = path.join(cliRoot, "SKILL.md");
4008
+ let skillContent;
4009
+ try {
4010
+ skillContent = fs2.readFileSync(skillPath, "utf-8");
4011
+ } catch {
4012
+ return;
4013
+ }
4014
+ switch (backend) {
4015
+ case "claude": {
4016
+ const commandsDir = path.join(os.homedir(), ".claude", "commands", "arbi");
4017
+ const targetPath = path.join(commandsDir, "SKILL.md");
4018
+ try {
4019
+ fs2.mkdirSync(commandsDir, { recursive: true });
4020
+ if (fs2.existsSync(targetPath)) {
4021
+ const existing = fs2.readFileSync(targetPath, "utf-8");
4022
+ if (existing === skillContent) return;
4023
+ }
4024
+ fs2.writeFileSync(targetPath, skillContent, "utf-8");
4025
+ dim(`Installed ARBI skill \u2192 ${targetPath}`);
4026
+ } catch {
4027
+ }
4028
+ break;
4029
+ }
4030
+ case "openclaw": {
4031
+ const ocAgentName = "arbi";
4032
+ const ocWorkspace = path.join(os.homedir(), ".arbi", "openclaw-workspace");
4033
+ const bootstrapPath = path.join(ocWorkspace, "BOOTSTRAP.md");
4034
+ try {
4035
+ const list = child_process.execFileSync("openclaw", ["agents", "list", "--json"], {
4036
+ encoding: "utf-8"
4037
+ });
4038
+ const agents = JSON.parse(list);
4039
+ const exists = Array.isArray(agents) ? agents.some((a) => a.id === ocAgentName) : false;
4040
+ if (!exists) {
4041
+ fs2.mkdirSync(ocWorkspace, { recursive: true });
4042
+ child_process.execFileSync(
4043
+ "openclaw",
4044
+ ["agents", "add", ocAgentName, "--workspace", ocWorkspace, "--non-interactive"],
4045
+ { encoding: "utf-8" }
4046
+ );
4047
+ dim(`Created OpenClaw agent "${ocAgentName}" \u2192 ${ocWorkspace}`);
4048
+ }
4049
+ } catch {
4050
+ fs2.mkdirSync(ocWorkspace, { recursive: true });
4051
+ try {
4052
+ child_process.execFileSync(
4053
+ "openclaw",
4054
+ ["agents", "add", ocAgentName, "--workspace", ocWorkspace, "--non-interactive"],
4055
+ { encoding: "utf-8" }
4056
+ );
4057
+ dim(`Created OpenClaw agent "${ocAgentName}" \u2192 ${ocWorkspace}`);
4058
+ } catch {
4059
+ }
4060
+ }
4061
+ try {
4062
+ fs2.mkdirSync(ocWorkspace, { recursive: true });
4063
+ if (fs2.existsSync(bootstrapPath)) {
4064
+ const existing = fs2.readFileSync(bootstrapPath, "utf-8");
4065
+ if (existing === skillContent) break;
4066
+ }
4067
+ fs2.writeFileSync(bootstrapPath, skillContent, "utf-8");
4068
+ dim(`Installed ARBI skill \u2192 ${bootstrapPath}`);
4069
+ } catch {
4070
+ }
4071
+ break;
4072
+ }
4073
+ }
4074
+ }
4075
+ async function setupAgent(agentName, config) {
4076
+ const backend = agentName ?? config.orchestrator;
4077
+ if (!backend) return void 0;
4078
+ if (!AGENT_BACKENDS.includes(backend)) {
4079
+ error(`Unknown agent backend: "${backend}". Choose from: ${AGENT_BACKENDS.join(", ")}`);
4080
+ process.exit(1);
4081
+ }
4082
+ checkAgentDependency(backend);
4083
+ installSkill(backend);
4084
+ if (agentName && agentName !== config.orchestrator) {
4085
+ updateConfig({ orchestrator: backend });
4086
+ dim(`Default agent backend set to "${backend}"`);
4087
+ }
4088
+ return backend;
4089
+ }
4090
+ async function startListening(agentName, config) {
4091
+ const backend = agentName ?? config.orchestrator;
4092
+ if (!backend) {
4093
+ error(
4094
+ `No agent backend configured. Use --agent to specify one:
4095
+ arbi connect --agent ${AGENT_BACKENDS[0]} --listen`
4096
+ );
4097
+ process.exit(1);
4098
+ }
4099
+ const creds = store.getCredentials();
4100
+ if (!creds?.parentExtId) {
4101
+ error(
4102
+ "DM listener requires a persistent agent identity (parentExtId).\n This session was not claimed as a delegated agent."
4103
+ );
4104
+ process.exit(1);
4105
+ }
4106
+ dim("Starting DM listener...");
4107
+ const { arbi, crypto } = await resolveDmCrypto();
4108
+ const sessionId = arbi.session.getState().userExtId ?? "default";
4109
+ const orchestrator = createAgent(backend, sessionId);
4110
+ const fullConfig = resolveConfig();
4111
+ const accessToken = arbi.session.getState().accessToken;
4112
+ if (!accessToken) {
4113
+ error("No access token \u2014 authentication may have failed.");
4114
+ process.exit(1);
4115
+ }
4116
+ const listener = await sdk.startDmListener({
4117
+ arbi,
4118
+ accessToken,
4119
+ baseUrl: fullConfig.baseUrl,
4120
+ crypto,
4121
+ orchestrator,
4122
+ parentExtId: creds.parentExtId,
4123
+ onLog: (msg) => dim(msg),
4124
+ onError: (msg, err) => error(`${msg}${err ? `: ${err}` : ""}`)
4125
+ });
4126
+ success(`Listening for DMs as ${chalk2__default.default.cyan(creds.email)} via ${chalk2__default.default.cyan(backend)}`);
4127
+ dim("Press Ctrl+C to stop.");
4128
+ const shutdown = () => {
4129
+ dim("\nShutting down listener...");
4130
+ listener.close();
4131
+ orchestrator.close?.();
4132
+ process.exit(0);
4133
+ };
4134
+ process.on("SIGINT", shutdown);
4135
+ process.on("SIGTERM", shutdown);
4136
+ await new Promise(() => {
4137
+ });
4138
+ }
4139
+ function registerConnectCommand(program2) {
4140
+ program2.command("connect").description("Claim a session and/or connect to an agent backend").option("--code <claim-code>", "Claim code from parent (required on first run)").option("--agent <name>", `Agent backend (${AGENT_BACKENDS.join(", ")})`).option("--listen", "Start DM listener (agent receives prompts via encrypted DMs)").action((opts) => {
4141
+ const run = async () => {
4142
+ const config = resolveConfig();
4143
+ const creds = store.getCredentials();
4144
+ const hasIdentity = creds?.signingPrivateKeyBase64;
4145
+ if (!hasIdentity) {
4146
+ const claimCode = opts.code?.trim() ?? "";
4147
+ if (!claimCode) {
4148
+ const authorizeUrl = `${config.baseUrl}#/action/authorize-agent`;
4149
+ error(
4150
+ `No saved identity. Provide a claim code:
4151
+ arbi connect --code purple-mountain-river
4152
+
4153
+ Get a claim code from the ARBI web UI:
4154
+ ${chalk2__default.default.underline(authorizeUrl)}`
4155
+ );
4156
+ process.exit(1);
4157
+ }
4158
+ dim("Claiming session...");
4159
+ const arbi = client.createArbiClient({
4160
+ baseUrl: config.baseUrl,
4161
+ deploymentDomain: config.deploymentDomain,
4162
+ credentials: "omit"
4163
+ });
4164
+ await arbi.crypto.initSodium();
4165
+ const keypair = arbi.crypto.generateRandomSigningKeypair();
4166
+ const signingPubKeyBase64 = client.bytesToBase64(keypair.publicKey);
4167
+ const signingPrivateKeyBase64 = client.bytesToBase64(keypair.secretKey);
4168
+ const claimResponse = await sdk.sessions.claimSession(arbi, claimCode, signingPubKeyBase64);
4169
+ const parentExtId = claimResponse.user.parent_ext_id ?? void 0;
4170
+ const agentEmail = claimResponse.user.email ?? claimResponse.user.external_id;
4171
+ store.saveCredentials({
4172
+ email: agentEmail,
4173
+ signingPrivateKeyBase64,
4174
+ serverSessionKeyBase64: claimResponse.session_key,
4175
+ accessToken: claimResponse.access_token,
4176
+ parentExtId
4177
+ });
4178
+ const workspaces3 = claimResponse.workspaces ?? [];
4179
+ if (workspaces3.length > 0) {
4180
+ const firstWs = workspaces3[0];
4181
+ const wsId = firstWs.external_id;
4182
+ if (wsId) {
4183
+ updateConfig({ selectedWorkspaceId: wsId });
4184
+ }
4185
+ }
4186
+ success("Session claimed!");
4187
+ console.log(` Identity: ${chalk2__default.default.cyan(agentEmail)}`);
4188
+ if (parentExtId) {
4189
+ console.log(` Parent: ${chalk2__default.default.cyan(parentExtId)}`);
4190
+ console.log(` Type: Persistent agent`);
4191
+ } else {
4192
+ console.log(` Type: Ephemeral session`);
4193
+ }
4194
+ console.log("");
4195
+ if (parentExtId) {
4196
+ try {
4197
+ arbi.session.setAccessToken(claimResponse.access_token);
4198
+ arbi.session.setUser(agentEmail, claimResponse.user.external_id);
4199
+ const contacts = await sdk.contacts.listContacts(arbi);
4200
+ const parentContact = contacts.find((c) => {
4201
+ const u = c.user;
4202
+ return u?.external_id === parentExtId;
4203
+ });
4204
+ const parentPubKey = parentContact?.user?.encryption_public_key;
4205
+ if (parentPubKey) {
4206
+ const userExtId = claimResponse.user.external_id ?? agentEmail;
4207
+ const crypto = sdk.dm.createDmCryptoContext(
4208
+ arbi,
4209
+ client.base64ToBytes(signingPrivateKeyBase64),
4210
+ userExtId
4211
+ );
4212
+ await sdk.dm.sendEncryptedDM(
4213
+ arbi,
4214
+ [
4215
+ {
4216
+ recipient_ext_id: parentExtId,
4217
+ content: `\u{1F511} Agent recovery key for ${agentEmail}
4218
+ signing_key: ${signingPrivateKeyBase64}
4219
+
4220
+ Save this key \u2014 it is the only way to recover this agent session.`,
4221
+ recipient_encryption_public_key: parentPubKey
4222
+ }
4223
+ ],
4224
+ crypto
4225
+ );
4226
+ dim("Recovery key sent to parent via encrypted DM.");
4227
+ } else {
4228
+ dim("Could not find parent encryption key \u2014 recovery key not sent.");
4229
+ dim(`Backup manually: ${signingPrivateKeyBase64}`);
4230
+ }
4231
+ } catch {
4232
+ dim("Failed to send recovery key DM \u2014 save it manually:");
4233
+ dim(` ${signingPrivateKeyBase64}`);
4234
+ }
4235
+ }
4236
+ }
4237
+ await setupAgent(opts.agent, config);
4238
+ if (opts.listen) {
4239
+ await startListening(opts.agent, config);
4240
+ } else {
4241
+ success("Connected. Use `arbi --help` to explore.");
4242
+ }
4243
+ };
4244
+ run().catch((err) => {
4245
+ error(`Error: ${err instanceof Error ? err.message : String(err)}`);
4246
+ process.exit(1);
4247
+ });
4248
+ });
4249
+ }
3924
4250
 
3925
4251
  // src/commands/login.ts
3926
4252
  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").option("--sso", "Log in with Auth0 SSO (device flow)").action(
4253
+ 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").option("--sso", "Log in with Auth0 SSO (device flow)").option("--agent <name>", "Agent backend to configure (claude, openclaw)").option("--listen", "Start DM listener after login").action(
3928
4254
  (opts) => runAction(async () => {
3929
4255
  const config = store.requireConfig();
3930
4256
  const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
@@ -3955,35 +4281,37 @@ Open this URL in your browser:
3955
4281
  const wsList = workspaces3 || [];
3956
4282
  updateCompletionCache(wsList);
3957
4283
  const memberWorkspaces = wsList.filter(
3958
- (ws2) => ws2.users?.some((u) => u.user.email === email)
4284
+ (ws) => ws.users?.some((u) => u.user.email === email)
3959
4285
  );
3960
4286
  if (memberWorkspaces.length === 0) {
3961
4287
  console.log("No workspaces found. Create one with: arbi workspace create <name>");
3962
- return;
3963
- }
3964
- if (opts.workspace) {
3965
- const ws2 = memberWorkspaces.find((w) => w.external_id === opts.workspace);
3966
- if (!ws2) {
4288
+ } else if (opts.workspace) {
4289
+ const ws = memberWorkspaces.find((w) => w.external_id === opts.workspace);
4290
+ if (!ws) {
3967
4291
  error(`Workspace ${opts.workspace} not found or you don't have access.`);
3968
4292
  process.exit(1);
3969
4293
  }
3970
- updateConfig({ selectedWorkspaceId: ws2.external_id });
3971
- success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
3972
- return;
3973
- }
3974
- if (memberWorkspaces.length === 1) {
4294
+ updateConfig({ selectedWorkspaceId: ws.external_id });
4295
+ success(`Workspace: ${ws.name} (${ref(ws.external_id)})`);
4296
+ } else if (memberWorkspaces.length === 1) {
3975
4297
  updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
3976
4298
  success(
3977
4299
  `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
3978
4300
  );
3979
- return;
4301
+ } else {
4302
+ const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
4303
+ const selected = await promptSelect("Select workspace", choices);
4304
+ updateConfig({ selectedWorkspaceId: selected });
4305
+ const ws = memberWorkspaces.find((w) => w.external_id === selected);
4306
+ success(`Workspace: ${ws.name} (${ref(selected)})`);
4307
+ dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
4308
+ }
4309
+ if (opts.agent || opts.listen) {
4310
+ await setupAgent(opts.agent, config);
4311
+ if (opts.listen) {
4312
+ await startListening(opts.agent, config);
4313
+ }
3980
4314
  }
3981
- const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
3982
- const selected = await promptSelect("Select workspace", choices);
3983
- updateConfig({ selectedWorkspaceId: selected });
3984
- const ws = memberWorkspaces.find((w) => w.external_id === selected);
3985
- success(`Workspace: ${ws.name} (${ref(selected)})`);
3986
- dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
3987
4315
  } catch (err) {
3988
4316
  error(`Login failed: ${formatCliError(err)}`);
3989
4317
  process.exit(1);
@@ -4149,15 +4477,16 @@ async function nonInteractiveRegister(config, opts) {
4149
4477
  }
4150
4478
  async function loginAfterRegister(config, email, password2) {
4151
4479
  try {
4152
- const { arbi } = await sdk.performPasswordLogin(config, email, password2, store);
4480
+ const { arbi, loginResult } = await sdk.performPasswordLogin(config, email, password2, store);
4153
4481
  success(`Logged in as ${email}`);
4154
4482
  const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
4155
4483
  const wsList = workspaces3 || [];
4156
4484
  updateCompletionCache(wsList);
4157
- const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.email === email));
4485
+ const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.user.email === email));
4158
4486
  if (memberWorkspaces.length === 0) {
4159
4487
  console.log("Creating your first workspace...");
4160
- const ws2 = await sdk.workspaces.createWorkspace(arbi, "My First Workspace");
4488
+ const encryptedKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
4489
+ const ws2 = await sdk.workspaces.createWorkspace(arbi, "My First Workspace", encryptedKey);
4161
4490
  updateConfig({ selectedWorkspaceId: ws2.external_id });
4162
4491
  success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
4163
4492
  return;
@@ -4271,10 +4600,12 @@ function registerWorkspacesCommand(program2) {
4271
4600
  );
4272
4601
  workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).action(
4273
4602
  (name, opts) => runAction(async () => {
4274
- const { arbi } = await resolveAuth();
4603
+ const { arbi, loginResult } = await resolveAuth();
4604
+ const encryptedKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
4275
4605
  const data = await sdk.workspaces.createWorkspace(
4276
4606
  arbi,
4277
4607
  name,
4608
+ encryptedKey,
4278
4609
  opts.description,
4279
4610
  opts.public ?? false
4280
4611
  );
@@ -4513,7 +4844,10 @@ function registerDocsCommand(program2) {
4513
4844
  const parsed = parseJsonArg(json, `arbi doc update '[{"external_id": "doc-123", "shared": true}]'`);
4514
4845
  const docs = Array.isArray(parsed) ? parsed : parsed.documents;
4515
4846
  const { arbi } = await resolveWorkspace();
4516
- const data = await sdk.documents.updateDocuments(arbi, docs);
4847
+ const data = await sdk.documents.updateDocuments(
4848
+ arbi,
4849
+ docs
4850
+ );
4517
4851
  success(`Updated ${data.length} document(s).`);
4518
4852
  } else {
4519
4853
  const { arbi, workspaceId } = await resolveWorkspace();
@@ -4556,7 +4890,7 @@ function registerDocsCommand(program2) {
4556
4890
  );
4557
4891
  doc.command("parsed <doc-id> [stage]").description("Get parsed document content (stage: marker, subchunk, final; default: final)").action(
4558
4892
  (docId, stage) => runAction(async () => {
4559
- const { accessToken, workspaceKeyHeader, config } = await resolveWorkspace();
4893
+ const { accessToken, config } = await resolveWorkspace();
4560
4894
  const validStages = ["marker", "subchunk", "final"];
4561
4895
  const selectedStage = stage ?? "final";
4562
4896
  if (!validStages.includes(selectedStage)) {
@@ -4564,7 +4898,7 @@ function registerDocsCommand(program2) {
4564
4898
  process.exit(1);
4565
4899
  }
4566
4900
  const data = await sdk.documents.getParsedContent(
4567
- { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader },
4901
+ { baseUrl: config.baseUrl, accessToken },
4568
4902
  docId,
4569
4903
  selectedStage
4570
4904
  );
@@ -4581,11 +4915,9 @@ function registerUploadCommand(program2) {
4581
4915
  process.exit(1);
4582
4916
  }
4583
4917
  }
4584
- const { config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace(
4585
- opts.workspace
4586
- );
4918
+ const { config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace);
4587
4919
  const uploadedDocs = /* @__PURE__ */ new Map();
4588
- const auth = { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader };
4920
+ const auth = { baseUrl: config.baseUrl, accessToken };
4589
4921
  for (const filePath of paths) {
4590
4922
  const stat = fs2__default.default.statSync(filePath);
4591
4923
  if (stat.isDirectory()) {
@@ -4697,7 +5029,7 @@ Connection closed. ${pending.size} document(s) still processing.`);
4697
5029
  function registerDownloadCommand(program2) {
4698
5030
  program2.command("download [doc-id]").description("Download a document (interactive picker if no ID given)").option("-o, --output <path>", "Output file path").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4699
5031
  (docId, opts) => runAction(async () => {
4700
- const { arbi, config, accessToken, workspaceKeyHeader, workspaceId } = await resolveWorkspace(opts.workspace);
5032
+ const { arbi, config, accessToken, workspaceId } = await resolveWorkspace(opts.workspace);
4701
5033
  if (!docId) {
4702
5034
  const data = await sdk.documents.listDocuments(arbi);
4703
5035
  if (data.length === 0) {
@@ -4712,7 +5044,7 @@ function registerDownloadCommand(program2) {
4712
5044
  docId = await promptSearch("Select document to download", choices);
4713
5045
  }
4714
5046
  const res = await sdk.documents.downloadDocument(
4715
- { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader },
5047
+ { baseUrl: config.baseUrl, accessToken },
4716
5048
  docId
4717
5049
  );
4718
5050
  let filename = `${docId}`;
@@ -4734,7 +5066,7 @@ function registerAskCommand(program2) {
4734
5066
  program2.command("ask <question...>").description("Ask the RAG assistant a question (no quotes needed)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("-c, --continue <msg-id>", "Continue from a specific message ID").option("--config <id>", "Config ext_id to use (e.g. cfg-xxx)").option("-n, --new", "Start a new conversation (ignore previous context)").option("-b, --background", "Submit as background task (fire and forget)").option("-q, --quiet", "Suppress agent steps and tool calls").option("--json", "Output in JSON format (background mode only)").action(
4735
5067
  (words, opts) => runAction(async () => {
4736
5068
  const question = words.join(" ");
4737
- const { arbi, accessToken, workspaceKeyHeader, workspaceId, config } = await resolveWorkspace(opts.workspace);
5069
+ const { arbi, accessToken, workspaceId, config } = await resolveWorkspace(opts.workspace);
4738
5070
  let previousResponseId = null;
4739
5071
  if (opts.continue) {
4740
5072
  previousResponseId = opts.continue;
@@ -4755,7 +5087,6 @@ function registerAskCommand(program2) {
4755
5087
  const result2 = await sdk.responses.submitBackgroundQuery({
4756
5088
  baseUrl: config.baseUrl,
4757
5089
  accessToken,
4758
- workspaceKeyHeader,
4759
5090
  workspaceId,
4760
5091
  question,
4761
5092
  docIds,
@@ -4785,7 +5116,6 @@ function registerAskCommand(program2) {
4785
5116
  res = await sdk.assistant.queryAssistant({
4786
5117
  baseUrl: config.baseUrl,
4787
5118
  accessToken,
4788
- workspaceKeyHeader,
4789
5119
  workspaceId,
4790
5120
  question,
4791
5121
  docIds,
@@ -4799,7 +5129,6 @@ function registerAskCommand(program2) {
4799
5129
  res = await sdk.assistant.queryAssistant({
4800
5130
  baseUrl: config.baseUrl,
4801
5131
  accessToken,
4802
- workspaceKeyHeader,
4803
5132
  workspaceId,
4804
5133
  question,
4805
5134
  docIds,
@@ -5190,11 +5519,11 @@ function registerContactsCommand(program2) {
5190
5519
  });
5191
5520
  }
5192
5521
  function registerDmCommand(program2) {
5193
- const dm = program2.command("dm").description("Direct messages");
5194
- dm.command("list").description("List all DMs and notifications").action(
5522
+ const dm = program2.command("dm").description("Direct messages (E2E encrypted)");
5523
+ dm.command("list").description("List all DMs and notifications (decrypted)").action(
5195
5524
  runAction(async () => {
5196
- const { arbi } = await resolveAuth();
5197
- const data = await sdk.dm.listDMs(arbi);
5525
+ const { arbi, crypto } = await resolveDmCrypto();
5526
+ const data = await sdk.dm.listDecryptedDMs(arbi, crypto);
5198
5527
  if (data.length === 0) {
5199
5528
  console.log("No messages found.");
5200
5529
  return;
@@ -5218,18 +5547,18 @@ function registerDmCommand(program2) {
5218
5547
  );
5219
5548
  })
5220
5549
  );
5221
- dm.command("send [recipient] [content]").description("Send a DM (interactive if no args; recipient can be email or user ext_id)").action(
5222
- (recipient, content) => runAction(async () => {
5223
- const { arbi } = await resolveAuth();
5550
+ dm.command("send [recipient] [content...]").description("Send an E2E encrypted DM (interactive if no args)").action(
5551
+ (recipient, contentParts) => runAction(async () => {
5552
+ const { arbi, crypto } = await resolveDmCrypto();
5224
5553
  if (!recipient) {
5225
- const contacts = await sdk.contacts.listContacts(arbi);
5226
- if (contacts.length === 0) {
5554
+ const contacts2 = await sdk.contacts.listContacts(arbi);
5555
+ if (contacts2.length === 0) {
5227
5556
  error("No contacts found. Add contacts first: arbi contacts add <email>");
5228
5557
  process.exit(1);
5229
5558
  }
5230
5559
  recipient = await promptSelect(
5231
5560
  "Send to",
5232
- contacts.map((c) => {
5561
+ contacts2.map((c) => {
5233
5562
  const u = c.user;
5234
5563
  const name = sdk.formatUserName(u);
5235
5564
  return {
@@ -5240,21 +5569,93 @@ function registerDmCommand(program2) {
5240
5569
  })
5241
5570
  );
5242
5571
  }
5572
+ let content = contentParts?.length ? contentParts.join(" ") : void 0;
5243
5573
  if (!content) {
5244
5574
  content = await promptInput("Message");
5245
5575
  }
5576
+ const contacts = await sdk.contacts.listContacts(arbi);
5246
5577
  let recipientExtId = recipient;
5578
+ let recipientPubKey;
5247
5579
  if (recipient.includes("@")) {
5248
- const contacts = await sdk.contacts.listContacts(arbi);
5249
5580
  const match = contacts.find((c) => c.email === recipient);
5250
- if (!match) {
5251
- error(`No contact found with email: ${recipient}`);
5252
- error("Add them first: arbi contacts add " + recipient);
5253
- process.exit(1);
5581
+ if (match) {
5582
+ const u = match.user;
5583
+ recipientExtId = u?.external_id ?? match.external_id;
5584
+ recipientPubKey = u?.encryption_public_key;
5585
+ } else {
5586
+ try {
5587
+ const agentsList = await sdk.agents.listAgents(arbi);
5588
+ const agentMatch = agentsList.find((a) => a.email === recipient);
5589
+ if (agentMatch) {
5590
+ recipientExtId = agentMatch.external_id;
5591
+ recipientPubKey = agentMatch.encryption_public_key;
5592
+ }
5593
+ } catch {
5594
+ }
5595
+ if (!recipientPubKey) {
5596
+ try {
5597
+ const wsUsers = await sdk.workspaces.listWorkspaceUsers(arbi);
5598
+ const wsMatch = wsUsers.find(
5599
+ (wu) => wu.email === recipient
5600
+ );
5601
+ if (wsMatch) {
5602
+ const u = wsMatch;
5603
+ recipientExtId = u.external_id;
5604
+ recipientPubKey = u.encryption_public_key;
5605
+ }
5606
+ } catch {
5607
+ }
5608
+ }
5609
+ if (!recipientExtId || recipientExtId === recipient) {
5610
+ error(`No contact, agent, or workspace member found with email: ${recipient}`);
5611
+ process.exit(1);
5612
+ }
5254
5613
  }
5255
- recipientExtId = match.user?.external_id ?? match.external_id;
5614
+ } else {
5615
+ const match = contacts.find((c) => {
5616
+ const u = c.user;
5617
+ return u?.external_id === recipient || c.external_id === recipient;
5618
+ });
5619
+ recipientPubKey = match?.user?.encryption_public_key;
5620
+ if (!recipientPubKey) {
5621
+ try {
5622
+ const agentsList = await sdk.agents.listAgents(arbi);
5623
+ const agentMatch = agentsList.find((a) => a.external_id === recipientExtId);
5624
+ if (agentMatch) {
5625
+ recipientPubKey = agentMatch.encryption_public_key;
5626
+ }
5627
+ } catch {
5628
+ }
5629
+ }
5630
+ if (!recipientPubKey) {
5631
+ try {
5632
+ const wsUsers = await sdk.workspaces.listWorkspaceUsers(arbi);
5633
+ const wsMatch = wsUsers.find(
5634
+ (wu) => wu.external_id === recipientExtId
5635
+ );
5636
+ if (wsMatch) {
5637
+ recipientPubKey = wsMatch.encryption_public_key;
5638
+ }
5639
+ } catch {
5640
+ }
5641
+ }
5642
+ }
5643
+ if (!recipientPubKey) {
5644
+ error("Cannot send encrypted DM \u2014 recipient public key not found.");
5645
+ error("The recipient is not in your contacts or workspace.");
5646
+ process.exit(1);
5256
5647
  }
5257
- const data = await sdk.dm.sendDM(arbi, [{ recipient_ext_id: recipientExtId, content }]);
5648
+ const data = await sdk.dm.sendEncryptedDM(
5649
+ arbi,
5650
+ [
5651
+ {
5652
+ recipient_ext_id: recipientExtId,
5653
+ content,
5654
+ recipient_encryption_public_key: recipientPubKey
5655
+ }
5656
+ ],
5657
+ crypto
5658
+ );
5258
5659
  for (const n of data) {
5259
5660
  success(`Sent: ${n.external_id} \u2192 ${n.recipient.email}`);
5260
5661
  }
@@ -5262,10 +5663,10 @@ function registerDmCommand(program2) {
5262
5663
  );
5263
5664
  dm.command("read [ids...]").description("Mark messages as read (interactive picker if no IDs given)").action(
5264
5665
  (ids) => runAction(async () => {
5265
- const { arbi } = await resolveAuth();
5666
+ const { arbi, crypto } = await resolveDmCrypto();
5266
5667
  let msgIds = ids && ids.length > 0 ? ids : void 0;
5267
5668
  if (!msgIds) {
5268
- const data2 = await sdk.dm.listDMs(arbi);
5669
+ const data2 = await sdk.dm.listDecryptedDMs(arbi, crypto);
5269
5670
  const unread = data2.filter((m) => !m.read);
5270
5671
  if (unread.length === 0) {
5271
5672
  console.log("No unread messages.");
@@ -5290,10 +5691,10 @@ function registerDmCommand(program2) {
5290
5691
  );
5291
5692
  dm.command("delete [ids...]").description("Delete messages (interactive picker if no IDs given)").action(
5292
5693
  (ids) => runAction(async () => {
5293
- const { arbi } = await resolveAuth();
5694
+ const { arbi, crypto } = await resolveDmCrypto();
5294
5695
  let msgIds = ids && ids.length > 0 ? ids : void 0;
5295
5696
  if (!msgIds) {
5296
- const data = await sdk.dm.listDMs(arbi);
5697
+ const data = await sdk.dm.listDecryptedDMs(arbi, crypto);
5297
5698
  if (data.length === 0) {
5298
5699
  console.log("No messages found.");
5299
5700
  return;
@@ -5385,7 +5786,10 @@ function registerTagsCommand(program2) {
5385
5786
  const shared = interactive ? opts.shared || await promptConfirm("Shared?", false) : opts.shared ?? false;
5386
5787
  const data = await sdk.tags.createTag(arbi, {
5387
5788
  name,
5388
- tagType: { type: tagType, options: [] },
5789
+ tagType: {
5790
+ type: tagType,
5791
+ options: []
5792
+ },
5389
5793
  instruction,
5390
5794
  shared
5391
5795
  });
@@ -6028,16 +6432,19 @@ function registerQuickstartCommand(program2) {
6028
6432
  password2 = await promptPassword("Password");
6029
6433
  }
6030
6434
  try {
6031
- const { arbi } = await sdk.performPasswordLogin(config, email, password2, store);
6435
+ const { arbi, loginResult } = await sdk.performPasswordLogin(config, email, password2, store);
6032
6436
  success(`Logged in as ${email}`);
6033
6437
  const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
6034
6438
  const wsList = workspaces3 || [];
6035
- const memberWorkspaces = wsList.filter((ws) => ws.users?.some((u) => u.email === email));
6439
+ const memberWorkspaces = wsList.filter(
6440
+ (ws) => ws.users?.some((u) => u.user.email === email)
6441
+ );
6036
6442
  let workspaceId;
6037
6443
  let workspaceName;
6038
6444
  if (memberWorkspaces.length === 0) {
6039
6445
  console.log("Creating your first workspace...");
6040
- const ws = await sdk.workspaces.createWorkspace(arbi, "My Workspace");
6446
+ const encryptedKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
6447
+ const ws = await sdk.workspaces.createWorkspace(arbi, "My Workspace", encryptedKey);
6041
6448
  workspaceId = ws.external_id;
6042
6449
  workspaceName = ws.name;
6043
6450
  } else {
@@ -6048,7 +6455,8 @@ function registerQuickstartCommand(program2) {
6048
6455
  const selected = await promptSelect("Select workspace", choices);
6049
6456
  if (selected === "__new__") {
6050
6457
  const name = await promptInput("Workspace name", false) || "My Workspace";
6051
- const ws = await sdk.workspaces.createWorkspace(arbi, name);
6458
+ const encKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
6459
+ const ws = await sdk.workspaces.createWorkspace(arbi, name, encKey);
6052
6460
  workspaceId = ws.external_id;
6053
6461
  workspaceName = ws.name;
6054
6462
  } else {
@@ -6147,8 +6555,14 @@ function registerAgentCreateCommand(program2) {
6147
6555
  process.exit(1);
6148
6556
  }
6149
6557
  try {
6150
- const { arbi } = await sdk.performPasswordLogin(config, email, opts.password, store);
6151
- const ws = await sdk.workspaces.createWorkspace(arbi, opts.workspaceName);
6558
+ const { arbi, loginResult } = await sdk.performPasswordLogin(
6559
+ config,
6560
+ email,
6561
+ opts.password,
6562
+ store
6563
+ );
6564
+ const encryptedKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
6565
+ const ws = await sdk.workspaces.createWorkspace(arbi, opts.workspaceName, encryptedKey);
6152
6566
  updateConfig({ selectedWorkspaceId: ws.external_id });
6153
6567
  console.log("");
6154
6568
  success("Agent account created!");
@@ -6166,6 +6580,60 @@ function registerAgentCreateCommand(program2) {
6166
6580
  }
6167
6581
  );
6168
6582
  }
6583
+ function registerAgentCommand(program2) {
6584
+ const agent = program2.command("agent").description("Manage persistent agents");
6585
+ agent.command("list").description("List your persistent agents").action(
6586
+ runAction(async () => {
6587
+ const { arbi } = await resolveAuth();
6588
+ const data = await sdk.agents.listAgents(arbi);
6589
+ if (data.length === 0) {
6590
+ console.log("No agents found.");
6591
+ return;
6592
+ }
6593
+ printTable(
6594
+ [
6595
+ { header: "ID", width: 20, value: (r) => r.external_id },
6596
+ {
6597
+ header: "NAME",
6598
+ width: 24,
6599
+ value: (r) => sdk.formatUserName(r) || r.email || ""
6600
+ },
6601
+ { header: "EMAIL", width: 40, value: (r) => r.email || "" }
6602
+ ],
6603
+ data
6604
+ );
6605
+ })
6606
+ );
6607
+ agent.command("delete [agent-id]").description("Delete a persistent agent (picker if no ID)").action(
6608
+ (agentId) => runAction(async () => {
6609
+ const { arbi } = await resolveAuth();
6610
+ if (!agentId) {
6611
+ const data = await sdk.agents.listAgents(arbi);
6612
+ if (data.length === 0) {
6613
+ console.log("No agents to delete.");
6614
+ return;
6615
+ }
6616
+ agentId = await promptSelect(
6617
+ "Select agent to delete",
6618
+ data.map((a) => ({
6619
+ name: `${sdk.formatUserName(a) || a.email} (${a.external_id})`,
6620
+ value: a.external_id
6621
+ }))
6622
+ );
6623
+ }
6624
+ const confirmed = await promptConfirm(`Delete agent ${agentId}?`, false);
6625
+ if (!confirmed) {
6626
+ console.log("Cancelled.");
6627
+ return;
6628
+ }
6629
+ await sdk.agents.deleteAgents(arbi, [agentId]);
6630
+ success(`Agent ${agentId} deleted.`);
6631
+ })()
6632
+ );
6633
+ agent.action(async () => {
6634
+ await agent.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
6635
+ });
6636
+ }
6169
6637
  function formatAge(isoDate) {
6170
6638
  const ms = Date.now() - new Date(isoDate).getTime();
6171
6639
  if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
@@ -6214,11 +6682,8 @@ function registerTaskCommand(program2) {
6214
6682
  task.command("status [task-id]").description("Check current task status (defaults to most recent)").option("-w, --workspace <id>", "Workspace ID").option("--json", "Output in JSON format").action(
6215
6683
  (taskId, opts) => runAction(async () => {
6216
6684
  const id = resolveTaskId(taskId);
6217
- const { accessToken, workspaceKeyHeader, config } = await resolveWorkspace(opts?.workspace);
6218
- const result = await sdk.responses.getResponse(
6219
- { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader },
6220
- id
6221
- );
6685
+ const { accessToken, config } = await resolveWorkspace(opts?.workspace);
6686
+ const result = await sdk.responses.getResponse({ baseUrl: config.baseUrl, accessToken }, id);
6222
6687
  if (result.status === "completed" || result.status === "failed") {
6223
6688
  updateTaskStatus(id, result.status);
6224
6689
  }
@@ -6237,11 +6702,8 @@ function registerTaskCommand(program2) {
6237
6702
  task.command("result [task-id]").description("Fetch and print the completed task result").option("-w, --workspace <id>", "Workspace ID").option("--json", "Output in JSON format").action(
6238
6703
  (taskId, opts) => runAction(async () => {
6239
6704
  const id = resolveTaskId(taskId);
6240
- const { accessToken, workspaceKeyHeader, config } = await resolveWorkspace(opts?.workspace);
6241
- const result = await sdk.responses.getResponse(
6242
- { baseUrl: config.baseUrl, accessToken, workspaceKeyHeader },
6243
- id
6244
- );
6705
+ const { accessToken, config } = await resolveWorkspace(opts?.workspace);
6706
+ const result = await sdk.responses.getResponse({ baseUrl: config.baseUrl, accessToken }, id);
6245
6707
  if (result.status === "completed" || result.status === "failed") {
6246
6708
  updateTaskStatus(id, result.status);
6247
6709
  }
@@ -6342,6 +6804,220 @@ function registerCompletionCommand(program2) {
6342
6804
  })
6343
6805
  );
6344
6806
  }
6807
+ function resolvePath(p) {
6808
+ const raw = p ?? ".";
6809
+ const expanded = raw.startsWith("~") ? raw.replace("~", os.homedir()) : raw;
6810
+ return path.resolve(expanded);
6811
+ }
6812
+ function formatSize(bytes) {
6813
+ if (bytes < 1024) return `${bytes}B`;
6814
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
6815
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
6816
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
6817
+ }
6818
+ function registerLocalCommand(program2) {
6819
+ const local = program2.command("local").description("Local filesystem operations");
6820
+ local.command("ls [path]").description("List files in a directory").option("-l, --long", "Show file sizes and types").action((path5, opts) => {
6821
+ const dir = resolvePath(path5);
6822
+ let entries;
6823
+ try {
6824
+ entries = fs2.readdirSync(dir);
6825
+ } catch {
6826
+ console.error(`Cannot read directory: ${dir}`);
6827
+ process.exit(1);
6828
+ }
6829
+ for (const entry of entries.filter((e) => !e.startsWith(".")).sort()) {
6830
+ if (opts?.long) {
6831
+ try {
6832
+ const stat = fs2.statSync(path.join(dir, entry));
6833
+ const type = stat.isDirectory() ? "dir" : "file";
6834
+ const size = stat.isFile() ? formatSize(stat.size) : "-";
6835
+ console.log(`${type.padEnd(5)} ${size.padStart(10)} ${entry}`);
6836
+ } catch {
6837
+ console.log(`? ${"-".padStart(10)} ${entry}`);
6838
+ }
6839
+ } else {
6840
+ console.log(entry);
6841
+ }
6842
+ }
6843
+ });
6844
+ local.command("find <pattern>").description('Find files matching a glob (e.g. "**/*.pdf")').option("-d, --dir <path>", "Directory to search in", ".").action((pattern, opts) => {
6845
+ const dir = resolvePath(opts.dir);
6846
+ const results = fs2.globSync(pattern, { cwd: dir });
6847
+ if (results.length === 0) {
6848
+ console.log(`No files matching "${pattern}" in ${dir}`);
6849
+ return;
6850
+ }
6851
+ for (const file of results) {
6852
+ console.log(file);
6853
+ }
6854
+ });
6855
+ local.command("cat <file>").description("Print file contents").option("--head <lines>", "Only show first N lines").action((file, opts) => {
6856
+ const filePath = resolvePath(file);
6857
+ let content;
6858
+ try {
6859
+ content = fs2.readFileSync(filePath, "utf-8");
6860
+ } catch {
6861
+ console.error(`Cannot read file: ${filePath}`);
6862
+ process.exit(1);
6863
+ }
6864
+ if (opts.head) {
6865
+ const n = parseInt(opts.head, 10);
6866
+ console.log(content.split("\n").slice(0, n).join("\n"));
6867
+ } else {
6868
+ console.log(content);
6869
+ }
6870
+ });
6871
+ local.command("tree [path]").description("Show directory tree").option("-d, --depth <n>", "Maximum depth", "3").action((path5, opts) => {
6872
+ const dir = resolvePath(path5);
6873
+ const maxDepth = parseInt(opts?.depth ?? "3", 10);
6874
+ console.log(path.basename(dir) + "/");
6875
+ printTree(dir, "", maxDepth, 0);
6876
+ });
6877
+ }
6878
+ function printTree(dir, prefix, maxDepth, depth) {
6879
+ if (depth >= maxDepth) return;
6880
+ let entries;
6881
+ try {
6882
+ entries = fs2.readdirSync(dir).filter((e) => !e.startsWith(".")).sort();
6883
+ } catch {
6884
+ return;
6885
+ }
6886
+ for (let i = 0; i < entries.length; i++) {
6887
+ const entry = entries[i];
6888
+ const isLast = i === entries.length - 1;
6889
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
6890
+ const full = path.join(dir, entry);
6891
+ let isDir = false;
6892
+ try {
6893
+ isDir = fs2.statSync(full).isDirectory();
6894
+ } catch {
6895
+ continue;
6896
+ }
6897
+ console.log(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
6898
+ if (isDir) {
6899
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
6900
+ printTree(full, nextPrefix, maxDepth, depth + 1);
6901
+ }
6902
+ }
6903
+ }
6904
+ async function authorizeShared(opts) {
6905
+ const { arbi, loginResult } = await resolveAuth();
6906
+ let name = opts.name;
6907
+ if (!name) {
6908
+ name = await promptInput(opts.persist ? "Agent name" : "Session name");
6909
+ }
6910
+ if (!name?.trim()) {
6911
+ error("Name is required");
6912
+ process.exit(1);
6913
+ }
6914
+ const { data: allWorkspaces, error: wsErr } = await arbi.fetch.GET("/v1/user/workspaces");
6915
+ if (wsErr || !allWorkspaces || allWorkspaces.length === 0) {
6916
+ error("No workspaces found");
6917
+ process.exit(1);
6918
+ }
6919
+ let workspaceIds = opts.workspace;
6920
+ if (!workspaceIds || workspaceIds.length === 0) {
6921
+ workspaceIds = await promptCheckbox(
6922
+ "Select workspaces to grant access",
6923
+ allWorkspaces.map((ws) => ({
6924
+ name: `${ws.name} (${ws.external_id})`,
6925
+ value: ws.external_id
6926
+ }))
6927
+ );
6928
+ }
6929
+ if (workspaceIds.length === 0) {
6930
+ error("At least one workspace is required");
6931
+ process.exit(1);
6932
+ }
6933
+ const creds = store.requireCredentials();
6934
+ const workspaceKeysMap = {};
6935
+ for (const wsId of workspaceIds) {
6936
+ const ws = allWorkspaces.find((w) => w.external_id === wsId);
6937
+ if (!ws?.wrapped_key) {
6938
+ error(`Workspace ${wsId} not found or has no encryption key`);
6939
+ process.exit(1);
6940
+ }
6941
+ const encryptedKey = await sdk.generateEncryptedWorkspaceKey(
6942
+ arbi,
6943
+ ws.wrapped_key,
6944
+ loginResult.serverSessionKey,
6945
+ creds.signingPrivateKeyBase64
6946
+ );
6947
+ workspaceKeysMap[wsId] = encryptedKey;
6948
+ }
6949
+ const ttl = parseInt(opts.ttl || "3600", 10);
6950
+ const response = await sdk.sessions.authorizeSession(arbi, workspaceKeysMap, {
6951
+ activeWorkspace: workspaceIds[0],
6952
+ ttl,
6953
+ persistIdentity: opts.persist,
6954
+ name: name.trim()
6955
+ });
6956
+ return { response, ttl };
6957
+ }
6958
+ function registerAuthorizeCommand(program2) {
6959
+ const authorize = program2.command("authorize").description("Create a claim code for a session or agent");
6960
+ authorize.command("agent").description("Create a persistent agent identity with a claim code").option("--name <name>", "Agent name").option("-w, --workspace <ids...>", "Workspace IDs to grant access to (interactive if omitted)").action(
6961
+ (opts) => runAction(async () => {
6962
+ const { response } = await authorizeShared({ persist: true, ...opts });
6963
+ console.log("");
6964
+ success("Agent authorized!");
6965
+ console.log("");
6966
+ console.log(` Claim code: ${chalk2__default.default.cyan(chalk2__default.default.bold(response.claim_code))}`);
6967
+ console.log("");
6968
+ dim("On the agent machine: arbi connect --code <claim-code> --agent claude");
6969
+ })()
6970
+ );
6971
+ authorize.command("session").description("Create an ephemeral session with a claim code").option("--name <name>", "Session name").option("--ttl <seconds>", "Session TTL in seconds (default: 3600)", "3600").option("-w, --workspace <ids...>", "Workspace IDs to grant access to (interactive if omitted)").action(
6972
+ (opts) => runAction(async () => {
6973
+ const { response, ttl } = await authorizeShared({ persist: false, ...opts });
6974
+ console.log("");
6975
+ success("Session authorized!");
6976
+ console.log("");
6977
+ console.log(` Claim code: ${chalk2__default.default.cyan(chalk2__default.default.bold(response.claim_code))}`);
6978
+ console.log(` Expires in: ${ttl}s`);
6979
+ console.log("");
6980
+ dim("Claim with: arbi connect --code <claim-code>");
6981
+ })()
6982
+ );
6983
+ authorize.action(() => {
6984
+ authorize.help();
6985
+ });
6986
+ }
6987
+ function registerSessionCommand(program2) {
6988
+ const session = program2.command("session").description("List and manage sessions");
6989
+ session.command("list").description("List active sessions").action(
6990
+ runAction(async () => {
6991
+ const { arbi } = await resolveAuth();
6992
+ const data = await sdk.sessions.listSessions(arbi);
6993
+ if (data.length === 0) {
6994
+ console.log("No active sessions.");
6995
+ return;
6996
+ }
6997
+ printTable(
6998
+ [
6999
+ {
7000
+ header: "ID",
7001
+ width: 18,
7002
+ value: (r) => r.external_id || r.session_id
7003
+ },
7004
+ { header: "NAME", width: 20, value: (r) => r.name || "" },
7005
+ { header: "STATUS", width: 12, value: (r) => r.status },
7006
+ { header: "TTL", width: 10, value: (r) => `${r.ttl}s` },
7007
+ {
7008
+ header: "WORKSPACES",
7009
+ width: 12,
7010
+ value: (r) => String(r.workspaces?.length ?? 0)
7011
+ }
7012
+ ],
7013
+ data
7014
+ );
7015
+ })
7016
+ );
7017
+ session.action(async () => {
7018
+ await session.commands.find((c) => c.name() === "list").parseAsync([], { from: "user" });
7019
+ });
7020
+ }
6345
7021
 
6346
7022
  // src/index.ts
6347
7023
  console.debug = () => {
@@ -6352,7 +7028,7 @@ console.info = (...args) => {
6352
7028
  _origInfo(...args);
6353
7029
  };
6354
7030
  var program = new commander.Command();
6355
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.19");
7031
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.20");
6356
7032
  registerConfigCommand(program);
6357
7033
  registerLoginCommand(program);
6358
7034
  registerRegisterCommand(program);
@@ -6378,8 +7054,13 @@ registerTuiCommand(program);
6378
7054
  registerUpdateCommand(program);
6379
7055
  registerQuickstartCommand(program);
6380
7056
  registerAgentCreateCommand(program);
7057
+ registerAgentCommand(program);
6381
7058
  registerTaskCommand(program);
6382
7059
  registerCompletionCommand(program);
7060
+ registerConnectCommand(program);
7061
+ registerAuthorizeCommand(program);
7062
+ registerSessionCommand(program);
7063
+ registerLocalCommand(program);
6383
7064
  var completionIdx = process.argv.indexOf("--get-completions");
6384
7065
  if (completionIdx !== -1) {
6385
7066
  const line = process.argv[completionIdx + 1] ?? "";