pruff-skills 0.13.0 → 1.0.1

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.
Files changed (3) hide show
  1. package/README.md +59 -4
  2. package/dist/cli.mjs +110 -58
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -131,6 +131,8 @@ npx pruff-skills add <skill> -g -a claude-code -y
131
131
  | `npx pruff-skills remove [skills]` | Remove installed skills from agents |
132
132
  | `npx pruff-skills update [skills]` | Update installed skills to latest versions |
133
133
  | `npx pruff-skills init [name]` | Create a new `SKILL.md` template |
134
+ | `npx pruff-skills restore` | Reinstall everything recorded in `skills-lock.json` |
135
+ | `npx pruff-skills sync` | Install skills found in `node_modules` into agent dirs |
134
136
 
135
137
  ```bash
136
138
  npx pruff-skills list # list installed skills
@@ -174,10 +176,63 @@ Scaffold one with `npx pruff-skills init my-skill`, then publish it by pushing t
174
176
 
175
177
  ## Environment Variables
176
178
 
177
- | Variable | Description |
178
- | ------------------------- | ------------------------------------------------------------------------------------ |
179
- | `PRUFF_SKILLS_CATALOG` | Source a bare skill name resolves to (default `Pruff-Tech/pruff-skills`). A GitHub `owner/repo`, git URL, or local path. Set to a local checkout for offline installs. |
180
- | `INSTALL_INTERNAL_SKILLS` | Set to `1` or `true` to show and install skills marked as `internal: true` |
179
+ Read by pruff-skills itself:
180
+
181
+ | Variable | Description |
182
+ | -------- | ----------- |
183
+ | `PRUFF_SKILLS_CATALOG` | Source a bare skill name resolves to (default `Pruff-Tech/pruff-skills`). A GitHub `owner/repo`, git URL, or local path. Set to a local checkout for offline installs. |
184
+ | `PRUFF_SKILLS_NO_ANIM` | Disable the animated logo. Also implied by `NO_COLOR` and `CI`. |
185
+ | `PRUFF_SKILLS_DOWNLOAD_URL` | Base URL for blob downloads. Empty by default. |
186
+ | `INSTALL_INTERNAL_SKILLS` | Set to `1` or `true` to show and install skills marked as `internal: true`. |
187
+ | `SKILLS_CLONE_TIMEOUT_MS` | Timeout for a `git clone`, in milliseconds. |
188
+ | `BUZZ_NEST_DIR` | Where `buzz setup` provisions its nest (default `~/.buzz`). |
189
+ | `BUZZ_APP_DATA_DIR` | Override the Buzz application-data directory. |
190
+ | `BUZZ_PACKS_DIR` | Where persona packs are read from (default: the `packs/` directory inside this package). |
191
+
192
+ Read from the surrounding environment, not owned by this CLI:
193
+
194
+ | Variable | Description |
195
+ | -------- | ----------- |
196
+ | `CLAUDE_CONFIG_DIR` | Claude Code's config directory (default `~/.claude`); determines where global skills, subagents and rules are installed. |
197
+ | `XDG_STATE_HOME` | When set, relocates the global lock to `$XDG_STATE_HOME/skills/.skill-lock.json` instead of `~/.agents/`. |
198
+ | `GITHUB_TOKEN` / `GH_TOKEN` | Used by `git`/`gh` when cloning a private catalog or skill repo. |
199
+ | `NO_COLOR` / `CI` | Suppress the animated logo. |
200
+
201
+ `CODEX_HOME`, `VIBE_HOME`, `HERMES_HOME`, `AUTOHAND_HOME`, `APPDATA`,
202
+ `FLATPAK_XDG_CONFIG_HOME` and `XDG_DATA_HOME` are read the same way, to locate the skills
203
+ directory of the corresponding agent.
204
+
205
+ ## Stability
206
+
207
+ pruff-skills follows [semantic versioning](https://semver.org/). From 1.0.0 onward, a
208
+ breaking change to anything in **Covered** below means a major version bump.
209
+
210
+ **Covered:**
211
+
212
+ - Command names and their documented aliases, and the flags listed in `--help`
213
+ - Exit codes: `0` on success, non-zero on failure
214
+ - The `PRUFF_SKILLS_*` and `INSTALL_INTERNAL_SKILLS` environment variables
215
+ - The `skills-lock.json` format, including its `version` field
216
+ - The `schemaVersion` of `list --json`
217
+ - The `schemaVersion` of `.well-known/agent-skills/index.json`, the catalog discovery manifest
218
+ - The required `SKILL.md` frontmatter fields: `name` and `description`
219
+
220
+ **Not covered** — these may change in any release:
221
+
222
+ - Human-readable output: log lines, colours, spinners, table layout, the banner. Parse
223
+ `list --json`, not stdout.
224
+ - The mapping from an agent to its on-disk skills directory. These track each third-party
225
+ tool's own layout, so they change when that tool changes, not on our schedule.
226
+ - The `buzz` command and its `BUZZ_*` variables — Pruff-internal team tooling.
227
+ - The contents of the catalog. Skills, subagents and rules are added, reworded, and
228
+ occasionally renamed or removed; the CLI's contract is how you install them, not which
229
+ ones exist. Pin a skill by committing `skills-lock.json`.
230
+ - Anything named `experimental_*`. `experimental_install` and `experimental_sync` are
231
+ retained as undocumented aliases of `restore` and `sync` and may be dropped.
232
+
233
+ **On version fields.** Both lock files and both JSON schemas carry an explicit version, and
234
+ a version this CLI cannot read is an error with instructions — never a silent reset. If you
235
+ see one, the message names the file, both versions, and what to run.
181
236
 
182
237
  ## Telemetry
183
238
 
package/dist/cli.mjs CHANGED
@@ -494,13 +494,12 @@ function isAuthFailure(message) {
494
494
  function createGitClient(extraEnv) {
495
495
  return esm_default({
496
496
  timeout: { block: CLONE_TIMEOUT_MS },
497
- env: {
498
- ...process.env,
499
- GIT_TERMINAL_PROMPT: "0",
500
- GIT_LFS_SKIP_SMUDGE: "1",
501
- ...extraEnv
502
- },
503
497
  config: ["filter.lfs.required=false"]
498
+ }).env({
499
+ ...process.env,
500
+ GIT_TERMINAL_PROMPT: "0",
501
+ GIT_LFS_SKIP_SMUDGE: "1",
502
+ ...extraEnv
504
503
  });
505
504
  }
506
505
  async function resetTempDir(dir) {
@@ -682,6 +681,8 @@ async function getPluginGroupings(basePath) {
682
681
  } catch {}
683
682
  return groupings;
684
683
  }
684
+ var UserFacingError = class extends Error {};
685
+ var LockVersionError = class extends UserFacingError {};
685
686
  const LOCAL_LOCK_FILE = "skills-lock.json";
686
687
  const CURRENT_VERSION$1 = 1;
687
688
  function getLocalLockPath(cwd) {
@@ -689,15 +690,23 @@ function getLocalLockPath(cwd) {
689
690
  }
690
691
  async function readLocalLock(cwd) {
691
692
  const lockPath = getLocalLockPath(cwd);
693
+ let content;
692
694
  try {
693
- const content = await readFile(lockPath, "utf-8");
694
- const parsed = JSON.parse(content);
695
- if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
696
- if (parsed.version < CURRENT_VERSION$1) return createEmptyLocalLock();
697
- return parsed;
698
- } catch {
699
- return createEmptyLocalLock();
695
+ content = await readFile(lockPath, "utf-8");
696
+ } catch (error) {
697
+ if (error.code === "ENOENT") return createEmptyLocalLock();
698
+ throw new LockVersionError(`Could not read ${lockPath}: ${error.message}`);
700
699
  }
700
+ let parsed;
701
+ try {
702
+ parsed = JSON.parse(content);
703
+ } catch (error) {
704
+ throw new LockVersionError(/^<{7} |^={7}$|^>{7} /m.test(content) ? `${lockPath} still has unresolved merge-conflict markers.\n Resolve them (skill entries are independent keys, so both sides can usually be kept) and re-run.` : `${lockPath} is not valid JSON: ${error.message}`);
705
+ }
706
+ if (typeof parsed.version !== "number" || !parsed.skills) throw new LockVersionError(`${lockPath} is missing a numeric "version" or a "skills" object.\n If it was hand-edited, restore it from git; otherwise delete it and re-add your skills.`);
707
+ if (parsed.version > CURRENT_VERSION$1) throw new LockVersionError(`${lockPath} is at lock schema v${parsed.version}, but this pruff-skills reads up to v${CURRENT_VERSION$1}.\n A newer pruff-skills wrote it. Upgrade with: npm i -g pruff-skills@latest`);
708
+ if (parsed.version < CURRENT_VERSION$1) throw new LockVersionError(`${lockPath} is at lock schema v${parsed.version}, which predates every released format (v1 is the oldest).\n Restore it from git, or delete it and re-add your skills.`);
709
+ return parsed;
701
710
  }
702
711
  async function writeLocalLock(lock, cwd) {
703
712
  const lockPath = getLocalLockPath(cwd);
@@ -803,13 +812,14 @@ async function parseSkillMd(skillMdPath, options) {
803
812
  const { data } = parseFrontmatter(content);
804
813
  if (!data.name || !data.description) return null;
805
814
  if (typeof data.name !== "string" || typeof data.description !== "string") return null;
806
- if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
815
+ const metadata = typeof data.metadata === "object" && data.metadata !== null && !Array.isArray(data.metadata) ? data.metadata : void 0;
816
+ if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
807
817
  return {
808
818
  name: sanitizeMetadata(data.name),
809
819
  description: sanitizeMetadata(data.description),
810
820
  path: dirname(skillMdPath),
811
821
  rawContent: content,
812
- metadata: data.metadata
822
+ metadata
813
823
  };
814
824
  } catch {
815
825
  return null;
@@ -2125,6 +2135,18 @@ const PRUFF_CATALOG = process.env.PRUFF_SKILLS_CATALOG || "Pruff-Tech/pruff-skil
2125
2135
  function isBareSkillName(value) {
2126
2136
  return !!value && /^[a-z0-9][a-z0-9-]*$/.test(value) && !value.includes("/") && !value.includes(".") && !value.includes(":");
2127
2137
  }
2138
+ var CatalogSchemaError = class extends UserFacingError {};
2139
+ function parseCatalogIndex(raw, sourceLabel) {
2140
+ let parsed;
2141
+ try {
2142
+ parsed = JSON.parse(raw);
2143
+ } catch (error) {
2144
+ throw new CatalogSchemaError(`Could not parse the catalog index from ${sourceLabel}: ${error.message}`);
2145
+ }
2146
+ const declared = parsed?.schemaVersion;
2147
+ if (typeof declared === "number" && declared > 1) throw new CatalogSchemaError(`The catalog at ${sourceLabel} declares index schema v${declared}, but this pruff-skills understands up to v1.\n Upgrade with: npm i -g pruff-skills@latest`);
2148
+ return parsed ?? {};
2149
+ }
2128
2150
  var import_dist = require_dist();
2129
2151
  let cachedResult = null;
2130
2152
  const agentNameToType = {
@@ -2628,15 +2650,23 @@ function getSkillLockPath() {
2628
2650
  }
2629
2651
  async function readSkillLock() {
2630
2652
  const lockPath = getSkillLockPath();
2653
+ let content;
2654
+ try {
2655
+ content = await readFile(lockPath, "utf-8");
2656
+ } catch (error) {
2657
+ if (error.code === "ENOENT") return createEmptyLockFile();
2658
+ throw new LockVersionError(`Could not read ${lockPath}: ${error.message}`);
2659
+ }
2660
+ let parsed;
2631
2661
  try {
2632
- const content = await readFile(lockPath, "utf-8");
2633
- const parsed = JSON.parse(content);
2634
- if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
2635
- if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
2636
- return parsed;
2662
+ parsed = JSON.parse(content);
2637
2663
  } catch (error) {
2638
- return createEmptyLockFile();
2664
+ throw new LockVersionError(`${lockPath} is not valid JSON: ${error.message}\n This file only records what is already installed. Deleting it is safe — you will just lose update tracking until each skill is re-added.`);
2639
2665
  }
2666
+ if (typeof parsed.version !== "number" || !parsed.skills) throw new LockVersionError(`${lockPath} is missing a numeric "version" or a "skills" object.\n Deleting it is safe — you will just lose update tracking until each skill is re-added.`);
2667
+ if (parsed.version > CURRENT_VERSION) throw new LockVersionError(`${lockPath} is at lock schema v${parsed.version}, but this pruff-skills reads up to v${CURRENT_VERSION}.\n A newer pruff-skills wrote it. Upgrade with: npm i -g pruff-skills@latest`);
2668
+ if (parsed.version < CURRENT_VERSION) throw new LockVersionError(`${lockPath} is at lock schema v${parsed.version}; this pruff-skills needs v${CURRENT_VERSION}, and the missing folder hashes cannot be reconstructed offline.\n Delete it and re-add your skills: rm ${lockPath}\n Nothing installed is removed by that — only the update-tracking record.`);
2669
+ return parsed;
2640
2670
  }
2641
2671
  async function writeSkillLock(lock) {
2642
2672
  const lockPath = getSkillLockPath();
@@ -4216,11 +4246,7 @@ const fetchRuleFile = (source, name) => fetchFromCatalog(source, `rules/${name}.
4216
4246
  async function fetchRuleIndex(source) {
4217
4247
  const raw = await fetchFromCatalog(source, ".well-known/agent-skills/index.json");
4218
4248
  if (!raw) return [];
4219
- try {
4220
- return JSON.parse(raw).rules ?? [];
4221
- } catch {
4222
- return [];
4223
- }
4249
+ return parseCatalogIndex(raw, source).rules ?? [];
4224
4250
  }
