@wundr.io/cli 1.0.0 → 1.0.3

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 (224) hide show
  1. package/README.md +696 -280
  2. package/dist/ai/ai-service.d.ts +2 -2
  3. package/dist/ai/ai-service.d.ts.map +1 -1
  4. package/dist/ai/ai-service.js +1 -1
  5. package/dist/ai/ai-service.js.map +1 -1
  6. package/dist/ai/claude-client.d.ts.map +1 -1
  7. package/dist/ai/claude-client.js +2 -1
  8. package/dist/ai/claude-client.js.map +1 -1
  9. package/dist/ai/conversation-manager.d.ts +2 -2
  10. package/dist/ai/conversation-manager.d.ts.map +1 -1
  11. package/dist/ai/conversation-manager.js +5 -3
  12. package/dist/ai/conversation-manager.js.map +1 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +32 -13
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/ai.d.ts +3 -3
  17. package/dist/commands/ai.d.ts.map +1 -1
  18. package/dist/commands/ai.js +2 -2
  19. package/dist/commands/ai.js.map +1 -1
  20. package/dist/commands/alignment.d.ts +78 -0
  21. package/dist/commands/alignment.d.ts.map +1 -0
  22. package/dist/commands/alignment.js +817 -0
  23. package/dist/commands/alignment.js.map +1 -0
  24. package/dist/commands/analyze-optimized.d.ts.map +1 -1
  25. package/dist/commands/analyze-optimized.js +9 -6
  26. package/dist/commands/analyze-optimized.js.map +1 -1
  27. package/dist/commands/analyze.d.ts +3 -3
  28. package/dist/commands/analyze.d.ts.map +1 -1
  29. package/dist/commands/analyze.js +2 -2
  30. package/dist/commands/analyze.js.map +1 -1
  31. package/dist/commands/batch.d.ts +3 -3
  32. package/dist/commands/batch.d.ts.map +1 -1
  33. package/dist/commands/batch.js +7 -7
  34. package/dist/commands/batch.js.map +1 -1
  35. package/dist/commands/chat.d.ts +3 -3
  36. package/dist/commands/chat.d.ts.map +1 -1
  37. package/dist/commands/chat.js +3 -3
  38. package/dist/commands/chat.js.map +1 -1
  39. package/dist/commands/claude-init.d.ts +1 -1
  40. package/dist/commands/claude-init.d.ts.map +1 -1
  41. package/dist/commands/claude-init.js +11 -7
  42. package/dist/commands/claude-init.js.map +1 -1
  43. package/dist/commands/claude-setup.d.ts +88 -1
  44. package/dist/commands/claude-setup.d.ts.map +1 -1
  45. package/dist/commands/claude-setup.js +549 -46
  46. package/dist/commands/claude-setup.js.map +1 -1
  47. package/dist/commands/computer-setup-commands.d.ts +17 -3
  48. package/dist/commands/computer-setup-commands.d.ts.map +1 -1
  49. package/dist/commands/computer-setup-commands.js +145 -3
  50. package/dist/commands/computer-setup-commands.js.map +1 -1
  51. package/dist/commands/computer-setup.d.ts.map +1 -1
  52. package/dist/commands/computer-setup.js +372 -4
  53. package/dist/commands/computer-setup.js.map +1 -1
  54. package/dist/commands/create-command.d.ts.map +1 -1
  55. package/dist/commands/create-command.js +3 -3
  56. package/dist/commands/create-command.js.map +1 -1
  57. package/dist/commands/create.d.ts +3 -3
  58. package/dist/commands/create.d.ts.map +1 -1
  59. package/dist/commands/create.js +3 -3
  60. package/dist/commands/create.js.map +1 -1
  61. package/dist/commands/dashboard.d.ts +3 -3
  62. package/dist/commands/dashboard.d.ts.map +1 -1
  63. package/dist/commands/dashboard.js +4 -3
  64. package/dist/commands/dashboard.js.map +1 -1
  65. package/dist/commands/govern.d.ts +3 -3
  66. package/dist/commands/govern.d.ts.map +1 -1
  67. package/dist/commands/govern.js +4 -3
  68. package/dist/commands/govern.js.map +1 -1
  69. package/dist/commands/governance.d.ts +17 -0
  70. package/dist/commands/governance.d.ts.map +1 -0
  71. package/dist/commands/governance.js +703 -0
  72. package/dist/commands/governance.js.map +1 -0
  73. package/dist/commands/guardian.d.ts +20 -0
  74. package/dist/commands/guardian.d.ts.map +1 -0
  75. package/dist/commands/guardian.js +597 -0
  76. package/dist/commands/guardian.js.map +1 -0
  77. package/dist/commands/init.d.ts +7 -3
  78. package/dist/commands/init.d.ts.map +1 -1
  79. package/dist/commands/init.js +71 -5
  80. package/dist/commands/init.js.map +1 -1
  81. package/dist/commands/performance-optimizer.d.ts +2 -2
  82. package/dist/commands/performance-optimizer.d.ts.map +1 -1
  83. package/dist/commands/performance-optimizer.js +8 -7
  84. package/dist/commands/performance-optimizer.js.map +1 -1
  85. package/dist/commands/plugins.d.ts +3 -3
  86. package/dist/commands/plugins.d.ts.map +1 -1
  87. package/dist/commands/plugins.js +2 -2
  88. package/dist/commands/plugins.js.map +1 -1
  89. package/dist/commands/rag.d.ts +7 -0
  90. package/dist/commands/rag.d.ts.map +1 -0
  91. package/dist/commands/rag.js +748 -0
  92. package/dist/commands/rag.js.map +1 -0
  93. package/dist/commands/session.d.ts +41 -0
  94. package/dist/commands/session.d.ts.map +1 -0
  95. package/dist/commands/session.js +441 -0
  96. package/dist/commands/session.js.map +1 -0
  97. package/dist/commands/setup.d.ts +3 -3
  98. package/dist/commands/setup.d.ts.map +1 -1
  99. package/dist/commands/setup.js +1 -3
  100. package/dist/commands/setup.js.map +1 -1
  101. package/dist/commands/test-init.d.ts.map +1 -1
  102. package/dist/commands/test-init.js +2 -2
  103. package/dist/commands/test-init.js.map +1 -1
  104. package/dist/commands/test.d.ts.map +1 -1
  105. package/dist/commands/test.js +2 -2
  106. package/dist/commands/test.js.map +1 -1
  107. package/dist/commands/vp.d.ts +7 -0
  108. package/dist/commands/vp.d.ts.map +1 -0
  109. package/dist/commands/vp.js +571 -0
  110. package/dist/commands/vp.js.map +1 -0
  111. package/dist/commands/watch.d.ts +3 -3
  112. package/dist/commands/watch.d.ts.map +1 -1
  113. package/dist/commands/watch.js +10 -7
  114. package/dist/commands/watch.js.map +1 -1
  115. package/dist/commands/worktree.d.ts +63 -0
  116. package/dist/commands/worktree.d.ts.map +1 -0
  117. package/dist/commands/worktree.js +774 -0
  118. package/dist/commands/worktree.js.map +1 -0
  119. package/dist/context/context-manager.d.ts +1 -1
  120. package/dist/context/context-manager.d.ts.map +1 -1
  121. package/dist/context/context-manager.js +1 -1
  122. package/dist/context/context-manager.js.map +1 -1
  123. package/dist/context/session-manager.d.ts +2 -2
  124. package/dist/context/session-manager.d.ts.map +1 -1
  125. package/dist/context/session-manager.js +9 -5
  126. package/dist/context/session-manager.js.map +1 -1
  127. package/dist/index.d.ts.map +1 -1
  128. package/dist/index.js +1 -1
  129. package/dist/index.js.map +1 -1
  130. package/dist/interactive/interactive-mode.d.ts +2 -2
  131. package/dist/interactive/interactive-mode.d.ts.map +1 -1
  132. package/dist/interactive/interactive-mode.js +6 -4
  133. package/dist/interactive/interactive-mode.js.map +1 -1
  134. package/dist/nlp/command-mapper.d.ts +1 -1
  135. package/dist/nlp/command-mapper.d.ts.map +1 -1
  136. package/dist/nlp/command-mapper.js +3 -2
  137. package/dist/nlp/command-mapper.js.map +1 -1
  138. package/dist/nlp/command-parser.d.ts +1 -1
  139. package/dist/nlp/command-parser.d.ts.map +1 -1
  140. package/dist/nlp/command-parser.js +2 -1
  141. package/dist/nlp/command-parser.js.map +1 -1
  142. package/dist/nlp/intent-parser.d.ts +1 -1
  143. package/dist/nlp/intent-parser.d.ts.map +1 -1
  144. package/dist/nlp/intent-parser.js +14 -9
  145. package/dist/nlp/intent-parser.js.map +1 -1
  146. package/dist/plugins/plugin-manager.d.ts +2 -2
  147. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  148. package/dist/plugins/plugin-manager.js +3 -3
  149. package/dist/plugins/plugin-manager.js.map +1 -1
  150. package/dist/types/index.d.ts +1 -1
  151. package/dist/types/index.d.ts.map +1 -1
  152. package/dist/utils/backup-rollback-manager.d.ts +72 -0
  153. package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
  154. package/dist/utils/backup-rollback-manager.js +289 -0
  155. package/dist/utils/backup-rollback-manager.js.map +1 -0
  156. package/dist/utils/claude-config-installer.d.ts +94 -0
  157. package/dist/utils/claude-config-installer.d.ts.map +1 -0
  158. package/dist/utils/claude-config-installer.js +628 -0
  159. package/dist/utils/claude-config-installer.js.map +1 -0
  160. package/dist/utils/config-manager.d.ts +1 -1
  161. package/dist/utils/config-manager.d.ts.map +1 -1
  162. package/dist/utils/config-manager.js +5 -5
  163. package/dist/utils/config-manager.js.map +1 -1
  164. package/dist/utils/error-handler.d.ts +1 -1
  165. package/dist/utils/error-handler.d.ts.map +1 -1
  166. package/dist/utils/error-handler.js.map +1 -1
  167. package/dist/utils/logger.d.ts +1 -1
  168. package/dist/utils/logger.d.ts.map +1 -1
  169. package/dist/utils/logger.js +22 -11
  170. package/dist/utils/logger.js.map +1 -1
  171. package/package.json +26 -7
  172. package/src/ai/ai-service.ts +22 -19
  173. package/src/ai/claude-client.ts +20 -16
  174. package/src/ai/conversation-manager.ts +37 -30
  175. package/src/cli.ts +42 -13
  176. package/src/commands/ai.ts +59 -57
  177. package/src/commands/alignment.ts +1212 -0
  178. package/src/commands/analyze-optimized.ts +70 -62
  179. package/src/commands/analyze.ts +22 -20
  180. package/src/commands/batch.ts +41 -38
  181. package/src/commands/chat.ts +37 -34
  182. package/src/commands/claude-init.ts +38 -30
  183. package/src/commands/claude-setup.ts +692 -97
  184. package/src/commands/computer-setup-commands.ts +203 -37
  185. package/src/commands/computer-setup.ts +474 -4
  186. package/src/commands/create-command.ts +7 -7
  187. package/src/commands/create.ts +36 -33
  188. package/src/commands/dashboard.ts +33 -28
  189. package/src/commands/govern.ts +34 -29
  190. package/src/commands/governance.ts +1005 -0
  191. package/src/commands/guardian.ts +887 -0
  192. package/src/commands/init.ts +112 -22
  193. package/src/commands/performance-optimizer.ts +48 -42
  194. package/src/commands/plugins.ts +35 -32
  195. package/src/commands/project-update.ts +1053 -0
  196. package/src/commands/rag.ts +904 -0
  197. package/src/commands/session.ts +631 -0
  198. package/src/commands/setup.ts +35 -31
  199. package/src/commands/test-init.ts +6 -5
  200. package/src/commands/test.ts +7 -6
  201. package/src/commands/vp.ts +762 -0
  202. package/src/commands/watch.ts +44 -33
  203. package/src/commands/worktree.ts +1057 -0
  204. package/src/context/context-manager.ts +15 -12
  205. package/src/context/session-manager.ts +51 -40
  206. package/src/index.ts +7 -6
  207. package/src/interactive/interactive-mode.ts +25 -18
  208. package/src/lib/conflict-resolution.ts +28 -0
  209. package/src/lib/merge-strategy.ts +28 -0
  210. package/src/lib/safety-mechanisms.ts +47 -0
  211. package/src/lib/state-detection.ts +28 -0
  212. package/src/nlp/command-mapper.ts +35 -30
  213. package/src/nlp/command-parser.ts +20 -17
  214. package/src/nlp/intent-classifier.ts +7 -7
  215. package/src/nlp/intent-parser.ts +61 -49
  216. package/src/plugins/plugin-manager.ts +27 -23
  217. package/src/tests/computer-setup-integration.test.ts +439 -0
  218. package/src/types/index.ts +1 -1
  219. package/src/types/modules.d.ts +1 -0
  220. package/src/utils/backup-rollback-manager.ts +363 -0
  221. package/src/utils/claude-config-installer.ts +734 -0
  222. package/src/utils/config-manager.ts +12 -9
  223. package/src/utils/error-handler.ts +5 -3
  224. package/src/utils/logger.ts +35 -12
