@erdoai/cli 0.151.1 → 0.153.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.
Files changed (2) hide show
  1. package/dist/index.js +54 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -331,6 +331,9 @@ var ErdoClient = class {
331
331
  listTokens() {
332
332
  return this.request("GET", "/v1/tokens");
333
333
  }
334
+ rotateToken(id) {
335
+ return this.request("POST", `/v1/tokens/${encodeURIComponent(id)}/rotate`, void 0, false);
336
+ }
334
337
  revokeToken(id) {
335
338
  return this.request("DELETE", `/v1/tokens/${encodeURIComponent(id)}`);
336
339
  }
@@ -3321,6 +3324,25 @@ ${res.from} to ${res.to} (${res.timezone} days)
3321
3324
  }
3322
3325
  );
3323
3326
  var tokenCmd = program.command("token").description("Manage your API tokens (account-level credentials)");
3327
+ function printNewToken(res, replacedTokenID) {
3328
+ process.stderr.write("Store this token now \u2014 it is shown only once and cannot be retrieved again.\n");
3329
+ console.log(res.token);
3330
+ const scoped = res.is_scoped === true || res.capabilities !== void 0;
3331
+ const scopeDetails = scoped ? `capabilities: ${res.capabilities?.join(", ") || "(none)"}
3332
+ resource slugs: ${res.resource_slugs?.join(", ") || "(none)"}
3333
+ ` : "";
3334
+ const scopeDescription = scoped ? "This token is pinned to the default org above and can use only its listed capabilities; resource restrictions apply when slugs are listed.\n" : res.access === "read" ? "This token can read as you in any org you belong to and cannot change anything; pass --org on a command to target another.\n" : "This token acts as you in any org you belong to; pass --org on a command to target another.\n";
3335
+ process.stderr.write(
3336
+ `
3337
+ id: ${res.id}
3338
+ hint: ${res.hint}
3339
+ expires: ${res.expires_at}
3340
+ default org: ${res.default_org ? `${res.default_org.name} (${res.default_org.id})` : "(none)"}
3341
+ access: ${res.access ?? "full"}
3342
+ ` + scopeDetails + (replacedTokenID ? `replaced token id: ${replacedTokenID}
3343
+ ` : "") + scopeDescription
3344
+ );
3345
+ }
3324
3346
  tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).option("--read-only", "mint a read-only token: it can read through the API in any org you belong to, and never write").addOption(
3325
3347
  orgOption("the token's default org (defaults to your active org; must be one you belong to)")
3326
3348
  ).action(async (opts) => {
@@ -3331,19 +3353,37 @@ tokenCmd.command("create").description("Mint a new API token; the secret is prin
3331
3353
  organization_id: opts.org,
3332
3354
  ...opts.readOnly ? { access: "read" } : {}
3333
3355
  });
3334
- process.stderr.write(
3335
- "Store this token now \u2014 it is shown only once and cannot be retrieved again.\n"
3336
- );
3337
- console.log(res.token);
3338
- process.stderr.write(
3339
- `
3340
- id: ${res.id}
3341
- hint: ${res.hint}
3342
- expires: ${res.expires_at}
3343
- default org: ${res.default_org ? `${res.default_org.name} (${res.default_org.id})` : "(none)"}
3344
- access: ${res.access ?? "full"}
3345
- ` + (res.access === "read" ? "This token can read as you in any org you belong to and cannot change anything; pass --org on a command to target another.\n" : "This token acts as you in any org you belong to; pass --org on a command to target another.\n")
3346
- );
3356
+ printNewToken(res);
3357
+ } catch (e) {
3358
+ fail(e);
3359
+ }
3360
+ });
3361
+ tokenCmd.command("rotate <id>").description("Replace an API token; the replacement secret is printed ONCE").option("--update-login", "save the new secret when the rotated token is the active account's credential").action(async (id, opts) => {
3362
+ try {
3363
+ let accountKey;
3364
+ let savedToken;
3365
+ if (opts.updateLogin) {
3366
+ const file = loadConfigFile();
3367
+ accountKey = file ? activeAccountKey(file) : void 0;
3368
+ const saved = accountKey ? file?.accounts[accountKey] : void 0;
3369
+ if (!saved?.token) throw new Error("No active saved login to update. Run: erdo auth login");
3370
+ savedToken = saved.token;
3371
+ const me = await fetchMe(saved.token);
3372
+ if (!me.credential_id || me.credential_id !== id) {
3373
+ throw new Error("--update-login requires the rotated token id to match the active saved login's credential id");
3374
+ }
3375
+ }
3376
+ const res = await new ErdoClient().rotateToken(id);
3377
+ printNewToken(res, res.replaced_token_id);
3378
+ if (accountKey) {
3379
+ const current = loadConfigFile();
3380
+ if (!current || activeAccountKey(current) !== accountKey || current.accounts[accountKey]?.token !== savedToken) {
3381
+ throw new Error("Rotation succeeded and the replacement secret was printed above, but the active saved login changed during the request and was not overwritten. Save the new key with erdo login --key if needed");
3382
+ }
3383
+ saveAccount(accountKey, { token: res.token }, false);
3384
+ process.stderr.write(`Updated saved login for ${accountKey}.
3385
+ `);
3386
+ }
3347
3387
  } catch (e) {
3348
3388
  fail(e);
3349
3389
  }
@@ -3358,7 +3398,7 @@ tokenCmd.command("list").description("List your API tokens (never shows the secr
3358
3398
  for (const t of tokens) {
3359
3399
  const org2 = t.default_org ? t.default_org.name || t.default_org.id : "-";
3360
3400
  const used = t.last_used_at ?? "never";
3361
- console.log(`${t.id} ${t.name || "-"} ${t.hint} access:${t.access ?? "full"} default-org:${org2} expires:${t.expires_at} used:${used}`);
3401
+ console.log(`${t.id} ${t.name || "-"} ${t.hint} access:${t.access ?? "full"} default-org:${org2} expires:${t.expires_at} last-used:${used}`);
3362
3402
  }
3363
3403
  } catch (e) {
3364
3404
  fail(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.151.1",
3
+ "version": "0.153.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {