@alchemy/cli 0.6.2 → 0.7.0-alpha.11

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.
@@ -5,7 +5,7 @@ import {
5
5
  isJSONMode,
6
6
  quiet,
7
7
  rgb
8
- } from "./chunk-56ZVYB4G.js";
8
+ } from "./chunk-OLVYWGY6.js";
9
9
 
10
10
  // src/lib/terminal-ui.ts
11
11
  import * as readline from "readline";
@@ -48,6 +48,9 @@ function suspendStdinKeypressListeners() {
48
48
  }
49
49
  async function runListPrompt(opts) {
50
50
  if (!stdin.isTTY || !stdout.isTTY) {
51
+ if (opts.allowMultiple) {
52
+ return { value: opts.initialValues ?? [], cancelled: false };
53
+ }
51
54
  const initial = opts.initialValue ?? opts.options.find((o) => !o.disabled)?.value ?? null;
52
55
  return { value: initial, cancelled: false };
53
56
  }
@@ -61,7 +64,7 @@ async function runListPrompt(opts) {
61
64
  0,
62
65
  opts.options.findIndex((o) => o.value === opts.initialValue && !o.disabled)
63
66
  );
64
- const selected = /* @__PURE__ */ new Set();
67
+ const selected = new Set(opts.initialValues ?? []);
65
68
  const maxVisible = 8;
66
69
  let renderedLines = 0;
67
70
  const getFiltered = () => {
@@ -104,10 +107,11 @@ async function runListPrompt(opts) {
104
107
  const active = start + i === cursor;
105
108
  const disabled = option.disabled === true;
106
109
  const selectedMark = opts.allowMultiple ? selected.has(option.value) ? ansi.green("\u25C6") : ansi.dim("\u25C7") : active ? ansi.cyan("\u25C6") : ansi.dim("\u25C7");
110
+ const activeMark = active ? ansi.cyan("\u203A") : " ";
107
111
  const label = optionLabel(option);
108
112
  const value = disabled ? ansi.dim(label) : label;
109
113
  const hint = option.hint ? ` ${ansi.dim(`\u2014 ${option.hint}`)}` : "";
110
- lines.push(` ${ansi.dim(FLOW_PIPE)} ${selectedMark} ${value}${hint}`);
114
+ lines.push(` ${ansi.dim(FLOW_PIPE)} ${activeMark} ${selectedMark} ${value}${hint}`);
111
115
  }
112
116
  if (filtered.length > maxVisible) {
113
117
  lines.push(` ${ansi.dim(FLOW_PIPE)} ${ansi.dim(`${filtered.length} options`)}`);
@@ -217,16 +221,26 @@ async function promptText(opts) {
217
221
  const question = ` ${ansi.cyan("\u25C6")} ${opts.message}${opts.placeholder ? ` ${ansi.dim(`(${opts.placeholder})`)}` : ""}: `;
218
222
  const previousRawMode = stdin.isRaw;
219
223
  if (previousRawMode) stdin.setRawMode(false);
224
+ const ABORTED = /* @__PURE__ */ Symbol("aborted");
220
225
  const value = await new Promise((resolve) => {
221
226
  rl.on("SIGINT", () => resolve(null));
222
227
  rl.question(question, (answer) => resolve(answer));
228
+ if (opts.abortSignal) {
229
+ const onAbort = () => resolve(ABORTED);
230
+ if (opts.abortSignal.aborted) {
231
+ onAbort();
232
+ } else {
233
+ opts.abortSignal.addEventListener("abort", onAbort, { once: true });
234
+ }
235
+ }
223
236
  });
224
237
  rl.close();
225
238
  restoreKeypressListeners();
226
239
  if (previousRawMode) stdin.setRawMode(true);
227
- if (opts.clearAfterSubmit) {
240
+ if (opts.clearAfterSubmit || value === ABORTED) {
228
241
  clearRenderedLines(2);
229
242
  }
243
+ if (value === ABORTED) return "";
230
244
  if (value === null) {
231
245
  printCancel(opts.cancelMessage);
232
246
  return null;
@@ -281,6 +295,7 @@ async function promptMultiselect(opts) {
281
295
  const result = await runListPrompt({
282
296
  message: opts.message,
283
297
  options: opts.options,
298
+ initialValues: opts.initialValues,
284
299
  allowMultiple: true,
285
300
  required: opts.required,
286
301
  commitLabel: "Selected"
@@ -362,29 +377,24 @@ async function withSpinner(label, doneLabel, fn) {
362
377
  if (isJSONMode() || quiet) return fn();
363
378
  return runWithSpinner(label, doneLabel, fn);
364
379
  }
365
- function printKeyValueBox(pairs) {
380
+ function printKeyValue(pairs, withBottomPadding = true) {
366
381
  if (isJSONMode()) return;
382
+ console.log("");
367
383
  if (pairs.length === 0) {
368
- console.log(` ${ansi2.brand("\u250C\u2500\u2500\u2510")}`);
369
- console.log(` ${ansi2.brand("\u2514\u2500\u2500\u2518")}`);
384
+ if (withBottomPadding) {
385
+ console.log("");
386
+ }
370
387
  return;
371
388
  }
372
- const keyWidth = Math.max(...pairs.map(([k]) => stripAnsi(k).length));
373
- const contentRows = pairs.map(([key, value]) => {
389
+ const maxLen = Math.max(...pairs.map(([k]) => stripAnsi(k).length));
390
+ for (const [key, value] of pairs) {
374
391
  const visibleKeyLen = stripAnsi(key).length;
375
- const paddedKey = key + " ".repeat(Math.max(0, keyWidth - visibleKeyLen));
376
- return `${ansi2.dim(paddedKey)} ${value}`;
377
- });
378
- const contentWidth = Math.max(...contentRows.map((row) => stripAnsi(row).length));
379
- const top = `\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`;
380
- const bottom = `\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`;
381
- console.log(` ${ansi2.dim(top)}`);
382
- for (const row of contentRows) {
383
- const visibleLen = stripAnsi(row).length;
384
- const padded = row + " ".repeat(Math.max(0, contentWidth - visibleLen));
385
- console.log(` ${ansi2.dim("\u2502")} ${padded} ${ansi2.dim("\u2502")}`);
392
+ const paddedKey = key + " ".repeat(Math.max(0, maxLen - visibleKeyLen));
393
+ console.log(` ${ansi2.dim(paddedKey)} ${value}`);
394
+ }
395
+ if (withBottomPadding) {
396
+ console.log("");
386
397
  }
387
- console.log(` ${ansi2.dim(bottom)}`);
388
398
  }
389
399
  function emptyState(message) {
390
400
  if (isJSONMode()) return;
@@ -543,7 +553,7 @@ export {
543
553
  successBadge,
544
554
  failBadge,
545
555
  withSpinner,
546
- printKeyValueBox,
556
+ printKeyValue,
547
557
  emptyState,
548
558
  printSyntaxJSON,
549
559
  printTable,
@@ -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-OLVYWGY6.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
  };
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ isRevealMode
5
+ } from "./chunk-OLVYWGY6.js";
6
+
7
+ // src/lib/secrets.ts
8
+ function maskSecret(value) {
9
+ if (value.length <= 8) return "\u2022".repeat(value.length);
10
+ return value.slice(0, 4) + "\u2022".repeat(value.length - 8) + value.slice(-4);
11
+ }
12
+ function maskIf(value) {
13
+ return isRevealMode() ? value : maskSecret(value);
14
+ }
15
+
16
+ // src/lib/config.ts
17
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
18
+ import { createHash, randomUUID } from "crypto";
19
+ import { homedir } from "os";
20
+ import { join, dirname } from "path";
21
+ import { z } from "zod";
22
+ var KEY_MAP = {
23
+ "api-key": "api_key",
24
+ api_key: "api_key",
25
+ "access-key": "access_key",
26
+ access_key: "access_key",
27
+ "webhook-api-key": "webhook_api_key",
28
+ webhook_api_key: "webhook_api_key",
29
+ network: "network",
30
+ verbose: "verbose",
31
+ "wallet-key-file": "wallet_key_file",
32
+ wallet_key_file: "wallet_key_file",
33
+ "wallet-address": "wallet_address",
34
+ wallet_address: "wallet_address",
35
+ x402: "x402",
36
+ "auth-token": "auth_token",
37
+ auth_token: "auth_token",
38
+ "auth-token-expires-at": "auth_token_expires_at",
39
+ auth_token_expires_at: "auth_token_expires_at",
40
+ "solana-wallet-key-file": "solana_wallet_key_file",
41
+ solana_wallet_key_file: "solana_wallet_key_file",
42
+ "solana-wallet-address": "solana_wallet_address",
43
+ solana_wallet_address: "solana_wallet_address",
44
+ "evm-gas-sponsored": "evm_gas_sponsored",
45
+ evm_gas_sponsored: "evm_gas_sponsored",
46
+ "evm-gas-policy-id": "evm_gas_policy_id",
47
+ evm_gas_policy_id: "evm_gas_policy_id",
48
+ "solana-fee-sponsored": "solana_fee_sponsored",
49
+ solana_fee_sponsored: "solana_fee_sponsored",
50
+ "solana-fee-policy-id": "solana_fee_policy_id",
51
+ solana_fee_policy_id: "solana_fee_policy_id",
52
+ "delegated-wallet": "delegated_wallet",
53
+ delegated_wallet: "delegated_wallet",
54
+ "active-signer": "active_signer",
55
+ active_signer: "active_signer",
56
+ "wallet-client-instance-name": "wallet_client_instance_name",
57
+ wallet_client_instance_name: "wallet_client_instance_name"
58
+ };
59
+ var SAFE_ID_RE = /^[A-Za-z0-9:_-]{1,128}$/;
60
+ var SAFE_NETWORK_RE = /^[A-Za-z0-9:_-]{1,128}$/;
61
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
62
+ var MAX_SECRET_LEN = 512;
63
+ var MAX_APP_NAME_LEN = 128;
64
+ var MAX_WALLET_CLIENT_INSTANCE_NAME_LEN = 64;
65
+ var CONTROL_CHAR_RE = /[\u0000-\u001f\u007f]/;
66
+ var WALLET_CLIENT_INSTANCE_ID_NAMESPACE = "alchemy-cli:wallet-client-instance-name:v1";
67
+ var safeTextSchema = (maxLen) => z.string().min(1).max(maxLen).refine((value) => !CONTROL_CHAR_RE.test(value));
68
+ var appConfigSchema = z.object({
69
+ id: z.string().regex(SAFE_ID_RE),
70
+ name: safeTextSchema(MAX_APP_NAME_LEN),
71
+ apiKey: safeTextSchema(MAX_SECRET_LEN),
72
+ webhookApiKey: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0)
73
+ }).strip();
74
+ var MAX_PATH_LEN = 4096;
75
+ var configSchema = z.object({
76
+ api_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
77
+ access_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
78
+ webhook_api_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
79
+ app: appConfigSchema.optional().catch(void 0),
80
+ network: z.string().regex(SAFE_NETWORK_RE).optional().catch(void 0),
81
+ verbose: z.boolean().optional().catch(void 0),
82
+ wallet_key_file: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
83
+ wallet_address: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
84
+ x402: z.boolean().optional().catch(void 0),
85
+ auth_token: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
86
+ auth_token_expires_at: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
87
+ siwe_token: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
88
+ siwe_token_expires_at: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
89
+ solana_wallet_key_file: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
90
+ solana_wallet_address: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
91
+ evm_gas_sponsored: z.boolean().optional().catch(void 0),
92
+ evm_gas_policy_id: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
93
+ solana_fee_sponsored: z.boolean().optional().catch(void 0),
94
+ solana_fee_policy_id: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
95
+ delegated_wallet: z.boolean().optional().catch(void 0),
96
+ active_signer: z.enum(["session", "local"]).optional().catch(void 0),
97
+ wallet_client_instance_id: z.string().regex(UUID_RE).optional().catch(void 0),
98
+ wallet_client_instance_name: z.string().transform(normalizeWhitespace).pipe(safeTextSchema(MAX_WALLET_CLIENT_INSTANCE_NAME_LEN)).optional().catch(void 0)
99
+ }).strip();
100
+ function normalizeWhitespace(value) {
101
+ return value.trim().replace(/\s+/g, " ");
102
+ }
103
+ function sanitizeConfig(input) {
104
+ const parsed = configSchema.safeParse(input);
105
+ if (!parsed.success) {
106
+ return {};
107
+ }
108
+ return parsed.data;
109
+ }
110
+ function applyLegacyMigration(cfg) {
111
+ if (cfg.active_signer === void 0 && cfg.delegated_wallet === true) {
112
+ const { delegated_wallet: _, ...rest } = cfg;
113
+ return { ...rest, active_signer: "session" };
114
+ }
115
+ return cfg;
116
+ }
117
+ function getHome() {
118
+ return process.env.HOME || homedir();
119
+ }
120
+ function configPath() {
121
+ if (process.env.ALCHEMY_CONFIG) return process.env.ALCHEMY_CONFIG;
122
+ const configHome = process.env.XDG_CONFIG_HOME || join(getHome(), ".config");
123
+ return join(configHome, "alchemy", "config.json");
124
+ }
125
+ function configDir() {
126
+ return dirname(configPath());
127
+ }
128
+ function load() {
129
+ const p = configPath();
130
+ if (!existsSync(p)) return {};
131
+ try {
132
+ const data = readFileSync(p, "utf-8");
133
+ return applyLegacyMigration(sanitizeConfig(JSON.parse(data)));
134
+ } catch {
135
+ console.error(`warning: could not parse config file at ${p} \u2014 using defaults`);
136
+ return {};
137
+ }
138
+ }
139
+ function save(cfg) {
140
+ const p = configPath();
141
+ const sanitized = sanitizeConfig(cfg);
142
+ delete sanitized.delegated_wallet;
143
+ mkdirSync(dirname(p), { recursive: true, mode: 493 });
144
+ writeFileSync(p, JSON.stringify(sanitized, null, 2) + "\n", {
145
+ mode: 384
146
+ });
147
+ }
148
+ function normalizeWalletClientInstanceName(value) {
149
+ const normalized = normalizeWhitespace(value);
150
+ const parsed = safeTextSchema(MAX_WALLET_CLIENT_INSTANCE_NAME_LEN).safeParse(normalized);
151
+ return parsed.success ? parsed.data : void 0;
152
+ }
153
+ function deriveWalletClientInstanceIdFromName(name) {
154
+ const normalized = normalizeWalletClientInstanceName(name);
155
+ if (normalized === void 0) {
156
+ throw new Error("Invalid wallet client instance name.");
157
+ }
158
+ const digest = createHash("sha256").update(WALLET_CLIENT_INSTANCE_ID_NAMESPACE).update("\0").update(normalized.toLowerCase()).digest();
159
+ const bytes = Buffer.from(digest.subarray(0, 16));
160
+ bytes[6] = bytes[6] & 15 | 80;
161
+ bytes[8] = bytes[8] & 63 | 128;
162
+ const hex = bytes.toString("hex");
163
+ return [
164
+ hex.slice(0, 8),
165
+ hex.slice(8, 12),
166
+ hex.slice(12, 16),
167
+ hex.slice(16, 20),
168
+ hex.slice(20, 32)
169
+ ].join("-");
170
+ }
171
+ function getOrCreateWalletClientInstance(input = {}) {
172
+ const cfg = load();
173
+ const instanceName = input.instanceName === void 0 ? cfg.wallet_client_instance_name : normalizeWalletClientInstanceName(input.instanceName);
174
+ if (input.instanceName !== void 0 && instanceName === void 0) {
175
+ throw new Error("Invalid wallet client instance name.");
176
+ }
177
+ if (instanceName !== void 0) {
178
+ const walletClientInstanceId2 = deriveWalletClientInstanceIdFromName(instanceName);
179
+ save({
180
+ ...cfg,
181
+ wallet_client_instance_id: walletClientInstanceId2,
182
+ wallet_client_instance_name: instanceName
183
+ });
184
+ return {
185
+ id: walletClientInstanceId2,
186
+ name: instanceName
187
+ };
188
+ }
189
+ if (cfg.wallet_client_instance_id) {
190
+ return { id: cfg.wallet_client_instance_id };
191
+ }
192
+ const walletClientInstanceId = randomUUID();
193
+ save({
194
+ ...cfg,
195
+ wallet_client_instance_id: walletClientInstanceId
196
+ });
197
+ return { id: walletClientInstanceId };
198
+ }
199
+ function get(cfg, key) {
200
+ if (key === "app") {
201
+ if (!cfg.app) return void 0;
202
+ return `${cfg.app.name} (${cfg.app.id})`;
203
+ }
204
+ const mapped = KEY_MAP[key];
205
+ if (!mapped) return void 0;
206
+ const value = cfg[mapped];
207
+ if (value === void 0) return void 0;
208
+ if (typeof value === "boolean") return String(value);
209
+ if (typeof value === "string") return value;
210
+ return void 0;
211
+ }
212
+ function validKeys() {
213
+ return [
214
+ "api-key",
215
+ "access-key",
216
+ "webhook-api-key",
217
+ "network",
218
+ "verbose",
219
+ "wallet-key-file",
220
+ "x402",
221
+ "evm-gas-sponsored",
222
+ "evm-gas-policy-id",
223
+ "solana-fee-sponsored",
224
+ "solana-fee-policy-id"
225
+ ];
226
+ }
227
+ function toMap(cfg) {
228
+ const m = {};
229
+ if (cfg.api_key) m["api-key"] = maskIf(cfg.api_key);
230
+ if (cfg.access_key) m["access-key"] = maskIf(cfg.access_key);
231
+ if (cfg.webhook_api_key) m["webhook-api-key"] = maskIf(cfg.webhook_api_key);
232
+ if (cfg.app) m["app"] = `${cfg.app.name} (${cfg.app.id})`;
233
+ if (cfg.network) m["network"] = cfg.network;
234
+ if (cfg.verbose !== void 0) m["verbose"] = String(cfg.verbose);
235
+ if (cfg.wallet_key_file) m["wallet-key-file"] = cfg.wallet_key_file;
236
+ if (cfg.wallet_address) m["wallet-address"] = cfg.wallet_address;
237
+ if (cfg.x402 !== void 0) m["x402"] = String(cfg.x402);
238
+ if (cfg.auth_token) m["auth-token"] = maskIf(cfg.auth_token);
239
+ if (cfg.auth_token_expires_at) m["auth-token-expires-at"] = cfg.auth_token_expires_at;
240
+ if (cfg.solana_wallet_key_file) m["solana-wallet-key-file"] = cfg.solana_wallet_key_file;
241
+ if (cfg.solana_wallet_address) m["solana-wallet-address"] = cfg.solana_wallet_address;
242
+ if (cfg.evm_gas_sponsored !== void 0) m["evm-gas-sponsored"] = String(cfg.evm_gas_sponsored);
243
+ if (cfg.evm_gas_policy_id) m["evm-gas-policy-id"] = cfg.evm_gas_policy_id;
244
+ if (cfg.solana_fee_sponsored !== void 0) m["solana-fee-sponsored"] = String(cfg.solana_fee_sponsored);
245
+ if (cfg.solana_fee_policy_id) m["solana-fee-policy-id"] = cfg.solana_fee_policy_id;
246
+ if (cfg.active_signer !== void 0) m["active-signer"] = cfg.active_signer;
247
+ return m;
248
+ }
249
+
250
+ export {
251
+ maskIf,
252
+ KEY_MAP,
253
+ configPath,
254
+ configDir,
255
+ load,
256
+ save,
257
+ getOrCreateWalletClientInstance,
258
+ get,
259
+ validKeys,
260
+ toMap
261
+ };
@@ -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-OLVYWGY6.js";
6
6
 
7
7
  // src/lib/interaction.ts
8
8
  import { stdin, stdout } from "process";