@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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # StackMemory
2
2
 
3
- **Lossless, project-scoped memory for AI tools** • v0.5.40
3
+ **Lossless, project-scoped memory for AI tools** • v0.5.47
4
4
 
5
5
  StackMemory is a **production-ready memory runtime** for AI coding tools that preserves full project context across sessions:
6
6
 
@@ -30,7 +30,7 @@ StackMemory solves this by:
30
30
 
31
31
  - Storing all events, tool calls, and decisions
32
32
  - Smart retrieval of relevant context
33
- - Call stack organization (10,000+ frame depth)
33
+ - Call stack organization for nested context
34
34
  - Configurable importance scoring
35
35
  - Team collaboration through shared stacks
36
36
 
@@ -92,25 +92,14 @@ The editor never manages memory directly; it asks StackMemory for the **context
92
92
 
93
93
  ## Product Health Metrics
94
94
 
95
- ### Current Status (v0.5.30)
95
+ ### Current Status (v0.5.47)
96
96
 
97
97
  | Metric | Current | Target | Status |
98
98
  |--------|---------|--------|--------|
99
99
  | **Test Coverage** | 85% | 90% | In Progress |
100
- | **Performance (p50)** | TBD | <50ms | Pending |
101
- | **Documentation** | 70% | 100% | In Progress |
102
- | **Active Issues** | 5 high | 0 high | In Progress |
103
- | **Code Quality** | 396 tests | 400+ | Done |
104
- | **npm Downloads** | Growing | 1K+/week | On Track |
105
-
106
- ### Quality Score: 78/100
107
-
108
- **Formula:** (Test Coverage × 0.3) + (Performance × 0.3) + (Documentation × 0.2) + (Issues Resolution × 0.2)
109
-
110
- ### Next Sprint Priorities
111
-
112
- 1. **[STA-289] Performance Optimization** - Achieve SLA targets
113
- 2. **[STA-291] Code Cleanup** - Zero TODOs, 90% coverage
100
+ | **Tests Passing** | 490 | 500+ | On Track |
101
+ | **Documentation** | 80% | 100% | In Progress |
102
+ | **Active Issues** | 3 high | 0 high | In Progress |
114
103
 
115
104
  ---
116
105
 
@@ -260,40 +249,33 @@ Available MCP tools in Claude Code:
260
249
 
261
250
  ## 2. Open-Source Local Mode
262
251
 
263
- ### Step 1: Clone
252
+ ### Step 1: Clone & Build
264
253
 
265
254
  ```bash
266
- git clone https://github.com/stackmemory/stackmemory
255
+ git clone https://github.com/stackmemoryai/stackmemory
267
256
  cd stackmemory
257
+ npm install
258
+ npm run build
268
259
  ```
269
260
 
270
261
  ### Step 2: Run local MCP server
271
262
 
272
263
  ```bash
273
- cargo run --bin stackmemory-mcp
274
- # or
275
- npm run dev
276
- ```
277
-
278
- This creates:
279
-
280
- ```
281
- .memory/
282
- └── memory.db # SQLite
264
+ npm run mcp:start
265
+ # or for development
266
+ npm run mcp:dev
283
267
  ```
284
268
 
285
- All project memory lives locally.
286
-
287
- ---
269
+ This creates `.stackmemory/` with SQLite storage.
288
270
 
289
271
  ### Step 3: Point your editor to local MCP
290
272
 
