@alchemy/cli 0.1.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/README.md +336 -0
- package/dist/chunk-F7KTEZFZ.js +282 -0
- package/dist/chunk-PH4BPYSY.js +1145 -0
- package/dist/chunk-QKXQW4OF.js +1113 -0
- package/dist/index.js +1897 -0
- package/dist/interactive-2ITFWH3B.js +406 -0
- package/dist/onboarding-3J4EXZMG.js +178 -0
- package/package.json +54 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
getRPCNetworkIds,
|
|
5
|
+
getSetupMethod
|
|
6
|
+
} from "./chunk-F7KTEZFZ.js";
|
|
7
|
+
import {
|
|
8
|
+
bgRgb,
|
|
9
|
+
bold,
|
|
10
|
+
brand,
|
|
11
|
+
brandedHelp,
|
|
12
|
+
configDir,
|
|
13
|
+
dim,
|
|
14
|
+
green,
|
|
15
|
+
isJSONMode,
|
|
16
|
+
load,
|
|
17
|
+
noColor,
|
|
18
|
+
rgb,
|
|
19
|
+
setBrandedHelpSuppressed,
|
|
20
|
+
setReplMode
|
|
21
|
+
} from "./chunk-QKXQW4OF.js";
|
|
22
|
+
|
|
23
|
+
// src/commands/interactive.ts
|
|
24
|
+
import * as readline from "readline";
|
|
25
|
+
import { stdin, stdout, stderr } from "process";
|
|
26
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
27
|
+
import { join, dirname } from "path";
|
|
28
|
+
var COMMAND_NAMES = [
|
|
29
|
+
"apps",
|
|
30
|
+
"apps list",
|
|
31
|
+
"apps get",
|
|
32
|
+
"apps create",
|
|
33
|
+
"apps delete",
|
|
34
|
+
"apps update",
|
|
35
|
+
"apps networks",
|
|
36
|
+
"apps address-allowlist",
|
|
37
|
+
"apps origin-allowlist",
|
|
38
|
+
"apps ip-allowlist",
|
|
39
|
+
"bal",
|
|
40
|
+
"balance",
|
|
41
|
+
"block",
|
|
42
|
+
"chains",
|
|
43
|
+
"chains list",
|
|
44
|
+
"trace",
|
|
45
|
+
"debug",
|
|
46
|
+
"transfers",
|
|
47
|
+
"prices",
|
|
48
|
+
"prices symbol",
|
|
49
|
+
"prices address",
|
|
50
|
+
"prices historical",
|
|
51
|
+
"portfolio",
|
|
52
|
+
"portfolio tokens",
|
|
53
|
+
"portfolio token-balances",
|
|
54
|
+
"portfolio nfts",
|
|
55
|
+
"portfolio nft-contracts",
|
|
56
|
+
"portfolio transactions",
|
|
57
|
+
"simulate",
|
|
58
|
+
"simulate asset-changes",
|
|
59
|
+
"simulate execution",
|
|
60
|
+
"simulate asset-changes-bundle",
|
|
61
|
+
"simulate execution-bundle",
|
|
62
|
+
"webhooks",
|
|
63
|
+
"webhooks list",
|
|
64
|
+
"webhooks create",
|
|
65
|
+
"webhooks update",
|
|
66
|
+
"webhooks delete",
|
|
67
|
+
"webhooks addresses",
|
|
68
|
+
"webhooks nft-filters",
|
|
69
|
+
"bundler",
|
|
70
|
+
"bundler send-user-operation",
|
|
71
|
+
"bundler estimate-user-operation-gas",
|
|
72
|
+
"bundler get-user-operation-receipt",
|
|
73
|
+
"gas-manager",
|
|
74
|
+
"gas-manager request-gas-and-paymaster",
|
|
75
|
+
"gas-manager request-paymaster-token-quote",
|
|
76
|
+
"solana",
|
|
77
|
+
"solana rpc",
|
|
78
|
+
"solana das",
|
|
79
|
+
"config",
|
|
80
|
+
"config set",
|
|
81
|
+
"config set api-key",
|
|
82
|
+
"config set access-key",
|
|
83
|
+
"config set app",
|
|
84
|
+
"config set network",
|
|
85
|
+
"config set verbose",
|
|
86
|
+
"config set wallet-key-file",
|
|
87
|
+
"config set x402",
|
|
88
|
+
"config get",
|
|
89
|
+
"config list",
|
|
90
|
+
"help",
|
|
91
|
+
"network",
|
|
92
|
+
"network list",
|
|
93
|
+
"nfts",
|
|
94
|
+
"nfts metadata",
|
|
95
|
+
"nfts contract",
|
|
96
|
+
"rpc",
|
|
97
|
+
"setup",
|
|
98
|
+
"setup status",
|
|
99
|
+
"tokens",
|
|
100
|
+
"tokens metadata",
|
|
101
|
+
"tokens allowance",
|
|
102
|
+
"tx",
|
|
103
|
+
"version",
|
|
104
|
+
"wallet",
|
|
105
|
+
"wallet generate",
|
|
106
|
+
"wallet import",
|
|
107
|
+
"wallet address"
|
|
108
|
+
];
|
|
109
|
+
var NETWORK_NAMES = getRPCNetworkIds();
|
|
110
|
+
var REPL_HISTORY_MAX = 100;
|
|
111
|
+
function formatSetupMethodLabel() {
|
|
112
|
+
const method = getSetupMethod(load());
|
|
113
|
+
if (method === "api_key") return "API key";
|
|
114
|
+
if (method === "access_key_app") return "Access key + app";
|
|
115
|
+
if (method === "x402_wallet") return "x402 wallet";
|
|
116
|
+
return "Not configured";
|
|
117
|
+
}
|
|
118
|
+
function replHistoryPath() {
|
|
119
|
+
return join(configDir(), "repl-history");
|
|
120
|
+
}
|
|
121
|
+
function loadReplHistory() {
|
|
122
|
+
const historyFilePath = replHistoryPath();
|
|
123
|
+
if (!existsSync(historyFilePath)) return [];
|
|
124
|
+
try {
|
|
125
|
+
return readFileSync(historyFilePath, "utf-8").split("\n").map((line) => line.trim()).filter(Boolean).slice(-REPL_HISTORY_MAX);
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function saveReplHistory(lines) {
|
|
131
|
+
const historyFilePath = replHistoryPath();
|
|
132
|
+
const normalized = Array.from(
|
|
133
|
+
new Set(lines.map((line) => line.trim()).filter(Boolean))
|
|
134
|
+
).slice(-REPL_HISTORY_MAX);
|
|
135
|
+
mkdirSync(dirname(historyFilePath), { recursive: true, mode: 493 });
|
|
136
|
+
writeFileSync(historyFilePath, normalized.join("\n") + "\n", { mode: 384 });
|
|
137
|
+
}
|
|
138
|
+
async function startREPL(program) {
|
|
139
|
+
if (!stdin.isTTY) return;
|
|
140
|
+
setReplMode(true);
|
|
141
|
+
setBrandedHelpSuppressed(true);
|
|
142
|
+
const applyExitOverride = (cmd) => {
|
|
143
|
+
cmd.exitOverride();
|
|
144
|
+
for (const subcommand of cmd.commands) {
|
|
145
|
+
applyExitOverride(subcommand);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
applyExitOverride(program);
|
|
149
|
+
const addressHistory = [];
|
|
150
|
+
const rootCommands = Array.from(
|
|
151
|
+
new Set(
|
|
152
|
+
COMMAND_NAMES.map((name) => name.split(" ")[0]).concat([
|
|
153
|
+
"help",
|
|
154
|
+
"exit",
|
|
155
|
+
"quit"
|
|
156
|
+
])
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
const getCompletions = (line) => {
|
|
160
|
+
const input = line.trimStart();
|
|
161
|
+
const hasTrailingSpace = /\s$/.test(input);
|
|
162
|
+
const tokens = input.length > 0 ? input.split(/\s+/) : [];
|
|
163
|
+
if (tokens.length === 0) {
|
|
164
|
+
return rootCommands;
|
|
165
|
+
}
|
|
166
|
+
if (tokens.length === 1 && !hasTrailingSpace) {
|
|
167
|
+
return rootCommands;
|
|
168
|
+
}
|
|
169
|
+
const first = tokens[0];
|
|
170
|
+
const phraseWithoutLeading = input;
|
|
171
|
+
const commandPhrases = first === "help" ? COMMAND_NAMES : COMMAND_NAMES.filter((name) => name.startsWith(first));
|
|
172
|
+
const phraseCandidates = commandPhrases.filter(
|
|
173
|
+
(name) => name.startsWith(phraseWithoutLeading)
|
|
174
|
+
);
|
|
175
|
+
const networkArgContext = /(?:^|\s)(?:-n|--network)\s+\S*$/.test(input);
|
|
176
|
+
const networkCandidates = networkArgContext ? NETWORK_NAMES : [];
|
|
177
|
+
const addressPrefix = tokens[tokens.length - 1] ?? "";
|
|
178
|
+
const addressCandidates = addressPrefix.startsWith("0x") && addressHistory.length > 0 ? addressHistory : [];
|
|
179
|
+
return Array.from(
|
|
180
|
+
/* @__PURE__ */ new Set([...phraseCandidates, ...networkCandidates, ...addressCandidates])
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
const longestCommonPrefix = (values) => {
|
|
184
|
+
if (values.length === 0) return "";
|
|
185
|
+
if (values.length === 1) return values[0];
|
|
186
|
+
let prefix = values[0];
|
|
187
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
188
|
+
const value = values[i];
|
|
189
|
+
let j = 0;
|
|
190
|
+
while (j < prefix.length && j < value.length && prefix[j] === value[j]) {
|
|
191
|
+
j += 1;
|
|
192
|
+
}
|
|
193
|
+
prefix = prefix.slice(0, j);
|
|
194
|
+
if (!prefix) break;
|
|
195
|
+
}
|
|
196
|
+
return prefix;
|
|
197
|
+
};
|
|
198
|
+
const getCompletionMatches = (input) => Array.from(new Set(getCompletions(input))).filter((c) => c.startsWith(input) && c !== input).sort((a, b) => a.localeCompare(b));
|
|
199
|
+
const getInlineSuggestion = (line) => {
|
|
200
|
+
const input = line.trimStart();
|
|
201
|
+
if (!input) return "";
|
|
202
|
+
const candidates = getCompletionMatches(input);
|
|
203
|
+
if (candidates.length === 0) return "";
|
|
204
|
+
const uniqueSorted = Array.from(new Set(candidates)).sort(
|
|
205
|
+
(a, b) => a.length - b.length || a.localeCompare(b)
|
|
206
|
+
);
|
|
207
|
+
const best = uniqueSorted[0];
|
|
208
|
+
if (!best || best === input) return "";
|
|
209
|
+
return best.slice(input.length);
|
|
210
|
+
};
|
|
211
|
+
const printIntro = () => {
|
|
212
|
+
if (isJSONMode()) return;
|
|
213
|
+
process.stdout.write(brandedHelp({ force: true }));
|
|
214
|
+
console.log("");
|
|
215
|
+
console.log(` ${brand("\u25C6")} ${bold("Welcome to Alchemy CLI")}`);
|
|
216
|
+
console.log(` ${green("\u2713")} ${dim(`Configured auth: ${formatSetupMethodLabel()}`)}`);
|
|
217
|
+
console.log(` ${dim("Run commands directly (no 'alchemy' prefix).")}`);
|
|
218
|
+
console.log("");
|
|
219
|
+
console.log(` ${brand("\u25C6")} ${bold("Quick commands")}`);
|
|
220
|
+
console.log(` ${dim("- rpc eth_chainId")}`);
|
|
221
|
+
console.log(` ${dim("- config list")}`);
|
|
222
|
+
console.log(` ${dim("- network list")}`);
|
|
223
|
+
console.log(` ${dim("- help")}`);
|
|
224
|
+
console.log("");
|
|
225
|
+
console.log(` ${dim("Press TAB for autocomplete. Type 'exit' or 'quit' to leave.")}`);
|
|
226
|
+
console.log("");
|
|
227
|
+
console.log("");
|
|
228
|
+
};
|
|
229
|
+
const PROMPT_STR = "\x1B[38;2;54;63;249m\u203A\x1B[39m ";
|
|
230
|
+
const submittedCommandBg = bgRgb(64, 64, 68);
|
|
231
|
+
const submittedCommandFg = rgb(232, 232, 236);
|
|
232
|
+
const OUTPUT_INDENT = " ";
|
|
233
|
+
const styleSubmittedCommand = (command) => {
|
|
234
|
+
if (!stdout.isTTY || noColor) return command;
|
|
235
|
+
return submittedCommandBg(submittedCommandFg(` ${command} `));
|
|
236
|
+
};
|
|
237
|
+
const runWithIndentedOutput = async (fn) => {
|
|
238
|
+
if (isJSONMode() || !stdout.isTTY) {
|
|
239
|
+
await fn();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const createIndentedWriter = (orig) => {
|
|
243
|
+
let atLineStart = true;
|
|
244
|
+
return function(chunk, ...rest) {
|
|
245
|
+
const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
246
|
+
if (!str) return true;
|
|
247
|
+
let out = "";
|
|
248
|
+
for (const ch of str) {
|
|
249
|
+
if (atLineStart && ch !== "\n" && ch !== "\r") {
|
|
250
|
+
out += OUTPUT_INDENT;
|
|
251
|
+
atLineStart = false;
|
|
252
|
+
}
|
|
253
|
+
out += ch;
|
|
254
|
+
if (ch === "\n" || ch === "\r") {
|
|
255
|
+
atLineStart = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return orig(out, ...rest);
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
const origStdoutWrite = stdout.write.bind(stdout);
|
|
262
|
+
const origStderrWrite = stderr.write.bind(stderr);
|
|
263
|
+
stdout.write = createIndentedWriter(origStdoutWrite);
|
|
264
|
+
stderr.write = createIndentedWriter(origStderrWrite);
|
|
265
|
+
try {
|
|
266
|
+
await fn();
|
|
267
|
+
} finally {
|
|
268
|
+
stdout.write = origStdoutWrite;
|
|
269
|
+
stderr.write = origStderrWrite;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const rl = readline.createInterface({
|
|
273
|
+
input: stdin,
|
|
274
|
+
output: stdout,
|
|
275
|
+
prompt: PROMPT_STR,
|
|
276
|
+
historySize: REPL_HISTORY_MAX,
|
|
277
|
+
removeHistoryDuplicates: true
|
|
278
|
+
});
|
|
279
|
+
const initialHistory = loadReplHistory();
|
|
280
|
+
const rlWithHistory = rl;
|
|
281
|
+
if (Array.isArray(rlWithHistory.history) && initialHistory.length > 0) {
|
|
282
|
+
rlWithHistory.history = [...initialHistory].reverse();
|
|
283
|
+
}
|
|
284
|
+
const renderInlineSuggestion = () => {
|
|
285
|
+
if (!stdout.isTTY) return;
|
|
286
|
+
const line = rl.line;
|
|
287
|
+
const cursor = typeof rl.cursor === "number" ? rl.cursor : line.length;
|
|
288
|
+
if (cursor !== line.length) return;
|
|
289
|
+
const suggestion = getInlineSuggestion(line);
|
|
290
|
+
readline.clearLine(stdout, 1);
|
|
291
|
+
if (!suggestion) return;
|
|
292
|
+
stdout.write(dim(suggestion));
|
|
293
|
+
readline.moveCursor(stdout, -suggestion.length, 0);
|
|
294
|
+
};
|
|
295
|
+
const acceptInlineCompletion = () => {
|
|
296
|
+
const line = rl.line;
|
|
297
|
+
const cursor = typeof rl.cursor === "number" ? rl.cursor : line.length;
|
|
298
|
+
if (cursor !== line.length) return;
|
|
299
|
+
const input = line.trimStart();
|
|
300
|
+
const candidates = getCompletionMatches(input);
|
|
301
|
+
if (candidates.length === 0) return;
|
|
302
|
+
const prefix = longestCommonPrefix(Array.from(new Set(candidates)));
|
|
303
|
+
if (prefix && prefix.length > input.length) {
|
|
304
|
+
rl.write(prefix.slice(input.length));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const suggestion = getInlineSuggestion(line);
|
|
308
|
+
if (suggestion) {
|
|
309
|
+
rl.write(suggestion);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const rlWithTTYWrite = rl;
|
|
313
|
+
const originalTTYWrite = rlWithTTYWrite._ttyWrite?.bind(rl);
|
|
314
|
+
if (originalTTYWrite) {
|
|
315
|
+
rlWithTTYWrite._ttyWrite = (s, key) => {
|
|
316
|
+
if (key?.name === "return" && !rl.line.trim()) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (key?.name === "tab") {
|
|
320
|
+
acceptInlineCompletion();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
originalTTYWrite(s, key);
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const onKeypress = (_char, key) => {
|
|
327
|
+
if (key?.name === "tab") return;
|
|
328
|
+
setImmediate(() => {
|
|
329
|
+
renderInlineSuggestion();
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
stdin.on("keypress", onKeypress);
|
|
333
|
+
const prompt = () => {
|
|
334
|
+
stdin.resume();
|
|
335
|
+
stdin.ref?.();
|
|
336
|
+
if (stdin.isTTY && typeof stdin.setRawMode === "function") {
|
|
337
|
+
stdin.setRawMode(true);
|
|
338
|
+
}
|
|
339
|
+
rl.prompt();
|
|
340
|
+
};
|
|
341
|
+
const printPostOutputSpacing = () => {
|
|
342
|
+
if (!isJSONMode() && stdout.isTTY) {
|
|
343
|
+
console.log("");
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const onLine = async (line) => {
|
|
347
|
+
const trimmed = line.trim();
|
|
348
|
+
if (!trimmed) {
|
|
349
|
+
prompt();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
353
|
+
rl.close();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (!isJSONMode() && stdout.isTTY) {
|
|
357
|
+
readline.moveCursor(stdout, 0, -1);
|
|
358
|
+
readline.clearLine(stdout, 0);
|
|
359
|
+
stdout.write(styleSubmittedCommand(trimmed) + "\n");
|
|
360
|
+
console.log("");
|
|
361
|
+
}
|
|
362
|
+
const words = trimmed.split(/\s+/);
|
|
363
|
+
if (words[0] === "help") {
|
|
364
|
+
const target = words.slice(1);
|
|
365
|
+
try {
|
|
366
|
+
await runWithIndentedOutput(async () => {
|
|
367
|
+
await program.parseAsync(["node", "alchemy", ...target, "--help"]);
|
|
368
|
+
});
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
printPostOutputSpacing();
|
|
372
|
+
prompt();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const w of words) {
|
|
376
|
+
if (w.startsWith("0x") && w.length > 10 && !addressHistory.includes(w)) {
|
|
377
|
+
addressHistory.push(w);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
await runWithIndentedOutput(async () => {
|
|
382
|
+
await program.parseAsync(["node", "alchemy", ...words]);
|
|
383
|
+
});
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
printPostOutputSpacing();
|
|
387
|
+
prompt();
|
|
388
|
+
};
|
|
389
|
+
return new Promise((resolve) => {
|
|
390
|
+
rl.on("line", (line) => void onLine(line));
|
|
391
|
+
rl.on("close", () => {
|
|
392
|
+
if (Array.isArray(rlWithHistory.history)) {
|
|
393
|
+
saveReplHistory([...rlWithHistory.history].reverse());
|
|
394
|
+
}
|
|
395
|
+
setReplMode(false);
|
|
396
|
+
setBrandedHelpSuppressed(false);
|
|
397
|
+
stdin.off("keypress", onKeypress);
|
|
398
|
+
resolve();
|
|
399
|
+
});
|
|
400
|
+
printIntro();
|
|
401
|
+
prompt();
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
export {
|
|
405
|
+
startREPL
|
|
406
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
AdminClient,
|
|
5
|
+
generateAndPersistWallet,
|
|
6
|
+
importAndPersistWallet,
|
|
7
|
+
selectOrCreateApp
|
|
8
|
+
} from "./chunk-PH4BPYSY.js";
|
|
9
|
+
import {
|
|
10
|
+
bold,
|
|
11
|
+
brand,
|
|
12
|
+
brandedHelp,
|
|
13
|
+
dim,
|
|
14
|
+
green,
|
|
15
|
+
load,
|
|
16
|
+
maskIf,
|
|
17
|
+
printKeyValueBox,
|
|
18
|
+
promptSelect,
|
|
19
|
+
promptText,
|
|
20
|
+
save
|
|
21
|
+
} from "./chunk-QKXQW4OF.js";
|
|
22
|
+
|
|
23
|
+
// src/commands/onboarding.ts
|
|
24
|
+
function printNextSteps(method) {
|
|
25
|
+
const commandsByMethod = {
|
|
26
|
+
"api-key": ["alchemy config set api-key <key>"],
|
|
27
|
+
"access-key": [
|
|
28
|
+
"alchemy config set access-key <key>",
|
|
29
|
+
"alchemy config set app <app-id>"
|
|
30
|
+
],
|
|
31
|
+
x402: [
|
|
32
|
+
"alchemy wallet generate",
|
|
33
|
+
"alchemy config set wallet-key-file <path>",
|
|
34
|
+
"alchemy config set x402 true"
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(` ${dim("Next steps:")}`);
|
|
39
|
+
for (const command of commandsByMethod[method]) {
|
|
40
|
+
console.log(` ${dim(`- ${command}`)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function printAPIKeyPostSetupGuidance() {
|
|
44
|
+
const cfg = load() ?? {};
|
|
45
|
+
const network = cfg.network ?? "eth-mainnet";
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(` ${brand("\u25C6")} ${bold("Your configuration")}`);
|
|
48
|
+
printKeyValueBox([
|
|
49
|
+
["api-key", cfg.api_key ? maskIf(cfg.api_key) : dim("(not set)")],
|
|
50
|
+
["network", cfg.network ? network : `${network} ${dim("(default)")}`]
|
|
51
|
+
]);
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(` ${brand("\u25C6")} ${bold("Next steps")}`);
|
|
54
|
+
printKeyValueBox([
|
|
55
|
+
["Verify setup", "rpc eth_chainId"],
|
|
56
|
+
["Need a different chain?", "config set network <network>"],
|
|
57
|
+
["List available chains", "network list"],
|
|
58
|
+
["View set API key", "config get api-key"],
|
|
59
|
+
["Need help?", "help"]
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
async function runAPIKeyOnboarding() {
|
|
63
|
+
const key = await promptText({
|
|
64
|
+
message: "Enter API Key",
|
|
65
|
+
cancelMessage: "Skipped API key setup.",
|
|
66
|
+
clearAfterSubmit: true
|
|
67
|
+
});
|
|
68
|
+
if (!key || !key.trim()) return;
|
|
69
|
+
const cfg = load();
|
|
70
|
+
save({ ...cfg, api_key: key.trim() });
|
|
71
|
+
console.log(` ${green("\u2713")} Saved API key`);
|
|
72
|
+
}
|
|
73
|
+
async function runAccessKeyOnboarding() {
|
|
74
|
+
const key = await promptText({
|
|
75
|
+
message: "Alchemy access key",
|
|
76
|
+
placeholder: "Used for Admin API operations",
|
|
77
|
+
cancelMessage: "Skipped access key setup."
|
|
78
|
+
});
|
|
79
|
+
if (!key || !key.trim()) return;
|
|
80
|
+
const cfg = load();
|
|
81
|
+
save({ ...cfg, access_key: key.trim() });
|
|
82
|
+
console.log(` ${green("\u2713")} Saved access key`);
|
|
83
|
+
await selectOrCreateApp(new AdminClient(key.trim()));
|
|
84
|
+
}
|
|
85
|
+
async function runX402Onboarding() {
|
|
86
|
+
const action = await promptSelect({
|
|
87
|
+
message: "x402 wallet setup",
|
|
88
|
+
options: [
|
|
89
|
+
{ label: "Generate a new wallet", value: "generate" },
|
|
90
|
+
{ label: "Import wallet from key file", value: "import" }
|
|
91
|
+
],
|
|
92
|
+
initialValue: "generate",
|
|
93
|
+
cancelMessage: "Skipped x402 setup."
|
|
94
|
+
});
|
|
95
|
+
if (!action) return;
|
|
96
|
+
const wallet = action === "generate" ? generateAndPersistWallet() : await (async () => {
|
|
97
|
+
const path = await promptText({
|
|
98
|
+
message: "Wallet private key file path",
|
|
99
|
+
cancelMessage: "Skipped wallet import."
|
|
100
|
+
});
|
|
101
|
+
if (!path || !path.trim()) return null;
|
|
102
|
+
return importAndPersistWallet(path.trim());
|
|
103
|
+
})();
|
|
104
|
+
if (!wallet) return;
|
|
105
|
+
const cfg = load();
|
|
106
|
+
save({ ...cfg, x402: true });
|
|
107
|
+
console.log(` ${green("\u2713")} x402 enabled with wallet ${wallet.address}`);
|
|
108
|
+
}
|
|
109
|
+
async function runOnboarding(_program) {
|
|
110
|
+
process.stdout.write(brandedHelp({ force: true }));
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(` ${brand("\u25C6")} ${bold("Welcome to Alchemy CLI")}`);
|
|
113
|
+
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")}`);
|
|
114
|
+
console.log(` ${dim(" Let's get you set up with authentication.")}`);
|
|
115
|
+
console.log(` ${dim(" Choose one auth path to continue.")}`);
|
|
116
|
+
console.log(` ${dim(" Tip: select 'exit' to skip setup for now.")}`);
|
|
117
|
+
console.log("");
|
|
118
|
+
const method = await promptSelect({
|
|
119
|
+
message: "Choose an auth setup path",
|
|
120
|
+
options: [
|
|
121
|
+
{
|
|
122
|
+
label: "API key",
|
|
123
|
+
hint: "Query Alchemy RPC nodes",
|
|
124
|
+
value: "api-key"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "Access Key",
|
|
128
|
+
hint: "Admin API plus RPC nodes",
|
|
129
|
+
value: "access-key"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: "x402",
|
|
133
|
+
hint: "Agentic API access and payment",
|
|
134
|
+
value: "x402"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "exit",
|
|
138
|
+
value: "exit"
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
initialValue: "api-key",
|
|
142
|
+
cancelMessage: "Skipped onboarding."
|
|
143
|
+
});
|
|
144
|
+
if (!method) return false;
|
|
145
|
+
if (method === "exit") {
|
|
146
|
+
console.log(` ${dim("Exited onboarding.")}`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (method === "api-key") {
|
|
150
|
+
await runAPIKeyOnboarding();
|
|
151
|
+
const complete2 = Boolean(load().api_key?.trim());
|
|
152
|
+
if (!complete2) {
|
|
153
|
+
printNextSteps("api-key");
|
|
154
|
+
} else {
|
|
155
|
+
printAPIKeyPostSetupGuidance();
|
|
156
|
+
}
|
|
157
|
+
return complete2;
|
|
158
|
+
}
|
|
159
|
+
if (method === "access-key") {
|
|
160
|
+
await runAccessKeyOnboarding();
|
|
161
|
+
const cfg2 = load();
|
|
162
|
+
const complete2 = Boolean(cfg2.access_key?.trim() && cfg2.app?.id && cfg2.app.apiKey);
|
|
163
|
+
if (!complete2) {
|
|
164
|
+
printNextSteps("access-key");
|
|
165
|
+
}
|
|
166
|
+
return complete2;
|
|
167
|
+
}
|
|
168
|
+
await runX402Onboarding();
|
|
169
|
+
const cfg = load();
|
|
170
|
+
const complete = cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
|
|
171
|
+
if (!complete) {
|
|
172
|
+
printNextSteps("x402");
|
|
173
|
+
}
|
|
174
|
+
return complete;
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
runOnboarding
|
|
178
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alchemy/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Alchemy CLI — interact with blockchain data",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"alchemy": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"lint": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"alchemy",
|
|
20
|
+
"blockchain",
|
|
21
|
+
"ethereum",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/alchemyplatform/alchemy-cli.git"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@alchemy/x402": "^0.4.0",
|
|
41
|
+
"cli-table3": "^0.6.5",
|
|
42
|
+
"commander": "^14.0.3",
|
|
43
|
+
"zod": "^4.3.6"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.3.0",
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
48
|
+
"tsup": "^8.5.1",
|
|
49
|
+
"tsx": "^4.21.0",
|
|
50
|
+
"typescript": "^5.9.3",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
52
|
+
},
|
|
53
|
+
"packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd"
|
|
54
|
+
}
|