@crouton-kit/crouter 0.1.9 → 0.2.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/dist/builtin-skills/.crouter-plugin/plugin.json +5 -0
- package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +157 -0
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +156 -0
- package/dist/commands/doctor.js +54 -2
- package/dist/commands/plugin.js +4 -1
- package/dist/commands/skill.js +68 -13
- package/dist/core/frontmatter.d.ts +1 -1
- package/dist/core/frontmatter.js +5 -0
- package/dist/core/resolver.d.ts +2 -0
- package/dist/core/resolver.js +63 -2
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +13 -2
- package/dist/prompts/skill.js +291 -36
- package/dist/types.d.ts +6 -1
- package/dist/types.js +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crouter-development/marketplaces
|
|
3
|
+
type: playbook
|
|
4
|
+
description: How to author a crtr marketplace — marketplace.json index, plugin entries, symlink-based install, auto-bump CI, dual-publishing. Use when creating a marketplace or contributing plugins to one.
|
|
5
|
+
keywords: [marketplace, marketplace.json, plugin, index, publishing]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Authoring crtr marketplaces
|
|
9
|
+
|
|
10
|
+
A **marketplace** is a git repo that indexes multiple plugins. Users register it once, then `crtr marketplace install <mkt>:<plugin>` symlinks any plugin from it. One `crtr marketplace update` pulls updates for every installed plugin at once.
|
|
11
|
+
|
|
12
|
+
Audience: LLM agents creating a marketplace or adding plugins to an existing one.
|
|
13
|
+
|
|
14
|
+
## When you need a marketplace (vs a single-plugin repo)
|
|
15
|
+
|
|
16
|
+
A solo plugin ships from any git URL via `crtr plugin install <url>` — no marketplace needed.
|
|
17
|
+
|
|
18
|
+
Reach for a marketplace when:
|
|
19
|
+
- You want to publish ≥3 plugins under one brand or theme.
|
|
20
|
+
- Users should install selectively without cloning each plugin's repo.
|
|
21
|
+
- You want centralized version-bump automation across many plugins.
|
|
22
|
+
|
|
23
|
+
If you have one plugin, ship it standalone. Promote to a marketplace later.
|
|
24
|
+
|
|
25
|
+
## Directory layout
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
<marketplace-repo>/
|
|
29
|
+
├── .crouter-marketplace/
|
|
30
|
+
│ └── marketplace.json # marketplace manifest — required
|
|
31
|
+
└── plugins/
|
|
32
|
+
├── plugin-a/
|
|
33
|
+
│ ├── .crouter-plugin/plugin.json
|
|
34
|
+
│ └── skills/...
|
|
35
|
+
└── plugin-b/
|
|
36
|
+
└── ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Each entry under `plugins/` is a complete plugin (see [[crouter-development/plugins]]). The marketplace adds an index on top.
|
|
40
|
+
|
|
41
|
+
## The manifest
|
|
42
|
+
|
|
43
|
+
`.crouter-marketplace/marketplace.json`:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"name": "my-marketplace",
|
|
48
|
+
"version": "0.3.0",
|
|
49
|
+
"owner": { "name": "Your Name", "email": "you@example.com" },
|
|
50
|
+
"plugins": [
|
|
51
|
+
{
|
|
52
|
+
"name": "plugin-a",
|
|
53
|
+
"version": "0.2.1",
|
|
54
|
+
"source": "https://github.com/<owner>/<repo>",
|
|
55
|
+
"description": "One sentence."
|
|
56
|
+
},
|
|
57
|
+
{ "name": "plugin-b", "version": "0.1.0", "source": "…", "description": "…" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Field | Required | Notes |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `name` | yes | Lowercase kebab. |
|
|
65
|
+
| `version` | yes | Semver. Bumps when any plugin bumps or when the marketplace manifest itself changes. |
|
|
66
|
+
| `owner` | optional | |
|
|
67
|
+
| `plugins` | yes | Array. Each entry: `name`, `version`, `source`, `description`. |
|
|
68
|
+
|
|
69
|
+
The `plugins[]` array IS the index — what's installable. A plugin on disk but not in the index won't resolve. A plugin in the index but missing from disk errors on install.
|
|
70
|
+
|
|
71
|
+
## Install mechanics — symlinks, not clones
|
|
72
|
+
|
|
73
|
+
When a user runs `crtr marketplace install my-marketplace:plugin-a`:
|
|
74
|
+
1. Crouter looks up `plugin-a` in the marketplace's manifest.
|
|
75
|
+
2. **Symlinks** `<marketplace-clone>/plugins/plugin-a/` → `<user-scope>/plugins/plugin-a/`.
|
|
76
|
+
3. Records the install in the scope config with `source_marketplace: my-marketplace`.
|
|
77
|
+
|
|
78
|
+
Therefore one `crtr marketplace update my-marketplace` (which does `git pull` in the marketplace clone) updates every installed plugin from it — no per-plugin re-install, no second clone.
|
|
79
|
+
|
|
80
|
+
## Version-bump automation (recommended)
|
|
81
|
+
|
|
82
|
+
Bumping N plugin manifests + the index by hand is error-prone. The canonical pattern is a GitHub Actions workflow that bumps versions from commit subjects (conventional commits):
|
|
83
|
+
|
|
84
|
+
| Commit subject | Bump |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `feat!: …` or `BREAKING CHANGE` in body | major |
|
|
87
|
+
| `feat: …` | minor |
|
|
88
|
+
| anything else (`fix:`, `chore:`, `docs:`, …) | patch |
|
|
89
|
+
|
|
90
|
+
Per commit:
|
|
91
|
+
- For each plugin folder touched, both `plugins/<name>/.crouter-plugin/plugin.json` and the matching entry in `marketplace.json` get bumped.
|
|
92
|
+
- Top-level `marketplace.json` version bumps when any plugin bumps OR the manifest itself was edited directly.
|
|
93
|
+
- Newly-added plugins are skipped — they keep the version you committed.
|
|
94
|
+
- Commits whose subject starts with `chore: release` are skipped to avoid loops.
|
|
95
|
+
|
|
96
|
+
If you adopt this: **never edit `version` fields by hand**. CI will overwrite. Document this in the marketplace's CLAUDE.md.
|
|
97
|
+
|
|
98
|
+
The crouter-official-marketplace repo's `.github/workflows/auto-bump.yml` is the reference implementation. Copy it.
|
|
99
|
+
|
|
100
|
+
## Adding a plugin to a marketplace
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cd <marketplace-repo>
|
|
104
|
+
|
|
105
|
+
# Create the plugin (see [[crouter-development/plugins]] for plugin layout)
|
|
106
|
+
mkdir -p plugins/my-new-plugin/.crouter-plugin plugins/my-new-plugin/skills
|
|
107
|
+
$EDITOR plugins/my-new-plugin/.crouter-plugin/plugin.json
|
|
108
|
+
|
|
109
|
+
# Add at least one skill
|
|
110
|
+
crtr skill new my-new-plugin:first-skill --type playbook --description "Use when …"
|
|
111
|
+
|
|
112
|
+
# Add the plugin to the marketplace index
|
|
113
|
+
$EDITOR .crouter-marketplace/marketplace.json
|
|
114
|
+
# (append to plugins[] with name, initial version, source, description)
|
|
115
|
+
|
|
116
|
+
# Validate
|
|
117
|
+
crtr doctor
|
|
118
|
+
|
|
119
|
+
# Commit — CI bumps versions if you've wired up auto-bump
|
|
120
|
+
git add -A
|
|
121
|
+
git commit -m "feat: add my-new-plugin"
|
|
122
|
+
git push
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Existing users pick it up on their next `crtr marketplace update <marketplace-name>` (or on the next auto-update tick if they've set `auto_update.content: "apply"`).
|
|
126
|
+
|
|
127
|
+
## Updating an existing plugin
|
|
128
|
+
|
|
129
|
+
Edit content under `plugins/<name>/`. Commit with a conventional-commit subject. CI bumps the plugin manifest and the marketplace index entry together. No manual sync.
|
|
130
|
+
|
|
131
|
+
## Removing a plugin
|
|
132
|
+
|
|
133
|
+
Delete the plugin's directory AND its entry in `marketplace.json` → `plugins[]`. Commit with `feat!: remove <plugin>` to signal a major bump (the marketplace's contract changed for anyone depending on that plugin).
|
|
134
|
+
|
|
135
|
+
## Marketplace registration scopes
|
|
136
|
+
|
|
137
|
+
A marketplace itself registers per-scope:
|
|
138
|
+
|
|
139
|
+
| Scope | Path | Use case |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| user | `~/.crouter/marketplaces/<name>/` | Private to your machine — your personal subscriptions |
|
|
142
|
+
| project | `<project>/.crouter/marketplaces/<name>/` | Checked into the repo — anyone cloning gets the same marketplace pinned |
|
|
143
|
+
|
|
144
|
+
`crtr marketplace add <git-url> --scope user` is the default. Use `--scope project` when the marketplace is integral to how the repo expects to be developed.
|
|
145
|
+
|
|
146
|
+
## Validation
|
|
147
|
+
|
|
148
|
+
`crtr doctor` checks marketplaces:
|
|
149
|
+
- `marketplace.json` is valid JSON.
|
|
150
|
+
- Every entry in `plugins[]` corresponds to a real directory under `plugins/`.
|
|
151
|
+
- Each plugin under `plugins/` passes plugin-level validation.
|
|
152
|
+
|
|
153
|
+
A plugin on disk but missing from the manifest is a **warning** (probably forgotten); a plugin in the manifest but missing on disk is an **error** (install would break).
|
|
154
|
+
|
|
155
|
+
## Cross-publishing with Claude Code
|
|
156
|
+
|
|
157
|
+
Some marketplaces ship a parallel `.claude-plugin/` tree so plugins can load directly into Claude Code without crtr. Optional. Sync the two manifests if you adopt it.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crouter-development/plugins
|
|
3
|
+
type: playbook
|
|
4
|
+
description: How to author a crtr plugin — plugin.json manifest, directory layout, scopes, install mechanics, versioning. Use when creating a new plugin, packaging skills for distribution, or debugging install/resolution.
|
|
5
|
+
keywords: [plugin, plugin.json, manifest, install, scope]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Authoring crtr plugins
|
|
9
|
+
|
|
10
|
+
A **plugin** is a directory shipping skills (and, in future, other artifact types like commands and hooks). Plugins are how you package skills for sharing across machines, projects, and people.
|
|
11
|
+
|
|
12
|
+
Audience: LLM agents creating or maintaining a crtr plugin.
|
|
13
|
+
|
|
14
|
+
## When you need a plugin (vs scope-owned skills)
|
|
15
|
+
|
|
16
|
+
Scope-owned skills live at `~/.crouter/skills/` (user) or `<project>/.crouter/skills/` (project). They're personal and per-machine/per-repo.
|
|
17
|
+
|
|
18
|
+
Reach for a **plugin** when:
|
|
19
|
+
- You want to share skills across multiple projects or with other people.
|
|
20
|
+
- You want versioning + update mechanics (`crtr plugin update`).
|
|
21
|
+
- You want a marketplace to index the work — see [[crouter-development/marketplaces]].
|
|
22
|
+
|
|
23
|
+
If it's a one-off note for yourself, scope-owned skills are simpler. Promote to a plugin later.
|
|
24
|
+
|
|
25
|
+
## Directory layout
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
<plugin-name>/
|
|
29
|
+
├── .crouter-plugin/
|
|
30
|
+
│ └── plugin.json # manifest — required
|
|
31
|
+
└── skills/
|
|
32
|
+
└── <skill-name>/
|
|
33
|
+
└── SKILL.md
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Future artifact types (`commands/`, `hooks/`, etc.) will be sibling dirs to `skills/`.
|
|
37
|
+
|
|
38
|
+
## The manifest
|
|
39
|
+
|
|
40
|
+
`.crouter-plugin/plugin.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"name": "my-plugin",
|
|
45
|
+
"version": "0.1.0",
|
|
46
|
+
"description": "One sentence — shown in `crtr plugin list`.",
|
|
47
|
+
"source": "https://github.com/<owner>/<repo>",
|
|
48
|
+
"owner": {
|
|
49
|
+
"name": "Your Name",
|
|
50
|
+
"email": "you@example.com"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Field | Required | Notes |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `name` | yes | Must match the directory name. Lowercase kebab. |
|
|
58
|
+
| `version` | yes | Semver. Marketplace CI may bump automatically — see marketplaces skill. |
|
|
59
|
+
| `description` | yes | One sentence. |
|
|
60
|
+
| `source` | recommended | Git URL where the plugin lives. Used by `crtr plugin update`. |
|
|
61
|
+
| `owner` | optional | Author info. |
|
|
62
|
+
|
|
63
|
+
## Scopes
|
|
64
|
+
|
|
65
|
+
A plugin can live in either scope:
|
|
66
|
+
|
|
67
|
+
| Scope | Path | Use case |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| user | `~/.crouter/plugins/<name>/` | Personal, available in all your projects |
|
|
70
|
+
| project | `<project>/.crouter/plugins/<name>/` | Pinned to a specific repo — checked in or vendored |
|
|
71
|
+
|
|
72
|
+
Project-scope plugins outrank user-scope on resolution. Both outrank marketplace-installed plugins. The builtin `crtr` plugin (ships with the CLI) sits at the bottom.
|
|
73
|
+
|
|
74
|
+
## Install mechanics
|
|
75
|
+
|
|
76
|
+
Three ways a plugin lands in a scope:
|
|
77
|
+
|
|
78
|
+
1. **From a git URL** (`crtr plugin install <url> --scope user`):
|
|
79
|
+
- Clones into `<scope>/plugins/<name>/` using the manifest's name.
|
|
80
|
+
- `crtr plugin update <name>` does `git pull`.
|
|
81
|
+
- Independent of any marketplace.
|
|
82
|
+
|
|
83
|
+
2. **From a marketplace** (`crtr marketplace install <mkt>:<name> --scope user`):
|
|
84
|
+
- **Symlinks** the marketplace's `plugins/<name>/` into `<scope>/plugins/<name>/`.
|
|
85
|
+
- One `crtr marketplace update <mkt>` pulls updates for every installed plugin from that marketplace.
|
|
86
|
+
- See [[crouter-development/marketplaces]].
|
|
87
|
+
|
|
88
|
+
3. **Authored in place** (you're writing the plugin in a working repo):
|
|
89
|
+
- Symlink for tight dev loop: `ln -s $(pwd) ~/.crouter/plugins/<name>`.
|
|
90
|
+
- Or `crtr plugin install file://$(pwd) --scope project` to clone-install.
|
|
91
|
+
|
|
92
|
+
## Local development loop
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Scaffold dir + manifest + first skill
|
|
96
|
+
mkdir -p my-plugin/.crouter-plugin my-plugin/skills
|
|
97
|
+
$EDITOR my-plugin/.crouter-plugin/plugin.json # write the manifest
|
|
98
|
+
cd my-plugin
|
|
99
|
+
crtr skill new my-plugin:my-first-skill --type playbook --description "Use when …"
|
|
100
|
+
|
|
101
|
+
# Symlink for fast iteration — no clone, edits land immediately
|
|
102
|
+
ln -s $(pwd) ~/.crouter/plugins/my-plugin
|
|
103
|
+
|
|
104
|
+
# Verify
|
|
105
|
+
crtr plugin list # my-plugin appears
|
|
106
|
+
crtr plugin show my-plugin # lists its skills
|
|
107
|
+
crtr skill list --plugin my-plugin
|
|
108
|
+
crtr doctor # validates manifest + every skill
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
When ready to share: push to a git remote; anyone can `crtr plugin install <url>`.
|
|
112
|
+
|
|
113
|
+
## Versioning
|
|
114
|
+
|
|
115
|
+
Standard semver:
|
|
116
|
+
|
|
117
|
+
| Change | Bump |
|
|
118
|
+
|---|---|
|
|
119
|
+
| Typo, wording polish | patch (0.1.0 → 0.1.1) |
|
|
120
|
+
| New skill, new section, new example | minor (0.1.0 → 0.2.0) |
|
|
121
|
+
| Removed skill, renamed skill, changed manifest schema | major (0.1.0 → 1.0.0) |
|
|
122
|
+
|
|
123
|
+
`crtr plugin update <name>` reads the new version after pulling and updates the local config. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[crouter-development/marketplaces]].
|
|
124
|
+
|
|
125
|
+
## Enable/disable
|
|
126
|
+
|
|
127
|
+
`crtr plugin disable <name>` flips the per-scope config without removing files. Disabled plugins are hidden from `crtr skill list` and don't resolve via `crtr skill show <name>`. Re-enable with `crtr plugin enable <name>`.
|
|
128
|
+
|
|
129
|
+
Individual skills inside an enabled plugin can also be disabled: `crtr skill disable <plugin>:<skill>`.
|
|
130
|
+
|
|
131
|
+
## What goes in a plugin
|
|
132
|
+
|
|
133
|
+
Good plugin scope:
|
|
134
|
+
- A coherent set of related skills (3–15 typical) sharing a theme.
|
|
135
|
+
- All skills serve the same user persona or workflow.
|
|
136
|
+
- Versioned together — a bump means a bump for the whole set.
|
|
137
|
+
|
|
138
|
+
Bad plugin scope:
|
|
139
|
+
- One mega-plugin with every skill you've ever written. Hard to install selectively, hard to version.
|
|
140
|
+
- A plugin per single skill. No value-add over scope-owned skills.
|
|
141
|
+
|
|
142
|
+
## Cross-plugin etiquette
|
|
143
|
+
|
|
144
|
+
If your skill conceptually depends on another plugin's skill, link via `## Related` with `` `<plugin>/<skill>` ``. Don't fork content; link it.
|
|
145
|
+
|
|
146
|
+
## Validation
|
|
147
|
+
|
|
148
|
+
`crtr doctor` checks every plugin:
|
|
149
|
+
- Manifest exists and is valid JSON.
|
|
150
|
+
- Manifest `name` matches the directory name.
|
|
151
|
+
- Every skill under `skills/` passes the skill-validation contract (frontmatter parses, `name` matches dir path, `type` in enum). Run `crtr skill` (no args) for the full format reference.
|
|
152
|
+
- Sibling artifact dirs (`commands/`, `hooks/`, etc.) — validated by their respective specs as those land.
|
|
153
|
+
|
|
154
|
+
## Cross-publishing with Claude Code
|
|
155
|
+
|
|
156
|
+
Some plugins also publish a `.claude-plugin/` manifest alongside `.crouter-plugin/` so they can be loaded directly into Claude Code without going through crtr. Optional. Only worth doing when your skills/commands meaningfully stand alone in the Claude Code surface. Keep manifests in sync if you do.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { SKILL_TYPES, isSkillType } from '../types.js';
|
|
2
3
|
import { out, jsonOut, handleError, stdoutColor } from '../core/output.js';
|
|
3
|
-
import { scopeRoot, pluginsDir, marketplacesDir, listScopes } from '../core/scope.js';
|
|
4
|
+
import { scopeRoot, pluginsDir, marketplacesDir, listScopes, builtinSkillsRoot } from '../core/scope.js';
|
|
4
5
|
import { readConfig, updateConfig } from '../core/config.js';
|
|
5
6
|
import { listInstalledPlugins, listSkillsInPlugin } from '../core/resolver.js';
|
|
6
|
-
import { pathExists, listDirs, removePath } from '../core/fs-utils.js';
|
|
7
|
+
import { pathExists, listDirs, removePath, readText } from '../core/fs-utils.js';
|
|
7
8
|
import { readPluginManifest, readMarketplaceManifest } from '../core/manifest.js';
|
|
9
|
+
import { parseFrontmatter } from '../core/frontmatter.js';
|
|
8
10
|
import { lsRemote } from '../core/git.js';
|
|
9
11
|
import { ExitCode } from '../types.js';
|
|
10
12
|
function pass(scope, name, message) {
|
|
@@ -16,7 +18,46 @@ function fail(scope, name, message) {
|
|
|
16
18
|
function warn(scope, name, message) {
|
|
17
19
|
return { scope, name, status: 'warn', message };
|
|
18
20
|
}
|
|
21
|
+
function readRawTypeField(skillPath) {
|
|
22
|
+
const content = readText(skillPath);
|
|
23
|
+
const { raw } = parseFrontmatter(content);
|
|
24
|
+
if (!raw)
|
|
25
|
+
return undefined;
|
|
26
|
+
const m = raw.match(/^type:\s*(.+?)\s*$/m);
|
|
27
|
+
if (!m)
|
|
28
|
+
return undefined;
|
|
29
|
+
let v = m[1].trim();
|
|
30
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
31
|
+
v = v.slice(1, -1);
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
34
|
+
}
|
|
35
|
+
function runChecksForBuiltin() {
|
|
36
|
+
const root = builtinSkillsRoot();
|
|
37
|
+
const plugins = listInstalledPlugins('builtin');
|
|
38
|
+
if (plugins.length === 0) {
|
|
39
|
+
return [fail('builtin', 'builtin:crtr:root', `builtin-skills root missing or has no valid plugin.json: ${root}`)];
|
|
40
|
+
}
|
|
41
|
+
const results = [
|
|
42
|
+
pass('builtin', 'builtin:crtr:root', `builtin-skills root present: ${root}`),
|
|
43
|
+
];
|
|
44
|
+
for (const plugin of plugins) {
|
|
45
|
+
results.push(pass('builtin', `builtin:${plugin.name}:manifest`, `manifest valid`));
|
|
46
|
+
const skills = listSkillsInPlugin(plugin);
|
|
47
|
+
for (const skill of skills) {
|
|
48
|
+
if (!skill.frontmatter.name) {
|
|
49
|
+
results.push(fail('builtin', `builtin:${plugin.name}:skill:${skill.name}:frontmatter`, `frontmatter missing or name field empty`));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
results.push(pass('builtin', `builtin:${plugin.name}:skill:${skill.name}:frontmatter`, `frontmatter valid`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
19
58
|
function runChecksForScope(scope, opts) {
|
|
59
|
+
if (scope === 'builtin')
|
|
60
|
+
return runChecksForBuiltin();
|
|
20
61
|
const results = [];
|
|
21
62
|
const root = scopeRoot(scope);
|
|
22
63
|
if (!root)
|
|
@@ -138,6 +179,17 @@ function runChecksForScope(scope, opts) {
|
|
|
138
179
|
else {
|
|
139
180
|
results.push(pass(scope, `plugin:${name}:skill:${skill.name}:frontmatter`, `frontmatter valid`));
|
|
140
181
|
}
|
|
182
|
+
const typeCheckName = `plugin:${name}:skill:${skill.name}:type`;
|
|
183
|
+
const rawType = readRawTypeField(skill.path);
|
|
184
|
+
if (rawType === undefined) {
|
|
185
|
+
results.push(warn(scope, typeCheckName, `missing type field — add one of: ${SKILL_TYPES.join(' | ')}`));
|
|
186
|
+
}
|
|
187
|
+
else if (!isSkillType(rawType)) {
|
|
188
|
+
results.push(fail(scope, typeCheckName, `invalid type "${rawType}" — valid: ${SKILL_TYPES.join(' | ')}`));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
results.push(pass(scope, typeCheckName, `type: ${rawType}`));
|
|
192
|
+
}
|
|
141
193
|
}
|
|
142
194
|
}
|
|
143
195
|
// Git remote check (slow, opt-in)
|
package/dist/commands/plugin.js
CHANGED
|
@@ -178,10 +178,13 @@ export function registerPluginCommands(program) {
|
|
|
178
178
|
.option('--yes', 'skip confirmation in non-TTY mode')
|
|
179
179
|
.action(async (name, opts) => {
|
|
180
180
|
try {
|
|
181
|
+
if (name === 'crtr') {
|
|
182
|
+
throw usage(`cannot uninstall builtin plugin "crtr" — it ships with the binary`);
|
|
183
|
+
}
|
|
181
184
|
if (!isTTY() && !opts.yes) {
|
|
182
185
|
throw usage(`uninstall requires --yes in non-TTY mode: crtr plugin uninstall ${name} --yes`);
|
|
183
186
|
}
|
|
184
|
-
const scopes = listScopes(opts.scope);
|
|
187
|
+
const scopes = listScopes(opts.scope).filter((s) => s !== 'builtin');
|
|
185
188
|
let removed = false;
|
|
186
189
|
for (const scope of scopes) {
|
|
187
190
|
const pDir = pluginsDir(scope);
|
package/dist/commands/skill.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { writeFileSync } from 'node:fs';
|
|
3
|
-
import { SCOPE_SKILL_PLUGIN, SKILL_ENTRY_FILE, SKILLS_DIR, } from '../types.js';
|
|
3
|
+
import { SCOPE_SKILL_PLUGIN, SKILL_ENTRY_FILE, SKILLS_DIR, SKILL_TYPES, isSkillType, } from '../types.js';
|
|
4
4
|
import { skillConfigKey } from '../types.js';
|
|
5
5
|
import { notFound, usage, general } from '../core/errors.js';
|
|
6
6
|
import { out, hint, info, jsonOut, handleError, } from '../core/output.js';
|
|
7
7
|
import { listScopes, requireScopeRoot, resolveScopeArg, projectScopeRoot, scopeSkillsDir, } from '../core/scope.js';
|
|
8
|
-
import { resolveSkill, listAllSkills, listInstalledPlugins, findPluginByName, parseSkillQualifier, } from '../core/resolver.js';
|
|
8
|
+
import { resolveSkill, listAllSkills, listInstalledPlugins, findPluginByName, parseSkillQualifier, listSkillSiblings, listSkillChildren, } from '../core/resolver.js';
|
|
9
9
|
import { updateConfig, ensureScopeInitialized } from '../core/config.js';
|
|
10
10
|
import { parseFrontmatter, serializeFrontmatter } from '../core/frontmatter.js';
|
|
11
11
|
import { ensureDir, pathExists, readText, walkFiles } from '../core/fs-utils.js';
|
|
@@ -23,14 +23,53 @@ const KNOWN_VERBS = new Set([
|
|
|
23
23
|
'disable',
|
|
24
24
|
'search',
|
|
25
25
|
]);
|
|
26
|
-
const AUTHORING_GUIDE_SKILL = 'authoring-skills';
|
|
27
26
|
function buildShowFooter(skillPath) {
|
|
28
27
|
return (`crtr: edit this skill directly at ${skillPath} — ` +
|
|
29
|
-
`for SKILL.md authoring
|
|
28
|
+
`for SKILL.md format + authoring workflow run \`crtr skill\` (no args)`);
|
|
30
29
|
}
|
|
31
30
|
function wrapSkill(name, path, content) {
|
|
32
31
|
return `<skill name="${name}" path="${path}">\n${content.endsWith('\n') ? content : content + '\n'}</skill>`;
|
|
33
32
|
}
|
|
33
|
+
function formatNeighborQualifier(s) {
|
|
34
|
+
return s.plugin === SCOPE_SKILL_PLUGIN ? `${s.scope}:${s.name}` : `${s.plugin}/${s.name}`;
|
|
35
|
+
}
|
|
36
|
+
function buildNeighborsSection(skill) {
|
|
37
|
+
const siblings = listSkillSiblings(skill);
|
|
38
|
+
const children = listSkillChildren(skill);
|
|
39
|
+
if (siblings.length === 0 && children.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
const lines = [
|
|
42
|
+
'## Neighbors',
|
|
43
|
+
'*Auto-discovered from filesystem. Use `--no-neighbors` to suppress.*',
|
|
44
|
+
'',
|
|
45
|
+
];
|
|
46
|
+
if (siblings.length > 0) {
|
|
47
|
+
lines.push('**Siblings:**');
|
|
48
|
+
for (const s of siblings) {
|
|
49
|
+
const desc = s.frontmatter.description !== undefined ? s.frontmatter.description : '';
|
|
50
|
+
lines.push(`- \`${formatNeighborQualifier(s)}\`${desc ? ` — ${desc}` : ''}`);
|
|
51
|
+
}
|
|
52
|
+
if (children.length > 0)
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
if (children.length > 0) {
|
|
56
|
+
lines.push('**Nested:**');
|
|
57
|
+
for (const s of children) {
|
|
58
|
+
const desc = s.frontmatter.description !== undefined ? s.frontmatter.description : '';
|
|
59
|
+
lines.push(`- \`${formatNeighborQualifier(s)}\`${desc ? ` — ${desc}` : ''}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
function appendNeighbors(skill, body, suppress) {
|
|
65
|
+
if (suppress)
|
|
66
|
+
return body;
|
|
67
|
+
const section = buildNeighborsSection(skill);
|
|
68
|
+
if (section === null)
|
|
69
|
+
return body;
|
|
70
|
+
const sep = body.endsWith('\n') ? '\n' : '\n\n';
|
|
71
|
+
return body + sep + `<neighbors>\n${section}\n</neighbors>\n`;
|
|
72
|
+
}
|
|
34
73
|
const SKILL_IDENTIFIER_HELP = 'Skill identifier forms (accepted by show, path, where, enable, disable):\n' +
|
|
35
74
|
' <name> bare name — resolves scope-root first, then plugins\n' +
|
|
36
75
|
' <plugin>:<name> explicit plugin (canonical)\n' +
|
|
@@ -52,7 +91,8 @@ export function registerSkillCommands(program) {
|
|
|
52
91
|
try {
|
|
53
92
|
const skillObj = resolveSkill(nameOrVerb);
|
|
54
93
|
const content = readText(skillObj.path);
|
|
55
|
-
const
|
|
94
|
+
const rawBody = opts.frontmatter ? content : parseFrontmatter(content).body;
|
|
95
|
+
const body = appendNeighbors(skillObj, rawBody, false);
|
|
56
96
|
out(wrapSkill(skillObj.name, skillObj.path, body));
|
|
57
97
|
hint(buildShowFooter(skillObj.path));
|
|
58
98
|
}
|
|
@@ -117,6 +157,7 @@ export function registerSkillCommands(program) {
|
|
|
117
157
|
.option('--scope <scope>', 'user|project')
|
|
118
158
|
.option('--plugin <name>', 'filter by plugin name')
|
|
119
159
|
.option('--frontmatter', 'include YAML frontmatter in the printed body')
|
|
160
|
+
.option('--no-neighbors', 'suppress the auto-appended ## Neighbors section')
|
|
120
161
|
.option('--json', 'emit JSON')
|
|
121
162
|
.addHelpText('after', '\nExamples:\n' +
|
|
122
163
|
' crtr skill show rules # bare name\n' +
|
|
@@ -133,7 +174,9 @@ export function registerSkillCommands(program) {
|
|
|
133
174
|
}
|
|
134
175
|
const skillObj = resolveSkill(name, resolveOpts);
|
|
135
176
|
const content = readText(skillObj.path);
|
|
136
|
-
const
|
|
177
|
+
const rawBody = opts.frontmatter ? content : parseFrontmatter(content).body;
|
|
178
|
+
const suppressNeighbors = opts.neighbors === false;
|
|
179
|
+
const body = appendNeighbors(skillObj, rawBody, suppressNeighbors);
|
|
137
180
|
if (opts.json) {
|
|
138
181
|
jsonOut({
|
|
139
182
|
name: skillObj.name,
|
|
@@ -141,7 +184,7 @@ export function registerSkillCommands(program) {
|
|
|
141
184
|
scope: skillObj.scope,
|
|
142
185
|
path: skillObj.path,
|
|
143
186
|
content,
|
|
144
|
-
authoring_guide_command: `crtr skill
|
|
187
|
+
authoring_guide_command: `crtr skill`,
|
|
145
188
|
});
|
|
146
189
|
return;
|
|
147
190
|
}
|
|
@@ -235,12 +278,20 @@ export function registerSkillCommands(program) {
|
|
|
235
278
|
.description('scaffold a new skill — <name> (scope-direct) or <plugin>:<name>')
|
|
236
279
|
.option('--scope <scope>', 'user|project (default: project then user)')
|
|
237
280
|
.option('--description <text>', 'skill description for frontmatter')
|
|
281
|
+
.option('--type <type>', `skill type for frontmatter — one of: ${SKILL_TYPES.join(' | ')}`)
|
|
238
282
|
.action(async (qualifier, opts) => {
|
|
239
283
|
try {
|
|
240
284
|
const { plugin: pluginName, name: skillName } = parseSkillQualifier(qualifier);
|
|
241
285
|
if (!skillName) {
|
|
242
286
|
throw usage('skill name required');
|
|
243
287
|
}
|
|
288
|
+
let skillType;
|
|
289
|
+
if (opts.type !== undefined) {
|
|
290
|
+
if (!isSkillType(opts.type)) {
|
|
291
|
+
throw usage(`unknown skill type: ${opts.type} / valid: ${SKILL_TYPES.join(' | ')}`);
|
|
292
|
+
}
|
|
293
|
+
skillType = opts.type;
|
|
294
|
+
}
|
|
244
295
|
const scopeArg = opts.scope !== undefined ? resolveScopeArg(opts.scope) : undefined;
|
|
245
296
|
// Scope-direct: no plugin qualifier, or explicit `_:` sentinel
|
|
246
297
|
if (pluginName === undefined || pluginName === SCOPE_SKILL_PLUGIN) {
|
|
@@ -266,11 +317,13 @@ export function registerSkillCommands(program) {
|
|
|
266
317
|
const fm = serializeFrontmatter({
|
|
267
318
|
name: skillName,
|
|
268
319
|
description: opts.description,
|
|
320
|
+
type: skillType,
|
|
269
321
|
});
|
|
270
322
|
writeFileSync(skillFile, fm, 'utf8');
|
|
271
323
|
out(skillFile);
|
|
272
|
-
|
|
273
|
-
|
|
324
|
+
const templateHint = skillType !== undefined ? skillType : '<type>';
|
|
325
|
+
hint(`crtr: scaffolded ${scope}-scope skill ${skillName} — edit directly; ` +
|
|
326
|
+
`\`crtr skill template ${templateHint}\` for body skeleton`);
|
|
274
327
|
return;
|
|
275
328
|
}
|
|
276
329
|
let plugin;
|
|
@@ -292,11 +345,13 @@ export function registerSkillCommands(program) {
|
|
|
292
345
|
const fm = serializeFrontmatter({
|
|
293
346
|
name: skillName,
|
|
294
347
|
description: opts.description,
|
|
348
|
+
type: skillType,
|
|
295
349
|
});
|
|
296
350
|
writeFileSync(skillFile, fm, 'utf8');
|
|
297
351
|
out(skillFile);
|
|
298
|
-
|
|
299
|
-
|
|
352
|
+
const templateHint = skillType !== undefined ? skillType : '<type>';
|
|
353
|
+
hint(`crtr: scaffolded ${skillFile} — edit directly; ` +
|
|
354
|
+
`\`crtr skill template ${templateHint}\` for body skeleton`);
|
|
300
355
|
}
|
|
301
356
|
catch (e) {
|
|
302
357
|
handleError(e);
|
|
@@ -305,7 +360,7 @@ export function registerSkillCommands(program) {
|
|
|
305
360
|
// create — pick a template type
|
|
306
361
|
skill
|
|
307
362
|
.command('create [topic...]')
|
|
308
|
-
.description(
|
|
363
|
+
.description(`pick a template type for a new skill (${SKILL_TYPES.join(' | ')})`)
|
|
309
364
|
.action(async (topic) => {
|
|
310
365
|
const arg = topic && topic.length > 0 ? topic.join(' ') : '';
|
|
311
366
|
out(skillCreatePrompt(arg));
|
|
@@ -313,7 +368,7 @@ export function registerSkillCommands(program) {
|
|
|
313
368
|
// template — full workflow + skeleton for one template type
|
|
314
369
|
skill
|
|
315
370
|
.command('template <type> [topic...]')
|
|
316
|
-
.description(
|
|
371
|
+
.description(`full workflow + skeleton for a template type (${SKILL_TYPES.join(' | ')})`)
|
|
317
372
|
.action(async (type, topic) => {
|
|
318
373
|
const arg = topic && topic.length > 0 ? topic.join(' ') : '';
|
|
319
374
|
out(skillTemplatePrompt(type, arg));
|
package/dist/core/frontmatter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSkillType } from '../types.js';
|
|
1
2
|
const FRONTMATTER_RE = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?/;
|
|
2
3
|
export function parseFrontmatter(source) {
|
|
3
4
|
const match = source.match(FRONTMATTER_RE);
|
|
@@ -126,6 +127,7 @@ function parseSimpleYaml(yaml) {
|
|
|
126
127
|
name: typeof out.name === 'string' ? out.name : '',
|
|
127
128
|
description: typeof out.description === 'string' ? out.description : undefined,
|
|
128
129
|
keywords: Array.isArray(out.keywords) ? out.keywords : undefined,
|
|
130
|
+
type: isSkillType(out.type) ? out.type : undefined,
|
|
129
131
|
};
|
|
130
132
|
return fm;
|
|
131
133
|
}
|
|
@@ -141,6 +143,9 @@ export function serializeFrontmatter(data) {
|
|
|
141
143
|
if (data.description !== undefined) {
|
|
142
144
|
lines.push(`description: ${quoteIfNeeded(data.description)}`);
|
|
143
145
|
}
|
|
146
|
+
if (data.type !== undefined) {
|
|
147
|
+
lines.push(`type: ${data.type}`);
|
|
148
|
+
}
|
|
144
149
|
if (data.keywords && data.keywords.length) {
|
|
145
150
|
const inline = `[${data.keywords.map(quoteIfNeeded).join(', ')}]`;
|
|
146
151
|
lines.push(`keywords: ${inline}`);
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare function effectiveSkillEnabled(pluginName: string, skillName: str
|
|
|
13
13
|
export declare function listSkillsInPlugin(plugin: InstalledPlugin, cfgs?: ScopeConfigs): Skill[];
|
|
14
14
|
export declare function listScopeRootSkills(scope: Scope, cfgs?: ScopeConfigs): Skill[];
|
|
15
15
|
export declare function listAllSkills(scopeFilter?: Scope): Skill[];
|
|
16
|
+
export declare function listSkillSiblings(skill: Skill): Skill[];
|
|
17
|
+
export declare function listSkillChildren(skill: Skill): Skill[];
|
|
16
18
|
export interface SkillResolutionOpts {
|
|
17
19
|
scope?: Scope;
|
|
18
20
|
pluginFilter?: string;
|
package/dist/core/resolver.js
CHANGED
|
@@ -5,8 +5,29 @@ import { listDirs, pathExists, readText, walkFiles, } from './fs-utils.js';
|
|
|
5
5
|
import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
|
|
6
6
|
import { parseFrontmatter } from './frontmatter.js';
|
|
7
7
|
import { ambiguous, notFound, usage } from './errors.js';
|
|
8
|
-
import { marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
|
|
8
|
+
import { builtinSkillsRoot, marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
|
|
9
|
+
function getBuiltinPlugin() {
|
|
10
|
+
const root = builtinSkillsRoot();
|
|
11
|
+
if (!pathExists(root))
|
|
12
|
+
return null;
|
|
13
|
+
const manifest = readPluginManifest(root);
|
|
14
|
+
if (!manifest)
|
|
15
|
+
return null;
|
|
16
|
+
return {
|
|
17
|
+
name: manifest.name,
|
|
18
|
+
scope: 'builtin',
|
|
19
|
+
root,
|
|
20
|
+
manifest,
|
|
21
|
+
enabled: true,
|
|
22
|
+
builtin: true,
|
|
23
|
+
version: manifest.version,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
9
26
|
export function listInstalledPlugins(scope) {
|
|
27
|
+
if (scope === 'builtin') {
|
|
28
|
+
const builtin = getBuiltinPlugin();
|
|
29
|
+
return builtin ? [builtin] : [];
|
|
30
|
+
}
|
|
10
31
|
const dir = pluginsDir(scope);
|
|
11
32
|
if (!dir || !pathExists(dir))
|
|
12
33
|
return [];
|
|
@@ -40,13 +61,14 @@ export function listAllPlugins() {
|
|
|
40
61
|
if (projectScopeRoot())
|
|
41
62
|
scopes.push('project');
|
|
42
63
|
scopes.push('user');
|
|
64
|
+
scopes.push('builtin');
|
|
43
65
|
return scopes.flatMap(listInstalledPlugins);
|
|
44
66
|
}
|
|
45
67
|
export function findPluginByName(name, scope) {
|
|
46
68
|
if (scope) {
|
|
47
69
|
return listInstalledPlugins(scope).find((p) => p.name === name) ?? null;
|
|
48
70
|
}
|
|
49
|
-
for (const s of ['project', 'user'].filter((sc) => sc === 'project' ? projectScopeRoot() !== null : true)) {
|
|
71
|
+
for (const s of ['project', 'user', 'builtin'].filter((sc) => sc === 'project' ? projectScopeRoot() !== null : true)) {
|
|
50
72
|
const match = listInstalledPlugins(s).find((p) => p.name === name);
|
|
51
73
|
if (match)
|
|
52
74
|
return match;
|
|
@@ -100,6 +122,8 @@ export function listSkillsInPlugin(plugin, cfgs) {
|
|
|
100
122
|
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
101
123
|
}
|
|
102
124
|
export function listScopeRootSkills(scope, cfgs) {
|
|
125
|
+
if (scope === 'builtin')
|
|
126
|
+
return [];
|
|
103
127
|
const skillsRoot = scopeSkillsDir(scope);
|
|
104
128
|
if (!skillsRoot || !pathExists(skillsRoot))
|
|
105
129
|
return [];
|
|
@@ -138,6 +162,41 @@ export function listAllSkills(scopeFilter) {
|
|
|
138
162
|
...plugins.filter((p) => p.enabled).flatMap((p) => listSkillsInPlugin(p, cfgs)),
|
|
139
163
|
];
|
|
140
164
|
}
|
|
165
|
+
function enumerateNeighborPool(skill) {
|
|
166
|
+
if (skill.plugin === SCOPE_SKILL_PLUGIN) {
|
|
167
|
+
return listScopeRootSkills(skill.scope);
|
|
168
|
+
}
|
|
169
|
+
const plugin = listInstalledPlugins(skill.scope).find((p) => p.name === skill.plugin);
|
|
170
|
+
if (!plugin)
|
|
171
|
+
return [];
|
|
172
|
+
return listSkillsInPlugin(plugin);
|
|
173
|
+
}
|
|
174
|
+
export function listSkillSiblings(skill) {
|
|
175
|
+
const pool = enumerateNeighborPool(skill);
|
|
176
|
+
const segs = skill.name.split('/');
|
|
177
|
+
const depth = segs.length;
|
|
178
|
+
const parentPrefix = segs.slice(0, -1).join('/');
|
|
179
|
+
return pool.filter((s) => {
|
|
180
|
+
if (s.name === skill.name)
|
|
181
|
+
return false;
|
|
182
|
+
const sSegs = s.name.split('/');
|
|
183
|
+
if (sSegs.length !== depth)
|
|
184
|
+
return false;
|
|
185
|
+
if (parentPrefix === '')
|
|
186
|
+
return !s.name.includes('/');
|
|
187
|
+
return s.name.startsWith(parentPrefix + '/');
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export function listSkillChildren(skill) {
|
|
191
|
+
const pool = enumerateNeighborPool(skill);
|
|
192
|
+
const prefix = skill.name + '/';
|
|
193
|
+
return pool.filter((s) => {
|
|
194
|
+
if (!s.name.startsWith(prefix))
|
|
195
|
+
return false;
|
|
196
|
+
const rest = s.name.slice(prefix.length);
|
|
197
|
+
return rest.length > 0 && !rest.includes('/');
|
|
198
|
+
});
|
|
199
|
+
}
|
|
141
200
|
export function resolveSkill(rawName, opts = {}) {
|
|
142
201
|
const parsed = parseSkillQualifier(rawName);
|
|
143
202
|
if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
|
|
@@ -353,6 +412,8 @@ export function parseSkillQualifier(raw) {
|
|
|
353
412
|
}
|
|
354
413
|
function orderPluginsByResolution(plugins) {
|
|
355
414
|
const score = (p) => {
|
|
415
|
+
if (p.scope === 'builtin')
|
|
416
|
+
return 4;
|
|
356
417
|
const fromMarketplace = Boolean(p.sourceMarketplace);
|
|
357
418
|
if (p.scope === 'project' && !fromMarketplace)
|
|
358
419
|
return 0;
|
package/dist/core/scope.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Scope } from '../types.js';
|
|
2
|
+
export declare function builtinSkillsRoot(): string;
|
|
2
3
|
export declare function userScopeRoot(): string;
|
|
3
4
|
export declare function findProjectScopeRoot(startDir?: string): string | null;
|
|
4
5
|
export declare function projectScopeRoot(startDir?: string): string | null;
|
package/dist/core/scope.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { existsSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { CRTR_DIR_NAME } from '../types.js';
|
|
5
6
|
import { usage } from './errors.js';
|
|
6
7
|
let cachedProjectRoot;
|
|
8
|
+
export function builtinSkillsRoot() {
|
|
9
|
+
// Resolve relative to this file: src/core/scope.ts → src/builtin-skills/ OR dist/core/scope.js → dist/builtin-skills/
|
|
10
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
11
|
+
const coreDir = dirname(thisFile);
|
|
12
|
+
const pkgDir = dirname(coreDir); // src/ or dist/
|
|
13
|
+
return join(pkgDir, 'builtin-skills');
|
|
14
|
+
}
|
|
7
15
|
export function userScopeRoot() {
|
|
8
16
|
return join(homedir(), CRTR_DIR_NAME);
|
|
9
17
|
}
|
|
@@ -37,6 +45,8 @@ export function projectScopeRoot(startDir) {
|
|
|
37
45
|
return findProjectScopeRoot(startDir);
|
|
38
46
|
}
|
|
39
47
|
export function scopeRoot(scope) {
|
|
48
|
+
if (scope === 'builtin')
|
|
49
|
+
return builtinSkillsRoot();
|
|
40
50
|
return scope === 'user' ? userScopeRoot() : projectScopeRoot();
|
|
41
51
|
}
|
|
42
52
|
export function requireScopeRoot(scope) {
|
|
@@ -71,9 +81,9 @@ export function resolveScopeArg(scopeArg) {
|
|
|
71
81
|
if (scopeArg === undefined)
|
|
72
82
|
return 'all';
|
|
73
83
|
const value = scopeArg.toLowerCase();
|
|
74
|
-
if (value === 'user' || value === 'project' || value === 'all')
|
|
84
|
+
if (value === 'user' || value === 'project' || value === 'builtin' || value === 'all')
|
|
75
85
|
return value;
|
|
76
|
-
throw usage(`invalid --scope: ${scopeArg} (expected user|project|all)`);
|
|
86
|
+
throw usage(`invalid --scope: ${scopeArg} (expected user|project|builtin|all)`);
|
|
77
87
|
}
|
|
78
88
|
export function listScopes(scopeArg) {
|
|
79
89
|
const v = resolveScopeArg(scopeArg);
|
|
@@ -82,6 +92,7 @@ export function listScopes(scopeArg) {
|
|
|
82
92
|
if (projectScopeRoot())
|
|
83
93
|
out.push('project');
|
|
84
94
|
out.push('user');
|
|
95
|
+
out.push('builtin');
|
|
85
96
|
return out;
|
|
86
97
|
}
|
|
87
98
|
return [v];
|
package/dist/prompts/skill.js
CHANGED
|
@@ -48,14 +48,49 @@ crtr skill where <name> # {scope, plugin, path} JSON
|
|
|
48
48
|
## Author (progressive disclosure)
|
|
49
49
|
|
|
50
50
|
\`\`\`
|
|
51
|
-
crtr skill create [topic...]
|
|
52
|
-
crtr skill template <type> [topic]
|
|
53
|
-
crtr skill new <name>
|
|
51
|
+
crtr skill create [topic...] # pick a template type
|
|
52
|
+
crtr skill template <type> [topic] # workflow + skeleton for that type
|
|
53
|
+
crtr skill new <name> --type <type> # bare scaffold with typed frontmatter
|
|
54
54
|
\`\`\`
|
|
55
55
|
|
|
56
|
+
Five types — pick by what the agent does after reading:
|
|
57
|
+
|
|
58
|
+
- \`playbook\` — decide (judgment, heuristics, when-to-use)
|
|
59
|
+
- \`primer\` — navigate (codebase facts, architecture)
|
|
60
|
+
- \`reference\` — look up (stable facts, tables)
|
|
61
|
+
- \`runbook\` — execute (numbered procedure)
|
|
62
|
+
- \`freeform\` — none of the above (catchall)
|
|
63
|
+
|
|
56
64
|
Don't load \`create\` and \`template\` in the same turn — \`create\` decides the
|
|
57
65
|
type, then call \`template\`.
|
|
58
66
|
|
|
67
|
+
## Format
|
|
68
|
+
|
|
69
|
+
A skill is a directory; \`SKILL.md\` is its entry file. The dir IS the skill —
|
|
70
|
+
siblings are assets, nested dirs are themselves skills.
|
|
71
|
+
|
|
72
|
+
Frontmatter (required: \`name\`, \`type\`, \`description\`):
|
|
73
|
+
- \`name\` — must equal the dir path under \`skills/\`. Slashes for nested
|
|
74
|
+
(\`skills/web/frontend/design/SKILL.md\` → \`name: web/frontend/design\`).
|
|
75
|
+
- \`type\` — one of the five above.
|
|
76
|
+
- \`description\` — one sentence, front-load with "Use when…" — drives discovery.
|
|
77
|
+
- \`keywords\` (optional) — array of strings, improves \`crtr skill search\`.
|
|
78
|
+
|
|
79
|
+
Intermediate dirs (\`web/\`, \`web/frontend/\`) don't need their own SKILL.md —
|
|
80
|
+
nesting is purely path-based. Budget ~150 lines per SKILL.md body; spill
|
|
81
|
+
deeper material into sibling files (\`reference.md\`, \`examples.md\`).
|
|
82
|
+
|
|
83
|
+
Validate with \`crtr doctor\` — checks frontmatter, name-vs-path match, type
|
|
84
|
+
enum, sibling-link reachability.
|
|
85
|
+
|
|
86
|
+
## Neighbors auto-append
|
|
87
|
+
|
|
88
|
+
\`crtr skill show <name>\` appends a \`## Neighbors\` section listing siblings
|
|
89
|
+
(same parent dir) and nested skills. Skill bodies should write \`## Related\`
|
|
90
|
+
**only** for cross-plugin or distant refs — within-plugin links are redundant.
|
|
91
|
+
|
|
92
|
+
Suppress with \`crtr skill show <name> --no-neighbors\`.
|
|
93
|
+
|
|
59
94
|
## Toggle
|
|
60
95
|
|
|
61
96
|
\`\`\`
|
|
@@ -81,11 +116,22 @@ ${topicLine}
|
|
|
81
116
|
|
|
82
117
|
## Templates
|
|
83
118
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- \`playbook\` —
|
|
87
|
-
|
|
88
|
-
- \`
|
|
119
|
+
Pick by what the agent does after reading the skill:
|
|
120
|
+
|
|
121
|
+
- \`playbook\` — **decide**. Judgment, heuristics, when-to-use / when-not-to-use.
|
|
122
|
+
Examples: skill-authoring, debugging methodology. Most skills are this.
|
|
123
|
+
- \`primer\` — **navigate**. Codebase/architectural facts. *"How does this
|
|
124
|
+
subsystem work, why, what are the gotchas."* Triggers parallel-explore.
|
|
125
|
+
- \`reference\` — **look up**. Stable facts: protocol fields, API surface,
|
|
126
|
+
glossaries, lookup tables. Source of truth is external (spec, docs).
|
|
127
|
+
- \`runbook\` — **execute**. Numbered procedure with decision points and
|
|
128
|
+
rollback. Examples: deploy, incident response, review workflow.
|
|
129
|
+
- \`freeform\` — **none of the above**. Catchall for decision records, prefs,
|
|
130
|
+
miscellany.
|
|
131
|
+
|
|
132
|
+
Litmus: *"when X, do Y"* → playbook. *"these are the fields of Y"* →
|
|
133
|
+
reference. *"step 1, step 2, step 3"* → runbook. *"how X is built inside
|
|
134
|
+
this repo"* → primer.
|
|
89
135
|
|
|
90
136
|
## Next
|
|
91
137
|
|
|
@@ -103,6 +149,7 @@ ${topicLine}
|
|
|
103
149
|
|
|
104
150
|
- Subsystem is small/self-evident → suggest CLAUDE.md note instead of primer.
|
|
105
151
|
- "Topic" is really a one-off task → don't capture; just do the work.
|
|
152
|
+
- Content is one-off lookup that lives elsewhere → link to it, don't mirror.
|
|
106
153
|
`;
|
|
107
154
|
}
|
|
108
155
|
export function skillTemplatePrompt(type, topic) {
|
|
@@ -111,9 +158,13 @@ export function skillTemplatePrompt(type, topic) {
|
|
|
111
158
|
return primerTemplatePrompt(topic);
|
|
112
159
|
if (t === 'playbook')
|
|
113
160
|
return playbookTemplatePrompt(topic);
|
|
161
|
+
if (t === 'reference')
|
|
162
|
+
return referenceTemplatePrompt(topic);
|
|
163
|
+
if (t === 'runbook')
|
|
164
|
+
return runbookTemplatePrompt(topic);
|
|
114
165
|
if (t === 'freeform')
|
|
115
166
|
return freeformTemplatePrompt(topic);
|
|
116
|
-
return `unknown template type: ${type}\nvalid: primer |
|
|
167
|
+
return `unknown template type: ${type}\nvalid: playbook | primer | reference | runbook | freeform\nrun \`crtr skill create\` to pick.\n`;
|
|
117
168
|
}
|
|
118
169
|
function topicLine(topic) {
|
|
119
170
|
return topic
|
|
@@ -176,7 +227,7 @@ assumptions.**
|
|
|
176
227
|
## 5. Scaffold
|
|
177
228
|
|
|
178
229
|
\`\`\`
|
|
179
|
-
crtr skill new <name> --scope project --description "<what+when, ≤250 chars, front-loaded triggers>"
|
|
230
|
+
crtr skill new <name> --type primer --scope project --description "<what+when, ≤250 chars, front-loaded triggers>"
|
|
180
231
|
\`\`\`
|
|
181
232
|
|
|
182
233
|
## 6. Write the body
|
|
@@ -204,11 +255,12 @@ Domain terms, invariants, non-obvious constraints.
|
|
|
204
255
|
|
|
205
256
|
## Gotchas
|
|
206
257
|
Non-obvious coupling. Looks-broken-but-isn't. Past footguns.
|
|
207
|
-
|
|
208
|
-
## Related
|
|
209
|
-
- \`<other-skill>\` — interaction
|
|
210
258
|
\`\`\`
|
|
211
259
|
|
|
260
|
+
**No \`## Related\` for within-plugin siblings** — the CLI auto-appends a
|
|
261
|
+
\`## Neighbors\` section on \`crtr skill show\`. Add a manual \`## Related\`
|
|
262
|
+
only for cross-plugin or distant refs.
|
|
263
|
+
|
|
212
264
|
**Density rules:**
|
|
213
265
|
- \`file:line\` over prose
|
|
214
266
|
- Tables where structure fits
|
|
@@ -229,7 +281,7 @@ Sharpen description if discovery misses. Cut body if bloated.
|
|
|
229
281
|
## Updates
|
|
230
282
|
|
|
231
283
|
If updating existing primer: diff draft vs current, call out changes + why
|
|
232
|
-
before writing.
|
|
284
|
+
before writing.
|
|
233
285
|
`;
|
|
234
286
|
}
|
|
235
287
|
function playbookTemplatePrompt(topic) {
|
|
@@ -275,7 +327,7 @@ PR over many small ones for refactors here, because review churn dominates"*
|
|
|
275
327
|
## 3. Scaffold
|
|
276
328
|
|
|
277
329
|
\`\`\`
|
|
278
|
-
crtr skill new <name> --scope <user|project> --description "<what it teaches + when to load, ≤250 chars, front-loaded triggers>"
|
|
330
|
+
crtr skill new <name> --type playbook --scope <user|project> --description "<what it teaches + when to load, ≤250 chars, front-loaded triggers>"
|
|
279
331
|
\`\`\`
|
|
280
332
|
|
|
281
333
|
## 4. Density rules
|
|
@@ -314,11 +366,12 @@ to read the whole thing for value, you've buried the judgment.
|
|
|
314
366
|
|
|
315
367
|
## Failure modes
|
|
316
368
|
- **<name>**: what it looks like; how to avoid
|
|
317
|
-
|
|
318
|
-
## Related
|
|
319
|
-
- \`<other-skill>\` — interaction
|
|
320
369
|
\`\`\`
|
|
321
370
|
|
|
371
|
+
**No \`## Related\` for within-plugin siblings** — the CLI auto-appends a
|
|
372
|
+
\`## Neighbors\` section on \`crtr skill show\`. Add a manual \`## Related\`
|
|
373
|
+
only for cross-plugin or distant refs.
|
|
374
|
+
|
|
322
375
|
## 6. Progressive disclosure
|
|
323
376
|
|
|
324
377
|
If deep reference is needed:
|
|
@@ -341,24 +394,11 @@ crtr skill show <name>
|
|
|
341
394
|
crtr skill search <keyword>
|
|
342
395
|
\`\`\`
|
|
343
396
|
|
|
344
|
-
## Deep-dive reference
|
|
345
|
-
|
|
346
|
-
For canonical SKILL.md authoring (frontmatter fields, argument passing,
|
|
347
|
-
dynamic context, subagent forking, hooks):
|
|
348
|
-
|
|
349
|
-
\`\`\`
|
|
350
|
-
crtr skill show authoring-skills
|
|
351
|
-
\`\`\`
|
|
352
|
-
|
|
353
|
-
The playbook above gives you structure + density rules. \`authoring-skills\`
|
|
354
|
-
covers the SKILL.md surface itself.
|
|
355
|
-
|
|
356
397
|
## Constraints
|
|
357
398
|
|
|
358
|
-
- Topic fails litmus? → \`crtr skill template freeform\` or \`
|
|
399
|
+
- Topic fails litmus? → \`crtr skill template freeform\`, \`reference\`, or \`runbook\`.
|
|
359
400
|
- No unconfirmed heuristics — if not from user experience or clear principle,
|
|
360
401
|
leave it out.
|
|
361
|
-
- Update related skills' \`## Related\` if interactions exist.
|
|
362
402
|
`;
|
|
363
403
|
}
|
|
364
404
|
function freeformTemplatePrompt(topic) {
|
|
@@ -391,7 +431,7 @@ or grep, omit it.
|
|
|
391
431
|
## 3. Scope + name + scaffold
|
|
392
432
|
|
|
393
433
|
\`\`\`
|
|
394
|
-
crtr skill new <name> --scope <user|project> --description "<what+when, ≤250 chars, front-loaded triggers>"
|
|
434
|
+
crtr skill new <name> --type freeform --scope <user|project> --description "<what+when, ≤250 chars, front-loaded triggers>"
|
|
395
435
|
\`\`\`
|
|
396
436
|
|
|
397
437
|
## 4. Body — pick the closest skeleton
|
|
@@ -441,11 +481,226 @@ crtr skill search <keyword>
|
|
|
441
481
|
|
|
442
482
|
## Switch templates if needed
|
|
443
483
|
|
|
444
|
-
|
|
484
|
+
Content actually fits a typed template?
|
|
445
485
|
|
|
446
486
|
\`\`\`
|
|
447
|
-
crtr skill template playbook ${topic ? topic : '<topic>'}
|
|
448
|
-
crtr skill template primer ${topic ? topic : '<topic>'}
|
|
487
|
+
crtr skill template playbook ${topic ? topic : '<topic>'} # decide
|
|
488
|
+
crtr skill template primer ${topic ? topic : '<topic>'} # navigate codebase
|
|
489
|
+
crtr skill template reference ${topic ? topic : '<topic>'} # look up stable facts
|
|
490
|
+
crtr skill template runbook ${topic ? topic : '<topic>'} # execute a procedure
|
|
449
491
|
\`\`\`
|
|
450
492
|
`;
|
|
451
493
|
}
|
|
494
|
+
function referenceTemplatePrompt(topic) {
|
|
495
|
+
return `# Reference — lookup-fact skill
|
|
496
|
+
|
|
497
|
+
**Audience: future LLM agent sessions.** Captures *stable lookup facts* an
|
|
498
|
+
agent will grep or scan: protocol fields, API surfaces, glossaries, enum
|
|
499
|
+
tables, status-code maps. Source of truth lives *outside* the skill (RFC,
|
|
500
|
+
spec, vendor docs); the skill is a fast in-repo cache.
|
|
501
|
+
|
|
502
|
+
${topicLine(topic)}
|
|
503
|
+
|
|
504
|
+
## Litmus test
|
|
505
|
+
|
|
506
|
+
> Would you *grep* this rather than *read* it?
|
|
507
|
+
|
|
508
|
+
If you'd skim end-to-end → it's not reference. If you'd jump straight to
|
|
509
|
+
the row you need → reference. If you'd make a decision after reading →
|
|
510
|
+
\`crtr skill template playbook\`. If you'd execute steps → \`runbook\`.
|
|
511
|
+
|
|
512
|
+
**Reference markers:** mostly tables · stable across releases · authoritative
|
|
513
|
+
source elsewhere · agent loads to *answer*, not to *think*.
|
|
514
|
+
|
|
515
|
+
## 1. Confirm reference, not playbook
|
|
516
|
+
|
|
517
|
+
Use \`AskUserQuestion\` (≤4, multi-choice, best-guess first):
|
|
518
|
+
|
|
519
|
+
- The lookup the agent will perform (e.g., "what does HTTP 423 mean")
|
|
520
|
+
- Stability: does this change every release? If yes, push back — link the
|
|
521
|
+
upstream source instead of mirroring it.
|
|
522
|
+
- Authoritative source URL (cite in the body)
|
|
523
|
+
|
|
524
|
+
Skip if the topic is clearly facts (RFCs, public API). **Never invent
|
|
525
|
+
field/flag/code values** — pull verbatim from source.
|
|
526
|
+
|
|
527
|
+
## 2. Scope + name
|
|
528
|
+
|
|
529
|
+
- **Scope**: \`user\` for cross-project facts. \`project\` for repo-specific.
|
|
530
|
+
- **Name**: noun-phrase. \`http-status-codes\` not \`learn-http-status\`.
|
|
531
|
+
- Check \`crtr skill where <name>\`.
|
|
532
|
+
|
|
533
|
+
## 3. Scaffold
|
|
534
|
+
|
|
535
|
+
\`\`\`
|
|
536
|
+
crtr skill new <name> --type reference --scope <user|project> --description "<what to look up + when to load, ≤250 chars>"
|
|
537
|
+
\`\`\`
|
|
538
|
+
|
|
539
|
+
## 4. Density rules
|
|
540
|
+
|
|
541
|
+
Reference skills are *load and scan*, not *load and read*. Optimize for jump-to-row.
|
|
542
|
+
|
|
543
|
+
- Tables for anything multi-row. Columns: most-queried field first.
|
|
544
|
+
- One topic per file. Split if it doesn't fit one screen.
|
|
545
|
+
- Source URL at the top — agent verifies before trusting cached facts.
|
|
546
|
+
- No prose paragraphs longer than 2 lines.
|
|
547
|
+
- Skip *why* — playbooks teach why. Reference teaches what.
|
|
548
|
+
|
|
549
|
+
## 5. Body skeleton
|
|
550
|
+
|
|
551
|
+
\`\`\`markdown
|
|
552
|
+
# <topic> reference
|
|
553
|
+
|
|
554
|
+
**Source of truth:** <URL or spec name>
|
|
555
|
+
**Last verified:** <date — when an agent should re-check>
|
|
556
|
+
|
|
557
|
+
## <table 1 title>
|
|
558
|
+
| <field> | <value> | <notes> |
|
|
559
|
+
|---------|---------|---------|
|
|
560
|
+
| … | … | … |
|
|
561
|
+
|
|
562
|
+
## <table 2 title>
|
|
563
|
+
…
|
|
564
|
+
|
|
565
|
+
## Edge cases / gotchas
|
|
566
|
+
- Brief bullets. *What* is misleading, not *why*.
|
|
567
|
+
\`\`\`
|
|
568
|
+
|
|
569
|
+
**No \`## Related\` for within-plugin siblings** — auto-appended by the CLI.
|
|
570
|
+
|
|
571
|
+
## 6. Progressive disclosure
|
|
572
|
+
|
|
573
|
+
If reference is large, split:
|
|
574
|
+
|
|
575
|
+
\`\`\`
|
|
576
|
+
<skill-dir>/
|
|
577
|
+
SKILL.md # top-level index + most-queried table
|
|
578
|
+
full-table.md # the full set
|
|
579
|
+
examples.md # rarely-needed worked examples
|
|
580
|
+
\`\`\`
|
|
581
|
+
|
|
582
|
+
SKILL.md links to siblings (\`see [full-table.md](full-table.md)\`).
|
|
583
|
+
|
|
584
|
+
## 7. Verify
|
|
585
|
+
|
|
586
|
+
\`\`\`
|
|
587
|
+
crtr skill where <name>
|
|
588
|
+
crtr skill show <name>
|
|
589
|
+
crtr skill search <keyword>
|
|
590
|
+
\`\`\`
|
|
591
|
+
|
|
592
|
+
Search must surface the skill on a typical lookup query. Sharpen the
|
|
593
|
+
description if it doesn't.
|
|
594
|
+
|
|
595
|
+
## Constraints
|
|
596
|
+
|
|
597
|
+
- No invented values. If you can't cite the source, leave the row out.
|
|
598
|
+
- Topic teaches *judgment*, not facts? → \`crtr skill template playbook\`.
|
|
599
|
+
- Topic is a *procedure*? → \`crtr skill template runbook\`.
|
|
600
|
+
- Source updates faster than you'll update the skill? → don't capture; link.
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
function runbookTemplatePrompt(topic) {
|
|
604
|
+
return `# Runbook — procedure skill
|
|
605
|
+
|
|
606
|
+
**Audience: future LLM agent sessions.** Captures a *procedure* the agent
|
|
607
|
+
executes: numbered steps, decision points, verification, rollback. Examples:
|
|
608
|
+
deploy, incident response, review workflow, release cut.
|
|
609
|
+
|
|
610
|
+
${topicLine(topic)}
|
|
611
|
+
|
|
612
|
+
## Litmus test
|
|
613
|
+
|
|
614
|
+
> After loading this, does the agent *do steps in order*?
|
|
615
|
+
|
|
616
|
+
If yes → runbook. If the agent makes a decision and stops → \`playbook\`. If
|
|
617
|
+
the agent looks up a value → \`reference\`. If neither fits → \`freeform\`.
|
|
618
|
+
|
|
619
|
+
**Runbook markers:** numbered steps · ordering matters · has rollback or
|
|
620
|
+
verification · maps an outcome to a known sequence.
|
|
621
|
+
|
|
622
|
+
## 1. Capture the procedure
|
|
623
|
+
|
|
624
|
+
Use \`AskUserQuestion\` (≤4, multi-choice, best-guess first):
|
|
625
|
+
|
|
626
|
+
- The trigger (when does the agent run this?)
|
|
627
|
+
- Steps in order — explicit, atomic. *"Update X"* is not atomic; *"run
|
|
628
|
+
\\\`pnpm db:migrate\\\` and confirm exit 0"* is.
|
|
629
|
+
- Decision points within the sequence (when does the agent branch?)
|
|
630
|
+
- Rollback / verification (how to confirm success; how to undo on failure)
|
|
631
|
+
|
|
632
|
+
Push back on vagueness. *"Deploy to prod"* is a label; *"run \\\`pnpm build\\\`,
|
|
633
|
+
push to \\\`main\\\`, wait for green CI, click promote"* is a runbook step.
|
|
634
|
+
|
|
635
|
+
## 2. Scope + name
|
|
636
|
+
|
|
637
|
+
- **Scope**: \`project\` for repo-specific procedures. \`user\` for cross-project.
|
|
638
|
+
- **Name**: verb-phrase. \`deploy-to-prod\` not \`production-deployment-guide\`.
|
|
639
|
+
- Check \`crtr skill where <name>\`.
|
|
640
|
+
|
|
641
|
+
## 3. Scaffold
|
|
642
|
+
|
|
643
|
+
\`\`\`
|
|
644
|
+
crtr skill new <name> --type runbook --scope <user|project> --description "<when to run + outcome, ≤250 chars, front-loaded trigger>"
|
|
645
|
+
\`\`\`
|
|
646
|
+
|
|
647
|
+
## 4. Density rules
|
|
648
|
+
|
|
649
|
+
- Steps are commands, not advice. Each step is something the agent can verify.
|
|
650
|
+
- Decision points get explicit branches, not "use judgment."
|
|
651
|
+
- Verification belongs in-line, after the step that produces the change.
|
|
652
|
+
- Rollback is mandatory if the procedure changes prod state.
|
|
653
|
+
|
|
654
|
+
## 5. Body skeleton
|
|
655
|
+
|
|
656
|
+
\`\`\`markdown
|
|
657
|
+
# <procedure>
|
|
658
|
+
|
|
659
|
+
## When to run
|
|
660
|
+
- <trigger>
|
|
661
|
+
|
|
662
|
+
## Pre-flight
|
|
663
|
+
- [ ] <thing that must be true before starting>
|
|
664
|
+
|
|
665
|
+
## Steps
|
|
666
|
+
|
|
667
|
+
1. **<atomic action>** — \\\`<command>\\\`
|
|
668
|
+
Verify: <observation that confirms success>
|
|
669
|
+
If <error condition>: <what to do>
|
|
670
|
+
|
|
671
|
+
2. **<atomic action>** — …
|
|
672
|
+
|
|
673
|
+
## Decision points
|
|
674
|
+
|
|
675
|
+
- After step N, if X then go to step M; else continue.
|
|
676
|
+
|
|
677
|
+
## Verification (post-flight)
|
|
678
|
+
- [ ] <how to confirm the procedure succeeded end-to-end>
|
|
679
|
+
|
|
680
|
+
## Rollback
|
|
681
|
+
1. <reverse-order undo steps with their own verification>
|
|
682
|
+
\`\`\`
|
|
683
|
+
|
|
684
|
+
**No \`## Related\` for within-plugin siblings** — auto-appended by the CLI.
|
|
685
|
+
|
|
686
|
+
## 6. Verify
|
|
687
|
+
|
|
688
|
+
\`\`\`
|
|
689
|
+
crtr skill where <name>
|
|
690
|
+
crtr skill show <name>
|
|
691
|
+
crtr skill search <keyword>
|
|
692
|
+
\`\`\`
|
|
693
|
+
|
|
694
|
+
Walk through the runbook mentally. Each step verifiable? Each decision
|
|
695
|
+
explicit? Rollback covers the state changes? If any answer is no, fix
|
|
696
|
+
before shipping.
|
|
697
|
+
|
|
698
|
+
## Constraints
|
|
699
|
+
|
|
700
|
+
- Steps without verification are wishes. Add the verify line or cut the step.
|
|
701
|
+
- "Use your judgment at step 4" → either turn step 4 into a playbook
|
|
702
|
+
reference, or write the decision criteria explicitly.
|
|
703
|
+
- A procedure that's actually one command isn't a runbook — make it a
|
|
704
|
+
CLAUDE.md note.
|
|
705
|
+
`;
|
|
706
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Scope = 'user' | 'project';
|
|
1
|
+
export type Scope = 'user' | 'project' | 'builtin';
|
|
2
2
|
export declare const ExitCode: {
|
|
3
3
|
readonly SUCCESS: 0;
|
|
4
4
|
readonly GENERAL: 1;
|
|
@@ -71,10 +71,14 @@ export interface ScopeState {
|
|
|
71
71
|
last_self_check?: string;
|
|
72
72
|
bootstrap_done?: boolean;
|
|
73
73
|
}
|
|
74
|
+
export declare const SKILL_TYPES: readonly ["playbook", "primer", "reference", "runbook", "freeform"];
|
|
75
|
+
export type SkillType = (typeof SKILL_TYPES)[number];
|
|
76
|
+
export declare function isSkillType(v: unknown): v is SkillType;
|
|
74
77
|
export interface SkillFrontmatter {
|
|
75
78
|
name: string;
|
|
76
79
|
description?: string;
|
|
77
80
|
keywords?: string[];
|
|
81
|
+
type?: SkillType;
|
|
78
82
|
}
|
|
79
83
|
export interface Skill {
|
|
80
84
|
name: string;
|
|
@@ -92,6 +96,7 @@ export interface InstalledPlugin {
|
|
|
92
96
|
root: string;
|
|
93
97
|
manifest: PluginManifest;
|
|
94
98
|
enabled: boolean;
|
|
99
|
+
builtin?: boolean;
|
|
95
100
|
sourceMarketplace?: string;
|
|
96
101
|
version?: string;
|
|
97
102
|
}
|
package/dist/types.js
CHANGED
|
@@ -7,6 +7,10 @@ export const ExitCode = {
|
|
|
7
7
|
NETWORK: 5,
|
|
8
8
|
};
|
|
9
9
|
export const SCHEMA_VERSION = 1;
|
|
10
|
+
export const SKILL_TYPES = ['playbook', 'primer', 'reference', 'runbook', 'freeform'];
|
|
11
|
+
export function isSkillType(v) {
|
|
12
|
+
return typeof v === 'string' && SKILL_TYPES.includes(v);
|
|
13
|
+
}
|
|
10
14
|
export const PLUGIN_MANIFEST_DIR = '.crouter-plugin';
|
|
11
15
|
export const PLUGIN_MANIFEST_FILE = 'plugin.json';
|
|
12
16
|
export const MARKETPLACE_MANIFEST_DIR = '.crouter-marketplace';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crouton-kit/crouter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "crtr — fast access to skills, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"bin"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "tsc",
|
|
26
|
+
"build": "tsc && rm -rf dist/builtin-skills && cp -R src/builtin-skills dist/builtin-skills",
|
|
27
27
|
"dev": "tsx src/cli.ts",
|
|
28
28
|
"link": "npm link",
|
|
29
29
|
"prepublishOnly": "npm run build"
|