291
273
  ```json
292
274
  {
293
- "tools": {
275
+ "mcpServers": {
294
276
  "stackmemory": {
295
- "command": "stackmemory-mcp",
296
- "args": ["--local"]
277
+ "command": "node",
278
+ "args": ["dist/integrations/mcp/server.js"]
297
279
  }
298
280
  }
299
281
  }
@@ -506,7 +488,7 @@ stackmemory mcp-server [--port 3001]
506
488
  - Hosted: **Private beta**
507
489
  - OSS mirror: **Production ready**
508
490
  - MCP integration: **Stable**
509
- - CLI: **v0.5.40** - Zero-config setup, diagnostics, full task/context/Linear management
491
+ - CLI: **v0.5.47** - Zero-config setup, diagnostics, full task/context/Linear management
510
492
  - Two-tier storage: **Complete**
511
493
  - Test Suite: **490 tests passing**
512
494
 
@@ -514,13 +496,18 @@ stackmemory mcp-server [--port 3001]
514
496
 
515
497
  ## Changelog
516
498
 
499
+ ### v0.5.47 (2026-01-27)
500
+ - **Graceful database failures**: Handles native module version mismatches
501
+ - **Suppress dotenv logs**: Cleaner terminal output
502
+ - **TTY preservation**: Fixes interactive mode for claude-sm/claude-smd
503
+ - **Silent Linear auth**: No spam when API key not configured
504
+
517
505
  ### v0.5.40 (2026-01-27)
518
506
  - **Zero-config onboarding**: `stackmemory init` now works without any prompts
519
507
  - **New `setup-mcp` command**: Auto-configures Claude Code MCP integration
520
508
  - **New `doctor` command**: Diagnoses issues and suggests fixes
521
509
  - **Interactive postinstall**: Asks for consent before modifying ~/.claude
522
510
  - **Better error messages**: Shows reason + fix + next step
523
- - 490 tests passing
524
511
 
525
512
  ### v0.5.39 (2026-01-27)
526
513
  - **AsyncMutex**: Thread-safe Linear sync with stale lock detection
@@ -567,9 +554,8 @@ stackmemory mcp-server [--port 3001]
567
554
  ## Roadmap
568
555
 
569
556
  **Phase 4 (Completed):** Two-tier storage system with local tiers and infinite remote storage
570
- **Phase 5 (Next):** PostgreSQL production adapter, enhanced team collaboration, advanced analytics
571
- **Phase 3:** Team collaboration, shared stacks, frame handoff
572
- **Phase 4:** Two-tier storage, enterprise features, cost optimization
557
+ **Phase 5 (Current):** PostgreSQL production adapter, enhanced team collaboration, advanced analytics
558
+ **Phase 6 (Next):** Enterprise features, multi-org support, cost optimization
573
559
 
574
560
  ---
575
561
 
@@ -0,0 +1,392 @@
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 { Command } from "commander";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { existsSync, readFileSync, unlinkSync } from "fs";
9
+ import { spawn } from "child_process";
10
+ import { join } from "path";
11
+ import {
12
+ loadDaemonConfig,
13
+ saveDaemonConfig,
14
+ readDaemonStatus,
15
+ getDaemonPaths,
16
+ DEFAULT_DAEMON_CONFIG
17
+ } from "../../daemon/daemon-config.js";
18
+ function createDaemonCommand() {
19
+ const cmd = new Command("daemon").description("Manage StackMemory unified daemon for background services").addHelpText(
20
+ "after",
21
+ `
22
+ Examples:
23
+ stackmemory daemon start Start the daemon
24
+ stackmemory daemon stop Stop the daemon
25
+ stackmemory daemon status Check daemon status
26
+ stackmemory daemon logs View daemon logs
27
+ stackmemory daemon config Show/edit configuration
28
+
29
+ The daemon provides:
30
+ - Context auto-save (default: every 15 minutes)
31
+ - Linear sync (optional, if configured)
32
+ - File watch (optional, for change detection)
33
+ `
34
+ );
35
+ cmd.command("start").description("Start the unified daemon").option("--foreground", "Run in foreground (for debugging)").option("--save-interval <minutes>", "Context save interval in minutes").option("--linear-interval <minutes>", "Linear sync interval in minutes").option("--no-linear", "Disable Linear sync").option("--log-level <level>", "Log level (debug|info|warn|error)").action(async (options) => {
36
+ const status = readDaemonStatus();
37
+ if (status.running) {
38
+ console.log(
39
+ chalk.yellow("Daemon already running"),
40
+ chalk.gray(`(pid: ${status.pid})`)
41
+ );
42
+ return;
43
+ }
44
+ const spinner = ora("Starting unified daemon...").start();
45
+ try {
46
+ const args = ["daemon-run"];
47
+ if (options.saveInterval) {
48
+ args.push("--save-interval", options.saveInterval);
49
+ }
50
+ if (options.linearInterval) {
51
+ args.push("--linear-interval", options.linearInterval);
52
+ }
53
+ if (options.linear === false) {
54
+ args.push("--no-linear");
55
+ }
56
+ if (options.logLevel) {
57
+ args.push("--log-level", options.logLevel);
58
+ }
59
+ if (options.foreground) {
60
+ spinner.stop();
61
+ console.log(chalk.cyan("Running in foreground (Ctrl+C to stop)"));
62
+ const { UnifiedDaemon } = await import("../../daemon/unified-daemon.js");
63
+ const config = {};
64
+ if (options.saveInterval) {
65
+ config.context = {
66
+ enabled: true,
67
+ interval: parseInt(options.saveInterval, 10)
68
+ };
69
+ }
70
+ if (options.linearInterval) {
71
+ config.linear = {
72
+ enabled: true,
73
+ interval: parseInt(options.linearInterval, 10),
74
+ retryAttempts: 3,
75
+ retryDelay: 3e4
76
+ };
77
+ }
78
+ if (options.linear === false) {
79
+ config.linear = {
80
+ enabled: false,
81
+ interval: 60,
82
+ retryAttempts: 3,
83
+ retryDelay: 3e4
84
+ };
85
+ }
86
+ const daemon = new UnifiedDaemon(config);
87
+ await daemon.start();
88
+ return;
89
+ }
90
+ const daemonScript = getDaemonScriptPath();
91
+ if (!daemonScript) {
92
+ spinner.fail(chalk.red("Daemon script not found"));
93
+ return;
94
+ }
95
+ const daemonProcess = spawn("node", [daemonScript, ...args.slice(1)], {
96
+ detached: true,
97
+ stdio: "ignore",
98
+ env: { ...process.env }
99
+ });
100
+ daemonProcess.unref();
101
+ await new Promise((r) => setTimeout(r, 1e3));
102
+ const newStatus = readDaemonStatus();
103
+ if (newStatus.running) {
104
+ spinner.succeed(chalk.green("Daemon started"));
105
+ console.log(chalk.gray(`PID: ${newStatus.pid}`));
106
+ const services = [];
107
+ if (newStatus.services.context.enabled) services.push("context");
108
+ if (newStatus.services.linear.enabled) services.push("linear");
109
+ if (newStatus.services.fileWatch.enabled) services.push("file-watch");
110
+ if (services.length > 0) {
111
+ console.log(chalk.gray(`Services: ${services.join(", ")}`));
112
+ }
113
+ } else {
114
+ spinner.fail(chalk.red("Failed to start daemon"));
115
+ console.log(chalk.gray("Check logs: stackmemory daemon logs"));
116
+ }
117
+ } catch (error) {
118
+ spinner.fail(chalk.red("Failed to start daemon"));
119
+ console.log(chalk.gray(error.message));
120
+ }
121
+ });
122
+ cmd.command("stop").description("Stop the unified daemon").action(() => {
123
+ const status = readDaemonStatus();
124
+ if (!status.running || !status.pid) {
125
+ console.log(chalk.yellow("Daemon not running"));
126
+ return;
127
+ }
128
+ try {
129
+ process.kill(status.pid, "SIGTERM");
130
+ console.log(chalk.green("Daemon stopped"));
131
+ } catch (err) {
132
+ console.log(chalk.red("Failed to stop daemon"));
133
+ console.log(chalk.gray(err.message));
134
+ const { pidFile } = getDaemonPaths();
135
+ if (existsSync(pidFile)) {
136
+ unlinkSync(pidFile);
137
+ console.log(chalk.gray("Cleaned up stale PID file"));
138
+ }
139
+ }
140
+ });
141
+ cmd.command("restart").description("Restart the unified daemon").action(async () => {
142
+ const status = readDaemonStatus();
143
+ if (status.running && status.pid) {
144
+ try {
145
+ process.kill(status.pid, "SIGTERM");
146
+ await new Promise((r) => setTimeout(r, 1e3));
147
+ } catch {
148
+ }
149
+ }
150
+ const config = loadDaemonConfig();
151
+ const daemonScript = getDaemonScriptPath();
152
+ if (!daemonScript) {
153
+ console.log(chalk.red("Daemon script not found"));
154
+ return;
155
+ }
156
+ const args = [];
157
+ if (config.context.interval !== 15) {
158
+ args.push("--save-interval", String(config.context.interval));
159
+ }
160
+ if (!config.linear.enabled) {
161
+ args.push("--no-linear");
162
+ } else if (config.linear.interval !== 60) {
163
+ args.push("--linear-interval", String(config.linear.interval));
164
+ }
165
+ const daemonProcess = spawn("node", [daemonScript, ...args], {
166
+ detached: true,
167
+ stdio: "ignore"
168
+ });
169
+ daemonProcess.unref();
170
+ await new Promise((r) => setTimeout(r, 1e3));
171
+ const newStatus = readDaemonStatus();
172
+ if (newStatus.running) {
173
+ console.log(
174
+ chalk.green("Daemon restarted"),
175
+ chalk.gray(`(pid: ${newStatus.pid})`)
176
+ );
177
+ } else {
178
+ console.log(chalk.red("Failed to restart daemon"));
179
+ }
180
+ });
181
+ cmd.command("status").description("Check daemon status").action(() => {
182
+ const status = readDaemonStatus();
183
+ const config = loadDaemonConfig();
184
+ console.log(chalk.bold("\nStackMemory Unified Daemon\n"));
185
+ console.log(
186
+ `Status: ${status.running ? chalk.green("Running") : chalk.yellow("Stopped")}`
187
+ );
188
+ if (status.running) {
189
+ console.log(chalk.gray(` PID: ${status.pid}`));
190
+ if (status.uptime) {
191
+ const uptime = Math.round(status.uptime / 1e3);
192
+ const hours = Math.floor(uptime / 3600);
193
+ const mins = Math.floor(uptime % 3600 / 60);
194
+ const secs = uptime % 60;
195
+ console.log(chalk.gray(` Uptime: ${hours}h ${mins}m ${secs}s`));
196
+ }
197
+ }
198
+ console.log("");
199
+ console.log(chalk.bold("Services:"));
200
+ const ctx = status.services.context;
201
+ console.log(
202
+ ` Context: ${ctx.enabled ? chalk.green("Enabled") : chalk.gray("Disabled")}`
203
+ );
204
+ if (ctx.enabled) {
205
+ console.log(chalk.gray(` Interval: ${config.context.interval} min`));
206
+ if (ctx.saveCount) {
207
+ console.log(chalk.gray(` Saves: ${ctx.saveCount}`));
208
+ }
209
+ if (ctx.lastRun) {
210
+ const ago = Math.round((Date.now() - ctx.lastRun) / 1e3 / 60);
211
+ console.log(chalk.gray(` Last save: ${ago} min ago`));
212
+ }
213
+ }
214
+ const lin = status.services.linear;
215
+ console.log(
216
+ ` Linear: ${lin.enabled ? chalk.green("Enabled") : chalk.gray("Disabled")}`
217
+ );
218
+ if (lin.enabled) {
219
+ console.log(chalk.gray(` Interval: ${config.linear.interval} min`));
220
+ if (config.linear.quietHours) {
221
+ console.log(
222
+ chalk.gray(
223
+ ` Quiet hours: ${config.linear.quietHours.start}:00 - ${config.linear.quietHours.end}:00`
224
+ )
225
+ );
226
+ }
227
+ if (lin.syncCount) {
228
+ console.log(chalk.gray(` Syncs: ${lin.syncCount}`));
229
+ }
230
+ }
231
+ const fw = status.services.fileWatch;
232
+ console.log(
233
+ ` FileWatch: ${fw.enabled ? chalk.green("Enabled") : chalk.gray("Disabled")}`
234
+ );
235
+ if (status.errors && status.errors.length > 0) {
236
+ console.log("");
237
+ console.log(chalk.bold("Recent Errors:"));
238
+ status.errors.slice(-3).forEach((err) => {
239
+ console.log(chalk.red(` - ${err.slice(0, 80)}`));
240
+ });
241
+ }
242
+ if (!status.running) {
243
+ console.log("");
244
+ console.log(chalk.bold("To start: stackmemory daemon start"));
245
+ }
246
+ });
247
+ cmd.command("logs").description("View daemon logs").option("-n, --lines <number>", "Number of lines to show", "50").option("-f, --follow", "Follow log output").option("--level <level>", "Filter by log level").action((options) => {
248
+ const { logFile } = getDaemonPaths();
249
+ if (!existsSync(logFile)) {
250
+ console.log(chalk.yellow("No log file found"));
251
+ console.log(
252
+ chalk.gray("Start the daemon first: stackmemory daemon start")
253
+ );
254
+ return;
255
+ }
256
+ if (options.follow) {
257
+ const tail = spawn("tail", ["-f", logFile], { stdio: "inherit" });
258
+ tail.on("error", () => {
259
+ console.log(chalk.red("Could not follow logs"));
260
+ });
261
+ return;
262
+ }
263
+ const content = readFileSync(logFile, "utf8");
264
+ const lines = content.trim().split("\n");
265
+ const count = parseInt(options.lines, 10);
266
+ let recent = lines.slice(-count);
267
+ if (options.level) {
268
+ const level = options.level.toUpperCase();
269
+ recent = recent.filter((line) => {
270
+ try {
271
+ const entry = JSON.parse(line);
272
+ return entry.level === level;
273
+ } catch {
274
+ return false;
275
+ }
276
+ });
277
+ }
278
+ console.log(chalk.bold(`
279
+ Daemon logs (${recent.length} lines):
280
+ `));
281
+ for (const line of recent) {
282
+ try {
283
+ const entry = JSON.parse(line);
284
+ const time = entry.timestamp.split("T")[1].split(".")[0];
285
+ const levelColor = entry.level === "ERROR" ? chalk.red : entry.level === "WARN" ? chalk.yellow : entry.level === "DEBUG" ? chalk.gray : chalk.white;
286
+ console.log(
287
+ `${chalk.gray(time)} ${levelColor(`[${entry.level}]`)} ${chalk.cyan(`[${entry.service}]`)} ${entry.message}`
288
+ );
289
+ } catch {
290
+ console.log(line);
291
+ }
292
+ }
293
+ });
294
+ cmd.command("config").description("Show or edit daemon configuration").option("--edit", "Open config in editor").option("--reset", "Reset to default configuration").option("--set <key=value>", "Set a config value").action((options) => {
295
+ const { configFile } = getDaemonPaths();
296
+ if (options.reset) {
297
+ saveDaemonConfig(DEFAULT_DAEMON_CONFIG);
298
+ console.log(chalk.green("Configuration reset to defaults"));
299
+ return;
300
+ }
301
+ if (options.edit) {
302
+ const editor = process.env["EDITOR"] || "vim";
303
+ spawn(editor, [configFile], { stdio: "inherit" });
304
+ return;
305
+ }
306
+ if (options.set) {
307
+ const [key, value] = options.set.split("=");
308
+ const config2 = loadDaemonConfig();
309
+ const parts = key.split(".");
310
+ let target = config2;
311
+ for (let i = 0; i < parts.length - 1; i++) {
312
+ if (target[parts[i]] && typeof target[parts[i]] === "object") {
313
+ target = target[parts[i]];
314
+ } else {
315
+ console.log(chalk.red(`Invalid config key: ${key}`));
316
+ return;
317
+ }
318
+ }
319
+ const lastKey = parts[parts.length - 1];
320
+ const parsed = value === "true" ? true : value === "false" ? false : isNaN(Number(value)) ? value : Number(value);
321
+ target[lastKey] = parsed;
322
+ saveDaemonConfig(config2);
323
+ console.log(chalk.green(`Set ${key} = ${value}`));
324
+ return;
325
+ }
326
+ const config = loadDaemonConfig();
327
+ console.log(chalk.bold("\nDaemon Configuration\n"));
328
+ console.log(chalk.gray(`File: ${configFile}`));
329
+ console.log("");
330
+ console.log(chalk.bold("Context Service:"));
331
+ console.log(` Enabled: ${config.context.enabled}`);
332
+ console.log(` Interval: ${config.context.interval} minutes`);
333
+ console.log("");
334
+ console.log(chalk.bold("Linear Service:"));
335
+ console.log(` Enabled: ${config.linear.enabled}`);
336
+ console.log(` Interval: ${config.linear.interval} minutes`);
337
+ if (config.linear.quietHours) {
338
+ console.log(
339
+ ` Quiet hours: ${config.linear.quietHours.start}:00 - ${config.linear.quietHours.end}:00`
340
+ );
341
+ }
342
+ console.log("");
343
+ console.log(chalk.bold("File Watch:"));
344
+ console.log(` Enabled: ${config.fileWatch.enabled}`);
345
+ console.log(` Extensions: ${config.fileWatch.extensions.join(", ")}`);
346
+ console.log("");
347
+ console.log(chalk.bold("General:"));
348
+ console.log(` Heartbeat: ${config.heartbeatInterval} seconds`);
349
+ console.log(` Log level: ${config.logLevel}`);
350
+ });
351
+ cmd.action(() => {
352
+ const status = readDaemonStatus();
353
+ console.log(chalk.bold("\nStackMemory Daemon\n"));
354
+ console.log(
355
+ `Status: ${status.running ? chalk.green("Running") : chalk.yellow("Stopped")}`
356
+ );
357
+ if (!status.running) {
358
+ console.log("");
359
+ console.log(chalk.bold("Quick start:"));
360
+ console.log(" stackmemory daemon start Start background services");
361
+ } else {
362
+ console.log("");
363
+ console.log(chalk.bold("Commands:"));
364
+ console.log(" stackmemory daemon status View detailed status");
365
+ console.log(" stackmemory daemon logs View daemon logs");
366
+ console.log(" stackmemory daemon stop Stop the daemon");
367
+ }
368
+ });
369
+ return cmd;
370
+ }
371
+ function getDaemonScriptPath() {
372
+ const candidates = [
373
+ join(__dirname, "../../daemon/unified-daemon.js"),
374
+ join(process.cwd(), "dist/daemon/unified-daemon.js"),
375
+ join(
376
+ process.cwd(),
377
+ "node_modules/@stackmemoryai/stackmemory/dist/daemon/unified-daemon.js"
378
+ )
379
+ ];
380
+ for (const candidate of candidates) {
381
+ if (existsSync(candidate)) {
382
+ return candidate;
383
+ }
384
+ }
385
+ return candidates[0];
386
+ }
387
+ var daemon_default = createDaemonCommand();
388
+ export {
389
+ createDaemonCommand,
390
+ daemon_default as default
391
+ };
392
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/cli/commands/daemon.ts"],
4
+ "sourcesContent": ["/**\n * Daemon CLI Command\n * Manage StackMemory unified daemon\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { existsSync, readFileSync, unlinkSync } from 'fs';\nimport { spawn } from 'child_process';\nimport { join } from 'path';\nimport {\n loadDaemonConfig,\n saveDaemonConfig,\n readDaemonStatus,\n getDaemonPaths,\n DEFAULT_DAEMON_CONFIG,\n type DaemonConfig,\n} from '../../daemon/daemon-config.js';\n\nexport function createDaemonCommand(): Command {\n const cmd = new Command('daemon')\n .description('Manage StackMemory unified daemon for background services')\n .addHelpText(\n 'after',\n `\nExamples:\n stackmemory daemon start Start the daemon\n stackmemory daemon stop Stop the daemon\n stackmemory daemon status Check daemon status\n stackmemory daemon logs View daemon logs\n stackmemory daemon config Show/edit configuration\n\nThe daemon provides:\n - Context auto-save (default: every 15 minutes)\n - Linear sync (optional, if configured)\n - File watch (optional, for change detection)\n`\n );\n\n // Start command\n cmd\n .command('start')\n .description('Start the unified daemon')\n .option('--foreground', 'Run in foreground (for debugging)')\n .option('--save-interval <minutes>', 'Context save interval in minutes')\n .option('--linear-interval <minutes>', 'Linear sync interval in minutes')\n .option('--no-linear', 'Disable Linear sync')\n .option('--log-level <level>', 'Log level (debug|info|warn|error)')\n .action(async (options) => {\n const status = readDaemonStatus();\n\n if (status.running) {\n console.log(\n chalk.yellow('Daemon already running'),\n chalk.gray(`(pid: ${status.pid})`)\n );\n return;\n }\n\n const spinner = ora('Starting unified daemon...').start();\n\n try {\n // Build args\n const args = ['daemon-run'];\n\n if (options.saveInterval) {\n args.push('--save-interval', options.saveInterval);\n }\n if (options.linearInterval) {\n args.push('--linear-interval', options.linearInterval);\n }\n if (options.linear === false) {\n args.push('--no-linear');\n }\n if (options.logLevel) {\n args.push('--log-level', options.logLevel);\n }\n\n if (options.foreground) {\n spinner.stop();\n console.log(chalk.cyan('Running in foreground (Ctrl+C to stop)'));\n const { UnifiedDaemon } =\n await import('../../daemon/unified-daemon.js');\n const config: Partial<DaemonConfig> = {};\n if (options.saveInterval) {\n config.context = {\n enabled: true,\n interval: parseInt(options.saveInterval, 10),\n };\n }\n if (options.linearInterval) {\n config.linear = {\n enabled: true,\n interval: parseInt(options.linearInterval, 10),\n retryAttempts: 3,\n retryDelay: 30000,\n };\n }\n if (options.linear === false) {\n config.linear = {\n enabled: false,\n interval: 60,\n retryAttempts: 3,\n retryDelay: 30000,\n };\n }\n const daemon = new UnifiedDaemon(config);\n await daemon.start();\n return;\n }\n\n // Get path to daemon script\n const daemonScript = getDaemonScriptPath();\n if (!daemonScript) {\n spinner.fail(chalk.red('Daemon script not found'));\n return;\n }\n\n // Start in background\n const daemonProcess = spawn('node', [daemonScript, ...args.slice(1)], {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env },\n });\n\n daemonProcess.unref();\n\n // Wait for startup\n await new Promise((r) => setTimeout(r, 1000));\n const newStatus = readDaemonStatus();\n\n if (newStatus.running) {\n spinner.succeed(chalk.green('Daemon started'));\n console.log(chalk.gray(`PID: ${newStatus.pid}`));\n\n // Show enabled services\n const services = [];\n if (newStatus.services.context.enabled) services.push('context');\n if (newStatus.services.linear.enabled) services.push('linear');\n if (newStatus.services.fileWatch.enabled) services.push('file-watch');\n if (services.length > 0) {\n console.log(chalk.gray(`Services: ${services.join(', ')}`));\n }\n } else {\n spinner.fail(chalk.red('Failed to start daemon'));\n console.log(chalk.gray('Check logs: stackmemory daemon logs'));\n }\n } catch (error) {\n spinner.fail(chalk.red('Failed to start daemon'));\n console.log(chalk.gray((error as Error).message));\n }\n });\n\n // Stop command\n cmd\n .command('stop')\n .description('Stop the unified daemon')\n .action(() => {\n const status = readDaemonStatus();\n\n if (!status.running || !status.pid) {\n console.log(chalk.yellow('Daemon not running'));\n return;\n }\n\n try {\n process.kill(status.pid, 'SIGTERM');\n console.log(chalk.green('Daemon stopped'));\n } catch (err) {\n console.log(chalk.red('Failed to stop daemon'));\n console.log(chalk.gray((err as Error).message));\n\n // Clean up stale PID file\n const { pidFile } = getDaemonPaths();\n if (existsSync(pidFile)) {\n unlinkSync(pidFile);\n console.log(chalk.gray('Cleaned up stale PID file'));\n }\n }\n });\n\n // Restart command\n cmd\n .command('restart')\n .description('Restart the unified daemon')\n .action(async () => {\n const status = readDaemonStatus();\n\n if (status.running && status.pid) {\n try {\n process.kill(status.pid, 'SIGTERM');\n await new Promise((r) => setTimeout(r, 1000));\n } catch {\n // Ignore\n }\n }\n\n // Get saved config\n const config = loadDaemonConfig();\n\n // Start with same config\n const daemonScript = getDaemonScriptPath();\n if (!daemonScript) {\n console.log(chalk.red('Daemon script not found'));\n return;\n }\n\n const args: string[] = [];\n if (config.context.interval !== 15) {\n args.push('--save-interval', String(config.context.interval));\n }\n if (!config.linear.enabled) {\n args.push('--no-linear');\n } else if (config.linear.interval !== 60) {\n args.push('--linear-interval', String(config.linear.interval));\n }\n\n const daemonProcess = spawn('node', [daemonScript, ...args], {\n detached: true,\n stdio: 'ignore',\n });\n daemonProcess.unref();\n\n await new Promise((r) => setTimeout(r, 1000));\n const newStatus = readDaemonStatus();\n\n if (newStatus.running) {\n console.log(\n chalk.green('Daemon restarted'),\n chalk.gray(`(pid: ${newStatus.pid})`)\n );\n } else {\n console.log(chalk.red('Failed to restart daemon'));\n }\n });\n\n // Status command\n cmd\n .command('status')\n .description('Check daemon status')\n .action(() => {\n const status = readDaemonStatus();\n const config = loadDaemonConfig();\n\n console.log(chalk.bold('\\nStackMemory Unified Daemon\\n'));\n\n console.log(\n `Status: ${status.running ? chalk.green('Running') : chalk.yellow('Stopped')}`\n );\n\n if (status.running) {\n console.log(chalk.gray(` PID: ${status.pid}`));\n if (status.uptime) {\n const uptime = Math.round(status.uptime / 1000);\n const hours = Math.floor(uptime / 3600);\n const mins = Math.floor((uptime % 3600) / 60);\n const secs = uptime % 60;\n console.log(chalk.gray(` Uptime: ${hours}h ${mins}m ${secs}s`));\n }\n }\n\n console.log('');\n console.log(chalk.bold('Services:'));\n\n // Context service\n const ctx = status.services.context;\n console.log(\n ` Context: ${ctx.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`\n );\n if (ctx.enabled) {\n console.log(chalk.gray(` Interval: ${config.context.interval} min`));\n if (ctx.saveCount) {\n console.log(chalk.gray(` Saves: ${ctx.saveCount}`));\n }\n if (ctx.lastRun) {\n const ago = Math.round((Date.now() - ctx.lastRun) / 1000 / 60);\n console.log(chalk.gray(` Last save: ${ago} min ago`));\n }\n }\n\n // Linear service\n const lin = status.services.linear;\n console.log(\n ` Linear: ${lin.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`\n );\n if (lin.enabled) {\n console.log(chalk.gray(` Interval: ${config.linear.interval} min`));\n if (config.linear.quietHours) {\n console.log(\n chalk.gray(\n ` Quiet hours: ${config.linear.quietHours.start}:00 - ${config.linear.quietHours.end}:00`\n )\n );\n }\n if (lin.syncCount) {\n console.log(chalk.gray(` Syncs: ${lin.syncCount}`));\n }\n }\n\n // File watch\n const fw = status.services.fileWatch;\n console.log(\n ` FileWatch: ${fw.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`\n );\n\n // Errors\n if (status.errors && status.errors.length > 0) {\n console.log('');\n console.log(chalk.bold('Recent Errors:'));\n status.errors.slice(-3).forEach((err) => {\n console.log(chalk.red(` - ${err.slice(0, 80)}`));\n });\n }\n\n if (!status.running) {\n console.log('');\n console.log(chalk.bold('To start: stackmemory daemon start'));\n }\n });\n\n // Logs command\n cmd\n .command('logs')\n .description('View daemon logs')\n .option('-n, --lines <number>', 'Number of lines to show', '50')\n .option('-f, --follow', 'Follow log output')\n .option('--level <level>', 'Filter by log level')\n .action((options) => {\n const { logFile } = getDaemonPaths();\n\n if (!existsSync(logFile)) {\n console.log(chalk.yellow('No log file found'));\n console.log(\n chalk.gray('Start the daemon first: stackmemory daemon start')\n );\n return;\n }\n\n if (options.follow) {\n const tail = spawn('tail', ['-f', logFile], { stdio: 'inherit' });\n tail.on('error', () => {\n console.log(chalk.red('Could not follow logs'));\n });\n return;\n }\n\n const content = readFileSync(logFile, 'utf8');\n const lines = content.trim().split('\\n');\n const count = parseInt(options.lines, 10);\n let recent = lines.slice(-count);\n\n // Filter by level if specified\n if (options.level) {\n const level = options.level.toUpperCase();\n recent = recent.filter((line) => {\n try {\n const entry = JSON.parse(line);\n return entry.level === level;\n } catch {\n return false;\n }\n });\n }\n\n console.log(chalk.bold(`\\nDaemon logs (${recent.length} lines):\\n`));\n\n for (const line of recent) {\n try {\n const entry = JSON.parse(line);\n const time = entry.timestamp.split('T')[1].split('.')[0];\n const levelColor =\n entry.level === 'ERROR'\n ? chalk.red\n : entry.level === 'WARN'\n ? chalk.yellow\n : entry.level === 'DEBUG'\n ? chalk.gray\n : chalk.white;\n\n console.log(\n `${chalk.gray(time)} ${levelColor(`[${entry.level}]`)} ${chalk.cyan(`[${entry.service}]`)} ${entry.message}`\n );\n } catch {\n console.log(line);\n }\n }\n });\n\n // Config command\n cmd\n .command('config')\n .description('Show or edit daemon configuration')\n .option('--edit', 'Open config in editor')\n .option('--reset', 'Reset to default configuration')\n .option('--set <key=value>', 'Set a config value')\n .action((options) => {\n const { configFile } = getDaemonPaths();\n\n if (options.reset) {\n saveDaemonConfig(DEFAULT_DAEMON_CONFIG);\n console.log(chalk.green('Configuration reset to defaults'));\n return;\n }\n\n if (options.edit) {\n const editor = process.env['EDITOR'] || 'vim';\n spawn(editor, [configFile], { stdio: 'inherit' });\n return;\n }\n\n if (options.set) {\n const [key, value] = options.set.split('=');\n const config = loadDaemonConfig();\n\n // Parse the key path (e.g., \"context.interval\")\n const parts = key.split('.');\n let target: Record<string, unknown> = config as unknown as Record<\n string,\n unknown\n >;\n for (let i = 0; i < parts.length - 1; i++) {\n if (target[parts[i]] && typeof target[parts[i]] === 'object') {\n target = target[parts[i]] as Record<string, unknown>;\n } else {\n console.log(chalk.red(`Invalid config key: ${key}`));\n return;\n }\n }\n\n const lastKey = parts[parts.length - 1];\n const parsed =\n value === 'true'\n ? true\n : value === 'false'\n ? false\n : isNaN(Number(value))\n ? value\n : Number(value);\n target[lastKey] = parsed;\n\n saveDaemonConfig(config);\n console.log(chalk.green(`Set ${key} = ${value}`));\n return;\n }\n\n // Show config\n const config = loadDaemonConfig();\n\n console.log(chalk.bold('\\nDaemon Configuration\\n'));\n console.log(chalk.gray(`File: ${configFile}`));\n console.log('');\n\n console.log(chalk.bold('Context Service:'));\n console.log(` Enabled: ${config.context.enabled}`);\n console.log(` Interval: ${config.context.interval} minutes`);\n\n console.log('');\n console.log(chalk.bold('Linear Service:'));\n console.log(` Enabled: ${config.linear.enabled}`);\n console.log(` Interval: ${config.linear.interval} minutes`);\n if (config.linear.quietHours) {\n console.log(\n ` Quiet hours: ${config.linear.quietHours.start}:00 - ${config.linear.quietHours.end}:00`\n );\n }\n\n console.log('');\n console.log(chalk.bold('File Watch:'));\n console.log(` Enabled: ${config.fileWatch.enabled}`);\n console.log(` Extensions: ${config.fileWatch.extensions.join(', ')}`);\n\n console.log('');\n console.log(chalk.bold('General:'));\n console.log(` Heartbeat: ${config.heartbeatInterval} seconds`);\n console.log(` Log level: ${config.logLevel}`);\n });\n\n // Default action\n cmd.action(() => {\n const status = readDaemonStatus();\n\n console.log(chalk.bold('\\nStackMemory Daemon\\n'));\n console.log(\n `Status: ${status.running ? chalk.green('Running') : chalk.yellow('Stopped')}`\n );\n\n if (!status.running) {\n console.log('');\n console.log(chalk.bold('Quick start:'));\n console.log(' stackmemory daemon start Start background services');\n } else {\n console.log('');\n console.log(chalk.bold('Commands:'));\n console.log(' stackmemory daemon status View detailed status');\n console.log(' stackmemory daemon logs View daemon logs');\n console.log(' stackmemory daemon stop Stop the daemon');\n }\n });\n\n return cmd;\n}\n\n/**\n * Get path to daemon script\n */\nfunction getDaemonScriptPath(): string | null {\n // Check various locations\n const candidates = [\n join(__dirname, '../../daemon/unified-daemon.js'),\n join(process.cwd(), 'dist/daemon/unified-daemon.js'),\n join(\n process.cwd(),\n 'node_modules/@stackmemoryai/stackmemory/dist/daemon/unified-daemon.js'\n ),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return candidates[0]; // Return first candidate as fallback\n}\n\nexport default createDaemonCommand();\n"],
5
+ "mappings": "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",
6
+ "names": ["config"]
7
+ }
@@ -237,11 +237,11 @@ Generated by stackmemory handoff at ${timestamp}
237
237
  handoffPrompt = enhancedGenerator.toMarkdown(enhancedHandoff);
238
238
  console.log(`Estimated tokens: ~${enhancedHandoff.estimatedTokens}`);
239
239
  }
240
- const handoffPath = join(
241
- projectRoot,
242
- ".stackmemory",
243
- "last-handoff.md"
244
- );
240
+ const stackmemoryDir = join(projectRoot, ".stackmemory");
241
+ if (!existsSync(stackmemoryDir)) {
242
+ mkdirSync(stackmemoryDir, { recursive: true });
243
+ }
244
+ const handoffPath = join(stackmemoryDir, "last-handoff.md");
245
245
  writeFileSync(handoffPath, handoffPrompt);
246
246
  let branch = "unknown";
247
247
  try {