@victortomaili/skill-cli 0.4.0 → 0.5.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 +24 -0
- package/README.md +7 -3
- package/package.json +1 -1
- package/src/commands/list.js +7 -9
- package/src/commands/manager.js +17 -16
- package/src/lib/agents-md.js +25 -6
- package/src/lib/format.js +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,30 @@ 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.5.1] - 2026-07-07
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- AGENTS.md bootstrap block hardened into hard rules after a live pi-agent test
|
|
18
|
+
surfaced two gaps. **(a) START GATE:** on the first user message, the agent's
|
|
19
|
+
VERY FIRST action — BEFORE ANYTHING ELSE (before thinking, before any tool call)
|
|
20
|
+
— MUST be `skill defaults` + `skill cat <name>` for each, then `skill list`
|
|
21
|
+
(discovery is mandatory, not optional). **(b) Context-altering = PROPOSE-ONLY:**
|
|
22
|
+
`active` / ★-default means *available*, not *applied* — propose ≠ auto-apply,
|
|
23
|
+
and the rule overrides any other skill's "always use" instruction. Also:
|
|
24
|
+
whenever a skill is loaded mid-session, re-evaluate the message and load/propose
|
|
25
|
+
any newly-relevant one.
|
|
26
|
+
|
|
27
|
+
## [0.5.0] - 2026-07-07
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- AGENTS.md bootstrap block now instructs the agent to (a) **discover & load relevant skills per message** (skills are not in context until loaded — match the message to a skill's triggers/topic, then `skill cat`), and (b) **propose — don't auto-apply — context-altering skills** (those that change HOW the agent responds, e.g. an output-style/mode skill): apply only after the user confirms (an explicit `/X` counts as confirmation).
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Manager + `skill list`: clearer activation UI. New **`source`** column shows *why* a skill is/isn't active in the current project — `global` (inherited default), `global·off` (default but denied here), `project` (project allow only), `—` (passive). The label is derived from `active` + `isDefault`, so it can never disagree with the ●/○ marker. The manager now also prints the project path + counts (`installed · active here · global defaults`) and an explicit legend; the `space` (per-project) vs `a` (global default) keys are labelled distinctly.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- `source` label: a global-default skill that is **also** explicitly enabled in the current project now shows `project` (the more-specific source), with ★ still marking its default membership. Previously `isDefault && active` short-circuited to `global`.
|
|
37
|
+
|
|
14
38
|
## [0.4.0] - 2026-07-07
|
|
15
39
|
|
|
16
40
|
### Changed (breaking — config format)
|
package/README.md
CHANGED
|
@@ -196,9 +196,13 @@ default; `space` toggles the per-project override.
|
|
|
196
196
|
`skill init -g` injects a short, idempotent block into each detected agent's global
|
|
197
197
|
instruction file (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`):
|
|
198
198
|
|
|
199
|
-
>
|
|
200
|
-
>
|
|
201
|
-
>
|
|
199
|
+
> **START GATE:** on the first user message, your VERY FIRST action — BEFORE
|
|
200
|
+
> ANYTHING ELSE (before thinking, before any tool call) — is `skill defaults` +
|
|
201
|
+
> `skill cat <name>` for each, then `skill list`. Then each
|
|
202
|
+
> message, discover/load relevant skills (`skill cat` / `skill trigger`), once each.
|
|
203
|
+
> A **context-altering** skill (changes HOW you respond — style/mode) is
|
|
204
|
+
> **PROPOSE-ONLY**: propose it, then apply only after the user confirms
|
|
205
|
+
> (`active` ≠ applied; an explicit `/X` counts). `/X` → `skill trigger X`.
|
|
202
206
|
|
|
203
207
|
It's wrapped in `<!-- BEGIN skill-cli --> … <!-- END skill-cli -->` markers, never
|
|
204
208
|
duplicates, and preserves your existing file content. Re-run `init -g` any time —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victortomaili/skill-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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/commands/list.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import c from 'picocolors'
|
|
2
2
|
import { listStore } from '../lib/store.js'
|
|
3
3
|
import { readGlobalConfig, readProjectConfig, computeEffective, computeDefaults } from '../lib/config.js'
|
|
4
|
-
import { trunc } from '../lib/format.js'
|
|
4
|
+
import { trunc, sourceLabel } from '../lib/format.js'
|
|
5
5
|
|
|
6
6
|
export function cmdList(_args = []) {
|
|
7
7
|
const installed = listStore()
|
|
@@ -22,19 +22,17 @@ export function cmdList(_args = []) {
|
|
|
22
22
|
for (const s of installed) {
|
|
23
23
|
const active = effective.includes(s.name)
|
|
24
24
|
const mark = active ? c.green('●') : c.gray('○')
|
|
25
|
-
const
|
|
26
|
-
const
|
|
25
|
+
const isDef = defaults.includes(s.name)
|
|
26
|
+
const isProjAllow = projCfg && (projCfg.allow || []).some(a => a.toLowerCase() === s.name.toLowerCase())
|
|
27
|
+
const star = isDef ? c.yellow('★') : c.gray('·')
|
|
28
|
+
const sc = sourceLabel(c, active, isDef, isProjAllow)
|
|
27
29
|
const trg = s.triggers.length ? ' ' + c.gray('/' + s.triggers.join(', /')) : ''
|
|
28
30
|
console.log(` ${mark} ${star} ${c.bold(s.name.padEnd(22))} ${c.gray(String(s.version).padEnd(8))} ${sc}${trg}`)
|
|
29
31
|
if (s.description) console.log(c.gray(' ' + trunc(s.description, 68)))
|
|
30
32
|
}
|
|
31
33
|
console.log()
|
|
32
|
-
console.log(c.green('● active ') + c.gray('○ passive ') + c.yellow('★
|
|
34
|
+
console.log(c.green('●') + c.gray(' active here ') + c.gray('○ passive ') + c.yellow('★') + c.gray(' global default'))
|
|
35
|
+
console.log(c.gray(' source: global = inherited default · project = on here only · global·off = default off here'))
|
|
33
36
|
console.log(c.cyan('Details: ') + 'skill show <name> ' + c.cyan('Load: ') + 'skill cat <name>')
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
function labelScope(name, globalCfg, projCfg) {
|
|
37
|
-
if (projCfg && (projCfg.allow || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.magenta('project')
|
|
38
|
-
if ((globalCfg.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.blue('global ')
|
|
39
|
-
return c.gray('- ')
|
|
40
|
-
}
|
package/src/commands/manager.js
CHANGED
|
@@ -6,16 +6,9 @@ import { STORE_DIR } from '../lib/paths.js'
|
|
|
6
6
|
import { listStore } from '../lib/store.js'
|
|
7
7
|
import { readGlobalConfig, writeGlobalConfig, readProjectConfig, writeProjectConfig, computeEffective, computeDefaults } from '../lib/config.js'
|
|
8
8
|
import { cleanConfig } from './remove.js'
|
|
9
|
-
import { trunc } from '../lib/format.js'
|
|
9
|
+
import { trunc, sourceLabel } from '../lib/format.js'
|
|
10
10
|
|
|
11
|
-
const KEYS = c.gray('↑↓ move · space toggle · a default · d delete · enter view · q/esc quit')
|
|
12
|
-
|
|
13
|
-
// where a skill's activation currently comes from (mirrors `skill list`)
|
|
14
|
-
function labelScope(name, g, p) {
|
|
15
|
-
if (p && (p.allow || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.magenta('project')
|
|
16
|
-
if ((g.defaults || []).some(a => a.toLowerCase() === name.toLowerCase())) return c.blue('global ')
|
|
17
|
-
return c.gray('- ')
|
|
18
|
-
}
|
|
11
|
+
const KEYS = c.gray('↑↓ move · space toggle here · a global default · d delete · enter view · q/esc quit')
|
|
19
12
|
|
|
20
13
|
// Pure decision: given the current configs, compute the new {allow, deny} arrays
|
|
21
14
|
// that flip `name`'s active state in THIS project. No I/O → unit-testable without
|
|
@@ -147,21 +140,29 @@ const managerPrompt = createPrompt((_config, done) => {
|
|
|
147
140
|
if (mode === 'view' && s) {
|
|
148
141
|
return prefix + ' ' + c.bold(s.name) + c.gray(' (any key to go back)') + '\n' + viewBody(s)
|
|
149
142
|
}
|
|
150
|
-
const lines = [
|
|
143
|
+
const lines = [
|
|
144
|
+
prefix + ' ' + c.bold('skill manager') + c.gray(' — project: ') + c.magenta(process.cwd()),
|
|
145
|
+
c.gray(' ' + installed.length + ' installed · ' + eff.length + ' active here · ' + defs.length + ' global default' + (defs.length === 1 ? '' : 's')),
|
|
146
|
+
'',
|
|
147
|
+
]
|
|
151
148
|
for (let i = 0; i < installed.length; i++) {
|
|
152
149
|
const sk = installed[i]
|
|
153
150
|
const active = eff.includes(sk.name)
|
|
151
|
+
const isDef = defs.includes(sk.name)
|
|
152
|
+
const isProjAllow = p && (p.allow || []).some(a => a.toLowerCase() === sk.name.toLowerCase())
|
|
154
153
|
const arrow = i === cur ? c.cyan('❯') : ' '
|
|
155
154
|
const mark = active ? c.green('●') : c.gray('○')
|
|
156
|
-
const star =
|
|
157
|
-
const
|
|
155
|
+
const star = isDef ? c.yellow('★') : c.gray('·')
|
|
156
|
+
const src = sourceLabel(c, active, isDef, isProjAllow)
|
|
158
157
|
const trg = sk.triggers.length ? ' ' + c.gray('/' + sk.triggers.join(', /')) : ''
|
|
159
|
-
const nameStr = sk.name.padEnd(
|
|
158
|
+
const nameStr = sk.name.padEnd(22)
|
|
160
159
|
const nameCol = i === cur ? c.bold(nameStr) : nameStr
|
|
161
|
-
lines.push(`${arrow} ${mark} ${star} ${nameCol} ${c.gray(String(sk.version || '').padEnd(
|
|
162
|
-
if (i === cur && sk.description) lines.push(c.gray(' ' + trunc(sk.description,
|
|
160
|
+
lines.push(`${arrow} ${mark} ${star} ${nameCol} ${c.gray(String(sk.version || '').padEnd(7))} ${src}${trg}`)
|
|
161
|
+
if (i === cur && sk.description) lines.push(c.gray(' ' + trunc(sk.description, 60)))
|
|
163
162
|
}
|
|
164
|
-
lines.push(c.gray(' ' + '─'.repeat(
|
|
163
|
+
lines.push(c.gray(' ' + '─'.repeat(62)))
|
|
164
|
+
lines.push(' ' + c.green('●') + c.gray(' active here ') + c.gray('○ passive ') + c.yellow('★') + c.gray(' global default ') + c.gray('· not default'))
|
|
165
|
+
lines.push(c.gray(' source: global = inherited default · project = on here only · global·off = default off here'))
|
|
165
166
|
lines.push(' ' + KEYS)
|
|
166
167
|
if (mode === 'confirm') {
|
|
167
168
|
lines.push('')
|
package/src/lib/agents-md.js
CHANGED
|
@@ -6,7 +6,8 @@ const BEGIN = '<!-- BEGIN skill-cli -->'
|
|
|
6
6
|
const END = '<!-- END skill-cli -->'
|
|
7
7
|
|
|
8
8
|
// Global instruction block — injected into each agent's global instruction file.
|
|
9
|
-
//
|
|
9
|
+
// Covers usage, defaults (auto-load), per-message discovery, and the
|
|
10
|
+
// propose-then-confirm rule for context-altering skills.
|
|
10
11
|
export const AGENTS_BLOCK = `## skill-cli
|
|
11
12
|
|
|
12
13
|
This machine uses the \`skill\` command to manage skills (instruction / workflow
|
|
@@ -15,18 +16,36 @@ NOT copied into agent directories (\`~/.claude\`, \`~/.codex\`, etc.) — so the
|
|
|
15
16
|
won't appear here.
|
|
16
17
|
|
|
17
18
|
Usage:
|
|
18
|
-
- \`skill list\` — skills installed
|
|
19
|
+
- \`skill list\` — skills installed + active in the current project (with triggers)
|
|
19
20
|
- \`skill show <name>\` — skill summary (path, triggers, version)
|
|
20
21
|
- \`skill cat <name>\` — load skill content into context
|
|
22
|
+
- \`skill default <name>\` — mark a skill as a global default (active + auto-load)
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
START GATE (mandatory): On the FIRST user message of a session, your VERY FIRST
|
|
25
|
+
action — BEFORE ANYTHING ELSE: before you start thinking/reasoning about the task
|
|
26
|
+
and before any tool call — MUST be to run \`skill defaults\` then
|
|
27
|
+
\`skill cat <name>\` for each result, then \`skill list\` to learn every skill's
|
|
28
|
+
triggers. Do this even if the task looks trivial: skills shape how you should
|
|
29
|
+
approach the task, so you cannot reason about it correctly until they are loaded.
|
|
30
|
+
|
|
31
|
+
Discovery (mandatory, every message): On EVERY user message, decide whether a
|
|
32
|
+
skill is relevant by matching the message against the trigger catalog from
|
|
33
|
+
\`skill list\`. If a relevant skill is not yet loaded, load it with \`skill cat
|
|
34
|
+
<name>\` (or \`skill trigger <keyword>\` to resolve a keyword). Load each skill only
|
|
35
|
+
ONCE per session. When you load or re-read ANY skill mid-session, immediately
|
|
36
|
+
re-evaluate the current message and load/propose any newly-relevant skill.
|
|
37
|
+
|
|
38
|
+
Context-altering skills (HARD RULE): Some skills change HOW you respond — output
|
|
39
|
+
style, tone, format, or a persistent mode (e.g. a terseness/compression style) —
|
|
40
|
+
rather than just adding task knowledge. These are PROPOSE-ONLY. You MUST propose
|
|
41
|
+
such a skill and apply it ONLY after the user confirms; an explicit \`/X\` trigger
|
|
42
|
+
counts as confirmation. \`active\` / \u2605-default means AVAILABLE, not APPLIED —
|
|
43
|
+
propose \u2260 auto-apply. This rule overrides any other skill's "always use" rule for
|
|
44
|
+
context-altering skills.
|
|
25
45
|
|
|
26
46
|
Triggers: when the user types \`/X\`, run \`skill trigger X\`.
|
|
27
47
|
- Single match → apply the output directly.
|
|
28
48
|
- Multiple matches → show the candidate list; load the right one with \`skill cat <name>\`.
|
|
29
|
-
- Load each skill only ONCE per session (avoid re-injecting).
|
|
30
49
|
`
|
|
31
50
|
|
|
32
51
|
export function injectBlock(content) {
|
package/src/lib/format.js
CHANGED
|
@@ -10,3 +10,17 @@ export function trunc(s, n) {
|
|
|
10
10
|
export function pad(s, n = 22) {
|
|
11
11
|
return (String(s) + ' '.repeat(n)).slice(0, n)
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
// WHY a skill is (or isn't) active in THIS project — source of activation.
|
|
15
|
+
// `hasProjectAllow` WINS: a skill explicitly enabled in this project shows
|
|
16
|
+
// `project` even when it's ALSO a global default (★ stays — it's a separate col).
|
|
17
|
+
// project = explicitly enabled in this project's allow-list (★ stays if default)
|
|
18
|
+
// global = inherited global default, active here
|
|
19
|
+
// global·off = global default, but this project denies it (passive here)
|
|
20
|
+
// — = passive
|
|
21
|
+
export function sourceLabel(c, active, isDefault, hasProjectAllow) {
|
|
22
|
+
if (hasProjectAllow) return c.magenta('project'.padEnd(10))
|
|
23
|
+
if (isDefault && active) return c.blue('global'.padEnd(10))
|
|
24
|
+
if (isDefault) return c.gray('global·off'.padEnd(10))
|
|
25
|
+
return c.gray('—'.padEnd(10))
|
|
26
|
+
}
|