@stackmemoryai/stackmemory 0.7.0 → 0.8.1

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.
Files changed (45) hide show
  1. package/README.md +39 -1
  2. package/dist/src/cli/claude-sm.js +69 -372
  3. package/dist/src/cli/claude-sm.js.map +2 -2
  4. package/dist/src/cli/codex-sm.js +28 -0
  5. package/dist/src/cli/codex-sm.js.map +2 -2
  6. package/dist/src/cli/commands/clear.js +10 -15
  7. package/dist/src/cli/commands/clear.js.map +2 -2
  8. package/dist/src/cli/commands/daemon.js +31 -0
  9. package/dist/src/cli/commands/daemon.js.map +2 -2
  10. package/dist/src/cli/commands/onboard.js +22 -6
  11. package/dist/src/cli/commands/onboard.js.map +2 -2
  12. package/dist/src/cli/commands/setup.js +1 -2
  13. package/dist/src/cli/commands/setup.js.map +2 -2
  14. package/dist/src/cli/index.js +1 -71
  15. package/dist/src/cli/index.js.map +3 -3
  16. package/dist/src/cli/opencode-sm.js +28 -0
  17. package/dist/src/cli/opencode-sm.js.map +2 -2
  18. package/dist/src/core/config/feature-flags.js +1 -13
  19. package/dist/src/core/config/feature-flags.js.map +2 -2
  20. package/dist/src/core/context/refactored-frame-manager.js +7 -0
  21. package/dist/src/core/context/refactored-frame-manager.js.map +2 -2
  22. package/dist/src/core/database/sqlite-adapter.js +1 -1
  23. package/dist/src/core/database/sqlite-adapter.js.map +2 -2
  24. package/dist/src/core/session/enhanced-handoff.js +1 -1
  25. package/dist/src/core/session/enhanced-handoff.js.map +2 -2
  26. package/dist/src/daemon/daemon-config.js +11 -0
  27. package/dist/src/daemon/daemon-config.js.map +2 -2
  28. package/dist/src/daemon/services/memory-service.js +190 -0
  29. package/dist/src/daemon/services/memory-service.js.map +7 -0
  30. package/dist/src/daemon/unified-daemon.js +33 -3
  31. package/dist/src/daemon/unified-daemon.js.map +2 -2
  32. package/dist/src/features/sweep/pty-wrapper.js +11 -1
  33. package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
  34. package/dist/src/hooks/index.js +0 -3
  35. package/dist/src/hooks/index.js.map +2 -2
  36. package/dist/src/hooks/schemas.js +0 -117
  37. package/dist/src/hooks/schemas.js.map +2 -2
  38. package/dist/src/hooks/session-summary.js.map +1 -1
  39. package/package.json +2 -2
  40. package/scripts/smoke-init-db.sh +77 -0
  41. package/scripts/test-pre-publish-quick.sh +26 -34
  42. package/scripts/install-notify-hook.sh +0 -84
  43. package/scripts/setup-notify-webhook.sh +0 -103
  44. package/templates/claude-hooks/notify-review-hook.js +0 -360
  45. package/templates/claude-hooks/sms-response-handler.js +0 -174
