skillio 0.1.9 → 0.1.10

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,16 @@
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
+ green,
7
+ red,
8
+ setColorEnabled,
9
+ yellow
10
+ } from "./shared/chunk-s3421yr2.js";
4
11
 
5
12
  // src/cli.ts
6
- import { createRequire as createRequire2 } from "node:module";
13
+ import { createRequire } from "node:module";
7
14
 
8
15
  // node_modules/citty/dist/_chunks/libs/scule.mjs
9
16
  var NUMBER_CHAR_RE = /\d/;
@@ -222,7 +229,7 @@ var noColor = /* @__PURE__ */ (() => {
222
229
  })();
223
230
  var _c = (c, r = 39) => (t) => noColor ? t : `\x1B[${c}m${t}\x1B[${r}m`;
224
231
  var bold = /* @__PURE__ */ _c(1, 22);
225
- var cyan = /* @__PURE__ */ _c(36);
232
+ var cyan2 = /* @__PURE__ */ _c(36);
226
233
  var gray = /* @__PURE__ */ _c(90);
227
234
  var underline = /* @__PURE__ */ _c(4, 24);
228
235
  function parseArgs(rawArgs, argsDef) {
@@ -274,7 +281,7 @@ function parseArgs(rawArgs, argsDef) {
274
281
  const argument = parsedArgsProxy[arg.name];
275
282
  const options = arg.options || [];
276
283
  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");
284
+ throw new CLIError(`Invalid value for argument: ${cyan2(`--${arg.name}`)} (${cyan2(argument)}). Expected one of: ${options.map((o) => cyan2(o)).join(", ")}.`, "EARG");
278
285
  } else if (arg.required && parsedArgsProxy[arg.name] === undefined)
279
286
  throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
280
287
  return parsedArgsProxy;
@@ -319,7 +326,7 @@ async function runCommand(cmd, opts) {
319
326
  if (explicitName) {
320
327
  const subCommand = await _findSubCommand(subCommands, explicitName);
321
328
  if (!subCommand)
322
- throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
329
+ throw new CLIError(`Unknown command ${cyan2(explicitName)}`, "E_UNKNOWN_COMMAND");
323
330
  await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
324
331
  } else {
325
332
  const defaultSubCommand = await resolveValue(cmd.default);
@@ -328,7 +335,7 @@ async function runCommand(cmd, opts) {
328
335
  throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
329
336
  const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
330
337
  if (!subCommand)
331
- throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
338
+ throw new CLIError(`Default sub command ${cyan2(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
332
339
  await runCommand(subCommand, { rawArgs: opts.rawArgs });
333
340
  } else if (!cmd.run)
334
341
  throw new CLIError(`No command specified.`, "E_NO_COMMAND");
@@ -432,15 +439,15 @@ async function renderUsage(cmd, parent) {
432
439
  if (arg.type === "positional") {
433
440
  const name = arg.name.toUpperCase();
434
441
  const isRequired = arg.required !== false && arg.default === undefined;
435
- posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
442
+ posLines.push([cyan2(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
436
443
  usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
437
444
  } else {
438
445
  const isRequired = arg.required === true && arg.default === undefined;
439
446
  const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
440
- argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
447
+ argLines.push([cyan2(argStr), renderDescription(arg, isRequired)]);
441
448
  if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
442
449
  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(" ")]);
450
+ argLines.push([cyan2(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
444
451
  }
445
452
  if (isRequired)
446
453
  usageLine.push(`--${arg.name}` + renderValueHint(arg));
@@ -454,7 +461,7 @@ async function renderUsage(cmd, parent) {
454
461
  continue;
455
462
  const aliases = toArray(meta?.alias);
456
463
  const label = [name, ...aliases].join(", ");
457
- commandsLines.push([cyan(label), meta?.description || ""]);
464
+ commandsLines.push([cyan2(label), meta?.description || ""]);
458
465
  commandNames.push(name, ...aliases);
459
466
  }
460
467
  usageLine.push(commandNames.join("|"));
@@ -463,7 +470,7 @@ async function renderUsage(cmd, parent) {
463
470
  const version = cmdMeta.version || parentMeta.version;
464
471
  usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
465
472
  const hasOptions = argLines.length > 0 || posLines.length > 0;
466
- usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
473
+ usageLines.push(`${underline(bold("USAGE"))} ${cyan2(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
467
474
  if (posLines.length > 0) {
468
475
  usageLines.push(underline(bold("ARGUMENTS")), "");
469
476
  usageLines.push(formatLineColumns(posLines, " "));
@@ -477,7 +484,7 @@ async function renderUsage(cmd, parent) {
477
484
  if (commandsLines.length > 0) {
478
485
  usageLines.push(underline(bold("COMMANDS")), "");
479
486
  usageLines.push(formatLineColumns(commandsLines, " "));
480
- usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
487
+ usageLines.push("", `Use ${cyan2(`${commandName} <command> --help`)} for more information about a command.`);
481
488
  }
482
489
  return usageLines.filter((l) => typeof l === "string").join(`
483
490
  `);
@@ -577,29 +584,6 @@ function removeSkillFromLock(path, skill) {
577
584
  return { removed: true };
578
585
  }
579
586
 
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
587
  // src/utils/discover-skills.ts
604
588
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
605
589
  import { homedir as homedir3 } from "node:os";
@@ -705,23 +689,28 @@ var costCommand = defineCommand({
705
689
  const rows = sortRows([...map.values()]);
706
690
  const total = rows.reduce((acc, r) => acc + (r.frontmatterTokens ?? 0), 0);
707
691
  const { message, paint } = classify(total);
708
- console.log(args.global ? "Global" : "Local");
709
692
  console.log("");
693
+ console.log(args.global ? "Global" : "Local");
710
694
  if (rows.length === 0) {
711
695
  console.log(`No skills in ${lockPath}`);
712
696
  return;
713
697
  }
714
698
  const nameWidth = Math.max(...rows.map((r) => r.name.length));
699
+ const tokenWidth = Math.max(...rows.map((r) => r.status === "ok" ? `~${r.frontmatterTokens} tok`.length : r.status === "missing" ? "~? tok".length : "(no frontmatter)".length));
715
700
  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}`);
701
+ let tokenCell;
702
+ let suffix = "";
703
+ if (r.status === "ok") {
704
+ tokenCell = `~${r.frontmatterTokens} tok`;
705
+ } else if (r.status === "missing") {
706
+ tokenCell = "~? tok";
707
+ suffix = ` ${red("missing")}`;
708
+ } else {
709
+ tokenCell = "(no frontmatter)";
710
+ }
711
+ const namePad = " ".repeat(nameWidth - r.name.length);
712
+ const tokenPad = " ".repeat(Math.max(0, tokenWidth - tokenCell.length));
713
+ console.log(`${cyan(r.name)}${namePad} ${tokenCell}${tokenPad}${suffix}`);
725
714
  }
726
715
  console.log("");
727
716
  console.log(`Total: ~${total} tok across ${rows.length} skills ${paint(message)}`);
@@ -729,8 +718,6 @@ var costCommand = defineCommand({
729
718
  });
730
719
 
731
720
  // 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
721
  function bySource(records) {
735
722
  const claudeNames = records.filter((r) => r.sources.includes(".claude")).map((r) => r.name).sort();
736
723
  const agentsNames = records.filter((r) => r.sources.includes(".agents")).map((r) => r.name).sort();
@@ -757,12 +744,6 @@ function bySource(records) {
757
744
  }
758
745
  };
759
746
  }
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
747
  var listCommand = defineCommand({
767
748
  meta: { description: "List skills per source with totals and lock-vs-disk diff" },
768
749
  args: {
@@ -773,38 +754,34 @@ var listCommand = defineCommand({
773
754
  const map = discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath });
774
755
  const records = [...map.values()];
775
756
  const rows = bySource(records);
776
- const showAgents = agentsDirExists(args.global, lockPath) || rows.agents.names.length > 0;
777
757
  const claudeNames = rows.claude.names;
778
758
  const agentsNames = rows.agents.names;
779
759
  const lockNames = rows.lock.names;
780
760
  const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
781
761
  const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
782
762
  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);
763
+ const sourceRows = [rows.claude, rows.agents, rows.lock];
787
764
  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"}`);
765
+ const countCells = sourceRows.map((r) => `${r.names.length} skill${r.names.length === 1 ? "" : "s"}`);
789
766
  const countWidth = Math.max(...countCells.map((c) => c.length));
790
767
  for (let i = 0;i < sourceRows.length; i++) {
791
768
  const row = sourceRows[i];
792
769
  if (!row)
793
770
  continue;
794
771
  const countCell = countCells[i] ?? "";
795
- const namesText = row.names.length ? row.names.map(cyan2).join(" ") : "";
772
+ const namesText = row.names.length ? row.names.map(cyan).join(" ") : "";
796
773
  const line = `${row.label.padEnd(labelWidth)} : ${countCell.padEnd(countWidth)}${namesText ? ` : ${namesText}` : ""}`;
797
774
  console.log(line.trimEnd());
798
775
  }
799
776
  const diffs = [];
800
777
  if (lockOnly.length) {
801
- diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan2).join(", ")}`);
778
+ diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.map(cyan).join(", ")}`);
802
779
  }
803
780
  if (claudeNotInLock.length) {
804
- diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan2).join(", ")}`);
781
+ diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.map(cyan).join(", ")}`);
805
782
  }
806
783
  if (agentsNotInLock.length) {
807
- diffs.push(`.agents/skills has ${agentsNotInLock.length} skill${agentsNotInLock.length === 1 ? "" : "s"} not in lock: ${agentsNotInLock.map(cyan2).join(", ")}`);
784
+ diffs.push(`.agents/skills has ${agentsNotInLock.length} skill${agentsNotInLock.length === 1 ? "" : "s"} not in lock: ${agentsNotInLock.map(cyan).join(", ")}`);
808
785
  }
809
786
  if (diffs.length) {
810
787
  console.log("");
@@ -815,9 +792,9 @@ var listCommand = defineCommand({
815
792
  });
816
793
 
817
794
  // src/commands/remove.ts
818
- import { existsSync as existsSync6 } from "node:fs";
795
+ import { existsSync as existsSync5 } from "node:fs";
819
796
  import { homedir as homedir4 } from "node:os";
820
- import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
797
+ import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
821
798
 
822
799
  // src/utils/confirm.ts
823
800
  import { createInterface } from "node:readline/promises";
@@ -837,15 +814,15 @@ async function confirm(question, opts = {}) {
837
814
  }
838
815
 
839
816
  // 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";
817
+ import { existsSync as existsSync4, lstatSync, readdirSync as readdirSync2, rmSync } from "node:fs";
818
+ import { join as join4, resolve as resolve3 } from "node:path";
842
819
  function isInside(target, root) {
843
- const t = resolve4(target);
844
- const r = resolve4(root);
820
+ const t = resolve3(target);
821
+ const r = resolve3(root);
845
822
  return t === r || t.startsWith(`${r}/`);
846
823
  }
847
824
  function countFiles(path) {
848
- if (!existsSync5(path))
825
+ if (!existsSync4(path))
849
826
  return 0;
850
827
  const stat = lstatSync(path);
851
828
  if (stat.isFile())
@@ -854,7 +831,7 @@ function countFiles(path) {
854
831
  return 0;
855
832
  let n = 0;
856
833
  for (const entry of readdirSync2(path)) {
857
- n += countFiles(join5(path, entry));
834
+ n += countFiles(join4(path, entry));
858
835
  }
859
836
  return n;
860
837
  }
@@ -863,22 +840,35 @@ function rmSkillDir(path, opts) {
863
840
  if (!safe) {
864
841
  throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
865
842
  }
866
- if (!existsSync5(path))
843
+ if (!existsSync4(path))
867
844
  return { removed: false, fileCount: 0 };
868
845
  const fileCount = countFiles(path);
869
846
  rmSync(path, { recursive: true, force: true });
870
847
  return { removed: true, fileCount };
871
848
  }
872
849
 
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
+
873
863
  // src/commands/remove.ts
874
- var q = (name) => `"${cyan2(name)}"`;
864
+ var q = (name) => `"${cyan(name)}"`;
875
865
  function buildTarget(name, isGlobal, lockPath) {
876
866
  const lock = readLock(lockPath);
877
867
  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;
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;
882
872
  return { name, inLock, claudeDir, agentsDir };
883
873
  }
884
874
  function fileCount(dir) {
@@ -892,17 +882,21 @@ function fileCount(dir) {
892
882
  n++;
893
883
  else if (stat.isDirectory())
894
884
  for (const e of readdirSync3(cur))
895
- stack.push(join6(cur, e));
885
+ stack.push(join5(cur, e));
896
886
  }
897
887
  return n;
898
888
  }
899
- function printPlan(plan) {
889
+ function printPlan(plan, lockTracked) {
900
890
  const { target } = plan;
901
891
  console.log(`Will remove ${q(target.name)}:`);
902
- if (target.inLock)
903
- console.log(" - skills-lock.json");
904
- else
892
+ if (target.inLock) {
893
+ if (lockTracked)
894
+ console.log(" - skills-lock.json (skipped: git-tracked; use --force-lock)");
895
+ else
896
+ console.log(" - skills-lock.json");
897
+ } else {
905
898
  console.log(" - skills-lock.json (not in lock)");
899
+ }
906
900
  if (target.claudeDir)
907
901
  console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
908
902
  else
@@ -917,10 +911,15 @@ var removeCommand = defineCommand({
917
911
  args: {
918
912
  global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
919
913
  "dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
920
- yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" }
914
+ yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" },
915
+ "force-lock": {
916
+ type: "boolean",
917
+ default: false,
918
+ description: "Modify skills-lock.json even if it is git-tracked"
919
+ }
921
920
  },
922
921
  async run({ args }) {
923
- const { global: isGlobal, "dry-run": dryRun, yes } = args;
922
+ const { global: isGlobal, "dry-run": dryRun, yes, "force-lock": forceLock } = args;
924
923
  const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
925
924
  const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
926
925
  if (names.length === 0) {
@@ -940,10 +939,14 @@ var removeCommand = defineCommand({
940
939
  claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
941
940
  agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
942
941
  }));
942
+ const lockTracked = !forceLock && isTrackedByGit(lockPath);
943
943
  for (const p of plans) {
944
- printPlan(p);
944
+ printPlan(p, lockTracked);
945
945
  console.log("");
946
946
  }
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
+ }
947
950
  if (dryRun)
948
951
  return;
949
952
  if (!yes) {
@@ -953,15 +956,16 @@ var removeCommand = defineCommand({
953
956
  process.exit(1);
954
957
  }
955
958
  }
956
- const allowedRoots = [
957
- isGlobal ? homedir4() : dirname5(resolve5(lockPath)),
958
- homedir4()
959
- ];
959
+ const allowedRoots = [isGlobal ? homedir4() : dirname5(resolve5(lockPath)), homedir4()];
960
960
  for (const { target } of plans) {
961
961
  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`);
962
+ if (lockTracked) {
963
+ console.log(`Skipped skills-lock.json (git-tracked) for ${q(target.name)}`);
964
+ } else {
965
+ const r = removeSkillFromLock(lockPath, target.name);
966
+ if (r.removed)
967
+ console.log(`Removed ${q(target.name)} from skills-lock.json`);
968
+ }
965
969
  } else {
966
970
  console.log(`Skipped skills-lock.json (not in lock)`);
967
971
  }
@@ -982,12 +986,91 @@ var removeCommand = defineCommand({
982
986
  });
983
987
 
984
988
  // src/commands/usage.ts
985
- import { existsSync as existsSync9 } from "node:fs";
986
- import { join as join10 } from "node:path";
989
+ import { existsSync as existsSync8 } from "node:fs";
990
+ import { join as join9 } from "node:path";
987
991
 
988
992
  // src/readers/claude.ts
989
993
  import { readFileSync as readFileSync5 } from "node:fs";
990
994
 
995
+ // src/utils/codex-cmd.ts
996
+ var READ_LIKE = new Set(["cat", "sed", "head", "tail", "bat", "batcat", "less", "more"]);
997
+ var SKILL_PATH_RE = /[^\s'"`]*\/([^\s'"`/]+)\/SKILL\.md/g;
998
+ function stripHeredocs(s) {
999
+ return s.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n[\t ]*\1\s*(?:\n|$)/g, "");
1000
+ }
1001
+ function splitTopLevel(s) {
1002
+ const out = [];
1003
+ let buf = "";
1004
+ let i = 0;
1005
+ let inSingle = false;
1006
+ let inDouble = false;
1007
+ let inBacktick = false;
1008
+ while (i < s.length) {
1009
+ const c = s[i];
1010
+ const next = s[i + 1];
1011
+ if (!inDouble && !inBacktick && c === "'") {
1012
+ inSingle = !inSingle;
1013
+ buf += c;
1014
+ i++;
1015
+ continue;
1016
+ }
1017
+ if (!inSingle && !inBacktick && c === '"') {
1018
+ inDouble = !inDouble;
1019
+ buf += c;
1020
+ i++;
1021
+ continue;
1022
+ }
1023
+ if (!inSingle && !inDouble && c === "`") {
1024
+ inBacktick = !inBacktick;
1025
+ buf += c;
1026
+ i++;
1027
+ continue;
1028
+ }
1029
+ if (!inSingle && !inDouble && !inBacktick) {
1030
+ if (c === "&" && next === "&" || c === "|" && next === "|") {
1031
+ out.push(buf);
1032
+ buf = "";
1033
+ i += 2;
1034
+ continue;
1035
+ }
1036
+ if (c === ";" || c === "|") {
1037
+ out.push(buf);
1038
+ buf = "";
1039
+ i++;
1040
+ continue;
1041
+ }
1042
+ }
1043
+ buf += c;
1044
+ i++;
1045
+ }
1046
+ out.push(buf);
1047
+ return out;
1048
+ }
1049
+ function hasRedirect(segment) {
1050
+ return /(?<![<2])>>?|&>>?/.test(segment);
1051
+ }
1052
+ function extractSkillReadsFromCmd(cmd) {
1053
+ const stripped = stripHeredocs(cmd);
1054
+ const segments = splitTopLevel(stripped);
1055
+ const found = new Set;
1056
+ for (const seg of segments) {
1057
+ if (hasRedirect(seg))
1058
+ continue;
1059
+ const trimmed = seg.trimStart();
1060
+ const firstTokenMatch = trimmed.match(/^(\S+)/);
1061
+ if (!firstTokenMatch)
1062
+ continue;
1063
+ const firstToken = firstTokenMatch[1] ?? "";
1064
+ if (!READ_LIKE.has(firstToken))
1065
+ continue;
1066
+ for (const m of seg.matchAll(SKILL_PATH_RE)) {
1067
+ if (m[1])
1068
+ found.add(m[1]);
1069
+ }
1070
+ }
1071
+ return [...found];
1072
+ }
1073
+
991
1074
  // src/utils/walk.ts
992
1075
  function walk(value, visit) {
993
1076
  visit(value);
@@ -1061,12 +1144,7 @@ function extractCodexActivations(entry) {
1061
1144
  const cmd = parsed?.cmd;
1062
1145
  if (typeof cmd !== "string")
1063
1146
  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);
1147
+ return extractSkillReadsFromCmd(cmd);
1070
1148
  }
1071
1149
  return [];
1072
1150
  }
@@ -1113,23 +1191,45 @@ function extractCodexMentions(entry) {
1113
1191
  return [...seen];
1114
1192
  }
1115
1193
 
1194
+ // src/utils/claude-entry.ts
1195
+ function isUserTurnEntry(entry) {
1196
+ if (typeof entry !== "object" || entry === null)
1197
+ return false;
1198
+ const e = entry;
1199
+ if (e.type !== "user")
1200
+ return false;
1201
+ const msg = e.message;
1202
+ if (!msg || msg.role !== "user")
1203
+ return false;
1204
+ const content = msg.content;
1205
+ if (typeof content === "string")
1206
+ return true;
1207
+ if (!Array.isArray(content))
1208
+ return false;
1209
+ return content.some((c) => {
1210
+ if (typeof c !== "object" || c === null)
1211
+ return false;
1212
+ return c.type === "text";
1213
+ });
1214
+ }
1215
+
1116
1216
  // src/utils/expand-home.ts
1117
1217
  import { homedir as homedir5 } from "node:os";
1118
- import { join as join7, resolve as resolve6 } from "node:path";
1218
+ import { join as join6, resolve as resolve6 } from "node:path";
1119
1219
  function expandHome(p) {
1120
1220
  if (p === "~")
1121
1221
  return homedir5();
1122
1222
  if (p.startsWith("~/"))
1123
- return join7(homedir5(), p.slice(2));
1223
+ return join6(homedir5(), p.slice(2));
1124
1224
  return resolve6(p);
1125
1225
  }
1126
1226
 
1127
1227
  // src/utils/jsonl.ts
1128
1228
  import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
1129
- import { join as join8 } from "node:path";
1229
+ import { join as join7 } from "node:path";
1130
1230
  function* findJsonlFiles(dir, since) {
1131
1231
  for (const item of readdirSync3(dir, { withFileTypes: true })) {
1132
- const path = join8(dir, item.name);
1232
+ const path = join7(dir, item.name);
1133
1233
  if (item.isDirectory()) {
1134
1234
  yield* findJsonlFiles(path, since);
1135
1235
  } else if (item.isFile() && item.name.endsWith(".jsonl")) {
@@ -1183,6 +1283,8 @@ function readClaudeUsage(options) {
1183
1283
  sessionAttr.set(cur, (sessionAttr.get(cur) ?? 0) + 1);
1184
1284
  }
1185
1285
  prevSkill = cur;
1286
+ } else if (isUserTurnEntry(entry)) {
1287
+ prevSkill = null;
1186
1288
  }
1187
1289
  }
1188
1290
  if (options.mode === "activations" || options.mode === "merged") {
@@ -1214,12 +1316,12 @@ function readClaudeUsage(options) {
1214
1316
  }
1215
1317
 
1216
1318
  // src/readers/codex.ts
1217
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
1319
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
1218
1320
 
1219
1321
  // src/utils/scope.ts
1220
- import { existsSync as existsSync7 } from "node:fs";
1322
+ import { existsSync as existsSync6 } from "node:fs";
1221
1323
  import { homedir as homedir6 } from "node:os";
1222
- import { dirname as dirname6, join as join9 } from "node:path";
1324
+ import { dirname as dirname6, join as join8 } from "node:path";
1223
1325
  function detectScope(opts) {
1224
1326
  const home = opts.home ?? homedir6();
1225
1327
  if (opts.global || opts.rootOverride)
@@ -1239,7 +1341,7 @@ function encodeClaudeProjectDir(absPath) {
1239
1341
  function findGitRoot(start) {
1240
1342
  let dir = start;
1241
1343
  while (true) {
1242
- if (existsSync7(join9(dir, ".git")))
1344
+ if (existsSync6(join8(dir, ".git")))
1243
1345
  return dir;
1244
1346
  const parent = dirname6(dir);
1245
1347
  if (parent === dir)
@@ -1312,7 +1414,7 @@ function readCodexMentions(options) {
1312
1414
  const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
1313
1415
  const counts = new Map;
1314
1416
  let linesRead = 0;
1315
- if (!existsSync8(historyPath))
1417
+ if (!existsSync7(historyPath))
1316
1418
  return { counts, filesRead: 0, linesRead: 0 };
1317
1419
  for (const line of readFileSync6(historyPath, "utf8").split(`
1318
1420
  `)) {
@@ -1364,7 +1466,7 @@ function pad(n, width) {
1364
1466
  return String(n).padStart(width);
1365
1467
  }
1366
1468
  function formatUsageRow(row) {
1367
- return `${pad(row.count, row.countWidth)} ${cyan2(row.name)}`;
1469
+ return `${pad(row.count, row.countWidth)} ${cyan(row.name)}`;
1368
1470
  }
1369
1471
  function parseAgents(agent) {
1370
1472
  if (!agent)
@@ -1420,8 +1522,8 @@ async function runUsage(args) {
1420
1522
  cwd: process.cwd()
1421
1523
  });
1422
1524
  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);
1525
+ const claudeRoot = args.root ?? (scope.projectRoot ? join9(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
1526
+ const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync8(claudeRoot);
1425
1527
  const lockPath = getLockPath(args.global);
1426
1528
  const skillUniverse = discoverSkills({
1427
1529
  isGlobal: args.global,
@@ -1451,7 +1553,7 @@ async function runUsage(args) {
1451
1553
  stats = { filesRead: result.filesRead, linesRead: result.linesRead };
1452
1554
  }
1453
1555
  const universeNames = new Set([...skillUniverse.keys(), ...counts.keys()]);
1454
- const rows = [...universeNames].map((name) => {
1556
+ const allRows = [...universeNames].map((name) => {
1455
1557
  const rec = skillUniverse.get(name);
1456
1558
  return {
1457
1559
  name,
@@ -1460,6 +1562,7 @@ async function runUsage(args) {
1460
1562
  status: rec?.status ?? "ok"
1461
1563
  };
1462
1564
  });
1565
+ const rows = allRows.filter((r) => r.count > 0);
1463
1566
  rows.sort((a, b) => {
1464
1567
  const aOk = a.status === "ok";
1465
1568
  const bOk = b.status === "ok";
@@ -1488,6 +1591,7 @@ async function runUsage(args) {
1488
1591
  }
1489
1592
  const periodLabel = args.since ? `since ${args.since}` : args.period ?? "all";
1490
1593
  const scopeHeader = scope.global ? "Global" : "Local";
1594
+ console.log("");
1491
1595
  console.log(scopeHeader);
1492
1596
  const distinct = new Set;
1493
1597
  let grandActivations = 0;
@@ -1524,12 +1628,12 @@ var usageCommand = defineCommand({
1524
1628
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
1525
1629
  import { get } from "node:https";
1526
1630
  import { homedir as homedir7 } from "node:os";
1527
- import { dirname as dirname7, join as join11 } from "node:path";
1631
+ import { dirname as dirname7, join as join10 } from "node:path";
1528
1632
  var PKG = "skillio";
1529
1633
  var TTL_MS = 24 * 60 * 60 * 1000;
1530
1634
  var FETCH_TIMEOUT_MS = 1500;
1531
1635
  function getCachePath() {
1532
- return join11(homedir7(), ".cache", "skillio", "version.json");
1636
+ return join10(homedir7(), ".cache", "skillio", "version.json");
1533
1637
  }
1534
1638
  function compareVersions(a, b) {
1535
1639
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1607,7 +1711,7 @@ Run: npm i -g skillio
1607
1711
  }
1608
1712
 
1609
1713
  // src/cli.ts
1610
- var { version } = createRequire2(import.meta.url)("../package.json");
1714
+ var { version } = createRequire(import.meta.url)("../package.json");
1611
1715
  function mergeAgentArgs(argv) {
1612
1716
  const out = [];
1613
1717
  const values = [];
@@ -1739,6 +1843,14 @@ var main = defineCommand({
1739
1843
  async run({ args }) {
1740
1844
  if (hasSubcommand(process.argv))
1741
1845
  return;
1846
+ const interactive = process.stdout.isTTY && process.stdin.isTTY;
1847
+ if (interactive) {
1848
+ const { runPicker } = await import("./shared/chunk-eq7h491z.js");
1849
+ const status = await runPicker({
1850
+ global: args.global ?? false
1851
+ });
1852
+ process.exit(status);
1853
+ }
1742
1854
  await costCommand.run?.({
1743
1855
  args,
1744
1856
  cmd: costCommand,
@@ -0,0 +1,113 @@
1
+ import {
2
+ cyan
3
+ } from "./chunk-s3421yr2.js";
4
+
5
+ // src/commands/picker.ts
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ // src/utils/prompt.ts
9
+ import { emitKeypressEvents } from "node:readline";
10
+ async function select(params) {
11
+ const input = params.input ?? process.stdin;
12
+ const output = params.output ?? process.stdout;
13
+ if (!input.isTTY || !output.isTTY)
14
+ return null;
15
+ let cursor = 0;
16
+ const total = params.options.length;
17
+ function render() {
18
+ output.write(`${params.title}
19
+ `);
20
+ for (let i = 0;i < total; i++) {
21
+ const opt = params.options[i];
22
+ if (!opt)
23
+ continue;
24
+ const marker = i === cursor ? cyan(">") : " ";
25
+ output.write(`${marker} ${opt.label}
26
+ `);
27
+ }
28
+ }
29
+ function clear() {
30
+ output.write(`\x1B[${total + 1}A\x1B[J`);
31
+ }
32
+ emitKeypressEvents(input);
33
+ if (input.setRawMode)
34
+ input.setRawMode(true);
35
+ input.resume();
36
+ render();
37
+ return await new Promise((resolve) => {
38
+ const onKey = (_str, key) => {
39
+ if (key.ctrl && key.name === "c") {
40
+ cleanup();
41
+ resolve(null);
42
+ return;
43
+ }
44
+ if (key.name === "escape" || key.name === "q") {
45
+ cleanup();
46
+ resolve(null);
47
+ return;
48
+ }
49
+ if (key.name === "up" && cursor > 0) {
50
+ cursor--;
51
+ clear();
52
+ render();
53
+ return;
54
+ }
55
+ if (key.name === "down" && cursor < total - 1) {
56
+ cursor++;
57
+ clear();
58
+ render();
59
+ return;
60
+ }
61
+ if (key.name === "return") {
62
+ cleanup();
63
+ const chosen = params.options[cursor]?.value ?? null;
64
+ resolve(chosen);
65
+ return;
66
+ }
67
+ };
68
+ function onSigterm() {
69
+ cleanup();
70
+ resolve(null);
71
+ }
72
+ function cleanup() {
73
+ input.removeListener("keypress", onKey);
74
+ process.removeListener("SIGTERM", onSigterm);
75
+ if (input.setRawMode)
76
+ input.setRawMode(false);
77
+ input.pause();
78
+ }
79
+ process.once("SIGTERM", onSigterm);
80
+ input.on("keypress", onKey);
81
+ });
82
+ }
83
+
84
+ // src/commands/picker.ts
85
+ async function runPicker(args) {
86
+ const choice = await select({
87
+ title: "skillio — pick a command",
88
+ options: [
89
+ { value: "usage", label: "usage — count of skill invocations" },
90
+ { value: "cost", label: "cost — per-skill ambient tokens" },
91
+ { value: "list", label: "list — installed skills per source" },
92
+ { value: "quit", label: "quit" }
93
+ ]
94
+ });
95
+ if (choice === null || choice === "quit")
96
+ return 0;
97
+ const cliPath = process.argv[1];
98
+ if (!cliPath) {
99
+ console.error("skillio: cannot resolve CLI path (process.argv[1] missing)");
100
+ return 1;
101
+ }
102
+ const argv = [choice];
103
+ if (args.global)
104
+ argv.push("-g");
105
+ const r = spawnSync(process.execPath, [cliPath, ...argv], {
106
+ stdio: "inherit",
107
+ env: process.env
108
+ });
109
+ return r.status ?? 0;
110
+ }
111
+ export {
112
+ runPicker
113
+ };
@@ -0,0 +1,27 @@
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillio",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Audit and manage AI agent skills for Claude Code and Codex",
5
5
  "license": "MIT",
6
6
  "author": "ihororlovskyi",