@crustjs/skills 0.0.8 → 0.0.10

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyCommand } from "@crustjs/core";
1
+ import { CommandNode } from "@crustjs/core";
2
2
  /**
3
3
  * Metadata for the generated skill bundle.
4
4
  *
@@ -97,7 +97,7 @@ type Scope = "global" | "project";
97
97
  */
98
98
  interface GenerateOptions {
99
99
  /** Root command to generate the skill from */
100
- command: AnyCommand;
100
+ command: CommandNode;
101
101
  /** Skill metadata for the generated bundle */
102
102
  meta: SkillMeta;
103
103
  /** Agent targets to install skills for */
@@ -423,21 +423,17 @@ import { CrustPlugin } from "@crustjs/core";
423
423
  *
424
424
  * @example
425
425
  * ```ts
426
- * import { defineCommand, runMain } from "@crustjs/core";
426
+ * import { Crust } from "@crustjs/core";
427
427
  * import { skillPlugin } from "@crustjs/skills";
428
428
  *
429
- * const app = defineCommand({
430
- * meta: { name: "my-cli", description: "My CLI" },
431
- * });
429
+ * const app = new Crust("my-cli").meta({ description: "My CLI" })
430
+ * .use(skillPlugin({
431
+ * version: "1.0.0",
432
+ * command: true, // registers "my-cli skill" subcommand
433
+ * }))
434
+ * .run(() => { /* ... *​/ });
432
435
  *
433
- * runMain(app, {
434
- * plugins: [
435
- * skillPlugin({
436
- * version: "1.0.0",
437
- * command: true, // registers "my-cli skill" subcommand
438
- * }),
439
- * ],
440
- * });
436
+ * await app.execute();
441
437
  * ```
442
438
  */
443
439
  declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // @bun
2
- import{access as JQ}from"fs/promises";import{homedir as h}from"os";import{join as G}from"path";var RQ=["claude-code","opencode"],V={"claude-code":"Claude Code",opencode:"OpenCode"};function F(X,Z,Q){let H=Z==="global"?h():process.cwd();switch(X){case"claude-code":return G(H,".claude","skills",Q);case"opencode":if(Z==="global")return G(H,".config","opencode","skills",Q);return G(H,".opencode","skills",Q)}}async function S(X){let Z=typeof X==="string"?{home:X}:X??{},Q=Z.scope??"global",H=Z.home??h(),W=Z.cwd??process.cwd(),$=[];for(let J of RQ){let R=qQ(J,Q,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(X,Z,Q,H){if(Z==="project")switch(X){case"claude-code":return[G(H,".claude"),G(Q,".claude")];case"opencode":return[G(H,".opencode"),G(Q,".config","opencode")]}switch(X){case"claude-code":return[G(Q,".claude")];case"opencode":return[G(Q,".config","opencode")]}}class j extends Error{name="SkillConflictError";details;constructor(X){let Z=`Skill conflict for agent "${X.agent}": directory "${X.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=X}}import{access as s,mkdir as LQ,rm as n,writeFile as TQ}from"fs/promises";import{dirname as PQ,join as t}from"path";function u(X){return p(X,[])}function p(X,Z){let Q=YQ(X.meta.name),H=[...Z,Q],W=BQ(X.args),$=OQ(X.flags),J=zQ(X.subCommands,H);return{name:Q,path:H,description:X.meta.description,usage:X.meta.usage,runnable:typeof X.run==="function",args:W,flags:$,children:J}}function YQ(X){return X.trim().toLowerCase()}function BQ(X){if(!X||X.length===0)return[];return X.map(KQ)}function KQ(X){let Z={name:X.name,type:X.type,required:X.required===!0,variadic:X.variadic===!0};if(X.description!==void 0)Z.description=X.description;if(X.default!==void 0)Z.default=f(X.default);return Z}function OQ(X){if(!X)return[];return Object.keys(X).sort().map((Q)=>{return xQ(Q,X[Q])})}function xQ(X,Z){let Q={name:X,type:Z.type,required:Z.required===!0,multiple:Z.multiple===!0,aliases:GQ(Z.alias)};if(Z.description!==void 0)Q.description=Z.description;if(Z.default!==void 0)Q.default=f(Z.default);return Q}function GQ(X){if(X===void 0)return[];if(typeof X==="string")return[X];return[...X].sort()}function zQ(X,Z){return Object.keys(X).sort().map((H)=>{return p(X[H],Z)})}function f(X){if(Array.isArray(X))return JSON.stringify(X);return String(X)}function w(X){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(X))return`"${X.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return X}function g(X){return X.replace(/(?<!\\)\|/g,"\\|")}function c(X,Z){let Q=[],H=m(X);Q.push({path:"SKILL.md",content:UQ(X,Z)}),Q.push({path:"command-index.md",content:VQ(X,H)});for(let W of H){let $=z(W),J=W.children.length>0?IQ(W,X):MQ(W,X);Q.push({path:$,content:J})}return Q}function m(X){let Z=[X];for(let Q of X.children)Z.push(...m(Q));return Z}function z(X){if(X.path.length<=1)return`commands/${X.name}.md`;return`commands/${X.path.slice(1).join("/")}.md`}function E(X){return X.path.join(" ")}function y(X,Z){let Q=X.split("/").slice(0,-1),H=Z.split("/"),W=0;while(W<Q.length&&W<H.length&&Q[W]===H[W])W++;let $=Q.length-W,J=H.slice(W);if($===0)return J.join("/");return[...Array.from({length:$},()=>".."),...J].join("/")}function UQ(X,Z){let Q=[];if(Q.push("---"),Q.push(`name: ${w(Z.name)}`),Q.push(`description: ${w(Z.description)}`),Z.license)Q.push(`license: ${w(Z.license)}`);if(Z.compatibility)Q.push(`compatibility: ${w(Z.compatibility)}`);if(Z.disableModelInvocation)Q.push("disable-model-invocation: true");if(Z.allowedTools)Q.push(`allowed-tools: ${w(Z.allowedTools)}`);if(Q.push("metadata:"),Q.push(` version: "${Z.version}"`),Q.push("---"),Q.push(""),Q.push(`# ${Z.name}`),Q.push(""),X.description)Q.push(X.description),Q.push("");let H=Z.name.startsWith("use-")?Z.name.slice(4):Z.name;if(Q.push(`Use this skill when working with \`${H}\` commands, or when you need help with \`${H}\` syntax, flags, or subcommands.`),Q.push(""),Q.push("## Command Reference"),Q.push(""),Q.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:"),Q.push(""),Q.push("1. Check [command-index.md](command-index.md) to find the relevant command"),Q.push("2. Read only the specific file from the `commands/` directory that you need"),Q.push(""),X.children.length>0){Q.push("## Available Commands"),Q.push("");for(let W of X.children){let $=z(W),J=W.description?` - ${W.description}`:"";Q.push(`- [\`${W.name}\`](${$})${J}`)}Q.push("")}if(X.runnable){Q.push("## Usage"),Q.push("");let W=z(X);Q.push(`The root command is directly executable. See [${X.name}](${W}) for usage details.`),Q.push("")}return Q.join(`
3
- `)}function VQ(X,Z){let Q=[];Q.push("# Command Index"),Q.push(""),Q.push("| Command | Type | Documentation |"),Q.push("| ------- | ---- | ------------- |");for(let H of Z){let W=E(H),$=z(H),J=jQ(H);Q.push(`| \`${W}\` | ${J} | [${$}](${$}) |`)}return Q.push(""),Q.join(`
4
- `)}function jQ(X){if(X.runnable&&X.children.length>0)return"runnable, group";if(X.runnable)return"runnable";return"group"}function MQ(X,Z){let Q=[],H=E(X);if(Q.push(`# \`${H}\``),Q.push(""),X.description)Q.push(X.description),Q.push("");if(Q.push("## Usage"),Q.push(""),X.usage)Q.push("```"),Q.push(X.usage),Q.push("```");else Q.push("```"),Q.push(d(X)),Q.push("```");if(Q.push(""),X.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...r(X.args)),Q.push("");if(X.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...l(X.flags)),Q.push("");return Q.push(...i(X,Z)),Q.join(`
5
- `)}function IQ(X,Z){let Q=[],H=E(X),W=z(X);if(Q.push(`# \`${H}\``),Q.push(""),X.description)Q.push(X.description),Q.push("");if(X.runnable){if(Q.push("## Usage"),Q.push(""),X.usage)Q.push("```"),Q.push(X.usage),Q.push("```");else Q.push("```"),Q.push(d(X)),Q.push("```");if(Q.push(""),X.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...r(X.args)),Q.push("");if(X.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...l(X.flags)),Q.push("")}Q.push("## Subcommands"),Q.push("");for(let $ of X.children){let J=z($),R=y(W,J),Y=$.description?` - ${$.description}`:"";Q.push(`- [\`${$.name}\`](${R})${Y}`)}return Q.push(""),Q.push(...i(X,Z)),Q.join(`
6
- `)}function d(X){let Z=[...X.path];for(let Q of X.args)if(Q.variadic)Z.push(Q.required?`<${Q.name}...>`:`[${Q.name}...]`);else Z.push(Q.required?`<${Q.name}>`:`[${Q.name}]`);if(X.flags.length>0)Z.push("[options]");return Z.join(" ")}function r(X){let Z=[];Z.push("| Argument | Type | Required | Description |"),Z.push("| -------- | ---- | -------- | ----------- |");for(let Q of X){let H=Q.variadic?`${Q.name}...`:Q.name,W=Q.required?"Yes":"No",$=g(_Q(Q));Z.push(`| \`${H}\` | ${Q.type} | ${W} | ${$} |`)}return Z}function _Q(X){let Z=[];if(X.description)Z.push(X.description);if(X.default!==void 0)Z.push(`Default: \`${X.default}\``);return Z.join(". ")||"-"}function l(X){let Z=[];Z.push("| Flag | Type | Required | Description |"),Z.push("| ---- | ---- | -------- | ----------- |");for(let Q of X){let H=wQ(Q),W=Q.required?"Yes":"No",$=g(kQ(Q));Z.push(`| ${H} | ${Q.type} | ${W} | ${$} |`)}return Z}function wQ(X){let Z=[`\`--${X.name}\``];for(let Q of X.aliases)Z.push(`\`-${Q}\``);return Z.join(", ")}function kQ(X){let Z=[];if(X.description)Z.push(X.description);if(X.multiple)Z.push("Can be specified multiple times");if(X.default!==void 0)Z.push(`Default: \`${X.default}\``);return Z.join(". ")||"-"}function i(X,Z){let Q=[],H=z(X);if(Q.push("---"),Q.push(""),X.path.length>1){let $=X.path.slice(0,-1),J=o(Z,$);if(J){let R=z(J),Y=y(H,R),K=E(J);Q.push(`Parent: [\`${K}\`](${Y})`),Q.push("")}}let W=y(H,"command-index.md");return Q.push(`[Command Index](${W})`),Q.push(""),Q}function o(X,Z){if(FQ(X.path,Z))return X;for(let Q of X.children){let H=o(Q,Z);if(H)return H}return}function FQ(X,Z){if(X.length!==Z.length)return!1;for(let Q=0;Q<X.length;Q++)if(X[Q]!==Z[Q])return!1;return!0}import{readFile as SQ}from"fs/promises";import{join as EQ}from"path";var C="crust.json";async function D(X){try{let Z=await SQ(EQ(X,C),"utf-8"),Q=JSON.parse(Z);if(typeof Q==="object"&&Q!==null&&"version"in Q&&typeof Q.version==="string")return Q.version;return null}catch{return null}}var a=/^[a-z0-9]+(-[a-z0-9]+)*$/;function e(X){return X.length>=1&&X.length<=64&&a.test(X)}function _(X){return X.startsWith("use-")?X:`use-${X}`}async function k(X){let{command:Z,meta:Q,agents:H,scope:W="global",clean:$=!0,force:J=!1}=X,R=_(Q.name);if(!e(R))throw Error(`Invalid skill name "${R}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${a.source}`);let Y={...Q,name:R},K=u(Z),x=c(K,Y),b=bQ(K,Y),M=[...x,...b].sort((q,B)=>q.path<B.path?-1:q.path>B.path?1:0),U=[];for(let q of H){let B=F(q,W,Y.name),O=await D(B);if(O===null){if(await s(B).then(()=>!0).catch(()=>!1)&&!J)throw new j({agent:q,outputDir:B})}let I=O===null?"installed":O===Y.version?"up-to-date":"updated";if(I==="up-to-date"){U.push({agent:q,outputDir:B,files:[],status:"up-to-date"});continue}let $Q=I==="updated"?O??void 0:void 0;if($)await CQ(B);await DQ(B,M),U.push({agent:q,outputDir:B,files:M.map((v)=>v.path),status:I,previousVersion:$Q})}return{agents:U}}async function A(X){let{name:Z,agents:Q,scope:H="global"}=X,W=_(Z),$=[];for(let J of Q){let R=F(J,H,W);if(await s(R).then(()=>!0).catch(()=>!1))await n(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 L(X){let{name:Z,agents:Q,scope:H="global"}=X,W=_(Z),$=[];for(let J of Q){let R=F(J,H,W),Y=await D(R);$.push({agent:J,outputDir:R,installed:Y!==null,version:Y??void 0})}return{agents:$}}function bQ(X,Z){return[{path:C,content:yQ(X,Z)}]}function yQ(X,Z){let Q=QQ(X),H={name:Z.name,description:Z.description,version:Z.version,entrypoint:"SKILL.md",commands:Q};return`${JSON.stringify(H,null,"\t")}
7
- `}function QQ(X){let Z=[X.path.join(" ")];for(let Q of X.children)Z.push(...QQ(Q));return Z}async function CQ(X){await n(X,{recursive:!0,force:!0})}async function DQ(X,Z){let Q=new Set;for(let W of Z){let $=t(X,W.path),J=PQ($);Q.add(J)}let H=[...Q].sort();for(let W of H)await LQ(W,{recursive:!0});for(let W of Z){let $=t(X,W.path);await TQ($,W.content,"utf-8")}}import{defineCommand as AQ,VALIDATION_MODE_ENV as NQ}from"@crustjs/core";import{confirm as vQ,multiselect as hQ,spinner as P}from"@crustjs/prompts";import{bold as N,dim as T,yellow as XQ}from"@crustjs/style";var ZQ="skill",HQ="global";function WQ(X,Z){return{name:X.meta.name,description:X.meta.description??"",version:Z}}async function uQ(X,Z){let Q=Z.scope??HQ,H=await S({scope:Q});if(H.length===0)return;let W=WQ(X,Z.version),J=(await L({name:W.name,agents:H,scope:Q})).agents.filter((R)=>R.installed&&R.version!==W.version);if(J.length===0)return;try{await P({message:"Updating skills...",task:async({updateMessage:R})=>{let Y=await k({command:X,meta:W,agents:J.map((x)=>x.agent),scope:Z.scope}),K=Y.agents.filter((x)=>x.status==="updated").map((x)=>V[x.agent]);if(K.length>0)R(`Updated skill "${_(W.name)}" to v${W.version} for ${K.join(", ")}`);return Y}})}catch(R){if(R instanceof j)console.warn(XQ(`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 pQ(X){let Z;return{name:"skills",async setup(Q,H){Z=Q.rootCommand;let W=typeof X.command==="string"?X.command:ZQ;if(X.command!==!1)H.addSubCommand(Z,W,fQ(Z,X));if(process.env[NQ]==="1")return;if(X.command!==!1&&Q.argv[0]===W)return;if(X.autoUpdate!==!1)await uQ(Z,X)}}}function fQ(X,Z){return AQ({meta:{name:ZQ,description:"Manage agent skill installations"},async run(){let Q=WQ(X,Z.version),H=Z.scope??HQ,W=await S({scope:H});if(W.length===0){console.log(XQ("No supported agents detected. Install Claude Code or OpenCode first."));return}let $=await L({name:Q.name,agents:W,scope:H}),J=[],R=$.agents.map((q)=>{let B=q.installed?`v${q.version} installed`:"not installed";if(q.installed)J.push(q.agent);return{label:V[q.agent],value:q.agent,hint:B}}),Y=process.stdin.isTTY,K=await hQ({message:"Select agents to install skills for",choices:R,default:J,initial:!Y?W:void 0,required:!1}),x=K.filter((q)=>!J.includes(q)),b=K.filter((q)=>{let B=$.agents.find((O)=>O.agent===q);return B?.installed===!0&&B.version!==Q.version}),M=J.filter((q)=>!K.includes(q)),U=[...x,...b];if(U.length>0)try{let q=await P({message:"Installing skills...",task:async()=>k({command:X,meta:Q,agents:U,scope:H})});console.log(`
8
- ${N(`Installed "${Q.name}" v${Q.version}`)}`);for(let B of q.agents)console.log(T(` ${V[B.agent]} \u2192 ${B.outputDir}`))}catch(q){if(q instanceof j)if(await vQ({message:`"${q.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1,initial:!Y?!1:void 0})){let O=await P({message:"Overwriting skill...",task:async()=>k({command:X,meta:Q,agents:[q.details.agent],scope:H,force:!0})});console.log(`
9
- ${N(`Installed "${Q.name}" v${Q.version}`)}`);for(let I of O.agents)console.log(T(` ${V[I.agent]} \u2192 ${I.outputDir}`))}else console.log(T(`
10
- Skipped ${V[q.details.agent]}`));else throw q}if(M.length>0){let B=(await P({message:"Removing skills...",task:async()=>A({name:Q.name,agents:M,scope:H})})).agents.filter((O)=>O.status==="removed").map((O)=>V[O.agent]);if(B.length>0)console.log(`
11
- ${N(`Removed from ${B.join(", ")}`)}`)}if(U.length===0&&M.length===0)console.log(T("No changes."))}})}export{A as uninstallSkill,L as skillStatus,pQ as skillPlugin,_ as resolveSkillName,e as isValidSkillName,k as generateSkill,S as detectInstalledAgents,j as SkillConflictError};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/skills",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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.9",
50
+ "@crustjs/core": "0.0.10",
51
51
  "bunup": "^0.16.29"
52
52
  },
53
53
  "peerDependencies": {
54
- "@crustjs/core": "0.0.9",
54
+ "@crustjs/core": "0.0.10",
55
55
  "typescript": "^5"
56
56
  }
57
57
  }