Skip to content

Commit 82eba8c

Browse files
Merge pull request #5 from johnlindquist/claude/2025-07-03-1751581918697
refactor: extract hook templates from init.ts to template files
2 parents 50f5b83 + 1324bbf commit 82eba8c

File tree

3 files changed

+96
-93
lines changed

3 files changed

+96
-93
lines changed

src/commands/init.ts

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -90,103 +90,15 @@ This command sets up basic Claude Code hooks in your project:
9090
}
9191

9292
private async generateHookFiles(): Promise<void> {
93-
// Copy lib.ts
93+
// Get templates directory path
9494
const distDir = path.dirname(fileURLToPath(import.meta.url))
9595
const rootDir = path.join(distDir, '..', '..')
9696
const templatesDir = path.join(rootDir, 'templates')
97-
await fs.copy(path.join(templatesDir, 'hooks', 'lib.ts'), '.claude/hooks/lib.ts')
9897

99-
// Add session.ts with the saveSessionData function
100-
const sessionContent = `import { mkdir } from 'node:fs/promises';
101-
import { writeFile, readFile } from 'node:fs/promises';
102-
import * as path from 'path';
103-
import { tmpdir } from 'node:os';
104-
105-
const SESSIONS_DIR = path.join(tmpdir(), 'claude-hooks-sessions');
106-
107-
export async function saveSessionData(hookType: string, payload: any): Promise<void> {
108-
try {
109-
// Ensure sessions directory exists
110-
await mkdir(SESSIONS_DIR, { recursive: true });
111-
112-
const timestamp = new Date().toISOString();
113-
const sessionFile = path.join(SESSIONS_DIR, \`\${payload.session_id}.json\`);
114-
115-
let sessionData: any[] = [];
116-
try {
117-
const existing = await readFile(sessionFile, 'utf-8');
118-
sessionData = JSON.parse(existing);
119-
} catch {
120-
// File doesn't exist yet
121-
}
122-
123-
sessionData.push({
124-
timestamp,
125-
hookType,
126-
payload
127-
});
128-
129-
await writeFile(sessionFile, JSON.stringify(sessionData, null, 2));
130-
} catch (error) {
131-
console.error('Failed to save session data:', error);
132-
}
133-
}
134-
`
135-
await fs.writeFile('.claude/hooks/session.ts', sessionContent)
136-
137-
// Generate default index.ts
138-
const indexContent = `#!/usr/bin/env bun
139-
140-
import { runHook, log, type PreToolUsePayload, type PostToolUsePayload, type NotificationPayload, type StopPayload, type HookResponse, type BashToolInput } from './lib';
141-
import { saveSessionData } from './session';
142-
143-
// PreToolUse handler - validate and potentially block dangerous commands
144-
async function preToolUse(payload: PreToolUsePayload): Promise<HookResponse> {
145-
// Save session data
146-
await saveSessionData('PreToolUse', payload);
147-
148-
// Example: Block dangerous commands
149-
if (payload.tool_name === 'Bash' && payload.tool_input && 'command' in payload.tool_input) {
150-
const bashInput = payload.tool_input as BashToolInput;
151-
const command = bashInput.command;
152-
153-
// Block rm -rf commands
154-
if (command && (command.includes('rm -rf /') || command.includes('rm -rf ~'))) {
155-
return {
156-
action: 'block',
157-
stopReason: 'Dangerous command detected: rm -rf on system directories'
158-
};
159-
}
160-
}
161-
162-
// Allow all other commands
163-
return { action: 'continue' };
164-
}
165-
166-
// PostToolUse handler - log tool results
167-
async function postToolUse(payload: PostToolUsePayload): Promise<void> {
168-
await saveSessionData('PostToolUse', payload);
169-
}
170-
171-
// Notification handler - log notifications
172-
async function notification(payload: NotificationPayload): Promise<void> {
173-
await saveSessionData('Notification', payload);
174-
}
175-
176-
// Stop handler - log session end
177-
async function stop(payload: StopPayload): Promise<void> {
178-
await saveSessionData('Stop', payload);
179-
}
180-
181-
// Run the hook with our handlers
182-
runHook({
183-
preToolUse,
184-
postToolUse,
185-
notification,
186-
stop
187-
});
188-
`
189-
await fs.writeFile('.claude/hooks/index.ts', indexContent)
98+
// Copy all hook template files
99+
await fs.copy(path.join(templatesDir, 'hooks', 'lib.ts'), '.claude/hooks/lib.ts')
100+
await fs.copy(path.join(templatesDir, 'hooks', 'session.ts'), '.claude/hooks/session.ts')
101+
await fs.copy(path.join(templatesDir, 'hooks', 'index.ts'), '.claude/hooks/index.ts')
190102
}
191103

