@esotech/contextuate 2.0.0 → 2.1.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 (103) hide show
  1. package/README.md +169 -1
  2. package/dist/commands/claude.d.ts +21 -0
  3. package/dist/commands/claude.js +213 -0
  4. package/dist/commands/context.d.ts +1 -0
  5. package/dist/commands/create.d.ts +3 -0
  6. package/dist/commands/index.d.ts +4 -0
  7. package/dist/commands/init.d.ts +7 -0
  8. package/dist/commands/init.js +67 -6
  9. package/dist/commands/install.d.ts +28 -0
  10. package/dist/commands/install.js +100 -11
  11. package/dist/commands/monitor.d.ts +55 -0
  12. package/dist/commands/monitor.js +1007 -0
  13. package/dist/commands/remove.d.ts +3 -0
  14. package/dist/commands/run.d.ts +6 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +113 -1
  17. package/dist/monitor/daemon/circuit-breaker.d.ts +121 -0
  18. package/dist/monitor/daemon/circuit-breaker.js +552 -0
  19. package/dist/monitor/daemon/cli.d.ts +8 -0
  20. package/dist/monitor/daemon/cli.js +82 -0
  21. package/dist/monitor/daemon/index.d.ts +137 -0
  22. package/dist/monitor/daemon/index.js +695 -0
  23. package/dist/monitor/daemon/notifier.d.ts +25 -0
  24. package/dist/monitor/daemon/notifier.js +98 -0
  25. package/dist/monitor/daemon/processor.d.ts +89 -0
  26. package/dist/monitor/daemon/processor.js +455 -0
  27. package/dist/monitor/daemon/state.d.ts +80 -0
  28. package/dist/monitor/daemon/state.js +162 -0
  29. package/dist/monitor/daemon/watcher.d.ts +47 -0
  30. package/dist/monitor/daemon/watcher.js +171 -0
  31. package/dist/monitor/daemon/wrapper-manager.d.ts +106 -0
  32. package/dist/monitor/daemon/wrapper-manager.js +374 -0
  33. package/dist/monitor/hooks/emit-event.js +652 -0
  34. package/dist/monitor/persistence/file-store.d.ts +88 -0
  35. package/dist/monitor/persistence/file-store.js +335 -0
  36. package/dist/monitor/persistence/index.d.ts +7 -0
  37. package/dist/monitor/persistence/index.js +10 -0
  38. package/dist/monitor/server/adapters/redis.d.ts +38 -0
  39. package/dist/monitor/server/adapters/redis.js +213 -0
  40. package/dist/monitor/server/adapters/unix-socket.d.ts +33 -0
  41. package/dist/monitor/server/adapters/unix-socket.js +182 -0
  42. package/dist/monitor/server/broker.d.ts +135 -0
  43. package/dist/monitor/server/broker.js +475 -0
  44. package/dist/monitor/server/cli.d.ts +8 -0
  45. package/dist/monitor/server/cli.js +98 -0
  46. package/dist/monitor/server/fastify.d.ts +16 -0
  47. package/dist/monitor/server/fastify.js +184 -0
  48. package/dist/monitor/server/index.d.ts +36 -0
  49. package/dist/monitor/server/index.js +153 -0
  50. package/dist/monitor/server/websocket.d.ts +80 -0
  51. package/dist/monitor/server/websocket.js +453 -0
  52. package/dist/monitor/ui/assets/index-4IssW9On.js +59 -0
  53. package/dist/monitor/ui/assets/index-vo9hLe5R.css +32 -0
  54. package/dist/monitor/ui/favicon.png +0 -0
  55. package/dist/monitor/ui/index.html +14 -0
  56. package/dist/monitor/ui/logo.png +0 -0
  57. package/dist/monitor/ui/logo.svg +1 -0
  58. package/dist/runtime/driver.d.ts +16 -0
  59. package/dist/runtime/tools.d.ts +10 -0
  60. package/dist/templates/README.md +33 -7
  61. package/dist/templates/agents/aegis.md +4 -0
  62. package/dist/templates/agents/archon.md +13 -22
  63. package/dist/templates/agents/atlas.md +4 -0
  64. package/dist/templates/agents/canvas.md +4 -0
  65. package/dist/templates/agents/chronicle.md +4 -0
  66. package/dist/templates/agents/chronos.md +4 -0
  67. package/dist/templates/agents/cipher.md +4 -0
  68. package/dist/templates/agents/crucible.md +4 -0
  69. package/dist/templates/agents/echo.md +4 -0
  70. package/dist/templates/agents/forge.md +4 -0
  71. package/dist/templates/agents/ledger.md +4 -0
  72. package/dist/templates/agents/meridian.md +4 -0
  73. package/dist/templates/agents/nexus.md +4 -0
  74. package/dist/templates/agents/pythia.md +217 -0
  75. package/dist/templates/agents/scribe.md +4 -0
  76. package/dist/templates/agents/sentinel.md +4 -0
  77. package/dist/templates/agents/{oracle.md → thoth.md} +11 -7
  78. package/dist/templates/agents/unity.md +4 -0
  79. package/dist/templates/agents/vox.md +4 -0
  80. package/dist/templates/agents/weaver.md +4 -0
  81. package/dist/templates/commands/consult.md +138 -0
  82. package/dist/templates/commands/orchestrate.md +173 -0
  83. package/dist/templates/framework-agents/documentation-expert.md +3 -3
  84. package/dist/templates/framework-agents/tools-expert.md +8 -8
  85. package/dist/templates/standards/agent-roles.md +68 -21
  86. package/dist/templates/standards/coding-standards.md +9 -26
  87. package/dist/templates/templates/context.md +17 -2
  88. package/dist/templates/templates/contextuate.md +21 -28
  89. package/dist/templates/tools/{agent-creator.tool.md → agent-creator.md} +3 -3
  90. package/dist/types/monitor.d.ts +660 -0
  91. package/dist/types/monitor.js +75 -0
  92. package/dist/utils/git.d.ts +9 -0
  93. package/dist/utils/tokens.d.ts +10 -0
  94. package/package.json +18 -5
  95. package/dist/templates/version.json +0 -8
  96. /package/dist/templates/templates/standards/{go.standards.md → go.md} +0 -0
  97. /package/dist/templates/templates/standards/{java.standards.md → java.md} +0 -0
  98. /package/dist/templates/templates/standards/{javascript.standards.md → javascript.md} +0 -0
  99. /package/dist/templates/templates/standards/{php.standards.md → php.md} +0 -0
  100. /package/dist/templates/templates/standards/{python.standards.md → python.md} +0 -0
  101. /package/dist/templates/tools/{quickref.tool.md → quickref.md} +0 -0
  102. /package/dist/templates/tools/{spawn.tool.md → spawn.md} +0 -0
  103. /package/dist/templates/tools/{standards-detector.tool.md → standards-detector.md} +0 -0
