skillio 0.1.4 → 0.1.6
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/README.md +46 -25
- package/dist/cli.js +852 -196
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,23 +66,24 @@ audits both Claude Code and Codex over all time).
|
|
|
66
66
|
## Usage
|
|
67
67
|
|
|
68
68
|
```sh
|
|
69
|
-
#
|
|
70
|
-
skl
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
skl
|
|
76
|
-
skl
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
# bare command — quick summary across global + local sources, with verdict
|
|
70
|
+
skl
|
|
71
|
+
skillio # equivalent
|
|
72
|
+
|
|
73
|
+
# subcommands
|
|
74
|
+
skl ls # list skills per source with diffs
|
|
75
|
+
skl cost # ambient ballast cost (frontmatter tokens) per skill
|
|
76
|
+
skl usage # consumption: usage count × frontmatter tokens
|
|
77
|
+
skl rm brainstorming # remove from lock + delete on-disk dir (with Y/n prompt)
|
|
78
|
+
skl rm brainstorming writing-plans # remove multiple
|
|
79
|
+
skl rm --yes brainstorming # skip confirmation
|
|
80
|
+
skl rm --dry-run brainstorming # preview only
|
|
81
|
+
|
|
82
|
+
# scope flags
|
|
83
|
+
skl -g # force global scope on any subcommand
|
|
84
|
+
skl usage -p 7d # last 7 days
|
|
85
|
+
skl usage -a claude-code codex # both agents (space-separated)
|
|
86
|
+
skl usage -a claude -a codex # equivalent: repeated --agent flag
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
### Scope (per-repo vs global)
|
|
@@ -96,26 +97,38 @@ skillio remove --dry-run brainstorming # preview removal
|
|
|
96
97
|
| anywhere with `-g` / `--global` | global override |
|
|
97
98
|
| with `--root <dir>` | that exact dir, treated as global |
|
|
98
99
|
|
|
100
|
+
> Bare `skl` (no subcommand) ignores `-g` — it always shows both Global and Local sections plus a grand Total.
|
|
101
|
+
|
|
99
102
|
## What it does
|
|
100
103
|
|
|
101
|
-
- **
|
|
102
|
-
- **
|
|
104
|
+
- **Summary** (`skl`) — counts and tokens across `.claude/skills`, `.agents/skills`, and `skills-lock.json` for both global and local scopes, with a cleanup verdict.
|
|
105
|
+
- **Audit skill usage** (`skl usage`) — parse agent session logs and count which skills were invoked, when, and how often.
|
|
106
|
+
- **Manage a skills lock** (`skl ls`, `skl rm`) — inspect and remove skills from a local or global lock file.
|
|
103
107
|
|
|
104
108
|
## Options
|
|
105
109
|
|
|
106
|
-
###
|
|
110
|
+
### Global flags
|
|
111
|
+
|
|
112
|
+
| Flag | Default | Description |
|
|
113
|
+
|------|---------|-------------|
|
|
114
|
+
| `-h, --help` | — | Show help and exit |
|
|
115
|
+
| `-v, --version` | — | Show version and exit |
|
|
116
|
+
| `-g, --global` | `false` | Use global scope (ignore current directory) |
|
|
117
|
+
| `-p, --period` | `all` | Period for `usage`: `30sec`, `5min`, `12h`, `7d`, `2w`, `1m`, `1y`, `all` |
|
|
118
|
+
| `-a, --agent` | both | Agent for `usage`: `claude-code` (alias `claude`), `codex` — pass both space-separated (`-a claude-code codex`) or repeat the flag |
|
|
119
|
+
|
|
120
|
+
### `skillio usage` / `us`
|
|
107
121
|
|
|
108
|
-
Audits skill usage from agent session logs.
|
|
109
|
-
no subcommand keyword is needed.
|
|
122
|
+
Audits skill usage from agent session logs.
|
|
110
123
|
|
|
111
124
|
```sh
|
|
112
|
-
skillio --agent claude --period 7d
|
|
113
|
-
skillio --agent codex --mode activations
|
|
125
|
+
skillio usage --agent claude --period 7d
|
|
126
|
+
skillio usage --agent codex --mode activations
|
|
114
127
|
```
|
|
115
128
|
|
|
116
129
|
| Flag | Default | Description |
|
|
117
130
|
|------|---------|-------------|
|
|
118
|
-
| `-a, --agent` | both | `claude-code`/`claude`, `codex`
|
|
131
|
+
| `-a, --agent` | both | `claude-code`/`claude`, `codex` |
|
|
119
132
|
| `-p, --period` | `all` | `7d`, `2w`, `1m`, `1y`, `all` |
|
|
120
133
|
| `--since` | — | `yyyy-mm-dd`, overrides `--period` |
|
|
121
134
|
| `--mode` | `attributed` (claude) / `activations` (codex) | `attributed` \| `activations` \| `mentions` |
|
|
@@ -137,6 +150,13 @@ skillio list # local skills-lock.json
|
|
|
137
150
|
skillio list --global # ~/.agents/.skill-lock.json
|
|
138
151
|
```
|
|
139
152
|
|
|
153
|
+
### `skillio cost` / `co`
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
skillio cost # local: per-skill frontmatter tokens with verdict
|
|
157
|
+
skillio cost --global # same, against ~/.agents/.skill-lock.json
|
|
158
|
+
```
|
|
159
|
+
|
|
140
160
|
### `skillio remove` / `rm`
|
|
141
161
|
|
|
142
162
|
```sh
|
|
@@ -144,6 +164,7 @@ skillio remove <skill-name>
|
|
|
144
164
|
skillio remove <skill-one> <skill-two>
|
|
145
165
|
skillio remove --global <skill-name>
|
|
146
166
|
skillio remove --dry-run <skill-name>
|
|
167
|
+
skillio remove --yes <skill-name> # skip confirmation prompt
|
|
147
168
|
```
|
|
148
169
|
|
|
149
170
|
## Requirements
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/cli.ts
|
|
4
|
-
import { createRequire } from "node:module";
|
|
6
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
5
7
|
|
|
6
8
|
// node_modules/citty/dist/_chunks/libs/scule.mjs
|
|
7
9
|
var NUMBER_CHAR_RE = /\d/;
|
|
@@ -545,12 +547,524 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
545
547
|
return [`--${long}`, `-${short}`];
|
|
546
548
|
}
|
|
547
549
|
|
|
548
|
-
// src/
|
|
549
|
-
import { existsSync
|
|
550
|
-
import {
|
|
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
|
+
|
|
600
|
+
// src/utils/discover-skills.ts
|
|
601
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
602
|
+
import { homedir as homedir3 } from "node:os";
|
|
603
|
+
import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
|
|
604
|
+
|
|
605
|
+
// src/utils/skill-files.ts
|
|
606
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
607
|
+
import { homedir as homedir2 } from "node:os";
|
|
608
|
+
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
609
|
+
var CHARS_PER_TOKEN = 4;
|
|
610
|
+
function extractFrontmatter(content) {
|
|
611
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
612
|
+
return match?.[1];
|
|
613
|
+
}
|
|
614
|
+
function estimateTokens(text) {
|
|
615
|
+
return Math.round(text.length / CHARS_PER_TOKEN);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/utils/discover-skills.ts
|
|
619
|
+
function resolveRoots(input) {
|
|
620
|
+
if (input.isGlobal) {
|
|
621
|
+
return {
|
|
622
|
+
claude: join3(homedir3(), ".claude", "skills"),
|
|
623
|
+
agents: join3(homedir3(), ".agents", "skills")
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const repo = dirname3(resolve2(input.lockPath));
|
|
627
|
+
return {
|
|
628
|
+
claude: join3(repo, ".claude", "skills"),
|
|
629
|
+
agents: join3(repo, ".agents", "skills")
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function listSkillNames(root) {
|
|
633
|
+
if (!root || !existsSync3(root))
|
|
634
|
+
return [];
|
|
635
|
+
return readdirSync(root).filter((name) => {
|
|
636
|
+
const skill = join3(root, name, "SKILL.md");
|
|
637
|
+
return existsSync3(skill) && statSync(skill).isFile();
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function tokensFromFile(path) {
|
|
641
|
+
const content = readFileSync3(path, "utf8");
|
|
642
|
+
const fm = extractFrontmatter(content);
|
|
643
|
+
if (fm === undefined)
|
|
644
|
+
return { status: "no-frontmatter" };
|
|
645
|
+
return { tokens: estimateTokens(fm), status: "ok" };
|
|
646
|
+
}
|
|
647
|
+
function discoverSkills(input) {
|
|
648
|
+
const roots = resolveRoots(input);
|
|
649
|
+
const lock = readLock(input.lockPath);
|
|
650
|
+
const lockNames = Object.keys(lock.skills);
|
|
651
|
+
const claudeNames = listSkillNames(roots.claude);
|
|
652
|
+
const agentsNames = listSkillNames(roots.agents);
|
|
653
|
+
const all = new Set([...lockNames, ...claudeNames, ...agentsNames]);
|
|
654
|
+
const out = new Map;
|
|
655
|
+
for (const name of all) {
|
|
656
|
+
const sources = [];
|
|
657
|
+
if (lockNames.includes(name))
|
|
658
|
+
sources.push("lock");
|
|
659
|
+
if (claudeNames.includes(name))
|
|
660
|
+
sources.push(".claude");
|
|
661
|
+
if (agentsNames.includes(name))
|
|
662
|
+
sources.push(".agents");
|
|
663
|
+
let skillFile;
|
|
664
|
+
if (claudeNames.includes(name) && roots.claude) {
|
|
665
|
+
skillFile = join3(roots.claude, name, "SKILL.md");
|
|
666
|
+
} else if (agentsNames.includes(name) && roots.agents) {
|
|
667
|
+
skillFile = join3(roots.agents, name, "SKILL.md");
|
|
668
|
+
}
|
|
669
|
+
if (!skillFile) {
|
|
670
|
+
out.set(name, { name, sources, status: "missing" });
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const { tokens, status } = tokensFromFile(skillFile);
|
|
674
|
+
out.set(name, { name, sources, skillFile, frontmatterTokens: tokens, status });
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/commands/cost.ts
|
|
680
|
+
function classify(total) {
|
|
681
|
+
if (total < 1000)
|
|
682
|
+
return { verdict: "ok", message: "OK — keep it lean", paint: green };
|
|
683
|
+
if (total <= 1500)
|
|
684
|
+
return { verdict: "plan", message: "time to plan some cleanup", paint: yellow };
|
|
685
|
+
return { verdict: "cleanup", message: "ballast — clean it up", paint: red };
|
|
686
|
+
}
|
|
687
|
+
function sortRows(records) {
|
|
688
|
+
const ok = records.filter((r) => r.status === "ok");
|
|
689
|
+
const rest = records.filter((r) => r.status !== "ok");
|
|
690
|
+
ok.sort((a, b) => (b.frontmatterTokens ?? 0) - (a.frontmatterTokens ?? 0) || a.name.localeCompare(b.name));
|
|
691
|
+
rest.sort((a, b) => a.name.localeCompare(b.name));
|
|
692
|
+
return [...ok, ...rest];
|
|
693
|
+
}
|
|
694
|
+
var costCommand = defineCommand({
|
|
695
|
+
meta: { description: "Show ambient ballast cost (per-skill frontmatter tokens) sorted desc" },
|
|
696
|
+
args: {
|
|
697
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
|
|
698
|
+
},
|
|
699
|
+
run({ args }) {
|
|
700
|
+
const lockPath = getLockPath(args.global);
|
|
701
|
+
const map = discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath });
|
|
702
|
+
const rows = sortRows([...map.values()]);
|
|
703
|
+
const total = rows.reduce((acc, r) => acc + (r.frontmatterTokens ?? 0), 0);
|
|
704
|
+
const { message, paint } = classify(total);
|
|
705
|
+
console.log(args.global ? "Global" : "Local");
|
|
706
|
+
console.log("");
|
|
707
|
+
if (rows.length === 0) {
|
|
708
|
+
console.log(`No skills in ${lockPath}`);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const nameWidth = Math.max(...rows.map((r) => r.name.length));
|
|
712
|
+
for (const r of rows) {
|
|
713
|
+
let cell;
|
|
714
|
+
if (r.status === "ok")
|
|
715
|
+
cell = `~${r.frontmatterTokens} tok`;
|
|
716
|
+
else if (r.status === "missing")
|
|
717
|
+
cell = "missing";
|
|
718
|
+
else
|
|
719
|
+
cell = "(no frontmatter)";
|
|
720
|
+
console.log(`${r.name.padEnd(nameWidth)} ${cell}`);
|
|
721
|
+
}
|
|
722
|
+
console.log("");
|
|
723
|
+
console.log(`Total: ~${total} tok across ${rows.length} skills ${paint(message)}`);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// src/commands/list.ts
|
|
728
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
729
|
+
import { dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
|
|
730
|
+
function bySource(records) {
|
|
731
|
+
const claudeNames = records.filter((r) => r.sources.includes(".claude")).map((r) => r.name).sort();
|
|
732
|
+
const agentsNames = records.filter((r) => r.sources.includes(".agents")).map((r) => r.name).sort();
|
|
733
|
+
const lockNames = records.filter((r) => r.sources.includes("lock")).map((r) => r.name).sort();
|
|
734
|
+
const sumTokens = (names) => names.reduce((acc, n) => acc + (records.find((r) => r.name === n)?.frontmatterTokens ?? 0), 0);
|
|
735
|
+
return {
|
|
736
|
+
claude: {
|
|
737
|
+
label: ".claude/skills",
|
|
738
|
+
names: claudeNames,
|
|
739
|
+
tokens: sumTokens(claudeNames),
|
|
740
|
+
exists: true
|
|
741
|
+
},
|
|
742
|
+
agents: {
|
|
743
|
+
label: ".agents/skills",
|
|
744
|
+
names: agentsNames,
|
|
745
|
+
tokens: sumTokens(agentsNames),
|
|
746
|
+
exists: true
|
|
747
|
+
},
|
|
748
|
+
lock: {
|
|
749
|
+
label: "skills-lock.json",
|
|
750
|
+
names: lockNames,
|
|
751
|
+
tokens: sumTokens(lockNames),
|
|
752
|
+
exists: true
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function agentsDirExists(isGlobal, lockPath) {
|
|
757
|
+
if (isGlobal) {
|
|
758
|
+
return existsSync4(join4(process.env.HOME ?? "", ".agents", "skills"));
|
|
759
|
+
}
|
|
760
|
+
return existsSync4(join4(dirname4(resolve3(lockPath)), ".agents", "skills"));
|
|
761
|
+
}
|
|
762
|
+
var listCommand = defineCommand({
|
|
763
|
+
meta: { description: "List skills per source with totals and lock-vs-disk diff" },
|
|
764
|
+
args: {
|
|
765
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
|
|
766
|
+
},
|
|
767
|
+
run({ args }) {
|
|
768
|
+
const lockPath = getLockPath(args.global);
|
|
769
|
+
const map = discoverSkills({ isGlobal: args.global, cwd: process.cwd(), lockPath });
|
|
770
|
+
const records = [...map.values()];
|
|
771
|
+
const rows = bySource(records);
|
|
772
|
+
const showAgents = agentsDirExists(args.global, lockPath) || rows.agents.names.length > 0;
|
|
773
|
+
const claudeNames = rows.claude.names;
|
|
774
|
+
const agentsNames = rows.agents.names;
|
|
775
|
+
const lockNames = rows.lock.names;
|
|
776
|
+
const lockOnly = lockNames.filter((n) => !claudeNames.includes(n) && !agentsNames.includes(n));
|
|
777
|
+
const claudeNotInLock = claudeNames.filter((n) => !lockNames.includes(n));
|
|
778
|
+
const agentsNotInLock = agentsNames.filter((n) => !lockNames.includes(n));
|
|
779
|
+
const sourceRows = [rows.claude];
|
|
780
|
+
if (showAgents)
|
|
781
|
+
sourceRows.push(rows.agents);
|
|
782
|
+
sourceRows.push(rows.lock);
|
|
783
|
+
const labelWidth = Math.max(...sourceRows.map((r) => r.label.length));
|
|
784
|
+
const countCells = sourceRows.map((r) => r.names.length === 0 ? "(empty)" : `${r.names.length} skill${r.names.length === 1 ? "" : "s"}`);
|
|
785
|
+
const countWidth = Math.max(...countCells.map((c) => c.length));
|
|
786
|
+
for (let i = 0;i < sourceRows.length; i++) {
|
|
787
|
+
const row = sourceRows[i];
|
|
788
|
+
if (!row)
|
|
789
|
+
continue;
|
|
790
|
+
const countCell = countCells[i] ?? "";
|
|
791
|
+
const namesText = row.names.length ? row.names.join(" ") : "";
|
|
792
|
+
const line = `${row.label.padEnd(labelWidth)} : ${countCell.padEnd(countWidth)}${namesText ? ` : ${namesText}` : ""}`;
|
|
793
|
+
console.log(line.trimEnd());
|
|
794
|
+
}
|
|
795
|
+
const diffs = [];
|
|
796
|
+
if (lockOnly.length) {
|
|
797
|
+
diffs.push(`skills-lock.json has ${lockOnly.length} skill${lockOnly.length === 1 ? "" : "s"} missing on disk: ${lockOnly.join(", ")}`);
|
|
798
|
+
}
|
|
799
|
+
if (claudeNotInLock.length) {
|
|
800
|
+
diffs.push(`.claude/skills has ${claudeNotInLock.length} skill${claudeNotInLock.length === 1 ? "" : "s"} not in lock: ${claudeNotInLock.join(", ")}`);
|
|
801
|
+
}
|
|
802
|
+
if (agentsNotInLock.length) {
|
|
803
|
+
diffs.push(`.agents/skills has ${agentsNotInLock.length} skill${agentsNotInLock.length === 1 ? "" : "s"} not in lock: ${agentsNotInLock.join(", ")}`);
|
|
804
|
+
}
|
|
805
|
+
if (diffs.length) {
|
|
806
|
+
console.log("");
|
|
807
|
+
for (const line of diffs)
|
|
808
|
+
console.log(line);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// src/commands/remove.ts
|
|
814
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
815
|
+
import { homedir as homedir4 } from "node:os";
|
|
816
|
+
import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
|
|
817
|
+
|
|
818
|
+
// src/utils/confirm.ts
|
|
819
|
+
import { createInterface } from "node:readline/promises";
|
|
820
|
+
async function confirm(question, opts = {}) {
|
|
821
|
+
const input = opts.input ?? process.stdin;
|
|
822
|
+
const output = opts.output ?? process.stdout;
|
|
823
|
+
const rl = createInterface({
|
|
824
|
+
input,
|
|
825
|
+
output
|
|
826
|
+
});
|
|
827
|
+
try {
|
|
828
|
+
const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
|
|
829
|
+
return answer === "y" || answer === "yes";
|
|
830
|
+
} finally {
|
|
831
|
+
rl.close();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/utils/fs-rm.ts
|
|
836
|
+
import { existsSync as existsSync5, lstatSync, readdirSync as readdirSync2, rmSync } from "node:fs";
|
|
837
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
838
|
+
function isInside(target, root) {
|
|
839
|
+
const t = resolve4(target);
|
|
840
|
+
const r = resolve4(root);
|
|
841
|
+
return t === r || t.startsWith(`${r}/`);
|
|
842
|
+
}
|
|
843
|
+
function countFiles(path) {
|
|
844
|
+
if (!existsSync5(path))
|
|
845
|
+
return 0;
|
|
846
|
+
const stat = lstatSync(path);
|
|
847
|
+
if (stat.isFile())
|
|
848
|
+
return 1;
|
|
849
|
+
if (!stat.isDirectory())
|
|
850
|
+
return 0;
|
|
851
|
+
let n = 0;
|
|
852
|
+
for (const entry of readdirSync2(path)) {
|
|
853
|
+
n += countFiles(join5(path, entry));
|
|
854
|
+
}
|
|
855
|
+
return n;
|
|
856
|
+
}
|
|
857
|
+
function rmSkillDir(path, opts) {
|
|
858
|
+
const safe = opts.allowedRoots.some((root) => isInside(path, root));
|
|
859
|
+
if (!safe) {
|
|
860
|
+
throw new Error(`Refusing to delete: "${path}" is outside allowed roots`);
|
|
861
|
+
}
|
|
862
|
+
if (!existsSync5(path))
|
|
863
|
+
return { removed: false, fileCount: 0 };
|
|
864
|
+
const fileCount = countFiles(path);
|
|
865
|
+
rmSync(path, { recursive: true, force: true });
|
|
866
|
+
return { removed: true, fileCount };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/commands/remove.ts
|
|
870
|
+
function buildTarget(name, isGlobal, lockPath) {
|
|
871
|
+
const lock = readLock(lockPath);
|
|
872
|
+
const inLock = Object.hasOwn(lock.skills, name);
|
|
873
|
+
const baseClaude = isGlobal ? join6(homedir4(), ".claude", "skills") : join6(dirname5(resolve5(lockPath)), ".claude", "skills");
|
|
874
|
+
const baseAgents = isGlobal ? join6(homedir4(), ".agents", "skills") : join6(dirname5(resolve5(lockPath)), ".agents", "skills");
|
|
875
|
+
const claudeDir = existsSync6(join6(baseClaude, name)) ? join6(baseClaude, name) : undefined;
|
|
876
|
+
const agentsDir = existsSync6(join6(baseAgents, name)) ? join6(baseAgents, name) : undefined;
|
|
877
|
+
return { name, inLock, claudeDir, agentsDir };
|
|
878
|
+
}
|
|
879
|
+
function fileCount(dir) {
|
|
880
|
+
const { readdirSync: readdirSync3, statSync: statSync2 } = __require("node:fs");
|
|
881
|
+
let n = 0;
|
|
882
|
+
const stack = [dir];
|
|
883
|
+
while (stack.length) {
|
|
884
|
+
const cur = stack.pop();
|
|
885
|
+
const stat = statSync2(cur);
|
|
886
|
+
if (stat.isFile())
|
|
887
|
+
n++;
|
|
888
|
+
else if (stat.isDirectory())
|
|
889
|
+
for (const e of readdirSync3(cur))
|
|
890
|
+
stack.push(join6(cur, e));
|
|
891
|
+
}
|
|
892
|
+
return n;
|
|
893
|
+
}
|
|
894
|
+
function printPlan(plan) {
|
|
895
|
+
const { target } = plan;
|
|
896
|
+
console.log(`Will remove "${target.name}":`);
|
|
897
|
+
if (target.inLock)
|
|
898
|
+
console.log(" - skills-lock.json");
|
|
899
|
+
else
|
|
900
|
+
console.log(" - skills-lock.json (not in lock)");
|
|
901
|
+
if (target.claudeDir)
|
|
902
|
+
console.log(` - .claude/skills/${target.name}/ (${plan.claudeFileCount} files)`);
|
|
903
|
+
else
|
|
904
|
+
console.log(" - .claude/skills/ (not found)");
|
|
905
|
+
if (target.agentsDir)
|
|
906
|
+
console.log(` - .agents/skills/${target.name}/ (${plan.agentsFileCount} files)`);
|
|
907
|
+
else
|
|
908
|
+
console.log(" - .agents/skills/ (not found)");
|
|
909
|
+
}
|
|
910
|
+
var removeCommand = defineCommand({
|
|
911
|
+
meta: { description: "Remove one or more skills from lock and delete their on-disk directories" },
|
|
912
|
+
args: {
|
|
913
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" },
|
|
914
|
+
"dry-run": { type: "boolean", default: false, description: "Print plan, do not delete" },
|
|
915
|
+
yes: { type: "boolean", alias: "y", default: false, description: "Skip confirmation prompt" }
|
|
916
|
+
},
|
|
917
|
+
async run({ args }) {
|
|
918
|
+
const { global: isGlobal, "dry-run": dryRun, yes } = args;
|
|
919
|
+
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
920
|
+
const names = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
921
|
+
if (names.length === 0) {
|
|
922
|
+
console.error("No skill names provided");
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
const lockPath = getLockPath(isGlobal);
|
|
926
|
+
const targets = names.map((n) => buildTarget(n, isGlobal, lockPath));
|
|
927
|
+
const orphan = targets.filter((t) => !t.inLock && !t.claudeDir && !t.agentsDir);
|
|
928
|
+
if (orphan.length) {
|
|
929
|
+
for (const o of orphan)
|
|
930
|
+
console.log(`"${o.name}" is not in lock or on disk`);
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
const plans = targets.map((t) => ({
|
|
934
|
+
target: t,
|
|
935
|
+
claudeFileCount: t.claudeDir ? fileCount(t.claudeDir) : undefined,
|
|
936
|
+
agentsFileCount: t.agentsDir ? fileCount(t.agentsDir) : undefined
|
|
937
|
+
}));
|
|
938
|
+
for (const p of plans) {
|
|
939
|
+
printPlan(p);
|
|
940
|
+
console.log("");
|
|
941
|
+
}
|
|
942
|
+
if (dryRun)
|
|
943
|
+
return;
|
|
944
|
+
if (!yes) {
|
|
945
|
+
const ok = await confirm("Proceed?");
|
|
946
|
+
if (!ok) {
|
|
947
|
+
console.log("Aborted");
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const allowedRoots = [
|
|
952
|
+
isGlobal ? homedir4() : dirname5(resolve5(lockPath)),
|
|
953
|
+
homedir4()
|
|
954
|
+
];
|
|
955
|
+
for (const { target } of plans) {
|
|
956
|
+
if (target.inLock) {
|
|
957
|
+
const r = removeSkillFromLock(lockPath, target.name);
|
|
958
|
+
if (r.removed)
|
|
959
|
+
console.log(`Removed "${target.name}" from skills-lock.json`);
|
|
960
|
+
} else {
|
|
961
|
+
console.log(`Skipped skills-lock.json (not in lock)`);
|
|
962
|
+
}
|
|
963
|
+
if (target.claudeDir) {
|
|
964
|
+
const r = rmSkillDir(target.claudeDir, { allowedRoots });
|
|
965
|
+
console.log(`Removed "${target.name}" from .claude/skills (${r.fileCount} files)`);
|
|
966
|
+
} else {
|
|
967
|
+
console.log("Skipped .claude/skills (not found)");
|
|
968
|
+
}
|
|
969
|
+
if (target.agentsDir) {
|
|
970
|
+
const r = rmSkillDir(target.agentsDir, { allowedRoots });
|
|
971
|
+
console.log(`Removed "${target.name}" from .agents/skills (${r.fileCount} files)`);
|
|
972
|
+
} else {
|
|
973
|
+
console.log("Skipped .agents/skills (not found)");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// src/commands/summary.ts
|
|
980
|
+
function classify2(total) {
|
|
981
|
+
if (total < 1000)
|
|
982
|
+
return { verdict: "ok", message: "OK — keep it lean", paint: green };
|
|
983
|
+
if (total <= 1500)
|
|
984
|
+
return { verdict: "plan", message: "time to plan some cleanup", paint: yellow };
|
|
985
|
+
return { verdict: "cleanup", message: "ballast — clean it up", paint: red };
|
|
986
|
+
}
|
|
987
|
+
function bucketTokens(records, source) {
|
|
988
|
+
return records.filter((r) => r.sources.includes(source)).reduce((acc, r) => acc + (r.frontmatterTokens ?? 0), 0);
|
|
989
|
+
}
|
|
990
|
+
function bucketCount(records, source) {
|
|
991
|
+
return records.filter((r) => r.sources.includes(source)).length;
|
|
992
|
+
}
|
|
993
|
+
function buildSection(opts) {
|
|
994
|
+
const lockPath = getLockPath(opts.isGlobal);
|
|
995
|
+
const records = [
|
|
996
|
+
...discoverSkills({ isGlobal: opts.isGlobal, cwd: opts.cwd, lockPath }).values()
|
|
997
|
+
];
|
|
998
|
+
const rows = [
|
|
999
|
+
{
|
|
1000
|
+
label: `${opts.prefix}.claude/skills`,
|
|
1001
|
+
count: bucketCount(records, ".claude"),
|
|
1002
|
+
tokens: bucketTokens(records, ".claude")
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
label: `${opts.prefix}.agents/skills`,
|
|
1006
|
+
count: bucketCount(records, ".agents"),
|
|
1007
|
+
tokens: bucketTokens(records, ".agents")
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
label: `${opts.prefix}skills-lock.json`,
|
|
1011
|
+
count: bucketCount(records, "lock"),
|
|
1012
|
+
tokens: bucketTokens(records, "lock")
|
|
1013
|
+
}
|
|
1014
|
+
];
|
|
1015
|
+
const totalTokens = records.reduce((acc, r) => acc + (r.frontmatterTokens ?? 0), 0);
|
|
1016
|
+
const totalCount = records.length;
|
|
1017
|
+
return {
|
|
1018
|
+
title: opts.isGlobal ? "Global" : "Local",
|
|
1019
|
+
rows,
|
|
1020
|
+
totalCount,
|
|
1021
|
+
totalTokens
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function formatRow(row, labelW, countW, tokenW) {
|
|
1025
|
+
const countCell = row.count === 0 ? "(empty)" : `${row.count} skill${row.count === 1 ? "" : "s"}`;
|
|
1026
|
+
const tokensCell = `~${row.tokens} tok`;
|
|
1027
|
+
return `${row.label.padEnd(labelW)} : ${countCell.padEnd(countW)} ${tokensCell.padStart(tokenW)}`;
|
|
1028
|
+
}
|
|
1029
|
+
function renderSection(section) {
|
|
1030
|
+
const labelW = Math.max(...section.rows.map((r) => r.label.length));
|
|
1031
|
+
const countCells = section.rows.map((r) => r.count === 0 ? "(empty)" : `${r.count} skill${r.count === 1 ? "" : "s"}`);
|
|
1032
|
+
const countW = Math.max(...countCells.map((c) => c.length));
|
|
1033
|
+
const tokenW = Math.max(...section.rows.map((r) => `~${r.tokens} tok`.length));
|
|
1034
|
+
return [section.title, ...section.rows.map((r) => formatRow(r, labelW, countW, tokenW))];
|
|
1035
|
+
}
|
|
1036
|
+
function runSummary(args) {
|
|
1037
|
+
const cwd = process.cwd();
|
|
1038
|
+
const global = buildSection({ isGlobal: true, cwd, prefix: "~/" });
|
|
1039
|
+
const local = buildSection({ isGlobal: false, cwd, prefix: "" });
|
|
1040
|
+
const lines = [];
|
|
1041
|
+
lines.push(...renderSection(global));
|
|
1042
|
+
lines.push("");
|
|
1043
|
+
lines.push(...renderSection(local));
|
|
1044
|
+
lines.push("");
|
|
1045
|
+
const grandTokens = global.totalTokens + local.totalTokens;
|
|
1046
|
+
const grandCount = global.totalCount + local.totalCount;
|
|
1047
|
+
const { message, paint } = classify2(grandTokens);
|
|
1048
|
+
lines.push(`Total: ${grandCount} skills ~${grandTokens} tok ${paint(message)}`);
|
|
1049
|
+
console.log(lines.join(`
|
|
1050
|
+
`));
|
|
1051
|
+
}
|
|
1052
|
+
var summaryCommand = defineCommand({
|
|
1053
|
+
meta: { description: "Show skill counts and tokens across global + local sources" },
|
|
1054
|
+
args: {
|
|
1055
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global scope" }
|
|
1056
|
+
},
|
|
1057
|
+
run({ args }) {
|
|
1058
|
+
runSummary({ global: args.global });
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// src/commands/usage.ts
|
|
1063
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1064
|
+
import { join as join10 } from "node:path";
|
|
551
1065
|
|
|
552
1066
|
// src/readers/claude.ts
|
|
553
|
-
import { readFileSync as
|
|
1067
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
554
1068
|
|
|
555
1069
|
// src/utils/walk.ts
|
|
556
1070
|
function walk(value, visit) {
|
|
@@ -658,26 +1172,26 @@ function extractCodexMentions(entry) {
|
|
|
658
1172
|
}
|
|
659
1173
|
|
|
660
1174
|
// src/utils/expand-home.ts
|
|
661
|
-
import { homedir } from "node:os";
|
|
662
|
-
import { join, resolve } from "node:path";
|
|
1175
|
+
import { homedir as homedir5 } from "node:os";
|
|
1176
|
+
import { join as join7, resolve as resolve6 } from "node:path";
|
|
663
1177
|
function expandHome(p) {
|
|
664
1178
|
if (p === "~")
|
|
665
|
-
return
|
|
1179
|
+
return homedir5();
|
|
666
1180
|
if (p.startsWith("~/"))
|
|
667
|
-
return
|
|
668
|
-
return
|
|
1181
|
+
return join7(homedir5(), p.slice(2));
|
|
1182
|
+
return resolve6(p);
|
|
669
1183
|
}
|
|
670
1184
|
|
|
671
1185
|
// src/utils/jsonl.ts
|
|
672
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
673
|
-
import { join as
|
|
1186
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
1187
|
+
import { join as join8 } from "node:path";
|
|
674
1188
|
function* findJsonlFiles(dir, since) {
|
|
675
|
-
for (const item of
|
|
676
|
-
const path =
|
|
1189
|
+
for (const item of readdirSync3(dir, { withFileTypes: true })) {
|
|
1190
|
+
const path = join8(dir, item.name);
|
|
677
1191
|
if (item.isDirectory()) {
|
|
678
1192
|
yield* findJsonlFiles(path, since);
|
|
679
1193
|
} else if (item.isFile() && item.name.endsWith(".jsonl")) {
|
|
680
|
-
if (!since ||
|
|
1194
|
+
if (!since || statSync2(path).mtime >= since)
|
|
681
1195
|
yield path;
|
|
682
1196
|
}
|
|
683
1197
|
}
|
|
@@ -711,7 +1225,7 @@ function readClaudeUsage(options) {
|
|
|
711
1225
|
const since = options.scanAllFiles ? undefined : options.since;
|
|
712
1226
|
for (const file of findJsonlFiles(root, since)) {
|
|
713
1227
|
filesRead++;
|
|
714
|
-
for (const line of
|
|
1228
|
+
for (const line of readFileSync5(file, "utf8").split(`
|
|
715
1229
|
`)) {
|
|
716
1230
|
if (!line.trim())
|
|
717
1231
|
continue;
|
|
@@ -733,14 +1247,14 @@ function readClaudeUsage(options) {
|
|
|
733
1247
|
}
|
|
734
1248
|
|
|
735
1249
|
// src/readers/codex.ts
|
|
736
|
-
import { existsSync as
|
|
1250
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
737
1251
|
|
|
738
1252
|
// src/utils/scope.ts
|
|
739
|
-
import { existsSync } from "node:fs";
|
|
740
|
-
import { homedir as
|
|
741
|
-
import { dirname, join as
|
|
1253
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1254
|
+
import { homedir as homedir6 } from "node:os";
|
|
1255
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
742
1256
|
function detectScope(opts) {
|
|
743
|
-
const home = opts.home ??
|
|
1257
|
+
const home = opts.home ?? homedir6();
|
|
744
1258
|
if (opts.global || opts.rootOverride)
|
|
745
1259
|
return { global: true };
|
|
746
1260
|
if (norm(opts.cwd) === norm(home))
|
|
@@ -758,9 +1272,9 @@ function encodeClaudeProjectDir(absPath) {
|
|
|
758
1272
|
function findGitRoot(start) {
|
|
759
1273
|
let dir = start;
|
|
760
1274
|
while (true) {
|
|
761
|
-
if (
|
|
1275
|
+
if (existsSync7(join9(dir, ".git")))
|
|
762
1276
|
return dir;
|
|
763
|
-
const parent =
|
|
1277
|
+
const parent = dirname6(dir);
|
|
764
1278
|
if (parent === dir)
|
|
765
1279
|
return;
|
|
766
1280
|
dir = parent;
|
|
@@ -774,7 +1288,7 @@ function norm(p) {
|
|
|
774
1288
|
function readSessionCwd(file) {
|
|
775
1289
|
let head;
|
|
776
1290
|
try {
|
|
777
|
-
head =
|
|
1291
|
+
head = readFileSync6(file, "utf8");
|
|
778
1292
|
} catch {
|
|
779
1293
|
return;
|
|
780
1294
|
}
|
|
@@ -807,7 +1321,7 @@ function readCodexActivations(options) {
|
|
|
807
1321
|
continue;
|
|
808
1322
|
}
|
|
809
1323
|
filesRead++;
|
|
810
|
-
for (const line of
|
|
1324
|
+
for (const line of readFileSync6(file, "utf8").split(`
|
|
811
1325
|
`)) {
|
|
812
1326
|
if (!line.trim())
|
|
813
1327
|
continue;
|
|
@@ -831,9 +1345,9 @@ function readCodexMentions(options) {
|
|
|
831
1345
|
const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
|
|
832
1346
|
const counts = new Map;
|
|
833
1347
|
let linesRead = 0;
|
|
834
|
-
if (!
|
|
1348
|
+
if (!existsSync8(historyPath))
|
|
835
1349
|
return { counts, filesRead: 0, linesRead: 0 };
|
|
836
|
-
for (const line of
|
|
1350
|
+
for (const line of readFileSync6(historyPath, "utf8").split(`
|
|
837
1351
|
`)) {
|
|
838
1352
|
if (!line.trim())
|
|
839
1353
|
continue;
|
|
@@ -854,38 +1368,77 @@ function readCodexMentions(options) {
|
|
|
854
1368
|
}
|
|
855
1369
|
|
|
856
1370
|
// src/utils/period.ts
|
|
857
|
-
var
|
|
858
|
-
var
|
|
1371
|
+
var SECOND_MS = 1000;
|
|
1372
|
+
var MINUTE_MS = 60 * SECOND_MS;
|
|
1373
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
1374
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
1375
|
+
var UNITS_MS = {
|
|
1376
|
+
sec: SECOND_MS,
|
|
1377
|
+
min: MINUTE_MS,
|
|
1378
|
+
h: HOUR_MS,
|
|
1379
|
+
d: DAY_MS,
|
|
1380
|
+
w: 7 * DAY_MS,
|
|
1381
|
+
m: 30 * DAY_MS,
|
|
1382
|
+
y: 365 * DAY_MS
|
|
1383
|
+
};
|
|
859
1384
|
function parsePeriod(period) {
|
|
860
1385
|
if (period === "all")
|
|
861
1386
|
return Number.POSITIVE_INFINITY;
|
|
862
|
-
const match = period.match(/^(\d+)([
|
|
863
|
-
if (!match)
|
|
864
|
-
throw new Error(`Invalid period: "${period}". Use values like 7d, 2w, 1m, 1y, all.`);
|
|
865
|
-
|
|
1387
|
+
const match = period.match(/^(\d+)(sec|min|[hdwmy])$/);
|
|
1388
|
+
if (!match) {
|
|
1389
|
+
throw new Error(`Invalid period: "${period}". Use values like 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all.`);
|
|
1390
|
+
}
|
|
1391
|
+
const unit = UNITS_MS[match[2] ?? ""] ?? 0;
|
|
866
1392
|
return Number(match[1]) * unit;
|
|
867
1393
|
}
|
|
868
1394
|
|
|
869
|
-
// src/commands/
|
|
1395
|
+
// src/commands/usage.ts
|
|
1396
|
+
function pad(n, width) {
|
|
1397
|
+
return String(n).padStart(width);
|
|
1398
|
+
}
|
|
1399
|
+
function formatUsageRow(row) {
|
|
1400
|
+
return `${pad(row.count, row.countWidth)} ${row.name}`;
|
|
1401
|
+
}
|
|
870
1402
|
function parseAgents(agent) {
|
|
871
1403
|
if (!agent)
|
|
872
1404
|
return ["claude-code", "codex"];
|
|
873
|
-
const
|
|
1405
|
+
const out = agent.split("\x1F").map((a) => a.trim()).filter(Boolean).map((a) => {
|
|
874
1406
|
if (a === "codex")
|
|
875
1407
|
return "codex";
|
|
876
1408
|
if (["claude", "claude-code", "claudecode"].includes(a))
|
|
877
1409
|
return "claude-code";
|
|
878
1410
|
throw new Error(`Unknown agent: "${a}". Use "claude-code" or "codex" (space-separated for both: -a claude-code codex).`);
|
|
879
1411
|
});
|
|
880
|
-
return [...new Set(
|
|
1412
|
+
return [...new Set(out)];
|
|
881
1413
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1414
|
+
var usageArgs = {
|
|
1415
|
+
agent: {
|
|
1416
|
+
type: "string",
|
|
1417
|
+
alias: "a",
|
|
1418
|
+
description: "claude-code, codex (default: both)"
|
|
1419
|
+
},
|
|
1420
|
+
period: {
|
|
1421
|
+
type: "string",
|
|
1422
|
+
alias: "p",
|
|
1423
|
+
default: "all",
|
|
1424
|
+
description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
|
|
1425
|
+
},
|
|
1426
|
+
since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
|
|
1427
|
+
mode: { type: "string", description: "attributed | activations | mentions" },
|
|
1428
|
+
format: { type: "string", default: "text", description: "text | json" },
|
|
1429
|
+
root: { type: "string", description: "Override agent sessions directory; implies global" },
|
|
1430
|
+
"scan-all-files": { type: "boolean", default: false, description: "Ignore file mtime" },
|
|
1431
|
+
global: {
|
|
1432
|
+
type: "boolean",
|
|
1433
|
+
alias: "g",
|
|
1434
|
+
default: false,
|
|
1435
|
+
description: "Force global scope"
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
async function runUsage(args) {
|
|
886
1439
|
const agents = parseAgents(args.agent);
|
|
887
1440
|
const allTime = !args.since && args.period === "all";
|
|
888
|
-
const since = args.since ? new Date(`${args.since}T00:00:00`) : args.period === "all" ? new Date(0) : new Date(Date.now() - parsePeriod(args.period)
|
|
1441
|
+
const since = args.since ? new Date(`${args.since}T00:00:00`) : args.period === "all" ? new Date(0) : new Date(Date.now() - parsePeriod(args.period));
|
|
889
1442
|
const scanAllFiles = allTime || args["scan-all-files"];
|
|
890
1443
|
if (Number.isNaN(since.getTime())) {
|
|
891
1444
|
console.error(`Invalid --since value: ${args.since}`);
|
|
@@ -897,21 +1450,26 @@ async function runAudit(args) {
|
|
|
897
1450
|
cwd: process.cwd()
|
|
898
1451
|
});
|
|
899
1452
|
const claudeProjectsRoot = expandHome("~/.claude/projects");
|
|
900
|
-
const claudeRoot = args.root ?? (scope.projectRoot ?
|
|
901
|
-
const claudeRootMissing = !args.root && !!scope.projectRoot && !
|
|
1453
|
+
const claudeRoot = args.root ?? (scope.projectRoot ? join10(claudeProjectsRoot, encodeClaudeProjectDir(scope.projectRoot)) : claudeProjectsRoot);
|
|
1454
|
+
const claudeRootMissing = !args.root && !!scope.projectRoot && !existsSync9(claudeRoot);
|
|
1455
|
+
const lockPath = getLockPath(args.global);
|
|
1456
|
+
const skillUniverse = discoverSkills({
|
|
1457
|
+
isGlobal: args.global,
|
|
1458
|
+
cwd: process.cwd(),
|
|
1459
|
+
lockPath
|
|
1460
|
+
});
|
|
902
1461
|
const results = [];
|
|
903
1462
|
for (const agent of agents) {
|
|
1463
|
+
let counts;
|
|
1464
|
+
let stats;
|
|
1465
|
+
let mode;
|
|
904
1466
|
if (agent === "claude-code") {
|
|
905
|
-
|
|
1467
|
+
mode = args.mode ?? "attributed";
|
|
906
1468
|
const result = claudeRootMissing ? { counts: new Map, filesRead: 0, linesRead: 0 } : readClaudeUsage({ since, mode, root: claudeRoot, scanAllFiles });
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
mode,
|
|
910
|
-
rows: toRows(result.counts),
|
|
911
|
-
stats: { filesRead: result.filesRead, linesRead: result.linesRead }
|
|
912
|
-
});
|
|
1469
|
+
counts = result.counts;
|
|
1470
|
+
stats = { filesRead: result.filesRead, linesRead: result.linesRead };
|
|
913
1471
|
} else {
|
|
914
|
-
|
|
1472
|
+
mode = args.mode ?? "activations";
|
|
915
1473
|
const result = readCodexUsage({
|
|
916
1474
|
since,
|
|
917
1475
|
mode,
|
|
@@ -919,165 +1477,167 @@ async function runAudit(args) {
|
|
|
919
1477
|
scanAllFiles,
|
|
920
1478
|
projectRoot: scope.projectRoot
|
|
921
1479
|
});
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
mode,
|
|
925
|
-
rows: toRows(result.counts),
|
|
926
|
-
stats: { filesRead: result.filesRead, linesRead: result.linesRead }
|
|
927
|
-
});
|
|
1480
|
+
counts = result.counts;
|
|
1481
|
+
stats = { filesRead: result.filesRead, linesRead: result.linesRead };
|
|
928
1482
|
}
|
|
1483
|
+
const universeNames = new Set([...skillUniverse.keys(), ...counts.keys()]);
|
|
1484
|
+
const rows = [...universeNames].map((name) => {
|
|
1485
|
+
const rec = skillUniverse.get(name);
|
|
1486
|
+
return {
|
|
1487
|
+
name,
|
|
1488
|
+
count: counts.get(name) ?? 0,
|
|
1489
|
+
tokens: rec?.frontmatterTokens,
|
|
1490
|
+
status: rec?.status ?? "ok"
|
|
1491
|
+
};
|
|
1492
|
+
});
|
|
1493
|
+
rows.sort((a, b) => {
|
|
1494
|
+
const aOk = a.status === "ok";
|
|
1495
|
+
const bOk = b.status === "ok";
|
|
1496
|
+
if (aOk !== bOk)
|
|
1497
|
+
return aOk ? -1 : 1;
|
|
1498
|
+
if (b.count !== a.count)
|
|
1499
|
+
return b.count - a.count;
|
|
1500
|
+
return a.name.localeCompare(b.name);
|
|
1501
|
+
});
|
|
1502
|
+
results.push({ agent, mode, rows, stats });
|
|
929
1503
|
}
|
|
930
1504
|
if (args.format === "json") {
|
|
931
1505
|
const output = results.map(({ agent, mode, rows }) => ({
|
|
932
1506
|
agent,
|
|
933
1507
|
mode,
|
|
934
1508
|
since: since.toISOString(),
|
|
935
|
-
skills: rows
|
|
1509
|
+
skills: rows.map((r) => ({
|
|
1510
|
+
skill: r.name,
|
|
1511
|
+
count: r.count,
|
|
1512
|
+
tokensPerSkill: r.tokens ?? null,
|
|
1513
|
+
consumption: (r.tokens ?? 0) * r.count
|
|
1514
|
+
}))
|
|
936
1515
|
}));
|
|
937
1516
|
console.log(JSON.stringify(output.length === 1 ? output[0] : output, null, 2));
|
|
938
1517
|
return;
|
|
939
1518
|
}
|
|
940
|
-
const
|
|
941
|
-
const
|
|
942
|
-
console.log(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1519
|
+
const periodLabel = args.since ? `since ${args.since}` : args.period ?? "all";
|
|
1520
|
+
const scopeHeader = scope.global ? "Global" : "Local";
|
|
1521
|
+
console.log(scopeHeader);
|
|
1522
|
+
const distinct = new Set;
|
|
1523
|
+
let grandActivations = 0;
|
|
1524
|
+
for (const { agent, rows } of results) {
|
|
1525
|
+
const activations = rows.reduce((acc, r) => acc + r.count, 0);
|
|
1526
|
+
console.log("");
|
|
1527
|
+
console.log(`${agent} ${rows.length} skill${rows.length === 1 ? "" : "s"} ${activations} time${activations === 1 ? "" : "s"} by ${periodLabel}`);
|
|
1528
|
+
if (rows.length === 0)
|
|
1529
|
+
continue;
|
|
1530
|
+
const countWidth = Math.max(...rows.map((r) => String(r.count).length));
|
|
1531
|
+
for (const r of rows) {
|
|
1532
|
+
console.log(formatUsageRow({ count: r.count, name: r.name, countWidth }));
|
|
1533
|
+
distinct.add(r.name);
|
|
953
1534
|
}
|
|
1535
|
+
grandActivations += activations;
|
|
954
1536
|
}
|
|
1537
|
+
console.log("");
|
|
1538
|
+
console.log(`Total: ${distinct.size} skill${distinct.size === 1 ? "" : "s"} usage ${grandActivations} time${grandActivations === 1 ? "" : "s"}`);
|
|
955
1539
|
}
|
|
956
|
-
var
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
root: { type: "string", description: "Override agent sessions directory; implies global" },
|
|
967
|
-
"scan-all-files": { type: "boolean", default: false, description: "Ignore file mtime" },
|
|
968
|
-
global: {
|
|
969
|
-
type: "boolean",
|
|
970
|
-
alias: "g",
|
|
971
|
-
default: false,
|
|
972
|
-
description: "Force global scope (ignore current directory)"
|
|
1540
|
+
var usageCommand = defineCommand({
|
|
1541
|
+
meta: { description: "Show skill usage x cost (consumption) with missed rows" },
|
|
1542
|
+
args: usageArgs,
|
|
1543
|
+
async run({ args }) {
|
|
1544
|
+
try {
|
|
1545
|
+
await runUsage(args);
|
|
1546
|
+
} catch (e) {
|
|
1547
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
1548
|
+
process.exit(1);
|
|
1549
|
+
}
|
|
973
1550
|
}
|
|
974
|
-
};
|
|
1551
|
+
});
|
|
975
1552
|
|
|
976
|
-
// src/
|
|
977
|
-
import {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
import { basename, dirname as dirname2, join as join5 } from "node:path";
|
|
987
|
-
function getLockPath(global) {
|
|
988
|
-
return global ? join5(homedir3(), ".agents", ".skill-lock.json") : "skills-lock.json";
|
|
989
|
-
}
|
|
990
|
-
function readLock(path) {
|
|
991
|
-
if (!existsSync4(path))
|
|
992
|
-
return { skills: {} };
|
|
993
|
-
return JSON.parse(readFileSync4(path, "utf8"));
|
|
1553
|
+
// src/utils/update-check.ts
|
|
1554
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1555
|
+
import { get } from "node:https";
|
|
1556
|
+
import { homedir as homedir7 } from "node:os";
|
|
1557
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
1558
|
+
var PKG = "skillio";
|
|
1559
|
+
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
1560
|
+
var FETCH_TIMEOUT_MS = 1500;
|
|
1561
|
+
function getCachePath() {
|
|
1562
|
+
return join11(homedir7(), ".cache", "skillio", "version.json");
|
|
994
1563
|
}
|
|
995
|
-
function
|
|
996
|
-
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1564
|
+
function compareVersions(a, b) {
|
|
1565
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1566
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1567
|
+
for (let i = 0;i < 3; i += 1) {
|
|
1568
|
+
const da = pa[i] ?? 0;
|
|
1569
|
+
const db = pb[i] ?? 0;
|
|
1570
|
+
if (da !== db)
|
|
1571
|
+
return da - db;
|
|
1572
|
+
}
|
|
1573
|
+
return 0;
|
|
1001
1574
|
}
|
|
1002
|
-
function
|
|
1003
|
-
|
|
1575
|
+
function readCache(path = getCachePath()) {
|
|
1576
|
+
try {
|
|
1577
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1578
|
+
} catch {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1004
1581
|
}
|
|
1005
|
-
function
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1582
|
+
function writeCache(cache, path = getCachePath()) {
|
|
1583
|
+
try {
|
|
1584
|
+
mkdirSync2(dirname7(path), { recursive: true });
|
|
1585
|
+
writeFileSync2(path, JSON.stringify(cache));
|
|
1586
|
+
} catch {}
|
|
1010
1587
|
}
|
|
1011
|
-
function
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1588
|
+
function fetchLatest() {
|
|
1589
|
+
return new Promise((resolve7) => {
|
|
1590
|
+
const req = get(`https://registry.npmjs.org/${PKG}/latest`, { timeout: FETCH_TIMEOUT_MS }, (res) => {
|
|
1591
|
+
if (res.statusCode !== 200) {
|
|
1592
|
+
res.resume();
|
|
1593
|
+
resolve7(undefined);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
let body = "";
|
|
1597
|
+
res.setEncoding("utf8");
|
|
1598
|
+
res.on("data", (chunk) => {
|
|
1599
|
+
body += chunk;
|
|
1600
|
+
});
|
|
1601
|
+
res.on("end", () => {
|
|
1602
|
+
try {
|
|
1603
|
+
const data = JSON.parse(body);
|
|
1604
|
+
resolve7(typeof data.version === "string" ? data.version : undefined);
|
|
1605
|
+
} catch {
|
|
1606
|
+
resolve7(undefined);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
});
|
|
1610
|
+
req.on("error", () => resolve7(undefined));
|
|
1611
|
+
req.on("timeout", () => {
|
|
1612
|
+
req.destroy();
|
|
1613
|
+
resolve7(undefined);
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1021
1616
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1617
|
+
async function maybePrintUpdateNotice(currentVersion) {
|
|
1618
|
+
if (process.env.SKILLIO_NO_UPDATE_CHECK)
|
|
1619
|
+
return;
|
|
1620
|
+
const now = Date.now();
|
|
1621
|
+
const cache = readCache();
|
|
1622
|
+
let latest = cache?.latest;
|
|
1623
|
+
if (!cache || now - cache.checkedAt > TTL_MS) {
|
|
1624
|
+
const fetched = await fetchLatest();
|
|
1625
|
+
if (fetched) {
|
|
1626
|
+
latest = fetched;
|
|
1627
|
+
writeCache({ checkedAt: now, latest });
|
|
1628
|
+
}
|
|
1034
1629
|
}
|
|
1035
|
-
|
|
1630
|
+
if (latest && compareVersions(latest, currentVersion) > 0) {
|
|
1631
|
+
process.stderr.write(`
|
|
1632
|
+
Update available: ${currentVersion} → ${latest}
|
|
1633
|
+
Run: npm i -g skillio
|
|
1036
1634
|
|
|
1037
|
-
|
|
1038
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
1039
|
-
var removeCommand = defineCommand({
|
|
1040
|
-
meta: { description: "Remove one or more skills from the lock file" },
|
|
1041
|
-
args: {
|
|
1042
|
-
global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" },
|
|
1043
|
-
"dry-run": { type: "boolean", default: false, description: "Print without making changes" }
|
|
1044
|
-
},
|
|
1045
|
-
run({ args }) {
|
|
1046
|
-
const { global: isGlobal, "dry-run": dryRun } = args;
|
|
1047
|
-
const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
|
|
1048
|
-
const skills = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
|
|
1049
|
-
if (skills.length === 0) {
|
|
1050
|
-
console.error("No skill names provided");
|
|
1051
|
-
process.exit(1);
|
|
1052
|
-
}
|
|
1053
|
-
const path = getLockPath(isGlobal);
|
|
1054
|
-
if (dryRun) {
|
|
1055
|
-
for (const skill of skills) {
|
|
1056
|
-
console.log(`Would remove "${skill}" from ${path}`);
|
|
1057
|
-
}
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
const backupPath = existsSync5(path) ? backupLock(path) : undefined;
|
|
1061
|
-
for (const skill of skills) {
|
|
1062
|
-
const result = removeSkillFromLock(path, skill, { skipBackup: true });
|
|
1063
|
-
if (result.removed) {
|
|
1064
|
-
console.log(`Removed "${skill}" from ${path}`);
|
|
1065
|
-
} else {
|
|
1066
|
-
console.log(`"${skill}" is not in ${path}`);
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
if (backupPath)
|
|
1070
|
-
console.log(`Backup: ${backupPath}`);
|
|
1071
|
-
const updated = readLock(path);
|
|
1072
|
-
console.log(JSON.stringify(Object.keys(updated.skills).sort(), null, 2));
|
|
1635
|
+
`);
|
|
1073
1636
|
}
|
|
1074
|
-
}
|
|
1637
|
+
}
|
|
1075
1638
|
|
|
1076
1639
|
// src/cli.ts
|
|
1077
|
-
var { version } =
|
|
1078
|
-
if (process.argv[2] === "audit") {
|
|
1079
|
-
process.argv.splice(2, 1);
|
|
1080
|
-
}
|
|
1640
|
+
var { version } = createRequire2(import.meta.url)("../package.json");
|
|
1081
1641
|
function mergeAgentArgs(argv) {
|
|
1082
1642
|
const out = [];
|
|
1083
1643
|
const values = [];
|
|
@@ -1110,30 +1670,126 @@ function mergeAgentArgs(argv) {
|
|
|
1110
1670
|
out.splice(slotIdx, 0, "--agent", values.join("\x1F"));
|
|
1111
1671
|
return out;
|
|
1112
1672
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1673
|
+
var SUBCOMMAND_NAMES = new Set(["list", "ls", "remove", "rm", "cost", "co", "usage", "us"]);
|
|
1674
|
+
function reorderRootFlagsToSubcommand(argv) {
|
|
1675
|
+
const tail = argv.slice(2);
|
|
1676
|
+
const subIdx = tail.findIndex((t) => !!t && SUBCOMMAND_NAMES.has(t));
|
|
1677
|
+
if (subIdx <= 0)
|
|
1678
|
+
return argv;
|
|
1679
|
+
const before = tail.slice(0, subIdx);
|
|
1680
|
+
const sub = tail[subIdx];
|
|
1681
|
+
const after = tail.slice(subIdx + 1);
|
|
1682
|
+
if (!sub)
|
|
1683
|
+
return argv;
|
|
1684
|
+
return [argv[0] ?? "", argv[1] ?? "", sub, ...before, ...after];
|
|
1685
|
+
}
|
|
1686
|
+
process.argv = reorderRootFlagsToSubcommand(mergeAgentArgs(process.argv));
|
|
1687
|
+
function printRootHelp() {
|
|
1688
|
+
const lines = [
|
|
1689
|
+
`Audit and manage AI agent skills (skillio v${version})`,
|
|
1690
|
+
"",
|
|
1691
|
+
"USAGE skillio [OPTIONS] [COMMAND]",
|
|
1692
|
+
"",
|
|
1693
|
+
"OPTIONS",
|
|
1694
|
+
"",
|
|
1695
|
+
" -h, --help Show this help and exit",
|
|
1696
|
+
" -v, --version Show version and exit",
|
|
1697
|
+
" -g, --global Use global scope (default: false)",
|
|
1698
|
+
" -p, --period Period for `usage`: 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all (default: all)",
|
|
1699
|
+
" -a, --agent Agent for `usage`: claude-code, codex (default: both)",
|
|
1700
|
+
"",
|
|
1701
|
+
"COMMANDS",
|
|
1702
|
+
"",
|
|
1703
|
+
" list, ls List skills per source with totals and lock-vs-disk diff",
|
|
1704
|
+
" remove, rm Remove skills from lock and delete their on-disk dirs",
|
|
1705
|
+
" cost, co Show ambient ballast cost (per-skill frontmatter tokens) sorted desc",
|
|
1706
|
+
" usage, us Show skill usage × cost (consumption) with missed rows"
|
|
1707
|
+
];
|
|
1708
|
+
console.log(lines.join(`
|
|
1709
|
+
`));
|
|
1710
|
+
}
|
|
1711
|
+
function isRootHelp(argv) {
|
|
1712
|
+
const args = argv.slice(2);
|
|
1713
|
+
const first = args[0];
|
|
1714
|
+
if (first && SUBCOMMAND_NAMES.has(first))
|
|
1715
|
+
return false;
|
|
1716
|
+
return args.includes("--help") || args.includes("-h");
|
|
1717
|
+
}
|
|
1718
|
+
function firstPositional(argv) {
|
|
1719
|
+
for (let i = 2;i < argv.length; i++) {
|
|
1720
|
+
const tok = argv[i];
|
|
1721
|
+
if (!tok)
|
|
1722
|
+
continue;
|
|
1723
|
+
if (tok.startsWith("-")) {
|
|
1724
|
+
if (tok === "-p" || tok === "--period" || tok === "-a" || tok === "--agent")
|
|
1725
|
+
i++;
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
return tok;
|
|
1729
|
+
}
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
function hasSubcommand(argv) {
|
|
1733
|
+
const tok = firstPositional(argv);
|
|
1734
|
+
return tok !== null && SUBCOMMAND_NAMES.has(tok);
|
|
1735
|
+
}
|
|
1736
|
+
function unknownCommand(argv) {
|
|
1737
|
+
const tok = firstPositional(argv);
|
|
1738
|
+
if (!tok)
|
|
1739
|
+
return null;
|
|
1740
|
+
if (SUBCOMMAND_NAMES.has(tok))
|
|
1741
|
+
return null;
|
|
1742
|
+
return tok;
|
|
1743
|
+
}
|
|
1744
|
+
function isRootVersion(argv) {
|
|
1745
|
+
const args = argv.slice(2);
|
|
1746
|
+
const first = args[0];
|
|
1747
|
+
if (first && SUBCOMMAND_NAMES.has(first))
|
|
1748
|
+
return false;
|
|
1749
|
+
return args.includes("--version") || args.includes("-v");
|
|
1750
|
+
}
|
|
1115
1751
|
var main = defineCommand({
|
|
1116
1752
|
meta: {
|
|
1117
1753
|
name: "skillio",
|
|
1118
1754
|
version,
|
|
1119
1755
|
description: "Audit and manage AI agent skills"
|
|
1120
1756
|
},
|
|
1121
|
-
args:
|
|
1757
|
+
args: summaryCommand.args,
|
|
1122
1758
|
async run({ args }) {
|
|
1123
|
-
if (
|
|
1759
|
+
if (hasSubcommand(process.argv))
|
|
1124
1760
|
return;
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1761
|
+
await summaryCommand.run?.({
|
|
1762
|
+
args,
|
|
1763
|
+
cmd: summaryCommand,
|
|
1764
|
+
rawArgs: process.argv.slice(2)
|
|
1765
|
+
});
|
|
1131
1766
|
},
|
|
1132
1767
|
subCommands: {
|
|
1133
1768
|
list: listCommand,
|
|
1134
1769
|
ls: listCommand,
|
|
1135
1770
|
remove: removeCommand,
|
|
1136
|
-
rm: removeCommand
|
|
1771
|
+
rm: removeCommand,
|
|
1772
|
+
cost: costCommand,
|
|
1773
|
+
co: costCommand,
|
|
1774
|
+
usage: usageCommand,
|
|
1775
|
+
us: usageCommand
|
|
1137
1776
|
}
|
|
1138
1777
|
});
|
|
1139
|
-
|
|
1778
|
+
(async () => {
|
|
1779
|
+
if (isRootHelp(process.argv)) {
|
|
1780
|
+
printRootHelp();
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (isRootVersion(process.argv)) {
|
|
1784
|
+
console.log(version);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const unknown = unknownCommand(process.argv);
|
|
1788
|
+
if (unknown) {
|
|
1789
|
+
console.error(`${unknown} - is unknowed, use skl -h for usage`);
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
setColorEnabled(detectColorSupport());
|
|
1793
|
+
await maybePrintUpdateNotice(version);
|
|
1794
|
+
runMain(main);
|
|
1795
|
+
})();
|