skillio 0.1.10 → 0.1.12

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/dist/cli.js CHANGED
@@ -3,11 +3,15 @@ import {
3
3
  __require,
4
4
  cyan,
5
5
  detectColorSupport,
6
+ discoverSkills,
7
+ getLockPath,
6
8
  green,
9
+ readLock,
7
10
  red,
11
+ removeSkillFromLock,
8
12
  setColorEnabled,
9
13
  yellow
10
- } from "./shared/chunk-s3421yr2.js";
14
+ } from "./shared/chunk-0qvp6v8g.js";
11
15
 
12
16
  // src/cli.ts
13
17
  import { createRequire } from "node:module";
@@ -554,115 +558,6 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
554
558
  return [`--${long}`, `-${short}`];
555
559
  }
556
560
 
557
- // src/lock/file.ts
558
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
559
- import { homedir } from "node:os";
560
- import { dirname, join } from "node:path";
561
- function getLockPath(global) {
562
- return global ? join(homedir(), ".agents", ".skill-lock.json") : "skills-lock.json";
563
- }
564
- function readLock(path) {
565
- if (!existsSync(path))
566
- return { skills: {} };
567
- return JSON.parse(readFileSync(path, "utf8"));
568
- }
569
- function writeLock(path, lock) {
570
- mkdirSync(dirname(path), { recursive: true });
571
- const tmp = join(dirname(path), `.${Date.now()}.skill-lock.json`);
572
- writeFileSync(tmp, `${JSON.stringify(lock, null, 2)}
573
- `);
574
- renameSync(tmp, path);
575
- }
576
- function removeSkillFromLock(path, skill) {
577
- if (!existsSync(path))
578
- return { removed: false };
579
- const lock = readLock(path);
580
- if (!Object.hasOwn(lock.skills, skill))
581
- return { removed: false };
582
- delete lock.skills[skill];
583
- writeLock(path, lock);
584
- return { removed: true };
585
- }
586
-
587
- // src/utils/discover-skills.ts
588
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
589
- import { homedir as homedir3 } from "node:os";
590
- import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
591
-
592
- // src/utils/skill-files.ts
593
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
594
- import { homedir as homedir2 } from "node:os";
595
- import { dirname as dirname2, join as join2, resolve } from "node:path";
596
- var CHARS_PER_TOKEN = 4;
597
- function extractFrontmatter(content) {
598
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
599
- return match?.[1];
600
- }
601
- function estimateTokens(text) {
602
- return Math.round(text.length / CHARS_PER_TOKEN);
603
- }
604
-
605
- // src/utils/discover-skills.ts
606
- function resolveRoots(input) {
607
- if (input.isGlobal) {
608
- return {
609
- claude: join3(homedir3(), ".claude", "skills"),
610
- agents: join3(homedir3(), ".agents", "skills")
611
- };
612
- }
613
- const repo = dirname3(resolve2(input.lockPath));
614
- return {
615
- claude: join3(repo, ".claude", "skills"),
616
- agents: join3(repo, ".agents", "skills")
617
- };
618
- }
619
- function listSkillNames(root) {
620
- if (!root || !existsSync3(root))
621
- return [];
622
- return readdirSync(root).filter((name) => {
623
- const skill = join3(root, name, "SKILL.md");
624
- return existsSync3(skill) && statSync(skill).isFile();
625
- });
626
- }
627
- function tokensFromFile(path) {
628
- const content = readFileSync3(path, "utf8");
629
- const fm = extractFrontmatter(content);
630
- if (fm === undefined)
631
- return { status: "no-frontmatter" };
632
- return { tokens: estimateTokens(fm), status: "ok" };
633
- }
634
- function discoverSkills(input) {
635
- const roots = resolveRoots(input);
636
- const lock = readLock(input.lockPath);
637
- const lockNames = Object.keys(lock.skills);
638
- const claudeNames = listSkillNames(roots.claude);
639
- const agentsNames = listSkillNames(roots.agents);
640
- const all = new Set([...lockNames, ...claudeNames, ...agentsNames]);
641
- const out = new Map;
642
- for (const name of all) {
643
- const sources = [];
644
- if (lockNames.includes(name))
645
- sources.push("lock");
646
- if (claudeNames.includes(name))
647
- sources.push(".claude");
648
- if (agentsNames.includes(name))
649
- sources.push(".agents");
650
- let skillFile;
651
- if (claudeNames.includes(name) && roots.claude) {
652
- skillFile = join3(roots.claude, name, "SKILL.md");
653
- } else if (agentsNames.includes(name) && roots.agents) {
654
- skillFile = join3(roots.agents, name, "SKILL.md");
655
- }
656
- if (!skillFile) {
657
- out.set(name, { name, sources, status: "missing" });
658
- continue;
659
- }
660
- const { tokens, status } = tokensFromFile(skillFile);
661
- out.set(name, { name, sources, skillFile, frontmatterTokens: tokens, status });
662
- }
663
- return out;
664
- }
665
-
666
561
  // src/commands/cost.ts
