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 # delete on-disk dir; lock kept (Y/n prompt)
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
88
- skl rm --yes brainstorming # skip confirmation
89
- skl rm --dry-run brainstorming # preview only
90
- skl rm --force-lock brainstorming # also remove the lock entry (-fl)
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> # delete on-disk dir; lock kept
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 (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
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 --dry-run <skill-name> # preview only
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-0qvp6v8g.js";
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 --dry-run -y --yes --force-lock -fl --lock-only -h --help" -- "\${cur}") )
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
- '--force-lock[also remove lock entry]' '-fl[alias for --force-lock]' \\
631
- '--lock-only[remove only lock entry, keep disk]'
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 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'
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/N] `)).trim().toLowerCase();
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/N] `);
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
- var q = (name) => `"${cyan(name)}"`;
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 lock = readLock(lockPath);
1005
- const inLock = Object.hasOwn(lock.skills, name);
1006
- const baseClaude = isGlobal ? join3(homedir2(), ".claude", "skills") : join3(dirname2(resolve3(lockPath)), ".claude", "skills");
1007
- const baseAgents = isGlobal ? join3(homedir2(), ".agents", "skills") : join3(dirname2(resolve3(lockPath)), ".agents", "skills");
1008
- const claudeDir = existsSync3(join3(baseClaude, name)) ? join3(baseClaude, name) : undefined;
1009
- const agentsDir = existsSync3(join3(baseAgents, name)) ? join3(baseAgents, name) : undefined;
1010
- return { name, inLock, claudeDir, agentsDir };
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 fileCount(dir) {
1017
- const { readdirSync: readdirSync2, statSync } = __require("node:fs");
1018
- let n = 0;
1019
- const stack = [dir];
1020
- while (stack.length) {
1021
- const cur = stack.pop();
1022
- const stat = statSync(cur);
1023
- if (stat.isFile())
1024
- n++;
1025
- else if (stat.isDirectory())
1026
- for (const e of readdirSync2(cur))
1027
- stack.push(join3(cur, e));
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 n;
1085
+ return { folders, files, symlinks, found: infos.some((i) => i.kind !== "missing") };
1030
1086
  }
1031
- function printPlan(plan, modifyLock, lockOnly) {
1032
- const { target } = plan;
1033
- console.log(`Will remove ${q(target.name)}:`);
1034
- if (target.inLock) {
1035
- if (lockOnly || modifyLock)
1036
- console.log(" - skills-lock.json");
1037
- else
1038
- console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
1039
- } else {
1040
- console.log(" - skills-lock.json (not in lock)");
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;
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 (lock preserved unless --force-lock)"
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
- "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
1069
- yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
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: "Also remove entry from skills-lock.json (default is to keep lock untouched)"
1135
+ description: "Only remove the skills-lock.json entry; keep on-disk directories"
1074
1136
  },
1075
- "lock-only": {
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: "Remove only the skills-lock.json entry; keep on-disk directories"
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
- "force-lock": modifyLock,
1087
- "lock-only": lockOnly
1154
+ "lock-only": lockOnly,
1155
+ "agents-only": agentsOnly,
1156
+ "claude-only": claudeOnly
1088
1157
  } = args;
1089
- if (lockOnly && modifyLock) {
1090
- console.error("--lock-only is mutually exclusive with --force-lock");
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 orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
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(`${q(o.name)} is not in lock or on disk`);
1186
+ console.log(`"${o.name}" is not in lock or on disk`);
1115
1187
  process.exit(1);
1116
1188
  }
1117
- const plans = targets.map((t) => ({
1118
- target: t,
1119
- claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
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
- 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);
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
- const removed = (s) => red("removed") + s;
1142
- const kept = (s) => green("kept") + s;
1143
- const skipped = (s) => yellow("skipped") + s;
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
- }
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
- function normalizeShortFlags(argv) {
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; lock kept unless --force-lock",
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 (lock preserved unless --force-lock).",
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... One or more skill names. Use "." to target every skill in scope.',
2062
+ ' SKILL... One or more skill names. Use "." to target every skill in scope.',
2025
2063
  "",
2026
2064
  "OPTIONS",
2027
2065
  "",
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",
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 . --dry-run",
2040
- " skillio rm . -fl",
2041
- " skillio rm --force-lock obsolete-skill",
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-vwrhawsv.js");
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-0qvp6v8g.js";
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 (disk-only; lock with --force-lock)" },
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, `${JSON.stringify(lock, null, 2)}
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillio",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Audit and manage AI agent skills for Claude Code and Codex",
5
5
  "license": "MIT",
6
6
  "author": "ihororlovskyi",