@artblocks/abx-cli 0.1.0-alpha.5 → 0.1.0-alpha.6

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/config.d.ts CHANGED
@@ -8,6 +8,17 @@ export declare function dataDir(): string;
8
8
  * duplicated inline — that drift is exactly how `deploy-code` shipped without the guard).
9
9
  */
10
10
  export declare function loopbackBaseUrl(baseUrl: string): boolean;
11
+ /**
12
+ * A short faucet pointer for a testnet — a 0-balance signer's #1 next step. A known URL plus a
13
+ * search hint (URLs rot; the search always works), so "fund it" is never a dead end.
14
+ *
15
+ * Lives here rather than in main.ts because the browser-wallet signing prompt needs it too, and
16
+ * importing main.ts would execute the CLI. That prompt is the one funding surface `warnUnfunded`
17
+ * can't cover: on the `--sign` lane without `--for` we don't learn the address until the wallet
18
+ * connects, so the up-front balance check never runs and the only warning a creator would get is
19
+ * a failed transaction.
20
+ */
21
+ export declare function faucetHint(chainKey: string): string;
11
22
  /** Canonical clone factory for the active chain — flag override → `ABX_FACTORY` → manifest. */
12
23
  export declare function factoryAddress(override?: string): Address | null;
13
24
  /** Canonical Series clone factory — flag override → `ABX_SERIES_FACTORY` → manifest. */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASL,KAAK,OAAO,EACb,MAAM,oBAAoB,CAAC;AAoB5B,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAOD,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEhE;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEtE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEjE;AAED,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEnE;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEzE;AAID,yGAAyG;AACzG,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACpC;;4CAEwC;IACxC,aAAa,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;CACnC;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,EAAE,GAAE,gBAAqB,GAAG,SAAS,GAAG,KAAK,CAGhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,GAAE,gBAAqB,GAAG,qBAAqB,CAgD/E;AAED,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,gBAAqB,GAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;CAAC,CAIlH;AAaD,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,SAAS,CAKvD;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,IAAI;IAAC,GAAG,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAQrG"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASL,KAAK,OAAO,EACb,MAAM,oBAAoB,CAAC;AAoB5B,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQnD;AAOD,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEhE;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEtE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEjE;AAED,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEnE;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAEzE;AAID,yGAAyG;AACzG,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACpC;;4CAEwC;IACxC,aAAa,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;CACnC;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,EAAE,GAAE,gBAAqB,GAAG,SAAS,GAAG,KAAK,CAGhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,GAAE,gBAAqB,GAAG,qBAAqB,CAgD/E;AAED,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,gBAAqB,GAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;CAAC,CAIlH;AAaD,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,SAAS,CAKvD;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,IAAI;IAAC,GAAG,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAQrG"}
package/dist/config.js CHANGED
@@ -30,6 +30,25 @@ export function dataDir() {
30
30
  export function loopbackBaseUrl(baseUrl) {
31
31
  return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?:[:/]|$)/i.test(baseUrl);
32
32
  }
33
+ /**
34
+ * A short faucet pointer for a testnet — a 0-balance signer's #1 next step. A known URL plus a
35
+ * search hint (URLs rot; the search always works), so "fund it" is never a dead end.
36
+ *
37
+ * Lives here rather than in main.ts because the browser-wallet signing prompt needs it too, and
38
+ * importing main.ts would execute the CLI. That prompt is the one funding surface `warnUnfunded`
39
+ * can't cover: on the `--sign` lane without `--for` we don't learn the address until the wallet
40
+ * connects, so the up-front balance check never runs and the only warning a creator would get is
41
+ * a failed transaction.
42
+ */
43
+ export function faucetHint(chainKey) {
44
+ const url = {
45
+ 'base-sepolia': 'https://portal.cdp.coinbase.com/products/faucet',
46
+ sepolia: 'https://www.alchemy.com/faucets/ethereum-sepolia',
47
+ };
48
+ return url[chainKey]
49
+ ? `get free test ETH from a ${chainKey} faucet (${url[chainKey]} — or search "${chainKey} faucet"), usually ≤1 min`
50
+ : `get test ETH from a "${chainKey}" faucet`;
51
+ }
33
52
  // ── canonical infrastructure (shipped manifest → env → flag) ─────────────────
34
53
  // Thin CLI wrappers over the SDK resolvers, pinned to the active chain. Return null (not
