@crafter/skillkit 0.1.7 → 0.2.0
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/package.json +1 -1
- package/src/bin.ts +6 -1
- package/src/commands/health.ts +4 -2
- package/src/commands/list.ts +1 -9
- package/src/commands/scan.ts +11 -4
- package/src/db/queries.ts +37 -2
- package/src/index.ts +1 -1
- package/src/scanner/connectors/claude.ts +2 -5
- package/src/scanner/connectors/opencode.ts +4 -6
- package/src/scanner/index.ts +3 -3
- package/src/scanner/skills.ts +18 -20
package/package.json
CHANGED
package/src/bin.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
2
4
|
import { bold, cyan, dim, yellow } from "./tui/colors";
|
|
3
5
|
|
|
4
|
-
const
|
|
6
|
+
const pkg = JSON.parse(
|
|
7
|
+
readFileSync(join(dirname(import.meta.dir), "package.json"), "utf-8"),
|
|
8
|
+
);
|
|
9
|
+
const VERSION: string = pkg.version;
|
|
5
10
|
|
|
6
11
|
function printHelp(): void {
|
|
7
12
|
console.log(`
|
package/src/commands/health.ts
CHANGED
|
@@ -115,8 +115,10 @@ export async function runHealth(): Promise<void> {
|
|
|
115
115
|
console.log(`\n ${bold("SKILLKIT HEALTH REPORT")}\n`);
|
|
116
116
|
|
|
117
117
|
const agents = getDetectedAgents();
|
|
118
|
-
console.log(check(`${skills.length} skills
|
|
119
|
-
|
|
118
|
+
console.log(check(`${skills.length} skills installed`));
|
|
119
|
+
if (agents.length > 0) {
|
|
120
|
+
console.log(dim(` ${agents.join(" + ")}`));
|
|
121
|
+
}
|
|
120
122
|
|
|
121
123
|
if (dbExists && hasDbData) {
|
|
122
124
|
console.log(
|
package/src/commands/list.ts
CHANGED
|
@@ -20,11 +20,6 @@ export function runList(): void {
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const agentCounts = new Map<string, number>();
|
|
24
|
-
for (const s of skills) {
|
|
25
|
-
agentCounts.set(s.agent, (agentCounts.get(s.agent) ?? 0) + 1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
23
|
const totalSize = skills.reduce((acc, s) => acc + s.size, 0);
|
|
29
24
|
|
|
30
25
|
console.log(`\n ${bold(`INSTALLED SKILLS (${skills.length})`)}\n`);
|
|
@@ -45,10 +40,7 @@ export function runList(): void {
|
|
|
45
40
|
console.log(` ${name}${desc}${size}`);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
const agentSummary = [...agentCounts.entries()]
|
|
49
|
-
.map(([a, c]) => `${a} (${c})`)
|
|
50
|
-
.join(", ");
|
|
51
43
|
console.log(
|
|
52
|
-
`\n ${dim(`Total: ${skills.length} skills | ${formatSize(totalSize)}
|
|
44
|
+
`\n ${dim(`Total: ${skills.length} skills | ${formatSize(totalSize)}`)}\n`,
|
|
53
45
|
);
|
|
54
46
|
}
|
package/src/commands/scan.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { upsertInstalledSkill } from "../db/queries";
|
|
3
|
+
import { upsertInstalledSkill, deduplicateInvocations } from "../db/queries";
|
|
4
4
|
import { getDb } from "../db/schema";
|
|
5
|
-
import {
|
|
5
|
+
import { countAllSessions, scanAllSessions } from "../scanner/index";
|
|
6
6
|
import { getDetectedAgents, scanInstalledSkills } from "../scanner/skills";
|
|
7
7
|
import { bold, cyan, dim } from "../tui/colors";
|
|
8
8
|
|
|
@@ -26,10 +26,12 @@ export async function runScan(): Promise<void> {
|
|
|
26
26
|
|
|
27
27
|
const agents = getDetectedAgents();
|
|
28
28
|
if (agents.length === 0) {
|
|
29
|
-
console.log(
|
|
29
|
+
console.log(
|
|
30
|
+
`\n ${dim("No supported agents found (Claude Code, OpenCode).")}\n`,
|
|
31
|
+
);
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
|
-
console.log(`\n ${dim(`Scanning ${agents.
|
|
34
|
+
console.log(`\n ${dim(`Scanning ${agents.join(" + ")}`)}`);
|
|
33
35
|
|
|
34
36
|
const skills = scanInstalledSkills();
|
|
35
37
|
|
|
@@ -84,6 +86,11 @@ export async function runScan(): Promise<void> {
|
|
|
84
86
|
} catch {}
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
const removed = deduplicateInvocations(db);
|
|
90
|
+
if (removed > 0) {
|
|
91
|
+
console.log(` ${dim(`Cleaned ${removed} duplicate entries`)}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
console.log(dim(" Scanning sessions..."));
|
|
88
95
|
|
|
89
96
|
const sessionCount = countAllSessions();
|
package/src/db/queries.ts
CHANGED
|
@@ -76,18 +76,53 @@ export function recordInvocation(
|
|
|
76
76
|
skillName: string,
|
|
77
77
|
sessionId?: string,
|
|
78
78
|
project?: string,
|
|
79
|
+
timestamp?: string,
|
|
79
80
|
): void {
|
|
81
|
+
const ts = timestamp ?? new Date().toISOString();
|
|
80
82
|
db.run(
|
|
81
83
|
"INSERT INTO skill_invocations (skill_name, timestamp, session_id, project) VALUES (?, ?, ?, ?)",
|
|
82
|
-
[skillName,
|
|
84
|
+
[skillName, ts, sessionId ?? null, project ?? null],
|
|
83
85
|
);
|
|
84
|
-
const date =
|
|
86
|
+
const date = ts.slice(0, 10);
|
|
85
87
|
db.run(
|
|
86
88
|
"INSERT INTO skill_daily_stats (date, skill_name, count) VALUES (?, ?, 1) ON CONFLICT(date, skill_name) DO UPDATE SET count = count + 1",
|
|
87
89
|
[date, skillName],
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
export function deduplicateInvocations(db: Database): number {
|
|
94
|
+
const before = db
|
|
95
|
+
.query<{ count: number }, []>(
|
|
96
|
+
"SELECT COUNT(*) as count FROM skill_invocations",
|
|
97
|
+
)
|
|
98
|
+
.get()?.count ?? 0;
|
|
99
|
+
|
|
100
|
+
db.run(`
|
|
101
|
+
DELETE FROM skill_invocations WHERE id NOT IN (
|
|
102
|
+
SELECT MIN(id) FROM skill_invocations
|
|
103
|
+
GROUP BY skill_name, session_id, timestamp
|
|
104
|
+
)
|
|
105
|
+
`);
|
|
106
|
+
|
|
107
|
+
const after = db
|
|
108
|
+
.query<{ count: number }, []>(
|
|
109
|
+
"SELECT COUNT(*) as count FROM skill_invocations",
|
|
110
|
+
)
|
|
111
|
+
.get()?.count ?? 0;
|
|
112
|
+
|
|
113
|
+
if (before !== after) {
|
|
114
|
+
db.run("DELETE FROM skill_daily_stats");
|
|
115
|
+
db.run(`
|
|
116
|
+
INSERT INTO skill_daily_stats (date, skill_name, count)
|
|
117
|
+
SELECT date(timestamp), skill_name, COUNT(*)
|
|
118
|
+
FROM skill_invocations
|
|
119
|
+
GROUP BY date(timestamp), skill_name
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return before - after;
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
export function upsertInstalledSkill(
|
|
92
127
|
db: Database,
|
|
93
128
|
name: string,
|
package/src/index.ts
CHANGED
|
@@ -12,8 +12,8 @@ export {
|
|
|
12
12
|
upsertInstalledSkill,
|
|
13
13
|
} from "./db/queries";
|
|
14
14
|
export { getDb } from "./db/schema";
|
|
15
|
-
export { scanAllSessions, countAllSessions } from "./scanner/index";
|
|
16
15
|
export { parseSessionFile } from "./scanner/connectors/claude";
|
|
16
|
+
export { countAllSessions, scanAllSessions } from "./scanner/index";
|
|
17
17
|
export { getDetectedAgents, scanInstalledSkills } from "./scanner/skills";
|
|
18
18
|
export type {
|
|
19
19
|
InstalledSkill,
|
|
@@ -51,8 +51,7 @@ export function parseSessionFile(filePath: string): Invocation[] {
|
|
|
51
51
|
const msg = obj.message as
|
|
52
52
|
| { content: Array<Record<string, unknown>> }
|
|
53
53
|
| undefined;
|
|
54
|
-
const msgContent =
|
|
55
|
-
obj.type === "assistant" && msg ? msg.content : null;
|
|
54
|
+
const msgContent = obj.type === "assistant" && msg ? msg.content : null;
|
|
56
55
|
|
|
57
56
|
if (!Array.isArray(msgContent)) continue;
|
|
58
57
|
|
|
@@ -63,9 +62,7 @@ export function parseSessionFile(filePath: string): Invocation[] {
|
|
|
63
62
|
block.type === "tool_use" &&
|
|
64
63
|
(block as unknown as ToolUseBlock).name === "Skill"
|
|
65
64
|
) {
|
|
66
|
-
const skillName = extractSkillName(
|
|
67
|
-
block as unknown as ToolUseBlock,
|
|
68
|
-
);
|
|
65
|
+
const skillName = extractSkillName(block as unknown as ToolUseBlock);
|
|
69
66
|
if (skillName) {
|
|
70
67
|
results.push({ skillName, timestamp, sessionId });
|
|
71
68
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Database as BunDatabase } from "bun:sqlite";
|
|
2
1
|
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { Database as BunDatabase } from "bun:sqlite";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { homedir, platform } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { recordNewInvocations } from "../index";
|
|
7
6
|
import type { Invocation } from "../index";
|
|
7
|
+
import { recordNewInvocations } from "../index";
|
|
8
8
|
|
|
9
9
|
function getDbPath(): string | null {
|
|
10
10
|
const os = platform();
|
|
@@ -48,9 +48,7 @@ export function countOpenCodeSessions(): number {
|
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
50
|
const row = ocDb
|
|
51
|
-
.query<{ count: number }, []>(
|
|
52
|
-
"SELECT COUNT(*) as count FROM session",
|
|
53
|
-
)
|
|
51
|
+
.query<{ count: number }, []>("SELECT COUNT(*) as count FROM session")
|
|
54
52
|
.get();
|
|
55
53
|
return row?.count ?? 0;
|
|
56
54
|
} catch {
|
|
@@ -70,7 +68,7 @@ export function scanOpenCodeSessions(
|
|
|
70
68
|
try {
|
|
71
69
|
const rows = ocDb
|
|
72
70
|
.query<PartRow, []>(
|
|
73
|
-
|
|
71
|
+
'SELECT p.session_id, p.time_created, p.data FROM part p WHERE p.data LIKE \'%"tool":"skill"%\'',
|
|
74
72
|
)
|
|
75
73
|
.all();
|
|
76
74
|
|
package/src/scanner/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import { recordInvocation } from "../db/queries";
|
|
3
|
-
import {
|
|
3
|
+
import { countClaudeSessions, scanClaudeSessions } from "./connectors/claude";
|
|
4
4
|
import {
|
|
5
|
-
scanOpenCodeSessions,
|
|
6
5
|
countOpenCodeSessions,
|
|
6
|
+
scanOpenCodeSessions,
|
|
7
7
|
} from "./connectors/opencode";
|
|
8
8
|
|
|
9
9
|
interface AlreadyTracked {
|
|
@@ -35,7 +35,7 @@ export function recordNewInvocations(
|
|
|
35
35
|
for (const inv of invocations) {
|
|
36
36
|
const key = `${inv.sessionId}::${inv.timestamp}`;
|
|
37
37
|
if (!trackedSet.has(key)) {
|
|
38
|
-
recordInvocation(db, inv.skillName, inv.sessionId);
|
|
38
|
+
recordInvocation(db, inv.skillName, inv.sessionId, undefined, inv.timestamp);
|
|
39
39
|
trackedSet.add(key);
|
|
40
40
|
count++;
|
|
41
41
|
}
|
package/src/scanner/skills.ts
CHANGED
|
@@ -3,22 +3,23 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { InstalledSkill } from "../types";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const SUPPORTED_AGENTS: Array<{ agent: string; dir: string }> = [
|
|
7
7
|
{ agent: "Claude Code", dir: join(homedir(), ".claude", "skills") },
|
|
8
|
-
{ agent: "Cursor", dir: join(homedir(), ".cursor", "skills") },
|
|
9
|
-
{ agent: "Codex", dir: join(homedir(), ".codex", "skills") },
|
|
10
|
-
{ agent: "Windsurf", dir: join(homedir(), ".codeium", "windsurf", "skills") },
|
|
11
|
-
{ agent: "Gemini CLI", dir: join(homedir(), ".gemini", "skills") },
|
|
12
|
-
{ agent: "Cline", dir: join(homedir(), ".cline", "skills") },
|
|
13
|
-
{ agent: "Roo Code", dir: join(homedir(), ".roo", "skills") },
|
|
14
|
-
{ agent: "Continue", dir: join(homedir(), ".continue", "skills") },
|
|
15
8
|
{ agent: "OpenCode", dir: join(homedir(), ".config", "opencode", "skills") },
|
|
16
|
-
|
|
17
|
-
{ agent: "
|
|
18
|
-
{ agent: "
|
|
19
|
-
{ agent: "
|
|
20
|
-
{ agent: "
|
|
21
|
-
{ agent: "
|
|
9
|
+
// Planned — needs session connector to enable full analytics pipeline
|
|
10
|
+
// { agent: "Cursor", dir: join(homedir(), ".cursor", "skills") }, // GH-1: injects skills as context rules, no discrete tool_use
|
|
11
|
+
// { agent: "Codex", dir: join(homedir(), ".codex", "skills") }, // GH-2
|
|
12
|
+
// { agent: "Windsurf", dir: join(homedir(), ".codeium", "windsurf", "skills") }, // GH-3
|
|
13
|
+
// { agent: "Gemini CLI", dir: join(homedir(), ".gemini", "skills") }, // GH-4
|
|
14
|
+
// { agent: "Cline", dir: join(homedir(), ".cline", "skills") }, // GH-5
|
|
15
|
+
// { agent: "Roo Code", dir: join(homedir(), ".roo", "skills") }, // GH-6
|
|
16
|
+
// { agent: "Continue", dir: join(homedir(), ".continue", "skills") }, // GH-7
|
|
17
|
+
// { agent: "GitHub Copilot", dir: join(homedir(), ".copilot", "skills") }, // GH-8
|
|
18
|
+
// { agent: "OpenHands", dir: join(homedir(), ".openhands", "skills") }, // GH-9
|
|
19
|
+
// { agent: "Amp", dir: join(homedir(), ".config", "agents", "skills") }, // GH-10
|
|
20
|
+
// { agent: "Goose", dir: join(homedir(), ".config", "goose", "skills") }, // GH-11
|
|
21
|
+
// { agent: "Kilo Code", dir: join(homedir(), ".kilocode", "skills") }, // GH-12
|
|
22
|
+
// { agent: "Trae", dir: join(homedir(), ".trae", "skills") }, // GH-13
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
function parseYamlFrontmatter(content: string): Record<string, string> {
|
|
@@ -56,10 +57,7 @@ function getDirSize(dirPath: string): number {
|
|
|
56
57
|
return total;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function scanSkillsDir(
|
|
60
|
-
skillsDir: string,
|
|
61
|
-
agent: string,
|
|
62
|
-
): InstalledSkill[] {
|
|
60
|
+
function scanSkillsDir(skillsDir: string, agent: string): InstalledSkill[] {
|
|
63
61
|
if (!existsSync(skillsDir)) return [];
|
|
64
62
|
|
|
65
63
|
const skills: InstalledSkill[] = [];
|
|
@@ -125,7 +123,7 @@ export function scanInstalledSkills(): InstalledSkill[] {
|
|
|
125
123
|
const allSkills: InstalledSkill[] = [];
|
|
126
124
|
const seen = new Set<string>();
|
|
127
125
|
|
|
128
|
-
for (const { agent, dir } of
|
|
126
|
+
for (const { agent, dir } of SUPPORTED_AGENTS) {
|
|
129
127
|
const skills = scanSkillsDir(dir, agent);
|
|
130
128
|
for (const skill of skills) {
|
|
131
129
|
try {
|
|
@@ -143,7 +141,7 @@ export function scanInstalledSkills(): InstalledSkill[] {
|
|
|
143
141
|
|
|
144
142
|
export function getDetectedAgents(): string[] {
|
|
145
143
|
const agents: string[] = [];
|
|
146
|
-
for (const { agent, dir } of
|
|
144
|
+
for (const { agent, dir } of SUPPORTED_AGENTS) {
|
|
147
145
|
if (existsSync(dir)) agents.push(agent);
|
|
148
146
|
}
|
|
149
147
|
return agents;
|