@stackmemoryai/stackmemory 0.5.4 → 0.5.5
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/bin/claude-sm +6 -0
- package/bin/claude-smd +6 -0
- package/dist/cli/claude-sm-danger.js +20 -0
- package/dist/cli/claude-sm-danger.js.map +7 -0
- package/dist/cli/commands/api.js +228 -0
- package/dist/cli/commands/api.js.map +7 -0
- package/dist/cli/commands/cleanup-processes.js +64 -0
- package/dist/cli/commands/cleanup-processes.js.map +7 -0
- package/dist/cli/commands/hooks.js +294 -0
- package/dist/cli/commands/hooks.js.map +7 -0
- package/dist/cli/commands/shell.js +248 -0
- package/dist/cli/commands/shell.js.map +7 -0
- package/dist/cli/index.js +9 -1
- package/dist/cli/index.js.map +2 -2
- package/dist/hooks/config.js +146 -0
- package/dist/hooks/config.js.map +7 -0
- package/dist/hooks/daemon.js +360 -0
- package/dist/hooks/daemon.js.map +7 -0
- package/dist/hooks/events.js +51 -0
- package/dist/hooks/events.js.map +7 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +7 -0
- package/dist/skills/api-discovery.js +349 -0
- package/dist/skills/api-discovery.js.map +7 -0
- package/dist/skills/api-skill.js +471 -0
- package/dist/skills/api-skill.js.map +7 -0
- package/dist/skills/claude-skills.js +49 -1
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/utils/process-cleanup.js +132 -0
- package/dist/utils/process-cleanup.js.map +7 -0
- package/package.json +4 -2
- package/templates/shell/sweep-complete.zsh +116 -0
- package/templates/shell/sweep-suggest.js +161 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
watch,
|
|
7
|
+
appendFileSync
|
|
8
|
+
} from "fs";
|
|
9
|
+
import { join, extname, relative } from "path";
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import { loadConfig } from "./config.js";
|
|
12
|
+
import {
|
|
13
|
+
hookEmitter
|
|
14
|
+
} from "./events.js";
|
|
15
|
+
const state = {
|
|
16
|
+
running: false,
|
|
17
|
+
startTime: 0,
|
|
18
|
+
eventsProcessed: 0,
|
|
19
|
+
watchers: /* @__PURE__ */ new Map(),
|
|
20
|
+
pendingPrediction: false
|
|
21
|
+
};
|
|
22
|
+
let config;
|
|
23
|
+
let logStream = null;
|
|
24
|
+
function log(level, message, data) {
|
|
25
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
26
|
+
const line = `[${timestamp}] [${level.toUpperCase()}] ${message}${data ? " " + JSON.stringify(data) : ""}`;
|
|
27
|
+
if (logStream) {
|
|
28
|
+
logStream(line);
|
|
29
|
+
}
|
|
30
|
+
const logLevels = ["debug", "info", "warn", "error"];
|
|
31
|
+
const configLevel = logLevels.indexOf(config?.daemon?.log_level || "info");
|
|
32
|
+
const msgLevel = logLevels.indexOf(level);
|
|
33
|
+
if (msgLevel >= configLevel) {
|
|
34
|
+
if (level === "error") {
|
|
35
|
+
console.error(line);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function startDaemon(options = {}) {
|
|
42
|
+
config = loadConfig();
|
|
43
|
+
if (!config.daemon.enabled) {
|
|
44
|
+
log("warn", "Daemon is disabled in config");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const pidFile = config.daemon.pid_file;
|
|
48
|
+
if (existsSync(pidFile)) {
|
|
49
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
log("warn", "Daemon already running", { pid });
|
|
53
|
+
return;
|
|
54
|
+
} catch {
|
|
55
|
+
unlinkSync(pidFile);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!options.foreground) {
|
|
59
|
+
const child = spawn(
|
|
60
|
+
process.argv[0],
|
|
61
|
+
[...process.argv.slice(1), "--foreground"],
|
|
62
|
+
{
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: "ignore"
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
child.unref();
|
|
68
|
+
log("info", "Daemon started in background", { pid: child.pid });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(pidFile, process.pid.toString());
|
|
72
|
+
state.running = true;
|
|
73
|
+
state.startTime = Date.now();
|
|
74
|
+
log("info", "Hook daemon starting", { pid: process.pid });
|
|
75
|
+
setupLogStream();
|
|
76
|
+
registerBuiltinHandlers();
|
|
77
|
+
startFileWatchers();
|
|
78
|
+
setupSignalHandlers();
|
|
79
|
+
hookEmitter.emitHook({
|
|
80
|
+
type: "session_start",
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
data: { pid: process.pid }
|
|
83
|
+
});
|
|
84
|
+
log("info", "Hook daemon ready", {
|
|
85
|
+
events: hookEmitter.getRegisteredEvents(),
|
|
86
|
+
watching: Array.from(state.watchers.keys())
|
|
87
|
+
});
|
|
88
|
+
await new Promise(() => {
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function stopDaemon() {
|
|
92
|
+
const pidFile = config?.daemon?.pid_file || join(process.env.HOME || "/tmp", ".stackmemory", "hooks.pid");
|
|
93
|
+
if (!existsSync(pidFile)) {
|
|
94
|
+
log("info", "Daemon not running");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
98
|
+
try {
|
|
99
|
+
process.kill(pid, "SIGTERM");
|
|
100
|
+
log("info", "Daemon stopped", { pid });
|
|
101
|
+
} catch {
|
|
102
|
+
log("warn", "Could not stop daemon", { pid });
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
unlinkSync(pidFile);
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function getDaemonStatus() {
|
|
110
|
+
config = loadConfig();
|
|
111
|
+
const pidFile = config.daemon.pid_file;
|
|
112
|
+
if (!existsSync(pidFile)) {
|
|
113
|
+
return { running: false };
|
|
114
|
+
}
|
|
115
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
116
|
+
try {
|
|
117
|
+
process.kill(pid, 0);
|
|
118
|
+
return {
|
|
119
|
+
running: true,
|
|
120
|
+
pid,
|
|
121
|
+
uptime: state.running ? Date.now() - state.startTime : void 0,
|
|
122
|
+
eventsProcessed: state.eventsProcessed
|
|
123
|
+
};
|
|
124
|
+
} catch {
|
|
125
|
+
return { running: false };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function setupLogStream() {
|
|
129
|
+
const logFile = config.daemon.log_file;
|
|
130
|
+
logStream = (msg) => {
|
|
131
|
+
try {
|
|
132
|
+
appendFileSync(logFile, msg + "\n");
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function registerBuiltinHandlers() {
|
|
138
|
+
hookEmitter.registerHandler("file_change", handleFileChange);
|
|
139
|
+
hookEmitter.registerHandler("suggestion_ready", handleSuggestionReady);
|
|
140
|
+
hookEmitter.registerHandler("error", handleError);
|
|
141
|
+
hookEmitter.on("*", () => {
|
|
142
|
+
state.eventsProcessed++;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async function handleFileChange(event) {
|
|
146
|
+
const fileEvent = event;
|
|
147
|
+
const hookConfig = config.hooks.file_change;
|
|
148
|
+
if (!hookConfig?.enabled) return;
|
|
149
|
+
log("debug", "File change detected", { path: fileEvent.data.path });
|
|
150
|
+
if (hookConfig.handler === "sweep-predict") {
|
|
151
|
+
await runSweepPrediction(fileEvent);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function runSweepPrediction(event) {
|
|
155
|
+
const hookConfig = config.hooks.file_change;
|
|
156
|
+
if (!hookConfig) return;
|
|
157
|
+
if (state.pendingPrediction) {
|
|
158
|
+
log("debug", "Prediction already pending, skipping");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (state.lastPrediction) {
|
|
162
|
+
const cooldown = hookConfig.cooldown_ms || 1e4;
|
|
163
|
+
if (Date.now() - state.lastPrediction < cooldown) {
|
|
164
|
+
log("debug", "In cooldown period, skipping");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
state.pendingPrediction = true;
|
|
169
|
+
const debounce = hookConfig.debounce_ms || 2e3;
|
|
170
|
+
await new Promise((r) => setTimeout(r, debounce));
|
|
171
|
+
try {
|
|
172
|
+
const sweepScript = findSweepScript();
|
|
173
|
+
if (!sweepScript) {
|
|
174
|
+
log("warn", "Sweep script not found");
|
|
175
|
+
state.pendingPrediction = false;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const filePath = event.data.path;
|
|
179
|
+
const content = event.data.content || (existsSync(filePath) ? readFileSync(filePath, "utf-8") : "");
|
|
180
|
+
const input = {
|
|
181
|
+
file_path: filePath,
|
|
182
|
+
current_content: content
|
|
183
|
+
};
|
|
184
|
+
const result = await runPythonScript(sweepScript, input);
|
|
185
|
+
if (result && result.success && result.predicted_content) {
|
|
186
|
+
state.lastPrediction = Date.now();
|
|
187
|
+
const suggestionEvent = {
|
|
188
|
+
type: "suggestion_ready",
|
|
189
|
+
timestamp: Date.now(),
|
|
190
|
+
data: {
|
|
191
|
+
suggestion: result.predicted_content,
|
|
192
|
+
source: "sweep",
|
|
193
|
+
confidence: result.confidence,
|
|
194
|
+
preview: result.predicted_content.split("\n").slice(0, 3).join("\n")
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
await hookEmitter.emitHook(suggestionEvent);
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
log("error", "Sweep prediction failed", {
|
|
201
|
+
error: error.message
|
|
202
|
+
});
|
|
203
|
+
} finally {
|
|
204
|
+
state.pendingPrediction = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function findSweepScript() {
|
|
208
|
+
const locations = [
|
|
209
|
+
join(process.env.HOME || "", ".stackmemory", "sweep", "sweep_predict.py"),
|
|
210
|
+
join(
|
|
211
|
+
process.cwd(),
|
|
212
|
+
"packages",
|
|
213
|
+
"sweep-addon",
|
|
214
|
+
"python",
|
|
215
|
+
"sweep_predict.py"
|
|
216
|
+
)
|
|
217
|
+
];
|
|
218
|
+
for (const loc of locations) {
|
|
219
|
+
if (existsSync(loc)) {
|
|
220
|
+
return loc;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
async function runPythonScript(scriptPath, input) {
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
const proc = spawn("python3", [scriptPath], {
|
|
228
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
229
|
+
});
|
|
230
|
+
let stdout = "";
|
|
231
|
+
proc.stdout.on("data", (data) => stdout += data);
|
|
232
|
+
proc.stderr.on("data", () => {
|
|
233
|
+
});
|
|
234
|
+
proc.on("close", () => {
|
|
235
|
+
try {
|
|
236
|
+
resolve(JSON.parse(stdout.trim()));
|
|
237
|
+
} catch {
|
|
238
|
+
resolve({ success: false });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
proc.on("error", () => resolve({ success: false }));
|
|
242
|
+
proc.stdin.write(JSON.stringify(input));
|
|
243
|
+
proc.stdin.end();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function handleSuggestionReady(event) {
|
|
247
|
+
const suggestionEvent = event;
|
|
248
|
+
const hookConfig = config.hooks.suggestion_ready;
|
|
249
|
+
if (!hookConfig?.enabled) return;
|
|
250
|
+
const output = hookConfig.output || "overlay";
|
|
251
|
+
switch (output) {
|
|
252
|
+
case "overlay":
|
|
253
|
+
displayOverlay(suggestionEvent.data);
|
|
254
|
+
break;
|
|
255
|
+
case "notification":
|
|
256
|
+
displayNotification(suggestionEvent.data);
|
|
257
|
+
break;
|
|
258
|
+
case "log":
|
|
259
|
+
log("info", "Suggestion ready", suggestionEvent.data);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function displayOverlay(data) {
|
|
264
|
+
const preview = data.preview || data.suggestion.slice(0, 200);
|
|
265
|
+
console.log("\n" + "\u2500".repeat(50));
|
|
266
|
+
console.log(`[${data.source}] Suggestion:`);
|
|
267
|
+
console.log(preview);
|
|
268
|
+
if (data.suggestion.length > 200) console.log("...");
|
|
269
|
+
console.log("\u2500".repeat(50) + "\n");
|
|
270
|
+
}
|
|
271
|
+
function displayNotification(data) {
|
|
272
|
+
const title = `StackMemory - ${data.source}`;
|
|
273
|
+
const message = data.preview || data.suggestion.slice(0, 100);
|
|
274
|
+
if (process.platform === "darwin") {
|
|
275
|
+
spawn("osascript", [
|
|
276
|
+
"-e",
|
|
277
|
+
`display notification "${message}" with title "${title}"`
|
|
278
|
+
]);
|
|
279
|
+
} else if (process.platform === "linux") {
|
|
280
|
+
spawn("notify-send", [title, message]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function handleError(event) {
|
|
284
|
+
log("error", "Hook error", event.data);
|
|
285
|
+
}
|
|
286
|
+
function startFileWatchers() {
|
|
287
|
+
if (!config.file_watch.enabled) return;
|
|
288
|
+
const paths = config.file_watch.paths;
|
|
289
|
+
const ignore = new Set(config.file_watch.ignore);
|
|
290
|
+
const extensions = new Set(config.file_watch.extensions);
|
|
291
|
+
for (const watchPath of paths) {
|
|
292
|
+
const absPath = join(process.cwd(), watchPath);
|
|
293
|
+
if (!existsSync(absPath)) continue;
|
|
294
|
+
try {
|
|
295
|
+
const watcher = watch(
|
|
296
|
+
absPath,
|
|
297
|
+
{ recursive: true },
|
|
298
|
+
(eventType, filename) => {
|
|
299
|
+
if (!filename) return;
|
|
300
|
+
const relPath = relative(absPath, join(absPath, filename));
|
|
301
|
+
const parts = relPath.split("/");
|
|
302
|
+
if (parts.some((p) => ignore.has(p))) return;
|
|
303
|
+
const ext = extname(filename);
|
|
304
|
+
if (!extensions.has(ext)) return;
|
|
305
|
+
const fullPath = join(absPath, filename);
|
|
306
|
+
const changeType = eventType === "rename" ? existsSync(fullPath) ? "create" : "delete" : "modify";
|
|
307
|
+
const fileEvent = {
|
|
308
|
+
type: "file_change",
|
|
309
|
+
timestamp: Date.now(),
|
|
310
|
+
data: {
|
|
311
|
+
path: fullPath,
|
|
312
|
+
changeType,
|
|
313
|
+
content: changeType !== "delete" && existsSync(fullPath) ? readFileSync(fullPath, "utf-8") : void 0
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
hookEmitter.emitHook(fileEvent);
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
state.watchers.set(absPath, watcher);
|
|
320
|
+
log("debug", "Watching directory", { path: absPath });
|
|
321
|
+
} catch (error) {
|
|
322
|
+
log("warn", "Failed to watch directory", {
|
|
323
|
+
path: absPath,
|
|
324
|
+
error: error.message
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function setupSignalHandlers() {
|
|
330
|
+
const cleanup = () => {
|
|
331
|
+
log("info", "Daemon shutting down");
|
|
332
|
+
state.running = false;
|
|
333
|
+
for (const [path, watcher] of state.watchers) {
|
|
334
|
+
watcher.close();
|
|
335
|
+
log("debug", "Stopped watching", { path });
|
|
336
|
+
}
|
|
337
|
+
hookEmitter.emitHook({
|
|
338
|
+
type: "session_end",
|
|
339
|
+
timestamp: Date.now(),
|
|
340
|
+
data: { uptime: Date.now() - state.startTime }
|
|
341
|
+
});
|
|
342
|
+
try {
|
|
343
|
+
unlinkSync(config.daemon.pid_file);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
process.exit(0);
|
|
347
|
+
};
|
|
348
|
+
process.on("SIGTERM", cleanup);
|
|
349
|
+
process.on("SIGINT", cleanup);
|
|
350
|
+
process.on("SIGHUP", cleanup);
|
|
351
|
+
}
|
|
352
|
+
export {
|
|
353
|
+
config,
|
|
354
|
+
getDaemonStatus,
|
|
355
|
+
log,
|
|
356
|
+
startDaemon,
|
|
357
|
+
state,
|
|
358
|
+
stopDaemon
|
|
359
|
+
};
|
|
360
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/daemon.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * StackMemory Hook Daemon\n * Background process that manages hooks and events\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n watch,\n appendFileSync,\n} from 'fs';\nimport { join, extname, relative } from 'path';\nimport { spawn } from 'child_process';\nimport { loadConfig, HooksConfig } from './config.js';\nimport {\n hookEmitter,\n HookEventData,\n FileChangeEvent,\n SuggestionReadyEvent,\n} from './events.js';\n\ninterface DaemonState {\n running: boolean;\n startTime: number;\n eventsProcessed: number;\n lastEvent?: HookEventData;\n watchers: Map<string, ReturnType<typeof watch>>;\n pendingPrediction: boolean;\n lastPrediction?: number;\n}\n\nconst state: DaemonState = {\n running: false,\n startTime: 0,\n eventsProcessed: 0,\n watchers: new Map(),\n pendingPrediction: false,\n};\n\nlet config: HooksConfig;\nlet logStream: ((msg: string) => void) | null = null;\n\nexport function log(level: string, message: string, data?: unknown): void {\n const timestamp = new Date().toISOString();\n const line = `[${timestamp}] [${level.toUpperCase()}] ${message}${data ? ' ' + JSON.stringify(data) : ''}`;\n\n if (logStream) {\n logStream(line);\n }\n\n const logLevels = ['debug', 'info', 'warn', 'error'];\n const configLevel = logLevels.indexOf(config?.daemon?.log_level || 'info');\n const msgLevel = logLevels.indexOf(level);\n\n if (msgLevel >= configLevel) {\n if (level === 'error') {\n console.error(line);\n } else {\n console.log(line);\n }\n }\n}\n\nexport async function startDaemon(\n options: { foreground?: boolean } = {}\n): Promise<void> {\n config = loadConfig();\n\n if (!config.daemon.enabled) {\n log('warn', 'Daemon is disabled in config');\n return;\n }\n\n const pidFile = config.daemon.pid_file;\n\n if (existsSync(pidFile)) {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n try {\n process.kill(pid, 0);\n log('warn', 'Daemon already running', { pid });\n return;\n } catch {\n unlinkSync(pidFile);\n }\n }\n\n if (!options.foreground) {\n const child = spawn(\n process.argv[0],\n [...process.argv.slice(1), '--foreground'],\n {\n detached: true,\n stdio: 'ignore',\n }\n );\n child.unref();\n log('info', 'Daemon started in background', { pid: child.pid });\n return;\n }\n\n writeFileSync(pidFile, process.pid.toString());\n state.running = true;\n state.startTime = Date.now();\n\n log('info', 'Hook daemon starting', { pid: process.pid });\n\n setupLogStream();\n registerBuiltinHandlers();\n startFileWatchers();\n setupSignalHandlers();\n\n hookEmitter.emitHook({\n type: 'session_start',\n timestamp: Date.now(),\n data: { pid: process.pid },\n });\n\n log('info', 'Hook daemon ready', {\n events: hookEmitter.getRegisteredEvents(),\n watching: Array.from(state.watchers.keys()),\n });\n\n await new Promise(() => {});\n}\n\nexport function stopDaemon(): void {\n const pidFile =\n config?.daemon?.pid_file ||\n join(process.env.HOME || '/tmp', '.stackmemory', 'hooks.pid');\n\n if (!existsSync(pidFile)) {\n log('info', 'Daemon not running');\n return;\n }\n\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n\n try {\n process.kill(pid, 'SIGTERM');\n log('info', 'Daemon stopped', { pid });\n } catch {\n log('warn', 'Could not stop daemon', { pid });\n }\n\n try {\n unlinkSync(pidFile);\n } catch {\n // Ignore\n }\n}\n\nexport function getDaemonStatus(): {\n running: boolean;\n pid?: number;\n uptime?: number;\n eventsProcessed?: number;\n} {\n config = loadConfig();\n const pidFile = config.daemon.pid_file;\n\n if (!existsSync(pidFile)) {\n return { running: false };\n }\n\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n\n try {\n process.kill(pid, 0);\n return {\n running: true,\n pid,\n uptime: state.running ? Date.now() - state.startTime : undefined,\n eventsProcessed: state.eventsProcessed,\n };\n } catch {\n return { running: false };\n }\n}\n\nfunction setupLogStream(): void {\n const logFile = config.daemon.log_file;\n\n logStream = (msg: string) => {\n try {\n appendFileSync(logFile, msg + '\\n');\n } catch {\n // Ignore\n }\n };\n}\n\nfunction registerBuiltinHandlers(): void {\n hookEmitter.registerHandler('file_change', handleFileChange);\n hookEmitter.registerHandler('suggestion_ready', handleSuggestionReady);\n hookEmitter.registerHandler('error', handleError);\n\n hookEmitter.on('*', () => {\n state.eventsProcessed++;\n });\n}\n\nasync function handleFileChange(event: HookEventData): Promise<void> {\n const fileEvent = event as FileChangeEvent;\n const hookConfig = config.hooks.file_change;\n\n if (!hookConfig?.enabled) return;\n\n log('debug', 'File change detected', { path: fileEvent.data.path });\n\n if (hookConfig.handler === 'sweep-predict') {\n await runSweepPrediction(fileEvent);\n }\n}\n\nasync function runSweepPrediction(event: FileChangeEvent): Promise<void> {\n const hookConfig = config.hooks.file_change;\n if (!hookConfig) return;\n\n if (state.pendingPrediction) {\n log('debug', 'Prediction already pending, skipping');\n return;\n }\n\n if (state.lastPrediction) {\n const cooldown = hookConfig.cooldown_ms || 10000;\n if (Date.now() - state.lastPrediction < cooldown) {\n log('debug', 'In cooldown period, skipping');\n return;\n }\n }\n\n state.pendingPrediction = true;\n\n const debounce = hookConfig.debounce_ms || 2000;\n await new Promise((r) => setTimeout(r, debounce));\n\n try {\n const sweepScript = findSweepScript();\n if (!sweepScript) {\n log('warn', 'Sweep script not found');\n state.pendingPrediction = false;\n return;\n }\n\n const filePath = event.data.path;\n const content =\n event.data.content ||\n (existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '');\n\n const input = {\n file_path: filePath,\n current_content: content,\n };\n\n const result = await runPythonScript(sweepScript, input);\n\n if (result && result.success && result.predicted_content) {\n state.lastPrediction = Date.now();\n\n const suggestionEvent: SuggestionReadyEvent = {\n type: 'suggestion_ready',\n timestamp: Date.now(),\n data: {\n suggestion: result.predicted_content,\n source: 'sweep',\n confidence: result.confidence,\n preview: result.predicted_content.split('\\n').slice(0, 3).join('\\n'),\n },\n };\n\n await hookEmitter.emitHook(suggestionEvent);\n }\n } catch (error) {\n log('error', 'Sweep prediction failed', {\n error: (error as Error).message,\n });\n } finally {\n state.pendingPrediction = false;\n }\n}\n\nfunction findSweepScript(): string | null {\n const locations = [\n join(process.env.HOME || '', '.stackmemory', 'sweep', 'sweep_predict.py'),\n join(\n process.cwd(),\n 'packages',\n 'sweep-addon',\n 'python',\n 'sweep_predict.py'\n ),\n ];\n\n for (const loc of locations) {\n if (existsSync(loc)) {\n return loc;\n }\n }\n return null;\n}\n\nasync function runPythonScript(\n scriptPath: string,\n input: Record<string, unknown>\n): Promise<{\n success: boolean;\n predicted_content?: string;\n confidence?: number;\n}> {\n return new Promise((resolve) => {\n const proc = spawn('python3', [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n proc.stdout.on('data', (data) => (stdout += data));\n proc.stderr.on('data', () => {});\n\n proc.on('close', () => {\n try {\n resolve(JSON.parse(stdout.trim()));\n } catch {\n resolve({ success: false });\n }\n });\n\n proc.on('error', () => resolve({ success: false }));\n\n proc.stdin.write(JSON.stringify(input));\n proc.stdin.end();\n });\n}\n\nfunction handleSuggestionReady(event: HookEventData): void {\n const suggestionEvent = event as SuggestionReadyEvent;\n const hookConfig = config.hooks.suggestion_ready;\n\n if (!hookConfig?.enabled) return;\n\n const output = hookConfig.output || 'overlay';\n\n switch (output) {\n case 'overlay':\n displayOverlay(suggestionEvent.data);\n break;\n case 'notification':\n displayNotification(suggestionEvent.data);\n break;\n case 'log':\n log('info', 'Suggestion ready', suggestionEvent.data);\n break;\n }\n}\n\nfunction displayOverlay(data: SuggestionReadyEvent['data']): void {\n const preview = data.preview || data.suggestion.slice(0, 200);\n console.log('\\n' + '\u2500'.repeat(50));\n console.log(`[${data.source}] Suggestion:`);\n console.log(preview);\n if (data.suggestion.length > 200) console.log('...');\n console.log('\u2500'.repeat(50) + '\\n');\n}\n\nfunction displayNotification(data: SuggestionReadyEvent['data']): void {\n const title = `StackMemory - ${data.source}`;\n const message = data.preview || data.suggestion.slice(0, 100);\n\n if (process.platform === 'darwin') {\n spawn('osascript', [\n '-e',\n `display notification \"${message}\" with title \"${title}\"`,\n ]);\n } else if (process.platform === 'linux') {\n spawn('notify-send', [title, message]);\n }\n}\n\nfunction handleError(event: HookEventData): void {\n log('error', 'Hook error', event.data);\n}\n\nfunction startFileWatchers(): void {\n if (!config.file_watch.enabled) return;\n\n const paths = config.file_watch.paths;\n const ignore = new Set(config.file_watch.ignore);\n const extensions = new Set(config.file_watch.extensions);\n\n for (const watchPath of paths) {\n const absPath = join(process.cwd(), watchPath);\n if (!existsSync(absPath)) continue;\n\n try {\n const watcher = watch(\n absPath,\n { recursive: true },\n (eventType, filename) => {\n if (!filename) return;\n\n const relPath = relative(absPath, join(absPath, filename));\n const parts = relPath.split('/');\n\n if (parts.some((p) => ignore.has(p))) return;\n\n const ext = extname(filename);\n if (!extensions.has(ext)) return;\n\n const fullPath = join(absPath, filename);\n const changeType =\n eventType === 'rename'\n ? existsSync(fullPath)\n ? 'create'\n : 'delete'\n : 'modify';\n\n const fileEvent: FileChangeEvent = {\n type: 'file_change',\n timestamp: Date.now(),\n data: {\n path: fullPath,\n changeType,\n content:\n changeType !== 'delete' && existsSync(fullPath)\n ? readFileSync(fullPath, 'utf-8')\n : undefined,\n },\n };\n\n hookEmitter.emitHook(fileEvent);\n }\n );\n\n state.watchers.set(absPath, watcher);\n log('debug', 'Watching directory', { path: absPath });\n } catch (error) {\n log('warn', 'Failed to watch directory', {\n path: absPath,\n error: (error as Error).message,\n });\n }\n }\n}\n\nfunction setupSignalHandlers(): void {\n const cleanup = () => {\n log('info', 'Daemon shutting down');\n state.running = false;\n\n for (const [path, watcher] of state.watchers) {\n watcher.close();\n log('debug', 'Stopped watching', { path });\n }\n\n hookEmitter.emitHook({\n type: 'session_end',\n timestamp: Date.now(),\n data: { uptime: Date.now() - state.startTime },\n });\n\n try {\n unlinkSync(config.daemon.pid_file);\n } catch {\n // Ignore\n }\n\n process.exit(0);\n };\n\n process.on('SIGTERM', cleanup);\n process.on('SIGINT', cleanup);\n process.on('SIGHUP', cleanup);\n}\n\nexport { config, state };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
class HookEventEmitter extends EventEmitter {
|
|
3
|
+
handlers = /* @__PURE__ */ new Map();
|
|
4
|
+
registerHandler(eventType, handler) {
|
|
5
|
+
if (!this.handlers.has(eventType)) {
|
|
6
|
+
this.handlers.set(eventType, /* @__PURE__ */ new Set());
|
|
7
|
+
}
|
|
8
|
+
this.handlers.get(eventType).add(handler);
|
|
9
|
+
this.on(eventType, handler);
|
|
10
|
+
}
|
|
11
|
+
unregisterHandler(eventType, handler) {
|
|
12
|
+
const handlers = this.handlers.get(eventType);
|
|
13
|
+
if (handlers) {
|
|
14
|
+
handlers.delete(handler);
|
|
15
|
+
this.off(eventType, handler);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async emitHook(event) {
|
|
19
|
+
const handlers = this.handlers.get(event.type);
|
|
20
|
+
if (!handlers || handlers.size === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const promises = [];
|
|
24
|
+
for (const handler of handlers) {
|
|
25
|
+
try {
|
|
26
|
+
const result = handler(event);
|
|
27
|
+
if (result instanceof Promise) {
|
|
28
|
+
promises.push(result);
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.emit("error", {
|
|
32
|
+
type: "error",
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
data: { error, originalEvent: event }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
await Promise.allSettled(promises);
|
|
39
|
+
}
|
|
40
|
+
getRegisteredEvents() {
|
|
41
|
+
return Array.from(this.handlers.keys()).filter(
|
|
42
|
+
(type) => (this.handlers.get(type)?.size ?? 0) > 0
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const hookEmitter = new HookEventEmitter();
|
|
47
|
+
export {
|
|
48
|
+
HookEventEmitter,
|
|
49
|
+
hookEmitter
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/events.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * StackMemory Hook Events\n * Event types and emitter for the hook system\n */\n\nimport { EventEmitter } from 'events';\n\nexport type HookEventType =\n | 'input_idle'\n | 'file_change'\n | 'context_switch'\n | 'session_start'\n | 'session_end'\n | 'prompt_submit'\n | 'tool_use'\n | 'suggestion_ready'\n | 'error';\n\nexport interface HookEvent {\n type: HookEventType;\n timestamp: number;\n data: Record<string, unknown>;\n}\n\nexport interface FileChangeEvent extends HookEvent {\n type: 'file_change';\n data: {\n path: string;\n changeType: 'create' | 'modify' | 'delete';\n content?: string;\n };\n}\n\nexport interface InputIdleEvent extends HookEvent {\n type: 'input_idle';\n data: {\n idleDuration: number;\n lastInput?: string;\n };\n}\n\nexport interface ContextSwitchEvent extends HookEvent {\n type: 'context_switch';\n data: {\n fromBranch?: string;\n toBranch?: string;\n fromProject?: string;\n toProject?: string;\n };\n}\n\nexport interface SuggestionReadyEvent extends HookEvent {\n type: 'suggestion_ready';\n data: {\n suggestion: string;\n source: string;\n confidence?: number;\n preview?: string;\n };\n}\n\nexport type HookEventData =\n | FileChangeEvent\n | InputIdleEvent\n | ContextSwitchEvent\n | SuggestionReadyEvent\n | HookEvent;\n\nexport type HookHandler = (event: HookEventData) => Promise<void> | void;\n\nexport class HookEventEmitter extends EventEmitter {\n private handlers: Map<HookEventType, Set<HookHandler>> = new Map();\n\n registerHandler(eventType: HookEventType, handler: HookHandler): void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n this.handlers.get(eventType)!.add(handler);\n this.on(eventType, handler);\n }\n\n unregisterHandler(eventType: HookEventType, handler: HookHandler): void {\n const handlers = this.handlers.get(eventType);\n if (handlers) {\n handlers.delete(handler);\n this.off(eventType, handler);\n }\n }\n\n async emitHook(event: HookEventData): Promise<void> {\n const handlers = this.handlers.get(event.type);\n if (!handlers || handlers.size === 0) {\n return;\n }\n\n const promises: Promise<void>[] = [];\n for (const handler of handlers) {\n try {\n const result = handler(event);\n if (result instanceof Promise) {\n promises.push(result);\n }\n } catch (error) {\n this.emit('error', {\n type: 'error',\n timestamp: Date.now(),\n data: { error, originalEvent: event },\n });\n }\n }\n\n await Promise.allSettled(promises);\n }\n\n getRegisteredEvents(): HookEventType[] {\n return Array.from(this.handlers.keys()).filter(\n (type) => (this.handlers.get(type)?.size ?? 0) > 0\n );\n }\n}\n\nexport const hookEmitter = new HookEventEmitter();\n"],
|
|
5
|
+
"mappings": "AAKA,SAAS,oBAAoB;AAiEtB,MAAM,yBAAyB,aAAa;AAAA,EACzC,WAAiD,oBAAI,IAAI;AAAA,EAEjE,gBAAgB,WAA0B,SAA4B;AACpE,QAAI,CAAC,KAAK,SAAS,IAAI,SAAS,GAAG;AACjC,WAAK,SAAS,IAAI,WAAW,oBAAI,IAAI,CAAC;AAAA,IACxC;AACA,SAAK,SAAS,IAAI,SAAS,EAAG,IAAI,OAAO;AACzC,SAAK,GAAG,WAAW,OAAO;AAAA,EAC5B;AAAA,EAEA,kBAAkB,WAA0B,SAA4B;AACtE,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,QAAI,UAAU;AACZ,eAAS,OAAO,OAAO;AACvB,WAAK,IAAI,WAAW,OAAO;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,OAAqC;AAClD,UAAM,WAAW,KAAK,SAAS,IAAI,MAAM,IAAI;AAC7C,QAAI,CAAC,YAAY,SAAS,SAAS,GAAG;AACpC;AAAA,IACF;AAEA,UAAM,WAA4B,CAAC;AACnC,eAAW,WAAW,UAAU;AAC9B,UAAI;AACF,cAAM,SAAS,QAAQ,KAAK;AAC5B,YAAI,kBAAkB,SAAS;AAC7B,mBAAS,KAAK,MAAM;AAAA,QACtB;AAAA,MACF,SAAS,OAAO;AACd,aAAK,KAAK,SAAS;AAAA,UACjB,MAAM;AAAA,UACN,WAAW,KAAK,IAAI;AAAA,UACpB,MAAM,EAAE,OAAO,eAAe,MAAM;AAAA,QACtC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,QAAQ;AAAA,EACnC;AAAA,EAEA,sBAAuC;AACrC,WAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC,EAAE;AAAA,MACtC,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,KAAK;AAAA,IACnD;AAAA,EACF;AACF;AAEO,MAAM,cAAc,IAAI,iBAAiB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * StackMemory Hooks Module\n * User-configurable hook system for automation and suggestions\n */\n\nexport * from './events.js';\nexport * from './config.js';\nexport * from './daemon.js';\n"],
|
|
5
|
+
"mappings": "AAKA,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|