@@ -0,0 +1,1007 @@
1
+ "use strict";
2
+ /**
3
+ * Monitor Command
4
+ *
5
+ * CLI commands for the Contextuate Monitor feature.
6
+ * - contextuate monitor init: Interactive setup
7
+ * - contextuate monitor [start]: Start the monitor server
8
+ * - contextuate monitor status: Show server status
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.monitorInitCommand = monitorInitCommand;
48
+ exports.monitorStartCommand = monitorStartCommand;
49
+ exports.monitorStopCommand = monitorStopCommand;
50
+ exports.monitorStatusCommand = monitorStatusCommand;
51
+ exports.monitorDaemonStartCommand = monitorDaemonStartCommand;
52
+ exports.monitorDaemonStopCommand = monitorDaemonStopCommand;
53
+ exports.monitorDaemonStatusCommand = monitorDaemonStatusCommand;
54
+ exports.monitorDaemonLogsCommand = monitorDaemonLogsCommand;
55
+ const chalk_1 = __importDefault(require("chalk"));
56
+ const fs_extra_1 = __importDefault(require("fs-extra"));
57
+ const path_1 = __importDefault(require("path"));
58
+ const os_1 = __importDefault(require("os"));
59
+ const inquirer_1 = __importDefault(require("inquirer"));
60
+ const child_process_1 = require("child_process");
61
+ const monitor_1 = require("../types/monitor");
62
+ // Use the centralized paths
63
+ const PATHS = (0, monitor_1.getDefaultMonitorPaths)();
64
+ // For backward compatibility, also define legacy paths
65
+ const LEGACY_CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.contextuate');
66
+ const LEGACY_CONFIG_FILE = path_1.default.join(LEGACY_CONFIG_DIR, 'monitor.config.json');
67
+ const LEGACY_SESSIONS_DIR = path_1.default.join(LEGACY_CONFIG_DIR, 'sessions');
68
+ // Claude settings file (unchanged)
69
+ const CLAUDE_SETTINGS_FILE = path_1.default.join(os_1.default.homedir(), '.claude', 'settings.json');
70
+ /**
71
+ * Migrate from old directory structure to new structure
72
+ *
73
+ * Old structure: ~/.contextuate/{monitor.config.json,sessions/,hooks/}
74
+ * New structure: ~/.contextuate/monitor/{config.json,sessions/,hooks/,raw/,processed/}
75
+ *
76
+ * @returns true if migration was performed, false if nothing to migrate or already migrated
77
+ */
78
+ async function migrateToNewStructure() {
79
+ // Check if old structure exists
80
+ const legacyConfigExists = await fs_extra_1.default.pathExists(LEGACY_CONFIG_FILE);
81
+ const legacySessionsExists = await fs_extra_1.default.pathExists(LEGACY_SESSIONS_DIR);
82
+ const newConfigExists = await fs_extra_1.default.pathExists(PATHS.configFile);
83
+ if (!legacyConfigExists && !legacySessionsExists) {
84
+ return false; // Nothing to migrate
85
+ }
86
+ if (newConfigExists) {
87
+ return false; // Already migrated
88
+ }
89
+ console.log(chalk_1.default.blue('[Migration] Migrating to new directory structure...'));
90
+ try {
91
+ // Create new directories
92
+ await fs_extra_1.default.mkdir(PATHS.baseDir, { recursive: true });
93
+ await fs_extra_1.default.mkdir(PATHS.rawDir, { recursive: true });
94
+ await fs_extra_1.default.mkdir(PATHS.processedDir, { recursive: true });
95
+ // Move config file
96
+ if (legacyConfigExists) {
97
+ await fs_extra_1.default.rename(LEGACY_CONFIG_FILE, PATHS.configFile);
98
+ console.log(chalk_1.default.green('[Migration] Moved config file'));
99
+ }
100
+ // Move sessions directory
101
+ if (legacySessionsExists) {
102
+ await fs_extra_1.default.rename(LEGACY_SESSIONS_DIR, PATHS.sessionsDir);
103
+ console.log(chalk_1.default.green('[Migration] Moved sessions directory'));
104
+ }
105
+ // Move hooks directory if it exists
106
+ const legacyHooksDir = path_1.default.join(LEGACY_CONFIG_DIR, 'hooks');
107
+ const legacyHooksExists = await fs_extra_1.default.pathExists(legacyHooksDir);
108
+ if (legacyHooksExists) {
109
+ await fs_extra_1.default.rename(legacyHooksDir, PATHS.hooksDir);
110
+ console.log(chalk_1.default.green('[Migration] Moved hooks directory'));
111
+ }
112
+ console.log(chalk_1.default.green('[Migration] Complete! New structure at ~/.contextuate/monitor/'));
113
+ console.log('');
114
+ return true;
115
+ }
116
+ catch (error) {
117
+ console.log(chalk_1.default.yellow(`[Migration] Warning: Migration partially failed: ${error.message}`));
118
+ console.log(chalk_1.default.yellow('[Migration] You may need to manually move files to ~/.contextuate/monitor/'));
119
+ return false;
120
+ }
121
+ }
122
+ /**
123
+ * Load monitor configuration
124
+ */
125
+ async function loadConfig() {
126
+ try {
127
+ if (await fs_extra_1.default.pathExists(PATHS.configFile)) {
128
+ const content = await fs_extra_1.default.readFile(PATHS.configFile, 'utf-8');
129
+ return { ...monitor_1.DEFAULT_CONFIG, ...JSON.parse(content) };
130
+ }
131
+ }
132
+ catch (err) {
133
+ // Ignore errors
134
+ }
135
+ return null;
136
+ }
137
+ /**
138
+ * Save monitor configuration
139
+ */
140
+ async function saveConfig(config) {
141
+ await fs_extra_1.default.ensureDir(PATHS.baseDir);
142
+ await fs_extra_1.default.writeFile(PATHS.configFile, JSON.stringify(config, null, 2));
143
+ }
144
+ /**
145
+ * Initialize monitor command
146
+ */
147
+ async function monitorInitCommand(options) {
148
+ console.log(chalk_1.default.blue(''));
149
+ console.log(chalk_1.default.blue('=========================================='));
150
+ console.log(chalk_1.default.blue(' Contextuate Monitor Setup'));
151
+ console.log(chalk_1.default.blue('=========================================='));
152
+ console.log('');
153
+ try {
154
+ // Attempt migration from old structure
155
+ await migrateToNewStructure();
156
+ // Check for existing configuration
157
+ const existingConfig = await loadConfig();
158
+ if (existingConfig) {
159
+ const { overwrite } = await inquirer_1.default.prompt([
160
+ {
161
+ type: 'confirm',
162
+ name: 'overwrite',
163
+ message: 'Configuration already exists. Overwrite?',
164
+ default: false,
165
+ },
166
+ ]);
167
+ if (!overwrite) {
168
+ console.log(chalk_1.default.yellow('[INFO] Setup cancelled.'));
169
+ return;
170
+ }
171
+ }
172
+ // Step 1: Choose mode
173
+ const { mode } = await inquirer_1.default.prompt([
174
+ {
175
+ type: 'list',
176
+ name: 'mode',
177
+ message: 'Select monitor mode:',
178
+ choices: [
179
+ { name: 'Local (Unix socket) - Single machine', value: 'local' },
180
+ { name: 'Redis (pub/sub) - Distributed/multi-machine', value: 'redis' },
181
+ ],
182
+ default: 'local',
183
+ },
184
+ ]);
185
+ const config = { ...monitor_1.DEFAULT_CONFIG, mode: mode };
186
+ // Step 2: Redis configuration (if selected)
187
+ if (mode === 'redis') {
188
+ console.log('');
189
+ console.log(chalk_1.default.blue('[INFO] Configure Redis connection:'));
190
+ const redisAnswers = await inquirer_1.default.prompt([
191
+ {
192
+ type: 'input',
193
+ name: 'host',
194
+ message: 'Redis host:',
195
+ default: 'localhost',
196
+ },
197
+ {
198
+ type: 'number',
199
+ name: 'port',
200
+ message: 'Redis port:',
201
+ default: 6379,
202
+ },
203
+ {
204
+ type: 'password',
205
+ name: 'password',
206
+ message: 'Redis password (leave empty for none):',
207
+ default: '',
208
+ },
209
+ {
210
+ type: 'input',
211
+ name: 'channel',
212
+ message: 'Redis channel:',
213
+ default: 'contextuate:events',
214
+ },
215
+ ]);
216
+ config.redis = {
217
+ host: redisAnswers.host,
218
+ port: redisAnswers.port,
219
+ password: redisAnswers.password || null,
220
+ channel: redisAnswers.channel,
221
+ };
222
+ }
223
+ // Step 3: Server configuration
224
+ console.log('');
225
+ console.log(chalk_1.default.blue('[INFO] Configure server ports:'));
226
+ const serverAnswers = await inquirer_1.default.prompt([
227
+ {
228
+ type: 'input',
229
+ name: 'host',
230
+ message: 'Server host:',
231
+ default: '0.0.0.0',
232
+ },
233
+ {
234
+ type: 'number',
235
+ name: 'port',
236
+ message: 'HTTP port:',
237
+ default: 3847,
238
+ },
239
+ {
240
+ type: 'number',
241
+ name: 'wsPort',
242
+ message: 'WebSocket port:',
243
+ default: 3848,
244
+ },
245
+ ]);
246
+ config.server = {
247
+ host: serverAnswers.host,
248
+ port: serverAnswers.port,
249
+ wsPort: serverAnswers.wsPort,
250
+ };
251
+ // Step 4: Persistence configuration
252
+ console.log('');
253
+ console.log(chalk_1.default.blue('[INFO] Configure data persistence:'));
254
+ const { persistenceType } = await inquirer_1.default.prompt([
255
+ {
256
+ type: 'list',
257
+ name: 'persistenceType',
258
+ message: 'Persistence type:',
259
+ choices: [
260
+ { name: 'File-based (recommended)', value: 'file' },
261
+ { name: 'MySQL (coming soon)', value: 'mysql', disabled: true },
262
+ { name: 'PostgreSQL (coming soon)', value: 'postgresql', disabled: true },
263
+ ],
264
+ default: 'file',
265
+ },
266
+ ]);
267
+ config.persistence = {
268
+ enabled: true,
269
+ type: persistenceType,
270
+ };
271
+ // Step 5: Socket path (local mode only)
272
+ if (mode === 'local') {
273
+ const { socketPath } = await inquirer_1.default.prompt([
274
+ {
275
+ type: 'input',
276
+ name: 'socketPath',
277
+ message: 'Unix socket path:',
278
+ default: '/tmp/contextuate-monitor.sock',
279
+ },
280
+ ]);
281
+ config.socketPath = socketPath;
282
+ }
283
+ // Create new directory structure
284
+ await fs_extra_1.default.ensureDir(PATHS.baseDir);
285
+ await fs_extra_1.default.ensureDir(PATHS.rawDir);
286
+ await fs_extra_1.default.ensureDir(PATHS.processedDir);
287
+ await fs_extra_1.default.ensureDir(PATHS.sessionsDir);
288
+ await fs_extra_1.default.ensureDir(PATHS.hooksDir);
289
+ // Save configuration
290
+ await saveConfig(config);
291
+ console.log('');
292
+ console.log(chalk_1.default.green(`[OK] Configuration saved to ${PATHS.configFile}`));
293
+ // Step 6: Install hook script
294
+ console.log('');
295
+ console.log(chalk_1.default.blue('[INFO] Installing hook script...'));
296
+ // Find the hook script source
297
+ let hookSource = path_1.default.join(__dirname, '../monitor/hooks/emit-event.js');
298
+ if (!await fs_extra_1.default.pathExists(hookSource)) {
299
+ hookSource = path_1.default.join(__dirname, '../../src/monitor/hooks/emit-event.js');
300
+ }
301
+ const hookDest = path_1.default.join(PATHS.hooksDir, 'emit-event.js');
302
+ if (await fs_extra_1.default.pathExists(hookSource)) {
303
+ await fs_extra_1.default.copy(hookSource, hookDest);
304
+ await fs_extra_1.default.chmod(hookDest, 0o755);
305
+ console.log(chalk_1.default.green(`[OK] Hook script installed to ${hookDest}`));
306
+ }
307
+ else {
308
+ console.log(chalk_1.default.yellow(`[WARN] Hook script source not found. You may need to copy it manually.`));
309
+ }
310
+ // Step 7: Determine hook location (from flags or interactive)
311
+ console.log('');
312
+ let hookLocation;
313
+ // Check if flags were provided
314
+ if (options?.global && options?.project) {
315
+ console.log(chalk_1.default.yellow('[WARN] Both --global and --project flags specified. Using --global (default).'));
316
+ hookLocation = 'home';
317
+ }
318
+ else if (options?.global) {
319
+ hookLocation = 'home';
320
+ console.log(chalk_1.default.blue('[INFO] Installing hooks at user level (--global)'));
321
+ }
322
+ else if (options?.project) {
323
+ hookLocation = 'project';
324
+ console.log(chalk_1.default.blue('[INFO] Installing hooks at project level (--project)'));
325
+ }
326
+ else {
327
+ // No flags provided - ask interactively (default to global/home)
328
+ const answer = await inquirer_1.default.prompt([
329
+ {
330
+ type: 'list',
331
+ name: 'hookLocation',
332
+ message: 'Where should hooks be registered?',
333
+ choices: [
334
+ { name: 'User level (~/.claude/settings.json - applies to all projects) - DEFAULT', value: 'home' },
335
+ { name: 'Project level (.claude/settings.json in current directory)', value: 'project' },
336
+ { name: 'Skip - I will configure hooks manually', value: 'skip' },
337
+ ],
338
+ default: 'home',
339
+ },
340
+ ]);
341
+ hookLocation = answer.hookLocation;
342
+ }
343
+ if (hookLocation !== 'skip') {
344
+ const settingsFile = hookLocation === 'project'
345
+ ? path_1.default.join(process.cwd(), '.claude', 'settings.json')
346
+ : CLAUDE_SETTINGS_FILE;
347
+ await updateClaudeHookSettings(hookDest, settingsFile);
348
+ }
349
+ // Done!
350
+ console.log('');
351
+ console.log(chalk_1.default.green('=========================================='));
352
+ console.log(chalk_1.default.green(' Setup Complete!'));
353
+ console.log(chalk_1.default.green('=========================================='));
354
+ console.log('');
355
+ console.log('Next steps:');
356
+ console.log(` 1. Start the monitor: ${chalk_1.default.cyan('contextuate monitor')}`);
357
+ console.log(` 2. Open your browser to: ${chalk_1.default.cyan(`http://localhost:${config.server.port}`)}`);
358
+ console.log(` 3. Start a Claude Code session - events will appear in the dashboard`);
359
+ console.log('');
360
+ }
361
+ catch (error) {
362
+ if (error.name === 'ExitPromptError' || error.message?.includes('User force closed')) {
363
+ console.log('');
364
+ console.log(chalk_1.default.yellow('Setup cancelled.'));
365
+ return;
366
+ }
367
+ throw error;
368
+ }
369
+ }
370
+ /**
371
+ * Check if a hook command path refers to a contextuate emit-event hook
372
+ */
373
+ function isContextuateHook(command) {
374
+ // Normalize the path and check for contextuate emit-event patterns
375
+ const normalized = command.replace(/^~/, os_1.default.homedir());
376
+ return normalized.includes('contextuate') && normalized.includes('emit-event');
377
+ }
378
+ /**
379
+ * Update Claude settings with hook registrations
380
+ * - Detects existing contextuate hooks (any path format)
381
+ * - Updates them to use the canonical path
382
+ * - Removes duplicates
383
+ * - Adds hooks for any missing hook types
384
+ */
385
+ async function updateClaudeHookSettings(hookPath, settingsFile) {
386
+ try {
387
+ const claudeDir = path_1.default.dirname(settingsFile);
388
+ await fs_extra_1.default.ensureDir(claudeDir);
389
+ let settings = {};
390
+ if (await fs_extra_1.default.pathExists(settingsFile)) {
391
+ try {
392
+ const content = await fs_extra_1.default.readFile(settingsFile, 'utf-8');
393
+ settings = JSON.parse(content);
394
+ }
395
+ catch {
396
+ // Start fresh if parsing fails
397
+ }
398
+ }
399
+ // All hook types we want to register
400
+ const hookTypes = [
401
+ 'SessionStart',
402
+ 'SessionEnd',
403
+ 'PreToolUse',
404
+ 'PostToolUse',
405
+ 'Notification',
406
+ 'Stop',
407
+ 'SubagentStart',
408
+ 'SubagentStop',
409
+ 'UserPromptSubmit'
410
+ ];
411
+ // Initialize hooks object if needed
412
+ if (!settings.hooks || typeof settings.hooks !== 'object') {
413
+ settings.hooks = {};
414
+ }
415
+ const hooks = settings.hooks;
416
+ // Process each hook type
417
+ for (const hookType of hookTypes) {
418
+ if (!Array.isArray(hooks[hookType])) {
419
+ hooks[hookType] = [];
420
+ }
421
+ // Filter out any existing contextuate hooks and other duplicates
422
+ const filteredEntries = [];
423
+ let hasContextuateHook = false;
424
+ for (const entry of hooks[hookType]) {
425
+ if (!entry.hooks || !Array.isArray(entry.hooks)) {
426
+ filteredEntries.push(entry);
427
+ continue;
428
+ }
429
+ // Filter hooks within this entry
430
+ const nonContextuateHooks = entry.hooks.filter((h) => {
431
+ if (h.type === 'command' && isContextuateHook(h.command)) {
432
+ hasContextuateHook = true;
433
+ return false; // Remove old contextuate hooks
434
+ }
435
+ return true;
436
+ });
437
+ // Keep entry if it has other hooks
438
+ if (nonContextuateHooks.length > 0) {
439
+ filteredEntries.push({ ...entry, hooks: nonContextuateHooks });
440
+ }
441
+ }
442
+ // Add our canonical hook entry
443
+ filteredEntries.push({
444
+ matcher: '',
445
+ hooks: [{ type: 'command', command: hookPath }]
446
+ });
447
+ hooks[hookType] = filteredEntries;
448
+ if (hasContextuateHook) {
449
+ console.log(chalk_1.default.blue(`[INFO] Updated ${hookType} hook to canonical path`));
450
+ }
451
+ }
452
+ // Save settings
453
+ await fs_extra_1.default.writeFile(settingsFile, JSON.stringify(settings, null, 2));
454
+ console.log(chalk_1.default.green(`[OK] Hooks registered in ${settingsFile}`));
455
+ }
456
+ catch (err) {
457
+ console.log(chalk_1.default.yellow(`[WARN] Could not update Claude settings: ${err}`));
458
+ }
459
+ }
460
+ /**
461
+ * Start monitor server command
462
+ */
463
+ async function monitorStartCommand(options) {
464
+ // Attempt migration from old structure
465
+ await migrateToNewStructure();
466
+ // Load configuration
467
+ let config = await loadConfig();
468
+ if (!config) {
469
+ console.log(chalk_1.default.yellow('[WARN] No configuration found. Running init...'));
470
+ console.log('');
471
+ await monitorInitCommand();
472
+ config = await loadConfig();
473
+ if (!config) {
474
+ console.log(chalk_1.default.red('[ERROR] Setup failed or cancelled.'));
475
+ return;
476
+ }
477
+ }
478
+ // Apply command-line overrides
479
+ if (options.port) {
480
+ config.server.port = options.port;
481
+ }
482
+ if (options.wsPort) {
483
+ config.server.wsPort = options.wsPort;
484
+ }
485
+ // Auto-start daemon if not running
486
+ await ensureDaemonRunning(config);
487
+ // Check if server is already running
488
+ const serverPid = await getServerPid();
489
+ if (serverPid && isProcessRunning(serverPid)) {
490
+ console.log(chalk_1.default.blue(`[Info] Server already running (PID: ${serverPid})`));
491
+ const url = `http://localhost:${config.server.port}`;
492
+ if (!options.noOpen) {
493
+ openBrowser(url);
494
+ }
495
+ console.log(chalk_1.default.green(`[OK] Monitor: ${url}`));
496
+ process.exit(0);
497
+ }
498
+ // Clean up stale PID file if server not running
499
+ if (serverPid) {
500
+ await fs_extra_1.default.remove(PATHS.serverPidFile);
501
+ }
502
+ // Run in foreground mode if requested
503
+ if (options.foreground) {
504
+ await runServerForeground(config, options);
505
+ return;
506
+ }
507
+ // Start server in background
508
+ await startServerBackground(config, options);
509
+ }
510
+ /**
511
+ * Run the server in foreground mode (blocking)
512
+ */
513
+ async function runServerForeground(config, options) {
514
+ console.log(chalk_1.default.blue('[INFO] Starting Contextuate Monitor in foreground...'));
515
+ console.log('');
516
+ try {
517
+ const { createMonitorServer } = await Promise.resolve().then(() => __importStar(require('../monitor/server')));
518
+ const server = await createMonitorServer({
519
+ config,
520
+ dataDir: PATHS.baseDir
521
+ });
522
+ await server.start();
523
+ const url = `http://localhost:${config.server.port}`;
524
+ if (!options.noOpen) {
525
+ openBrowser(url);
526
+ }
527
+ // Handle shutdown signals
528
+ const shutdown = async () => {
529
+ console.log('');
530
+ console.log(chalk_1.default.blue('[INFO] Shutting down...'));
531
+ await server.stop();
532
+ process.exit(0);
533
+ };
534
+ process.on('SIGINT', shutdown);
535
+ process.on('SIGTERM', shutdown);
536
+ console.log(chalk_1.default.green('[OK] Monitor is running. Press Ctrl+C to stop.'));
537
+ }
538
+ catch (err) {
539
+ console.error(chalk_1.default.red(`[ERROR] Failed to start server: ${err.message}`));
540
+ process.exit(1);
541
+ }
542
+ }
543
+ /**
544
+ * Start the server in background mode (non-blocking)
545
+ */
546
+ async function startServerBackground(config, options) {
547
+ console.log(chalk_1.default.blue('[INFO] Starting Contextuate Monitor...'));
548
+ // Find the server CLI entry point
549
+ let serverPath = path_1.default.join(__dirname, '..', 'monitor', 'server', 'cli.js');
550
+ if (!await fs_extra_1.default.pathExists(serverPath)) {
551
+ const alternatives = [
552
+ path_1.default.join(__dirname, '..', '..', 'dist', 'monitor', 'server', 'cli.js'),
553
+ path_1.default.join(__dirname, 'monitor', 'server', 'cli.js'),
554
+ ];
555
+ for (const altPath of alternatives) {
556
+ if (await fs_extra_1.default.pathExists(altPath)) {
557
+ serverPath = altPath;
558
+ break;
559
+ }
560
+ }
561
+ }
562
+ if (!await fs_extra_1.default.pathExists(serverPath)) {
563
+ console.log(chalk_1.default.red(`[Error] Server CLI not found at ${serverPath}`));
564
+ console.log(chalk_1.default.yellow('[Info] Try running: npm run build'));
565
+ return;
566
+ }
567
+ // Build command arguments
568
+ const args = [
569
+ serverPath,
570
+ '--config', PATHS.configFile,
571
+ ];
572
+ if (options.port) {
573
+ args.push('--port', options.port.toString());
574
+ }
575
+ if (options.wsPort) {
576
+ args.push('--ws-port', options.wsPort.toString());
577
+ }
578
+ // Ensure log directory exists and open log file
579
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(PATHS.serverLogFile));
580
+ const logFd = fs_extra_1.default.openSync(PATHS.serverLogFile, 'a');
581
+ const child = (0, child_process_1.spawn)(process.execPath, args, {
582
+ detached: true,
583
+ stdio: ['ignore', logFd, logFd],
584
+ });
585
+ // Close file descriptor in parent
586
+ fs_extra_1.default.closeSync(logFd);
587
+ // Write PID file
588
+ await fs_extra_1.default.writeFile(PATHS.serverPidFile, child.pid.toString());
589
+ child.unref();
590
+ // Wait briefly for server to start
591
+ await new Promise(r => setTimeout(r, 1500));
592
+ // Verify server started successfully
593
+ const url = `http://localhost:${config.server.port}`;
594
+ try {
595
+ const response = await fetch(`${url}/api/status`);
596
+ if (response.ok) {
597
+ console.log(chalk_1.default.green(`[OK] Server started (PID: ${child.pid})`));
598
+ }
599
+ else {
600
+ console.log(chalk_1.default.yellow(`[WARN] Server may not have started properly. Check logs: ${PATHS.serverLogFile}`));
601
+ }
602
+ }
603
+ catch {
604
+ console.log(chalk_1.default.yellow(`[WARN] Could not verify server. Check logs: ${PATHS.serverLogFile}`));
605
+ }
606
+ // Open browser
607
+ if (!options.noOpen) {
608
+ openBrowser(url);
609
+ }
610
+ console.log('');
611
+ console.log(chalk_1.default.green(`Monitor: ${url}`));
612
+ console.log(chalk_1.default.blue(`Logs: ${PATHS.serverLogFile}`));
613
+ console.log('');
614
+ console.log(`To stop: ${chalk_1.default.cyan('contextuate monitor stop')}`);
615
+ // Exit cleanly - background processes are now running independently
616
+ process.exit(0);
617
+ }
618
+ /**
619
+ * Open browser to URL (fire-and-forget, doesn't block)
620
+ */
621
+ function openBrowser(url) {
622
+ const platform = process.platform;
623
+ const command = platform === 'darwin' ? 'open' :
624
+ platform === 'win32' ? 'cmd' : 'xdg-open';
625
+ const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
626
+ try {
627
+ const child = (0, child_process_1.spawn)(command, args, {
628
+ detached: true,
629
+ stdio: 'ignore',
630
+ });
631
+ child.unref();
632
+ }
633
+ catch (err) {
634
+ console.log(chalk_1.default.yellow(`[WARN] Could not open browser. Please open ${url} manually.`));
635
+ }
636
+ }
637
+ /**
638
+ * Get server PID from PID file
639
+ */
640
+ async function getServerPid() {
641
+ try {
642
+ const pidStr = await fs_extra_1.default.readFile(PATHS.serverPidFile, 'utf-8');
643
+ return parseInt(pidStr.trim(), 10);
644
+ }
645
+ catch {
646
+ return null;
647
+ }
648
+ }
649
+ /**
650
+ * Stop monitor server command
651
+ */
652
+ async function monitorStopCommand(options) {
653
+ let stoppedServer = false;
654
+ let stoppedDaemon = false;
655
+ // Stop UI server
656
+ const serverPid = await getServerPid();
657
+ if (serverPid && isProcessRunning(serverPid)) {
658
+ console.log(chalk_1.default.blue(`[INFO] Stopping UI server (PID: ${serverPid})...`));
659
+ try {
660
+ process.kill(serverPid, 'SIGTERM');
661
+ // Wait for process to exit
662
+ let attempts = 0;
663
+ while (isProcessRunning(serverPid) && attempts < 10) {
664
+ await new Promise(r => setTimeout(r, 500));
665
+ attempts++;
666
+ }
667
+ if (isProcessRunning(serverPid)) {
668
+ console.log(chalk_1.default.yellow('[WARN] Server still running, sending SIGKILL'));
669
+ process.kill(serverPid, 'SIGKILL');
670
+ }
671
+ await fs_extra_1.default.remove(PATHS.serverPidFile);
672
+ console.log(chalk_1.default.green('[OK] UI server stopped'));
673
+ stoppedServer = true;
674
+ }
675
+ catch (err) {
676
+ console.error(chalk_1.default.red(`[ERROR] Failed to stop server: ${err.message}`));
677
+ }
678
+ }
679
+ else {
680
+ if (serverPid) {
681
+ // Clean up stale PID file
682
+ await fs_extra_1.default.remove(PATHS.serverPidFile);
683
+ }
684
+ console.log(chalk_1.default.blue('[INFO] UI server not running'));
685
+ }
686
+ // Stop daemon if --all flag is specified
687
+ if (options.all) {
688
+ const daemonPid = await getDaemonPid();
689
+ if (daemonPid && isProcessRunning(daemonPid)) {
690
+ console.log(chalk_1.default.blue(`[INFO] Stopping daemon (PID: ${daemonPid})...`));
691
+ await monitorDaemonStopCommand();
692
+ stoppedDaemon = true;
693
+ }
694
+ else {
695
+ console.log(chalk_1.default.blue('[INFO] Daemon not running'));
696
+ }
697
+ }
698
+ if (!stoppedServer && !stoppedDaemon) {
699
+ console.log('');
700
+ console.log(chalk_1.default.blue('[INFO] Nothing to stop'));
701
+ if (!options.all) {
702
+ console.log(chalk_1.default.blue('[INFO] Use --all to also stop the daemon'));
703
+ }
704
+ }
705
+ }
706
+ /**
707
+ * Show monitor status command
708
+ */
709
+ async function monitorStatusCommand() {
710
+ const config = await loadConfig();
711
+ console.log(chalk_1.default.blue(''));
712
+ console.log(chalk_1.default.blue('=========================================='));
713
+ console.log(chalk_1.default.blue(' Contextuate Monitor Status'));
714
+ console.log(chalk_1.default.blue('=========================================='));
715
+ console.log('');
716
+ if (!config) {
717
+ console.log(chalk_1.default.yellow('Status: Not configured'));
718
+ console.log('');
719
+ console.log(`Run ${chalk_1.default.cyan('contextuate monitor init')} to set up the monitor.`);
720
+ return;
721
+ }
722
+ // Configuration info
723
+ console.log(chalk_1.default.white('Configuration:'));
724
+ console.log(` Mode: ${chalk_1.default.cyan(config.mode)}`);
725
+ console.log(` HTTP Port: ${chalk_1.default.cyan(config.server.port)}`);
726
+ console.log(` WS Port: ${chalk_1.default.cyan(config.server.wsPort)}`);
727
+ if (config.mode === 'redis') {
728
+ console.log(` Redis: ${chalk_1.default.cyan(`${config.redis.host}:${config.redis.port}`)}`);
729
+ }
730
+ else {
731
+ console.log(` Socket: ${chalk_1.default.cyan(config.socketPath)}`);
732
+ }
733
+ console.log(` Persistence: ${chalk_1.default.cyan(config.persistence.type)}`);
734
+ console.log('');
735
+ // Check daemon status
736
+ console.log(chalk_1.default.white('Daemon Status:'));
737
+ const pid = await getDaemonPid();
738
+ if (pid && isProcessRunning(pid)) {
739
+ console.log(` Status: ${chalk_1.default.green('Running')} (PID: ${pid})`);
740
+ }
741
+ else {
742
+ console.log(` Status: ${chalk_1.default.yellow('Not running')}`);
743
+ }
744
+ console.log('');
745
+ // Check if server is running
746
+ console.log(chalk_1.default.white('UI Server Status:'));
747
+ const serverPid = await getServerPid();
748
+ try {
749
+ const response = await fetch(`http://localhost:${config.server.port}/api/status`);
750
+ if (response.ok) {
751
+ const status = await response.json();
752
+ console.log(` Status: ${chalk_1.default.green('Running')}${serverPid ? ` (PID: ${serverPid})` : ''}`);
753
+ console.log(` Sessions: ${chalk_1.default.cyan(status.sessions)} total, ${chalk_1.default.cyan(status.activeSessions)} active`);
754
+ console.log(` Uptime: ${chalk_1.default.cyan(formatUptime(status.uptime))}`);
755
+ console.log('');
756
+ console.log(`Dashboard: ${chalk_1.default.cyan(`http://localhost:${config.server.port}`)}`);
757
+ }
758
+ else {
759
+ console.log(` Status: ${chalk_1.default.red('Error (HTTP ' + response.status + ')')}`);
760
+ }
761
+ }
762
+ catch (err) {
763
+ if (serverPid && isProcessRunning(serverPid)) {
764
+ console.log(` Status: ${chalk_1.default.yellow('Starting...')} (PID: ${serverPid})`);
765
+ }
766
+ else {
767
+ if (serverPid) {
768
+ // Clean up stale PID file
769
+ await fs_extra_1.default.remove(PATHS.serverPidFile);
770
+ }
771
+ console.log(` Status: ${chalk_1.default.yellow('Not running')}`);
772
+ console.log('');
773
+ console.log(`Start with: ${chalk_1.default.cyan('contextuate monitor')}`);
774
+ }
775
+ }
776
+ console.log('');
777
+ }
778
+ /**
779
+ * Format uptime in human-readable form
780
+ */
781
+ function formatUptime(seconds) {
782
+ const hours = Math.floor(seconds / 3600);
783
+ const minutes = Math.floor((seconds % 3600) / 60);
784
+ const secs = Math.floor(seconds % 60);
785
+ const parts = [];
786
+ if (hours > 0)
787
+ parts.push(`${hours}h`);
788
+ if (minutes > 0)
789
+ parts.push(`${minutes}m`);
790
+ parts.push(`${secs}s`);
791
+ return parts.join(' ');
792
+ }
793
+ // =============================================================================
794
+ // Daemon Management Helper Functions
795
+ // =============================================================================
796
+ /**
797
+ * Get daemon PID from PID file
798
+ */
799
+ async function getDaemonPid() {
800
+ try {
801
+ const pidStr = await fs_extra_1.default.readFile(PATHS.daemonPidFile, 'utf-8');
802
+ return parseInt(pidStr.trim(), 10);
803
+ }
804
+ catch {
805
+ return null;
806
+ }
807
+ }
808
+ /**
809
+ * Check if a process is running
810
+ */
811
+ function isProcessRunning(pid) {
812
+ try {
813
+ process.kill(pid, 0); // Signal 0 just checks if process exists
814
+ return true;
815
+ }
816
+ catch {
817
+ return false;
818
+ }
819
+ }
820
+ /**
821
+ * Ensure daemon is running
822
+ * - If not running, start it
823
+ * - Clean up stale PID files if process not running
824
+ */
825
+ async function ensureDaemonRunning(config) {
826
+ const pid = await getDaemonPid();
827
+ const socketPath = '/tmp/contextuate-daemon.sock';
828
+ if (pid && isProcessRunning(pid)) {
829
+ // Daemon is already running
830
+ console.log(chalk_1.default.green(`[OK] Daemon is running (PID: ${pid})`));
831
+ return;
832
+ }
833
+ if (pid) {
834
+ // PID file exists but process not running - clean up stale files
835
+ console.log(chalk_1.default.blue('[INFO] Cleaning up stale daemon files...'));
836
+ await fs_extra_1.default.remove(PATHS.daemonPidFile);
837
+ try {
838
+ await fs_extra_1.default.remove(socketPath);
839
+ }
840
+ catch {
841
+ // Ignore
842
+ }
843
+ }
844
+ // Start the daemon
845
+ console.log(chalk_1.default.blue('[INFO] Starting daemon...'));
846
+ await monitorDaemonStartCommand({ detach: true });
847
+ // Brief wait for daemon to initialize socket
848
+ await new Promise(r => setTimeout(r, 1000));
849
+ console.log('');
850
+ }
851
+ // =============================================================================
852
+ // Daemon Management Commands
853
+ // =============================================================================
854
+ /**
855
+ * Start daemon command
856
+ */
857
+ async function monitorDaemonStartCommand(options) {
858
+ const config = await loadConfig();
859
+ if (!config) {
860
+ console.log(chalk_1.default.red('[Error] Monitor not initialized. Run: contextuate monitor init'));
861
+ return;
862
+ }
863
+ // Check if already running
864
+ const pid = await getDaemonPid();
865
+ if (pid && isProcessRunning(pid)) {
866
+ console.log(chalk_1.default.blue(`[Info] Daemon already running (PID: ${pid})`));
867
+ return;
868
+ }
869
+ if (options.detach) {
870
+ // Start as background process
871
+ // Find the daemon CLI entry point
872
+ let daemonPath = path_1.default.join(__dirname, '..', 'monitor', 'daemon', 'cli.js');
873
+ if (!await fs_extra_1.default.pathExists(daemonPath)) {
874
+ // Try alternative paths
875
+ const alternatives = [
876
+ path_1.default.join(__dirname, '..', '..', 'dist', 'monitor', 'daemon', 'cli.js'),
877
+ path_1.default.join(__dirname, 'monitor', 'daemon', 'cli.js'),
878
+ ];
879
+ for (const altPath of alternatives) {
880
+ if (await fs_extra_1.default.pathExists(altPath)) {
881
+ daemonPath = altPath;
882
+ break;
883
+ }
884
+ }
885
+ }
886
+ if (!await fs_extra_1.default.pathExists(daemonPath)) {
887
+ console.log(chalk_1.default.red(`[Error] Daemon CLI not found at ${daemonPath}`));
888
+ console.log(chalk_1.default.yellow('[Info] Try running: npm run build'));
889
+ return;
890
+ }
891
+ // Ensure log directory exists and open log file for daemon output
892
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(PATHS.daemonLogFile));
893
+ const logFd = fs_extra_1.default.openSync(PATHS.daemonLogFile, 'a');
894
+ const child = (0, child_process_1.spawn)(process.execPath, [
895
+ daemonPath,
896
+ '--config', PATHS.configFile,
897
+ ], {
898
+ detached: true,
899
+ // Use file descriptor directly so daemon doesn't depend on parent's pipes
900
+ stdio: ['ignore', logFd, logFd],
901
+ });
902
+ // Close the file descriptor in the parent - the child has its own copy
903
+ fs_extra_1.default.closeSync(logFd);
904
+ // Write PID file
905
+ await fs_extra_1.default.writeFile(PATHS.daemonPidFile, child.pid.toString());
906
+ child.unref();
907
+ console.log(chalk_1.default.green(`[OK] Daemon started in background (PID: ${child.pid})`));
908
+ console.log(chalk_1.default.blue(`[Info] Logs: ${PATHS.daemonLogFile}`));
909
+ }
910
+ else {
911
+ // Run in foreground
912
+ console.log(chalk_1.default.blue('[Info] Starting daemon in foreground (Ctrl+C to stop)'));
913
+ const { startDaemon } = await Promise.resolve().then(() => __importStar(require('../monitor/daemon/index.js')));
914
+ const daemon = await startDaemon(config);
915
+ // Handle shutdown
916
+ const shutdown = async () => {
917
+ console.log('');
918
+ console.log(chalk_1.default.blue('[Info] Shutting down daemon...'));
919
+ await daemon.stop();
920
+ process.exit(0);
921
+ };
922
+ process.on('SIGINT', shutdown);
923
+ process.on('SIGTERM', shutdown);
924
+ }
925
+ }
926
+ /**
927
+ * Stop daemon command
928
+ */
929
+ async function monitorDaemonStopCommand() {
930
+ const pid = await getDaemonPid();
931
+ if (!pid) {
932
+ console.log(chalk_1.default.blue('[Info] Daemon not running (no PID file)'));
933
+ return;
934
+ }
935
+ if (!isProcessRunning(pid)) {
936
+ console.log(chalk_1.default.blue('[Info] Daemon not running (stale PID file)'));
937
+ await fs_extra_1.default.remove(PATHS.daemonPidFile);
938
+ return;
939
+ }
940
+ try {
941
+ process.kill(pid, 'SIGTERM');
942
+ console.log(chalk_1.default.green(`[OK] Sent shutdown signal to daemon (PID: ${pid})`));
943
+ // Wait for process to exit
944
+ let attempts = 0;
945
+ while (isProcessRunning(pid) && attempts < 10) {
946
+ await new Promise(r => setTimeout(r, 500));
947
+ attempts++;
948
+ }
949
+ if (isProcessRunning(pid)) {
950
+ console.log(chalk_1.default.yellow('[Warning] Daemon still running, sending SIGKILL'));
951
+ process.kill(pid, 'SIGKILL');
952
+ }
953
+ await fs_extra_1.default.remove(PATHS.daemonPidFile);
954
+ console.log(chalk_1.default.green('[OK] Daemon stopped'));
955
+ }
956
+ catch (err) {
957
+ console.error(chalk_1.default.red(`[Error] Failed to stop daemon: ${err.message}`));
958
+ }
959
+ }
960
+ /**
961
+ * Show daemon status command
962
+ */
963
+ async function monitorDaemonStatusCommand() {
964
+ const pid = await getDaemonPid();
965
+ if (!pid) {
966
+ console.log('Daemon: not running (no PID file)');
967
+ return;
968
+ }
969
+ if (isProcessRunning(pid)) {
970
+ console.log(`Daemon: ${chalk_1.default.green('running')} (PID: ${pid})`);
971
+ console.log(`Logs: ${PATHS.daemonLogFile}`);
972
+ }
973
+ else {
974
+ console.log('Daemon: not running (stale PID file)');
975
+ await fs_extra_1.default.remove(PATHS.daemonPidFile);
976
+ }
977
+ }
978
+ /**
979
+ * View daemon logs command
980
+ */
981
+ async function monitorDaemonLogsCommand(options) {
982
+ const lines = options.lines || 50;
983
+ try {
984
+ await fs_extra_1.default.access(PATHS.daemonLogFile);
985
+ }
986
+ catch {
987
+ console.log(chalk_1.default.blue('[Info] No daemon log file found'));
988
+ return;
989
+ }
990
+ if (options.follow) {
991
+ // Use tail -f
992
+ const tail = (0, child_process_1.spawn)('tail', ['-f', '-n', lines.toString(), PATHS.daemonLogFile], {
993
+ stdio: 'inherit',
994
+ });
995
+ process.on('SIGINT', () => {
996
+ tail.kill();
997
+ process.exit(0);
998
+ });
999
+ }
1000
+ else {
1001
+ // Read last N lines
1002
+ const content = await fs_extra_1.default.readFile(PATHS.daemonLogFile, 'utf-8');
1003
+ const allLines = content.split('\n');
1004
+ const lastLines = allLines.slice(-lines).join('\n');
1005
+ console.log(lastLines);
1006
+ }
1007
+ }