4225
4251
  function expandScopes(scopes) {
4226
4252
  const set = new Set(scopes);
@@ -4335,10 +4361,7 @@ const UNCHECKED = "◻";
4335
4361
  const CATALOG_SOURCE = process.env.PRUFF_SKILLS_CATALOG || "Pruff-Tech/pruff-skills";
4336
4362
  async function fetchCatalogIndex(source) {
4337
4363
  const rel = ".well-known/agent-skills/index.json";
4338
- if (source.startsWith(".") || isAbsolute(source)) {
4339
- const raw = readFileSync(join(source, rel), "utf8");
4340
- return JSON.parse(raw);
4341
- }
4364
+ if (source.startsWith(".") || isAbsolute(source)) return parseCatalogIndex(readFileSync(join(source, rel), "utf8"), source);
4342
4365
  if (source.match(/^[^/]+\/[^/]+$/)) {
4343
4366
  const { stdout } = await execFileAsync$2("gh", [
4344
4367
  "api",
@@ -4349,8 +4372,7 @@ async function fetchCatalogIndex(source) {
4349
4372
  ...process.env,
4350
4373
  GIT_TERMINAL_PROMPT: "0"
4351
4374
  } });
4352
- const json = Buffer.from(stdout.trim(), "base64").toString("utf8");
4353
- return JSON.parse(json);
4375
+ return parseCatalogIndex(Buffer.from(stdout.trim(), "base64").toString("utf8"), source);
4354
4376
  }
4355
4377
  return {};
4356
4378
  }
@@ -4400,7 +4422,8 @@ async function loadCatalog() {
4400
4422
  ...(index.agents ?? []).map((e) => toEntry(e, "agent")),
4401
4423
  ...(index.rules ?? []).map((e) => toEntry(e, "rule"))
4402
4424
  ];
4403
- } catch {
4425
+ } catch (error) {
4426
+ if (error instanceof CatalogSchemaError) throw error;
4404
4427
  catalogCache = [];
4405
4428
  }
