skillio 0.1.8 → 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 +286 -125
- package/dist/shared/chunk-eq7h491z.js +113 -0
- package/dist/shared/chunk-s3421yr2.js +27 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
|
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
|
|
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: ${
|
|
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 ${
|
|
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 ${
|
|
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([
|
|
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([
|
|
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([
|
|
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([
|
|
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"))} ${
|
|
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 ${
|
|
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
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
795
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
819
796
|
import { homedir as homedir4 } from "node:os";
|
|
820
|
-
import { dirname as dirname5, join as
|
|
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
|
|
841
|
-
import { join as
|
|
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 =
|
|
844
|
-
const r =
|
|
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 (!
|
|
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(
|
|
834
|
+
n += countFiles(join4(path, entry));
|
|
858
835
|
}
|
|
859
836
|
return n;
|
|
860
837
|
}
|
|
@@ -863,21 +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 (!
|
|
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
|
|
864
|
+
var q = (name) => `"${cyan(name)}"`;
|
|
874
865
|
function buildTarget(name, isGlobal, lockPath) {
|
|
875
866
|
const lock = readLock(lockPath);
|
|
876
867
|
const inLock = Object.hasOwn(lock.skills, name);
|
|
877
|
-
const baseClaude = isGlobal ?
|
|
878
|
-
const baseAgents = isGlobal ?
|
|
879
|
-
const claudeDir =
|
|
880
|
-
const agentsDir =
|
|
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;
|
|
881
872
|
return { name, inLock, claudeDir, agentsDir };
|
|
882
873
|
}
|
|
883
874
|
function fileCount(dir) {
|
|
@@ -891,17 +882,21 @@ function fileCount(dir) {
|
|
|
891
882
|
n++;
|
|
892
883
|
else if (stat.isDirectory())
|
|
893
884
|
for (const e of readdirSync3(cur))
|
|
894
|
-
stack.push(
|
|
885
|
+
stack.push(join5(cur, e));
|
|
895
886
|
}
|
|
896
887
|
return n;
|
|
897
888
|
}
|
|
898
|
-
function printPlan(plan) {
|
|
889
|
+
function printPlan(plan, lockTracked) {
|
|
899
890
|
const { target } = plan;
|
|
900
|
-
console.log(`Will remove
|
|
901
|
-
if (target.inLock)
|
|
902
|
-
|
|
903
|
-
|
|
891
|
+
console.log(`Will remove ${q(target.name)}:`);
|
|
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 {
|
|
904
898
|
console.log(" - skills-lock.json (not in lock)");
|
|
899
|
+
}
|
|
905
900
|
if (target.claudeDir)
|
|
906
901
|
console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
|
|
907
902
|
else
|
|
@@ -916,10 +911,15 @@ var removeCommand = defineCommand({
|
|
|
916
911
|
args: {
|
|
917
912
|
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
918
913
|
"dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
|
|
919
|
-
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
|
+
}
|
|
920
920
|
},
|
|
921
921
|
async run({ args }) {
|
|
922
|
-
const { global: isGlobal, "dry-run": dryRun, yes } = args;
|
|
922
|
+
const { global: isGlobal, "dry-run": dryRun, yes, "force-lock": forceLock } = args;
|
|
923
923
|
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
924
924
|
const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
925
925
|
if (names.length === 0) {
|
|
@@ -931,7 +931,7 @@ var removeCommand = defineCommand({
|
|
|
931
931
|
const orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
|
|
932
932
|
if (orphan.length) {
|
|
933
933
|
for (const o of orphan)
|
|
934
|
-
console.log(
|
|
934
|
+
console.log(`${q(o.name)} is not in lock or on disk`);
|
|
935
935
|
process.exit(1);
|
|
936
936
|
}
|
|
937
937
|
const plans = targets.map((t) => ({
|
|
@@ -939,10 +939,14 @@ var removeCommand = defineCommand({
|
|
|
939
939
|
claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
|
|
940
940
|
agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
|
|
941
941
|
}));
|
|
942
|
+
const lockTracked = !forceLock && isTrackedByGit(lockPath);
|
|
942
943
|
for (const p of plans) {
|
|
943
|
-
printPlan(p);
|
|
944
|
+
printPlan(p, lockTracked);
|
|
944
945
|
console.log("");
|
|
945
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
|
+
}
|
|
946
950
|
if (dryRun)
|
|
947
951
|
return;
|
|
948
952
|
if (!yes) {
|
|
@@ -952,27 +956,28 @@ var removeCommand = defineCommand({
|
|
|
952
956
|
process.exit(1);
|
|
953
957
|
}
|
|
954
958
|
}
|
|
955
|
-
const allowedRoots = [
|
|
956
|
-
isGlobal ? homedir4() : dirname5(resolve5(lockPath)),
|
|
957
|
-
homedir4()
|
|
958
|
-
];
|
|
959
|
+
const allowedRoots = [isGlobal ? homedir4() : dirname5(resolve5(lockPath)), homedir4()];
|
|
959
960
|
for (const { target } of plans) {
|
|
960
961
|
if (target.inLock) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
+
}
|
|
964
969
|
} else {
|
|
965
970
|
console.log(`Skipped skills-lock.json (not in lock)`);
|
|
966
971
|
}
|
|
967
972
|
if (target.claudeDir) {
|
|
968
973
|
const r = rmSkillDir(target.claudeDir, { allowedRoots });
|
|
969
|
-
console.log(`Removed
|
|
974
|
+
console.log(`Removed ${q(target.name)} from .claude/skills (${r.fileCount} files)`);
|
|
970
975
|
} else {
|
|
971
976
|
console.log("Skipped .claude/skills (not found)");
|
|
972
977
|
}
|
|
973
978
|
if (target.agentsDir) {
|
|
974
979
|
const r = rmSkillDir(target.agentsDir, { allowedRoots });
|
|
975
|
-
console.log(`Removed
|
|
980
|
+
console.log(`Removed ${q(target.name)} from .agents/skills (${r.fileCount} files)`);
|
|
976
981
|
} else {
|
|
977
982
|
console.log("Skipped .agents/skills (not found)");
|
|
978
983
|
}
|
|
@@ -981,12 +986,91 @@ var removeCommand = defineCommand({
|
|
|
981
986
|
});
|
|
982
987
|
|
|
983
988
|
// src/commands/usage.ts
|
|
984
|
-
import { existsSync as
|
|
985
|
-
import { join as
|
|
989
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
990
|
+
import { join as join9 } from "node:path";
|
|
986
991
|
|
|
987
992
|
// src/readers/claude.ts
|
|
988
993
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
989
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
|
+
|
|
990
1074
|
// src/utils/walk.ts
|
|
991
1075
|
function walk(value, visit) {
|
|
992
1076
|
visit(value);
|
|
@@ -1047,6 +1131,21 @@ function extractCodexActivations(entry) {
|
|
|
1047
1131
|
}
|
|
1048
1132
|
return [...paths].map(skillNameFromPath).filter((s) => s !== null);
|
|
1049
1133
|
}
|
|
1134
|
+
if (e.type === "response_item" && payload?.type === "function_call" && payload?.name === "exec_command") {
|
|
1135
|
+
const argsStr = payload?.arguments;
|
|
1136
|
+
if (typeof argsStr !== "string")
|
|
1137
|
+
return [];
|
|
1138
|
+
let parsed;
|
|
1139
|
+
try {
|
|
1140
|
+
parsed = JSON.parse(argsStr);
|
|
1141
|
+
} catch {
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
const cmd = parsed?.cmd;
|
|
1145
|
+
if (typeof cmd !== "string")
|
|
1146
|
+
return [];
|
|
1147
|
+
return extractSkillReadsFromCmd(cmd);
|
|
1148
|
+
}
|
|
1050
1149
|
return [];
|
|
1051
1150
|
}
|
|
1052
1151
|
|
|
@@ -1092,23 +1191,45 @@ function extractCodexMentions(entry) {
|
|
|
1092
1191
|
return [...seen];
|
|
1093
1192
|
}
|
|
1094
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
|
+
|
|
1095
1216
|
// src/utils/expand-home.ts
|
|
1096
1217
|
import { homedir as homedir5 } from "node:os";
|
|
1097
|
-
import { join as
|
|
1218
|
+
import { join as join6, resolve as resolve6 } from "node:path";
|
|
1098
1219
|
function expandHome(p) {
|
|
1099
1220
|
if (p === "~")
|
|
1100
1221
|
return homedir5();
|
|
1101
1222
|
if (p.startsWith("~/"))
|
|
1102
|
-
return
|
|
1223
|
+
return join6(homedir5(), p.slice(2));
|
|
1103
1224
|
return resolve6(p);
|
|
1104
1225
|
}
|
|
1105
1226
|
|
|
1106
1227
|
// src/utils/jsonl.ts
|
|
1107
1228
|
import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
1108
|
-
import { join as
|
|
1229
|
+
import { join as join7 } from "node:path";
|
|
1109
1230
|
function* findJsonlFiles(dir, since) {
|
|
1110
1231
|
for (const item of readdirSync3(dir, { withFileTypes: true })) {
|
|
1111
|
-
const path =
|
|
1232
|
+
const path = join7(dir, item.name);
|
|
1112
1233
|
if (item.isDirectory()) {
|
|
1113
1234
|
yield* findJsonlFiles(path, since);
|
|
1114
1235
|
} else if (item.isFile() && item.name.endsWith(".jsonl")) {
|
|
@@ -1131,13 +1252,6 @@ function isRecentEntry(entry, since) {
|
|
|
1131
1252
|
}
|
|
1132
1253
|
|
|
1133
1254
|
// src/readers/claude.ts
|
|
1134
|
-
function extractSkills(entry, mode) {
|
|
1135
|
-
if (mode === "attributed")
|
|
1136
|
-
return extractAttributed(entry);
|
|
1137
|
-
if (mode === "activations")
|
|
1138
|
-
return extractClaudeActivations(entry);
|
|
1139
|
-
return extractClaudeMentions(entry);
|
|
1140
|
-
}
|
|
1141
1255
|
function readClaudeUsage(options) {
|
|
1142
1256
|
const root = expandHome(options.root ?? "~/.claude/projects");
|
|
1143
1257
|
const counts = new Map;
|
|
@@ -1146,6 +1260,9 @@ function readClaudeUsage(options) {
|
|
|
1146
1260
|
const since = options.scanAllFiles ? undefined : options.since;
|
|
1147
1261
|
for (const file of findJsonlFiles(root, since)) {
|
|
1148
1262
|
filesRead++;
|
|
1263
|
+
let prevSkill = null;
|
|
1264
|
+
const sessionAttr = new Map;
|
|
1265
|
+
const sessionAct = new Map;
|
|
1149
1266
|
for (const line of readFileSync5(file, "utf8").split(`
|
|
1150
1267
|
`)) {
|
|
1151
1268
|
if (!line.trim())
|
|
@@ -1159,8 +1276,39 @@ function readClaudeUsage(options) {
|
|
|
1159
1276
|
}
|
|
1160
1277
|
if (!isRecentEntry(entry, options.since))
|
|
1161
1278
|
continue;
|
|
1162
|
-
|
|
1163
|
-
|
|
1279
|
+
if (options.mode === "attributed" || options.mode === "merged") {
|
|
1280
|
+
const cur = extractAttributed(entry)[0];
|
|
1281
|
+
if (cur !== undefined) {
|
|
1282
|
+
if (cur !== prevSkill) {
|
|
1283
|
+
sessionAttr.set(cur, (sessionAttr.get(cur) ?? 0) + 1);
|
|
1284
|
+
}
|
|
1285
|
+
prevSkill = cur;
|
|
1286
|
+
} else if (isUserTurnEntry(entry)) {
|
|
1287
|
+
prevSkill = null;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (options.mode === "activations" || options.mode === "merged") {
|
|
1291
|
+
for (const skill of extractClaudeActivations(entry)) {
|
|
1292
|
+
sessionAct.set(skill, (sessionAct.get(skill) ?? 0) + 1);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (options.mode === "mentions") {
|
|
1296
|
+
for (const skill of extractClaudeMentions(entry)) {
|
|
1297
|
+
counts.set(skill, (counts.get(skill) ?? 0) + 1);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (options.mode === "attributed") {
|
|
1302
|
+
for (const [k, v] of sessionAttr)
|
|
1303
|
+
counts.set(k, (counts.get(k) ?? 0) + v);
|
|
1304
|
+
} else if (options.mode === "activations") {
|
|
1305
|
+
for (const [k, v] of sessionAct)
|
|
1306
|
+
counts.set(k, (counts.get(k) ?? 0) + v);
|
|
1307
|
+
} else if (options.mode === "merged") {
|
|
1308
|
+
const keys = new Set([...sessionAttr.keys(), ...sessionAct.keys()]);
|
|
1309
|
+
for (const k of keys) {
|
|
1310
|
+
const merged = Math.max(sessionAttr.get(k) ?? 0, sessionAct.get(k) ?? 0);
|
|
1311
|
+
counts.set(k, (counts.get(k) ?? 0) + merged);
|
|
1164
1312
|
}
|
|
1165
1313
|
}
|
|
1166
1314
|
}
|
|
@@ -1168,12 +1316,12 @@ function readClaudeUsage(options) {
|
|
|
1168
1316
|
}
|
|
1169
1317
|
|
|
1170
1318
|
// src/readers/codex.ts
|
|
1171
|
-
import { existsSync as
|
|
1319
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
1172
1320
|
|
|
1173
1321
|
// src/utils/scope.ts
|
|
1174
|
-
import { existsSync as
|
|
1322
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1175
1323
|
import { homedir as homedir6 } from "node:os";
|
|
1176
|
-
import { dirname as dirname6, join as
|
|
1324
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
1177
1325
|
function detectScope(opts) {
|
|
1178
1326
|
const home = opts.home ?? homedir6();
|
|
1179
1327
|
if (opts.global || opts.rootOverride)
|
|
@@ -1193,7 +1341,7 @@ function encodeClaudeProjectDir(absPath) {
|
|
|
1193
1341
|
function findGitRoot(start) {
|
|
1194
1342
|
let dir = start;
|
|
1195
1343
|
while (true) {
|
|
1196
|
-
if (
|
|
1344
|
+
if (existsSync6(join8(dir, ".git")))
|
|
1197
1345
|
return dir;
|
|
1198
1346
|
const parent = dirname6(dir);
|
|
1199
1347
|
if (parent === dir)
|
|
@@ -1266,7 +1414,7 @@ function readCodexMentions(options) {
|
|
|
1266
1414
|
const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
|
|
1267
1415
|
const counts = new Map;
|
|
1268
1416
|
let linesRead = 0;
|
|
1269
|
-
if (!
|
|
1417
|
+
if (!existsSync7(historyPath))
|
|
1270
1418
|
return { counts, filesRead: 0, linesRead: 0 };
|
|
1271
1419
|
for (const line of readFileSync6(historyPath, "utf8").split(`
|
|
1272
1420
|
`)) {
|
|
@@ -1318,7 +1466,7 @@ function pad(n, width) {
|
|
|
1318
1466
|
return String(n).padStart(width);
|
|
1319
1467
|
}
|
|
1320
1468
|
function formatUsageRow(row) {
|
|
1321
|
-
return `${pad(row.count, row.countWidth)} ${
|
|
1469
|
+
return `${pad(row.count, row.countWidth)} ${cyan(row.name)}`;
|
|
1322
1470
|
}
|
|
1323
1471
|
function parseAgents(agent) {
|
|
1324
1472
|
if (!agent)
|
|
@@ -1345,7 +1493,10 @@ var usageArgs = {
|
|
|
1345
1493
|
description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
|
|
1346
1494
|
},
|
|
1347
1495
|
since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
|
|
1348
|
-
mode: {
|
|
1496
|
+
mode: {
|
|
1497
|
+
type: "string",
|
|
1498
|
+
description: "merged (default for claude-code) | attributed | activations | mentions"
|
|
1499
|
+
},
|
|
1349
1500
|
format: { type: "string", default: "text", description: "text | json" },
|
|
1350
1501
|
root: { type: "string", description: "Override agent sessions directory; implies global" },
|
|
1351
1502
|
"scan-all-files": { type: "boolean", default: false, description: "Ignore file mtime" },
|
|
@@ -1371,8 +1522,8 @@ async function runUsage(args) {
|
|
|
1371
1522
|
cwd: process.cwd()
|
|
1372
1523
|
});
|
|
1373
1524
|
const claudeProjectsRoot = expandHome("~/.claude/projects");
|
|
1374
|
-
const claudeRoot = args.root ?? (scope.projectRoot ?
|
|
1375
|
-
const claudeRootMissing = !args.root && !!scope.projectRoot && !
|
|
1525
|
+
const claudeRoot = args.root ?? (scope.projectRoot ? join9(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
|
|
1526
|
+
const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync8(claudeRoot);
|
|
1376
1527
|
const lockPath = getLockPath(args.global);
|
|
1377
1528
|
const skillUniverse = discoverSkills({
|
|
1378
1529
|
isGlobal: args.global,
|
|
@@ -1385,7 +1536,7 @@ async function runUsage(args) {
|
|
|
1385
1536
|
let stats;
|
|
1386
1537
|
let mode;
|
|
1387
1538
|
if (agent === "claude-code") {
|
|
1388
|
-
mode = args.mode ?? "
|
|
1539
|
+
mode = args.mode ?? "merged";
|
|
1389
1540
|
const result = claudeRootMissing ? { counts: new Map, filesRead: 0, linesRead: 0 } : readClaudeUsage({ since, mode, root: claudeRoot, scanAllFiles });
|
|
1390
1541
|
counts = result.counts;
|
|
1391
1542
|
stats = { filesRead: result.filesRead, linesRead: result.linesRead };
|
|
@@ -1402,7 +1553,7 @@ async function runUsage(args) {
|
|
|
1402
1553
|
stats = { filesRead: result.filesRead, linesRead: result.linesRead };
|
|
1403
1554
|
}
|
|
1404
1555
|
const universeNames = new Set([...skillUniverse.keys(), ...counts.keys()]);
|
|
1405
|
-
const
|
|
1556
|
+
const allRows = [...universeNames].map((name) => {
|
|
1406
1557
|
const rec = skillUniverse.get(name);
|
|
1407
1558
|
return {
|
|
1408
1559
|
name,
|
|
@@ -1411,6 +1562,7 @@ async function runUsage(args) {
|
|
|
1411
1562
|
status: rec?.status ?? "ok"
|
|
1412
1563
|
};
|
|
1413
1564
|
});
|
|
1565
|
+
const rows = allRows.filter((r) => r.count > 0);
|
|
1414
1566
|
rows.sort((a, b) => {
|
|
1415
1567
|
const aOk = a.status === "ok";
|
|
1416
1568
|
const bOk = b.status === "ok";
|
|
@@ -1439,6 +1591,7 @@ async function runUsage(args) {
|
|
|
1439
1591
|
}
|
|
1440
1592
|
const periodLabel = args.since ? `since ${args.since}` : args.period ?? "all";
|
|
1441
1593
|
const scopeHeader = scope.global ? "Global" : "Local";
|
|
1594
|
+
console.log("");
|
|
1442
1595
|
console.log(scopeHeader);
|
|
1443
1596
|
const distinct = new Set;
|
|
1444
1597
|
let grandActivations = 0;
|
|
@@ -1475,12 +1628,12 @@ var usageCommand = defineCommand({
|
|
|
1475
1628
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1476
1629
|
import { get } from "node:https";
|
|
1477
1630
|
import { homedir as homedir7 } from "node:os";
|
|
1478
|
-
import { dirname as dirname7, join as
|
|
1631
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
1479
1632
|
var PKG = "skillio";
|
|
1480
1633
|
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
1481
1634
|
var FETCH_TIMEOUT_MS = 1500;
|
|
1482
1635
|
function getCachePath() {
|
|
1483
|
-
return
|
|
1636
|
+
return join10(homedir7(), ".cache", "skillio", "version.json");
|
|
1484
1637
|
}
|
|
1485
1638
|
function compareVersions(a, b) {
|
|
1486
1639
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -1558,7 +1711,7 @@ Run: npm i -g skillio
|
|
|
1558
1711
|
}
|
|
1559
1712
|
|
|
1560
1713
|
// src/cli.ts
|
|
1561
|
-
var { version } =
|
|
1714
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
1562
1715
|
function mergeAgentArgs(argv) {
|
|
1563
1716
|
const out = [];
|
|
1564
1717
|
const values = [];
|
|
@@ -1690,6 +1843,14 @@ var main = defineCommand({
|
|
|
1690
1843
|
async run({ args }) {
|
|
1691
1844
|
if (hasSubcommand(process.argv))
|
|
1692
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
|
+
}
|
|
1693
1854
|
await costCommand.run?.({
|
|
1694
1855
|
args,
|
|
1695
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 };
|