@crustjs/skills 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/dist/index.d.ts +43 -7
- package/dist/index.js +84 -134
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,10 +149,10 @@ for (const file of files) {
|
|
|
149
149
|
|
|
150
150
|
## Output Structure
|
|
151
151
|
|
|
152
|
-
Generated output goes to `<outDir>/skills
|
|
152
|
+
Generated output goes to `<outDir>/skills/use-<name>/` (the `use-` prefix is applied automatically):
|
|
153
153
|
|
|
154
154
|
```
|
|
155
|
-
skills/my-cli/
|
|
155
|
+
skills/use-my-cli/
|
|
156
156
|
SKILL.md # Entrypoint — loaded by the agent
|
|
157
157
|
command-index.md # Maps all commands to documentation file paths
|
|
158
158
|
commands/ # Per-command documentation mirroring the CLI hierarchy
|
|
@@ -162,7 +162,6 @@ skills/my-cli/
|
|
|
162
162
|
migrate.md # Nested subcommand
|
|
163
163
|
seed.md
|
|
164
164
|
manifest.json # Machine-readable bundle metadata
|
|
165
|
-
README.md # Install instructions for consumers
|
|
166
165
|
```
|
|
167
166
|
|
|
168
167
|
### File Details
|
|
@@ -173,7 +172,6 @@ skills/my-cli/
|
|
|
173
172
|
| `command-index.md` | Markdown table listing every command, its type (runnable/group), and documentation path. |
|
|
174
173
|
| `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
|
|
175
174
|
| `manifest.json` | JSON metadata: name, description, version, entrypoint, and list of all command paths. |
|
|
176
|
-
| `README.md` | Human-readable install instructions for OpenCode and Claude Code. |
|
|
177
175
|
|
|
178
176
|
## Installing Generated Skills
|
|
179
177
|
|
|
@@ -182,13 +180,13 @@ After generating a skill bundle, consumers can install it by copying the skill d
|
|
|
182
180
|
### OpenCode
|
|
183
181
|
|
|
184
182
|
```sh
|
|
185
|
-
cp -r skills/my-cli/ .opencode/skills/my-cli/
|
|
183
|
+
cp -r skills/use-my-cli/ .opencode/skills/use-my-cli/
|
|
186
184
|
```
|
|
187
185
|
|
|
188
186
|
### Claude Code
|
|
189
187
|
|
|
190
188
|
```sh
|
|
191
|
-
cp -r skills/my-cli/ .claude/skills/my-cli/
|
|
189
|
+
cp -r skills/use-my-cli/ .claude/skills/use-my-cli/
|
|
192
190
|
```
|
|
193
191
|
|
|
194
192
|
The agent will discover the skill from `SKILL.md` and load command documentation on demand from the `commands/` directory.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,10 +12,18 @@ import { AnyCommand } from "@crustjs/core";
|
|
|
12
12
|
* description: "CLI tool for managing widgets",
|
|
13
13
|
* version: "1.0.0",
|
|
14
14
|
* };
|
|
15
|
+
* // generateSkill() will output to `use-my-cli/` with name "use-my-cli"
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
interface SkillMeta {
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Skill name — the user-facing CLI name (e.g. `"my-cli"`).
|
|
21
|
+
*
|
|
22
|
+
* `generateSkill()`, `uninstallSkill()`, and `skillStatus()` automatically
|
|
23
|
+
* prefix `use-` to this name for output directory paths, SKILL.md frontmatter,
|
|
24
|
+
* and manifest.json metadata. For example, `name: "my-cli"` produces output
|
|
25
|
+
* under `use-my-cli/`.
|
|
26
|
+
*/
|
|
19
27
|
name: string;
|
|
20
28
|
/** Human-readable description of what the CLI does */
|
|
21
29
|
description: string;
|
|
@@ -29,6 +37,10 @@ type Scope = "global" | "project";
|
|
|
29
37
|
/**
|
|
30
38
|
* Top-level options for generating a skill bundle from a command tree.
|
|
31
39
|
*
|
|
40
|
+
* The `meta.name` value is automatically prefixed with `use-` for all output
|
|
41
|
+
* paths and metadata. For example, `name: "my-cli"` produces skill directories
|
|
42
|
+
* named `use-my-cli/` and sets the manifest/frontmatter name to `"use-my-cli"`.
|
|
43
|
+
*
|
|
32
44
|
* @example
|
|
33
45
|
* ```ts
|
|
34
46
|
* import { generateSkill } from "@crustjs/skills";
|
|
@@ -37,7 +49,7 @@ type Scope = "global" | "project";
|
|
|
37
49
|
* await generateSkill({
|
|
38
50
|
* command: rootCommand,
|
|
39
51
|
* meta: {
|
|
40
|
-
* name: "my-cli",
|
|
52
|
+
* name: "my-cli", // output: use-my-cli/
|
|
41
53
|
* description: "CLI tool for managing widgets",
|
|
42
54
|
* version: "1.0.0",
|
|
43
55
|
* },
|
|
@@ -146,7 +158,7 @@ interface StatusResult {
|
|
|
146
158
|
* are not yet present.
|
|
147
159
|
*
|
|
148
160
|
* **Interactive command**: set `command: true` to register a `skill` subcommand
|
|
149
|
-
*
|
|
161
|
+
* that presents a single multiselect prompt for toggling agent installations.
|
|
150
162
|
*/
|
|
151
163
|
interface SkillPluginOptions {
|
|
152
164
|
/** Skill version string — compared against the installed manifest */
|
|
@@ -170,6 +182,13 @@ interface SkillPluginOptions {
|
|
|
170
182
|
autoUpdate?: boolean;
|
|
171
183
|
/**
|
|
172
184
|
* Register an interactive skill management subcommand on the root command.
|
|
185
|
+
*
|
|
186
|
+
* The command presents a single multiselect prompt listing all detected
|
|
187
|
+
* agents with their current installation status pre-filled. The user
|
|
188
|
+
* toggles agents on/off and the system reconciles the desired state:
|
|
189
|
+
* newly selected agents are installed, deselected agents are uninstalled,
|
|
190
|
+
* and already-correct agents are skipped.
|
|
191
|
+
*
|
|
173
192
|
* - `true`: register with default name `"skill"`
|
|
174
193
|
* - `string`: register with a custom command name
|
|
175
194
|
* @default false
|
|
@@ -202,6 +221,22 @@ interface SkillPluginOptions {
|
|
|
202
221
|
*/
|
|
203
222
|
declare function detectInstalledAgents(home?: string): Promise<AgentTarget[]>;
|
|
204
223
|
/**
|
|
224
|
+
* Resolves the canonical skill name by applying the `use-` prefix.
|
|
225
|
+
*
|
|
226
|
+
* All generated output (directory names, manifest metadata, SKILL.md content)
|
|
227
|
+
* uses the resolved name. Consumers pass the raw CLI name (e.g. `"my-cli"`),
|
|
228
|
+
* and this function returns the prefixed form (e.g. `"use-my-cli"`).
|
|
229
|
+
*
|
|
230
|
+
* @param name - The raw CLI tool name
|
|
231
|
+
* @returns The prefixed skill name
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* resolveSkillName("my-cli"); // "use-my-cli"
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
declare function resolveSkillName(name: string): string;
|
|
239
|
+
/**
|
|
205
240
|
* Generates and installs agent skill bundles from a Crust command tree.
|
|
206
241
|
*
|
|
207
242
|
* For each target agent:
|
|
@@ -265,9 +300,10 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
265
300
|
* are not yet present.
|
|
266
301
|
*
|
|
267
302
|
* **Interactive command**: set `command: true` to register a `skill` subcommand
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
303
|
+
* that presents a single multiselect prompt for toggling agent installations.
|
|
304
|
+
* Detected agents are shown with their current installation status pre-filled.
|
|
305
|
+
* The system reconciles the desired state: newly selected agents are installed,
|
|
306
|
+
* deselected agents are uninstalled, and already-correct agents are skipped.
|
|
271
307
|
*
|
|
272
308
|
* @param options - Plugin configuration with version and scope
|
|
273
309
|
* @returns A `CrustPlugin` to register in a command's `plugins` array
|
|
@@ -291,4 +327,4 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
291
327
|
* ```
|
|
292
328
|
*/
|
|
293
329
|
declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
|
|
294
|
-
export { uninstallSkill, skillStatus, skillPlugin, generateSkill, detectInstalledAgents, UninstallStatus, UninstallResult, UninstallOptions, StatusResult, StatusOptions, SkillPluginOptions, SkillMeta, Scope, InstallStatus, GenerateResult, GenerateOptions, AgentTarget, AgentResult };
|
|
330
|
+
export { uninstallSkill, skillStatus, skillPlugin, resolveSkillName, generateSkill, detectInstalledAgents, UninstallStatus, UninstallResult, UninstallOptions, StatusResult, StatusOptions, SkillPluginOptions, SkillMeta, Scope, InstallStatus, GenerateResult, GenerateOptions, AgentTarget, AgentResult };
|
package/dist/index.js
CHANGED
|
@@ -474,16 +474,23 @@ async function checkVersion(dir, newVersion) {
|
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
// src/generate.ts
|
|
477
|
+
function resolveSkillName(name) {
|
|
478
|
+
return name.startsWith("use-") ? name : `use-${name}`;
|
|
479
|
+
}
|
|
477
480
|
async function generateSkill(options) {
|
|
478
481
|
const { command, meta, agents, scope = "global", clean = true } = options;
|
|
482
|
+
const resolvedMeta = {
|
|
483
|
+
...meta,
|
|
484
|
+
name: resolveSkillName(meta.name)
|
|
485
|
+
};
|
|
479
486
|
const manifest = buildManifest(command);
|
|
480
|
-
const renderedFiles = renderSkill(manifest,
|
|
481
|
-
const metadataFiles = renderDistributionMetadata(manifest,
|
|
487
|
+
const renderedFiles = renderSkill(manifest, resolvedMeta);
|
|
488
|
+
const metadataFiles = renderDistributionMetadata(manifest, resolvedMeta);
|
|
482
489
|
const allFiles = [...renderedFiles, ...metadataFiles].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
483
490
|
const results = [];
|
|
484
491
|
for (const agent of agents) {
|
|
485
|
-
const outputDir = resolveAgentPath(agent, scope,
|
|
486
|
-
const { status, installedVersion } = await checkVersion(outputDir,
|
|
492
|
+
const outputDir = resolveAgentPath(agent, scope, resolvedMeta.name);
|
|
493
|
+
const { status, installedVersion } = await checkVersion(outputDir, resolvedMeta.version);
|
|
487
494
|
if (status === "up-to-date") {
|
|
488
495
|
results.push({
|
|
489
496
|
agent,
|
|
@@ -510,9 +517,10 @@ async function generateSkill(options) {
|
|
|
510
517
|
}
|
|
511
518
|
async function uninstallSkill(options) {
|
|
512
519
|
const { name, agents, scope = "global" } = options;
|
|
520
|
+
const resolvedName = resolveSkillName(name);
|
|
513
521
|
const results = [];
|
|
514
522
|
for (const agent of agents) {
|
|
515
|
-
const outputDir = resolveAgentPath(agent, scope,
|
|
523
|
+
const outputDir = resolveAgentPath(agent, scope, resolvedName);
|
|
516
524
|
const exists = await access2(outputDir).then(() => true).catch(() => false);
|
|
517
525
|
if (exists) {
|
|
518
526
|
await rm(outputDir, { recursive: true, force: true });
|
|
@@ -525,9 +533,10 @@ async function uninstallSkill(options) {
|
|
|
525
533
|
}
|
|
526
534
|
async function skillStatus(options) {
|
|
527
535
|
const { name, agents, scope = "global" } = options;
|
|
536
|
+
const resolvedName = resolveSkillName(name);
|
|
528
537
|
const results = [];
|
|
529
538
|
for (const agent of agents) {
|
|
530
|
-
const outputDir = resolveAgentPath(agent, scope,
|
|
539
|
+
const outputDir = resolveAgentPath(agent, scope, resolvedName);
|
|
531
540
|
const version = await readInstalledVersion(outputDir);
|
|
532
541
|
results.push({
|
|
533
542
|
agent,
|
|
@@ -586,7 +595,7 @@ async function writeFiles(baseDir, files) {
|
|
|
586
595
|
}
|
|
587
596
|
// src/plugin.ts
|
|
588
597
|
import { defineCommand } from "@crustjs/core";
|
|
589
|
-
import {
|
|
598
|
+
import { multiselect, spinner } from "@crustjs/prompts";
|
|
590
599
|
function deriveSkillMeta(command, version) {
|
|
591
600
|
return {
|
|
592
601
|
name: command.meta.name,
|
|
@@ -652,147 +661,88 @@ function buildSkillCommand(rootCmd, options) {
|
|
|
652
661
|
return defineCommand({
|
|
653
662
|
meta: {
|
|
654
663
|
name: "skill",
|
|
655
|
-
description: "Manage agent
|
|
664
|
+
description: "Manage agent skill installations"
|
|
656
665
|
},
|
|
657
|
-
|
|
658
|
-
action: {
|
|
659
|
-
type: "string",
|
|
660
|
-
description: 'Action to perform: "install", "uninstall", or "status" (skips prompt)'
|
|
661
|
-
},
|
|
662
|
-
scope: {
|
|
663
|
-
type: "string",
|
|
664
|
-
description: 'Installation scope: "global" or "project" (skips prompt)'
|
|
665
|
-
},
|
|
666
|
-
force: {
|
|
667
|
-
type: "boolean",
|
|
668
|
-
description: "Skip confirmation prompts"
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
async run({ flags }) {
|
|
666
|
+
async run() {
|
|
672
667
|
const meta = deriveSkillMeta(rootCmd, options.version);
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
const installedAgents = await detectInstalledAgents();
|
|
677
|
-
if (installedAgents.length === 0) {
|
|
668
|
+
const scope = options.scope ?? "global";
|
|
669
|
+
const detectedAgents = await detectInstalledAgents();
|
|
670
|
+
if (detectedAgents.length === 0) {
|
|
678
671
|
console.log("No supported agents detected. Install Claude Code or OpenCode first.");
|
|
679
672
|
return;
|
|
680
673
|
}
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
{ label: "Uninstall skills", value: "uninstall" },
|
|
686
|
-
{ label: "Check status", value: "status" }
|
|
687
|
-
],
|
|
688
|
-
initial: actionFlag
|
|
674
|
+
const status = await skillStatus({
|
|
675
|
+
name: meta.name,
|
|
676
|
+
agents: detectedAgents,
|
|
677
|
+
scope
|
|
689
678
|
});
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
})
|
|
727
|
-
});
|
|
728
|
-
console.log(`
|
|
679
|
+
const installedAgents = [];
|
|
680
|
+
const choices = status.agents.map((entry) => {
|
|
681
|
+
const hint = entry.installed ? `v${entry.version} installed` : "not installed";
|
|
682
|
+
if (entry.installed) {
|
|
683
|
+
installedAgents.push(entry.agent);
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
label: AGENT_LABELS[entry.agent],
|
|
687
|
+
value: entry.agent,
|
|
688
|
+
hint
|
|
689
|
+
};
|
|
690
|
+
});
|
|
691
|
+
const selected = await multiselect({
|
|
692
|
+
message: "Select agents to install skills for",
|
|
693
|
+
choices,
|
|
694
|
+
default: installedAgents,
|
|
695
|
+
required: false
|
|
696
|
+
});
|
|
697
|
+
const toInstall = selected.filter((agent) => !installedAgents.includes(agent));
|
|
698
|
+
const toUpdate = selected.filter((agent) => {
|
|
699
|
+
const entry = status.agents.find((a) => a.agent === agent);
|
|
700
|
+
return entry?.installed === true && entry.version !== meta.version;
|
|
701
|
+
});
|
|
702
|
+
const toUninstall = installedAgents.filter((agent) => !selected.includes(agent));
|
|
703
|
+
const agentsToGenerate = [...toInstall, ...toUpdate];
|
|
704
|
+
if (agentsToGenerate.length > 0) {
|
|
705
|
+
const result = await spinner({
|
|
706
|
+
message: "Installing skills...",
|
|
707
|
+
task: async () => generateSkill({
|
|
708
|
+
command: rootCmd,
|
|
709
|
+
meta,
|
|
710
|
+
agents: agentsToGenerate,
|
|
711
|
+
scope
|
|
712
|
+
})
|
|
713
|
+
});
|
|
714
|
+
console.log(`
|
|
729
715
|
Installed "${meta.name}" v${meta.version}`);
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
required: true
|
|
747
|
-
});
|
|
748
|
-
const agentNames = agents.map((a) => AGENT_LABELS[a]).join(", ");
|
|
749
|
-
const confirmed = await confirm({
|
|
750
|
-
message: `Remove "${meta.name}" skills from ${agentNames}?`,
|
|
751
|
-
initial: forceFlag ? true : undefined
|
|
752
|
-
});
|
|
753
|
-
if (!confirmed) {
|
|
754
|
-
console.log("Cancelled.");
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
const scope = scopeFlag ?? "global";
|
|
758
|
-
const result = await spinner({
|
|
759
|
-
message: "Removing skills...",
|
|
760
|
-
task: async () => uninstallSkill({
|
|
761
|
-
name: meta.name,
|
|
762
|
-
agents,
|
|
763
|
-
scope
|
|
764
|
-
})
|
|
765
|
-
});
|
|
766
|
-
const removed = result.agents.filter((a) => a.status === "removed").map((a) => AGENT_LABELS[a.agent]);
|
|
767
|
-
if (removed.length > 0) {
|
|
768
|
-
console.log(`
|
|
716
|
+
for (const r of result.agents) {
|
|
717
|
+
console.log(` ${AGENT_LABELS[r.agent]} \u2192 ${r.outputDir}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (toUninstall.length > 0) {
|
|
721
|
+
const result = await spinner({
|
|
722
|
+
message: "Removing skills...",
|
|
723
|
+
task: async () => uninstallSkill({
|
|
724
|
+
name: meta.name,
|
|
725
|
+
agents: toUninstall,
|
|
726
|
+
scope
|
|
727
|
+
})
|
|
728
|
+
});
|
|
729
|
+
const removed = result.agents.filter((a) => a.status === "removed").map((a) => AGENT_LABELS[a.agent]);
|
|
730
|
+
if (removed.length > 0) {
|
|
731
|
+
console.log(`
|
|
769
732
|
Removed from ${removed.join(", ")}`);
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const scope = scopeFlag ?? "global";
|
|
777
|
-
const result = await skillStatus({
|
|
778
|
-
name: meta.name,
|
|
779
|
-
agents: installedAgents,
|
|
780
|
-
scope
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (agentsToGenerate.length === 0 && toUninstall.length === 0) {
|
|
736
|
+
console.log("No changes.");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
781
739
|
});
|
|
782
|
-
console.log("");
|
|
783
|
-
console.log(`${"Agent".padEnd(14)}${"Scope".padEnd(10)}${"Version".padEnd(10)}Path`);
|
|
784
|
-
for (const entry of result.agents) {
|
|
785
|
-
const agentName = AGENT_LABELS[entry.agent];
|
|
786
|
-
const version = entry.installed ? entry.version ?? "\u2014" : "(not installed)";
|
|
787
|
-
const path = entry.installed ? entry.outputDir : "";
|
|
788
|
-
console.log(`${agentName.padEnd(14)}${scope.padEnd(10)}${version.padEnd(10)}${path}`);
|
|
789
|
-
}
|
|
790
|
-
console.log("");
|
|
791
740
|
}
|
|
792
741
|
export {
|
|
793
742
|
uninstallSkill,
|
|
794
743
|
skillStatus,
|
|
795
744
|
skillPlugin,
|
|
745
|
+
resolveSkillName,
|
|
796
746
|
generateSkill,
|
|
797
747
|
detectInstalledAgents
|
|
798
748
|
};
|