192104
private async updateSettings(): Promise<void> {

templates/hooks/index.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env bun
2+
3+
import {
4+
type BashToolInput,
5+
type HookResponse,
6+
type NotificationPayload,
7+
type PostToolUsePayload,
8+
type PreToolUsePayload,
9+
runHook,
10+
type StopPayload,
11+
} from './lib'
12+
import {saveSessionData} from './session'
13+
14+
// PreToolUse handler - validate and potentially block dangerous commands
15+
async function preToolUse(payload: PreToolUsePayload): Promise<HookResponse> {
16+
// Save session data
17+
await saveSessionData('PreToolUse', payload)
18+
19+
// Example: Block dangerous commands
20+
if (payload.tool_name === 'Bash' && payload.tool_input && 'command' in payload.tool_input) {
21+
const bashInput = payload.tool_input as BashToolInput
22+
const command = bashInput.command
23+
24+
// Block rm -rf commands
25+
if (command && (command.includes('rm -rf /') || command.includes('rm -rf ~'))) {
26+
return {
27+
action: 'block',
28+
stopReason: 'Dangerous command detected: rm -rf on system directories',
29+
}
30+
}
31+
}
32+
33+
// Allow all other commands
34+
return {action: 'continue'}
35+
}
36+
37+
// PostToolUse handler - log tool results
38+
async function postToolUse(payload: PostToolUsePayload): Promise<void> {
39+
await saveSessionData('PostToolUse', payload)
40+
}
41+
42+
// Notification handler - log notifications
43+
async function notification(payload: NotificationPayload): Promise<void> {
44+
await saveSessionData('Notification', payload)
45+
}
46+
47+
// Stop handler - log session end
48+
async function stop(payload: StopPayload): Promise<void> {
49+
await saveSessionData('Stop', payload)
50+
}
51+
52+
// Run the hook with our handlers
53+
runHook({
54+
preToolUse,
55+
postToolUse,
56+
notification,
57+
stop,
58+
})

templates/hooks/session.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {mkdir, readFile, writeFile} from 'node:fs/promises'
2+
import {tmpdir} from 'node:os'
3+
import * as path from 'path'
4+
5+
const SESSIONS_DIR = path.join(tmpdir(), 'claude-hooks-sessions')
6+
7+
export async function saveSessionData(hookType: string, payload: any): Promise<void> {
8+
try {
9+
// Ensure sessions directory exists
10+
await mkdir(SESSIONS_DIR, {recursive: true})
11+
12+
const timestamp = new Date().toISOString()
13+
const sessionFile = path.join(SESSIONS_DIR, `${payload.session_id}.json`)
14+
15+
let sessionData: any[] = []
16+
try {
17+
const existing = await readFile(sessionFile, 'utf-8')
18+
sessionData = JSON.parse(existing)
19+
} catch {
20+
// File doesn't exist yet
21+
}
22+
23+
sessionData.push({
24+
timestamp,
25+
hookType,
26+
payload,
27+
})
28+
29+
await writeFile(sessionFile, JSON.stringify(sessionData, null, 2))
30+
} catch (error) {
31+
console.error('Failed to save session data:', error)
32+
}
33+
}

0 commit comments

Comments
 (0)