@wundr.io/cli 1.0.1 → 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.
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +340 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +614 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +192 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/alignment.d.ts +78 -0
- package/dist/commands/alignment.d.ts.map +1 -0
- package/dist/commands/alignment.js +817 -0
- package/dist/commands/alignment.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +440 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +591 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +119 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +1073 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +53 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +705 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +849 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +538 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +481 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/governance.d.ts +17 -0
- package/dist/commands/governance.d.ts.map +1 -0
- package/dist/commands/governance.js +703 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/guardian.d.ts +20 -0
- package/dist/commands/guardian.d.ts.map +1 -0
- package/dist/commands/guardian.js +597 -0
- package/dist/commands/guardian.js.map +1 -0
- package/dist/commands/init.d.ts +59 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +650 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +650 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/rag.d.ts +7 -0
- package/dist/commands/rag.d.ts.map +1 -0
- package/dist/commands/rag.js +748 -0
- package/dist/commands/rag.js.map +1 -0
- package/dist/commands/session.d.ts +41 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +441 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +397 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/vp.d.ts +7 -0
- package/dist/commands/vp.d.ts.map +1 -0
- package/dist/commands/vp.js +571 -0
- package/dist/commands/vp.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +613 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/worktree.d.ts +63 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +774 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +686 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +732 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +624 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +417 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +744 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/backup-rollback-manager.d.ts +72 -0
- package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
- package/dist/utils/backup-rollback-manager.js +289 -0
- package/dist/utils/backup-rollback-manager.js.map +1 -0
- package/dist/utils/claude-config-installer.d.ts +94 -0
- package/dist/utils/claude-config-installer.d.ts.map +1 -0
- package/dist/utils/claude-config-installer.js +628 -0
- package/dist/utils/claude-config-installer.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +23 -4
- package/src/ai/ai-service.ts +22 -19
- package/src/ai/claude-client.ts +20 -16
- package/src/ai/conversation-manager.ts +37 -30
- package/src/cli.ts +42 -13
- package/src/commands/ai.ts +59 -57
- package/src/commands/alignment.ts +1212 -0
- package/src/commands/analyze-optimized.ts +70 -62
- package/src/commands/analyze.ts +22 -20
- package/src/commands/batch.ts +41 -38
- package/src/commands/chat.ts +37 -34
- package/src/commands/claude-init.ts +38 -30
- package/src/commands/claude-setup.ts +692 -97
- package/src/commands/computer-setup-commands.ts +45 -39
- package/src/commands/computer-setup.ts +474 -4
- package/src/commands/create-command.ts +7 -7
- package/src/commands/create.ts +36 -33
- package/src/commands/dashboard.ts +33 -28
- package/src/commands/govern.ts +34 -29
- package/src/commands/governance.ts +1005 -0
- package/src/commands/guardian.ts +887 -0
- package/src/commands/init.ts +112 -22
- package/src/commands/performance-optimizer.ts +48 -42
- package/src/commands/plugins.ts +35 -32
- package/src/commands/project-update.ts +1053 -0
- package/src/commands/rag.ts +904 -0
- package/src/commands/session.ts +631 -0
- package/src/commands/setup.ts +35 -31
- package/src/commands/test-init.ts +6 -5
- package/src/commands/test.ts +7 -6
- package/src/commands/vp.ts +762 -0
- package/src/commands/watch.ts +44 -33
- package/src/commands/worktree.ts +1057 -0
- package/src/context/context-manager.ts +15 -12
- package/src/context/session-manager.ts +51 -40
- package/src/index.ts +7 -6
- package/src/interactive/interactive-mode.ts +25 -18
- package/src/lib/conflict-resolution.ts +28 -0
- package/src/lib/merge-strategy.ts +28 -0
- package/src/lib/safety-mechanisms.ts +47 -0
- package/src/lib/state-detection.ts +28 -0
- package/src/nlp/command-mapper.ts +35 -30
- package/src/nlp/command-parser.ts +20 -17
- package/src/nlp/intent-classifier.ts +7 -7
- package/src/nlp/intent-parser.ts +61 -49
- package/src/plugins/plugin-manager.ts +27 -23
- package/src/types/index.ts +1 -1
- package/src/types/modules.d.ts +1 -0
- package/src/utils/backup-rollback-manager.ts +13 -11
- package/src/utils/claude-config-installer.ts +18 -16
- package/src/utils/config-manager.ts +12 -9
- package/src/utils/error-handler.ts +5 -3
- 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
|
+
}
|