skill-analyzer 0.1.2 → 0.1.3
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/args.d.ts +10 -0
- package/dist/args.js +24 -0
- package/dist/budget.d.ts +15 -1
- package/dist/budget.js +21 -18
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +13 -23
- package/dist/dead.d.ts +9 -1
- package/dist/dead.js +20 -21
- package/dist/formatter.js +1 -1
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +54 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.js +6 -0
- package/dist/locale/ja.d.ts +2 -0
- package/dist/locale/ja.js +54 -0
- package/dist/locale/types.d.ts +35 -0
- package/dist/locale/types.js +1 -0
- package/dist/registry.d.ts +0 -1
- package/dist/registry.js +3 -11
- package/dist/report.d.ts +2 -1
- package/dist/report.js +20 -37
- package/dist/scanner.d.ts +0 -1
- package/dist/scanner.js +0 -8
- package/dist/usage.d.ts +6 -1
- package/dist/usage.js +13 -16
- package/package.json +1 -1
package/dist/args.d.ts
ADDED
package/dist/args.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { getLocale } from "./locale/index.js";
|
|
4
|
+
const SUPPORTED_LANGS = ["en", "ja"];
|
|
5
|
+
export function parseArgs(args) {
|
|
6
|
+
const rawLang = stringFlag(args, "--lang") ?? "en";
|
|
7
|
+
const lang = SUPPORTED_LANGS.includes(rawLang) ? rawLang : "en";
|
|
8
|
+
return {
|
|
9
|
+
skillsDir: stringFlag(args, "--skills-dir") ?? join(homedir(), ".claude", "skills"),
|
|
10
|
+
logsDir: stringFlag(args, "--logs-dir") ?? join(homedir(), ".claude", "projects"),
|
|
11
|
+
days: intFlag(args, "--days"),
|
|
12
|
+
json: args.includes("--json"),
|
|
13
|
+
locale: getLocale(lang),
|
|
14
|
+
help: args.includes("--help"),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function stringFlag(args, flag) {
|
|
18
|
+
const idx = args.indexOf(flag);
|
|
19
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
20
|
+
}
|
|
21
|
+
function intFlag(args, flag) {
|
|
22
|
+
const val = stringFlag(args, flag);
|
|
23
|
+
return val !== undefined ? parseInt(val, 10) : undefined;
|
|
24
|
+
}
|
package/dist/budget.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import type { ParsedArgs } from "./args.js";
|
|
2
|
+
import { type SkillEntry } from "./registry.js";
|
|
1
3
|
export declare const OVERHEAD_PER_SKILL = 109;
|
|
2
4
|
export declare const FALLBACK_BUDGET = 16000;
|
|
3
|
-
export
|
|
5
|
+
export interface BudgetEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
descriptionLength: number;
|
|
8
|
+
totalChars: number;
|
|
9
|
+
}
|
|
10
|
+
export interface BudgetResult {
|
|
11
|
+
budget: number;
|
|
12
|
+
isFallback: boolean;
|
|
13
|
+
totalUsed: number;
|
|
14
|
+
entries: BudgetEntry[];
|
|
15
|
+
}
|
|
16
|
+
export declare function computeBudget(registry: SkillEntry[]): BudgetResult;
|
|
17
|
+
export declare function budgetCommand(args: ParsedArgs, registry?: SkillEntry[]): void;
|
package/dist/budget.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
2
|
+
import { loadSkillRegistry } from "./registry.js";
|
|
3
3
|
import { sectionHeader, keyValue, budgetBar, makeTable } from "./formatter.js";
|
|
4
4
|
export const OVERHEAD_PER_SKILL = 109;
|
|
5
5
|
export const FALLBACK_BUDGET = 16_000;
|
|
6
|
-
export function
|
|
7
|
-
const skillsDir = getSkillsDir(args);
|
|
8
|
-
const skills = loadSkillRegistry(skillsDir);
|
|
6
|
+
export function computeBudget(registry) {
|
|
9
7
|
const envBudget = process.env["SLASH_COMMAND_TOOL_CHAR_BUDGET"];
|
|
10
8
|
const budget = parseInt(envBudget ?? String(FALLBACK_BUDGET), 10);
|
|
11
9
|
const isFallback = !envBudget;
|
|
12
|
-
const
|
|
13
|
-
const entries = skills
|
|
10
|
+
const entries = registry
|
|
14
11
|
.map((s) => ({
|
|
15
12
|
name: s.name,
|
|
16
13
|
descriptionLength: s.descriptionLength,
|
|
@@ -18,35 +15,41 @@ export function budgetCommand(args) {
|
|
|
18
15
|
}))
|
|
19
16
|
.sort((a, b) => b.totalChars - a.totalChars);
|
|
20
17
|
const totalUsed = entries.reduce((sum, e) => sum + e.totalChars, 0);
|
|
21
|
-
|
|
18
|
+
return { budget, isFallback, totalUsed, entries };
|
|
19
|
+
}
|
|
20
|
+
export function budgetCommand(args, registry) {
|
|
21
|
+
const t = args.locale;
|
|
22
|
+
const skills = registry ?? loadSkillRegistry(args.skillsDir);
|
|
23
|
+
const { budget, isFallback, totalUsed, entries } = computeBudget(skills);
|
|
24
|
+
if (args.json) {
|
|
22
25
|
console.log(JSON.stringify({ budget, isFallback, totalUsed, entries }, null, 2));
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
25
28
|
const overBudget = totalUsed > budget;
|
|
26
|
-
sectionHeader(
|
|
29
|
+
sectionHeader(t.budgetTitle, overBudget ? t.overBudget : undefined);
|
|
27
30
|
keyValue([
|
|
28
|
-
[
|
|
31
|
+
[t.budgetLabel, budgetBar(totalUsed, budget)],
|
|
29
32
|
[
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
t.usedLabel,
|
|
34
|
+
t.usedOf(totalUsed.toLocaleString(), budget.toLocaleString()),
|
|
32
35
|
],
|
|
33
36
|
[
|
|
34
|
-
|
|
37
|
+
t.remainingLabel,
|
|
35
38
|
overBudget
|
|
36
|
-
? chalk.red(`-${(totalUsed - budget).toLocaleString()}
|
|
37
|
-
: chalk.green(
|
|
39
|
+
? chalk.red(`-${t.remainingChars((totalUsed - budget).toLocaleString())}`)
|
|
40
|
+
: chalk.green(t.remainingChars((budget - totalUsed).toLocaleString())),
|
|
38
41
|
],
|
|
39
|
-
[
|
|
42
|
+
[t.skillsLabel, `${entries.length} (${t.charsOverheadEach(OVERHEAD_PER_SKILL)})`],
|
|
40
43
|
]);
|
|
41
44
|
makeTable({
|
|
42
|
-
head: [
|
|
45
|
+
head: [t.headerRank, t.headerSkill, t.headerDescChars, t.headerTotalChars],
|
|
43
46
|
colAligns: ["right", "left", "right", "right"],
|
|
44
47
|
rows: entries.map((e, i) => [i + 1, e.name, e.descriptionLength, e.totalChars]),
|
|
45
48
|
});
|
|
46
49
|
if (isFallback) {
|
|
47
50
|
console.log();
|
|
48
|
-
console.log(chalk.dim(` ${budget.toLocaleString()}
|
|
49
|
-
console.log(chalk.dim(
|
|
51
|
+
console.log(chalk.dim(` ${t.fallbackNote(budget.toLocaleString())}`));
|
|
52
|
+
console.log(chalk.dim(` ${t.fallbackLink}`));
|
|
50
53
|
}
|
|
51
54
|
console.log();
|
|
52
55
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function run(
|
|
1
|
+
export declare function run(rawArgs: string[]): void;
|
package/dist/cli.js
CHANGED
|
@@ -1,39 +1,29 @@
|
|
|
1
|
+
import { parseArgs } from "./args.js";
|
|
1
2
|
import { budgetCommand } from "./budget.js";
|
|
2
3
|
import { usageCommand } from "./usage.js";
|
|
3
4
|
import { deadCommand } from "./dead.js";
|
|
4
5
|
import { reportCommand } from "./report.js";
|
|
5
|
-
export function run(
|
|
6
|
-
const command =
|
|
6
|
+
export function run(rawArgs) {
|
|
7
|
+
const command = rawArgs[0] ?? "report";
|
|
8
|
+
const args = parseArgs(rawArgs.slice(1));
|
|
9
|
+
if (args.help || command === "--help") {
|
|
10
|
+
console.log(args.locale.help);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
7
13
|
switch (command) {
|
|
8
14
|
case "budget":
|
|
9
|
-
budgetCommand(args
|
|
15
|
+
budgetCommand(args);
|
|
10
16
|
break;
|
|
11
17
|
case "usage":
|
|
12
|
-
usageCommand(args
|
|
18
|
+
usageCommand(args);
|
|
13
19
|
break;
|
|
14
20
|
case "dead":
|
|
15
|
-
deadCommand(args
|
|
21
|
+
deadCommand(args);
|
|
16
22
|
break;
|
|
17
23
|
case "report":
|
|
18
|
-
reportCommand(args
|
|
24
|
+
reportCommand(args);
|
|
19
25
|
break;
|
|
20
26
|
default:
|
|
21
|
-
|
|
27
|
+
console.log(args.locale.help);
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
|
-
function printHelp() {
|
|
25
|
-
console.log(`skill-analyzer - Analyze your Claude Code skill library
|
|
26
|
-
|
|
27
|
-
Usage: skill-analyzer <command> [options]
|
|
28
|
-
|
|
29
|
-
Commands:
|
|
30
|
-
report Full analysis report (default)
|
|
31
|
-
usage Skill usage frequency
|
|
32
|
-
dead Unused skills
|
|
33
|
-
budget Description budget consumption
|
|
34
|
-
|
|
35
|
-
Options:
|
|
36
|
-
--skills-dir <path> Skills directory (default: ~/.claude/skills)
|
|
37
|
-
--json Output as JSON
|
|
38
|
-
--help Show this help`);
|
|
39
|
-
}
|
package/dist/dead.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ParsedArgs } from "./args.js";
|
|
2
|
+
import { type SkillEntry } from "./registry.js";
|
|
3
|
+
import { type SkillInvocation, type UsageResult } from "./scanner.js";
|
|
4
|
+
export declare function computeDeadSkills(registry: SkillEntry[], usage: UsageResult[]): SkillEntry[];
|
|
5
|
+
export declare function deadCommand(args: ParsedArgs, preloaded?: {
|
|
6
|
+
registry: SkillEntry[];
|
|
7
|
+
invocations: SkillInvocation[];
|
|
8
|
+
usage: UsageResult[];
|
|
9
|
+
}): void;
|
package/dist/dead.js
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { loadSkillRegistry } from "./registry.js";
|
|
3
|
+
import { scanSessions, aggregateUsage } from "./scanner.js";
|
|
4
4
|
import { sectionHeader, keyValue, makeTable } from "./formatter.js";
|
|
5
5
|
import { OVERHEAD_PER_SKILL } from "./budget.js";
|
|
6
|
-
export function
|
|
7
|
-
const skillsDir = getSkillsDir(args);
|
|
8
|
-
const logsDir = getLogsDir(args);
|
|
9
|
-
const daysIdx = args.indexOf("--days");
|
|
10
|
-
const days = daysIdx !== -1 ? parseInt(args[daysIdx + 1], 10) : undefined;
|
|
11
|
-
const isJson = args.includes("--json");
|
|
12
|
-
const registry = loadSkillRegistry(skillsDir);
|
|
13
|
-
const invocations = scanSessions(logsDir, days);
|
|
14
|
-
const usage = aggregateUsage(invocations);
|
|
6
|
+
export function computeDeadSkills(registry, usage) {
|
|
15
7
|
const usedSkills = new Set(usage.map((u) => u.skill));
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
return registry.filter((s) => !usedSkills.has(s.name));
|
|
9
|
+
}
|
|
10
|
+
export function deadCommand(args, preloaded) {
|
|
11
|
+
const t = args.locale;
|
|
12
|
+
const registry = preloaded?.registry ?? loadSkillRegistry(args.skillsDir);
|
|
13
|
+
const invocations = preloaded?.invocations ?? scanSessions(args.logsDir, args.days);
|
|
14
|
+
const usage = preloaded?.usage ?? aggregateUsage(invocations);
|
|
15
|
+
const deadSkills = computeDeadSkills(registry, usage);
|
|
16
|
+
if (args.json) {
|
|
18
17
|
console.log(JSON.stringify({ deadSkills: deadSkills.map((s) => s.name), count: deadSkills.length }, null, 2));
|
|
19
18
|
return;
|
|
20
19
|
}
|
|
21
|
-
const period = days ?
|
|
22
|
-
sectionHeader(
|
|
20
|
+
const period = args.days ? t.periodDays(args.days) : t.periodAll;
|
|
21
|
+
sectionHeader(t.deadTitle(period));
|
|
23
22
|
const totalWaste = deadSkills.reduce((sum, s) => sum + s.descriptionLength + OVERHEAD_PER_SKILL, 0);
|
|
24
23
|
keyValue([
|
|
25
|
-
[
|
|
24
|
+
[t.deadLabel, t.deadOf(deadSkills.length, registry.length)],
|
|
26
25
|
[
|
|
27
|
-
|
|
26
|
+
t.savingsLabel,
|
|
28
27
|
deadSkills.length > 0
|
|
29
|
-
? chalk.yellow(
|
|
30
|
-
: chalk.green(
|
|
28
|
+
? chalk.yellow(t.charsRecoverable(totalWaste.toLocaleString()))
|
|
29
|
+
: chalk.green(t.noneNeeded),
|
|
31
30
|
],
|
|
32
31
|
]);
|
|
33
32
|
if (deadSkills.length === 0) {
|
|
34
|
-
console.log(chalk.green(
|
|
33
|
+
console.log(chalk.green(` ${t.allUsed}\n`));
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
36
|
makeTable({
|
|
38
|
-
head: [
|
|
37
|
+
head: [t.headerRank, t.headerSkill, t.headerDescChars, t.headerTotalChars],
|
|
39
38
|
colAligns: ["right", "left", "right", "right"],
|
|
40
39
|
rows: deadSkills.map((s, i) => [
|
|
41
40
|
i + 1,
|
package/dist/formatter.js
CHANGED
|
@@ -15,7 +15,7 @@ export function keyValue(pairs) {
|
|
|
15
15
|
export function budgetBar(used, total) {
|
|
16
16
|
const pct = used / total;
|
|
17
17
|
const width = 30;
|
|
18
|
-
const filled = Math.round(pct * width);
|
|
18
|
+
const filled = Math.min(Math.round(pct * width), width);
|
|
19
19
|
const empty = width - filled;
|
|
20
20
|
const color = pct > 1 ? chalk.red : pct > 0.7 ? chalk.yellow : chalk.green;
|
|
21
21
|
const bar = color("█".repeat(filled)) + chalk.dim("░".repeat(empty));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const en = {
|
|
2
|
+
// budget
|
|
3
|
+
budgetTitle: "Description Budget",
|
|
4
|
+
overBudget: "OVER BUDGET",
|
|
5
|
+
budgetLabel: "Budget:",
|
|
6
|
+
usedLabel: "Used:",
|
|
7
|
+
remainingLabel: "Remaining:",
|
|
8
|
+
skillsLabel: "Skills:",
|
|
9
|
+
charsOverheadEach: (n) => `${n} chars overhead each`,
|
|
10
|
+
usedOf: (used, total) => `${used} / ${total} chars`,
|
|
11
|
+
remainingChars: (n) => `${n} chars`,
|
|
12
|
+
fallbackNote: (budget) => `${budget} is a static fallback. The actual budget is determined dynamically by Claude Code.`,
|
|
13
|
+
fallbackLink: "See: https://code.claude.com/docs/en/skills#claude-doesnt-see-all-my-skills",
|
|
14
|
+
// dead
|
|
15
|
+
deadTitle: (period) => `Dead Skills (${period})`,
|
|
16
|
+
deadLabel: "Dead:",
|
|
17
|
+
deadOf: (dead, total) => `${dead} of ${total} local skills`,
|
|
18
|
+
savingsLabel: "Savings:",
|
|
19
|
+
charsRecoverable: (n) => `${n} chars recoverable`,
|
|
20
|
+
noneNeeded: "None needed",
|
|
21
|
+
allUsed: "All skills have been used at least once.",
|
|
22
|
+
// usage
|
|
23
|
+
usageTitle: (period) => `Skill Usage (${period})`,
|
|
24
|
+
invocationsLabel: "Invocations:",
|
|
25
|
+
uniqueSkillsLabel: "Unique skills:",
|
|
26
|
+
noInvocations: "No skill invocations found.",
|
|
27
|
+
// period
|
|
28
|
+
periodAll: "all time",
|
|
29
|
+
periodDays: (days) => `last ${days} days`,
|
|
30
|
+
// table headers
|
|
31
|
+
headerRank: "#",
|
|
32
|
+
headerSkill: "Skill",
|
|
33
|
+
headerDescChars: "Description Chars",
|
|
34
|
+
headerTotalChars: "Total Chars",
|
|
35
|
+
headerCount: "Count",
|
|
36
|
+
headerLastUsed: "Last Used",
|
|
37
|
+
unknown: "unknown",
|
|
38
|
+
// help
|
|
39
|
+
help: `skill-analyzer - Analyze your Claude Code skill library
|
|
40
|
+
|
|
41
|
+
Usage: skill-analyzer <command> [options]
|
|
42
|
+
|
|
43
|
+
Commands:
|
|
44
|
+
report Full analysis report (default)
|
|
45
|
+
usage Skill usage frequency
|
|
46
|
+
dead Unused skills
|
|
47
|
+
budget Description budget consumption
|
|
48
|
+
|
|
49
|
+
Options:
|
|
50
|
+
--skills-dir <path> Skills directory (default: ~/.claude/skills)
|
|
51
|
+
--lang <en|ja> Output language (default: en)
|
|
52
|
+
--json Output as JSON
|
|
53
|
+
--help Show this help`,
|
|
54
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const ja = {
|
|
2
|
+
// budget
|
|
3
|
+
budgetTitle: "説明文バジェット",
|
|
4
|
+
overBudget: "超過",
|
|
5
|
+
budgetLabel: "バジェット:",
|
|
6
|
+
usedLabel: "使用量:",
|
|
7
|
+
remainingLabel: "残り:",
|
|
8
|
+
skillsLabel: "スキル数:",
|
|
9
|
+
charsOverheadEach: (n) => `各スキル ${n} 文字のオーバーヘッド`,
|
|
10
|
+
usedOf: (used, total) => `${used} / ${total} 文字`,
|
|
11
|
+
remainingChars: (n) => `${n} 文字`,
|
|
12
|
+
fallbackNote: (budget) => `${budget} は静的なフォールバック値です。実際のバジェットは Claude Code が動的に決定します。`,
|
|
13
|
+
fallbackLink: "参照: https://code.claude.com/docs/en/skills#claude-doesnt-see-all-my-skills",
|
|
14
|
+
// dead
|
|
15
|
+
deadTitle: (period) => `未使用スキル (${period})`,
|
|
16
|
+
deadLabel: "未使用:",
|
|
17
|
+
deadOf: (dead, total) => `${total} 件中 ${dead} 件`,
|
|
18
|
+
savingsLabel: "削減可能:",
|
|
19
|
+
charsRecoverable: (n) => `${n} 文字を回収可能`,
|
|
20
|
+
noneNeeded: "不要",
|
|
21
|
+
allUsed: "すべてのスキルが1回以上使用されています。",
|
|
22
|
+
// usage
|
|
23
|
+
usageTitle: (period) => `スキル使用状況 (${period})`,
|
|
24
|
+
invocationsLabel: "呼び出し回数:",
|
|
25
|
+
uniqueSkillsLabel: "ユニークスキル数:",
|
|
26
|
+
noInvocations: "スキルの呼び出し履歴が見つかりません。",
|
|
27
|
+
// period
|
|
28
|
+
periodAll: "全期間",
|
|
29
|
+
periodDays: (days) => `直近 ${days} 日`,
|
|
30
|
+
// table headers
|
|
31
|
+
headerRank: "#",
|
|
32
|
+
headerSkill: "スキル",
|
|
33
|
+
headerDescChars: "説明文字数",
|
|
34
|
+
headerTotalChars: "合計文字数",
|
|
35
|
+
headerCount: "回数",
|
|
36
|
+
headerLastUsed: "最終使用日",
|
|
37
|
+
unknown: "不明",
|
|
38
|
+
// help
|
|
39
|
+
help: `skill-analyzer - Claude Code スキルライブラリの分析ツール
|
|
40
|
+
|
|
41
|
+
使い方: skill-analyzer <コマンド> [オプション]
|
|
42
|
+
|
|
43
|
+
コマンド:
|
|
44
|
+
report 全体レポート(デフォルト)
|
|
45
|
+
usage スキル使用頻度
|
|
46
|
+
dead 未使用スキル
|
|
47
|
+
budget 説明文バジェット消費量
|
|
48
|
+
|
|
49
|
+
オプション:
|
|
50
|
+
--skills-dir <パス> スキルディレクトリ(デフォルト: ~/.claude/skills)
|
|
51
|
+
--lang <en|ja> 出力言語(デフォルト: en)
|
|
52
|
+
--json JSON形式で出力
|
|
53
|
+
--help このヘルプを表示`,
|
|
54
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type Lang = "en" | "ja";
|
|
2
|
+
export interface Locale {
|
|
3
|
+
budgetTitle: string;
|
|
4
|
+
overBudget: string;
|
|
5
|
+
budgetLabel: string;
|
|
6
|
+
usedLabel: string;
|
|
7
|
+
remainingLabel: string;
|
|
8
|
+
skillsLabel: string;
|
|
9
|
+
charsOverheadEach: (n: number) => string;
|
|
10
|
+
usedOf: (used: string, total: string) => string;
|
|
11
|
+
remainingChars: (n: string) => string;
|
|
12
|
+
fallbackNote: (budget: string) => string;
|
|
13
|
+
fallbackLink: string;
|
|
14
|
+
deadTitle: (period: string) => string;
|
|
15
|
+
deadLabel: string;
|
|
16
|
+
deadOf: (dead: number, total: number) => string;
|
|
17
|
+
savingsLabel: string;
|
|
18
|
+
charsRecoverable: (n: string) => string;
|
|
19
|
+
noneNeeded: string;
|
|
20
|
+
allUsed: string;
|
|
21
|
+
usageTitle: (period: string) => string;
|
|
22
|
+
invocationsLabel: string;
|
|
23
|
+
uniqueSkillsLabel: string;
|
|
24
|
+
noInvocations: string;
|
|
25
|
+
periodAll: string;
|
|
26
|
+
periodDays: (days: number) => string;
|
|
27
|
+
headerRank: string;
|
|
28
|
+
headerSkill: string;
|
|
29
|
+
headerDescChars: string;
|
|
30
|
+
headerTotalChars: string;
|
|
31
|
+
headerCount: string;
|
|
32
|
+
headerLastUsed: string;
|
|
33
|
+
unknown: string;
|
|
34
|
+
help: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/registry.d.ts
CHANGED
package/dist/registry.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import { readFileSync, readdirSync
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
export function getSkillsDir(args) {
|
|
5
|
-
const idx = args.indexOf("--skills-dir");
|
|
6
|
-
if (idx !== -1 && args[idx + 1]) {
|
|
7
|
-
return args[idx + 1];
|
|
8
|
-
}
|
|
9
|
-
return join(homedir(), ".claude", "skills");
|
|
10
|
-
}
|
|
11
3
|
export function loadSkillRegistry(skillsDir) {
|
|
12
4
|
const entries = [];
|
|
13
5
|
let dirs;
|
|
@@ -20,13 +12,13 @@ export function loadSkillRegistry(skillsDir) {
|
|
|
20
12
|
}
|
|
21
13
|
for (const dir of dirs) {
|
|
22
14
|
const skillMdPath = join(skillsDir, dir, "SKILL.md");
|
|
15
|
+
let content;
|
|
23
16
|
try {
|
|
24
|
-
|
|
17
|
+
content = readFileSync(skillMdPath, "utf-8");
|
|
25
18
|
}
|
|
26
19
|
catch {
|
|
27
20
|
continue;
|
|
28
21
|
}
|
|
29
|
-
const content = readFileSync(skillMdPath, "utf-8");
|
|
30
22
|
const { name, description } = parseFrontmatter(content, dir);
|
|
31
23
|
entries.push({
|
|
32
24
|
name,
|
package/dist/report.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ParsedArgs } from "./args.js";
|
|
2
|
+
export declare function reportCommand(args: ParsedArgs): void;
|
package/dist/report.js
CHANGED
|
@@ -1,43 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { loadSkillRegistry } from "./registry.js";
|
|
2
|
+
import { scanSessions, aggregateUsage } from "./scanner.js";
|
|
3
|
+
import { computeBudget } from "./budget.js";
|
|
4
|
+
import { computeDeadSkills } from "./dead.js";
|
|
5
|
+
import { budgetCommand } from "./budget.js";
|
|
4
6
|
import { usageCommand } from "./usage.js";
|
|
5
7
|
import { deadCommand } from "./dead.js";
|
|
6
8
|
export function reportCommand(args) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const registry = loadSkillRegistry(args.skillsDir);
|
|
10
|
+
const invocations = scanSessions(args.logsDir, args.days);
|
|
11
|
+
const usage = aggregateUsage(invocations);
|
|
12
|
+
if (args.json) {
|
|
13
|
+
const deadSkills = computeDeadSkills(registry, usage).map((s) => s.name);
|
|
14
|
+
const budgetData = computeBudget(registry);
|
|
15
|
+
console.log(JSON.stringify({
|
|
16
|
+
usage: { totalInvocations: invocations.length, skills: usage },
|
|
17
|
+
dead: { count: deadSkills.length, skills: deadSkills },
|
|
18
|
+
budget: { limit: budgetData.budget, isFallback: budgetData.isFallback, totalUsed: budgetData.totalUsed, entries: budgetData.entries },
|
|
19
|
+
}, null, 2));
|
|
10
20
|
return;
|
|
11
21
|
}
|
|
12
|
-
|
|
13
|
-
usageCommand(args);
|
|
14
|
-
deadCommand(args);
|
|
15
|
-
budgetCommand(args);
|
|
16
|
-
}
|
|
17
|
-
function reportJson(args) {
|
|
18
|
-
const skillsDir = getSkillsDir(args);
|
|
19
|
-
const logsDir = getLogsDir(args);
|
|
20
|
-
const daysIdx = args.indexOf("--days");
|
|
21
|
-
const days = daysIdx !== -1 ? parseInt(args[daysIdx + 1], 10) : undefined;
|
|
22
|
-
const registry = loadSkillRegistry(skillsDir);
|
|
23
|
-
const invocations = scanSessions(logsDir, days);
|
|
24
|
-
const usage = aggregateUsage(invocations);
|
|
25
|
-
const usedSkills = new Set(usage.map((u) => u.skill));
|
|
26
|
-
const deadSkills = registry.filter((s) => !usedSkills.has(s.name)).map((s) => s.name);
|
|
27
|
-
const envBudget = process.env["SLASH_COMMAND_TOOL_CHAR_BUDGET"];
|
|
28
|
-
const budget = parseInt(envBudget ?? String(FALLBACK_BUDGET), 10);
|
|
29
|
-
const isFallback = !envBudget;
|
|
30
|
-
const budgetEntries = registry
|
|
31
|
-
.map((s) => ({
|
|
32
|
-
name: s.name,
|
|
33
|
-
descriptionLength: s.descriptionLength,
|
|
34
|
-
totalChars: s.descriptionLength + OVERHEAD_PER_SKILL,
|
|
35
|
-
}))
|
|
36
|
-
.sort((a, b) => b.totalChars - a.totalChars);
|
|
37
|
-
const totalUsed = budgetEntries.reduce((sum, e) => sum + e.totalChars, 0);
|
|
38
|
-
console.log(JSON.stringify({
|
|
39
|
-
usage: { totalInvocations: invocations.length, skills: usage },
|
|
40
|
-
dead: { count: deadSkills.length, skills: deadSkills },
|
|
41
|
-
budget: { limit: budget, isFallback, totalUsed, entries: budgetEntries },
|
|
42
|
-
}, null, 2));
|
|
22
|
+
const preloaded = { registry, invocations, usage };
|
|
23
|
+
usageCommand(args, preloaded);
|
|
24
|
+
deadCommand(args, preloaded);
|
|
25
|
+
budgetCommand(args, registry);
|
|
43
26
|
}
|
package/dist/scanner.d.ts
CHANGED
|
@@ -8,6 +8,5 @@ export interface UsageResult {
|
|
|
8
8
|
count: number;
|
|
9
9
|
lastUsed: string;
|
|
10
10
|
}
|
|
11
|
-
export declare function getLogsDir(args: string[]): string;
|
|
12
11
|
export declare function scanSessions(logsDir: string, daysLimit?: number): SkillInvocation[];
|
|
13
12
|
export declare function aggregateUsage(invocations: SkillInvocation[]): UsageResult[];
|
package/dist/scanner.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { normalizeSkillName } from "./normalizer.js";
|
|
5
|
-
export function getLogsDir(args) {
|
|
6
|
-
const idx = args.indexOf("--logs-dir");
|
|
7
|
-
if (idx !== -1 && args[idx + 1]) {
|
|
8
|
-
return args[idx + 1];
|
|
9
|
-
}
|
|
10
|
-
return join(homedir(), ".claude", "projects");
|
|
11
|
-
}
|
|
12
4
|
export function scanSessions(logsDir, daysLimit) {
|
|
13
5
|
const invocations = [];
|
|
14
6
|
const cutoff = daysLimit
|
package/dist/usage.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ParsedArgs } from "./args.js";
|
|
2
|
+
import { type SkillInvocation, type UsageResult } from "./scanner.js";
|
|
3
|
+
export declare function usageCommand(args: ParsedArgs, preloaded?: {
|
|
4
|
+
invocations: SkillInvocation[];
|
|
5
|
+
usage: UsageResult[];
|
|
6
|
+
}): void;
|
package/dist/usage.js
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
2
|
+
import { scanSessions, aggregateUsage } from "./scanner.js";
|
|
3
3
|
import { sectionHeader, keyValue, makeTable } from "./formatter.js";
|
|
4
|
-
export function usageCommand(args) {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const invocations = scanSessions(logsDir, days);
|
|
10
|
-
const usage = aggregateUsage(invocations);
|
|
11
|
-
if (isJson) {
|
|
4
|
+
export function usageCommand(args, preloaded) {
|
|
5
|
+
const t = args.locale;
|
|
6
|
+
const invocations = preloaded?.invocations ?? scanSessions(args.logsDir, args.days);
|
|
7
|
+
const usage = preloaded?.usage ?? aggregateUsage(invocations);
|
|
8
|
+
if (args.json) {
|
|
12
9
|
console.log(JSON.stringify({ totalInvocations: invocations.length, skills: usage }, null, 2));
|
|
13
10
|
return;
|
|
14
11
|
}
|
|
15
|
-
const period = days ?
|
|
16
|
-
sectionHeader(
|
|
12
|
+
const period = args.days ? t.periodDays(args.days) : t.periodAll;
|
|
13
|
+
sectionHeader(t.usageTitle(period));
|
|
17
14
|
keyValue([
|
|
18
|
-
[
|
|
19
|
-
[
|
|
15
|
+
[t.invocationsLabel, String(invocations.length)],
|
|
16
|
+
[t.uniqueSkillsLabel, String(usage.length)],
|
|
20
17
|
]);
|
|
21
18
|
if (usage.length === 0) {
|
|
22
|
-
console.log(chalk.dim(
|
|
19
|
+
console.log(chalk.dim(` ${t.noInvocations}\n`));
|
|
23
20
|
return;
|
|
24
21
|
}
|
|
25
22
|
makeTable({
|
|
26
|
-
head: [
|
|
23
|
+
head: [t.headerRank, t.headerSkill, t.headerCount, t.headerLastUsed],
|
|
27
24
|
colAligns: ["right", "left", "right", "left"],
|
|
28
25
|
rows: usage.map((u, i) => [
|
|
29
26
|
i + 1,
|
|
30
27
|
u.skill,
|
|
31
28
|
u.count,
|
|
32
|
-
u.lastUsed ? u.lastUsed.slice(0, 10) :
|
|
29
|
+
u.lastUsed ? u.lastUsed.slice(0, 10) : t.unknown,
|
|
33
30
|
]),
|
|
34
31
|
});
|
|
35
32
|
console.log();
|