@victortomaili/skill-cli 0.3.0 → 0.4.0
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 +12 -0
- package/README.md +34 -25
- package/package.json +1 -1
- package/src/cli.js +6 -6
- package/src/commands/defaults.js +29 -48
- package/src/commands/disable.js +4 -4
- package/src/commands/enable.js +3 -3
- package/src/commands/list.js +2 -2
- package/src/commands/manager.js +16 -16
- package/src/commands/remove.js +2 -2
- package/src/lib/agents-md.js +3 -3
- package/src/lib/config.js +28 -18
- package/src/lib/paths.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
- Cursor adapter (`.cursor/rules` format) for `init -g` bootstrap.
|
|
12
12
|
- Per-agent hook adapters for automatic `/X` triggering (push model).
|
|
13
13
|
|
|
14
|
+
## [0.4.0] - 2026-07-07
|
|
15
|
+
|
|
16
|
+
### Changed (breaking — config format)
|
|
17
|
+
- **Unified the "default" and "enabled" concepts into ONE global list.** Previously there were two independent global lists — `enabled_global` (skills active by default in every project) and `defaults_global` (skills auto-loaded on session start, independent of active state). Now there is a single `defaults` list in `config.yaml`: a default skill is BOTH active by default in every project AND auto-loaded on session start. This dissolves the confusion where the manager used a hidden `deny` to turn off a global skill — a default is simply the active-by-default set, and a project refines it with `allow` / `deny`.
|
|
18
|
+
- `skill default <name>` is now always GLOBAL (the `-g` flag is accepted but a no-op). `skill enable -g <name>` and `skill default <name>` now do the same thing; `skill disable -g` / `skill undefault` remove it.
|
|
19
|
+
- The per-project `defaults:` key in `skill.config` is removed — defaults are a global concept only. A project refines the active set with `allow` / `deny` / `inherit` (see Activation).
|
|
20
|
+
- **Automatic migration:** a legacy `config.yaml` with `enabled_global:` and/or `defaults_global:` is folded into `defaults:` (the union of both, deduped) on the next write. No manual steps.
|
|
21
|
+
- AGENTS.md bootstrap block: the "Defaults" line now describes the unified model (active by default + auto-load, global).
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `skill init -g` now bootstraps **pi** too — the AGENTS.md block is injected into `~/.pi/agent/AGENTS.md` (pi's global context file). Claude Code, Codex, Gemini, and pi are now all bootstrapped on a single re-run.
|
|
25
|
+
|
|
14
26
|
## [0.3.0] - 2026-07-07
|
|
15
27
|
|
|
16
28
|
### Added
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ config file.
|
|
|
19
19
|
- 🔌 **Universal sources** — `owner/repo`, GitHub/GitLab URL, git URL, local path, npm package (via `npx skills`).
|
|
20
20
|
- ⚡ **Pull-based** — agents pull skill content into context on demand via `skill trigger /X`. No hooks required.
|
|
21
21
|
- 🔍 **Interactive TUIs** — `skill search` to discover & multi-install from the skills registry; `skill` (no args) to manage every installed skill with the keyboard. TTY-only; agents & CI stay non-interactive.
|
|
22
|
-
- ⭐ **Default skills** —
|
|
22
|
+
- ⭐ **Default skills** — one global list: active by default in every project AND auto-loaded on session start.
|
|
23
23
|
- 🖥️ **Cross-platform** — Windows, macOS, Linux (handles the Windows `npx` spawn quirk for you).
|
|
24
24
|
- 🧪 **Well tested** — 225 unit + CLI tests, network-free by default.
|
|
25
25
|
|
|
@@ -58,12 +58,13 @@ That's it. Your agent now knows: *when the user types `/X`, run `skill trigger X
|
|
|
58
58
|
```
|
|
59
59
|
~/.skill-cli/
|
|
60
60
|
store/<skill>/SKILL.md ← one canonical store (skills live here)
|
|
61
|
-
config.yaml ← global
|
|
61
|
+
config.yaml ← global defaults (active by default + auto-load)
|
|
62
62
|
|
|
63
|
-
<project>/skill.config ← per-project
|
|
63
|
+
<project>/skill.config ← per-project overrides (inherit / deny / allow)
|
|
64
64
|
~/.claude/CLAUDE.md ← bootstrap block injected by `skill init -g`
|
|
65
65
|
~/.codex/AGENTS.md (idempotent — preserves your existing content)
|
|
66
66
|
~/.gemini/GEMINI.md
|
|
67
|
+
~/.pi/agent/AGENTS.md
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
Agents don't read `skill.config` directly — the CLI does. The agent only needs the
|
|
@@ -78,10 +79,10 @@ short bootstrap block telling it to run `skill` on `/X`.
|
|
|
78
79
|
| `skill install <source>` | Fetch skill(s) to the store (agent dirs untouched) |
|
|
79
80
|
| `skill search` | Interactive search & multi-install from the skills registry (TTY) |
|
|
80
81
|
| `skill` / `skill manager` | Interactive manager: toggle, default, delete, view (TTY) |
|
|
81
|
-
| `skill enable <name> [-g]` |
|
|
82
|
-
| `skill disable <name> [-g]` |
|
|
83
|
-
| `skill default <name
|
|
84
|
-
| `skill undefault <name
|
|
82
|
+
| `skill enable <name> [-g]` | Allow in project, or global default with `-g` |
|
|
83
|
+
| `skill disable <name> [-g]` | Deny in project, or remove global default with `-g` |
|
|
84
|
+
| `skill default <name>` | Mark a default skill (global: active + auto-load) |
|
|
85
|
+
| `skill undefault <name>` | Remove the default flag |
|
|
85
86
|
| `skill defaults` | List default skills (the command your agent runs on start) |
|
|
86
87
|
| `skill list` | Show installed + active skills (cwd-aware, ★ = default) |
|
|
87
88
|
| `skill show <name>` | Skill metadata (path, triggers, version) |
|
|
@@ -129,23 +130,26 @@ for a minimal, copy-paste template.
|
|
|
129
130
|
|
|
130
131
|
## Activation
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
One global concept, refined per project:
|
|
133
134
|
|
|
134
135
|
1. **installed** — present in the store (passive)
|
|
135
|
-
2. **
|
|
136
|
-
3. **
|
|
136
|
+
2. **default (global)** — listed in `config.yaml` `defaults:`; **active by default in every project** and **auto-loaded on agent session start**. Mark one with `skill default <name>` (or `skill enable -g`).
|
|
137
|
+
3. **per-project override** — a project's `skill.config` refines the set:
|
|
138
|
+
- `allow` — activate an otherwise-passive skill **in this project only**
|
|
139
|
+
- `deny` — turn off a default skill **in this project only**
|
|
140
|
+
- `inherit: false` — ignore global defaults entirely (only `allow` applies)
|
|
137
141
|
|
|
138
|
-
A project `skill.config
|
|
142
|
+
A project with **no `skill.config`** inherits the global defaults (all active). Pure
|
|
143
|
+
allowlist mode in a single project:
|
|
139
144
|
|
|
140
145
|
```yaml
|
|
141
146
|
# <project>/skill.config
|
|
142
|
-
inherit:
|
|
143
|
-
|
|
144
|
-
allow: [react-best-practices] # then open them one by one
|
|
147
|
+
inherit: false # ignore global defaults in this project
|
|
148
|
+
allow: [react-best-practices] # only this skill is active here
|
|
145
149
|
```
|
|
146
150
|
|
|
147
|
-
> `allow` always wins over `deny`, so `
|
|
148
|
-
> Matching is case-insensitive throughout.
|
|
151
|
+
> `allow` always wins over `deny`, so with `inherit: true` you can also write
|
|
152
|
+
> `deny: ["*"]` + `allow: [X]` = "only X here". Matching is case-insensitive throughout.
|
|
149
153
|
|
|
150
154
|
## Interactive mode (terminal only)
|
|
151
155
|
|
|
@@ -160,7 +164,7 @@ Opens a full-screen manager over your store:
|
|
|
160
164
|
|---|---|
|
|
161
165
|
| `↑` / `↓` | move |
|
|
162
166
|
| `space` | toggle active in the current project |
|
|
163
|
-
| `a` | toggle the default (auto-load)
|
|
167
|
+
| `a` | toggle the global default (active + auto-load) |
|
|
164
168
|
| `d` | delete (asks `y/N`) |
|
|
165
169
|
| `enter` | view the `SKILL.md` |
|
|
166
170
|
| `q` / `esc` | quit |
|
|
@@ -172,15 +176,20 @@ Type a query → browse live results from the [skills registry](https://skills.s
|
|
|
172
176
|
→ loops back so you can search again. `esc` or an empty query quits. `skill install`
|
|
173
177
|
with no source also opens it.
|
|
174
178
|
|
|
175
|
-
## Default skills
|
|
179
|
+
## Default skills
|
|
176
180
|
|
|
177
|
-
A
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
list them with `skill defaults`. The AGENTS.md bootstrap
|
|
181
|
-
run `skill defaults` then `skill cat <name>`
|
|
181
|
+
A **default** is a skill that is **active by default in every project** AND
|
|
182
|
+
**auto-loaded on agent session start** — one unified global list in `config.yaml`
|
|
183
|
+
(`defaults:`). Mark one with `skill default <name>`, remove with
|
|
184
|
+
`skill undefault <name>`, list them with `skill defaults`. The AGENTS.md bootstrap
|
|
185
|
+
block tells your agent to run `skill defaults` on start, then `skill cat <name>`
|
|
186
|
+
for each.
|
|
182
187
|
|
|
183
|
-
Defaults
|
|
188
|
+
Defaults are a **global** concept (never per-project). A project can still turn a
|
|
189
|
+
default off locally with `skill disable <name>` (a project `deny`), or activate a
|
|
190
|
+
non-default locally with `skill enable <name>` (a project `allow`) — see
|
|
191
|
+
[Activation](#activation). In the interactive manager, `a` toggles the global
|
|
192
|
+
default; `space` toggles the per-project override.
|
|
184
193
|
|
|
185
194
|
## Bootstrap
|
|
186
195
|
|
|
@@ -195,7 +204,7 @@ It's wrapped in `<!-- BEGIN skill-cli --> … <!-- END skill-cli -->` markers, n
|
|
|
195
204
|
duplicates, and preserves your existing file content. Re-run `init -g` any time —
|
|
196
205
|
it reports `already set up` and rewrites nothing.
|
|
197
206
|
|
|
198
|
-
**Supported agents:** Claude Code, Codex, Gemini. (Cursor uses `.cursor/rules` with
|
|
207
|
+
**Supported agents:** Claude Code, Codex, Gemini, and pi. (Cursor uses `.cursor/rules` with
|
|
199
208
|
a different format — adapter planned. See [issue tracker](https://github.com/victortomaili/skill-cli/issues).)
|
|
200
209
|
|
|
201
210
|
## Cross-platform
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victortomaili/skill-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Cross-agent skill manager — one global store, clean agent folders, activation via skill.config",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Victor Tomaili <victor@tomaili.com>",
|
package/src/cli.js
CHANGED
|
@@ -30,14 +30,14 @@ ${c.bold('Acquire skills')}
|
|
|
30
30
|
${c.cyan('skill search')} interactive search & multi-install (TTY only)
|
|
31
31
|
|
|
32
32
|
${c.bold('Activation')}
|
|
33
|
-
${c.cyan('skill enable')} ${c.gray('<name> [-g]')}
|
|
34
|
-
${c.cyan('skill disable')} ${c.gray('<name> [-g]')}
|
|
33
|
+
${c.cyan('skill enable')} ${c.gray('<name> [-g]')} allow in project, or global default (-g)
|
|
34
|
+
${c.cyan('skill disable')} ${c.gray('<name> [-g]')} deny in project, or remove global default (-g)
|
|
35
35
|
${c.cyan('skill list')} installed + active skills (cwd-aware)
|
|
36
36
|
|
|
37
|
-
${c.bold('Defaults (auto-load)')}
|
|
38
|
-
${c.cyan('skill default')} ${c.gray('<name>
|
|
39
|
-
${c.cyan('skill undefault')} ${c.gray('<name>
|
|
40
|
-
${c.cyan('skill defaults')}
|
|
37
|
+
${c.bold('Defaults (active + auto-load)')}
|
|
38
|
+
${c.cyan('skill default')} ${c.gray('<name>')} mark a default skill (global: active in every project)
|
|
39
|
+
${c.cyan('skill undefault')} ${c.gray('<name>')} remove the default flag
|
|
40
|
+
${c.cyan('skill defaults')} list default skills (agent runs this on start)
|
|
41
41
|
|
|
42
42
|
${c.bold('Usage (agent)')}
|
|
43
43
|
${c.cyan('skill show')} ${c.gray('<name>')} metadata + path + triggers
|
package/src/commands/defaults.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import c from 'picocolors'
|
|
2
2
|
import { listStore } from '../lib/store.js'
|
|
3
|
-
import { readGlobalConfig, writeGlobalConfig,
|
|
3
|
+
import { readGlobalConfig, writeGlobalConfig, computeDefaults } from '../lib/config.js'
|
|
4
4
|
import { trunc } from '../lib/format.js'
|
|
5
5
|
|
|
6
|
-
// `skill defaults` — list the skills marked as defaults
|
|
7
|
-
//
|
|
8
|
-
// agent
|
|
6
|
+
// `skill defaults` — list the skills marked as defaults. In the unified model a
|
|
7
|
+
// default skill is BOTH active-by-default in every project AND auto-loaded on
|
|
8
|
+
// agent session start (one global `defaults` list). This is the agent-facing
|
|
9
|
+
// command the AGENTS.md block tells the agent to run, then `skill cat <name>`
|
|
10
|
+
// for each. Defaults are a GLOBAL concept (never per-folder).
|
|
9
11
|
export function cmdDefaults() {
|
|
10
12
|
const installed = listStore()
|
|
11
13
|
const globalCfg = readGlobalConfig()
|
|
12
|
-
const
|
|
13
|
-
const eff = computeDefaults(installed, globalCfg, projCfg)
|
|
14
|
+
const eff = computeDefaults(installed, globalCfg)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
console.log(c.bold('skill defaults') + c.gray(' — auto-load on session start · project: ') + scope)
|
|
16
|
+
console.log(c.bold('skill defaults') + c.gray(' — active by default + auto-load on session start (global)'))
|
|
17
17
|
console.log()
|
|
18
18
|
|
|
19
19
|
if (eff.length === 0) {
|
|
@@ -31,14 +31,15 @@ export function cmdDefaults() {
|
|
|
31
31
|
console.log(c.gray('Load each into context: ') + c.cyan('skill cat <name>'))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// `skill default <name
|
|
35
|
-
//
|
|
34
|
+
// `skill default <name>` — mark a skill as a default (active by default in every
|
|
35
|
+
// project + auto-loaded on session start). Always GLOBAL. Requires the skill to
|
|
36
|
+
// be installed (a typo would silently "succeed" but never resolve). `-g`/`--global`
|
|
37
|
+
// is accepted for compatibility but is a no-op — defaults are inherently global.
|
|
36
38
|
export function cmdDefault(args) {
|
|
37
|
-
const global = args.includes('-g') || args.includes('--global')
|
|
38
39
|
const name = args.find(a => !a.startsWith('-'))
|
|
39
40
|
if (!name) {
|
|
40
|
-
console.error(c.red('Usage: skill default <name>
|
|
41
|
-
console.error(c.gray(' Marks a skill as a default
|
|
41
|
+
console.error(c.red('Usage: skill default <name>'))
|
|
42
|
+
console.error(c.gray(' Marks a skill as a default: active by default in every project + auto-load on session start.'))
|
|
42
43
|
process.exit(1)
|
|
43
44
|
}
|
|
44
45
|
if (!listStore().some(s => s.name.toLowerCase() === name.toLowerCase())) {
|
|
@@ -46,47 +47,27 @@ export function cmdDefault(args) {
|
|
|
46
47
|
console.error(c.gray(' Install first: skill install <source>'))
|
|
47
48
|
process.exit(1)
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
cfg.defaults_global.sort()
|
|
54
|
-
}
|
|
55
|
-
writeGlobalConfig(cfg)
|
|
56
|
-
console.log(c.green('✓') + ' default globally: ' + c.bold(name) + c.gray(' (auto-load in every project)'))
|
|
57
|
-
} else {
|
|
58
|
-
const cwd = process.cwd()
|
|
59
|
-
const cfg = readProjectConfig(cwd) || { inherit: true, deny: [], allow: [], defaults: [] }
|
|
60
|
-
if (!(cfg.defaults || []).some(d => d.toLowerCase() === name.toLowerCase())) cfg.defaults.push(name.toLowerCase())
|
|
61
|
-
writeProjectConfig(cwd, cfg)
|
|
62
|
-
console.log(c.green('✓') + ' default in project: ' + c.bold(name) + c.gray(' (' + cwd + ')'))
|
|
50
|
+
const cfg = readGlobalConfig()
|
|
51
|
+
if (!(cfg.defaults || []).some(d => d.toLowerCase() === name.toLowerCase())) {
|
|
52
|
+
cfg.defaults.push(name.toLowerCase())
|
|
53
|
+
cfg.defaults.sort()
|
|
63
54
|
}
|
|
55
|
+
writeGlobalConfig(cfg)
|
|
56
|
+
console.log(c.green('✓') + ' default (global): ' + c.bold(name) + c.gray(' (active + auto-load in every project)'))
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
// `skill undefault <name
|
|
59
|
+
// `skill undefault <name>` — remove the default flag (always global).
|
|
67
60
|
export function cmdUndefault(args) {
|
|
68
|
-
const global = args.includes('-g') || args.includes('--global')
|
|
69
61
|
const name = args.find(a => !a.startsWith('-'))
|
|
70
62
|
if (!name) {
|
|
71
|
-
console.error(c.red('Usage: skill undefault <name>
|
|
63
|
+
console.error(c.red('Usage: skill undefault <name>'))
|
|
72
64
|
process.exit(1)
|
|
73
65
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
: (c.gray('·') + ' not a global default: ' + c.bold(name) + c.gray(' (nothing to do)')))
|
|
82
|
-
} else {
|
|
83
|
-
const cwd = process.cwd()
|
|
84
|
-
const cfg = readProjectConfig(cwd) || { inherit: true, deny: [], allow: [], defaults: [] }
|
|
85
|
-
const had = (cfg.defaults || []).some(d => d.toLowerCase() === name.toLowerCase())
|
|
86
|
-
cfg.defaults = (cfg.defaults || []).filter(d => d.toLowerCase() !== name.toLowerCase())
|
|
87
|
-
writeProjectConfig(cwd, cfg)
|
|
88
|
-
console.log(had
|
|
89
|
-
? (c.green('✓') + ' removed project default: ' + c.bold(name))
|
|
90
|
-
: (c.gray('·') + ' not a project default: ' + c.bold(name) + c.gray(' (nothing to do)')))
|
|
91
|
-
}
|
|
66
|
+
const cfg = readGlobalConfig()
|
|
67
|
+
const had = (cfg.defaults || []).some(d => d.toLowerCase() === name.toLowerCase())
|
|
68
|
+
cfg.defaults = (cfg.defaults || []).filter(d => d.toLowerCase() !== name.toLowerCase())
|
|
69
|
+
writeGlobalConfig(cfg)
|
|
70
|
+
console.log(had
|
|
71
|
+
? (c.green('✓') + ' removed default: ' + c.bold(name))
|
|
72
|
+
: (c.gray('·') + ' not a default: ' + c.bold(name) + c.gray(' (nothing to do)')))
|
|
92
73
|
}
|
package/src/commands/disable.js
CHANGED
|
@@ -8,12 +8,12 @@ export function cmdDisable(args) {
|
|
|
8
8
|
|
|
9
9
|
if (global) {
|
|
10
10
|
const cfg = readGlobalConfig()
|
|
11
|
-
const had = (cfg.
|
|
12
|
-
cfg.
|
|
11
|
+
const had = (cfg.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())
|
|
12
|
+
cfg.defaults = (cfg.defaults || []).filter(a => a.toLowerCase() !== name.toLowerCase())
|
|
13
13
|
writeGlobalConfig(cfg)
|
|
14
14
|
console.log(had
|
|
15
|
-
? (c.green('✓') + '
|
|
16
|
-
: (c.gray('·') + ' not
|
|
15
|
+
? (c.green('✓') + ' removed global default: ' + c.bold(name))
|
|
16
|
+
: (c.gray('·') + ' not a global default: ' + c.bold(name) + c.gray(' (nothing to do)')))
|
|
17
17
|
} else {
|
|
18
18
|
const cwd = process.cwd()
|
|
19
19
|
const cfg = readProjectConfig(cwd) || { inherit: true, deny: [], allow: [] }
|
package/src/commands/enable.js
CHANGED
|
@@ -16,11 +16,11 @@ export function cmdEnable(args) {
|
|
|
16
16
|
|
|
17
17
|
if (global) {
|
|
18
18
|
const cfg = readGlobalConfig()
|
|
19
|
-
if (!cfg.
|
|
20
|
-
cfg.
|
|
19
|
+
if (!(cfg.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) {
|
|
20
|
+
cfg.defaults.push(name.toLowerCase()); cfg.defaults.sort()
|
|
21
21
|
}
|
|
22
22
|
writeGlobalConfig(cfg)
|
|
23
|
-
console.log(c.green('✓') + ' enabled globally: ' + c.bold(name) + c.gray(' (active in all projects)'))
|
|
23
|
+
console.log(c.green('✓') + ' enabled globally (default): ' + c.bold(name) + c.gray(' (active + auto-load in all projects)'))
|
|
24
24
|
} else {
|
|
25
25
|
const cwd = process.cwd()
|
|
26
26
|
const cfg = readProjectConfig(cwd) || { inherit: true, deny: [], allow: [] }
|
package/src/commands/list.js
CHANGED
|
@@ -8,7 +8,7 @@ export function cmdList(_args = []) {
|
|
|
8
8
|
const globalCfg = readGlobalConfig()
|
|
9
9
|
const projCfg = readProjectConfig()
|
|
10
10
|
const effective = computeEffective(installed, globalCfg, projCfg)
|
|
11
|
-
const defaults = computeDefaults(installed, globalCfg
|
|
11
|
+
const defaults = computeDefaults(installed, globalCfg)
|
|
12
12
|
|
|
13
13
|
const scope = projCfg ? c.magenta(process.cwd()) : c.gray('(global)')
|
|
14
14
|
console.log(c.bold('skill list') + c.gray(' — project: ') + scope)
|
|
@@ -35,6 +35,6 @@ export function cmdList(_args = []) {
|
|
|
35
35
|
|
|
36
36
|
function labelScope(name, globalCfg, projCfg) {
|
|
37
37
|
if (projCfg && (projCfg.allow || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.magenta('project')
|
|
38
|
-
if ((globalCfg.
|
|
38
|
+
if ((globalCfg.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.blue('global ')
|
|
39
39
|
return c.gray('- ')
|
|
40
40
|
}
|
package/src/commands/manager.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createPrompt, useState, useKeypress, usePrefix, isEnterKey, isUpKey, is
|
|
|
4
4
|
import c from 'picocolors'
|
|
5
5
|
import { STORE_DIR } from '../lib/paths.js'
|
|
6
6
|
import { listStore } from '../lib/store.js'
|
|
7
|
-
import { readGlobalConfig, readProjectConfig, writeProjectConfig, computeEffective, computeDefaults } from '../lib/config.js'
|
|
7
|
+
import { readGlobalConfig, writeGlobalConfig, readProjectConfig, writeProjectConfig, computeEffective, computeDefaults } from '../lib/config.js'
|
|
8
8
|
import { cleanConfig } from './remove.js'
|
|
9
9
|
import { trunc } from '../lib/format.js'
|
|
10
10
|
|
|
@@ -13,13 +13,13 @@ const KEYS = c.gray('↑↓ move · space toggle · a default · d delete · ent
|
|
|
13
13
|
// where a skill's activation currently comes from (mirrors `skill list`)
|
|
14
14
|
function labelScope(name, g, p) {
|
|
15
15
|
if (p && (p.allow || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.magenta('project')
|
|
16
|
-
if ((g.
|
|
16
|
+
if ((g.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.blue('global ')
|
|
17
17
|
return c.gray('- ')
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Pure decision: given the current configs, compute the new {allow, deny} arrays
|
|
21
21
|
// that flip `name`'s active state in THIS project. No I/O → unit-testable without
|
|
22
|
-
// SKILL_CLI_HOME.
|
|
22
|
+
// SKILL_CLI_HOME. The global `defaults` list is untouched (the manager toggle is project-scoped).
|
|
23
23
|
// passive → add to project allow (active here)
|
|
24
24
|
// active via project allow → remove from allow
|
|
25
25
|
// active via global only → add a local deny so it goes passive HERE
|
|
@@ -31,7 +31,7 @@ export function computeToggle(installed, globalCfg, projCfg, name) {
|
|
|
31
31
|
allow: [...(projCfg?.allow || [])],
|
|
32
32
|
}
|
|
33
33
|
const active = computeEffective(installed, globalCfg, p).includes(name)
|
|
34
|
-
const isGlobal = (globalCfg.
|
|
34
|
+
const isGlobal = (globalCfg.defaults || []).some(a => String(a).toLowerCase() === lc)
|
|
35
35
|
const isProjectAllow = p.allow.some(a => String(a).toLowerCase() === lc)
|
|
36
36
|
if (active) {
|
|
37
37
|
p.allow = p.allow.filter(a => String(a).toLowerCase() !== lc)
|
|
@@ -73,25 +73,25 @@ function viewBody(s) {
|
|
|
73
73
|
return shown.join('\n')
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// Pure decision: the new
|
|
77
|
-
// flag. (
|
|
78
|
-
|
|
76
|
+
// Pure decision: the new global `defaults` array after toggling `name`'s default
|
|
77
|
+
// flag. (Defaults are a GLOBAL concept — active by default + auto-load. The
|
|
78
|
+
// manager `a` key edits this list; `space` does the per-folder allow/deny.)
|
|
79
|
+
export function computeToggleDefault(list, name) {
|
|
79
80
|
const lc = String(name).toLowerCase()
|
|
80
|
-
const defs = [...(
|
|
81
|
+
const defs = [...(list || [])]
|
|
81
82
|
return defs.some(d => String(d).toLowerCase() === lc)
|
|
82
83
|
? defs.filter(d => String(d).toLowerCase() !== lc)
|
|
83
84
|
: [...defs, lc]
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
// Apply the default toggle to
|
|
87
|
-
function toggleDefault(name
|
|
87
|
+
// Apply the default toggle to the GLOBAL config and return a status line for the UI.
|
|
88
|
+
function toggleDefault(name) {
|
|
88
89
|
const installed = listStore()
|
|
89
90
|
const g = readGlobalConfig()
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return wasDefault ? (c.gray('· un-defaulted: ') + name) : (c.yellow('★ default: ') + name + c.gray(' (auto-load)'))
|
|
91
|
+
const wasDefault = computeDefaults(installed, g).includes(name)
|
|
92
|
+
g.defaults = computeToggleDefault(g.defaults, name)
|
|
93
|
+
writeGlobalConfig(g)
|
|
94
|
+
return wasDefault ? (c.gray('· un-defaulted: ') + name) : (c.yellow('★ default: ') + name + c.gray(' (active + auto-load)'))
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Clamp a cursor move so it can't leave the list bounds (pure → unit-tested).
|
|
@@ -115,7 +115,7 @@ const managerPrompt = createPrompt((_config, done) => {
|
|
|
115
115
|
const g = readGlobalConfig()
|
|
116
116
|
const p = readProjectConfig()
|
|
117
117
|
const eff = computeEffective(installed, g, p)
|
|
118
|
-
const defs = computeDefaults(installed, g
|
|
118
|
+
const defs = computeDefaults(installed, g)
|
|
119
119
|
const cur = installed.length ? Math.max(0, Math.min(cursor, installed.length - 1)) : 0
|
|
120
120
|
const s = installed[cur]
|
|
121
121
|
|
package/src/commands/remove.js
CHANGED
|
@@ -94,8 +94,8 @@ export function cmdRemove(args) {
|
|
|
94
94
|
// Exported so the interactive manager can reuse it on its `d` (delete) action.
|
|
95
95
|
export function cleanConfig(name) {
|
|
96
96
|
const g = readGlobalConfig()
|
|
97
|
-
if ((g.
|
|
98
|
-
g.
|
|
97
|
+
if ((g.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) {
|
|
98
|
+
g.defaults = (g.defaults || []).filter(a => a.toLowerCase() !== name.toLowerCase())
|
|
99
99
|
writeGlobalConfig(g)
|
|
100
100
|
}
|
|
101
101
|
const cwd = process.cwd()
|
package/src/lib/agents-md.js
CHANGED
|
@@ -19,9 +19,9 @@ Usage:
|
|
|
19
19
|
- \`skill show <name>\` — skill summary (path, triggers, version)
|
|
20
20
|
- \`skill cat <name>\` — load skill content into context
|
|
21
21
|
|
|
22
|
-
Defaults: on session start, run \`skill defaults\` to list skills marked
|
|
23
|
-
auto-load, then \`skill cat <name>\` for each. Mark
|
|
24
|
-
\`skill default <name>\` (
|
|
22
|
+
Defaults: on session start, run \`skill defaults\` to list skills marked as
|
|
23
|
+
defaults (active by default + auto-load), then \`skill cat <name>\` for each. Mark
|
|
24
|
+
your own with \`skill default <name>\` (global — active in every project).
|
|
25
25
|
|
|
26
26
|
Triggers: when the user types \`/X\`, run \`skill trigger X\`.
|
|
27
27
|
- Single match → apply the output directly.
|
package/src/lib/config.js
CHANGED
|
@@ -6,8 +6,7 @@ import { CLI_ROOT, GLOBAL_CONFIG, PROJECT_CONFIG, STORE_DIR } from './paths.js'
|
|
|
6
6
|
const DEFAULT_GLOBAL = {
|
|
7
7
|
version: 1,
|
|
8
8
|
store: STORE_DIR,
|
|
9
|
-
|
|
10
|
-
defaults_global: [],
|
|
9
|
+
defaults: [],
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export function readGlobalConfig() {
|
|
@@ -18,7 +17,20 @@ export function readGlobalConfig() {
|
|
|
18
17
|
process.stderr.write('skill-cli config: parse error (' + (e.message || e) + ') — using defaults\n')
|
|
19
18
|
return { ...DEFAULT_GLOBAL }
|
|
20
19
|
}
|
|
21
|
-
|
|
20
|
+
const merged = { ...DEFAULT_GLOBAL, ...(parsed || {}) }
|
|
21
|
+
// backward-compat: legacy configs split the active-by-default set
|
|
22
|
+
// (`enabled_global`) from the auto-load set (`defaults_global`). The unified
|
|
23
|
+
// model collapses them into one `defaults` list (a default skill is now BOTH
|
|
24
|
+
// active-by-default AND auto-loaded). On read, adopt the union of both legacy
|
|
25
|
+
// lists when no new-format `defaults` is present; `defaults` always wins.
|
|
26
|
+
if (!Array.isArray(merged.defaults) || merged.defaults.length === 0) {
|
|
27
|
+
const legacy = [
|
|
28
|
+
...(Array.isArray(parsed?.enabled_global) ? parsed.enabled_global : []),
|
|
29
|
+
...(Array.isArray(parsed?.defaults_global) ? parsed.defaults_global : []),
|
|
30
|
+
]
|
|
31
|
+
if (legacy.length) merged.defaults = [...new Set(legacy.map(String))]
|
|
32
|
+
}
|
|
33
|
+
return merged
|
|
22
34
|
}
|
|
23
35
|
|
|
24
36
|
export function writeGlobalConfig(cfg) {
|
|
@@ -29,8 +41,7 @@ export function writeGlobalConfig(cfg) {
|
|
|
29
41
|
const out = {
|
|
30
42
|
version: cfg.version ?? 1,
|
|
31
43
|
store: cfg.store ?? STORE_DIR,
|
|
32
|
-
|
|
33
|
-
defaults_global: cfg.defaults_global || [],
|
|
44
|
+
defaults: cfg.defaults || [],
|
|
34
45
|
}
|
|
35
46
|
fs.writeFileSync(GLOBAL_CONFIG, yaml.stringify(out), 'utf8')
|
|
36
47
|
}
|
|
@@ -49,7 +60,7 @@ export function readProjectConfig(cwd = process.cwd()) {
|
|
|
49
60
|
process.stderr.write('skill.config: parse error (' + (e.message || e) + ') — using global behavior\n')
|
|
50
61
|
return null
|
|
51
62
|
}
|
|
52
|
-
return { inherit: true, deny: [], allow: [],
|
|
63
|
+
return { inherit: true, deny: [], allow: [], ...(parsed || []) }
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export function writeProjectConfig(cwd, cfg) {
|
|
@@ -58,7 +69,6 @@ export function writeProjectConfig(cwd, cfg) {
|
|
|
58
69
|
inherit: cfg.inherit !== false,
|
|
59
70
|
deny: cfg.deny || [],
|
|
60
71
|
allow: cfg.allow || [],
|
|
61
|
-
defaults: cfg.defaults || [],
|
|
62
72
|
}
|
|
63
73
|
fs.writeFileSync(projectConfigPath(cwd), yaml.stringify(out), 'utf8')
|
|
64
74
|
}
|
|
@@ -81,7 +91,7 @@ function globMatch(pattern, name) {
|
|
|
81
91
|
export function computeEffective(installed, globalCfg, projCfg) {
|
|
82
92
|
const canonByLower = new Map(installed.map(s => [String(s.name).toLowerCase(), s.name]))
|
|
83
93
|
const enabled = new Set(
|
|
84
|
-
(projCfg && projCfg.inherit === false ? [] : (globalCfg.
|
|
94
|
+
(projCfg && projCfg.inherit === false ? [] : (globalCfg.defaults || []))
|
|
85
95
|
.map(s => String(s).toLowerCase())
|
|
86
96
|
)
|
|
87
97
|
if (projCfg) {
|
|
@@ -103,15 +113,15 @@ export function computeEffective(installed, globalCfg, projCfg) {
|
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
// Effective DEFAULT skills (auto-loaded on agent session start), as CANONICAL
|
|
106
|
-
// names.
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
116
|
+
// names. In the unified model the default list IS the globally-active-by-default
|
|
117
|
+
// set (one `defaults` key in config.yaml): a default skill is active in every
|
|
118
|
+
// project AND auto-loaded on start. So this just returns that list filtered to
|
|
119
|
+
// installed skills. Defaults are GLOBAL (never per-folder) and ignore a
|
|
120
|
+
// project's deny rules (deny only governs active state, not auto-load).
|
|
121
|
+
export function computeDefaults(installed, globalCfg) {
|
|
110
122
|
const canonByLower = new Map(installed.map(s => [String(s.name).toLowerCase(), s.name]))
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (projCfg) for (const d of (projCfg.defaults || [])) set.add(String(d).toLowerCase())
|
|
116
|
-
return [...set].filter(n => canonByLower.has(n)).map(n => canonByLower.get(n)).sort()
|
|
123
|
+
return [...new Set((globalCfg.defaults || []).map(s => String(s).toLowerCase()))]
|
|
124
|
+
.filter(n => canonByLower.has(n))
|
|
125
|
+
.map(n => canonByLower.get(n))
|
|
126
|
+
.sort()
|
|
117
127
|
}
|
package/src/lib/paths.js
CHANGED
|
@@ -17,4 +17,6 @@ export const AGENT_GLOBALS = [
|
|
|
17
17
|
{ id: 'claude', dir: '.claude', file: 'CLAUDE.md' },
|
|
18
18
|
{ id: 'codex', dir: '.codex', file: 'AGENTS.md' },
|
|
19
19
|
{ id: 'gemini', dir: '.gemini', file: 'GEMINI.md' },
|
|
20
|
+
// pi loads global context from ~/.pi/agent/AGENTS.md (or CLAUDE.md).
|
|
21
|
+
{ id: 'pi', dir: '.pi/agent', file: 'AGENTS.md' },
|
|
20
22
|
]
|