@artblocks/abx-cli 0.1.0-alpha.0 → 0.1.0-alpha.2

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/flags.d.ts CHANGED
@@ -12,6 +12,12 @@
12
12
  export type Flags = Record<string, string | undefined>;
13
13
  /** Flags where every occurrence counts (values comma-join, in argv order). */
14
14
  export declare const REPEATABLE_FLAGS: ReadonlySet<string>;
15
+ /**
16
+ * Flags valid on ANY command (handled centrally in main, not by a command handler), so
17
+ * {@link unknownFlags} never flags them as unrecognized regardless of the per-command allowlist.
18
+ * `no-update-check` opts out of the startup update nudge (see update-check.ts).
19
+ */
20
+ export declare const GLOBAL_FLAGS: ReadonlySet<string>;
15
21
  export declare function parseFlags(args: string[]): Flags;
16
22
  /**
17
23
  * The flag keys in `flags` that are NOT in `allowed` — for a non-fatal "unrecognized flag"
@@ -1 +1 @@
1
- {"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEvD,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA8B,CAAC;AAEhF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAchD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAG9E"}
1
+ {"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEvD,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA8B,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAAgC,CAAC;AAE9E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAchD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAG9E"}
package/dist/flags.js CHANGED
@@ -1,5 +1,11 @@
1
1
  /** Flags where every occurrence counts (values comma-join, in argv order). */
2
2
  export const REPEATABLE_FLAGS = new Set(['dep', 'schema']);
