simple-skills-manager 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/config.ts +736 -0
- package/docs/config.md +117 -0
- package/docs/discovery.md +93 -0
- package/index.ts +419 -0
- package/menu.ts +1292 -0
- package/package.json +31 -0
- package/scan.ts +397 -0
- package/simple-skills-manager.example.json +114 -0
- package/tests/fixtures/bad/malformed/SKILL.md +5 -0
- package/tests/fixtures/bad/unparseable/SKILL.md +7 -0
- package/tests/fixtures/single-skill/SKILL.md +8 -0
- package/tests/fixtures/single-skill/references/notes.md +1 -0
- package/tests/fixtures/skills-dir/dupe-a/SKILL.md +6 -0
- package/tests/fixtures/skills-dir/dupe-b/SKILL.md +6 -0
- package/tests/fixtures/skills-dir/grouped/doc/SKILL.md +8 -0
- package/tests/fixtures/skills-dir/grouped/scripted/SKILL.md +8 -0
- package/tests/fixtures/skills-dir/grouped/scripted/scripts/run.sh +2 -0
- package/tests/fixtures/skills-dir/hidden/SKILL.md +9 -0
- package/tests/fixtures/skills-dir/plain/SKILL.md +8 -0
- package/tests/smoke-extension.ts +577 -0
- package/tests/smoke.sh +247 -0
- package/tsconfig.json +20 -0
- package/util.ts +74 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
Quality pass — bug fixes and hardening, no config or manifest format changes.
|
|
6
|
+
|
|
7
|
+
- Fixed the directory import flow: when the picked `SKILL.md` was nested,
|
|
8
|
+
the whole import source was copied instead of the skill's own directory,
|
|
9
|
+
so the copy never contained a root-level `SKILL.md` and the flow always
|
|
10
|
+
failed. The copy now starts at the chosen skill directory, and the
|
|
11
|
+
pre-read of its `SKILL.md` is size- and symlink-guarded.
|
|
12
|
+
- The tree (and root details) no longer crash on a root whose `${...}`
|
|
13
|
+
path template cannot be resolved: labels, mirror checks, and exposure
|
|
14
|
+
counts fail closed to zero/unmirrored instead of throwing.
|
|
15
|
+
- Scan hygiene: root-level `.md` files are size-checked via `lstat` before
|
|
16
|
+
being read (a giant file no longer loads into memory first), the shebang
|
|
17
|
+
inventory reads only the first two bytes of each file instead of whole
|
|
18
|
+
files (skill directories may contain large assets), and the root-level
|
|
19
|
+
inventory runs once per scan instead of once per root-level file.
|
|
20
|
+
- `copyTreeInto` counts every filesystem entry against `maxScanFiles`
|
|
21
|
+
(previously only directories were counted).
|
|
22
|
+
- Drift/manifest-miss at invocation now evicts the cached scan, so a fixed
|
|
23
|
+
skill works on the next call without a reload.
|
|
24
|
+
- `promptGuidelines` are no longer duplicated once per exposed skill tool;
|
|
25
|
+
the shared awareness lines ride on the first registered tool and every
|
|
26
|
+
tool gets a guideline that names it (pi's flat-guidelines requirement).
|
|
27
|
+
- The truncation marker now lands inside the `maxResultBytes` budget.
|
|
28
|
+
- Editing a root-level `.md` store skill keeps its instruction file name
|
|
29
|
+
(the refreshed snapshot no longer falls back to `SKILL.md`); removing
|
|
30
|
+
one deletes its file instead of refusing with a wrong message.
|
|
31
|
+
- Tree QoL: unchecking every skill disables the root (an enabled root with
|
|
32
|
+
an empty selection registered nothing anyway); Enter on a group row
|
|
33
|
+
folds it; the rejected-directory suppression no longer swallows sibling
|
|
34
|
+
directories that share a name prefix.
|
|
35
|
+
- Cleanup: shared `writeFileAtomic` reused by `writeConfigText`, static
|
|
36
|
+
imports in `util.ts`, duplicate `NAME_RE` removed, `addRoot` reuses
|
|
37
|
+
`isNativePath` (templates now recognized), magic literals replaced with
|
|
38
|
+
the exported limits, raw `scannedAt` sanitized for display, and the
|
|
39
|
+
exposure list in the awareness line is capped.
|
|
40
|
+
- New smoke regressions, each failing against the exact bug above:
|
|
41
|
+
template-root tree resilience, snapshot file-name preservation, and the
|
|
42
|
+
copyTreeInto entry budget.
|
|
43
|
+
|
|
44
|
+
## 1.0.0
|
|
45
|
+
|
|
46
|
+
Initial release.
|
|
47
|
+
|
|
48
|
+
- Single-file configuration: every library root (the registrar), approved
|
|
49
|
+
skill manifest, and extension setting lives in
|
|
50
|
+
`~/.pi/agent/simple-skills-manager.json` (JSONC; auto-created with
|
|
51
|
+
commented defaults and a seeded store root on first run; the store
|
|
52
|
+
directory `~/.pi/agent/managed-skills` is created alongside).
|
|
53
|
+
- Vocabulary: the *library* is all known skills; the *registrar* is the
|
|
54
|
+
corpus of directory roots, displayed flat in the TUI; the *store* is the
|
|
55
|
+
built-in root skills accumulate in.
|
|
56
|
+
- `/skills-manager` TUI: a tree navigator (roots → groups → skill leaves
|
|
57
|
+
with spacebar exposure checkboxes; Enter on a root opens its actions:
|
|
58
|
+
bulk enable/disable, scan-or-refresh, remove), adding skills to the store
|
|
59
|
+
(editor / .md import / wholesale directory import with an interactive
|
|
60
|
+
SKILL.md picker), adding roots (guided skills-dir, single-skill), the
|
|
61
|
+
scan-and-approve ceremony (which is also the adoption path for native
|
|
62
|
+
locations), per-skill edit (auto re-approval) and removal (store only),
|
|
63
|
+
root removal (unregister only), and extension settings (view, edit the
|
|
64
|
+
raw config file, reset to defaults).
|
|
65
|
+
- Groups as subdirectories: the first path segment under a root is the
|
|
66
|
+
display group — no group config, re-tagging is a directory move plus a
|
|
67
|
+
scan.
|
|
68
|
+
- Approval workflow: scans snapshot each skill's name, description, group
|
|
69
|
+
path, file inventory, executable-script flags, and a SHA-256 content
|
|
70
|
+
hash; the manifest is fingerprinted to the root identity. Drift between
|
|
71
|
+
the approved hash and the delivered bytes fails closed on every
|
|
72
|
+
invocation; changing the root path invalidates the manifest.
|
|
73
|
+
- Lazy loading: a root is never scanned merely because pi starts; the
|
|
74
|
+
first approved invocation opens the scan (cached per session).
|
|
75
|
+
- Exposure surfaces: namespaced tools `skill_<root>__<skill>` (bounded,
|
|
76
|
+
sanitized, collision-checked, hidden skills excluded) with prompt
|
|
77
|
+
snippets and harness awareness lines, plus `skill:<name>` extension
|
|
78
|
+
commands that shadow pi's native skill-command namespace, dispatch
|
|
79
|
+
before native expansion, load through the same drift-checked path, and
|
|
80
|
+
appear in the live autocomplete dropdown with group paths.
|
|
81
|
+
- Auto-adoption of native pi locations (`~/.pi/agent/skills`,
|
|
82
|
+
`~/.agents/skills`): existing directories are seeded into the registrar
|
|
83
|
+
automatically — disabled, unapproved, flagged — and rendered in the tree
|
|
84
|
+
with their natively-live skills as red unapproved-but-live leaves.
|
|
85
|
+
"Scan or refresh" adopts them; auto-detected roots cannot be dismissed
|
|
86
|
+
(they are real exposure) and are persisted to the file only once a save
|
|
87
|
+
blesses them. Mirroring registers nothing by itself: an auto-detected root
|
|
88
|
+
is disabled with no manifest.
|
|
89
|
+
- Path hygiene: `${workspace}`/`${home}`/`~` expansion, symlinked roots
|
|
90
|
+
and skills fail closed, scan budgets (entries, depth, per-file sizes),
|
|
91
|
+
mutable-location warnings, duplicate-name rejection with first-wins.
|
|
92
|
+
- Settings knobs: `maxResultBytes`, `maxResultLines`, `maxSkills`,
|
|
93
|
+
`toolNameLimit`, `maxSkillBytes`, `maxScanFiles` — all editable from the
|
|
94
|
+
TUI or the file.
|
|
95
|
+
- Smoke test covering both transports, validation invariants, single-file
|
|
96
|
+
discovery, per-entry isolation, lazy registration (tool registered
|
|
97
|
+
while its directory does not exist), native-tool preservation, command
|
|
98
|
+
registration, native mirror computation, and first-run starter creation,
|
|
99
|
+
plus regression suites for name-length invariants, malformed-manifest
|
|
100
|
+
safety, scan budget/maxSkills enforcement, and pathological sources.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The simple-mcp-manager authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# simple-skills-manager
|
|
2
|
+
|
|
3
|
+
A [pi](https://github.com/earendil-works/pi-mono) extension for exposing
|
|
4
|
+
**reviewed** skills to the model — with a scan-approval workflow, content
|
|
5
|
+
hashes that fail closed on drift, a tree navigator for the whole library,
|
|
6
|
+
and an honest mirror of pi's own native skill discovery.
|
|
7
|
+
|
|
8
|
+
One file, one command. Everything — library roots (the *registrar*),
|
|
9
|
+
approved skill manifests, and extension settings — lives in
|
|
10
|
+
`~/.pi/agent/simple-skills-manager.json`, and everything is manageable from
|
|
11
|
+
the `/skills-manager` TUI. Skills themselves are plain files: the built-in
|
|
12
|
+
*store* accumulates them at `~/.pi/agent/managed-skills`, and existing or
|
|
13
|
+
native directories are adopted without moving anything.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why this exists
|
|
18
|
+
|
|
19
|
+
pi already has native skills: drop a folder in `~/.agents/skills/` and pi
|
|
20
|
+
exposes it. Native discovery has zero ceremony — which is exactly the
|
|
21
|
+
point of divergence. A skill is **untrusted instructions the model will
|
|
22
|
+
follow, plus scripts it may execute, with your user permissions.** Native
|
|
23
|
+
pi's answer to "is this skill safe to expose?" is *"it's on your disk, so
|
|
24
|
+
yes."*
|
|
25
|
+
|
|
26
|
+
**"A skill is on your disk" never silently means "the model follows it."**
|
|
27
|
+
This extension keeps three decisions separate, on purpose:
|
|
28
|
+
|
|
29
|
+
1. **Configuration** — where a root is and how the library is scanned.
|
|
30
|
+
2. **Manifest approval** — which skill names, descriptions, and content
|
|
31
|
+
hashes a human actually inspected, fingerprinted to the exact root.
|
|
32
|
+
3. **Invocation** — whether the model may load a particular skill right now.
|
|
33
|
+
|
|
34
|
+
If you wrote every skill yourself and trust your own directories, native
|
|
35
|
+
pi is fine — this extension is opt-in governance for when the collection
|
|
36
|
+
grows, comes from third parties (Anthropic skills, pi-skills, Claude/Codex
|
|
37
|
+
imports), or you simply want to see and control what every chat is being
|
|
38
|
+
influenced by.
|
|
39
|
+
|
|
40
|
+
## What it adds over native pi skills
|
|
41
|
+
|
|
42
|
+
| | Native pi skills | simple-skills-manager |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Exposure gate | On disk → in every chat's system prompt at next startup. Zero ceremony. | Scan → approve (fingerprint-bound) → expose. |
|
|
45
|
+
| Content drift | Silent. | Hash-bound approval; changed content **fails closed** at invocation until re-approved. |
|
|
46
|
+
| Per-skill control | Minimal (frontmatter flag). | Tree navigator with spacebar exposure checkboxes, enable/disable, live state. |
|
|
47
|
+
| Management UI | None. | `/skills-manager`: grouped tree, rendered skill view, metadata (hash, file inventory, executable-script warnings), edit-in-place, remove, imports. |
|
|
48
|
+
| Invocation | `/skill:name` (user), read-on-demand (model). | Same `/skill:name` syntax (shadowed, drift-checked) **plus** namespaced tools the model calls; live autocomplete dropdown carries group paths. |
|
|
49
|
+
| Delivery hygiene | Raw file content. | Sanitized, bounded, collision-checked names (`skill_<root>__<skill>`), harness awareness lines. |
|
|
50
|
+
| Laziness | Frontmatter read for every chat at startup. | The root is not scanned until an approved skill is invoked. |
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Inside pi:
|
|
55
|
+
|
|
56
|
+
1. Run `/skills-manager` — a starter config and the store
|
|
57
|
+
(`~/.pi/agent/managed-skills`) are created on first run.
|
|
58
|
+
2. **Add a skill to the store**: write one in the editor, import an `.md`
|
|
59
|
+
file, or import a whole directory (if it has no `SKILL.md`, the TUI
|
|
60
|
+
prints its tree and asks which one it is).
|
|
61
|
+
3. Or **Add or import a library root**: point at any skills directory or a
|
|
62
|
+
single skill. Native pi locations need no adding — they appear
|
|
63
|
+
automatically when they exist.
|
|
64
|
+
4. The **scan ceremony** shows exactly what was found — descriptions, file
|
|
65
|
+
counts, executable-script warnings, rejections — and asks which skills
|
|
66
|
+
to expose.
|
|
67
|
+
5. Reload. Exposed skills appear as `skill_<root>__<skill>` tools for the
|
|
68
|
+
model and `skill:<name>` commands for you.
|
|
69
|
+
|
|
70
|
+
## Groups
|
|
71
|
+
|
|
72
|
+
Subdirectories of the store are groups — "subdirs as automatic tagging."
|
|
73
|
+
`managed-skills/research/arxiv/SKILL.md` displays as `research / arxiv` in
|
|
74
|
+
the tree; move the directory and it re-tags on the next scan. No group
|
|
75
|
+
config exists anywhere.
|
|
76
|
+
|
|
77
|
+
## The tree navigator
|
|
78
|
+
|
|
79
|
+
`/skills-manager` → *Open the library tree*: every root sits flat at the
|
|
80
|
+
top level (store first, auto-detected native roots included), groups nest
|
|
81
|
+
beneath, skills are leaves with `[√]`/`[ ]` checkboxes. **Spacebar toggles
|
|
82
|
+
exposure; unchecked means invisible to the model.** Enter inspects (metadata,
|
|
83
|
+
live drift check, full content, edit/remove for store skills; on roots:
|
|
84
|
+
overview plus bulk enable/disable, scan-or-refresh, and remove actions).
|
|
85
|
+
Red `⊘` leaves are **native mirrors** — skills pi is exposing through its own
|
|
86
|
+
discovery that cannot be revoked here; the footer explains how (remove the
|
|
87
|
+
files, or launch `pi --no-skills`).
|
|
88
|
+
|
|
89
|
+
## Native roots and the override story
|
|
90
|
+
|
|
91
|
+
- **Auto-adoption.** `~/.pi/agent/skills` and `~/.agents/skills` are
|
|
92
|
+
recognized automatically: if they exist on disk, they appear in the
|
|
93
|
+
registrar on the next load — disabled, unapproved, flagged
|
|
94
|
+
"Auto-detected" — showing their natively-live skills as red unscanned
|
|
95
|
+
leaves. *Scan or refresh* is the adoption ceremony. Auto-adoption never
|
|
96
|
+
creates exposure: nothing is enabled, approved, or even scanned without
|
|
97
|
+
an explicit act.
|
|
98
|
+
- **Command layer — fully ours.** Every exposed skill registers a
|
|
99
|
+
`skill:<name>` *extension* command; extension commands dispatch before
|
|
100
|
+
pi's native skill expansion, so the namespace is shadowed, with drift
|
|
101
|
+
checking pi doesn't have. The live autocomplete dropdown narrows as you
|
|
102
|
+
type, with `group/skill` paths in the descriptions.
|
|
103
|
+
- **Discovery layer — not overrideable by an extension.** If files sit in
|
|
104
|
+
`~/.pi/agent/skills/` or `~/.agents/skills/`, pi *will* list them in the
|
|
105
|
+
system prompt. The manager is the honest mirror: auto-detected roots
|
|
106
|
+
render their skills red and locked in the tree, drift-checked on our
|
|
107
|
+
surfaces, with unapproved-but-live skills visible.
|
|
108
|
+
- **Library-only mode — the full takeover:** launch `pi --no-skills` and
|
|
109
|
+
native discovery stops entirely. The mirror rule finds nothing, native
|
|
110
|
+
roots become fully gated, and the manager is the *sole* skills surface.
|
|
111
|
+
(The extension detects and displays which mode is live in the tree
|
|
112
|
+
footer.)
|
|
113
|
+
|
|
114
|
+
## Security model
|
|
115
|
+
|
|
116
|
+
Skills instruct the model and may include code it executes with your
|
|
117
|
+
permissions. Permission prompts are not an OS sandbox. This extension's
|
|
118
|
+
boundaries:
|
|
119
|
+
|
|
120
|
+
- scanning is read-only and bounded (file counts, depth, per-file sizes);
|
|
121
|
+
- symlinks fail closed (roots and skills); nothing is ever executed;
|
|
122
|
+
- exposure requires an approved manifest, and every invocation re-hashes the
|
|
123
|
+
**bytes being delivered** against the approval — drift fails closed;
|
|
124
|
+
- the manifest fingerprint binds the approval to the exact root
|
|
125
|
+
(name/transport/path) — move it and the approval is void;
|
|
126
|
+
- helper scripts are inventoried and shebang files flagged at inspection
|
|
127
|
+
(their content is not hash-bound; re-scan before trusting — same posture
|
|
128
|
+
as an MCP server whose code may change under bound schemas);
|
|
129
|
+
- skill text is sanitized and size-bounded on delivery;
|
|
130
|
+
- external and native roots are read-only to the manager; store deletions
|
|
131
|
+
are constrained to `managed-skills/`;
|
|
132
|
+
- hidden skills (`disable-model-invocation`) are command-only, mirroring
|
|
133
|
+
pi's semantics.
|
|
134
|
+
|
|
135
|
+
Review skill content; pin or vendor third-party skills; use a container or
|
|
136
|
+
VM when you need real isolation.
|
|
137
|
+
|
|
138
|
+
## Testing
|
|
139
|
+
|
|
140
|
+
No external model or untrusted network calls — local fixtures only:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm run typecheck # tsc --noEmit
|
|
144
|
+
npm test # tests/smoke.sh
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The smoke test exercises both transports end-to-end (discovery, grouping,
|
|
148
|
+
script flagging, rejections), validation invariants, config save/load
|
|
149
|
+
round-trips (upsert, remove, settings fallback, loud failure on a corrupt
|
|
150
|
+
file), manifest fingerprint binding, the full registered-tool execute path
|
|
151
|
+
(lazy scan at first call, reuse, fail-closed content drift), managed
|
|
152
|
+
discovery through pi's normal extension loading (per-entry isolation,
|
|
153
|
+
lazy registration against a *nonexistent* directory, native-tool
|
|
154
|
+
preservation, `skill:<name>` command registration), native mirror
|
|
155
|
+
computation, and first-run starter creation. Dedicated regression suites
|
|
156
|
+
cover the registered-name length invariant, defensive handling of a
|
|
157
|
+
malformed approved manifest, scan-budget and maxSkills enforcement,
|
|
158
|
+
pathological-source survival, copy-budget and snapshot file-name
|
|
159
|
+
invariants, and tree resilience against an unresolvable root-path
|
|
160
|
+
template — each verified to fail against the exact bug it guards.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
[MIT](./LICENSE)
|