@@ -0,0 +1,762 @@
1
+ /**
2
+ * VP Daemon CLI Commands
3
+ * Manages the Virtual Principal (VP) Daemon for agent orchestration
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ import * as os from 'os';
9
+ import * as path from 'path';
10
+
11
+ import chalk from 'chalk';
12
+ import { Command } from 'commander';
13
+ import ora from 'ora';
14
+ import YAML from 'yaml';
15
+
16
+ // Constants
17
+ const VP_CONFIG_DIR = path.join(os.homedir(), '.wundr', 'vp-daemon');
18
+ const VP_CONFIG_FILE = path.join(VP_CONFIG_DIR, 'config.yaml');
19
+ const VP_PID_FILE = path.join(VP_CONFIG_DIR, 'daemon.pid');
20
+ const VP_LOG_FILE = path.join(VP_CONFIG_DIR, 'daemon.log');
21
+
22
+ // Types
23
+ interface VPConfig {
24
+ daemon: {
25
+ port: number;
26
+ host: string;
27
+ name: string;
28
+ maxSessions: number;
29
+ heartbeatInterval: number;
30
+ shutdownTimeout: number;
31
+ };
32
+ identity: {
33
+ name: string;
34
+ email: string;
35
+ slackHandle: string;
36
+ };
37
+ subsystems: {
38
+ triage: {
39
+ memoryBankPath: string;
40
+ enableRAG: boolean;
41
+ };
42
+ intervention: {
43
+ enabled: boolean;
44
+ autoRollbackOnCritical: boolean;
45
+ };
46
+ telemetry: {
47
+ enabled: boolean;
48
+ flushInterval: number;
49
+ };
50
+ };
51
+ safety: {
52
+ autoApprovePatterns: string[];
53
+ alwaysRejectPatterns: string[];
54
+ escalationPatterns: string[];
55
+ };
56
+ budget: {
57
+ dailyLimit: number;
58
+ monthlyLimit: number;
59
+ warningThreshold: number;
60
+ criticalThreshold: number;
61
+ };
62
+ }
63
+
64
+ interface DaemonStatus {
65
+ running: boolean;
66
+ pid?: number;
67
+ uptime?: string;
68
+ port?: number;
69
+ host?: string;
70
+ sessionCount?: number;
71
+ queueDepth?: number;
72
+ health?: 'healthy' | 'degraded' | 'unhealthy';
73
+ subsystems?: Record<string, string>;
74
+ }
75
+
76
+ // Utility functions
77
+ function getDefaultConfig(): VPConfig {
78
+ return {
79
+ daemon: {
80
+ port: 8787,
81
+ host: '127.0.0.1',
82
+ name: 'vp-daemon',
83
+ maxSessions: 100,
84
+ heartbeatInterval: 30000,
85
+ shutdownTimeout: 10000,
86
+ },
87
+ identity: {
88
+ name: 'Virtual Principal',
89
+ email: 'vp@wundr.local',
90
+ slackHandle: '@virtual-principal',
91
+ },
92
+ subsystems: {
93
+ triage: {
94
+ memoryBankPath: path.join(VP_CONFIG_DIR, 'memory-bank'),
95
+ enableRAG: false,
96
+ },
97
+ intervention: {
98
+ enabled: true,
99
+ autoRollbackOnCritical: false,
100
+ },
101
+ telemetry: {
102
+ enabled: true,
103
+ flushInterval: 10000,
104
+ },
105
+ },
106
+ safety: {
107
+ autoApprovePatterns: ['read|cat|ls|grep|find', 'npm test|yarn test|jest'],
108
+ alwaysRejectPatterns: ['rm\\s+-rf\\s+/', 'git\\s+push.*--force'],
109
+ escalationPatterns: ['deploy.*prod', 'password|secret|token|api.?key'],
110
+ },
111
+ budget: {
112
+ dailyLimit: 1000000,
113
+ monthlyLimit: 20000000,
114
+ warningThreshold: 0.8,
115
+ criticalThreshold: 0.95,
116
+ },
117
+ };
118
+ }
119
+
120
+ async function loadConfig(): Promise<VPConfig> {
121
+ try {
122
+ if (existsSync(VP_CONFIG_FILE)) {
123
+ const content = await fs.readFile(VP_CONFIG_FILE, 'utf-8');
124
+ const parsed = YAML.parse(content) as Partial<VPConfig>;
125
+ return { ...getDefaultConfig(), ...parsed };
126
+ }
127
+ } catch (error) {
128
+ // Fall through to default
129
+ }
130
+ return getDefaultConfig();
131
+ }
132
+
133
+ async function saveConfig(config: VPConfig): Promise<void> {
134
+ await fs.mkdir(VP_CONFIG_DIR, { recursive: true });
135
+ await fs.writeFile(VP_CONFIG_FILE, YAML.stringify(config), 'utf-8');
136
+ }
137
+
138
+ async function ensureConfigDir(): Promise<void> {
139
+ if (!existsSync(VP_CONFIG_DIR)) {
140
+ await fs.mkdir(VP_CONFIG_DIR, { recursive: true });
141
+ }
142
+ }
143
+
144
+ async function getDaemonStatus(): Promise<DaemonStatus> {
145
+ const status: DaemonStatus = { running: false };
146
+
147
+ try {
148
+ if (existsSync(VP_PID_FILE)) {
149
+ const pidContent = await fs.readFile(VP_PID_FILE, 'utf-8');
150
+ const pid = parseInt(pidContent.trim(), 10);
151
+
152
+ // Check if process is running
153
+ try {
154
+ process.kill(pid, 0);
155
+ status.running = true;
156
+ status.pid = pid;
157
+
158
+ // Try to read status from socket or API
159
+ const config = await loadConfig();
160
+ status.port = config.daemon.port;
161
+ status.host = config.daemon.host;
162
+
163
+ // Read additional status info if available
164
+ const statusFile = path.join(VP_CONFIG_DIR, 'status.json');
165
+ if (existsSync(statusFile)) {
166
+ const statusContent = await fs.readFile(statusFile, 'utf-8');
167
+ const statusData = JSON.parse(statusContent);
168
+ status.uptime = formatUptime(statusData.uptime);
169
+ status.sessionCount = statusData.sessionCount ?? 0;
170
+ status.queueDepth = statusData.queueDepth ?? 0;
171
+ status.health = statusData.health ?? 'unknown';
172
+ status.subsystems = statusData.subsystems ?? {};
173
+ }
174
+ } catch {
175
+ // Process not running, clean up stale PID file
176
+ await fs.unlink(VP_PID_FILE).catch(() => {});
177
+ }
178
+ }
179
+ } catch (error) {
180
+ // Ignore errors
181
+ }
182
+
183
+ return status;
184
+ }
185
+
186
+ function formatUptime(ms: number): string {
187
+ const seconds = Math.floor(ms / 1000);
188
+ const minutes = Math.floor(seconds / 60);
189
+ const hours = Math.floor(minutes / 60);
190
+ const days = Math.floor(hours / 24);
191
+
192
+ if (days > 0) {
193
+ return `${days}d ${hours % 24}h ${minutes % 60}m`;
194
+ }
195
+ if (hours > 0) {
196
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
197
+ }
198
+ if (minutes > 0) {
199
+ return `${minutes}m ${seconds % 60}s`;
200
+ }
201
+ return `${seconds}s`;
202
+ }
203
+
204
+ // Create VP command
205
+ export function createVPCommand(): Command {
206
+ const command = new Command('vp')
207
+ .description(
208
+ 'Manage the VP (Virtual Principal) Daemon for agent orchestration'
209
+ )
210
+ .addHelpText(
211
+ 'after',
212
+ chalk.gray(`
213
+ Examples:
214
+ ${chalk.green('wundr vp start')} Start the VP Daemon
215
+ ${chalk.green('wundr vp start --port 9000')} Start on custom port
216
+ ${chalk.green('wundr vp status')} Check daemon status
217
+ ${chalk.green('wundr vp stop')} Stop the daemon gracefully
218
+ ${chalk.green('wundr vp config show')} View current configuration
219
+ ${chalk.green('wundr vp config set daemon.port=9000')} Update configuration
220
+ `)
221
+ );
222
+
223
+ // Start command
224
+ command
225
+ .command('start')
226
+ .description('Start the VP Daemon')
227
+ .option('-p, --port <number>', 'Port to listen on')
228
+ .option('-c, --config <path>', 'Path to configuration file')
229
+ .option('-v, --verbose', 'Enable verbose logging')
230
+ .option('--detach', 'Run daemon in background (detached mode)')
231
+ .action(async options => {
232
+ await startDaemon(options);
233
+ });
234
+
235
+ // Status command (default)
236
+ command
237
+ .command('status', { isDefault: true })
238
+ .description('Check VP Daemon status')
239
+ .option('--json', 'Output as JSON')
240
+ .action(async options => {
241
+ await showStatus(options);
242
+ });
243
+
244
+ // Stop command
245
+ command
246
+ .command('stop')
247
+ .description('Stop the VP Daemon gracefully')
248
+ .option('-f, --force', 'Force immediate termination')
249
+ .option('-t, --timeout <ms>', 'Shutdown timeout in milliseconds', '10000')
250
+ .action(async options => {
251
+ await stopDaemon(options);
252
+ });
253
+
254
+ // Config command group
255
+ const configCmd = command
256
+ .command('config')
257
+ .description('View or edit VP configuration');
258
+
259
+ configCmd
260
+ .command('show')
261
+ .description('Display current configuration')
262
+ .option('--json', 'Output as JSON instead of YAML')
263
+ .action(async options => {
264
+ await showConfig(options);
265
+ });
266
+
267
+ configCmd
268
+ .command('set <key=value>')
269
+ .description('Set a configuration value (e.g., daemon.port=9000)')
270
+ .action(async keyValue => {
271
+ await setConfig(keyValue);
272
+ });
273
+
274
+ configCmd
275
+ .command('reset')
276
+ .description('Reset configuration to defaults')
277
+ .option('--force', 'Skip confirmation')
278
+ .action(async options => {
279
+ await resetConfig(options);
280
+ });
281
+
282
+ // Logs command
283
+ command
284
+ .command('logs')
285
+ .description('View VP Daemon logs')
286
+ .option('-f, --follow', 'Follow log output')
287
+ .option('-n, --lines <number>', 'Number of lines to show', '50')
288
+ .action(async options => {
289
+ await viewLogs(options);
290
+ });
291
+
292
+ return command;
293
+ }
294
+
295
+ // Command implementations
296
+ async function startDaemon(options: {
297
+ port?: string;
298
+ config?: string;
299
+ verbose?: boolean;
300
+ detach?: boolean;
301
+ }): Promise<void> {
302
+ const spinner = ora('Starting VP Daemon...').start();
303
+
304
+ try {
305
+ await ensureConfigDir();
306
+
307
+ // Check if already running
308
+ const currentStatus = await getDaemonStatus();
309
+ if (currentStatus.running) {
310
+ spinner.fail(
311
+ `VP Daemon is already running (PID: ${currentStatus.pid}, port: ${currentStatus.port})`
312
+ );
313
+ return;
314
+ }
315
+
316
+ // Load configuration
317
+ let config: VPConfig;
318
+ if (options.config && existsSync(options.config)) {
319
+ const content = await fs.readFile(options.config, 'utf-8');
320
+ config = { ...getDefaultConfig(), ...YAML.parse(content) };
321
+ spinner.text = `Loading config from ${options.config}...`;
322
+ } else {
323
+ config = await loadConfig();
324
+ }
325
+
326
+ // Apply CLI overrides
327
+ if (options.port) {
328
+ config.daemon.port = parseInt(options.port, 10);
329
+ }
330
+
331
+ // Save current config for daemon use
332
+ await saveConfig(config);
333
+
334
+ spinner.text = 'Initializing VP Daemon subsystems...';
335
+
336
+ // Dynamically import VPDaemon at runtime to avoid TypeScript rootDir issues
337
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
+ let daemon: any;
339
+ try {
340
+ // Use require for CommonJS compatibility or dynamic import for ESM
341
+ const daemonModulePath = require
342
+ .resolve('@wundr/vp-daemon')
343
+ .replace(/\.js$/, '');
344
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
345
+ const daemonModule = require(daemonModulePath);
346
+ const VPDaemon = daemonModule.VPDaemon || daemonModule.default?.VPDaemon;
347
+
348
+ if (!VPDaemon) {
349
+ throw new Error('VPDaemon class not found in module');
350
+ }
351
+
352
+ daemon = new VPDaemon({
353
+ name: config.daemon.name,
354
+ port: config.daemon.port,
355
+ host: config.daemon.host,
356
+ maxSessions: config.daemon.maxSessions,
357
+ heartbeatInterval: config.daemon.heartbeatInterval,
358
+ shutdownTimeout: config.daemon.shutdownTimeout,
359
+ verbose: options.verbose ?? false,
360
+ });
361
+ } catch (importError) {
362
+ // Fallback: try to spawn the daemon as a subprocess
363
+ spinner.fail('Failed to load VP Daemon module');
364
+ console.error(chalk.red('\nThe VP Daemon module could not be loaded.'));
365
+ console.error(chalk.gray('Options to resolve:'));
366
+ console.error(chalk.white(' 1. Install: npm install @wundr/vp-daemon'));
367
+ console.error(
368
+ chalk.white(
369
+ ' 2. Or build from source: cd scripts/vp-daemon && npm run build'
370
+ )
371
+ );
372
+ console.error(
373
+ chalk.gray(
374
+ `\nError: ${importError instanceof Error ? importError.message : String(importError)}`
375
+ )
376
+ );
377
+ return;
378
+ }
379
+
380
+ // Write PID file
381
+ await fs.writeFile(VP_PID_FILE, String(process.pid));
382
+
383
+ // Set up status updates
384
+ const updateStatus = () => {
385
+ const status = daemon.getStatus();
386
+ const statusData = {
387
+ uptime: status.uptime,
388
+ sessionCount: status.metrics?.activeSessions ?? 0,
389
+ queueDepth: 0,
390
+ health: status.status,
391
+ subsystems: Object.fromEntries(
392
+ Object.entries(status.subsystems ?? {}).map(
393
+ ([k, v]: [string, unknown]) => [
394
+ k,
395
+ (v as { status?: string })?.status ?? 'unknown',
396
+ ]
397
+ )
398
+ ),
399
+ };
400
+ fs.writeFile(
401
+ path.join(VP_CONFIG_DIR, 'status.json'),
402
+ JSON.stringify(statusData, null, 2)
403
+ ).catch(() => {});
404
+ };
405
+
406
+ daemon.on('healthCheck', updateStatus);
407
+
408
+ // Set up cleanup handlers
409
+ const cleanup = async () => {
410
+ await fs.unlink(VP_PID_FILE).catch(() => {});
411
+ await fs.unlink(path.join(VP_CONFIG_DIR, 'status.json')).catch(() => {});
412
+ };
413
+
414
+ daemon.on('stopped', cleanup);
415
+
416
+ // Start daemon
417
+ await daemon.start();
418
+
419
+ spinner.succeed(
420
+ `VP Daemon started successfully on ${config.daemon.host}:${config.daemon.port}`
421
+ );
422
+
423
+ console.log(chalk.gray('\nDaemon Information:'));
424
+ console.log(chalk.white(` PID: ${process.pid}`));
425
+ console.log(chalk.white(` Port: ${config.daemon.port}`));
426
+ console.log(chalk.white(` Host: ${config.daemon.host}`));
427
+ console.log(chalk.white(` Config: ${VP_CONFIG_FILE}`));
428
+ console.log(chalk.white(` Logs: ${VP_LOG_FILE}`));
429
+
430
+ if (options.verbose) {
431
+ console.log(chalk.gray('\nVerbose mode enabled - showing detailed logs'));
432
+ }
433
+
434
+ console.log(chalk.green('\nPress Ctrl+C to stop the daemon.\n'));
435
+
436
+ // Keep process running
437
+ if (!options.detach) {
438
+ await new Promise<void>(resolve => {
439
+ daemon.on('stopped', resolve);
440
+ });
441
+ }
442
+ } catch (error) {
443
+ spinner.fail('Failed to start VP Daemon');
444
+ console.error(
445
+ chalk.red(error instanceof Error ? error.message : String(error))
446
+ );
447
+ }
448
+ }
449
+
450
+ async function showStatus(options: { json?: boolean }): Promise<void> {
451
+ const spinner = ora('Checking VP Daemon status...').start();
452
+
453
+ try {
454
+ const status = await getDaemonStatus();
455
+ const config = await loadConfig();
456
+
457
+ spinner.stop();
458
+
459
+ if (options.json) {
460
+ console.log(
461
+ JSON.stringify(
462
+ {
463
+ ...status,
464
+ config: {
465
+ port: config.daemon.port,
466
+ host: config.daemon.host,
467
+ maxSessions: config.daemon.maxSessions,
468
+ },
469
+ },
470
+ null,
471
+ 2
472
+ )
473
+ );
474
+ return;
475
+ }
476
+
477
+ console.log(chalk.cyan('\nVP Daemon Status\n'));
478
+ console.log(chalk.gray('='.repeat(50)));
479
+
480
+ if (status.running) {
481
+ console.log(chalk.green('Status: RUNNING'));
482
+ console.log(chalk.white(`PID: ${status.pid}`));
483
+ console.log(chalk.white(`Host: ${status.host}:${status.port}`));
484
+
485
+ if (status.uptime) {
486
+ console.log(chalk.white(`Uptime: ${status.uptime}`));
487
+ }
488
+
489
+ if (status.sessionCount !== undefined) {
490
+ console.log(chalk.white(`Sessions: ${status.sessionCount}`));
491
+ }
492
+
493
+ if (status.queueDepth !== undefined) {
494
+ console.log(chalk.white(`Queue Depth: ${status.queueDepth}`));
495
+ }
496
+
497
+ if (status.health) {
498
+ const healthColor =
499
+ status.health === 'healthy'
500
+ ? chalk.green
501
+ : status.health === 'degraded'
502
+ ? chalk.yellow
503
+ : chalk.red;
504
+ console.log(
505
+ healthColor(`Health: ${status.health.toUpperCase()}`)
506
+ );
507
+ }
508
+
509
+ if (status.subsystems && Object.keys(status.subsystems).length > 0) {
510
+ console.log(chalk.gray('\nSubsystems:'));
511
+ for (const [name, state] of Object.entries(status.subsystems)) {
512
+ const stateColor =
513
+ state === 'running'
514
+ ? chalk.green
515
+ : state === 'error'
516
+ ? chalk.red
517
+ : chalk.yellow;
518
+ console.log(` ${chalk.white(name.padEnd(15))} ${stateColor(state)}`);
519
+ }
520
+ }
521
+ } else {
522
+ console.log(chalk.yellow('Status: STOPPED'));
523
+ console.log(chalk.gray('\nDaemon is not running.'));
524
+ console.log(chalk.gray('Start it with: wundr vp start'));
525
+ }
526
+
527
+ console.log(chalk.gray('\n' + '='.repeat(50)));
528
+ console.log(chalk.gray(`Config: ${VP_CONFIG_FILE}`));
529
+ console.log('');
530
+ } catch (error) {
531
+ spinner.fail('Failed to get daemon status');
532
+ console.error(
533
+ chalk.red(error instanceof Error ? error.message : String(error))
534
+ );
535
+ }
536
+ }
537
+
538
+ async function stopDaemon(options: {
539
+ force?: boolean;
540
+ timeout?: string;
541
+ }): Promise<void> {
542
+ const spinner = ora('Stopping VP Daemon...').start();
543
+
544
+ try {
545
+ const status = await getDaemonStatus();
546
+
547
+ if (!status.running || !status.pid) {
548
+ spinner.info('VP Daemon is not running');
549
+ return;
550
+ }
551
+
552
+ const pid = status.pid;
553
+ const timeout = parseInt(options.timeout ?? '10000', 10);
554
+
555
+ if (options.force) {
556
+ spinner.text = 'Force stopping daemon...';
557
+ process.kill(pid, 'SIGKILL');
558
+ await fs.unlink(VP_PID_FILE).catch(() => {});
559
+ await fs.unlink(path.join(VP_CONFIG_DIR, 'status.json')).catch(() => {});
560
+ spinner.succeed('VP Daemon force stopped');
561
+ return;
562
+ }
563
+
564
+ // Send SIGTERM for graceful shutdown
565
+ spinner.text = `Sending shutdown signal (timeout: ${timeout}ms)...`;
566
+ process.kill(pid, 'SIGTERM');
567
+
568
+ // Wait for process to exit
569
+ const startTime = Date.now();
570
+ while (Date.now() - startTime < timeout) {
571
+ try {
572
+ process.kill(pid, 0);
573
+ await new Promise(resolve => setTimeout(resolve, 500));
574
+ } catch {
575
+ // Process exited
576
+ await fs.unlink(VP_PID_FILE).catch(() => {});
577
+ await fs
578
+ .unlink(path.join(VP_CONFIG_DIR, 'status.json'))
579
+ .catch(() => {});
580
+ spinner.succeed('VP Daemon stopped gracefully');
581
+ return;
582
+ }
583
+ }
584
+
585
+ // Timeout reached, force kill
586
+ spinner.text = 'Graceful shutdown timed out, forcing stop...';
587
+ process.kill(pid, 'SIGKILL');
588
+ await fs.unlink(VP_PID_FILE).catch(() => {});
589
+ await fs.unlink(path.join(VP_CONFIG_DIR, 'status.json')).catch(() => {});
590
+ spinner.warn('VP Daemon stopped (forced after timeout)');
591
+ } catch (error) {
592
+ spinner.fail('Failed to stop VP Daemon');
593
+ console.error(
594
+ chalk.red(error instanceof Error ? error.message : String(error))
595
+ );
596
+ }
597
+ }
598
+
599
+ async function showConfig(options: { json?: boolean }): Promise<void> {
600
+ try {
601
+ const config = await loadConfig();
602
+
603
+ console.log(chalk.cyan('\nVP Daemon Configuration\n'));
604
+ console.log(chalk.gray(`File: ${VP_CONFIG_FILE}`));
605
+ console.log(chalk.gray('='.repeat(50) + '\n'));
606
+
607
+ if (options.json) {
608
+ console.log(JSON.stringify(config, null, 2));
609
+ } else {
610
+ console.log(YAML.stringify(config));
611
+ }
612
+ } catch (error) {
613
+ console.error(
614
+ chalk.red(error instanceof Error ? error.message : String(error))
615
+ );
616
+ }
617
+ }
618
+
619
+ async function setConfig(keyValue: string): Promise<void> {
620
+ try {
621
+ const [keyPath, ...valueParts] = keyValue.split('=');
622
+ const valueStr = valueParts.join('=');
623
+
624
+ if (!keyPath || valueStr === undefined) {
625
+ console.error(
626
+ chalk.red('Invalid format. Use: wundr vp config set <key>=<value>')
627
+ );
628
+ console.error(
629
+ chalk.gray('Example: wundr vp config set daemon.port=9000')
630
+ );
631
+ return;
632
+ }
633
+
634
+ const config = await loadConfig();
635
+ const keys = keyPath.split('.');
636
+
637
+ // Navigate to parent object
638
+ let obj: Record<string, unknown> = config as unknown as Record<
639
+ string,
640
+ unknown
641
+ >;
642
+ for (let i = 0; i < keys.length - 1; i++) {
643
+ const key = keys[i];
644
+ if (key && typeof obj[key] === 'object' && obj[key] !== null) {
645
+ obj = obj[key] as Record<string, unknown>;
646
+ } else {
647
+ console.error(chalk.red(`Invalid key path: ${keyPath}`));
648
+ return;
649
+ }
650
+ }
651
+
652
+ const lastKey = keys[keys.length - 1];
653
+ if (!lastKey) {
654
+ console.error(chalk.red('Invalid key path'));
655
+ return;
656
+ }
657
+
658
+ // Parse and set value
659
+ let value: unknown;
660
+ try {
661
+ value = JSON.parse(valueStr);
662
+ } catch {
663
+ value = valueStr;
664
+ }
665
+
666
+ const oldValue = obj[lastKey];
667
+ obj[lastKey] = value;
668
+
669
+ await saveConfig(config);
670
+
671
+ console.log(chalk.green('Configuration updated:'));
672
+ console.log(
673
+ chalk.white(
674
+ ` ${keyPath}: ${JSON.stringify(oldValue)} -> ${JSON.stringify(value)}`
675
+ )
676
+ );
677
+ console.log(chalk.gray('\nRestart the daemon for changes to take effect.'));
678
+ } catch (error) {
679
+ console.error(
680
+ chalk.red(error instanceof Error ? error.message : String(error))
681
+ );
682
+ }
683
+ }
684
+
685
+ async function resetConfig(options: { force?: boolean }): Promise<void> {
686
+ try {
687
+ if (!options.force) {
688
+ // Dynamic import for inquirer
689
+ const inquirer = await import('inquirer');
690
+ const answers = await inquirer.default.prompt([
691
+ {
692
+ type: 'confirm',
693
+ name: 'confirm',
694
+ message: 'Reset VP Daemon configuration to defaults?',
695
+ default: false,
696
+ },
697
+ ]);
698
+
699
+ if (!answers.confirm) {
700
+ console.log(chalk.yellow('Cancelled.'));
701
+ return;
702
+ }
703
+ }
704
+
705
+ const config = getDefaultConfig();
706
+ await saveConfig(config);
707
+
708
+ console.log(chalk.green('Configuration reset to defaults.'));
709
+ console.log(chalk.gray(`Saved to: ${VP_CONFIG_FILE}`));
710
+ } catch (error) {
711
+ console.error(
712
+ chalk.red(error instanceof Error ? error.message : String(error))
713
+ );
714
+ }
715
+ }
716
+
717
+ async function viewLogs(options: {
718
+ follow?: boolean;
719
+ lines?: string;
720
+ }): Promise<void> {
721
+ const lines = parseInt(options.lines ?? '50', 10);
722
+
723
+ if (!existsSync(VP_LOG_FILE)) {
724
+ console.log(chalk.yellow('No log file found.'));
725
+ console.log(chalk.gray(`Expected location: ${VP_LOG_FILE}`));
726
+ console.log(
727
+ chalk.gray('Start the daemon with --verbose to enable logging.')
728
+ );
729
+ return;
730
+ }
731
+
732
+ try {
733
+ if (options.follow) {
734
+ console.log(chalk.cyan(`Following logs from ${VP_LOG_FILE}...`));
735
+ console.log(chalk.gray('Press Ctrl+C to stop.\n'));
736
+
737
+ const { spawn } = await import('child_process');
738
+ const tail = spawn('tail', ['-f', '-n', String(lines), VP_LOG_FILE], {
739
+ stdio: 'inherit',
740
+ });
741
+
742
+ await new Promise<void>(resolve => {
743
+ process.on('SIGINT', () => {
744
+ tail.kill();
745
+ resolve();
746
+ });
747
+ tail.on('close', () => resolve());
748
+ });
749
+ } else {
750
+ const content = await fs.readFile(VP_LOG_FILE, 'utf-8');
751
+ const logLines = content.split('\n');
752
+ const lastLines = logLines.slice(-lines).join('\n');
753
+
754
+ console.log(chalk.cyan(`Last ${lines} lines from ${VP_LOG_FILE}:\n`));
755
+ console.log(lastLines);
756
+ }
757
+ } catch (error) {
758
+ console.error(
759
+ chalk.red(error instanceof Error ? error.message : String(error))
760
+ );
761
+ }
762
+ }