@stackmemoryai/stackmemory 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli/commands/config.js +81 -0
  2. package/dist/cli/commands/config.js.map +2 -2
  3. package/dist/cli/commands/decision.js +262 -0
  4. package/dist/cli/commands/decision.js.map +7 -0
  5. package/dist/cli/commands/handoff.js +87 -24
  6. package/dist/cli/commands/handoff.js.map +3 -3
  7. package/dist/cli/commands/service.js +684 -0
  8. package/dist/cli/commands/service.js.map +7 -0
  9. package/dist/cli/commands/sweep.js +311 -0
  10. package/dist/cli/commands/sweep.js.map +7 -0
  11. package/dist/cli/index.js +98 -4
  12. package/dist/cli/index.js.map +2 -2
  13. package/dist/cli/streamlined-cli.js +144 -0
  14. package/dist/cli/streamlined-cli.js.map +7 -0
  15. package/dist/core/config/storage-config.js +111 -0
  16. package/dist/core/config/storage-config.js.map +7 -0
  17. package/dist/core/events/event-bus.js +110 -0
  18. package/dist/core/events/event-bus.js.map +7 -0
  19. package/dist/core/plugins/plugin-interface.js +87 -0
  20. package/dist/core/plugins/plugin-interface.js.map +7 -0
  21. package/dist/core/session/enhanced-handoff.js +654 -0
  22. package/dist/core/session/enhanced-handoff.js.map +7 -0
  23. package/dist/core/storage/simplified-storage.js +328 -0
  24. package/dist/core/storage/simplified-storage.js.map +7 -0
  25. package/dist/daemon/session-daemon.js +308 -0
  26. package/dist/daemon/session-daemon.js.map +7 -0
  27. package/dist/plugins/linear/index.js +166 -0
  28. package/dist/plugins/linear/index.js.map +7 -0
  29. package/dist/plugins/loader.js +57 -0
  30. package/dist/plugins/loader.js.map +7 -0
  31. package/dist/plugins/plugin-interface.js +67 -0
  32. package/dist/plugins/plugin-interface.js.map +7 -0
  33. package/dist/plugins/ralph/simple-ralph-plugin.js +305 -0
  34. package/dist/plugins/ralph/simple-ralph-plugin.js.map +7 -0
  35. package/dist/plugins/ralph/use-cases/code-generator.js +151 -0
  36. package/dist/plugins/ralph/use-cases/code-generator.js.map +7 -0
  37. package/dist/plugins/ralph/use-cases/test-generator.js +201 -0
  38. package/dist/plugins/ralph/use-cases/test-generator.js.map +7 -0
  39. package/dist/skills/repo-ingestion-skill.js +54 -10
  40. package/dist/skills/repo-ingestion-skill.js.map +2 -2
  41. package/package.json +4 -8
  42. package/scripts/archive/check-all-duplicates.ts +2 -2
  43. package/scripts/archive/merge-linear-duplicates.ts +6 -4
  44. package/scripts/install-claude-hooks-auto.js +72 -15
  45. package/scripts/measure-handoff-impact.mjs +395 -0
  46. package/scripts/measure-handoff-impact.ts +450 -0
  47. package/templates/claude-hooks/on-startup.js +200 -19
  48. package/templates/services/com.stackmemory.guardian.plist +59 -0
  49. package/templates/services/stackmemory-guardian.service +41 -0
  50. package/scripts/testing/results/real-performance-results.json +0 -90
  51. package/scripts/testing/test-tier-migration.js +0 -100
