@artblocks/abx-cli 0.1.0-alpha.1 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +145 -92
- package/dist/main.js.map +1 -1
- package/dist/update-check.d.ts +29 -6
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +60 -20
- package/dist/update-check.js.map +1 -1
- package/package.json +2 -2
- package/skill/SKILL.md +4 -0
package/dist/main.js
CHANGED
|
@@ -34,13 +34,14 @@
|
|
|
34
34
|
* abx storage backup-key copy the managed Turbo/Arweave key (holds credits) to a safe path
|
|
35
35
|
* abx status list indexed projects + node info
|
|
36
36
|
* abx doctor check environment (key, RPC, balance, factory, storage)
|
|
37
|
-
* abx skill install install the abx agent skill into your agent
|
|
37
|
+
* abx skill install install the version-locked abx agent skill into your agent(s)
|
|
38
|
+
* (default: Claude Code + the neutral .agents/skills; --agent/--global)
|
|
38
39
|
*
|
|
39
40
|
* Every write picks a signing lane: default hot (env key signs), `--sign` (a human
|
|
40
41
|
* approves in their own wallet via a one-shot localhost page), `--unsigned` (print
|
|
41
42
|
* the tx for a multisig / offline signer). The agent picks the lane; the CLI signs.
|
|
42
43
|
*/
|
|
43
|
-
import { appendFileSync, copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
44
|
+
import { appendFileSync, copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
44
45
|
import { randomBytes } from 'node:crypto';
|
|
45
46
|
import { basename, extname, join as joinPath, resolve as resolvePath } from 'node:path';
|
|
46
47
|
import { fileURLToPath } from 'node:url';
|
|
@@ -54,7 +55,7 @@ import { DEP_RESOLUTION, deploySeedSource, deploySeriesCodeFactory, prepareCodeS
|
|
|
54
55
|
import { checkRegistryDeps, dependencySetupCalls, parseDepFlag, resolveDepRegistryPointer } from './deps.js';
|
|
55
56
|
import { composeParamsKeys, expectedChainComplete, hasOnChainUriLane, onchainUriSetupCalls, onChainUriReport } from './onchain-uri.js';
|
|
56
57
|
import { parseFlags, unknownFlags } from './flags.js';
|
|
57
|
-
import { checkForCliUpdate, compareVersions, installedSkillVersions, readCliVersion,
|
|
58
|
+
import { AGENT_SKILL_PARENTS, checkForCliUpdate, compareVersions, installedSkillVersions, readCliVersion, readSkillVersion, SKILL_DIR_NAME, } from './update-check.js';
|
|
58
59
|
import { analyzeScript, recommendLane } from './inspect.js';
|
|
59
60
|
import { parseSchemaSpecs, describeSchema } from './schema.js';
|
|
60
61
|
import { parseSeriesTraits, looksPerTokenAttributes, parseSeriesTraitsById } from './series-traits.js';
|
|
@@ -133,7 +134,7 @@ async function maybeNotifyUpdate(flags) {
|
|
|
133
134
|
const latest = await checkForCliUpdate(current);
|
|
134
135
|
if (latest) {
|
|
135
136
|
console.error(`\n ${c.orange}⚠${c.reset} update available: ${bold('abx')} ${dim(current)} → ${g(latest)}\n` +
|
|
136
|
-
` upgrade: ${g('npm i -g @artblocks/abx-cli@latest')} ${dim('· or invoke:')} ${g('npx abx@latest <command>')}\n` +
|
|
137
|
+
` upgrade: ${g('npm i -g @artblocks/abx-cli@latest')} ${dim('· or invoke:')} ${g('npx @artblocks/abx-cli@latest <command>')}\n` +
|
|
137
138
|
` release notes: https://github.com/ArtBlocks/abx/releases ${dim('· silence: ABX_NO_UPDATE_CHECK=1')}\n`);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
@@ -4166,99 +4167,109 @@ async function cmdMigrate(address, flags) {
|
|
|
4166
4167
|
}
|
|
4167
4168
|
// ── doctor ────────────────────────────────────────────────────────────────--
|
|
4168
4169
|
async function cmdDoctor(flags) {
|
|
4169
|
-
console.log(bold('\n abx doctor\n')
|
|
4170
|
-
|
|
4170
|
+
console.log(bold('\n abx doctor') + dim(` · ${CHAIN}`) + '\n');
|
|
4171
|
+
// Two visual tiers: PASS/FAIL checks (✓/✗) for things that are either working or broken, and an
|
|
4172
|
+
// "Optional" block (·) for path-dependent setup that is fine to be unset. We deliberately do NOT
|
|
4173
|
+
// use ⚠ for "unset but often fine" — that read as noise; ⚠ is reserved for a real gotcha (a
|
|
4174
|
+
// range-capped RPC). Labels are padded so both tiers align.
|
|
4175
|
+
const CONT = ' '.repeat(17); // continuation indent: aligns under a check/opt detail column
|
|
4176
|
+
const check = (label, pass, detail = '') => console.log(` ${pass ? g('✓') : `${c.red}✗${c.reset}`} ${label.padEnd(13)}${detail ? dim(detail) : ''}`);
|
|
4177
|
+
const opt = (label, detail) => console.log(` ${dim('·')} ${label.padEnd(13)}${dim(detail)}`);
|
|
4171
4178
|
const hasKey = !!(process.env.ABX_DEPLOYER_PK ?? process.env.SEPOLIA_FUNDED_PK ?? process.env.SEPOLIA_WALLET_PK);
|
|
4172
|
-
//
|
|
4173
|
-
//
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4179
|
+
// 1. Agent skill — FIRST and prominent. The primary way to use abx is to let a coding agent drive
|
|
4180
|
+
// it, so a missing/stale skill is a ✗: not broken infra, but the main UX isn't set up. Its
|
|
4181
|
+
// version lives in SKILL.md frontmatter (version-locked to this CLI). Notify-only — no exit code.
|
|
4182
|
+
const cliVersion = readCliVersion();
|
|
4183
|
+
const skillVersions = installedSkillVersions();
|
|
4184
|
+
const staleSkills = skillVersions.filter((v) => compareVersions(cliVersion, v) > 0);
|
|
4185
|
+
if (skillVersions.length === 0) {
|
|
4186
|
+
check('agent skill', false, `not installed — run ${g('abx skill install')}`);
|
|
4187
|
+
console.log(`${CONT}${dim('(recommended: let a coding agent drive abx)')}`);
|
|
4188
|
+
}
|
|
4189
|
+
else if (staleSkills.length > 0) {
|
|
4190
|
+
check('agent skill', false, `v${staleSkills.join(', v')} behind CLI v${cliVersion} — run ${g('abx skill install')}`);
|
|
4191
|
+
}
|
|
4192
|
+
else {
|
|
4193
|
+
check('agent skill', true, `in sync (v${cliVersion})`);
|
|
4194
|
+
}
|
|
4195
|
+
console.log('');
|
|
4196
|
+
// 2. Core environment (✓/✗). Signing-wallet balances are computed here (they need the RPC) but
|
|
4197
|
+
// printed in the Optional block below, so buffer them.
|
|
4198
|
+
let signingOpt = null;
|
|
4199
|
+
let forOpt = null;
|
|
4178
4200
|
try {
|
|
4179
4201
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
4180
4202
|
const bn = await publicClient.getBlockNumber();
|
|
4181
|
-
|
|
4182
|
-
//
|
|
4183
|
-
// so the toolkit uses (and recommends) the one fit for the job — and says so if none are.
|
|
4203
|
+
// Collapse the RPC report to one line (best endpoint + head), and only add a ⚠ when there is a
|
|
4204
|
+
// genuine problem — a range-capped-only set that will grind a resolver under load.
|
|
4184
4205
|
const probes = await probeRpcEndpoints({ chainKey: CHAIN });
|
|
4185
4206
|
const usable = probes.filter((pr) => pr.verdict !== 'unusable');
|
|
4186
4207
|
const best = probes.find((pr) => pr.verdict === 'best') ?? usable[0];
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
console.log(` ${glyph} ${pr.label} ${dim('— ' + (pr.verdict === 'best' ? 'wide getLogs range + archive' : pr.reason ?? pr.verdict))}`);
|
|
4193
|
-
}
|
|
4194
|
-
if (usable.length === 0) {
|
|
4195
|
-
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”')}`);
|
|
4196
|
-
}
|
|
4197
|
-
else if (!probes.some((pr) => pr.verdict === 'best')) {
|
|
4198
|
-
// Every usable endpoint is range-capped. Indexing still works (chunking), but a first
|
|
4199
|
-
// reconstruction — and a resolver under marketplace load — is slow + rate-limit-prone. That's
|
|
4200
|
-
// a serious infra signal, NOT "a paid plan is required": flag it. A normal deploy→index only
|
|
4201
|
-
// scans from the deploy block, so this bites first reconstructions and busy resolvers most.
|
|
4202
|
-
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”).')}`);
|
|
4203
|
-
}
|
|
4204
|
-
if (hasKey) {
|
|
4205
|
-
const { account } = makeWalletClient({ chainKey: CHAIN });
|
|
4206
|
-
const bal = await publicClient.getBalance({ address: account.address });
|
|
4207
|
-
check('deployer funded', bal > 0n, `${account.address} · ${formatEther(bal)} ETH`);
|
|
4208
|
-
}
|
|
4209
|
-
// Wallet lane has no env key — let a creator preflight THEIR OWN signing wallet's balance,
|
|
4210
|
-
// the one gap where an unfunded wallet otherwise only surfaces at the signing step.
|
|
4211
|
-
if (flags.for) {
|
|
4212
|
-
const bal = await publicClient.getBalance({ address: flags.for });
|
|
4213
|
-
check('wallet funded (--for)', bal > 0n, `${flags.for} · ${formatEther(bal)} ETH${bal > 0n ? '' : ` — ${faucetHint(CHAIN)}`}`);
|
|
4208
|
+
if (usable.length > 0) {
|
|
4209
|
+
check('RPC', true, `${best.label} · head ${bn} · ${best.verdict === 'best' ? 'wide range + archive' : 'range-capped'}`);
|
|
4210
|
+
if (!probes.some((pr) => pr.verdict === 'best')) {
|
|
4211
|
+
console.log(`${CONT}${c.orange}⚠${c.reset}${dim(' every endpoint is getLogs-range-capped — add a wide-range archive RPC to ABX_RPC_URLS before running a resolver under load')}`);
|
|
4212
|
+
}
|
|
4214
4213
|
}
|
|
4215
|
-
else
|
|
4216
|
-
|
|
4214
|
+
else {
|
|
4215
|
+
check('RPC', false, `${CHAIN} — no endpoint usable for reconstruction; add a wide-range archive RPC to ABX_RPC_URLS`);
|
|
4217
4216
|
}
|
|
4218
4217
|
const factory = factoryAddress();
|
|
4219
4218
|
if (factory) {
|
|
4220
4219
|
const code = await publicClient.getCode({ address: factory });
|
|
4221
|
-
if (!code || code === '0x')
|
|
4222
|
-
check('
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
else {
|
|
4228
|
-
check('canonical factory deployed', false, `${factory} — older/incompatible version; \`abx deploy\` redeploys`);
|
|
4229
|
-
}
|
|
4220
|
+
if (!code || code === '0x')
|
|
4221
|
+
check('factory', false, `${factory} — no code on ${CHAIN}; \`abx deploy\` redeploys`);
|
|
4222
|
+
else if (await isCurrentFactory(publicClient, factory))
|
|
4223
|
+
check('factory', true, factory);
|
|
4224
|
+
else
|
|
4225
|
+
check('factory', false, `${factory} — older/incompatible; \`abx deploy\` redeploys`);
|
|
4230
4226
|
}
|
|
4231
4227
|
else {
|
|
4232
|
-
check('
|
|
4228
|
+
check('factory', false, 'none yet — `abx demo` deploys one');
|
|
4229
|
+
}
|
|
4230
|
+
if (hasKey) {
|
|
4231
|
+
const { account } = makeWalletClient({ chainKey: CHAIN });
|
|
4232
|
+
const bal = await publicClient.getBalance({ address: account.address });
|
|
4233
|
+
signingOpt = `env key ${account.address} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — fund it (${faucetHint(CHAIN)})`}`;
|
|
4234
|
+
}
|
|
4235
|
+
if (flags.for) {
|
|
4236
|
+
const bal = await publicClient.getBalance({ address: flags.for });
|
|
4237
|
+
forOpt = `${flags.for} · ${bal > 0n ? `funded ${formatEther(bal)} ETH` : `empty — ${faucetHint(CHAIN)}`}`;
|
|
4233
4238
|
}
|
|
4234
4239
|
}
|
|
4235
4240
|
catch (err) {
|
|
4236
|
-
check('RPC
|
|
4241
|
+
check('RPC', false, err.message);
|
|
4237
4242
|
}
|
|
4238
|
-
// public base URL — baked into on-chain URIs at deploy; unset is fine for a demo
|
|
4239
|
-
// but a silent footgun for a real launch, so surface it explicitly.
|
|
4240
|
-
const baseUrl = process.env.ABX_PUBLIC_BASE_URL;
|
|
4241
|
-
if (baseUrl)
|
|
4242
|
-
check('public base URL set (baked into on-chain URIs)', true, baseUrl);
|
|
4243
|
-
else
|
|
4244
|
-
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.')}`);
|
|
4245
4243
|
// storage backend — resolve it (catches missing config), then probe liveness/creds
|
|
4246
4244
|
const backendId = activeBackendId();
|
|
4247
4245
|
try {
|
|
4248
4246
|
const backend = resolveBackend(storageOptions());
|
|
4249
4247
|
const h = await backend.health?.();
|
|
4250
4248
|
if (h)
|
|
4251
|
-
check(
|
|
4249
|
+
check('storage', h.ok, `${backend.id} · ${h.detail ?? ''}`);
|
|
4252
4250
|
else
|
|
4253
|
-
check(
|
|
4251
|
+
check('storage', true, `${backend.id} · configured`);
|
|
4254
4252
|
}
|
|
4255
4253
|
catch (err) {
|
|
4256
|
-
check(
|
|
4254
|
+
check('storage', false, `${backendId} · ${err.message}`);
|
|
4257
4255
|
}
|
|
4258
|
-
//
|
|
4256
|
+
// 3. Optional — path-dependent setup. Unset is fine; these say WHEN you'll need each, so an unset
|
|
4257
|
+
// value never reads as a warning.
|
|
4258
|
+
console.log(`\n ${dim('Optional — depends how you deploy:')}`);
|
|
4259
|
+
if (signingOpt)
|
|
4260
|
+
opt('signing', signingOpt);
|
|
4261
|
+
else
|
|
4262
|
+
opt('signing', 'no env key → sign in your browser wallet (--sign). Preflight yours: `abx doctor --for 0x<addr>`. Set ABX_DEPLOYER_PK for the unattended hot lane.');
|
|
4263
|
+
if (forOpt)
|
|
4264
|
+
opt('wallet --for', forOpt);
|
|
4265
|
+
const baseUrl = process.env.ABX_PUBLIC_BASE_URL;
|
|
4266
|
+
if (baseUrl)
|
|
4267
|
+
opt('resolver URL', baseUrl);
|
|
4268
|
+
else
|
|
4269
|
+
opt('resolver URL', 'ABX_PUBLIC_BASE_URL unset → needed for off-chain/resolver-served deploys; skip if fully on-chain (--onchain-image / --onchain-uri).');
|
|
4259
4270
|
if (!process.env.ARWEAVE_JWK && existsSync(arweaveKeyFilePath())) {
|
|
4260
4271
|
const jwk = loadArweaveJwk();
|
|
4261
|
-
|
|
4272
|
+
opt('arweave key', `${jwk ? arweaveAddress(jwk) + ' ' : ''}holds upload credits — back it up: \`abx storage backup-key --out <path>\``);
|
|
4262
4273
|
}
|
|
4263
4274
|
console.log('');
|
|
4264
4275
|
}
|
|
@@ -4558,12 +4569,15 @@ function keepAlive() {
|
|
|
4558
4569
|
// ── per-command usage (printed by `<cmd> --help` / `abx help <cmd>`; read-only) ──
|
|
4559
4570
|
const COMMAND_HELP = {
|
|
4560
4571
|
skill: `
|
|
4561
|
-
${bold('abx skill')} ${dim('— install the abx agent skill so your coding agent can drive abx')}
|
|
4562
|
-
${g('abx skill install')} copy the skill
|
|
4563
|
-
${
|
|
4564
|
-
${g('--
|
|
4572
|
+
${bold('abx skill')} ${dim('— install the version-locked abx agent skill so your coding agent can drive abx')}
|
|
4573
|
+
${g('abx skill install')} copy the bundled skill (version-locked to this CLI) into your agent(s)
|
|
4574
|
+
${dim('default: both')} ${bold('.claude/skills')} ${dim('(Claude Code) and')} ${bold('.agents/skills')} ${dim('(Cursor · Codex · Gemini · Copilot)')}
|
|
4575
|
+
${g('--agent <name>')} only one agent: ${dim('claude | cursor | codex | gemini | copilot')}
|
|
4576
|
+
${g('--global')} install into your home dir (~) instead of the current project
|
|
4577
|
+
${g('--target <dir>')} install the skill folder straight under <dir> (writes <dir>/abx-self-host)
|
|
4565
4578
|
${g('abx skill path')} print the absolute path to the bundled skill (for ${g('npx skills add <path>')} or manual copy)
|
|
4566
|
-
${dim('Restart your agent after installing so it picks up the skill.
|
|
4579
|
+
${dim('Restart your agent after installing so it picks up the skill. Once the repo is public, the git-based')}
|
|
4580
|
+
${dim('cross-agent installer also works (not version-locked):')} ${g('npx skills add ArtBlocks/abx --skill abx-self-host')}`,
|
|
4567
4581
|
deploy: `
|
|
4568
4582
|
${bold('abx deploy')} ${dim('— deploy + index a 1/1 (no server). Sends a tx in the chosen lane.')}
|
|
4569
4583
|
--image <path> custody your own image (png · jpg · gif · svg · webp); else generative demo art
|
|
@@ -5019,7 +5033,7 @@ function help() {
|
|
|
5019
5033
|
${g('abx forget')} <address> drop a project's local registration + projection (on-chain untouched)
|
|
5020
5034
|
${g('abx status')} list indexed projects + node info
|
|
5021
5035
|
${g('abx doctor')} check environment (key, RPC, balance, factory, storage)
|
|
5022
|
-
${g('abx skill install')} install the abx
|
|
5036
|
+
${g('abx skill install')} install the version-locked abx skill into your agent(s) [--agent <name>] [--global] [--target <dir>] · ${g('abx skill path')} prints the bundled skill
|
|
5023
5037
|
${g('abx version')} print the installed CLI version
|
|
5024
5038
|
|
|
5025
5039
|
${dim('Run')} ${g('abx <command> --help')} ${dim('for per-command usage. --help / -h never executes — it only prints usage.')}
|
|
@@ -5041,8 +5055,13 @@ function parseSaltFlag(raw) {
|
|
|
5041
5055
|
// ── skill: install the abx agent skill into the user's coding agent ───────────────────────
|
|
5042
5056
|
// The skill is the agentic half of the toolkit — the CLI is only useful once an agent knows how
|
|
5043
5057
|
// to drive it. It ships bundled inside the published package (packages/cli/skill/, created at
|
|
5044
|
-
// prepack); in dev the same command reads the canonical copy straight from the repo.
|
|
5045
|
-
//
|
|
5058
|
+
// prepack); in dev the same command reads the canonical copy straight from the repo. This is the
|
|
5059
|
+
// CANONICAL install path precisely because the bundle is version-locked to the CLI it ships inside
|
|
5060
|
+
// (the version lives in SKILL.md frontmatter — see update-check.ts). By default it writes to BOTH
|
|
5061
|
+
// `.claude/skills` (Claude Code) and the neutral `.agents/skills` (Cursor · Codex · Gemini ·
|
|
5062
|
+
// Copilot all read it), so one command covers the whole ecosystem; `--agent` narrows it. A separate
|
|
5063
|
+
// git-based channel, `npx skills add ArtBlocks/abx --skill abx-self-host`, works once the repo is public but is NOT
|
|
5064
|
+
// version-locked to a local CLI — prefer `abx skill install`.
|
|
5046
5065
|
/** Locate the skill folder: the bundled copy beside the compiled CLI (published), else the
|
|
5047
5066
|
* canonical repo copy (dev). Returns null if neither is present. */
|
|
5048
5067
|
function resolveBundledSkill() {
|
|
@@ -5106,37 +5125,71 @@ function cmdScaffoldRenderer(rest, flags) {
|
|
|
5106
5125
|
console.log(` ${g('abx deploy-code --image-renderer <MyRenderer> --attributes-renderer <MyTraits> --onchain-uri --schema palette:HexColor:TokenOwner --name "…" --symbol …')}`);
|
|
5107
5126
|
info(`full walkthrough: ${bold(`${basename(dir)}/README.md`)} · interface + invariants: https://abx.docs.artblocks.io/protocol/renderers/`);
|
|
5108
5127
|
}
|
|
5128
|
+
/** Human label for each skills-parent dir — which agents pick the skill up from there. */
|
|
5129
|
+
const SKILL_PARENT_LABELS = {
|
|
5130
|
+
'.claude/skills': 'Claude Code',
|
|
5131
|
+
'.agents/skills': 'Cursor · Codex · Gemini · Copilot',
|
|
5132
|
+
};
|
|
5133
|
+
/** Resolve which skills-parent dirs `install` should write to. No `--agent` → the whole-ecosystem
|
|
5134
|
+
* default (Claude Code + the neutral `.agents/skills` everyone else reads). `--agent a,b` narrows
|
|
5135
|
+
* to those agents' dirs (de-duped, since several share `.agents/skills`). */
|
|
5136
|
+
function resolveInstallParents(agentFlag) {
|
|
5137
|
+
if (!agentFlag || agentFlag === 'true')
|
|
5138
|
+
return ['.claude/skills', '.agents/skills'];
|
|
5139
|
+
const parents = new Set();
|
|
5140
|
+
for (const raw of agentFlag.split(',')) {
|
|
5141
|
+
const a = raw.trim().toLowerCase();
|
|
5142
|
+
const parent = AGENT_SKILL_PARENTS[a];
|
|
5143
|
+
if (!parent) {
|
|
5144
|
+
throw new Error(`unknown --agent '${a}'. Use one of: ${Object.keys(AGENT_SKILL_PARENTS).join(', ')} (or omit --agent to install for every agent).`);
|
|
5145
|
+
}
|
|
5146
|
+
parents.add(parent);
|
|
5147
|
+
}
|
|
5148
|
+
return [...parents];
|
|
5149
|
+
}
|
|
5150
|
+
/** Copy the bundled skill folder to `dest`, replacing any prior copy so a re-install after an
|
|
5151
|
+
* upgrade never leaves stale reference files behind. */
|
|
5152
|
+
function installSkillTo(src, dest) {
|
|
5153
|
+
mkdirSync(joinPath(dest, '..'), { recursive: true });
|
|
5154
|
+
rmSync(dest, { recursive: true, force: true });
|
|
5155
|
+
cpSync(src, dest, { recursive: true });
|
|
5156
|
+
}
|
|
5109
5157
|
async function cmdSkill(rest, flags) {
|
|
5110
5158
|
const sub = rest[0] ?? 'install';
|
|
5111
5159
|
const src = resolveBundledSkill();
|
|
5112
5160
|
if (!src) {
|
|
5113
5161
|
throw new Error('bundled skill not found (expected <pkg>/skill or .claude/skills/abx-self-host). ' +
|
|
5114
|
-
'Reinstall @artblocks/abx-cli, or install cross-agent with: npx skills add ArtBlocks/abx');
|
|
5162
|
+
'Reinstall @artblocks/abx-cli, or install cross-agent with: npx skills add ArtBlocks/abx --skill abx-self-host');
|
|
5115
5163
|
}
|
|
5116
5164
|
if (sub === 'path') {
|
|
5117
5165
|
console.log(src);
|
|
5118
5166
|
return;
|
|
5119
5167
|
}
|
|
5120
5168
|
if (sub === 'install') {
|
|
5121
|
-
const
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
}
|
|
5131
|
-
catch {
|
|
5132
|
-
/* non-fatal */
|
|
5169
|
+
const version = readSkillVersion(joinPath(src, 'SKILL.md')) ?? readCliVersion();
|
|
5170
|
+
// Escape hatch: --target <dir> writes the skill folder straight under <dir> (for an agent
|
|
5171
|
+
// whose skills dir we don't special-case, or a bespoke location).
|
|
5172
|
+
if (typeof flags.target === 'string' && flags.target !== 'true') {
|
|
5173
|
+
const dest = joinPath(resolvePath(flags.target), SKILL_DIR_NAME);
|
|
5174
|
+
installSkillTo(src, dest);
|
|
5175
|
+
ok(`installed the abx skill v${version} → ${dest}`);
|
|
5176
|
+
info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
|
|
5177
|
+
return;
|
|
5133
5178
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5179
|
+
const base = flags.global !== undefined ? homedir() : process.cwd();
|
|
5180
|
+
const parents = resolveInstallParents(flags.agent);
|
|
5181
|
+
ok(`installed the abx skill v${version}${flags.global !== undefined ? ' (global, ~)' : ''}:`);
|
|
5182
|
+
for (const parent of parents) {
|
|
5183
|
+
const dest = joinPath(base, parent, SKILL_DIR_NAME);
|
|
5184
|
+
installSkillTo(src, dest);
|
|
5185
|
+
const label = SKILL_PARENT_LABELS[parent];
|
|
5186
|
+
console.log(` ${g(joinPath(parent, SKILL_DIR_NAME))}${label ? dim(' → ' + label) : ''}`);
|
|
5187
|
+
}
|
|
5188
|
+
info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
|
|
5189
|
+
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.`);
|
|
5137
5190
|
return;
|
|
5138
5191
|
}
|
|
5139
|
-
throw new Error('usage: abx skill <install|path> [--global] [--target <dir>]');
|
|
5192
|
+
throw new Error('usage: abx skill <install|path> [--agent claude|cursor|codex|gemini|copilot] [--global] [--target <dir>]');
|
|
5140
5193
|
}
|
|
5141
5194
|
main().catch((err) => {
|
|
5142
5195
|
// Keep the actionable message; strip viem's verbose boilerplate trailer (Docs:/Version: lines) so
|