braintracker 1.0.1 → 1.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/README.md CHANGED
@@ -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 to `<session>.jsonl`, deletes the snapshot |
69
+ | `PostToolUse` (Skill) | `post_skill.js` | Reads the snapshot, computes duration, appends a log entry, deletes the snapshot |
59
70
 
60
71
  Logs live at `~/.claude/plugin/cache/braintracker/<project>/` — one JSONL file per session, grouped by project name.
@@ -6,8 +6,12 @@ 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 = `node ${path.join(DEST_HOOKS, 'pre_skill.js')}`;
10
- const POST_CMD = `node ${path.join(DEST_HOOKS, 'post_skill.js')}`;
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')}`;
13
+ const PRE_TURN_CMD = `node ${path.join(DEST_HOOKS, 'pre_turn.js')}`;
14
+ const POST_TURN_CMD = `node ${path.join(DEST_HOOKS, 'post_turn.js')}`;
11
15
 
12
16
  function readSettings() {
13
17
  try { return JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); } catch { return {}; }
@@ -19,8 +23,13 @@ function writeSettings(settings) {
19
23
  }
20
24
 
21
25
  function addHook(arr, matcher, command) {
22
- let group = arr.find(h => h.matcher === matcher);
23
- if (!group) { group = { matcher, hooks: [] }; arr.push(group); }
26
+ let group = matcher != null
27
+ ? arr.find(h => h.matcher === matcher)
28
+ : arr.find(h => h.matcher == null);
29
+ if (!group) {
30
+ group = matcher != null ? { matcher, hooks: [] } : { hooks: [] };
31
+ arr.push(group);
32
+ }
24
33
  group.hooks = group.hooks || [];
25
34
  if (!group.hooks.some(h => h.command === command)) {
26
35
  group.hooks.push({ type: 'command', command });
@@ -37,16 +46,22 @@ function removeHook(arr, command) {
37
46
  function install() {
38
47
  const SRC_HOOKS = path.join(__dirname, '..', 'braintracker', 'hooks');
39
48
  fs.mkdirSync(DEST_HOOKS, { recursive: true });
40
- for (const file of ['pre_skill.js', 'post_skill.js']) {
49
+ for (const file of ['pre_skill.js', 'post_skill.js', 'pre_prompt.js', 'post_prompt.js', 'pre_turn.js', 'post_turn.js']) {
41
50
  fs.copyFileSync(path.join(SRC_HOOKS, file), path.join(DEST_HOOKS, file));
42
51
  }
43
52
 
44
53
  const settings = readSettings();
45
- settings.hooks = settings.hooks || {};
46
- settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
47
- settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
48
- addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
49
- addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
54
+ settings.hooks = settings.hooks || {};
55
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
56
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
57
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || [];
58
+ settings.hooks.Stop = settings.hooks.Stop || [];
59
+ addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
60
+ addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
61
+ addHook(settings.hooks.UserPromptSubmit, null, PRE_PROMPT_CMD);
62
+ addHook(settings.hooks.UserPromptSubmit, null, PRE_TURN_CMD);
63
+ addHook(settings.hooks.Stop, null, POST_PROMPT_CMD);
64
+ addHook(settings.hooks.Stop, null, POST_TURN_CMD);
50
65
  writeSettings(settings);
51
66
 
52
67
  console.log('braintracker installed.');
@@ -58,8 +73,12 @@ function uninstall() {
58
73
 
59
74
  const settings = readSettings();
60
75
  if (settings.hooks) {
61
- settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
62
- settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
76
+ settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
77
+ settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
78
+ settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_PROMPT_CMD);
79
+ settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_TURN_CMD);
80
+ settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_PROMPT_CMD);
81
+ settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_TURN_CMD);
63
82
  }
64
83
  writeSettings(settings);
65
84
 
@@ -69,8 +88,12 @@ function uninstall() {
69
88
  function disable() {
70
89
  const settings = readSettings();
71
90
  if (settings.hooks) {
72
- settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
73
- settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
91
+ settings.hooks.PreToolUse = removeHook(settings.hooks.PreToolUse, PRE_CMD);
92
+ settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
93
+ settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_PROMPT_CMD);
94
+ settings.hooks.UserPromptSubmit = removeHook(settings.hooks.UserPromptSubmit, PRE_TURN_CMD);
95
+ settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_PROMPT_CMD);
96
+ settings.hooks.Stop = removeHook(settings.hooks.Stop, POST_TURN_CMD);
74
97
  }
75
98
  writeSettings(settings);
76
99
  console.log('braintracker disabled. Hook files kept. Run "npx braintracker enable" to re-enable.');
@@ -83,11 +106,17 @@ function enable() {
83
106
  }
84
107
 
85
108
  const settings = readSettings();
86
- settings.hooks = settings.hooks || {};
87
- settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
88
- settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
89
- addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
90
- addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
109
+ settings.hooks = settings.hooks || {};
110
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
111
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
112
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || [];
113
+ settings.hooks.Stop = settings.hooks.Stop || [];
114
+ addHook(settings.hooks.PreToolUse, 'Skill', PRE_CMD);
115
+ addHook(settings.hooks.PostToolUse, 'Skill', POST_CMD);
116
+ addHook(settings.hooks.UserPromptSubmit, null, PRE_PROMPT_CMD);
117
+ addHook(settings.hooks.UserPromptSubmit, null, PRE_TURN_CMD);
118
+ addHook(settings.hooks.Stop, null, POST_PROMPT_CMD);
119
+ addHook(settings.hooks.Stop, null, POST_TURN_CMD);
91
120
  writeSettings(settings);
92
121
 
93
122
  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
+ });
@@ -0,0 +1,46 @@
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 { payload = JSON.parse(raw); } catch { process.exit(0); }
11
+
12
+ const sessionId = payload.session_id;
13
+ const prj = path.basename(process.cwd());
14
+ const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
15
+ const preFile = path.join(cacheDir, `${sessionId}.turn.pre.json`);
16
+
17
+ let preData;
18
+ try { preData = JSON.parse(fs.readFileSync(preFile, 'utf8')); } catch { process.exit(0); }
19
+
20
+ const transcript = payload.transcript || [];
21
+ const lastAssistant = [...transcript].reverse().find(m => m.role === 'assistant');
22
+ let assistantResponse = '';
23
+ if (lastAssistant) {
24
+ const c = lastAssistant.content;
25
+ if (typeof c === 'string') {
26
+ assistantResponse = c;
27
+ } else if (Array.isArray(c)) {
28
+ assistantResponse = c.filter(b => b.type === 'text').map(b => b.text).join('');
29
+ }
30
+ }
31
+
32
+ const entry = {
33
+ type: 'conversation_turn',
34
+ session_id: sessionId,
35
+ user_prompt: preData.prompt,
36
+ assistant_response: assistantResponse,
37
+ started_at: new Date(preData.start_time).toISOString(),
38
+ duration_seconds: (Date.now() - preData.start_time) / 1000,
39
+ };
40
+
41
+ const logFile = path.join(cacheDir, `${sessionId}.jsonl`);
42
+ fs.appendFileSync(logFile, JSON.stringify(entry) + '\n');
43
+ fs.unlinkSync(preFile);
44
+
45
+ process.exit(0);
46
+ });
@@ -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
+ });
@@ -0,0 +1,27 @@
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 { payload = JSON.parse(raw); } catch { process.exit(0); }
11
+
12
+ const sessionId = payload.session_id;
13
+ const prompt = (payload.prompt || '').trim();
14
+ const prj = path.basename(process.cwd());
15
+ const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
16
+
17
+ fs.mkdirSync(cacheDir, { recursive: true });
18
+
19
+ const preFile = path.join(cacheDir, `${sessionId}.turn.pre.json`);
20
+ fs.writeFileSync(preFile, JSON.stringify({
21
+ session_id: sessionId,
22
+ prompt,
23
+ start_time: Date.now(),
24
+ }));
25
+
26
+ process.exit(0);
27
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braintracker",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Claude Code plugin that logs every skill invocation with duration and triggering prompt",
5
5
  "bin": {
6
6
  "braintracker": "bin/braintracker.js"