4406
4429
  return catalogCache;
@@ -5026,6 +5049,7 @@ const BOLD$2 = "\x1B[1m";
5026
5049
  const DIM$2 = "\x1B[38;5;102m";
5027
5050
  const CYAN = "\x1B[36m";
5028
5051
  const YELLOW = "\x1B[33m";
5052
+ const LIST_JSON_SCHEMA_VERSION = 1;
5029
5053
  function shortenPath(fullPath, cwd) {
5030
5054
  const home = homedir();
5031
5055
  if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
@@ -5070,12 +5094,15 @@ async function runList(args) {
5070
5094
  agentFilter
5071
5095
  });
5072
5096
  if (options.json) {
5073
- const jsonOutput = installedSkills.map((skill) => ({
5074
- name: skill.name,
5075
- path: skill.canonicalPath,
5076
- scope: skill.scope,
5077
- agents: skill.agents.map((a) => agents[a].displayName)
5078
- }));
5097
+ const jsonOutput = {
5098
+ schemaVersion: LIST_JSON_SCHEMA_VERSION,
5099
+ skills: installedSkills.map((skill) => ({
5100
+ name: skill.name,
5101
+ path: skill.canonicalPath,
5102
+ scope: skill.scope,
5103
+ agents: skill.agents
5104
+ }))
5105
+ };
5079
5106
  console.log(JSON.stringify(jsonOutput, null, 2));
5080
5107
  return;
5081
5108
  }