3
+ /**
4
+ * Flags valid on ANY command (handled centrally in main, not by a command handler), so
5
+ * {@link unknownFlags} never flags them as unrecognized regardless of the per-command allowlist.
6
+ * `no-update-check` opts out of the startup update nudge (see update-check.ts).
7
+ */
8
+ export const GLOBAL_FLAGS = new Set(['no-update-check']);
3
9
  export function parseFlags(args) {
4
10
  const out = {};
5
11
  const set = (k, v) => {
@@ -29,6 +35,6 @@ export function parseFlags(args) {
29
35
  */
30
36
  export function unknownFlags(flags, allowed) {
31
37
  const ok = new Set(allowed);
32
- return Object.keys(flags).filter((k) => !ok.has(k));
38
+ return Object.keys(flags).filter((k) => !ok.has(k) && !GLOBAL_FLAGS.has(k));
33
39
  }
34
40
  //# sourceMappingURL=flags.js.map
package/dist/flags.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"flags.js","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"AAaA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEhF,MAAM,UAAU,UAAU,CAAC,IAAc;IACvC,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QACnC,GAAG,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;aAC/C,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;;YAC7E,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,OAAyB;IAClE,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC"}
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"AAaA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAwB,IAAI,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAE9E,MAAM,UAAU,UAAU,CAAC,IAAc;IACvC,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QACnC,GAAG,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;aAC/C,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;;YAC7E,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,OAAyB;IAClE,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC"}
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 (.claude/skills; --global)
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,6 +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';
58
+ import { AGENT_SKILL_PARENTS, checkForCliUpdate, compareVersions, installedSkillVersions, readCliVersion, readSkillVersion, SKILL_DIR_NAME, } from './update-check.js';
57
59
  import { analyzeScript, recommendLane } from './inspect.js';
58
60
  import { parseSchemaSpecs, describeSchema } from './schema.js';
59
61
  import { parseSeriesTraits, looksPerTokenAttributes, parseSeriesTraitsById } from './series-traits.js';
@@ -110,10 +112,49 @@ const step = (s) => console.log(`\n${p(`[${++stepN}]`)} ${bold(s)}`);
110
112
  const ok = (s) => console.log(` ${g('✓')} ${s}`);
111
113
  const info = (s) => console.log(` ${dim(s)}`);
112
114
  const warn = (s) => console.log(` ${c.orange}⚠${c.reset} ${s}`);
115
+ /**
116
+ * Notify-only update nudge. Prints an upgrade one-liner to STDERR (never stdout — an agent may be
117
+ * parsing that) when a newer @artblocks/abx-cli is published, plus a nudge when a locally-installed
118
+ * skill has drifted behind the running CLI. Fully suppressed by CI / ABX_NO_UPDATE_CHECK /
119
+ * --no-update-check, and wrapped so it can NEVER break a command (all failures are swallowed).
120
+ */
121
+ async function maybeNotifyUpdate(flags) {
122
+ try {
123
+ if (flags['no-update-check'] !== undefined || process.env.ABX_NO_UPDATE_CHECK || process.env.CI)
124
+ return;
125
+ const current = readCliVersion();
126
+ // Skill drift (cheap, offline): the skill and CLI are co-versioned, but a separately-installed
127
+ // skill copy doesn't move when the CLI upgrades. Nudge a reinstall when the CLI is ahead.
128
+ for (const v of installedSkillVersions()) {
129
+ if (compareVersions(current, v) > 0) {
130
+ console.error(` ${c.orange}⚠${c.reset} installed abx skill is v${v} but the CLI is v${current} — refresh it: ${g('abx skill install')}`);
131
+ break;
132
+ }
133
+ }
134
+ const latest = await checkForCliUpdate(current);
135
+ if (latest) {
136
+ 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` +
138
+ ` release notes: https://github.com/ArtBlocks/abx/releases ${dim('· silence: ABX_NO_UPDATE_CHECK=1')}\n`);
139
+ }
140
+ }
141
+ catch {
142
+ /* an update check must never break a command */
143
+ }
144
+ }
113
145
  async function main() {
114
146
  loadDotEnv();
115
147
  const [cmd, ...rest] = process.argv.slice(2);
116
148
  const flags = parseFlags(rest);
149
+ // `version` / --version / -v is a crisp query — print and exit, no update nag around it.
150
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
151
+ console.log(readCliVersion());
152
+ return;
153
+ }
154
+ // Notify-only "you're behind" nudge (cached, opt-out, stderr — see update-check.ts). Awaited so
155
+ // the notice lands before command output, but it hits the network at most once a day and never
156
+ // throws, so it can't break or meaningfully slow a command. Skipped for the pure help path below.
157
+ await maybeNotifyUpdate(flags);
117
158
  // SAFETY: --help / -h on ANY command is read-only — print usage, never execute.
118
159
  // (A write command must never deploy/mint/spend just because someone asked for help.)
119
160
  if (cmd && cmd !== 'help' && (flags.help !== undefined || rest.includes('-h'))) {
@@ -4220,6 +4261,21 @@ async function cmdDoctor(flags) {
4220
4261
  const jwk = loadArweaveJwk();
4221
4262
  console.log(` ${dim('↳ Turbo/Arweave key ' + (jwk ? arweaveAddress(jwk) + ' ' : '') + 'holds your upload credits — back it up: `abx storage backup-key --out <path>`')}`);
4222
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);
4278
+ }
4223
4279
  console.log('');
4224
4280
  }
4225
4281
  // ── shared output ─────────────────────────────────────────────────────────--
@@ -4518,12 +4574,15 @@ function keepAlive() {
4518
4574
  // ── per-command usage (printed by `<cmd> --help` / `abx help <cmd>`; read-only) ──
4519
4575
  const COMMAND_HELP = {
4520
4576
  skill: `
4521
- ${bold('abx skill')} ${dim('— install the abx agent skill so your coding agent can drive abx')}
4522
- ${g('abx skill install')} copy the skill into ${bold('.claude/skills/abx-self-host')} in the current project
4523
- ${g('--global')} install into your home dir (~/.claude/skills) instead of the project
4524
- ${g('--target <dir>')} install under a different base dir (writes <dir>/.claude/skills/abx-self-host)
4577
+ ${bold('abx skill')} ${dim('— install the version-locked abx agent skill so your coding agent can drive abx')}
4578
+ ${g('abx skill install')} copy the bundled skill (version-locked to this CLI) into your agent(s)
4579
+ ${dim('default: both')} ${bold('.claude/skills')} ${dim('(Claude Code) and')} ${bold('.agents/skills')} ${dim('(Cursor · Codex · Gemini · Copilot)')}
4580
+ ${g('--agent <name>')} only one agent: ${dim('claude | cursor | codex | gemini | copilot')}
4581
+ ${g('--global')} install into your home dir (~) instead of the current project
4582
+ ${g('--target <dir>')} install the skill folder straight under <dir> (writes <dir>/abx-self-host)
4525
4583
  ${g('abx skill path')} print the absolute path to the bundled skill (for ${g('npx skills add <path>')} or manual copy)
4526
- ${dim('Restart your agent after installing so it picks up the skill. Cross-agent install:')} ${g('npx skills add ArtBlocks/abx')}`,
4584
+ ${dim('Restart your agent after installing so it picks up the skill. Once the repo is public, the git-based')}
4585
+ ${dim('cross-agent installer also works (not version-locked):')} ${g('npx skills add ArtBlocks/abx --skill abx-self-host')}`,
4527
4586
  deploy: `
4528
4587
  ${bold('abx deploy')} ${dim('— deploy + index a 1/1 (no server). Sends a tx in the chosen lane.')}
4529
4588
  --image <path> custody your own image (png · jpg · gif · svg · webp); else generative demo art
@@ -4979,9 +5038,11 @@ function help() {
4979
5038
  ${g('abx forget')} <address> drop a project's local registration + projection (on-chain untouched)
4980
5039
  ${g('abx status')} list indexed projects + node info
4981
5040
  ${g('abx doctor')} check environment (key, RPC, balance, factory, storage)
4982
- ${g('abx skill install')} install the abx agent skill into your agent [--global] [--target <dir>] · ${g('abx skill path')} prints the bundled skill
5041
+ ${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
5042
+ ${g('abx version')} print the installed CLI version
4983
5043
 
4984
5044
  ${dim('Run')} ${g('abx <command> --help')} ${dim('for per-command usage. --help / -h never executes — it only prints usage.')}
5045
+ ${dim('abx checks npm for a newer release (once a day, notify-only). Silence with')} ${g('ABX_NO_UPDATE_CHECK=1')} ${dim('or')} ${g('--no-update-check')}${dim('.')}
4985
5046
  `);
4986
5047
  }
4987
5048
  /** Validate an explicit --salt (a 32-byte hex). Undefined when absent. */
@@ -4999,8 +5060,13 @@ function parseSaltFlag(raw) {
4999
5060
  // ── skill: install the abx agent skill into the user's coding agent ───────────────────────
5000
5061
  // The skill is the agentic half of the toolkit — the CLI is only useful once an agent knows how
5001
5062
  // to drive it. It ships bundled inside the published package (packages/cli/skill/, created at
5002
- // prepack); in dev the same command reads the canonical copy straight from the repo. A separate
5003
- // cross-agent channel is `npx skills add ArtBlocks/abx` (the Agent Skills standard, git-based).
5063
+ // prepack); in dev the same command reads the canonical copy straight from the repo. This is the
5064
+ // CANONICAL install path precisely because the bundle is version-locked to the CLI it ships inside
5065
+ // (the version lives in SKILL.md frontmatter — see update-check.ts). By default it writes to BOTH
5066
+ // `.claude/skills` (Claude Code) and the neutral `.agents/skills` (Cursor · Codex · Gemini ·
5067
+ // Copilot all read it), so one command covers the whole ecosystem; `--agent` narrows it. A separate
5068
+ // git-based channel, `npx skills add ArtBlocks/abx --skill abx-self-host`, works once the repo is public but is NOT
5069
+ // version-locked to a local CLI — prefer `abx skill install`.
5004
5070
  /** Locate the skill folder: the bundled copy beside the compiled CLI (published), else the
5005
5071
  * canonical repo copy (dev). Returns null if neither is present. */
5006
5072
  function resolveBundledSkill() {
@@ -5064,29 +5130,71 @@ function cmdScaffoldRenderer(rest, flags) {
5064
5130
  console.log(` ${g('abx deploy-code --image-renderer <MyRenderer> --attributes-renderer <MyTraits> --onchain-uri --schema palette:HexColor:TokenOwner --name "…" --symbol …')}`);
5065
5131
  info(`full walkthrough: ${bold(`${basename(dir)}/README.md`)} · interface + invariants: https://abx.docs.artblocks.io/protocol/renderers/`);
5066
5132
  }
5133
+ /** Human label for each skills-parent dir — which agents pick the skill up from there. */
5134
+ const SKILL_PARENT_LABELS = {
5135
+ '.claude/skills': 'Claude Code',
5136
+ '.agents/skills': 'Cursor · Codex · Gemini · Copilot',
5137
+ };
5138
+ /** Resolve which skills-parent dirs `install` should write to. No `--agent` → the whole-ecosystem
5139
+ * default (Claude Code + the neutral `.agents/skills` everyone else reads). `--agent a,b` narrows
5140
+ * to those agents' dirs (de-duped, since several share `.agents/skills`). */
5141
+ function resolveInstallParents(agentFlag) {
5142
+ if (!agentFlag || agentFlag === 'true')
5143
+ return ['.claude/skills', '.agents/skills'];
5144
+ const parents = new Set();
5145
+ for (const raw of agentFlag.split(',')) {
5146
+ const a = raw.trim().toLowerCase();
5147
+ const parent = AGENT_SKILL_PARENTS[a];
5148
+ if (!parent) {
5149
+ throw new Error(`unknown --agent '${a}'. Use one of: ${Object.keys(AGENT_SKILL_PARENTS).join(', ')} (or omit --agent to install for every agent).`);
5150
+ }
5151
+ parents.add(parent);
5152
+ }
5153
+ return [...parents];
5154
+ }
5155
+ /** Copy the bundled skill folder to `dest`, replacing any prior copy so a re-install after an
5156
+ * upgrade never leaves stale reference files behind. */
5157
+ function installSkillTo(src, dest) {
5158
+ mkdirSync(joinPath(dest, '..'), { recursive: true });
5159
+ rmSync(dest, { recursive: true, force: true });
5160
+ cpSync(src, dest, { recursive: true });
5161
+ }
5067
5162
  async function cmdSkill(rest, flags) {
5068
5163
  const sub = rest[0] ?? 'install';
5069
5164
  const src = resolveBundledSkill();
5070
5165
  if (!src) {
5071
5166
  throw new Error('bundled skill not found (expected <pkg>/skill or .claude/skills/abx-self-host). ' +
5072
- 'Reinstall @artblocks/abx-cli, or install cross-agent with: npx skills add ArtBlocks/abx');
5167
+ 'Reinstall @artblocks/abx-cli, or install cross-agent with: npx skills add ArtBlocks/abx --skill abx-self-host');
5073
5168
  }
5074
5169
  if (sub === 'path') {
5075
5170
  console.log(src);
5076
5171
  return;
5077
5172
  }
5078
5173
  if (sub === 'install') {
5079
- const base = flags.global !== undefined ? homedir() : flags.target ?? process.cwd();
5080
- const destParent = joinPath(base, '.claude', 'skills');
5081
- const dest = joinPath(destParent, 'abx-self-host');
5082
- mkdirSync(destParent, { recursive: true });
5083
- cpSync(src, dest, { recursive: true });
5084
- ok(`installed the abx skill → ${dest}`);
5085
- info('restart your agent (Claude Code / Cursor) so it loads the skill, then ask it to launch an NFT with abx.');
5086
- info('cross-agent alternative (git-based): npx skills add ArtBlocks/abx');
5174
+ const version = readSkillVersion(joinPath(src, 'SKILL.md')) ?? readCliVersion();
5175
+ // Escape hatch: --target <dir> writes the skill folder straight under <dir> (for an agent
5176
+ // whose skills dir we don't special-case, or a bespoke location).
5177
+ if (typeof flags.target === 'string' && flags.target !== 'true') {
5178
+ const dest = joinPath(resolvePath(flags.target), SKILL_DIR_NAME);
5179
+ installSkillTo(src, dest);
5180
+ ok(`installed the abx skill v${version} ${dest}`);
5181
+ info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
5182
+ return;
5183
+ }
5184
+ const base = flags.global !== undefined ? homedir() : process.cwd();
5185
+ const parents = resolveInstallParents(flags.agent);
5186
+ ok(`installed the abx skill v${version}${flags.global !== undefined ? ' (global, ~)' : ''}:`);
5187
+ for (const parent of parents) {
5188
+ const dest = joinPath(base, parent, SKILL_DIR_NAME);
5189
+ installSkillTo(src, dest);
5190
+ const label = SKILL_PARENT_LABELS[parent];
5191
+ console.log(` ${g(joinPath(parent, SKILL_DIR_NAME))}${label ? dim(' → ' + label) : ''}`);
5192
+ }
5193
+ info('restart your agent so it loads the skill, then ask it to launch an NFT with abx.');
5194
+ 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.`);
5087
5195
  return;
5088
5196
  }
5089
- throw new Error('usage: abx skill <install|path> [--global] [--target <dir>]');
5197
+ throw new Error('usage: abx skill <install|path> [--agent claude|cursor|codex|gemini|copilot] [--global] [--target <dir>]');
5090
5198
  }
5091
5199
  main().catch((err) => {
5092
5200
  // Keep the actionable message; strip viem's verbose boilerplate trailer (Docs:/Version: lines) so