@arbidocs/cli 0.3.41 → 0.3.43

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
@@ -3641,7 +3641,7 @@ function getLatestVersion(skipCache = false) {
3641
3641
  }
3642
3642
  }
3643
3643
  function getCurrentVersion() {
3644
- return "0.3.41";
3644
+ return "0.3.43";
3645
3645
  }
3646
3646
  function readChangelog(fromVersion, toVersion) {
3647
3647
  try {
@@ -3694,17 +3694,17 @@ function showChangelog(fromVersion, toVersion) {
3694
3694
  async function checkForUpdates(autoUpdate) {
3695
3695
  try {
3696
3696
  const latest = getLatestVersion();
3697
- if (!latest || latest === "0.3.41") return;
3697
+ if (!latest || latest === "0.3.43") return;
3698
3698
  if (autoUpdate) {
3699
3699
  warn(`
3700
- Your arbi version is out of date (${"0.3.41"} \u2192 ${latest}). Updating...`);
3700
+ Your arbi version is out of date (${"0.3.43"} \u2192 ${latest}). Updating...`);
3701
3701
  child_process.execSync("npm install -g @arbidocs/cli@latest", { stdio: "inherit" });
3702
- showChangelog("0.3.41", latest);
3702
+ showChangelog("0.3.43", latest);
3703
3703
  console.log(`Updated to ${latest}.`);
3704
3704
  } else {
3705
3705
  warn(
3706
3706
  `
3707
- Your arbi version is out of date (${"0.3.41"} \u2192 ${latest}).
3707
+ Your arbi version is out of date (${"0.3.43"} \u2192 ${latest}).
3708
3708
  Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3709
3709
  );
3710
3710
  }
@@ -3714,9 +3714,9 @@ Run "arbi update" to upgrade, or "arbi update auto" to always stay up to date.`
3714
3714
  function hintUpdateOnError() {
3715
3715
  try {
3716
3716
  const cached = readCache();
3717
- if (cached && cached.latest !== "0.3.41") {
3717
+ if (cached && cached.latest !== "0.3.43") {
3718
3718
  warn(
3719
- `Your arbi version is out of date (${"0.3.41"} \u2192 ${cached.latest}). Run "arbi update".`
3719
+ `Your arbi version is out of date (${"0.3.43"} \u2192 ${cached.latest}). Run "arbi update".`
3720
3720
  );
3721
3721
  }
3722
3722
  } catch {
@@ -4101,14 +4101,29 @@ function registerListenCommand(program2) {
4101
4101
 
4102
4102
  // src/commands/login.ts
4103
4103
  function registerLoginCommand(program2) {
4104
- 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("-k, --signing-key <key>", "Signing key (base64) for agent recovery login").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(
4104
+ 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("-k, --signing-key <key>", "Signing key (base64) for agent recovery login").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").option("--json", "Emit a single JSON object on success (scripting)").action(
4105
4105
  (opts) => runAction(async () => {
4106
4106
  const config = store.requireConfig();
4107
- const email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
4107
+ const isTty = process.stdin.isTTY;
4108
+ const emailFromFlagOrEnv = opts.email || process.env.ARBI_EMAIL;
4109
+ if (!isTty && !emailFromFlagOrEnv) {
4110
+ const msg = "Email is required when stdin is not a TTY. Use --email <email> or set ARBI_EMAIL.";
4111
+ if (opts.json) console.log(JSON.stringify({ ok: false, error: msg }));
4112
+ else error(msg);
4113
+ process.exit(1);
4114
+ }
4115
+ const passwordFromFlagOrEnv = opts.password || process.env.ARBI_PASSWORD;
4116
+ if (!isTty && !passwordFromFlagOrEnv && !opts.signingKey) {
4117
+ const msg = "Password is required when stdin is not a TTY. Use --password <password> or set ARBI_PASSWORD.";
4118
+ if (opts.json) console.log(JSON.stringify({ ok: false, error: msg }));
4119
+ else error(msg);
4120
+ process.exit(1);
4121
+ }
4122
+ const email = emailFromFlagOrEnv || await promptInput("Email");
4108
4123
  try {
4109
4124
  let ssoPolling = false;
4110
4125
  const { arbi } = opts.signingKey ? await sdk.performSigningKeyLogin(config, email, opts.signingKey, store) : opts.sso ? await (async () => {
4111
- const pw = opts.password || process.env.ARBI_PASSWORD || await promptPassword("Password");
4126
+ const pw = passwordFromFlagOrEnv || await promptPassword("Password");
4112
4127
  const result = await sdk.performSsoDeviceFlowLogin(config, email, pw, store, {
4113
4128
  onUserCode: (userCode, verificationUri) => {
4114
4129
  console.log(`
@@ -4127,9 +4142,10 @@ Open this URL in your browser:
4127
4142
  if (ssoPolling) console.log("\n");
4128
4143
  return result;
4129
4144
  })() : await (async () => {
4130
- const pw = opts.password || process.env.ARBI_PASSWORD || await promptPassword("Password");
4145
+ const pw = passwordFromFlagOrEnv || await promptPassword("Password");
4131
4146
  return sdk.performPasswordLogin(config, email, pw, store);
4132
4147
  })();
4148
+ if (!opts.json) success(`Logged in as ${email}`);
4133
4149
  clearChatSession();
4134
4150
  const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
4135
4151
  const wsList = workspaces3 || [];
@@ -4137,26 +4153,39 @@ Open this URL in your browser:
4137
4153
  const memberWorkspaces = wsList.filter(
4138
4154
  (ws) => ws.users?.some((u) => u.user.email === email)
4139
4155
  );
4156
+ let selectedWorkspace;
4140
4157
  if (memberWorkspaces.length === 0) {
4141
- console.log("No workspaces found. Create one with: arbi workspace create <name>");
4158
+ if (!opts.json) {
4159
+ console.log("No workspaces found. Create one with: arbi workspace create <name>");
4160
+ }
4142
4161
  } else if (opts.workspace) {
4143
4162
  const ws = memberWorkspaces.find((w) => w.external_id === opts.workspace);
4144
4163
  if (!ws) {
4145
- error(`Workspace ${opts.workspace} not found or you don't have access.`);
4164
+ const msg = `Workspace ${opts.workspace} not found or you don't have access.`;
4165
+ if (opts.json) console.log(JSON.stringify({ ok: false, error: msg }));
4166
+ else error(msg);
4146
4167
  process.exit(1);
4147
4168
  }
4148
4169
  updateConfig({ selectedWorkspaceId: ws.external_id });
4149
- success(`Workspace: ${ws.name} (${ref(ws.external_id)})`);
4170
+ selectedWorkspace = { external_id: ws.external_id, name: ws.name };
4171
+ if (!opts.json) success(`Workspace: ${ws.name} (${ref(ws.external_id)})`);
4150
4172
  } else if (memberWorkspaces.length === 1) {
4151
4173
  updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
4152
- success(
4153
- `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
4154
- );
4174
+ selectedWorkspace = {
4175
+ external_id: memberWorkspaces[0].external_id,
4176
+ name: memberWorkspaces[0].name
4177
+ };
4178
+ if (!opts.json)
4179
+ success(
4180
+ `Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`
4181
+ );
4182
+ } else if (opts.json) {
4155
4183
  } else {
4156
4184
  const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
4157
4185
  const selected = await promptSelect("Select workspace", choices);
4158
4186
  updateConfig({ selectedWorkspaceId: selected });
4159
4187
  const ws = memberWorkspaces.find((w) => w.external_id === selected);
4188
+ selectedWorkspace = { external_id: selected, name: ws.name };
4160
4189
  success(`Workspace: ${ws.name} (${ref(selected)})`);
4161
4190
  dim('\nTip: Run "arbi config alias" to use A as a shortcut for "arbi ask"');
4162
4191
  }
@@ -4166,8 +4195,21 @@ Open this URL in your browser:
4166
4195
  await startListening(opts.agent, config);
4167
4196
  }
4168
4197
  }
4198
+ if (opts.json) {
4199
+ console.log(
4200
+ JSON.stringify({
4201
+ ok: true,
4202
+ email,
4203
+ server: config.baseUrl,
4204
+ workspace: selectedWorkspace ?? null,
4205
+ workspaces_available: memberWorkspaces.length
4206
+ })
4207
+ );
4208
+ }
4169
4209
  } catch (err) {
4170
- error(`Login failed: ${formatCliError(err)}`);
4210
+ const msg = formatCliError(err);
4211
+ if (opts.json) console.log(JSON.stringify({ ok: false, error: msg }));
4212
+ else error(`Login failed: ${msg}`);
4171
4213
  process.exit(1);
4172
4214
  } finally {
4173
4215
  await checkForUpdates(getConfig()?.autoUpdate);
@@ -4176,7 +4218,19 @@ Open this URL in your browser:
4176
4218
  );
4177
4219
  }
4178
4220
  var CENTRAL_API_URL = "https://central.arbi.work";
4179
- async function getVerificationCode(email, apiKey) {
4221
+ var RegisterHintError = class extends Error {
4222
+ constructor(message, kind) {
4223
+ super(message);
4224
+ this.kind = kind;
4225
+ this.name = "RegisterHintError";
4226
+ }
4227
+ kind;
4228
+ };
4229
+ function looksLikeAgentEmail(email) {
4230
+ const local = email.split("@")[0] ?? "";
4231
+ return local.toLowerCase().startsWith("agent-");
4232
+ }
4233
+ async function getVerificationCode(email, apiKey, deploymentDomain) {
4180
4234
  const params = new URLSearchParams({ email });
4181
4235
  const res = await fetch(`${CENTRAL_API_URL}/license-management/verify-ci?${params.toString()}`, {
4182
4236
  method: "GET",
@@ -4184,6 +4238,24 @@ async function getVerificationCode(email, apiKey) {
4184
4238
  });
4185
4239
  if (!res.ok) {
4186
4240
  const body = await res.text().catch(() => "");
4241
+ if (res.status === 403 && /agent-\*/i.test(body)) {
4242
+ throw new RegisterHintError(
4243
+ `This SUPPORT_API_KEY is a deployment key that can only register agent-*@${deploymentDomain} emails. Prefix your email with "agent-" (e.g. agent-<id>@${deploymentDomain}) or use a superuser key.`,
4244
+ "agent-email-required"
4245
+ );
4246
+ }
4247
+ if (res.status === 403 && /Invalid API Key/i.test(body)) {
4248
+ throw new RegisterHintError(
4249
+ "SUPPORT_API_KEY was rejected by central. Check the key value.",
4250
+ "bad-api-key"
4251
+ );
4252
+ }
4253
+ if (res.status === 404 && /No verification record/i.test(body)) {
4254
+ throw new RegisterHintError(
4255
+ `No pending verification for ${email}. The email may already be registered \u2014 try: arbi login --email ${email}`,
4256
+ "already-registered"
4257
+ );
4258
+ }
4187
4259
  throw new Error(`Failed to get verification code: ${res.status} ${body}`);
4188
4260
  }
4189
4261
  const data = await res.json();
@@ -4192,7 +4264,7 @@ async function getVerificationCode(email, apiKey) {
4192
4264
  return Array.isArray(words) ? words.join(" ") : String(words);
4193
4265
  }
4194
4266
  function registerRegisterCommand(program2) {
4195
- 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(
4267
+ 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").option("-y, --yes", 'Skip the post-register "Log in now?" confirmation').option("--json", "Emit a single JSON object on success (scripting)").action(
4196
4268
  (opts) => runAction(async () => {
4197
4269
  const config = requireConfig();
4198
4270
  if (opts.nonInteractive) {
@@ -4207,7 +4279,7 @@ async function smartRegister(config, opts) {
4207
4279
  let email = opts.email || process.env.ARBI_EMAIL || await promptInput("Email");
4208
4280
  if ((opts.email || process.env.ARBI_EMAIL) && !email.includes("@")) {
4209
4281
  email = `${email}@${config.deploymentDomain}`;
4210
- console.log(`Using email: ${email}`);
4282
+ if (!opts.json) console.log(`Using email: ${email}`);
4211
4283
  }
4212
4284
  const arbi = client.createArbiClient({
4213
4285
  baseUrl: config.baseUrl,
@@ -4261,16 +4333,22 @@ async function smartRegister(config, opts) {
4261
4333
  firstName,
4262
4334
  lastName
4263
4335
  });
4264
- success(`
4336
+ if (!opts.json) success(`
4265
4337
  Registered successfully as ${email}`);
4266
4338
  } catch (err) {
4267
- error(`Registration failed: ${formatCliError(err)}`);
4339
+ if (opts.json) {
4340
+ console.log(JSON.stringify({ ok: false, stage: "register", error: formatCliError(err) }));
4341
+ } else {
4342
+ error(`Registration failed: ${formatCliError(err)}`);
4343
+ }
4268
4344
  process.exit(1);
4269
4345
  }
4270
4346
  const allFlagsProvided = !!(opts.email || process.env.ARBI_EMAIL) && !!(opts.password || process.env.ARBI_PASSWORD) && !!opts.verificationCode;
4271
- const doLogin = allFlagsProvided || await promptConfirm("Log in now?");
4347
+ const doLogin = allFlagsProvided || opts.yes || await promptConfirm("Log in now?");
4272
4348
  if (doLogin) {
4273
- await loginAfterRegister(config, email, pw);
4349
+ await loginAfterRegister(config, email, pw, { json: opts.json });
4350
+ } else if (opts.json) {
4351
+ console.log(JSON.stringify({ ok: true, email, server: config.baseUrl, logged_in: false }));
4274
4352
  }
4275
4353
  }
4276
4354
  async function nonInteractiveRegister(config, opts) {
@@ -4283,12 +4361,18 @@ async function nonInteractiveRegister(config, opts) {
4283
4361
  }
4284
4362
  if (!email.includes("@")) {
4285
4363
  email = `${email}@${config.deploymentDomain}`;
4286
- console.log(`Using email: ${email}`);
4364
+ if (!opts.json) console.log(`Using email: ${email}`);
4287
4365
  }
4288
4366
  if (!password2) {
4289
4367
  error("Password required. Use --password <password> or set ARBI_PASSWORD");
4290
4368
  process.exit(1);
4291
4369
  }
4370
+ if (supportApiKey && !opts.verificationCode && !looksLikeAgentEmail(email)) {
4371
+ const hint = `Email "${email}" does not start with "agent-". SUPPORT_API_KEY deployment keys only accept agent-*@${config.deploymentDomain} emails. If your key is a superuser key this may still succeed \u2014 proceeding...`;
4372
+ if (!opts.json) {
4373
+ console.error(`Warning: ${hint}`);
4374
+ }
4375
+ }
4292
4376
  const arbi = client.createArbiClient({
4293
4377
  baseUrl: config.baseUrl,
4294
4378
  deploymentDomain: config.deploymentDomain,
@@ -4309,10 +4393,37 @@ async function nonInteractiveRegister(config, opts) {
4309
4393
  body: { email }
4310
4394
  });
4311
4395
  if (verifyResponse.error) {
4312
- error(`verify-email failed: ${JSON.stringify(verifyResponse.error)}`);
4396
+ if (opts.json) {
4397
+ console.log(
4398
+ JSON.stringify({
4399
+ ok: false,
4400
+ stage: "verify-email",
4401
+ error: JSON.stringify(verifyResponse.error)
4402
+ })
4403
+ );
4404
+ } else {
4405
+ error(`verify-email failed: ${JSON.stringify(verifyResponse.error)}`);
4406
+ }
4407
+ process.exit(1);
4408
+ }
4409
+ try {
4410
+ verificationCode = await getVerificationCode(email, supportApiKey, config.deploymentDomain);
4411
+ } catch (err) {
4412
+ const msg = err instanceof Error ? err.message : String(err);
4413
+ if (opts.json) {
4414
+ console.log(
4415
+ JSON.stringify({
4416
+ ok: false,
4417
+ stage: "verify-ci",
4418
+ error: msg,
4419
+ kind: err instanceof RegisterHintError ? err.kind : void 0
4420
+ })
4421
+ );
4422
+ } else {
4423
+ error(`Error: ${msg}`);
4424
+ }
4313
4425
  process.exit(1);
4314
4426
  }
4315
- verificationCode = await getVerificationCode(email, supportApiKey);
4316
4427
  }
4317
4428
  try {
4318
4429
  await arbi.auth.register({
@@ -4322,60 +4433,93 @@ async function nonInteractiveRegister(config, opts) {
4322
4433
  firstName: opts.firstName ?? "Test",
4323
4434
  lastName: opts.lastName ?? "User"
4324
4435
  });
4325
- success(`Registered: ${email}`);
4436
+ if (!opts.json) success(`Registered: ${email}`);
4326
4437
  } catch (err) {
4327
- error(`Registration failed: ${formatCliError(err)}`);
4438
+ if (opts.json) {
4439
+ console.log(JSON.stringify({ ok: false, stage: "register", error: formatCliError(err) }));
4440
+ } else {
4441
+ error(`Registration failed: ${formatCliError(err)}`);
4442
+ }
4328
4443
  process.exit(1);
4329
4444
  }
4330
- await loginAfterRegister(config, email, password2);
4445
+ await loginAfterRegister(config, email, password2, { json: opts.json });
4331
4446
  }
4332
- async function loginAfterRegister(config, email, password2) {
4447
+ async function loginAfterRegister(config, email, password2, { json = false } = {}) {
4333
4448
  try {
4334
4449
  const { arbi, loginResult } = await sdk.performPasswordLogin(config, email, password2, store);
4335
- success(`Logged in as ${email}`);
4450
+ if (!json) success(`Logged in as ${email}`);
4336
4451
  const { data: workspaces3 } = await arbi.fetch.GET("/v1/user/workspaces");
4337
4452
  const wsList = workspaces3 || [];
4338
4453
  updateCompletionCache(wsList);
4339
- const memberWorkspaces = wsList.filter((ws2) => ws2.users?.some((u) => u.user.email === email));
4454
+ const memberWorkspaces = wsList.filter((ws) => ws.users?.some((u) => u.user.email === email));
4455
+ let selectedWorkspace;
4340
4456
  if (memberWorkspaces.length === 0) {
4341
- console.log("Creating your first workspace...");
4457
+ if (!json) console.log("Creating your first workspace...");
4342
4458
  const userProjects = await sdk.projects.listProjects(arbi);
4343
4459
  const defaultProjectExtId = userProjects[0]?.external_id;
4344
4460
  if (!defaultProjectExtId) throw new Error("No projects found for user");
4345
4461
  const encryptedKey = await sdk.generateNewWorkspaceKey(arbi, loginResult.serverSessionKey);
4346
- const ws2 = await sdk.workspaces.createWorkspace(
4462
+ const ws = await sdk.workspaces.createWorkspace(
4347
4463
  arbi,
4348
4464
  "My First Workspace",
4349
4465
  encryptedKey,
4350
4466
  defaultProjectExtId
4351
4467
  );
4352
- updateConfig({ selectedWorkspaceId: ws2.external_id });
4353
- success(`Workspace: ${ws2.name} (${ref(ws2.external_id)})`);
4354
- return;
4355
- }
4356
- if (memberWorkspaces.length === 1) {
4468
+ updateConfig({ selectedWorkspaceId: ws.external_id });
4469
+ selectedWorkspace = { external_id: ws.external_id, name: ws.name };
4470
+ if (!json) success(`Workspace: ${ws.name} (${ref(ws.external_id)})`);
4471
+ } else if (memberWorkspaces.length === 1) {
4357
4472
  updateConfig({ selectedWorkspaceId: memberWorkspaces[0].external_id });
4358
- success(`Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`);
4359
- return;
4473
+ selectedWorkspace = {
4474
+ external_id: memberWorkspaces[0].external_id,
4475
+ name: memberWorkspaces[0].name
4476
+ };
4477
+ if (!json)
4478
+ success(`Workspace: ${memberWorkspaces[0].name} (${ref(memberWorkspaces[0].external_id)})`);
4479
+ } else if (json) {
4480
+ } else {
4481
+ const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
4482
+ const selected = await promptSelect("Select workspace", choices);
4483
+ updateConfig({ selectedWorkspaceId: selected });
4484
+ const ws = memberWorkspaces.find((w) => w.external_id === selected);
4485
+ selectedWorkspace = { external_id: selected, name: ws.name };
4486
+ success(`Workspace: ${ws.name} (${ref(selected)})`);
4487
+ }
4488
+ if (json) {
4489
+ console.log(
4490
+ JSON.stringify({
4491
+ ok: true,
4492
+ email,
4493
+ server: config.baseUrl,
4494
+ logged_in: true,
4495
+ workspace: selectedWorkspace ?? null,
4496
+ workspaces_available: memberWorkspaces.length
4497
+ })
4498
+ );
4360
4499
  }
4361
- const choices = sdk.formatWorkspaceChoices(memberWorkspaces);
4362
- const selected = await promptSelect("Select workspace", choices);
4363
- updateConfig({ selectedWorkspaceId: selected });
4364
- const ws = memberWorkspaces.find((w) => w.external_id === selected);
4365
- success(`Workspace: ${ws.name} (${ref(selected)})`);
4366
4500
  } catch (err) {
4367
- error(`Login failed: ${formatCliError(err)}`);
4368
- error("You can log in later with: arbi login");
4501
+ if (json) {
4502
+ console.log(
4503
+ JSON.stringify({ ok: false, stage: "post-register-login", error: formatCliError(err) })
4504
+ );
4505
+ } else {
4506
+ error(`Login failed: ${formatCliError(err)}`);
4507
+ error("You can log in later with: arbi login");
4508
+ }
4369
4509
  }
4370
4510
  }
4371
4511
 
4372
4512
  // src/commands/logout.ts
4373
4513
  function registerLogoutCommand(program2) {
4374
- program2.command("logout").description("Log out of ARBI").action(() => {
4514
+ program2.command("logout").description("Log out of ARBI").option("--json", "Emit a single JSON object on success (scripting)").action((opts) => {
4375
4515
  deleteCredentials();
4376
4516
  clearChatSession();
4377
4517
  updateConfig({ selectedWorkspaceId: void 0 });
4378
- success("Logged out.");
4518
+ if (opts.json) {
4519
+ console.log(JSON.stringify({ ok: true }));
4520
+ } else {
4521
+ success("Logged out.");
4522
+ }
4379
4523
  });
4380
4524
  }
4381
4525
 
@@ -4417,12 +4561,49 @@ function registerStatusCommand(program2) {
4417
4561
  }
4418
4562
  });
4419
4563
  }
4564
+ function resolveWorkspaceSelector(list, selector) {
4565
+ const byId = list.find((w) => w.external_id === selector);
4566
+ if (byId) return { ok: true, id: byId.external_id, ws: byId };
4567
+ const lower = selector.toLowerCase();
4568
+ const byName = list.filter((w) => (w.name ?? "").toLowerCase() === lower);
4569
+ if (byName.length === 1) return { ok: true, id: byName[0].external_id, ws: byName[0] };
4570
+ if (byName.length > 1) return { ok: false, reason: "ambiguous", matches: byName };
4571
+ return { ok: false, reason: "not-found" };
4572
+ }
4573
+ function printResolveError(list, selector, res) {
4574
+ if (res.ok) return;
4575
+ if (res.reason === "ambiguous") {
4576
+ error(`Workspace name "${selector}" is ambiguous \u2014 multiple matches:`);
4577
+ for (const w of res.matches ?? []) error(` ${w.external_id} ${w.name}`);
4578
+ error("Use the workspace ID instead.");
4579
+ return;
4580
+ }
4581
+ error(`Workspace ${selector} not found.`);
4582
+ error("Available workspaces:");
4583
+ for (const w of list) error(` ${w.external_id} ${w.name}`);
4584
+ }
4420
4585
  function registerWorkspacesCommand(program2) {
4421
- program2.command("workspaces").description("List workspaces").action(
4422
- runAction(async () => {
4586
+ program2.command("workspaces").description("List workspaces").option("--json", "Output as JSON").option("--ids", "Output only workspace IDs (one per line)").action(
4587
+ (opts) => runAction(async () => {
4423
4588
  const { arbi } = await resolveAuth();
4424
4589
  const data = await sdk.workspaces.listWorkspaces(arbi);
4425
4590
  updateCompletionCache(data);
4591
+ if (opts.ids) {
4592
+ for (const w of data) console.log(w.external_id);
4593
+ return;
4594
+ }
4595
+ if (opts.json) {
4596
+ const selectedId = getConfig()?.selectedWorkspaceId ?? null;
4597
+ const out = data.map((w) => ({
4598
+ id: w.external_id,
4599
+ name: w.name,
4600
+ docs: w.shared_document_count + w.private_document_count,
4601
+ role: w.users?.[0]?.role ?? null,
4602
+ is_selected: w.external_id === selectedId
4603
+ }));
4604
+ console.log(JSON.stringify(out, null, 2));
4605
+ return;
4606
+ }
4426
4607
  if (data.length === 0) {
4427
4608
  console.log("No workspaces found.");
4428
4609
  return;
@@ -4444,11 +4625,51 @@ function registerWorkspacesCommand(program2) {
4444
4625
  ],
4445
4626
  data
4446
4627
  );
4447
- })
4628
+ })()
4448
4629
  );
4449
4630
  const workspace = program2.command("workspace").description("Workspace management");
4450
- workspace.command("select [id]").description("Select active workspace (interactive picker if no ID given)").action(
4451
- (id) => runAction(async () => {
4631
+ workspace.command("current").description("Print the currently selected workspace ID (empty string if none)").option("--json", "Output full details as JSON").action(
4632
+ (opts) => runAction(async () => {
4633
+ const selectedId = getConfig()?.selectedWorkspaceId ?? "";
4634
+ if (!opts.json) {
4635
+ console.log(selectedId);
4636
+ return;
4637
+ }
4638
+ if (!selectedId) {
4639
+ console.log(JSON.stringify({ id: null, name: null, docs: 0, role: null }, null, 2));
4640
+ return;
4641
+ }
4642
+ const { arbi } = await resolveAuth();
4643
+ const data = await sdk.workspaces.listWorkspaces(arbi);
4644
+ const ws = data.find((w) => w.external_id === selectedId);
4645
+ if (!ws) {
4646
+ console.log(
4647
+ JSON.stringify(
4648
+ { id: selectedId, name: null, docs: 0, role: null, stale: true },
4649
+ null,
4650
+ 2
4651
+ )
4652
+ );
4653
+ return;
4654
+ }
4655
+ console.log(
4656
+ JSON.stringify(
4657
+ {
4658
+ id: ws.external_id,
4659
+ name: ws.name,
4660
+ docs: ws.shared_document_count + ws.private_document_count,
4661
+ role: ws.users?.[0]?.role ?? null
4662
+ },
4663
+ null,
4664
+ 2
4665
+ )
4666
+ );
4667
+ })()
4668
+ );
4669
+ workspace.command("select [id-or-name]").description(
4670
+ "Select active workspace (accepts ID or unique name; interactive picker if nothing given)"
4671
+ ).action(
4672
+ (idOrName) => runAction(async () => {
4452
4673
  const { arbi } = await resolveAuth();
4453
4674
  const data = await sdk.workspaces.listWorkspaces(arbi);
4454
4675
  updateCompletionCache(data);
@@ -4457,15 +4678,13 @@ function registerWorkspacesCommand(program2) {
4457
4678
  return;
4458
4679
  }
4459
4680
  let selectedId;
4460
- if (id) {
4461
- const ws2 = data.find((w) => w.external_id === id);
4462
- if (!ws2) {
4463
- error(`Workspace ${id} not found.`);
4464
- error("Available workspaces:");
4465
- for (const w of data) error(` ${w.external_id} ${w.name}`);
4681
+ if (idOrName) {
4682
+ const res = resolveWorkspaceSelector(data, idOrName);
4683
+ if (!res.ok) {
4684
+ printResolveError(data, idOrName, res);
4466
4685
  process.exit(1);
4467
4686
  }
4468
- selectedId = id;
4687
+ selectedId = res.id;
4469
4688
  } else {
4470
4689
  const choices = sdk.formatWorkspaceChoices(data);
4471
4690
  selectedId = await promptSelect("Select workspace", choices);
@@ -4476,7 +4695,7 @@ function registerWorkspacesCommand(program2) {
4476
4695
  success(`Selected: ${ws.name} (${ref(selectedId)})`);
4477
4696
  })()
4478
4697
  );
4479
- workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).action(
4698
+ workspace.command("create <name>").description("Create a new workspace").option("-d, --description <text>", "Workspace description").option("--public", "Make workspace public", false).option("--select", "Set the new workspace as the active selection", false).option("--json", "Output the new workspace as JSON").action(
4480
4699
  (name, opts) => runAction(async () => {
4481
4700
  const { arbi, loginResult } = await resolveAuth();
4482
4701
  const userProjects = await sdk.projects.listProjects(arbi);
@@ -4491,37 +4710,114 @@ function registerWorkspacesCommand(program2) {
4491
4710
  opts.description,
4492
4711
  opts.public ?? false
4493
4712
  );
4713
+ if (opts.select) {
4714
+ updateConfig({ selectedWorkspaceId: data.external_id });
4715
+ clearChatSession();
4716
+ }
4717
+ if (opts.json) {
4718
+ console.log(
4719
+ JSON.stringify(
4720
+ {
4721
+ id: data.external_id,
4722
+ name: data.name,
4723
+ selected: opts.select ?? false
4724
+ },
4725
+ null,
4726
+ 2
4727
+ )
4728
+ );
4729
+ return;
4730
+ }
4494
4731
  success(`Created: ${data.name} (${ref(data.external_id)})`);
4732
+ if (opts.select) success(`Selected: ${data.name} (${ref(data.external_id)})`);
4495
4733
  })()
4496
4734
  );
4497
- workspace.command("delete [id]").description("Delete a workspace (defaults to selected workspace)").action(
4498
- (id) => runAction(async () => {
4735
+ workspace.command("delete [id-or-name]").description("Delete a workspace (defaults to selected workspace)").option("-y, --yes", "Skip confirmation prompt (required in non-interactive shells)", false).option("--json", "Output the result as JSON").action(
4736
+ (idOrName, opts) => runAction(async () => {
4499
4737
  const { arbi } = await resolveAuth();
4500
- const targetId = id ?? getConfig()?.selectedWorkspaceId;
4501
- if (!targetId) {
4502
- error("No workspace ID given and no workspace selected. Run: arbi workspace select");
4503
- process.exit(1);
4738
+ const config = getConfig();
4739
+ let targetId;
4740
+ let targetName;
4741
+ if (idOrName) {
4742
+ const data = await sdk.workspaces.listWorkspaces(arbi);
4743
+ const res = resolveWorkspaceSelector(data, idOrName);
4744
+ if (!res.ok) {
4745
+ printResolveError(data, idOrName, res);
4746
+ process.exit(1);
4747
+ }
4748
+ targetId = res.id;
4749
+ targetName = res.ws.name;
4750
+ } else {
4751
+ targetId = config?.selectedWorkspaceId;
4752
+ if (!targetId) {
4753
+ error("No workspace ID given and no workspace selected. Run: arbi workspace select");
4754
+ process.exit(1);
4755
+ }
4756
+ }
4757
+ const isInteractive = process.stdin.isTTY === true && process.stdout.isTTY === true;
4758
+ if (!opts.yes) {
4759
+ if (!isInteractive) {
4760
+ error(
4761
+ `Refusing to delete ${targetId} without confirmation. Re-run with --yes (non-interactive shell).`
4762
+ );
4763
+ process.exit(1);
4764
+ }
4765
+ const label2 = targetName ? `"${targetName}" (${targetId})` : targetId;
4766
+ const confirmed = await promptConfirm(
4767
+ `Delete workspace ${label2}? This removes all documents, conversations, and tags in it.`,
4768
+ false
4769
+ );
4770
+ if (!confirmed) {
4771
+ console.log("Cancelled.");
4772
+ return;
4773
+ }
4504
4774
  }
4505
4775
  await sdk.workspaces.deleteWorkspaces(arbi, [targetId]);
4776
+ if (config?.selectedWorkspaceId === targetId) {
4777
+ updateConfig({ selectedWorkspaceId: void 0 });
4778
+ }
4506
4779
  const session = getChatSession();
4507
4780
  if (session.workspaceId === targetId) {
4508
4781
  clearChatSession();
4509
4782
  }
4783
+ if (opts.json) {
4784
+ console.log(JSON.stringify({ id: targetId, deleted: true }, null, 2));
4785
+ return;
4786
+ }
4510
4787
  success(`Deleted workspace ${targetId}`);
4511
4788
  })()
4512
4789
  );
4513
- workspace.command("update <json>").description("Update workspace properties (pass JSON)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4790
+ workspace.command("update <json>").description("Update workspace properties (pass JSON)").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output the updated workspace as JSON").action(
4514
4791
  (json, opts) => runAction(async () => {
4515
4792
  const body = parseJsonArg(json, `arbi workspace update '{"name": "New Name"}'`);
4516
4793
  const { arbi } = await resolveWorkspace(opts.workspace);
4517
4794
  const data = await sdk.workspaces.updateWorkspace(arbi, body);
4795
+ if (opts.json) {
4796
+ console.log(JSON.stringify({ id: data.external_id, name: data.name }, null, 2));
4797
+ return;
4798
+ }
4518
4799
  success(`Updated: ${data.name} (${ref(data.external_id)})`);
4519
4800
  })()
4520
4801
  );
4521
- workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").action(
4802
+ workspace.command("users").description("List users in the active workspace").option("-w, --workspace <id>", "Workspace ID (defaults to selected workspace)").option("--json", "Output as JSON").action(
4522
4803
  (opts) => runAction(async () => {
4523
4804
  const { arbi } = await resolveWorkspace(opts.workspace);
4524
4805
  const data = await sdk.workspaces.listWorkspaceUsers(arbi);
4806
+ if (opts.json) {
4807
+ const out = data.map((r) => {
4808
+ const u = r.user ?? {};
4809
+ return {
4810
+ user_id: u.external_id ?? null,
4811
+ email: u.email ?? null,
4812
+ name: [u.given_name, u.family_name].filter(Boolean).join(" ") || null,
4813
+ role: r.role,
4814
+ document_count: r.document_count,
4815
+ conversation_count: r.conversation_count
4816
+ };
4817
+ });
4818
+ console.log(JSON.stringify(out, null, 2));
4819
+ return;
4820
+ }
4525
4821
  if (data.length === 0) {
4526
4822
  console.log("No users found.");
4527
4823
  return;
@@ -4598,7 +4894,18 @@ function registerWorkspacesCommand(program2) {
4598
4894
  signingPrivateKeyBase64
4599
4895
  );
4600
4896
  const data = await sdk.workspaces.copyDocuments(arbi, targetId, docIds, targetKey);
4601
- success(`${data.detail} (${data.documents_copied} document(s) copied)`);
4897
+ const copied = data.documents_copied ?? 0;
4898
+ const requested = docIds.length;
4899
+ if (copied === 0) {
4900
+ error(`Copied 0 of ${requested} document(s). ${data.detail ?? ""}`.trim());
4901
+ error("Hint: documents must be fully indexed before they can be copied.");
4902
+ process.exit(1);
4903
+ }
4904
+ if (copied < requested) {
4905
+ error(`Copied ${copied} of ${requested} document(s). ${data.detail ?? ""}`.trim());
4906
+ return;
4907
+ }
4908
+ success(`Copied ${copied} document(s) to ${targetId}`);
4602
4909
  })()
4603
4910
  );
4604
4911
  }
@@ -8299,7 +8606,7 @@ console.info = (...args) => {
8299
8606
  _origInfo(...args);
8300
8607
  };
8301
8608
  var program = new commander.Command();
8302
- program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.41");
8609
+ program.name("arbi").description("ARBI CLI \u2014 interact with ARBI from the terminal").version("0.3.43");
8303
8610
  registerConfigCommand(program);
8304
8611
  registerLoginCommand(program);
8305
8612
  registerRegisterCommand(program);