@crustjs/skills 0.0.9 → 0.0.11

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/README.md CHANGED
@@ -282,7 +282,6 @@ Generated output goes to `<outDir>/skills/use-<name>/` (the `use-` prefix is app
282
282
  ```
283
283
  skills/use-my-cli/
284
284
  SKILL.md # Entrypoint — loaded by the agent
285
- command-index.md # Maps all commands to documentation file paths
286
285
  commands/ # Per-command documentation mirroring the CLI hierarchy
287
286
  my-cli.md # Root command
288
287
  serve.md # Subcommand
@@ -296,8 +295,7 @@ skills/use-my-cli/
296
295
 
297
296
  | File | Purpose |
298
297
  | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
299
- | `SKILL.md` | Agent entrypoint with YAML frontmatter. Directs agents to load specific command files on demand (lazy loading). |
300
- | `command-index.md` | Markdown table listing every command, its type (runnable/group), and documentation path. |
298
+ | `SKILL.md` | Agent entrypoint with YAML frontmatter and an embedded command reference table listing every command path, type (runnable/group), and documentation link. |
301
299
  | `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
302
300
  | `crust.json` | Crust-specific JSON metadata: name, description, version, entrypoint, and list of all command paths. Also serves as an ownership marker — its presence indicates the skill was generated by Crust. |
303
301
 
package/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  // @bun
2
- import{access as JQ}from"fs/promises";import{homedir as u}from"os";import{join as z}from"path";var RQ=["claude-code","opencode"],j={"claude-code":"Claude Code",opencode:"OpenCode"};function S(Q,Z,X){let H=Z==="global"?u():process.cwd();switch(Q){case"claude-code":return z(H,".claude","skills",X);case"opencode":if(Z==="global")return z(H,".config","opencode","skills",X);return z(H,".opencode","skills",X)}}async function E(Q){let Z=typeof Q==="string"?{home:Q}:Q??{},X=Z.scope??"global",H=Z.home??u(),W=Z.cwd??process.cwd(),$=[];for(let J of RQ){let R=qQ(J,X,H,W),Y=!1;for(let K of R)if(Y=await JQ(K).then(()=>!0).catch(()=>!1),Y)break;if(Y)$.push(J)}return $}function qQ(Q,Z,X,H){if(Z==="project")switch(Q){case"claude-code":return[z(H,".claude"),z(X,".claude")];case"opencode":return[z(H,".opencode"),z(X,".config","opencode")]}switch(Q){case"claude-code":return[z(X,".claude")];case"opencode":return[z(X,".config","opencode")]}}class M extends Error{name="SkillConflictError";details;constructor(Q){let Z=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}" already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`;super(Z);this.details=Q}}import{access as n,mkdir as EQ,rm as a,writeFile as LQ}from"fs/promises";import{dirname as TQ,join as t}from"path";function p(Q){return f(Q,[])}function f(Q,Z){let X=YQ(Q.meta.name),H=[...Z,X],W=BQ(Q.args),$=OQ(Q.effectiveFlags),J=GQ(Q.subCommands,H);return{name:X,path:H,description:Q.meta.description,usage:Q.meta.usage,runnable:typeof Q.run==="function",args:W,flags:$,children:J}}function YQ(Q){return Q.trim().toLowerCase()}function BQ(Q){if(!Q||Q.length===0)return[];return Q.map(KQ)}function KQ(Q){let Z={name:Q.name,type:Q.type,required:Q.required===!0,variadic:Q.variadic===!0};if(Q.description!==void 0)Z.description=Q.description;if(Q.default!==void 0)Z.default=c(Q.default);return Z}function OQ(Q){if(!Q)return[];return Object.keys(Q).sort().map((X)=>{return xQ(X,Q[X])})}function xQ(Q,Z){let X={name:Q,type:Z.type,required:Z.required===!0,multiple:Z.multiple===!0,short:Z.short,aliases:Z.aliases?[...Z.aliases].sort():[]};if(Z.description!==void 0)X.description=Z.description;if(Z.default!==void 0)X.default=c(Z.default);return X}function GQ(Q,Z){return Object.keys(Q).sort().map((H)=>{return f(Q[H],Z)})}function c(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function _(Q){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(Q))return`"${Q.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return Q}function g(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function m(Q,Z){let X=[],H=d(Q);X.push({path:"SKILL.md",content:zQ(Q,Z)}),X.push({path:"command-index.md",content:UQ(Q,H)});for(let W of H){let $=U(W),J=W.children.length>0?MQ(W,Q):jQ(W,Q);X.push({path:$,content:J})}return X}function d(Q){let Z=[Q];for(let X of Q.children)Z.push(...d(X));return Z}function U(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function L(Q){return Q.path.join(" ")}function A(Q,Z){let X=Q.split("/").slice(0,-1),H=Z.split("/"),W=0;while(W<X.length&&W<H.length&&X[W]===H[W])W++;let $=X.length-W,J=H.slice(W);if($===0)return J.join("/");return[...Array.from({length:$},()=>".."),...J].join("/")}function zQ(Q,Z){let X=[];if(X.push("---"),X.push(`name: ${_(Z.name)}`),X.push(`description: ${_(Z.description)}`),Z.license)X.push(`license: ${_(Z.license)}`);if(Z.compatibility)X.push(`compatibility: ${_(Z.compatibility)}`);if(Z.disableModelInvocation)X.push("disable-model-invocation: true");if(Z.allowedTools)X.push(`allowed-tools: ${_(Z.allowedTools)}`);if(X.push("metadata:"),X.push(` version: "${Z.version}"`),X.push("---"),X.push(""),X.push(`# ${Z.name}`),X.push(""),Q.description)X.push(Q.description),X.push("");let H=Z.name.startsWith("use-")?Z.name.slice(4):Z.name;if(X.push(`Use this skill when working with \`${H}\` commands, or when you need help with \`${H}\` syntax, flags, or subcommands.`),X.push(""),X.push("## Command Reference"),X.push(""),X.push("For the full list of commands and their documentation paths, see [command-index.md](command-index.md). **Do not read all command files at once.** Instead:"),X.push(""),X.push("1. Check [command-index.md](command-index.md) to find the relevant command"),X.push("2. Read only the specific file from the `commands/` directory that you need"),X.push(""),Q.children.length>0){X.push("## Available Commands"),X.push("");for(let W of Q.children){let $=U(W),J=W.description?` - ${W.description}`:"";X.push(`- [\`${W.name}\`](${$})${J}`)}X.push("")}if(Q.runnable){X.push("## Usage"),X.push("");let W=U(Q);X.push(`The root command is directly executable. See [${Q.name}](${W}) for usage details.`),X.push("")}return X.join(`
3
- `)}function UQ(Q,Z){let X=[];X.push("# Command Index"),X.push(""),X.push("| Command | Type | Documentation |"),X.push("| ------- | ---- | ------------- |");for(let H of Z){let W=L(H),$=U(H),J=VQ(H);X.push(`| \`${W}\` | ${J} | [${$}](${$}) |`)}return X.push(""),X.join(`
4
- `)}function VQ(Q){if(Q.runnable&&Q.children.length>0)return"runnable, group";if(Q.runnable)return"runnable";return"group"}function jQ(Q,Z){let X=[],H=L(Q);if(X.push(`# \`${H}\``),X.push(""),Q.description)X.push(Q.description),X.push("");if(X.push("## Usage"),X.push(""),Q.usage)X.push("```"),X.push(Q.usage),X.push("```");else X.push("```"),X.push(r(Q)),X.push("```");if(X.push(""),Q.args.length>0)X.push("## Arguments"),X.push(""),X.push(...l(Q.args)),X.push("");if(Q.flags.length>0)X.push("## Flags"),X.push(""),X.push(...i(Q.flags)),X.push("");return X.push(...o(Q,Z)),X.join(`
5
- `)}function MQ(Q,Z){let X=[],H=L(Q),W=U(Q);if(X.push(`# \`${H}\``),X.push(""),Q.description)X.push(Q.description),X.push("");if(Q.runnable){if(X.push("## Usage"),X.push(""),Q.usage)X.push("```"),X.push(Q.usage),X.push("```");else X.push("```"),X.push(r(Q)),X.push("```");if(X.push(""),Q.args.length>0)X.push("## Arguments"),X.push(""),X.push(...l(Q.args)),X.push("");if(Q.flags.length>0)X.push("## Flags"),X.push(""),X.push(...i(Q.flags)),X.push("")}X.push("## Subcommands"),X.push("");for(let $ of Q.children){let J=U($),R=A(W,J),Y=$.description?` - ${$.description}`:"";X.push(`- [\`${$.name}\`](${R})${Y}`)}return X.push(""),X.push(...o(Q,Z)),X.join(`
6
- `)}function r(Q){let Z=[...Q.path];for(let X of Q.args)if(X.variadic)Z.push(X.required?`<${X.name}...>`:`[${X.name}...]`);else Z.push(X.required?`<${X.name}>`:`[${X.name}]`);if(Q.flags.length>0)Z.push("[options]");return Z.join(" ")}function l(Q){let Z=[];Z.push("| Argument | Type | Required | Description |"),Z.push("| -------- | ---- | -------- | ----------- |");for(let X of Q){let H=X.variadic?`${X.name}...`:X.name,W=X.required?"Yes":"No",$=g(IQ(X));Z.push(`| \`${H}\` | ${X.type} | ${W} | ${$} |`)}return Z}function IQ(Q){let Z=[];if(Q.description)Z.push(Q.description);if(Q.default!==void 0)Z.push(`Default: \`${Q.default}\``);return Z.join(". ")||"-"}function i(Q){let Z=[];Z.push("| Flag | Type | Required | Description |"),Z.push("| ---- | ---- | -------- | ----------- |");for(let X of Q){let H=_Q(X),W=X.required?"Yes":"No",$=g(wQ(X));Z.push(`| ${H} | ${X.type} | ${W} | ${$} |`)}return Z}function _Q(Q){let Z=[`\`--${Q.name}\``];if(Q.short)Z.push(`\`-${Q.short}\``);for(let X of Q.aliases)Z.push(`\`--${X}\``);return Z.join(", ")}function wQ(Q){let Z=[];if(Q.description)Z.push(Q.description);if(Q.multiple)Z.push("Can be specified multiple times");if(Q.default!==void 0)Z.push(`Default: \`${Q.default}\``);return Z.join(". ")||"-"}function o(Q,Z){let X=[],H=U(Q);if(X.push("---"),X.push(""),Q.path.length>1){let $=Q.path.slice(0,-1),J=s(Z,$);if(J){let R=U(J),Y=A(H,R),K=L(J);X.push(`Parent: [\`${K}\`](${Y})`),X.push("")}}let W=A(H,"command-index.md");return X.push(`[Command Index](${W})`),X.push(""),X}function s(Q,Z){if(kQ(Q.path,Z))return Q;for(let X of Q.children){let H=s(X,Z);if(H)return H}return}function kQ(Q,Z){if(Q.length!==Z.length)return!1;for(let X=0;X<Q.length;X++)if(Q[X]!==Z[X])return!1;return!0}import{readFile as FQ}from"fs/promises";import{join as SQ}from"path";var C="crust.json";async function D(Q){try{let Z=await FQ(SQ(Q,C),"utf-8"),X=JSON.parse(Z);if(typeof X==="object"&&X!==null&&"version"in X&&typeof X.version==="string")return X.version;return null}catch{return null}}var e=/^[a-z0-9]+(-[a-z0-9]+)*$/;function QQ(Q){return Q.length>=1&&Q.length<=64&&e.test(Q)}function I(Q){return Q.startsWith("use-")?Q:`use-${Q}`}async function w(Q){let{command:Z,meta:X,agents:H,scope:W="global",clean:$=!0,force:J=!1}=Q,R=I(X.name);if(!QQ(R))throw Error(`Invalid skill name "${R}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${e.source}`);let Y={...X,name:R},K=p(Z),O=m(K,Y),y=PQ(K,Y),k=[...O,...y].sort((x,q)=>x.path<q.path?-1:x.path>q.path?1:0),V=[];for(let x of H){let q=S(x,W,Y.name),B=await D(q);if(B===null){if(await n(q).then(()=>!0).catch(()=>!1)&&!J)throw new M({agent:x,outputDir:q})}let G=B===null?"installed":B===Y.version?"up-to-date":"updated";if(G==="up-to-date"){V.push({agent:x,outputDir:q,files:[],status:"up-to-date"});continue}let F=G==="updated"?B??void 0:void 0;if($)await yQ(q);await AQ(q,k),V.push({agent:x,outputDir:q,files:k.map((h)=>h.path),status:G,previousVersion:F})}return{agents:V}}async function N(Q){let{name:Z,agents:X,scope:H="global"}=Q,W=I(Z),$=[];for(let J of X){let R=S(J,H,W);if(await n(R).then(()=>!0).catch(()=>!1))await a(R,{recursive:!0,force:!0}),$.push({agent:J,outputDir:R,status:"removed"});else $.push({agent:J,outputDir:R,status:"not-found"})}return{agents:$}}async function T(Q){let{name:Z,agents:X,scope:H="global"}=Q,W=I(Z),$=[];for(let J of X){let R=S(J,H,W),Y=await D(R);$.push({agent:J,outputDir:R,installed:Y!==null,version:Y??void 0})}return{agents:$}}function PQ(Q,Z){return[{path:C,content:bQ(Q,Z)}]}function bQ(Q,Z){let X=XQ(Q),H={name:Z.name,description:Z.description,version:Z.version,entrypoint:"SKILL.md",commands:X};return`${JSON.stringify(H,null,"\t")}
7
- `}function XQ(Q){let Z=[Q.path.join(" ")];for(let X of Q.children)Z.push(...XQ(X));return Z}async function yQ(Q){await a(Q,{recursive:!0,force:!0})}async function AQ(Q,Z){let X=new Set;for(let W of Z){let $=t(Q,W.path),J=TQ($);X.add(J)}let H=[...X].sort();for(let W of H)await EQ(W,{recursive:!0});for(let W of Z){let $=t(Q,W.path);await LQ($,W.content,"utf-8")}}import{createCommandNode as CQ,VALIDATION_MODE_ENV as DQ}from"@crustjs/core";import{confirm as NQ,multiselect as vQ,spinner as b}from"@crustjs/prompts";import{bold as v,dim as P,yellow as ZQ}from"@crustjs/style";var HQ="skill",WQ="global";function $Q(Q,Z){return{name:Q.meta.name,description:Q.meta.description??"",version:Z}}async function hQ(Q,Z){let X=Z.scope??WQ,H=await E({scope:X});if(H.length===0)return;let W=$Q(Q,Z.version),J=(await T({name:W.name,agents:H,scope:X})).agents.filter((R)=>R.installed&&R.version!==W.version);if(J.length===0)return;try{await b({message:"Updating skills...",task:async({updateMessage:R})=>{let Y=await w({command:Q,meta:W,agents:J.map((O)=>O.agent),scope:Z.scope}),K=Y.agents.filter((O)=>O.status==="updated").map((O)=>j[O.agent]);if(K.length>0)R(`Updated skill "${I(W.name)}" to v${W.version} for ${K.join(", ")}`);return Y}})}catch(R){if(R instanceof M)console.warn(ZQ(`Skill conflict: "${R.details.outputDir}" already exists but was not created by ${W.name}. Skipping auto-update. Delete or rename the conflicting skill to resolve.`));else throw R}}function uQ(Q){let Z;return{name:"skills",async setup(X,H){Z=X.rootCommand;let W=typeof Q.command==="string"?Q.command:HQ;if(Q.command!==!1)H.addSubCommand(Z,W,pQ(Z,Q));if(process.env[DQ]==="1")return;if(Q.command!==!1&&X.argv[0]===W)return;if(Q.autoUpdate!==!1)await hQ(Z,Q)}}}function pQ(Q,Z){let X=CQ(HQ);return X.meta.description="Manage agent skill installations",X.run=async()=>{let H=$Q(Q,Z.version),W=Z.scope??WQ,$=await E({scope:W});if($.length===0){console.log(ZQ("No supported agents detected. Install Claude Code or OpenCode first."));return}let J=await T({name:H.name,agents:$,scope:W}),R=[],Y=J.agents.map((q)=>{let B=q.installed?`v${q.version} installed`:"not installed";if(q.installed)R.push(q.agent);return{label:j[q.agent],value:q.agent,hint:B}}),K=process.stdin.isTTY,O=await vQ({message:"Select agents to install skills for",choices:Y,default:R,initial:!K?$:void 0,required:!1}),y=O.filter((q)=>!R.includes(q)),k=O.filter((q)=>{let B=J.agents.find((G)=>G.agent===q);return B?.installed===!0&&B.version!==H.version}),V=R.filter((q)=>!O.includes(q)),x=[...y,...k];if(x.length>0)try{let q=await b({message:"Installing skills...",task:async()=>w({command:Q,meta:H,agents:x,scope:W})});console.log(`
8
- ${v(`Installed "${H.name}" v${H.version}`)}`);for(let B of q.agents)console.log(P(` ${j[B.agent]} \u2192 ${B.outputDir}`))}catch(q){if(q instanceof M)if(await NQ({message:`"${q.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1,initial:!K?!1:void 0})){let G=await b({message:"Overwriting skill...",task:async()=>w({command:Q,meta:H,agents:[q.details.agent],scope:W,force:!0})});console.log(`
9
- ${v(`Installed "${H.name}" v${H.version}`)}`);for(let F of G.agents)console.log(P(` ${j[F.agent]} \u2192 ${F.outputDir}`))}else console.log(P(`
10
- Skipped ${j[q.details.agent]}`));else throw q}if(V.length>0){let B=(await b({message:"Removing skills...",task:async()=>N({name:H.name,agents:V,scope:W})})).agents.filter((G)=>G.status==="removed").map((G)=>j[G.agent]);if(B.length>0)console.log(`
11
- ${v(`Removed from ${B.join(", ")}`)}`)}if(x.length===0&&V.length===0)console.log(P("No changes."))},X}export{N as uninstallSkill,T as skillStatus,uQ as skillPlugin,I as resolveSkillName,QQ as isValidSkillName,w as generateSkill,E as detectInstalledAgents,M as SkillConflictError};
2
+ import{access as JQ}from"fs/promises";import{homedir as u}from"os";import{join as z}from"path";var qQ=["claude-code","opencode"],V={"claude-code":"Claude Code",opencode:"OpenCode"};function S(Q,Z,X){let H=Z==="global"?u():process.cwd();switch(Q){case"claude-code":return z(H,".claude","skills",X);case"opencode":if(Z==="global")return z(H,".config","opencode","skills",X);return z(H,".opencode","skills",X)}}async function E(Q){let Z=typeof Q==="string"?{home:Q}:Q??{},X=Z.scope??"global",H=Z.home??u(),W=Z.cwd??process.cwd(),$=[];for(let J of qQ){let q=xQ(J,X,H,W),R=!1;for(let B of q)if(R=await JQ(B).then(()=>!0).catch(()=>!1),R)break;if(R)$.push(J)}return $}function xQ(Q,Z,X,H){if(Z==="project")switch(Q){case"claude-code":return[z(H,".claude"),z(X,".claude")];case"opencode":return[z(H,".opencode"),z(X,".config","opencode")]}switch(Q){case"claude-code":return[z(X,".claude")];case"opencode":return[z(X,".config","opencode")]}}class j extends Error{name="SkillConflictError";details;constructor(Q){let Z=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}" already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`;super(Z);this.details=Q}}import{access as n,mkdir as EQ,rm as a,writeFile as LQ}from"fs/promises";import{dirname as TQ,join as t}from"path";function p(Q){return f(Q,[])}function f(Q,Z){let X=RQ(Q.meta.name),H=[...Z,X],W=YQ(Q.args),$=KQ(Q.effectiveFlags),J=GQ(Q.subCommands,H);return{name:X,path:H,description:Q.meta.description,usage:Q.meta.usage,runnable:typeof Q.run==="function",args:W,flags:$,children:J}}function RQ(Q){return Q.trim().toLowerCase()}function YQ(Q){if(!Q||Q.length===0)return[];return Q.map(BQ)}function BQ(Q){let Z={name:Q.name,type:Q.type,required:Q.required===!0,variadic:Q.variadic===!0};if(Q.description!==void 0)Z.description=Q.description;if(Q.default!==void 0)Z.default=c(Q.default);return Z}function KQ(Q){if(!Q)return[];return Object.keys(Q).sort().map((X)=>{return OQ(X,Q[X])})}function OQ(Q,Z){let X={name:Q,type:Z.type,required:Z.required===!0,multiple:Z.multiple===!0,short:Z.short,aliases:Z.aliases?[...Z.aliases].sort():[]};if(Z.description!==void 0)X.description=Z.description;if(Z.default!==void 0)X.default=c(Z.default);return X}function GQ(Q,Z){return Object.keys(Q).sort().map((H)=>{return f(Q[H],Z)})}function c(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function _(Q){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(Q))return`"${Q.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return Q}function g(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function m(Q,Z){let X=[],H=d(Q);X.push({path:"SKILL.md",content:zQ(Q,Z,H)});for(let W of H){let $=M(W),J=W.children.length>0?MQ(W,Q):jQ(W,Q);X.push({path:$,content:J})}return X}function d(Q){let Z=[Q];for(let X of Q.children)Z.push(...d(X));return Z}function M(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function L(Q){return Q.path.join(" ")}function A(Q,Z){let X=Q.split("/").slice(0,-1),H=Z.split("/"),W=0;while(W<X.length&&W<H.length&&X[W]===H[W])W++;let $=X.length-W,J=H.slice(W);if($===0)return J.join("/");return[...Array.from({length:$},()=>".."),...J].join("/")}function zQ(Q,Z,X){let H=[];if(H.push("---"),H.push(`name: ${_(Z.name)}`),H.push(`description: ${_(Z.description)}`),Z.license)H.push(`license: ${_(Z.license)}`);if(Z.compatibility)H.push(`compatibility: ${_(Z.compatibility)}`);if(Z.disableModelInvocation)H.push("disable-model-invocation: true");if(Z.allowedTools)H.push(`allowed-tools: ${_(Z.allowedTools)}`);if(H.push("metadata:"),H.push(` version: "${Z.version}"`),H.push("---"),H.push(""),H.push(`# ${Z.name}`),H.push(""),Q.description)H.push(Q.description),H.push("");let W=Z.name.startsWith("use-")?Z.name.slice(4):Z.name;if(H.push(`Use this skill when working with \`${W}\` commands, or when you need help with \`${W}\` syntax, flags, or subcommands.`),H.push(""),H.push("## Command Reference"),H.push(""),H.push("This table lists all commands and their documentation paths. **Do not read all command files at once.** Instead:"),H.push(""),H.push("1. Use the table below to find the relevant command"),H.push("2. Use the `Type` column to choose what to execute: commands labeled `runnable` (including `runnable, group`) are executable, while `group` commands are not"),H.push("3. Read only the specific file from the `commands/` directory that you need"),H.push(""),H.push(...UQ(X)),H.push(""),Q.runnable){H.push("## Usage"),H.push("");let $=M(Q);H.push(`The root command is directly executable. See [${Q.name}](${$}) for usage details.`),H.push("")}return H.join(`
3
+ `)}function UQ(Q){let Z=[];Z.push("| Command | Type | Documentation |"),Z.push("| ------- | ---- | ------------- |");for(let X of Q){let H=L(X),W=M(X),$=VQ(X);Z.push(`| \`${H}\` | ${$} | [${W}](${W}) |`)}return Z}function VQ(Q){if(Q.runnable&&Q.children.length>0)return"runnable, group";if(Q.runnable)return"runnable";return"group"}function jQ(Q,Z){let X=[],H=L(Q);if(X.push(`# \`${H}\``),X.push(""),Q.description)X.push(Q.description),X.push("");if(X.push("## Usage"),X.push(""),Q.usage)X.push("```"),X.push(Q.usage),X.push("```");else X.push("```"),X.push(r(Q)),X.push("```");if(X.push(""),Q.args.length>0)X.push("## Arguments"),X.push(""),X.push(...l(Q.args)),X.push("");if(Q.flags.length>0)X.push("## Flags"),X.push(""),X.push(...i(Q.flags)),X.push("");return X.push(...o(Q,Z)),X.join(`
4
+ `)}function MQ(Q,Z){let X=[],H=L(Q),W=M(Q);if(X.push(`# \`${H}\``),X.push(""),Q.description)X.push(Q.description),X.push("");if(Q.runnable){if(X.push("## Usage"),X.push(""),Q.usage)X.push("```"),X.push(Q.usage),X.push("```");else X.push("```"),X.push(r(Q)),X.push("```");if(X.push(""),Q.args.length>0)X.push("## Arguments"),X.push(""),X.push(...l(Q.args)),X.push("");if(Q.flags.length>0)X.push("## Flags"),X.push(""),X.push(...i(Q.flags)),X.push("")}X.push("## Subcommands"),X.push("");for(let $ of Q.children){let J=M($),q=A(W,J),R=$.description?` - ${$.description}`:"";X.push(`- [\`${$.name}\`](${q})${R}`)}return X.push(""),X.push(...o(Q,Z)),X.join(`
5
+ `)}function r(Q){let Z=[...Q.path];for(let X of Q.args)if(X.variadic)Z.push(X.required?`<${X.name}...>`:`[${X.name}...]`);else Z.push(X.required?`<${X.name}>`:`[${X.name}]`);if(Q.flags.length>0)Z.push("[options]");return Z.join(" ")}function l(Q){let Z=[];Z.push("| Argument | Type | Required | Description |"),Z.push("| -------- | ---- | -------- | ----------- |");for(let X of Q){let H=X.variadic?`${X.name}...`:X.name,W=X.required?"Yes":"No",$=g(IQ(X));Z.push(`| \`${H}\` | ${X.type} | ${W} | ${$} |`)}return Z}function IQ(Q){let Z=[];if(Q.description)Z.push(Q.description);if(Q.default!==void 0)Z.push(`Default: \`${Q.default}\``);return Z.join(". ")||"-"}function i(Q){let Z=[];Z.push("| Flag | Type | Required | Description |"),Z.push("| ---- | ---- | -------- | ----------- |");for(let X of Q){let H=_Q(X),W=X.required?"Yes":"No",$=g(wQ(X));Z.push(`| ${H} | ${X.type} | ${W} | ${$} |`)}return Z}function _Q(Q){let Z=[`\`--${Q.name}\``];if(Q.short)Z.push(`\`-${Q.short}\``);for(let X of Q.aliases)Z.push(`\`--${X}\``);return Z.join(", ")}function wQ(Q){let Z=[];if(Q.description)Z.push(Q.description);if(Q.multiple)Z.push("Can be specified multiple times");if(Q.default!==void 0)Z.push(`Default: \`${Q.default}\``);return Z.join(". ")||"-"}function o(Q,Z){let X=[],H=M(Q);if(X.push("---"),X.push(""),Q.path.length>1){let $=Q.path.slice(0,-1),J=s(Z,$);if(J){let q=M(J),R=A(H,q),B=L(J);X.push(`Parent: [\`${B}\`](${R})`),X.push("")}}let W=A(H,"SKILL.md");return X.push(`[Skill Overview](${W})`),X.push(""),X}function s(Q,Z){if(FQ(Q.path,Z))return Q;for(let X of Q.children){let H=s(X,Z);if(H)return H}return}function FQ(Q,Z){if(Q.length!==Z.length)return!1;for(let X=0;X<Q.length;X++)if(Q[X]!==Z[X])return!1;return!0}import{readFile as kQ}from"fs/promises";import{join as SQ}from"path";var C="crust.json";async function D(Q){try{let Z=await kQ(SQ(Q,C),"utf-8"),X=JSON.parse(Z);if(typeof X==="object"&&X!==null&&"version"in X&&typeof X.version==="string")return X.version;return null}catch{return null}}var e=/^[a-z0-9]+(-[a-z0-9]+)*$/;function QQ(Q){return Q.length>=1&&Q.length<=64&&e.test(Q)}function I(Q){return Q.startsWith("use-")?Q:`use-${Q}`}async function w(Q){let{command:Z,meta:X,agents:H,scope:W="global",clean:$=!0,force:J=!1}=Q,q=I(X.name);if(!QQ(q))throw Error(`Invalid skill name "${q}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${e.source}`);let R={...X,name:q},B=p(Z),K=m(B,R),y=PQ(B,R),F=[...K,...y].sort((O,x)=>O.path<x.path?-1:O.path>x.path?1:0),U=[];for(let O of H){let x=S(O,W,R.name),Y=await D(x);if(Y===null){if(await n(x).then(()=>!0).catch(()=>!1)&&!J)throw new j({agent:O,outputDir:x})}let G=Y===null?"installed":Y===R.version?"up-to-date":"updated";if(G==="up-to-date"){U.push({agent:O,outputDir:x,files:[],status:"up-to-date"});continue}let k=G==="updated"?Y??void 0:void 0;if($)await yQ(x);await AQ(x,F),U.push({agent:O,outputDir:x,files:F.map((h)=>h.path),status:G,previousVersion:k})}return{agents:U}}async function N(Q){let{name:Z,agents:X,scope:H="global"}=Q,W=I(Z),$=[];for(let J of X){let q=S(J,H,W);if(await n(q).then(()=>!0).catch(()=>!1))await a(q,{recursive:!0,force:!0}),$.push({agent:J,outputDir:q,status:"removed"});else $.push({agent:J,outputDir:q,status:"not-found"})}return{agents:$}}async function T(Q){let{name:Z,agents:X,scope:H="global"}=Q,W=I(Z),$=[];for(let J of X){let q=S(J,H,W),R=await D(q);$.push({agent:J,outputDir:q,installed:R!==null,version:R??void 0})}return{agents:$}}function PQ(Q,Z){return[{path:C,content:bQ(Q,Z)}]}function bQ(Q,Z){let X=XQ(Q),H={name:Z.name,description:Z.description,version:Z.version,entrypoint:"SKILL.md",commands:X};return`${JSON.stringify(H,null,"\t")}
6
+ `}function XQ(Q){let Z=[Q.path.join(" ")];for(let X of Q.children)Z.push(...XQ(X));return Z}async function yQ(Q){await a(Q,{recursive:!0,force:!0})}async function AQ(Q,Z){let X=new Set;for(let W of Z){let $=t(Q,W.path),J=TQ($);X.add(J)}let H=[...X].sort();for(let W of H)await EQ(W,{recursive:!0});for(let W of Z){let $=t(Q,W.path);await LQ($,W.content,"utf-8")}}import{createCommandNode as CQ,VALIDATION_MODE_ENV as DQ}from"@crustjs/core";import{confirm as NQ,multiselect as vQ,spinner as b}from"@crustjs/prompts";import{bold as v,dim as P,yellow as ZQ}from"@crustjs/style";var HQ="skill",WQ="global";function $Q(Q,Z){return{name:Q.meta.name,description:Q.meta.description??"",version:Z}}async function hQ(Q,Z){let X=Z.scope??WQ,H=await E({scope:X});if(H.length===0)return;let W=$Q(Q,Z.version),J=(await T({name:W.name,agents:H,scope:X})).agents.filter((q)=>q.installed&&q.version!==W.version);if(J.length===0)return;try{await b({message:"Updating skills...",task:async({updateMessage:q})=>{let R=await w({command:Q,meta:W,agents:J.map((K)=>K.agent),scope:Z.scope}),B=R.agents.filter((K)=>K.status==="updated").map((K)=>V[K.agent]);if(B.length>0)q(`Updated skill "${I(W.name)}" to v${W.version} for ${B.join(", ")}`);return R}})}catch(q){if(q instanceof j)console.warn(ZQ(`Skill conflict: "${q.details.outputDir}" already exists but was not created by ${W.name}. Skipping auto-update. Delete or rename the conflicting skill to resolve.`));else throw q}}function uQ(Q){let Z;return{name:"skills",async setup(X,H){Z=X.rootCommand;let W=typeof Q.command==="string"?Q.command:HQ;if(Q.command!==!1)H.addSubCommand(Z,W,pQ(Z,Q));if(process.env[DQ]==="1")return;if(Q.command!==!1&&X.argv[0]===W)return;if(Q.autoUpdate!==!1)await hQ(Z,Q)}}}function pQ(Q,Z){let X=CQ(HQ);return X.meta.description="Manage agent skill installations",X.run=async()=>{let H=$Q(Q,Z.version),W=Z.scope??WQ,$=await E({scope:W});if($.length===0){console.log(ZQ("No supported agents detected. Install Claude Code or OpenCode first."));return}let J=await T({name:H.name,agents:$,scope:W}),q=[],R=J.agents.map((x)=>{let Y=x.installed?`v${x.version} installed`:"not installed";if(x.installed)q.push(x.agent);return{label:V[x.agent],value:x.agent,hint:Y}}),B=process.stdin.isTTY,K=await vQ({message:"Select agents to install skills for",choices:R,default:q,initial:!B?$:void 0,required:!1}),y=K.filter((x)=>!q.includes(x)),F=K.filter((x)=>{let Y=J.agents.find((G)=>G.agent===x);return Y?.installed===!0&&Y.version!==H.version}),U=q.filter((x)=>!K.includes(x)),O=[...y,...F];if(O.length>0)try{let x=await b({message:"Installing skills...",task:async()=>w({command:Q,meta:H,agents:O,scope:W})});console.log(`
7
+ ${v(`Installed "${H.name}" v${H.version}`)}`);for(let Y of x.agents)console.log(P(` ${V[Y.agent]} \u2192 ${Y.outputDir}`))}catch(x){if(x instanceof j)if(await NQ({message:`"${x.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1,initial:!B?!1:void 0})){let G=await b({message:"Overwriting skill...",task:async()=>w({command:Q,meta:H,agents:[x.details.agent],scope:W,force:!0})});console.log(`
8
+ ${v(`Installed "${H.name}" v${H.version}`)}`);for(let k of G.agents)console.log(P(` ${V[k.agent]} \u2192 ${k.outputDir}`))}else console.log(P(`
9
+ Skipped ${V[x.details.agent]}`));else throw x}if(U.length>0){let Y=(await b({message:"Removing skills...",task:async()=>N({name:H.name,agents:U,scope:W})})).agents.filter((G)=>G.status==="removed").map((G)=>V[G.agent]);if(Y.length>0)console.log(`
10
+ ${v(`Removed from ${Y.join(", ")}`)}`)}if(O.length===0&&U.length===0)console.log(P("No changes."))},X}export{N as uninstallSkill,T as skillStatus,uQ as skillPlugin,I as resolveSkillName,QQ as isValidSkillName,w as generateSkill,E as detectInstalledAgents,j as SkillConflictError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/skills",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Agent skill generation from Crust command definitions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,16 +42,16 @@
42
42
  "test": "bun test"
43
43
  },
44
44
  "dependencies": {
45
- "@crustjs/prompts": "0.0.7",
45
+ "@crustjs/prompts": "0.0.8",
46
46
  "@crustjs/style": "0.0.4"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@crustjs/config": "0.0.0",
50
- "@crustjs/core": "0.0.10",
50
+ "@crustjs/core": "0.0.11",
51
51
  "bunup": "^0.16.29"
52
52
  },
53
53
  "peerDependencies": {
54
- "@crustjs/core": "0.0.10",
54
+ "@crustjs/core": "0.0.11",
55
55
  "typescript": "^5"
56
56
  }
57
57
  }