skillio 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -12
- package/dist/cli.js +333 -58
- package/package.json +3 -1
- /package/dist/shared/{chunk-j1p4zpqy.js → chunk-vwrhawsv.js} +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/skillio)
|
|
4
4
|
[](https://github.com/ihororlovskyi/skillio/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/ihororlovskyi/skillio/actions/workflows/codeql.yml)
|
|
6
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/ihororlovskyi/skillio)
|
|
7
|
+
[](https://codecov.io/gh/ihororlovskyi/skillio)
|
|
8
|
+
[](https://github.com/ihororlovskyi/skillio/blob/main/LICENSE)
|
|
9
|
+
[](https://www.npmjs.com/package/skillio)
|
|
5
10
|
|
|
6
11
|
Audit and manage AI agent skills for Claude Code and OpenAI Codex.
|
|
7
12
|
|
|
@@ -73,13 +78,16 @@ skillio # equivalent
|
|
|
73
78
|
# subcommands
|
|
74
79
|
skl ls # list skills per source with diffs
|
|
75
80
|
skl cost # ambient ballast cost (frontmatter tokens) per skill
|
|
76
|
-
skl
|
|
81
|
+
skl cs # alias for cost (also: cst)
|
|
77
82
|
skl usage # consumption: usage count × frontmatter tokens
|
|
78
83
|
skl usg # alias for usage
|
|
79
|
-
skl rm brainstorming #
|
|
84
|
+
skl rm brainstorming # delete on-disk dir; lock kept (Y/n prompt)
|
|
80
85
|
skl rm brainstorming writing-plans # remove multiple
|
|
86
|
+
skl rm . # remove all skills in scope (lock kept)
|
|
87
|
+
skl rm . -fl # remove all, including lock entries
|
|
81
88
|
skl rm --yes brainstorming # skip confirmation
|
|
82
89
|
skl rm --dry-run brainstorming # preview only
|
|
90
|
+
skl rm --force-lock brainstorming # also remove the lock entry (-fl)
|
|
83
91
|
|
|
84
92
|
# scope flags
|
|
85
93
|
skl -g # force global scope on any subcommand
|
|
@@ -114,7 +122,7 @@ skl usage -a claude -a codex # equivalent: repeated --agent flag
|
|
|
114
122
|
| `-h, --help` | — | Show help and exit |
|
|
115
123
|
| `-v, --version` | — | Show version and exit |
|
|
116
124
|
| `-g, --global` | `false` | Use global scope (ignore current directory) |
|
|
117
|
-
| `-p, --period` | `all` | Period for `usage`: `
|
|
125
|
+
| `-p, --period` | `all` | Period for `usage`: `60s`, `30m`, `12h`, `7d`, `2w`, `6mo`, `all` (note: `1m` = 1 minute, `1mo` = 30 days) |
|
|
118
126
|
| `-a, --agent` | both | Agent for `usage`: `claude-code` (alias `claude`), `codex` — pass both space-separated (`-a claude-code codex`) or repeat the flag |
|
|
119
127
|
|
|
120
128
|
### `skillio usage` / `us`
|
|
@@ -129,9 +137,9 @@ skillio usage --agent codex --mode activations
|
|
|
129
137
|
| Flag | Default | Description |
|
|
130
138
|
|------|---------|-------------|
|
|
131
139
|
| `-a, --agent` | both | `claude-code`/`claude`, `codex` |
|
|
132
|
-
| `-p, --period` | `all` | `
|
|
140
|
+
| `-p, --period` | `all` | `60s`, `30m`, `24h`, `7d`, `2w`, `6mo`, `all` |
|
|
133
141
|
| `--since` | — | `yyyy-mm-dd`, overrides `--period` |
|
|
134
|
-
| `--mode` | `
|
|
142
|
+
| `--mode` | `merged` (claude) / `activations` (codex) | `merged` \| `attributed` \| `activations` \| `mentions` |
|
|
135
143
|
| `--format` | `text` | `text` \| `json` |
|
|
136
144
|
| `-g, --global` | `false` | Force global scope (ignore current directory) |
|
|
137
145
|
| `--root` | — | Override agent sessions directory; implies global |
|
|
@@ -139,9 +147,10 @@ skillio usage --agent codex --mode activations
|
|
|
139
147
|
|
|
140
148
|
### Modes
|
|
141
149
|
|
|
142
|
-
- **`
|
|
143
|
-
- **`
|
|
144
|
-
- **`
|
|
150
|
+
- **`merged`** — per-session union of `attributed` and `activations` (`max` per skill). Default for Claude.
|
|
151
|
+
- **`attributed`** — entries with an `attributionSkill` field set by Claude Code.
|
|
152
|
+
- **`activations`** — explicit `Skill` tool invocations (Claude) or read-like `exec_command_end` events / `<skill>` XML (Codex). Default for Codex.
|
|
153
|
+
- **`mentions`** — skill paths (`foo/SKILL.md`) or `superpowers:name` strings found anywhere. Broadest signal; can include matches from prompts, specs, or documentation.
|
|
145
154
|
|
|
146
155
|
### `skillio list` / `ls`
|
|
147
156
|
|
|
@@ -150,7 +159,7 @@ skillio list # local skills-lock.json
|
|
|
150
159
|
skillio list --global # ~/.agents/.skill-lock.json
|
|
151
160
|
```
|
|
152
161
|
|
|
153
|
-
### `skillio cost` / `
|
|
162
|
+
### `skillio cost` / `cs`
|
|
154
163
|
|
|
155
164
|
```sh
|
|
156
165
|
skillio cost # local: per-skill frontmatter tokens with verdict
|
|
@@ -160,13 +169,37 @@ skillio cost --global # same, against ~/.agents/.skill-lock.json
|
|
|
160
169
|
### `skillio remove` / `rm`
|
|
161
170
|
|
|
162
171
|
```sh
|
|
163
|
-
skillio remove <skill-name>
|
|
172
|
+
skillio remove <skill-name> # delete on-disk dir; lock kept
|
|
164
173
|
skillio remove <skill-one> <skill-two>
|
|
174
|
+
skillio remove . # remove all skills in scope (lock kept)
|
|
175
|
+
skillio remove . -fl # remove all, including lock entries
|
|
176
|
+
skillio remove --force-lock <skill-name> # also remove the lock entry (alias -fl)
|
|
177
|
+
skillio remove --lock-only <skill-name> # only the lock entry; keep on disk
|
|
165
178
|
skillio remove --global <skill-name>
|
|
166
|
-
skillio remove --dry-run <skill-name>
|
|
167
|
-
skillio remove --yes <skill-name>
|
|
179
|
+
skillio remove --dry-run <skill-name> # preview only
|
|
180
|
+
skillio remove --yes <skill-name> # skip confirmation prompt
|
|
168
181
|
```
|
|
169
182
|
|
|
183
|
+
### Shell completion
|
|
184
|
+
|
|
185
|
+
`skl completion <shell>` prints a completion script. Sourced once in your
|
|
186
|
+
rc-file, it tab-completes subcommands and dynamic skill names for `skl rm`.
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
# bash (one-time setup)
|
|
190
|
+
skl completion bash >> ~/.bashrc
|
|
191
|
+
|
|
192
|
+
# zsh
|
|
193
|
+
skl completion zsh >> ~/.zshrc
|
|
194
|
+
|
|
195
|
+
# fish
|
|
196
|
+
skl completion fish | source # one-off in current shell
|
|
197
|
+
skl completion fish > ~/.config/fish/completions/skl.fish
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`skl list --names` prints one skill name per line (no headers, no colors) and
|
|
201
|
+
is what the completion script calls under the hood.
|
|
202
|
+
|
|
170
203
|
## Requirements
|
|
171
204
|
|
|
172
205
|
- Node.js ≥ 20
|
package/dist/cli.js
CHANGED
|
@@ -558,6 +558,166 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
558
558
|
return [`--${long}`, `-${short}`];
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
// src/commands/completion.ts
|
|
562
|
+
var BASH = `# skillio bash completion
|
|
563
|
+
# Install: source <(skl completion bash)
|
|
564
|
+
_skillio_completions() {
|
|
565
|
+
local cur prev words cword
|
|
566
|
+
COMPREPLY=()
|
|
567
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
568
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
569
|
+
|
|
570
|
+
local cmds="list ls remove rm cost cs cst usage us usg completion"
|
|
571
|
+
if [ "\${COMP_CWORD}" -eq 1 ]; then
|
|
572
|
+
COMPREPLY=( $(compgen -W "\${cmds} -h --help -v --version" -- "\${cur}") )
|
|
573
|
+
return 0
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
local sub="\${COMP_WORDS[1]}"
|
|
577
|
+
case "\${sub}" in
|
|
578
|
+
rm|remove)
|
|
579
|
+
if [[ "\${cur}" == -* ]]; then
|
|
580
|
+
COMPREPLY=( $(compgen -W "-g --global --dry-run -y --yes --force-lock -fl --lock-only -h --help" -- "\${cur}") )
|
|
581
|
+
else
|
|
582
|
+
local names
|
|
583
|
+
local scope=""
|
|
584
|
+
for w in "\${COMP_WORDS[@]}"; do
|
|
585
|
+
if [ "\${w}" = "-g" ] || [ "\${w}" = "--global" ]; then scope="-g"; fi
|
|
586
|
+
done
|
|
587
|
+
names="$(skl list --names \${scope} 2>/dev/null)"
|
|
588
|
+
COMPREPLY=( $(compgen -W "\${names}" -- "\${cur}") )
|
|
589
|
+
fi
|
|
590
|
+
return 0
|
|
591
|
+
;;
|
|
592
|
+
completion)
|
|
593
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
|
|
594
|
+
return 0
|
|
595
|
+
;;
|
|
596
|
+
esac
|
|
597
|
+
}
|
|
598
|
+
complete -F _skillio_completions skl
|
|
599
|
+
complete -F _skillio_completions skillio
|
|
600
|
+
`;
|
|
601
|
+
var ZSH = `# skillio zsh completion
|
|
602
|
+
# Install: source <(skl completion zsh)
|
|
603
|
+
_skillio() {
|
|
604
|
+
local -a cmds
|
|
605
|
+
cmds=(
|
|
606
|
+
'list:List skills per source'
|
|
607
|
+
'ls:Alias for list'
|
|
608
|
+
'remove:Delete on-disk skill dirs'
|
|
609
|
+
'rm:Alias for remove'
|
|
610
|
+
'cost:Show ambient ballast cost'
|
|
611
|
+
'cs:Alias for cost'
|
|
612
|
+
'cst:Alias for cost'
|
|
613
|
+
'usage:Show skill usage'
|
|
614
|
+
'us:Alias for usage'
|
|
615
|
+
'usg:Alias for usage'
|
|
616
|
+
'completion:Print shell completion script'
|
|
617
|
+
)
|
|
618
|
+
if (( CURRENT == 2 )); then
|
|
619
|
+
_describe 'command' cmds
|
|
620
|
+
return
|
|
621
|
+
fi
|
|
622
|
+
local sub=\${words[2]}
|
|
623
|
+
case $sub in
|
|
624
|
+
rm|remove)
|
|
625
|
+
if [[ \${words[CURRENT]} == -* ]]; then
|
|
626
|
+
_values 'flag' \\
|
|
627
|
+
'-g[global scope]' '--global[global scope]' \\
|
|
628
|
+
'--dry-run[print plan without deleting]' \\
|
|
629
|
+
'-y[skip confirmation]' '--yes[skip confirmation]' \\
|
|
630
|
+
'--force-lock[also remove lock entry]' '-fl[alias for --force-lock]' \\
|
|
631
|
+
'--lock-only[remove only lock entry, keep disk]'
|
|
632
|
+
else
|
|
633
|
+
local scope=""
|
|
634
|
+
for w in \${words[@]}; do
|
|
635
|
+
if [[ $w == "-g" || $w == "--global" ]]; then scope="-g"; fi
|
|
636
|
+
done
|
|
637
|
+
local -a names
|
|
638
|
+
names=(\${(f)"$(skl list --names $scope 2>/dev/null)"})
|
|
639
|
+
compadd -- $names
|
|
640
|
+
fi
|
|
641
|
+
;;
|
|
642
|
+
completion)
|
|
643
|
+
_values 'shell' bash zsh fish
|
|
644
|
+
;;
|
|
645
|
+
esac
|
|
646
|
+
}
|
|
647
|
+
compdef _skillio skl skillio
|
|
648
|
+
`;
|
|
649
|
+
var FISH = `# skillio fish completion
|
|
650
|
+
# Install: skl completion fish | source
|
|
651
|
+
function __skillio_skill_names
|
|
652
|
+
set -l scope ""
|
|
653
|
+
for w in (commandline -opc)
|
|
654
|
+
if test "$w" = "-g" -o "$w" = "--global"
|
|
655
|
+
set scope "-g"
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
skl list --names $scope 2>/dev/null
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
function __skillio_needs_command
|
|
662
|
+
set -l cmd (commandline -opc)
|
|
663
|
+
test (count $cmd) -le 1
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
function __skillio_using_subcommand
|
|
667
|
+
set -l cmd (commandline -opc)
|
|
668
|
+
if test (count $cmd) -lt 2; return 1; end
|
|
669
|
+
test "$cmd[2]" = "$argv[1]"
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
complete -c skl -n __skillio_needs_command -a 'list ls remove rm cost cs cst usage us usg completion'
|
|
673
|
+
complete -c skillio -n __skillio_needs_command -a 'list ls remove rm cost cs cst usage us usg completion'
|
|
674
|
+
|
|
675
|
+
for sub in rm remove
|
|
676
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
|
|
677
|
+
complete -c skillio -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
|
|
678
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -s g -l global -d 'Use global scope'
|
|
679
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l dry-run -d 'Print plan without deleting'
|
|
680
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -s y -l yes -d 'Skip confirmation prompt'
|
|
681
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l force-lock -d 'Also remove lock entry'
|
|
682
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -o fl -d 'Alias for --force-lock'
|
|
683
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l lock-only -d 'Remove only lock entry, keep disk'
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
for sub in completion
|
|
687
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -f -a 'bash zsh fish'
|
|
688
|
+
complete -c skillio -n "__skillio_using_subcommand $sub" -f -a 'bash zsh fish'
|
|
689
|
+
end
|
|
690
|
+
`;
|
|
691
|
+
var completionCommand = defineCommand({
|
|
692
|
+
meta: {
|
|
693
|
+
description: "Print shell completion script (bash, zsh, fish)"
|
|
694
|
+
},
|
|
695
|
+
args: {
|
|
696
|
+
shell: {
|
|
697
|
+
type: "positional",
|
|
698
|
+
required: true,
|
|
699
|
+
description: "Target shell: bash, zsh, or fish"
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
run({ args }) {
|
|
703
|
+
const shell = String(args.shell ?? "");
|
|
704
|
+
switch (shell) {
|
|
705
|
+
case "bash":
|
|
706
|
+
process.stdout.write(BASH);
|
|
707
|
+
return;
|
|
708
|
+
case "zsh":
|
|
709
|
+
process.stdout.write(ZSH);
|
|
710
|
+
return;
|
|
711
|
+
case "fish":
|
|
712
|
+
process.stdout.write(FISH);
|
|
713
|
+
return;
|
|
714
|
+
default:
|
|
715
|
+
console.error(`unknown shell: ${shell || "(none)"} — supported: bash, zsh, fish`);
|
|
716
|
+
process.exit(1);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
561
721
|
// src/commands/cost.ts
|
|
562
722
|
function classify(total) {
|
|
563
723
|
if (total < 1000)
|
|
@@ -608,7 +768,7 @@ var costCommand = defineCommand({
|
|
|
608
768
|
console.log(`${cyan(r.name)}${namePad} ${tokenCell}${tokenPad}${suffix}`);
|
|
609
769
|
}
|
|
610
770
|
console.log("");
|
|
611
|
-
console.log(`Total: ~${total} tok across ${rows.length} skills ${paint(message)}`);
|
|
771
|
+
console.log(`Total: ~${total} tok across ${rows.length} skills ${paint(message)} · method: chars/4, yaml-frontmatter`);
|
|
612
772
|
}
|
|
613
773
|
});
|
|
614
774
|
|
|
@@ -650,7 +810,12 @@ function bySource(records, roots, lockLabel) {
|
|
|
650
810
|
var listCommand = defineCommand({
|
|
651
811
|
meta: { description: "List skills per source with install-type coloring and lock orphan filter" },
|
|
652
812
|
args: {
|
|
653
|
-
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
|
|
813
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
814
|
+
names: {
|
|
815
|
+
type: "boolean",
|
|
816
|
+
default: false,
|
|
817
|
+
description: "Print one skill name per line (no header, no colors) — for completion scripts"
|
|
818
|
+
}
|
|
654
819
|
},
|
|
655
820
|
run({ args }) {
|
|
656
821
|
const lockPath = getLockPath(args.global);
|
|
@@ -663,6 +828,18 @@ var listCommand = defineCommand({
|
|
|
663
828
|
};
|
|
664
829
|
const lockLabel = args.global ? ".agents/.skill-lock.json" : "skills-lock.json";
|
|
665
830
|
const rows = bySource(records, roots, lockLabel);
|
|
831
|
+
if (args.names) {
|
|
832
|
+
const all = new Set;
|
|
833
|
+
for (const n of rows.agents.names)
|
|
834
|
+
all.add(n.name);
|
|
835
|
+
for (const n of rows.claude.names)
|
|
836
|
+
all.add(n.name);
|
|
837
|
+
for (const n of rows.lock.names)
|
|
838
|
+
all.add(n.name);
|
|
839
|
+
for (const name of [...all].sort())
|
|
840
|
+
console.log(name);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
666
843
|
console.log(args.global ? "Global" : "Local");
|
|
667
844
|
const claudeSet = new Set(rows.claude.names.map((n) => n.name));
|
|
668
845
|
const agentsSet = new Set(rows.agents.names.map((n) => n.name));
|
|
@@ -702,13 +879,9 @@ var listCommand = defineCommand({
|
|
|
702
879
|
const claudeNames = rows.claude.names.map((n) => n.name);
|
|
703
880
|
const agentsNames = rows.agents.names.map((n) => n.name);
|
|
704
881
|
const lockNames = rows.lock.names.map((n) => n.name);
|
|
705
|
-
const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
|
|
706
882
|
const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
|
|
707
883
|
const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
|
|
708
884
|
const diffs = [];
|
|
709
|
-
if (lockOnly.length) {
|
|
710
|
-
diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
|
|
711
|
-
}
|
|
712
885
|
if (claudeNotInLock.length) {
|
|
713
886
|
diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan).join(", ")}`);
|
|
714
887
|
}
|
|
@@ -855,17 +1028,28 @@ function fileCount(dir) {
|
|
|
855
1028
|
}
|
|
856
1029
|
return n;
|
|
857
1030
|
}
|
|
858
|
-
function printPlan(plan, modifyLock) {
|
|
1031
|
+
function printPlan(plan, modifyLock, lockOnly) {
|
|
859
1032
|
const { target } = plan;
|
|
860
1033
|
console.log(`Will remove ${q(target.name)}:`);
|
|
861
1034
|
if (target.inLock) {
|
|
862
|
-
if (modifyLock)
|
|
1035
|
+
if (lockOnly || modifyLock)
|
|
863
1036
|
console.log(" - skills-lock.json");
|
|
864
1037
|
else
|
|
865
1038
|
console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
|
|
866
1039
|
} else {
|
|
867
1040
|
console.log(" - skills-lock.json (not in lock)");
|
|
868
1041
|
}
|
|
1042
|
+
if (lockOnly) {
|
|
1043
|
+
if (target.claudeDir)
|
|
1044
|
+
console.log(` - .claude/skills/${target.name}/ (kept; --lock-only)`);
|
|
1045
|
+
else
|
|
1046
|
+
console.log(" - .claude/skills/ (not found)");
|
|
1047
|
+
if (target.agentsDir)
|
|
1048
|
+
console.log(` - .agents/skills/${target.name}/ (kept; --lock-only)`);
|
|
1049
|
+
else
|
|
1050
|
+
console.log(" - .agents/skills/ (not found)");
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
869
1053
|
if (target.claudeDir)
|
|
870
1054
|
console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
|
|
871
1055
|
else
|
|
@@ -883,19 +1067,35 @@ var removeCommand = defineCommand({
|
|
|
883
1067
|
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
884
1068
|
"dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
|
|
885
1069
|
yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
|
|
886
|
-
all: { type: "boolean", default: false, description: "Remove every skill in scope" },
|
|
887
1070
|
"force-lock": {
|
|
888
1071
|
type: "boolean",
|
|
889
1072
|
default: false,
|
|
890
1073
|
description: "Also remove entry from skills-lock.json (default is to keep lock untouched)"
|
|
1074
|
+
},
|
|
1075
|
+
"lock-only": {
|
|
1076
|
+
type: "boolean",
|
|
1077
|
+
default: false,
|
|
1078
|
+
description: "Remove only the skills-lock.json entry; keep on-disk directories"
|
|
891
1079
|
}
|
|
892
1080
|
},
|
|
893
1081
|
async run({ args }) {
|
|
894
|
-
const {
|
|
1082
|
+
const {
|
|
1083
|
+
global: isGlobal,
|
|
1084
|
+
"dry-run": dryRun,
|
|
1085
|
+
yes,
|
|
1086
|
+
"force-lock": modifyLock,
|
|
1087
|
+
"lock-only": lockOnly
|
|
1088
|
+
} = args;
|
|
1089
|
+
if (lockOnly && modifyLock) {
|
|
1090
|
+
console.error("--lock-only is mutually exclusive with --force-lock");
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
895
1093
|
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
896
|
-
const
|
|
1094
|
+
const rawNames = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
1095
|
+
const all = rawNames.includes(".");
|
|
1096
|
+
const names = rawNames.filter((n) => n !== ".");
|
|
897
1097
|
if (all && names.length > 0) {
|
|
898
|
-
console.error("
|
|
1098
|
+
console.error('"." (all skills) is mutually exclusive with positional skill names');
|
|
899
1099
|
process.exit(1);
|
|
900
1100
|
}
|
|
901
1101
|
if (!all && names.length === 0) {
|
|
@@ -920,43 +1120,63 @@ var removeCommand = defineCommand({
|
|
|
920
1120
|
agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
|
|
921
1121
|
}));
|
|
922
1122
|
for (const p of plans) {
|
|
923
|
-
printPlan(p, modifyLock);
|
|
1123
|
+
printPlan(p, modifyLock, lockOnly);
|
|
924
1124
|
console.log("");
|
|
925
1125
|
}
|
|
926
1126
|
if (dryRun)
|
|
927
1127
|
return;
|
|
928
1128
|
if (!yes) {
|
|
929
|
-
|
|
930
|
-
|
|
1129
|
+
let question = "Proceed?";
|
|
1130
|
+
if (all) {
|
|
1131
|
+
const subject = lockOnly ? `ALL ${plans.length} lock entries (disk preserved)` : `ALL ${plans.length} skills`;
|
|
1132
|
+
question = `Remove ${subject}?`;
|
|
1133
|
+
}
|
|
1134
|
+
const ok = await confirm(question);
|
|
931
1135
|
if (!ok) {
|
|
932
1136
|
console.log("Aborted");
|
|
933
1137
|
process.exit(1);
|
|
934
1138
|
}
|
|
935
1139
|
}
|
|
936
1140
|
const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
|
|
1141
|
+
const removed = (s) => red("removed") + s;
|
|
1142
|
+
const kept = (s) => green("kept") + s;
|
|
1143
|
+
const skipped = (s) => yellow("skipped") + s;
|
|
937
1144
|
for (const { target } of plans) {
|
|
1145
|
+
console.log("");
|
|
1146
|
+
console.log(q(target.name));
|
|
1147
|
+
if (lockOnly) {
|
|
1148
|
+
if (target.agentsDir)
|
|
1149
|
+
console.log(kept(" .agents/skills (--lock-only)"));
|
|
1150
|
+
else
|
|
1151
|
+
console.log(skipped(" .agents/skills (not found)"));
|
|
1152
|
+
if (target.claudeDir)
|
|
1153
|
+
console.log(kept(" .claude/skills (--lock-only)"));
|
|
1154
|
+
else
|
|
1155
|
+
console.log(skipped(" .claude/skills (not found)"));
|
|
1156
|
+
} else {
|
|
1157
|
+
if (target.agentsDir) {
|
|
1158
|
+
const r = rmSkillDir(target.agentsDir, { allowedRoots });
|
|
1159
|
+
console.log(removed(` from .agents/skills (${r.fileCount} files)`));
|
|
1160
|
+
} else {
|
|
1161
|
+
console.log(skipped(" .agents/skills (not found)"));
|
|
1162
|
+
}
|
|
1163
|
+
if (target.claudeDir) {
|
|
1164
|
+
const r = rmSkillDir(target.claudeDir, { allowedRoots });
|
|
1165
|
+
console.log(removed(` from .claude/skills (${r.fileCount} files)`));
|
|
1166
|
+
} else {
|
|
1167
|
+
console.log(skipped(" .claude/skills (not found)"));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
938
1170
|
if (target.inLock) {
|
|
939
|
-
if (modifyLock) {
|
|
1171
|
+
if (lockOnly || modifyLock) {
|
|
940
1172
|
const r = removeSkillFromLock(lockPath, target.name);
|
|
941
1173
|
if (r.removed)
|
|
942
|
-
console.log(
|
|
1174
|
+
console.log(removed(" from skills-lock.json"));
|
|
943
1175
|
} else {
|
|
944
|
-
console.log(
|
|
1176
|
+
console.log(kept(" in skills-lock.json"));
|
|
945
1177
|
}
|
|
946
1178
|
} else {
|
|
947
|
-
console.log(
|
|
948
|
-
}
|
|
949
|
-
if (target.claudeDir) {
|
|
950
|
-
const r = rmSkillDir(target.claudeDir, { allowedRoots });
|
|
951
|
-
console.log(`Removed ${q(target.name)} from .claude/skills (${r.fileCount} files)`);
|
|
952
|
-
} else {
|
|
953
|
-
console.log("Skipped .claude/skills (not found)");
|
|
954
|
-
}
|
|
955
|
-
if (target.agentsDir) {
|
|
956
|
-
const r = rmSkillDir(target.agentsDir, { allowedRoots });
|
|
957
|
-
console.log(`Removed ${q(target.name)} from .agents/skills (${r.fileCount} files)`);
|
|
958
|
-
} else {
|
|
959
|
-
console.log("Skipped .agents/skills (not found)");
|
|
1179
|
+
console.log(skipped(" skills-lock.json (not in lock)"));
|
|
960
1180
|
}
|
|
961
1181
|
}
|
|
962
1182
|
}
|
|
@@ -1146,7 +1366,7 @@ function extractClaudeMentions(entry) {
|
|
|
1146
1366
|
}
|
|
1147
1367
|
for (const m of node.matchAll(/\bsuperpowers:([a-z0-9-]+)\b/g)) {
|
|
1148
1368
|
if (m[1] !== undefined)
|
|
1149
|
-
seen.add(
|
|
1369
|
+
seen.add(m[1]);
|
|
1150
1370
|
}
|
|
1151
1371
|
});
|
|
1152
1372
|
return [...seen];
|
|
@@ -1217,7 +1437,7 @@ function* findJsonlFiles(dir, since) {
|
|
|
1217
1437
|
}
|
|
1218
1438
|
function isRecentEntry(entry, since) {
|
|
1219
1439
|
if (typeof entry !== "object" || entry === null)
|
|
1220
|
-
return
|
|
1440
|
+
return false;
|
|
1221
1441
|
const e = entry;
|
|
1222
1442
|
if (typeof e.timestamp === "string") {
|
|
1223
1443
|
const d = new Date(e.timestamp);
|
|
@@ -1225,7 +1445,7 @@ function isRecentEntry(entry, since) {
|
|
|
1225
1445
|
}
|
|
1226
1446
|
if (typeof e.ts === "number")
|
|
1227
1447
|
return new Date(e.ts * 1000) >= since;
|
|
1228
|
-
return
|
|
1448
|
+
return false;
|
|
1229
1449
|
}
|
|
1230
1450
|
|
|
1231
1451
|
// src/readers/claude.ts
|
|
@@ -1240,6 +1460,7 @@ function readClaudeUsage(options) {
|
|
|
1240
1460
|
let prevSkill = null;
|
|
1241
1461
|
const sessionAttr = new Map;
|
|
1242
1462
|
const sessionAct = new Map;
|
|
1463
|
+
const sessionMen = new Map;
|
|
1243
1464
|
for (const line of readFileSync2(file, "utf8").split(`
|
|
1244
1465
|
`)) {
|
|
1245
1466
|
if (!line.trim())
|
|
@@ -1271,7 +1492,7 @@ function readClaudeUsage(options) {
|
|
|
1271
1492
|
}
|
|
1272
1493
|
if (options.mode === "mentions") {
|
|
1273
1494
|
for (const skill of extractClaudeMentions(entry)) {
|
|
1274
|
-
|
|
1495
|
+
sessionMen.set(skill, (sessionMen.get(skill) ?? 0) + 1);
|
|
1275
1496
|
}
|
|
1276
1497
|
}
|
|
1277
1498
|
}
|
|
@@ -1281,6 +1502,9 @@ function readClaudeUsage(options) {
|
|
|
1281
1502
|
} else if (options.mode === "activations") {
|
|
1282
1503
|
for (const [k, v] of sessionAct)
|
|
1283
1504
|
counts.set(k, (counts.get(k) ?? 0) + v);
|
|
1505
|
+
} else if (options.mode === "mentions") {
|
|
1506
|
+
for (const [k, v] of sessionMen)
|
|
1507
|
+
counts.set(k, (counts.get(k) ?? 0) + v);
|
|
1284
1508
|
} else if (options.mode === "merged") {
|
|
1285
1509
|
const keys = new Set([...sessionAttr.keys(), ...sessionAct.keys()]);
|
|
1286
1510
|
for (const k of keys) {
|
|
@@ -1418,19 +1642,21 @@ var SECOND_MS = 1000;
|
|
|
1418
1642
|
var MINUTE_MS = 60 * SECOND_MS;
|
|
1419
1643
|
var HOUR_MS = 60 * MINUTE_MS;
|
|
1420
1644
|
var DAY_MS = 24 * HOUR_MS;
|
|
1645
|
+
var MONTH_MS = 30 * DAY_MS;
|
|
1421
1646
|
var UNITS_MS = {
|
|
1422
1647
|
s: SECOND_MS,
|
|
1423
1648
|
m: MINUTE_MS,
|
|
1424
1649
|
h: HOUR_MS,
|
|
1425
1650
|
d: DAY_MS,
|
|
1426
|
-
w: 7 * DAY_MS
|
|
1651
|
+
w: 7 * DAY_MS,
|
|
1652
|
+
mo: MONTH_MS
|
|
1427
1653
|
};
|
|
1428
1654
|
function parsePeriod(period) {
|
|
1429
1655
|
if (period === "all")
|
|
1430
1656
|
return Number.POSITIVE_INFINITY;
|
|
1431
|
-
const match = period.match(/^(\d+)([smhdw])$/);
|
|
1657
|
+
const match = period.match(/^(\d+)(mo|[smhdw])$/);
|
|
1432
1658
|
if (!match) {
|
|
1433
|
-
throw new Error(`Invalid period: "${period}". Use values like 60s, 30m, 24h, 30d, 2w, all.`);
|
|
1659
|
+
throw new Error(`Invalid period: "${period}". Use values like 60s, 30m, 24h, 30d, 2w, 6mo, all.`);
|
|
1434
1660
|
}
|
|
1435
1661
|
const unit = UNITS_MS[match[2] ?? ""] ?? 0;
|
|
1436
1662
|
return Number(match[1]) * unit;
|
|
@@ -1441,7 +1667,8 @@ function pad(n, width) {
|
|
|
1441
1667
|
return String(n).padStart(width);
|
|
1442
1668
|
}
|
|
1443
1669
|
function formatUsageRow(row) {
|
|
1444
|
-
|
|
1670
|
+
const suffix = row.installed === false ? ` ${red("(missing)")}` : "";
|
|
1671
|
+
return `${pad(row.count, row.countWidth)} ${cyan(row.name)}${suffix}`;
|
|
1445
1672
|
}
|
|
1446
1673
|
function parseAgents(agent) {
|
|
1447
1674
|
if (!agent)
|
|
@@ -1465,7 +1692,7 @@ var usageArgs = {
|
|
|
1465
1692
|
type: "string",
|
|
1466
1693
|
alias: "p",
|
|
1467
1694
|
default: "all",
|
|
1468
|
-
description: "60s, 30m, 24h, 30d, 2w, all"
|
|
1695
|
+
description: "60s, 30m, 24h, 30d, 2w, 6mo, all"
|
|
1469
1696
|
},
|
|
1470
1697
|
since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
|
|
1471
1698
|
mode: {
|
|
@@ -1534,15 +1761,13 @@ async function runUsage(args) {
|
|
|
1534
1761
|
name,
|
|
1535
1762
|
count: counts.get(name) ?? 0,
|
|
1536
1763
|
tokens: rec?.frontmatterTokens,
|
|
1537
|
-
|
|
1764
|
+
installed: rec !== undefined && rec.status !== "missing"
|
|
1538
1765
|
};
|
|
1539
1766
|
});
|
|
1540
1767
|
const rows = allRows.filter((r) => r.count > 0);
|
|
1541
1768
|
rows.sort((a, b) => {
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
if (aOk !== bOk)
|
|
1545
|
-
return aOk ? -1 : 1;
|
|
1769
|
+
if (a.installed !== b.installed)
|
|
1770
|
+
return a.installed ? -1 : 1;
|
|
1546
1771
|
if (b.count !== a.count)
|
|
1547
1772
|
return b.count - a.count;
|
|
1548
1773
|
return a.name.localeCompare(b.name);
|
|
@@ -1558,7 +1783,8 @@ async function runUsage(args) {
|
|
|
1558
1783
|
skill: r.name,
|
|
1559
1784
|
count: r.count,
|
|
1560
1785
|
tokensPerSkill: r.tokens ?? null,
|
|
1561
|
-
consumption: (r.tokens ?? 0) * r.count
|
|
1786
|
+
consumption: (r.tokens ?? 0) * r.count,
|
|
1787
|
+
installed: r.installed
|
|
1562
1788
|
}))
|
|
1563
1789
|
}));
|
|
1564
1790
|
console.log(JSON.stringify(output.length === 1 ? output[0] : output, null, 2));
|
|
@@ -1577,7 +1803,7 @@ async function runUsage(args) {
|
|
|
1577
1803
|
continue;
|
|
1578
1804
|
const countWidth = Math.max(...rows.map((r) => String(r.count).length));
|
|
1579
1805
|
for (const r of rows) {
|
|
1580
|
-
console.log(formatUsageRow({ count: r.count, name: r.name, countWidth }));
|
|
1806
|
+
console.log(formatUsageRow({ count: r.count, name: r.name, countWidth, installed: r.installed }));
|
|
1581
1807
|
distinct.add(r.name);
|
|
1582
1808
|
}
|
|
1583
1809
|
grandActivations += activations;
|
|
@@ -1724,11 +1950,12 @@ var SUBCOMMAND_NAMES = new Set([
|
|
|
1724
1950
|
"remove",
|
|
1725
1951
|
"rm",
|
|
1726
1952
|
"cost",
|
|
1727
|
-
"
|
|
1953
|
+
"cs",
|
|
1728
1954
|
"cst",
|
|
1729
1955
|
"usage",
|
|
1730
1956
|
"us",
|
|
1731
|
-
"usg"
|
|
1957
|
+
"usg",
|
|
1958
|
+
"completion"
|
|
1732
1959
|
]);
|
|
1733
1960
|
function reorderRootFlagsToSubcommand(argv) {
|
|
1734
1961
|
const tail = argv.slice(2);
|
|
@@ -1742,7 +1969,10 @@ function reorderRootFlagsToSubcommand(argv) {
|
|
|
1742
1969
|
return argv;
|
|
1743
1970
|
return [argv[0] ?? "", argv[1] ?? "", sub, ...before, ...after];
|
|
1744
1971
|
}
|
|
1745
|
-
|
|
1972
|
+
function normalizeShortFlags(argv) {
|
|
1973
|
+
return argv.map((tok) => tok === "-fl" ? "--force-lock" : tok);
|
|
1974
|
+
}
|
|
1975
|
+
process.argv = reorderRootFlagsToSubcommand(normalizeShortFlags(mergeAgentArgs(process.argv)));
|
|
1746
1976
|
function printRootHelp() {
|
|
1747
1977
|
const lines = [
|
|
1748
1978
|
`Audit and manage AI agent skills (skillio v${version})`,
|
|
@@ -1754,15 +1984,16 @@ function printRootHelp() {
|
|
|
1754
1984
|
" -h, --help Show this help and exit",
|
|
1755
1985
|
" -v, --version Show version and exit",
|
|
1756
1986
|
" -g, --global Use global scope (default: false)",
|
|
1757
|
-
" -p, --period Period for `usage`: 60s, 30m, 24h, 30d, 2w, all (default: all)",
|
|
1987
|
+
" -p, --period Period for `usage`: 60s, 30m, 24h, 30d, 2w, 6mo, all (default: all)",
|
|
1758
1988
|
" -a, --agent Agent for `usage`: claude-code, codex (default: both)",
|
|
1759
1989
|
"",
|
|
1760
1990
|
"COMMANDS",
|
|
1761
1991
|
"",
|
|
1762
|
-
" list, ls List skills per source
|
|
1763
|
-
" remove, rm
|
|
1764
|
-
" cost,
|
|
1765
|
-
" usage, us, usg Show skill usage × cost (consumption) with missed rows"
|
|
1992
|
+
" list, ls List skills per source: install type, lock orphans, disk/lock diff",
|
|
1993
|
+
" remove, rm Delete on-disk skill dirs; lock kept unless --force-lock",
|
|
1994
|
+
" cost, cs, cst Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
|
|
1995
|
+
" usage, us, usg Show skill usage × cost (consumption) with missed rows",
|
|
1996
|
+
" completion Print shell completion script (bash, zsh, fish)"
|
|
1766
1997
|
];
|
|
1767
1998
|
console.log(lines.join(`
|
|
1768
1999
|
`));
|
|
@@ -1774,6 +2005,45 @@ function isRootHelp(argv) {
|
|
|
1774
2005
|
return false;
|
|
1775
2006
|
return args.includes("--help") || args.includes("-h");
|
|
1776
2007
|
}
|
|
2008
|
+
function isRemoveHelp(argv) {
|
|
2009
|
+
const args = argv.slice(2);
|
|
2010
|
+
const first = args[0];
|
|
2011
|
+
if (first !== "remove" && first !== "rm")
|
|
2012
|
+
return false;
|
|
2013
|
+
return args.includes("--help") || args.includes("-h");
|
|
2014
|
+
}
|
|
2015
|
+
function printRemoveHelp() {
|
|
2016
|
+
const lines = [
|
|
2017
|
+
"Remove skills from on-disk dirs (lock preserved unless --force-lock).",
|
|
2018
|
+
"",
|
|
2019
|
+
"USAGE skillio remove [SKILL...] [OPTIONS]",
|
|
2020
|
+
" skillio rm [SKILL...] [OPTIONS]",
|
|
2021
|
+
"",
|
|
2022
|
+
"ARGUMENTS",
|
|
2023
|
+
"",
|
|
2024
|
+
' SKILL... One or more skill names. Use "." to target every skill in scope.',
|
|
2025
|
+
"",
|
|
2026
|
+
"OPTIONS",
|
|
2027
|
+
"",
|
|
2028
|
+
" -g, --global Use global scope (default: false)",
|
|
2029
|
+
" --dry-run Print plan without deleting",
|
|
2030
|
+
' -y, --yes Skip confirmation prompt (non-TTY only for ".")',
|
|
2031
|
+
" --force-lock Also remove entry from skills-lock.json (default: lock preserved)",
|
|
2032
|
+
" -fl Alias for --force-lock",
|
|
2033
|
+
" --lock-only Remove only the lock entry; keep on-disk directories",
|
|
2034
|
+
"",
|
|
2035
|
+
"EXAMPLES",
|
|
2036
|
+
"",
|
|
2037
|
+
" skillio rm brainstorming",
|
|
2038
|
+
" skillio rm brainstorming writing-plans --yes",
|
|
2039
|
+
" skillio rm . --dry-run",
|
|
2040
|
+
" skillio rm . -fl",
|
|
2041
|
+
" skillio rm --force-lock obsolete-skill",
|
|
2042
|
+
" skillio rm --lock-only stale-entry"
|
|
2043
|
+
];
|
|
2044
|
+
console.log(lines.join(`
|
|
2045
|
+
`));
|
|
2046
|
+
}
|
|
1777
2047
|
function firstPositional(argv) {
|
|
1778
2048
|
for (let i = 2;i < argv.length; i++) {
|
|
1779
2049
|
const tok = argv[i];
|
|
@@ -1819,7 +2089,7 @@ var main = defineCommand({
|
|
|
1819
2089
|
return;
|
|
1820
2090
|
const interactive = process.stdout.isTTY && process.stdin.isTTY;
|
|
1821
2091
|
if (interactive) {
|
|
1822
|
-
const { runPicker } = await import("./shared/chunk-
|
|
2092
|
+
const { runPicker } = await import("./shared/chunk-vwrhawsv.js");
|
|
1823
2093
|
const status = await runPicker({
|
|
1824
2094
|
global: args.global ?? false
|
|
1825
2095
|
});
|
|
@@ -1837,11 +2107,12 @@ var main = defineCommand({
|
|
|
1837
2107
|
remove: removeCommand,
|
|
1838
2108
|
rm: removeCommand,
|
|
1839
2109
|
cost: costCommand,
|
|
1840
|
-
|
|
2110
|
+
cs: costCommand,
|
|
1841
2111
|
cst: costCommand,
|
|
1842
2112
|
usage: usageCommand,
|
|
1843
2113
|
us: usageCommand,
|
|
1844
|
-
usg: usageCommand
|
|
2114
|
+
usg: usageCommand,
|
|
2115
|
+
completion: completionCommand
|
|
1845
2116
|
}
|
|
1846
2117
|
});
|
|
1847
2118
|
(async () => {
|
|
@@ -1849,6 +2120,10 @@ var main = defineCommand({
|
|
|
1849
2120
|
printRootHelp();
|
|
1850
2121
|
return;
|
|
1851
2122
|
}
|
|
2123
|
+
if (isRemoveHelp(process.argv)) {
|
|
2124
|
+
printRemoveHelp();
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
1852
2127
|
if (isRootVersion(process.argv)) {
|
|
1853
2128
|
console.log(version);
|
|
1854
2129
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Audit and manage AI agent skills for Claude Code and Codex",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ihororlovskyi",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"lint": "biome check src/",
|
|
46
46
|
"format": "biome format --write src/",
|
|
47
47
|
"test": "vitest run",
|
|
48
|
+
"test:coverage": "vitest run --coverage",
|
|
48
49
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
49
50
|
"release": "changeset publish",
|
|
50
51
|
"prepublishOnly": "biome check src/ && vitest run && ~/.bun/bin/bun run node_modules/.bin/bunup && vitest run --config vitest.e2e.config.ts"
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"@biomejs/biome": "^2.4.14",
|
|
54
55
|
"@changesets/cli": "^2.31.0",
|
|
55
56
|
"@types/node": "^25.6.2",
|
|
57
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
56
58
|
"bunup": "^0.16.31",
|
|
57
59
|
"citty": "^0.2.2",
|
|
58
60
|
"typescript": "^6.0.3",
|
|
File without changes
|