@computesdk/cli 2.0.0 → 2.1.0

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,21 @@ 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
+
37
47
  ## Migrating from @computesdk/cli 1.x
38
48
 
39
49
  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) => {
@@ -2199,6 +2199,9 @@ function registerSandboxesCommands(program2) {
2199
2199
  fail(e, opts);
2200
2200
  }
2201
2201
  });
2202
+ for (const sub of cmd.commands) {
2203
+ sub.option("--org <slug>", "organization slug for this command (or $COMPUTE_ORG)");
2204
+ }
2202
2205
  }
2203
2206
  async function followProcess(c, sandboxId, jobId) {
2204
2207
  let exitCode = 0;
@@ -2234,17 +2237,41 @@ var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includ
2234
2237
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
2235
2238
  var program = new Command();
2236
2239
  program.name("compute").description("ComputeSDK CLI").version(VERSION).enablePositionalOptions().passThroughOptions();
2240
+ function envBaseUrl() {
2241
+ return process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL;
2242
+ }
2243
+ async function benchAuth(opts = {}) {
2244
+ const { resolveAuth } = await import("@benchsdk/cli");
2245
+ return resolveAuth({ baseUrl: opts.baseUrl ?? envBaseUrl() });
2246
+ }
2247
+ async function printSessionLine(options) {
2248
+ try {
2249
+ const { getMe } = await import("@benchsdk/cli");
2250
+ const me = await getMe(await benchAuth({ baseUrl: options?.baseUrl }));
2251
+ const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
2252
+ const who = me.user.email ?? me.user.name ?? me.user.id;
2253
+ if (active) {
2254
+ p.log.success(
2255
+ `Logged in as ${who} \u2014 active org: ${active.slug}. Change with \`compute org use <slug>\`.`
2256
+ );
2257
+ } else {
2258
+ p.log.success(`Logged in as ${who} \u2014 no active org`);
2259
+ }
2260
+ } catch {
2261
+ p.log.warn("Logged in, but could not fetch session info.");
2262
+ }
2263
+ }
2237
2264
  async function runLogin(options) {
2238
2265
  const { oauthLogin } = await import("@benchsdk/cli");
2239
2266
  await oauthLogin({
2240
- baseUrl: options?.baseUrl ?? process.env.COMPUTE_PLATFORM_URL ?? process.env.BENCHMARKS_PLATFORM_URL
2267
+ baseUrl: options?.baseUrl ?? envBaseUrl()
2241
2268
  });
2242
2269
  }
2243
2270
  async function runLogout() {
2244
2271
  const { clearCredentials } = await import("@benchsdk/cli");
2245
2272
  await clearCredentials();
2246
2273
  }
2247
- program.option("--login", "force re-authentication").option("--logout", "clear stored credentials").action(async (opts) => {
2274
+ 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
2275
  if (opts.logout) {
2249
2276
  console.log();
2250
2277
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
@@ -2257,11 +2284,16 @@ program.option("--login", "force re-authentication").option("--logout", "clear s
2257
2284
  console.log();
2258
2285
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
2259
2286
  await runLogin();
2287
+ await printSessionLine();
2260
2288
  p.outro(pc4.green("Authenticated!"));
2261
2289
  process.exit(0);
2262
2290
  }
2263
2291
  program.help();
2264
2292
  });
2293
+ program.hook("preAction", (thisCommand) => {
2294
+ const org2 = thisCommand.opts().org;
2295
+ if (org2) process.env.COMPUTE_ORG = org2;
2296
+ });
2265
2297
  program.command("providers").description("List all providers and their configuration status").action(async () => {
2266
2298
  console.log();
2267
2299
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
@@ -2279,6 +2311,7 @@ program.command("login").description("Authenticate with ComputeSDK (OAuth device
2279
2311
  console.log();
2280
2312
  p.intro(pc4.cyan(`@computesdk/cli v${VERSION}`));
2281
2313
  await runLogin({ baseUrl: opts.baseUrl });
2314
+ await printSessionLine({ baseUrl: opts.baseUrl });
2282
2315
  p.outro(pc4.green("Authenticated!"));
2283
2316
  });
2284
2317
  program.command("logout").description("Clear stored credentials").action(async () => {
@@ -2288,6 +2321,57 @@ program.command("logout").description("Clear stored credentials").action(async (
2288
2321
  p.log.success("Logged out. Stored credentials removed.");
2289
2322
  p.outro(pc4.green("Done!"));
2290
2323
  });
2324
+ async function printWhoami() {
2325
+ const { getMe } = await import("@benchsdk/cli");
2326
+ const me = await getMe(await benchAuth());
2327
+ const active = me.organizations.find((o) => o.id === me.activeOrganizationId);
2328
+ console.log(me.user.email ?? me.user.name ?? me.user.id);
2329
+ console.log(
2330
+ `active org: ${active ? `${active.slug} (${active.id})` : "\u2014"}${me.organizations.length > 1 ? " \u2014 change with `compute org use <slug>`" : ""}`
2331
+ );
2332
+ }
2333
+ var org = program.command("org").description("Manage the active organization");
2334
+ org.command("list").description("List your organizations").option("--json", "print machine-readable JSON").action(async (opts) => {
2335
+ const { listOrganizations, getMe } = await import("@benchsdk/cli");
2336
+ const auth = await benchAuth();
2337
+ const [organizations, me] = await Promise.all([listOrganizations(auth), getMe(auth)]);
2338
+ if (opts.json) {
2339
+ console.log(JSON.stringify(organizations, null, 2));
2340
+ return;
2341
+ }
2342
+ for (const o of organizations) {
2343
+ const marker = o.id === me.activeOrganizationId ? pc4.green("*") : " ";
2344
+ console.log(`${marker} ${pc4.cyan(o.slug)} ${o.name ?? ""} ${pc4.dim(o.id)}`);
2345
+ }
2346
+ });
2347
+ org.command("use <slug>").description("Set the active organization (persisted to the stored login)").action(async (slug) => {
2348
+ const { loadCredentials, saveCredentials, createApiClient, setActiveOrganization } = await import("@benchsdk/cli");
2349
+ const credentials = await loadCredentials() ?? {};
2350
+ const { auth } = await createApiClient({ baseUrl: envBaseUrl() });
2351
+ const result = await setActiveOrganization(auth, slug);
2352
+ if (!result.organization) {
2353
+ throw new Error(`Could not set active organization to ${slug}`);
2354
+ }
2355
+ await saveCredentials({
2356
+ ...credentials,
2357
+ baseUrl: auth.baseUrl,
2358
+ // An API-key login must not be rewritten as an OAuth one — keep its
2359
+ // key and kind, and only stamp token fields when this auth actually
2360
+ // is a token session.
2361
+ ...auth.token ? {
2362
+ token: auth.token,
2363
+ refreshToken: auth.refreshToken,
2364
+ tokenExpiresAt: auth.tokenExpiresAt,
2365
+ refreshExpiresAt: auth.refreshExpiresAt,
2366
+ kind: "oauth"
2367
+ } : {},
2368
+ orgSlug: result.organization.slug,
2369
+ orgId: result.organization.id
2370
+ });
2371
+ console.log(`Active organization set to ${result.organization.slug} (${result.organization.id})`);
2372
+ });
2373
+ org.command("current").description("Show the current user and active organization").action(printWhoami);
2374
+ program.command("whoami").description("Show the current user and active organization").action(printWhoami);
2291
2375
  registerActionsCommands(program);
2292
2376
  registerMarketCommands(program);
2293
2377
  registerSandboxesCommands(program);