skillio 0.1.13 → 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
@@ -78,15 +78,16 @@ skillio # equivalent
78
78
  # subcommands
79
79
  skl ls # list skills per source with diffs
80
80
  skl cost # ambient ballast cost (frontmatter tokens) per skill
81
- skl cst # alias for cost
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 --all # remove all skills in scope
87
- skl rm --yes brainstorming # skip confirmation
88
- skl rm --dry-run brainstorming # preview only
89
- skl rm --force-lock brainstorming # also remove the lock entry
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)
90
91
 
91
92
  # scope flags
92
93
  skl -g # force global scope on any subcommand
@@ -158,7 +159,7 @@ skillio list # local skills-lock.json
158
159
  skillio list --global # ~/.agents/.skill-lock.json
159
160
  ```
160
161
 
161
- ### `skillio cost` / `co`
162
+ ### `skillio cost` / `cs`
162
163
 
163
164
  ```sh
164
165
  skillio cost # local: per-skill frontmatter tokens with verdict
@@ -168,14 +169,14 @@ skillio cost --global # same, against ~/.agents/.skill-lock.json
168
169
  ### `skillio remove` / `rm`
169
170
 
170
171
  ```sh
171
- skillio remove <skill-name> # delete on-disk dir; lock kept
172
+ skillio remove <skill-name> # colored plan/summary, two prompts (disk, then lock)
172
173
  skillio remove <skill-one> <skill-two>
173
- skillio remove --all # remove all skills in scope
174
- skillio remove --force-lock <skill-name> # also remove the lock entry
175
- 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)
176
178
  skillio remove --global <skill-name>
177
- skillio remove --dry-run <skill-name> # preview only
178
- skillio remove --yes <skill-name> # skip confirmation prompt
179
+ skillio remove --yes <skill-name> # skip both confirmation prompts
179
180
  ```
180
181
 
181
182
  ### Shell completion
package/dist/cli.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  __require,
4
+ countLockLinesToRemove,
4
5
  cyan,
5
6
  detectColorSupport,
6
7
  discoverSkills,
7
8
  getLockPath,
8
9
  green,
9
- promptText,
10
10
  readLock,
11
11
  red,
12
12
  removeSkillFromLock,
13
13
  setColorEnabled,
14
14
  yellow
