@artblocks/abx-cli 0.1.0-alpha.2 → 0.1.0-alpha.3

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/dist/main.js CHANGED
@@ -134,7 +134,7 @@ async function maybeNotifyUpdate(flags) {
134
134
  const latest = await checkForCliUpdate(current);
135
135
  if (latest) {
136
136
  console.error(`\n ${c.orange}⚠${c.reset} update available: ${bold('abx')} ${dim(current)} → ${g(latest)}\n` +
137
- ` upgrade: ${g('npm i -g @artblocks/abx-cli@latest')} ${dim('· or invoke:')} ${g('npx abx@latest <command>')}\n` +
137
+ ` upgrade: ${g('npm i -g @artblocks/abx-cli@latest')} ${dim('· or invoke:')} ${g('npx @artblocks/abx-cli@latest <command>')}\n` +
138
138
  ` release notes: https://github.com/ArtBlocks/abx/releases ${dim('· silence: ABX_NO_UPDATE_CHECK=1')}\n`);
139
139
  }
140
140
  }
@@ -4167,114 +4167,109 @@ async function cmdMigrate(address, flags) {
4167
4167
  }
4168
4168
  // ── doctor ────────────────────────────────────────────────────────────────--
4169
4169
  async function cmdDoctor(flags) {
4170
- console.log(bold('\n abx doctor\n'));
4171
- const check = (label, pass, detail = '') => console.log(` ${pass ? g('✓') : `${c.red}✗${c.reset}`} ${label}${detail ? dim(' ' + detail) : ''}`);
4170
+ console.log(bold('\n abx doctor') + dim(` · ${CHAIN}`) + '\n');
4171
+ // Two visual tiers: PASS/FAIL checks (✓/✗) for things that are either working or broken, and an
4172
+ // "Optional" block (·) for path-dependent setup that is fine to be unset. We deliberately do NOT
4173
+ // use ⚠ for "unset but often fine" — that read as noise; ⚠ is reserved for a real gotcha (a
4174
+ // range-capped RPC). Labels are padded so both tiers align.
4175
+ const CONT = ' '.repeat(17); // continuation indent: aligns under a check/opt detail column
4176
+ const check = (label, pass, detail = '') => console.log(` ${pass ? g('✓') : `${c.red}✗${c.reset}`} ${label.padEnd(13)}${detail ? dim(detail) : ''}`);
4177
+ const opt = (label, detail) => console.log(` ${dim('·')} ${label.padEnd(13)}${dim(detail)}`);
4172
4178
  const hasKey = !!(process.env.ABX_DEPLOYER_PK ?? process.env.SEPOLIA_FUNDED_PK ?? process.env.SEPOLIA_WALLET_PK);
4173
- // Not having an env key is NOT fatal: the wallet lane (`--sign`) signs in the user's own wallet,
4174
- // key-free. Only flag it as a soft note + point at the lanes never imply "paste a private key."
4175
- if (hasKey)
4176
- check('signing key in env', true, 'hot lane ready (env key signs unattended)');
4177
- else
4178
- console.log(` ${c.orange}⚠${c.reset} signing key in env${dim(' none fine if you sign in your own wallet (`--sign`; the key never touches the CLI). Only set ABX_DEPLOYER_PK / SEPOLIA_FUNDED_PK / SEPOLIA_WALLET_PK for hot/unattended signing.')}`);
4179
+ // 1. Agent skill FIRST and prominent. The primary way to use abx is to let a coding agent drive
4180
+ // it, so a missing/stale skill is a ✗: not broken infra, but the main UX isn't set up. Its
4181
+ // version lives in SKILL.md frontmatter (version-locked to this CLI). Notify-only — no exit code.
4182
+ const cliVersion = readCliVersion();
4183
+ const skillVersions = installedSkillVersions();
4184
+ const staleSkills = skillVersions.filter((v) => compareVersions(cliVersion, v) > 0);
4185
+ if (skillVersions.length === 0) {
4186
+ check('agent skill', false, `not installed — run ${g('abx skill install')}`);
4187
+ console.log(`${CONT}${dim('(recommended: let a coding agent drive abx)')}`);
4188
+ }
4189
+ else if (staleSkills.length > 0) {
4190
+ check('agent skill', false, `v${staleSkills.join(', v')} behind CLI v${cliVersion} — run ${g('abx skill install')}`);
4191
+ }
4192
+ else {
4193
+ check('agent skill', true, `in sync (v${cliVersion})`);
4194
+ }
4195
+ console.log('');
4196
+ // 2. Core environment (✓/✗). Signing-wallet balances are computed here (they need the RPC) but
4197
+ // printed in the Optional block below, so buffer them.
4198
+ let signingOpt = null;
4199
+ let forOpt = null;
4179
4200
  try {
4180
4201
  const publicClient = makePublicClient({ chainKey: CHAIN });
4181
4202
  const bn = await publicClient.getBlockNumber();
4182
- check(`RPC reachable (${CHAIN})`, true, `head block ${bn}`);
4183
- // RPC endpointsprobe each for reachability, getLogs range, and archive depth,
4184
- // so the toolkit uses (and recommends) the one fit for the job — and says so if none are.
4203
+ // Collapse the RPC report to one line (best endpoint + head), and only add a ⚠ when there is a
4204
+ // genuine problema range-capped-only set that will grind a resolver under load.
4185
4205
  const probes = await probeRpcEndpoints({ chainKey: CHAIN });
4186
4206
  const usable = probes.filter((pr) => pr.verdict !== 'unusable');
4187
4207
  const best = probes.find((pr) => pr.verdict === 'best') ?? usable[0];
4188
- check(`RPC endpoints for ${CHAIN} (${probes.length})`, usable.length > 0, usable.length > 0
4189
- ? `best for reconstruction: ${best.label} (${best.verdict === 'best' ? 'wide range + archive' : 'usable, range-capped'})`
4190
- : 'none usable for reconstruction');
4191
- for (const pr of probes) {
4192
- const glyph = pr.verdict === 'best' ? g('✓') : pr.verdict === 'capped' ? `${c.orange}⚠${c.reset}` : `${c.red}✗${c.reset}`;
4193
- console.log(` ${glyph} ${pr.label} ${dim('— ' + (pr.verdict === 'best' ? 'wide getLogs range + archive' : pr.reason ?? pr.verdict))}`);
4194
- }
4195
- if (usable.length === 0) {
4196
- console.log(` ${c.orange}↳${c.reset} ${dim('research a current free archive RPC with a wide getLogs range and add it to ABX_RPC_URLS — see the skill’s “Choosing an RPC”')}`);
4197
- }
4198
- else if (!probes.some((pr) => pr.verdict === 'best')) {
4199
- // Every usable endpoint is range-capped. Indexing still works (chunking), but a first
4200
- // reconstruction — and a resolver under marketplace load — is slow + rate-limit-prone. That's
4201
- // a serious infra signal, NOT "a paid plan is required": flag it. A normal deploy→index only
4202
- // scans from the deploy block, so this bites first reconstructions and busy resolvers most.
4203
- console.log(` ${c.orange}⚠ every usable RPC is getLogs-range-capped${c.reset} ${dim('— add a wide-range archive endpoint to ABX_RPC_URLS before running a resolver under load; capped ones grind on wide scans (skill → “Choosing an RPC”).')}`);
4204
- }
4205
- if (hasKey) {
4206
- const { account } = makeWalletClient({ chainKey: CHAIN });
4207
- const bal = await publicClient.getBalance({ address: account.address });
4208
- check('deployer funded', bal > 0n, `${account.address} · ${formatEther(bal)} ETH`);
4209
- }
4210
- // Wallet lane has no env key — let a creator preflight THEIR OWN signing wallet's balance,
4211
- // the one gap where an unfunded wallet otherwise only surfaces at the signing step.
4212
- if (flags.for) {
4213
- const bal = await publicClient.getBalance({ address: flags.for });
4214
- check('wallet funded (--for)', bal > 0n, `${flags.for} · ${formatEther(bal)} ETH${bal > 0n ? '' : ` — ${faucetHint(CHAIN)}`}`);
4208
+ if (usable.length > 0) {
4209
+ check('RPC', true, `${best.label} · head ${bn} · ${best.verdict === 'best' ? 'wide range + archive' : 'range-capped'}`);
4210
+ if (!probes.some((pr) => pr.verdict === 'best')) {
4211
+ console.log(`${CONT}${c.orange}⚠${c.reset}${dim(' every endpoint is getLogs-range-capped — add a wide-range archive RPC to ABX_RPC_URLS before running a resolver under load')}`);
4212
+ }
4215
4213
  }
4216
- else if (!hasKey) {
4217
- console.log(` ${dim(' wallet lane: run `abx doctor --for 0x<your wallet>` to check your signing wallet is funded')}`);
4214
+ else {
4215
+ check('RPC', false, `${CHAIN} no endpoint usable for reconstruction; add a wide-range archive RPC to ABX_RPC_URLS`);
4218
4216
  }
4219
4217
  const factory = factoryAddress();
4220
4218
  if (factory) {
4221
4219
  const code = await publicClient.getCode({ address: factory });
4222
- if (!code || code === '0x') {
4223
- check('canonical factory deployed', false, `${factory} — no code on ${CHAIN}; \`abx deploy\` redeploys`);
4224
- }
4225
- else if (await isCurrentFactory(publicClient, factory)) {
4226
- check('canonical factory deployed', true, factory);
4227
- }
4228
- else {
4229
- check('canonical factory deployed', false, `${factory} — older/incompatible version; \`abx deploy\` redeploys`);
4230
- }
4220
+ if (!code || code === '0x')
4221
+ check('factory', false, `${factory} — no code on ${CHAIN}; \`abx deploy\` redeploys`);
4222
+ else if (await isCurrentFactory(publicClient, factory))
4223
+ check('factory', true, factory);
4224
+ else
4225
+ check('factory', false, `${factory} — older/incompatible; \`abx deploy\` redeploys`);
4231
4226
  }
4232
4227
  else {
4233
- check('canonical factory deployed', false, 'none yet — `abx demo` deploys one');
4228
+ check('factory', false, 'none yet — `abx demo` deploys one');
4229
+ }
4230
+ if (hasKey) {
4231
+ const { account } = makeWalletClient({ chainKey: CHAIN });
4232
+ const bal = await publicClient.getBalance({ address: account.address });
4233
+ signingOpt = `env key ${account.address} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — fund it (${faucetHint(CHAIN)})`}`;
4234
+ }
4235
+ if (flags.for) {
4236
+ const bal = await publicClient.getBalance({ address: flags.for });
4237
+ forOpt = `${flags.for} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — ${faucetHint(CHAIN)}`}`;
4234
4238
  }
4235
4239
  }
4236
4240
  catch (err) {
4237
- check('RPC reachable', false, err.message);
4241
+ check('RPC', false, err.message);
4238
4242
  }
4239
- // public base URL — baked into on-chain URIs at deploy; unset is fine for a demo
4240
- // but a silent footgun for a real launch, so surface it explicitly.
4241
- const baseUrl = process.env.ABX_PUBLIC_BASE_URL;
4242
- if (baseUrl)
4243
- check('public base URL set (baked into on-chain URIs)', true, baseUrl);
4244
- else
4245
- console.log(` ${c.orange}⚠${c.reset} public base URL${dim(' ABX_PUBLIC_BASE_URL unset → resolver-served deploys REFUSE (localhost on-chain resolves for no one). Set it (or --public-base-url) for a hosted resolver. IRRELEVANT if you go fully on-chain: --onchain-image (1/1) or --onchain-uri (code project) bake no resolver base.')}`);
4246
4243
  // storage backend — resolve it (catches missing config), then probe liveness/creds
4247
4244
  const backendId = activeBackendId();
4248
4245
  try {
4249
4246
  const backend = resolveBackend(storageOptions());
4250
4247
  const h = await backend.health?.();
4251
4248
  if (h)
4252
- check(`storage backend '${backend.id}' reachable`, h.ok, h.detail ?? '');
4249
+ check('storage', h.ok, `${backend.id} · ${h.detail ?? ''}`);
4253
4250
  else
4254
- check(`storage backend '${backend.id}' configured`, true);
4251
+ check('storage', true, `${backend.id} · configured`);
4255
4252
  }
4256
4253
  catch (err) {
4257
- check(`storage backend '${backendId}' configured`, false, err.message);
4254
+ check('storage', false, `${backendId} · ${err.message}`);
4258
4255
  }
4259
- // Managed Turbo/Arweave key if it exists, it holds prepaid credits, so remind them to back it up.
4256
+ // 3. Optionalpath-dependent setup. Unset is fine; these say WHEN you'll need each, so an unset
4257
+ // value never reads as a warning.
4258
+ console.log(`\n ${dim('Optional — depends how you deploy:')}`);
4259
+ if (signingOpt)
4260
+ opt('signing', signingOpt);
4261
+ else
4262
+ opt('signing', 'no env key → sign in your browser wallet (--sign). Preflight yours: `abx doctor --for 0x<addr>`. Set ABX_DEPLOYER_PK for the unattended hot lane.');
4263
+ if (forOpt)
4264
+ opt('wallet --for', forOpt);
4265
+ const baseUrl = process.env.ABX_PUBLIC_BASE_URL;
4266
+ if (baseUrl)
4267
+ opt('resolver URL', baseUrl);
4268
+ else
4269
+ opt('resolver URL', 'ABX_PUBLIC_BASE_URL unset → needed for off-chain/resolver-served deploys; skip if fully on-chain (--onchain-image / --onchain-uri).');
4260
4270
  if (!process.env.ARWEAVE_JWK && existsSync(arweaveKeyFilePath())) {
4261
4271
  const jwk = loadArweaveJwk();
4262
- console.log(` ${dim(' Turbo/Arweave key ' + (jwk ? arweaveAddress(jwk) + ' ' : '') + 'holds your upload credits — back it up: `abx storage backup-key --out <path>`')}`);
4263
- }
4264
- // Agent skill co-versioning — the skill drives this CLI and is version-locked to it (its version
4265
- // lives in SKILL.md frontmatter). Report any installed copy and flag drift; notify-only, so a
4266
- // stale/absent skill never fails doctor (`abx skill install` resyncs).
4267
- const cliVersion = readCliVersion();
4268
- const skillVersions = installedSkillVersions();
4269
- const staleSkills = skillVersions.filter((v) => compareVersions(cliVersion, v) > 0);
4270
- if (skillVersions.length === 0) {
4271
- console.log(` ${dim('↳ agent skill: not installed here — `abx skill install` so a coding agent can drive abx')}`);
4272
- }
4273
- else if (staleSkills.length > 0) {
4274
- console.log(` ${c.orange}⚠${c.reset} agent skill behind CLI${dim(` installed v${staleSkills.join(', v')} · CLI v${cliVersion} — refresh: `)}${g('abx skill install')}`);
4275
- }
4276
- else {
4277
- check(`agent skill in sync (v${cliVersion})`, true);
4272
+ opt('arweave key', `${jwk ? arweaveAddress(jwk) + ' ' : ''}holds upload credits — back it up: \`abx storage backup-key --out <path>\``);
4278
4273
  }
4279
4274
  console.log('');
4280
4275
  }