@artblocks/abx-cli 0.1.0-alpha.2 → 0.1.0-alpha.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/dist/main.js +188 -82
- package/dist/main.js.map +1 -1
- package/dist/preview.d.ts +63 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +483 -0
- package/dist/preview.js.map +1 -0
- package/package.json +7 -4
- package/skill/SKILL.md +49 -5
- package/skill/reference/code-projects.md +35 -0
package/dist/main.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* abx demo deploy a 1/1 to Sepolia, index it, and serve it
|
|
15
15
|
* abx deploy [--image ..] deploy + index a 1/1 (--image to custody your own bytes; --no-mint to defer)
|
|
16
16
|
* abx predict [--salt ..] pre-compute a deploy address (reserve / vanity it before signing)
|
|
17
|
+
* abx preview run a code project on localhost while it's still being made (no chain)
|
|
17
18
|
* abx deploy-code deploy a code project (SeriesCode): --script <file> (on-chain template)
|
|
18
19
|
* or --code-dir <dir> (build directory → ipfs/arweave `code` field)
|
|
19
20
|
* abx add <address> register + index a project (--remote: on a hosted resolver, not this machine)
|
|
@@ -57,6 +58,7 @@ import { composeParamsKeys, expectedChainComplete, hasOnChainUriLane, onchainUri
|
|
|
57
58
|
import { parseFlags, unknownFlags } from './flags.js';
|
|
58
59
|
import { AGENT_SKILL_PARENTS, checkForCliUpdate, compareVersions, installedSkillVersions, readCliVersion, readSkillVersion, SKILL_DIR_NAME, } from './update-check.js';
|
|
59
60
|
import { analyzeScript, recommendLane } from './inspect.js';
|
|
61
|
+
import { previewConfigFromFlags, previewDepTags, shootPreview, startPreviewServer, DEFAULT_PREVIEW_PORT, PREVIEW_FLAGS } from './preview.js';
|
|
60
62
|
import { parseSchemaSpecs, describeSchema } from './schema.js';
|
|
61
63
|
import { parseSeriesTraits, looksPerTokenAttributes, parseSeriesTraitsById } from './series-traits.js';
|
|
62
64
|
/** The `--schema` type/auth/format catalog, shown wherever the CLI nudges `--schema`. Kept accurate
|
|
@@ -134,7 +136,7 @@ async function maybeNotifyUpdate(flags) {
|
|
|
134
136
|
const latest = await checkForCliUpdate(current);
|
|
135
137
|
if (latest) {
|
|
136
138
|
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` +
|
|
139
|
+
` upgrade: ${g('npm i -g @artblocks/abx-cli@latest')} ${dim('· or invoke:')} ${g('npx @artblocks/abx-cli@latest <command>')}\n` +
|
|
138
140
|
` release notes: https://github.com/ArtBlocks/abx/releases ${dim('· silence: ABX_NO_UPDATE_CHECK=1')}\n`);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
@@ -166,6 +168,7 @@ async function main() {
|
|
|
166
168
|
case 'deploy-series': return cmdDeploySeries(flags);
|
|
167
169
|
case 'deploy-code': return cmdDeployCode(flags);
|
|
168
170
|
case 'inspect': return cmdInspect(rest[0], flags);
|
|
171
|
+
case 'preview': return cmdPreview(flags);
|
|
169
172
|
case 'scaffold-renderer': return cmdScaffoldRenderer(rest, flags);
|
|
170
173
|
case 'predict': return cmdPredict(flags);
|
|
171
174
|
case 'add': return cmdAdd(rest[0], flags);
|
|
@@ -984,7 +987,24 @@ async function cmdDeploy(flags, serveAfter) {
|
|
|
984
987
|
// lockable) — mirrors the description model. On-chain wins if both ever set the same trait.
|
|
985
988
|
const traits = parseDeployTraits(flags);
|
|
986
989
|
console.log(bold(`\n ABX Self-Host Toolkit — ${serveAfter ? 'demo' : 'deploy'}\n ${dim('a project, served from chain alone')}`));
|
|
987
|
-
|
|
990
|
+
// Signing lane. `demo` used to hard-force the hot lane, which SILENTLY dropped `--sign`: with no
|
|
991
|
+
// env key it died confusingly, and WITH one it signed from that key while the operator had asked
|
|
992
|
+
// for their browser wallet — a signing choke point that ignored the lane it was handed. The demo
|
|
993
|
+
// deploy is a single tx, so the wallet lane works here exactly as it does for `deploy`.
|
|
994
|
+
const lane = laneFromFlags(flags);
|
|
995
|
+
// The cold lane only PRINTS a tx; demo's whole point is to index + serve what it just deployed,
|
|
996
|
+
// and there is nothing to index until someone broadcasts. Refuse the combo instead of doing
|
|
997
|
+
// half the job — `abx deploy --unsigned` is the command for that lane.
|
|
998
|
+
if (serveAfter && lane === 'unsigned') {
|
|
999
|
+
throw new Error('`abx demo --unsigned` can\'t work: the cold lane only prints a transaction, and the demo ' +
|
|
1000
|
+
'indexes + serves the contract it just deployed. Use `abx demo` (hot key) or `abx demo --sign` ' +
|
|
1001
|
+
'(browser wallet) — or `abx deploy --unsigned` if you only want the raw tx.');
|
|
1002
|
+
}
|
|
1003
|
+
// Same shape for --dry-run: previewing sends nothing, so there is nothing to serve.
|
|
1004
|
+
if (serveAfter && flags['dry-run']) {
|
|
1005
|
+
throw new Error('`abx demo --dry-run` can\'t work: a dry run sends nothing, and the demo indexes + serves what ' +
|
|
1006
|
+
'it deployed. Use `abx deploy --dry-run` to preview a 1/1 deploy without sending.');
|
|
1007
|
+
}
|
|
988
1008
|
const dryRun = !serveAfter && !!flags['dry-run']; // preview only — no send, no custody, no factory deploy
|
|
989
1009
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
990
1010
|
// Verify the RPC really is CHAIN before any send (factory/renderer/staging/deploy). A dry run
|
|
@@ -2227,6 +2247,78 @@ async function ensureSeedSource(publicClient) {
|
|
|
2227
2247
|
* Schemas: --schema key:Type:Auth[,key:Type:Auth…] (e.g. palette:HexColor:TokenOwner).
|
|
2228
2248
|
* Seeds: canonical randomizer by default; --no-seed opts out.
|
|
2229
2249
|
*/
|
|
2250
|
+
/**
|
|
2251
|
+
* `abx preview` — serve the program on localhost, live, for as long as the work is being made.
|
|
2252
|
+
*
|
|
2253
|
+
* The studio lane, and deliberately the FIRST thing to reach for on a code project: it renders the
|
|
2254
|
+
* same document the generator serves (real `abx.js`, real tokenData shape, real dependency tags)
|
|
2255
|
+
* with a synthetic seed, so a creator can refresh for new seeds, drive their PostParams from real
|
|
2256
|
+
* inputs, and watch an animated piece actually move — none of which a still-image sweep can show.
|
|
2257
|
+
* No chain, no key, no deploy. `--shoot` renders the same document headlessly for an agent that
|
|
2258
|
+
* can't open a browser.
|
|
2259
|
+
*/
|
|
2260
|
+
async function cmdPreview(flags) {
|
|
2261
|
+
warnStrayFlags(flags, PREVIEW_FLAGS, 'preview');
|
|
2262
|
+
const cfg = previewConfigFromFlags(flags);
|
|
2263
|
+
const shootDir = flags.shoot && flags.shoot !== 'true' ? String(flags.shoot) : flags.shoot === 'true' ? 'abx-preview' : undefined;
|
|
2264
|
+
const count = Math.min(Math.max(Number(flags.count ?? 9) || 9, 1), 64);
|
|
2265
|
+
console.log(bold(`\n ABX Self-Host Toolkit — preview\n ${dim('the program, running locally — no chain, no deploy')}`));
|
|
2266
|
+
const { notes } = previewDepTags(cfg.deps);
|
|
2267
|
+
step('Program');
|
|
2268
|
+
info(cfg.source.kind === 'dir' ? `directory build ${cfg.source.path}/ (its own index.html + abx.js)` : `script ${cfg.source.path} ${dim('(re-read from disk on every render)')}`);
|
|
2269
|
+
if (cfg.schemas.length)
|
|
2270
|
+
info(`params: ${cfg.schemas.map(describeSchema).join(' · ')}`);
|
|
2271
|
+
else
|
|
2272
|
+
info(dim('params: none declared — add --schema key:Type:Auth to drive them from the studio'));
|
|
2273
|
+
for (const n of notes)
|
|
2274
|
+
info(`dep ${n}`);
|
|
2275
|
+
// A raw on-chain dependency can't be fetched without a chain, so the preview would render a
|
|
2276
|
+
// sketch missing its runtime and look broken for the wrong reason. Say so rather than let them
|
|
2277
|
+
// debug their own art.
|
|
2278
|
+
if (cfg.deps.some((d) => d.display.startsWith('0x'))) {
|
|
2279
|
+
warn('an on-chain data-contract dep is NOT loaded in preview — the sketch will run without it here. Use a name@version ref to preview against the CDN copy.');
|
|
2280
|
+
}
|
|
2281
|
+
const server = await startPreviewServer(cfg, shootDir ? 0 : Number(flags.port ?? DEFAULT_PREVIEW_PORT));
|
|
2282
|
+
if (shootDir) {
|
|
2283
|
+
step(`Render ${count} seeds headlessly`);
|
|
2284
|
+
try {
|
|
2285
|
+
const shots = await shootPreview(server.url, shootDir, count, {
|
|
2286
|
+
width: Number(flags.width ?? 1000) || 1000,
|
|
2287
|
+
timeoutMs: Number(flags['timeout-ms'] ?? 10_000) || 10_000,
|
|
2288
|
+
});
|
|
2289
|
+
ok(`${shots.length} frames → ${shootDir}/ ${dim('(traits in traits.json)')}`);
|
|
2290
|
+
for (const s of shots) {
|
|
2291
|
+
const t = s.traits && Object.keys(s.traits).length
|
|
2292
|
+
? Object.entries(s.traits).map(([k, v]) => `${k} ${String(v)}`).join(' · ')
|
|
2293
|
+
: `${c.orange}no traits reported${c.reset}`;
|
|
2294
|
+
console.log(` ${dim(s.seed.slice(0, 10) + '…')} ${t}${s.done ? '' : dim(' (no abx.done())')}`);
|
|
2295
|
+
}
|
|
2296
|
+
const silent = shots.filter((s) => !s.traits || !Object.keys(s.traits).length).length;
|
|
2297
|
+
if (silent === shots.length) {
|
|
2298
|
+
warn('NO frame reported traits — `abx.traits({…})` is the only thing that becomes marketplace `attributes`. Verify with `abx inspect`.');
|
|
2299
|
+
}
|
|
2300
|
+
else if (shots.length > 1 && new Set(shots.map((s) => JSON.stringify(s.traits))).size === 1) {
|
|
2301
|
+
// Only meaningful when traits DID come back: identical values across seeds is the signature
|
|
2302
|
+
// of a sketch that never reads `abx.tokenData.seed` (prototyped on Math.random()), which
|
|
2303
|
+
// deploys as N visually identical tokens. Skipped when nothing reported at all — the
|
|
2304
|
+
// warning above already covers that, and firing both reads as noise.
|
|
2305
|
+
warn('every seed produced identical traits — check the sketch actually reads `abx.tokenData.seed` (the silent "all tokens the same" failure).');
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
finally {
|
|
2309
|
+
await server.close();
|
|
2310
|
+
}
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
step('Studio');
|
|
2314
|
+
console.log(`\n ${g('●')} ${bold('preview')} ${server.url}`);
|
|
2315
|
+
console.log(` ${dim('studio ')}${server.url} ${dim('seed + params + live traits')}`);
|
|
2316
|
+
console.log(` ${dim('grid ')}${server.url}/grid ${dim('9 seeds at once, all live')}`);
|
|
2317
|
+
console.log(` ${dim('bare view ')}${server.url}/view ${dim('the generator document itself')}`);
|
|
2318
|
+
console.log(`\n ${dim('Edit the program and refresh — it is re-read from disk. Ctrl-C to stop.')}`);
|
|
2319
|
+
console.log(` ${dim('This is a preview: `abx inspect` is still the wiring check, and a testnet deploy is the faithful end-to-end.')}\n`);
|
|
2320
|
+
await new Promise(() => { }); // block like `serve` — the creator drives it
|
|
2321
|
+
}
|
|
2230
2322
|
/**
|
|
2231
2323
|
* `abx inspect <script.js>` — read a generative script and, WITHOUT executing it, report what it
|
|
2232
2324
|
* needs (traits + their on-chain reproducibility, dependency hints, size → assembled-document size →
|
|
@@ -4167,114 +4259,109 @@ async function cmdMigrate(address, flags) {
|
|
|
4167
4259
|
}
|
|
4168
4260
|
// ── doctor ────────────────────────────────────────────────────────────────--
|
|
4169
4261
|
async function cmdDoctor(flags) {
|
|
4170
|
-
console.log(bold('\n abx doctor\n')
|
|
4171
|
-
|
|
4262
|
+
console.log(bold('\n abx doctor') + dim(` · ${CHAIN}`) + '\n');
|
|
4263
|
+
// Two visual tiers: PASS/FAIL checks (✓/✗) for things that are either working or broken, and an
|
|
4264
|
+
// "Optional" block (·) for path-dependent setup that is fine to be unset. We deliberately do NOT
|
|
4265
|
+
// use ⚠ for "unset but often fine" — that read as noise; ⚠ is reserved for a real gotcha (a
|
|
4266
|
+
// range-capped RPC). Labels are padded so both tiers align.
|
|
4267
|
+
const CONT = ' '.repeat(17); // continuation indent: aligns under a check/opt detail column
|
|
4268
|
+
const check = (label, pass, detail = '') => console.log(` ${pass ? g('✓') : `${c.red}✗${c.reset}`} ${label.padEnd(13)}${detail ? dim(detail) : ''}`);
|
|
4269
|
+
const opt = (label, detail) => console.log(` ${dim('·')} ${label.padEnd(13)}${dim(detail)}`);
|
|
4172
4270
|
const hasKey = !!(process.env.ABX_DEPLOYER_PK ?? process.env.SEPOLIA_FUNDED_PK ?? process.env.SEPOLIA_WALLET_PK);
|
|
4173
|
-
//
|
|
4174
|
-
//
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4271
|
+
// 1. Agent skill — FIRST and prominent. The primary way to use abx is to let a coding agent drive
|
|
4272
|
+
// it, so a missing/stale skill is a ✗: not broken infra, but the main UX isn't set up. Its
|
|
4273
|
+
// version lives in SKILL.md frontmatter (version-locked to this CLI). Notify-only — no exit code.
|
|
4274
|
+
const cliVersion = readCliVersion();
|
|
4275
|
+
const skillVersions = installedSkillVersions();
|
|
4276
|
+
const staleSkills = skillVersions.filter((v) => compareVersions(cliVersion, v) > 0);
|
|
4277
|
+
if (skillVersions.length === 0) {
|
|
4278
|
+
check('agent skill', false, `not installed — run ${g('abx skill install')}`);
|
|
4279
|
+
console.log(`${CONT}${dim('(recommended: let a coding agent drive abx)')}`);
|
|
4280
|
+
}
|
|
4281
|
+
else if (staleSkills.length > 0) {
|
|
4282
|
+
check('agent skill', false, `v${staleSkills.join(', v')} behind CLI v${cliVersion} — run ${g('abx skill install')}`);
|
|
4283
|
+
}
|
|
4284
|
+
else {
|
|
4285
|
+
check('agent skill', true, `in sync (v${cliVersion})`);
|
|
4286
|
+
}
|
|
4287
|
+
console.log('');
|
|
4288
|
+
// 2. Core environment (✓/✗). Signing-wallet balances are computed here (they need the RPC) but
|
|
4289
|
+
// printed in the Optional block below, so buffer them.
|
|
4290
|
+
let signingOpt = null;
|
|
4291
|
+
let forOpt = null;
|
|
4179
4292
|
try {
|
|
4180
4293
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
4181
4294
|
const bn = await publicClient.getBlockNumber();
|
|
4182
|
-
|
|
4183
|
-
//
|
|
4184
|
-
// so the toolkit uses (and recommends) the one fit for the job — and says so if none are.
|
|
4295
|
+
// Collapse the RPC report to one line (best endpoint + head), and only add a ⚠ when there is a
|
|
4296
|
+
// genuine problem — a range-capped-only set that will grind a resolver under load.
|
|
4185
4297
|
const probes = await probeRpcEndpoints({ chainKey: CHAIN });
|
|
4186
4298
|
const usable = probes.filter((pr) => pr.verdict !== 'unusable');
|
|
4187
4299
|
const best = probes.find((pr) => pr.verdict === 'best') ?? usable[0];
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
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)}`}`);
|
|
4300
|
+
if (usable.length > 0) {
|
|
4301
|
+
check('RPC', true, `${best.label} · head ${bn} · ${best.verdict === 'best' ? 'wide range + archive' : 'range-capped'}`);
|
|
4302
|
+
if (!probes.some((pr) => pr.verdict === 'best')) {
|
|
4303
|
+
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')}`);
|
|
4304
|
+
}
|
|
4215
4305
|
}
|
|
4216
|
-
else
|
|
4217
|
-
|
|
4306
|
+
else {
|
|
4307
|
+
check('RPC', false, `${CHAIN} — no endpoint usable for reconstruction; add a wide-range archive RPC to ABX_RPC_URLS`);
|
|
4218
4308
|
}
|
|
4219
4309
|
const factory = factoryAddress();
|
|
4220
4310
|
if (factory) {
|
|
4221
4311
|
const code = await publicClient.getCode({ address: factory });
|
|
4222
|
-
if (!code || code === '0x')
|
|
4223
|
-
check('
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
else {
|
|
4229
|
-
check('canonical factory deployed', false, `${factory} — older/incompatible version; \`abx deploy\` redeploys`);
|
|
4230
|
-
}
|
|
4312
|
+
if (!code || code === '0x')
|
|
4313
|
+
check('factory', false, `${factory} — no code on ${CHAIN}; \`abx deploy\` redeploys`);
|
|
4314
|
+
else if (await isCurrentFactory(publicClient, factory))
|
|
4315
|
+
check('factory', true, factory);
|
|
4316
|
+
else
|
|
4317
|
+
check('factory', false, `${factory} — older/incompatible; \`abx deploy\` redeploys`);
|
|
4231
4318
|
}
|
|
4232
4319
|
else {
|
|
4233
|
-
check('
|
|
4320
|
+
check('factory', false, 'none yet — `abx demo` deploys one');
|
|
4321
|
+
}
|
|
4322
|
+
if (hasKey) {
|
|
4323
|
+
const { account } = makeWalletClient({ chainKey: CHAIN });
|
|
4324
|
+
const bal = await publicClient.getBalance({ address: account.address });
|
|
4325
|
+
signingOpt = `env key ${account.address} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — fund it (${faucetHint(CHAIN)})`}`;
|
|
4326
|
+
}
|
|
4327
|
+
if (flags.for) {
|
|
4328
|
+
const bal = await publicClient.getBalance({ address: flags.for });
|
|
4329
|
+
forOpt = `${flags.for} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — ${faucetHint(CHAIN)}`}`;
|
|
4234
4330
|
}
|
|
4235
4331
|
}
|
|
4236
4332
|
catch (err) {
|
|
4237
|
-
check('RPC
|
|
4333
|
+
check('RPC', false, err.message);
|
|
4238
4334
|
}
|
|
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
4335
|
// storage backend — resolve it (catches missing config), then probe liveness/creds
|
|
4247
4336
|
const backendId = activeBackendId();
|
|
4248
4337
|
try {
|
|
4249
4338
|
const backend = resolveBackend(storageOptions());
|
|
4250
4339
|
const h = await backend.health?.();
|
|
4251
4340
|
if (h)
|
|
4252
|
-
check(
|
|
4341
|
+
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''}`);
|
|
4253
4342
|
else
|
|
4254
|
-
check(
|
|
4343
|
+
check('storage', true, `${backend.id} · configured`);
|
|
4255
4344
|
}
|
|
4256
4345
|
catch (err) {
|
|
4257
|
-
check(
|
|
4346
|
+
check('storage', false, `${backendId} · ${err.message}`);
|
|
4258
4347
|
}
|
|
4259
|
-
//
|
|
4348
|
+
// 3. Optional — path-dependent setup. Unset is fine; these say WHEN you'll need each, so an unset
|
|
4349
|
+
// value never reads as a warning.
|
|
4350
|
+
console.log(`\n ${dim('Optional — depends how you deploy:')}`);
|
|
4351
|
+
if (signingOpt)
|
|
4352
|
+
opt('signing', signingOpt);
|
|
4353
|
+
else
|
|
4354
|
+
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.');
|
|
4355
|
+
if (forOpt)
|
|
4356
|
+
opt('wallet --for', forOpt);
|
|
4357
|
+
const baseUrl = process.env.ABX_PUBLIC_BASE_URL;
|
|
4358
|
+
if (baseUrl)
|
|
4359
|
+
opt('resolver URL', baseUrl);
|
|
4360
|
+
else
|
|
4361
|
+
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
4362
|
if (!process.env.ARWEAVE_JWK && existsSync(arweaveKeyFilePath())) {
|
|
4261
4363
|
const jwk = loadArweaveJwk();
|
|
4262
|
-
|
|
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);
|
|
4364
|
+
opt('arweave key', `${jwk ? arweaveAddress(jwk) + ' ' : ''}holds upload credits — back it up: \`abx storage backup-key --out <path>\``);
|
|
4278
4365
|
}
|
|
4279
4366
|
console.log('');
|
|
4280
4367
|
}
|
|
@@ -4639,6 +4726,20 @@ const COMMAND_HELP = {
|
|
|
4639
4726
|
${g('abx deploy-series')} --dir ./photos --name "My Series" --symbol MS --onchain-uri --backend ipfs --mint-all --sign
|
|
4640
4727
|
${dim('# tiny SVGs → fully on-chain (no storage at all):')}
|
|
4641
4728
|
${g('abx deploy-series')} --dir ./svgs --name "My Series" --symbol MS --onchain-image --compress fastlz --mint-all --sign`,
|
|
4729
|
+
preview: `
|
|
4730
|
+
${bold('abx preview')} (--script <file.js> | --code-dir <dir>) ${dim('— run the program on localhost, live. No chain, no key, no deploy.')}
|
|
4731
|
+
${g('--schema key:Type:Auth')}[,…] declare PostParams so the studio gives you real inputs for them (e.g. palette:HexColor:TokenOwner)
|
|
4732
|
+
${g('--dep <name@version>')}[,…] load a library the way the resolver would (built-in CDN map; on-chain refs can't be fetched offline)
|
|
4733
|
+
${g('--port')} <n> studio port (default ${DEFAULT_PREVIEW_PORT}; the resolver's ${DEFAULT_PORT} stays free)
|
|
4734
|
+
${g('--shoot')} <dir> render headlessly to PNGs + traits.json and EXIT ${dim('(for an agent that has no browser)')}
|
|
4735
|
+
${g('--count')} <n> seeds to shoot / show in the grid (default 9) ${g('--width')} <px> ${g('--timeout-ms')} <n>
|
|
4736
|
+
Serves the ${bold('same document the generator serves')} — real ${g('abx.js')}, real tokenData shape, real dep tags — with a synthetic
|
|
4737
|
+
seed, so what you iterate on is what deploys. Routes: ${g('/')} studio (seed + params + live traits) · ${g('/grid')} N seeds at once,
|
|
4738
|
+
all live · ${g('/view')} the bare document. The program is re-read from disk per render, so ${bold('edit and refresh')} — no watcher.
|
|
4739
|
+
Unlike a still-image sweep this shows ${bold('animation')}, which is most of what a screenshot throws away.
|
|
4740
|
+
${dim('Still do both after the art settles:')} ${g('abx inspect')} ${dim('(is it wired right?) and a testnet deploy (the faithful end-to-end).')}
|
|
4741
|
+
${g('abx preview')} --script art.js --schema palette:HexColor:TokenOwner
|
|
4742
|
+
${g('abx preview')} --script art.js --shoot ./frames --count 12`,
|
|
4642
4743
|
inspect: `
|
|
4643
4744
|
${bold('abx inspect')} <script.js> ${dim('— static analysis of a generative script + a lane recommendation. Read-only; the script is never executed.')}
|
|
4644
4745
|
${g('--dep <name@version>[,…]')} the on-chain deps you plan to declare, so the assembled-document size estimate is realistic (e.g. --dep p5@1.0.0)
|
|
@@ -4896,7 +4997,10 @@ const COMMAND_HELP = {
|
|
|
4896
4997
|
${g('upload')} <path> upload ONE file → prints its locator (the URI ${g('abx attach')} wants) [--backend …] [--dry-run]
|
|
4897
4998
|
${g('balance')} · ${g('topup')} --usd <n> Turbo (arweave) upload credits · ${g('backup-key')} --out <path> copy the managed key`,
|
|
4898
4999
|
demo: `
|
|
4899
|
-
${bold('abx demo')} <${dim('no args')}> ${dim('— deploy a throwaway 1/1 to the testnet, index it, and serve it — a guided first run. Sends a tx.')}
|
|
5000
|
+
${bold('abx demo')} <${dim('no args')}> ${dim('— deploy a throwaway 1/1 to the testnet, index it, and serve it — a guided first run. Sends a tx.')}
|
|
5001
|
+
${g('--sign')} ${dim('approve in your browser wallet instead of a hot env key (no key needed)')}
|
|
5002
|
+
${g('--for')} <0x…> ${dim('pin who must connect on --sign (owner + royalty receiver + mint recipient)')}
|
|
5003
|
+
${dim('--unsigned / --dry-run are refused here: both skip the broadcast, and the demo indexes + serves what it deployed.')}`,
|
|
4900
5004
|
minter: `
|
|
4901
5005
|
${bold('abx minter')} <configure|show|buy> <token> ${dim('— sell a Series via the shared fixed-price minter (Minter spine).')}
|
|
4902
5006
|
${g('configure')} <token> (--price <eth> | --price-raw <units>) --allocation <n> [--erc20 0x..] ${dim('(token-owner only)')}
|
|
@@ -4990,7 +5094,9 @@ function help() {
|
|
|
4990
5094
|
project + notify the effects layer on change (${g('ABX_WATCH_INTERVAL_MS')}; 0 = off)
|
|
4991
5095
|
|
|
4992
5096
|
${bold('code / generative projects')} ${dim('— a program is the content; output is a function of live on-chain state')}
|
|
4993
|
-
${g('abx
|
|
5097
|
+
${g('abx preview')} (--script <f> | --code-dir <d>) ${bold('while you are still making it')} — run the program on localhost, live: refresh for new seeds,
|
|
5098
|
+
drive your PostParams, watch it animate. Same document the generator serves. ${g('--shoot <dir>')} for headless frames. No chain.
|
|
5099
|
+
${g('abx inspect')} <script.js> ${bold('before you pick a lane')} — static analysis (traits + on-chain reproducibility, deps, doc size → RPC viability) + a lane recommendation
|
|
4994
5100
|
${g('abx scaffold-renderer')} [<dir>] write a buildable Foundry project for the ${bold('in-chain Solidity art lane')} (seed + PostParam → on-chain SVG + traits; you forge build/test/deploy)
|
|
4995
5101
|
${g('abx deploy-code')} (--script <file> | --code-dir <dir> | ${g('--image-renderer 0x..')}) deploy a ${bold('generative / code project')} (on-chain script, a build directory, or a Solidity SVG renderer — in-chain art)
|
|
4996
5102
|
${bold('--public-base-url <url>')} OR ${bold('--onchain-uri')} · --schema key:Type:Auth · ${g('--dep')} name@version|0x.. (ordered; index 0 = the runtime) ·
|