@clanker-chain/clanker-cli 2026.9.7-2 → 2026.9.7-4
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/bin/clanker.mjs +183 -42
- package/lib/doctor.mjs +185 -0
- package/lib/profile.mjs +7 -4
- package/lib/setup-detect.mjs +90 -2
- package/lib/setup.mjs +240 -253
- package/lib/ui.mjs +104 -0
- package/package.json +3 -1
package/lib/setup.mjs
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* Interactive / flag-driven `clanker setup`.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import { stdin as input } from "node:process";
|
|
7
6
|
import { existsSync } from "node:fs";
|
|
8
7
|
import { getAddress } from "viem";
|
|
8
|
+
import * as clack from "@clack/prompts";
|
|
9
9
|
import {
|
|
10
10
|
ANVIL_DEFAULT_ADDRESS,
|
|
11
11
|
PRESETS,
|
|
12
|
+
SEPOLIA_FAST_FROM_BLOCK,
|
|
12
13
|
clankerHome,
|
|
13
14
|
configPath,
|
|
14
15
|
initProfile,
|
|
@@ -22,11 +23,12 @@ import {
|
|
|
22
23
|
resolvePreferredOperator,
|
|
23
24
|
} from "./identity-query.mjs";
|
|
24
25
|
import {
|
|
25
|
-
SEPOLIA_FAST_FROM_BLOCK,
|
|
26
26
|
addressFromEnv,
|
|
27
27
|
addressFromKeyFile,
|
|
28
28
|
detectSetupHints,
|
|
29
|
+
formatSetupDetectTable,
|
|
29
30
|
} from "./setup-detect.mjs";
|
|
31
|
+
import { c, nextHint } from "./ui.mjs";
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* @param {string[]} argv
|
|
@@ -71,6 +73,14 @@ export function parseSetupFlags(argv) {
|
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
function cancelIf(value) {
|
|
77
|
+
if (clack.isCancel(value)) {
|
|
78
|
+
clack.cancel("Setup aborted.");
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
/**
|
|
75
85
|
* Validate address + label against registry (and Anvil-on-public).
|
|
76
86
|
* @param {{ rpc: string, registry: string, label: string, address: string, skipChainCheck?: boolean }} opts
|
|
@@ -100,7 +110,6 @@ export async function assertSetupIdentity(opts) {
|
|
|
100
110
|
const op = await readOperator(pub, opts.registry, opts.label);
|
|
101
111
|
|
|
102
112
|
if (op.registeredAt === 0n) {
|
|
103
|
-
// New operator — fine; mint later
|
|
104
113
|
return { address, operator: op, skipped: false, unregistered: true };
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -116,14 +125,12 @@ export async function assertSetupIdentity(opts) {
|
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
/**
|
|
119
|
-
* Build key pointer from flags / choices.
|
|
120
128
|
* @returns {{ type: 'env'|'keyFile', value: string }|null}
|
|
121
129
|
*/
|
|
122
130
|
export function buildKeyPointer({ keyFile, keyEnv, skipKey, env = process.env }) {
|
|
123
131
|
if (skipKey) return null;
|
|
124
132
|
if (keyFile) {
|
|
125
133
|
if (!existsSync(keyFile)) throw new Error(`Key file not found: ${keyFile}`);
|
|
126
|
-
// validate readable
|
|
127
134
|
addressFromKeyFile(keyFile);
|
|
128
135
|
return { type: "keyFile", value: keyFile };
|
|
129
136
|
}
|
|
@@ -175,8 +182,6 @@ export function applySetup(choices, opts = {}) {
|
|
|
175
182
|
|
|
176
183
|
/**
|
|
177
184
|
* Non-interactive setup (agents / CI).
|
|
178
|
-
* @param {string[]} argv
|
|
179
|
-
* @param {{ home?: string, env?: NodeJS.ProcessEnv, skipChainCheck?: boolean }} [opts]
|
|
180
185
|
*/
|
|
181
186
|
export async function runSetupNonInteractive(argv, opts = {}) {
|
|
182
187
|
const flags = parseSetupFlags(argv);
|
|
@@ -226,7 +231,6 @@ export async function runSetupNonInteractive(argv, opts = {}) {
|
|
|
226
231
|
env,
|
|
227
232
|
});
|
|
228
233
|
|
|
229
|
-
// Address from key must match --address when both set
|
|
230
234
|
if (flags.keyFile && flags.address) {
|
|
231
235
|
const fromKey = getAddress(addressFromKeyFile(flags.keyFile));
|
|
232
236
|
if (fromKey !== getAddress(flags.address)) {
|
|
@@ -251,7 +255,7 @@ export async function runSetupNonInteractive(argv, opts = {}) {
|
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
/**
|
|
254
|
-
* Interactive wizard when stdin is a TTY.
|
|
258
|
+
* Interactive wizard (Clack) when stdin is a TTY.
|
|
255
259
|
*/
|
|
256
260
|
export async function runSetupInteractive(argv, opts = {}) {
|
|
257
261
|
const flags = parseSetupFlags(argv);
|
|
@@ -265,273 +269,249 @@ export async function runSetupInteractive(argv, opts = {}) {
|
|
|
265
269
|
openclawDir: opts.openclawDir,
|
|
266
270
|
});
|
|
267
271
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
console.log("clanker setup — create your local operator profile");
|
|
286
|
-
console.log("");
|
|
287
|
-
console.log(
|
|
288
|
-
"This writes ~/.clanker/config.json (network) and operator.json (who you are).",
|
|
289
|
-
);
|
|
290
|
-
console.log(
|
|
291
|
-
"Reads like `clanker whoami` can use the owner address without a private key;",
|
|
292
|
-
);
|
|
293
|
-
console.log("mint/revoke still need a key file or OPERATOR_PRIVATE_KEY later.");
|
|
294
|
-
console.log("");
|
|
295
|
-
console.log(`Profile directory: ${home}`);
|
|
296
|
-
console.log("");
|
|
297
|
-
|
|
298
|
-
// Detection summary
|
|
299
|
-
console.log("Detected on this machine:");
|
|
300
|
-
if (hints.hasConfig) {
|
|
301
|
-
const p = hints.config?.preset ?? "?";
|
|
302
|
-
const reg = hints.config?.registryAddress ?? "(none)";
|
|
303
|
-
console.log(` • Network config already at config.json (preset=${p}, registry=${reg})`);
|
|
304
|
-
} else {
|
|
305
|
-
console.log(" • No config.json yet — will create one");
|
|
306
|
-
}
|
|
307
|
-
if (hints.hasOperator) {
|
|
308
|
-
console.log(
|
|
309
|
-
` • Operator profile already at operator.json (label=${hints.operator?.label}, owner=${hints.operator?.owner})`,
|
|
272
|
+
clack.intro(c.bold("clanker setup"));
|
|
273
|
+
clack.log.step("Creates ~/.clanker/config.json (network) and operator.json (identity).");
|
|
274
|
+
clack.log.message(
|
|
275
|
+
c.dim("whoami works from owner without a key; mint/revoke need a key pointer later."),
|
|
276
|
+
);
|
|
277
|
+
clack.log.message(c.dim(`Profile: ${home}`));
|
|
278
|
+
console.log("");
|
|
279
|
+
console.log(c.dim(formatSetupDetectTable(hints)));
|
|
280
|
+
console.log("");
|
|
281
|
+
|
|
282
|
+
if ((hints.hasConfig || hints.hasOperator) && !flags.force) {
|
|
283
|
+
if (hints.hasConfig && !hints.hasOperator) {
|
|
284
|
+
const cont = cancelIf(
|
|
285
|
+
await clack.confirm({
|
|
286
|
+
message: "Network config exists; continue to create operator.json?",
|
|
287
|
+
initialValue: true,
|
|
288
|
+
}),
|
|
310
289
|
);
|
|
290
|
+
if (!cont) {
|
|
291
|
+
clack.cancel("Aborted.");
|
|
292
|
+
process.exit(0);
|
|
293
|
+
}
|
|
294
|
+
flags.force = true;
|
|
311
295
|
} else {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
);
|
|
318
|
-
} else if (!hints.foundryAvailable) {
|
|
319
|
-
console.log(" • Foundry `cast` not on PATH (optional)");
|
|
320
|
-
}
|
|
321
|
-
if (hints.openclawBots.length) {
|
|
322
|
-
console.log(
|
|
323
|
-
` • OpenClaw bot key files: ${hints.openclawBots.join(", ")}`,
|
|
324
|
-
);
|
|
325
|
-
console.log(
|
|
326
|
-
" (These are bot signing keys — not your operator wallet. Useful context only.)",
|
|
296
|
+
const overwrite = cancelIf(
|
|
297
|
+
await clack.confirm({
|
|
298
|
+
message: "Replace existing config.json / operator.json?",
|
|
299
|
+
initialValue: false,
|
|
300
|
+
}),
|
|
327
301
|
);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
` • OPERATOR_PRIVATE_KEY is set in this shell → ${hints.envAddress ?? "(invalid key)"}`,
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
console.log("");
|
|
335
|
-
|
|
336
|
-
if ((hints.hasConfig || hints.hasOperator) && !flags.force) {
|
|
337
|
-
if (hints.hasConfig && !hints.hasOperator) {
|
|
338
|
-
console.log(
|
|
339
|
-
"You already have network settings from `clanker init`, but no operator identity.",
|
|
340
|
-
);
|
|
341
|
-
console.log(
|
|
342
|
-
"Continuing will refresh config.json if needed and create operator.json.",
|
|
343
|
-
);
|
|
344
|
-
const cont = await askYesNo("Continue setup?", true);
|
|
345
|
-
if (!cont) {
|
|
346
|
-
throw new Error("Aborted. Re-run `clanker setup` when ready.");
|
|
347
|
-
}
|
|
348
|
-
flags.force = true;
|
|
349
|
-
} else {
|
|
350
|
-
console.log(
|
|
351
|
-
"A full profile already exists. Continuing will REPLACE config.json and/or operator.json",
|
|
352
|
-
);
|
|
353
|
-
console.log("with the answers you give next (same files, new contents).");
|
|
354
|
-
const overwrite = await askYesNo("Replace existing profile files?", false);
|
|
355
|
-
if (!overwrite) {
|
|
356
|
-
throw new Error(
|
|
357
|
-
"Aborted. Pass --force to replace, or edit ~/.clanker/*.json by hand.",
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
flags.force = true;
|
|
302
|
+
if (!overwrite) {
|
|
303
|
+
clack.cancel("Aborted. Pass --force to replace.");
|
|
304
|
+
process.exit(0);
|
|
361
305
|
}
|
|
362
|
-
|
|
306
|
+
flags.force = true;
|
|
363
307
|
}
|
|
308
|
+
}
|
|
364
309
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
310
|
+
let preset = flags.preset || hints.config?.preset || null;
|
|
311
|
+
if (!preset) {
|
|
312
|
+
preset = cancelIf(
|
|
313
|
+
await clack.select({
|
|
314
|
+
message: "Network",
|
|
315
|
+
options: [
|
|
316
|
+
{ value: "sepolia", label: "sepolia", hint: "public closed-beta hub" },
|
|
317
|
+
{ value: "local", label: "local", hint: "Anvil" },
|
|
318
|
+
],
|
|
319
|
+
initialValue: "sepolia",
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
preset = String(preset).toLowerCase();
|
|
324
|
+
if (!PRESETS[preset]) throw new Error(`Unknown preset "${preset}"`);
|
|
325
|
+
|
|
326
|
+
// Fast Sepolia default — no interactive fromBlock question
|
|
327
|
+
let fromBlock = flags.fromBlock;
|
|
328
|
+
if (fromBlock == null && preset === "sepolia") {
|
|
329
|
+
fromBlock = SEPOLIA_FAST_FROM_BLOCK;
|
|
330
|
+
}
|
|
386
331
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
332
|
+
let address = flags.address ?? null;
|
|
333
|
+
if (!address && flags.keyFile) {
|
|
334
|
+
address = addressFromKeyFile(flags.keyFile);
|
|
335
|
+
clack.log.info(`Address from --key-file: ${address}`);
|
|
336
|
+
}
|
|
337
|
+
if (!address && hints.envAddress) {
|
|
338
|
+
const useEnv = cancelIf(
|
|
339
|
+
await clack.confirm({
|
|
340
|
+
message: `Use OPERATOR_PRIVATE_KEY address (${hints.envAddress})?`,
|
|
341
|
+
initialValue: true,
|
|
342
|
+
}),
|
|
391
343
|
);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
344
|
+
if (useEnv) address = hints.envAddress;
|
|
345
|
+
}
|
|
346
|
+
if (!address && hints.operator?.owner) {
|
|
347
|
+
const useOp = cancelIf(
|
|
348
|
+
await clack.confirm({
|
|
349
|
+
message: `Keep existing owner (${hints.operator.owner})?`,
|
|
350
|
+
initialValue: true,
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
if (useOp) address = hints.operator.owner;
|
|
354
|
+
}
|
|
355
|
+
if (!address && hints.foundryAccounts.length) {
|
|
356
|
+
const options = [
|
|
357
|
+
...hints.foundryAccounts.map((n) => ({
|
|
358
|
+
value: n,
|
|
359
|
+
label: n,
|
|
360
|
+
hint: "Foundry keystore — paste 0x next",
|
|
361
|
+
})),
|
|
362
|
+
{ value: "__paste__", label: "Paste an address…", hint: "0x…" },
|
|
363
|
+
];
|
|
364
|
+
const pick = cancelIf(
|
|
365
|
+
await clack.select({
|
|
366
|
+
message: "Operator owner source",
|
|
367
|
+
options,
|
|
368
|
+
initialValue: hints.foundryAccounts[0],
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
if (pick === "__paste__") {
|
|
372
|
+
address = cancelIf(
|
|
373
|
+
await clack.text({
|
|
374
|
+
message: "Operator owner address",
|
|
375
|
+
placeholder: "0x…",
|
|
376
|
+
validate: (v) =>
|
|
377
|
+
/^0x[0-9a-fA-F]{40}$/.test(v || "") ? undefined : "Need 0x + 40 hex",
|
|
378
|
+
}),
|
|
408
379
|
);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
hints.foundryAccounts[0] ?? "",
|
|
380
|
+
} else {
|
|
381
|
+
address = cancelIf(
|
|
382
|
+
await clack.text({
|
|
383
|
+
message: `Paste 0x address for Foundry account "${pick}"`,
|
|
384
|
+
placeholder: "0x…",
|
|
385
|
+
validate: (v) =>
|
|
386
|
+
/^0x[0-9a-fA-F]{40}$/.test(v || "") ? undefined : "Need 0x + 40 hex",
|
|
387
|
+
}),
|
|
418
388
|
);
|
|
419
|
-
if (pick) {
|
|
420
|
-
address = await ask(
|
|
421
|
-
`Paste the 0x address for "${pick}" (cast wallet address ${pick} after unlock)`,
|
|
422
|
-
"",
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!address) {
|
|
427
|
-
address = await ask("Operator owner address (0x…)", "");
|
|
428
389
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
390
|
+
}
|
|
391
|
+
if (!address) {
|
|
392
|
+
address = cancelIf(
|
|
393
|
+
await clack.text({
|
|
394
|
+
message: "Operator owner address",
|
|
395
|
+
placeholder: "0x…",
|
|
396
|
+
validate: (v) =>
|
|
397
|
+
/^0x[0-9a-fA-F]{40}$/.test(v || "") ? undefined : "Need 0x + 40 hex",
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
address = getAddress(address);
|
|
402
|
+
|
|
403
|
+
let label = flags.operator || hints.operator?.label || null;
|
|
404
|
+
if (!label) {
|
|
405
|
+
label = cancelIf(
|
|
406
|
+
await clack.text({
|
|
407
|
+
message: "Operator label",
|
|
408
|
+
placeholder: "org.openclaw.pat",
|
|
409
|
+
validate: (v) => (v && v.trim() ? undefined : "Label required"),
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const presetCfg = PRESETS[preset];
|
|
415
|
+
const spin = clack.spinner();
|
|
416
|
+
spin.start(`Looking up "${label}" on ${preset}`);
|
|
417
|
+
let check;
|
|
418
|
+
try {
|
|
419
|
+
check = await assertSetupIdentity({
|
|
445
420
|
rpc: presetCfg.chainRpcUrl,
|
|
446
421
|
registry: presetCfg.registryAddress,
|
|
447
422
|
label,
|
|
448
423
|
address,
|
|
449
424
|
skipChainCheck: flags.skipChainCheck || !presetCfg.registryAddress,
|
|
450
425
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
`
|
|
426
|
+
spin.stop(
|
|
427
|
+
check.unregistered
|
|
428
|
+
? c.yellow(`"${label}" not registered yet — mint after setup`)
|
|
429
|
+
: check.skipped
|
|
430
|
+
? "Chain check skipped"
|
|
431
|
+
: c.green(`"${label}" active and owned by this address`),
|
|
432
|
+
);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
spin.stop(c.red("Chain check failed"));
|
|
435
|
+
throw err;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let keyFile = flags.keyFile;
|
|
439
|
+
let keyEnv = flags.keyEnv;
|
|
440
|
+
let skipKey = flags.skipKey;
|
|
441
|
+
if (!skipKey && !keyFile && !keyEnv) {
|
|
442
|
+
if (hints.hasOperatorPrivateKeyEnv) {
|
|
443
|
+
const use = cancelIf(
|
|
444
|
+
await clack.confirm({
|
|
445
|
+
message: "Store OPERATOR_PRIVATE_KEY pointer for signing?",
|
|
446
|
+
initialValue: true,
|
|
447
|
+
}),
|
|
454
448
|
);
|
|
455
|
-
|
|
456
|
-
|
|
449
|
+
if (use) keyEnv = "OPERATOR_PRIVATE_KEY";
|
|
450
|
+
else skipKey = true;
|
|
451
|
+
} else {
|
|
452
|
+
const path = cancelIf(
|
|
453
|
+
await clack.text({
|
|
454
|
+
message: "Operator key file path (Enter = read-only)",
|
|
455
|
+
placeholder: "~/.clanker/op.key",
|
|
456
|
+
}),
|
|
457
|
+
);
|
|
458
|
+
if (path && String(path).trim()) keyFile = String(path).trim();
|
|
459
|
+
else skipKey = true;
|
|
457
460
|
}
|
|
461
|
+
}
|
|
458
462
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
let keyFile = flags.keyFile;
|
|
465
|
-
let keyEnv = flags.keyEnv;
|
|
466
|
-
let skipKey = flags.skipKey;
|
|
467
|
-
if (!skipKey && !keyFile && !keyEnv) {
|
|
468
|
-
if (hints.hasOperatorPrivateKeyEnv) {
|
|
469
|
-
const use = await askYesNo(
|
|
470
|
-
"Remember OPERATOR_PRIVATE_KEY as the signing pointer in operator.json?",
|
|
471
|
-
true,
|
|
472
|
-
);
|
|
473
|
-
if (use) keyEnv = "OPERATOR_PRIVATE_KEY";
|
|
474
|
-
else skipKey = true;
|
|
475
|
-
} else {
|
|
476
|
-
const path = await ask(
|
|
477
|
-
"Path to operator private-key file (blank = read-only profile)",
|
|
478
|
-
"",
|
|
479
|
-
);
|
|
480
|
-
if (path) keyFile = path;
|
|
481
|
-
else skipKey = true;
|
|
482
|
-
}
|
|
463
|
+
const key = buildKeyPointer({ keyFile, keyEnv, skipKey, env });
|
|
464
|
+
if (key?.type === "keyFile") {
|
|
465
|
+
const fromKey = getAddress(addressFromKeyFile(key.value));
|
|
466
|
+
if (fromKey !== address) {
|
|
467
|
+
throw new Error(`Key file address ${fromKey} does not match chosen owner ${address}`);
|
|
483
468
|
}
|
|
469
|
+
}
|
|
484
470
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
471
|
+
clack.note(
|
|
472
|
+
[
|
|
473
|
+
`network: ${preset}`,
|
|
474
|
+
`fromBlock: ${fromBlock ?? presetCfg.fromBlock}`,
|
|
475
|
+
`label: ${label}`,
|
|
476
|
+
`owner: ${address}`,
|
|
477
|
+
`signing: ${key ? `${key.type}=${key.value}` : "none (read-only)"}`,
|
|
478
|
+
].join("\n"),
|
|
479
|
+
"Summary",
|
|
480
|
+
);
|
|
492
481
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
console.log(` fromBlock:${fromBlock ?? presetCfg.fromBlock}`);
|
|
497
|
-
console.log(` label: ${label}`);
|
|
498
|
-
console.log(` owner: ${address}`);
|
|
499
|
-
console.log(
|
|
500
|
-
` signing: ${key ? `${key.type}=${key.value}` : "none (read-only whoami/bots)"}`,
|
|
501
|
-
);
|
|
502
|
-
const ok = flags.yes || (await askYesNo("Write these files now?", true));
|
|
503
|
-
if (!ok) throw new Error("Aborted");
|
|
504
|
-
|
|
505
|
-
const result = applySetup(
|
|
506
|
-
{
|
|
507
|
-
preset,
|
|
508
|
-
force: true,
|
|
509
|
-
fromBlock: fromBlock ?? undefined,
|
|
510
|
-
registryAddress: presetCfg.registryAddress,
|
|
511
|
-
label,
|
|
512
|
-
address,
|
|
513
|
-
key,
|
|
514
|
-
},
|
|
515
|
-
{ home, env },
|
|
482
|
+
if (!flags.yes) {
|
|
483
|
+
const ok = cancelIf(
|
|
484
|
+
await clack.confirm({ message: "Write these files?", initialValue: true }),
|
|
516
485
|
);
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
console.log(`Wrote ${result.operatorPath}`);
|
|
521
|
-
if (!key) {
|
|
522
|
-
console.log("");
|
|
523
|
-
console.log("Next: clanker whoami");
|
|
524
|
-
console.log(
|
|
525
|
-
"For mint/revoke later: re-run setup with a key file, or pass --key-file / OPERATOR_PRIVATE_KEY.",
|
|
526
|
-
);
|
|
527
|
-
} else {
|
|
528
|
-
console.log("");
|
|
529
|
-
console.log("Next: clanker whoami");
|
|
486
|
+
if (!ok) {
|
|
487
|
+
clack.cancel("Aborted.");
|
|
488
|
+
process.exit(0);
|
|
530
489
|
}
|
|
531
|
-
return result;
|
|
532
|
-
} finally {
|
|
533
|
-
if (!opts.rl) rl.close();
|
|
534
490
|
}
|
|
491
|
+
|
|
492
|
+
const result = applySetup(
|
|
493
|
+
{
|
|
494
|
+
preset,
|
|
495
|
+
force: true,
|
|
496
|
+
fromBlock: fromBlock ?? undefined,
|
|
497
|
+
registryAddress: presetCfg.registryAddress,
|
|
498
|
+
label,
|
|
499
|
+
address,
|
|
500
|
+
key,
|
|
501
|
+
},
|
|
502
|
+
{ home, env },
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
clack.outro(c.green(`Wrote ${result.configPath}\nWrote ${result.operatorPath}`));
|
|
506
|
+
if (!key) {
|
|
507
|
+
nextHint([
|
|
508
|
+
"clanker whoami",
|
|
509
|
+
"clanker setup --key-file ~/.clanker/op.key --force # when you need mint",
|
|
510
|
+
]);
|
|
511
|
+
} else {
|
|
512
|
+
nextHint(["clanker whoami", `clanker bot mint <label>`]);
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
535
515
|
}
|
|
536
516
|
|
|
537
517
|
/**
|
|
@@ -541,7 +521,6 @@ export async function runSetup(argv, opts = {}) {
|
|
|
541
521
|
const flags = parseSetupFlags(argv);
|
|
542
522
|
const isTTY = opts.isTTY ?? Boolean(input.isTTY);
|
|
543
523
|
|
|
544
|
-
// Fully flagged non-interactive path
|
|
545
524
|
if (
|
|
546
525
|
!isTTY ||
|
|
547
526
|
(flags.yes && flags.preset && flags.operator && (flags.address || flags.keyFile))
|
|
@@ -549,7 +528,15 @@ export async function runSetup(argv, opts = {}) {
|
|
|
549
528
|
if (!isTTY && !(flags.preset && flags.operator)) {
|
|
550
529
|
return runSetupNonInteractive(argv, opts);
|
|
551
530
|
}
|
|
552
|
-
if (
|
|
531
|
+
if (
|
|
532
|
+
flags.yes &&
|
|
533
|
+
flags.preset &&
|
|
534
|
+
flags.operator &&
|
|
535
|
+
(flags.address ||
|
|
536
|
+
flags.keyFile ||
|
|
537
|
+
opts.env?.OPERATOR_PRIVATE_KEY ||
|
|
538
|
+
process.env.OPERATOR_PRIVATE_KEY)
|
|
539
|
+
) {
|
|
553
540
|
return runSetupNonInteractive(argv, opts);
|
|
554
541
|
}
|
|
555
542
|
}
|