braintracker 1.0.0 → 1.1.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/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Removes the hook files and cleans up `~/.claude/settings.json`.
|
|
|
34
34
|
Invoke any skill inside Claude Code (e.g. `/review`), then:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
cat ~/.claude/plugin/cache/
|
|
37
|
+
cat ~/.claude/plugin/cache/braintracker/<project>/<session_id>.jsonl
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Each line is a JSON object:
|
|
@@ -52,9 +52,20 @@ Each line is a JSON object:
|
|
|
52
52
|
|
|
53
53
|
## How It Works
|
|
54
54
|
|
|
55
|
+
Two capture paths cover all skill invocations:
|
|
56
|
+
|
|
57
|
+
**Slash commands** (user types `/skill-name`):
|
|
58
|
+
|
|
59
|
+
| Hook | Script | Action |
|
|
60
|
+
|------|--------|--------|
|
|
61
|
+
| `UserPromptSubmit` | `pre_prompt.js` | Detects `/skill-name` in the prompt, writes `<session>.prompt.pre.json` |
|
|
62
|
+
| `Stop` | `post_prompt.js` | Reads the snapshot, computes duration, appends a log entry, deletes the snapshot |
|
|
63
|
+
|
|
64
|
+
**Mid-conversation tool calls** (Claude calls `Skill(...)` explicitly):
|
|
65
|
+
|
|
55
66
|
| Hook | Script | Action |
|
|
56
67
|
|------|--------|--------|
|
|
57
68
|
| `PreToolUse` (Skill) | `pre_skill.js` | Writes a timing snapshot to `<session>.pre.json` |
|
|
58
|
-
| `PostToolUse` (Skill) | `post_skill.js` | Reads the snapshot, computes duration, appends a log entry
|
|
69
|
+
| `PostToolUse` (Skill) | `post_skill.js` | Reads the snapshot, computes duration, appends a log entry, deletes the snapshot |
|
|
59
70
|
|
|
60
|
-
Logs live at `~/.claude/plugin/cache/
|
|
71
|
+
Logs live at `~/.claude/plugin/cache/braintracker/<project>/` — one JSONL file per session, grouped by project name.
|
package/bin/braintracker.js
CHANGED
|
@@ -6,8 +6,10 @@ const os = require('os');
|
|
|
6
6
|
const DEST_HOOKS = path.join(os.homedir(), '.claude', 'plugins', 'braintracker', 'hooks');
|
|
7
7
|
const SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
8
8
|
|
|
9
|
-
const PRE_CMD
|
|
10
|
-
const POST_CMD
|
|
9
|
+
const PRE_CMD = `node ${path.join(DEST_HOOKS, 'pre_skill.js')}`;
|
|
10
|
+
const POST_CMD = `node ${path.join(DEST_HOOKS, 'post_skill.js')}`;
|
|
11
|
+
const PRE_PROMPT_CMD = `node ${path.join(DEST_HOOKS, 'pre_prompt.js')}`;
|
|
12
|
+
const POST_PROMPT_CMD = `node ${path.join(DEST_HOOKS, 'post_prompt.js')}`;
|
|
11
13
|
|
|
12
14
|
function readSettings() {
|
|
13
15
|
try { return JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); } catch { return {}; }
|
|
@@ -19,8 +21,13 @@ function writeSettings(settings) {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function addHook(arr, matcher, command) {
|
|
22
|
-
let group =
|
|
23
|
-
|
|
24
|
+
let group = matcher != null
|
|
25
|
+
? arr.find(h => h.matcher === matcher)
|
|
26
|
+
: arr.find(h => h.matcher == null);
|
|
27
|
+
if (!group) {
|
|
28
|
+
group = matcher != null ? { matcher, hooks: [] } : { hooks: [] };
|
|
29
|
+
arr.push(group);
|
|
30
|
+
}
|
|
24
31
|
group.hooks = group.hooks || [];
|
|
25
32
|
if (!group.hooks.some(h => h.command === command)) {
|
|
26
33
|
group.hooks.push({ type: 'command', command });
|
|
@@ -37,20 +44,24 @@ function removeHook(arr, command) {
|
|
|
37
44
|
function install() {
|
|
38
45
|
const SRC_HOOKS = path.join(__dirname, '..', 'braintracker', 'hooks');
|
|
39
46
|
fs.mkdirSync(DEST_HOOKS, { recursive: true });
|
|
40
|
-
for (const file of ['pre_skill.js', 'post_skill.js']) {
|
|
47
|
+
for (const file of ['pre_skill.js', 'post_skill.js', 'pre_prompt.js', 'post_prompt.js']) {
|
|
41
48
|
fs.copyFileSync(path.join(SRC_HOOKS, file), path.join(DEST_HOOKS, file));
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const settings = readSettings();
|
|
45
|
-
settings.hooks
|
|
46
|
-
settings.hooks.PreToolUse
|
|
47
|
-
settings.hooks.PostToolUse
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
settings.hooks = settings.hooks || {};
|
|
53
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
54
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
55
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || [];
|
|
56
|
+
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
57
|
+
addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
|
|
58
|
+
addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
|
|
59
|
+
addHook(settings.hooks.UserPromptSubmit, null, PRE_PROMPT_CMD);
|
|
60
|
+
addHook(settings.hooks.Stop, null, POST_PROMPT_CMD);
|
|
50
61
|
writeSettings(settings);
|
|
51
62
|
|
|
52
63
|
console.log('braintracker installed.');
|
|
53
|
-
console.log('Logs → ~/.claude/plugin/cache/
|
|
64
|
+
console.log('Logs → ~/.claude/plugin/cache/braintracker/<project>/<session>.jsonl');
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
function uninstall() {
|
|
@@ -58,8 +69,10 @@ function uninstall() {
|
|
|
58
69
|
|
|
59
70
|
const settings = readSettings();
|
|
60
71
|
if (settings.hooks) {
|
|
61
|
-
settings.hooks.PreToolUse
|
|
62
|
-
settings.hooks.PostToolUse
|
|
72
|
+
settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
|
|
73
|
+
settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
|
|
74
|
+
settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_PROMPT_CMD);
|
|
75
|
+
settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_PROMPT_CMD);
|
|
63
76
|
}
|
|
64
77
|
writeSettings(settings);
|
|
65
78
|
|
|
@@ -69,8 +82,10 @@ function uninstall() {
|
|
|
69
82
|
function disable() {
|
|
70
83
|
const settings = readSettings();
|
|
71
84
|
if (settings.hooks) {
|
|
72
|
-
settings.hooks.PreToolUse
|
|
73
|
-
settings.hooks.PostToolUse
|
|
85
|
+
settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
|
|
86
|
+
settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
|
|
87
|
+
settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_PROMPT_CMD);
|
|
88
|
+
settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_PROMPT_CMD);
|
|
74
89
|
}
|
|
75
90
|
writeSettings(settings);
|
|
76
91
|
console.log('braintracker disabled. Hook files kept. Run "npx braintracker enable" to re-enable.');
|
|
@@ -83,11 +98,15 @@ function enable() {
|
|
|
83
98
|
}
|
|
84
99
|
|
|
85
100
|
const settings = readSettings();
|
|
86
|
-
settings.hooks
|
|
87
|
-
settings.hooks.PreToolUse
|
|
88
|
-
settings.hooks.PostToolUse
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
settings.hooks = settings.hooks || {};
|
|
102
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
103
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
104
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || [];
|
|
105
|
+
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
106
|
+
addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
|
|
107
|
+
addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
|
|
108
|
+
addHook(settings.hooks.UserPromptSubmit, null, PRE_PROMPT_CMD);
|
|
109
|
+
addHook(settings.hooks.Stop, null, POST_PROMPT_CMD);
|
|
91
110
|
writeSettings(settings);
|
|
92
111
|
|
|
93
112
|
console.log('braintracker enabled.');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
let raw = '';
|
|
7
|
+
process.stdin.on('data', chunk => (raw += chunk));
|
|
8
|
+
process.stdin.on('end', () => {
|
|
9
|
+
let payload;
|
|
10
|
+
try {
|
|
11
|
+
payload = JSON.parse(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sessionId = payload.session_id;
|
|
17
|
+
const prj = path.basename(process.cwd());
|
|
18
|
+
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
|
|
19
|
+
const preFile = path.join(cacheDir, `${sessionId}.prompt.pre.json`);
|
|
20
|
+
|
|
21
|
+
let preData;
|
|
22
|
+
try {
|
|
23
|
+
preData = JSON.parse(fs.readFileSync(preFile, 'utf8'));
|
|
24
|
+
} catch {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entry = {
|
|
29
|
+
type: 'skill_invocation',
|
|
30
|
+
session_id: sessionId,
|
|
31
|
+
skill: preData.skill,
|
|
32
|
+
triggered_by: preData.triggered_by,
|
|
33
|
+
started_at: new Date(preData.start_time).toISOString(),
|
|
34
|
+
duration_seconds: (Date.now() - preData.start_time) / 1000,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const logFile = path.join(cacheDir, `${sessionId}.jsonl`);
|
|
38
|
+
fs.appendFileSync(logFile, JSON.stringify(entry) + '\n');
|
|
39
|
+
fs.unlinkSync(preFile);
|
|
40
|
+
|
|
41
|
+
process.exit(0);
|
|
42
|
+
});
|
|
@@ -17,7 +17,7 @@ process.stdin.on('end', () => {
|
|
|
17
17
|
|
|
18
18
|
const sessionId = payload.session_id;
|
|
19
19
|
const prj = path.basename(process.cwd());
|
|
20
|
-
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', '
|
|
20
|
+
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
|
|
21
21
|
const preFile = path.join(cacheDir, `${sessionId}.pre.json`);
|
|
22
22
|
|
|
23
23
|
let preData;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
let raw = '';
|
|
7
|
+
process.stdin.on('data', chunk => (raw += chunk));
|
|
8
|
+
process.stdin.on('end', () => {
|
|
9
|
+
let payload;
|
|
10
|
+
try {
|
|
11
|
+
payload = JSON.parse(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const prompt = (payload.prompt || '').trim();
|
|
17
|
+
const match = prompt.match(/^\/(\w[\w-]*)/);
|
|
18
|
+
if (!match) process.exit(0);
|
|
19
|
+
|
|
20
|
+
const sessionId = payload.session_id;
|
|
21
|
+
const skill = match[1];
|
|
22
|
+
const triggeredBy = prompt.slice(match[0].length).trim();
|
|
23
|
+
const prj = path.basename(process.cwd());
|
|
24
|
+
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
|
|
25
|
+
|
|
26
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const preFile = path.join(cacheDir, `${sessionId}.prompt.pre.json`);
|
|
29
|
+
fs.writeFileSync(preFile, JSON.stringify({
|
|
30
|
+
session_id: sessionId,
|
|
31
|
+
skill,
|
|
32
|
+
triggered_by: triggeredBy,
|
|
33
|
+
start_time: Date.now(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
process.exit(0);
|
|
37
|
+
});
|
|
@@ -19,7 +19,7 @@ process.stdin.on('end', () => {
|
|
|
19
19
|
const skill = (payload.tool_input && payload.tool_input.skill) || 'unknown';
|
|
20
20
|
const triggeredBy = (payload.tool_input && payload.tool_input.args) || '';
|
|
21
21
|
const prj = path.basename(process.cwd());
|
|
22
|
-
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', '
|
|
22
|
+
const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
|
|
23
23
|
|
|
24
24
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
25
25
|
|