skillio 0.1.9 → 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
@@ -1,9 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
2
+ import {
3
+ __require,
4
+ cyan,
5
+ detectColorSupport,
6
+ discoverSkills,
7
+ getLockPath,
8
+ green,
9
+ readLock,
10
+ red,
11
+ removeSkillFromLock,
12
+ setColorEnabled,
13
+ yellow
14
+ } from "./shared/chunk-0qvp6v8g.js";
4
15
 
5
16
  // src/cli.ts
6
- import { createRequire as createRequire2 } from "node:module";
17
+ import { createRequire } from "node:module";
7
18
 
8
19
  // node_modules/citty/dist/_chunks/libs/scule.mjs
9
20
  var NUMBER_CHAR_RE = /\d/;
@@ -222,7 +233,7 @@ var noColor = /* @__PURE__ */ (() => {
222
233
  })();
223
234
  var _c = (c, r = 39) => (t) => noColor ? t : `\x1B[${c}m${t}\x1B[${r}m`;
224
235
  var bold = /* @__PURE__ */ _c(1, 22);
225
- var cyan = /* @__PURE__ */ _c(36);
236
+ var cyan2 = /* @__PURE__ */ _c(36);
226
237
  var gray = /* @__PURE__ */ _c(90);
227
238
  var underline = /* @__PURE__ */ _c(4, 24);