@@ -5083,10 +5110,6 @@ async function runList(args) {
5083
5110
  const cwd = process.cwd();
5084
5111
  const scopeLabel = scope ? "Global" : "Project";
5085
5112
  if (installedSkills.length === 0) {
5086
- if (options.json) {
5087
- console.log("[]");
5088
- return;
5089
- }
5090
5113
  console.log(`${DIM$2}No ${scopeLabel.toLowerCase()} skills found.${RESET$2}`);
5091
5114
  if (scope) console.log(`${DIM$2}Try listing project skills without -g${RESET$2}`);
5092
5115
  else console.log(`${DIM$2}Try listing global skills with -g${RESET$2}`);
@@ -5190,6 +5213,7 @@ async function removeCommand(skillNames, options) {
5190
5213
  Se(import_picocolors.default.yellow("No skills found to remove."));
5191
5214
  return;
5192
5215
  }
5216
+ if (options.agent?.includes("*")) options.agent = Object.keys(agents);
5193
5217
  if (options.agent && options.agent.length > 0) {
5194
5218
  const validAgents = Object.keys(agents);
5195
5219
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
@@ -5200,7 +5224,7 @@ async function removeCommand(skillNames, options) {
5200
5224
  }
5201
5225
  }
5202
5226
  let selectedSkills = [];
5203
- if (options.all) selectedSkills = installedSkills;
5227
+ if (options.all || skillNames.includes("*")) selectedSkills = installedSkills;
5204
5228
  else if (skillNames.length > 0) {
5205
5229
  selectedSkills = installedSkills.filter((s) => skillNames.some((name) => name.toLowerCase() === s.toLowerCase()));
5206
5230
  if (selectedSkills.length === 0) {
@@ -5339,6 +5363,15 @@ function parseRemoveOptions(args) {
5339
5363
  nextArg = args[i];
5340
5364
  }
5341
5365
  i--;
5366
+ } else if (arg === "-s" || arg === "--skill") {
5367
+ i++;
5368
+ let nextArg = args[i];
5369
+ while (i < args.length && nextArg && !nextArg.startsWith("-")) {
5370
+ skills.push(nextArg);
5371
+ i++;
5372
+ nextArg = args[i];
5373
+ }
5374
+ i--;
5342
5375
  } else if (arg && !arg.startsWith("-")) skills.push(arg);
5343
5376
  }
5344
5377
  return {
@@ -8846,9 +8879,9 @@ async function showBanner() {
8846
8879
  console.log();
8847
8880
  console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills update${RESET} ${DIM}Update installed skills + subagents${RESET}`);
8848
8881
  console.log();
8849
- console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
8882
+ console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills restore${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
8850
8883
  console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
8851
- console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
8884
+ console.log(` ${DIM}$${RESET} ${TEXT}npx pruff-skills sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
8852
8885
  console.log();
8853
8886
  console.log(`${DIM}try:${RESET} ${BLUE}npx pruff-skills add <skill>${RESET}`);
8854
8887
  console.log();
@@ -8883,6 +8916,13 @@ ${BOLD}Usage analytics:${RESET}
8883
8916
  ${DIM}Reports token/cost by skill, MCP tool, subagent and model.${RESET}
8884
8917
  ${DIM}Never exports prompts, responses or file contents.${RESET}
8885
8918
 
8919
+ ${BOLD}Buzz nest:${RESET}
8920
+ buzz setup <team> Provision a Buzz agent nest (~/.buzz): vault, team skills,
8921
+ subagents and persona packs
8922
+ buzz sync [team] Re-sync an existing nest with the catalog
8923
+ buzz packs list List the persona packs shipped with this CLI
8924
+ buzz packs init <t> Scaffold a new persona pack for a team
8925
+
8886
8926
  ${BOLD}Onboarding:${RESET}
8887
8927
  onboarding [dir] Set up a new machine (macOS), end to end: prerequisites +
8888
8928
  GitHub auth, Claude Code, Obsidian, Bun, clone the
@@ -8908,9 +8948,9 @@ ${BOLD}Update Options:${RESET}
8908
8948
  -y, --yes Skip scope prompt (auto-detect: project if in a project, else global)
8909
8949
 
8910
8950
  ${BOLD}Project:${RESET}
8911
- experimental_install Restore skills from skills-lock.json
8951
+ restore Restore skills from skills-lock.json
8912
8952
  init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
8913
- experimental_sync Sync skills from node_modules into agent directories
8953
+ sync Sync skills from node_modules into agent directories
8914
8954
 
8915
8955
  ${BOLD}Add Options:${RESET}
8916
8956
  -g, --global Install skill globally (user-level) instead of project-level
@@ -8936,9 +8976,10 @@ ${BOLD}Remove Options:${RESET}
8936
8976
  -y, --yes Skip confirmation prompts
8937
8977
  --all Shorthand for --skill '*' --agent '*' -y
8938
8978
 
8939
- ${BOLD}Experimental Sync Options:${RESET}
8979
+ ${BOLD}Sync Options:${RESET}
8940
8980
  -a, --agent <agents> Specify agents to install to (use '*' for all agents)
8941
8981
  -y, --yes Skip confirmation prompts
8982
+ -f, --force Re-copy skills already present in the agent directory
8942
8983
 
8943
8984
  ${BOLD}List Options:${RESET}
8944
8985
  -g, --global List global skills (default: project)
@@ -8968,10 +9009,10 @@ ${BOLD}Examples:${RESET}
8968
9009
  ${DIM}$${RESET} pruff-skills update
8969
9010
  ${DIM}$${RESET} pruff-skills update <skill> ${DIM}# update a single skill${RESET}
8970
9011
  ${DIM}$${RESET} pruff-skills update -g ${DIM}# update global skills only${RESET}
8971
- ${DIM}$${RESET} pruff-skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
9012
+ ${DIM}$${RESET} pruff-skills restore ${DIM}# restore from skills-lock.json${RESET}
8972
9013
  ${DIM}$${RESET} pruff-skills init my-skill
8973
- ${DIM}$${RESET} pruff-skills experimental_sync ${DIM}# sync from node_modules${RESET}
8974
- ${DIM}$${RESET} pruff-skills experimental_sync -y ${DIM}# sync without prompts${RESET}
9014
+ ${DIM}$${RESET} pruff-skills sync ${DIM}# sync from node_modules${RESET}
9015
+ ${DIM}$${RESET} pruff-skills sync -y ${DIM}# sync without prompts${RESET}
8975
9016
 
8976
9017
  Browse the catalog with ${BLUE}npx pruff-skills find${RESET}
8977
9018
  `);
@@ -9058,7 +9099,7 @@ async function main() {
9058
9099
  const inAgent = await isRunningInAgent();
9059
9100
  if (args.length === 0) {
9060
9101
  if (!inAgent) await showBanner();
9061
- return;
9102
+ return 0;
9062
9103
  }
9063
9104
  const command = args[0];
9064
9105
  const restArgs = args.slice(1);
@@ -9076,6 +9117,7 @@ async function main() {
9076
9117
  console.log();
9077
9118
  runInit(restArgs);
9078
9119
  break;
9120
+ case "restore":
9079
9121
  case "experimental_install":
9080
9122
  if (!inAgent) await showLogo();
9081
9123
  await runInstallFromLock(restArgs);
@@ -9130,6 +9172,7 @@ async function main() {
9130
9172
  const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
9131
9173
  await removeCommand(skills, removeOptions);
9132
9174
  break;
9175
+ case "sync":
9133
9176
  case "experimental_sync": {
9134
9177
  if (!inAgent) await showLogo();
9135
9178
  const { options: syncOptions } = parseSyncOptions(restArgs);
@@ -9154,9 +9197,18 @@ async function main() {
9154
9197
  console.log(VERSION);
9155
9198
  break;
9156
9199
  default:
9157
- console.log(`Unknown command: ${command}`);
9158
- console.log(`Run ${BOLD}pruff-skills --help${RESET} for usage.`);
9159
- }
9160
- }
9161
- main().finally(() => flushTelemetry().then(() => process.exit(0)));
9200
+ console.error(`Unknown command: ${command}`);
9201
+ console.error(`Run ${BOLD}pruff-skills --help${RESET} for usage.`);
9202
+ return 1;
9203
+ }
9204
+ return 0;
9205
+ }
9206
+ main().then((code) => code ?? 0).catch((error) => {
9207
+ if (error instanceof UserFacingError) console.error(error.message);
9208
+ else console.error(error instanceof Error ? error.stack ?? error.message : String(error));
9209
+ return 1;
9210
+ }).then(async (code) => {
9211
+ await flushTelemetry();
9212
+ process.exit(code);
9213
+ });
9162
9214
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruff-skills",
3
- "version": "0.13.0",
3
+ "version": "1.0.1",
4
4
  "description": "The official Pruff agent skills CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,8 +22,8 @@
22
22
  "sync-catalog:check": "node scripts/sync-catalog.mjs --check",
23
23
  "exec:test": "node scripts/execute-tests.ts",
24
24
  "prepublishOnly": "npm run build",
25
- "format": "prettier --write \"src/**/*.ts\" \"scripts/**/*.ts\"",
26
- "format:check": "prettier --check \"src/**/*.ts\" \"scripts/**/*.ts\"",
25
+ "format": "prettier --write \"src/**/*.ts\" \"scripts/**/*.ts\" \"tests/**/*.ts\"",
26
+ "format:check": "prettier --check \"src/**/*.ts\" \"scripts/**/*.ts\" \"tests/**/*.ts\"",
27
27
  "prepare": "husky || true",
28
28
  "test": "vitest",
29
29
  "type-check": "tsc --noEmit",