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/cctracking/<project>/<session_id>.jsonl
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 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
- Logs live at `~/.claude/plugin/cache/cctracking/<project>/` — one JSONL file per session, grouped by project name.
71
+ Logs live at `~/.claude/plugin/cache/braintracker/<project>/` — one JSONL file per session, grouped by project name.
@@ -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 = `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')}`;
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 = arr.find(h => h.matcher === matcher);
23
- if (!group) { group = { matcher, hooks: [] }; arr.push(group); }
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 = 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);
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/cctracking/<project>/<session>.jsonl');
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 = removeHook(settings.hooks.PreToolUse, PRE_CMD);
62
- settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
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 = removeHook(settings.hooks.PreToolUse, PRE_CMD);
73
- settings.hooks.PostToolUse = removeHook(settings.hooks.PostToolUse, POST_CMD);
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 = 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);
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', 'cctracking', prj);
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', 'cctracking', prj);
22
+ const cacheDir = path.join(os.homedir(), '.claude', 'plugin', 'cache', 'braintracker', prj);
23
23
 
24
24
  fs.mkdirSync(cacheDir, { recursive: true });
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braintracker",
3
- "version": "1.0.0",
3
+ "version": "1.1.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"