skillio 0.1.10 → 0.1.11

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,90 @@ 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) {
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: "skills-lock.json", 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 rows = bySource(records, roots);
665
+ const claudeSet = new Set(rows.claude.names.map((n) => n.name));
666
+ const agentsSet = new Set(rows.agents.names.map((n) => n.name));
667
+ const orphans = rows.lock.names.filter((n) => !claudeSet.has(n.name) && !agentsSet.has(n.name));
668
+ const sourceRows = [
669
+ {
670
+ row: rows.agents,
671
+ render: () => rows.agents.names.map(paintDisk).join(" ")
672
+ },
673
+ {
674
+ row: rows.claude,
675
+ render: () => rows.claude.names.map(paintDisk).join(" ")
676
+ },
677
+ {
678
+ row: rows.lock,
679
+ render: () => orphans.length === 0 ? green("All skills onboard!") : orphans.map((n) => red(n.name)).join(" ")
680
+ }
681
+ ];
682
+ const labelWidth = Math.max(...sourceRows.map((r) => r.row.label.length));
683
+ const countCells = sourceRows.map((r) => `${r.row.totalCount} skill${r.row.totalCount === 1 ? "" : "s"}`);
766
684
  const countWidth = Math.max(...countCells.map((c) => c.length));
767
685
  for (let i = 0;i < sourceRows.length; i++) {
768
- const row = sourceRows[i];
769
- if (!row)
686
+ const entry = sourceRows[i];
687
+ if (!entry)
770
688
  continue;
771
689
  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}` : ""}`;
690
+ const namesText = entry.render();
691
+ const line = `${entry.row.label.padEnd(labelWidth)} : ${countCell.padEnd(countWidth)}${namesText ? ` : ${namesText}` : ""}`;
774
692
  console.log(line.trimEnd());
775
693
  }
694
+ const claudeNames = rows.claude.names.map((n) => n.name);
695
+ const agentsNames = rows.agents.names.map((n) => n.name);
696
+ const lockNames = rows.lock.names.map((n) => n.name);
697
+ const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
698
+ const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
699
+ const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
776
700
  const diffs = [];
777
701
  if (lockOnly.length) {
778
702
  diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
@@ -792,46 +716,92 @@ var listCommand = defineCommand({
792
716
  });
793
717
 
794
718
  // 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";
719
+ import { existsSync as existsSync3 } from "node:fs";
720
+ import { homedir as homedir2 } from "node:os";
721
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
798
722
 
799
723
  // src/utils/confirm.ts
800
- import { createInterface } from "node:readline/promises";
724
+ import { emitKeypressEvents } from "node:readline";
801
725
  async function confirm(question, opts = {}) {
802
726
  const input = opts.input ?? process.stdin;
803
727
  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();
728
+ if (!input.isTTY || !output.isTTY) {
729
+ const { createInterface } = await import("node:readline/promises");
730
+ const rl = createInterface({ input, output });
731
+ try {
732
+ const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
733
+ return answer === "y" || answer === "yes";
734
+ } finally {
735
+ rl.close();
736
+ }
813
737
  }
738
+ output.write(`${question} [y/N] `);
739
+ emitKeypressEvents(input);
740
+ if (input.setRawMode)
741
+ input.setRawMode(true);
742
+ input.resume();
743
+ return new Promise((resolve2) => {
744
+ const onKey = (str, key) => {
745
+ const lower = (str ?? "").toLowerCase();
746
+ if (key.ctrl && key.name === "c") {
747
+ cleanup();
748
+ output.write(`
749
+ `);
750
+ resolve2(false);
751
+ return;
752
+ }
753
+ if (lower === "y") {
754
+ cleanup();
755
+ output.write(`y
756
+ `);
757
+ resolve2(true);
758
+ return;
759
+ }
760
+ if (lower === "n" || key.name === "return" || key.name === "escape" || lower === "q") {
761
+ cleanup();
762
+ output.write(`${lower || "n"}
763
+ `);
764
+ resolve2(false);
765
+ return;
766
+ }
767
+ };
768
+ function onSigterm() {
769
+ cleanup();
770
+ output.write(`
771
+ `);
772
+ resolve2(false);
773
+ }
774
+ function cleanup() {
775
+ input.removeListener("keypress", onKey);
776
+ process.removeListener("SIGTERM", onSigterm);
777
+ if (input.setRawMode)
778
+ input.setRawMode(false);
779
+ input.pause();
780
+ }
781
+ process.once("SIGTERM", onSigterm);
782
+ input.on("keypress", onKey);
783
+ });
814
784
  }
815
785
 
816
786
  // 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";
787
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, rmSync } from "node:fs";
788
+ import { join as join2, resolve as resolve2 } from "node:path";
819
789
  function isInside(target, root) {
820
- const t = resolve3(target);
821
- const r = resolve3(root);
790
+ const t = resolve2(target);
791
+ const r = resolve2(root);
822
792
  return t === r || t.startsWith(`${r}/`);
823
793
  }
