skillio 0.1.14 → 0.1.16
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
CHANGED
|
@@ -81,13 +81,13 @@ skl cost # ambient ballast cost (frontmatter token
|
|
|
81
81
|
skl cs # alias for cost (also: cst)
|
|
82
82
|
skl usage # consumption: usage count × frontmatter tokens
|
|
83
83
|
skl usg # alias for usage
|
|
84
|
-
skl rm brainstorming #
|
|
85
|
-
skl rm brainstorming writing-plans # remove multiple
|
|
86
|
-
skl rm . # remove all skills in scope
|
|
87
|
-
skl rm
|
|
88
|
-
skl rm --
|
|
89
|
-
skl rm --
|
|
90
|
-
skl rm --
|
|
84
|
+
skl rm brainstorming # colored plan, Proceed? [y/n], then Clean lock? [y/n]
|
|
85
|
+
skl rm brainstorming writing-plans # remove multiple, one pair of prompts
|
|
86
|
+
skl rm . # remove all skills in scope
|
|
87
|
+
skl rm --yes brainstorming # skip both prompts
|
|
88
|
+
skl rm brainstorming --lock-only # only the lock entry (alias --lo)
|
|
89
|
+
skl rm brainstorming --agents-only # only .agents/skills (alias --ao)
|
|
90
|
+
skl rm brainstorming --claude-only # only .claude/skills (alias --co)
|
|
91
91
|
|
|
92
92
|
# scope flags
|
|
93
93
|
skl -g # force global scope on any subcommand
|
|
@@ -169,15 +169,14 @@ skillio cost --global # same, against ~/.agents/.skill-lock.json
|
|
|
169
169
|
### `skillio remove` / `rm`
|
|
170
170
|
|
|
171
171
|
```sh
|
|
172
|
-
skillio remove <skill-name> #
|
|
172
|
+
skillio remove <skill-name> # colored plan/summary, two prompts (disk, then lock)
|
|
173
173
|
skillio remove <skill-one> <skill-two>
|
|
174
|
-
skillio remove . # remove all skills in scope
|
|
175
|
-
skillio remove
|
|
176
|
-
skillio remove --
|
|
177
|
-
skillio remove --
|
|
174
|
+
skillio remove . # remove all skills in scope
|
|
175
|
+
skillio remove --lock-only <skill-name> # only the lock entry; keep on disk (alias --lo)
|
|
176
|
+
skillio remove --agents-only <skill-name> # only .agents/skills; keep .claude/skills and lock (alias --ao)
|
|
177
|
+
skillio remove --claude-only <skill-name> # only .claude/skills; keep .agents/skills and lock (alias --co)
|
|
178
178
|
skillio remove --global <skill-name>
|
|
179
|
-
skillio remove --
|
|
180
|
-
skillio remove --yes <skill-name> # skip confirmation prompt
|
|
179
|
+
skillio remove --yes <skill-name> # skip both confirmation prompts
|
|
181
180
|
```
|
|
182
181
|
|
|
183
182
|
### Shell completion
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
__require,
|
|
4
|
+
countLockLinesToRemove,
|
|
4
5
|
cyan,
|
|
5
6
|
detectColorSupport,
|
|
6
7
|
discoverSkills,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
removeSkillFromLock,
|
|
12
13
|
setColorEnabled,
|
|
13
14
|
yellow
|
|
14
|
-
} from "./shared/chunk-
|
|
15
|
+
} from "./shared/chunk-nkpp3h85.js";
|
|
15
16
|
|
|
16
17
|
// src/cli.ts
|
|
17
18
|
import { createRequire } from "node:module";
|
|
@@ -577,7 +578,7 @@ _skillio_completions() {
|
|
|
577
578
|
case "\${sub}" in
|
|
578
579
|
rm|remove)
|
|
579
580
|
if [[ "\${cur}" == -* ]]; then
|
|
580
|
-
COMPREPLY=( $(compgen -W "-g --global
|
|
581
|
+
COMPREPLY=( $(compgen -W "-g --global -y --yes --lock-only --lo --agents-only --ao --claude-only --co -h --help" -- "\${cur}") )
|
|
581
582
|
else
|
|
582
583
|
local names
|
|
583
584
|
local scope=""
|
|
@@ -625,10 +626,10 @@ _skillio() {
|
|
|
625
626
|
if [[ \${words[CURRENT]} == -* ]]; then
|
|
626
627
|
_values 'flag' \\
|
|
627
628
|
'-g[global scope]' '--global[global scope]' \\
|
|
628
|
-
'--dry-run[print plan without deleting]' \\
|
|
629
629
|
'-y[skip confirmation]' '--yes[skip confirmation]' \\
|
|
630
|
-
'--
|
|
631
|
-
'--
|
|
630
|
+
'--lock-only[only remove lock entry]' '--lo[alias for --lock-only]' \\
|
|
631
|
+
'--agents-only[only remove from .agents/skills]' '--ao[alias for --agents-only]' \\
|
|
632
|
+
'--claude-only[only remove from .claude/skills]' '--co[alias for --claude-only]'
|
|
632
633
|
else
|
|
633
634
|
local scope=""
|
|
634
635
|
for w in \${words[@]}; do
|
|
@@ -676,11 +677,13 @@ for sub in rm remove
|
|
|
676
677
|
complete -c skl -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
|
|
677
678
|
complete -c skillio -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
|
|
678
679
|
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
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
|
|
682
|
-
complete -c skl -n "__skillio_using_subcommand $sub" -
|
|
683
|
-
complete -c skl -n "__skillio_using_subcommand $sub" -l
|
|
681
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l lock-only -d 'Only remove lock entry'
|
|
682
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l lo -d 'Alias for --lock-only'
|
|
683
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l agents-only -d 'Only remove from .agents/skills'
|
|
684
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l ao -d 'Alias for --agents-only'
|
|
685
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l claude-only -d 'Only remove from .claude/skills'
|
|
686
|
+
complete -c skl -n "__skillio_using_subcommand $sub" -l co -d 'Alias for --claude-only'
|
|
684
687
|
end
|
|
685
688
|
|
|
686
689
|
for sub in completion
|
|
@@ -858,9 +861,8 @@ var listCommand = defineCommand({
|
|
|
858
861
|
render: () => {
|
|
859
862
|
if (rows.lock.totalCount === 0)
|
|
860
863
|
return "";
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
return orphans.map((n) => red(n.name)).join(" ");
|
|
864
|
+
const orphanSet = new Set(orphans.map((n) => n.name));
|
|
865
|
+
return rows.lock.names.map((n) => orphanSet.has(n.name) ? red(n.name) : n.name).join(" ");
|
|
864
866
|
}
|
|
865
867
|
}
|
|
866
868
|
];
|
|
@@ -897,7 +899,6 @@ var listCommand = defineCommand({
|
|
|
897
899
|
});
|
|
898
900
|
|
|
899
901
|
// src/commands/remove.ts
|
|
900
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
901
902
|
import { homedir as homedir2 } from "node:os";
|
|
902
903
|
import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
|
|
903
904
|
|
|
@@ -910,13 +911,13 @@ async function confirm(question, opts = {}) {
|
|
|
910
911
|
const { createInterface } = await import("node:readline/promises");
|
|
911
912
|
const rl = createInterface({ input, output });
|
|
912
913
|
try {
|
|
913
|
-
const answer = (await rl.question(`${question} [y/
|
|
914
|
+
const answer = (await rl.question(`${question} [y/n] `)).trim().toLowerCase();
|
|
914
915
|
return answer === "y" || answer === "yes";
|
|
915
916
|
} finally {
|
|
916
917
|
rl.close();
|
|
917
918
|
}
|
|
918
919
|
}
|
|
919
|
-
output.write(`${question} [y/
|
|
920
|
+
output.write(`${question} [y/n] `);
|
|
920
921
|
emitKeypressEvents(input);
|
|
921
922
|
if (input.setRawMode)
|
|
922
923
|
input.setRawMode(true);
|
|
@@ -963,19 +964,46 @@ async function confirm(question, opts = {}) {
|
|
|
963
964
|
input.on("keypress", onKey);
|
|
964
965
|
});
|
|
965
966
|
}
|
|
967
|
+
function createConfirmer(opts = {}) {
|
|
968
|
+
const input = opts.input ?? process.stdin;
|
|
969
|
+
const output = opts.output ?? process.stdout;
|
|
970
|
+
if (input.isTTY && output.isTTY) {
|
|
971
|
+
return (question) => confirm(question, { input, output });
|
|
972
|
+
}
|
|
973
|
+
let queue;
|
|
974
|
+
return async (question) => {
|
|
975
|
+
if (queue === undefined) {
|
|
976
|
+
let raw = "";
|
|
977
|
+
for await (const chunk of input)
|
|
978
|
+
raw += chunk;
|
|
979
|
+
queue = raw.split(`
|
|
980
|
+
`);
|
|
981
|
+
}
|
|
982
|
+
output.write(`${question} [y/n] `);
|
|
983
|
+
const line = (queue.shift() ?? "").trim().toLowerCase();
|
|
984
|
+
return line === "y" || line === "yes";
|
|
985
|
+
};
|
|
986
|
+
}
|
|
966
987
|
|
|
967
988
|
// src/utils/fs-rm.ts
|
|
968
|
-
import {
|
|
989
|
+
import { lstatSync as lstatSync2, readdirSync, rmSync } from "node:fs";
|
|
969
990
|
import { join as join2, resolve as resolve2 } from "node:path";
|
|
970
991
|
function isInside(target, root) {
|
|
971
992
|
const t = resolve2(target);
|
|
972
993
|
const r = resolve2(root);
|
|
973
994
|
return t === r || t.startsWith(`${r}/`);
|
|
974
995
|
}
|
|
996
|
+
function lstatOrNull(path) {
|
|
997
|
+
try {
|
|
998
|
+
return lstatSync2(path);
|
|
999
|
+
} catch {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
975
1003
|
function countFiles(path) {
|
|
976
|
-
|
|
1004
|
+
const stat = lstatOrNull(path);
|
|
1005
|
+
if (!stat)
|
|
977
1006
|
return 0;
|
|
978
|
-
const stat = lstatSync2(path);
|
|
979
1007
|
if (stat.isFile())
|
|
980
1008
|
return 1;
|
|
981
1009
|
if (!stat.isDirectory())
|
|
@@ -986,12 +1014,27 @@ function countFiles(path) {
|
|
|
986
1014
|
}
|
|
987
1015
|
return n;
|
|
988
1016
|
}
|
|
1017
|
+
function countFoldersAndFiles(dir) {
|
|
1018
|
+
let folders = 0;
|
|
1019
|
+
let files = 0;
|
|
1020
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1021
|
+
if (entry.isDirectory()) {
|
|
1022
|
+
folders++;
|
|
1023
|
+
const nested = countFoldersAndFiles(join2(dir, entry.name));
|
|
1024
|
+
folders += nested.folders;
|
|
1025
|
+
files += nested.files;
|
|
1026
|
+
} else {
|
|
1027
|
+
files++;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return { folders, files };
|
|
1031
|
+
}
|
|
989
1032
|
function rmSkillDir(path, opts) {
|
|
990
1033
|
const safe = opts.allowedRoots.some((root) => isInside(path, root));
|
|
991
1034
|
if (!safe) {
|
|
992
1035
|
throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
|
|
993
1036
|
}
|
|
994
|
-
if (!
|
|
1037
|
+
if (!lstatOrNull(path))
|
|
995
1038
|
return { removed: false, fileCount: 0 };
|
|
996
1039
|
const fileCount = countFiles(path);
|
|
997
1040
|
rmSync(path, { recursive: true, force: true });
|
|
@@ -999,97 +1042,141 @@ function rmSkillDir(path, opts) {
|
|
|
999
1042
|
}
|
|
1000
1043
|
|
|
1001
1044
|
// src/commands/remove.ts
|
|
1002
|
-
|
|
1045
|
+
function buildLocation(dir) {
|
|
1046
|
+
const stat = lstatOrNull(dir);
|
|
1047
|
+
if (!stat)
|
|
1048
|
+
return { kind: "missing", subfolders: 0, files: 0 };
|
|
1049
|
+
if (stat.isSymbolicLink())
|
|
1050
|
+
return { kind: "symlink", subfolders: 0, files: 0 };
|
|
1051
|
+
const { folders, files } = countFoldersAndFiles(dir);
|
|
1052
|
+
return { kind: "real", subfolders: folders, files };
|
|
1053
|
+
}
|
|
1054
|
+
function rootFor2(base, isGlobal, lockPath) {
|
|
1055
|
+
return isGlobal ? join3(homedir2(), base, "skills") : join3(dirname2(resolve3(lockPath)), base, "skills");
|
|
1056
|
+
}
|
|
1003
1057
|
function buildTarget(name, isGlobal, lockPath) {
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1058
|
+
const agentsDir = join3(rootFor2(".agents", isGlobal, lockPath), name);
|
|
1059
|
+
const claudeDir = join3(rootFor2(".claude", isGlobal, lockPath), name);
|
|
1060
|
+
return {
|
|
1061
|
+
name,
|
|
1062
|
+
agentsDir,
|
|
1063
|
+
claudeDir,
|
|
1064
|
+
agents: buildLocation(agentsDir),
|
|
1065
|
+
claude: buildLocation(claudeDir)
|
|
1066
|
+
};
|
|
1011
1067
|
}
|
|
1012
1068
|
function collectAllTargets(isGlobal, lockPath) {
|
|
1013
1069
|
const map = discoverSkills({ isGlobal, cwd: process.cwd(), lockPath });
|
|
1014
1070
|
return [...map.keys()].sort().map((name) => buildTarget(name, isGlobal, lockPath));
|
|
1015
1071
|
}
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1072
|
+
function plural(n, word) {
|
|
1073
|
+
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
1074
|
+
}
|
|
1075
|
+
function header(targets, verb) {
|
|
1076
|
+
const names = targets.map((t) => red(t.name)).join(" ");
|
|
1077
|
+
return `${plural(targets.length, "skill")} ${names} ${verb}`;
|
|
1078
|
+
}
|
|
1079
|
+
function aggregateLocations(infos) {
|
|
1080
|
+
let folders = 0;
|
|
1081
|
+
let subfolders = 0;
|
|
1082
|
+
let files = 0;
|
|
1083
|
+
let symlinks = 0;
|
|
1084
|
+
for (const info of infos) {
|
|
1085
|
+
if (info.kind === "real") {
|
|
1086
|
+
folders += 1;
|
|
1087
|
+
subfolders += info.subfolders;
|
|
1088
|
+
files += info.files;
|
|
1089
|
+
} else if (info.kind === "symlink") {
|
|
1090
|
+
symlinks += 1;
|
|
1091
|
+
}
|
|
1028
1092
|
}
|
|
1029
|
-
return
|
|
1093
|
+
return { folders, subfolders, files, symlinks, found: infos.some((i) => i.kind !== "missing") };
|
|
1030
1094
|
}
|
|
1031
|
-
function
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1095
|
+
function diskCell(infos, variant) {
|
|
1096
|
+
const agg = aggregateLocations(infos);
|
|
1097
|
+
if (!agg.found)
|
|
1098
|
+
return "not found";
|
|
1099
|
+
const parts = [];
|
|
1100
|
+
if (agg.folders > 0 || agg.files > 0) {
|
|
1101
|
+
const text = `${plural(agg.folders, "folder")}, ${plural(agg.subfolders, "subfolder")}, ${plural(agg.files, "file")}`;
|
|
1102
|
+
parts.push(variant === "summary" ? red(text) : green(text));
|
|
1103
|
+
}
|
|
1104
|
+
if (agg.symlinks > 0) {
|
|
1105
|
+
const text = plural(agg.symlinks, "symlink");
|
|
1106
|
+
parts.push(variant === "summary" ? red(text) : yellow(text));
|
|
1107
|
+
}
|
|
1108
|
+
return parts.join(", ");
|
|
1109
|
+
}
|
|
1110
|
+
function lockCell(n, variant) {
|
|
1111
|
+
if (n === 0)
|
|
1112
|
+
return "not in lock";
|
|
1113
|
+
if (variant === "kept")
|
|
1114
|
+
return green(`${plural(n, "line")} kept`);
|
|
1115
|
+
return red(plural(n, "line"));
|
|
1116
|
+
}
|
|
1117
|
+
function printBlock(targets, scope, verb, lockLinesToRemove, diskVariant, lockVariant) {
|
|
1118
|
+
console.log(header(targets, verb));
|
|
1119
|
+
const rows = [];
|
|
1120
|
+
if (scope === "all" || scope === "agents-only") {
|
|
1121
|
+
rows.push([
|
|
1122
|
+
".agents/skills/",
|
|
1123
|
+
diskCell(targets.map((t) => t.agents), diskVariant)
|
|
1124
|
+
]);
|
|
1125
|
+
}
|
|
1126
|
+
if (scope === "all" || scope === "claude-only") {
|
|
1127
|
+
rows.push([
|
|
1128
|
+
".claude/skills/",
|
|
1129
|
+
diskCell(targets.map((t) => t.claude), diskVariant)
|
|
1130
|
+
]);
|
|
1131
|
+
}
|
|
1132
|
+
if (scope === "all" || scope === "lock-only") {
|
|
1133
|
+
rows.push(["skills-lock.json", lockCell(lockLinesToRemove, lockVariant)]);
|
|
1134
|
+
}
|
|
1135
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
1136
|
+
for (const [label, cell] of rows) {
|
|
1137
|
+
console.log(`${label.padEnd(labelWidth)} ${cell}`);
|
|
1052
1138
|
}
|
|
1053
|
-
if (target.claudeDir)
|
|
1054
|
-
console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
|
|
1055
|
-
else
|
|
1056
|
-
console.log(" - .claude/skills/ (not found)");
|
|
1057
|
-
if (target.agentsDir)
|
|
1058
|
-
console.log(` - .agents/skills/${target.name}/ (${plan.agentsFileCount} files)`);
|
|
1059
|
-
else
|
|
1060
|
-
console.log(" - .agents/skills/ (not found)");
|
|
1061
1139
|
}
|
|
1062
1140
|
var removeCommand = defineCommand({
|
|
1063
1141
|
meta: {
|
|
1064
|
-
description: "Remove one or more skills from on-disk dirs
|
|
1142
|
+
description: "Remove one or more skills from on-disk dirs and/or skills-lock.json"
|
|
1065
1143
|
},
|
|
1066
1144
|
args: {
|
|
1067
1145
|
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
"force-lock": {
|
|
1146
|
+
yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompts" },
|
|
1147
|
+
"lock-only": {
|
|
1071
1148
|
type: "boolean",
|
|
1149
|
+
alias: "lo",
|
|
1072
1150
|
default: false,
|
|
1073
|
-
description: "
|
|
1151
|
+
description: "Only remove the skills-lock.json entry; keep on-disk directories"
|
|
1074
1152
|
},
|
|
1075
|
-
"
|
|
1153
|
+
"agents-only": {
|
|
1154
|
+
type: "boolean",
|
|
1155
|
+
alias: "ao",
|
|
1156
|
+
default: false,
|
|
1157
|
+
description: "Only remove from .agents/skills; keep .claude/skills and the lock entry"
|
|
1158
|
+
},
|
|
1159
|
+
"claude-only": {
|
|
1076
1160
|
type: "boolean",
|
|
1161
|
+
alias: "co",
|
|
1077
1162
|
default: false,
|
|
1078
|
-
description: "
|
|
1163
|
+
description: "Only remove from .claude/skills; keep .agents/skills and the lock entry"
|
|
1079
1164
|
}
|
|
1080
1165
|
},
|
|
1081
1166
|
async run({ args }) {
|
|
1082
1167
|
const {
|
|
1083
1168
|
global: isGlobal,
|
|
1084
|
-
"dry-run": dryRun,
|
|
1085
1169
|
yes,
|
|
1086
|
-
"
|
|
1087
|
-
"
|
|
1170
|
+
"lock-only": lockOnly,
|
|
1171
|
+
"agents-only": agentsOnly,
|
|
1172
|
+
"claude-only": claudeOnly
|
|
1088
1173
|
} = args;
|
|
1089
|
-
|
|
1090
|
-
|
|
1174
|
+
const onlyFlagCount = [lockOnly, agentsOnly, claudeOnly].filter(Boolean).length;
|
|
1175
|
+
if (onlyFlagCount > 1) {
|
|
1176
|
+
console.error("--lock-only, --agents-only, and --claude-only are mutually exclusive");
|
|
1091
1177
|
process.exit(1);
|
|
1092
1178
|
}
|
|
1179
|
+
const scope = lockOnly ? "lock-only" : agentsOnly ? "agents-only" : claudeOnly ? "claude-only" : "all";
|
|
1093
1180
|
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
1094
1181
|
const rawNames = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
1095
1182
|
const all = rawNames.includes(".");
|
|
@@ -1108,82 +1195,57 @@ var removeCommand = defineCommand({
|
|
|
1108
1195
|
console.log("No skills to remove in scope.");
|
|
1109
1196
|
return;
|
|
1110
1197
|
}
|
|
1111
|
-
const
|
|
1198
|
+
const lockNames = new Set(Object.keys(readLock(lockPath).skills));
|
|
1199
|
+
const orphan = targets.filter((t) => t.agents.kind === "missing" && t.claude.kind === "missing" && !lockNames.has(t.name));
|
|
1112
1200
|
if (orphan.length) {
|
|
1113
1201
|
for (const o of orphan)
|
|
1114
|
-
console.log(
|
|
1202
|
+
console.log(`"${o.name}" is not in lock or on disk`);
|
|
1115
1203
|
process.exit(1);
|
|
1116
1204
|
}
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
|
|
1121
|
-
}));
|
|
1122
|
-
for (const p of plans) {
|
|
1123
|
-
printPlan(p, modifyLock, lockOnly);
|
|
1124
|
-
console.log("");
|
|
1125
|
-
}
|
|
1126
|
-
if (dryRun)
|
|
1127
|
-
return;
|
|
1205
|
+
const lockLinesToRemove = countLockLinesToRemove(lockPath, targets.map((t) => t.name));
|
|
1206
|
+
printBlock(targets, scope, "will be removed from:", lockLinesToRemove, "plan", "removed");
|
|
1207
|
+
const ask = createConfirmer();
|
|
1128
1208
|
if (!yes) {
|
|
1129
|
-
|
|
1130
|
-
|
|
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);
|
|
1209
|
+
console.log("");
|
|
1210
|
+
const ok = await ask("Proceed?");
|
|
1135
1211
|
if (!ok) {
|
|
1136
1212
|
console.log("Aborted");
|
|
1137
1213
|
process.exit(1);
|
|
1138
1214
|
}
|
|
1139
1215
|
}
|
|
1140
1216
|
const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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
|
-
}
|
|
1217
|
+
if (scope === "all" || scope === "agents-only") {
|
|
1218
|
+
for (const t of targets)
|
|
1219
|
+
rmSkillDir(t.agentsDir, { allowedRoots });
|
|
1220
|
+
}
|
|
1221
|
+
if (scope === "all" || scope === "claude-only") {
|
|
1222
|
+
for (const t of targets)
|
|
1223
|
+
rmSkillDir(t.claudeDir, { allowedRoots });
|
|
1224
|
+
}
|
|
1225
|
+
let lockCleaned = false;
|
|
1226
|
+
if (scope === "lock-only") {
|
|
1227
|
+
for (const t of targets)
|
|
1228
|
+
removeSkillFromLock(lockPath, t.name);
|
|
1229
|
+
lockCleaned = true;
|
|
1230
|
+
} else if (scope === "all" && lockLinesToRemove > 0) {
|
|
1231
|
+
let cleanLock = yes;
|
|
1232
|
+
if (!yes) {
|
|
1233
|
+
console.log("");
|
|
1234
|
+
cleanLock = await ask(`Clean skills-lock.json (${plural(lockLinesToRemove, "line")})?`);
|
|
1169
1235
|
}
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
console.log(removed(" from skills-lock.json"));
|
|
1175
|
-
} else {
|
|
1176
|
-
console.log(kept(" in skills-lock.json"));
|
|
1177
|
-
}
|
|
1178
|
-
} else {
|
|
1179
|
-
console.log(skipped(" skills-lock.json (not in lock)"));
|
|
1236
|
+
if (cleanLock) {
|
|
1237
|
+
for (const t of targets)
|
|
1238
|
+
removeSkillFromLock(lockPath, t.name);
|
|
1239
|
+
lockCleaned = true;
|
|
1180
1240
|
}
|
|
1181
1241
|
}
|
|
1242
|
+
console.log("");
|
|
1243
|
+
printBlock(targets, scope, "removed from:", lockLinesToRemove, "summary", lockCleaned ? "removed" : "kept");
|
|
1182
1244
|
}
|
|
1183
1245
|
});
|
|
1184
1246
|
|
|
1185
1247
|
// src/commands/usage.ts
|
|
1186
|
-
import { existsSync as
|
|
1248
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1187
1249
|
import { join as join7 } from "node:path";
|
|
1188
1250
|
|
|
1189
1251
|
// src/readers/claude.ts
|
|
@@ -1517,10 +1579,10 @@ function readClaudeUsage(options) {
|
|
|
1517
1579
|
}
|
|
1518
1580
|
|
|
1519
1581
|
// src/readers/codex.ts
|
|
1520
|
-
import { existsSync as
|
|
1582
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
1521
1583
|
|
|
1522
1584
|
// src/utils/scope.ts
|
|
1523
|
-
import { existsSync as
|
|
1585
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1524
1586
|
import { homedir as homedir4 } from "node:os";
|
|
1525
1587
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1526
1588
|
function detectScope(opts) {
|
|
@@ -1542,7 +1604,7 @@ function encodeClaudeProjectDir(absPath) {
|
|
|
1542
1604
|
function findGitRoot(start) {
|
|
1543
1605
|
let dir = start;
|
|
1544
1606
|
while (true) {
|
|
1545
|
-
if (
|
|
1607
|
+
if (existsSync2(join6(dir, ".git")))
|
|
1546
1608
|
return dir;
|
|
1547
1609
|
const parent = dirname3(dir);
|
|
1548
1610
|
if (parent === dir)
|
|
@@ -1615,7 +1677,7 @@ function readCodexMentions(options) {
|
|
|
1615
1677
|
const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
|
|
1616
1678
|
const counts = new Map;
|
|
1617
1679
|
let linesRead = 0;
|
|
1618
|
-
if (!
|
|
1680
|
+
if (!existsSync3(historyPath))
|
|
1619
1681
|
return { counts, filesRead: 0, linesRead: 0 };
|
|
1620
1682
|
for (const line of readFileSync3(historyPath, "utf8").split(`
|
|
1621
1683
|
`)) {
|
|
@@ -1725,7 +1787,7 @@ async function runUsage(args) {
|
|
|
1725
1787
|
});
|
|
1726
1788
|
const claudeProjectsRoot = expandHome("~/.claude/projects");
|
|
1727
1789
|
const claudeRoot = args.root ?? (scope.projectRoot ? join7(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
|
|
1728
|
-
const claudeRootMissing = !args.root && !!scope.projectRoot && !
|
|
1790
|
+
const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync4(claudeRoot);
|
|
1729
1791
|
const lockPath = getLockPath(args.global);
|
|
1730
1792
|
const skillUniverse = discoverSkills({
|
|
1731
1793
|
isGlobal: args.global,
|
|
@@ -1969,10 +2031,7 @@ function reorderRootFlagsToSubcommand(argv) {
|
|
|
1969
2031
|
return argv;
|
|
1970
2032
|
return [argv[0] ?? "", argv[1] ?? "", sub, ...before, ...after];
|
|
1971
2033
|
}
|
|
1972
|
-
|
|
1973
|
-
return argv.map((tok) => tok === "-fl" ? "--force-lock" : tok);
|
|
1974
|
-
}
|
|
1975
|
-
process.argv = reorderRootFlagsToSubcommand(normalizeShortFlags(mergeAgentArgs(process.argv)));
|
|
2034
|
+
process.argv = reorderRootFlagsToSubcommand(mergeAgentArgs(process.argv));
|
|
1976
2035
|
function printRootHelp() {
|
|
1977
2036
|
const lines = [
|
|
1978
2037
|
`Audit and manage AI agent skills (skillio v${version})`,
|
|
@@ -1990,7 +2049,7 @@ function printRootHelp() {
|
|
|
1990
2049
|
"COMMANDS",
|
|
1991
2050
|
"",
|
|
1992
2051
|
" list, ls List skills per source: install type, lock orphans, disk/lock diff",
|
|
1993
|
-
" remove, rm Delete on-disk skill dirs
|
|
2052
|
+
" remove, rm Delete on-disk skill dirs and/or skills-lock.json (interactive)",
|
|
1994
2053
|
" cost, cs, cst Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
|
|
1995
2054
|
" usage, us, usg Show skill usage × cost (consumption) with missed rows",
|
|
1996
2055
|
" completion Print shell completion script (bash, zsh, fish)"
|
|
@@ -2014,32 +2073,30 @@ function isRemoveHelp(argv) {
|
|
|
2014
2073
|
}
|
|
2015
2074
|
function printRemoveHelp() {
|
|
2016
2075
|
const lines = [
|
|
2017
|
-
"Remove skills from on-disk dirs
|
|
2076
|
+
"Remove skills from on-disk dirs and/or skills-lock.json.",
|
|
2018
2077
|
"",
|
|
2019
2078
|
"USAGE skillio remove [SKILL...] [OPTIONS]",
|
|
2020
2079
|
" skillio rm [SKILL...] [OPTIONS]",
|
|
2021
2080
|
"",
|
|
2022
2081
|
"ARGUMENTS",
|
|
2023
2082
|
"",
|
|
2024
|
-
' SKILL...
|
|
2083
|
+
' SKILL... One or more skill names. Use "." to target every skill in scope.',
|
|
2025
2084
|
"",
|
|
2026
2085
|
"OPTIONS",
|
|
2027
2086
|
"",
|
|
2028
|
-
" -g, --global
|
|
2029
|
-
"
|
|
2030
|
-
|
|
2031
|
-
" --
|
|
2032
|
-
"
|
|
2033
|
-
" --lock-only Remove only the lock entry; keep on-disk directories",
|
|
2087
|
+
" -g, --global Use global scope (default: false)",
|
|
2088
|
+
" -y, --yes Skip confirmation prompts",
|
|
2089
|
+
" --lock-only Only remove the skills-lock.json entry (alias --lo)",
|
|
2090
|
+
" --agents-only Only remove from .agents/skills (alias --ao)",
|
|
2091
|
+
" --claude-only Only remove from .claude/skills (alias --co)",
|
|
2034
2092
|
"",
|
|
2035
2093
|
"EXAMPLES",
|
|
2036
2094
|
"",
|
|
2037
2095
|
" skillio rm brainstorming",
|
|
2038
2096
|
" skillio rm brainstorming writing-plans --yes",
|
|
2039
|
-
" skillio rm .
|
|
2040
|
-
" skillio rm
|
|
2041
|
-
" skillio rm --
|
|
2042
|
-
" skillio rm --lock-only stale-entry"
|
|
2097
|
+
" skillio rm .",
|
|
2098
|
+
" skillio rm brainstorming --agents-only",
|
|
2099
|
+
" skillio rm brainstorming --lock-only"
|
|
2043
2100
|
];
|
|
2044
2101
|
console.log(lines.join(`
|
|
2045
2102
|
`));
|
|
@@ -2089,7 +2146,7 @@ var main = defineCommand({
|
|
|
2089
2146
|
return;
|
|
2090
2147
|
const interactive = process.stdout.isTTY && process.stdin.isTTY;
|
|
2091
2148
|
if (interactive) {
|
|
2092
|
-
const { runPicker } = await import("./shared/chunk-
|
|
2149
|
+
const { runPicker } = await import("./shared/chunk-dwrjph29.js");
|
|
2093
2150
|
const status = await runPicker({
|
|
2094
2151
|
global: args.global ?? false
|
|
2095
2152
|
});
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
discoverSkills,
|
|
4
4
|
getLockPath,
|
|
5
5
|
red
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-nkpp3h85.js";
|
|
7
7
|
|
|
8
8
|
// src/commands/picker.ts
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
@@ -219,7 +219,7 @@ async function runPicker(args) {
|
|
|
219
219
|
{ value: "usage", label: "usage — count of skill invocations" },
|
|
220
220
|
{ value: "cost", label: "cost — per-skill ambient tokens" },
|
|
221
221
|
{ value: "list", label: "list — installed skills per source" },
|
|
222
|
-
{ value: "remove", label: "remove — delete a skill (
|
|
222
|
+
{ value: "remove", label: "remove — delete a skill (asks about lock cleanup)" },
|
|
223
223
|
{ value: "quit", label: "quit" }
|
|
224
224
|
]
|
|
225
225
|
});
|
|
@@ -13,13 +13,28 @@ function readLock(path) {
|
|
|
13
13
|
return { skills: {} };
|
|
14
14
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
15
15
|
}
|
|
16
|
+
function serialize(lock) {
|
|
17
|
+
return `${JSON.stringify(lock, null, 2)}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
16
20
|
function writeLock(path, lock) {
|
|
17
21
|
mkdirSync(dirname(path), { recursive: true });
|
|
18
22
|
const tmp = join(dirname(path), `.${Date.now()}.skill-lock.json`);
|
|
19
|
-
writeFileSync(tmp,
|
|
20
|
-
`);
|
|
23
|
+
writeFileSync(tmp, serialize(lock));
|
|
21
24
|
renameSync(tmp, path);
|
|
22
25
|
}
|
|
26
|
+
function countLockLinesToRemove(path, names) {
|
|
27
|
+
if (!existsSync(path))
|
|
28
|
+
return 0;
|
|
29
|
+
const lock = readLock(path);
|
|
30
|
+
const before = serialize(lock);
|
|
31
|
+
const after = { skills: { ...lock.skills } };
|
|
32
|
+
for (const name of names)
|
|
33
|
+
delete after.skills[name];
|
|
34
|
+
return before.split(`
|
|
35
|
+
`).length - serialize(after).split(`
|
|
36
|
+
`).length;
|
|
37
|
+
}
|
|
23
38
|
function removeSkillFromLock(path, skill) {
|
|
24
39
|
if (!existsSync(path))
|
|
25
40
|
return { removed: false };
|
|
@@ -135,4 +150,4 @@ function discoverSkills(input) {
|
|
|
135
150
|
return out;
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
export { __require, getLockPath, readLock, removeSkillFromLock, setColorEnabled, detectColorSupport, green, yellow, red, cyan, discoverSkills };
|
|
153
|
+
export { __require, getLockPath, readLock, countLockLinesToRemove, removeSkillFromLock, setColorEnabled, detectColorSupport, green, yellow, red, cyan, discoverSkills };
|