@computesdk/cli 2.0.0 → 2.1.1

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/README.md CHANGED
@@ -29,11 +29,32 @@ compute market … # sell side of the compute market
29
29
  compute sandboxes … # platform sandbox control plane
30
30
  compute bench … # benchmarks CLI (run, auth, org, results, …)
31
31
  compute providers # sandbox provider credential status
32
+ compute org list # your organizations (* = active)
33
+ compute org use <slug> # switch the stored login's active org
34
+ compute org current / whoami # current user + active org
32
35
  ```
33
36
 
34
37
  All platform commands accept `--api-key` / `COMPUTE_API_KEY` and fall back to
35
38
  the stored OAuth credentials.
36
39
 
40
+ The active org comes from the stored login (`compute org use` changes it —
41
+ it's per user, so every CLI on every machine sees the switch). For a one-off
42
+ override, pass `--org <slug>` (on the root command or any `sandboxes` /
43
+ `actions` / `market` subcommand) or set `COMPUTE_ORG`; it sends `X-Org-Slug`
44
+ on the request. Precedence: `--org` > `COMPUTE_ORG` > the stored login's org.
45
+ The platform ignores the header for org API keys.
46
+
47
+ For `compute sandboxes exec` and `compute sandboxes spawn`, everything after
48
+ the command's first word goes to the sandbox untouched — flags like `uname
49
+ -a` or `node -e` are not CLI options. Pass CLI options (`--json`,
50
+ `--timeout-ms`, `--cwd`, `-e/--env`, `--stdin`, `--org`) before the command;
51
+ a bare `--` before the command is optional but supported.
52
+
53
+ ```bash
54
+ compute sandboxes exec sb1 uname -a
55
+ compute sandboxes spawn sb1 --cwd /app -e NODE_ENV=production -- npm run dev
56
+ ```
57
+
37
58
  ## Migrating from @computesdk/cli 1.x
38
59
 
39
60
  v2 removes the legacy console authentication and its commands:
package/dist/index.js CHANGED
@@ -54,12 +54,6 @@ function isProviderAuthComplete(provider) {
54
54
  }
55
55
  function getProviderStatus() {
56
56
  const statuses = [];
57
- const hasGatewayKey = !!process.env.COMPUTESDK_API_KEY;
58
- statuses.push({
59
- name: "computesdk",
60
- ready: hasGatewayKey,
61
- missing: hasGatewayKey ? [] : ["COMPUTESDK_API_KEY"]
62
- });
63
57
  for (const provider of PROVIDER_NAMES) {
64
58
  statuses.push({
65
59
  name: provider,
@@ -262,7 +256,12 @@ var ActionsClient = class {
262
256
  return new Uint8Array(await res.arrayBuffer());
263
257
  }
264
258
  headers() {
265
- return { authorization: `Bearer ${this.auth.apiKey}`, accept: "application/json" };
259
+ const headers = {
260
+ authorization: `Bearer ${this.auth.apiKey}`,
261
+ accept: "application/json"
262
+ };
263
+ if (this.auth.orgSlug) headers["X-Org-Slug"] = this.auth.orgSlug;
264
+ return headers;
266
265
  }
267
266
  async parse(res) {
268
267
  const body = await res.json().catch(() => void 0);
@@ -336,6 +335,7 @@ var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute
336
335
  async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAuth) {
337
336
  const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
338
337
  DEFAULT_BASE_URL).replace(/\/+$/, "");
338
+ let orgSlug = opts.org || process.env.COMPUTE_ORG || void 0;
339
339
  if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
340
340
  throw new ActionsCliError(
341
341
  "untrusted_host",
@@ -370,12 +370,12 @@ async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAut
370
370
  if (!apiKey) {
371
371
  throw new ActionsCliError("no_credentials", `No API key. ${NO_CREDENTIALS_HINT}`);
372
372
  }
373
- return { apiKey, baseUrl };
373
+ return { apiKey, baseUrl, ...orgSlug && { orgSlug } };
374
374
  }
375
375
  async function resolveStoredPlatformAuth(opts) {
376
376
  const { resolveAuth } = await import("@benchsdk/cli");
377
377
  const auth = await resolveAuth({ baseUrl: opts.baseUrl });
378
- return { apiKey: auth.apiKey, token: auth.token };
378
+ return { apiKey: auth.apiKey, token: auth.token, orgSlug: auth.orgSlug, orgId: auth.orgId };
379
379
  }
380
380
  function isLoopbackHost(host) {
381
381
  return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
@@ -883,7 +883,7 @@ function formatVaultRow(item) {
883
883
  }
884
884
  function registerActionsCommands(program2) {
885
885
  const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
886
- const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
886
+ const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
887
887
  common(
888
888
  actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)").option("--max-bid <usd>", "max price per vCPU for a market fill (e.g. 0.12); unfilled jobs fall through to the next provider").option("--max-bid-per <unit>", "time unit --max-bid is priced in: second, minute, or hour (default: second)")
889
889
  ).action(async (repo, opts) => {
@@ -906,8 +906,8 @@ function registerActionsCommands(program2) {
906
906
  "/api/v1/actions/dispatch",
907
907
  body
908
908
  );
909
- const org = await c.org();
910
- const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
909
+ const org2 = await c.org();
910
+ const url = `${c.baseUrl}/${org2.slug}/actions/runs/${result.runId}`;
911
911
  output(opts, { ...result, url }, (r) => {
912
912
  console.log(`${r.created ? "dispatched" : "already running"} ${pc.cyan(r.runId)}`);
913
913
  console.log(`sha: ${r.headSha}`);
@@ -965,8 +965,8 @@ function registerActionsCommands(program2) {
965
965
  try {
966
966
  const c = await client(opts);
967
967
  const run = await c.get(`/api/v1/actions/runs/${runId}`);
968
- const org = await c.org();
969
- const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
968
+ const org2 = await c.org();
969
+ const url = `${c.baseUrl}/${org2.slug}/actions/runs/${run.id}`;
970
970
  const summary = run.conclusion === "failed" ? await fetchRunSummary(c, runId) : null;
971
971
  output(opts, { ...run, url, summary }, (r) => {
972
972
  console.log(formatRunDetail(r, r.url));
@@ -1301,8 +1301,8 @@ function registerActionsCommands(program2) {
1301
1301
  const result = await c.post(
1302
1302
  `/api/v1/actions/runs/${runId}/rerun`
1303
1303
  );
1304
- const org = await c.org();
1305
- const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
1304
+ const org2 = await c.org();
1305
+ const url = `${c.baseUrl}/${org2.slug}/actions/runs/${result.runId}`;
1306
1306
  output(opts, { ...result, url }, (r) => {
1307
1307
  console.log(`${r.created ? "rerun dispatched" : "rerun already running"} ${pc.cyan(r.runId)}`);
1308
1308
  console.log(`url: ${r.url}`);
@@ -1609,7 +1609,7 @@ function formatSettlementRow(s) {
1609
1609
  }
1610
1610
  function registerMarketCommands(program2) {
1611
1611
  const market = program2.command("market").description("Sell compute capacity on the compute market").configureOutput({ outputError: usageErrorOutput });
1612
- const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
1612
+ const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
1613
1613
  common(
1614
1614
  market.command("listings").alias("list").description("Your listings \u2014 live, paused, expired, and withdrawn")
1615
1615
  ).action(async (opts) => {
@@ -1872,8 +1872,82 @@ function printSnapshot(s) {
1872
1872
  ` provider: ${s.provider}${s.region ? ` (${s.region})` : ""} label: ${s.label ?? "\u2014"} created: ${s.createdAt}${s.expiresAt ? ` expires: ${s.expiresAt}` : ""}`
1873
1873
  );
1874
1874
  }
1875
+ var AUTH_LEAD_OPTS = [
1876
+ { aliases: ["--api-key"], key: "apiKey", flag: "value" },
1877
+ { aliases: ["--base-url"], key: "baseUrl", flag: "value" },
1878
+ { aliases: ["--allow-untrusted-host"], key: "allowUntrustedHost", flag: "bool" },
1879
+ { aliases: ["--org"], key: "org", flag: "value" },
1880
+ { aliases: ["--json"], key: "json", flag: "bool" }
1881
+ ];
1882
+ var EXEC_LEAD_OPTS = [
1883
+ ...AUTH_LEAD_OPTS,
1884
+ { aliases: ["--timeout-ms"], key: "timeoutMs", flag: "value" }
1885
+ ];
1886
+ var SPAWN_LEAD_OPTS = [
1887
+ ...AUTH_LEAD_OPTS,
1888
+ { aliases: ["--cwd"], key: "cwd", flag: "value" },
1889
+ { aliases: ["-e", "--env"], key: "env", flag: "repeat" },
1890
+ { aliases: ["--stdin"], key: "stdin", flag: "bool" }
1891
+ ];
1892
+ function takeLeadingOptions(argv, specs) {
1893
+ var _a;
1894
+ const byAlias = /* @__PURE__ */ new Map();
1895
+ for (const spec of specs) for (const alias of spec.aliases) byAlias.set(alias, spec);
1896
+ const inline = {};
1897
+ let i = 0;
1898
+ for (; i < argv.length; i++) {
1899
+ const token = argv[i];
1900
+ if (token === "--") {
1901
+ i++;
1902
+ break;
1903
+ }
1904
+ if (!token.startsWith("-") || token === "-") break;
1905
+ const eq = token.indexOf("=");
1906
+ const flag = eq === -1 ? token : token.slice(0, eq);
1907
+ let spec = byAlias.get(flag);
1908
+ let attached;
1909
+ if (!spec && !flag.startsWith("--") && flag.length > 2) {
1910
+ const shortSpec = byAlias.get(flag.slice(0, 2));
1911
+ if (shortSpec && shortSpec.flag !== "bool") {
1912
+ spec = shortSpec;
1913
+ attached = flag.slice(2);
1914
+ if (attached.startsWith("=")) attached = attached.slice(1);
1915
+ if (eq !== -1) attached += token.slice(eq);
1916
+ }
1917
+ }
1918
+ if (!spec) {
1919
+ throw new Error(
1920
+ `unknown option '${flag}' \u2014 pass CLI options before the command, or use -- to separate them`
1921
+ );
1922
+ }
1923
+ if (spec.flag === "bool") {
1924
+ if (eq !== -1) throw new Error(`option '${flag}' takes no value`);
1925
+ inline[spec.key] = true;
1926
+ } else {
1927
+ const value = attached ?? (eq !== -1 ? token.slice(eq + 1) : argv[++i]);
1928
+ if (value === void 0) throw new Error(`option '${flag}' requires a value`);
1929
+ if (spec.flag === "repeat") {
1930
+ (inline[_a = spec.key] ?? (inline[_a] = [])).push(value);
1931
+ } else {
1932
+ inline[spec.key] = value;
1933
+ }
1934
+ }
1935
+ }
1936
+ return { command: argv.slice(i), inline };
1937
+ }
1938
+ function mergeLeadOpts(opts, inline) {
1939
+ const merged = { ...opts };
1940
+ for (const [key, value] of Object.entries(inline)) {
1941
+ if (Array.isArray(value)) {
1942
+ merged[key] = [...merged[key] ?? [], ...value];
1943
+ } else {
1944
+ merged[key] = value;
1945
+ }
1946
+ }
1947
+ return merged;
1948
+ }
1875
1949
  function registerSandboxesCommands(program2) {
1876
- const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
1950
+ const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").enablePositionalOptions().configureOutput({ outputError: usageErrorOutput });
1877
1951
  cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
1878
1952
  try {
1879
1953
  const c = await client(opts);
@@ -1928,8 +2002,13 @@ function registerSandboxesCommands(program2) {
1928
2002
  fail(e, opts);
1929
2003
  }
1930
2004
  });
1931
- cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2005
+ cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result (options before the command; -- also works)").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").passThroughOptions().action(async (sandboxId, rawCommand, rawOpts) => {
2006
+ let opts = rawOpts;
1932
2007
  try {
2008
+ const lead = takeLeadingOptions(rawCommand, EXEC_LEAD_OPTS);
2009
+ opts = mergeLeadOpts(rawOpts, lead.inline);
2010
+ const command = lead.command;
2011
+ if (command.length === 0) throw new Error("missing command to run");
1933
2012
  const c = await client(opts);
1934
2013
  const res = await c.post(
1935
2014
  `/api/v1/sandboxes/${sandboxId}/commands`,
@@ -1948,8 +2027,13 @@ function registerSandboxesCommands(program2) {
1948
2027
  fail(e, opts);
1949
2028
  }
1950
2029
  });
1951
- cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--stdin", "keep the process's stdin pipe open for `stdin`/`close-stdin`").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2030
+ cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request (options before the command; -- also works)").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--stdin", "keep the process's stdin pipe open for `stdin`/`close-stdin`").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").passThroughOptions().action(async (sandboxId, rawCommand, rawOpts) => {
2031
+ let opts = rawOpts;
1952
2032
  try {
2033
+ const lead = takeLeadingOptions(rawCommand, SPAWN_LEAD_OPTS);
2034
+ opts = mergeLeadOpts(rawOpts, lead.inline);
2035
+ const command = lead.command;
2036
+ if (command.length === 0) throw new Error("missing command to spawn");
1953
2037
  const c = await client(opts);
1954
2038
  const res = await c.post(
1955
2039
  `/api/v1/sandboxes/${sandboxId}/processes`,
@@ -2199,6 +2283,9 @@ function registerSandboxesCommands(program2) {
2199
2283
  fail(e, opts);
2200
2284
  }
2201
2285
  });
2286
+ for (const sub of cmd.commands) {
2287
+ sub.option("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)");
2288
+ }
2202
2289
  }
2203
2290
  async function followProcess(c, sandboxId, jobId) {
2204
2291
  let exitCode = 0;
@@ -2234,17 +2321,41 @@ var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includ
2234
2321
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
2235
2322
  var program = new Command();
2236
2323
  program.name("compute").description("ComputeSDK CLI").version(VERSION).enablePositionalOptions().passThroughOptions();
2324
+ function envBaseUrl() {
2325
+ return process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL;
2326
+ }
2327
+ async function benchAuth(opts = {}) {
2328
+ const { resolveAuth } = await import("@benchsdk/cli");
2329
+ return resolveAuth({ baseUrl: opts.baseUrl ?? envBaseUrl() });
2330
+ }
2331
+ async function printSessionLine(options) {
2332
+ try {
2333
+ const { getMe } = await import("@benchsdk/cli");
2334
+ const me = await getMe(await benchAuth({ baseUrl: options?.baseUrl }));
2335
+ const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
2336
+ const who = me.user.email ?? me.user.name ?? me.user.id;
2337
+ if (active) {
2338
+ p.log.success(
2339
+ `Logged in as ${who} \u2014 active org: ${active.slug}. Change with \`compute org use <slug>\`.`
2340
+ );
2341
+ } else {
2342
+ p.log.success(`Logged in as ${who} \u2014 no active org`);
2343
+ }
2344
+ } catch {
2345
+ p.log.warn("Logged in, but could not fetch session info.");
2346
+ }
2347
+ }
2237
2348
  async function runLogin(options) {
2238
2349
  const { oauthLogin } = await import("@benchsdk/cli");
2239
2350
  await oauthLogin({
2240
- baseUrl: options?.baseUrl ?? process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL
2351
+ baseUrl: options?.baseUrl ?? envBaseUrl()
2241
2352
  });
2242
2353
  }
