@hasna/hooks 0.2.6 → 0.2.7

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/dist/index.js CHANGED
@@ -412,7 +412,7 @@ var HOOKS = [
412
412
  {
413
413
  name: "sessionlog",
414
414
  displayName: "Session Log",
415
- description: "Logs every tool call to .claude/session-log-<date>.jsonl",
415
+ description: "Logs every tool call to SQLite (~/.hooks/hooks.db)",
416
416
  version: "0.1.0",
417
417
  category: "Observability",
418
418
  event: "PostToolUse",
@@ -422,7 +422,7 @@ var HOOKS = [
422
422
  {
423
423
  name: "commandlog",
424
424
  displayName: "Command Log",
425
- description: "Logs every bash command Claude runs to .claude/commands.log",
425
+ description: "Logs every Bash command to SQLite (~/.hooks/hooks.db)",
426
426
  version: "0.1.0",
427
427
  category: "Observability",
428
428
  event: "PostToolUse",
@@ -432,7 +432,7 @@ var HOOKS = [
432
432
  {
433
433
  name: "costwatch",
434
434
  displayName: "Cost Watch",
435
- description: "Estimates session token usage and warns when budget threshold is exceeded",
435
+ description: "Estimates session token usage, persists cost history to SQLite, and warns on budget overrun",
436
436
  version: "0.1.0",
437
437
  category: "Observability",
438
438
  event: "Stop",
@@ -442,7 +442,7 @@ var HOOKS = [
442
442
  {
443
443
  name: "errornotify",
444
444
  displayName: "Error Notify",
445
- description: "Detects tool failures and logs errors to .claude/errors.log",
445
+ description: "Detects tool failures and logs errors to SQLite (~/.hooks/hooks.db)",
446
446
  version: "0.1.0",
447
447
  category: "Observability",
448
448
  event: "PostToolUse",
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI for hook-agentmessages
4
+ *
5
+ * Usage:
6
+ * hook-agentmessages install - Install hooks into Claude Code
7
+ * hook-agentmessages uninstall - Remove hooks from Claude Code
8
+ * hook-agentmessages status - Show hook status
9
+ */
10
+
11
+ import { homedir } from 'os';
12
+ import { join } from 'path';
13
+
14
+ const CLAUDE_SETTINGS_FILE = join(homedir(), '.claude', 'settings.json');
15
+
16
+ const args = process.argv.slice(2);
17
+ const command = args[0];
18
+
19
+ async function showStatus() {
20
+ console.log('hook-agentmessages status\n');
21
+
22
+ // Check if hooks are installed
23
+ try {
24
+ const file = Bun.file(CLAUDE_SETTINGS_FILE);
25
+ if (await file.exists()) {
26
+ const settings = await file.json();
27
+
28
+ if (settings.hooks) {
29
+ let installed = false;
30
+
31
+ if (settings.hooks.SessionStart) {
32
+ const hasHook = settings.hooks.SessionStart.some((h: any) =>
33
+ h.hooks?.some((hook: any) => hook.command?.includes('hook-agentmessages'))
34
+ );
35
+ if (hasHook) {
36
+ console.log(' SessionStart hook: installed');
37
+ installed = true;
38
+ }
39
+ }
40
+
41
+ if (settings.hooks.Stop) {
42
+ const hasHook = settings.hooks.Stop.some((h: any) =>
43
+ h.hooks?.some((hook: any) => hook.command?.includes('hook-agentmessages'))
44
+ );
45
+ if (hasHook) {
46
+ console.log(' Stop hook: installed');
47
+ installed = true;
48
+ }
49
+ }
50
+
51
+ if (!installed) {
52
+ console.log(' No hook-agentmessages hooks installed');
53
+ }
54
+ } else {
55
+ console.log(' No hooks configured in Claude Code');
56
+ }
57
+ } else {
58
+ console.log(' Claude Code settings not found');
59
+ }
60
+ } catch (err) {
61
+ console.log(' Error reading settings:', (err as Error).message);
62
+ }
63
+
64
+ // Check service-message status
65
+ const serviceDir = join(homedir(), '.service', 'service-message');
66
+ const configFile = Bun.file(join(serviceDir, 'config.json'));
67
+
68
+ console.log('\nservice-message integration:');
69
+ if (await configFile.exists()) {
70
+ const config = await configFile.json();
71
+ console.log(` Agent ID: ${config.agentId || 'not set'}`);
72
+ console.log(` Data dir: ${serviceDir}`);
73
+ } else {
74
+ console.log(' Not configured');
75
+ }
76
+ }
77
+
78
+ function showHelp() {
79
+ console.log(`
80
+ hook-agentmessages - Claude Code hook for service-message integration
81
+
82
+ Usage:
83
+ hook-agentmessages <command>
84
+
85
+ Commands:
86
+ install Install hooks into Claude Code settings
87
+ uninstall Remove hooks from Claude Code settings
88
+ status Show current hook status
89
+
90
+ Examples:
91
+ hook-agentmessages install
92
+ hook-agentmessages status
93
+ `);
94
+ }
95
+
96
+ async function main() {
97
+ if (!command || command === '--help' || command === '-h') {
98
+ showHelp();
99
+ return;
100
+ }
101
+
102
+ switch (command) {
103
+ case 'install':
104
+ await import('../src/install.ts');
105
+ break;
106
+
107
+ case 'uninstall':
108
+ await import('../src/uninstall.ts');
109
+ break;
110
+
111
+ case 'status':
112
+ await showStatus();
113
+ break;
114
+
115
+ default:
116
+ console.error(`Unknown command: ${command}`);
117
+ showHelp();
118
+ process.exit(1);
119
+ }
120
+ }
121
+
122
+ main().catch((err) => {
123
+ console.error('Error:', err.message);
124
+ process.exit(1);
125
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "@hasnaxyz/hook-checkfiles",
7
+ "devDependencies": {
8
+ "@types/bun": "^1.3.8",
9
+ "@types/node": "^20",
10
+ "typescript": "^5.0.0",
11
+ },
12
+ },
13
+ },
14
+ "packages": {
15
+ "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
16
+
17
+ "@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="],
18
+
19
+ "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
20
+
21
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
22
+
23
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
24
+ }
25
+ }
@@ -3,15 +3,11 @@
3
3
  /**
4
4
  * Claude Code Hook: commandlog
5
5
  *
6
- * PostToolUse hook that logs every bash command Claude runs to
7
- * .claude/commands.log in the project directory.
8
- *
9
- * Format: [ISO timestamp] <exit_code> <command>
10
- * One command per line.
6
+ * PostToolUse hook that logs every Bash command to SQLite (~/.hooks/hooks.db).
11
7
  */
12
8
 
13
- import { readFileSync, existsSync, mkdirSync, appendFileSync } from "fs";
14
- import { join } from "path";
9
+ import { readFileSync } from "fs";
10
+ import { writeHookEvent } from "../../../src/lib/db-writer";
15
11
 
16
12
  interface HookInput {
17
13
  session_id: string;
@@ -38,31 +34,6 @@ function respond(output: HookOutput): void {
38
34
  console.log(JSON.stringify(output));
39
35
  }
40
36
 
41
- function logCommand(input: HookInput): void {
42
- const claudeDir = join(input.cwd, ".claude");
43
-
44
- // Create .claude/ directory if it doesn't exist
45
- if (!existsSync(claudeDir)) {
46
- mkdirSync(claudeDir, { recursive: true });
47
- }
48
-
49
- const logFile = join(claudeDir, "commands.log");
50
- const timestamp = new Date().toISOString();
51
- const command = (input.tool_input.command as string) || "(unknown command)";
52
- const exitCode = input.tool_input.exit_code;
53
-
54
- // Format: [timestamp] exit_code command
55
- // If exit_code is available, include it; otherwise just log the command
56
- let logLine: string;
57
- if (exitCode !== undefined && exitCode !== null) {
58
- logLine = `[${timestamp}] exit=${exitCode} ${command}\n`;
59
- } else {
60
- logLine = `[${timestamp}] ${command}\n`;
61
- }
62
-
63
- appendFileSync(logFile, logLine);
64
- }
65
-
66
37
  export function run(): void {
67
38
  const input = readStdinJson();
68
39
 
@@ -77,12 +48,18 @@ export function run(): void {
77
48
  return;
78
49
  }
79
50
 
80
- try {
81
- logCommand(input);
82
- } catch (error) {
83
- const errMsg = error instanceof Error ? error.message : String(error);
84
- console.error(`[hook-commandlog] Warning: failed to log command: ${errMsg}`);
85
- }
51
+ const command = (input.tool_input.command as string) || "(unknown command)";
52
+ const exitCode = input.tool_input.exit_code;
53
+
54
+ writeHookEvent({
55
+ session_id: input.session_id,
56
+ hook_name: "commandlog",
57
+ event_type: "PostToolUse",
58
+ tool_name: "Bash",
59
+ tool_input: command,
60
+ project_dir: input.cwd,
61
+ metadata: exitCode !== undefined && exitCode !== null ? JSON.stringify({ exit_code: exitCode }) : null,
62
+ });
86
63
 
87
64
  respond({ continue: true });
88
65
  }
@@ -3,24 +3,19 @@
3
3
  /**
4
4
  * Claude Code Hook: costwatch
5
5
  *
6
- * Stop hook that estimates session token usage and warns if a budget
7
- * threshold is exceeded.
6
+ * Stop hook that estimates session token usage and persists cost data to
7
+ * SQLite (~/.hooks/hooks.db) for cross-session history queries.
8
8
  *
9
9
  * Configuration:
10
10
  * - Environment variable: COST_WATCH_BUDGET (max $ per session, e.g. "5.00")
11
11
  * - If not set, no budget enforcement (just logs a reminder)
12
12
  *
13
- * Token estimation is rough:
14
- * - ~4 characters per token (English text average)
15
- * - Claude Opus pricing: ~$15/M input tokens, ~$75/M output tokens
16
- * - We estimate a blended rate of ~$30/M tokens for simplicity
17
- *
18
- * Since the Stop event provides limited session info, this hook
19
- * primarily serves as a reminder to check actual usage.
13
+ * Token estimation: ~4 chars/token, blended rate ~$30/M tokens.
20
14
  */
21
15
 
22
16
  import { readFileSync, existsSync, readdirSync, statSync } from "fs";
23
17
  import { join } from "path";
18
+ import { writeHookEvent } from "../../../src/lib/db-writer";
24
19
 
25
20
  interface HookInput {
26
21
  session_id: string;
@@ -33,10 +28,7 @@ interface HookOutput {
33
28
  continue: boolean;
34
29
  }
35
30
 
36
- /** Approximate cost per million tokens (blended input/output estimate) */
37
31
  const BLENDED_COST_PER_MILLION_TOKENS = 30;
38
-
39
- /** Average characters per token */
40
32
  const CHARS_PER_TOKEN = 4;
41
33
 
42
34
  function readStdinJson(): HookInput | null {
@@ -59,8 +51,8 @@ function getBudget(): number | null {
59
51
 
60
52
  const budget = parseFloat(budgetStr);
61
53
  if (isNaN(budget) || budget <= 0) {
62
- console.error(
63
- `[hook-costwatch] Invalid COST_WATCH_BUDGET value: "${budgetStr}". Must be a positive number.`
54
+ process.stderr.write(
55
+ `[hook-costwatch] Invalid COST_WATCH_BUDGET value: "${budgetStr}". Must be a positive number.\n`
64
56
  );
65
57
  return null;
66
58
  }
@@ -74,12 +66,10 @@ function estimateTranscriptCost(transcriptPath: string): {
74
66
  } | null {
75
67
  try {
76
68
  if (!existsSync(transcriptPath)) return null;
77
-
78
69
  const stat = statSync(transcriptPath);
79
70
  const charCount = stat.size;
80
71
  const estimatedTokens = Math.ceil(charCount / CHARS_PER_TOKEN);
81
72
  const estimatedCost = (estimatedTokens / 1_000_000) * BLENDED_COST_PER_MILLION_TOKENS;
82
-
83
73
  return { charCount, estimatedTokens, estimatedCost };
84
74
  } catch {
85
75
  return null;
@@ -87,7 +77,6 @@ function estimateTranscriptCost(transcriptPath: string): {
87
77
  }
88
78
 
89
79
  function findSessionTranscript(cwd: string, sessionId: string): string | null {
90
- // Check common transcript locations
91
80
  const possibleDirs = [
92
81
  join(cwd, ".claude"),
93
82
  join(process.env.HOME || "", ".claude", "projects"),
@@ -98,9 +87,8 @@ function findSessionTranscript(cwd: string, sessionId: string): string | null {
98
87
  try {
99
88
  const files = readdirSync(dir, { recursive: true }) as string[];
100
89
  for (const file of files) {
101
- const filePath = join(dir, file);
102
90
  if (typeof file === "string" && file.includes(sessionId)) {
103
- return filePath;
91
+ return join(dir, file);
104
92
  }
105
93
  }
106
94
  } catch {
@@ -121,19 +109,13 @@ export function run(): void {
121
109
 
122
110
  const budget = getBudget();
123
111
 
124
- // Try to estimate cost from transcript
125
- let estimate: {
126
- charCount: number;
127
- estimatedTokens: number;
128
- estimatedCost: number;
129
- } | null = null;
112
+ let estimate: { charCount: number; estimatedTokens: number; estimatedCost: number } | null = null;
130
113
 
131
114
  if (input.transcript_path) {
132
115
  estimate = estimateTranscriptCost(input.transcript_path);
133
116
  }
134
117
 
135
118
  if (!estimate) {
136
- // Try to find transcript by session ID
137
119
  const transcriptPath = findSessionTranscript(input.cwd, input.session_id);
138
120
  if (transcriptPath) {
139
121
  estimate = estimateTranscriptCost(transcriptPath);
@@ -143,31 +125,46 @@ export function run(): void {
143
125
  if (estimate) {
144
126
  const costStr = estimate.estimatedCost.toFixed(2);
145
127
  const tokensStr = (estimate.estimatedTokens / 1000).toFixed(1);
128
+ const budgetExceeded = budget !== null && estimate.estimatedCost > budget;
146
129
 
147
- console.error(`[hook-costwatch] Session estimate: ~${tokensStr}K tokens, ~$${costStr}`);
130
+ process.stderr.write(`[hook-costwatch] Session estimate: ~${tokensStr}K tokens, ~$${costStr}\n`);
148
131
 
149
- if (budget !== null && estimate.estimatedCost > budget) {
150
- console.error(
151
- `[hook-costwatch] WARNING: Estimated cost ($${costStr}) exceeds budget ($${budget.toFixed(2)})!`
152
- );
153
- console.error(
154
- `[hook-costwatch] Check your actual usage at https://console.anthropic.com/`
132
+ if (budgetExceeded) {
133
+ process.stderr.write(
134
+ `[hook-costwatch] WARNING: Estimated cost ($${costStr}) exceeds budget ($${budget!.toFixed(2)})!\n`
155
135
  );
136
+ process.stderr.write(`[hook-costwatch] Check your actual usage at https://console.anthropic.com/\n`);
156
137
  }
138
+
139
+ writeHookEvent({
140
+ session_id: input.session_id,
141
+ hook_name: "costwatch",
142
+ event_type: "Stop",
143
+ project_dir: input.cwd,
144
+ metadata: JSON.stringify({
145
+ char_count: estimate.charCount,
146
+ estimated_tokens: estimate.estimatedTokens,
147
+ estimated_cost_usd: estimate.estimatedCost,
148
+ budget_usd: budget,
149
+ budget_exceeded: budgetExceeded,
150
+ }),
151
+ });
157
152
  } else {
158
- console.error(
159
- `[hook-costwatch] Could not estimate session cost (no transcript found).`
160
- );
153
+ process.stderr.write(`[hook-costwatch] Could not estimate session cost (no transcript found).\n`);
154
+
155
+ writeHookEvent({
156
+ session_id: input.session_id,
157
+ hook_name: "costwatch",
158
+ event_type: "Stop",
159
+ project_dir: input.cwd,
160
+ metadata: JSON.stringify({ error: "no_transcript", budget_usd: budget }),
161
+ });
161
162
  }
162
163
 
163
164
  if (budget !== null) {
164
- console.error(
165
- `[hook-costwatch] Budget: $${budget.toFixed(2)}/session. Remember to check actual usage.`
166
- );
165
+ process.stderr.write(`[hook-costwatch] Budget: $${budget.toFixed(2)}/session. Remember to check actual usage.\n`);
167
166
  } else {
168
- console.error(
169
- `[hook-costwatch] No budget set. Set COST_WATCH_BUDGET env var to enable budget warnings.`
170
- );
167
+ process.stderr.write(`[hook-costwatch] No budget set. Set COST_WATCH_BUDGET env var to enable budget warnings.\n`);
171
168
  }
172
169
 
173
170
  respond({ continue: true });
@@ -3,16 +3,13 @@
3
3
  /**
4
4
  * Claude Code Hook: errornotify
5
5
  *
6
- * PostToolUse hook that detects tool failures and logs errors.
7
- * Checks tool output for error indicators (non-zero exit codes,
8
- * error messages) and logs warnings to stderr. Optionally writes
9
- * to a .claude/errors.log file for persistent error tracking.
10
- *
6
+ * PostToolUse hook that detects tool failures and logs errors to SQLite (~/.hooks/hooks.db).
7
+ * Also writes warnings to stderr for immediate terminal visibility.
11
8
  * Never blocks — always outputs { continue: true }.
12
9
  */
13
10
 
14
- import { readFileSync, appendFileSync, mkdirSync, existsSync } from "fs";
15
- import { join } from "path";
11
+ import { readFileSync } from "fs";
12
+ import { writeHookEvent } from "../../../src/lib/db-writer";
16
13
 
17
14
  interface HookInput {
18
15
  session_id: string;
@@ -26,9 +23,6 @@ interface HookOutput {
26
23
  continue: true;
27
24
  }
28
25
 
29
- /**
30
- * Read and parse JSON from stdin
31
- */
32
26
  function readStdinJson(): HookInput | null {
33
27
  try {
34
28
  const input = readFileSync(0, "utf-8").trim();
@@ -39,31 +33,19 @@ function readStdinJson(): HookInput | null {
39
33
  }
40
34
  }
41
35
 
42
- /**
43
- * Check if the tool output indicates a failure
44
- */
45
36
  function detectError(input: HookInput): { isError: boolean; message: string } {
46
37
  const output = input.tool_output || {};
47
38
 
48
- // Check for explicit exit code
49
39
  const exitCode = output.exit_code ?? output.exitCode ?? output.code;
50
40
  if (exitCode !== undefined && exitCode !== null && exitCode !== 0) {
51
41
  const stderr = (output.stderr as string) || (output.output as string) || "unknown error";
52
- return {
53
- isError: true,
54
- message: `Exit code ${exitCode}: ${truncate(stderr, 200)}`,
55
- };
42
+ return { isError: true, message: `Exit code ${exitCode}: ${truncate(stderr, 200)}` };
56
43
  }
57
44
 
58
- // Check for error field
59
45
  if (output.error && typeof output.error === "string") {
60
- return {
61
- isError: true,
62
- message: `Error: ${truncate(output.error, 200)}`,
63
- };
46
+ return { isError: true, message: `Error: ${truncate(output.error, 200)}` };
64
47
  }
65
48
 
66
- // Check output text for common error indicators
67
49
  const outputText =
68
50
  (output.stderr as string) ||
69
51
  (output.output as string) ||
@@ -72,7 +54,6 @@ function detectError(input: HookInput): { isError: boolean; message: string } {
72
54
  "";
73
55
 
74
56
  if (typeof outputText === "string" && outputText.length > 0) {
75
- // Check for common error patterns in output
76
57
  const errorPatterns = [
77
58
  /^error:/im,
78
59
  /^fatal:/im,
@@ -98,13 +79,9 @@ function detectError(input: HookInput): { isError: boolean; message: string } {
98
79
 
99
80
  for (const pattern of errorPatterns) {
100
81
  if (pattern.test(outputText)) {
101
- // Extract the first relevant line
102
82
  const lines = outputText.split("\n").filter((l: string) => l.trim());
103
83
  const errorLine = lines.find((l: string) => pattern.test(l)) || lines[0] || "";
104
- return {
105
- isError: true,
106
- message: truncate(errorLine.trim(), 200),
107
- };
84
+ return { isError: true, message: truncate(errorLine.trim(), 200) };
108
85
  }
109
86
  }
110
87
  }
@@ -112,17 +89,11 @@ function detectError(input: HookInput): { isError: boolean; message: string } {
112
89
  return { isError: false, message: "" };
113
90
  }
114
91
 
115
- /**
116
- * Truncate a string to a maximum length
117
- */
118
92
  function truncate(str: string, maxLen: number): string {
119
93
  if (str.length <= maxLen) return str;
120
94
  return str.slice(0, maxLen) + "...";
121
95
  }
122
96
 
123
- /**
124
- * Get a human-readable description of what was being executed
125
- */
126
97
  function getToolContext(input: HookInput): string {
127
98
  const toolName = input.tool_name;
128
99
  const toolInput = input.tool_input || {};
@@ -140,36 +111,11 @@ function getToolContext(input: HookInput): string {
140
111
  }
141
112
  }
142
113
 
143
- /**
144
- * Write error to .claude/errors.log
145
- */
146
- function writeErrorLog(cwd: string, toolContext: string, errorMessage: string, sessionId: string): void {
147
- try {
148
- const claudeDir = join(cwd, ".claude");
149
- if (!existsSync(claudeDir)) {
150
- mkdirSync(claudeDir, { recursive: true });
151
- }
152
-
153
- const logFile = join(claudeDir, "errors.log");
154
- const timestamp = new Date().toISOString();
155
- const entry = `[${timestamp}] [session:${sessionId.slice(0, 8)}] ${toolContext} — ${errorMessage}\n`;
156
- appendFileSync(logFile, entry);
157
- } catch {
158
- // Silently fail — logging should never cause issues
159
- }
160
- }
161
-
162
- /**
163
- * Output hook response
164
- */
165
114
  function respond(): void {
166
115
  const output: HookOutput = { continue: true };
167
116
  console.log(JSON.stringify(output));
168
117
  }
169
118
 
170
- /**
171
- * Main hook execution
172
- */
173
119
  export function run(): void {
174
120
  const input = readStdinJson();
175
121
 
@@ -182,11 +128,20 @@ export function run(): void {
182
128
 
183
129
  if (isError) {
184
130
  const toolContext = getToolContext(input);
185
- console.error(`[hook-errornotify] FAILURE in ${toolContext}`);
186
- console.error(`[hook-errornotify] ${message}`);
187
131
 
188
- // Write to persistent error log
189
- writeErrorLog(input.cwd, toolContext, message, input.session_id);
132
+ // Keep stderr warnings for immediate terminal visibility
133
+ process.stderr.write(`[hook-errornotify] FAILURE in ${toolContext}\n`);
134
+ process.stderr.write(`[hook-errornotify] ${message}\n`);
135
+
136
+ writeHookEvent({
137
+ session_id: input.session_id,
138
+ hook_name: "errornotify",
139
+ event_type: "PostToolUse",
140
+ tool_name: input.tool_name,
141
+ tool_input: JSON.stringify(input.tool_input),
142
+ error: message,
143
+ project_dir: input.cwd,
144
+ });
190
145
  }
191
146
 
192
147
  respond();
@@ -3,18 +3,11 @@
3
3
  /**
4
4
  * Claude Code Hook: sessionlog
5
5
  *
6
- * PostToolUse hook that logs every tool call to a session log file.
7
- * Creates .claude/session-log-<date>.jsonl in the project directory.
8
- *
9
- * Each line is a JSON object with:
10
- * - timestamp: ISO string
11
- * - tool_name: name of the tool that was called
12
- * - tool_input: first 500 characters of the stringified tool input
13
- * - session_id: current session ID
6
+ * PostToolUse hook that logs every tool call to SQLite (~/.hooks/hooks.db).
14
7
  */
15
8
 
16
- import { readFileSync, existsSync, mkdirSync, appendFileSync } from "fs";
17
- import { join } from "path";
9
+ import { readFileSync } from "fs";
10
+ import { writeHookEvent } from "../../../src/lib/db-writer";
18
11
 
19
12
  interface HookInput {
20
13
  session_id: string;
@@ -41,42 +34,6 @@ function respond(output: HookOutput): void {
41
34
  console.log(JSON.stringify(output));
42
35
  }
43
36
 
44
- function getDateString(): string {
45
- const now = new Date();
46
- const year = now.getFullYear();
47
- const month = String(now.getMonth() + 1).padStart(2, "0");
48
- const day = String(now.getDate()).padStart(2, "0");
49
- return `${year}-${month}-${day}`;
50
- }
51
-
52
- function truncate(str: string, maxLength: number): string {
53
- if (str.length <= maxLength) return str;
54
- return str.slice(0, maxLength) + "...";
55
- }
56
-
57
- function logToolCall(input: HookInput): void {
58
- const claudeDir = join(input.cwd, ".claude");
59
-
60
- // Create .claude/ directory if it doesn't exist
61
- if (!existsSync(claudeDir)) {
62
- mkdirSync(claudeDir, { recursive: true });
63
- }
64
-
65
- const dateStr = getDateString();
66
- const logFile = join(claudeDir, `session-log-${dateStr}.jsonl`);
67
-
68
- const toolInputStr = truncate(JSON.stringify(input.tool_input), 500);
69
-
70
- const logEntry = {
71
- timestamp: new Date().toISOString(),
72
- tool_name: input.tool_name,
73
- tool_input: toolInputStr,
74
- session_id: input.session_id,
75
- };
76
-
77
- appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
78
- }
79
-
80
37
  export function run(): void {
81
38
  const input = readStdinJson();
82
39
 
@@ -85,12 +42,14 @@ export function run(): void {
85
42
  return;
86
43
  }
87
44
 
88
- try {
89
- logToolCall(input);
90
- } catch (error) {
91
- const errMsg = error instanceof Error ? error.message : String(error);
92
- console.error(`[hook-sessionlog] Warning: failed to log tool call: ${errMsg}`);
93
- }
45
+ writeHookEvent({
46
+ session_id: input.session_id,
47
+ hook_name: "sessionlog",
48
+ event_type: "PostToolUse",
49
+ tool_name: input.tool_name,
50
+ tool_input: JSON.stringify(input.tool_input),
51
+ project_dir: input.cwd,
52
+ });
94
53
 
95
54
  respond({ continue: true });
96
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/hooks",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command",
5
5
  "type": "module",
6
6
  "bin": {