824
794
  function countFiles(path) {
825
- if (!existsSync4(path))
795
+ if (!existsSync2(path))
826
796
  return 0;
827
- const stat = lstatSync(path);
797
+ const stat = lstatSync2(path);
828
798
  if (stat.isFile())
829
799
  return 1;
830
800
  if (!stat.isDirectory())
831
801
  return 0;
832
802
  let n = 0;
833
- for (const entry of readdirSync2(path)) {
834
- n += countFiles(join4(path, entry));
803
+ for (const entry of readdirSync(path)) {
804
+ n += countFiles(join2(path, entry));
835
805
  }
836
806
  return n;
837
807
  }
@@ -840,60 +810,51 @@ function rmSkillDir(path, opts) {
840
810
  if (!safe) {
841
811
  throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
842
812
  }
843
- if (!existsSync4(path))
813
+ if (!existsSync2(path))
844
814
  return { removed: false, fileCount: 0 };
845
815
  const fileCount = countFiles(path);
846
816
  rmSync(path, { recursive: true, force: true });
847
817
  return { removed: true, fileCount };
848
818
  }
849
819
 
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
820
  // src/commands/remove.ts
864
821
  var q = (name) => `"${cyan(name)}"`;
865
822
  function buildTarget(name, isGlobal, lockPath) {
866
823
  const lock = readLock(lockPath);
867
824
  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;
825
+ const baseClaude = isGlobal ? join3(homedir2(), ".claude", "skills") : join3(dirname2(resolve3(lockPath)), ".claude", "skills");
826
+ const baseAgents = isGlobal ? join3(homedir2(), ".agents", "skills") : join3(dirname2(resolve3(lockPath)), ".agents", "skills");
827
+ const claudeDir = existsSync3(join3(baseClaude, name)) ? join3(baseClaude, name) : undefined;
828
+ const agentsDir = existsSync3(join3(baseAgents, name)) ? join3(baseAgents, name) : undefined;
872
829
  return { name, inLock, claudeDir, agentsDir };
873
830
  }
831
+ function collectAllTargets(isGlobal, lockPath) {
832
+ const map = discoverSkills({ isGlobal, cwd: process.cwd(), lockPath });
833
+ return [...map.keys()].sort().map((name) => buildTarget(name, isGlobal, lockPath));
834
+ }
874
835
  function fileCount(dir) {
875
- const { readdirSync: readdirSync3, statSync: statSync2 } = __require("node:fs");
836
+ const { readdirSync: readdirSync2, statSync } = __require("node:fs");
876
837
  let n = 0;
877
838
  const stack = [dir];
878
839
  while (stack.length) {
879
840
  const cur = stack.pop();
880
- const stat = statSync2(cur);
841
+ const stat = statSync(cur);
881
842
  if (stat.isFile())
882
843
  n++;
883
844
  else if (stat.isDirectory())
884
- for (const e of readdirSync3(cur))
885
- stack.push(join5(cur, e));
845
+ for (const e of readdirSync2(cur))
846
+ stack.push(join3(cur, e));
886
847
  }
887
848
  return n;
888
849
  }
889
- function printPlan(plan, lockTracked) {
850
+ function printPlan(plan, modifyLock) {
890
851
  const { target } = plan;
891
852
  console.log(`Will remove ${q(target.name)}:`);
892
853
  if (target.inLock) {
893
- if (lockTracked)
894
- console.log(" - skills-lock.json (skipped: git-tracked; use --force-lock)");
895
- else
854
+ if (modifyLock)
896
855
  console.log(" - skills-lock.json");
856
+ else
857
+ console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
897
858
  } else {
898
859
  console.log(" - skills-lock.json (not in lock)");
899
860
  }
@@ -907,27 +868,38 @@ function printPlan(plan, lockTracked) {
907
868
  console.log(" - .agents/skills/ (not found)");
908
869
  }
909
870
  var removeCommand = defineCommand({
910
- meta: { description: "Remove one or more skills from lock and delete their on-disk directories" },
871
+ meta: {
872
+ description: "Remove one or more skills from on-disk dirs (lock preserved unless --force-lock)"
873
+ },
911
874
  args: {
912
875
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
913
876
  "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
914
877
  yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
878
+ all: { type: "boolean", default: false, description: "Remove every skill in scope" },
915
879
  "force-lock": {
916
880
  type: "boolean",
917
881
  default: false,
918
- description: "Modify skills-lock.json even if it is git-tracked"
882
+ description: "Also remove entry from skills-lock.json (default is to keep lock untouched)"
919
883
  }
920
884
  },
921
885
  async run({ args }) {
922
- const { global: isGlobal, "dry-run": dryRun, yes, "force-lock": forceLock } = args;
886
+ const { global: isGlobal, "dry-run": dryRun, yes, all, "force-lock": modifyLock } = args;
923
887
  const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
924
888
  const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
925
- if (names.length === 0) {
889
+ if (all && names.length > 0) {
890
+ console.error("--all is mutually exclusive with positional skill names");
891
+ process.exit(1);
892
+ }
893
+ if (!all && names.length === 0) {
926
894
  console.error("No skill names provided");
927
895
  process.exit(1);
928
896
  }
929
897
  const lockPath = getLockPath(isGlobal);
930
- const targets = names.map((n) => buildTarget(n, isGlobal, lockPath));
898
+ const targets = all ? collectAllTargets(isGlobal, lockPath) : names.map((n) => buildTarget(n, isGlobal, lockPath));
899
+ if (all && targets.length === 0) {
900
+ console.log("No skills to remove in scope.");
901
+ return;
902
+ }
931
903
  const orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
932
904
  if (orphan.length) {
933
905
  for (const o of orphan)
@@ -939,32 +911,29 @@ var removeCommand = defineCommand({
939
911
  claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
940
912
  agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
941
913
  }));
942
- const lockTracked = !forceLock && isTrackedByGit(lockPath);
943
914
  for (const p of plans) {
944
- printPlan(p, lockTracked);
915
+ printPlan(p, modifyLock);
945
916
  console.log("");
946
917
  }
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
918
  if (dryRun)
951
919
  return;
952
920
  if (!yes) {
953
- const ok = await confirm("Proceed?");
921
+ const promptText = all ? `Remove ALL ${plans.length} skills?` : "Proceed?";
922
+ const ok = await confirm(promptText);
954
923
  if (!ok) {
955
924
  console.log("Aborted");
956
925
  process.exit(1);
957
926
  }
958
927
  }
959
- const allowedRoots = [isGlobal ? homedir4() : dirname5(resolve5(lockPath)), homedir4()];
928
+ const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
960
929
  for (const { target } of plans) {
961
930
  if (target.inLock) {
962
- if (lockTracked) {
963
- console.log(`Skipped skills-lock.json (git-tracked) for ${q(target.name)}`);
964
- } else {
931
+ if (modifyLock) {
965
932
  const r = removeSkillFromLock(lockPath, target.name);
966
933
  if (r.removed)
967
934
  console.log(`Removed ${q(target.name)} from skills-lock.json`);
935
+ } else {
936
+ console.log(`Kept ${q(target.name)} in skills-lock.json (no --force-lock)`);
968
937
  }
969
938
  } else {
970
939
  console.log(`Skipped skills-lock.json (not in lock)`);
@@ -986,11 +955,11 @@ var removeCommand = defineCommand({
986
955
  });
987
956
 
988
957
  // src/commands/usage.ts
989
- import { existsSync as existsSync8 } from "node:fs";
990
- import { join as join9 } from "node:path";
958
+ import { existsSync as existsSync6 } from "node:fs";
959
+ import { join as join7 } from "node:path";
991
960
 
992
961
  // src/readers/claude.ts
993
- import { readFileSync as readFileSync5 } from "node:fs";
962
+ import { readFileSync as readFileSync2 } from "node:fs";
994
963
 
995
964
  // src/utils/codex-cmd.ts
996
965
  var READ_LIKE = new Set(["cat", "sed", "head", "tail", "bat", "batcat", "less", "more"]);
@@ -1214,26 +1183,26 @@ function isUserTurnEntry(entry) {
1214
1183
  }
1215
1184
 
1216
1185
  // src/utils/expand-home.ts
1217
- import { homedir as homedir5 } from "node:os";
1218
- import { join as join6, resolve as resolve6 } from "node:path";
1186
+ import { homedir as homedir3 } from "node:os";
1187
+ import { join as join4, resolve as resolve4 } from "node:path";
1219
1188
  function expandHome(p) {
1220
1189
  if (p === "~")
1221
- return homedir5();
1190
+ return homedir3();
1222
1191
  if (p.startsWith("~/"))
1223
- return join6(homedir5(), p.slice(2));
1224
- return resolve6(p);
1192
+ return join4(homedir3(), p.slice(2));
1193
+ return resolve4(p);
1225
1194
  }
1226
1195
 
1227
1196
  // 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";
1197
+ import { readdirSync as readdirSync2, readFileSync, statSync } from "node:fs";
1198
+ import { join as join5 } from "node:path";
1230
1199
  function* findJsonlFiles(dir, since) {
1231
- for (const item of readdirSync3(dir, { withFileTypes: true })) {
1232
- const path = join7(dir, item.name);
1200
+ for (const item of readdirSync2(dir, { withFileTypes: true })) {
1201
+ const path = join5(dir, item.name);
1233
1202
  if (item.isDirectory()) {
1234
1203
  yield* findJsonlFiles(path, since);
1235
1204
  } else if (item.isFile() && item.name.endsWith(".jsonl")) {
1236
- if (!since || statSync2(path).mtime >= since)
1205
+ if (!since || statSync(path).mtime >= since)
1237
1206
  yield path;
1238
1207
  }
1239
1208
  }
@@ -1263,7 +1232,7 @@ function readClaudeUsage(options) {
1263
1232
  let prevSkill = null;
1264
1233
  const sessionAttr = new Map;
1265
1234
  const sessionAct = new Map;
1266
- for (const line of readFileSync5(file, "utf8").split(`
1235
+ for (const line of readFileSync2(file, "utf8").split(`
1267
1236
  `)) {
1268
1237
  if (!line.trim())
1269
1238
  continue;
@@ -1316,14 +1285,14 @@ function readClaudeUsage(options) {
1316
1285
  }
1317
1286
 
1318
1287
  // src/readers/codex.ts
1319
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
1288
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
1320
1289
 
1321
1290
  // 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";
1291
+ import { existsSync as existsSync4 } from "node:fs";
1292
+ import { homedir as homedir4 } from "node:os";
1293
+ import { dirname as dirname3, join as join6 } from "node:path";
1325
1294
  function detectScope(opts) {
1326
- const home = opts.home ?? homedir6();
1295
+ const home = opts.home ?? homedir4();
1327
1296
  if (opts.global || opts.rootOverride)
1328
1297
  return { global: true };
1329
1298
  if (norm(opts.cwd) === norm(home))
@@ -1341,9 +1310,9 @@ function encodeClaudeProjectDir(absPath) {
1341
1310
  function findGitRoot(start) {
1342
1311
  let dir = start;
1343
1312
  while (true) {
1344
- if (existsSync6(join8(dir, ".git")))
1313
+ if (existsSync4(join6(dir, ".git")))
1345
1314
  return dir;
1346
- const parent = dirname6(dir);
1315
+ const parent = dirname3(dir);
1347
1316
  if (parent === dir)
1348
1317
  return;
1349
1318
  dir = parent;
@@ -1357,7 +1326,7 @@ function norm(p) {
1357
1326
  function readSessionCwd(file) {
1358
1327
  let head;
1359
1328
  try {
1360
- head = readFileSync6(file, "utf8");
1329
+ head = readFileSync3(file, "utf8");
1361
1330
  } catch {
1362
1331
  return;
1363
1332
  }
@@ -1390,7 +1359,7 @@ function readCodexActivations(options) {
1390
1359
  continue;
1391
1360
  }
1392
1361
  filesRead++;
1393
- for (const line of readFileSync6(file, "utf8").split(`
1362
+ for (const line of readFileSync3(file, "utf8").split(`
1394
1363
  `)) {
1395
1364
  if (!line.trim())
1396
1365
  continue;
@@ -1414,9 +1383,9 @@ function readCodexMentions(options) {
1414
1383
  const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
1415
1384
  const counts = new Map;
1416
1385
  let linesRead = 0;
1417
- if (!existsSync7(historyPath))
1386
+ if (!existsSync5(historyPath))
1418
1387
  return { counts, filesRead: 0, linesRead: 0 };
1419
- for (const line of readFileSync6(historyPath, "utf8").split(`
1388
+ for (const line of readFileSync3(historyPath, "utf8").split(`
1420
1389
  `)) {
1421
1390
  if (!line.trim())
1422
1391
  continue;
@@ -1442,20 +1411,18 @@ var MINUTE_MS = 60 * SECOND_MS;
1442
1411
  var HOUR_MS = 60 * MINUTE_MS;
1443
1412
  var DAY_MS = 24 * HOUR_MS;
1444
1413
  var UNITS_MS = {
1445
- sec: SECOND_MS,
1446
- min: MINUTE_MS,
1414
+ s: SECOND_MS,
1415
+ m: MINUTE_MS,
1447
1416
  h: HOUR_MS,
1448
1417
  d: DAY_MS,
1449
- w: 7 * DAY_MS,
1450
- m: 30 * DAY_MS,
1451
- y: 365 * DAY_MS
1418
+ w: 7 * DAY_MS
1452
1419
  };
1453
1420
  function parsePeriod(period) {
1454
1421
  if (period === "all")
1455
1422
  return Number.POSITIVE_INFINITY;
1456
- const match = period.match(/^(\d+)(sec|min|[hdwmy])$/);
1423
+ const match = period.match(/^(\d+)([smhdw])$/);
1457
1424
  if (!match) {
1458
- throw new Error(`Invalid period: "${period}". Use values like 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all.`);
1425
+ throw new Error(`Invalid period: "${period}". Use values like 60s, 30m, 24h, 30d, 2w, all.`);
1459
1426
  }
1460
1427
  const unit = UNITS_MS[match[2] ?? ""] ?? 0;
1461
1428
  return Number(match[1]) * unit;
@@ -1490,7 +1457,7 @@ var usageArgs = {
1490
1457
  type: "string",
1491
1458
  alias: "p",
1492
1459
  default: "all",
1493
- description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
1460
+ description: "60s, 30m, 24h, 30d, 2w, all"
1494
1461
  },
1495
1462
  since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
1496
1463
  mode: {
@@ -1522,8 +1489,8 @@ async function runUsage(args) {
1522
1489
  cwd: process.cwd()
1523
1490
  });
1524
1491
  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);
1492
+ const claudeRoot = args.root ?? (scope.projectRoot ? join7(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1493
+ const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync6(claudeRoot);
1527
1494
  const lockPath = getLockPath(args.global);
1528
1495
  const skillUniverse = discoverSkills({
1529
1496
  isGlobal: args.global,
@@ -1597,7 +1564,6 @@ async function runUsage(args) {
1597
1564
  let grandActivations = 0;
1598
1565
  for (const { agent, rows } of results) {
1599
1566
  const activations = rows.reduce((acc, r) => acc + r.count, 0);
1600
- console.log("");
1601
1567
  console.log(`${agent} ${rows.length} skill${rows.length === 1 ? "" : "s"} ${activations} time${activations === 1 ? "" : "s"} by ${periodLabel}`);
1602
1568
  if (rows.length === 0)
1603
1569
  continue;
@@ -1625,15 +1591,15 @@ var usageCommand = defineCommand({
1625
1591
  });
1626
1592
 
1627
1593
  // src/utils/update-check.ts
1628
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
1594
+ import { mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
1629
1595
  import { get } from "node:https";
1630
- import { homedir as homedir7 } from "node:os";
1631
- import { dirname as dirname7, join as join10 } from "node:path";
1596
+ import { homedir as homedir5 } from "node:os";
1597
+ import { dirname as dirname4, join as join8 } from "node:path";
1632
1598
  var PKG = "skillio";
1633
1599
  var TTL_MS = 24 * 60 * 60 * 1000;
1634
1600
  var FETCH_TIMEOUT_MS = 1500;
1635
1601
  function getCachePath() {
1636
- return join10(homedir7(), ".cache", "skillio", "version.json");
1602
+ return join8(homedir5(), ".cache", "skillio", "version.json");
1637
1603
  }
1638
1604
  function compareVersions(a, b) {
1639
1605
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1648,23 +1614,23 @@ function compareVersions(a, b) {
1648
1614
  }
1649
1615
  function readCache(path = getCachePath()) {
1650
1616
  try {
1651
- return JSON.parse(readFileSync7(path, "utf8"));
1617
+ return JSON.parse(readFileSync4(path, "utf8"));
1652
1618
  } catch {
1653
1619
  return;
1654
1620
  }
1655
1621
  }
1656
1622
  function writeCache(cache, path = getCachePath()) {
1657
1623
  try {
1658
- mkdirSync2(dirname7(path), { recursive: true });
1659
- writeFileSync2(path, JSON.stringify(cache));
1624
+ mkdirSync(dirname4(path), { recursive: true });
1625
+ writeFileSync(path, JSON.stringify(cache));
1660
1626
  } catch {}
1661
1627
  }
1662
1628
  function fetchLatest() {
1663
- return new Promise((resolve7) => {
1629
+ return new Promise((resolve5) => {
1664
1630
  const req = get(`https://registry.npmjs.org/${PKG}/latest`, { timeout: FETCH_TIMEOUT_MS }, (res) => {
1665
1631
  if (res.statusCode !== 200) {
1666
1632
  res.resume();
1667
- resolve7(undefined);
1633
+ resolve5(undefined);
1668
1634
  return;
1669
1635
  }
1670
1636
  let body = "";
@@ -1675,16 +1641,16 @@ function fetchLatest() {
1675
1641
  res.on("end", () => {
1676
1642
  try {
1677
1643
  const data = JSON.parse(body);
1678
- resolve7(typeof data.version === "string" ? data.version : undefined);
1644
+ resolve5(typeof data.version === "string" ? data.version : undefined);
1679
1645
  } catch {
1680
- resolve7(undefined);
1646
+ resolve5(undefined);
1681
1647
  }
1682
1648
  });
1683
1649
  });
1684
- req.on("error", () => resolve7(undefined));
1650
+ req.on("error", () => resolve5(undefined));
1685
1651
  req.on("timeout", () => {
1686
1652
  req.destroy();
1687
- resolve7(undefined);
1653
+ resolve5(undefined);
1688
1654
  });
1689
1655
  });
1690
1656
  }
@@ -1780,7 +1746,7 @@ function printRootHelp() {
1780
1746
  " -h, --help Show this help and exit",
1781
1747
  " -v, --version Show version and exit",
1782
1748
  " -g, --global Use global scope (default: false)",
1783
- " -p, --period Period for `usage`: 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all (default: all)",
1749
+ " -p, --period Period for `usage`: 60s, 30m, 24h, 30d, 2w, all (default: all)",
1784
1750
  " -a, --agent Agent for `usage`: claude-code, codex (default: both)",
1785
1751
  "",
1786
1752
  "COMMANDS",
@@ -1845,7 +1811,7 @@ var main = defineCommand({
1845
1811
  return;
1846
1812
  const interactive = process.stdout.isTTY && process.stdin.isTTY;
1847
1813
  if (interactive) {
1848
- const { runPicker } = await import("./shared/chunk-eq7h491z.js");
1814
+ const { runPicker } = await import("./shared/chunk-j9jc4e4c.js");
1849
1815
  const status = await runPicker({
1850
1816
  global: args.global ?? false
1851
1817
  });
@@ -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 };
@@ -1,10 +1,29 @@
1
1
  import {
2
- cyan
3
- } from "./chunk-s3421yr2.js";
2
+ cyan,
3
+ discoverSkills,
4
+ getLockPath,
5
+ red
6
+ } from "./chunk-0qvp6v8g.js";
4
7
 
5
8
  // src/commands/picker.ts
6
9
  import { spawnSync } from "node:child_process";
7
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
+
8
27
  // src/utils/prompt.ts
9
28
  import { emitKeypressEvents } from "node:readline";
10
29
  async function select(params) {
@@ -82,6 +101,28 @@ async function select(params) {
82
101
  }
83
102
 
84
103
  // src/commands/picker.ts
104
+ var CANCEL = "__cancel__";
105
+ async function pickRemoveTarget(args) {
106
+ const lockPath = getLockPath(args.global);
107
+ const { inLock, orphan } = listRemovableTargets({
108
+ isGlobal: args.global,
109
+ cwd: process.cwd(),
110
+ lockPath
111
+ });
112
+ if (inLock.length === 0 && orphan.length === 0) {
113
+ console.log("No skills found in scope.");
114
+ return null;
115
+ }
116
+ const options = [
117
+ ...inLock.map((name) => ({ value: name, label: name })),
118
+ ...orphan.map((name) => ({ value: name, label: `${name} ${red("(orphan)")}` })),
119
+ { value: CANCEL, label: "cancel" }
120
+ ];
121
+ const choice = await select({ title: "skillio — pick a skill to remove", options });
122
+ if (choice === null || choice === CANCEL)
123
+ return null;
124
+ return choice;
125
+ }
85
126
  async function runPicker(args) {
86
127
  const choice = await select({
87
128
  title: "skillio — pick a command",
@@ -89,6 +130,7 @@ async function runPicker(args) {
89
130
  { value: "usage", label: "usage — count of skill invocations" },
90
131
  { value: "cost", label: "cost — per-skill ambient tokens" },
91
132
  { value: "list", label: "list — installed skills per source" },
133
+ { value: "remove", label: "remove — delete a skill (disk-only; lock with --force-lock)" },
92
134
  { value: "quit", label: "quit" }
93
135
  ]
94
136
  });
@@ -99,7 +141,15 @@ async function runPicker(args) {
99
141
  console.error("skillio: cannot resolve CLI path (process.argv[1] missing)");
100
142
  return 1;
101
143
  }
102
- const argv = [choice];
144
+ let argv;
145
+ if (choice === "remove") {
146
+ const target = await pickRemoveTarget(args);
147
+ if (target === null)
148
+ return 0;
149
+ argv = ["rm", target];
150
+ } else {
151
+ argv = [choice];
152
+ }
103
153
  if (args.global)
104
154
  argv.push("-g");
105
155
  const r = spawnSync(process.execPath, [cliPath, ...argv], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillio",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Audit and manage AI agent skills for Claude Code and Codex",
5
5
  "license": "MIT",
6
6
  "author": "ihororlovskyi",
@@ -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 };