@youkno/edge-cli 1.21.316 → 1.21.318
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/.pnp.cjs +70 -69
- package/.pnp.loader.mjs +5 -2
- package/.yarnrc.yml +11 -0
- package/README.md +8 -3
- package/dist/commands/token.js +12 -1
- package/dist/lib/auth.js +162 -12
- package/package.json +1 -1
- package/src/commands/token.ts +26 -3
- package/src/lib/auth.ts +206 -13
- package/.yarn/install-state.gz +0 -0
package/.yarnrc.yml
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
# The cache lives inside the repo, not in ~/.yarn/berry/cache.
|
|
2
|
+
#
|
|
3
|
+
# .pnp.cjs is committed and stores package locations as paths RELATIVE TO ITSELF. With a global
|
|
4
|
+
# cache those paths have to climb out of the repo to reach $HOME, so the number of `../` encodes how
|
|
5
|
+
# deep the checkout happens to be on the machine that ran `yarn install`. The committed file carried
|
|
6
|
+
# six levels; a checkout three levels deep resolves them to nothing and every CLI invocation dies
|
|
7
|
+
# with "the locator that owns .../commander/ can't be found inside the dependency tree".
|
|
8
|
+
#
|
|
9
|
+
# A repo-local cache makes those paths `./.yarn/cache/...`, which means the same for everyone.
|
|
10
|
+
enableGlobalCache: false
|
|
11
|
+
|
|
1
12
|
approvedGitRepositories:
|
|
2
13
|
- "**"
|
|
3
14
|
|
package/README.md
CHANGED
|
@@ -48,6 +48,10 @@ edge-cli login
|
|
|
48
48
|
|
|
49
49
|
This opens your browser to the admin sign-in page. After signing in, tokens are saved to `~/.edge-cli-auth/accounts.json`.
|
|
50
50
|
|
|
51
|
+
Login creates **two** sessions from the one sign-in: a shell session that drives `edge-cli shell`, and an API-only session that `edge-cli token` prints. Token scope is fixed when the session is created and a refresh re-derives it from the session, so a shell token cannot be narrowed afterwards — the second session is what makes an API-only token possible without a second sign-in.
|
|
52
|
+
|
|
53
|
+
Prefer the API-only token anywhere a token leaves the CLI (Postman, scripts, a colleague's terminal): a `--scope shell` token can run any shell command your account is allowed to, against whatever environment it was issued for. If you logged in before this existed, `edge-cli token` will tell you to log in again rather than hand back the shell-capable one.
|
|
54
|
+
|
|
51
55
|
```bash
|
|
52
56
|
edge-cli login:list # see saved accounts
|
|
53
57
|
edge-cli login:use <email> # switch default account
|
|
@@ -65,9 +69,10 @@ edge-cli health-check
|
|
|
65
69
|
edge-cli config
|
|
66
70
|
|
|
67
71
|
# Get an API token (useful for Postman or curl)
|
|
68
|
-
edge-cli token
|
|
69
|
-
edge-cli token --
|
|
70
|
-
edge-cli token --
|
|
72
|
+
edge-cli token # API-only token (aud: edge-api)
|
|
73
|
+
edge-cli token --scope shell # also carries edge-shell; can run shell commands
|
|
74
|
+
edge-cli token --refresh # force token refresh
|
|
75
|
+
edge-cli token --header # print as Authorization header
|
|
71
76
|
|
|
72
77
|
# Run a shell command
|
|
73
78
|
edge-cli shell "<command>"
|
package/dist/commands/token.js
CHANGED
|
@@ -3,17 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerToken = registerToken;
|
|
4
4
|
const auth_1 = require("../lib/auth");
|
|
5
5
|
const config_1 = require("../lib/config");
|
|
6
|
+
const SCOPES = ["api", "shell"];
|
|
6
7
|
function registerToken(program) {
|
|
7
8
|
program
|
|
8
9
|
.command("token")
|
|
9
10
|
.description("Print a JWT access token for API calls (e.g. Postman)")
|
|
10
11
|
.option("--refresh", "Force refresh using refresh token when available")
|
|
11
12
|
.option("--header", "Print as Authorization header line")
|
|
13
|
+
.option("--scope <scope>", `Which token to print: ${SCOPES.join(" | ")}. 'api' carries the edge-api audience only; ` +
|
|
14
|
+
`'shell' also carries edge-shell and can run shell commands`, "api")
|
|
12
15
|
.action(async function tokenAction(commandOptions) {
|
|
13
16
|
const parent = this.parent;
|
|
14
17
|
const opts = (parent?.opts() ?? {});
|
|
15
18
|
const cfg = await (0, config_1.resolveEffectiveConfig)(opts);
|
|
16
|
-
const
|
|
19
|
+
const requested = (commandOptions.scope ?? "api").toLowerCase();
|
|
20
|
+
if (!SCOPES.includes(requested)) {
|
|
21
|
+
// Refused rather than defaulted: printing a token of a scope nobody asked for is
|
|
22
|
+
// how a shell-capable credential ends up somewhere it was never meant to go.
|
|
23
|
+
throw new Error(`Unknown --scope: ${commandOptions.scope} (expected ${SCOPES.join(" or ")})`);
|
|
24
|
+
}
|
|
25
|
+
const token = requested === "shell"
|
|
26
|
+
? await (0, auth_1.resolveAccessToken)(cfg, Boolean(commandOptions.refresh))
|
|
27
|
+
: await (0, auth_1.resolveApiAccessToken)(cfg, Boolean(commandOptions.refresh));
|
|
17
28
|
if (commandOptions.header) {
|
|
18
29
|
process.stdout.write(`Authorization: Bearer ${token}\n`);
|
|
19
30
|
return;
|
package/dist/lib/auth.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.setDefaultLogin = setDefaultLogin;
|
|
|
8
8
|
exports.logout = logout;
|
|
9
9
|
exports.login = login;
|
|
10
10
|
exports.resolveAccessToken = resolveAccessToken;
|
|
11
|
+
exports.resolveApiAccessToken = resolveApiAccessToken;
|
|
11
12
|
exports.resolveAuthHeader = resolveAuthHeader;
|
|
12
13
|
exports.resolveAuthHeaderFast = resolveAuthHeaderFast;
|
|
13
14
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -45,6 +46,16 @@ const DEFAULT_DEVICE_NAME = "edge-cli";
|
|
|
45
46
|
* exchange.
|
|
46
47
|
*/
|
|
47
48
|
const EXCHANGE_SCOPE_SHELL = "shell";
|
|
49
|
+
/**
|
|
50
|
+
* The app scope is the *absence* of a scope field -- `TokenScope.fromWire` maps blank to `APP` --
|
|
51
|
+
* so an API-only session is requested by omitting it, not by sending a name for it.
|
|
52
|
+
*
|
|
53
|
+
* `null`, never `undefined`: a default parameter fires when an argument is `undefined`, so passing
|
|
54
|
+
* an `undefined` sentinel to `authExchangeFirebaseToken` silently selected the shell default and
|
|
55
|
+
* every "API-only" session came back with the `edge-shell` audience. `f(null)` does not trigger the
|
|
56
|
+
* default.
|
|
57
|
+
*/
|
|
58
|
+
const EXCHANGE_SCOPE_APP = null;
|
|
48
59
|
function scopeKey(cfg) {
|
|
49
60
|
return `${cfg.product}/${cfg.env}`;
|
|
50
61
|
}
|
|
@@ -124,6 +135,24 @@ function decodeJwtPayload(token) {
|
|
|
124
135
|
return {};
|
|
125
136
|
}
|
|
126
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The extra audience `TokenScope.SHELL` stamps. Matched by name rather than by "anything that is
|
|
140
|
+
* not the API audience", because the API audience is server-configurable (`api.jwt.audience`) and
|
|
141
|
+
* the CLI cannot know what it was set to.
|
|
142
|
+
*/
|
|
143
|
+
const SHELL_AUDIENCE = "edge-shell";
|
|
144
|
+
/**
|
|
145
|
+
* Whether a token can drive the shell.
|
|
146
|
+
*
|
|
147
|
+
* Read from the token rather than inferred from what was requested. Asking for an app-scoped
|
|
148
|
+
* session and checking nothing is how every "API-only" token shipped carrying `edge-shell`: the
|
|
149
|
+
* exchange succeeded, so nothing looked wrong, and the CLI handed out shell credentials for a day.
|
|
150
|
+
*/
|
|
151
|
+
function carriesShellAudience(accessToken) {
|
|
152
|
+
const aud = decodeJwtPayload(accessToken).aud;
|
|
153
|
+
const list = Array.isArray(aud) ? aud : typeof aud === "string" ? [aud] : [];
|
|
154
|
+
return list.some((entry) => entry === SHELL_AUDIENCE);
|
|
155
|
+
}
|
|
127
156
|
function extractEmailFromToken(accessToken) {
|
|
128
157
|
const payload = decodeJwtPayload(accessToken);
|
|
129
158
|
const email = payload.email;
|
|
@@ -151,12 +180,12 @@ async function requestTokenEndpoint(cfg, endpoint, payload) {
|
|
|
151
180
|
}
|
|
152
181
|
return (await response.json());
|
|
153
182
|
}
|
|
154
|
-
async function authExchangeFirebaseToken(cfg, firebaseToken) {
|
|
183
|
+
async function authExchangeFirebaseToken(cfg, firebaseToken, scope = EXCHANGE_SCOPE_SHELL) {
|
|
155
184
|
const resp = await requestTokenEndpoint(cfg, "exchange", {
|
|
156
185
|
firebaseToken,
|
|
157
186
|
deviceId: authDeviceId(),
|
|
158
187
|
deviceName: DEFAULT_DEVICE_NAME,
|
|
159
|
-
scope:
|
|
188
|
+
...(scope === null ? {} : { scope })
|
|
160
189
|
});
|
|
161
190
|
if (!resp?.accessToken) {
|
|
162
191
|
throw new Error("Token exchange did not return accessToken");
|
|
@@ -222,9 +251,14 @@ async function logout(cfg, email) {
|
|
|
222
251
|
if (!account) {
|
|
223
252
|
throw new Error(`Account not found for ${cfg.product}/${cfg.env}: ${target}`);
|
|
224
253
|
}
|
|
225
|
-
|
|
254
|
+
// Both sessions, or logout leaves the API-only one alive on the server after the CLI has
|
|
255
|
+
// forgotten it -- a session nobody can see and nobody will revoke.
|
|
256
|
+
for (const refreshToken of [account.refreshToken, account.api?.refreshToken]) {
|
|
257
|
+
if (!refreshToken) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
226
260
|
try {
|
|
227
|
-
await authLogoutRefreshToken(cfg,
|
|
261
|
+
await authLogoutRefreshToken(cfg, refreshToken);
|
|
228
262
|
}
|
|
229
263
|
catch {
|
|
230
264
|
// best effort
|
|
@@ -238,19 +272,44 @@ async function logout(cfg, email) {
|
|
|
238
272
|
writeDb(db);
|
|
239
273
|
return target;
|
|
240
274
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
const scope = getScope(db, cfg);
|
|
244
|
-
const now = nowEpoch();
|
|
245
|
-
scope.accounts[email] = {
|
|
275
|
+
function toSession(tokenResp, now) {
|
|
276
|
+
return {
|
|
246
277
|
accessToken: tokenResp.accessToken,
|
|
247
278
|
refreshToken: tokenResp.refreshToken,
|
|
248
279
|
accessExpEpoch: now + (tokenResp.accessTokenExpiresInSec ?? 0),
|
|
249
280
|
refreshExpEpoch: tokenResp.refreshToken ? now + (tokenResp.refreshTokenExpiresInSec ?? 0) : 0
|
|
250
281
|
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @param apiResp the API-only session, when this call has a fresh one. Omitted on a shell refresh,
|
|
285
|
+
* which must leave any existing `api` session in place -- rewriting the account wholesale would
|
|
286
|
+
* silently drop it and send `edge-cli token` back to "run login again" after every refresh.
|
|
287
|
+
*/
|
|
288
|
+
function storeTokens(cfg, email, tokenResp, apiResp) {
|
|
289
|
+
const db = readDb();
|
|
290
|
+
const scope = getScope(db, cfg);
|
|
291
|
+
const now = nowEpoch();
|
|
292
|
+
const existingApi = scope.accounts[email]?.api;
|
|
293
|
+
scope.accounts[email] = {
|
|
294
|
+
...toSession(tokenResp, now),
|
|
295
|
+
api: apiResp ? toSession(apiResp, now) : existingApi
|
|
296
|
+
};
|
|
251
297
|
scope.default = email;
|
|
252
298
|
writeDb(db);
|
|
253
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Replace only the API session, leaving the shell session untouched.
|
|
302
|
+
*/
|
|
303
|
+
function storeApiTokens(cfg, email, apiResp) {
|
|
304
|
+
const db = readDb();
|
|
305
|
+
const scope = getScope(db, cfg);
|
|
306
|
+
const account = scope.accounts[email];
|
|
307
|
+
if (!account) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
account.api = toSession(apiResp, nowEpoch());
|
|
311
|
+
writeDb(db);
|
|
312
|
+
}
|
|
254
313
|
function parseCallbackQuery(queryString) {
|
|
255
314
|
const query = queryString.startsWith("?") ? queryString.slice(1) : queryString;
|
|
256
315
|
const params = new URLSearchParams(query);
|
|
@@ -401,8 +460,13 @@ async function login(cfg) {
|
|
|
401
460
|
throw new Error("Authentication callback returned code-based response, exchange not implemented.");
|
|
402
461
|
}
|
|
403
462
|
let tokenResp;
|
|
463
|
+
// Kept so the API-only session can be exchanged from the same credential below. The
|
|
464
|
+
// already-first-party branch leaves it undefined: there is no Firebase token to re-exchange,
|
|
465
|
+
// so that path yields a shell session only.
|
|
466
|
+
let firebaseToken;
|
|
404
467
|
if (parsed.firebaseToken) {
|
|
405
|
-
|
|
468
|
+
firebaseToken = parsed.firebaseToken;
|
|
469
|
+
tokenResp = await authExchangeFirebaseToken(cfg, firebaseToken);
|
|
406
470
|
}
|
|
407
471
|
else if (parsed.accessToken && parsed.refreshToken) {
|
|
408
472
|
// Already a first-party pair; nothing to exchange.
|
|
@@ -415,15 +479,47 @@ async function login(cfg) {
|
|
|
415
479
|
}
|
|
416
480
|
else if (parsed.accessToken) {
|
|
417
481
|
// No refresh token alongside it means this is the console's `?jwt=` Firebase token.
|
|
418
|
-
|
|
482
|
+
firebaseToken = parsed.accessToken;
|
|
483
|
+
tokenResp = await exchangeCallbackToken(cfg, firebaseToken);
|
|
419
484
|
}
|
|
420
485
|
if (!tokenResp?.accessToken) {
|
|
421
486
|
throw new Error("Authentication callback did not include usable token data.");
|
|
422
487
|
}
|
|
423
488
|
const email = parsed.email ?? extractEmailFromToken(tokenResp.accessToken);
|
|
424
|
-
storeTokens(cfg, email, tokenResp);
|
|
489
|
+
storeTokens(cfg, email, tokenResp, await exchangeApiSession(cfg, firebaseToken));
|
|
425
490
|
return email;
|
|
426
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Exchange the same Firebase token a second time for an API-only session.
|
|
494
|
+
*
|
|
495
|
+
* Failure is reported and swallowed rather than failing the login: the shell session -- the reason
|
|
496
|
+
* anyone runs this CLI -- already succeeded by this point, and taking the whole login down over the
|
|
497
|
+
* optional half would turn a working setup into a broken one. `edge-cli token` says what to do when
|
|
498
|
+
* the session is missing.
|
|
499
|
+
*/
|
|
500
|
+
async function exchangeApiSession(cfg, firebaseToken) {
|
|
501
|
+
if (!firebaseToken) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const resp = await authExchangeFirebaseToken(cfg, firebaseToken, EXCHANGE_SCOPE_APP);
|
|
506
|
+
if (carriesShellAudience(resp.accessToken)) {
|
|
507
|
+
// Storing it would put a shell-capable token behind `edge-cli token`, which is the one
|
|
508
|
+
// outcome this whole feature exists to prevent. Better no API session than a mislabelled
|
|
509
|
+
// one.
|
|
510
|
+
process.stderr.write("Warning: the server returned a shell-scoped token for an app-scoped exchange; " +
|
|
511
|
+
"discarding it.\n'edge-cli token' will need --scope shell until this is resolved.\n");
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
return resp;
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
518
|
+
process.stderr.write(`Warning: could not create an API-only session (${reason}).\n` +
|
|
519
|
+
`'edge-cli token' will need --scope shell until the next successful login.\n`);
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
427
523
|
async function validateAccessToken(cfg, accessToken) {
|
|
428
524
|
const url = new URL(`${cfg.baseUrl.replace(/\/$/, "")}/shell`);
|
|
429
525
|
url.searchParams.set("cmd", "help");
|
|
@@ -496,6 +592,60 @@ async function resolveAccessToken(cfg, forceRefresh = false, skipValidate = fals
|
|
|
496
592
|
}
|
|
497
593
|
return accessToken;
|
|
498
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* The account's API-only access token, refreshed when it is at or near expiry.
|
|
597
|
+
*
|
|
598
|
+
* Deliberately does no liveness probe. `validateAccessToken` asks `GET /shell?cmd=help`, which this
|
|
599
|
+
* token is *supposed* to be refused by -- running it here would read a correctly-scoped token as a
|
|
600
|
+
* broken one and refresh it on every call.
|
|
601
|
+
*
|
|
602
|
+
* A missing API session is an error rather than a silent fall back to the shell token: handing back
|
|
603
|
+
* a token with more audiences than the caller asked for is the same silent substitution the server
|
|
604
|
+
* refuses when it will not downgrade a shell request to APP.
|
|
605
|
+
*/
|
|
606
|
+
async function resolveApiAccessToken(cfg, forceRefresh = false) {
|
|
607
|
+
const db = readDb();
|
|
608
|
+
const scope = getScope(db, cfg);
|
|
609
|
+
const email = scope.default;
|
|
610
|
+
if (!email) {
|
|
611
|
+
throw new Error(`No JWT session for ${cfg.product}/${cfg.env}. Run 'edge-cli login'.`);
|
|
612
|
+
}
|
|
613
|
+
const account = scope.accounts[email];
|
|
614
|
+
if (!account) {
|
|
615
|
+
throw new Error(`No access token for ${email}. Run 'edge-cli login'.`);
|
|
616
|
+
}
|
|
617
|
+
const api = account.api;
|
|
618
|
+
if (!api?.accessToken) {
|
|
619
|
+
throw new Error(`No API-only session for ${email} on ${cfg.product}/${cfg.env}.\n` +
|
|
620
|
+
`Run 'edge-cli login' to create one, or 'edge-cli token --scope shell' to print the ` +
|
|
621
|
+
`shell-capable token instead (it also drives the shell, so do not paste it elsewhere).`);
|
|
622
|
+
}
|
|
623
|
+
// Checked on read, not only on write, so a session stored by a build that asked for the wrong
|
|
624
|
+
// scope is refused rather than handed out. Those tokens are already on disk in the wild.
|
|
625
|
+
if (carriesShellAudience(api.accessToken)) {
|
|
626
|
+
throw new Error(`The stored API session for ${email} carries the ${SHELL_AUDIENCE} audience, so it is ` +
|
|
627
|
+
`not API-only. Run 'edge-cli login' to replace it.`);
|
|
628
|
+
}
|
|
629
|
+
const expiry = api.accessExpEpoch ?? 0;
|
|
630
|
+
const stale = expiry > 0 && expiry <= nowEpoch() + 60;
|
|
631
|
+
if ((forceRefresh || stale) && api.refreshToken) {
|
|
632
|
+
try {
|
|
633
|
+
const refreshed = await authRefreshToken(cfg, api.refreshToken);
|
|
634
|
+
const merged = {
|
|
635
|
+
accessToken: refreshed.accessToken,
|
|
636
|
+
refreshToken: refreshed.refreshToken || api.refreshToken,
|
|
637
|
+
accessTokenExpiresInSec: refreshed.accessTokenExpiresInSec,
|
|
638
|
+
refreshTokenExpiresInSec: refreshed.refreshTokenExpiresInSec
|
|
639
|
+
};
|
|
640
|
+
storeApiTokens(cfg, email, merged);
|
|
641
|
+
return merged.accessToken;
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Fall through to the stored token; it may still have life left in it.
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return api.accessToken;
|
|
648
|
+
}
|
|
499
649
|
async function resolveAuthHeader(cfg) {
|
|
500
650
|
const accessToken = await resolveAccessToken(cfg);
|
|
501
651
|
return `Bearer ${accessToken}`;
|
package/package.json
CHANGED
package/src/commands/token.ts
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
|
|
3
|
-
import { resolveAccessToken } from "../lib/auth";
|
|
3
|
+
import { resolveAccessToken, resolveApiAccessToken } from "../lib/auth";
|
|
4
4
|
import { resolveEffectiveConfig } from "../lib/config";
|
|
5
5
|
import { CliOptions } from "../lib/types";
|
|
6
6
|
|
|
7
|
+
const SCOPES = ["api", "shell"] as const;
|
|
8
|
+
type TokenScope = (typeof SCOPES)[number];
|
|
9
|
+
|
|
7
10
|
export function registerToken(program: Command): void {
|
|
8
11
|
program
|
|
9
12
|
.command("token")
|
|
10
13
|
.description("Print a JWT access token for API calls (e.g. Postman)")
|
|
11
14
|
.option("--refresh", "Force refresh using refresh token when available")
|
|
12
15
|
.option("--header", "Print as Authorization header line")
|
|
13
|
-
.
|
|
16
|
+
.option(
|
|
17
|
+
"--scope <scope>",
|
|
18
|
+
`Which token to print: ${SCOPES.join(" | ")}. 'api' carries the edge-api audience only; ` +
|
|
19
|
+
`'shell' also carries edge-shell and can run shell commands`,
|
|
20
|
+
"api"
|
|
21
|
+
)
|
|
22
|
+
.action(async function tokenAction(
|
|
23
|
+
this: Command,
|
|
24
|
+
commandOptions: { refresh?: boolean; header?: boolean; scope?: string }
|
|
25
|
+
) {
|
|
14
26
|
const parent = this.parent;
|
|
15
27
|
const opts = (parent?.opts() ?? {}) as CliOptions;
|
|
16
28
|
const cfg = await resolveEffectiveConfig(opts);
|
|
17
|
-
|
|
29
|
+
|
|
30
|
+
const requested = (commandOptions.scope ?? "api").toLowerCase();
|
|
31
|
+
if (!SCOPES.includes(requested as TokenScope)) {
|
|
32
|
+
// Refused rather than defaulted: printing a token of a scope nobody asked for is
|
|
33
|
+
// how a shell-capable credential ends up somewhere it was never meant to go.
|
|
34
|
+
throw new Error(`Unknown --scope: ${commandOptions.scope} (expected ${SCOPES.join(" or ")})`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const token =
|
|
38
|
+
requested === "shell"
|
|
39
|
+
? await resolveAccessToken(cfg, Boolean(commandOptions.refresh))
|
|
40
|
+
: await resolveApiAccessToken(cfg, Boolean(commandOptions.refresh));
|
|
18
41
|
|
|
19
42
|
if (commandOptions.header) {
|
|
20
43
|
process.stdout.write(`Authorization: Bearer ${token}\n`);
|