@alchemy/cli 0.5.1 → 0.6.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.
@@ -2,25 +2,39 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  AUTH_PORT,
5
- getLoginUrl,
6
- performBrowserLogin,
7
- revokeToken
8
- } from "./chunk-IGD4NIK7.js";
5
+ exchangeCodeForToken,
6
+ openBrowser,
7
+ prepareBrowserLogin,
8
+ revokeToken,
9
+ waitForCallback
10
+ } from "./chunk-FFMNT74F.js";
11
+ import {
12
+ isInteractiveAllowed
13
+ } from "./chunk-KDMIWPZH.js";
9
14
  import {
10
15
  AdminClient,
16
+ resolveAuthToken
17
+ } from "./chunk-ATX65U7J.js";
18
+ import {
19
+ deleteCredentials,
20
+ getCredentials,
21
+ getStorageBackend,
22
+ saveCredentials
23
+ } from "./chunk-JQRGILIS.js";
24
+ import {
11
25
  bold,
12
26
  brand,
13
- configPath,
14
27
  dim,
15
28
  green,
16
- isInteractiveAllowed,
29
+ promptAutocomplete,
30
+ promptText,
31
+ withSpinner
32
+ } from "./chunk-NBDWF4ZQ.js";
33
+ import {
17
34
  load,
18
35
  maskIf,
19
- promptSelect,
20
- resolveAuthToken,
21
- save,
22
- withSpinner
23
- } from "./chunk-T2XSNZE3.js";
36
+ save
37
+ } from "./chunk-BAAQ7ELR.js";
24
38
  import {
25
39
  CLIError,
26
40
  ErrorCode,
@@ -32,11 +46,12 @@ import {
32
46
 
33
47
  // src/commands/auth.ts
34
48
  function registerAuth(program) {
35
- const cmd = program.command("auth").description("Authenticate with your Alchemy account");
36
- cmd.command("login", { isDefault: true }).description("Log in via browser").option("--force", "Force re-authentication even if a valid token exists").action(async (opts) => {
49
+ const cmd = program.command("auth").description("Authenticate with your Alchemy account").option("-y, --yes", "Skip confirmation prompt and open browser immediately");
50
+ cmd.command("login", { isDefault: true }).description("Log in via browser").option("--force", "Force re-authentication even if a valid token exists").option("-y, --yes", "Skip confirmation prompt and open browser immediately").action(async (opts) => {
51
+ const yes = opts.yes || cmd.opts().yes;
37
52
  try {
38
53
  if (!opts.force) {
39
- const existing = resolveAuthToken();
54
+ const existing = await resolveAuthToken();
40
55
  if (existing) {
41
56
  printHuman(
42
57
  ` ${green("\u2713")} Already authenticated
@@ -49,39 +64,89 @@ function registerAuth(program) {
49
64
  }
50
65
  }
51
66
  if (opts.force) {
52
- const cfg2 = load();
53
- if (cfg2.auth_token) {
54
- await revokeToken(cfg2.auth_token);
55
- save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
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);
56
77
  }
78
+ await deleteCredentials();
57
79
  }
80
+ const prepared = prepareBrowserLogin();
81
+ const callbackPromise = waitForCallback(AUTH_PORT);
58
82
  if (!isJSONMode()) {
59
83
  console.log("");
60
84
  console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
61
85
  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")}`);
62
86
  console.log("");
63
- console.log(` Opening browser to log in...`);
64
- console.log(` ${dim(getLoginUrl(AUTH_PORT))}`);
87
+ console.log(` ${dim(prepared.authorizeUrl)}`);
65
88
  console.log("");
66
- console.log(` ${dim("Waiting for authentication...")}`);
67
89
  }
68
- const result = await performBrowserLogin();
69
- const cfg = load();
70
- save({
71
- ...cfg,
90
+ let browserOpened = false;
91
+ 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...")}`);
113
+ }
114
+ openBrowser(prepared.authorizeUrl);
115
+ }
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;
130
+ }
131
+ await saveCredentials({
72
132
  auth_token: result.token,
73
133
  auth_token_expires_at: result.expiresAt
74
134
  });
135
+ const cfg = load();
136
+ if (cfg.auth_token) {
137
+ save({ ...cfg, auth_token: void 0, auth_token_expires_at: void 0 });
138
+ }
75
139
  const expiresAt = result.expiresAt;
140
+ const backend = await getStorageBackend();
76
141
  printHuman(
77
142
  ` ${green("\u2713")} Logged in successfully
78
- ${dim("Token saved to")} ${configPath()}
143
+ ${dim("Credentials stored in")} ${backend}
79
144
  ${dim("Expires:")} ${expiresAt}
80
145
  `,
81
146
  {
82
147
  status: "authenticated",
83
148
  expiresAt,
84
- configPath: configPath()
149
+ storageBackend: backend
85
150
  }
86
151
  );
87
152
  if (isInteractiveAllowed(program)) {
@@ -94,11 +159,13 @@ function registerAuth(program) {
94
159
  );
95
160
  }
96
161
  });
97
- cmd.command("status").description("Show current authentication status").action(() => {
162
+ cmd.command("status").description("Show current authentication status").action(async () => {
98
163
  try {
164
+ const creds = await getCredentials();
99
165
  const cfg = load();
100
- const validToken = resolveAuthToken(cfg);
101
- if (!cfg.auth_token) {
166
+ const validToken = await resolveAuthToken(cfg);
167
+ const hasToken = creds?.auth_token || cfg.auth_token;
168
+ if (!hasToken) {
102
169
  printHuman(
103
170
  ` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
104
171
  `,
@@ -114,15 +181,20 @@ function registerAuth(program) {
114
181
  );
115
182
  return;
116
183
  }
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)";
117
187
  printHuman(
118
188
  ` ${green("\u2713")} Authenticated
119
189
  ${dim("Token:")} ${maskIf(validToken)}
120
- ${dim("Expires:")} ${cfg.auth_token_expires_at || "unknown"}
190
+ ${dim("Storage:")} ${storedIn}
191
+ ${dim("Expires:")} ${expiresAt}
121
192
  `,
122
193
  {
123
194
  authenticated: true,
124
195
  expired: false,
125
- expiresAt: cfg.auth_token_expires_at
196
+ expiresAt,
197
+ storageBackend: storedIn
126
198
  }
127
199
  );
128
200
  } catch (err) {
@@ -131,14 +203,17 @@ function registerAuth(program) {
131
203
  });
132
204
  cmd.command("logout").description("Clear saved authentication token").action(async () => {
133
205
  try {
206
+ const creds = await getCredentials();
134
207
  const cfg = load();
208
+ const activeToken = creds?.auth_token || cfg.auth_token;
135
209
  let revokeResult;
136
- if (cfg.auth_token) {
137
- revokeResult = await revokeToken(cfg.auth_token);
210
+ if (activeToken) {
211
+ revokeResult = await revokeToken(activeToken);
138
212
  }
139
- const { auth_token: _, auth_token_expires_at: __, ...rest } = cfg;
213
+ await deleteCredentials();
214
+ const { auth_token: _, auth_token_expires_at: __, app: ___, ...rest } = cfg;
140
215
  save(rest);
141
- if (!cfg.auth_token) {
216
+ if (!activeToken) {
142
217
  printHuman(
143
218
  ` ${dim("No active session.")}
144
219
  `,
@@ -196,8 +271,9 @@ async function selectAppAfterAuth(authToken) {
196
271
  console.log(` ${green("\u2713")} Auto-selected app: ${bold(selectedApp.name)}`);
197
272
  } else {
198
273
  console.log("");
199
- const appId = await promptSelect({
274
+ const appId = await promptAutocomplete({
200
275
  message: "Select an app",
276
+ placeholder: "Type to search by name",
201
277
  options: apps.map((app) => ({
202
278
  value: app.id,
203
279
  label: app.name,
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ deleteCredentials,
5
+ getCredentials,
6
+ getStorageBackend,
7
+ saveCredentials
8
+ } from "./chunk-JQRGILIS.js";
9
+ export {
10
+ deleteCredentials,
11
+ getCredentials,
12
+ getStorageBackend,
13
+ saveCredentials
14
+ };