@crafter/skillkit 0.2.0 → 0.2.1
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/commands/scan.ts +19 -2
- package/src/scanner/connectors/claude.ts +52 -4
- package/src/scanner/index.ts +5 -2
package/package.json
CHANGED
package/src/commands/scan.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
3
|
import { upsertInstalledSkill, deduplicateInvocations } from "../db/queries";
|
|
4
4
|
import { getDb } from "../db/schema";
|
|
5
5
|
import { countAllSessions, scanAllSessions } from "../scanner/index";
|
|
@@ -86,6 +86,23 @@ export async function runScan(): Promise<void> {
|
|
|
86
86
|
} catch {}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const knownSkills = new Set<string>();
|
|
90
|
+
for (const skill of skills) {
|
|
91
|
+
knownSkills.add(skill.name);
|
|
92
|
+
knownSkills.add(basename(skill.path));
|
|
93
|
+
}
|
|
94
|
+
if (existsSync(localSkillsDir)) {
|
|
95
|
+
try {
|
|
96
|
+
for (const e of readdirSync(localSkillsDir)) {
|
|
97
|
+
try {
|
|
98
|
+
if (statSync(join(localSkillsDir, e)).isDirectory()) {
|
|
99
|
+
knownSkills.add(e);
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
const removed = deduplicateInvocations(db);
|
|
90
107
|
if (removed > 0) {
|
|
91
108
|
console.log(` ${dim(`Cleaned ${removed} duplicate entries`)}`);
|
|
@@ -94,7 +111,7 @@ export async function runScan(): Promise<void> {
|
|
|
94
111
|
console.log(dim(" Scanning sessions..."));
|
|
95
112
|
|
|
96
113
|
const sessionCount = countAllSessions();
|
|
97
|
-
const newInvocations = await scanAllSessions(db);
|
|
114
|
+
const newInvocations = await scanAllSessions(db, knownSkills);
|
|
98
115
|
const totalRow = db
|
|
99
116
|
.query<{ count: number }, []>(
|
|
100
117
|
"SELECT COUNT(*) as count FROM skill_invocations",
|
|
@@ -19,7 +19,28 @@ function extractSkillName(block: ToolUseBlock): string | null {
|
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const COMMAND_NAME_RE = /<command-name>\/?([a-zA-Z][\w-]*(?::[\w-]*)*)<\/command-name>/g;
|
|
23
|
+
|
|
24
|
+
function extractCommandNames(
|
|
25
|
+
text: string,
|
|
26
|
+
knownSkills: Set<string>,
|
|
27
|
+
): string[] {
|
|
28
|
+
const names: string[] = [];
|
|
29
|
+
let match: RegExpExecArray | null;
|
|
30
|
+
while ((match = COMMAND_NAME_RE.exec(text)) !== null) {
|
|
31
|
+
const name = match[1];
|
|
32
|
+
if (knownSkills.has(name)) {
|
|
33
|
+
names.push(name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
COMMAND_NAME_RE.lastIndex = 0;
|
|
37
|
+
return names;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseSessionFile(
|
|
41
|
+
filePath: string,
|
|
42
|
+
knownSkills: Set<string> = new Set(),
|
|
43
|
+
): Invocation[] {
|
|
23
44
|
const results: Invocation[] = [];
|
|
24
45
|
const sessionId = basename(filePath, ".jsonl");
|
|
25
46
|
|
|
@@ -49,9 +70,35 @@ export function parseSessionFile(filePath: string): Invocation[] {
|
|
|
49
70
|
: new Date().toISOString();
|
|
50
71
|
|
|
51
72
|
const msg = obj.message as
|
|
52
|
-
| { content: Array<Record<string, unknown>> }
|
|
73
|
+
| { content: Array<Record<string, unknown>> | string }
|
|
53
74
|
| undefined;
|
|
54
|
-
|
|
75
|
+
|
|
76
|
+
if (obj.type === "user" && msg) {
|
|
77
|
+
const text =
|
|
78
|
+
typeof msg.content === "string"
|
|
79
|
+
? msg.content
|
|
80
|
+
: Array.isArray(msg.content)
|
|
81
|
+
? msg.content
|
|
82
|
+
.filter(
|
|
83
|
+
(b): b is { type: string; text: string } =>
|
|
84
|
+
typeof b === "object" &&
|
|
85
|
+
b !== null &&
|
|
86
|
+
b.type === "text" &&
|
|
87
|
+
typeof b.text === "string",
|
|
88
|
+
)
|
|
89
|
+
.map((b) => b.text)
|
|
90
|
+
.join("\n")
|
|
91
|
+
: "";
|
|
92
|
+
for (const name of extractCommandNames(text, knownSkills)) {
|
|
93
|
+
results.push({ skillName: name, timestamp, sessionId });
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const msgContent =
|
|
99
|
+
obj.type === "assistant" && msg && Array.isArray(msg.content)
|
|
100
|
+
? (msg.content as Array<Record<string, unknown>>)
|
|
101
|
+
: null;
|
|
55
102
|
|
|
56
103
|
if (!Array.isArray(msgContent)) continue;
|
|
57
104
|
|
|
@@ -88,6 +135,7 @@ export function countClaudeSessions(): number {
|
|
|
88
135
|
export async function scanClaudeSessions(
|
|
89
136
|
db: Database,
|
|
90
137
|
trackedSet: Set<string>,
|
|
138
|
+
knownSkills: Set<string> = new Set(),
|
|
91
139
|
): Promise<number> {
|
|
92
140
|
const projectsDir = join(homedir(), ".claude", "projects");
|
|
93
141
|
if (!existsSync(projectsDir)) return 0;
|
|
@@ -101,7 +149,7 @@ export async function scanClaudeSessions(
|
|
|
101
149
|
|
|
102
150
|
let total = 0;
|
|
103
151
|
for (const file of files) {
|
|
104
|
-
const invocations = parseSessionFile(file);
|
|
152
|
+
const invocations = parseSessionFile(file, knownSkills);
|
|
105
153
|
total += recordNewInvocations(db, trackedSet, invocations);
|
|
106
154
|
}
|
|
107
155
|
|
package/src/scanner/index.ts
CHANGED
|
@@ -43,10 +43,13 @@ export function recordNewInvocations(
|
|
|
43
43
|
return count;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export async function scanAllSessions(
|
|
46
|
+
export async function scanAllSessions(
|
|
47
|
+
db: Database,
|
|
48
|
+
knownSkills: Set<string> = new Set(),
|
|
49
|
+
): Promise<number> {
|
|
47
50
|
const trackedSet = getTrackedSet(db);
|
|
48
51
|
let total = 0;
|
|
49
|
-
total += await scanClaudeSessions(db, trackedSet);
|
|
52
|
+
total += await scanClaudeSessions(db, trackedSet, knownSkills);
|
|
50
53
|
total += scanOpenCodeSessions(db, trackedSet);
|
|
51
54
|
return total;
|
|
52
55
|
}
|