35
54
  // undefined) to read cleanly at call sites (`factoryAddress() ?? 'none'`). `null` means the
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/main.js CHANGED
@@ -60,6 +60,7 @@ import { AGENT_SKILL_PARENTS, checkForCliUpdate, compareVersions, installedSkill
60
60
  import { analyzeScript, recommendLane } from './inspect.js';
61
61
  import { previewConfigFromFlags, previewDepTags, shootPreview, startPreviewServer, DEFAULT_PREVIEW_PORT, PREVIEW_FLAGS } from './preview.js';
62
62
  import { parseSchemaSpecs, describeSchema } from './schema.js';
63
+ import { declinesSkillInstall } from './prompt.js';
63
64
  import { parseSeriesTraits, looksPerTokenAttributes, parseSeriesTraitsById } from './series-traits.js';
64
65
  /** The `--schema` type/auth/format catalog, shown wherever the CLI nudges `--schema`. Kept accurate
65
66
  * to the on-chain enums (PARAM_TYPES / AUTH_OPTIONS) + the Select-options / Range-bounds format —
@@ -77,7 +78,7 @@ import { cmdTransfer, cmdMint, cmdSetMinter, cmdSetMaxInvocations, cmdConfigureP
77
78
  import { openWalletSession, signTx } from './signer.js';
78
79
  import { remoteAddProject, remoteRemoveProject } from './remote.js';
79
80
  import { buildMigrationPlan, repinNodeCustody, verifyParity } from './migrate.js';
80
- import { activeBackendId, arweaveKeyFilePath, backendResolution, ensureArweaveJwk, factoryAddress, seriesFactoryAddress, fixedPriceMinterAddress, loadArweaveJwk, loopbackBaseUrl, rendererAddress, storageOptions, storageSignerChoice, } from './config.js';
81
+ import { activeBackendId, arweaveKeyFilePath, backendResolution, ensureArweaveJwk, factoryAddress, seriesFactoryAddress, fixedPriceMinterAddress, loadArweaveJwk, loopbackBaseUrl, faucetHint, rendererAddress, storageOptions, storageSignerChoice, } from './config.js';
81
82
  const CHAIN = process.env.ABX_CHAIN ?? DEFAULT_CHAIN_KEY;
82
83
  // Block explorer base per chain (keyed by EIP-155 chainId), for the tx/address links the CLI prints.
83
84
  const EXPLORERS = {
@@ -297,17 +298,6 @@ async function detectCanonicalFactory(address, override, stored) {
297
298
  }
298
299
  return factoryAddress() ?? undefined;
299
300
  }
300
- /** A short faucet pointer for the active testnet — a 0-balance signer's #1 next step. A known URL
301
- * plus a search hint (URLs rot; the search always works), so "fund it" isn't a dead end. */
302
- function faucetHint(chainKey) {
303
- const url = {
304
- 'base-sepolia': 'https://portal.cdp.coinbase.com/products/faucet',
305
- sepolia: 'https://www.alchemy.com/faucets/ethereum-sepolia',
306
- };
307
- return url[chainKey]
308
- ? `get free test ETH from a ${chainKey} faucet (${url[chainKey]} — or search "${chainKey} faucet"), usually ≤1 min`
309
- : `get test ETH from a "${chainKey}" faucet`;
310
- }
311
301
  // Funding preflight — a 0-balance signer fails only at the tx, with a confusing error. Surface it
312
302
  // up front. Especially the wallet lane (--sign/--for), which has no env key for `doctor` to check.
313
303
  async function warnUnfunded(publicClient, address) {
@@ -987,7 +977,7 @@ async function cmdDeploy(flags, serveAfter) {
987
977
  // lockable) — mirrors the description model. On-chain wins if both ever set the same trait.
988
978
  const traits = parseDeployTraits(flags);
989
979
  console.log(serveAfter
990
- ? bold(`\n ABX guided first run\n ${dim('a token deployed, then rebuilt from the chain alone')}`)
980
+ ? bold(`\n ABX · your first token\n ${dim("we'll put art on a blockchain then delete our copy and get all of it back")}`)
991
981
  : bold(`\n ABX Self-Host Toolkit — deploy\n ${dim('a project, served from chain alone')}`));
992
982
  // Signing lane. `demo` used to hard-force the hot lane, which SILENTLY dropped `--sign`: with no
993
983
  // env key it died confusingly, and WITH one it signed from that key while the operator had asked
@@ -1048,7 +1038,7 @@ async function cmdDeploy(flags, serveAfter) {
1048
1038
  if (signer)
1049
1039
  await warnUnfunded(publicClient, signer);
1050
1040
  }
1051
- step('Trust anchor');
1041
+ step(serveAfter ? 'Who vouches for this token?' : 'Trust anchor');
1052
1042
  let factory;
1053
1043
  if (dryRun) {
1054
1044
  const existing = factoryAddress(flags.factory);
@@ -1064,10 +1054,13 @@ async function cmdDeploy(flags, serveAfter) {
1064
1054
  factory = await ensureFactory(flags.factory, !!flags['bootstrap-factory']);
1065
1055
  }
1066
1056
  // The demo explains what it just did; `deploy` stays terse. The authenticity point is the least
1067
- // obvious thing about ABX and the easiest to get wrong, so it's worth a sentence here.
1057
+ // obvious thing about ABX and the easiest to get wrong, so it's worth spelling out — but in plain
1058
+ // language, with the actual term parked in a dim aside for whoever wants to look it up.
1068
1059
  if (serveAfter) {
1069
- info(dim('why it matters: a contract counts as ABX because THIS factory made it (isAbxClone),'));
1070
- info(dim('not because it emitted an event claiming to be — anyone can emit that event.'));
1060
+ console.log(` ${g('✦')} anyone can write a contract that ${bold('claims')} to be an ABX token`);
1061
+ console.log(` ${g('✦')} only this one factory can make a token that ${bold('is')} one`);
1062
+ info(dim('so "is this real?" is a yes/no question anyone can ask the chain — not a matter of trusting us'));
1063
+ info(dim(`the factory answers it on-chain: isAbxClone(yourToken) · ${EXPLORER}/address/${factory}`));
1071
1064
  }
1072
1065
  // --onchain-uri: resolve tokenURI/contractURI fully on-chain via the canonical renderer
1073
1066
  // (the JSON is assembled from on-chain fields → no resolver needed, ever). The off-chain
@@ -1075,18 +1068,32 @@ async function cmdDeploy(flags, serveAfter) {
1075
1068
  // --onchain-image: stage the --image bytes on-chain (chunk store, ownerless) and bake a
1076
1069
  // `reader` field into the deploy — so large on-chain content needs NO post-deploy tx. It
1077
1070
  // implies on-chain URI resolution (the renderer emits the reader-backed image).
1078
- const onchainImage = !!flags['onchain-image'];
1079
- const onChainUri = !!flags['onchain-uri'] || onchainImage;
1080
- // On-chain traits when explicitly asked, or implied by --onchain-uri (the renderer can only
1081
- // emit on-chain fields, so off-chain-only traits would be invisible there). Else off-chain.
1082
- const traitsOnchain = traits.length > 0 && (!!flags['traits-onchain'] || onChainUri);
1083
1071
  // A fully on-chain token self-resolves via the renderer; the stored off-chain pointer is
1084
1072
  // never read while a renderer is set. So bake a real URL only if one was explicitly given —
1085
1073
  // otherwise leave it EMPTY rather than baking a misleading localhost into the contract.
1086
1074
  const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
1075
+ const onchainImage = !!flags['onchain-image'];
1076
+ // `abx demo` defaults to FULLY ON-CHAIN. Its default art is a generative SVG, which the renderer
1077
+ // can inline — so the token self-resolves and, crucially, NO localhost gets baked into the
1078
+ // contract as the tokenURI base. The old default shipped a first-ever token that resolved for
1079
+ // nobody but its author (broken on every marketplace, dead the moment `abx serve` stops) and
1080
+ // taught that as the normal shape of an NFT. It also undercut the demo's own claim: with the art
1081
+ // on-chain, "rebuilt from the chain alone" now covers the IMAGE, not just the metadata.
1082
+ //
1083
+ // Opting back into off-chain custody is anything that says "I have somewhere to host": a real base
1084
+ // URL, an explicit --backend, or a RASTER --image. An SVG --image still goes on-chain — inlining is
1085
+ // exactly what the renderer supports (v1 is SVG-only), so there's no reason to send someone's own
1086
+ // vector art down the localhost path. Raster stays off-chain because forcing it on-chain would
1087
+ // silently inline a placeholder instead of their image.
1088
+ const demoImageInlineable = !flags.image || contentTypeFromPath(flags.image) === 'image/svg+xml';
1089
+ const demoDefaultsOnChain = serveAfter && demoImageInlineable && !flags.backend && !hasPublicUrl;
1090
+ const onChainUri = !!flags['onchain-uri'] || onchainImage || demoDefaultsOnChain;
1091
+ // On-chain traits when explicitly asked, or implied by --onchain-uri (the renderer can only
1092
+ // emit on-chain fields, so off-chain-only traits would be invisible there). Else off-chain.
1093
+ const traitsOnchain = traits.length > 0 && (!!flags['traits-onchain'] || onChainUri);
1087
1094
  let renderer = zeroAddress;
1088
1095
  if (onChainUri) {
1089
- step('On-chain renderer');
1096
+ step(serveAfter ? 'What will answer when someone asks about your token' : 'On-chain renderer');
1090
1097
  if (dryRun) {
1091
1098
  renderer = rendererAddress(flags.renderer) ?? zeroAddress;
1092
1099
  info(renderer === zeroAddress ? 'would deploy the canonical renderer first' : `would use renderer ${renderer}`);
@@ -1094,13 +1101,27 @@ async function cmdDeploy(flags, serveAfter) {
1094
1101
  else {
1095
1102
  renderer = await ensureRenderer(flags.renderer);
1096
1103
  }
1104
+ // A first-timer has no idea what a "renderer" is, and the word suggests something that draws
1105
+ // pictures. What it actually does is assemble the JSON a marketplace asks for, on-chain, out of
1106
+ // the fields your contract holds — worth one plain sentence, since it's why no server is needed.
1107
+ if (serveAfter) {
1108
+ info(dim('a marketplace asks your contract a question; this shared contract composes the answer'));
1109
+ info(dim('already deployed, used by every ABX token, owned by no one — you are not paying to set it up'));
1110
+ }
1097
1111
  }
1098
- step(serveAfter ? `Deploy one transaction to ${CHAIN}` : `Deploy a ${DIMENSIONS[dimension].label} (--type ${dimension}) to ${CHAIN}`);
1099
- // The custody split is the single most useful thing to understand about an ABX token, and it's
1100
- // invisible unless someone says it out loud. Demo only `deploy` prints the same facts per-field.
1101
- if (serveAfter) {
1102
- info(dim(`on chain: name · symbol · royalty · a tokenURI base · a keccak256 commitment to the image`));
1103
- info(dim(`NOT on chain: the image bytes themselves this node serves them, the chain pins their hash`));
1112
+ step(serveAfter ? `Mint it · one transaction on ${CHAIN}` : `Deploy a ${DIMENSIONS[dimension].label} (--type ${dimension}) to ${CHAIN}`);
1113
+ // What the chain ends up holding is the single most useful thing to understand about an ABX token,
1114
+ // and it's invisible unless someone says it out loud. Two honest versions, because the answer is
1115
+ // genuinely different per lane. Demo only — `deploy` prints the same facts per-field.
1116
+ if (serveAfter && onChainUri) {
1117
+ console.log(` Your art goes ${bold('INTO')} the contract. Not a link to it the image itself.`);
1118
+ console.log(` ${g('✦')} the chain will hold ${dim('your art · your name on it · your royalty · you as owner')}`);
1119
+ console.log(` ${g('✦')} you will need ${dim('nothing else. no server, no IPFS pin, no monthly bill to forget')}`);
1120
+ }
1121
+ else if (serveAfter) {
1122
+ console.log(` ${g('✦')} the chain will hold ${dim('your name · your royalty · you as owner · a fingerprint of the art')}`);
1123
+ console.log(` ${g('✦')} this computer holds ${dim('the image bytes themselves')}`);
1124
+ info(dim('the chain proves the bytes are unaltered; it does not store them (that fingerprint is a keccak256 hash)'));
1104
1125
  }
1105
1126
  // Off-chain custody (no renderer) bakes the resolver URL straight into the on-chain
1106
1127
  // tokenURI/contractURI at deploy. A localhost / loopback URL there resolves for NO ONE —
@@ -1128,7 +1149,14 @@ async function cmdDeploy(flags, serveAfter) {
1128
1149
  }
1129
1150
  }
1130
1151
  }
1131
- if (onChainUri) {
1152
+ if (onChainUri && serveAfter) {
1153
+ // The demo's version of the same fact, in words a first-timer can act on. Naming what we are
1154
+ // NOT doing matters here: a localhost URL written into a contract is the single most common way
1155
+ // a first NFT ends up permanently broken, and the demo used to model exactly that.
1156
+ info(dim('nothing points at this computer — no http://localhost anywhere in your contract.'));
1157
+ info(dim('anyone can read your token from the chain, forever, with you offline.'));
1158
+ }
1159
+ else if (onChainUri) {
1132
1160
  info('tokenURI/contractURI resolve ON-CHAIN via the renderer — no resolver, no server, no localhost.');
1133
1161
  if (!hasPublicUrl)
1134
1162
  info('off-chain fallback pointer left empty (the renderer is authoritative); set --public-base-url to bake one anyway.');
@@ -1305,7 +1333,9 @@ async function cmdDeploy(flags, serveAfter) {
1305
1333
  // wallet lane + on-chain staging: ONE sign session signs every chunk write AND the deploy.
1306
1334
  // The connecting wallet pays for (and is the deployer of) all of it — staging can't precede
1307
1335
  // the connect here, so it happens inside the session, then the deploy bakes in the manifest.
1308
- info(`a wallet will become the owner; URIs point at ${baseUrl}`);
1336
+ // --onchain-image implies on-chain resolution, so NOTHING points at baseUrl — saying it did was
1337
+ // a flat contradiction of the line above it (and reintroduced the localhost the lane exists to avoid).
1338
+ info('a wallet will become the owner; the token resolves from chain — no URI base is baked in.');
1309
1339
  const plan = computeContentPlan(readFileSync(resolvePath(flags.image)), parseCompress(flags.compress)).plan;
1310
1340
  const stagingTxs = plan.mode === 'single' ? 1 : plan.txCount;
1311
1341
  const session = await openWalletSession({
@@ -1370,8 +1400,12 @@ async function cmdDeploy(flags, serveAfter) {
1370
1400
  info(`tx ${EXPLORER}/tx/${r.txHash} (block ${blockNumber})`);
1371
1401
  }
1372
1402
  else {
1373
- // wallet lane (off-chain custody) or cold lane: a single deploy tx, no staging sequence.
1374
- info(`a wallet will become the owner; URIs point at ${baseUrl}`);
1403
+ // wallet lane (off-chain custody OR on-chain URI) or cold lane: a single deploy tx, no staging.
1404
+ // Only claim a URI base when one is actually written — on the on-chain lane this line used to
1405
+ // announce `http://localhost:8787` two lines after promising no localhost anywhere.
1406
+ info(onChainUri
1407
+ ? 'a wallet will become the owner; the token resolves from chain — no URI base is baked in.'
1408
+ : `a wallet will become the owner; URIs point at ${baseUrl}`);
1375
1409
  const result = await signTx(async (signer) => {
1376
1410
  const { clone: predicted, params, salt } = await buildForDeployer(signer);
1377
1411
  return prepareDeployOneOfOne({ factory, params, salt, chainId: resolveChain(CHAIN).id, clone: predicted });
@@ -1384,7 +1418,7 @@ async function cmdDeploy(flags, serveAfter) {
1384
1418
  blockNumber = result.blockNumber;
1385
1419
  ok(`deployed ${clone}`);
1386
1420
  }
1387
- step('Index it — replay the event spine from chain');
1421
+ step(serveAfter ? 'What the chain knows now' : 'Index it — replay the event spine from chain');
1388
1422
  const indexer = new SelfHostIndexer();
1389
1423
  // Off-chain traits ride in the registration (on-chain ones are already in the contract fields).
1390
1424
  const offChainTraits = !traitsOnchain && traits.length ? JSON.stringify(traits) : undefined;
@@ -1400,12 +1434,19 @@ async function cmdDeploy(flags, serveAfter) {
1400
1434
  };
1401
1435
  indexer.register(baseReg);
1402
1436
  const { state, elapsedMs } = await reindexAfterDeploy(indexer, clone);
1403
- if (state.eventCount > 0)
1404
- ok(`reconstructed ${state.eventCount} events in ${elapsedMs}ms — no provider involved`);
1405
- info(`name "${state.name}" · owner ${state.owner} · canonical: ${state.isCanonical ? 'yes (factory-verified)' : 'unverified'}`);
1406
- info(`extensions: ${state.extensions.map((e) => e.name).join(', ') || 'none'}`);
1407
- if (serveAfter)
1437
+ if (serveAfter) {
1438
+ if (state.eventCount > 0)
1439
+ ok(`read ${bold(String(state.eventCount))} events straight off ${CHAIN} in ${elapsedMs}ms no API key, no company's server`);
1440
+ info(`it says: "${state.name}" · owned by ${state.owner} · ${state.isCanonical ? `${g('verified real')} ${dim('(that factory vouched for it)')}` : `${c.orange}unverified${c.reset}`}`);
1441
+ info(dim(`optional features switched on: ${state.extensions.map((e) => e.name.replace(/^abx\.extension\./, '')).join(' · ') || 'none'}`));
1408
1442
  walkthroughSpine(state);
1443
+ }
1444
+ else {
1445
+ if (state.eventCount > 0)
1446
+ ok(`reconstructed ${state.eventCount} events in ${elapsedMs}ms — no provider involved`);
1447
+ info(`name "${state.name}" · owner ${state.owner} · canonical: ${state.isCanonical ? 'yes (factory-verified)' : 'unverified'}`);
1448
+ info(`extensions: ${state.extensions.map((e) => e.name).join(', ') || 'none'}`);
1449
+ }
1409
1450
  // For off-chain custody (image committed as keccak256), resolve the durable locator (ipfs://…)
1410
1451
  // from this machine's content index and store it on the registration — so the LOCAL resolver
1411
1452
  // points `image` at IPFS, and `abx add --remote` can ship it to a hosted one (the localhost-image fix).
@@ -1463,8 +1504,12 @@ async function cmdDeploy(flags, serveAfter) {
1463
1504
  // while the read-back prints below, which reads like the step failed.
1464
1505
  const { url } = await startTokenApiServer({ indexer, port, baseUrl, storage: resolveBackend(storageOptions()) });
1465
1506
  if (serveAfter)
1466
- await walkthroughReadBack(state, url);
1467
- step('Serve the token API + dashboard');
1507
+ await walkthroughReadBack(state, url, onChainUri);
1508
+ // On the on-chain lane this server is a convenience, not infrastructure — say so, or standing one
1509
+ // up as the finale re-teaches the dependency the whole run just disproved.
1510
+ step(onChainUri ? 'Go look at it' : 'Serve the token API + dashboard');
1511
+ if (onChainUri)
1512
+ info(dim('a local viewer, purely for your eyes — your token does not need it. Ctrl-C whenever; the token stays up.'));
1468
1513
  printServing(url, clone);
1469
1514
  keepAlive();
1470
1515
  }
@@ -1906,8 +1951,11 @@ async function cmdDeploySeries(flags) {
1906
1951
  }
1907
1952
  else {
1908
1953
  // wallet lane (off-chain / inline) or cold lane: a single deploy tx, no staging sequence —
1909
- // tokenFields is already built.
1910
- info(`a wallet will become the owner; URIs point at ${baseUrl}`);
1954
+ // tokenFields is already built. Same correction as the 1/1 lane: only claim a URI base when one
1955
+ // is actually written, or an --onchain-uri Series announces a localhost it never bakes.
1956
+ info(onChainUri
1957
+ ? 'a wallet will become the owner; tokens resolve from chain — no URI base is baked in.'
1958
+ : `a wallet will become the owner; URIs point at ${baseUrl}`);
1911
1959
  const result = await signTx(async (signer) => {
1912
1960
  const { clone: predicted, params, salt } = await buildForDeployer(signer);
1913
1961
  return prepareDeploySeries({ factory, params, salt, chainId: resolveChain(CHAIN).id, clone: predicted });
@@ -2345,9 +2393,9 @@ function walkthroughSpine(state) {
2345
2393
  const reg = e.register === 2 ? p('ABX') : dim('ERC');
2346
2394
  console.log(` ${dim(`#${String(i + 1).padStart(2)}`)} ${reg} ${e.name.padEnd(width)} ${dim(e.what)}`);
2347
2395
  });
2348
- console.log(` ${dim('→')} those ${bold(String(state.events.length))} rows are the entire database ` +
2349
- dim('no row in a server anywhere is authoritative.'));
2350
- console.log(` ${p('ABX')} ${dim('= a native ABX event')} ${dim('ERC')} ${dim('= plain ERC-721/7572, so wallets and marketplaces that have never heard of ABX still index this token.')}`);
2396
+ console.log(` ${dim('→')} those ${bold(String(state.events.length))} lines ${bold('are')} the database. ` +
2397
+ dim('There is no other copy that counts not ours, not anyone\'s.'));
2398
+ console.log(` ${p('ABX')} ${dim('= ABX\'s own events')} ${dim('ERC')} ${dim('= bog-standard ERC-721/7572, which is why wallets and marketplaces that have never heard of ABX still show your token.')}`);
2351
2399
  }
2352
2400
  /**
2353
2401
  * [4] The claim, demonstrated: delete the local projection and rebuild it from the chain.
@@ -2357,37 +2405,78 @@ function walkthroughSpine(state) {
2357
2405
  * fingerprint of everything chain-derived. If ABX's premise is wrong, this step fails loudly.
2358
2406
  */
2359
2407
  async function walkthroughRebuild(indexer, address, before) {
2360
- step('Prove it rebuild from nothing');
2408
+ step('The moment of truth · delete it all');
2361
2409
  const fpBefore = projectionFingerprint(before);
2362
2410
  indexer.dropProjection(address);
2363
2411
  const gone = indexer.getProject(address) === null;
2364
- console.log(` ${dim('deleting this machine\'s projection …')} ${gone ? g(' gone') : `${c.orange}⚠ still present${c.reset}`}`);
2412
+ console.log(` ${dim('wiping this computer\'s copy …')} ${gone ? g('gone. nothing left locally.') : `${c.orange}⚠ still present${c.reset}`}`);
2365
2413
  const { state: after, elapsedMs } = await indexer.reindex(address, { full: true });
2366
2414
  const fpAfter = projectionFingerprint(after);
2367
- console.log(` ${dim(`replaying from block ${after.deployBlock ?? after.fromBlock} …`)} ` +
2368
- `${g('✓')} ${after.eventCount} events in ${elapsedMs}ms`);
2415
+ console.log(` ${dim(`asking ${CHAIN} to tell us everything again …`)} ` +
2416
+ `${g(`${after.eventCount} events, ${elapsedMs}ms`)}`);
2369
2417
  if (fpBefore === fpAfter) {
2370
- ok(`identical state ${dim(`(sha256 ${fpAfter.slice(0, 12)}… over every chain-derived field)`)}`);
2371
- info('no provider, no backup, no API key an RPC endpoint and the chain were enough.');
2418
+ ok(bold('byte-for-byte identical.'));
2419
+ info('Your token just survived losing every local file. No backup, no API key, no company ');
2420
+ info(`the chain remembered. ${dim('That is the whole point of ABX.')}`);
2421
+ info(dim(`checked by hashing every chain-derived field, not by eyeballing it: sha256 ${fpAfter.slice(0, 12)}…`));
2372
2422
  }
2373
2423
  else {
2374
2424
  warn('the rebuilt state does NOT match what we just had — that is a real bug, please report it.');
2375
2425
  info(`before ${fpBefore.slice(0, 16)}… · after ${fpAfter.slice(0, 16)}…`);
2376
2426
  }
2377
2427
  }
2378
- /** [5] Read the token back the way a marketplace would, and check the bytes against the chain. */
2379
- async function walkthroughReadBack(state, baseUrl) {
2428
+ /**
2429
+ * [5] Read the token back the way a marketplace would.
2430
+ *
2431
+ * Which is genuinely a different act per lane, so it reads from the real source in each case rather
2432
+ * than always going through the local server:
2433
+ * • fully on-chain → call `tokenURI(0)` on the contract. That IS what a marketplace does, and on
2434
+ * this lane the whole answer (art included) comes back from the chain with nothing else running.
2435
+ * • off-chain custody → fetch the resolver, because that's what the baked URI points at.
2436
+ * Reading the on-chain lane over HTTP would have quietly implied the local server was load-bearing
2437
+ * when it isn't — the opposite of the lesson.
2438
+ */
2439
+ async function walkthroughReadBack(state, baseUrl, onChainUri) {
2380
2440
  step('Read it back the way a marketplace would');
2381
2441
  const token = state.tokens[0];
2382
2442
  if (!token?.minted) {
2383
2443
  info('no minted token to read yet.');
2384
2444
  return;
2385
2445
  }
2386
- const uri = token.tokenURI;
2387
- info(`tokenURI(0) ${dim('on chain →')} ${uri ?? dim('(none)')}`);
2388
- // The demo's default is off-chain custody: the CHAIN holds a keccak256 commitment to the image and
2389
- // a URI base; this NODE holds the bytes. That split is the thing worth understanding, so name it
2390
- // rather than implying the JSON came from the chain.
2446
+ if (onChainUri) {
2447
+ info(dim(`calling tokenURI(0) on your contract the same call OpenSea makes …`));
2448
+ try {
2449
+ const uri = (await makePublicClient({ chainKey: CHAIN }).readContract({
2450
+ address: state.address,
2451
+ abi: oneOfOneImageAbi,
2452
+ functionName: 'tokenURI',
2453
+ args: [0n],
2454
+ }));
2455
+ const json = decodeOnChainJson(uri);
2456
+ if (json) {
2457
+ const parsed = JSON.parse(json);
2458
+ const img = typeof parsed.image === 'string' ? parsed.image : '';
2459
+ console.log(` ${g('✓')} came back with: ${bold(String(parsed.name ?? '(no name)'))}`);
2460
+ console.log(` ${dim(`image: ${img.slice(0, 48)}${img.length > 48 ? '…' : ''}`)}`);
2461
+ // The punchline of the whole lane: a data: URI means the art travelled IN the answer.
2462
+ if (img.startsWith('data:'))
2463
+ info(`${g('the art itself came back in that answer')} ${dim('— no link to follow, nothing to go missing')}`);
2464
+ }
2465
+ else {
2466
+ console.log(` ${dim(uri.slice(0, 160))}${uri.length > 160 ? dim('…') : ''}`);
2467
+ }
2468
+ info(dim('nothing was running to answer that. no server of ours, no server of yours.'));
2469
+ }
2470
+ catch (e) {
2471
+ info(dim(`could not read tokenURI from the chain: ${e.message}`));
2472
+ }
2473
+ console.log(` ${dim('read it yourself any time:')} ${bold(`abx tokenuri ${state.address}`)}`);
2474
+ return;
2475
+ }
2476
+ info(`tokenURI(0) ${dim('on chain →')} ${token.tokenURI ?? dim('(none)')}`);
2477
+ // Off-chain custody: the CHAIN holds a keccak256 commitment to the image and a URI base; this NODE
2478
+ // holds the bytes. That split is the thing worth understanding, so name it rather than implying the
2479
+ // JSON came from the chain.
2391
2480
  info(dim('the chain stored a URI base + a keccak256 commitment; this node serves the bytes.'));
2392
2481
  try {
2393
2482
  const res = await fetch(`${baseUrl}/t/${resolveChain(CHAIN).id}/${state.address}/0`);
@@ -4459,6 +4548,44 @@ async function cmdMigrate(address, flags) {
4459
4548
  info('keep the source running until DNS / base-URI propagates (source-only images were already re-pinned above, unless a warning said otherwise).');
4460
4549
  }
4461
4550
  // ── doctor ────────────────────────────────────────────────────────────────--
4551
+ /**
4552
+ * Doctor's "want me to fix that?" for a missing or stale skill. Three lanes, deliberately:
4553
+ * `--fix` install without asking (CI, scripts, an agent running doctor for someone)
4554
+ * interactive name the exact directories, then ask — Enter accepts, since doctor's whole job is
4555
+ * getting setup right and this is the one check whose fix is a local file copy
4556
+ * non-TTY change NOTHING and print how to do it; a diagnostic must never mutate a
4557
+ * scripted environment just because nobody was there to say no
4558
+ * Honors `--global` / `--agent` so the fix can target the same place an explicit install would.
4559
+ */
4560
+ async function offerSkillInstall(flags, stale, indent) {
4561
+ const opts = { global: flags.global !== undefined, agent: flags.agent };
4562
+ const src = resolveBundledSkill();
4563
+ if (!src)
4564
+ return; // no bundled skill to install (dev checkout oddity) — the hint above still stands
4565
+ const verb = stale ? 'resync' : 'install';
4566
+ const dests = defaultSkillDests(opts);
4567
+ if (flags.fix === undefined) {
4568
+ if (!process.stdin.isTTY) {
4569
+ console.log(`${indent}${dim(`non-interactive — run \`abx doctor --fix\` (or \`abx skill install\`) to ${verb} it.`)}`);
4570
+ return;
4571
+ }
4572
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
4573
+ const answer = await new Promise((resolve) => {
4574
+ // EOF (Ctrl-D) closes the interface WITHOUT firing the question callback — awaiting only the
4575
+ // callback would hang doctor forever. Treat a closed stream as a decline: the safe direction is
4576
+ // always "change nothing", never "write files because nobody answered".
4577
+ rl.once('close', () => resolve('n'));
4578
+ rl.question(`${indent}${verb} the abx skill into ${bold(dests)} now? [Y/n] `, resolve);
4579
+ });
4580
+ rl.close();
4581
+ if (declinesSkillInstall(answer)) {
4582
+ console.log(`${indent}${dim(`skipped — \`abx skill install\` when you want it.`)}`);
4583
+ return;
4584
+ }
4585
+ }
4586
+ console.log('');
4587
+ installSkillToDefaults(src, opts);
4588
+ }
4462
4589
  async function cmdDoctor(flags) {
4463
4590
  console.log(bold('\n abx doctor') + dim(` · ${CHAIN}`) + '\n');
4464
4591
  // Two visual tiers: PASS/FAIL checks (✓/✗) for things that are either working or broken, and an
@@ -4475,16 +4602,28 @@ async function cmdDoctor(flags) {
4475
4602
  const cliVersion = readCliVersion();
4476
4603
  const skillVersions = installedSkillVersions();
4477
4604
  const staleSkills = skillVersions.filter((v) => compareVersions(cliVersion, v) > 0);
4478
- if (skillVersions.length === 0) {
4605
+ const skillMissing = skillVersions.length === 0;
4606
+ const skillStale = !skillMissing && staleSkills.length > 0;
4607
+ if (skillMissing) {
4479
4608
  check('agent skill', false, `not installed — run ${g('abx skill install')}`);
4480
4609
  console.log(`${CONT}${dim('(recommended: let a coding agent drive abx)')}`);
4481
4610
  }
4482
- else if (staleSkills.length > 0) {
4611
+ else if (skillStale) {
4483
4612
  check('agent skill', false, `v${staleSkills.join(', v')} behind CLI v${cliVersion} — run ${g('abx skill install')}`);
4484
4613
  }
4485
4614
  else {
4486
4615
  check('agent skill', true, `in sync (v${cliVersion})`);
4487
4616
  }
4617
+ // Offer to fix it here rather than only naming the command. `npm i -g` + `abx skill install` was a
4618
+ // two-step install flow where the second step is easy to skip and invisible when skipped (an agent
4619
+ // that never learned abx just... doesn't use it). Doctor is already the documented first run, so
4620
+ // this collapses the flow without an npm `postinstall` hook — which could not work anyway: npm runs
4621
+ // lifecycle scripts with cwd set to the installed package dir (so the skill would land inside
4622
+ // node_modules), pnpm gates install scripts by default, and writing to a user's ~/.claude on
4623
+ // install is the kind of side effect that belongs to the user, not to us.
4624
+ if (skillMissing || skillStale) {
4625
+ await offerSkillInstall(flags, skillStale, CONT);
4626
+ }
4488
4627
  console.log('');
4489
4628
  // 2. Core environment (✓/✗). Signing-wallet balances are computed here (they need the RPC) but
4490
4629
  // printed in the Optional block below, so buffer them.
@@ -5193,8 +5332,12 @@ const COMMAND_HELP = {
5193
5332
  ${bold('abx set-max-invocations')} <address> --max <N> ${dim('— LOWER the supply cap (Series). Sends a tx.')}
5194
5333
  --max <N> the new cap — MONOTONIC: can only DECREASE, and never below what's already minted (else it reverts)`,
5195
5334
  doctor: `
5196
- ${bold('abx doctor')} ${dim('— preflight readiness: signing key/wallet, RPC health, canonical factory, storage. Read-only.')}
5335
+ ${bold('abx doctor')} ${dim('— preflight readiness: agent skill, signing key/wallet, RPC health, canonical factory, storage.')}
5197
5336
  --for 0x.. also report that address's balance (fund before signing)
5337
+ ${g('--fix')} ${dim('install/resync the agent skill without asking (the one thing doctor can repair)')}
5338
+ ${g('--global')} · ${g('--agent')} <a> ${dim('where --fix installs the skill (mirrors `abx skill install`)')}
5339
+ ${dim('Read-only apart from --fix. Interactively it OFFERS to install a missing/stale skill; a non-TTY')}
5340
+ ${dim('run changes nothing and just prints the command, so scripts and CI are never mutated.')}
5198
5341
  ${dim('a missing signing key is NOT fatal — the wallet lane (`--sign`) needs no key in `.env`.')}`,
5199
5342
  status: `
5200
5343
  ${bold('abx status')} ${dim('— list the projects this node has indexed, plus node info. Read-only.')}`,
@@ -5472,6 +5615,31 @@ function installSkillTo(src, dest) {
5472
5615
  rmSync(dest, { recursive: true, force: true });
5473
5616
  cpSync(src, dest, { recursive: true });
5474
5617
  }
5618
+ /**
5619
+ * Install the bundled skill into the default per-agent parents (or `~` with `global`), reporting
5620
+ * each destination. Shared by `abx skill install` and `abx doctor`'s offer to fix a missing/stale
5621
+ * skill — one implementation, so the two can't drift on where the skill lands or what it prints.
5622
+ */
5623
+ function installSkillToDefaults(src, opts = {}) {
5624
+ const version = readSkillVersion(joinPath(src, 'SKILL.md')) ?? readCliVersion();
5625
+ const base = opts.global ? homedir() : process.cwd();
5626
+ const parents = resolveInstallParents(opts.agent);
5627
+ ok(`installed the abx skill v${version}${opts.global ? ' (global, ~)' : ''}:`);
5628
+ for (const parent of parents) {
5629
+ const dest = joinPath(base, parent, SKILL_DIR_NAME);
5630
+ installSkillTo(src, dest);
5631
+ const label = SKILL_PARENT_LABELS[parent];
5632
+ console.log(` ${g(joinPath(parent, SKILL_DIR_NAME))}${label ? dim(' → ' + label) : ''}`);
5633
+ }
5634
+ info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
5635
+ info(`the skill is version-locked to this CLI (v${version}); re-run ${g('abx skill install')} after upgrading so the two stay in sync.`);
5636
+ return version;
5637
+ }
5638
+ /** The default skill destinations, as a display string — what doctor's prompt has to name up front. */
5639
+ function defaultSkillDests(opts = {}) {
5640
+ const prefix = opts.global ? '~/' : './';
5641
+ return resolveInstallParents(opts.agent).map((p) => `${prefix}${p}`).join(' and ');
5642
+ }
5475
5643
  async function cmdSkill(rest, flags) {
5476
5644
  const sub = rest[0] ?? 'install';
5477
5645
  const src = resolveBundledSkill();
@@ -5494,17 +5662,7 @@ async function cmdSkill(rest, flags) {
5494
5662
  info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
5495
5663
  return;
5496
5664
  }
5497
- const base = flags.global !== undefined ? homedir() : process.cwd();
5498
- const parents = resolveInstallParents(flags.agent);
5499
- ok(`installed the abx skill v${version}${flags.global !== undefined ? ' (global, ~)' : ''}:`);
5500
- for (const parent of parents) {
5501
- const dest = joinPath(base, parent, SKILL_DIR_NAME);
5502
- installSkillTo(src, dest);
5503
- const label = SKILL_PARENT_LABELS[parent];
5504
- console.log(` ${g(joinPath(parent, SKILL_DIR_NAME))}${label ? dim(' → ' + label) : ''}`);
5505
- }
5506
- info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
5507
- info(`the skill is version-locked to this CLI (v${version}); re-run ${g('abx skill install')} after upgrading so the two stay in sync.`);
5665
+ installSkillToDefaults(src, { global: flags.global !== undefined, agent: flags.agent });
5508
5666
  return;
5509
5667
  }
5510
5668
  throw new Error('usage: abx skill <install|path> [--agent claude|cursor|codex|gemini|copilot] [--global] [--target <dir>]');