@clanker-chain/clanker-cli 2026.9.7-4 → 2026.9.7

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 CHANGED
@@ -23,15 +23,7 @@ import {
23
23
  readBot,
24
24
  resolvePreferredOperator,
25
25
  } from "../lib/identity-query.mjs";
26
- import { resolveForRead, resolveOperatorKey, resolveReadIdentity } from "../lib/resolve.mjs";
27
- import { runSetup } from "../lib/setup.mjs";
28
- import { runDoctor } from "../lib/doctor.mjs";
29
- import {
30
- c,
31
- confirmPlan,
32
- exitCliError,
33
- nextHint,
34
- } from "../lib/ui.mjs";
26
+ import { resolveForRead, resolveOperatorKey } from "../lib/resolve.mjs";
35
27
 
36
28
  function resolveFoundryBinary(name) {
37
29
  const home = os.homedir();
@@ -152,18 +144,16 @@ function usage() {
152
144
  console.log(`clanker - clanker-chain helper CLI
153
145
 
154
146
  Usage:
155
- clanker setup [--preset sepolia|local] [--operator <label>] [--address 0x…] [--key-file path] [--force]
156
- clanker doctor [--json]
157
147
  clanker init --preset sepolia|local [--force]
158
- clanker whoami [--json] [--operator <label>] [--address 0x…] [--with-bots]
159
- clanker operator mint <label> [--json] [--yes]
160
- clanker operator transfer propose <label> <newOwner> [--yes]
161
- clanker operator transfer accept <label> [--yes]
162
- clanker bot mint <label> [operator] [--json] [--yes]
148
+ clanker whoami [--json] [--operator <label>] [--address 0x…]
149
+ clanker operator mint <label> [--json]
150
+ clanker operator transfer propose <label> <newOwner>
151
+ clanker operator transfer accept <label>
152
+ clanker bot mint <label> [operator] [--json]
163
153
  clanker bots [--json] [--operator <label>] [--address 0x…]
164
154
  clanker bot status <label> [--json]
165
- clanker bot revoke <label> [--json] [--yes]
166
- clanker bot rotate <label> <newKeyAddress> [--json] [--yes]
155
+ clanker bot revoke <label> [--json]
156
+ clanker bot rotate <label> <newKeyAddress> [--json]
167
157
  clanker init-openclaw
168
158
  clanker chain up|deploy|mint-operator|mint-bot|rotate-bot-key|revoke-bot ...
169
159
  clanker check mqtt <bot_id> <operator_id>
@@ -171,12 +161,12 @@ Usage:
171
161
 
172
162
  Profile:
173
163
  ~/.clanker/config.json network preset (registry, RPC, broker URLs)
174
- ~/.clanker/operator.json label + owner + optional key pointer (never raw hex)
164
+ ~/.clanker/operator.json label + owner + key pointer (never raw hex)
175
165
  ~/.clanker/keys/ bot keys (also written to ~/.openclaw/keys/)
176
166
 
177
- Humans: \`clanker setup\` then \`clanker doctor\` / \`whoami\`.
178
- Mutates print a plan and confirm unless --yes or --json.
179
- whoami is fast by default; pass --with-bots to enrich child bots (or use \`clanker bots\`).
167
+ Key resolution (mutating commands):
168
+ --key > --key-file > OPERATOR_PRIVATE_KEY > profile keyFile > profile env
169
+ Anvil account #0 is allowed only on localhost RPC.
180
170
 
181
171
  See docs/operator-cli.md.
182
172
  `);
@@ -223,20 +213,18 @@ async function cmdWhoami(argv) {
223
213
  let address;
224
214
  let source;
225
215
  let network;
226
- try {
227
- const resolved = resolveReadIdentity(argv);
216
+ const addressFlag = flagValue(argv, "--address");
217
+ if (addressFlag) {
218
+ network = resolveForRead(argv);
219
+ address = getAddress(addressFlag);
220
+ source = "--address";
221
+ } else {
222
+ const resolved = resolveOperatorKey(argv);
228
223
  address = resolved.address;
229
224
  source = resolved.source;
230
225
  network = resolved.network;
231
- } catch (err) {
232
- exitCliError({
233
- error: err.message,
234
- because: "whoami needs an owner address from --address, a key, or operator.json",
235
- try: ["clanker setup", "clanker doctor", "clanker whoami --address 0x…"],
236
- });
237
226
  }
238
227
 
239
- const withBots = hasFlag(argv, "--with-bots");
240
228
  const pub = await publicClientFromRpc(network.rpc);
241
229
  const preferred =
242
230
  flagValue(argv, "--operator") ?? loadOperator(network.home)?.label ?? null;
@@ -249,14 +237,9 @@ async function cmdWhoami(argv) {
249
237
  owner: getAddress(address),
250
238
  });
251
239
  if (resolved.error) {
252
- exitCliError({
253
- error: resolved.error,
254
- because: "preferred operator label did not match this owner on-chain",
255
- try: [
256
- "clanker whoami --address 0x…",
257
- "clanker setup --force # fix label/owner",
258
- ],
259
- });
240
+ const err = new Error(resolved.error);
241
+ err.candidates = resolved.candidates;
242
+ throw err;
260
243
  }
261
244
  selected = [resolved.operator];
262
245
  } else {
@@ -267,14 +250,12 @@ async function cmdWhoami(argv) {
267
250
  });
268
251
  }
269
252
 
270
- if (withBots) {
271
- for (const op of selected) {
272
- op.bots = await findBotsByOperator(pub, {
273
- registry: network.registry,
274
- operatorId: op.id,
275
- fromBlock: network.fromBlock,
276
- });
277
- }
253
+ for (const op of selected) {
254
+ op.bots = await findBotsByOperator(pub, {
255
+ registry: network.registry,
256
+ operatorId: op.id,
257
+ fromBlock: network.fromBlock,
258
+ });
278
259
  }
279
260
 
280
261
  const data = {
@@ -290,38 +271,31 @@ async function cmdWhoami(argv) {
290
271
  active: o.active,
291
272
  registeredAt: o.registeredAt.toString(),
292
273
  revokedAt: o.revokedAt.toString(),
293
- bots: withBots
294
- ? (o.bots ?? []).map((b) => ({
295
- label: b.label,
296
- botKey: b.botKey,
297
- active: b.active,
298
- registeredAt: b.registeredAt.toString(),
299
- revokedAt: b.revokedAt.toString(),
300
- }))
301
- : [],
274
+ bots: (o.bots ?? []).map((b) => ({
275
+ label: b.label,
276
+ botKey: b.botKey,
277
+ active: b.active,
278
+ registeredAt: b.registeredAt.toString(),
279
+ revokedAt: b.revokedAt.toString(),
280
+ })),
302
281
  })),
303
282
  };