2243
2354
  async function runLogout() {
2244
2355
  const { clearCredentials } = await import("@benchsdk/cli");
2245
2356
  await clearCredentials();
2246
2357
  }
2247
- program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
2358
+ program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").option("--org <slug>", "organization slug override for this invocation (or $COMPUTE_ORG)").action(async (opts) => {
2248
2359
  if (opts.logout) {
2249
2360
  console.log();
2250
2361
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
@@ -2257,11 +2368,16 @@ program.option("--login", "force re-authentication").option("--logout", "clear s
2257
2368
  console.log();
2258
2369
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
2259
2370
  await runLogin();
2371
+ await printSessionLine();
2260
2372
  p.outro(pc4.green("Authenticated!"));
2261
2373
  process.exit(0);
2262
2374
  }
2263
2375
  program.help();
2264
2376
  });
2377
+ program.hook("preAction", (thisCommand) => {
2378
+ const org2 = thisCommand.opts().org;
2379
+ if (org2) process.env.COMPUTE_ORG = org2;
2380
+ });
2265
2381
  program.command("providers").description("List all providers and their configuration status").action(async () => {
2266
2382
  console.log();
2267
2383
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
@@ -2279,6 +2395,7 @@ program.command("login").description("Authenticate with ComputeSDK (OAuth device
2279
2395
  console.log();
2280
2396
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
2281
2397
  await runLogin({ baseUrl: opts.baseUrl });
2398
+ await printSessionLine({ baseUrl: opts.baseUrl });
2282
2399
  p.outro(pc4.green("Authenticated!"));
2283
2400
  });
2284
2401
  program.command("logout").description("Clear stored credentials").action(async () => {
@@ -2288,6 +2405,57 @@ program.command("logout").description("Clear stored credentials").action(async (
2288
2405
  p.log.success("Logged out. Stored credentials removed.");
2289
2406
  p.outro(pc4.green("Done!"));
2290
2407
  });
2408
+ async function printWhoami() {
2409
+ const { getMe } = await import("@benchsdk/cli");
2410
+ const me = await getMe(await benchAuth());
2411
+ const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
2412
+ console.log(me.user.email ?? me.user.name ?? me.user.id);
2413
+ console.log(
2414
+ `active org: ${active ? `${active.slug} (${active.id})` : "\u2014"}${me.organizations.length > 1 ? " \u2014 change with `compute org use <slug>`" : ""}`
2415
+ );
2416
+ }
2417
+ var org = program.command("org").description("Manage the active organization");
2418
+ org.command("list").description("List your organizations").option("--json", "print machine-readable JSON").action(async (opts) => {
2419
+ const { listOrganizations, getMe } = await import("@benchsdk/cli");
2420
+ const auth = await benchAuth();
2421
+ const [organizations, me] = await Promise.all([listOrganizations(auth), getMe(auth)]);
2422
+ if (opts.json) {
2423
+ console.log(JSON.stringify(organizations, null, 2));
2424
+ return;
2425
+ }
2426
+ for (const o of organizations) {
2427
+ const marker = o.id === me.activeOrganizationId ? pc4.green("*") : " ";
2428
+ console.log(`${marker} ${pc4.cyan(o.slug)} ${o.name ?? ""} ${pc4.dim(o.id)}`);
2429
+ }
2430
+ });
2431
+ org.command("use <slug>").description("Set the active organization (persisted to the stored login)").action(async (slug) => {
2432
+ const { loadCredentials, saveCredentials, createApiClient, setActiveOrganization } = await import("@benchsdk/cli");
2433
+ const credentials = await loadCredentials() ?? {};
2434
+ const { auth } = await createApiClient({ baseUrl: envBaseUrl() });
2435
+ const result = await setActiveOrganization(auth, slug);
2436
+ if (!result.organization) {
2437
+ throw new Error(`Could not set active organization to ${slug}`);
2438
+ }
2439
+ await saveCredentials({
2440
+ ...credentials,
2441
+ baseUrl: auth.baseUrl,
2442
+ // An API-key login must not be rewritten as an OAuth one — keep its
2443
+ // key and kind, and only stamp token fields when this auth actually
2444
+ // is a token session.
2445
+ ...auth.token ? {
2446
+ token: auth.token,
2447
+ refreshToken: auth.refreshToken,
2448
+ tokenExpiresAt: auth.tokenExpiresAt,
2449
+ refreshExpiresAt: auth.refreshExpiresAt,
2450
+ kind: "oauth"
2451
+ } : {},
2452
+ orgSlug: result.organization.slug,
2453
+ orgId: result.organization.id
2454
+ });
2455
+ console.log(`Active organization set to ${result.organization.slug} (${result.organization.id})`);
2456
+ });
2457
+ org.command("current").description("Show the current user and active organization").action(printWhoami);
2458
+ program.command("whoami").description("Show the current user and active organization").action(printWhoami);
2291
2459
  registerActionsCommands(program);
2292
2460
  registerMarketCommands(program);
2293
2461
  registerSandboxesCommands(program);