@artblocks/abx-cli 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +188 -19
- package/dist/main.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +2 -1
- package/skill/reference/hosting.md +6 -4
- package/skill/reference/troubleshooting.md +1 -1
package/dist/main.js
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* abx remote [<name|url>] inspect a remote service (descriptor · chains · managed rendering · your projects there)
|
|
22
22
|
* abx index [<address>] re-index a project from chain (replay; --remote to nudge a remote resolver)
|
|
23
23
|
* abx verify <address> re-hash served bytes vs the on-chain commitment (no server)
|
|
24
|
+
* abx tokenuri <address> read tokenURI(id) from the contract + decode · contracturi: the ERC-7572 collection JSON
|
|
25
|
+
* (the contract holds the URL — never hand-build a resolver path)
|
|
24
26
|
* abx configure-param <addr> <id> <key> <value> set a governed PostParam (typed encode; any lane)
|
|
25
27
|
* abx set-param-hooks <addr> wire/clear a SeriesCode's configure/augment/transfer param hooks
|
|
26
28
|
* abx render <addr> [id…] effect-runner repair lane: render missing stills/traits
|
|
@@ -177,6 +179,7 @@ async function main() {
|
|
|
177
179
|
case 'render': return cmdRender(rest[0], rest.slice(1), flags);
|
|
178
180
|
case 'effects': return cmdEffects(flags);
|
|
179
181
|
case 'tokenuri': return cmdTokenUri(rest[0], flags);
|
|
182
|
+
case 'contracturi': return cmdContractUri(rest[0], flags);
|
|
180
183
|
case 'serve': return cmdServe(flags);
|
|
181
184
|
// owner operations — write + sign (hot/wallet/cold lane), then re-index
|
|
182
185
|
case 'mint': return cmdMint(rest[0], flags);
|
|
@@ -3610,8 +3613,15 @@ async function cmdDeployCode(flags) {
|
|
|
3610
3613
|
// has nothing to run — point at the from-chain check instead.
|
|
3611
3614
|
if (hasImageRenderer && !hasProgram)
|
|
3612
3615
|
info(` ${g('nothing to run')} — verify from chain: ${bold('abx tokenuri ' + (predicted ?? '<address>'))} ${dim('(decodes name + on-chain SVG + traits)')}`);
|
|
3613
|
-
else
|
|
3614
|
-
|
|
3616
|
+
else {
|
|
3617
|
+
const remoteRender = !(onChainUri && hasImageBase);
|
|
3618
|
+
info(` ${dim('stand up the runner:')} ${bold('abx deploy-effects --resolver-url ' + baseUrl)} · one-shot: ${bold('abx render ' + (predicted ?? '<address>') + (remoteRender ? ' --remote ' + baseUrl : ''))} · verify: ${bold('abx verify ' + (predicted ?? '<address>'))}`);
|
|
3619
|
+
// Rendering against a resolver you don't share a disk with means YOU hold the bytes and it
|
|
3620
|
+
// holds the URL — so say now which backend that needs, rather than letting the render fail.
|
|
3621
|
+
if (remoteRender && !resolveBackend(storageOptions(storageOverrides(flags))).locator) {
|
|
3622
|
+
info(` ${dim('that render publishes a URL, so it needs a backend that can name one:')} ${bold('--backend cloud')} ${dim('(S3/R2 + public base) ·')} ${bold('ipfs')} ${dim('·')} ${bold('arweave')} ${dim('— or render co-located with the resolver (`abx effects` on its host).')}`);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3615
3625
|
if (dirUpload)
|
|
3616
3626
|
await noteStorageReadiness(storageOptions(storageOverrides(flags)), dirUpload.sizes);
|
|
3617
3627
|
if (deployer && salt && !parseSaltFlag(flags.salt)) {
|
|
@@ -3889,6 +3899,10 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3889
3899
|
const remote = remoteFlag(flags);
|
|
3890
3900
|
const resolverUrl = (remote?.url ?? process.env.ABX_RESOLVER_URL ?? resolveBaseUrl()).replace(/\/$/, '');
|
|
3891
3901
|
const adminToken = remote ? requireRemoteToken(remote) : undefined;
|
|
3902
|
+
// Refuse the combination that can't work before launching Chromium (a laptop render against a
|
|
3903
|
+
// hosted resolver, on a backend with no public URL) — the render would end in a 400 either way.
|
|
3904
|
+
if (remote)
|
|
3905
|
+
requirePublishableBackend(flags, 'abx render --remote');
|
|
3892
3906
|
// Co-located (no --remote): record each declared output into the shared store's artifact
|
|
3893
3907
|
// registry so the local resolver's `artifacts` manifest enumerates it. Remote: the publish
|
|
3894
3908
|
// lane (adminToken) records rows on the hosted resolver instead.
|
|
@@ -3913,6 +3927,41 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3913
3927
|
else
|
|
3914
3928
|
ok(summary);
|
|
3915
3929
|
}
|
|
3930
|
+
/**
|
|
3931
|
+
* The publish topology's one hard prerequisite, checked BEFORE any capture.
|
|
3932
|
+
*
|
|
3933
|
+
* A resolver that doesn't share this machine's disk serves referenced output (the still, a video, a
|
|
3934
|
+
* model) by **redirect** — so it needs a URL, and it refuses the bytes (`specs/protocol/effects.md →
|
|
3935
|
+
* Bound vs referenced`). A backend that can't name a locator therefore has no publish lane at all,
|
|
3936
|
+
* and every render against one would be work spent to earn a 400.
|
|
3937
|
+
*
|
|
3938
|
+
* So this refuses up front and names the ways out, rather than warning and letting the render run.
|
|
3939
|
+
* It deliberately does NOT pick a backend for the operator: which store holds their art — and which
|
|
3940
|
+
* gateway serves it — is theirs to decide. The three that work are peers, not a ranking: `https://`
|
|
3941
|
+
* from S3/R2, an IPFS gateway, and Arweave are all fine, because derived output is re-creatable and
|
|
3942
|
+
* the only real requirement is that a third party can fetch it.
|
|
3943
|
+
*/
|
|
3944
|
+
function requirePublishableBackend(flags, what) {
|
|
3945
|
+
const backend = resolveBackend(storageOptions(storageOverrides(flags)));
|
|
3946
|
+
// `cloud` is the one backend that HAS a locator method and can still return null: the URL is
|
|
3947
|
+
// `<publicBase>/<key>`, so without a public base it can name nothing. Catch that here too — it
|
|
3948
|
+
// would otherwise be the same failure one render later.
|
|
3949
|
+
if (backend.id === 'cloud' && !backend.publicBase) {
|
|
3950
|
+
throw new Error(`${what} hands the resolver a URL for each render, but the 'cloud' backend has no public read base — ` +
|
|
3951
|
+
`set ${bold('ABX_S3_PUBLIC_BASE')} to the bucket's public URL (an R2/CloudFront/S3-website base), ` +
|
|
3952
|
+
`or use ${bold('--backend ipfs')} / ${bold('--backend arweave')}, or render co-located with the resolver.`);
|
|
3953
|
+
}
|
|
3954
|
+
if (backend.locator)
|
|
3955
|
+
return;
|
|
3956
|
+
const { backend: name, source } = backendResolution(storageOverrides(flags));
|
|
3957
|
+
throw new Error(`${what} publishes each render to a resolver that doesn't share this machine's disk, but the '${name}' backend ` +
|
|
3958
|
+
`(${source === 'default' ? 'the default' : `from ${source}`}) can't produce a URL for what it stores — so the resolver would have nothing to serve.\n` +
|
|
3959
|
+
` Point it at a backend that can (equal options — pick on cost/ops, not durability dogma):\n` +
|
|
3960
|
+
` ${bold('--backend cloud')} S3 / R2 / B2 with a public base (ABX_S3_* + ABX_S3_PUBLIC_BASE)\n` +
|
|
3961
|
+
` ${bold('--backend ipfs')} Pinata or your own Kubo + a public gateway (PINATA_JWT / ABX_IPFS_*)\n` +
|
|
3962
|
+
` ${bold('--backend arweave')} pay-once permanent (uploads under 100KiB are free, no setup)\n` +
|
|
3963
|
+
` …or run co-located instead: ${bold('abx effects')} on the same host as ${bold('abx serve')}, sharing one backend and no remote token.`);
|
|
3964
|
+
}
|
|
3916
3965
|
/**
|
|
3917
3966
|
* `abx effects` — run the reference effect runner LOCALLY, in-process (the local counterpart to
|
|
3918
3967
|
* `abx deploy-effects`, which scaffolds a HOSTED runner). This is what makes a param change / new
|
|
@@ -3932,6 +3981,8 @@ async function cmdEffects(flags) {
|
|
|
3932
3981
|
// the shared artifact registry so the resolver's `artifacts` manifest enumerates it. With an
|
|
3933
3982
|
// admin token, the publish lane records rows on the hosted resolver instead.
|
|
3934
3983
|
const localStore = adminToken ? null : new SqliteStore();
|
|
3984
|
+
if (adminToken)
|
|
3985
|
+
requirePublishableBackend(flags, 'abx effects against a remote resolver');
|
|
3935
3986
|
const { EffectRunner, renderEffect } = await loadEffects();
|
|
3936
3987
|
const runner = new EffectRunner({
|
|
3937
3988
|
resolverUrl,
|
|
@@ -4397,6 +4448,101 @@ async function cmdTokenUri(address, flags) {
|
|
|
4397
4448
|
console.log(` ${uri.slice(0, 240)}${uri.length > 240 ? dim(`… (${uri.length} chars)`) : ''}\n`);
|
|
4398
4449
|
}
|
|
4399
4450
|
}
|
|
4451
|
+
// ── contracturi ──────────────────────────────────────────────────────────────
|
|
4452
|
+
/**
|
|
4453
|
+
* `abx contracturi <address>` — the collection-level counterpart of `tokenuri`: read
|
|
4454
|
+
* `contractURI()` (ERC-7572) STRAIGHT FROM THE CONTRACT, then FOLLOW it and decode the JSON.
|
|
4455
|
+
*
|
|
4456
|
+
* Why this exists, and why it follows the URL: the resolver's route grammar is committed
|
|
4457
|
+
* on-chain at deploy (`contractURIBase` = `<baseUrl>/c`), so the contract — not a doc, not a
|
|
4458
|
+
* service descriptor — is the source of truth for where a collection's metadata lives. Without
|
|
4459
|
+
* this command the only way to look was to hand-build the URL from memory of the grammar, and a
|
|
4460
|
+
* guessed path that 404s reads exactly like a broken service. Ask the chain instead.
|
|
4461
|
+
*/
|
|
4462
|
+
async function cmdContractUri(address, _flags) {
|
|
4463
|
+
if (!address || address.startsWith('--')) {
|
|
4464
|
+
console.error('usage: abx contracturi <address>\n');
|
|
4465
|
+
process.exit(1);
|
|
4466
|
+
}
|
|
4467
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
4468
|
+
console.error(`abx contracturi: '${address}' isn't a 0x contract address.\n`);
|
|
4469
|
+
process.exit(1);
|
|
4470
|
+
}
|
|
4471
|
+
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
4472
|
+
let uri;
|
|
4473
|
+
try {
|
|
4474
|
+
uri = (await publicClient.readContract({
|
|
4475
|
+
address,
|
|
4476
|
+
abi: oneOfOneImageAbi,
|
|
4477
|
+
functionName: 'contractURI',
|
|
4478
|
+
args: [],
|
|
4479
|
+
}));
|
|
4480
|
+
}
|
|
4481
|
+
catch {
|
|
4482
|
+
const code = await publicClient.getCode({ address }).catch(() => undefined);
|
|
4483
|
+
if (!code || code === '0x') {
|
|
4484
|
+
// Name the endpoint we actually asked — "no contract here" and "you're pointed at the wrong
|
|
4485
|
+
// node" are indistinguishable otherwise (same reasoning as `tokenuri`).
|
|
4486
|
+
console.error(`abx contracturi: no contract at ${address} on ${CHAIN} (asked ${redactRpcUrl(resolveRpcUrl(CHAIN))}) — ` +
|
|
4487
|
+
`double-check the address, and that this endpoint is the network you deployed to. ` +
|
|
4488
|
+
`If you JUST deployed, give the tx a block or two to mine.\n`);
|
|
4489
|
+
}
|
|
4490
|
+
else {
|
|
4491
|
+
console.error(`abx contracturi: ${address} didn't return a contractURI — it may not be an ABX/ERC-7572 contract, or ` +
|
|
4492
|
+
`— on a large on-chain contractURI — an unauthenticated RPC read hit its gas cap (try a wallet-connected / high-gas RPC).\n`);
|
|
4493
|
+
}
|
|
4494
|
+
process.exit(1);
|
|
4495
|
+
}
|
|
4496
|
+
console.log(`\n ${bold('contractURI()')} ${dim(`— read directly from ${address} on ${CHAIN}`)}`);
|
|
4497
|
+
if (!uri) {
|
|
4498
|
+
console.error(`\n ${bold('empty')} — this contract has no contractURI set: no collection-level metadata to resolve. ` +
|
|
4499
|
+
`Set one with ${bold(`abx set-contract-uri ${address} --uri <base>`)}, or point it at the canonical renderer for the on-chain lane.\n`);
|
|
4500
|
+
process.exit(1);
|
|
4501
|
+
}
|
|
4502
|
+
const onChain = decodeOnChainJson(uri);
|
|
4503
|
+
if (onChain) {
|
|
4504
|
+
info('resolution: ON-CHAIN (data: URI from the renderer — no server in the path)');
|
|
4505
|
+
console.log(onChain.split('\n').map((l) => ' ' + l).join('\n') + '\n');
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
console.log(` ${dim('resolves to')} ${uri}`);
|
|
4509
|
+
if (!/^https?:\/\//i.test(uri)) {
|
|
4510
|
+
// ipfs:// / ar:// — a locator, not something we can fetch without choosing a gateway. Print it
|
|
4511
|
+
// rather than silently picking one; the creator's gateway choice is theirs.
|
|
4512
|
+
info(`not an http(s) URL — a ${uri.split(':')[0]}: locator needs a gateway to fetch. Nothing more to read from here.`);
|
|
4513
|
+
console.log('');
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
let body;
|
|
4517
|
+
try {
|
|
4518
|
+
const res = await fetch(uri, { headers: { accept: 'application/json' } });
|
|
4519
|
+
body = await res.text();
|
|
4520
|
+
if (!res.ok) {
|
|
4521
|
+
// The URL came FROM THE CHAIN, so a bad status here is genuinely about the service (or the
|
|
4522
|
+
// contract pointing somewhere stale) — never a mistyped path. Say which, so nobody re-guesses.
|
|
4523
|
+
console.error(`\n ${bold(`HTTP ${res.status}`)} from the contract's own contractURI — the URL is correct by construction (it came from ` +
|
|
4524
|
+
`${address} on-chain), so this is the SERVICE, not the path. Likely: the project isn't registered on that resolver ` +
|
|
4525
|
+
`(${bold('abx add ' + address + ' --remote')}), the node serves a different chain, or it's down. ` +
|
|
4526
|
+
`Response: ${body.slice(0, 200)}\n`);
|
|
4527
|
+
process.exit(1);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
catch (e) {
|
|
4531
|
+
console.error(`\n couldn't reach ${uri} — ${e.message}. The URL is what the contract commits to, so check that the ` +
|
|
4532
|
+
`host is up and publicly reachable (a localhost base URL resolves for no one but this machine).\n`);
|
|
4533
|
+
process.exit(1);
|
|
4534
|
+
}
|
|
4535
|
+
info('resolution: OFF-CHAIN (fetched from the URL the contract commits to)');
|
|
4536
|
+
try {
|
|
4537
|
+
console.log(JSON.stringify(JSON.parse(body), null, 2)
|
|
4538
|
+
.split('\n')
|
|
4539
|
+
.map((l) => ' ' + l)
|
|
4540
|
+
.join('\n') + '\n');
|
|
4541
|
+
}
|
|
4542
|
+
catch {
|
|
4543
|
+
console.log(` ${dim('(not JSON)')} ${body.slice(0, 400)}\n`);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4400
4546
|
// ── serve ──────────────────────────────────────────────────────────────────--
|
|
4401
4547
|
async function cmdServe(flags) {
|
|
4402
4548
|
const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
|
|
@@ -5222,10 +5368,16 @@ async function cmdDoctor(flags) {
|
|
|
5222
5368
|
try {
|
|
5223
5369
|
const backend = resolveBackend(storageOptions());
|
|
5224
5370
|
const h = await backend.health?.();
|
|
5371
|
+
// Whether this backend can name a public URL for what it stores decides one thing operators hit
|
|
5372
|
+
// later: a REMOTE effects runner needs it (the resolver serves referenced output by redirect, so
|
|
5373
|
+
// it takes a URL and refuses bytes). Co-located rendering doesn't care — hence a note, not a fail.
|
|
5374
|
+
const publishable = backend.locator
|
|
5375
|
+
? 'can publish to a remote resolver'
|
|
5376
|
+
: 'local-only — a remote effects runner needs cloud/ipfs/arweave (co-located rendering is fine)';
|
|
5225
5377
|
if (h)
|
|
5226
|
-
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''}`);
|
|
5378
|
+
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''} · ${publishable}`);
|
|
5227
5379
|
else
|
|
5228
|
-
check('storage', true, `${backend.id} · configured`);
|
|
5380
|
+
check('storage', true, `${backend.id} · configured · ${publishable}`);
|
|
5229
5381
|
}
|
|
5230
5382
|
catch (err) {
|
|
5231
5383
|
check('storage', false, `${backendId} · ${err.message}`);
|
|
@@ -5434,13 +5586,19 @@ async function cmdDeployEffects(flags) {
|
|
|
5434
5586
|
const intervalMs = flags['interval-ms'] ? Number(flags['interval-ms']) : undefined;
|
|
5435
5587
|
const environmentId = flags['env-id'] ?? process.env.ABX_ENVIRONMENT_ID;
|
|
5436
5588
|
const storageBackend = process.env.ABX_STORAGE_BACKEND;
|
|
5437
|
-
// A HOSTED runner
|
|
5438
|
-
// (
|
|
5439
|
-
// which
|
|
5440
|
-
//
|
|
5589
|
+
// A HOSTED runner holds its own render bytes and hands the resolver a URL — that's the whole
|
|
5590
|
+
// topology (`specs/protocol/effects.md → Bound vs referenced`). The default `fs` writes to the
|
|
5591
|
+
// runner CONTAINER's disk, which nothing else can reach, so there is no URL to publish: the runner
|
|
5592
|
+
// now REFUSES to start on that config. Scaffolding it anyway would just deploy a container that
|
|
5593
|
+
// exits, so this is a hard stop rather than the warning it used to be.
|
|
5441
5594
|
if (!storageBackend || storageBackend === 'fs') {
|
|
5442
|
-
|
|
5443
|
-
`
|
|
5595
|
+
throw new Error(`ABX_STORAGE_BACKEND is ${storageBackend ? "'fs'" : 'unset (defaults to fs)'} — a hosted runner can't serve renders off its own container disk, ` +
|
|
5596
|
+
`and it refuses to start without a backend that can name a public URL for what it stores.\n` +
|
|
5597
|
+
` Set ABX_STORAGE_BACKEND to one of (equal options — pick on cost/ops):\n` +
|
|
5598
|
+
` ${bold('cloud')} (alias s3) S3 / R2 / B2 with ABX_S3_* + a public base\n` +
|
|
5599
|
+
` ${bold('ipfs')} Pinata or your own Kubo + a public gateway\n` +
|
|
5600
|
+
` ${bold('arweave')} pay-once permanent (<100KiB uploads are free)\n` +
|
|
5601
|
+
` …then re-run. (Rendering on the resolver's own host instead? Use ${bold('abx effects')} co-located — no publish lane, no locator needed.)`);
|
|
5444
5602
|
}
|
|
5445
5603
|
const repoRoot = findRepoRoot();
|
|
5446
5604
|
if (!repoRoot) {
|
|
@@ -5462,10 +5620,7 @@ async function cmdDeployEffects(flags) {
|
|
|
5462
5620
|
const outDir = resolvePath(joinPath(dir, 'effects'));
|
|
5463
5621
|
console.log(bold(`\n ABX Self-Host Toolkit — provision effects runner (fly)`));
|
|
5464
5622
|
info(`resolver: ${bold(cleanResolver)} ${dim('(reads token state + publishes renders here)')}`);
|
|
5465
|
-
info(`storage home: ${bold(storageBackend
|
|
5466
|
-
if (!storageBackend || storageBackend === 'fs') {
|
|
5467
|
-
warn('storage backend is fs (LOCAL) — a HOSTED runner needs a PUBLIC home (ipfs/arweave/s3) so the resolver can serve the render. Set ABX_STORAGE_BACKEND before deploying, or the runner will push raw bytes to the resolver.');
|
|
5468
|
-
}
|
|
5623
|
+
info(`storage home: ${bold(storageBackend)} ${dim('— the runner HOLDS the render bytes here and publishes their URL; the resolver redirects to it and never proxies')}`);
|
|
5469
5624
|
step('Write the effects artifact (self-contained — Dockerfile.effects + fly.toml + vendored source)');
|
|
5470
5625
|
mkdirSync(outDir, { recursive: true });
|
|
5471
5626
|
for (const f of art.files) {
|
|
@@ -5709,8 +5864,12 @@ const COMMAND_HELP = {
|
|
|
5709
5864
|
No tokenId → sweeps all minted tokens; pass ids (${g('0 1 2')}) to target specific tokens.
|
|
5710
5865
|
${g('--force')} RE-RENDER even when the still already exists — the fix for a bad / blank / timed-out capture
|
|
5711
5866
|
(the art is otherwise deterministic, so a plain render idempotent-skips an existing still). Overwrites it (+ republishes on --remote).
|
|
5712
|
-
${g('--remote [name|url]')}
|
|
5713
|
-
|
|
5867
|
+
${g('--remote [name|url]')} register each render with a REMOTE resolver: the bytes go to ${bold('ABX_STORAGE_BACKEND')} (YOU hold them), and
|
|
5868
|
+
POST /v1/effect-artifacts hands the resolver the URL — it redirects there and never proxies. Traits are the
|
|
5869
|
+
exception: they stitch into the token JSON, so their content (≤64KB) goes to the resolver itself.
|
|
5870
|
+
${bold('Requires a backend that can name a public URL')} — ${g('--backend cloud')} (S3/R2 + public base), ${g('ipfs')}, or ${g('arweave')};
|
|
5871
|
+
equal options, pick on cost/ops. The default ${g('fs')} is refused up front rather than after the render.
|
|
5872
|
+
Idempotent; a re-run re-registers rows on a resolver that lost them. Needs its token (a named remote's
|
|
5714
5873
|
${g('ABX_REMOTE_<NAME>_TOKEN')}, else ${g('ABX_RESOLVER_ADMIN_TOKEN')}).
|
|
5715
5874
|
--effects-url <url> enqueue on a running effect-runner service instead of rendering inline (else ${g('ABX_EFFECTS_URL')})
|
|
5716
5875
|
${dim('Inline (no --remote) renders on THIS machine (needs `npx playwright install chromium` once), pointing Chromium at the live')}
|
|
@@ -5727,8 +5886,10 @@ const COMMAND_HELP = {
|
|
|
5727
5886
|
--port <n> HTTP port (default ${g('ABX_EFFECTS_PORT')} / 8788) — ${g('POST /notify')} enqueues (watcher lane) · ${g('POST /run')} sweeps synchronously (command lane)
|
|
5728
5887
|
--interval-ms <n> the SAFETY-FLOOR sweep (default ${g('ABX_EFFECTS_INTERVAL_MS')} / 300000) — catches a missed notify / cold start; the watcher is the trigger
|
|
5729
5888
|
--concurrency <n> parallel renders while draining (default ${g('ABX_EFFECTS_CONCURRENCY')} / 1 — Chromium is heavy; raise deliberately)
|
|
5730
|
-
${dim('Co-located with a local `abx serve` (same store) → no tokens needed
|
|
5731
|
-
${dim('token
|
|
5889
|
+
${dim('Co-located with a local `abx serve` (same store) → no tokens needed, any backend works (fs included).')}
|
|
5890
|
+
${dim('Against a REMOTE resolver: --remote <name|url> (its token registers renders + reports status), or ABX_RESOLVER_URL +')}
|
|
5891
|
+
${dim('ABX_RESOLVER_ADMIN_TOKEN — and a backend that can name a public URL (cloud/ipfs/arweave), since the resolver takes the')}
|
|
5892
|
+
${dim('URL and refuses the bytes. PUBLIC runner? set ABX_EFFECTS_TOKEN — it gates /run + /notify.')}
|
|
5732
5893
|
${dim('To HOST the runner (fly/docker), use `abx deploy-effects`.')}`,
|
|
5733
5894
|
'configure-param': `
|
|
5734
5895
|
${bold('abx configure-param')} <address> <tokenId|-> <key> <value> ${dim('— set a PostParam (typed, canonical encode). Sends a tx.')}
|
|
@@ -5951,7 +6112,14 @@ const COMMAND_HELP = {
|
|
|
5951
6112
|
${dim(`${g('onChainStatus')} (branch · chain-complete · unresolved refs · URL budget) and decodes ${g('tokenURI')} straight from the contract.`)}`,
|
|
5952
6113
|
tokenuri: `
|
|
5953
6114
|
${bold('abx tokenuri')} <address> [--token <id>] ${dim('— read tokenURI(id) straight from the contract on-chain + decode the JSON (read-only; no server).')}
|
|
5954
|
-
${dim('The proof a fully on-chain token self-resolves: any RPC returns the renderer-assembled metadata. Default token 0.')}
|
|
6115
|
+
${dim('The proof a fully on-chain token self-resolves: any RPC returns the renderer-assembled metadata. Default token 0.')}
|
|
6116
|
+
${dim('Collection-level (ERC-7572) counterpart:')} ${g('abx contracturi <address>')}`,
|
|
6117
|
+
contracturi: `
|
|
6118
|
+
${bold('abx contracturi')} <address> ${dim('— read contractURI() (ERC-7572 collection metadata) from the contract, FOLLOW it, and decode (read-only).')}
|
|
6119
|
+
${dim('The collection-level counterpart of')} ${g('tokenuri')}${dim('. On-chain lane: decodes the data: URI. Off-chain lane: fetches the')}
|
|
6120
|
+
${dim('URL the contract itself commits to and prints the JSON.')}
|
|
6121
|
+
${bold('Never hand-build a resolver URL to check this')} ${dim('— the contract holds the answer, so a URL from here is right by')}
|
|
6122
|
+
${dim('construction. A bad status is then about the SERVICE (unregistered project · wrong chain · down), never a mistyped path.')}`,
|
|
5955
6123
|
forget: `
|
|
5956
6124
|
${bold('abx forget')} <address> ${dim('— drop a project’s local registration + projection. On-chain data is untouched.')}
|
|
5957
6125
|
${g('--remote [name|url]')} deregister on a REMOTE resolver instead (it stops serving the project; re-add any time)`,
|
|
@@ -6022,6 +6190,7 @@ function help() {
|
|
|
6022
6190
|
${g('abx index')} [<address>] re-index from chain (incremental by default; ${g('--full')} forces a replay from deploy)
|
|
6023
6191
|
${g('abx verify')} <addr> re-hash served bytes vs the on-chain commitment (no server needed)
|
|
6024
6192
|
${g('abx tokenuri')} <addr> read tokenURI(0) on-chain + decode the JSON (proof a self-resolving token works)
|
|
6193
|
+
${g('abx contracturi')} <addr> read contractURI() (ERC-7572 collection metadata) on-chain, follow it, decode — never hand-build the URL
|
|
6025
6194
|
${g('abx state')} <addr> one-glance on-chain snapshot: owner · supply · paused · minter · payee · royalty · renderer
|
|
6026
6195
|
${g('abx serve')} [--port ..] serve the token API + dashboard — and WATCH the chain: auto-index every registered
|
|
6027
6196
|
project + notify the effects layer on change (${g('ABX_WATCH_INTERVAL_MS')}; 0 = off)
|