@alchemy/cli 0.6.2 → 0.7.0-alpha.5

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.
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getBaseDomain
5
- } from "./chunk-56ZVYB4G.js";
5
+ } from "./chunk-QEDAULQ2.js";
6
6
 
7
7
  // src/lib/auth.ts
8
8
  import { createHash, randomBytes } from "crypto";
@@ -130,7 +130,7 @@ ${SHARED_STYLE}
130
130
  // src/lib/auth.ts
131
131
  var AUTH_PORT = 16424;
132
132
  var AUTH_CALLBACK_PATH = "/callback";
133
- var OAUTH_CLIENT_ID = "alchemy-cli";
133
+ var DEFAULT_EXPIRES_IN_SECONDS = 90 * 24 * 60 * 60;
134
134
  function getAuthBaseUrl() {
135
135
  return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
136
136
  }
@@ -140,40 +140,56 @@ function generateCodeVerifier() {
140
140
  function deriveCodeChallenge(verifier) {
141
141
  return createHash("sha256").update(verifier).digest("base64url");
142
142
  }
143
- function generateState() {
144
- return randomBytes(32).toString("base64url");
145
- }
146
- function getAuthorizeUrl(port, codeChallenge, state) {
143
+ function getLoginUrl(port, codeChallenge) {
147
144
  const base = getAuthBaseUrl();
148
- const url = new URL(`${base}/oauth/authorize`);
149
- url.searchParams.set("response_type", "code");
150
- url.searchParams.set("client_id", OAUTH_CLIENT_ID);
151
- url.searchParams.set("redirect_uri", `http://localhost:${port}${AUTH_CALLBACK_PATH}`);
152
- url.searchParams.set("code_challenge", codeChallenge);
153
- url.searchParams.set("code_challenge_method", "S256");
154
- url.searchParams.set("state", state);
155
- return url.toString();
156
- }
157
- function prepareBrowserLogin(port = AUTH_PORT) {
158
- const codeVerifier = generateCodeVerifier();
159
- const codeChallenge = deriveCodeChallenge(codeVerifier);
160
- const state = generateState();
161
- return {
162
- authorizeUrl: getAuthorizeUrl(port, codeChallenge, state),
163
- codeVerifier,
164
- state
165
- };
145
+ const redirect = encodeURIComponent(`http://localhost:${port}${AUTH_CALLBACK_PATH}`);
146
+ let url = `${base}/login?redirectUrl=${redirect}&_t=${Date.now()}`;
147
+ if (codeChallenge) {
148
+ url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
149
+ }
150
+ return url;
166
151
  }
167
152
  function openBrowser(url) {
168
153
  const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
169
154
  execFile(cmd, [url]);
170
155
  }
156
+ function closeServer(args) {
157
+ const { server, sockets } = args;
158
+ return new Promise((resolve) => {
159
+ server.close(() => resolve());
160
+ for (const socket of sockets) {
161
+ socket.destroy();
162
+ }
163
+ });
164
+ }
165
+ async function sendCallbackResponse(args) {
166
+ const { server, sockets, req, res, statusCode, body } = args;
167
+ req.socket.setKeepAlive(false);
168
+ res.shouldKeepAlive = false;
169
+ res.writeHead(statusCode, {
170
+ "Content-Type": "text/html",
171
+ Connection: "close"
172
+ });
173
+ await new Promise((resolve) => {
174
+ res.end(body, () => resolve());
175
+ });
176
+ await closeServer({ server, sockets });
177
+ }
171
178
  function waitForCallback(port, timeoutMs = 12e4) {
172
- return new Promise((resolve, reject) => {
179
+ let cancelFn;
180
+ const promise = new Promise((resolve, reject) => {
181
+ const sockets = /* @__PURE__ */ new Set();
173
182
  const timer = setTimeout(() => {
174
183
  server.close();
184
+ for (const socket of sockets) {
185
+ socket.destroy();
186
+ }
175
187
  reject(new Error("Authentication timed out. Please try again."));
176
188
  }, timeoutMs);
189
+ cancelFn = () => {
190
+ clearTimeout(timer);
191
+ closeServer({ server, sockets });
192
+ };
177
193
  const server = createServer((req, res) => {
178
194
  const url = new URL(req.url || "/", `http://localhost:${port}`);
179
195
  if (url.pathname !== AUTH_CALLBACK_PATH) {
@@ -185,35 +201,52 @@ function waitForCallback(port, timeoutMs = 12e4) {
185
201
  if (error) {
186
202
  const description = url.searchParams.get("error_description") || error;
187
203
  clearTimeout(timer);
188
- res.writeHead(200, { "Content-Type": "text/html" });
189
- res.end(authErrorHtml(description));
190
- server.close();
191
- reject(new Error(`Authentication failed: ${description}`));
204
+ void sendCallbackResponse({
205
+ server,
206
+ sockets,
207
+ req,
208
+ res,
209
+ statusCode: 200,
210
+ body: authErrorHtml(description)
211
+ }).finally(() => {
212
+ reject(new Error(`Authentication failed: ${description}`));
213
+ });
192
214
  return;
193
215
  }
194
216
  const code = url.searchParams.get("code");
195
217
  if (!code) {
196
218
  clearTimeout(timer);
197
- res.writeHead(400);
198
- res.end("Missing auth code");
199
- server.close();
200
- reject(new Error("Authentication callback missing auth code."));
219
+ void sendCallbackResponse({
220
+ server,
221
+ sockets,
222
+ req,
223
+ res,
224
+ statusCode: 400,
225
+ body: "Missing auth code"
226
+ }).finally(() => {
227
+ reject(new Error("Authentication callback missing auth code."));
228
+ });
201
229
  return;
202
230
  }
203
231
  clearTimeout(timer);
204
232
  resolve({
205
233
  code,
206
- state: url.searchParams.get("state"),
207
- sendSuccess: () => {
208
- res.writeHead(200, { "Content-Type": "text/html" });
209
- res.end(AUTH_SUCCESS_HTML);
210
- server.close();
211
- },
212
- sendError: (message) => {
213
- res.writeHead(500, { "Content-Type": "text/html" });
214
- res.end(authErrorHtml(message));
215
- server.close();
216
- }
234
+ sendSuccess: () => sendCallbackResponse({
235
+ server,
236
+ sockets,
237
+ req,
238
+ res,
239
+ statusCode: 200,
240
+ body: AUTH_SUCCESS_HTML
241
+ }),
242
+ sendError: (message) => sendCallbackResponse({
243
+ server,
244
+ sockets,
245
+ req,
246
+ res,
247
+ statusCode: 500,
248
+ body: authErrorHtml(message)
249
+ })
217
250
  });
218
251
  });
219
252
  server.on("error", (err) => {
@@ -229,60 +262,75 @@ function waitForCallback(port, timeoutMs = 12e4) {
229
262
  reject(err);
230
263
  }
231
264
  });
265
+ server.on("connection", (socket) => {
266
+ sockets.add(socket);
267
+ socket.on("close", () => {
268
+ sockets.delete(socket);
269
+ });
270
+ });
232
271
  server.listen(port, "127.0.0.1");
233
272
  server.unref();
234
273
  });
274
+ return { promise, cancel: () => cancelFn() };
235
275
  }
236
276
  async function exchangeCodeForToken(code, port, options) {
237
277
  const baseUrl = getAuthBaseUrl();
238
278
  const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
239
- const body = new URLSearchParams({
240
- grant_type: "authorization_code",
279
+ const body = {
241
280
  code,
242
- redirect_uri: redirectUri,
243
- client_id: OAUTH_CLIENT_ID,
244
- code_verifier: options.codeVerifier
245
- });
246
- const response = await fetch(`${baseUrl}/oauth/token`, {
281
+ redirect_uri: redirectUri
282
+ };
283
+ if (options?.expiresInSeconds) {
284
+ body.expires_in_seconds = options.expiresInSeconds;
285
+ }
286
+ if (options?.codeVerifier) {
287
+ body.code_verifier = options.codeVerifier;
288
+ }
289
+ const response = await fetch(`${baseUrl}/api/cli/token`, {
247
290
  method: "POST",
248
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
249
- body: body.toString()
291
+ headers: { "Content-Type": "application/json" },
292
+ body: JSON.stringify(body)
250
293
  });
251
294
  if (!response.ok) {
252
295
  const errBody = await response.json().catch(() => ({}));
253
- const errMsg = errBody.error_description || errBody.error || `Token exchange failed (HTTP ${response.status})`;
254
- throw new Error(errMsg);
296
+ throw new Error(
297
+ errBody.error || `Token exchange failed (HTTP ${response.status})`
298
+ );
255
299
  }
256
300
  const data = await response.json();
257
- if (!data.access_token) {
258
- throw new Error("Token exchange response missing access_token");
301
+ if (!data.authToken) {
302
+ throw new Error("Token exchange response missing authToken");
259
303
  }
260
- const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
261
- return { token: data.access_token, expiresAt };
304
+ return { token: data.authToken, expiresAt: data.expiresAt };
305
+ }
306
+ function prepareLogin(port = AUTH_PORT) {
307
+ const codeVerifier = generateCodeVerifier();
308
+ const codeChallenge = deriveCodeChallenge(codeVerifier);
309
+ const loginUrl = getLoginUrl(port, codeChallenge);
310
+ const handle = waitForCallback(port);
311
+ return { loginUrl, callbackPromise: handle.promise, codeVerifier, port, cancel: handle.cancel };
262
312
  }
263
- async function performBrowserLogin(prepared, options) {
264
- const port = options?.port ?? AUTH_PORT;
265
- const { authorizeUrl, codeVerifier, state } = prepared ?? prepareBrowserLogin(port);
266
- const callbackPromise = waitForCallback(port);
313
+ async function completeLogin(prepared, options) {
267
314
  if (!options?.skipBrowserOpen) {
268
- openBrowser(authorizeUrl);
269
- }
270
- const callback = await callbackPromise;
271
- if (callback.state !== state) {
272
- callback.sendError("State mismatch \u2014 possible CSRF attack.");
273
- throw new Error("OAuth state mismatch. Authentication aborted.");
315
+ openBrowser(prepared.loginUrl);
274
316
  }
317
+ const callback = await prepared.callbackPromise;
275
318
  try {
276
- const result = await exchangeCodeForToken(callback.code, port, {
277
- codeVerifier
319
+ const result = await exchangeCodeForToken(callback.code, prepared.port, {
320
+ expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
321
+ codeVerifier: prepared.codeVerifier
278
322
  });
279
- callback.sendSuccess();
323
+ await callback.sendSuccess();
280
324
  return result;
281
325
  } catch (err) {
282
- callback.sendError("Failed to complete authentication. Please try again.");
326
+ await callback.sendError("Failed to complete authentication. Please try again.");
283
327
  throw err;
284
328
  }
285
329
  }
330
+ async function performBrowserLogin(port = AUTH_PORT, options) {
331
+ const prepared = prepareLogin(port);
332
+ return completeLogin(prepared, { expiresInSeconds: options?.expiresInSeconds });
333
+ }
286
334
  async function revokeToken(token) {
287
335
  const baseUrl = getAuthBaseUrl();
288
336
  try {
@@ -307,12 +355,13 @@ async function revokeToken(token) {
307
355
 
308
356
  export {
309
357
  AUTH_PORT,
310
- OAUTH_CLIENT_ID,
311
- getAuthorizeUrl,
312
- prepareBrowserLogin,
358
+ DEFAULT_EXPIRES_IN_SECONDS,
359
+ getLoginUrl,
313
360
  openBrowser,
314
361
  waitForCallback,
315
362
  exchangeCodeForToken,
363
+ prepareLogin,
364
+ completeLogin,
316
365
  performBrowserLogin,
317
366
  revokeToken
318
367
  };
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isJSONMode
5
- } from "./chunk-56ZVYB4G.js";
5
+ } from "./chunk-QEDAULQ2.js";
6
6
 
7
7
  // src/lib/interaction.ts
8
8
  import { stdin, stdout } from "process";
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ isInteractiveAllowed
5
+ } from "./chunk-HYCRHNPX.js";
6
+ import {
7
+ resolveAuthToken
8
+ } from "./chunk-PKAN5FKD.js";
9
+
10
+ // src/lib/onboarding.ts
11
+ function hasAPIKey(cfg) {
12
+ return Boolean(cfg.api_key?.trim());
13
+ }
14
+ function hasAccessKeyAndApp(cfg) {
15
+ return Boolean(cfg.access_key?.trim() && cfg.app?.id && cfg.app.apiKey);
16
+ }
17
+ function hasX402Wallet(cfg) {
18
+ return cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
19
+ }
20
+ function hasAuthToken(cfg) {
21
+ return resolveAuthToken(cfg) !== void 0;
22
+ }
23
+ function getSetupMethod(cfg) {
24
+ if (hasAPIKey(cfg)) return "api_key";
25
+ if (hasAccessKeyAndApp(cfg)) return "access_key_app";
26
+ if (hasX402Wallet(cfg)) return "x402_wallet";
27
+ if (hasAuthToken(cfg)) return "auth_token";
28
+ return null;
29
+ }
30
+ function isSetupComplete(cfg) {
31
+ return getSetupMethod(cfg) !== null;
32
+ }
33
+ function getSetupStatus(cfg) {
34
+ const satisfiedBy = getSetupMethod(cfg);
35
+ if (satisfiedBy) {
36
+ return {
37
+ complete: true,
38
+ satisfiedBy,
39
+ missing: [],
40
+ nextCommands: []
41
+ };
42
+ }
43
+ return {
44
+ complete: false,
45
+ satisfiedBy: null,
46
+ missing: ["Provide one auth path: alchemy auth OR api-key OR access-key+app OR SIWx wallet"],
47
+ nextCommands: [
48
+ "alchemy auth",
49
+ "alchemy config set app",
50
+ "alchemy config set access-key <key> && alchemy config set app <app-id>",
51
+ "alchemy wallet connect --mode local --chain evm && alchemy config set x402 true"
52
+ ]
53
+ };
54
+ }
55
+ function shouldRunOnboarding(program, cfg) {
56
+ return isInteractiveAllowed(program) && !isSetupComplete(cfg);
57
+ }
58
+
59
+ export {
60
+ getSetupMethod,
61
+ isSetupComplete,
62
+ getSetupStatus,
63
+ shouldRunOnboarding
64
+ };
@@ -1,26 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
- AUTH_PORT,
5
- exchangeCodeForToken,
6
- openBrowser,
7
- prepareBrowserLogin,
8
- revokeToken,
9
- waitForCallback
10
- } from "./chunk-FFMNT74F.js";
4
+ completeLogin,
5
+ prepareLogin,
6
+ revokeToken
7
+ } from "./chunk-HSKKIATB.js";
11
8
  import {
12
9
  isInteractiveAllowed
13
- } from "./chunk-KDMIWPZH.js";
10
+ } from "./chunk-HYCRHNPX.js";
14
11
  import {
15
12
  AdminClient,
16
13
  resolveAuthToken
17
- } from "./chunk-ATX65U7J.js";
18
- import {
19
- deleteCredentials,
20
- getCredentials,
21
- getStorageBackend,
22
- saveCredentials
23
- } from "./chunk-JQRGILIS.js";
14
+ } from "./chunk-PKAN5FKD.js";
24
15
  import {
25
16
  bold,
26
17
  brand,
@@ -29,12 +20,13 @@ import {
29
20
  promptAutocomplete,
30
21
  promptText,
31
22
  withSpinner
32
- } from "./chunk-NBDWF4ZQ.js";
23
+ } from "./chunk-A6L3WCJN.js";
33
24
  import {
25
+ configPath,
34
26
  load,
35
27
  maskIf,
36
28
  save
37
- } from "./chunk-BAAQ7ELR.js";
29
+ } from "./chunk-B3R6PRAL.js";
38
30
  import {
39
31
  CLIError,
40
32
  ErrorCode,
@@ -42,7 +34,7 @@ import {
42
34
  exitWithError,
43
35
  isJSONMode,
44
36
  printHuman
45
- } from "./chunk-56ZVYB4G.js";
37
+ } from "./chunk-QEDAULQ2.js";
46
38
 
47
39
  // src/commands/auth.ts
48
40
  function registerAuth(program) {
@@ -51,7 +43,7 @@ function registerAuth(program) {
51
43
  const yes = opts.yes || cmd.opts().yes;
52
44
  try {
53
45
  if (!opts.force) {
54
- const existing = await resolveAuthToken();
46
+ const existing = resolveAuthToken();
55
47
  if (existing) {
56
48
  printHuman(
57
49
  ` ${green("\u2713")} Already authenticated
@@ -64,108 +56,79 @@ function registerAuth(program) {
64
56
  }
65
57
  }
66
58
  if (opts.force) {
67
- const existingCreds = await getCredentials();
68
- const tokenToRevoke = existingCreds?.auth_token;
69
- if (!tokenToRevoke) {
70
- const cfg2 = load();
71
- if (cfg2.auth_token) {
72
- await revokeToken(cfg2.auth_token);
73
- save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
74
- }
75
- } else {
76
- await revokeToken(tokenToRevoke);
59
+ const cfg2 = load();
60
+ if (cfg2.auth_token) {
61
+ await revokeToken(cfg2.auth_token);
62
+ save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
77
63
  }
78
- await deleteCredentials();
79
64
  }
80
- const prepared = prepareBrowserLogin();
81
- const callbackPromise = waitForCallback(AUTH_PORT);
65
+ const prepared = prepareLogin();
82
66
  if (!isJSONMode()) {
83
67
  console.log("");
84
68
  console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
85
69
  console.log(` ${dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
86
70
  console.log("");
87
- console.log(` ${dim(prepared.authorizeUrl)}`);
71
+ console.log(` ${dim(prepared.loginUrl)}`);
88
72
  console.log("");
89
73
  }
90
- let browserOpened = false;
74
+ const promptAbort = new AbortController();
75
+ let earlyAuth = false;
76
+ prepared.callbackPromise.then(() => {
77
+ earlyAuth = true;
78
+ promptAbort.abort();
79
+ }).catch(() => {
80
+ });
91
81
  if (!yes && !isJSONMode() && isInteractiveAllowed(program)) {
92
- const promptResult = await Promise.race([
93
- promptText({
94
- message: "Press Enter to open browser, or paste the URL above to log in manually",
95
- cancelMessage: "Login cancelled."
96
- }),
97
- callbackPromise.then(() => "callback_received")
98
- ]);
99
- if (promptResult === null) return;
100
- if (promptResult !== "callback_received") {
101
- if (!isJSONMode()) {
102
- console.log(` Opening browser to log in...`);
103
- console.log(` ${dim("Waiting for authentication...")}`);
104
- }
105
- openBrowser(prepared.authorizeUrl);
106
- browserOpened = true;
107
- }
108
- }
109
- if (!browserOpened && yes) {
110
- if (!isJSONMode()) {
111
- console.log(` Opening browser to log in...`);
112
- console.log(` ${dim("Waiting for authentication...")}`);
82
+ const answer = await promptText({
83
+ message: "Press Enter to open browser, or paste the link above to authenticate",
84
+ cancelMessage: "Login cancelled.",
85
+ abortSignal: promptAbort.signal
86
+ });
87
+ if (answer === null) {
88
+ prepared.cancel();
89
+ return;
113
90
  }
114
- openBrowser(prepared.authorizeUrl);
115
91
  }
116
- const callback = await callbackPromise;
117
- if (callback.state !== prepared.state) {
118
- callback.sendError("State mismatch \u2014 possible CSRF attack.");
119
- throw new Error("OAuth state mismatch. Authentication aborted.");
120
- }
121
- let result;
122
- try {
123
- result = await exchangeCodeForToken(callback.code, AUTH_PORT, {
124
- codeVerifier: prepared.codeVerifier
125
- });
126
- callback.sendSuccess();
127
- } catch (err) {
128
- callback.sendError("Failed to complete authentication. Please try again.");
129
- throw err;
92
+ if (!earlyAuth && !isJSONMode()) {
93
+ console.log(` Opening browser to log in...`);
94
+ console.log(` ${dim("Waiting for authentication...")}`);
130
95
  }
131
- await saveCredentials({
96
+ const result = await completeLogin(prepared, {
97
+ skipBrowserOpen: earlyAuth
98
+ });
99
+ const cfg = load();
100
+ const hasConfiguredApp = Boolean(cfg.app);
101
+ save({
102
+ ...cfg,
132
103
  auth_token: result.token,
133
104
  auth_token_expires_at: result.expiresAt
134
105
  });
135
- const cfg = load();
136
- if (cfg.auth_token) {
137
- save({ ...cfg, auth_token: void 0, auth_token_expires_at: void 0 });
138
- }
139
106
  const expiresAt = result.expiresAt;
140
- const backend = await getStorageBackend();
141
107
  printHuman(
142
108
  ` ${green("\u2713")} Logged in successfully
143
- ${dim("Credentials stored in")} ${backend}
109
+ ${dim("Token saved to")} ${configPath()}
144
110
  ${dim("Expires:")} ${expiresAt}
145
111
  `,
146
112
  {
147
113
  status: "authenticated",
148
114
  expiresAt,
149
- storageBackend: backend
115
+ configPath: configPath()
150
116
  }
151
117
  );
152
- if (isInteractiveAllowed(program)) {
118
+ if (isInteractiveAllowed(program) && !hasConfiguredApp) {
153
119
  await selectAppAfterAuth(result.token);
154
120
  }
155
- process.exit(0);
156
121
  } catch (err) {
157
122
  exitWithError(
158
123
  err instanceof CLIError ? err : new CLIError(ErrorCode.AUTH_REQUIRED, String(err.message))
159
124
  );
160
125
  }
161
126
  });
162
- cmd.command("status").description("Show current authentication status").action(async () => {
127
+ cmd.command("status").description("Show current authentication status").action(() => {
163
128
  try {
164
- const creds = await getCredentials();
165
129
  const cfg = load();
166
- const validToken = await resolveAuthToken(cfg);
167
- const hasToken = creds?.auth_token || cfg.auth_token;
168
- if (!hasToken) {
130
+ const validToken = resolveAuthToken(cfg);
131
+ if (!cfg.auth_token) {
169
132
  printHuman(
170
133
  ` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
171
134
  `,
@@ -181,20 +144,15 @@ function registerAuth(program) {
181
144
  );
182
145
  return;
183
146
  }
184
- const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at || "unknown";
185
- const backend = await getStorageBackend();
186
- const storedIn = creds?.auth_token ? backend : "config file (legacy)";
187
147
  printHuman(
188
148
  ` ${green("\u2713")} Authenticated
189
149
  ${dim("Token:")} ${maskIf(validToken)}
190
- ${dim("Storage:")} ${storedIn}
191
- ${dim("Expires:")} ${expiresAt}
150
+ ${dim("Expires:")} ${cfg.auth_token_expires_at || "unknown"}
192
151
  `,
193
152
  {
194
153
  authenticated: true,
195
154
  expired: false,
196
- expiresAt,
197
- storageBackend: storedIn
155
+ expiresAt: cfg.auth_token_expires_at
198
156
  }
199
157
  );
200
158
  } catch (err) {
@@ -203,17 +161,14 @@ function registerAuth(program) {
203
161
  });
204
162
  cmd.command("logout").description("Clear saved authentication token").action(async () => {
205
163
  try {
206
- const creds = await getCredentials();
207
164
  const cfg = load();
208
- const activeToken = creds?.auth_token || cfg.auth_token;
209
165
  let revokeResult;
210
- if (activeToken) {
211
- revokeResult = await revokeToken(activeToken);
166
+ if (cfg.auth_token) {
167
+ revokeResult = await revokeToken(cfg.auth_token);
212
168
  }
213
- await deleteCredentials();
214
- const { auth_token: _, auth_token_expires_at: __, app: ___, ...rest } = cfg;
169
+ const { auth_token: _, auth_token_expires_at: __, ...rest } = cfg;
215
170
  save(rest);
216
- if (!activeToken) {
171
+ if (!cfg.auth_token) {
217
172
  printHuman(
218
173
  ` ${dim("No active session.")}
219
174
  `,