skill-analyzer 0.1.1 → 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 ADDED
@@ -0,0 +1,10 @@
1
+ import { type Locale } from "./locale/index.js";
2
+ export interface ParsedArgs {
3
+ skillsDir: string;
4
+ logsDir: string;
5
+ days?: number;
6
+ json: boolean;
7
+ locale: Locale;
8
+ help: boolean;
9
+ }
10
+ export declare function parseArgs(args: string[]): ParsedArgs;
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,2 +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
- export declare function budgetCommand(args: string[]): void;
4
+ export declare const FALLBACK_BUDGET = 16000;
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,14 +1,13 @@
1
1
  import chalk from "chalk";
2
- import { getSkillsDir, loadSkillRegistry } from "./registry.js";
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
- const DEFAULT_BUDGET = 16_000;
6
- export function budgetCommand(args) {
7
- const skillsDir = getSkillsDir(args);
8
- const skills = loadSkillRegistry(skillsDir);
9
- const budget = parseInt(process.env["SLASH_COMMAND_TOOL_CHAR_BUDGET"] ?? String(DEFAULT_BUDGET), 10);
10
- const isJson = args.includes("--json");
11
- const entries = skills
5
+ export const FALLBACK_BUDGET = 16_000;
6
+ export function computeBudget(registry) {
7
+ const envBudget = process.env["SLASH_COMMAND_TOOL_CHAR_BUDGET"];
8
+ const budget = parseInt(envBudget ?? String(FALLBACK_BUDGET), 10);
9
+ const isFallback = !envBudget;
10
+ const entries = registry
12
11
  .map((s) => ({
13
12
  name: s.name,
14
13
  descriptionLength: s.descriptionLength,
@@ -16,30 +15,41 @@ export function budgetCommand(args) {
16
15
  }))
17
16
  .sort((a, b) => b.totalChars - a.totalChars);
18
17
  const totalUsed = entries.reduce((sum, e) => sum + e.totalChars, 0);
19
- if (isJson) {
20
- console.log(JSON.stringify({ budget, totalUsed, entries }, null, 2));
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) {
25
+ console.log(JSON.stringify({ budget, isFallback, totalUsed, entries }, null, 2));
21
26
  return;
22
27
  }
23
28
  const overBudget = totalUsed > budget;
24
- sectionHeader("Description Budget", overBudget ? "OVER BUDGET" : undefined);
29
+ sectionHeader(t.budgetTitle, overBudget ? t.overBudget : undefined);
25
30
  keyValue([
26
- ["Budget:", budgetBar(totalUsed, budget)],
31
+ [t.budgetLabel, budgetBar(totalUsed, budget)],
27
32
  [
28
- "Used:",
29
- `${totalUsed.toLocaleString()} / ${budget.toLocaleString()} chars`,
33
+ t.usedLabel,
34
+ t.usedOf(totalUsed.toLocaleString(), budget.toLocaleString()),
30
35
  ],
31
36
  [
32
- "Remaining:",
37
+ t.remainingLabel,
33
38
  overBudget
34
- ? chalk.red(`-${(totalUsed - budget).toLocaleString()} chars`)
35
- : chalk.green(`${(budget - totalUsed).toLocaleString()} chars`),
39
+ ? chalk.red(`-${t.remainingChars((totalUsed - budget).toLocaleString())}`)
40
+ : chalk.green(t.remainingChars((budget - totalUsed).toLocaleString())),
36
41
  ],
37
- ["Skills:", `${entries.length} (${OVERHEAD_PER_SKILL} chars overhead each)`],
42
+ [t.skillsLabel, `${entries.length} (${t.charsOverheadEach(OVERHEAD_PER_SKILL)})`],
38
43
  ]);
39
44
  makeTable({
40
- head: ["#", "Skill", "Description Chars", "Total Chars"],
45
+ head: [t.headerRank, t.headerSkill, t.headerDescChars, t.headerTotalChars],
41
46
  colAligns: ["right", "left", "right", "right"],
42
47
  rows: entries.map((e, i) => [i + 1, e.name, e.descriptionLength, e.totalChars]),
43
48
  });
49
+ if (isFallback) {
50
+ console.log();
51
+ console.log(chalk.dim(` ${t.fallbackNote(budget.toLocaleString())}`));
52
+ console.log(chalk.dim(` ${t.fallbackLink}`));
53
+ }
44
54
  console.log();
45
55
  }
package/dist/cli.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function run(args: string[]): void;
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(args) {
6
- const command = args[0] ?? "report";
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.slice(1));
15
+ budgetCommand(args);
10
16
  break;
11
17
  case "usage":
12
- usageCommand(args.slice(1));
18
+ usageCommand(args);
13
19
  break;
14
20
  case "dead":
15
- deadCommand(args.slice(1));
21
+ deadCommand(args);
16
22
  break;
17
23
  case "report":
18
- reportCommand(args.slice(1));
24
+ reportCommand(args);
19
25
  break;
20
26
  default:
21
- printHelp();
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
- export declare function deadCommand(args: string[]): void;
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 { getSkillsDir, loadSkillRegistry } from "./registry.js";
3
- import { getLogsDir, scanSessions, aggregateUsage } from "./scanner.js";
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 deadCommand(args) {
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
- const deadSkills = registry.filter((s) => !usedSkills.has(s.name));
17
- if (isJson) {
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 ? `last ${days} days` : "all time";
22
- sectionHeader(`Dead Skills (${period})`);
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
- ["Dead:", `${deadSkills.length} of ${registry.length} local skills`],
24
+ [t.deadLabel, t.deadOf(deadSkills.length, registry.length)],
26
25
  [
27
- "Savings:",
26
+ t.savingsLabel,
28
27
  deadSkills.length > 0
29
- ? chalk.yellow(`${totalWaste.toLocaleString()} chars recoverable`)
30
- : chalk.green("None needed"),
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(" All skills have been used at least once.\n"));
33
+ console.log(chalk.green(` ${t.allUsed}\n`));
35
34
  return;
36
35
  }
37
36
  makeTable({
38
- head: ["#", "Skill", "Description Chars", "Total Chars"],
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,2 @@
1
+ import type { Locale } from "./types.js";
2
+ export declare const en: Locale;
@@ -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,3 @@
1
+ import type { Locale, Lang } from "./types.js";
2
+ export declare function getLocale(lang: Lang): Locale;
3
+ export type { Locale, Lang };
@@ -0,0 +1,6 @@
1
+ import { en } from "./en.js";
2
+ import { ja } from "./ja.js";
3
+ const locales = { en, ja };
4
+ export function getLocale(lang) {
5
+ return locales[lang];
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { Locale } from "./types.js";
2
+ export declare const ja: Locale;
@@ -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 {};
@@ -4,5 +4,4 @@ export interface SkillEntry {
4
4
  descriptionLength: number;
5
5
  path: string;
6
6
  }
7
- export declare function getSkillsDir(args: string[]): string;
8
7
  export declare function loadSkillRegistry(skillsDir: string): SkillEntry[];
package/dist/registry.js CHANGED
@@ -1,13 +1,5 @@
1
- import { readFileSync, readdirSync, statSync } from "node:fs";
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
- statSync(skillMdPath);
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
- export declare function reportCommand(args: string[]): void;
1
+ import type { ParsedArgs } from "./args.js";
2
+ export declare function reportCommand(args: ParsedArgs): void;
package/dist/report.js CHANGED
@@ -1,41 +1,26 @@
1
- import { getSkillsDir, loadSkillRegistry } from "./registry.js";
2
- import { getLogsDir, scanSessions, aggregateUsage } from "./scanner.js";
3
- import { budgetCommand, OVERHEAD_PER_SKILL } from "./budget.js";
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 isJson = args.includes("--json");
8
- if (isJson) {
9
- reportJson(args);
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
- // Just run all three in sequence
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 budget = parseInt(process.env["SLASH_COMMAND_TOOL_CHAR_BUDGET"] ?? "16000", 10);
28
- const budgetEntries = registry
29
- .map((s) => ({
30
- name: s.name,
31
- descriptionLength: s.descriptionLength,
32
- totalChars: s.descriptionLength + OVERHEAD_PER_SKILL,
33
- }))
34
- .sort((a, b) => b.totalChars - a.totalChars);
35
- const totalUsed = budgetEntries.reduce((sum, e) => sum + e.totalChars, 0);
36
- console.log(JSON.stringify({
37
- usage: { totalInvocations: invocations.length, skills: usage },
38
- dead: { count: deadSkills.length, skills: deadSkills },
39
- budget: { limit: budget, totalUsed, entries: budgetEntries },
40
- }, null, 2));
22
+ const preloaded = { registry, invocations, usage };
23
+ usageCommand(args, preloaded);
24
+ deadCommand(args, preloaded);
25
+ budgetCommand(args, registry);
41
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
- export declare function usageCommand(args: string[]): void;
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 { getLogsDir, scanSessions, aggregateUsage } from "./scanner.js";
2
+ import { scanSessions, aggregateUsage } from "./scanner.js";
3
3
  import { sectionHeader, keyValue, makeTable } from "./formatter.js";
4
- export function usageCommand(args) {
5
- const logsDir = getLogsDir(args);
6
- const daysIdx = args.indexOf("--days");
7
- const days = daysIdx !== -1 ? parseInt(args[daysIdx + 1], 10) : undefined;
8
- const isJson = args.includes("--json");
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 ? `last ${days} days` : "all time";
16
- sectionHeader(`Skill Usage (${period})`);
12
+ const period = args.days ? t.periodDays(args.days) : t.periodAll;
13
+ sectionHeader(t.usageTitle(period));
17
14
  keyValue([
18
- ["Invocations:", String(invocations.length)],
19
- ["Unique skills:", String(usage.length)],
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(" No skill invocations found.\n"));
19
+ console.log(chalk.dim(` ${t.noInvocations}\n`));
23
20
  return;
24
21
  }
25
22
  makeTable({
26
- head: ["#", "Skill", "Count", "Last Used"],
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) : "unknown",
29
+ u.lastUsed ? u.lastUsed.slice(0, 10) : t.unknown,
33
30
  ]),
34
31
  });
35
32
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-analyzer",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Analyze Claude Code skill library: usage frequency, dead skills, description budget consumption",
5
5
  "type": "module",
6
6
  "bin": {