@@ -0,0 +1,684 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import * as fs from "fs/promises";
5
+ import * as path from "path";
6
+ import { spawn, execSync } from "child_process";
7
+ import { existsSync, readFileSync } from "fs";
8
+ function getServiceConfig() {
9
+ const home = process.env.HOME || "";
10
+ const platform = process.platform;
11
+ if (platform === "darwin") {
12
+ return {
13
+ platform: "darwin",
14
+ serviceDir: path.join(home, "Library", "LaunchAgents"),
15
+ serviceName: "com.stackmemory.guardian",
16
+ serviceFile: path.join(
17
+ home,
18
+ "Library",
19
+ "LaunchAgents",
20
+ "com.stackmemory.guardian.plist"
21
+ ),
22
+ logDir: path.join(home, ".stackmemory", "logs")
23
+ };
24
+ } else if (platform === "linux") {
25
+ return {
26
+ platform: "linux",
27
+ serviceDir: path.join(home, ".config", "systemd", "user"),
28
+ serviceName: "stackmemory-guardian",
29
+ serviceFile: path.join(
30
+ home,
31
+ ".config",
32
+ "systemd",
33
+ "user",
34
+ "stackmemory-guardian.service"
35
+ ),
36
+ logDir: path.join(home, ".stackmemory", "logs")
37
+ };
38
+ }
39
+ return {
40
+ platform: "unsupported",
41
+ serviceDir: "",
42
+ serviceName: "",
43
+ serviceFile: "",
44
+ logDir: path.join(home, ".stackmemory", "logs")
45
+ };
46
+ }
47
+ function _getStackMemoryBinPath() {
48
+ const localBin = path.join(process.cwd(), "dist", "cli", "index.js");
49
+ if (existsSync(localBin)) {
50
+ return localBin;
51
+ }
52
+ const globalBin = path.join(
53
+ process.env.HOME || "",
54
+ ".stackmemory",
55
+ "bin",
56
+ "stackmemory"
57
+ );
58
+ if (existsSync(globalBin)) {
59
+ return globalBin;
60
+ }
61
+ return "npx stackmemory";
62
+ }
63
+ void _getStackMemoryBinPath;
64
+ function getNodePath() {
65
+ try {
66
+ const nodePath = execSync("which node", { encoding: "utf-8" }).trim();
67
+ return nodePath;
68
+ } catch {
69
+ return "/usr/local/bin/node";
70
+ }
71
+ }
72
+ function generateMacOSPlist(config) {
73
+ const home = process.env.HOME || "";
74
+ const nodePath = getNodePath();
75
+ const guardianScript = path.join(home, ".stackmemory", "guardian.js");
76
+ return `<?xml version="1.0" encoding="UTF-8"?>
77
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
78
+ <plist version="1.0">
79
+ <dict>
80
+ <key>Label</key>
81
+ <string>${config.serviceName}</string>
82
+
83
+ <key>ProgramArguments</key>
84
+ <array>
85
+ <string>${nodePath}</string>
86
+ <string>${guardianScript}</string>
87
+ </array>
88
+
89
+ <key>RunAtLoad</key>
90
+ <true/>
91
+
92
+ <key>KeepAlive</key>
93
+ <dict>
94
+ <key>SuccessfulExit</key>
95
+ <false/>
96
+ </dict>
97
+
98
+ <key>WorkingDirectory</key>
99
+ <string>${home}/.stackmemory</string>
100
+
101
+ <key>StandardOutPath</key>
102
+ <string>${config.logDir}/guardian.log</string>
103
+
104
+ <key>StandardErrorPath</key>
105
+ <string>${config.logDir}/guardian.error.log</string>
106
+
107
+ <key>EnvironmentVariables</key>
108
+ <dict>
109
+ <key>HOME</key>
110
+ <string>${home}</string>
111
+ <key>PATH</key>
112
+ <string>/usr/local/bin:/usr/bin:/bin</string>
113
+ </dict>
114
+
115
+ <key>ThrottleInterval</key>
116
+ <integer>30</integer>
117
+ </dict>
118
+ </plist>`;
119
+ }
120
+ function generateLinuxSystemdService(config) {
121
+ const home = process.env.HOME || "";
122
+ const nodePath = getNodePath();
123
+ const guardianScript = path.join(home, ".stackmemory", "guardian.js");
124
+ return `[Unit]
125
+ Description=StackMemory Guardian Service
126
+ Documentation=https://github.com/stackmemoryai/stackmemory
127
+ After=network.target
128
+
129
+ [Service]
130
+ Type=simple
131
+ ExecStart=${nodePath} ${guardianScript}
132
+ Restart=on-failure
133
+ RestartSec=30
134
+ WorkingDirectory=${home}/.stackmemory
135
+
136
+ Environment=HOME=${home}
137
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin
138
+
139
+ StandardOutput=append:${config.logDir}/guardian.log
140
+ StandardError=append:${config.logDir}/guardian.error.log
141
+
142
+ [Install]
143
+ WantedBy=default.target`;
144
+ }
145
+ function generateGuardianScript() {
146
+ return `#!/usr/bin/env node
147
+ /**
148
+ * StackMemory Guardian Service
149
+ * Monitors ~/.stackmemory/sessions/ for active sessions
150
+ * and manages context sync accordingly.
151
+ */
152
+
153
+ const fs = require('fs');
154
+ const path = require('path');
155
+ const { spawn } = require('child_process');
156
+
157
+ const HOME = process.env.HOME || '';
158
+ const SESSIONS_DIR = path.join(HOME, '.stackmemory', 'sessions');
159
+ const STATE_FILE = path.join(HOME, '.stackmemory', 'guardian.state');
160
+ const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
161
+
162
+ class Guardian {
163
+ constructor() {
164
+ this.syncProcess = null;
165
+ this.lastActivityTime = Date.now();
166
+ this.activeSessions = new Set();
167
+ this.checkInterval = null;
168
+ }
169
+
170
+ log(message, level = 'INFO') {
171
+ const timestamp = new Date().toISOString();
172
+ console.log('[' + timestamp + '] [' + level + '] ' + message);
173
+ }
174
+
175
+ async getActiveSessions() {
176
+ const sessions = new Set();
177
+
178
+ try {
179
+ if (!fs.existsSync(SESSIONS_DIR)) {
180
+ return sessions;
181
+ }
182
+
183
+ const files = fs.readdirSync(SESSIONS_DIR);
184
+
185
+ for (const file of files) {
186
+ if (!file.endsWith('.json')) continue;
187
+
188
+ const filePath = path.join(SESSIONS_DIR, file);
189
+ try {
190
+ const content = fs.readFileSync(filePath, 'utf8');
191
+ const session = JSON.parse(content);
192
+
193
+ // Check if session is active (updated within last 5 minutes)
194
+ const lastUpdate = new Date(session.lastActiveAt || session.startedAt).getTime();
195
+ const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
196
+
197
+ if (session.state === 'active' && lastUpdate > fiveMinutesAgo) {
198
+ sessions.add(session.sessionId);
199
+ }
200
+ } catch (err) {
201
+ // Skip invalid session files
202
+ }
203
+ }
204
+ } catch (err) {
205
+ this.log('Error reading sessions: ' + err.message, 'ERROR');
206
+ }
207
+
208
+ return sessions;
209
+ }
210
+
211
+ startContextSync() {
212
+ if (this.syncProcess) {
213
+ this.log('Context sync already running');
214
+ return;
215
+ }
216
+
217
+ this.log('Starting context sync...');
218
+
219
+ // Find stackmemory binary
220
+ const stackmemoryPaths = [
221
+ path.join(HOME, '.stackmemory', 'bin', 'stackmemory'),
222
+ 'npx'
223
+ ];
224
+
225
+ let binPath = null;
226
+ for (const p of stackmemoryPaths) {
227
+ if (p === 'npx' || fs.existsSync(p)) {
228
+ binPath = p;
229
+ break;
230
+ }
231
+ }
232
+
233
+ if (!binPath) {
234
+ this.log('Cannot find stackmemory binary', 'ERROR');
235
+ return;
236
+ }
237
+
238
+ const args = binPath === 'npx'
239
+ ? ['stackmemory', 'monitor', '--daemon']
240
+ : ['monitor', '--daemon'];
241
+
242
+ this.syncProcess = spawn(binPath, args, {
243
+ detached: true,
244
+ stdio: ['ignore', 'pipe', 'pipe']
245
+ });
246
+
247
+ this.syncProcess.stdout.on('data', (data) => {
248
+ this.log('sync: ' + data.toString().trim());
249
+ });
250
+
251
+ this.syncProcess.stderr.on('data', (data) => {
252
+ this.log('sync error: ' + data.toString().trim(), 'WARN');
253
+ });
254
+
255
+ this.syncProcess.on('exit', (code) => {
256
+ this.log('Context sync exited with code: ' + code);
257
+ this.syncProcess = null;
258
+ });
259
+
260
+ this.log('Context sync started');
261
+ }
262
+
263
+ stopContextSync() {
264
+ if (!this.syncProcess) {
265
+ return;
266
+ }
267
+
268
+ this.log('Stopping context sync...');
269
+
270
+ try {
271
+ this.syncProcess.kill('SIGTERM');
272
+ this.syncProcess = null;
273
+ this.log('Context sync stopped');
274
+ } catch (err) {
275
+ this.log('Error stopping sync: ' + err.message, 'ERROR');
276
+ }
277
+ }
278
+
279
+ saveState() {
280
+ const state = {
281
+ lastCheck: new Date().toISOString(),
282
+ activeSessions: Array.from(this.activeSessions),
283
+ syncRunning: this.syncProcess !== null,
284
+ lastActivity: new Date(this.lastActivityTime).toISOString()
285
+ };
286
+
287
+ try {
288
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
289
+ } catch (err) {
290
+ this.log('Error saving state: ' + err.message, 'ERROR');
291
+ }
292
+ }
293
+
294
+ async check() {
295
+ const currentSessions = await this.getActiveSessions();
296
+ const hadActivity = currentSessions.size > 0;
297
+
298
+ if (hadActivity) {
299
+ this.lastActivityTime = Date.now();
300
+ }
301
+
302
+ // Detect session changes
303
+ const newSessions = [...currentSessions].filter(s => !this.activeSessions.has(s));
304
+ const closedSessions = [...this.activeSessions].filter(s => !currentSessions.has(s));
305
+
306
+ if (newSessions.length > 0) {
307
+ this.log('New sessions detected: ' + newSessions.join(', '));
308
+ if (!this.syncProcess) {
309
+ this.startContextSync();
310
+ }
311
+ }
312
+
313
+ if (closedSessions.length > 0) {
314
+ this.log('Sessions closed: ' + closedSessions.join(', '));
315
+ }
316
+
317
+ this.activeSessions = currentSessions;
318
+
319
+ // Check idle timeout
320
+ const idleTime = Date.now() - this.lastActivityTime;
321
+ if (this.syncProcess && currentSessions.size === 0 && idleTime > IDLE_TIMEOUT_MS) {
322
+ this.log('No activity for 30 minutes, stopping sync');
323
+ this.stopContextSync();
324
+ }
325
+
326
+ this.saveState();
327
+ }
328
+
329
+ async start() {
330
+ this.log('StackMemory Guardian starting...');
331
+ this.log('Monitoring: ' + SESSIONS_DIR);
332
+
333
+ // Ensure directories exist
334
+ const dirs = [
335
+ SESSIONS_DIR,
336
+ path.join(HOME, '.stackmemory', 'logs')
337
+ ];
338
+
339
+ for (const dir of dirs) {
340
+ if (!fs.existsSync(dir)) {
341
+ fs.mkdirSync(dir, { recursive: true });
342
+ }
343
+ }
344
+
345
+ // Initial check
346
+ await this.check();
347
+
348
+ // Start monitoring loop (every 30 seconds)
349
+ this.checkInterval = setInterval(() => this.check(), 30 * 1000);
350
+
351
+ this.log('Guardian started successfully');
352
+
353
+ // Handle shutdown signals
354
+ process.on('SIGTERM', () => this.stop());
355
+ process.on('SIGINT', () => this.stop());
356
+ }
357
+
358
+ stop() {
359
+ this.log('Guardian stopping...');
360
+
361
+ if (this.checkInterval) {
362
+ clearInterval(this.checkInterval);
363
+ }
364
+
365
+ this.stopContextSync();
366
+
367
+ // Clean up state file
368
+ try {
369
+ if (fs.existsSync(STATE_FILE)) {
370
+ fs.unlinkSync(STATE_FILE);
371
+ }
372
+ } catch (err) {
373
+ // Ignore
374
+ }
375
+
376
+ this.log('Guardian stopped');
377
+ process.exit(0);
378
+ }
379
+ }
380
+
381
+ const guardian = new Guardian();
382
+ guardian.start().catch(err => {
383
+ console.error('Guardian failed to start:', err);
384
+ process.exit(1);
385
+ });
386
+ `;
387
+ }
388
+ async function installService(config, spinner) {
389
+ const home = process.env.HOME || "";
390
+ await fs.mkdir(config.serviceDir, { recursive: true });
391
+ await fs.mkdir(config.logDir, { recursive: true });
392
+ const guardianPath = path.join(home, ".stackmemory", "guardian.js");
393
+ await fs.writeFile(guardianPath, generateGuardianScript(), "utf-8");
394
+ await fs.chmod(guardianPath, 493);
395
+ if (config.platform === "darwin") {
396
+ const plistContent = generateMacOSPlist(config);
397
+ await fs.writeFile(config.serviceFile, plistContent, "utf-8");
398
+ spinner.text = "Loading service...";
399
+ try {
400
+ execSync(`launchctl load -w "${config.serviceFile}"`, { stdio: "pipe" });
401
+ } catch {
402
+ try {
403
+ execSync(`launchctl unload "${config.serviceFile}"`, { stdio: "pipe" });
404
+ execSync(`launchctl load -w "${config.serviceFile}"`, {
405
+ stdio: "pipe"
406
+ });
407
+ } catch {
408
+ throw new Error("Failed to load launchd service");
409
+ }
410
+ }
411
+ spinner.succeed(chalk.green("Guardian service installed and started"));
412
+ console.log(chalk.gray(`Service file: ${config.serviceFile}`));
413
+ console.log(chalk.gray(`Guardian script: ${guardianPath}`));
414
+ console.log(chalk.gray(`Logs: ${config.logDir}/guardian.log`));
415
+ } else if (config.platform === "linux") {
416
+ const serviceContent = generateLinuxSystemdService(config);
417
+ await fs.writeFile(config.serviceFile, serviceContent, "utf-8");
418
+ spinner.text = "Enabling service...";
419
+ try {
420
+ execSync("systemctl --user daemon-reload", { stdio: "pipe" });
421
+ execSync(`systemctl --user enable ${config.serviceName}`, {
422
+ stdio: "pipe"
423
+ });
424
+ execSync(`systemctl --user start ${config.serviceName}`, {
425
+ stdio: "pipe"
426
+ });
427
+ } catch {
428
+ throw new Error(
429
+ "Failed to enable systemd service. Make sure systemd user session is available."
430
+ );
431
+ }
432
+ spinner.succeed(chalk.green("Guardian service installed and started"));
433
+ console.log(chalk.gray(`Service file: ${config.serviceFile}`));
434
+ console.log(chalk.gray(`Guardian script: ${guardianPath}`));
435
+ console.log(chalk.gray(`Logs: ${config.logDir}/guardian.log`));
436
+ }
437
+ }
438
+ async function uninstallService(config, spinner) {
439
+ const home = process.env.HOME || "";
440
+ const guardianPath = path.join(home, ".stackmemory", "guardian.js");
441
+ if (config.platform === "darwin") {
442
+ spinner.text = "Unloading service...";
443
+ try {
444
+ execSync(`launchctl unload "${config.serviceFile}"`, { stdio: "pipe" });
445
+ } catch {
446
+ }
447
+ try {
448
+ await fs.unlink(config.serviceFile);
449
+ } catch {
450
+ }
451
+ try {
452
+ await fs.unlink(guardianPath);
453
+ } catch {
454
+ }
455
+ spinner.succeed(chalk.green("Guardian service uninstalled"));
456
+ } else if (config.platform === "linux") {
457
+ spinner.text = "Stopping service...";
458
+ try {
459
+ execSync(`systemctl --user stop ${config.serviceName}`, {
460
+ stdio: "pipe"
461
+ });
462
+ execSync(`systemctl --user disable ${config.serviceName}`, {
463
+ stdio: "pipe"
464
+ });
465
+ } catch {
466
+ }
467
+ try {
468
+ await fs.unlink(config.serviceFile);
469
+ } catch {
470
+ }
471
+ try {
472
+ await fs.unlink(guardianPath);
473
+ } catch {
474
+ }
475
+ try {
476
+ execSync("systemctl --user daemon-reload", { stdio: "pipe" });
477
+ } catch {
478
+ }
479
+ spinner.succeed(chalk.green("Guardian service uninstalled"));
480
+ }
481
+ }
482
+ async function showServiceStatus(config) {
483
+ const home = process.env.HOME || "";
484
+ const stateFile = path.join(home, ".stackmemory", "guardian.state");
485
+ console.log(chalk.bold("\nStackMemory Guardian Service Status\n"));
486
+ if (config.platform === "unsupported") {
487
+ console.log(chalk.red("Platform not supported for service installation"));
488
+ console.log(
489
+ chalk.gray("Supported platforms: macOS (launchd), Linux (systemd)")
490
+ );
491
+ return;
492
+ }
493
+ if (!existsSync(config.serviceFile)) {
494
+ console.log(chalk.yellow("Service not installed"));
495
+ console.log(chalk.gray("Install with: stackmemory service install"));
496
+ return;
497
+ }
498
+ let isRunning = false;
499
+ let serviceOutput = "";
500
+ if (config.platform === "darwin") {
501
+ try {
502
+ serviceOutput = execSync(`launchctl list | grep ${config.serviceName}`, {
503
+ encoding: "utf-8",
504
+ stdio: ["pipe", "pipe", "pipe"]
505
+ });
506
+ isRunning = serviceOutput.includes(config.serviceName);
507
+ } catch {
508
+ isRunning = false;
509
+ }
510
+ } else if (config.platform === "linux") {
511
+ try {
512
+ serviceOutput = execSync(
513
+ `systemctl --user is-active ${config.serviceName}`,
514
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
515
+ ).trim();
516
+ isRunning = serviceOutput === "active";
517
+ } catch {
518
+ isRunning = false;
519
+ }
520
+ }
521
+ if (isRunning) {
522
+ console.log(chalk.green("Status: Running"));
523
+ } else {
524
+ console.log(chalk.yellow("Status: Stopped"));
525
+ }
526
+ console.log(chalk.gray(`Platform: ${config.platform}`));
527
+ console.log(chalk.gray(`Service: ${config.serviceName}`));
528
+ console.log(chalk.gray(`Config: ${config.serviceFile}`));
529
+ if (existsSync(stateFile)) {
530
+ try {
531
+ const state = JSON.parse(readFileSync(stateFile, "utf-8"));
532
+ console.log(chalk.bold("\nGuardian State:"));
533
+ console.log(` Last check: ${state.lastCheck}`);
534
+ console.log(` Active sessions: ${state.activeSessions?.length || 0}`);
535
+ console.log(` Sync running: ${state.syncRunning ? "Yes" : "No"}`);
536
+ console.log(` Last activity: ${state.lastActivity}`);
537
+ } catch {
538
+ }
539
+ }
540
+ }
541
+ async function showServiceLogs(config, lines) {
542
+ console.log(
543
+ chalk.bold(`
544
+ StackMemory Guardian Logs (last ${lines} lines)
545
+ `)
546
+ );
547
+ const logFile = path.join(config.logDir, "guardian.log");
548
+ if (!existsSync(logFile)) {
549
+ console.log(chalk.yellow("No logs found"));
550
+ console.log(chalk.gray(`Expected at: ${logFile}`));
551
+ return;
552
+ }
553
+ try {
554
+ const content = readFileSync(logFile, "utf-8");
555
+ const logLines = content.split("\n").filter(Boolean);
556
+ const lastLines = logLines.slice(-lines);
557
+ lastLines.forEach((line) => {
558
+ if (line.includes("[ERROR]")) {
559
+ console.log(chalk.red(line));
560
+ } else if (line.includes("[WARN]")) {
561
+ console.log(chalk.yellow(line));
562
+ } else {
563
+ console.log(chalk.gray(line));
564
+ }
565
+ });
566
+ console.log(chalk.gray(`
567
+ Full log: ${logFile}`));
568
+ } catch (err) {
569
+ console.log(chalk.red(`Failed to read logs: ${err.message}`));
570
+ }
571
+ }
572
+ function createServiceCommand() {
573
+ const cmd = new Command("service").description("Manage StackMemory guardian OS service (auto-start on login)").addHelpText(
574
+ "after",
575
+ `
576
+ Examples:
577
+ stackmemory service install Install and start the guardian service
578
+ stackmemory service uninstall Remove the guardian service
579
+ stackmemory service status Show service status
580
+ stackmemory service logs Show recent service logs
581
+ stackmemory service logs -n 50 Show last 50 log lines
582
+
583
+ The guardian service:
584
+ - Monitors ~/.stackmemory/sessions/ for active sessions
585
+ - Starts context sync when an active session is detected
586
+ - Stops gracefully after 30 minutes of inactivity
587
+ - Runs automatically on system login (opt-in)
588
+ `
589
+ );
590
+ cmd.command("install").description("Install the guardian service (starts on login)").action(async () => {
591
+ const spinner = ora("Installing guardian service...").start();
592
+ try {
593
+ const config = getServiceConfig();
594
+ if (config.platform === "unsupported") {
595
+ spinner.fail(chalk.red("Platform not supported"));
596
+ console.log(
597
+ chalk.gray("Supported: macOS (launchd), Linux (systemd)")
598
+ );
599
+ process.exit(1);
600
+ }
601
+ await installService(config, spinner);
602
+ console.log(chalk.bold("\nGuardian service will:"));
603
+ console.log(" - Start automatically on login");
604
+ console.log(" - Monitor for active StackMemory sessions");
605
+ console.log(" - Manage context sync based on activity");
606
+ console.log(" - Stop gracefully after 30 min idle");
607
+ } catch (err) {
608
+ spinner.fail(
609
+ chalk.red(`Installation failed: ${err.message}`)
610
+ );
611
+ process.exit(1);
612
+ }
613
+ });
614
+ cmd.command("uninstall").description("Remove the guardian service").action(async () => {
615
+ const spinner = ora("Uninstalling guardian service...").start();
616
+ try {
617
+ const config = getServiceConfig();
618
+ if (config.platform === "unsupported") {
619
+ spinner.fail(chalk.red("Platform not supported"));
620
+ process.exit(1);
621
+ }
622
+ await uninstallService(config, spinner);
623
+ } catch (err) {
624
+ spinner.fail(
625
+ chalk.red(`Uninstallation failed: ${err.message}`)
626
+ );
627
+ process.exit(1);
628
+ }
629
+ });
630
+ cmd.command("status").description("Show guardian service status").action(async () => {
631
+ try {
632
+ const config = getServiceConfig();
633
+ await showServiceStatus(config);
634
+ } catch (err) {
635
+ console.error(
636
+ chalk.red(`Status check failed: ${err.message}`)
637
+ );
638
+ process.exit(1);
639
+ }
640
+ });
641
+ cmd.command("logs").description("Show recent guardian service logs").option("-n, --lines <number>", "Number of log lines to show", "20").option("-f, --follow", "Follow log output (tail -f style)").action(async (options) => {
642
+ try {
643
+ const config = getServiceConfig();
644
+ const lines = parseInt(options.lines) || 20;
645
+ if (options.follow) {
646
+ const logFile = path.join(config.logDir, "guardian.log");
647
+ console.log(chalk.bold(`Following ${logFile} (Ctrl+C to stop)
648
+ `));
649
+ const tail = spawn("tail", ["-f", "-n", lines.toString(), logFile], {
650
+ stdio: "inherit"
651
+ });
652
+ process.on("SIGINT", () => {
653
+ tail.kill();
654
+ process.exit(0);
655
+ });
656
+ } else {
657
+ await showServiceLogs(config, lines);
658
+ }
659
+ } catch (err) {
660
+ console.error(
661
+ chalk.red(`Failed to show logs: ${err.message}`)
662
+ );
663
+ process.exit(1);
664
+ }
665
+ });
666
+ cmd.action(async () => {
667
+ try {
668
+ const config = getServiceConfig();
669
+ await showServiceStatus(config);
670
+ } catch (err) {
671
+ console.error(
672
+ chalk.red(`Status check failed: ${err.message}`)
673
+ );
674
+ process.exit(1);
675
+ }
676
+ });
677
+ return cmd;
678
+ }
679
+ var service_default = createServiceCommand();
680
+ export {
681
+ createServiceCommand,
682
+ service_default as default
683
+ };
684
+ //# sourceMappingURL=service.js.map