@artblocks/abx-cli 0.1.0-alpha.11 → 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 +81 -18
- package/dist/main.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +1 -1
- package/skill/reference/hosting.md +4 -4
package/dist/main.js
CHANGED
|
@@ -3613,8 +3613,15 @@ async function cmdDeployCode(flags) {
|
|
|
3613
3613
|
// has nothing to run — point at the from-chain check instead.
|
|
3614
3614
|
if (hasImageRenderer && !hasProgram)
|
|
3615
3615
|
info(` ${g('nothing to run')} — verify from chain: ${bold('abx tokenuri ' + (predicted ?? '<address>'))} ${dim('(decodes name + on-chain SVG + traits)')}`);
|
|
3616
|
-
else
|
|
3617
|
-
|
|
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
|
+
}
|
|
3618
3625
|
if (dirUpload)
|
|
3619
3626
|
await noteStorageReadiness(storageOptions(storageOverrides(flags)), dirUpload.sizes);
|
|
3620
3627
|
if (deployer && salt && !parseSaltFlag(flags.salt)) {
|
|
@@ -3892,6 +3899,10 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3892
3899
|
const remote = remoteFlag(flags);
|
|
3893
3900
|
const resolverUrl = (remote?.url ?? process.env.ABX_RESOLVER_URL ?? resolveBaseUrl()).replace(/\/$/, '');
|
|
3894
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');
|
|
3895
3906
|
// Co-located (no --remote): record each declared output into the shared store's artifact
|
|
3896
3907
|
// registry so the local resolver's `artifacts` manifest enumerates it. Remote: the publish
|
|
3897
3908
|
// lane (adminToken) records rows on the hosted resolver instead.
|
|
@@ -3916,6 +3927,41 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3916
3927
|
else
|
|
3917
3928
|
ok(summary);
|
|
3918
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
|
+
}
|
|
3919
3965
|
/**
|
|
3920
3966
|
* `abx effects` — run the reference effect runner LOCALLY, in-process (the local counterpart to
|
|
3921
3967
|
* `abx deploy-effects`, which scaffolds a HOSTED runner). This is what makes a param change / new
|
|
@@ -3935,6 +3981,8 @@ async function cmdEffects(flags) {
|
|
|
3935
3981
|
// the shared artifact registry so the resolver's `artifacts` manifest enumerates it. With an
|
|
3936
3982
|
// admin token, the publish lane records rows on the hosted resolver instead.
|
|
3937
3983
|
const localStore = adminToken ? null : new SqliteStore();
|
|
3984
|
+
if (adminToken)
|
|
3985
|
+
requirePublishableBackend(flags, 'abx effects against a remote resolver');
|
|
3938
3986
|
const { EffectRunner, renderEffect } = await loadEffects();
|
|
3939
3987
|
const runner = new EffectRunner({
|
|
3940
3988
|
resolverUrl,
|
|
@@ -5320,10 +5368,16 @@ async function cmdDoctor(flags) {
|
|
|
5320
5368
|
try {
|
|
5321
5369
|
const backend = resolveBackend(storageOptions());
|
|
5322
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)';
|
|
5323
5377
|
if (h)
|
|
5324
|
-
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''}`);
|
|
5378
|
+
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''} · ${publishable}`);
|
|
5325
5379
|
else
|
|
5326
|
-
check('storage', true, `${backend.id} · configured`);
|
|
5380
|
+
check('storage', true, `${backend.id} · configured · ${publishable}`);
|
|
5327
5381
|
}
|
|
5328
5382
|
catch (err) {
|
|
5329
5383
|
check('storage', false, `${backendId} · ${err.message}`);
|
|
@@ -5532,13 +5586,19 @@ async function cmdDeployEffects(flags) {
|
|
|
5532
5586
|
const intervalMs = flags['interval-ms'] ? Number(flags['interval-ms']) : undefined;
|
|
5533
5587
|
const environmentId = flags['env-id'] ?? process.env.ABX_ENVIRONMENT_ID;
|
|
5534
5588
|
const storageBackend = process.env.ABX_STORAGE_BACKEND;
|
|
5535
|
-
// A HOSTED runner
|
|
5536
|
-
// (
|
|
5537
|
-
// which
|
|
5538
|
-
//
|
|
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.
|
|
5539
5594
|
if (!storageBackend || storageBackend === 'fs') {
|
|
5540
|
-
|
|
5541
|
-
`
|
|
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.)`);
|
|
5542
5602
|
}
|
|
5543
5603
|
const repoRoot = findRepoRoot();
|
|
5544
5604
|
if (!repoRoot) {
|
|
@@ -5560,10 +5620,7 @@ async function cmdDeployEffects(flags) {
|
|
|
5560
5620
|
const outDir = resolvePath(joinPath(dir, 'effects'));
|
|
5561
5621
|
console.log(bold(`\n ABX Self-Host Toolkit — provision effects runner (fly)`));
|
|
5562
5622
|
info(`resolver: ${bold(cleanResolver)} ${dim('(reads token state + publishes renders here)')}`);
|
|
5563
|
-
info(`storage home: ${bold(storageBackend
|
|
5564
|
-
if (!storageBackend || storageBackend === 'fs') {
|
|
5565
|
-
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.');
|
|
5566
|
-
}
|
|
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')}`);
|
|
5567
5624
|
step('Write the effects artifact (self-contained — Dockerfile.effects + fly.toml + vendored source)');
|
|
5568
5625
|
mkdirSync(outDir, { recursive: true });
|
|
5569
5626
|
for (const f of art.files) {
|
|
@@ -5807,8 +5864,12 @@ const COMMAND_HELP = {
|
|
|
5807
5864
|
No tokenId → sweeps all minted tokens; pass ids (${g('0 1 2')}) to target specific tokens.
|
|
5808
5865
|
${g('--force')} RE-RENDER even when the still already exists — the fix for a bad / blank / timed-out capture
|
|
5809
5866
|
(the art is otherwise deterministic, so a plain render idempotent-skips an existing still). Overwrites it (+ republishes on --remote).
|
|
5810
|
-
${g('--remote [name|url]')}
|
|
5811
|
-
|
|
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
|
|
5812
5873
|
${g('ABX_REMOTE_<NAME>_TOKEN')}, else ${g('ABX_RESOLVER_ADMIN_TOKEN')}).
|
|
5813
5874
|
--effects-url <url> enqueue on a running effect-runner service instead of rendering inline (else ${g('ABX_EFFECTS_URL')})
|
|
5814
5875
|
${dim('Inline (no --remote) renders on THIS machine (needs `npx playwright install chromium` once), pointing Chromium at the live')}
|
|
@@ -5825,8 +5886,10 @@ const COMMAND_HELP = {
|
|
|
5825
5886
|
--port <n> HTTP port (default ${g('ABX_EFFECTS_PORT')} / 8788) — ${g('POST /notify')} enqueues (watcher lane) · ${g('POST /run')} sweeps synchronously (command lane)
|
|
5826
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
|
|
5827
5888
|
--concurrency <n> parallel renders while draining (default ${g('ABX_EFFECTS_CONCURRENCY')} / 1 — Chromium is heavy; raise deliberately)
|
|
5828
|
-
${dim('Co-located with a local `abx serve` (same store) → no tokens needed
|
|
5829
|
-
${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.')}
|
|
5830
5893
|
${dim('To HOST the runner (fly/docker), use `abx deploy-effects`.')}`,
|
|
5831
5894
|
'configure-param': `
|
|
5832
5895
|
${bold('abx configure-param')} <address> <tokenId|-> <key> <value> ${dim('— set a PostParam (typed, canonical encode). Sends a tx.')}
|