skill-tree-ai 1.0.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/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +8 -0
- package/README.md +58 -0
- package/dist/core/classify.js +171 -0
- package/dist/core/extract.js +179 -0
- package/dist/core/profile.js +254 -0
- package/dist/core/render.js +58 -0
- package/dist/index.js +10 -0
- package/dist/local.js +220 -0
- package/dist/remote.js +113 -0
- package/dist/shared.js +16 -0
- package/hooks/hooks.json +15 -0
- package/hooks-handlers/session-start.sh +29 -0
- package/package.json +46 -0
- package/skills/skill-tree/SKILL.md +49 -0
- package/templates/skill-tree.html +728 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
function getTemplateDir() {
|
|
9
|
+
// Try relative to this file first (works in dev and dist)
|
|
10
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
// In dist/core/render.js → templates is at ../../templates
|
|
12
|
+
const distPath = join(thisDir, "..", "..", "templates");
|
|
13
|
+
if (existsSync(distPath))
|
|
14
|
+
return distPath;
|
|
15
|
+
// In src/core/render.ts → templates is at ../../templates
|
|
16
|
+
const srcPath = join(thisDir, "..", "..", "templates");
|
|
17
|
+
if (existsSync(srcPath))
|
|
18
|
+
return srcPath;
|
|
19
|
+
// Fallback: look in cwd
|
|
20
|
+
const cwdPath = join(process.cwd(), "templates");
|
|
21
|
+
if (existsSync(cwdPath))
|
|
22
|
+
return cwdPath;
|
|
23
|
+
throw new Error("Cannot find templates directory");
|
|
24
|
+
}
|
|
25
|
+
function getOutputDir() {
|
|
26
|
+
const dir = join(homedir(), ".skill-tree");
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
export function renderHTML(profile) {
|
|
30
|
+
const templatePath = join(getTemplateDir(), "skill-tree.html");
|
|
31
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
32
|
+
// Escape </script> in JSON to prevent HTML parser breaking
|
|
33
|
+
const jsonStr = JSON.stringify(profile).replace(/<\/script>/gi, "<\\/script>");
|
|
34
|
+
return template.replace("__PROFILE_DATA__", jsonStr);
|
|
35
|
+
}
|
|
36
|
+
export function writeAndOpen(profile) {
|
|
37
|
+
const html = renderHTML(profile);
|
|
38
|
+
const outputPath = join(getOutputDir(), "report.html");
|
|
39
|
+
writeFileSync(outputPath, html);
|
|
40
|
+
return outputPath;
|
|
41
|
+
}
|
|
42
|
+
export async function openInBrowser(filePath) {
|
|
43
|
+
const platform = process.platform;
|
|
44
|
+
try {
|
|
45
|
+
if (platform === "darwin") {
|
|
46
|
+
await execFileAsync("open", [filePath]);
|
|
47
|
+
}
|
|
48
|
+
else if (platform === "linux") {
|
|
49
|
+
await execFileAsync("xdg-open", [filePath]);
|
|
50
|
+
}
|
|
51
|
+
else if (platform === "win32") {
|
|
52
|
+
await execFileAsync("cmd", ["/c", "start", filePath]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Silent — caller has the path
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/index.js
ADDED
package/dist/local.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
6
|
+
import { findAllSessions } from "./core/extract.js";
|
|
7
|
+
import { classifySessions } from "./core/classify.js";
|
|
8
|
+
import { buildProfile } from "./core/profile.js";
|
|
9
|
+
import { writeAndOpen, openInBrowser } from "./core/render.js";
|
|
10
|
+
import { MCP_INSTRUCTIONS } from "./shared.js";
|
|
11
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
// --- State & Persistence ---
|
|
15
|
+
let cachedProfile = null;
|
|
16
|
+
function getSkillTreeDir() {
|
|
17
|
+
const dir = join(homedir(), ".skill-tree");
|
|
18
|
+
if (!existsSync(dir))
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
function getProfilePath() {
|
|
23
|
+
return join(getSkillTreeDir(), "profile.json");
|
|
24
|
+
}
|
|
25
|
+
function getHistoryDir() {
|
|
26
|
+
const dir = join(getSkillTreeDir(), "history");
|
|
27
|
+
if (!existsSync(dir))
|
|
28
|
+
mkdirSync(dir, { recursive: true });
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
function loadCachedProfile() {
|
|
32
|
+
const path = getProfilePath();
|
|
33
|
+
if (!existsSync(path))
|
|
34
|
+
return null;
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function saveProfile(profile) {
|
|
43
|
+
const dir = getSkillTreeDir();
|
|
44
|
+
// Save current profile
|
|
45
|
+
writeFileSync(join(dir, "profile.json"), JSON.stringify(profile, null, 2));
|
|
46
|
+
// Save timestamped snapshot for progress tracking
|
|
47
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
48
|
+
writeFileSync(join(getHistoryDir(), `profile-${ts}.json`), JSON.stringify(profile, null, 2));
|
|
49
|
+
// Write growth-quest.txt for SessionStart hook
|
|
50
|
+
writeFileSync(join(dir, "growth-quest.txt"), profile.archetype.growth_quest);
|
|
51
|
+
}
|
|
52
|
+
// --- MCP Server ---
|
|
53
|
+
const server = new McpServer({ name: "skill-tree-ai", version: "1.0.0" }, { instructions: MCP_INSTRUCTIONS });
|
|
54
|
+
server.tool("analyze", "Analyze your Claude conversation history. Scans Claude Code and Cowork sessions, classifies 11 AI collaboration behaviors, builds a skill profile with character archetype and growth recommendation. Returns the full profile.", {
|
|
55
|
+
max_sessions: z
|
|
56
|
+
.number()
|
|
57
|
+
.default(100)
|
|
58
|
+
.describe("Maximum number of sessions to analyze (most recent first)"),
|
|
59
|
+
force_refresh: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.default(false)
|
|
62
|
+
.describe("Force re-analysis even if cached results exist"),
|
|
63
|
+
}, async ({ max_sessions, force_refresh }) => {
|
|
64
|
+
// Check cache unless forced
|
|
65
|
+
if (!force_refresh) {
|
|
66
|
+
const cached = loadCachedProfile();
|
|
67
|
+
if (cached) {
|
|
68
|
+
cachedProfile = cached;
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: formatProfileSummary(cached),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
80
|
+
if (!apiKey) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: "Error: ANTHROPIC_API_KEY not set. Set it in your environment to enable skill tree analysis.",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const client = new Anthropic({ apiKey });
|
|
91
|
+
// Extract sessions
|
|
92
|
+
const sessions = findAllSessions(max_sessions);
|
|
93
|
+
if (sessions.length === 0) {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: "No conversation sessions found. Make sure you have Claude Code or Cowork conversations on this machine.",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Classify (uses per-session cache — only new sessions hit the API)
|
|
104
|
+
const classifications = await classifySessions(client, sessions.map((s) => ({
|
|
105
|
+
sessionId: s.sessionId,
|
|
106
|
+
messages: s.messages,
|
|
107
|
+
})), 50);
|
|
108
|
+
// Build profile (deterministic — no LLM call)
|
|
109
|
+
const previousProfile = loadCachedProfile();
|
|
110
|
+
const profile = buildProfile(classifications, previousProfile);
|
|
111
|
+
cachedProfile = profile;
|
|
112
|
+
saveProfile(profile);
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: formatProfileSummary(profile),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
server.tool("visualize", "Generate a beautiful HTML skill tree visualization and open it in your browser. Run 'analyze' first to generate the profile data.", {}, async () => {
|
|
123
|
+
const profile = cachedProfile || loadCachedProfile();
|
|
124
|
+
if (!profile) {
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: "No profile data available. Run the 'analyze' tool first.",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const outputPath = writeAndOpen(profile);
|
|
135
|
+
await openInBrowser(outputPath);
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: `Skill tree visualization generated and opened in browser.\nFile: ${outputPath}`,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
server.tool("growth_quest", "Get your current growth recommendation — one specific thing to try in your next session, based on your archetype's growth path.", {}, async () => {
|
|
146
|
+
const profile = cachedProfile || loadCachedProfile();
|
|
147
|
+
if (!profile) {
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: "No profile data available. Run the 'analyze' tool first to get personalized recommendations.",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const a = profile.archetype;
|
|
158
|
+
const ge = profile.growth_edge;
|
|
159
|
+
const userPct = Math.round(ge.rate * 100);
|
|
160
|
+
const basePct = Math.round(ge.baseline * 100);
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: [
|
|
166
|
+
`${a.name} — ${a.tagline}`,
|
|
167
|
+
``,
|
|
168
|
+
`Superpower: ${a.superpower}`,
|
|
169
|
+
``,
|
|
170
|
+
`Next Unlock: ${a.growth_unlock}`,
|
|
171
|
+
``,
|
|
172
|
+
`Quest: ${a.growth_quest}`,
|
|
173
|
+
``,
|
|
174
|
+
`Growth edge: ${ge.label} — you're at ${userPct}%, population average is ${basePct}%`,
|
|
175
|
+
].join("\n"),
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
// --- Format helpers ---
|
|
181
|
+
function formatProfileSummary(profile) {
|
|
182
|
+
const a = profile.archetype;
|
|
183
|
+
const lines = [
|
|
184
|
+
`╔══════════════════════════════════════╗`,
|
|
185
|
+
`║ ${a.name.padStart(18).padEnd(36)}║`,
|
|
186
|
+
`╚══════════════════════════════════════╝`,
|
|
187
|
+
`"${a.tagline}"`,
|
|
188
|
+
``,
|
|
189
|
+
`Superpower: ${a.superpower}`,
|
|
190
|
+
``,
|
|
191
|
+
`─── Skill Profile (${profile.total_sessions} sessions) ───`,
|
|
192
|
+
``,
|
|
193
|
+
];
|
|
194
|
+
for (const [axisName, axis] of Object.entries(profile.branches)) {
|
|
195
|
+
const indicator = axis.above_baseline ? "+" : "-";
|
|
196
|
+
lines.push(`${axisName} (${indicator} vs avg):`);
|
|
197
|
+
for (const key of axis.behaviors) {
|
|
198
|
+
const b = profile.behaviors[key];
|
|
199
|
+
if (!b)
|
|
200
|
+
continue;
|
|
201
|
+
const pct = Math.round(b.rate * 100);
|
|
202
|
+
const basePct = Math.round(b.baseline * 100);
|
|
203
|
+
const arrow = b.above_baseline ? "↑" : "↓";
|
|
204
|
+
const bar = "■".repeat(Math.round(pct / 10)) +
|
|
205
|
+
"□".repeat(10 - Math.round(pct / 10));
|
|
206
|
+
lines.push(` ${bar} ${pct}% ${b.label} (avg: ${basePct}%) ${arrow}`);
|
|
207
|
+
}
|
|
208
|
+
lines.push(``);
|
|
209
|
+
}
|
|
210
|
+
const ge = profile.growth_edge;
|
|
211
|
+
lines.push(`Next Unlock: ${a.growth_unlock}`);
|
|
212
|
+
lines.push(`Quest: ${a.growth_quest}`);
|
|
213
|
+
if (profile.previous_archetype && profile.previous_archetype !== a.key) {
|
|
214
|
+
lines.push(`\nProgress: Archetype changed from ${profile.previous_archetype} → ${a.key}`);
|
|
215
|
+
}
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
// --- Start ---
|
|
219
|
+
const transport = new StdioServerTransport();
|
|
220
|
+
await server.connect(transport);
|
package/dist/remote.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StreamableHTTPServerTransport, } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
7
|
+
import { findAllSessions } from "./core/extract.js";
|
|
8
|
+
import { classifySessions } from "./core/classify.js";
|
|
9
|
+
import { buildProfile } from "./core/profile.js";
|
|
10
|
+
import { renderHTML } from "./core/render.js";
|
|
11
|
+
import { MCP_INSTRUCTIONS } from "./shared.js";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
const PORT = parseInt(process.env.PORT || "3000", 10);
|
|
14
|
+
// Per-session MCP servers
|
|
15
|
+
const sessions = new Map();
|
|
16
|
+
function createSessionServer() {
|
|
17
|
+
const server = new McpServer({ name: "skill-tree-ai", version: "1.0.0" }, { instructions: MCP_INSTRUCTIONS });
|
|
18
|
+
// analyze tool (simplified for remote — operates on provided data or sample)
|
|
19
|
+
server.tool("analyze", "Analyze conversation history and return skill profile.", {
|
|
20
|
+
conversation_json: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Optional: paste conversation JSONL content to analyze"),
|
|
24
|
+
}, async ({ conversation_json }) => {
|
|
25
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: "Error: ANTHROPIC_API_KEY not configured on server." }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const client = new Anthropic({ apiKey });
|
|
32
|
+
// If conversation_json provided, analyze it directly
|
|
33
|
+
// Otherwise, analyze local sessions (server-side)
|
|
34
|
+
const sessions = findAllSessions(50);
|
|
35
|
+
if (sessions.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: "No sessions found on this machine." }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const classifications = await classifySessions(client, sessions.map((s) => ({ sessionId: s.sessionId, messages: s.messages })), 20);
|
|
41
|
+
const profile = buildProfile(classifications);
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{ type: "text", text: JSON.stringify(profile, null, 2) },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
// visualize tool — returns HTML string
|
|
49
|
+
server.tool("visualize", "Generate HTML visualization from a profile.", {
|
|
50
|
+
profile_json: z.string().describe("Profile JSON from the analyze tool"),
|
|
51
|
+
}, async ({ profile_json }) => {
|
|
52
|
+
const profile = JSON.parse(profile_json);
|
|
53
|
+
const html = renderHTML(profile);
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: html }],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
|
|
59
|
+
server.connect(transport);
|
|
60
|
+
return { server, transport };
|
|
61
|
+
}
|
|
62
|
+
const httpServer = createServer(async (req, res) => {
|
|
63
|
+
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
|
64
|
+
// CORS
|
|
65
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
66
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
|
67
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
|
|
68
|
+
if (req.method === "OPTIONS") {
|
|
69
|
+
res.writeHead(204);
|
|
70
|
+
res.end();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (url.pathname === "/health") {
|
|
74
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
75
|
+
res.end(JSON.stringify({ status: "ok", sessions: sessions.size }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (url.pathname === "/mcp" && req.method === "POST") {
|
|
79
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
80
|
+
let session;
|
|
81
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
82
|
+
session = sessions.get(sessionId);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
session = createSessionServer();
|
|
86
|
+
const newId = randomUUID();
|
|
87
|
+
sessions.set(newId, session);
|
|
88
|
+
res.setHeader("mcp-session-id", newId);
|
|
89
|
+
}
|
|
90
|
+
// Forward request to transport
|
|
91
|
+
await session.transport.handleRequest(req, res, await readBody(req));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
res.writeHead(404);
|
|
95
|
+
res.end("Not found");
|
|
96
|
+
});
|
|
97
|
+
function readBody(req) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
let body = "";
|
|
100
|
+
req.on("data", (chunk) => (body += chunk));
|
|
101
|
+
req.on("end", () => {
|
|
102
|
+
try {
|
|
103
|
+
resolve(JSON.parse(body));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
resolve(body);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
httpServer.listen(PORT, () => {
|
|
112
|
+
console.error(`Skill Tree MCP server running on http://localhost:${PORT}/mcp`);
|
|
113
|
+
});
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MCP_INSTRUCTIONS = `Skill Tree analyzes your Claude conversation history to reveal your AI collaboration style.
|
|
2
|
+
|
|
3
|
+
It classifies 11 observable behaviors from the AI Fluency Framework across your sessions, builds a skill profile, and generates a character archetype card with a personalized growth recommendation.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
- **analyze**: Scans your conversation history (Claude Code + Cowork), classifies behaviors, and returns your full skill profile with archetype and growth edge. Run this first.
|
|
8
|
+
- **visualize**: Generates a beautiful HTML skill tree visualization and opens it in your browser. Requires a profile from analyze.
|
|
9
|
+
- **growth_quest**: Returns your current recommended challenge — one specific thing to try in your next session.
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
Use \`analyze\` when the user asks about their Claude usage style, wants to see their skill tree, or runs /skill-tree.
|
|
14
|
+
Use \`visualize\` after analyze to show the results.
|
|
15
|
+
Use \`growth_quest\` when the user wants a quick recommendation without the full analysis.
|
|
16
|
+
`;
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Skill tree growth quest — injects your active challenge into each session",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks-handlers/session-start.sh\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Inject the active growth quest into the session context.
|
|
3
|
+
# Reads from ~/.skill-tree/growth-quest.txt (written by profile.py after /skill-tree runs).
|
|
4
|
+
|
|
5
|
+
QUEST_FILE="$HOME/.skill-tree/growth-quest.txt"
|
|
6
|
+
|
|
7
|
+
if [ -f "$QUEST_FILE" ] && [ -s "$QUEST_FILE" ]; then
|
|
8
|
+
QUEST=$(cat "$QUEST_FILE")
|
|
9
|
+
cat << EOF
|
|
10
|
+
{
|
|
11
|
+
"hookSpecificOutput": {
|
|
12
|
+
"hookEventName": "SessionStart",
|
|
13
|
+
"additionalContext": "Skill Tree growth quest active: ${QUEST} — If a natural opportunity arises during this session, gently encourage the user to practice this behavior. Do not force it or mention this unless relevant."
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
EOF
|
|
17
|
+
else
|
|
18
|
+
# No quest yet — user hasn't run /skill-tree
|
|
19
|
+
cat << 'EOF'
|
|
20
|
+
{
|
|
21
|
+
"hookSpecificOutput": {
|
|
22
|
+
"hookEventName": "SessionStart",
|
|
23
|
+
"additionalContext": ""
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
EOF
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
exit 0
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-tree-ai",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Your AI collaboration style — skill tree visualization with character archetype cards and growth recommendations, grounded in the AI Fluency Framework. MCP server for Claude Code.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skill-tree-ai": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates",
|
|
12
|
+
"skills",
|
|
13
|
+
".claude-plugin",
|
|
14
|
+
".mcp.json",
|
|
15
|
+
"hooks",
|
|
16
|
+
"hooks-handlers"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"start:remote": "SKILL_TREE_REMOTE=1 node dist/index.js",
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"dev:remote": "SKILL_TREE_REMOTE=1 tsx src/index.ts",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"skill-tree",
|
|
30
|
+
"ai-fluency",
|
|
31
|
+
"mcp-server",
|
|
32
|
+
"education"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": "Robert Nowell",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
39
|
+
"zod": "^3.23.8"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"tsx": "^4.19.2",
|
|
44
|
+
"typescript": "^5.6.3"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-tree
|
|
3
|
+
description: Analyze your Claude collaboration style and generate a skill tree visualization with character archetype card. Use when the user says "skill tree", "show my skills", "analyze my style", or wants to see their AI fluency profile.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill Tree
|
|
7
|
+
|
|
8
|
+
Generate a personalized AI fluency profile by analyzing the user's Claude conversation history.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
Use the MCP tools in this order:
|
|
13
|
+
|
|
14
|
+
1. Call the **`analyze`** tool — this scans your Claude Code and Cowork conversation history, classifies 11 AI collaboration behaviors using Claude Haiku, and returns your full skill profile with character archetype.
|
|
15
|
+
|
|
16
|
+
2. Call the **`visualize`** tool — this generates a beautiful HTML skill tree visualization and opens it in your browser.
|
|
17
|
+
|
|
18
|
+
3. Present the results to the user — show the archetype card, key strengths, and growth quest.
|
|
19
|
+
|
|
20
|
+
If the user just wants a quick recommendation without the full analysis, use the **`growth_quest`** tool.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- `ANTHROPIC_API_KEY` environment variable must be set (for behavioral classification via Claude Haiku)
|
|
25
|
+
- If not set, the tools will return an error with setup instructions
|
|
26
|
+
|
|
27
|
+
## What It Shows
|
|
28
|
+
|
|
29
|
+
- **Character Card**: Your archetype name, tagline, signature strengths, and vibe
|
|
30
|
+
- **Skill Tree**: 11 behaviors across 4 branches (Planning, Craft, Judgment, Rigor) with animated bars and population baseline markers
|
|
31
|
+
- **Growth Quest**: Your "next unlock" — one specific challenge for your next session
|
|
32
|
+
|
|
33
|
+
## The 11 Behaviors (from AI Fluency Framework)
|
|
34
|
+
|
|
35
|
+
| Branch | Behavior | Population Avg |
|
|
36
|
+
|--------|----------|---------------|
|
|
37
|
+
| Planning | Clarifies goals upfront | 51% |
|
|
38
|
+
| Planning | Discusses approach first | 10% |
|
|
39
|
+
| Craft | Iterates on outputs | 86% |
|
|
40
|
+
| Craft | Provides examples | 41% |
|
|
41
|
+
| Craft | Specifies format | 30% |
|
|
42
|
+
| Craft | Sets interaction style | 30% |
|
|
43
|
+
| Craft | Expresses tone preferences | 23% |
|
|
44
|
+
| Craft | Defines audience | 18% |
|
|
45
|
+
| Judgment | Flags context gaps | 20% |
|
|
46
|
+
| Judgment | Questions Claude's logic | 16% |
|
|
47
|
+
| Rigor | Verifies facts | 9% |
|
|
48
|
+
|
|
49
|
+
Baselines from [Anthropic's AI Fluency Index](https://www.anthropic.com/research/AI-fluency-index) (Feb 2026, N=9,830 conversations).
|