skillio 0.1.14 → 0.1.15
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
|
|
@@ -897,7 +900,7 @@ var listCommand = defineCommand({
|
|
|
897
900
|
});
|
|
898
901
|
|
|
899
902
|
// src/commands/remove.ts
|
|
900
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
903
|
+
import { existsSync as existsSync3, lstatSync as lstatSync3 } from "node:fs";
|
|
901
904
|
import { homedir as homedir2 } from "node:os";
|
|
902
905
|
import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
|
|
903
906
|
|
|
@@ -910,13 +913,13 @@ async function confirm(question, opts = {}) {
|
|
|
910
913
|
const { createInterface } = await import("node:readline/promises");
|
|
911
914
|
const rl = createInterface({ input, output });
|
|
912
915
|
try {
|
|
913
|
-
const answer = (await rl.question(`${question} [y/
|
|
916
|
+
const answer = (await rl.question(`${question} [y/n] `)).trim().toLowerCase();
|
|
914
917
|
return answer === "y" || answer === "yes";
|
|
915
918
|
} finally {
|
|
916
919
|
rl.close();
|
|
917
920
|
}
|
|
918
921
|
}
|
|
919
|
-
output.write(`${question} [y/
|
|
922
|
+
output.write(`${question} [y/n] `);
|
|
920
923
|
emitKeypressEvents(input);
|
|
921
924
|
if (input.setRawMode)
|
|
922
925
|
input.setRawMode(true);
|
|
@@ -963,6 +966,26 @@ async function confirm(question, opts = {}) {
|
|
|
963
966
|
input.on("keypress", onKey);
|
|
964
967
|
});
|
|
965
968
|
}
|
|
969
|
+
function createConfirmer(opts = {}) {
|
|
970
|
+
const input = opts.input ?? process.stdin;
|
|
971
|
+
const output = opts.output ?? process.stdout;
|
|
972
|
+
if (input.isTTY && output.isTTY) {
|
|
973
|
+
return (question) => confirm(question, { input, output });
|
|
974
|
+
}
|
|
975
|
+
let queue;
|
|
976
|
+
return async (question) => {
|
|
977
|
+
if (queue === undefined) {
|
|
978
|
+
let raw = "";
|
|
979
|
+
for await (const chunk of input)
|
|
980
|
+
raw += chunk;
|
|
981
|
+
queue = raw.split(`
|
|
982
|
+
`);
|
|
983
|
+
}
|
|
984
|
+
output.write(`${question} [y/n] `);
|
|
985
|
+
const line = (queue.shift() ?? "").trim().toLowerCase();
|
|
986
|
+
return line === "y" || line === "yes";
|
|
987
|
+
};
|
|
988
|
+
}
|
|
966
989
|
|
|
967
990
|
// src/utils/fs-rm.ts
|
|
968
991
|
import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, rmSync } from "node:fs";
|
|
@@ -986,6 +1009,21 @@ function countFiles(path) {
|
|
|
986
1009
|
}
|
|
987
1010
|
return n;
|
|
988
1011
|
}
|
|
1012
|
+
function countFoldersAndFiles(dir) {
|
|
1013
|
+
let folders = 0;
|
|
1014
|
+
let files = 0;
|
|
1015
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1016
|
+
if (entry.isDirectory()) {
|
|
1017
|
+
folders++;
|
|
1018
|
+
const nested = countFoldersAndFiles(join2(dir, entry.name));
|
|
1019
|
+
folders += nested.folders;
|
|
1020
|
+
files += nested.files;
|
|
1021
|
+
} else {
|
|
1022
|
+
files++;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return { folders, files };
|
|
1026
|
+
}
|
|
989
1027
|
function rmSkillDir(path, opts) {
|
|
990
1028
|
const safe = opts.allowedRoots.some((root) => isInside(path, root));
|
|
991
1029
|
if (!safe) {
|
|
@@ -999,97 +1037,130 @@ function rmSkillDir(path, opts) {
|
|
|
999
1037
|
}
|
|
1000
1038
|
|
|
1001
1039
|
// src/commands/remove.ts
|
|
1002
|
-
|
|
1040
|
+
function buildLocation(dir) {
|
|
1041
|
+
if (!existsSync3(dir))
|
|
1042
|
+
return { kind: "missing", folders: 0, files: 0 };
|
|
1043
|
+
if (lstatSync3(dir).isSymbolicLink())
|
|
1044
|
+
return { kind: "symlink", folders: 0, files: 0 };
|
|
1045
|
+
const { folders, files } = countFoldersAndFiles(dir);
|
|
1046
|
+
return { kind: "real", folders, files };
|
|
1047
|
+
}
|
|
1048
|
+
function rootFor2(base, isGlobal, lockPath) {
|
|
1049
|
+
return isGlobal ? join3(homedir2(), base, "skills") : join3(dirname2(resolve3(lockPath)), base, "skills");
|
|
1050
|
+
}
|
|
1003
1051
|
function buildTarget(name, isGlobal, lockPath) {
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1052
|
+
const agentsDir = join3(rootFor2(".agents", isGlobal, lockPath), name);
|
|
1053
|
+
const claudeDir = join3(rootFor2(".claude", isGlobal, lockPath), name);
|
|
1054
|
+
return {
|
|
1055
|
+
name,
|
|
1056
|
+
agentsDir,
|
|
1057
|
+
claudeDir,
|
|
1058
|
+
agents: buildLocation(agentsDir),
|
|
1059
|
+
claude: buildLocation(claudeDir)
|
|
1060
|
+
};
|
|
1011
1061
|
}
|
|
1012
1062
|
function collectAllTargets(isGlobal, lockPath) {
|
|
1013
1063
|
const map = discoverSkills({ isGlobal, cwd: process.cwd(), lockPath });
|
|
1014
1064
|
return [...map.keys()].sort().map((name) => buildTarget(name, isGlobal, lockPath));
|
|
1015
1065
|
}
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1066
|
+
function plural(n, word) {
|
|
1067
|
+
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
1068
|
+
}
|
|
1069
|
+
function header(targets, verb) {
|
|
1070
|
+
const names = targets.map((t) => red(t.name)).join(" ");
|
|
1071
|
+
return `${plural(targets.length, "skill")} ${names} ${verb}`;
|
|
1072
|
+
}
|
|
1073
|
+
function aggregateLocations(infos) {
|
|
1074
|
+
let folders = 0;
|
|
1075
|
+
let files = 0;
|
|
1076
|
+
let symlinks = 0;
|
|
1077
|
+
for (const info of infos) {
|
|
1078
|
+
if (info.kind === "real") {
|
|
1079
|
+
folders += info.folders;
|
|
1080
|
+
files += info.files;
|
|
1081
|
+
} else if (info.kind === "symlink") {
|
|
1082
|
+
symlinks += 1;
|
|
1083
|
+
}
|
|
1028
1084
|
}
|
|
1029
|
-
return
|
|
1085
|
+
return { folders, files, symlinks, found: infos.some((i) => i.kind !== "missing") };
|
|
1030
1086
|
}
|
|
1031
|
-
function
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
if (
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1087
|
+
function diskLine(label, singleName, infos, variant) {
|
|
1088
|
+
const displayLabel = singleName ? `${label}/${singleName}/` : label;
|
|
1089
|
+
const agg = aggregateLocations(infos);
|
|
1090
|
+
if (!agg.found)
|
|
1091
|
+
return ` ${displayLabel} (not found)`;
|
|
1092
|
+
const parts = [];
|
|
1093
|
+
if (agg.folders > 0 || agg.files > 0) {
|
|
1094
|
+
const text = `${plural(agg.folders, "folder")}, ${plural(agg.files, "file")}`;
|
|
1095
|
+
parts.push(variant === "summary" ? red(text) : green(text));
|
|
1096
|
+
}
|
|
1097
|
+
if (agg.symlinks > 0) {
|
|
1098
|
+
const text = plural(agg.symlinks, "symlink");
|
|
1099
|
+
parts.push(variant === "summary" ? red(text) : yellow(text));
|
|
1100
|
+
}
|
|
1101
|
+
return ` ${displayLabel} (${parts.join(", ")})`;
|
|
1102
|
+
}
|
|
1103
|
+
function lockLine(n, variant) {
|
|
1104
|
+
const label = "skills-lock.json";
|
|
1105
|
+
if (n === 0)
|
|
1106
|
+
return ` ${label} (not in lock)`;
|
|
1107
|
+
if (variant === "kept")
|
|
1108
|
+
return ` ${green(`${label} (${plural(n, "line")} kept)`)}`;
|
|
1109
|
+
return ` ${red(`${label} (${plural(n, "line")})`)}`;
|
|
1110
|
+
}
|
|
1111
|
+
function printBlock(targets, scope, verb, lockLinesToRemove, diskVariant, lockVariant) {
|
|
1112
|
+
console.log(header(targets, verb));
|
|
1113
|
+
const singleName = targets.length === 1 ? targets[0]?.name : undefined;
|
|
1114
|
+
if (scope === "all" || scope === "agents-only") {
|
|
1115
|
+
console.log(diskLine(".agents/skills", singleName, targets.map((t) => t.agents), diskVariant));
|
|
1116
|
+
}
|
|
1117
|
+
if (scope === "all" || scope === "claude-only") {
|
|
1118
|
+
console.log(diskLine(".claude/skills", singleName, targets.map((t) => t.claude), diskVariant));
|
|
1119
|
+
}
|
|
1120
|
+
if (scope === "all" || scope === "lock-only") {
|
|
1121
|
+
console.log(lockLine(lockLinesToRemove, lockVariant));
|
|
1052
1122
|
}
|
|
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
1123
|
}
|
|
1062
1124
|
var removeCommand = defineCommand({
|
|
1063
1125
|
meta: {
|
|
1064
|
-
description: "Remove one or more skills from on-disk dirs
|
|
1126
|
+
description: "Remove one or more skills from on-disk dirs and/or skills-lock.json"
|
|
1065
1127
|
},
|
|
1066
1128
|
args: {
|
|
1067
1129
|
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
"force-lock": {
|
|
1130
|
+
yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompts" },
|
|
1131
|
+
"lock-only": {
|
|
1071
1132
|
type: "boolean",
|
|
1133
|
+
alias: "lo",
|
|
1072
1134
|
default: false,
|
|
1073
|
-
description: "
|
|
1135
|
+
description: "Only remove the skills-lock.json entry; keep on-disk directories"
|
|
1074
1136
|
},
|
|
1075
|
-
"
|
|
1137
|
+
"agents-only": {
|
|
1138
|
+
type: "boolean",
|
|
1139
|
+
alias: "ao",
|
|
1140
|
+
default: false,
|
|
1141
|
+
description: "Only remove from .agents/skills; keep .claude/skills and the lock entry"
|
|
1142
|
+
},
|
|
1143
|
+
"claude-only": {
|
|
1076
1144
|
type: "boolean",
|
|
1145
|
+
alias: "co",
|
|
1077
1146
|
default: false,
|
|
1078
|
-
description: "
|
|
1147
|
+
description: "Only remove from .claude/skills; keep .agents/skills and the lock entry"
|
|
1079
1148
|
}
|
|
1080
1149
|
},
|
|
1081
1150
|
async run({ args }) {
|
|
1082
1151
|
const {
|
|
1083
1152
|
global: isGlobal,
|
|
1084
|
-
"dry-run": dryRun,
|
|
1085
1153
|
yes,
|
|
1086
|
-
"
|
|
1087
|
-
"
|
|
1154
|
+
"lock-only": lockOnly,
|
|
1155
|
+
"agents-only": agentsOnly,
|
|
1156
|
+
"claude-only": claudeOnly
|
|
1088
1157
|
} = args;
|
|
1089
|
-
|
|
1090
|
-
|
|
1158
|
+
const onlyFlagCount = [lockOnly, agentsOnly, claudeOnly].filter(Boolean).length;
|
|
1159
|
+
if (onlyFlagCount > 1) {
|
|
1160
|
+
console.error("--lock-only, --agents-only, and --claude-only are mutually exclusive");
|
|
1091
1161
|
process.exit(1);
|
|
1092
1162
|
}
|
|
1163
|
+
const scope = lockOnly ? "lock-only" : agentsOnly ? "agents-only" : claudeOnly ? "claude-only" : "all";
|
|
1093
1164
|
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
1094
1165
|
const rawNames = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
1095
1166
|
const all = rawNames.includes(".");
|
|
@@ -1108,77 +1179,47 @@ var removeCommand = defineCommand({
|
|
|
1108
1179
|
console.log("No skills to remove in scope.");
|
|
1109
1180
|
return;
|
|
1110
1181
|
}
|
|
1111
|
-
const
|
|
1182
|
+
const lockNames = new Set(Object.keys(readLock(lockPath).skills));
|
|
1183
|
+
const orphan = targets.filter((t) => t.agents.kind === "missing" && t.claude.kind === "missing" && !lockNames.has(t.name));
|
|
1112
1184
|
if (orphan.length) {
|
|
1113
1185
|
for (const o of orphan)
|
|
1114
|
-
console.log(
|
|
1186
|
+
console.log(`"${o.name}" is not in lock or on disk`);
|
|
1115
1187
|
process.exit(1);
|
|
1116
1188
|
}
|
|
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;
|
|
1189
|
+
const lockLinesToRemove = countLockLinesToRemove(lockPath, targets.map((t) => t.name));
|
|
1190
|
+
printBlock(targets, scope, "will be removed from:", lockLinesToRemove, "plan", "removed");
|
|
1191
|
+
const ask = createConfirmer();
|
|
1128
1192
|
if (!yes) {
|
|
1129
|
-
|
|
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);
|
|
1193
|
+
const ok = await ask("Proceed?");
|
|
1135
1194
|
if (!ok) {
|
|
1136
1195
|
console.log("Aborted");
|
|
1137
1196
|
process.exit(1);
|
|
1138
1197
|
}
|
|
1139
1198
|
}
|
|
1140
1199
|
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
|
-
|
|
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
|
-
}
|
|
1170
|
-
if (target.inLock) {
|
|
1171
|
-
if (lockOnly || modifyLock) {
|
|
1172
|
-
const r = removeSkillFromLock(lockPath, target.name);
|
|
1173
|
-
if (r.removed)
|
|
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)"));
|
|
1200
|
+
if (scope === "all" || scope === "agents-only") {
|
|
1201
|
+
for (const t of targets)
|
|
1202
|
+
rmSkillDir(t.agentsDir, { allowedRoots });
|
|
1203
|
+
}
|
|
1204
|
+
if (scope === "all" || scope === "claude-only") {
|
|
1205
|
+
for (const t of targets)
|
|
1206
|
+
rmSkillDir(t.claudeDir, { allowedRoots });
|
|
1207
|
+
}
|
|
1208
|
+
let lockCleaned = false;
|
|
1209
|
+
if (scope === "lock-only") {
|
|
1210
|
+
for (const t of targets)
|
|
1211
|
+
removeSkillFromLock(lockPath, t.name);
|
|
1212
|
+
lockCleaned = true;
|
|
1213
|
+
} else if (scope === "all" && lockLinesToRemove > 0) {
|
|
1214
|
+
const cleanLock = yes || await ask(`Clean skills-lock.json (${plural(lockLinesToRemove, "line")})?`);
|
|
1215
|
+
if (cleanLock) {
|
|
1216
|
+
for (const t of targets)
|
|
1217
|
+
removeSkillFromLock(lockPath, t.name);
|
|
1218
|
+
lockCleaned = true;
|
|
1180
1219
|
}
|
|
1181
1220
|
}
|
|
1221
|
+
console.log("");
|
|
1222
|
+
printBlock(targets, scope, "removed from:", lockLinesToRemove, "summary", lockCleaned ? "removed" : "kept");
|
|
1182
1223
|
}
|
|
1183
1224
|
});
|
|
1184
1225
|
|
|
@@ -1969,10 +2010,7 @@ function reorderRootFlagsToSubcommand(argv) {
|
|
|
1969
2010
|
return argv;
|
|
1970
2011
|
return [argv[0] ?? "", argv[1] ?? "", sub, ...before, ...after];
|
|
1971
2012
|
}
|
|
1972
|
-
|
|
1973
|
-
return argv.map((tok) => tok === "-fl" ? "--force-lock" : tok);
|
|
1974
|
-
}
|
|
1975
|
-
process.argv = reorderRootFlagsToSubcommand(normalizeShortFlags(mergeAgentArgs(process.argv)));
|
|
2013
|
+
process.argv = reorderRootFlagsToSubcommand(mergeAgentArgs(process.argv));
|
|
1976
2014
|
function printRootHelp() {
|
|
1977
2015
|
const lines = [
|
|
1978
2016
|
`Audit and manage AI agent skills (skillio v${version})`,
|
|
@@ -1990,7 +2028,7 @@ function printRootHelp() {
|
|
|
1990
2028
|
"COMMANDS",
|
|
1991
2029
|
"",
|
|
1992
2030
|
" list, ls List skills per source: install type, lock orphans, disk/lock diff",
|
|
1993
|
-
" remove, rm Delete on-disk skill dirs
|
|
2031
|
+
" remove, rm Delete on-disk skill dirs and/or skills-lock.json (interactive)",
|
|
1994
2032
|
" cost, cs, cst Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
|
|
1995
2033
|
" usage, us, usg Show skill usage × cost (consumption) with missed rows",
|
|
1996
2034
|
" completion Print shell completion script (bash, zsh, fish)"
|
|
@@ -2014,32 +2052,30 @@ function isRemoveHelp(argv) {
|
|
|
2014
2052
|
}
|
|
2015
2053
|
function printRemoveHelp() {
|
|
2016
2054
|
const lines = [
|
|
2017
|
-
"Remove skills from on-disk dirs
|
|
2055
|
+
"Remove skills from on-disk dirs and/or skills-lock.json.",
|
|
2018
2056
|
"",
|
|
2019
2057
|
"USAGE skillio remove [SKILL...] [OPTIONS]",
|
|
2020
2058
|
" skillio rm [SKILL...] [OPTIONS]",
|
|
2021
2059
|
"",
|
|
2022
2060
|
"ARGUMENTS",
|
|
2023
2061
|
"",
|
|
2024
|
-
' SKILL...
|
|
2062
|
+
' SKILL... One or more skill names. Use "." to target every skill in scope.',
|
|
2025
2063
|
"",
|
|
2026
2064
|
"OPTIONS",
|
|
2027
2065
|
"",
|
|
2028
|
-
" -g, --global
|
|
2029
|
-
"
|
|
2030
|
-
|
|
2031
|
-
" --
|
|
2032
|
-
"
|
|
2033
|
-
" --lock-only Remove only the lock entry; keep on-disk directories",
|
|
2066
|
+
" -g, --global Use global scope (default: false)",
|
|
2067
|
+
" -y, --yes Skip confirmation prompts",
|
|
2068
|
+
" --lock-only Only remove the skills-lock.json entry (alias --lo)",
|
|
2069
|
+
" --agents-only Only remove from .agents/skills (alias --ao)",
|
|
2070
|
+
" --claude-only Only remove from .claude/skills (alias --co)",
|
|
2034
2071
|
"",
|
|
2035
2072
|
"EXAMPLES",
|
|
2036
2073
|
"",
|
|
2037
2074
|
" skillio rm brainstorming",
|
|
2038
2075
|
" skillio rm brainstorming writing-plans --yes",
|
|
2039
|
-
" skillio rm .
|
|
2040
|
-
" skillio rm
|
|
2041
|
-
" skillio rm --
|
|
2042
|
-
" skillio rm --lock-only stale-entry"
|
|
2076
|
+
" skillio rm .",
|
|
2077
|
+
" skillio rm brainstorming --agents-only",
|
|
2078
|
+
" skillio rm brainstorming --lock-only"
|
|
2043
2079
|
];
|
|
2044
2080
|
console.log(lines.join(`
|
|
2045
2081
|
`));
|
|
@@ -2089,7 +2125,7 @@ var main = defineCommand({
|
|
|
2089
2125
|
return;
|
|
2090
2126
|
const interactive = process.stdout.isTTY && process.stdin.isTTY;
|
|
2091
2127
|
if (interactive) {
|
|
2092
|
-
const { runPicker } = await import("./shared/chunk-
|
|
2128
|
+
const { runPicker } = await import("./shared/chunk-dwrjph29.js");
|
|
2093
2129
|
const status = await runPicker({
|
|
2094
2130
|
global: args.global ?? false
|
|
2095
2131
|
});
|
|
@@ -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 };
|