667
562
  function classify(total) {
668
563
  if (total < 1000)
@@ -718,61 +613,98 @@ var costCommand = defineCommand({
718
613
  });
719
614
 
720
615
  // src/commands/list.ts
721
- function bySource(records) {
722
- const claudeNames = records.filter((r) => r.sources.includes(".claude")).map((r) => r.name).sort();
723
- const agentsNames = records.filter((r) => r.sources.includes(".agents")).map((r) => r.name).sort();
724
- const lockNames = records.filter((r) => r.sources.includes("lock")).map((r) => r.name).sort();
725
- const sumTokens = (names) => names.reduce((acc, n) => acc + (records.find((r) => r.name === n)?.frontmatterTokens ?? 0), 0);
616
+ import { existsSync, lstatSync } from "node:fs";
617
+ import { homedir } from "node:os";
618
+ import { dirname, join, resolve } from "node:path";
619
+ function rootFor(isGlobal, lockPath, kind) {
620
+ if (isGlobal)
621
+ return join(homedir(), kind, "skills");
622
+ return join(dirname(resolve(lockPath)), kind, "skills");
623
+ }
624
+ function getInstall(root, name) {
625
+ const dir = join(root, name);
626
+ if (!existsSync(dir))
627
+ return;
628
+ return lstatSync(dir).isSymbolicLink() ? "symlink" : "real";
629
+ }
630
+ function paintDisk(n) {
631
+ if (n.install === "symlink")
632
+ return yellow(n.name);
633
+ if (n.install === "real")
634
+ return green(n.name);
635
+ return cyan(n.name);
636
+ }
637
+ function bySource(records, roots, lockLabel) {
638
+ const claudeRecords = records.filter((r) => r.sources.includes(".claude"));
639
+ const agentsRecords = records.filter((r) => r.sources.includes(".agents"));
640
+ const lockRecords = records.filter((r) => r.sources.includes("lock"));
641
+ const claudeNames = claudeRecords.map((r) => ({ name: r.name, install: getInstall(roots.claude, r.name) })).sort((a, b) => a.name.localeCompare(b.name));
642
+ const agentsNames = agentsRecords.map((r) => ({ name: r.name, install: getInstall(roots.agents, r.name) })).sort((a, b) => a.name.localeCompare(b.name));
643
+ const lockNames = lockRecords.map((r) => ({ name: r.name })).sort((a, b) => a.name.localeCompare(b.name));
726
644
  return {
727
- claude: {
728
- label: ".claude/skills",
729
- names: claudeNames,
730
- tokens: sumTokens(claudeNames),
731
- exists: true
732
- },
733
- agents: {
734
- label: ".agents/skills",
735
- names: agentsNames,
736
- tokens: sumTokens(agentsNames),
737
- exists: true
738
- },
739
- lock: {
740
- label: "skills-lock.json",
741
- names: lockNames,
742
- tokens: sumTokens(lockNames),
743
- exists: true
744
- }
645
+ agents: { label: ".agents/skills", names: agentsNames, totalCount: agentsNames.length },
646
+ claude: { label: ".claude/skills", names: claudeNames, totalCount: claudeNames.length },
647
+ lock: { label: lockLabel, names: lockNames, totalCount: lockNames.length }
745
648
  };
746
649
  }
747
650
  var listCommand = defineCommand({
748
- meta: { description: "List skills per source with totals and lock-vs-disk diff" },
651
+ meta: { description: "List skills per source with install-type coloring and lock orphan filter" },
749
652
  args: {
750
653
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
751
654
  },
752
655
  run({ args }) {
753
656
  const lockPath = getLockPath(args.global);
754
- const map = discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath });
755
- const records = [...map.values()];
756
- const rows = bySource(records);
757
- const claudeNames = rows.claude.names;
758
- const agentsNames = rows.agents.names;
759
- const lockNames = rows.lock.names;
760
- const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
761
- const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
762
- const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
763
- const sourceRows = [rows.claude, rows.agents, rows.lock];
764
- const labelWidth = Math.max(...sourceRows.map((r) => r.label.length));
765
- const countCells = sourceRows.map((r) => `${r.names.length} skill${r.names.length === 1 ? "" : "s"}`);
657
+ const records = [
658
+ ...discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath }).values()
659
+ ];
660
+ const roots = {
661
+ claude: rootFor(args.global, lockPath, ".claude"),
662
+ agents: rootFor(args.global, lockPath, ".agents")
663
+ };
664
+ const lockLabel = args.global ? ".agents/.skill-lock.json" : "skills-lock.json";
665
+ const rows = bySource(records, roots, lockLabel);
666
+ console.log(args.global ? "Global" : "Local");
667
+ const claudeSet = new Set(rows.claude.names.map((n) => n.name));
668
+ const agentsSet = new Set(rows.agents.names.map((n) => n.name));
669
+ const orphans = rows.lock.names.filter((n) => !claudeSet.has(n.name) && !agentsSet.has(n.name));
670
+ const sourceRows = [
671
+ {
672
+ row: rows.agents,
673
+ render: () => rows.agents.names.map(paintDisk).join(" ")
674
+ },
675
+ {
676
+ row: rows.claude,
677
+ render: () => rows.claude.names.map(paintDisk).join(" ")
678
+ },
679
+ {
680
+ row: rows.lock,
681
+ render: () => {
682
+ if (rows.lock.totalCount === 0)
683
+ return "";
684
+ if (orphans.length === 0)
685
+ return green("All skills onboard!");
686
+ return orphans.map((n) => red(n.name)).join(" ");
687
+ }
688
+ }
689
+ ];
690
+ const labelWidth = Math.max(...sourceRows.map((r) => r.row.label.length));
691
+ const countCells = sourceRows.map((r) => `${r.row.totalCount} skill${r.row.totalCount === 1 ? "" : "s"}`);
766
692
  const countWidth = Math.max(...countCells.map((c) => c.length));
767
693
  for (let i = 0;i < sourceRows.length; i++) {
768
- const row = sourceRows[i];
769
- if (!row)
694
+ const entry = sourceRows[i];
695
+ if (!entry)
770
696
  continue;
771
697
  const countCell = countCells[i] ?? "";
772
- const namesText = row.names.length ? row.names.map(cyan).join(" ") : "";
773
- const line = `${row.label.padEnd(labelWidth)} : ${countCell.padEnd(countWidth)}${namesText ? ` : ${namesText}` : ""}`;
698
+ const namesText = entry.render();
699
+ const line = `${entry.row.label.padEnd(labelWidth)} : ${countCell.padEnd(countWidth)}${namesText ? ` : ${namesText}` : ""}`;
774
700
  console.log(line.trimEnd());
775
701
  }
702
+ const claudeNames = rows.claude.names.map((n) => n.name);
703
+ const agentsNames = rows.agents.names.map((n) => n.name);
704
+ const lockNames = rows.lock.names.map((n) => n.name);
705
+ const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
706
+ const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
707
+ const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
776
708
  const diffs = [];
777
709
  if (lockOnly.length) {
778
710
  diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
@@ -792,46 +724,92 @@ var listCommand = defineCommand({
792
724
  });
793
725
 
794
726
  // src/commands/remove.ts
795
- import { existsSync as existsSync5 } from "node:fs";
796
- import { homedir as homedir4 } from "node:os";
797
- import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
727
+ import { existsSync as existsSync3 } from "node:fs";
728
+ import { homedir as homedir2 } from "node:os";
729
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
798
730
 
799
731
  // src/utils/confirm.ts
800
- import { createInterface } from "node:readline/promises";
732
+ import { emitKeypressEvents } from "node:readline";
801
733
  async function confirm(question, opts = {}) {
802
734
  const input = opts.input ?? process.stdin;
803
735
  const output = opts.output ?? process.stdout;
804
- const rl = createInterface({
805
- input,
806
- output
807
- });
808
- try {
809
- const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
810
- return answer === "y" || answer === "yes";
811
- } finally {
812
- rl.close();
736
+ if (!input.isTTY || !output.isTTY) {
737
+ const { createInterface } = await import("node:readline/promises");
738
+ const rl = createInterface({ input, output });
739
+ try {
740
+ const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
741
+ return answer === "y" || answer === "yes";
742
+ } finally {
743
+ rl.close();
744
+ }
813
745
  }
746
+ output.write(`${question} [y/N] `);
747
+ emitKeypressEvents(input);
748
+ if (input.setRawMode)
749
+ input.setRawMode(true);
750
+ input.resume();
751
+ return new Promise((resolve2) => {
752
+ const onKey = (str, key) => {
753
+ const lower = (str ?? "").toLowerCase();
754
+ if (key.ctrl && key.name === "c") {
755
+ cleanup();
756
+ output.write(`
757
+ `);
758
+ resolve2(false);
759
+ return;
760
+ }
761
+ if (lower === "y") {
762
+ cleanup();
763
+ output.write(`y
764
+ `);
765
+ resolve2(true);
766
+ return;
767
+ }
768
+ if (lower === "n" || key.name === "return" || key.name === "escape" || lower === "q") {
769
+ cleanup();
770
+ output.write(`${lower || "n"}
771
+ `);
772
+ resolve2(false);
773
+ return;
774
+ }
775
+ };
776
+ function onSigterm() {
777
+ cleanup();
778
+ output.write(`
779
+ `);
780
+ resolve2(false);
781
+ }
782
+ function cleanup() {
783
+ input.removeListener("keypress", onKey);
784
+ process.removeListener("SIGTERM", onSigterm);
785
+ if (input.setRawMode)
786
+ input.setRawMode(false);
787
+ input.pause();
788
+ }
789
+ process.once("SIGTERM", onSigterm);
790
+ input.on("keypress", onKey);
791
+ });
814
792
  }
815
793
 
816
794
  // src/utils/fs-rm.ts
817
- import { existsSync as existsSync4, lstatSync, readdirSync as readdirSync2, rmSync } from "node:fs";
818
- import { join as join4, resolve as resolve3 } from "node:path";
795
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, rmSync } from "node:fs";
796
+ import { join as join2, resolve as resolve2 } from "node:path";
819
797
  function isInside(target, root) {
820
- const t = resolve3(target);
821
- const r = resolve3(root);
798
+ const t = resolve2(target);
799
+ const r = resolve2(root);
822
800
  return t === r || t.startsWith(`${r}/`);
823
801
  }
824
802
  function countFiles(path) {
825
- if (!existsSync4(path))
803
+ if (!existsSync2(path))
826
804
  return 0;
827
- const stat = lstatSync(path);
805
+ const stat = lstatSync2(path);
828
806
  if (stat.isFile())
829
807
  return 1;
830
808
  if (!stat.isDirectory())
831
809
  return 0;
832
810
  let n = 0;
833
- for (const entry of readdirSync2(path)) {
834
- n += countFiles(join4(path, entry));
811
+ for (const entry of readdirSync(path)) {
812
+ n += countFiles(join2(path, entry));
835
813
  }
836
814
  return n;
837
815
  }
@@ -840,60 +818,51 @@ function rmSkillDir(path, opts) {
840
818
  if (!safe) {
841
819
  throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
842
820
  }
843
- if (!existsSync4(path))
821
+ if (!existsSync2(path))
844
822
  return { removed: false, fileCount: 0 };
845
823
  const fileCount = countFiles(path);
846
824
  rmSync(path, { recursive: true, force: true });
847
825
  return { removed: true, fileCount };
848
826
  }
849
827
 
850
- // src/utils/git.ts
851
- import { spawnSync } from "node:child_process";
852
- import { dirname as dirname4, resolve as resolve4 } from "node:path";
853
- function isTrackedByGit(path) {
854
- const abs = resolve4(path);
855
- const cwd = dirname4(abs);
856
- const r = spawnSync("git", ["ls-files", "--error-unmatch", abs], {
857
- cwd,
858
- stdio: ["ignore", "ignore", "ignore"]
859
- });
860
- return r.status === 0;
861
- }
862
-
863
828
  // src/commands/remove.ts
864
829
  var q = (name) => `"${cyan(name)}"`;
865
830
  function buildTarget(name, isGlobal, lockPath) {
866
831
  const lock = readLock(lockPath);
867
832
  const inLock = Object.hasOwn(lock.skills, name);
868
- const baseClaude = isGlobal ? join5(homedir4(), ".claude", "skills") : join5(dirname5(resolve5(lockPath)), ".claude", "skills");
869
- const baseAgents = isGlobal ? join5(homedir4(), ".agents", "skills") : join5(dirname5(resolve5(lockPath)), ".agents", "skills");
870
- const claudeDir = existsSync5(join5(baseClaude, name)) ? join5(baseClaude, name) : undefined;
871
- const agentsDir = existsSync5(join5(baseAgents, name)) ? join5(baseAgents, name) : undefined;
833
+ const baseClaude = isGlobal ? join3(homedir2(), ".claude", "skills") : join3(dirname2(resolve3(lockPath)), ".claude", "skills");
834
+ const baseAgents = isGlobal ? join3(homedir2(), ".agents", "skills") : join3(dirname2(resolve3(lockPath)), ".agents", "skills");
835
+ const claudeDir = existsSync3(join3(baseClaude, name)) ? join3(baseClaude, name) : undefined;
836
+ const agentsDir = existsSync3(join3(baseAgents, name)) ? join3(baseAgents, name) : undefined;
872
837
  return { name, inLock, claudeDir, agentsDir };
873
838
  }
839
+ function collectAllTargets(isGlobal, lockPath) {
840
+ const map = discoverSkills({ isGlobal, cwd: process.cwd(), lockPath });
841
+ return [...map.keys()].sort().map((name) => buildTarget(name, isGlobal, lockPath));
842
+ }
874
843
  function fileCount(dir) {
875
- const { readdirSync: readdirSync3, statSync: statSync2 } = __require("node:fs");
844
+ const { readdirSync: readdirSync2, statSync } = __require("node:fs");
876
845
  let n = 0;
877
846
  const stack = [dir];
878
847
  while (stack.length) {
879
848
  const cur = stack.pop();
880
- const stat = statSync2(cur);
849
+ const stat = statSync(cur);
881
850
  if (stat.isFile())
882
851
  n++;
883
852
  else if (stat.isDirectory())
884
- for (const e of readdirSync3(cur))
885
- stack.push(join5(cur, e));
853
+ for (const e of readdirSync2(cur))
854
+ stack.push(join3(cur, e));
886
855
  }
887
856
  return n;
888
857
  }
889
- function printPlan(plan, lockTracked) {
858
+ function printPlan(plan, modifyLock) {
890
859
  const { target } = plan;
891
860
  console.log(`Will remove ${q(target.name)}:`);
892
861
  if (target.inLock) {
893
- if (lockTracked)
894
- console.log(" - skills-lock.json (skipped: git-tracked; use --force-lock)");
895
- else
862
+ if (modifyLock)
896
863
  console.log(" - skills-lock.json");
864
+ else
865
+ console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
897
866
  } else {
898
867
  console.log(" - skills-lock.json (not in lock)");
899
868
  }
@@ -907,27 +876,38 @@ function printPlan(plan, lockTracked) {
907
876
  console.log(" - .agents/skills/ (not found)");
908
877
  }
909
878
  var removeCommand = defineCommand({
910
- meta: { description: "Remove one or more skills from lock and delete their on-disk directories" },
879
+ meta: {
880
+ description: "Remove one or more skills from on-disk dirs (lock preserved unless --force-lock)"
881
+ },
911
882
  args: {
912
883
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
913
884
  "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
914
885
  yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
886
+ all: { type: "boolean", default: false, description: "Remove every skill in scope" },
915
887
  "force-lock": {
916
888
  type: "boolean",
917
889
  default: false,
918
- description: "Modify skills-lock.json even if it is git-tracked"
890
+ description: "Also remove entry from skills-lock.json (default is to keep lock untouched)"
919
891
  }
920
892
  },
921
893
  async run({ args }) {
922
- const { global: isGlobal, "dry-run": dryRun, yes, "force-lock": forceLock } = args;
894
+ const { global: isGlobal, "dry-run": dryRun, yes, all, "force-lock": modifyLock } = args;
923
895
  const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
924
896
  const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
925
- if (names.length === 0) {
897
+ if (all && names.length > 0) {
898
+ console.error("--all is mutually exclusive with positional skill names");
899
+ process.exit(1);
900
+ }
901
+ if (!all && names.length === 0) {
926
902
  console.error("No skill names provided");
927
903
  process.exit(1);
928
904
  }
929
905
  const lockPath = getLockPath(isGlobal);
930
- const targets = names.map((n) => buildTarget(n, isGlobal, lockPath));
906
+ const targets = all ? collectAllTargets(isGlobal, lockPath) : names.map((n) => buildTarget(n, isGlobal, lockPath));
907
+ if (all && targets.length === 0) {
908
+ console.log("No skills to remove in scope.");
909
+ return;
910
+ }
931
911
  const orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
932
912
  if (orphan.length) {
933
913
  for (const o of orphan)
@@ -939,32 +919,29 @@ var removeCommand = defineCommand({
939
919
  claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
940
920
  agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
941
921
  }));
942
- const lockTracked = !forceLock && isTrackedByGit(lockPath);
943
922
  for (const p of plans) {
944
- printPlan(p, lockTracked);
923
+ printPlan(p, modifyLock);
945
924
  console.log("");
946
925
  }
947
- if (lockTracked && plans.some((p) => p.target.inLock)) {
948
- console.error(red("Skipping skills-lock.json (tracked by git; pass --force-lock to override)"));
949
- }
950
926
  if (dryRun)
951
927
  return;
952
928
  if (!yes) {
953
- const ok = await confirm("Proceed?");
929
+ const promptText = all ? `Remove ALL ${plans.length} skills?` : "Proceed?";
930
+ const ok = await confirm(promptText);
954
931
  if (!ok) {
955
932
  console.log("Aborted");
956
933
  process.exit(1);
957
934
  }
958
935
  }
959
- const allowedRoots = [isGlobal ? homedir4() : dirname5(resolve5(lockPath)), homedir4()];
936
+ const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
960
937
  for (const { target } of plans) {
961
938
  if (target.inLock) {
962
- if (lockTracked) {
963
- console.log(`Skipped skills-lock.json (git-tracked) for ${q(target.name)}`);
964
- } else {
939
+ if (modifyLock) {
965
940
  const r = removeSkillFromLock(lockPath, target.name);
966
941
  if (r.removed)
967
942
  console.log(`Removed ${q(target.name)} from skills-lock.json`);
943
+ } else {
944
+ console.log(`Kept ${q(target.name)} in skills-lock.json (no --force-lock)`);
968
945
  }
969
946
  } else {
970
947
  console.log(`Skipped skills-lock.json (not in lock)`);
@@ -986,11 +963,11 @@ var removeCommand = defineCommand({
986
963
  });
987
964
 
988
965
  // src/commands/usage.ts
989
- import { existsSync as existsSync8 } from "node:fs";
990
- import { join as join9 } from "node:path";
966
+ import { existsSync as existsSync6 } from "node:fs";
967
+ import { join as join7 } from "node:path";
991
968
 
992
969
  // src/readers/claude.ts
993
- import { readFileSync as readFileSync5 } from "node:fs";
970
+ import { readFileSync as readFileSync2 } from "node:fs";
994
971
 
995
972
  // src/utils/codex-cmd.ts
996
973
  var READ_LIKE = new Set(["cat", "sed", "head", "tail", "bat", "batcat", "less", "more"]);
@@ -1214,26 +1191,26 @@ function isUserTurnEntry(entry) {
1214
1191
  }
1215
1192
 
1216
1193
  // src/utils/expand-home.ts
1217
- import { homedir as homedir5 } from "node:os";
1218
- import { join as join6, resolve as resolve6 } from "node:path";
1194
+ import { homedir as homedir3 } from "node:os";
1195
+ import { join as join4, resolve as resolve4 } from "node:path";
1219
1196
  function expandHome(p) {
1220
1197
  if (p === "~")
1221
- return homedir5();
1198
+ return homedir3();
1222
1199
  if (p.startsWith("~/"))
1223
- return join6(homedir5(), p.slice(2));
1224
- return resolve6(p);
1200
+ return join4(homedir3(), p.slice(2));
1201
+ return resolve4(p);
1225
1202
  }
1226
1203
 
1227
1204
  // src/utils/jsonl.ts
1228
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
1229
- import { join as join7 } from "node:path";
1205
+ import { readdirSync as readdirSync2, readFileSync, statSync } from "node:fs";
1206
+ import { join as join5 } from "node:path";
1230
1207
  function* findJsonlFiles(dir, since) {
1231
- for (const item of readdirSync3(dir, { withFileTypes: true })) {
1232
- const path = join7(dir, item.name);
1208
+ for (const item of readdirSync2(dir, { withFileTypes: true })) {
1209
+ const path = join5(dir, item.name);
1233
1210
  if (item.isDirectory()) {
1234
1211
  yield* findJsonlFiles(path, since);
1235
1212
  } else if (item.isFile() && item.name.endsWith(".jsonl")) {
1236
- if (!since || statSync2(path).mtime >= since)
1213
+ if (!since || statSync(path).mtime >= since)
1237
1214
  yield path;
1238
1215
  }
1239
1216
  }
@@ -1263,7 +1240,7 @@ function readClaudeUsage(options) {
1263
1240
  let prevSkill = null;
1264
1241
  const sessionAttr = new Map;
1265
1242
  const sessionAct = new Map;
1266
- for (const line of readFileSync5(file, "utf8").split(`
1243
+ for (const line of readFileSync2(file, "utf8").split(`
1267
1244
  `)) {
1268
1245
  if (!line.trim())
1269
1246
  continue;
@@ -1316,14 +1293,14 @@ function readClaudeUsage(options) {
1316
1293
  }
1317
1294
 
1318
1295
  // src/readers/codex.ts
1319
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
1296
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
1320
1297
 
1321
1298
  // src/utils/scope.ts
1322
- import { existsSync as existsSync6 } from "node:fs";
1323
- import { homedir as homedir6 } from "node:os";
1324
- import { dirname as dirname6, join as join8 } from "node:path";
1299
+ import { existsSync as existsSync4 } from "node:fs";
1300
+ import { homedir as homedir4 } from "node:os";
1301
+ import { dirname as dirname3, join as join6 } from "node:path";
1325
1302
  function detectScope(opts) {
1326
- const home = opts.home ?? homedir6();
1303
+ const home = opts.home ?? homedir4();
1327
1304
  if (opts.global || opts.rootOverride)
1328
1305
  return { global: true };
1329
1306
  if (norm(opts.cwd) === norm(home))
@@ -1341,9 +1318,9 @@ function encodeClaudeProjectDir(absPath) {
1341
1318
  function findGitRoot(start) {
1342
1319
  let dir = start;
1343
1320
  while (true) {
1344
- if (existsSync6(join8(dir, ".git")))
1321
+ if (existsSync4(join6(dir, ".git")))
1345
1322
  return dir;
1346
- const parent = dirname6(dir);
1323
+ const parent = dirname3(dir);
1347
1324
  if (parent === dir)
1348
1325
  return;
1349
1326
  dir = parent;
@@ -1357,7 +1334,7 @@ function norm(p) {
1357
1334
  function readSessionCwd(file) {
1358
1335
  let head;
1359
1336
  try {
1360
- head = readFileSync6(file, "utf8");
1337
+ head = readFileSync3(file, "utf8");
1361
1338
  } catch {
1362
1339
  return;
1363
1340
  }
@@ -1390,7 +1367,7 @@ function readCodexActivations(options) {
1390
1367
  continue;
1391
1368
  }
1392
1369
  filesRead++;
1393
- for (const line of readFileSync6(file, "utf8").split(`
1370
+ for (const line of readFileSync3(file, "utf8").split(`
1394
1371
  `)) {
1395
1372
  if (!line.trim())
1396
1373
  continue;
@@ -1414,9 +1391,9 @@ function readCodexMentions(options) {
1414
1391
  const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
1415
1392
  const counts = new Map;
1416
1393
  let linesRead = 0;
1417
- if (!existsSync7(historyPath))
1394
+ if (!existsSync5(historyPath))
1418
1395
  return { counts, filesRead: 0, linesRead: 0 };
1419
- for (const line of readFileSync6(historyPath, "utf8").split(`
1396
+ for (const line of readFileSync3(historyPath, "utf8").split(`
1420
1397
  `)) {
1421
1398
  if (!line.trim())
1422
1399
  continue;
@@ -1442,20 +1419,18 @@ var MINUTE_MS = 60 * SECOND_MS;
1442
1419
  var HOUR_MS = 60 * MINUTE_MS;
1443
1420
  var DAY_MS = 24 * HOUR_MS;
1444
1421
  var UNITS_MS = {
1445
- sec: SECOND_MS,
1446
- min: MINUTE_MS,
1422
+ s: SECOND_MS,
1423
+ m: MINUTE_MS,
1447
1424
  h: HOUR_MS,
1448
1425
  d: DAY_MS,
1449
- w: 7 * DAY_MS,
1450
- m: 30 * DAY_MS,
1451
- y: 365 * DAY_MS
1426
+ w: 7 * DAY_MS
1452
1427
  };
1453
1428
  function parsePeriod(period) {
1454
1429
  if (period === "all")
1455
1430
  return Number.POSITIVE_INFINITY;
1456
- const match = period.match(/^(\d+)(sec|min|[hdwmy])$/);
1431
+ const match = period.match(/^(\d+)([smhdw])$/);
1457
1432
  if (!match) {
1458
- throw new Error(`Invalid period: "${period}". Use values like 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all.`);
1433
+ throw new Error(`Invalid period: "${period}". Use values like 60s, 30m, 24h, 30d, 2w, all.`);
1459
1434
  }
1460
1435
  const unit = UNITS_MS[match[2] ?? ""] ?? 0;
1461
1436
  return Number(match[1]) * unit;
@@ -1490,7 +1465,7 @@ var usageArgs = {
1490
1465
  type: "string",
1491
1466
  alias: "p",
1492
1467
  default: "all",
1493
- description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
1468
+ description: "60s, 30m, 24h, 30d, 2w, all"
1494
1469
  },
1495
1470
  since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
1496
1471
  mode: {
@@ -1522,8 +1497,8 @@ async function runUsage(args) {
1522
1497
  cwd: process.cwd()
1523
1498
  });
1524
1499
  const claudeProjectsRoot = expandHome("~/.claude/projects");
1525
- const claudeRoot = args.root ?? (scope.projectRoot ? join9(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1526
- const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync8(claudeRoot);
1500
+ const claudeRoot = args.root ?? (scope.projectRoot ? join7(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1501
+ const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync6(claudeRoot);
1527
1502
  const lockPath = getLockPath(args.global);
1528
1503
  const skillUniverse = discoverSkills({
1529
1504
  isGlobal: args.global,
@@ -1597,7 +1572,6 @@ async function runUsage(args) {
1597
1572
  let grandActivations = 0;
1598
1573
  for (const { agent, rows } of results) {
1599
1574
  const activations = rows.reduce((acc, r) => acc + r.count, 0);
1600
- console.log("");
1601
1575
  console.log(`${agent} ${rows.length} skill${rows.length === 1 ? "" : "s"} ${activations} time${activations === 1 ? "" : "s"} by ${periodLabel}`);
1602
1576
  if (rows.length === 0)
1603
1577
  continue;
@@ -1625,15 +1599,15 @@ var usageCommand = defineCommand({
1625
1599
  });
1626
1600
 
1627
1601
  // src/utils/update-check.ts
1628
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
1602
+ import { mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
1629
1603
  import { get } from "node:https";
1630
- import { homedir as homedir7 } from "node:os";
1631
- import { dirname as dirname7, join as join10 } from "node:path";
1604
+ import { homedir as homedir5 } from "node:os";
1605
+ import { dirname as dirname4, join as join8 } from "node:path";
1632
1606
  var PKG = "skillio";
1633
1607
  var TTL_MS = 24 * 60 * 60 * 1000;
1634
1608
  var FETCH_TIMEOUT_MS = 1500;
1635
1609
  function getCachePath() {
1636
- return join10(homedir7(), ".cache", "skillio", "version.json");
1610
+ return join8(homedir5(), ".cache", "skillio", "version.json");
1637
1611
  }
1638
1612
  function compareVersions(a, b) {
1639
1613
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1648,23 +1622,23 @@ function compareVersions(a, b) {
1648
1622
  }
1649
1623
  function readCache(path = getCachePath()) {
1650
1624
  try {
1651
- return JSON.parse(readFileSync7(path, "utf8"));
1625
+ return JSON.parse(readFileSync4(path, "utf8"));
1652
1626
  } catch {
1653
1627
  return;
1654
1628
  }
1655
1629
  }
1656
1630
  function writeCache(cache, path = getCachePath()) {
1657
1631
  try {
1658
- mkdirSync2(dirname7(path), { recursive: true });
1659
- writeFileSync2(path, JSON.stringify(cache));
1632
+ mkdirSync(dirname4(path), { recursive: true });
1633
+ writeFileSync(path, JSON.stringify(cache));
1660
1634
  } catch {}
1661
1635
  }
1662
1636
  function fetchLatest() {
1663
- return new Promise((resolve7) => {
1637
+ return new Promise((resolve5) => {
1664
1638
  const req = get(`https://registry.npmjs.org/${PKG}/latest`, { timeout: FETCH_TIMEOUT_MS }, (res) => {
1665
1639
  if (res.statusCode !== 200) {
1666
1640
  res.resume();
1667
- resolve7(undefined);
1641
+ resolve5(undefined);
1668
1642
  return;
1669
1643
  }
1670
1644
  let body = "";
@@ -1675,16 +1649,16 @@ function fetchLatest() {
1675
1649
  res.on("end", () => {
1676
1650
  try {
1677
1651
  const data = JSON.parse(body);
1678
- resolve7(typeof data.version === "string" ? data.version : undefined);
1652
+ resolve5(typeof data.version === "string" ? data.version : undefined);
1679
1653
  } catch {
1680
- resolve7(undefined);
1654
+ resolve5(undefined);
1681
1655
  }
1682
1656
  });
1683
1657
  });
1684
- req.on("error", () => resolve7(undefined));
1658
+ req.on("error", () => resolve5(undefined));
1685
1659
  req.on("timeout", () => {
1686
1660
  req.destroy();
1687
- resolve7(undefined);
1661
+ resolve5(undefined);
1688
1662
  });
1689
1663
  });
1690
1664
  }
@@ -1780,7 +1754,7 @@ function printRootHelp() {
1780
1754
  " -h, --help Show this help and exit",
1781
1755
  " -v, --version Show version and exit",
1782
1756
  " -g, --global Use global scope (default: false)",
1783
- " -p, --period Period for `usage`: 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all (default: all)",
1757
+ " -p, --period Period for `usage`: 60s, 30m, 24h, 30d, 2w, all (default: all)",
1784
1758
  " -a, --agent Agent for `usage`: claude-code, codex (default: both)",
1785
1759
  "",
1786
1760
  "COMMANDS",
@@ -1845,7 +1819,7 @@ var main = defineCommand({
1845
1819
  return;
1846
1820
  const interactive = process.stdout.isTTY && process.stdin.isTTY;
1847
1821
  if (interactive) {
1848
- const { runPicker } = await import("./shared/chunk-eq7h491z.js");
1822
+ const { runPicker } = await import("./shared/chunk-j1p4zpqy.js");
1849
1823
  const status = await runPicker({
1850
1824
  global: args.global ?? false
1851
1825
  });
@@ -0,0 +1,138 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/lock/file.ts
5
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join } from "node:path";
8
+ function getLockPath(global) {
9
+ return global ? join(homedir(), ".agents", ".skill-lock.json") : "skills-lock.json";
10
+ }
11
+ function readLock(path) {
12
+ if (!existsSync(path))
13
+ return { skills: {} };
14
+ return JSON.parse(readFileSync(path, "utf8"));
15
+ }
16
+ function writeLock(path, lock) {
17
+ mkdirSync(dirname(path), { recursive: true });
18
+ const tmp = join(dirname(path), `.${Date.now()}.skill-lock.json`);
19
+ writeFileSync(tmp, `${JSON.stringify(lock, null, 2)}
20
+ `);
21
+ renameSync(tmp, path);
22
+ }
23
+ function removeSkillFromLock(path, skill) {
24
+ if (!existsSync(path))
25
+ return { removed: false };
26
+ const lock = readLock(path);
27
+ if (!Object.hasOwn(lock.skills, skill))
28
+ return { removed: false };
29
+ delete lock.skills[skill];
30
+ writeLock(path, lock);
31
+ return { removed: true };
32
+ }
33
+
34
+ // src/utils/ansi.ts
35
+ var enabled = false;
36
+ function setColorEnabled(value) {
37
+ enabled = value;
38
+ }
39
+ function detectColorSupport() {
40
+ if (process.env.NO_COLOR)
41
+ return false;
42
+ if (process.env.FORCE_COLOR)
43
+ return true;
44
+ return Boolean(process.stdout.isTTY);
45
+ }
46
+ function green(s) {
47
+ return enabled ? `\x1B[32m${s}\x1B[0m` : s;
48
+ }
49
+ function yellow(s) {
50
+ return enabled ? `\x1B[33m${s}\x1B[0m` : s;
51
+ }
52
+ function red(s) {
53
+ return enabled ? `\x1B[31m${s}\x1B[0m` : s;
54
+ }
55
+ function cyan(s) {
56
+ return enabled ? `\x1B[36m${s}\x1B[0m` : s;
57
+ }
58
+
59
+ // src/utils/discover-skills.ts
60
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
61
+ import { homedir as homedir3 } from "node:os";
62
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
63
+
64
+ // src/utils/skill-files.ts
65
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
66
+ import { homedir as homedir2 } from "node:os";
67
+ import { dirname as dirname2, join as join2, resolve } from "node:path";
68
+ var CHARS_PER_TOKEN = 4;
69
+ function extractFrontmatter(content) {
70
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
71
+ return match?.[1];
72
+ }
73
+ function estimateTokens(text) {
74
+ return Math.round(text.length / CHARS_PER_TOKEN);
75
+ }
76
+
77
+ // src/utils/discover-skills.ts
78
+ function resolveRoots(input) {
79
+ if (input.isGlobal) {
80
+ return {
81
+ claude: join3(homedir3(), ".claude", "skills"),
82
+ agents: join3(homedir3(), ".agents", "skills")
83
+ };
84
+ }
85
+ const repo = dirname3(resolve2(input.lockPath));
86
+ return {
87
+ claude: join3(repo, ".claude", "skills"),
88
+ agents: join3(repo, ".agents", "skills")
89
+ };
90
+ }
91
+ function listSkillNames(root) {
92
+ if (!root || !existsSync3(root))
93
+ return [];
94
+ return readdirSync(root).filter((name) => {
95
+ const skill = join3(root, name, "SKILL.md");
96
+ return existsSync3(skill) && statSync(skill).isFile();
97
+ });
98
+ }
99
+ function tokensFromFile(path) {
100
+ const content = readFileSync3(path, "utf8");
101
+ const fm = extractFrontmatter(content);
102
+ if (fm === undefined)
103
+ return { status: "no-frontmatter" };
104
+ return { tokens: estimateTokens(fm), status: "ok" };
105
+ }
106
+ function discoverSkills(input) {
107
+ const roots = resolveRoots(input);
108
+ const lock = readLock(input.lockPath);
109
+ const lockNames = Object.keys(lock.skills);
110
+ const claudeNames = listSkillNames(roots.claude);
111
+ const agentsNames = listSkillNames(roots.agents);
112
+ const all = new Set([...lockNames, ...claudeNames, ...agentsNames]);
113
+ const out = new Map;
114
+ for (const name of all) {
115
+ const sources = [];
116
+ if (lockNames.includes(name))
117
+ sources.push("lock");
118
+ if (claudeNames.includes(name))
119
+ sources.push(".claude");
120
+ if (agentsNames.includes(name))
121
+ sources.push(".agents");
122
+ let skillFile;
123
+ if (claudeNames.includes(name) && roots.claude) {
124
+ skillFile = join3(roots.claude, name, "SKILL.md");
125
+ } else if (agentsNames.includes(name) && roots.agents) {
126
+ skillFile = join3(roots.agents, name, "SKILL.md");
127
+ }
128
+ if (!skillFile) {
129
+ out.set(name, { name, sources, status: "missing" });
130
+ continue;
131
+ }
132
+ const { tokens, status } = tokensFromFile(skillFile);
133
+ out.set(name, { name, sources, skillFile, frontmatterTokens: tokens, status });
134
+ }
135
+ return out;
136
+ }
137
+
138
+ export { __require, getLockPath, readLock, removeSkillFromLock, setColorEnabled, detectColorSupport, green, yellow, red, cyan, discoverSkills };
@@ -0,0 +1,252 @@
1
+ import {
2
+ cyan,
3
+ discoverSkills,
4
+ getLockPath,
5
+ red
6
+ } from "./chunk-0qvp6v8g.js";
7
+
8
+ // src/commands/picker.ts
9
+ import { spawnSync } from "node:child_process";
10
+
11
+ // src/utils/list-removable.ts
12
+ function listRemovableTargets(input) {
13
+ const records = [...discoverSkills(input).values()];
14
+ const inLock = [];
15
+ const orphan = [];
16
+ for (const r of records) {
17
+ if (r.sources.includes("lock"))
18
+ inLock.push(r.name);
19
+ else
20
+ orphan.push(r.name);
21
+ }
22
+ inLock.sort();
23
+ orphan.sort();
24
+ return { inLock, orphan };
25
+ }
26
+
27
+ // src/utils/prompt.ts
28
+ import { emitKeypressEvents } from "node:readline";
29
+ async function select(params) {
30
+ const input = params.input ?? process.stdin;
31
+ const output = params.output ?? process.stdout;
32
+ if (!input.isTTY || !output.isTTY)
33
+ return null;
34
+ let cursor = 0;
35
+ const total = params.options.length;
36
+ function render() {
37
+ output.write(`${params.title}
38
+ `);
39
+ for (let i = 0;i < total; i++) {
40
+ const opt = params.options[i];
41
+ if (!opt)
42
+ continue;
43
+ const marker = i === cursor ? cyan(">") : " ";
44
+ output.write(`${marker} ${opt.label}
45
+ `);
46
+ }
47
+ }
48
+ function clear() {
49
+ output.write(`\x1B[${total + 1}A\x1B[J`);
50
+ }
51
+ emitKeypressEvents(input);
52
+ if (input.setRawMode)
53
+ input.setRawMode(true);
54
+ input.resume();
55
+ render();
56
+ return await new Promise((resolve) => {
57
+ const onKey = (_str, key) => {
58
+ if (key.ctrl && key.name === "c") {
59
+ cleanup();
60
+ resolve(null);
61
+ return;
62
+ }
63
+ if (key.name === "escape" || key.name === "q") {
64
+ cleanup();
65
+ resolve(null);
66
+ return;
67
+ }
68
+ if (key.name === "up" && cursor > 0) {
69
+ cursor--;
70
+ clear();
71
+ render();
72
+ return;
73
+ }
74
+ if (key.name === "down" && cursor < total - 1) {
75
+ cursor++;
76
+ clear();
77
+ render();
78
+ return;
79
+ }
80
+ if (key.name === "return") {
81
+ cleanup();
82
+ const chosen = params.options[cursor]?.value ?? null;
83
+ resolve(chosen);
84
+ return;
85
+ }
86
+ };
87
+ function onSigterm() {
88
+ cleanup();
89
+ resolve(null);
90
+ }
91
+ function cleanup() {
92
+ input.removeListener("keypress", onKey);
93
+ process.removeListener("SIGTERM", onSigterm);
94
+ if (input.setRawMode)
95
+ input.setRawMode(false);
96
+ input.pause();
97
+ }
98
+ process.once("SIGTERM", onSigterm);
99
+ input.on("keypress", onKey);
100
+ });
101
+ }
102
+ async function multiSelect(params) {
103
+ const input = params.input ?? process.stdin;
104
+ const output = params.output ?? process.stdout;
105
+ if (!input.isTTY || !output.isTTY)
106
+ return null;
107
+ let cursor = 0;
108
+ const total = params.options.length;
109
+ const selected = new Set;
110
+ function render() {
111
+ output.write(`${params.title}
112
+ `);
113
+ for (let i = 0;i < total; i++) {
114
+ const opt = params.options[i];
115
+ if (!opt)
116
+ continue;
117
+ const cursorMark = i === cursor ? cyan(">") : " ";
118
+ const checkbox = selected.has(i) ? "[x]" : "[ ]";
119
+ output.write(`${cursorMark} ${checkbox} ${opt.label}
120
+ `);
121
+ }
122
+ }
123
+ function clear() {
124
+ output.write(`\x1B[${total + 1}A\x1B[J`);
125
+ }
126
+ emitKeypressEvents(input);
127
+ if (input.setRawMode)
128
+ input.setRawMode(true);
129
+ input.resume();
130
+ render();
131
+ return await new Promise((resolve) => {
132
+ const onKey = (_str, key) => {
133
+ if (key.ctrl && key.name === "c") {
134
+ cleanup();
135
+ resolve(null);
136
+ return;
137
+ }
138
+ if (key.name === "escape" || key.name === "q") {
139
+ cleanup();
140
+ resolve(null);
141
+ return;
142
+ }
143
+ if (key.name === "up" && cursor > 0) {
144
+ cursor--;
145
+ clear();
146
+ render();
147
+ return;
148
+ }
149
+ if (key.name === "down" && cursor < total - 1) {
150
+ cursor++;
151
+ clear();
152
+ render();
153
+ return;
154
+ }
155
+ if (key.name === "space") {
156
+ if (selected.has(cursor))
157
+ selected.delete(cursor);
158
+ else
159
+ selected.add(cursor);
160
+ clear();
161
+ render();
162
+ return;
163
+ }
164
+ if (key.name === "return") {
165
+ cleanup();
166
+ const values = [];
167
+ for (let i = 0;i < total; i++) {
168
+ if (selected.has(i)) {
169
+ const v = params.options[i]?.value;
170
+ if (v !== undefined)
171
+ values.push(v);
172
+ }
173
+ }
174
+ resolve(values);
175
+ return;
176
+ }
177
+ };
178
+ function onSigterm() {
179
+ cleanup();
180
+ resolve(null);
181
+ }
182
+ function cleanup() {
183
+ input.removeListener("keypress", onKey);
184
+ process.removeListener("SIGTERM", onSigterm);
185
+ if (input.setRawMode)
186
+ input.setRawMode(false);
187
+ input.pause();
188
+ }
189
+ process.once("SIGTERM", onSigterm);
190
+ input.on("keypress", onKey);
191
+ });
192
+ }
193
+
194
+ // src/commands/picker.ts
195
+ async function pickRemoveTargets(args) {
196
+ const lockPath = getLockPath(args.global);
197
+ const { inLock, orphan } = listRemovableTargets({
198
+ isGlobal: args.global,
199
+ cwd: process.cwd(),
200
+ lockPath
201
+ });
202
+ if (inLock.length === 0 && orphan.length === 0) {
203
+ console.log("No skills found in scope.");
204
+ return [];
205
+ }
206
+ const options = [
207
+ ...inLock.map((name) => ({ value: name, label: name })),
208
+ ...orphan.map((name) => ({ value: name, label: `${name} ${red("(orphan)")}` }))
209
+ ];
210
+ return await multiSelect({
211
+ title: "skillio — pick skills to remove (Space toggle, Enter confirm)",
212
+ options
213
+ });
214
+ }
215
+ async function runPicker(args) {
216
+ const choice = await select({
217
+ title: "skillio — pick a command",
218
+ options: [
219
+ { value: "usage", label: "usage — count of skill invocations" },
220
+ { value: "cost", label: "cost — per-skill ambient tokens" },
221
+ { value: "list", label: "list — installed skills per source" },
222
+ { value: "remove", label: "remove — delete a skill (disk-only; lock with --force-lock)" },
223
+ { value: "quit", label: "quit" }
224
+ ]
225
+ });
226
+ if (choice === null || choice === "quit")
227
+ return 0;
228
+ const cliPath = process.argv[1];
229
+ if (!cliPath) {
230
+ console.error("skillio: cannot resolve CLI path (process.argv[1] missing)");
231
+ return 1;
232
+ }
233
+ let argv;
234
+ if (choice === "remove") {
235
+ const targets = await pickRemoveTargets(args);
236
+ if (targets === null || targets.length === 0)
237
+ return 0;
238
+ argv = ["rm", ...targets];
239
+ } else {
240
+ argv = [choice];
241
+ }
242
+ if (args.global)
243
+ argv.push("-g");
244
+ const r = spawnSync(process.execPath, [cliPath, ...argv], {
245
+ stdio: "inherit",
246
+ env: process.env
247
+ });
248
+ return r.status ?? 0;
249
+ }
250
+ export {
251
+ runPicker
252
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillio",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Audit and manage AI agent skills for Claude Code and Codex",
5
5
  "license": "MIT",
6
6
  "author": "ihororlovskyi",
@@ -1,113 +0,0 @@
1
- import {
2
- cyan
3
- } from "./chunk-s3421yr2.js";
4
-
5
- // src/commands/picker.ts
6
- import { spawnSync } from "node:child_process";
7
-
8
- // src/utils/prompt.ts
9
- import { emitKeypressEvents } from "node:readline";
10
- async function select(params) {
11
- const input = params.input ?? process.stdin;
12
- const output = params.output ?? process.stdout;
13
- if (!input.isTTY || !output.isTTY)
14
- return null;
15
- let cursor = 0;
16
- const total = params.options.length;
17
- function render() {
18
- output.write(`${params.title}
19
- `);
20
- for (let i = 0;i < total; i++) {
21
- const opt = params.options[i];
22
- if (!opt)
23
- continue;
24
- const marker = i === cursor ? cyan(">") : " ";
25
- output.write(`${marker} ${opt.label}
26
- `);
27
- }
28
- }
29
- function clear() {
30
- output.write(`\x1B[${total + 1}A\x1B[J`);
31
- }
32
- emitKeypressEvents(input);
33
- if (input.setRawMode)
34
- input.setRawMode(true);
35
- input.resume();
36
- render();
37
- return await new Promise((resolve) => {
38
- const onKey = (_str, key) => {
39
- if (key.ctrl && key.name === "c") {
40
- cleanup();
41
- resolve(null);
42
- return;
43
- }
44
- if (key.name === "escape" || key.name === "q") {
45
- cleanup();
46
- resolve(null);
47
- return;
48
- }
49
- if (key.name === "up" && cursor > 0) {
50
- cursor--;
51
- clear();
52
- render();
53
- return;
54
- }
55
- if (key.name === "down" && cursor < total - 1) {
56
- cursor++;
57
- clear();
58
- render();
59
- return;
60
- }
61
- if (key.name === "return") {
62
- cleanup();
63
- const chosen = params.options[cursor]?.value ?? null;
64
- resolve(chosen);
65
- return;
66
- }
67
- };
68
- function onSigterm() {
69
- cleanup();
70
- resolve(null);
71
- }
72
- function cleanup() {
73
- input.removeListener("keypress", onKey);
74
- process.removeListener("SIGTERM", onSigterm);
75
- if (input.setRawMode)
76
- input.setRawMode(false);
77
- input.pause();
78
- }
79
- process.once("SIGTERM", onSigterm);
80
- input.on("keypress", onKey);
81
- });
82
- }
83
-
84
- // src/commands/picker.ts
85
- async function runPicker(args) {
86
- const choice = await select({
87
- title: "skillio — pick a command",
88
- options: [
89
- { value: "usage", label: "usage — count of skill invocations" },
90
- { value: "cost", label: "cost — per-skill ambient tokens" },
91
- { value: "list", label: "list — installed skills per source" },
92
- { value: "quit", label: "quit" }
93
- ]
94
- });
95
- if (choice === null || choice === "quit")
96
- return 0;
97
- const cliPath = process.argv[1];
98
- if (!cliPath) {
99
- console.error("skillio: cannot resolve CLI path (process.argv[1] missing)");
100
- return 1;
101
- }
102
- const argv = [choice];
103
- if (args.global)
104
- argv.push("-g");
105
- const r = spawnSync(process.execPath, [cliPath, ...argv], {
106
- stdio: "inherit",
107
- env: process.env
108
- });
109
- return r.status ?? 0;
110
- }
111
- export {
112
- runPicker
113
- };
@@ -1,27 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/utils/ansi.ts
5
- var enabled = false;
6
- function setColorEnabled(value) {
7
- enabled = value;
8
- }
9
- function detectColorSupport() {
10
- if (process.env.NO_COLOR)
11
- return false;
12
- return Boolean(process.stdout.isTTY);
13
- }
14
- function green(s) {
15
- return enabled ? `\x1B[32m${s}\x1B[0m` : s;
16
- }
17
- function yellow(s) {
18
- return enabled ? `\x1B[33m${s}\x1B[0m` : s;
19
- }
20
- function red(s) {
21
- return enabled ? `\x1B[31m${s}\x1B[0m` : s;
22
- }
23
- function cyan(s) {
24
- return enabled ? `\x1B[36m${s}\x1B[0m` : s;
25
- }
26
-
27
- export { __require, setColorEnabled, detectColorSupport, green, yellow, red, cyan };