228
239
  function parseArgs(rawArgs, argsDef) {
@@ -274,7 +285,7 @@ function parseArgs(rawArgs, argsDef) {
274
285
  const argument = parsedArgsProxy[arg.name];
275
286
  const options = arg.options || [];
276
287
  if (argument !== undefined && options.length > 0 && !options.includes(argument))
277
- throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
288
+ throw new CLIError(`Invalid value for argument: ${cyan2(`--${arg.name}`)} (${cyan2(argument)}). Expected one of: ${options.map((o) => cyan2(o)).join(", ")}.`, "EARG");
278
289
  } else if (arg.required && parsedArgsProxy[arg.name] === undefined)
279
290
  throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
280
291
  return parsedArgsProxy;
@@ -319,7 +330,7 @@ async function runCommand(cmd, opts) {
319
330
  if (explicitName) {
320
331
  const subCommand = await _findSubCommand(subCommands, explicitName);
321
332
  if (!subCommand)
322
- throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
333
+ throw new CLIError(`Unknown command ${cyan2(explicitName)}`, "E_UNKNOWN_COMMAND");
323
334
  await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
324
335
  } else {
325
336
  const defaultSubCommand = await resolveValue(cmd.default);
@@ -328,7 +339,7 @@ async function runCommand(cmd, opts) {
328
339
  throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
329
340
  const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
330
341
  if (!subCommand)
331
- throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
342
+ throw new CLIError(`Default sub command ${cyan2(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
332
343
  await runCommand(subCommand, { rawArgs: opts.rawArgs });
333
344
  } else if (!cmd.run)
334
345
  throw new CLIError(`No command specified.`, "E_NO_COMMAND");
@@ -432,15 +443,15 @@ async function renderUsage(cmd, parent) {
432
443
  if (arg.type === "positional") {
433
444
  const name = arg.name.toUpperCase();
434
445
  const isRequired = arg.required !== false && arg.default === undefined;
435
- posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
446
+ posLines.push([cyan2(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
436
447
  usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
437
448
  } else {
438
449
  const isRequired = arg.required === true && arg.default === undefined;
439
450
  const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
440
- argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
451
+ argLines.push([cyan2(argStr), renderDescription(arg, isRequired)]);
441
452
  if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
442
453
  const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
443
- argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
454
+ argLines.push([cyan2(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
444
455
  }
445
456
  if (isRequired)
446
457
  usageLine.push(`--${arg.name}` + renderValueHint(arg));
@@ -454,7 +465,7 @@ async function renderUsage(cmd, parent) {
454
465
  continue;
455
466
  const aliases = toArray(meta?.alias);
456
467
  const label = [name, ...aliases].join(", ");
457
- commandsLines.push([cyan(label), meta?.description || ""]);
468
+ commandsLines.push([cyan2(label), meta?.description || ""]);
458
469
  commandNames.push(name, ...aliases);
459
470
  }
460
471
  usageLine.push(commandNames.join("|"));
@@ -463,7 +474,7 @@ async function renderUsage(cmd, parent) {
463
474
  const version = cmdMeta.version || parentMeta.version;
464
475
  usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
465
476
  const hasOptions = argLines.length > 0 || posLines.length > 0;
466
- usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
477
+ usageLines.push(`${underline(bold("USAGE"))} ${cyan2(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
467
478
  if (posLines.length > 0) {
468
479
  usageLines.push(underline(bold("ARGUMENTS")), "");
469
480
  usageLines.push(formatLineColumns(posLines, " "));
@@ -477,7 +488,7 @@ async function renderUsage(cmd, parent) {
477
488
  if (commandsLines.length > 0) {
478
489
  usageLines.push(underline(bold("COMMANDS")), "");
479
490
  usageLines.push(formatLineColumns(commandsLines, " "));
480
- usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
491
+ usageLines.push("", `Use ${cyan2(`${commandName} <command> --help`)} for more information about a command.`);
481
492
  }
482
493
  return usageLines.filter((l) => typeof l === "string").join(`
483
494
  `);
@@ -547,138 +558,6 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
547
558
  return [`--${long}`, `-${short}`];
548
559
  }
549
560
 
550
- // src/lock/file.ts
551
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
552
- import { homedir } from "node:os";
553
- import { dirname, join } from "node:path";
554
- function getLockPath(global) {
555
- return global ? join(homedir(), ".agents", ".skill-lock.json") : "skills-lock.json";
556
- }
557
- function readLock(path) {
558
- if (!existsSync(path))
559
- return { skills: {} };
560
- return JSON.parse(readFileSync(path, "utf8"));
561
- }
562
- function writeLock(path, lock) {
563
- mkdirSync(dirname(path), { recursive: true });
564
- const tmp = join(dirname(path), `.${Date.now()}.skill-lock.json`);
565
- writeFileSync(tmp, `${JSON.stringify(lock, null, 2)}
566
- `);
567
- renameSync(tmp, path);
568
- }
569
- function removeSkillFromLock(path, skill) {
570
- if (!existsSync(path))
571
- return { removed: false };
572
- const lock = readLock(path);
573
- if (!Object.hasOwn(lock.skills, skill))
574
- return { removed: false };
575
- delete lock.skills[skill];
576
- writeLock(path, lock);
577
- return { removed: true };
578
- }
579
-
580
- // src/utils/ansi.ts
581
- var enabled = false;
582
- function setColorEnabled(value) {
583
- enabled = value;
584
- }
585
- function detectColorSupport() {
586
- if (process.env.NO_COLOR)
587
- return false;
588
- return Boolean(process.stdout.isTTY);
589
- }
590
- function green(s) {
591
- return enabled ? `\x1B[32m${s}\x1B[0m` : s;
592
- }
593
- function yellow(s) {
594
- return enabled ? `\x1B[33m${s}\x1B[0m` : s;
595
- }
596
- function red(s) {
597
- return enabled ? `\x1B[31m${s}\x1B[0m` : s;
598
- }
599
- function cyan2(s) {
600
- return enabled ? `\x1B[36m${s}\x1B[0m` : s;
601
- }
602
-
603
- // src/utils/discover-skills.ts
604
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
605
- import { homedir as homedir3 } from "node:os";
606
- import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
607
-
608
- // src/utils/skill-files.ts
609
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
610
- import { homedir as homedir2 } from "node:os";
611
- import { dirname as dirname2, join as join2, resolve } from "node:path";
612
- var CHARS_PER_TOKEN = 4;
613
- function extractFrontmatter(content) {
614
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
615
- return match?.[1];
616
- }
617
- function estimateTokens(text) {
618
- return Math.round(text.length / CHARS_PER_TOKEN);
619
- }
620
-
621
- // src/utils/discover-skills.ts
622
- function resolveRoots(input) {
623
- if (input.isGlobal) {
624
- return {
625
- claude: join3(homedir3(), ".claude", "skills"),
626
- agents: join3(homedir3(), ".agents", "skills")
627
- };
628
- }
629
- const repo = dirname3(resolve2(input.lockPath));
630
- return {
631
- claude: join3(repo, ".claude", "skills"),
632
- agents: join3(repo, ".agents", "skills")
633
- };
634
- }
635
- function listSkillNames(root) {
636
- if (!root || !existsSync3(root))
637
- return [];
638
- return readdirSync(root).filter((name) => {
639
- const skill = join3(root, name, "SKILL.md");
640
- return existsSync3(skill) && statSync(skill).isFile();
641
- });
642
- }
643
- function tokensFromFile(path) {
644
- const content = readFileSync3(path, "utf8");
645
- const fm = extractFrontmatter(content);
646
- if (fm === undefined)
647
- return { status: "no-frontmatter" };
648
- return { tokens: estimateTokens(fm), status: "ok" };
649
- }
650
- function discoverSkills(input) {
651
- const roots = resolveRoots(input);
652
- const lock = readLock(input.lockPath);
653
- const lockNames = Object.keys(lock.skills);
654
- const claudeNames = listSkillNames(roots.claude);
655
- const agentsNames = listSkillNames(roots.agents);
656
- const all = new Set([...lockNames, ...claudeNames, ...agentsNames]);
657
- const out = new Map;
658
- for (const name of all) {
659
- const sources = [];
660
- if (lockNames.includes(name))
661
- sources.push("lock");
662
- if (claudeNames.includes(name))
663
- sources.push(".claude");
664
- if (agentsNames.includes(name))
665
- sources.push(".agents");
666
- let skillFile;
667
- if (claudeNames.includes(name) && roots.claude) {
668
- skillFile = join3(roots.claude, name, "SKILL.md");
669
- } else if (agentsNames.includes(name) && roots.agents) {
670
- skillFile = join3(roots.agents, name, "SKILL.md");
671
- }
672
- if (!skillFile) {
673
- out.set(name, { name, sources, status: "missing" });
674
- continue;
675
- }
676
- const { tokens, status } = tokensFromFile(skillFile);
677
- out.set(name, { name, sources, skillFile, frontmatterTokens: tokens, status });
678
- }
679
- return out;
680
- }
681
-
682
561
  // src/commands/cost.ts
683
562
  function classify(total) {
684
563
  if (total < 1000)
@@ -705,23 +584,28 @@ var costCommand = defineCommand({
705
584
  const rows = sortRows([...map.values()]);
706
585
  const total = rows.reduce((acc, r) => acc + (r.frontmatterTokens ?? 0), 0);
707
586
  const { message, paint } = classify(total);
708
- console.log(args.global ? "Global" : "Local");
709
587
  console.log("");
588
+ console.log(args.global ? "Global" : "Local");
710
589
  if (rows.length === 0) {
711
590
  console.log(`No skills in ${lockPath}`);
712
591
  return;
713
592
  }
714
593
  const nameWidth = Math.max(...rows.map((r) => r.name.length));
594
+ const tokenWidth = Math.max(...rows.map((r) => r.status === "ok" ? `~${r.frontmatterTokens} tok`.length : r.status === "missing" ? "~? tok".length : "(no frontmatter)".length));
715
595
  for (const r of rows) {
716
- let cell;
717
- if (r.status === "ok")
718
- cell = `~${r.frontmatterTokens} tok`;
719
- else if (r.status === "missing")
720
- cell = "missing";
721
- else
722
- cell = "(no frontmatter)";
723
- const pad = " ".repeat(nameWidth - r.name.length);
724
- console.log(`${cyan2(r.name)}${pad} ${cell}`);
596
+ let tokenCell;
597
+ let suffix = "";
598
+ if (r.status === "ok") {
599
+ tokenCell = `~${r.frontmatterTokens} tok`;
600
+ } else if (r.status === "missing") {
601
+ tokenCell = "~? tok";
602
+ suffix = ` ${red("missing")}`;
603
+ } else {
604
+ tokenCell = "(no frontmatter)";
605
+ }
606
+ const namePad = " ".repeat(nameWidth - r.name.length);
607
+ const tokenPad = " ".repeat(Math.max(0, tokenWidth - tokenCell.length));
608
+ console.log(`${cyan(r.name)}${namePad} ${tokenCell}${tokenPad}${suffix}`);
725
609
  }
726
610
  console.log("");
727
611
  console.log(`Total: ~${total} tok across ${rows.length} skills ${paint(message)}`);
@@ -729,82 +613,99 @@ var costCommand = defineCommand({
729
613
  });
730
614
 
731
615
  // src/commands/list.ts
732
- import { existsSync as existsSync4 } from "node:fs";
733
- import { dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
734
- function bySource(records) {
735
- const claudeNames = records.filter((r) => r.sources.includes(".claude")).map((r) => r.name).sort();
736
- const agentsNames = records.filter((r) => r.sources.includes(".agents")).map((r) => r.name).sort();
737
- const lockNames = records.filter((r) => r.sources.includes("lock")).map((r) => r.name).sort();
738
- 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));
739
644
  return {
740
- claude: {
741
- label: ".claude/skills",
742
- names: claudeNames,
743
- tokens: sumTokens(claudeNames),
744
- exists: true
745
- },
746
- agents: {
747
- label: ".agents/skills",
748
- names: agentsNames,
749
- tokens: sumTokens(agentsNames),
750
- exists: true
751
- },
752
- lock: {
753
- label: "skills-lock.json",
754
- names: lockNames,
755
- tokens: sumTokens(lockNames),
756
- exists: true
757
- }
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 }
758
648
  };
759
649
  }
760
- function agentsDirExists(isGlobal, lockPath) {
761
- if (isGlobal) {
762
- return existsSync4(join4(process.env.HOME ?? "", ".agents", "skills"));
763
- }
764
- return existsSync4(join4(dirname4(resolve3(lockPath)), ".agents", "skills"));
765
- }
766
650
  var listCommand = defineCommand({
767
- 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" },
768
652
  args: {
769
653
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
770
654
  },
771
655
  run({ args }) {
772
656
  const lockPath = getLockPath(args.global);
773
- const map = discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath });
774
- const records = [...map.values()];
775
- const rows = bySource(records);
776
- const showAgents = agentsDirExists(args.global, lockPath) || rows.agents.names.length > 0;
777
- const claudeNames = rows.claude.names;
778
- const agentsNames = rows.agents.names;
779
- const lockNames = rows.lock.names;
780
- const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
781
- const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
782
- const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
783
- const sourceRows = [rows.claude];
784
- if (showAgents)
785
- sourceRows.push(rows.agents);
786
- sourceRows.push(rows.lock);
787
- const labelWidth = Math.max(...sourceRows.map((r) => r.label.length));
788
- const countCells = sourceRows.map((r) => r.names.length === 0 ? "(empty)" : `${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"}`);
789
684
  const countWidth = Math.max(...countCells.map((c) => c.length));
790
685
  for (let i = 0;i < sourceRows.length; i++) {
791
- const row = sourceRows[i];
792
- if (!row)
686
+ const entry = sourceRows[i];
687
+ if (!entry)
793
688
  continue;
794
689
  const countCell = countCells[i] ?? "";
795
- const namesText = row.names.length ? row.names.map(cyan2).join(" ") : "";
796
- 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}` : ""}`;
797
692
  console.log(line.trimEnd());
798
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));
799
700
  const diffs = [];
800
701
  if (lockOnly.length) {
801
- diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan2).join(", ")}`);
702
+ diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
802
703
  }
803
704
  if (claudeNotInLock.length) {
804
- diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan2).join(", ")}`);
705
+ diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan).join(", ")}`);
805
706
  }
806
707
  if (agentsNotInLock.length) {
807
- diffs.push(`.agents/skills has ${agentsNotInLock.length} skill${agentsNotInLock.length === 1 ? "" : "s"} not in lock: ${agentsNotInLock.map(cyan2).join(", ")}`);
708
+ diffs.push(`.agents/skills has ${agentsNotInLock.length} skill${agentsNotInLock.length === 1 ? "" : "s"} not in lock: ${agentsNotInLock.map(cyan).join(", ")}`);
808
709
  }
809
710
  if (diffs.length) {
810
711
  console.log("");
@@ -815,46 +716,92 @@ var listCommand = defineCommand({
815
716
  });
816
717
 
817
718
  // src/commands/remove.ts
818
- import { existsSync as existsSync6 } from "node:fs";
819
- import { homedir as homedir4 } from "node:os";
820
- import { dirname as dirname5, join as join6, 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";
821
722
 
822
723
  // src/utils/confirm.ts
823
- import { createInterface } from "node:readline/promises";
724
+ import { emitKeypressEvents } from "node:readline";
824
725
  async function confirm(question, opts = {}) {
825
726
  const input = opts.input ?? process.stdin;
826
727
  const output = opts.output ?? process.stdout;
827
- const rl = createInterface({
828
- input,
829
- output
830
- });
831
- try {
832
- const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
833
- return answer === "y" || answer === "yes";
834
- } finally {
835
- 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
+ }
836
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
+ });
837
784
  }
838
785
 
839
786
  // src/utils/fs-rm.ts
840
- import { existsSync as existsSync5, lstatSync, readdirSync as readdirSync2, rmSync } from "node:fs";
841
- import { join as join5, resolve as resolve4 } 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";
842
789
  function isInside(target, root) {
843
- const t = resolve4(target);
844
- const r = resolve4(root);
790
+ const t = resolve2(target);
791
+ const r = resolve2(root);
845
792
  return t === r || t.startsWith(`${r}/`);
846
793
  }
847
794
  function countFiles(path) {
848
- if (!existsSync5(path))
795
+ if (!existsSync2(path))
849
796
  return 0;
850
- const stat = lstatSync(path);
797
+ const stat = lstatSync2(path);
851
798
  if (stat.isFile())
852
799
  return 1;
853
800
  if (!stat.isDirectory())
854
801
  return 0;
855
802
  let n = 0;
856
- for (const entry of readdirSync2(path)) {
857
- n += countFiles(join5(path, entry));
803
+ for (const entry of readdirSync(path)) {
804
+ n += countFiles(join2(path, entry));
858
805
  }
859
806
  return n;
860
807
  }
@@ -863,7 +810,7 @@ function rmSkillDir(path, opts) {
863
810
  if (!safe) {
864
811
  throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
865
812
  }
866
- if (!existsSync5(path))
813
+ if (!existsSync2(path))
867
814
  return { removed: false, fileCount: 0 };
868
815
  const fileCount = countFiles(path);
869
816
  rmSync(path, { recursive: true, force: true });
@@ -871,38 +818,46 @@ function rmSkillDir(path, opts) {
871
818
  }
872
819
 
873
820
  // src/commands/remove.ts
874
- var q = (name) => `"${cyan2(name)}"`;
821
+ var q = (name) => `"${cyan(name)}"`;
875
822
  function buildTarget(name, isGlobal, lockPath) {
876
823
  const lock = readLock(lockPath);
877
824
  const inLock = Object.hasOwn(lock.skills, name);
878
- const baseClaude = isGlobal ? join6(homedir4(), ".claude", "skills") : join6(dirname5(resolve5(lockPath)), ".claude", "skills");
879
- const baseAgents = isGlobal ? join6(homedir4(), ".agents", "skills") : join6(dirname5(resolve5(lockPath)), ".agents", "skills");
880
- const claudeDir = existsSync6(join6(baseClaude, name)) ? join6(baseClaude, name) : undefined;
881
- const agentsDir = existsSync6(join6(baseAgents, name)) ? join6(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;
882
829
  return { name, inLock, claudeDir, agentsDir };
883
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
+ }
884
835
  function fileCount(dir) {
885
- const { readdirSync: readdirSync3, statSync: statSync2 } = __require("node:fs");
836
+ const { readdirSync: readdirSync2, statSync } = __require("node:fs");
886
837
  let n = 0;
887
838
  const stack = [dir];
888
839
  while (stack.length) {
889
840
  const cur = stack.pop();
890
- const stat = statSync2(cur);
841
+ const stat = statSync(cur);
891
842
  if (stat.isFile())
892
843
  n++;
893
844
  else if (stat.isDirectory())
894
- for (const e of readdirSync3(cur))
895
- stack.push(join6(cur, e));
845
+ for (const e of readdirSync2(cur))
846
+ stack.push(join3(cur, e));
896
847
  }
897
848
  return n;
898
849
  }
899
- function printPlan(plan) {
850
+ function printPlan(plan, modifyLock) {
900
851
  const { target } = plan;
901
852
  console.log(`Will remove ${q(target.name)}:`);
902
- if (target.inLock)
903
- console.log(" - skills-lock.json");
904
- else
853
+ if (target.inLock) {
854
+ if (modifyLock)
855
+ console.log(" - skills-lock.json");
856
+ else
857
+ console.log(" - skills-lock.json (kept; use --force-lock to remove lock entry)");
858
+ } else {
905
859
  console.log(" - skills-lock.json (not in lock)");
860
+ }
906
861
  if (target.claudeDir)
907
862
  console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
908
863
  else
@@ -913,22 +868,38 @@ function printPlan(plan) {
913
868
  console.log(" - .agents/skills/ (not found)");
914
869
  }
915
870
  var removeCommand = defineCommand({
916
- 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
+ },
917
874
  args: {
918
875
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
919
876
  "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
920
- yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" }
877
+ yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
878
+ all: { type: "boolean", default: false, description: "Remove every skill in scope" },
879
+ "force-lock": {
880
+ type: "boolean",
881
+ default: false,
882
+ description: "Also remove entry from skills-lock.json (default is to keep lock untouched)"
883
+ }
921
884
  },
922
885
  async run({ args }) {
923
- const { global: isGlobal, "dry-run": dryRun, yes } = args;
886
+ const { global: isGlobal, "dry-run": dryRun, yes, all, "force-lock": modifyLock } = args;
924
887
  const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
925
888
  const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
926
- 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) {
927
894
  console.error("No skill names provided");
928
895
  process.exit(1);
929
896
  }
930
897
  const lockPath = getLockPath(isGlobal);
931
- 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
+ }
932
903
  const orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
933
904
  if (orphan.length) {
934
905
  for (const o of orphan)
@@ -941,27 +912,29 @@ var removeCommand = defineCommand({
941
912
  agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
942
913
  }));
943
914
  for (const p of plans) {
944
- printPlan(p);
915
+ printPlan(p, modifyLock);
945
916
  console.log("");
946
917
  }
947
918
  if (dryRun)
948
919
  return;
949
920
  if (!yes) {
950
- const ok = await confirm("Proceed?");
921
+ const promptText = all ? `Remove ALL ${plans.length} skills?` : "Proceed?";
922
+ const ok = await confirm(promptText);
951
923
  if (!ok) {
952
924
  console.log("Aborted");
953
925
  process.exit(1);
954
926
  }
955
927
  }
956
- const allowedRoots = [
957
- isGlobal ? homedir4() : dirname5(resolve5(lockPath)),
958
- homedir4()
959
- ];
928
+ const allowedRoots = [isGlobal ? homedir2() : dirname2(resolve3(lockPath)), homedir2()];
960
929
  for (const { target } of plans) {
961
930
  if (target.inLock) {
962
- const r = removeSkillFromLock(lockPath, target.name);
963
- if (r.removed)
964
- console.log(`Removed ${q(target.name)} from skills-lock.json`);
931
+ if (modifyLock) {
932
+ const r = removeSkillFromLock(lockPath, target.name);
933
+ if (r.removed)
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)`);
937
+ }
965
938
  } else {
966
939
  console.log(`Skipped skills-lock.json (not in lock)`);
967
940
  }
@@ -982,11 +955,90 @@ var removeCommand = defineCommand({
982
955
  });
983
956
 
984
957
  // src/commands/usage.ts
985
- import { existsSync as existsSync9 } from "node:fs";
986
- import { join as join10 } from "node:path";
958
+ import { existsSync as existsSync6 } from "node:fs";
959
+ import { join as join7 } from "node:path";
987
960
 
988
961
  // src/readers/claude.ts
989
- import { readFileSync as readFileSync5 } from "node:fs";
962
+ import { readFileSync as readFileSync2 } from "node:fs";
963
+
964
+ // src/utils/codex-cmd.ts
965
+ var READ_LIKE = new Set(["cat", "sed", "head", "tail", "bat", "batcat", "less", "more"]);
966
+ var SKILL_PATH_RE = /[^\s'"`]*\/([^\s'"`/]+)\/SKILL\.md/g;
967
+ function stripHeredocs(s) {
968
+ return s.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n[\t ]*\1\s*(?:\n|$)/g, "");
969
+ }
970
+ function splitTopLevel(s) {
971
+ const out = [];
972
+ let buf = "";
973
+ let i = 0;
974
+ let inSingle = false;
975
+ let inDouble = false;
976
+ let inBacktick = false;
977
+ while (i < s.length) {
978
+ const c = s[i];
979
+ const next = s[i + 1];
980
+ if (!inDouble && !inBacktick && c === "'") {
981
+ inSingle = !inSingle;
982
+ buf += c;
983
+ i++;
984
+ continue;
985
+ }
986
+ if (!inSingle && !inBacktick && c === '"') {
987
+ inDouble = !inDouble;
988
+ buf += c;
989
+ i++;
990
+ continue;
991
+ }
992
+ if (!inSingle && !inDouble && c === "`") {
993
+ inBacktick = !inBacktick;
994
+ buf += c;
995
+ i++;
996
+ continue;
997
+ }
998
+ if (!inSingle && !inDouble && !inBacktick) {
999
+ if (c === "&" && next === "&" || c === "|" && next === "|") {
1000
+ out.push(buf);
1001
+ buf = "";
1002
+ i += 2;
1003
+ continue;
1004
+ }
1005
+ if (c === ";" || c === "|") {
1006
+ out.push(buf);
1007
+ buf = "";
1008
+ i++;
1009
+ continue;
1010
+ }
1011
+ }
1012
+ buf += c;
1013
+ i++;
1014
+ }
1015
+ out.push(buf);
1016
+ return out;
1017
+ }
1018
+ function hasRedirect(segment) {
1019
+ return /(?<![<2])>>?|&>>?/.test(segment);
1020
+ }
1021
+ function extractSkillReadsFromCmd(cmd) {
1022
+ const stripped = stripHeredocs(cmd);
1023
+ const segments = splitTopLevel(stripped);
1024
+ const found = new Set;
1025
+ for (const seg of segments) {
1026
+ if (hasRedirect(seg))
1027
+ continue;
1028
+ const trimmed = seg.trimStart();
1029
+ const firstTokenMatch = trimmed.match(/^(\S+)/);
1030
+ if (!firstTokenMatch)
1031
+ continue;
1032
+ const firstToken = firstTokenMatch[1] ?? "";
1033
+ if (!READ_LIKE.has(firstToken))
1034
+ continue;
1035
+ for (const m of seg.matchAll(SKILL_PATH_RE)) {
1036
+ if (m[1])
1037
+ found.add(m[1]);
1038
+ }
1039
+ }
1040
+ return [...found];
1041
+ }
990
1042
 
991
1043
  // src/utils/walk.ts
992
1044
  function walk(value, visit) {
@@ -1061,12 +1113,7 @@ function extractCodexActivations(entry) {
1061
1113
  const cmd = parsed?.cmd;
1062
1114
  if (typeof cmd !== "string")
1063
1115
  return [];
1064
- const paths = new Set;
1065
- for (const m of cmd.matchAll(/(?:^|['"\s])([^'"\s]+\/SKILL\.md)(?=$|['"\s])/g)) {
1066
- if (m[1])
1067
- paths.add(m[1]);
1068
- }
1069
- return [...paths].map(skillNameFromPath).filter((s) => s !== null);
1116
+ return extractSkillReadsFromCmd(cmd);
1070
1117
  }
1071
1118
  return [];
1072
1119
  }
@@ -1113,27 +1160,49 @@ function extractCodexMentions(entry) {
1113
1160
  return [...seen];
1114
1161
  }
1115
1162
 
1163
+ // src/utils/claude-entry.ts
1164
+ function isUserTurnEntry(entry) {
1165
+ if (typeof entry !== "object" || entry === null)
1166
+ return false;
1167
+ const e = entry;
1168
+ if (e.type !== "user")
1169
+ return false;
1170
+ const msg = e.message;
1171
+ if (!msg || msg.role !== "user")
1172
+ return false;
1173
+ const content = msg.content;
1174
+ if (typeof content === "string")
1175
+ return true;
1176
+ if (!Array.isArray(content))
1177
+ return false;
1178
+ return content.some((c) => {
1179
+ if (typeof c !== "object" || c === null)
1180
+ return false;
1181
+ return c.type === "text";
1182
+ });
1183
+ }
1184
+
1116
1185
  // src/utils/expand-home.ts
1117
- import { homedir as homedir5 } from "node:os";
1118
- import { join as join7, 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";
1119
1188
  function expandHome(p) {
1120
1189
  if (p === "~")
1121
- return homedir5();
1190
+ return homedir3();
1122
1191
  if (p.startsWith("~/"))
1123
- return join7(homedir5(), p.slice(2));
1124
- return resolve6(p);
1192
+ return join4(homedir3(), p.slice(2));
1193
+ return resolve4(p);
1125
1194
  }
1126
1195
 
1127
1196
  // src/utils/jsonl.ts
1128
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
1129
- import { join as join8 } from "node:path";
1197
+ import { readdirSync as readdirSync2, readFileSync, statSync } from "node:fs";
1198
+ import { join as join5 } from "node:path";
1130
1199
  function* findJsonlFiles(dir, since) {
1131
- for (const item of readdirSync3(dir, { withFileTypes: true })) {
1132
- const path = join8(dir, item.name);
1200
+ for (const item of readdirSync2(dir, { withFileTypes: true })) {
1201
+ const path = join5(dir, item.name);
1133
1202
  if (item.isDirectory()) {
1134
1203
  yield* findJsonlFiles(path, since);
1135
1204
  } else if (item.isFile() && item.name.endsWith(".jsonl")) {
1136
- if (!since || statSync2(path).mtime >= since)
1205
+ if (!since || statSync(path).mtime >= since)
1137
1206
  yield path;
1138
1207
  }
1139
1208
  }
@@ -1163,7 +1232,7 @@ function readClaudeUsage(options) {
1163
1232
  let prevSkill = null;
1164
1233
  const sessionAttr = new Map;
1165
1234
  const sessionAct = new Map;
1166
- for (const line of readFileSync5(file, "utf8").split(`
1235
+ for (const line of readFileSync2(file, "utf8").split(`
1167
1236
  `)) {
1168
1237
  if (!line.trim())
1169
1238
  continue;
@@ -1183,6 +1252,8 @@ function readClaudeUsage(options) {
1183
1252
  sessionAttr.set(cur, (sessionAttr.get(cur) ?? 0) + 1);
1184
1253
  }
1185
1254
  prevSkill = cur;
1255
+ } else if (isUserTurnEntry(entry)) {
1256
+ prevSkill = null;
1186
1257
  }
1187
1258
  }
1188
1259
  if (options.mode === "activations" || options.mode === "merged") {
@@ -1214,14 +1285,14 @@ function readClaudeUsage(options) {
1214
1285
  }
1215
1286
 
1216
1287
  // src/readers/codex.ts
1217
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
1288
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
1218
1289
 
1219
1290
  // src/utils/scope.ts
1220
- import { existsSync as existsSync7 } from "node:fs";
1221
- import { homedir as homedir6 } from "node:os";
1222
- import { dirname as dirname6, join as join9 } 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";
1223
1294
  function detectScope(opts) {
1224
- const home = opts.home ?? homedir6();
1295
+ const home = opts.home ?? homedir4();
1225
1296
  if (opts.global || opts.rootOverride)
1226
1297
  return { global: true };
1227
1298
  if (norm(opts.cwd) === norm(home))
@@ -1239,9 +1310,9 @@ function encodeClaudeProjectDir(absPath) {
1239
1310
  function findGitRoot(start) {
1240
1311
  let dir = start;
1241
1312
  while (true) {
1242
- if (existsSync7(join9(dir, ".git")))
1313
+ if (existsSync4(join6(dir, ".git")))
1243
1314
  return dir;
1244
- const parent = dirname6(dir);
1315
+ const parent = dirname3(dir);
1245
1316
  if (parent === dir)
1246
1317
  return;
1247
1318
  dir = parent;
@@ -1255,7 +1326,7 @@ function norm(p) {
1255
1326
  function readSessionCwd(file) {
1256
1327
  let head;
1257
1328
  try {
1258
- head = readFileSync6(file, "utf8");
1329
+ head = readFileSync3(file, "utf8");
1259
1330
  } catch {
1260
1331
  return;
1261
1332
  }
@@ -1288,7 +1359,7 @@ function readCodexActivations(options) {
1288
1359
  continue;
1289
1360
  }
1290
1361
  filesRead++;
1291
- for (const line of readFileSync6(file, "utf8").split(`
1362
+ for (const line of readFileSync3(file, "utf8").split(`
1292
1363
  `)) {
1293
1364
  if (!line.trim())
1294
1365
  continue;
@@ -1312,9 +1383,9 @@ function readCodexMentions(options) {
1312
1383
  const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
1313
1384
  const counts = new Map;
1314
1385
  let linesRead = 0;
1315
- if (!existsSync8(historyPath))
1386
+ if (!existsSync5(historyPath))
1316
1387
  return { counts, filesRead: 0, linesRead: 0 };
1317
- for (const line of readFileSync6(historyPath, "utf8").split(`
1388
+ for (const line of readFileSync3(historyPath, "utf8").split(`
1318
1389
  `)) {
1319
1390
  if (!line.trim())
1320
1391
  continue;
@@ -1340,20 +1411,18 @@ var MINUTE_MS = 60 * SECOND_MS;
1340
1411
  var HOUR_MS = 60 * MINUTE_MS;
1341
1412
  var DAY_MS = 24 * HOUR_MS;
1342
1413
  var UNITS_MS = {
1343
- sec: SECOND_MS,
1344
- min: MINUTE_MS,
1414
+ s: SECOND_MS,
1415
+ m: MINUTE_MS,
1345
1416
  h: HOUR_MS,
1346
1417
  d: DAY_MS,
1347
- w: 7 * DAY_MS,
1348
- m: 30 * DAY_MS,
1349
- y: 365 * DAY_MS
1418
+ w: 7 * DAY_MS
1350
1419
  };
1351
1420
  function parsePeriod(period) {
1352
1421
  if (period === "all")
1353
1422
  return Number.POSITIVE_INFINITY;
1354
- const match = period.match(/^(\d+)(sec|min|[hdwmy])$/);
1423
+ const match = period.match(/^(\d+)([smhdw])$/);
1355
1424
  if (!match) {
1356
- 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.`);
1357
1426
  }
1358
1427
  const unit = UNITS_MS[match[2] ?? ""] ?? 0;
1359
1428
  return Number(match[1]) * unit;
@@ -1364,7 +1433,7 @@ function pad(n, width) {
1364
1433
  return String(n).padStart(width);
1365
1434
  }
1366
1435
  function formatUsageRow(row) {
1367
- return `${pad(row.count, row.countWidth)} ${cyan2(row.name)}`;
1436
+ return `${pad(row.count, row.countWidth)} ${cyan(row.name)}`;
1368
1437
  }
1369
1438
  function parseAgents(agent) {
1370
1439
  if (!agent)
@@ -1388,7 +1457,7 @@ var usageArgs = {
1388
1457
  type: "string",
1389
1458
  alias: "p",
1390
1459
  default: "all",
1391
- description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
1460
+ description: "60s, 30m, 24h, 30d, 2w, all"
1392
1461
  },
1393
1462
  since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
1394
1463
  mode: {
@@ -1420,8 +1489,8 @@ async function runUsage(args) {
1420
1489
  cwd: process.cwd()
1421
1490
  });
1422
1491
  const claudeProjectsRoot = expandHome("~/.claude/projects");
1423
- const claudeRoot = args.root ?? (scope.projectRoot ? join10(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1424
- const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync9(claudeRoot);
1492
+ const claudeRoot = args.root ?? (scope.projectRoot ? join7(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1493
+ const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync6(claudeRoot);
1425
1494
  const lockPath = getLockPath(args.global);
1426
1495
  const skillUniverse = discoverSkills({
1427
1496
  isGlobal: args.global,
@@ -1451,7 +1520,7 @@ async function runUsage(args) {
1451
1520
  stats = { filesRead: result.filesRead, linesRead: result.linesRead };
1452
1521
  }
1453
1522
  const universeNames = new Set([...skillUniverse.keys(), ...counts.keys()]);
1454
- const rows = [...universeNames].map((name) => {
1523
+ const allRows = [...universeNames].map((name) => {
1455
1524
  const rec = skillUniverse.get(name);
1456
1525
  return {
1457
1526
  name,
@@ -1460,6 +1529,7 @@ async function runUsage(args) {
1460
1529
  status: rec?.status ?? "ok"
1461
1530
  };
1462
1531
  });
1532
+ const rows = allRows.filter((r) => r.count > 0);
1463
1533
  rows.sort((a, b) => {
1464
1534
  const aOk = a.status === "ok";
1465
1535
  const bOk = b.status === "ok";
@@ -1488,12 +1558,12 @@ async function runUsage(args) {
1488
1558
  }
1489
1559
  const periodLabel = args.since ? `since ${args.since}` : args.period ?? "all";
1490
1560
  const scopeHeader = scope.global ? "Global" : "Local";
1561
+ console.log("");
1491
1562
  console.log(scopeHeader);
1492
1563
  const distinct = new Set;
1493
1564
  let grandActivations = 0;
1494
1565
  for (const { agent, rows } of results) {
1495
1566
  const activations = rows.reduce((acc, r) => acc + r.count, 0);
1496
- console.log("");
1497
1567
  console.log(`${agent} ${rows.length} skill${rows.length === 1 ? "" : "s"} ${activations} time${activations === 1 ? "" : "s"} by ${periodLabel}`);
1498
1568
  if (rows.length === 0)
1499
1569
  continue;
@@ -1521,15 +1591,15 @@ var usageCommand = defineCommand({
1521
1591
  });
1522
1592
 
1523
1593
  // src/utils/update-check.ts
1524
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
1594
+ import { mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
1525
1595
  import { get } from "node:https";
1526
- import { homedir as homedir7 } from "node:os";
1527
- import { dirname as dirname7, join as join11 } from "node:path";
1596
+ import { homedir as homedir5 } from "node:os";
1597
+ import { dirname as dirname4, join as join8 } from "node:path";
1528
1598
  var PKG = "skillio";
1529
1599
  var TTL_MS = 24 * 60 * 60 * 1000;
1530
1600
  var FETCH_TIMEOUT_MS = 1500;
1531
1601
  function getCachePath() {
1532
- return join11(homedir7(), ".cache", "skillio", "version.json");
1602
+ return join8(homedir5(), ".cache", "skillio", "version.json");
1533
1603
  }
1534
1604
  function compareVersions(a, b) {
1535
1605
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1544,23 +1614,23 @@ function compareVersions(a, b) {
1544
1614
  }
1545
1615
  function readCache(path = getCachePath()) {
1546
1616
  try {
1547
- return JSON.parse(readFileSync7(path, "utf8"));
1617
+ return JSON.parse(readFileSync4(path, "utf8"));
1548
1618
  } catch {
1549
1619
  return;
1550
1620
  }
1551
1621
  }
1552
1622
  function writeCache(cache, path = getCachePath()) {
1553
1623
  try {
1554
- mkdirSync2(dirname7(path), { recursive: true });
1555
- writeFileSync2(path, JSON.stringify(cache));
1624
+ mkdirSync(dirname4(path), { recursive: true });
1625
+ writeFileSync(path, JSON.stringify(cache));
1556
1626
  } catch {}
1557
1627
  }
1558
1628
  function fetchLatest() {
1559
- return new Promise((resolve7) => {
1629
+ return new Promise((resolve5) => {
1560
1630
  const req = get(`https://registry.npmjs.org/${PKG}/latest`, { timeout: FETCH_TIMEOUT_MS }, (res) => {
1561
1631
  if (res.statusCode !== 200) {
1562
1632
  res.resume();
1563
- resolve7(undefined);
1633
+ resolve5(undefined);
1564
1634
  return;
1565
1635
  }
1566
1636
  let body = "";
@@ -1571,16 +1641,16 @@ function fetchLatest() {
1571
1641
  res.on("end", () => {
1572
1642
  try {
1573
1643
  const data = JSON.parse(body);
1574
- resolve7(typeof data.version === "string" ? data.version : undefined);
1644
+ resolve5(typeof data.version === "string" ? data.version : undefined);
1575
1645
  } catch {
1576
- resolve7(undefined);
1646
+ resolve5(undefined);
1577
1647
  }
1578
1648
  });
1579
1649
  });
1580
- req.on("error", () => resolve7(undefined));
1650
+ req.on("error", () => resolve5(undefined));
1581
1651
  req.on("timeout", () => {
1582
1652
  req.destroy();
1583
- resolve7(undefined);
1653
+ resolve5(undefined);
1584
1654
  });
1585
1655
  });
1586
1656
  }
@@ -1607,7 +1677,7 @@ Run: npm i -g skillio
1607
1677
  }
1608
1678
 
1609
1679
  // src/cli.ts
1610
- var { version } = createRequire2(import.meta.url)("../package.json");
1680
+ var { version } = createRequire(import.meta.url)("../package.json");
1611
1681
  function mergeAgentArgs(argv) {
1612
1682
  const out = [];
1613
1683
  const values = [];
@@ -1676,7 +1746,7 @@ function printRootHelp() {
1676
1746
  " -h, --help Show this help and exit",
1677
1747
  " -v, --version Show version and exit",
1678
1748
  " -g, --global Use global scope (default: false)",
1679
- " -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)",
1680
1750
  " -a, --agent Agent for `usage`: claude-code, codex (default: both)",
1681
1751
  "",
1682
1752
  "COMMANDS",
@@ -1739,6 +1809,14 @@ var main = defineCommand({
1739
1809
  async run({ args }) {
1740
1810
  if (hasSubcommand(process.argv))
1741
1811
  return;
1812
+ const interactive = process.stdout.isTTY && process.stdin.isTTY;
1813
+ if (interactive) {
1814
+ const { runPicker } = await import("./shared/chunk-j9jc4e4c.js");
1815
+ const status = await runPicker({
1816
+ global: args.global ?? false
1817
+ });
1818
+ process.exit(status);
1819
+ }
1742
1820
  await costCommand.run?.({
1743
1821
  args,
1744
1822
  cmd: costCommand,