@@ -18,12 +18,14 @@ import {
18
18
  import { DaemonContextService } from "./services/context-service.js";
19
19
  import { DaemonLinearService } from "./services/linear-service.js";
20
20
  import { DaemonMaintenanceService } from "./services/maintenance-service.js";
21
+ import { DaemonMemoryService } from "./services/memory-service.js";
21
22
  class UnifiedDaemon {
22
23
  config;
23
24
  paths;
24
25
  contextService;
25
26
  linearService;
26
27
  maintenanceService;
28
+ memoryService;
27
29
  heartbeatInterval;
28
30
  isShuttingDown = false;
29
31
  startTime = 0;
@@ -42,6 +44,10 @@ class UnifiedDaemon {
42
44
  this.config.maintenance,
43
45
  (level, msg, data) => this.log(level, "maintenance", msg, data)
44
46
  );
47
+ this.memoryService = new DaemonMemoryService(
48
+ this.config.memory,
49
+ (level, msg, data) => this.log(level, "memory", msg, data)
50
+ );
45
51
  }
46
52
  log(level, service, message, data) {
47
53
  const logLevel = level.toUpperCase();
@@ -95,6 +101,7 @@ class UnifiedDaemon {
95
101
  }
96
102
  updateStatus() {
97
103
  const maintenanceState = this.maintenanceService.getState();
104
+ const memoryState = this.memoryService.getState();
98
105
  const status = {
99
106
  running: true,
100
107
  pid: process.pid,
@@ -120,6 +127,12 @@ class UnifiedDaemon {
120
127
  embeddingsTotal: maintenanceState.embeddingsTotal,
121
128
  embeddingsRemaining: maintenanceState.embeddingsRemaining
122
129
  },
130
+ memory: {
131
+ enabled: this.config.memory.enabled,
132
+ lastTrigger: memoryState.lastTriggerTime || void 0,
133
+ triggerCount: memoryState.triggerCount,
134
+ currentRamPercent: memoryState.currentRamPercent
135
+ },
123
136
  fileWatch: {
124
137
  enabled: this.config.fileWatch.enabled
125
138
  }
@@ -127,7 +140,8 @@ class UnifiedDaemon {
127
140
  errors: [
128
141
  ...this.contextService.getState().errors.slice(-5),
129
142
  ...this.linearService.getState().errors.slice(-5),
130
- ...maintenanceState.errors.slice(-5)
143
+ ...maintenanceState.errors.slice(-5),
144
+ ...memoryState.errors.slice(-5)
131
145
  ]
132
146
  };
133
147
  writeDaemonStatus(status);
@@ -182,6 +196,10 @@ class UnifiedDaemon {
182
196
  staleFramesCleaned: this.maintenanceService.getState().staleFramesCleaned,
183
197
  ftsRebuilds: this.maintenanceService.getState().ftsRebuilds
184
198
  },
199
+ memory: {
200
+ enabled: false,
201
+ triggerCount: this.memoryService.getState().triggerCount
202
+ },
185
203
  fileWatch: { enabled: false }
186
204
  },
187
205
  errors: []
@@ -196,7 +214,8 @@ class UnifiedDaemon {
196
214
  uptime: Date.now() - this.startTime,
197
215
  contextSaves: this.contextService.getState().saveCount,
198
216
  linearSyncs: this.linearService.getState().syncCount,
199
- maintenanceRuns: this.maintenanceService.getState().ftsRebuilds
217
+ maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,
218
+ memoryTriggers: this.memoryService.getState().triggerCount
200
219
  });
201
220
  if (this.heartbeatInterval) {
202
221
  clearInterval(this.heartbeatInterval);
@@ -205,6 +224,7 @@ class UnifiedDaemon {
205
224
  this.contextService.stop();
206
225
  this.linearService.stop();
207
226
  this.maintenanceService.stop();
227
+ this.memoryService.stop();
208
228
  this.cleanup();
209
229
  const exitCode = reason === "sigterm" || reason === "sigint" || reason === "sighup" ? 0 : 1;
210
230
  process.exit(exitCode);
@@ -223,12 +243,14 @@ class UnifiedDaemon {
223
243
  context: this.config.context.enabled,
224
244
  linear: this.config.linear.enabled,
225
245
  maintenance: this.config.maintenance.enabled,
246
+ memory: this.config.memory.enabled,
226
247
  fileWatch: this.config.fileWatch.enabled
227
248
  }
228
249
  });
229
250
  this.contextService.start();
230
251
  await this.linearService.start();
231
252
  this.maintenanceService.start();
253
+ this.memoryService.start();
232
254
  this.heartbeatInterval = setInterval(() => {
233
255
  this.updateStatus();
234
256
  }, this.config.heartbeatInterval * 1e3);
@@ -236,6 +258,7 @@ class UnifiedDaemon {
236
258
  }
237
259
  getStatus() {
238
260
  const maintenanceState = this.maintenanceService.getState();
261
+ const memoryState = this.memoryService.getState();
239
262
  return {
240
263
  running: !this.isShuttingDown,
241
264
  pid: process.pid,
@@ -261,6 +284,12 @@ class UnifiedDaemon {
261
284
  embeddingsTotal: maintenanceState.embeddingsTotal,
262
285
  embeddingsRemaining: maintenanceState.embeddingsRemaining
263
286
  },
287
+ memory: {
288
+ enabled: this.config.memory.enabled,
289
+ lastTrigger: memoryState.lastTriggerTime || void 0,
290
+ triggerCount: memoryState.triggerCount,
291
+ currentRamPercent: memoryState.currentRamPercent
292
+ },
264
293
  fileWatch: {
265
294
  enabled: this.config.fileWatch.enabled
266
295
  }
@@ -268,7 +297,8 @@ class UnifiedDaemon {
268
297
  errors: [
269
298
  ...this.contextService.getState().errors,
270
299
  ...this.linearService.getState().errors,
271
- ...maintenanceState.errors
300
+ ...maintenanceState.errors,
301
+ ...memoryState.errors
272
302
  ]
273
303
  };
274
304
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/daemon/unified-daemon.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Unified Daemon for StackMemory\n *\n * Single background process managing multiple services:\n * - Context auto-save\n * - Linear sync\n * - File watch (future)\n */\n\nimport {\n existsSync,\n writeFileSync,\n unlinkSync,\n appendFileSync,\n readFileSync,\n} from 'fs';\nimport {\n loadDaemonConfig,\n getDaemonPaths,\n writeDaemonStatus,\n type DaemonConfig,\n type DaemonStatus,\n} from './daemon-config.js';\nimport { DaemonContextService } from './services/context-service.js';\nimport { DaemonLinearService } from './services/linear-service.js';\nimport { DaemonMaintenanceService } from './services/maintenance-service.js';\n\ninterface LogEntry {\n timestamp: string;\n level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n service: string;\n message: string;\n data?: unknown;\n}\n\nexport class UnifiedDaemon {\n private config: DaemonConfig;\n private paths: ReturnType<typeof getDaemonPaths>;\n private contextService: DaemonContextService;\n private linearService: DaemonLinearService;\n private maintenanceService: DaemonMaintenanceService;\n private heartbeatInterval?: NodeJS.Timeout;\n private isShuttingDown = false;\n private startTime: number = 0;\n\n constructor(config?: Partial<DaemonConfig>) {\n this.paths = getDaemonPaths();\n this.config = { ...loadDaemonConfig(), ...config };\n\n // Initialize services\n this.contextService = new DaemonContextService(\n this.config.context,\n (level, msg, data) => this.log(level, 'context', msg, data)\n );\n\n this.linearService = new DaemonLinearService(\n this.config.linear,\n (level, msg, data) => this.log(level, 'linear', msg, data)\n );\n\n this.maintenanceService = new DaemonMaintenanceService(\n this.config.maintenance,\n (level, msg, data) => this.log(level, 'maintenance', msg, data)\n );\n }\n\n private log(\n level: string,\n service: string,\n message: string,\n data?: unknown\n ): void {\n const logLevel = level.toUpperCase() as LogEntry['level'];\n\n // Check log level\n const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];\n const configLevel = this.config.logLevel.toUpperCase();\n if (levels.indexOf(logLevel) < levels.indexOf(configLevel)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level: logLevel,\n service,\n message,\n data,\n };\n\n const logLine = JSON.stringify(entry) + '\\n';\n\n try {\n appendFileSync(this.paths.logFile, logLine);\n } catch {\n console.error(`[${entry.timestamp}] ${level} [${service}]: ${message}`);\n }\n }\n\n private checkIdempotency(): boolean {\n if (existsSync(this.paths.pidFile)) {\n try {\n const existingPid = readFileSync(this.paths.pidFile, 'utf8').trim();\n const pid = parseInt(existingPid, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n this.log('WARN', 'daemon', 'Daemon already running', { pid });\n return false;\n } catch {\n // Process not running, stale PID\n this.log('INFO', 'daemon', 'Cleaning stale PID file', { pid });\n unlinkSync(this.paths.pidFile);\n }\n } catch {\n try {\n unlinkSync(this.paths.pidFile);\n } catch {\n // Ignore\n }\n }\n }\n return true;\n }\n\n private writePidFile(): void {\n writeFileSync(this.paths.pidFile, process.pid.toString());\n this.log('INFO', 'daemon', 'PID file created', {\n pid: process.pid,\n file: this.paths.pidFile,\n });\n }\n\n private updateStatus(): void {\n const maintenanceState = this.maintenanceService.getState();\n const status: DaemonStatus = {\n running: true,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors.slice(-5),\n ...this.linearService.getState().errors.slice(-5),\n ...maintenanceState.errors.slice(-5),\n ],\n };\n\n writeDaemonStatus(status);\n }\n\n private setupSignalHandlers(): void {\n const handleSignal = (signal: string) => {\n this.log('INFO', 'daemon', `Received ${signal}, shutting down`);\n this.shutdown(signal.toLowerCase());\n };\n\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGHUP', () => handleSignal('SIGHUP'));\n\n process.on('uncaughtException', (err) => {\n this.log('ERROR', 'daemon', 'Uncaught exception', {\n error: err.message,\n stack: err.stack,\n });\n this.shutdown('uncaught_exception');\n });\n\n process.on('unhandledRejection', (reason) => {\n this.log('ERROR', 'daemon', 'Unhandled rejection', {\n reason: String(reason),\n });\n });\n }\n\n private cleanup(): void {\n // Remove PID file\n try {\n if (existsSync(this.paths.pidFile)) {\n unlinkSync(this.paths.pidFile);\n this.log('INFO', 'daemon', 'PID file removed');\n }\n } catch (e) {\n this.log('WARN', 'daemon', 'Failed to remove PID file', {\n error: String(e),\n });\n }\n\n // Update status\n const finalStatus: DaemonStatus = {\n running: false,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: false,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: false,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: false,\n staleFramesCleaned:\n this.maintenanceService.getState().staleFramesCleaned,\n ftsRebuilds: this.maintenanceService.getState().ftsRebuilds,\n },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n writeDaemonStatus(finalStatus);\n }\n\n private shutdown(reason: string): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n this.log('INFO', 'daemon', 'Daemon shutting down', {\n reason,\n uptime: Date.now() - this.startTime,\n contextSaves: this.contextService.getState().saveCount,\n linearSyncs: this.linearService.getState().syncCount,\n maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,\n });\n\n // Stop heartbeat\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = undefined;\n }\n\n // Stop services\n this.contextService.stop();\n this.linearService.stop();\n this.maintenanceService.stop();\n\n // Cleanup\n this.cleanup();\n\n const exitCode =\n reason === 'sigterm' || reason === 'sigint' || reason === 'sighup'\n ? 0\n : 1;\n process.exit(exitCode);\n }\n\n async start(): Promise<void> {\n // Check idempotency\n if (!this.checkIdempotency()) {\n this.log('INFO', 'daemon', 'Exiting - daemon already running');\n process.exit(0);\n }\n\n this.startTime = Date.now();\n\n // Write PID file\n this.writePidFile();\n\n // Setup signal handlers\n this.setupSignalHandlers();\n\n this.log('INFO', 'daemon', 'Unified daemon started', {\n pid: process.pid,\n config: {\n context: this.config.context.enabled,\n linear: this.config.linear.enabled,\n maintenance: this.config.maintenance.enabled,\n fileWatch: this.config.fileWatch.enabled,\n },\n });\n\n // Start services\n this.contextService.start();\n await this.linearService.start();\n this.maintenanceService.start();\n\n // Start heartbeat\n this.heartbeatInterval = setInterval(() => {\n this.updateStatus();\n }, this.config.heartbeatInterval * 1000);\n\n // Initial status update\n this.updateStatus();\n }\n\n getStatus(): DaemonStatus {\n const maintenanceState = this.maintenanceService.getState();\n return {\n running: !this.isShuttingDown,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors,\n ...this.linearService.getState().errors,\n ...maintenanceState.errors,\n ],\n };\n }\n}\n\n// CLI entry point\nif (\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('unified-daemon.js')\n) {\n const args = process.argv.slice(2);\n const config: Partial<DaemonConfig> = {};\n\n // Parse command line args\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--save-interval' && args[i + 1]) {\n config.context = { enabled: true, interval: parseInt(args[i + 1], 10) };\n i++;\n } else if (arg === '--linear-interval' && args[i + 1]) {\n config.linear = {\n enabled: true,\n interval: parseInt(args[i + 1], 10),\n retryAttempts: 3,\n retryDelay: 30000,\n };\n i++;\n } else if (arg === '--no-linear') {\n config.linear = {\n enabled: false,\n interval: 60,\n retryAttempts: 3,\n retryDelay: 30000,\n };\n } else if (arg === '--log-level' && args[i + 1]) {\n config.logLevel = args[i + 1] as DaemonConfig['logLevel'];\n i++;\n }\n }\n\n const daemon = new UnifiedDaemon(config);\n daemon.start().catch((err) => {\n console.error('Failed to start daemon:', err);\n process.exit(1);\n });\n}\n"],
5
- "mappings": ";;;;;AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AAUlC,MAAM,cAAc;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAoB;AAAA,EAE5B,YAAY,QAAgC;AAC1C,SAAK,QAAQ,eAAe;AAC5B,SAAK,SAAS,EAAE,GAAG,iBAAiB,GAAG,GAAG,OAAO;AAGjD,SAAK,iBAAiB,IAAI;AAAA,MACxB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,WAAW,KAAK,IAAI;AAAA,IAC5D;AAEA,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3D;AAEA,SAAK,qBAAqB,IAAI;AAAA,MAC5B,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI;AAAA,IAChE;AAAA,EACF;AAAA,EAEQ,IACN,OACA,SACA,SACA,MACM;AACN,UAAM,WAAW,MAAM,YAAY;AAGnC,UAAM,SAAS,CAAC,SAAS,QAAQ,QAAQ,OAAO;AAChD,UAAM,cAAc,KAAK,OAAO,SAAS,YAAY;AACrD,QAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO,QAAQ,WAAW,GAAG;AAC1D;AAAA,IACF;AAEA,UAAM,QAAkB;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,KAAK,IAAI;AAExC,QAAI;AACF,qBAAe,KAAK,MAAM,SAAS,OAAO;AAAA,IAC5C,QAAQ;AACN,cAAQ,MAAM,IAAI,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,IACxE;AAAA,EACF;AAAA,EAEQ,mBAA4B;AAClC,QAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,UAAI;AACF,cAAM,cAAc,aAAa,KAAK,MAAM,SAAS,MAAM,EAAE,KAAK;AAClE,cAAM,MAAM,SAAS,aAAa,EAAE;AAGpC,YAAI;AACF,kBAAQ,KAAK,KAAK,CAAC;AACnB,eAAK,IAAI,QAAQ,UAAU,0BAA0B,EAAE,IAAI,CAAC;AAC5D,iBAAO;AAAA,QACT,QAAQ;AAEN,eAAK,IAAI,QAAQ,UAAU,2BAA2B,EAAE,IAAI,CAAC;AAC7D,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B;AAAA,MACF,QAAQ;AACN,YAAI;AACF,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAC3B,kBAAc,KAAK,MAAM,SAAS,QAAQ,IAAI,SAAS,CAAC;AACxD,SAAK,IAAI,QAAQ,UAAU,oBAAoB;AAAA,MAC7C,KAAK,QAAQ;AAAA,MACb,MAAM,KAAK,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC3B,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,UAAM,SAAuB;AAAA,MAC3B,SAAS;AAAA,MACT,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QACjD,GAAG,KAAK,cAAc,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QAChD,GAAG,iBAAiB,OAAO,MAAM,EAAE;AAAA,MACrC;AAAA,IACF;AAEA,sBAAkB,MAAM;AAAA,EAC1B;AAAA,EAEQ,sBAA4B;AAClC,UAAM,eAAe,CAAC,WAAmB;AACvC,WAAK,IAAI,QAAQ,UAAU,YAAY,MAAM,iBAAiB;AAC9D,WAAK,SAAS,OAAO,YAAY,CAAC;AAAA,IACpC;AAEA,YAAQ,GAAG,WAAW,MAAM,aAAa,SAAS,CAAC;AACnD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AACjD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AAEjD,YAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,WAAK,IAAI,SAAS,UAAU,sBAAsB;AAAA,QAChD,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,MACb,CAAC;AACD,WAAK,SAAS,oBAAoB;AAAA,IACpC,CAAC;AAED,YAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,WAAK,IAAI,SAAS,UAAU,uBAAuB;AAAA,QACjD,QAAQ,OAAO,MAAM;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,UAAgB;AAEtB,QAAI;AACF,UAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,mBAAW,KAAK,MAAM,OAAO;AAC7B,aAAK,IAAI,QAAQ,UAAU,kBAAkB;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,QAAQ,UAAU,6BAA6B;AAAA,QACtD,OAAO,OAAO,CAAC;AAAA,MACjB,CAAC;AAAA,IACH;AAGA,UAAM,cAA4B;AAAA,MAChC,SAAS;AAAA,MACT,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS;AAAA,UACT,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS;AAAA,UACT,oBACE,KAAK,mBAAmB,SAAS,EAAE;AAAA,UACrC,aAAa,KAAK,mBAAmB,SAAS,EAAE;AAAA,QAClD;AAAA,QACA,WAAW,EAAE,SAAS,MAAM;AAAA,MAC9B;AAAA,MACA,QAAQ,CAAC;AAAA,IACX;AACA,sBAAkB,WAAW;AAAA,EAC/B;AAAA,EAEQ,SAAS,QAAsB;AACrC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB;AAEtB,SAAK,IAAI,QAAQ,UAAU,wBAAwB;AAAA,MACjD;AAAA,MACA,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,cAAc,KAAK,eAAe,SAAS,EAAE;AAAA,MAC7C,aAAa,KAAK,cAAc,SAAS,EAAE;AAAA,MAC3C,iBAAiB,KAAK,mBAAmB,SAAS,EAAE;AAAA,IACtD,CAAC;AAGD,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAGA,SAAK,eAAe,KAAK;AACzB,SAAK,cAAc,KAAK;AACxB,SAAK,mBAAmB,KAAK;AAG7B,SAAK,QAAQ;AAEb,UAAM,WACJ,WAAW,aAAa,WAAW,YAAY,WAAW,WACtD,IACA;AACN,YAAQ,KAAK,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AAE3B,QAAI,CAAC,KAAK,iBAAiB,GAAG;AAC5B,WAAK,IAAI,QAAQ,UAAU,kCAAkC;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,SAAK,YAAY,KAAK,IAAI;AAG1B,SAAK,aAAa;AAGlB,SAAK,oBAAoB;AAEzB,SAAK,IAAI,QAAQ,UAAU,0BAA0B;AAAA,MACnD,KAAK,QAAQ;AAAA,MACb,QAAQ;AAAA,QACN,SAAS,KAAK,OAAO,QAAQ;AAAA,QAC7B,QAAQ,KAAK,OAAO,OAAO;AAAA,QAC3B,aAAa,KAAK,OAAO,YAAY;AAAA,QACrC,WAAW,KAAK,OAAO,UAAU;AAAA,MACnC;AAAA,IACF,CAAC;AAGD,SAAK,eAAe,MAAM;AAC1B,UAAM,KAAK,cAAc,MAAM;AAC/B,SAAK,mBAAmB,MAAM;AAG9B,SAAK,oBAAoB,YAAY,MAAM;AACzC,WAAK,aAAa;AAAA,IACpB,GAAG,KAAK,OAAO,oBAAoB,GAAI;AAGvC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,YAA0B;AACxB,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,WAAO;AAAA,MACL,SAAS,CAAC,KAAK;AAAA,MACf,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE;AAAA,QAClC,GAAG,KAAK,cAAc,SAAS,EAAE;AAAA,QACjC,GAAG,iBAAiB;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;AAGA,IACE,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAC7C,QAAQ,KAAK,CAAC,GAAG,SAAS,mBAAmB,GAC7C;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAgC,CAAC;AAGvC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,qBAAqB,KAAK,IAAI,CAAC,GAAG;AAC5C,aAAO,UAAU,EAAE,SAAS,MAAM,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE;AACtE;AAAA,IACF,WAAW,QAAQ,uBAAuB,KAAK,IAAI,CAAC,GAAG;AACrD,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE;AAAA,QAClC,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AACA;AAAA,IACF,WAAW,QAAQ,eAAe;AAChC,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,QACV,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF,WAAW,QAAQ,iBAAiB,KAAK,IAAI,CAAC,GAAG;AAC/C,aAAO,WAAW,KAAK,IAAI,CAAC;AAC5B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,cAAc,MAAM;AACvC,SAAO,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC5B,YAAQ,MAAM,2BAA2B,GAAG;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Unified Daemon for StackMemory\n *\n * Single background process managing multiple services:\n * - Context auto-save\n * - Linear sync\n * - File watch (future)\n */\n\nimport {\n existsSync,\n writeFileSync,\n unlinkSync,\n appendFileSync,\n readFileSync,\n} from 'fs';\nimport {\n loadDaemonConfig,\n getDaemonPaths,\n writeDaemonStatus,\n type DaemonConfig,\n type DaemonStatus,\n} from './daemon-config.js';\nimport { DaemonContextService } from './services/context-service.js';\nimport { DaemonLinearService } from './services/linear-service.js';\nimport { DaemonMaintenanceService } from './services/maintenance-service.js';\nimport { DaemonMemoryService } from './services/memory-service.js';\n\ninterface LogEntry {\n timestamp: string;\n level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n service: string;\n message: string;\n data?: unknown;\n}\n\nexport class UnifiedDaemon {\n private config: DaemonConfig;\n private paths: ReturnType<typeof getDaemonPaths>;\n private contextService: DaemonContextService;\n private linearService: DaemonLinearService;\n private maintenanceService: DaemonMaintenanceService;\n private memoryService: DaemonMemoryService;\n private heartbeatInterval?: NodeJS.Timeout;\n private isShuttingDown = false;\n private startTime: number = 0;\n\n constructor(config?: Partial<DaemonConfig>) {\n this.paths = getDaemonPaths();\n this.config = { ...loadDaemonConfig(), ...config };\n\n // Initialize services\n this.contextService = new DaemonContextService(\n this.config.context,\n (level, msg, data) => this.log(level, 'context', msg, data)\n );\n\n this.linearService = new DaemonLinearService(\n this.config.linear,\n (level, msg, data) => this.log(level, 'linear', msg, data)\n );\n\n this.maintenanceService = new DaemonMaintenanceService(\n this.config.maintenance,\n (level, msg, data) => this.log(level, 'maintenance', msg, data)\n );\n\n this.memoryService = new DaemonMemoryService(\n this.config.memory,\n (level, msg, data) => this.log(level, 'memory', msg, data)\n );\n }\n\n private log(\n level: string,\n service: string,\n message: string,\n data?: unknown\n ): void {\n const logLevel = level.toUpperCase() as LogEntry['level'];\n\n // Check log level\n const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];\n const configLevel = this.config.logLevel.toUpperCase();\n if (levels.indexOf(logLevel) < levels.indexOf(configLevel)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level: logLevel,\n service,\n message,\n data,\n };\n\n const logLine = JSON.stringify(entry) + '\\n';\n\n try {\n appendFileSync(this.paths.logFile, logLine);\n } catch {\n console.error(`[${entry.timestamp}] ${level} [${service}]: ${message}`);\n }\n }\n\n private checkIdempotency(): boolean {\n if (existsSync(this.paths.pidFile)) {\n try {\n const existingPid = readFileSync(this.paths.pidFile, 'utf8').trim();\n const pid = parseInt(existingPid, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n this.log('WARN', 'daemon', 'Daemon already running', { pid });\n return false;\n } catch {\n // Process not running, stale PID\n this.log('INFO', 'daemon', 'Cleaning stale PID file', { pid });\n unlinkSync(this.paths.pidFile);\n }\n } catch {\n try {\n unlinkSync(this.paths.pidFile);\n } catch {\n // Ignore\n }\n }\n }\n return true;\n }\n\n private writePidFile(): void {\n writeFileSync(this.paths.pidFile, process.pid.toString());\n this.log('INFO', 'daemon', 'PID file created', {\n pid: process.pid,\n file: this.paths.pidFile,\n });\n }\n\n private updateStatus(): void {\n const maintenanceState = this.maintenanceService.getState();\n const memoryState = this.memoryService.getState();\n const status: DaemonStatus = {\n running: true,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n memory: {\n enabled: this.config.memory.enabled,\n lastTrigger: memoryState.lastTriggerTime || undefined,\n triggerCount: memoryState.triggerCount,\n currentRamPercent: memoryState.currentRamPercent,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors.slice(-5),\n ...this.linearService.getState().errors.slice(-5),\n ...maintenanceState.errors.slice(-5),\n ...memoryState.errors.slice(-5),\n ],\n };\n\n writeDaemonStatus(status);\n }\n\n private setupSignalHandlers(): void {\n const handleSignal = (signal: string) => {\n this.log('INFO', 'daemon', `Received ${signal}, shutting down`);\n this.shutdown(signal.toLowerCase());\n };\n\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGHUP', () => handleSignal('SIGHUP'));\n\n process.on('uncaughtException', (err) => {\n this.log('ERROR', 'daemon', 'Uncaught exception', {\n error: err.message,\n stack: err.stack,\n });\n this.shutdown('uncaught_exception');\n });\n\n process.on('unhandledRejection', (reason) => {\n this.log('ERROR', 'daemon', 'Unhandled rejection', {\n reason: String(reason),\n });\n });\n }\n\n private cleanup(): void {\n // Remove PID file\n try {\n if (existsSync(this.paths.pidFile)) {\n unlinkSync(this.paths.pidFile);\n this.log('INFO', 'daemon', 'PID file removed');\n }\n } catch (e) {\n this.log('WARN', 'daemon', 'Failed to remove PID file', {\n error: String(e),\n });\n }\n\n // Update status\n const finalStatus: DaemonStatus = {\n running: false,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: false,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: false,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: false,\n staleFramesCleaned:\n this.maintenanceService.getState().staleFramesCleaned,\n ftsRebuilds: this.maintenanceService.getState().ftsRebuilds,\n },\n memory: {\n enabled: false,\n triggerCount: this.memoryService.getState().triggerCount,\n },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n writeDaemonStatus(finalStatus);\n }\n\n private shutdown(reason: string): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n this.log('INFO', 'daemon', 'Daemon shutting down', {\n reason,\n uptime: Date.now() - this.startTime,\n contextSaves: this.contextService.getState().saveCount,\n linearSyncs: this.linearService.getState().syncCount,\n maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,\n memoryTriggers: this.memoryService.getState().triggerCount,\n });\n\n // Stop heartbeat\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = undefined;\n }\n\n // Stop services\n this.contextService.stop();\n this.linearService.stop();\n this.maintenanceService.stop();\n this.memoryService.stop();\n\n // Cleanup\n this.cleanup();\n\n const exitCode =\n reason === 'sigterm' || reason === 'sigint' || reason === 'sighup'\n ? 0\n : 1;\n process.exit(exitCode);\n }\n\n async start(): Promise<void> {\n // Check idempotency\n if (!this.checkIdempotency()) {\n this.log('INFO', 'daemon', 'Exiting - daemon already running');\n process.exit(0);\n }\n\n this.startTime = Date.now();\n\n // Write PID file\n this.writePidFile();\n\n // Setup signal handlers\n this.setupSignalHandlers();\n\n this.log('INFO', 'daemon', 'Unified daemon started', {\n pid: process.pid,\n config: {\n context: this.config.context.enabled,\n linear: this.config.linear.enabled,\n maintenance: this.config.maintenance.enabled,\n memory: this.config.memory.enabled,\n fileWatch: this.config.fileWatch.enabled,\n },\n });\n\n // Start services\n this.contextService.start();\n await this.linearService.start();\n this.maintenanceService.start();\n this.memoryService.start();\n\n // Start heartbeat\n this.heartbeatInterval = setInterval(() => {\n this.updateStatus();\n }, this.config.heartbeatInterval * 1000);\n\n // Initial status update\n this.updateStatus();\n }\n\n getStatus(): DaemonStatus {\n const maintenanceState = this.maintenanceService.getState();\n const memoryState = this.memoryService.getState();\n return {\n running: !this.isShuttingDown,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n memory: {\n enabled: this.config.memory.enabled,\n lastTrigger: memoryState.lastTriggerTime || undefined,\n triggerCount: memoryState.triggerCount,\n currentRamPercent: memoryState.currentRamPercent,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors,\n ...this.linearService.getState().errors,\n ...maintenanceState.errors,\n ...memoryState.errors,\n ],\n };\n }\n}\n\n// CLI entry point\nif (\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('unified-daemon.js')\n) {\n const args = process.argv.slice(2);\n const config: Partial<DaemonConfig> = {};\n\n // Parse command line args\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--save-interval' && args[i + 1]) {\n config.context = { enabled: true, interval: parseInt(args[i + 1], 10) };\n i++;\n } else if (arg === '--linear-interval' && args[i + 1]) {\n config.linear = {\n enabled: true,\n interval: parseInt(args[i + 1], 10),\n retryAttempts: 3,\n retryDelay: 30000,\n };\n i++;\n } else if (arg === '--no-linear') {\n config.linear = {\n enabled: false,\n interval: 60,\n retryAttempts: 3,\n retryDelay: 30000,\n };\n } else if (arg === '--log-level' && args[i + 1]) {\n config.logLevel = args[i + 1] as DaemonConfig['logLevel'];\n i++;\n }\n }\n\n const daemon = new UnifiedDaemon(config);\n daemon.start().catch((err) => {\n console.error('Failed to start daemon:', err);\n process.exit(1);\n });\n}\n"],
5
+ "mappings": ";;;;;AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AAU7B,MAAM,cAAc;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAoB;AAAA,EAE5B,YAAY,QAAgC;AAC1C,SAAK,QAAQ,eAAe;AAC5B,SAAK,SAAS,EAAE,GAAG,iBAAiB,GAAG,GAAG,OAAO;AAGjD,SAAK,iBAAiB,IAAI;AAAA,MACxB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,WAAW,KAAK,IAAI;AAAA,IAC5D;AAEA,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3D;AAEA,SAAK,qBAAqB,IAAI;AAAA,MAC5B,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI;AAAA,IAChE;AAEA,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3D;AAAA,EACF;AAAA,EAEQ,IACN,OACA,SACA,SACA,MACM;AACN,UAAM,WAAW,MAAM,YAAY;AAGnC,UAAM,SAAS,CAAC,SAAS,QAAQ,QAAQ,OAAO;AAChD,UAAM,cAAc,KAAK,OAAO,SAAS,YAAY;AACrD,QAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO,QAAQ,WAAW,GAAG;AAC1D;AAAA,IACF;AAEA,UAAM,QAAkB;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,KAAK,IAAI;AAExC,QAAI;AACF,qBAAe,KAAK,MAAM,SAAS,OAAO;AAAA,IAC5C,QAAQ;AACN,cAAQ,MAAM,IAAI,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,IACxE;AAAA,EACF;AAAA,EAEQ,mBAA4B;AAClC,QAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,UAAI;AACF,cAAM,cAAc,aAAa,KAAK,MAAM,SAAS,MAAM,EAAE,KAAK;AAClE,cAAM,MAAM,SAAS,aAAa,EAAE;AAGpC,YAAI;AACF,kBAAQ,KAAK,KAAK,CAAC;AACnB,eAAK,IAAI,QAAQ,UAAU,0BAA0B,EAAE,IAAI,CAAC;AAC5D,iBAAO;AAAA,QACT,QAAQ;AAEN,eAAK,IAAI,QAAQ,UAAU,2BAA2B,EAAE,IAAI,CAAC;AAC7D,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B;AAAA,MACF,QAAQ;AACN,YAAI;AACF,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAC3B,kBAAc,KAAK,MAAM,SAAS,QAAQ,IAAI,SAAS,CAAC;AACxD,SAAK,IAAI,QAAQ,UAAU,oBAAoB;AAAA,MAC7C,KAAK,QAAQ;AAAA,MACb,MAAM,KAAK,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC3B,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,UAAM,cAAc,KAAK,cAAc,SAAS;AAChD,UAAM,SAAuB;AAAA,MAC3B,SAAS;AAAA,MACT,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,aAAa,YAAY,mBAAmB;AAAA,UAC5C,cAAc,YAAY;AAAA,UAC1B,mBAAmB,YAAY;AAAA,QACjC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QACjD,GAAG,KAAK,cAAc,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QAChD,GAAG,iBAAiB,OAAO,MAAM,EAAE;AAAA,QACnC,GAAG,YAAY,OAAO,MAAM,EAAE;AAAA,MAChC;AAAA,IACF;AAEA,sBAAkB,MAAM;AAAA,EAC1B;AAAA,EAEQ,sBAA4B;AAClC,UAAM,eAAe,CAAC,WAAmB;AACvC,WAAK,IAAI,QAAQ,UAAU,YAAY,MAAM,iBAAiB;AAC9D,WAAK,SAAS,OAAO,YAAY,CAAC;AAAA,IACpC;AAEA,YAAQ,GAAG,WAAW,MAAM,aAAa,SAAS,CAAC;AACnD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AACjD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AAEjD,YAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,WAAK,IAAI,SAAS,UAAU,sBAAsB;AAAA,QAChD,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,MACb,CAAC;AACD,WAAK,SAAS,oBAAoB;AAAA,IACpC,CAAC;AAED,YAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,WAAK,IAAI,SAAS,UAAU,uBAAuB;AAAA,QACjD,QAAQ,OAAO,MAAM;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,UAAgB;AAEtB,QAAI;AACF,UAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,mBAAW,KAAK,MAAM,OAAO;AAC7B,aAAK,IAAI,QAAQ,UAAU,kBAAkB;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,QAAQ,UAAU,6BAA6B;AAAA,QACtD,OAAO,OAAO,CAAC;AAAA,MACjB,CAAC;AAAA,IACH;AAGA,UAAM,cAA4B;AAAA,MAChC,SAAS;AAAA,MACT,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS;AAAA,UACT,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS;AAAA,UACT,oBACE,KAAK,mBAAmB,SAAS,EAAE;AAAA,UACrC,aAAa,KAAK,mBAAmB,SAAS,EAAE;AAAA,QAClD;AAAA,QACA,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,cAAc,KAAK,cAAc,SAAS,EAAE;AAAA,QAC9C;AAAA,QACA,WAAW,EAAE,SAAS,MAAM;AAAA,MAC9B;AAAA,MACA,QAAQ,CAAC;AAAA,IACX;AACA,sBAAkB,WAAW;AAAA,EAC/B;AAAA,EAEQ,SAAS,QAAsB;AACrC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB;AAEtB,SAAK,IAAI,QAAQ,UAAU,wBAAwB;AAAA,MACjD;AAAA,MACA,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,cAAc,KAAK,eAAe,SAAS,EAAE;AAAA,MAC7C,aAAa,KAAK,cAAc,SAAS,EAAE;AAAA,MAC3C,iBAAiB,KAAK,mBAAmB,SAAS,EAAE;AAAA,MACpD,gBAAgB,KAAK,cAAc,SAAS,EAAE;AAAA,IAChD,CAAC;AAGD,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAGA,SAAK,eAAe,KAAK;AACzB,SAAK,cAAc,KAAK;AACxB,SAAK,mBAAmB,KAAK;AAC7B,SAAK,cAAc,KAAK;AAGxB,SAAK,QAAQ;AAEb,UAAM,WACJ,WAAW,aAAa,WAAW,YAAY,WAAW,WACtD,IACA;AACN,YAAQ,KAAK,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AAE3B,QAAI,CAAC,KAAK,iBAAiB,GAAG;AAC5B,WAAK,IAAI,QAAQ,UAAU,kCAAkC;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,SAAK,YAAY,KAAK,IAAI;AAG1B,SAAK,aAAa;AAGlB,SAAK,oBAAoB;AAEzB,SAAK,IAAI,QAAQ,UAAU,0BAA0B;AAAA,MACnD,KAAK,QAAQ;AAAA,MACb,QAAQ;AAAA,QACN,SAAS,KAAK,OAAO,QAAQ;AAAA,QAC7B,QAAQ,KAAK,OAAO,OAAO;AAAA,QAC3B,aAAa,KAAK,OAAO,YAAY;AAAA,QACrC,QAAQ,KAAK,OAAO,OAAO;AAAA,QAC3B,WAAW,KAAK,OAAO,UAAU;AAAA,MACnC;AAAA,IACF,CAAC;AAGD,SAAK,eAAe,MAAM;AAC1B,UAAM,KAAK,cAAc,MAAM;AAC/B,SAAK,mBAAmB,MAAM;AAC9B,SAAK,cAAc,MAAM;AAGzB,SAAK,oBAAoB,YAAY,MAAM;AACzC,WAAK,aAAa;AAAA,IACpB,GAAG,KAAK,OAAO,oBAAoB,GAAI;AAGvC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,YAA0B;AACxB,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,UAAM,cAAc,KAAK,cAAc,SAAS;AAChD,WAAO;AAAA,MACL,SAAS,CAAC,KAAK;AAAA,MACf,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,aAAa,YAAY,mBAAmB;AAAA,UAC5C,cAAc,YAAY;AAAA,UAC1B,mBAAmB,YAAY;AAAA,QACjC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE;AAAA,QAClC,GAAG,KAAK,cAAc,SAAS,EAAE;AAAA,QACjC,GAAG,iBAAiB;AAAA,QACpB,GAAG,YAAY;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;AAGA,IACE,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAC7C,QAAQ,KAAK,CAAC,GAAG,SAAS,mBAAmB,GAC7C;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAgC,CAAC;AAGvC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,qBAAqB,KAAK,IAAI,CAAC,GAAG;AAC5C,aAAO,UAAU,EAAE,SAAS,MAAM,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE;AACtE;AAAA,IACF,WAAW,QAAQ,uBAAuB,KAAK,IAAI,CAAC,GAAG;AACrD,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE;AAAA,QAClC,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AACA;AAAA,IACF,WAAW,QAAQ,eAAe;AAChC,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,QACV,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF,WAAW,QAAQ,iBAAiB,KAAK,IAAI,CAAC,GAAG;AAC/C,aAAO,WAAW,KAAK,IAAI,CAAC;AAC5B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,cAAc,MAAM;AACvC,SAAO,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC5B,YAAQ,MAAM,2BAA2B,GAAG;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -29,7 +29,8 @@ class PtyWrapper {
29
29
  this.config = {
30
30
  claudeBin: config.claudeBin || this.findClaude(),
31
31
  claudeArgs: config.claudeArgs || [],
32
- stateFile: config.stateFile || getSweepPath("sweep-state.json")
32
+ stateFile: config.stateFile || getSweepPath("sweep-state.json"),
33
+ initialInput: config.initialInput || ""
33
34
  };
34
35
  this.stateWatcher = new SweepStateWatcher(this.config.stateFile);
35
36
  this.statusBar = new StatusBar();
@@ -69,10 +70,19 @@ class PtyWrapper {
69
70
  process.stdin.setRawMode(true);
70
71
  }
71
72
  process.stdin.resume();
73
+ let inputInjected = false;
74
+ const initialInput = this.config.initialInput;
75
+ const ptyRef = this.ptyProcess;
72
76
  this.ptyProcess.onData((data) => {
73
77
  if (data.includes(ALT_SCREEN_ENTER)) {
74
78
  this.inAltScreen = true;
75
79
  this.statusBar.hide();
80
+ if (!inputInjected && initialInput && ptyRef) {
81
+ inputInjected = true;
82
+ setTimeout(() => {
83
+ ptyRef.write("\x1B[200~" + initialInput + "\x1B[201~");
84
+ }, 800);
85
+ }
76
86
  }
77
87
  if (data.includes(ALT_SCREEN_EXIT)) {
78
88
  this.inAltScreen = false;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/features/sweep/pty-wrapper.ts"],
4
- "sourcesContent": ["/**\n * Sweep PTY Wrapper\n *\n * Wraps Claude Code in a pseudo-terminal to add a Sweep prediction\n * status bar at the bottom of the terminal. Predictions from the\n * PostToolUse hook are displayed via the status bar. Tab to accept,\n * Esc to dismiss.\n */\n\nimport { join } from 'path';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { SweepStateWatcher, type PredictionEvent } from './state-watcher.js';\nimport { StatusBar } from './status-bar.js';\nimport { TabInterceptor } from './tab-interceptor.js';\n\nconst HOME = process.env['HOME'] || '/tmp';\n\nfunction getSweepDir(): string {\n return process.env['SWEEP_STATE_DIR'] || join(HOME, '.stackmemory');\n}\n\nfunction getSweepPath(filename: string): string {\n return join(getSweepDir(), filename);\n}\n\n// Alt screen buffer detection\nconst ALT_SCREEN_ENTER = '\\x1b[?1049h';\nconst ALT_SCREEN_EXIT = '\\x1b[?1049l';\n\nexport interface PtyWrapperConfig {\n claudeBin?: string;\n claudeArgs?: string[];\n stateFile?: string;\n}\n\n// Minimal interface for node-pty process to avoid compile-time dep\ninterface PtyProcess {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n onData(cb: (data: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n kill(): void;\n}\n\nexport class PtyWrapper {\n private config: Required<PtyWrapperConfig>;\n private stateWatcher: SweepStateWatcher;\n private statusBar: StatusBar;\n private tabInterceptor: TabInterceptor;\n private currentPrediction: PredictionEvent | null = null;\n private inAltScreen = false;\n private ptyProcess: PtyProcess | null = null;\n\n constructor(config: PtyWrapperConfig = {}) {\n this.config = {\n claudeBin: config.claudeBin || this.findClaude(),\n claudeArgs: config.claudeArgs || [],\n stateFile: config.stateFile || getSweepPath('sweep-state.json'),\n };\n\n this.stateWatcher = new SweepStateWatcher(this.config.stateFile);\n this.statusBar = new StatusBar();\n this.tabInterceptor = new TabInterceptor({\n onAccept: () => this.acceptPrediction(),\n onDismiss: () => this.dismissPrediction(),\n onPassthrough: (data) => this.ptyProcess?.write(data.toString('utf-8')),\n });\n }\n\n async start(): Promise<void> {\n // Ensure the sweep state directory exists\n const sweepDir = getSweepDir();\n if (!existsSync(sweepDir)) {\n mkdirSync(sweepDir, { recursive: true });\n }\n\n // Dynamic import for optional dependency\n let pty: typeof import('node-pty');\n try {\n pty = await import('node-pty');\n } catch {\n throw new Error(\n 'node-pty is required for the PTY wrapper.\\n' +\n 'Install with: npm install node-pty'\n );\n }\n\n const cols = process.stdout.columns || 80;\n const rows = process.stdout.rows || 24;\n\n // Filter undefined values from env\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) env[k] = v;\n }\n\n // Spawn Claude Code in a PTY with 1 row reserved for status bar\n this.ptyProcess = pty.spawn(this.config.claudeBin, this.config.claudeArgs, {\n name: process.env['TERM'] || 'xterm-256color',\n cols,\n rows: rows - 1,\n cwd: process.cwd(),\n env,\n }) as PtyProcess;\n\n // Set raw mode on stdin\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n process.stdin.resume();\n\n // PTY stdout -> parent stdout (transparent passthrough)\n this.ptyProcess.onData((data: string) => {\n // Detect alt screen buffer transitions\n if (data.includes(ALT_SCREEN_ENTER)) {\n this.inAltScreen = true;\n this.statusBar.hide();\n }\n if (data.includes(ALT_SCREEN_EXIT)) {\n this.inAltScreen = false;\n }\n\n process.stdout.write(data);\n });\n\n // Parent stdin -> tab interceptor -> PTY\n process.stdin.on('data', (data: Buffer) => {\n this.tabInterceptor.process(data);\n });\n\n // State watcher -> status bar\n this.stateWatcher.on('loading', () => {\n if (!this.inAltScreen) {\n this.statusBar.showLoading();\n }\n });\n\n this.stateWatcher.on('prediction', (event: PredictionEvent) => {\n this.currentPrediction = event;\n this.tabInterceptor.setPredictionActive(true);\n if (!this.inAltScreen) {\n this.statusBar.show(\n event.prediction,\n event.file_path,\n event.latency_ms\n );\n }\n });\n\n this.stateWatcher.start();\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const newCols = process.stdout.columns || 80;\n const newRows = process.stdout.rows || 24;\n this.ptyProcess?.resize(newCols, newRows - 1);\n this.statusBar.resize(newRows, newCols);\n });\n\n // Handle PTY exit\n this.ptyProcess.onExit(({ exitCode }) => {\n this.cleanup();\n process.exit(exitCode);\n });\n\n // Handle signals\n const onSignal = () => {\n this.cleanup();\n process.exit(0);\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n }\n\n private acceptPrediction(): void {\n if (!this.currentPrediction || !this.ptyProcess) return;\n\n // Write prediction to pending file for Claude to read\n const dir = getSweepDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const pendingFile = getSweepPath('sweep-pending.json');\n writeFileSync(\n pendingFile,\n JSON.stringify(\n {\n file_path: this.currentPrediction.file_path,\n predicted_content: this.currentPrediction.prediction,\n timestamp: Date.now(),\n },\n null,\n 2\n )\n );\n\n // Inject acceptance prompt into PTY stdin.\n // SAFETY: pendingFile is derived from env or a constant path, not\n // arbitrary user input. The prompt is written to Claude Code's input,\n // which interprets it as a user message, not as a shell command.\n const prompt = `Apply the Sweep prediction from ${pendingFile}\\n`;\n this.ptyProcess.write(prompt);\n\n this.dismissPrediction();\n }\n\n private dismissPrediction(): void {\n this.currentPrediction = null;\n this.tabInterceptor.setPredictionActive(false);\n this.statusBar.hide();\n }\n\n private cleanup(): void {\n this.stateWatcher.stop();\n this.statusBar.hide();\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n }\n\n private findClaude(): string {\n // Check PATH first via which\n try {\n const resolved = execSync('which claude', { encoding: 'utf-8' }).trim();\n if (resolved) return resolved;\n } catch {\n // Not on PATH\n }\n\n // Check known locations\n const candidates = [\n join(HOME, '.bun', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n\n return 'claude';\n }\n}\n\n/**\n * Launch the PTY wrapper\n */\nexport async function launchWrapper(config?: PtyWrapperConfig): Promise<void> {\n const wrapper = new PtyWrapper(config);\n await wrapper.start();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Sweep PTY Wrapper\n *\n * Wraps Claude Code in a pseudo-terminal to add a Sweep prediction\n * status bar at the bottom of the terminal. Predictions from the\n * PostToolUse hook are displayed via the status bar. Tab to accept,\n * Esc to dismiss.\n */\n\nimport { join } from 'path';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { SweepStateWatcher, type PredictionEvent } from './state-watcher.js';\nimport { StatusBar } from './status-bar.js';\nimport { TabInterceptor } from './tab-interceptor.js';\n\nconst HOME = process.env['HOME'] || '/tmp';\n\nfunction getSweepDir(): string {\n return process.env['SWEEP_STATE_DIR'] || join(HOME, '.stackmemory');\n}\n\nfunction getSweepPath(filename: string): string {\n return join(getSweepDir(), filename);\n}\n\n// Alt screen buffer detection\nconst ALT_SCREEN_ENTER = '\\x1b[?1049h';\nconst ALT_SCREEN_EXIT = '\\x1b[?1049l';\n\nexport interface PtyWrapperConfig {\n claudeBin?: string;\n claudeArgs?: string[];\n stateFile?: string;\n initialInput?: string;\n}\n\n// Minimal interface for node-pty process to avoid compile-time dep\ninterface PtyProcess {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n onData(cb: (data: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n kill(): void;\n}\n\nexport class PtyWrapper {\n private config: Required<PtyWrapperConfig>;\n private stateWatcher: SweepStateWatcher;\n private statusBar: StatusBar;\n private tabInterceptor: TabInterceptor;\n private currentPrediction: PredictionEvent | null = null;\n private inAltScreen = false;\n private ptyProcess: PtyProcess | null = null;\n\n constructor(config: PtyWrapperConfig = {}) {\n this.config = {\n claudeBin: config.claudeBin || this.findClaude(),\n claudeArgs: config.claudeArgs || [],\n stateFile: config.stateFile || getSweepPath('sweep-state.json'),\n initialInput: config.initialInput || '',\n };\n\n this.stateWatcher = new SweepStateWatcher(this.config.stateFile);\n this.statusBar = new StatusBar();\n this.tabInterceptor = new TabInterceptor({\n onAccept: () => this.acceptPrediction(),\n onDismiss: () => this.dismissPrediction(),\n onPassthrough: (data) => this.ptyProcess?.write(data.toString('utf-8')),\n });\n }\n\n async start(): Promise<void> {\n // Ensure the sweep state directory exists\n const sweepDir = getSweepDir();\n if (!existsSync(sweepDir)) {\n mkdirSync(sweepDir, { recursive: true });\n }\n\n // Dynamic import for optional dependency\n let pty: typeof import('node-pty');\n try {\n pty = await import('node-pty');\n } catch {\n throw new Error(\n 'node-pty is required for the PTY wrapper.\\n' +\n 'Install with: npm install node-pty'\n );\n }\n\n const cols = process.stdout.columns || 80;\n const rows = process.stdout.rows || 24;\n\n // Filter undefined values from env\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) env[k] = v;\n }\n\n // Spawn Claude Code in a PTY with 1 row reserved for status bar\n this.ptyProcess = pty.spawn(this.config.claudeBin, this.config.claudeArgs, {\n name: process.env['TERM'] || 'xterm-256color',\n cols,\n rows: rows - 1,\n cwd: process.cwd(),\n env,\n }) as PtyProcess;\n\n // Set raw mode on stdin\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n process.stdin.resume();\n\n // PTY stdout -> parent stdout (transparent passthrough)\n let inputInjected = false;\n const initialInput = this.config.initialInput;\n const ptyRef = this.ptyProcess;\n\n this.ptyProcess.onData((data: string) => {\n // Detect alt screen buffer transitions\n if (data.includes(ALT_SCREEN_ENTER)) {\n this.inAltScreen = true;\n this.statusBar.hide();\n\n // Inject initial input as bracketed paste after Claude UI enters alt screen\n if (!inputInjected && initialInput && ptyRef) {\n inputInjected = true;\n setTimeout(() => {\n // Bracketed paste mode: text appears in input area without auto-sending\n ptyRef.write('\\x1b[200~' + initialInput + '\\x1b[201~');\n }, 800);\n }\n }\n if (data.includes(ALT_SCREEN_EXIT)) {\n this.inAltScreen = false;\n }\n\n process.stdout.write(data);\n });\n\n // Parent stdin -> tab interceptor -> PTY\n process.stdin.on('data', (data: Buffer) => {\n this.tabInterceptor.process(data);\n });\n\n // State watcher -> status bar\n this.stateWatcher.on('loading', () => {\n if (!this.inAltScreen) {\n this.statusBar.showLoading();\n }\n });\n\n this.stateWatcher.on('prediction', (event: PredictionEvent) => {\n this.currentPrediction = event;\n this.tabInterceptor.setPredictionActive(true);\n if (!this.inAltScreen) {\n this.statusBar.show(\n event.prediction,\n event.file_path,\n event.latency_ms\n );\n }\n });\n\n this.stateWatcher.start();\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const newCols = process.stdout.columns || 80;\n const newRows = process.stdout.rows || 24;\n this.ptyProcess?.resize(newCols, newRows - 1);\n this.statusBar.resize(newRows, newCols);\n });\n\n // Handle PTY exit\n this.ptyProcess.onExit(({ exitCode }) => {\n this.cleanup();\n process.exit(exitCode);\n });\n\n // Handle signals\n const onSignal = () => {\n this.cleanup();\n process.exit(0);\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n }\n\n private acceptPrediction(): void {\n if (!this.currentPrediction || !this.ptyProcess) return;\n\n // Write prediction to pending file for Claude to read\n const dir = getSweepDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const pendingFile = getSweepPath('sweep-pending.json');\n writeFileSync(\n pendingFile,\n JSON.stringify(\n {\n file_path: this.currentPrediction.file_path,\n predicted_content: this.currentPrediction.prediction,\n timestamp: Date.now(),\n },\n null,\n 2\n )\n );\n\n // Inject acceptance prompt into PTY stdin.\n // SAFETY: pendingFile is derived from env or a constant path, not\n // arbitrary user input. The prompt is written to Claude Code's input,\n // which interprets it as a user message, not as a shell command.\n const prompt = `Apply the Sweep prediction from ${pendingFile}\\n`;\n this.ptyProcess.write(prompt);\n\n this.dismissPrediction();\n }\n\n private dismissPrediction(): void {\n this.currentPrediction = null;\n this.tabInterceptor.setPredictionActive(false);\n this.statusBar.hide();\n }\n\n private cleanup(): void {\n this.stateWatcher.stop();\n this.statusBar.hide();\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n }\n\n private findClaude(): string {\n // Check PATH first via which\n try {\n const resolved = execSync('which claude', { encoding: 'utf-8' }).trim();\n if (resolved) return resolved;\n } catch {\n // Not on PATH\n }\n\n // Check known locations\n const candidates = [\n join(HOME, '.bun', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n\n return 'claude';\n }\n}\n\n/**\n * Launch the PTY wrapper\n */\nexport async function launchWrapper(config?: PtyWrapperConfig): Promise<void> {\n const wrapper = new PtyWrapper(config);\n await wrapper.start();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,4 @@ export * from "./events.js";
6
6
  export * from "./config.js";
7
7
  export * from "./daemon.js";
8
8
  export * from "./auto-background.js";
9
- export * from "./sms-notify.js";
10
- export * from "./sms-webhook.js";
11
- export * from "./sms-action-runner.js";
12
9
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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';\nexport * from './auto-background.js';\nexport * from './sms-notify.js';\nexport * from './sms-webhook.js';\nexport * from './sms-action-runner.js';\n"],
5
- "mappings": ";;;;AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
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';\nexport * from './auto-background.js';\n"],
5
+ "mappings": ";;;;AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
6
  "names": []
7
7
  }
@@ -4,63 +4,6 @@ const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { z } from "zod";
6
6
  import { logConfigInvalid } from "./security-logger.js";
7
- const PromptOptionSchema = z.object({
8
- key: z.string().max(10),
9
- label: z.string().max(200),
10
- action: z.string().max(500).optional()
11
- });
12
- const PendingPromptSchema = z.object({
13
- id: z.string().max(32),
14
- timestamp: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)),
15
- message: z.string().max(1e3),
16
- options: z.array(PromptOptionSchema).max(10),
17
- type: z.enum(["options", "yesno", "freeform"]),
18
- callback: z.string().max(500).optional(),
19
- expiresAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
20
- });
21
- const NotifyOnSchema = z.object({
22
- taskComplete: z.boolean(),
23
- reviewReady: z.boolean(),
24
- error: z.boolean(),
25
- custom: z.boolean(),
26
- contextSync: z.boolean().optional().default(true)
27
- });
28
- const QuietHoursSchema = z.object({
29
- enabled: z.boolean(),
30
- start: z.string().regex(/^\d{2}:\d{2}$/),
31
- end: z.string().regex(/^\d{2}:\d{2}$/)
32
- });
33
- const SMSConfigSchema = z.object({
34
- enabled: z.boolean(),
35
- channel: z.enum(["whatsapp", "sms"]),
36
- accountSid: z.string().max(100).optional(),
37
- authToken: z.string().max(100).optional(),
38
- smsFromNumber: z.string().max(20).optional(),
39
- smsToNumber: z.string().max(20).optional(),
40
- whatsappFromNumber: z.string().max(30).optional(),
41
- whatsappToNumber: z.string().max(30).optional(),
42
- fromNumber: z.string().max(20).optional(),
43
- toNumber: z.string().max(20).optional(),
44
- webhookUrl: z.string().url().max(500).optional(),
45
- notifyOn: NotifyOnSchema,
46
- quietHours: QuietHoursSchema.optional(),
47
- responseTimeout: z.number().int().min(30).max(3600),
48
- pendingPrompts: z.array(PendingPromptSchema).max(100)
49
- });
50
- const PendingActionSchema = z.object({
51
- id: z.string().max(32),
52
- promptId: z.string().max(32),
53
- response: z.string().max(1e3),
54
- action: z.string().max(500),
55
- timestamp: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)),
56
- status: z.enum(["pending", "running", "completed", "failed"]),
57
- result: z.string().max(1e4).optional(),
58
- error: z.string().max(1e3).optional()
59
- });
60
- const ActionQueueSchema = z.object({
61
- actions: z.array(PendingActionSchema).max(1e3),
62
- lastChecked: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
63
- });
64
7
  const AutoBackgroundConfigSchema = z.object({
65
8
  enabled: z.boolean(),
66
9
  timeoutMs: z.number().int().min(1e3).max(6e5),
@@ -68,53 +11,6 @@ const AutoBackgroundConfigSchema = z.object({
68
11
  neverBackground: z.array(z.string().max(200)).max(100),
69
12
  verbose: z.boolean().optional()
70
13
  });
71
- const SyncOptionsSchema = z.object({
72
- autoSyncOnClose: z.boolean(),
73
- minFrameDuration: z.number().int().min(0).max(3600),
74
- // 0 to 1 hour
75
- includeDecisions: z.boolean(),
76
- includeFiles: z.boolean(),
77
- includeTests: z.boolean(),
78
- maxDigestLength: z.number().int().min(100).max(1e3)
79
- // WhatsApp limit ~4096 chars
80
- });
81
- const ScheduleConfigSchema = z.object({
82
- type: z.enum(["daily", "hourly", "interval"]),
83
- time: z.string().regex(/^\d{2}:\d{2}$/).optional(),
84
- // "HH:MM" for daily
85
- intervalMinutes: z.number().int().min(5).max(1440).optional(),
86
- // 5 min to 24 hours
87
- includeInactive: z.boolean(),
88
- // Include when no activity
89
- quietHoursRespect: z.boolean()
90
- // Respect quiet hours setting
91
- });
92
- const ScheduleSchema = z.object({
93
- id: z.string().max(32),
94
- config: ScheduleConfigSchema,
95
- enabled: z.boolean(),
96
- lastRun: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)).optional(),
97
- nextRun: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)).optional(),
98
- createdAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
99
- });
100
- const ScheduleStorageSchema = z.object({
101
- schedules: z.array(ScheduleSchema).max(10),
102
- lastChecked: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
103
- });
104
- const WhatsAppCommandSchema = z.object({
105
- name: z.string().max(50),
106
- description: z.string().max(200),
107
- enabled: z.boolean(),
108
- action: z.string().max(500).optional(),
109
- // Safe action to execute
110
- requiresArg: z.boolean().optional(),
111
- argPattern: z.string().max(100).optional()
112
- // Regex pattern for arg validation
113
- });
114
- const WhatsAppCommandsConfigSchema = z.object({
115
- enabled: z.boolean(),
116
- commands: z.array(WhatsAppCommandSchema).max(50)
117
- });
118
14
  const ModelProviderSchema = z.enum([
119
15
  "anthropic",
120
16
  "qwen",
@@ -175,23 +71,10 @@ function parseConfigSafe(schema, data, defaultValue, configName) {
175
71
  return defaultValue;
176
72
  }
177
73
  export {
178
- ActionQueueSchema,
179
74
  AutoBackgroundConfigSchema,
180
75
  ModelConfigSchema,
181
76
  ModelProviderSchema,
182
77
  ModelRouterConfigSchema,
183
- NotifyOnSchema,
184
- PendingActionSchema,
185
- PendingPromptSchema,
186
- PromptOptionSchema,
187
- QuietHoursSchema,
188
- SMSConfigSchema,
189
- ScheduleConfigSchema,
190
- ScheduleSchema,
191
- ScheduleStorageSchema,
192
- SyncOptionsSchema,
193
- WhatsAppCommandSchema,
194
- WhatsAppCommandsConfigSchema,
195
78
  parseConfigSafe
196
79
  };
197
80
  //# sourceMappingURL=schemas.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/schemas.ts"],
4
- "sourcesContent": ["/**\n * Zod schemas for hook configuration validation\n * Prevents malformed or malicious configs from being loaded\n */\n\nimport { z } from 'zod';\nimport { logConfigInvalid } from './security-logger.js';\n\n// SMS/WhatsApp notification schemas\nexport const PromptOptionSchema = z.object({\n key: z.string().max(10),\n label: z.string().max(200),\n action: z.string().max(500).optional(),\n});\n\nexport const PendingPromptSchema = z.object({\n id: z.string().max(32),\n timestamp: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n message: z.string().max(1000),\n options: z.array(PromptOptionSchema).max(10),\n type: z.enum(['options', 'yesno', 'freeform']),\n callback: z.string().max(500).optional(),\n expiresAt: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n});\n\nexport const NotifyOnSchema = z.object({\n taskComplete: z.boolean(),\n reviewReady: z.boolean(),\n error: z.boolean(),\n custom: z.boolean(),\n contextSync: z.boolean().optional().default(true),\n});\n\nexport const QuietHoursSchema = z.object({\n enabled: z.boolean(),\n start: z.string().regex(/^\\d{2}:\\d{2}$/),\n end: z.string().regex(/^\\d{2}:\\d{2}$/),\n});\n\nexport const SMSConfigSchema = z.object({\n enabled: z.boolean(),\n channel: z.enum(['whatsapp', 'sms']),\n accountSid: z.string().max(100).optional(),\n authToken: z.string().max(100).optional(),\n smsFromNumber: z.string().max(20).optional(),\n smsToNumber: z.string().max(20).optional(),\n whatsappFromNumber: z.string().max(30).optional(),\n whatsappToNumber: z.string().max(30).optional(),\n fromNumber: z.string().max(20).optional(),\n toNumber: z.string().max(20).optional(),\n webhookUrl: z.string().url().max(500).optional(),\n notifyOn: NotifyOnSchema,\n quietHours: QuietHoursSchema.optional(),\n responseTimeout: z.number().int().min(30).max(3600),\n pendingPrompts: z.array(PendingPromptSchema).max(100),\n});\n\n// Action queue schemas\nexport const PendingActionSchema = z.object({\n id: z.string().max(32),\n promptId: z.string().max(32),\n response: z.string().max(1000),\n action: z.string().max(500),\n timestamp: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n status: z.enum(['pending', 'running', 'completed', 'failed']),\n result: z.string().max(10000).optional(),\n error: z.string().max(1000).optional(),\n});\n\nexport const ActionQueueSchema = z.object({\n actions: z.array(PendingActionSchema).max(1000),\n lastChecked: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n});\n\n// Auto-background config schema\nexport const AutoBackgroundConfigSchema = z.object({\n enabled: z.boolean(),\n timeoutMs: z.number().int().min(1000).max(600000),\n alwaysBackground: z.array(z.string().max(200)).max(100),\n neverBackground: z.array(z.string().max(200)).max(100),\n verbose: z.boolean().optional(),\n});\n\n// WhatsApp Sync Options schema\nexport const SyncOptionsSchema = z.object({\n autoSyncOnClose: z.boolean(),\n minFrameDuration: z.number().int().min(0).max(3600), // 0 to 1 hour\n includeDecisions: z.boolean(),\n includeFiles: z.boolean(),\n includeTests: z.boolean(),\n maxDigestLength: z.number().int().min(100).max(1000), // WhatsApp limit ~4096 chars\n});\n\n// WhatsApp Schedule Config schema\nexport const ScheduleConfigSchema = z.object({\n type: z.enum(['daily', 'hourly', 'interval']),\n time: z\n .string()\n .regex(/^\\d{2}:\\d{2}$/)\n .optional(), // \"HH:MM\" for daily\n intervalMinutes: z.number().int().min(5).max(1440).optional(), // 5 min to 24 hours\n includeInactive: z.boolean(), // Include when no activity\n quietHoursRespect: z.boolean(), // Respect quiet hours setting\n});\n\n// WhatsApp Schedule storage schema\nexport const ScheduleSchema = z.object({\n id: z.string().max(32),\n config: ScheduleConfigSchema,\n enabled: z.boolean(),\n lastRun: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/))\n .optional(),\n nextRun: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/))\n .optional(),\n createdAt: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n});\n\nexport const ScheduleStorageSchema = z.object({\n schedules: z.array(ScheduleSchema).max(10),\n lastChecked: z\n .string()\n .datetime({ offset: true })\n .or(z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T/)),\n});\n\n// WhatsApp Command schema\nexport const WhatsAppCommandSchema = z.object({\n name: z.string().max(50),\n description: z.string().max(200),\n enabled: z.boolean(),\n action: z.string().max(500).optional(), // Safe action to execute\n requiresArg: z.boolean().optional(),\n argPattern: z.string().max(100).optional(), // Regex pattern for arg validation\n});\n\nexport const WhatsAppCommandsConfigSchema = z.object({\n enabled: z.boolean(),\n commands: z.array(WhatsAppCommandSchema).max(50),\n});\n\n// Model Router schemas\nexport const ModelProviderSchema = z.enum([\n 'anthropic',\n 'qwen',\n 'openai',\n 'ollama',\n 'custom',\n]);\n\nexport const ModelConfigSchema = z.object({\n provider: ModelProviderSchema,\n model: z.string().max(100),\n baseUrl: z.string().url().max(500).optional(),\n apiKeyEnv: z.string().max(100),\n headers: z.record(z.string().max(500)).optional(),\n params: z.record(z.unknown()).optional(),\n});\n\nexport const ModelRouterConfigSchema = z.object({\n enabled: z.boolean(),\n defaultProvider: ModelProviderSchema,\n taskRouting: z\n .object({\n plan: ModelProviderSchema.optional(),\n think: ModelProviderSchema.optional(),\n code: ModelProviderSchema.optional(),\n review: ModelProviderSchema.optional(),\n })\n .optional()\n .default({}),\n fallback: z.object({\n enabled: z.boolean(),\n provider: ModelProviderSchema,\n onRateLimit: z.boolean(),\n onError: z.boolean(),\n onTimeout: z.boolean(),\n maxRetries: z.number().int().min(0).max(10),\n retryDelayMs: z.number().int().min(100).max(30000),\n }),\n providers: z\n .object({\n anthropic: ModelConfigSchema.optional(),\n qwen: ModelConfigSchema.optional(),\n openai: ModelConfigSchema.optional(),\n ollama: ModelConfigSchema.optional(),\n custom: ModelConfigSchema.optional(),\n })\n .optional()\n .default({}),\n thinkingMode: z.object({\n enabled: z.boolean(),\n budget: z.number().int().min(1000).max(100000).optional(),\n temperature: z.number().min(0).max(2).optional(),\n topP: z.number().min(0).max(1).optional(),\n }),\n});\n\n// Type exports\nexport type ModelRouterConfigValidated = z.infer<\n typeof ModelRouterConfigSchema\n>;\nexport type SMSConfigValidated = z.infer<typeof SMSConfigSchema>;\nexport type ActionQueueValidated = z.infer<typeof ActionQueueSchema>;\nexport type AutoBackgroundConfigValidated = z.infer<\n typeof AutoBackgroundConfigSchema\n>;\nexport type SyncOptionsValidated = z.infer<typeof SyncOptionsSchema>;\nexport type ScheduleConfigValidated = z.infer<typeof ScheduleConfigSchema>;\nexport type ScheduleValidated = z.infer<typeof ScheduleSchema>;\nexport type ScheduleStorageValidated = z.infer<typeof ScheduleStorageSchema>;\nexport type WhatsAppCommandValidated = z.infer<typeof WhatsAppCommandSchema>;\nexport type WhatsAppCommandsConfigValidated = z.infer<\n typeof WhatsAppCommandsConfigSchema\n>;\n\n/**\n * Safely parse and validate config, returning default on failure\n */\nexport function parseConfigSafe<T>(\n schema: z.ZodSchema<T>,\n data: unknown,\n defaultValue: T,\n configName: string\n): T {\n const result = schema.safeParse(data);\n if (result.success) {\n return result.data;\n }\n const errors = result.error.issues.map(\n (i) => `${i.path.join('.')}: ${i.message}`\n );\n logConfigInvalid(configName, errors);\n console.error(`[hooks] Invalid ${configName} config:`, errors.join(', '));\n return defaultValue;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Zod schemas for hook configuration validation\n * Prevents malformed or malicious configs from being loaded\n */\n\nimport { z } from 'zod';\nimport { logConfigInvalid } from './security-logger.js';\n\n// Auto-background config schema\nexport const AutoBackgroundConfigSchema = z.object({\n enabled: z.boolean(),\n timeoutMs: z.number().int().min(1000).max(600000),\n alwaysBackground: z.array(z.string().max(200)).max(100),\n neverBackground: z.array(z.string().max(200)).max(100),\n verbose: z.boolean().optional(),\n});\n\n// Model Router schemas\nexport const ModelProviderSchema = z.enum([\n 'anthropic',\n 'qwen',\n 'openai',\n 'ollama',\n 'custom',\n]);\n\nexport const ModelConfigSchema = z.object({\n provider: ModelProviderSchema,\n model: z.string().max(100),\n baseUrl: z.string().url().max(500).optional(),\n apiKeyEnv: z.string().max(100),\n headers: z.record(z.string().max(500)).optional(),\n params: z.record(z.unknown()).optional(),\n});\n\nexport const ModelRouterConfigSchema = z.object({\n enabled: z.boolean(),\n defaultProvider: ModelProviderSchema,\n taskRouting: z\n .object({\n plan: ModelProviderSchema.optional(),\n think: ModelProviderSchema.optional(),\n code: ModelProviderSchema.optional(),\n review: ModelProviderSchema.optional(),\n })\n .optional()\n .default({}),\n fallback: z.object({\n enabled: z.boolean(),\n provider: ModelProviderSchema,\n onRateLimit: z.boolean(),\n onError: z.boolean(),\n onTimeout: z.boolean(),\n maxRetries: z.number().int().min(0).max(10),\n retryDelayMs: z.number().int().min(100).max(30000),\n }),\n providers: z\n .object({\n anthropic: ModelConfigSchema.optional(),\n qwen: ModelConfigSchema.optional(),\n openai: ModelConfigSchema.optional(),\n ollama: ModelConfigSchema.optional(),\n custom: ModelConfigSchema.optional(),\n })\n .optional()\n .default({}),\n thinkingMode: z.object({\n enabled: z.boolean(),\n budget: z.number().int().min(1000).max(100000).optional(),\n temperature: z.number().min(0).max(2).optional(),\n topP: z.number().min(0).max(1).optional(),\n }),\n});\n\n// Type exports\nexport type ModelRouterConfigValidated = z.infer<\n typeof ModelRouterConfigSchema\n>;\nexport type AutoBackgroundConfigValidated = z.infer<\n typeof AutoBackgroundConfigSchema\n>;\n\n/**\n * Safely parse and validate config, returning default on failure\n */\nexport function parseConfigSafe<T>(\n schema: z.ZodSchema<T>,\n data: unknown,\n defaultValue: T,\n configName: string\n): T {\n const result = schema.safeParse(data);\n if (result.success) {\n return result.data;\n }\n const errors = result.error.issues.map(\n (i) => `${i.path.join('.')}: ${i.message}`\n );\n logConfigInvalid(configName, errors);\n console.error(`[hooks] Invalid ${configName} config:`, errors.join(', '));\n return defaultValue;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/session-summary.ts"],
4
- "sourcesContent": ["/**\n * Session Summary Generator\n * Generates intelligent suggestions for what to do next after a Claude session\n */\n\nimport { execSync } from 'child_process';\nimport { pickNextLinearTask, TaskSuggestion } from './linear-task-picker.js';\n\nexport interface SessionContext {\n instanceId: string;\n exitCode: number | null;\n sessionStartTime: number;\n worktreePath?: string;\n branch?: string;\n task?: string;\n}\n\nexport interface Suggestion {\n key: string;\n label: string;\n action: string;\n priority: number;\n}\n\nexport interface SessionSummary {\n duration: string;\n exitCode: number | null;\n branch: string;\n status: 'success' | 'error' | 'interrupted';\n suggestions: Suggestion[];\n linearTask?: TaskSuggestion;\n}\n\n/**\n * Format duration in human-readable form\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}min`;\n }\n if (minutes > 0) {\n return `${minutes}min`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get current git branch\n */\nfunction getCurrentBranch(): string {\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Check for uncommitted changes\n */\nfunction hasUncommittedChanges(): { changed: boolean; count: number } {\n try {\n const status = execSync('git status --porcelain', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const lines = status.trim().split('\\n').filter(Boolean);\n return { changed: lines.length > 0, count: lines.length };\n } catch {\n return { changed: false, count: 0 };\n }\n}\n\n/**\n * Check if we're in a worktree\n */\nfunction isInWorktree(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Check if it's a worktree (not the main repo)\n const gitDir = execSync('git rev-parse --git-dir', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return gitDir.includes('.git/worktrees/');\n } catch {\n return false;\n }\n}\n\n/**\n * Check if tests exist and might need running\n */\nfunction hasTestScript(): boolean {\n try {\n const packageJson = execSync('cat package.json', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const pkg = JSON.parse(packageJson);\n return !!(pkg.scripts?.test || pkg.scripts?.['test:run']);\n } catch {\n return false;\n }\n}\n\n/**\n * Generate suggestions based on session context\n */\nasync function generateSuggestions(\n context: SessionContext\n): Promise<Suggestion[]> {\n const suggestions: Suggestion[] = [];\n let keyIndex = 1;\n\n const changes = hasUncommittedChanges();\n const inWorktree = isInWorktree();\n const hasTests = hasTestScript();\n\n // Error case - suggest reviewing logs\n if (context.exitCode !== 0 && context.exitCode !== null) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Review error logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -50',\n priority: 100,\n });\n }\n\n // Uncommitted changes - suggest commit or PR\n if (changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Commit changes (${changes.count} files)`,\n action: 'git add -A && git commit',\n priority: 90,\n });\n\n // If on feature branch, suggest PR\n const branch = getCurrentBranch();\n if (branch !== 'main' && branch !== 'master' && branch !== 'unknown') {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Create PR',\n action: 'gh pr create --fill',\n priority: 80,\n });\n }\n }\n\n // If tests exist and changes were made, suggest running tests\n if (hasTests && changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Run tests',\n action: 'npm run test:run',\n priority: 85,\n });\n }\n\n // Worktree-specific suggestions\n if (inWorktree) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Merge to main',\n action: 'cwm', // custom alias\n priority: 70,\n });\n }\n\n // Try to get next Linear task\n try {\n const linearTask = await pickNextLinearTask({ preferTestTasks: true });\n if (linearTask) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Start: ${linearTask.identifier} - ${linearTask.title.substring(0, 40)}${linearTask.title.length > 40 ? '...' : ''}${linearTask.hasTestRequirements ? ' (has tests)' : ''}`,\n action: `stackmemory task start ${linearTask.id} --assign-me`,\n priority: 60,\n });\n }\n } catch {\n // Linear not available, skip\n }\n\n // Long session suggestion\n const durationMs = Date.now() - context.sessionStartTime;\n if (durationMs > 30 * 60 * 1000) {\n // > 30 minutes\n suggestions.push({\n key: String(keyIndex++),\n label: 'Take a break',\n action: 'echo \"Great work! Time for a coffee break.\"',\n priority: 10,\n });\n }\n\n // Sort by priority (highest first) and re-key\n suggestions.sort((a, b) => b.priority - a.priority);\n\n // Ensure minimum 2 options always\n if (suggestions.length < 2) {\n // Add default options if not enough suggestions\n if (suggestions.length === 0) {\n suggestions.push({\n key: '1',\n label: 'Start new Claude session',\n action: 'claude-sm',\n priority: 50,\n });\n }\n if (suggestions.length < 2) {\n suggestions.push({\n key: '2',\n label: 'View session logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -30',\n priority: 40,\n });\n }\n }\n\n suggestions.forEach((s, i) => {\n s.key = String(i + 1);\n });\n\n return suggestions;\n}\n\n/**\n * Generate full session summary\n */\nexport async function generateSessionSummary(\n context: SessionContext\n): Promise<SessionSummary> {\n const durationMs = Date.now() - context.sessionStartTime;\n const duration = formatDuration(durationMs);\n const branch = context.branch || getCurrentBranch();\n\n let status: 'success' | 'error' | 'interrupted' = 'success';\n if (context.exitCode !== 0 && context.exitCode !== null) {\n status = 'error';\n }\n\n const suggestions = await generateSuggestions(context);\n\n // Extract linear task if present\n let linearTask: TaskSuggestion | undefined;\n try {\n linearTask = await pickNextLinearTask({ preferTestTasks: true });\n } catch {\n // Linear not available\n }\n\n return {\n duration,\n exitCode: context.exitCode,\n branch,\n status,\n suggestions,\n linearTask,\n };\n}\n\n/**\n * Format session summary as WhatsApp message\n */\nexport function formatSummaryMessage(\n summary: SessionSummary,\n sessionId?: string\n): string {\n const statusEmoji = summary.status === 'success' ? '' : '';\n const exitInfo =\n summary.exitCode !== null ? ` | Exit: ${summary.exitCode}` : '';\n const sessionInfo = sessionId ? ` | Session: ${sessionId}` : '';\n\n let message = `Claude session complete ${statusEmoji}\\n`;\n message += `Duration: ${summary.duration}${exitInfo}${sessionInfo}\\n`;\n message += `Branch: ${summary.branch}\\n`;\n\n // Add Claude Code session URL if session ID is available\n if (sessionId) {\n message += `View: https://claude.ai/chat/${sessionId}\\n`;\n }\n\n message += '\\n';\n\n if (summary.suggestions.length > 0) {\n message += `What to do next:\\n`;\n for (const s of summary.suggestions.slice(0, 4)) {\n message += `${s.key}. ${s.label}\\n`;\n }\n message += `\\nReply with number or custom action`;\n } else {\n message += `No pending actions. Nice work!`;\n }\n\n return message;\n}\n\n/**\n * Get action for a suggestion key\n */\nexport function getActionForKey(\n suggestions: Suggestion[],\n key: string\n): string | null {\n const suggestion = suggestions.find((s) => s.key === key);\n return suggestion?.action || null;\n}\n"],
4
+ "sourcesContent": ["/**\n * Session Summary Generator\n * Generates intelligent suggestions for what to do next after a Claude session\n */\n\nimport { execSync } from 'child_process';\nimport { pickNextLinearTask, TaskSuggestion } from './linear-task-picker.js';\n\nexport interface SessionContext {\n instanceId: string;\n exitCode: number | null;\n sessionStartTime: number;\n worktreePath?: string;\n branch?: string;\n task?: string;\n}\n\nexport interface Suggestion {\n key: string;\n label: string;\n action: string;\n priority: number;\n}\n\nexport interface SessionSummary {\n duration: string;\n exitCode: number | null;\n branch: string;\n status: 'success' | 'error' | 'interrupted';\n suggestions: Suggestion[];\n linearTask?: TaskSuggestion;\n}\n\n/**\n * Format duration in human-readable form\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}min`;\n }\n if (minutes > 0) {\n return `${minutes}min`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get current git branch\n */\nfunction getCurrentBranch(): string {\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Check for uncommitted changes\n */\nfunction hasUncommittedChanges(): { changed: boolean; count: number } {\n try {\n const status = execSync('git status --porcelain', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const lines = status.trim().split('\\n').filter(Boolean);\n return { changed: lines.length > 0, count: lines.length };\n } catch {\n return { changed: false, count: 0 };\n }\n}\n\n/**\n * Check if we're in a worktree\n */\nfunction isInWorktree(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Check if it's a worktree (not the main repo)\n const gitDir = execSync('git rev-parse --git-dir', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return gitDir.includes('.git/worktrees/');\n } catch {\n return false;\n }\n}\n\n/**\n * Check if tests exist and might need running\n */\nfunction hasTestScript(): boolean {\n try {\n const packageJson = execSync('cat package.json', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const pkg = JSON.parse(packageJson);\n return !!(pkg.scripts?.test || pkg.scripts?.['test:run']);\n } catch {\n return false;\n }\n}\n\n/**\n * Generate suggestions based on session context\n */\nasync function generateSuggestions(\n context: SessionContext\n): Promise<Suggestion[]> {\n const suggestions: Suggestion[] = [];\n let keyIndex = 1;\n\n const changes = hasUncommittedChanges();\n const inWorktree = isInWorktree();\n const hasTests = hasTestScript();\n\n // Error case - suggest reviewing logs\n if (context.exitCode !== 0 && context.exitCode !== null) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Review error logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -50',\n priority: 100,\n });\n }\n\n // Uncommitted changes - suggest commit or PR\n if (changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Commit changes (${changes.count} files)`,\n action: 'git add -A && git commit',\n priority: 90,\n });\n\n // If on feature branch, suggest PR\n const branch = getCurrentBranch();\n if (branch !== 'main' && branch !== 'master' && branch !== 'unknown') {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Create PR',\n action: 'gh pr create --fill',\n priority: 80,\n });\n }\n }\n\n // If tests exist and changes were made, suggest running tests\n if (hasTests && changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Run tests',\n action: 'npm run test:run',\n priority: 85,\n });\n }\n\n // Worktree-specific suggestions\n if (inWorktree) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Merge to main',\n action: 'cwm', // custom alias\n priority: 70,\n });\n }\n\n // Try to get next Linear task\n try {\n const linearTask = await pickNextLinearTask({ preferTestTasks: true });\n if (linearTask) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Start: ${linearTask.identifier} - ${linearTask.title.substring(0, 40)}${linearTask.title.length > 40 ? '...' : ''}${linearTask.hasTestRequirements ? ' (has tests)' : ''}`,\n action: `stackmemory task start ${linearTask.id} --assign-me`,\n priority: 60,\n });\n }\n } catch {\n // Linear not available, skip\n }\n\n // Long session suggestion\n const durationMs = Date.now() - context.sessionStartTime;\n if (durationMs > 30 * 60 * 1000) {\n // > 30 minutes\n suggestions.push({\n key: String(keyIndex++),\n label: 'Take a break',\n action: 'echo \"Great work! Time for a coffee break.\"',\n priority: 10,\n });\n }\n\n // Sort by priority (highest first) and re-key\n suggestions.sort((a, b) => b.priority - a.priority);\n\n // Ensure minimum 2 options always\n if (suggestions.length < 2) {\n // Add default options if not enough suggestions\n if (suggestions.length === 0) {\n suggestions.push({\n key: '1',\n label: 'Start new Claude session',\n action: 'claude-sm',\n priority: 50,\n });\n }\n if (suggestions.length < 2) {\n suggestions.push({\n key: '2',\n label: 'View session logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -30',\n priority: 40,\n });\n }\n }\n\n suggestions.forEach((s, i) => {\n s.key = String(i + 1);\n });\n\n return suggestions;\n}\n\n/**\n * Generate full session summary\n */\nexport async function generateSessionSummary(\n context: SessionContext\n): Promise<SessionSummary> {\n const durationMs = Date.now() - context.sessionStartTime;\n const duration = formatDuration(durationMs);\n const branch = context.branch || getCurrentBranch();\n\n let status: 'success' | 'error' | 'interrupted' = 'success';\n if (context.exitCode !== 0 && context.exitCode !== null) {\n status = 'error';\n }\n\n const suggestions = await generateSuggestions(context);\n\n // Extract linear task if present\n let linearTask: TaskSuggestion | undefined;\n try {\n linearTask = await pickNextLinearTask({ preferTestTasks: true });\n } catch {\n // Linear not available\n }\n\n return {\n duration,\n exitCode: context.exitCode,\n branch,\n status,\n suggestions,\n linearTask,\n };\n}\n\n/**\n * Format session summary as a compact message\n */\nexport function formatSummaryMessage(\n summary: SessionSummary,\n sessionId?: string\n): string {\n const statusEmoji = summary.status === 'success' ? '' : '';\n const exitInfo =\n summary.exitCode !== null ? ` | Exit: ${summary.exitCode}` : '';\n const sessionInfo = sessionId ? ` | Session: ${sessionId}` : '';\n\n let message = `Claude session complete ${statusEmoji}\\n`;\n message += `Duration: ${summary.duration}${exitInfo}${sessionInfo}\\n`;\n message += `Branch: ${summary.branch}\\n`;\n\n // Add Claude Code session URL if session ID is available\n if (sessionId) {\n message += `View: https://claude.ai/chat/${sessionId}\\n`;\n }\n\n message += '\\n';\n\n if (summary.suggestions.length > 0) {\n message += `What to do next:\\n`;\n for (const s of summary.suggestions.slice(0, 4)) {\n message += `${s.key}. ${s.label}\\n`;\n }\n message += `\\nReply with number or custom action`;\n } else {\n message += `No pending actions. Nice work!`;\n }\n\n return message;\n}\n\n/**\n * Get action for a suggestion key\n */\nexport function getActionForKey(\n suggestions: Suggestion[],\n key: string\n): string | null {\n const suggestion = suggestions.find((s) => s.key === key);\n return suggestion?.action || null;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
@@ -94,6 +94,7 @@
94
94
  "sync:setup": "./scripts/setup-background-sync.sh",
95
95
  "prepare": "echo 'Prepare step completed'",
96
96
  "verify:dist": "node scripts/verify-dist.cjs",
97
+ "test:smoke-db": "bash scripts/smoke-init-db.sh",
97
98
  "rebuild:native": "npm rebuild better-sqlite3 || true",
98
99
  "deps:reset": "rm -rf node_modules package-lock.json && npm ci"
99
100
  },
@@ -123,7 +124,6 @@
123
124
  "ignore": "^7.0.5",
124
125
  "inquirer": "^9.3.8",
125
126
  "msgpackr": "^1.10.1",
126
- "ngrok": "^5.0.0-beta.2",
127
127
  "node-pty": "^1.1.0",
128
128
  "open": "^11.0.0",
129
129
  "ora": "^9.0.0",