15
- } from "./shared/chunk-2gt0ysd1.js";
15
+ } from "./shared/chunk-nkpp3h85.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { createRequire } from "node:module";
@@ -568,7 +568,7 @@ _skillio_completions() {
568
568
  cur="\${COMP_WORDS[COMP_CWORD]}"
569
569
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
570
570
 
571
- local cmds="list ls remove rm cost co cst usage us usg completion"
571
+ local cmds="list ls remove rm cost cs cst usage us usg completion"
572
572
  if [ "\${COMP_CWORD}" -eq 1 ]; then
573
573
  COMPREPLY=( $(compgen -W "\${cmds} -h --help -v --version" -- "\${cur}") )
574
574
  return 0
@@ -578,7 +578,7 @@ _skillio_completions() {
578
578
  case "\${sub}" in
579
579
  rm|remove)
580
580
  if [[ "\${cur}" == -* ]]; then
581
- COMPREPLY=( $(compgen -W "-g --global --all --dry-run -y --yes --force-lock --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}") )
582
582
  else
583
583
  local names
584
584
  local scope=""
@@ -609,7 +609,7 @@ _skillio() {
609
609
  'remove:Delete on-disk skill dirs'
610
610
  'rm:Alias for remove'
611
611
  'cost:Show ambient ballast cost'
612
- 'co:Alias for cost'
612
+ 'cs:Alias for cost'
613
613
  'cst:Alias for cost'
614
614
  'usage:Show skill usage'
615
615
  'us:Alias for usage'
@@ -626,11 +626,10 @@ _skillio() {
626
626
  if [[ \${words[CURRENT]} == -* ]]; then
627
627
  _values 'flag' \\
628
628
  '-g[global scope]' '--global[global scope]' \\
629
- '--all[remove every skill in scope]' \\
630
- '--dry-run[print plan without deleting]' \\
631
629
  '-y[skip confirmation]' '--yes[skip confirmation]' \\
632
- '--force-lock[also remove lock entry]' \\
633
- '--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]'
634
633
  else
635
634
  local scope=""
636
635
  for w in \${words[@]}; do
@@ -671,18 +670,20 @@ function __skillio_using_subcommand
671
670
  test "$cmd[2]" = "$argv[1]"
672
671
  end
673
672
 
674
- complete -c skl -n __skillio_needs_command -a 'list ls remove rm cost co cst usage us usg completion'
675
- complete -c skillio -n __skillio_needs_command -a 'list ls remove rm cost co cst usage us usg completion'
673
+ complete -c skl -n __skillio_needs_command -a 'list ls remove rm cost cs cst usage us usg completion'
674
+ complete -c skillio -n __skillio_needs_command -a 'list ls remove rm cost cs cst usage us usg completion'
676
675
 
677
676
  for sub in rm remove
678
677
  complete -c skl -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
679
678
  complete -c skillio -n "__skillio_using_subcommand $sub" -f -a '(__skillio_skill_names)'
680
679
  complete -c skl -n "__skillio_using_subcommand $sub" -s g -l global -d 'Use global scope'
681
- complete -c skl -n "__skillio_using_subcommand $sub" -l all -d 'Remove every skill in scope'
682
- complete -c skl -n "__skillio_using_subcommand $sub" -l dry-run -d 'Print plan without deleting'
683
680
  complete -c skl -n "__skillio_using_subcommand $sub" -s y -l yes -d 'Skip confirmation prompt'
684
- complete -c skl -n "__skillio_using_subcommand $sub" -l force-lock -d 'Also remove lock entry'
685
- 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'
686
687
  end
687
688
 
688
689
  for sub in completion
@@ -881,13 +882,9 @@ var listCommand = defineCommand({
881
882
  const claudeNames = rows.claude.names.map((n) => n.name);
882
883
  const agentsNames = rows.agents.names.map((n) => n.name);
883
884
  const lockNames = rows.lock.names.map((n) => n.name);
884
- const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
885
885
  const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
886
886
  const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
887
887
  const diffs = [];
888
- if (lockOnly.length) {
889
- diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
890
- }
891
888
  if (claudeNotInLock.length) {
892
889
  diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan).join(", ")}`);
893
890
  }
@@ -903,7 +900,7 @@ var listCommand = defineCommand({
903
900
  });
904
901
 
905
902
  // src/commands/remove.ts
906
- import { existsSync as existsSync3 } from "node:fs";
903
+ import { existsSync as existsSync3, lstatSync as lstatSync3 } from "node:fs";
907
904
  import { homedir as homedir2 } from "node:os";
908
905
  import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
909
906
 
@@ -916,13 +913,13 @@ async function confirm(question, opts = {}) {
916
913
  const { createInterface } = await import("node:readline/promises");
917
914
  const rl = createInterface({ input, output });
918
915
  try {
919
- const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
916
+ const answer = (await rl.question(`${question} [y/n] `)).trim().toLowerCase();
920
917
  return answer === "y" || answer === "yes";
921
918
  } finally {
922
919
  rl.close();
923
920
  }
924
921
  }
925
- output.write(`${question} [y/N] `);
922
+ output.write(`${question} [y/n] `);
926
923
  emitKeypressEvents(input);
927
924
  if (input.setRawMode)
928
925
  input.setRawMode(true);
@@ -969,6 +966,26 @@ async function confirm(question, opts = {}) {
969
966
  input.on("keypress", onKey);
970
967
  });
971
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
+ }
972
989
 
973
990
  // src/utils/fs-rm.ts
974
991
  import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, rmSync } from "node:fs";
@@ -992,6 +1009,21 @@ function countFiles(path) {
992
1009
  }
993
1010
  return n;
994
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
+ }
995
1027
  function rmSkillDir(path, opts) {
996
1028
  const safe = opts.allowedRoots.some((root) => isInside(path, root));
997
1029
  if (!safe) {
@@ -1005,103 +1037,136 @@ function rmSkillDir(path, opts) {
1005
1037
  }
1006
1038
 
1007
1039
  // src/commands/remove.ts
1008
- 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
+ }
1009
1051
  function buildTarget(name, isGlobal, lockPath) {
1010
- const lock = readLock(lockPath);
1011
- const inLock = Object.hasOwn(lock.skills, name);
1012
- const baseClaude = isGlobal ? join3(homedir2(), ".claude", "skills") : join3(dirname2(resolve3(lockPath)), ".claude", "skills");
1013
- const baseAgents = isGlobal ? join3(homedir2(), ".agents", "skills") : join3(dirname2(resolve3(lockPath)), ".agents", "skills");
1014
- const claudeDir = existsSync3(join3(baseClaude, name)) ? join3(baseClaude, name) : undefined;
1015
- const agentsDir = existsSync3(join3(baseAgents, name)) ? join3(baseAgents, name) : undefined;
1016
- 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
+ };
1017
1061
  }
1018
1062
  function collectAllTargets(isGlobal, lockPath) {
1019
1063
  const map = discoverSkills({ isGlobal, cwd: process.cwd(), lockPath });
1020
1064
  return [...map.keys()].sort().map((name) => buildTarget(name, isGlobal, lockPath));
1021
1065
  }
1022
- function fileCount(dir) {
1023
- const { readdirSync: readdirSync2, statSync } = __require("node:fs");
1024
- let n = 0;
1025
- const stack = [dir];
1026
- while (stack.length) {
1027
- const cur = stack.pop();
1028
- const stat = statSync(cur);
1029
- if (stat.isFile())
1030
- n++;
1031
- else if (stat.isDirectory())
1032
- for (const e of readdirSync2(cur))
1033
- 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
+ }
1034
1084
  }
1035
- return n;
1085
+ return { folders, files, symlinks, found: infos.some((i) => i.kind !== "missing") };
1036
1086
  }
1037
- function printPlan(plan, modifyLock, lockOnly) {
1038
- const { target } = plan;
1039
- console.log(`Will remove ${q(target.name)}:`);
1040
- if (target.inLock) {
1041
- if (lockOnly || modifyLock)
1042
- console.log(" - skills-lock.json");
1043
- else
1044
- console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
1045
- } else {
1046
- console.log(" - skills-lock.json (not in lock)");
1047
- }
1048
- if (lockOnly) {
1049
- if (target.claudeDir)
1050
- console.log(` - .claude/skills/${target.name}/ (kept; --lock-only)`);
1051
- else
1052
- console.log(" - .claude/skills/ (not found)");
1053
- if (target.agentsDir)
1054
- console.log(` - .agents/skills/${target.name}/ (kept; --lock-only)`);
1055
- else
1056
- console.log(" - .agents/skills/ (not found)");
1057
- 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));
1058
1122
  }
1059
- if (target.claudeDir)
1060
- console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
1061
- else
1062
- console.log(" - .claude/skills/ (not found)");
1063
- if (target.agentsDir)
1064
- console.log(` - .agents/skills/${target.name}/ (${plan.agentsFileCount} files)`);
1065
- else
1066
- console.log(" - .agents/skills/ (not found)");
1067
1123
  }
1068
1124
  var removeCommand = defineCommand({
1069
1125
  meta: {
1070
- 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"
1071
1127
  },
1072
1128
  args: {
1073
1129
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
1074
- "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
1075
- yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
1076
- all: { type: "boolean", default: false, description: "Remove every skill in scope" },
1077
- "force-lock": {
1130
+ yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompts" },
1131
+ "lock-only": {
1078
1132
  type: "boolean",
1133
+ alias: "lo",
1079
1134
  default: false,
1080
- 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"
1081
1136
  },
1082
- "lock-only": {
1137
+ "agents-only": {
1083
1138
  type: "boolean",
1139
+ alias: "ao",
1084
1140
  default: false,
1085
- description: "Remove only the skills-lock.json entry; keep on-disk directories"
1141
+ description: "Only remove from .agents/skills; keep .claude/skills and the lock entry"
1142
+ },
1143
+ "claude-only": {
1144
+ type: "boolean",
1145
+ alias: "co",
1146
+ default: false,
1147
+ description: "Only remove from .claude/skills; keep .agents/skills and the lock entry"
1086
1148
  }
1087
1149
  },
1088
1150
  async run({ args }) {
1089
1151
  const {
1090
1152
  global: isGlobal,
1091
- "dry-run": dryRun,
1092
1153
  yes,
1093
- all,
1094
- "force-lock": modifyLock,
1095
- "lock-only": lockOnly
1154
+ "lock-only": lockOnly,
1155
+ "agents-only": agentsOnly,
1156
+ "claude-only": claudeOnly
1096
1157
  } = args;
1097
- if (lockOnly && modifyLock) {
1098
- 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");
1099
1161
  process.exit(1);
1100
1162
  }
1163
+ const scope = lockOnly ? "lock-only" : agentsOnly ? "agents-only" : claudeOnly ? "claude-only" : "all";
1101
1164
  const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
1102
- const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
1165
+ const rawNames = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
1166
+ const all = rawNames.includes(".");
1167
+ const names = rawNames.filter((n) => n !== ".");
1103
1168
  if (all && names.length > 0) {
1104
- console.error("--all is mutually exclusive with positional skill names");
1169
+ console.error('"." (all skills) is mutually exclusive with positional skill names');
1105
1170
  process.exit(1);
1106
1171
  }
1107
1172
  if (!all && names.length === 0) {
@@ -1114,83 +1179,47 @@ var removeCommand = defineCommand({
1114
1179
  console.log("No skills to remove in scope.");
1115
1180
  return;
1116
1181
  }
1117
- 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));
1118
1184
  if (orphan.length) {
1119
1185
  for (const o of orphan)
1120
- console.log(`${q(o.name)} is not in lock or on disk`);
1186
+ console.log(`"${o.name}" is not in lock or on disk`);
1121
1187
  process.exit(1);
1122
1188
  }
1123
- const plans = targets.map((t) => ({
1124
- target: t,
1125
- claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
1126
- agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
1127
- }));
1128
- for (const p of plans) {
1129
- printPlan(p, modifyLock, lockOnly);
1130
- console.log("");
1131
- }
1132
- if (dryRun)
1133
- return;
1134
- if (all) {
1135
- const subject = lockOnly ? `ALL ${plans.length} lock entries (disk preserved)` : `ALL ${plans.length} skills`;
1136
- const interactive = process.stdin.isTTY && process.stdout.isTTY;
1137
- if (interactive) {
1138
- const phrase = await promptText(`This will remove ${subject}. Type "all" to confirm:`);
1139
- if (phrase !== "all") {
1140
- console.log("Aborted");
1141
- process.exit(1);
1142
- }
1143
- } else if (!yes) {
1144
- const ok = await confirm(`Remove ${subject}?`);
1145
- if (!ok) {
1146
- console.log("Aborted");
1147
- process.exit(1);
1148
- }
1149
- }
1150
- } else if (!yes) {
1151
- const ok = await confirm("Proceed?");
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();
1192
+ if (!yes) {
1193
+ const ok = await ask("Proceed?");
1152
1194
  if (!ok) {
1153
1195
  console.log("Aborted");
1154
1196
  process.exit(1);
1155
1197
  }
1156
1198
  }
1157
1199
  const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
1158
- for (const { target } of plans) {
1159
- if (target.inLock) {
1160
- if (lockOnly || modifyLock) {
1161
- const r = removeSkillFromLock(lockPath, target.name);
1162
- if (r.removed)
1163
- console.log(`Removed ${q(target.name)} from skills-lock.json`);
1164
- } else {
1165
- console.log(`Kept ${q(target.name)} in skills-lock.json (no --force-lock)`);
1166
- }
1167
- } else {
1168
- console.log(`Skipped skills-lock.json (not in lock)`);
1169
- }
1170
- if (lockOnly) {
1171
- if (target.claudeDir)
1172
- console.log(`Kept .claude/skills/${target.name}/ (--lock-only)`);
1173
- else
1174
- console.log("Skipped .claude/skills (not found)");
1175
- if (target.agentsDir)
1176
- console.log(`Kept .agents/skills/${target.name}/ (--lock-only)`);
1177
- else
1178
- console.log("Skipped .agents/skills (not found)");
1179
- continue;
1180
- }
1181
- if (target.claudeDir) {
1182
- const r = rmSkillDir(target.claudeDir, { allowedRoots });
1183
- console.log(`Removed ${q(target.name)} from .claude/skills (${r.fileCount} files)`);
1184
- } else {
1185
- console.log("Skipped .claude/skills (not found)");
1186
- }
1187
- if (target.agentsDir) {
1188
- const r = rmSkillDir(target.agentsDir, { allowedRoots });
1189
- console.log(`Removed ${q(target.name)} from .agents/skills (${r.fileCount} files)`);
1190
- } else {
1191
- console.log("Skipped .agents/skills (not found)");
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;
1192
1219
  }
1193
1220
  }
1221
+ console.log("");
1222
+ printBlock(targets, scope, "removed from:", lockLinesToRemove, "summary", lockCleaned ? "removed" : "kept");
1194
1223
  }
1195
1224
  });
1196
1225
 
@@ -1962,7 +1991,7 @@ var SUBCOMMAND_NAMES = new Set([
1962
1991
  "remove",
1963
1992
  "rm",
1964
1993
  "cost",
1965
- "co",
1994
+ "cs",
1966
1995
  "cst",
1967
1996
  "usage",
1968
1997
  "us",
@@ -1999,8 +2028,8 @@ function printRootHelp() {
1999
2028
  "COMMANDS",
2000
2029
  "",
2001
2030
  " list, ls List skills per source: install type, lock orphans, disk/lock diff",
2002
- " remove, rm Delete on-disk skill dirs; lock kept unless --force-lock",
2003
- " cost, co, cst Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
2031
+ " remove, rm Delete on-disk skill dirs and/or skills-lock.json (interactive)",
2032
+ " cost, cs, cst Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
2004
2033
  " usage, us, usg Show skill usage × cost (consumption) with missed rows",
2005
2034
  " completion Print shell completion script (bash, zsh, fish)"
2006
2035
  ];
@@ -2023,31 +2052,30 @@ function isRemoveHelp(argv) {
2023
2052
  }
2024
2053
  function printRemoveHelp() {
2025
2054
  const lines = [
2026
- "Remove skills from on-disk dirs (lock preserved unless --force-lock).",
2055
+ "Remove skills from on-disk dirs and/or skills-lock.json.",
2027
2056
  "",
2028
2057
  "USAGE skillio remove [SKILL...] [OPTIONS]",
2029
2058
  " skillio rm [SKILL...] [OPTIONS]",
2030
2059
  "",
2031
2060
  "ARGUMENTS",
2032
2061
  "",
2033
- " SKILL... One or more skill names. Use --all to target every skill in scope.",
2062
+ ' SKILL... One or more skill names. Use "." to target every skill in scope.',
2034
2063
  "",
2035
2064
  "OPTIONS",
2036
2065
  "",
2037
- " -g, --global Use global scope (default: false)",
2038
- " --all Remove every skill in scope (mutually exclusive with SKILL)",
2039
- " --dry-run Print plan without deleting",
2040
- " -y, --yes Skip confirmation prompt (non-TTY only for --all)",
2041
- " --force-lock Also remove entry from skills-lock.json (default: lock preserved)",
2042
- " --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)",
2043
2071
  "",
2044
2072
  "EXAMPLES",
2045
2073
  "",
2046
2074
  " skillio rm brainstorming",
2047
2075
  " skillio rm brainstorming writing-plans --yes",
2048
- " skillio rm --all --dry-run",
2049
- " skillio rm --force-lock obsolete-skill",
2050
- " skillio rm --lock-only stale-entry"
2076
+ " skillio rm .",
2077
+ " skillio rm brainstorming --agents-only",
2078
+ " skillio rm brainstorming --lock-only"
2051
2079
  ];
2052
2080
  console.log(lines.join(`
2053
2081
  `));
@@ -2097,7 +2125,7 @@ var main = defineCommand({
2097
2125
  return;
2098
2126
  const interactive = process.stdout.isTTY && process.stdin.isTTY;
2099
2127
  if (interactive) {
2100
- const { runPicker } = await import("./shared/chunk-ajnqh9j9.js");
2128
+ const { runPicker } = await import("./shared/chunk-dwrjph29.js");
2101
2129
  const status = await runPicker({
2102
2130
  global: args.global ?? false
2103
2131
  });
@@ -2115,7 +2143,7 @@ var main = defineCommand({
2115
2143
  remove: removeCommand,
2116
2144
  rm: removeCommand,
2117
2145
  cost: costCommand,
2118
- co: costCommand,
2146
+ cs: costCommand,
2119
2147
  cst: costCommand,
2120
2148
  usage: usageCommand,
2121
2149
  us: usageCommand,