@erdoai/cli 0.151.0 → 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.
- package/dist/index.js +54 -14
- 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
|
-
|
|
3335
|
-
|
|
3336
|
-
);
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
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);
|