304
283
 
305
284
  if (hasFlag(argv, "--json")) printJson(data);
306
- else {
307
- printHumanWhoami(data);
308
- if (!withBots && data.operators.length) {
309
- console.log(c.dim("(bots omitted — pass --with-bots or run clanker bots)"));
310
- }
311
- nextHint(["clanker bots", "clanker doctor"]);
312
- }
285
+ else printHumanWhoami(data);
313
286
  }
314
287
 
315
288
  async function cmdBots(argv) {
316
289
  let address;
317
290
  let network;
318
- try {
319
- const resolved = resolveReadIdentity(argv);
291
+ const addressFlag = flagValue(argv, "--address");
292
+ if (addressFlag) {
293
+ network = resolveForRead(argv);
294
+ address = getAddress(addressFlag);
295
+ } else {
296
+ const resolved = resolveOperatorKey(argv);
320
297
  address = resolved.address;
321
298
  network = resolved.network;
322
- } catch (err) {
323
- console.error(err.message);
324
- process.exit(1);
325
299
  }
326
300
  const { label, pub } = await resolveOperatorLabel(argv, address, network);
327
301
  const bots = await findBotsByOperator(pub, {
@@ -383,27 +357,6 @@ async function main() {
383
357
 
384
358
  const repoRoot = findRepoRoot();
385
359
 
386
- if (cmd === "setup") {
387
- try {
388
- await runSetup(rest);
389
- } catch (err) {
390
- exitCliError({
391
- error: err.message,
392
- because: "setup could not write a valid local profile",
393
- try: [
394
- "clanker setup --preset sepolia --operator org.you --address 0x… --yes --force",
395
- "clanker doctor",
396
- ],
397
- });
398
- }
399
- return;
400
- }
401
-
402
- if (cmd === "doctor") {
403
- const { exitCode } = await runDoctor(rest);
404
- process.exit(exitCode);
405
- }
406
-
407
360
  if (cmd === "init") {
408
361
  const preset = flagValue(rest, "--preset") ?? rest.find((a) => !a.startsWith("--"));
409
362
  if (!preset || preset === "init") {
@@ -450,38 +403,13 @@ async function main() {
450
403
  console.error("Usage: clanker operator mint <label>");
451
404
  process.exit(1);
452
405
  }
453
- let planRows;
454
- try {
455
- const preview = resolveOperatorKey(flags);
456
- planRows = [
457
- ["action", "registerOperator"],
458
- ["label", label],
459
- ["owner", preview.address],
460
- ["rpc", preview.network.rpc],
461
- ["registry", preview.network.registry ?? "(none)"],
462
- ["key", preview.source],
463
- ];
464
- } catch (err) {
465
- exitCliError({
466
- error: err.message,
467
- because: "operator mint needs a signing key on this network",
468
- try: [
469
- "export OPERATOR_PRIVATE_KEY=0x…",
470
- "clanker operator mint " + label + " --key-file ~/.clanker/op.key --yes",
471
- "clanker doctor",
472
- ],
473
- });
474
- }
475
- const ok = await confirmPlan(flags, planRows, `Mint operator ${label}?`);
476
- if (!ok) process.exit(0);
477
406
  const result = await chainMintOperator(label, flags);
478
407
  if (hasFlag(flags, "--json") || hasFlag(opArgv, "--json")) printJson(result);
479
408
  else {
480
- console.log(c.green(`Minted operator ${label}`));
409
+ console.log(`Minted operator ${label}`);
481
410
  console.log(`owner: ${result.owner}`);
482
411
  console.log(`tx: ${result.tx}`);
483
412
  console.log(`Wrote ~/.clanker/operator.json`);
484
- nextHint([`clanker bot mint <bot_label>`, "clanker whoami"]);
485
413
  }
486
414
  return;
487
415
  }
@@ -493,23 +421,9 @@ async function main() {
493
421
  console.error("Usage: clanker operator transfer propose <label> <newOwner>");
494
422
  process.exit(1);
495
423
  }
496
- const ok = await confirmPlan(
497
- flags,
498
- [
499
- ["action", "proposeOperatorTransfer"],
500
- ["label", label],
501
- ["newOwner", getAddress(newOwner)],
502
- ],
503
- `Propose transfer of ${label}?`,
504
- );
505
- if (!ok) process.exit(0);
506
424
  const result = await chainProposeOperatorTransfer(label, getAddress(newOwner), flags);
507
425
  if (hasFlag(flags, "--json")) printJson(result);
508
- else {
509
- console.log(c.green(`Proposed transfer of ${label} → ${newOwner}`));
510
- console.log(`tx: ${result.tx}`);
511
- nextHint([`clanker operator transfer accept ${label} # as new owner`]);
512
- }
426
+ else console.log(`Proposed transfer of ${label} → ${newOwner}\ntx: ${result.tx}`);
513
427
  return;
514
428
  }
515
429
  if (action === "accept") {
@@ -517,23 +431,9 @@ async function main() {
517
431
  console.error("Usage: clanker operator transfer accept <label>");
518
432
  process.exit(1);
519
433
  }
520
- const ok = await confirmPlan(
521
- flags,
522
- [
523
- ["action", "acceptOperatorTransfer"],
524
- ["label", label],
525
- ],
526
- `Accept transfer of ${label}?`,
527
- );
528
- if (!ok) process.exit(0);
529
434
  const result = await chainAcceptOperatorTransfer(label, flags);
530
435
  if (hasFlag(flags, "--json")) printJson(result);
531
- else {
532
- console.log(c.green(`Accepted transfer of ${label}`));
533
- console.log(`owner: ${result.owner}`);
534
- console.log(`tx: ${result.tx}`);
535
- nextHint(["clanker whoami", "clanker bots"]);
536
- }
436
+ else console.log(`Accepted transfer of ${label}\nowner: ${result.owner}\ntx: ${result.tx}`);
537
437
  return;
538
438
  }
539
439
  console.error("Usage: clanker operator transfer propose|accept ...");
@@ -573,45 +473,20 @@ async function main() {
573
473
  }
574
474
  let operatorLabel = operatorArg ?? flagValue(flags, "--operator") ?? null;
575
475
  if (!operatorLabel) {
576
- try {
577
- const { address, network } = resolveOperatorKey(flags);
578
- const inferred = await resolveOperatorLabel(flags, address, network);
579
- operatorLabel = inferred.label;
580
- } catch (err) {
581
- exitCliError({
582
- error: err.message,
583
- because: "bot mint needs an operator label or a resolvable signing key",
584
- try: [
585
- `clanker bot mint ${botLabel} <operator_label> --yes`,
586
- "clanker setup",
587
- ],
588
- });
589
- }
476
+ const { address, network } = resolveOperatorKey(flags);
477
+ const inferred = await resolveOperatorLabel(flags, address, network);
478
+ operatorLabel = inferred.label;
590
479
  }
591
- const ok = await confirmPlan(
592
- flags,
593
- [
594
- ["action", "registerBot"],
595
- ["bot", botLabel],
596
- ["operator", operatorLabel],
597
- ],
598
- `Mint bot ${botLabel} under ${operatorLabel}?`,
599
- );
600
- if (!ok) process.exit(0);
601
480
  const result = await chainMintBot(botLabel, operatorLabel, flags);
602
481
  if (hasFlag(flags, "--json")) printJson(result);
603
482
  else {
604
- console.log(c.green(`Minted bot ${botLabel} under ${operatorLabel}`));
483
+ console.log(`Minted bot ${botLabel} under ${operatorLabel}`);
605
484
  console.log(`botKey: ${result.bot_key}`);
606
485
  console.log(`key file: ${result.key_path}`);
607
486
  console.log(`also: ${result.clanker_key_path}`);
608
487
  console.log(`tx: ${result.tx}`);
609
488
  console.log("\nchannels.mqtt stub:");
610
489
  console.log(JSON.stringify(result.channels_mqtt, null, 2));
611
- nextHint([
612
- "Paste channels.mqtt into openclaw.json",
613
- "openclaw plugins install @clanker-chain/mqtt-channel-plugin@…",
614
- ]);
615
490
  }
616
491
  return;
617
492
  }
@@ -622,22 +497,9 @@ async function main() {
622
497
  console.error("Usage: clanker bot revoke <label>");
623
498
  process.exit(1);
624
499
  }
625
- const ok = await confirmPlan(
626
- flags,
627
- [
628
- ["action", "revokeBot"],
629
- ["bot", botLabel],
630
- ],
631
- `Revoke bot ${botLabel}?`,
632
- );
633
- if (!ok) process.exit(0);
634
500
  const result = await chainRevokeBot(botLabel, flags);
635
501
  if (hasFlag(flags, "--json")) printJson(result);
636
- else {
637
- console.log(c.yellow(`Revoked ${botLabel}`));
638
- console.log(`tx: ${result.tx}`);
639
- nextHint(["clanker bots", "clanker bot status " + botLabel]);
640
- }
502
+ else console.log(`Revoked ${botLabel}\ntx: ${result.tx}`);
641
503
  return;
642
504
  }
643
505
 
@@ -647,23 +509,9 @@ async function main() {
647
509
  console.error("Usage: clanker bot rotate <label> <newKeyAddress>");
648
510
  process.exit(1);
649
511
  }
650
- const ok = await confirmPlan(
651
- flags,
652
- [
653
- ["action", "rotateBotKey"],
654
- ["bot", botLabel],
655
- ["newKey", getAddress(newKey)],
656
- ],
657
- `Rotate key for ${botLabel}?`,
658
- );
659
- if (!ok) process.exit(0);
660
512
  const result = await chainRotateBotKey(botLabel, getAddress(newKey), flags);
661
513
  if (hasFlag(flags, "--json")) printJson(result);
662
- else {
663
- console.log(c.green(`Rotated ${botLabel} → ${newKey}`));
664
- console.log(`tx: ${result.tx}`);
665
- nextHint(["Update ~/.openclaw/keys/" + botLabel + ".key", "clanker bot status " + botLabel]);
666
- }
514
+ else console.log(`Rotated ${botLabel} → ${newKey}\ntx: ${result.tx}`);
667
515
  return;
668
516
  }
669
517
 
package/lib/profile.mjs CHANGED
@@ -17,14 +17,11 @@ export const ANVIL_DEFAULT_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
17
17
  export const SEPOLIA_REGISTRY = "0xD650467f9D7A20f37E55ec23Ca1c711598f97958";
18
18
 
19
19
  /**
20
- * Safe floor for Sepolia getLogs (before registry deploy). Use for full historical
21
- * scans via --from-block; default preset uses the faster public-RPC floor below.
20
+ * Safe floor for Sepolia getLogs (before registry deploy). Documented so scans
21
+ * stay cheap; bump only if you redeploy the registry earlier.
22
22
  */
23
23
  export const SEPOLIA_FROM_BLOCK = 35_000_000n;
24
24
 
25
- /** Default Sepolia fromBlock for public RPC (faster whoami/bots scans). */
26
- export const SEPOLIA_FAST_FROM_BLOCK = 46_000_000n;
27
-
28
25
  export const PRESETS = {
29
26
  local: {
30
27
  preset: "local",
@@ -40,7 +37,7 @@ export const PRESETS = {
40
37
  chainRpcUrl: "https://sepolia.base.org",
41
38
  brokerUrl: "mqtts://mqtt.clanker-chain.com:8883",
42
39
  mqttAuthServiceUrl: "https://mqtt-auth.clanker-chain.com",
43
- fromBlock: SEPOLIA_FAST_FROM_BLOCK,
40
+ fromBlock: SEPOLIA_FROM_BLOCK,
44
41
  },
45
42
  };
46
43
 
@@ -66,7 +63,7 @@ export function openclawKeysDir() {
66
63
 
67
64
  /**
68
65
  * @param {string} presetName
69
- * @param {{ force?: boolean, registryAddress?: string|null, home?: string, fromBlock?: bigint|string|number }} [opts]
66
+ * @param {{ force?: boolean, registryAddress?: string|null, home?: string }} [opts]
70
67
  */
71
68
  export function initProfile(presetName, opts = {}) {
72
69
  const name = String(presetName || "").toLowerCase();
@@ -82,15 +79,13 @@ export function initProfile(presetName, opts = {}) {
82
79
  }
83
80
  mkdirSync(home, { recursive: true });
84
81
  const preset = PRESETS[name];
85
- const fromBlock =
86
- opts.fromBlock != null ? BigInt(opts.fromBlock) : preset.fromBlock;
87
82
  const config = {
88
83
  preset: preset.preset,
89
84
  registryAddress: opts.registryAddress ?? preset.registryAddress,
90
85
  chainRpcUrl: preset.chainRpcUrl,
91
86
  brokerUrl: preset.brokerUrl,
92
87
  mqttAuthServiceUrl: preset.mqttAuthServiceUrl,
93
- fromBlock: fromBlock.toString(),
88
+ fromBlock: preset.fromBlock.toString(),
94
89
  };
95
90
  if (name === "local" && !config.registryAddress) {
96
91
  // Placeholder until chain deploy; operator must set after forge deploy.
@@ -125,9 +120,8 @@ export function loadOperator(home = clankerHome()) {
125
120
  }
126
121
 
127
122
  /**
128
- * Write operator profile (label + owner + optional key pointer). Never stores hex keys.
129
- * Omit `key` (or pass null) for a read-only profile; mint/revoke still need a pointer later.
130
- * @param {{ label: string, owner: string, key?: { type: 'env'|'keyFile', value?: string }|null }} op
123
+ * Write operator profile (label + owner + key pointer). Never stores hex keys.
124
+ * @param {{ label: string, owner: string, key?: { type: 'env'|'keyFile', value?: string } }} op
131
125
  * @param {string} [home]
132
126
  */
133
127
  export function writeOperator(op, home = clankerHome()) {
@@ -136,10 +130,8 @@ export function writeOperator(op, home = clankerHome()) {
136
130
  const out = {
137
131
  label: op.label,
138
132
  owner: op.owner,
133
+ key: op.key ?? { type: "env", value: "OPERATOR_PRIVATE_KEY" },
139
134
  };
140
- if (op.key != null) {
141
- out.key = op.key;
142
- }
143
135
  writeFileSync(path, `${JSON.stringify(out, null, 2)}\n`, { mode: 0o600 });
144
136
  return { path, operator: out };
145
137
  }
@@ -190,7 +182,7 @@ export function resolveNetwork(argv = [], opts = {}) {
190
182
  }
191
183
 
192
184
  if (fromBlock == null) {
193
- fromBlock = isLocalRpc(rpc) ? 0n : SEPOLIA_FAST_FROM_BLOCK;
185
+ fromBlock = isLocalRpc(rpc) ? 0n : SEPOLIA_FROM_BLOCK;
194
186
  }
195
187
 
196
188
  return {
package/lib/resolve.mjs CHANGED
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- import { getAddress } from "viem";
7
6
  import { privateKeyToAccount } from "viem/accounts";
8
7
  import {
9
8
  ANVIL_DEFAULT_PRIVATE_KEY,
@@ -175,63 +174,8 @@ export function resolveForRead(argv = [], opts = {}) {
175
174
  if (!network.registry || !/^0x[0-9a-fA-F]{40}$/.test(network.registry)) {
176
175
  throw new Error(
177
176
  "REGISTRY_ADDRESS or --registry is required (0x + 40 hex). " +
178
- "Run `clanker init --preset sepolia` or `clanker setup`, or set registry after deploy.",
177
+ "Run `clanker init --preset sepolia` or set registry after `clanker chain deploy`.",
179
178
  );
180
179
  }
181
180
  return network;
182
181
  }
183
-
184
- /**
185
- * Resolve address for read-only commands.
186
- * Order: --address > signing key > operator.json owner > error (point at setup).
187
- *
188
- * @param {string[]} argv
189
- * @param {{ home?: string, env?: NodeJS.ProcessEnv }} [opts]
190
- * @returns {{ address: string, source: string, network: object }}
191
- */
192
- export function resolveReadIdentity(argv = [], opts = {}) {
193
- const network = resolveForRead(argv, opts);
194
- const home = opts.home ?? network.home;
195
-
196
- let addressFlag = null;
197
- for (let i = 0; i < argv.length; i += 1) {
198
- if (argv[i] === "--address" && argv[i + 1] && !argv[i + 1].startsWith("--")) {
199
- addressFlag = argv[++i];
200
- }
201
- }
202
- if (addressFlag) {
203
- return {
204
- address: getAddress(addressFlag),
205
- source: "--address",
206
- network,
207
- };
208
- }
209
-
210
- try {
211
- const resolved = resolveOperatorKey(argv, { ...opts, home });
212
- return {
213
- address: resolved.address,
214
- source: resolved.source,
215
- network: resolved.network,
216
- };
217
- } catch (err) {
218
- const msg = err?.message ?? String(err);
219
- if (!/private key required|Anvil account #0|Key file not found|Profile keyFile/i.test(msg)) {
220
- throw err;
221
- }
222
- }
223
-
224
- const operator = loadOperator(home);
225
- if (operator?.owner && /^0x[0-9a-fA-F]{40}$/.test(operator.owner)) {
226
- return {
227
- address: getAddress(operator.owner),
228
- source: "profile owner",
229
- network,
230
- };
231
- }
232
-
233
- throw new Error(
234
- "No operator identity configured. Run `clanker setup`, pass --address 0x…, " +
235
- "or set a signing key (OPERATOR_PRIVATE_KEY / --key-file / operator.json key pointer).",
236
- );
237
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clanker-chain/clanker-cli",
3
- "version": "2026.9.7-4",
3
+ "version": "2026.9.7",
4
4
  "description": "CLI for wiring clanker-chain identity and MQTT into OpenClaw and other agentic stacks.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,8 +23,6 @@
23
23
  "mint-bot:sepolia": "bash ./scripts/with-sepolia-env.sh mint-bot"
24
24
  },
25
25
  "dependencies": {
26
- "@clack/prompts": "^1.8.0",
27
- "picocolors": "^1.1.1",
28
26
  "viem": "^2.21.0"
29
27
  }
30
28
  }