claude-mem 2.1.2 → 3.0.2

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.
@@ -1,318 +0,0 @@
1
- /**
2
- * Shared utilities for Claude Code hooks
3
- *
4
- * This module provides common functionality for all hook files to eliminate
5
- * duplicated code patterns across pre-compact.js, session-start.js, etc.
6
- */
7
-
8
- import { spawn } from 'child_process';
9
- import { readFileSync, existsSync } from 'fs';
10
- import { join } from 'path';
11
- import { homedir } from 'os';
12
-
13
- // Import constants - note: hooks are JavaScript, not TypeScript
14
- const PACKAGE_NAME = 'claude-mem';
15
- const CLAUDE_MEM_BASE = '.claude-mem';
16
-
17
- // =============================================================================
18
- // HOOK CONFIGURATION LOADING
19
- // =============================================================================
20
-
21
- /**
22
- * Load hook configuration from settings (eliminates ~33 lines per hook)
23
- */
24
- export function loadHookConfig() {
25
- const settingsPath = join(homedir(), CLAUDE_MEM_BASE, 'settings.json');
26
-
27
- if (!existsSync(settingsPath)) {
28
- return {};
29
- }
30
-
31
- try {
32
- const content = readFileSync(settingsPath, 'utf8');
33
- return JSON.parse(content);
34
- } catch (error) {
35
- console.warn(`Warning: Could not load hook config: ${error.message}`);
36
- return {};
37
- }
38
- }
39
-
40
- // =============================================================================
41
- // STDIN JSON READING
42
- // =============================================================================
43
-
44
- /**
45
- * Read and parse JSON from stdin (removes 30+ duplicate lines)
46
- * Returns a Promise that resolves with the parsed JSON data
47
- */
48
- export function readStdinJson() {
49
- return new Promise((resolve, reject) => {
50
- let input = '';
51
-
52
- process.stdin.on('data', chunk => {
53
- input += chunk;
54
- });
55
-
56
- process.stdin.on('end', () => {
57
- try {
58
- const data = JSON.parse(input);
59
- resolve(data);
60
- } catch (error) {
61
- reject(new Error(`Invalid JSON input: ${error.message}`));
62
- }
63
- });
64
-
65
- process.stdin.on('error', error => {
66
- reject(new Error(`Stdin error: ${error.message}`));
67
- });
68
- });
69
- }
70
-
71
- // =============================================================================
72
- // HOOK RESPONSE HELPERS
73
- // =============================================================================
74
-
75
- /**
76
- * Standard hook response objects (matches constants.ts HOOK_RESPONSES pattern)
77
- */
78
- export const HookResponse = {
79
- /**
80
- * Success response with optional message (matches HOOK_RESPONSES.SUCCESS pattern)
81
- */
82
- success(hookEventName, message) {
83
- return {
84
- hookSpecificOutput: {
85
- hookEventName,
86
- status: "success",
87
- message
88
- },
89
- suppressOutput: true
90
- };
91
- },
92
-
93
- /**
94
- * Skipped response (matches HOOK_RESPONSES.SKIPPED pattern)
95
- */
96
- skipped(hookEventName, message) {
97
- return {
98
- hookSpecificOutput: {
99
- hookEventName,
100
- status: "skipped",
101
- message
102
- },
103
- suppressOutput: true
104
- };
105
- },
106
-
107
- /**
108
- * Error response (matches HOOK_RESPONSES.ERROR pattern)
109
- */
110
- error(hookEventName, message) {
111
- return {
112
- continue: false,
113
- stopReason: message,
114
- suppressOutput: true,
115
- hookSpecificOutput: {
116
- hookEventName,
117
- status: "error",
118
- message
119
- }
120
- };
121
- },
122
-
123
- /**
124
- * Permission response for tool use
125
- */
126
- permission(decision, reason = '') {
127
- return {
128
- continue: true,
129
- permissionDecision: decision, // 'allow' or 'deny'
130
- permissionDecisionReason: reason
131
- };
132
- }
133
- };
134
-
135
- // =============================================================================
136
- // CLI SPAWNING UTILITIES
137
- // =============================================================================
138
-
139
- /**
140
- * Spawn CLI command (consolidates 90+ lines of spawn logic)
141
- */
142
- export function spawnCLI(command, args = [], options = {}) {
143
- return new Promise((resolve, reject) => {
144
- const defaultOptions = {
145
- stdio: ['pipe', 'pipe', 'pipe'],
146
- env: process.env,
147
- timeout: 30000, // 30 second default timeout
148
- ...options
149
- };
150
-
151
- // Find CLI executable
152
- const possibleCommands = [
153
- PACKAGE_NAME,
154
- `npx ${PACKAGE_NAME}`,
155
- join(process.cwd(), 'node_modules', '.bin', PACKAGE_NAME),
156
- join(homedir(), '.npm-global', 'bin', PACKAGE_NAME)
157
- ];
158
-
159
- let claudeMemCmd = command;
160
- if (!claudeMemCmd) {
161
- // Try to find the best command
162
- claudeMemCmd = PACKAGE_NAME; // Default fallback
163
- }
164
-
165
- const child = spawn(claudeMemCmd, args, defaultOptions);
166
-
167
- let stdout = '';
168
- let stderr = '';
169
-
170
- child.stdout?.on('data', (data) => {
171
- stdout += data.toString();
172
- });
173
-
174
- child.stderr?.on('data', (data) => {
175
- stderr += data.toString();
176
- });
177
-
178
- child.on('close', (code) => {
179
- if (code === 0) {
180
- resolve({
181
- code,
182
- stdout: stdout.trim(),
183
- stderr: stderr.trim()
184
- });
185
- } else {
186
- reject(new Error(`Command failed with code ${code}: ${stderr.trim()}`));
187
- }
188
- });
189
-
190
- child.on('error', (error) => {
191
- reject(new Error(`Failed to spawn command: ${error.message}`));
192
- });
193
-
194
- // Handle timeout
195
- if (defaultOptions.timeout) {
196
- setTimeout(() => {
197
- child.kill('SIGTERM');
198
- reject(new Error(`Command timed out after ${defaultOptions.timeout}ms`));
199
- }, defaultOptions.timeout);
200
- }
201
- });
202
- }
203
-
204
- /**
205
- * Run CLI command with specific subcommand and arguments
206
- */
207
- export async function runClaudeMemCommand(subcommand, args = [], options = {}) {
208
- const fullArgs = [subcommand, ...args];
209
- return spawnCLI(PACKAGE_NAME, fullArgs, options);
210
- }
211
-
212
- // =============================================================================
213
- // PROJECT NAME EXTRACTION
214
- // =============================================================================
215
-
216
- /**
217
- * Extract project name from transcript path
218
- * Consolidates the 3 different implementations found in the codebase
219
- */
220
- export function extractProjectName(transcriptPath) {
221
- if (!transcriptPath) {
222
- return 'unknown';
223
- }
224
-
225
- // Standard pattern: Scripts/[project-name]/...
226
- const match = transcriptPath.match(/Scripts[\\/\\]([^\\/\\]+)/);
227
- if (match) {
228
- return match[1];
229
- }
230
-
231
- // Fallback: try to extract from directory structure
232
- const parts = transcriptPath.split(/[\\/]/);
233
- for (let i = 0; i < parts.length - 1; i++) {
234
- if (parts[i] === 'Scripts' && parts[i + 1]) {
235
- return parts[i + 1];
236
- }
237
- }
238
-
239
- return 'unknown';
240
- }
241
-
242
- // =============================================================================
243
- // UTILITY HELPERS
244
- // =============================================================================
245
-
246
- /**
247
- * Safe JSON output for hook responses
248
- */
249
- export function outputJSON(data) {
250
- try {
251
- console.log(JSON.stringify(data, null, 2));
252
- } catch (error) {
253
- console.error(JSON.stringify({
254
- continue: false,
255
- stopReason: `Failed to serialize response: ${error.message}`
256
- }));
257
- }
258
- }
259
-
260
- /**
261
- * Debug logging helper
262
- */
263
- export function debugLog(message) {
264
- if (process.env.CLAUDE_MEM_DEBUG) {
265
- console.error(`[DEBUG] ${message}`);
266
- }
267
- }
268
-
269
- /**
270
- * Validate hook payload structure
271
- */
272
- export function validateHookPayload(payload, requiredFields = []) {
273
- const errors = [];
274
-
275
- if (!payload || typeof payload !== 'object') {
276
- errors.push('Payload must be an object');
277
- return errors;
278
- }
279
-
280
- requiredFields.forEach(field => {
281
- if (!(field in payload)) {
282
- errors.push(`Missing required field: ${field}`);
283
- }
284
- });
285
-
286
- return errors;
287
- }
288
-
289
- /**
290
- * Create hook execution context
291
- */
292
- export function createHookContext(payload) {
293
- return {
294
- sessionId: payload.session_id || 'unknown',
295
- transcriptPath: payload.transcript_path || null,
296
- hookEventName: payload.hook_event_name || 'unknown',
297
- source: payload.source || 'unknown', // For SessionStart hooks
298
- timestamp: new Date().toISOString(),
299
- projectName: payload.transcript_path ? extractProjectName(payload.transcript_path) : null
300
- };
301
- }
302
-
303
- // =============================================================================
304
- // EXPORT ALL UTILITIES
305
- // =============================================================================
306
-
307
- export default {
308
- loadHookConfig,
309
- readStdinJson,
310
- HookResponse,
311
- spawnCLI,
312
- runClaudeMemCommand,
313
- extractProjectName,
314
- outputJSON,
315
- debugLog,
316
- validateHookPayload,
317
- createHookContext
318
- };