@stackmemoryai/stackmemory 0.5.47 → 0.5.49

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.
@@ -0,0 +1,136 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { existsSync } from "fs";
6
+ import { join } from "path";
7
+ import { homedir } from "os";
8
+ class DaemonLinearService {
9
+ config;
10
+ state;
11
+ intervalId;
12
+ isRunning = false;
13
+ onLog;
14
+ constructor(config, onLog) {
15
+ this.config = config;
16
+ this.onLog = onLog;
17
+ this.state = {
18
+ lastSyncTime: 0,
19
+ syncCount: 0,
20
+ errors: []
21
+ };
22
+ }
23
+ async start() {
24
+ if (this.isRunning || !this.config.enabled) {
25
+ return;
26
+ }
27
+ if (!this.isLinearConfigured()) {
28
+ this.onLog("WARN", "Linear not configured, skipping linear service");
29
+ return;
30
+ }
31
+ this.isRunning = true;
32
+ const intervalMs = this.config.interval * 60 * 1e3;
33
+ this.onLog("INFO", "Linear service started", {
34
+ interval: this.config.interval,
35
+ quietHours: this.config.quietHours
36
+ });
37
+ await this.performSync();
38
+ this.intervalId = setInterval(async () => {
39
+ await this.performSync();
40
+ }, intervalMs);
41
+ }
42
+ stop() {
43
+ if (this.intervalId) {
44
+ clearInterval(this.intervalId);
45
+ this.intervalId = void 0;
46
+ }
47
+ this.isRunning = false;
48
+ this.onLog("INFO", "Linear service stopped");
49
+ }
50
+ getState() {
51
+ return {
52
+ ...this.state,
53
+ nextSyncTime: this.isRunning ? this.state.lastSyncTime + this.config.interval * 60 * 1e3 : void 0
54
+ };
55
+ }
56
+ updateConfig(config) {
57
+ const wasRunning = this.isRunning;
58
+ if (wasRunning) {
59
+ this.stop();
60
+ }
61
+ this.config = { ...this.config, ...config };
62
+ if (wasRunning && this.config.enabled) {
63
+ this.start();
64
+ }
65
+ }
66
+ async forceSync() {
67
+ await this.performSync();
68
+ }
69
+ async performSync() {
70
+ if (!this.isRunning) return;
71
+ if (this.isInQuietHours()) {
72
+ this.onLog("DEBUG", "Skipping sync during quiet hours");
73
+ return;
74
+ }
75
+ try {
76
+ const { LinearAutoSyncService } = await import("../../integrations/linear/auto-sync.js");
77
+ const projectRoot = this.findProjectRoot();
78
+ if (!projectRoot) {
79
+ this.onLog("WARN", "No project root found for Linear sync");
80
+ return;
81
+ }
82
+ const syncService = new LinearAutoSyncService(projectRoot, {
83
+ enabled: true,
84
+ interval: this.config.interval,
85
+ retryAttempts: this.config.retryAttempts,
86
+ retryDelay: this.config.retryDelay,
87
+ quietHours: this.config.quietHours
88
+ });
89
+ await syncService.forceSync();
90
+ syncService.stop();
91
+ this.state.syncCount++;
92
+ this.state.lastSyncTime = Date.now();
93
+ this.onLog("INFO", "Linear sync completed", {
94
+ syncCount: this.state.syncCount
95
+ });
96
+ } catch (err) {
97
+ const errorMsg = err instanceof Error ? err.message : String(err);
98
+ this.state.errors.push(errorMsg);
99
+ this.onLog("ERROR", "Linear sync failed", { error: errorMsg });
100
+ if (this.state.errors.length > 10) {
101
+ this.state.errors = this.state.errors.slice(-10);
102
+ }
103
+ }
104
+ }
105
+ isLinearConfigured() {
106
+ const homeDir = homedir();
107
+ const configPath = join(homeDir, ".stackmemory", "linear-auth.json");
108
+ return existsSync(configPath) || !!process.env["LINEAR_API_KEY"];
109
+ }
110
+ findProjectRoot() {
111
+ const cwd = process.cwd();
112
+ if (existsSync(join(cwd, ".stackmemory"))) {
113
+ return cwd;
114
+ }
115
+ const homeDir = homedir();
116
+ if (existsSync(join(homeDir, ".stackmemory"))) {
117
+ return homeDir;
118
+ }
119
+ return null;
120
+ }
121
+ isInQuietHours() {
122
+ if (!this.config.quietHours) return false;
123
+ const now = /* @__PURE__ */ new Date();
124
+ const currentHour = now.getHours();
125
+ const { start, end } = this.config.quietHours;
126
+ if (start > end) {
127
+ return currentHour >= start || currentHour < end;
128
+ } else {
129
+ return currentHour >= start && currentHour < end;
130
+ }
131
+ }
132
+ }
133
+ export {
134
+ DaemonLinearService
135
+ };
136
+ //# sourceMappingURL=linear-service.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/daemon/services/linear-service.ts"],
4
+ "sourcesContent": ["/**\n * Linear Sync Service Wrapper\n * Wraps LinearAutoSyncService for daemon integration\n */\n\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { LinearServiceConfig } from '../daemon-config.js';\n\nexport interface LinearServiceState {\n lastSyncTime: number;\n syncCount: number;\n errors: string[];\n nextSyncTime?: number;\n}\n\nexport class DaemonLinearService {\n private config: LinearServiceConfig;\n private state: LinearServiceState;\n private intervalId?: NodeJS.Timeout;\n private isRunning = false;\n private onLog: (level: string, message: string, data?: unknown) => void;\n\n constructor(\n config: LinearServiceConfig,\n onLog: (level: string, message: string, data?: unknown) => void\n ) {\n this.config = config;\n this.onLog = onLog;\n this.state = {\n lastSyncTime: 0,\n syncCount: 0,\n errors: [],\n };\n }\n\n async start(): Promise<void> {\n if (this.isRunning || !this.config.enabled) {\n return;\n }\n\n // Check if Linear is configured\n if (!this.isLinearConfigured()) {\n this.onLog('WARN', 'Linear not configured, skipping linear service');\n return;\n }\n\n this.isRunning = true;\n const intervalMs = this.config.interval * 60 * 1000;\n\n this.onLog('INFO', 'Linear service started', {\n interval: this.config.interval,\n quietHours: this.config.quietHours,\n });\n\n // Initial sync\n await this.performSync();\n\n // Schedule periodic syncs\n this.intervalId = setInterval(async () => {\n await this.performSync();\n }, intervalMs);\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n this.isRunning = false;\n this.onLog('INFO', 'Linear service stopped');\n }\n\n getState(): LinearServiceState {\n return {\n ...this.state,\n nextSyncTime: this.isRunning\n ? this.state.lastSyncTime + this.config.interval * 60 * 1000\n : undefined,\n };\n }\n\n updateConfig(config: Partial<LinearServiceConfig>): void {\n const wasRunning = this.isRunning;\n if (wasRunning) {\n this.stop();\n }\n\n this.config = { ...this.config, ...config };\n\n if (wasRunning && this.config.enabled) {\n this.start();\n }\n }\n\n async forceSync(): Promise<void> {\n await this.performSync();\n }\n\n private async performSync(): Promise<void> {\n if (!this.isRunning) return;\n\n // Check quiet hours\n if (this.isInQuietHours()) {\n this.onLog('DEBUG', 'Skipping sync during quiet hours');\n return;\n }\n\n try {\n // Dynamically import LinearAutoSyncService to avoid loading if not needed\n const { LinearAutoSyncService } =\n await import('../../integrations/linear/auto-sync.js');\n\n const projectRoot = this.findProjectRoot();\n if (!projectRoot) {\n this.onLog('WARN', 'No project root found for Linear sync');\n return;\n }\n\n const syncService = new LinearAutoSyncService(projectRoot, {\n enabled: true,\n interval: this.config.interval,\n retryAttempts: this.config.retryAttempts,\n retryDelay: this.config.retryDelay,\n quietHours: this.config.quietHours,\n });\n\n await syncService.forceSync();\n syncService.stop();\n\n this.state.syncCount++;\n this.state.lastSyncTime = Date.now();\n\n this.onLog('INFO', 'Linear sync completed', {\n syncCount: this.state.syncCount,\n });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n this.state.errors.push(errorMsg);\n this.onLog('ERROR', 'Linear sync failed', { error: errorMsg });\n\n // Keep only last 10 errors\n if (this.state.errors.length > 10) {\n this.state.errors = this.state.errors.slice(-10);\n }\n }\n }\n\n private isLinearConfigured(): boolean {\n const homeDir = homedir();\n const configPath = join(homeDir, '.stackmemory', 'linear-auth.json');\n return existsSync(configPath) || !!process.env['LINEAR_API_KEY'];\n }\n\n private findProjectRoot(): string | null {\n // Check common locations\n const cwd = process.cwd();\n if (existsSync(join(cwd, '.stackmemory'))) {\n return cwd;\n }\n\n // Check home directory\n const homeDir = homedir();\n if (existsSync(join(homeDir, '.stackmemory'))) {\n return homeDir;\n }\n\n return null;\n }\n\n private isInQuietHours(): boolean {\n if (!this.config.quietHours) return false;\n\n const now = new Date();\n const currentHour = now.getHours();\n const { start, end } = this.config.quietHours;\n\n if (start > end) {\n // Quiet hours span midnight (e.g., 22:00 - 07:00)\n return currentHour >= start || currentHour < end;\n } else {\n // Quiet hours within same day\n return currentHour >= start && currentHour < end;\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath as __fileURLToPath } from 'url';
3
+ import { dirname as __pathDirname } from 'path';
4
+ const __filename = __fileURLToPath(import.meta.url);
5
+ const __dirname = __pathDirname(__filename);
6
+ import {
7
+ existsSync,
8
+ writeFileSync,
9
+ unlinkSync,
10
+ appendFileSync,
11
+ readFileSync
12
+ } from "fs";
13
+ import {
14
+ loadDaemonConfig,
15
+ getDaemonPaths,
16
+ writeDaemonStatus
17
+ } from "./daemon-config.js";
18
+ import { DaemonContextService } from "./services/context-service.js";
19
+ import { DaemonLinearService } from "./services/linear-service.js";
20
+ class UnifiedDaemon {
21
+ config;
22
+ paths;
23
+ contextService;
24
+ linearService;
25
+ heartbeatInterval;
26
+ isShuttingDown = false;
27
+ startTime = 0;
28
+ constructor(config) {
29
+ this.paths = getDaemonPaths();
30
+ this.config = { ...loadDaemonConfig(), ...config };
31
+ this.contextService = new DaemonContextService(
32
+ this.config.context,
33
+ (level, msg, data) => this.log(level, "context", msg, data)
34
+ );
35
+ this.linearService = new DaemonLinearService(
36
+ this.config.linear,
37
+ (level, msg, data) => this.log(level, "linear", msg, data)
38
+ );
39
+ }
40
+ log(level, service, message, data) {
41
+ const logLevel = level.toUpperCase();
42
+ const levels = ["DEBUG", "INFO", "WARN", "ERROR"];
43
+ const configLevel = this.config.logLevel.toUpperCase();
44
+ if (levels.indexOf(logLevel) < levels.indexOf(configLevel)) {
45
+ return;
46
+ }
47
+ const entry = {
48
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
49
+ level: logLevel,
50
+ service,
51
+ message,
52
+ data
53
+ };
54
+ const logLine = JSON.stringify(entry) + "\n";
55
+ try {
56
+ appendFileSync(this.paths.logFile, logLine);
57
+ } catch {
58
+ console.error(`[${entry.timestamp}] ${level} [${service}]: ${message}`);
59
+ }
60
+ }
61
+ checkIdempotency() {
62
+ if (existsSync(this.paths.pidFile)) {
63
+ try {
64
+ const existingPid = readFileSync(this.paths.pidFile, "utf8").trim();
65
+ const pid = parseInt(existingPid, 10);
66
+ try {
67
+ process.kill(pid, 0);
68
+ this.log("WARN", "daemon", "Daemon already running", { pid });
69
+ return false;
70
+ } catch {
71
+ this.log("INFO", "daemon", "Cleaning stale PID file", { pid });
72
+ unlinkSync(this.paths.pidFile);
73
+ }
74
+ } catch {
75
+ try {
76
+ unlinkSync(this.paths.pidFile);
77
+ } catch {
78
+ }
79
+ }
80
+ }
81
+ return true;
82
+ }
83
+ writePidFile() {
84
+ writeFileSync(this.paths.pidFile, process.pid.toString());
85
+ this.log("INFO", "daemon", "PID file created", {
86
+ pid: process.pid,
87
+ file: this.paths.pidFile
88
+ });
89
+ }
90
+ updateStatus() {
91
+ const status = {
92
+ running: true,
93
+ pid: process.pid,
94
+ startTime: this.startTime,
95
+ uptime: Date.now() - this.startTime,
96
+ services: {
97
+ context: {
98
+ enabled: this.config.context.enabled,
99
+ lastRun: this.contextService.getState().lastSaveTime || void 0,
100
+ saveCount: this.contextService.getState().saveCount
101
+ },
102
+ linear: {
103
+ enabled: this.config.linear.enabled,
104
+ lastRun: this.linearService.getState().lastSyncTime || void 0,
105
+ syncCount: this.linearService.getState().syncCount
106
+ },
107
+ fileWatch: {
108
+ enabled: this.config.fileWatch.enabled
109
+ }
110
+ },
111
+ errors: [
112
+ ...this.contextService.getState().errors.slice(-5),
113
+ ...this.linearService.getState().errors.slice(-5)
114
+ ]
115
+ };
116
+ writeDaemonStatus(status);
117
+ }
118
+ setupSignalHandlers() {
119
+ const handleSignal = (signal) => {
120
+ this.log("INFO", "daemon", `Received ${signal}, shutting down`);
121
+ this.shutdown(signal.toLowerCase());
122
+ };
123
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
124
+ process.on("SIGINT", () => handleSignal("SIGINT"));
125
+ process.on("SIGHUP", () => handleSignal("SIGHUP"));
126
+ process.on("uncaughtException", (err) => {
127
+ this.log("ERROR", "daemon", "Uncaught exception", {
128
+ error: err.message,
129
+ stack: err.stack
130
+ });
131
+ this.shutdown("uncaught_exception");
132
+ });
133
+ process.on("unhandledRejection", (reason) => {
134
+ this.log("ERROR", "daemon", "Unhandled rejection", {
135
+ reason: String(reason)
136
+ });
137
+ });
138
+ }
139
+ cleanup() {
140
+ try {
141
+ if (existsSync(this.paths.pidFile)) {
142
+ unlinkSync(this.paths.pidFile);
143
+ this.log("INFO", "daemon", "PID file removed");
144
+ }
145
+ } catch (e) {
146
+ this.log("WARN", "daemon", "Failed to remove PID file", {
147
+ error: String(e)
148
+ });
149
+ }
150
+ const finalStatus = {
151
+ running: false,
152
+ startTime: this.startTime,
153
+ uptime: Date.now() - this.startTime,
154
+ services: {
155
+ context: {
156
+ enabled: false,
157
+ saveCount: this.contextService.getState().saveCount
158
+ },
159
+ linear: {
160
+ enabled: false,
161
+ syncCount: this.linearService.getState().syncCount
162
+ },
163
+ fileWatch: { enabled: false }
164
+ },
165
+ errors: []
166
+ };
167
+ writeDaemonStatus(finalStatus);
168
+ }
169
+ shutdown(reason) {
170
+ if (this.isShuttingDown) return;
171
+ this.isShuttingDown = true;
172
+ this.log("INFO", "daemon", "Daemon shutting down", {
173
+ reason,
174
+ uptime: Date.now() - this.startTime,
175
+ contextSaves: this.contextService.getState().saveCount,
176
+ linearSyncs: this.linearService.getState().syncCount
177
+ });
178
+ if (this.heartbeatInterval) {
179
+ clearInterval(this.heartbeatInterval);
180
+ this.heartbeatInterval = void 0;
181
+ }
182
+ this.contextService.stop();
183
+ this.linearService.stop();
184
+ this.cleanup();
185
+ const exitCode = reason === "sigterm" || reason === "sigint" || reason === "sighup" ? 0 : 1;
186
+ process.exit(exitCode);
187
+ }
188
+ async start() {
189
+ if (!this.checkIdempotency()) {
190
+ this.log("INFO", "daemon", "Exiting - daemon already running");
191
+ process.exit(0);
192
+ }
193
+ this.startTime = Date.now();
194
+ this.writePidFile();
195
+ this.setupSignalHandlers();
196
+ this.log("INFO", "daemon", "Unified daemon started", {
197
+ pid: process.pid,
198
+ config: {
199
+ context: this.config.context.enabled,
200
+ linear: this.config.linear.enabled,
201
+ fileWatch: this.config.fileWatch.enabled
202
+ }
203
+ });
204
+ this.contextService.start();
205
+ await this.linearService.start();
206
+ this.heartbeatInterval = setInterval(() => {
207
+ this.updateStatus();
208
+ }, this.config.heartbeatInterval * 1e3);
209
+ this.updateStatus();
210
+ }
211
+ getStatus() {
212
+ return {
213
+ running: !this.isShuttingDown,
214
+ pid: process.pid,
215
+ startTime: this.startTime,
216
+ uptime: Date.now() - this.startTime,
217
+ services: {
218
+ context: {
219
+ enabled: this.config.context.enabled,
220
+ lastRun: this.contextService.getState().lastSaveTime || void 0,
221
+ saveCount: this.contextService.getState().saveCount
222
+ },
223
+ linear: {
224
+ enabled: this.config.linear.enabled,
225
+ lastRun: this.linearService.getState().lastSyncTime || void 0,
226
+ syncCount: this.linearService.getState().syncCount
227
+ },
228
+ fileWatch: {
229
+ enabled: this.config.fileWatch.enabled
230
+ }
231
+ },
232
+ errors: [
233
+ ...this.contextService.getState().errors,
234
+ ...this.linearService.getState().errors
235
+ ]
236
+ };
237
+ }
238
+ }
239
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("unified-daemon.js")) {
240
+ const args = process.argv.slice(2);
241
+ const config = {};
242
+ for (let i = 0; i < args.length; i++) {
243
+ const arg = args[i];
244
+ if (arg === "--save-interval" && args[i + 1]) {
245
+ config.context = { enabled: true, interval: parseInt(args[i + 1], 10) };
246
+ i++;
247
+ } else if (arg === "--linear-interval" && args[i + 1]) {
248
+ config.linear = {
249
+ enabled: true,
250
+ interval: parseInt(args[i + 1], 10),
251
+ retryAttempts: 3,
252
+ retryDelay: 3e4
253
+ };
254
+ i++;
255
+ } else if (arg === "--no-linear") {
256
+ config.linear = {
257
+ enabled: false,
258
+ interval: 60,
259
+ retryAttempts: 3,
260
+ retryDelay: 3e4
261
+ };
262
+ } else if (arg === "--log-level" && args[i + 1]) {
263
+ config.logLevel = args[i + 1];
264
+ i++;
265
+ }
266
+ }
267
+ const daemon = new UnifiedDaemon(config);
268
+ daemon.start().catch((err) => {
269
+ console.error("Failed to start daemon:", err);
270
+ process.exit(1);
271
+ });
272
+ }
273
+ export {
274
+ UnifiedDaemon
275
+ };
276
+ //# sourceMappingURL=unified-daemon.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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';\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 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\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 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 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 ],\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 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 });\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\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 fileWatch: this.config.fileWatch.enabled,\n },\n });\n\n // Start services\n this.contextService.start();\n await this.linearService.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 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 fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors,\n ...this.linearService.getState().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": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.5.47",
3
+ "version": "0.5.49",
4
4
  "description": "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with team collaboration and infinite retention",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
@@ -74,6 +74,9 @@
74
74
  "daemons:stop": "node scripts/claude-sm-autostart.js stop",
75
75
  "daemon:session": "node dist/daemon/session-daemon.js",
76
76
  "daemon:session:start": "node dist/daemon/session-daemon.js --session-id",
77
+ "daemon:start": "node dist/daemon/unified-daemon.js",
78
+ "daemon:stop": "node dist/cli/index.js daemon stop",
79
+ "daemon:status": "node dist/cli/index.js daemon status",
77
80
  "sync:start": "node scripts/background-sync-manager.js",
78
81
  "sync:setup": "./scripts/setup-background-sync.sh",
79
82
  "prepare": "echo 'Prepare step completed'"