@yeaft/webchat-agent 0.0.233 → 0.0.235
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/connection/buffer.js +87 -0
- package/connection/heartbeat.js +47 -0
- package/connection/index.js +89 -0
- package/connection/message-router.js +271 -0
- package/connection/upgrade-worker-template.js +103 -0
- package/connection/upgrade.js +294 -0
- package/connection.js +14 -777
- package/crew/control.js +364 -0
- package/crew/human-interaction.js +115 -0
- package/crew/persistence.js +287 -0
- package/crew/role-management.js +131 -0
- package/crew/role-output.js +315 -0
- package/crew/role-query.js +309 -0
- package/crew/routing.js +194 -0
- package/crew/session.js +474 -0
- package/crew/shared-dir.js +116 -0
- package/crew/task-files.js +370 -0
- package/crew/ui-messages.js +246 -0
- package/crew/worktree.js +130 -0
- package/package.json +6 -2
- package/service/config.js +133 -0
- package/service/index.js +99 -0
- package/service/linux.js +111 -0
- package/service/macos.js +137 -0
- package/service/windows.js +181 -0
- package/service.js +23 -624
- package/workbench/file-ops.js +436 -0
- package/workbench/file-search.js +66 -0
- package/workbench/git-ops.js +313 -0
- package/workbench/transfer.js +99 -0
- package/workbench/utils.js +41 -0
- package/workbench.js +15 -938
package/service.js
CHANGED
|
@@ -1,627 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-platform service management for yeaft-agent
|
|
3
|
-
* Supports: Linux (systemd), macOS (launchd), Windows (
|
|
3
|
+
* Supports: Linux (systemd), macOS (launchd), Windows (pm2)
|
|
4
|
+
*
|
|
5
|
+
* Re-export entry point — actual implementation lives in service/ submodules:
|
|
6
|
+
* service/config.js — shared configuration and utility functions
|
|
7
|
+
* service/linux.js — Linux (systemd) implementation
|
|
8
|
+
* service/macos.js — macOS (launchd) implementation
|
|
9
|
+
* service/windows.js — Windows (pm2) implementation
|
|
10
|
+
* service/index.js — platform dispatcher
|
|
4
11
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const candidates = [join(__dirname, '.env'), join(process.cwd(), '.env')];
|
|
21
|
-
for (const envPath of candidates) {
|
|
22
|
-
if (!existsSync(envPath)) continue;
|
|
23
|
-
try {
|
|
24
|
-
const content = readFileSync(envPath, 'utf-8');
|
|
25
|
-
for (const line of content.split('\n')) {
|
|
26
|
-
const match = line.match(/^\s*([^#][^=]*)\s*=\s*(.*)$/);
|
|
27
|
-
if (match) {
|
|
28
|
-
const key = match[1].trim();
|
|
29
|
-
let value = match[2].trim();
|
|
30
|
-
value = value.replace(/^["']|["']$/g, '');
|
|
31
|
-
// Don't override existing env vars
|
|
32
|
-
if (!process.env[key]) {
|
|
33
|
-
process.env[key] = value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return; // loaded successfully, stop
|
|
38
|
-
} catch {
|
|
39
|
-
// continue to next candidate
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Standard config/log directory per platform
|
|
45
|
-
export function getConfigDir() {
|
|
46
|
-
if (platform() === 'win32') {
|
|
47
|
-
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), SERVICE_NAME);
|
|
48
|
-
}
|
|
49
|
-
return join(homedir(), '.config', SERVICE_NAME);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function getLogDir() {
|
|
53
|
-
return join(getConfigDir(), 'logs');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getConfigPath() {
|
|
57
|
-
return join(getConfigDir(), 'config.json');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Save agent configuration to standard location */
|
|
61
|
-
export function saveServiceConfig(config) {
|
|
62
|
-
const dir = getConfigDir();
|
|
63
|
-
mkdirSync(dir, { recursive: true });
|
|
64
|
-
mkdirSync(getLogDir(), { recursive: true });
|
|
65
|
-
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Load agent configuration from standard location */
|
|
69
|
-
export function loadServiceConfig() {
|
|
70
|
-
const configPath = getConfigPath();
|
|
71
|
-
if (!existsSync(configPath)) return null;
|
|
72
|
-
try {
|
|
73
|
-
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Resolve the full path to the node binary */
|
|
80
|
-
function getNodePath() {
|
|
81
|
-
return process.execPath;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Resolve the full path to cli.js */
|
|
85
|
-
function getCliPath() {
|
|
86
|
-
return join(__dirname, 'cli.js');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Parse --server/--name/--secret/--work-dir from args, merge with existing config
|
|
91
|
-
*/
|
|
92
|
-
export function parseServiceArgs(args) {
|
|
93
|
-
// Load .env if available (for dev / source-based usage)
|
|
94
|
-
loadDotenv();
|
|
95
|
-
|
|
96
|
-
const existing = loadServiceConfig() || {};
|
|
97
|
-
const config = {
|
|
98
|
-
serverUrl: existing.serverUrl || '',
|
|
99
|
-
agentName: existing.agentName || '',
|
|
100
|
-
agentSecret: existing.agentSecret || '',
|
|
101
|
-
workDir: existing.workDir || '',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Environment variables override saved config
|
|
105
|
-
if (process.env.SERVER_URL) config.serverUrl = process.env.SERVER_URL;
|
|
106
|
-
if (process.env.AGENT_NAME) config.agentName = process.env.AGENT_NAME;
|
|
107
|
-
if (process.env.AGENT_SECRET) config.agentSecret = process.env.AGENT_SECRET;
|
|
108
|
-
if (process.env.WORK_DIR) config.workDir = process.env.WORK_DIR;
|
|
109
|
-
|
|
110
|
-
// CLI args override everything
|
|
111
|
-
for (let i = 0; i < args.length; i++) {
|
|
112
|
-
const arg = args[i];
|
|
113
|
-
const next = args[i + 1];
|
|
114
|
-
switch (arg) {
|
|
115
|
-
case '--server': if (next) { config.serverUrl = next; i++; } break;
|
|
116
|
-
case '--name': if (next) { config.agentName = next; i++; } break;
|
|
117
|
-
case '--secret': if (next) { config.agentSecret = next; i++; } break;
|
|
118
|
-
case '--work-dir': if (next) { config.workDir = next; i++; } break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return config;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function validateConfig(config) {
|
|
126
|
-
if (!config.serverUrl) {
|
|
127
|
-
console.error('Error: --server <url> is required');
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
if (!config.agentSecret) {
|
|
131
|
-
console.error('Error: --secret <secret> is required');
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ─── Linux (systemd) ─────────────────────────────────────────
|
|
137
|
-
|
|
138
|
-
function getSystemdServicePath() {
|
|
139
|
-
const dir = join(homedir(), '.config', 'systemd', 'user');
|
|
140
|
-
mkdirSync(dir, { recursive: true });
|
|
141
|
-
return join(dir, `${SERVICE_NAME}.service`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function generateSystemdUnit(config) {
|
|
145
|
-
const nodePath = getNodePath();
|
|
146
|
-
const cliPath = getCliPath();
|
|
147
|
-
const envLines = [];
|
|
148
|
-
if (config.serverUrl) envLines.push(`Environment=SERVER_URL=${config.serverUrl}`);
|
|
149
|
-
if (config.agentName) envLines.push(`Environment=AGENT_NAME=${config.agentName}`);
|
|
150
|
-
if (config.agentSecret) envLines.push(`Environment=AGENT_SECRET=${config.agentSecret}`);
|
|
151
|
-
if (config.workDir) envLines.push(`Environment=WORK_DIR=${config.workDir}`);
|
|
152
|
-
|
|
153
|
-
// Include node's bin dir in PATH for claude CLI access
|
|
154
|
-
const nodeBinDir = dirname(nodePath);
|
|
155
|
-
|
|
156
|
-
return `[Unit]
|
|
157
|
-
Description=Yeaft WebChat Agent
|
|
158
|
-
After=network-online.target
|
|
159
|
-
Wants=network-online.target
|
|
160
|
-
|
|
161
|
-
[Service]
|
|
162
|
-
Type=simple
|
|
163
|
-
ExecStart=${nodePath} ${cliPath}
|
|
164
|
-
WorkingDirectory=${config.workDir || homedir()}
|
|
165
|
-
Restart=on-failure
|
|
166
|
-
RestartSec=10
|
|
167
|
-
KillMode=process
|
|
168
|
-
${envLines.join('\n')}
|
|
169
|
-
Environment=PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
|
|
170
|
-
|
|
171
|
-
StandardOutput=append:${getLogDir()}/out.log
|
|
172
|
-
StandardError=append:${getLogDir()}/error.log
|
|
173
|
-
|
|
174
|
-
[Install]
|
|
175
|
-
WantedBy=default.target
|
|
176
|
-
`;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function linuxInstall(config) {
|
|
180
|
-
const servicePath = getSystemdServicePath();
|
|
181
|
-
writeFileSync(servicePath, generateSystemdUnit(config));
|
|
182
|
-
execSync('systemctl --user daemon-reload');
|
|
183
|
-
execSync(`systemctl --user enable ${SERVICE_NAME}`);
|
|
184
|
-
execSync(`systemctl --user start ${SERVICE_NAME}`);
|
|
185
|
-
console.log(`Service installed and started.`);
|
|
186
|
-
console.log(`\nManage with:`);
|
|
187
|
-
console.log(` yeaft-agent status`);
|
|
188
|
-
console.log(` yeaft-agent logs`);
|
|
189
|
-
console.log(` yeaft-agent restart`);
|
|
190
|
-
console.log(` yeaft-agent uninstall`);
|
|
191
|
-
console.log(`\nTo run when not logged in:`);
|
|
192
|
-
console.log(` sudo loginctl enable-linger $(whoami)`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function linuxUninstall() {
|
|
196
|
-
try { execSync(`systemctl --user stop ${SERVICE_NAME} 2>/dev/null`); } catch {}
|
|
197
|
-
try { execSync(`systemctl --user disable ${SERVICE_NAME} 2>/dev/null`); } catch {}
|
|
198
|
-
const servicePath = getSystemdServicePath();
|
|
199
|
-
if (existsSync(servicePath)) unlinkSync(servicePath);
|
|
200
|
-
try { execSync('systemctl --user daemon-reload'); } catch {}
|
|
201
|
-
console.log('Service uninstalled.');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function linuxStart() {
|
|
205
|
-
execSync(`systemctl --user start ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
206
|
-
console.log('Service started.');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function linuxStop() {
|
|
210
|
-
execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
211
|
-
console.log('Service stopped.');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function linuxRestart() {
|
|
215
|
-
execSync(`systemctl --user restart ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
216
|
-
console.log('Service restarted.');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function linuxStatus() {
|
|
220
|
-
try {
|
|
221
|
-
execSync(`systemctl --user status ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
222
|
-
} catch {
|
|
223
|
-
// systemctl status returns non-zero when service is stopped
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function linuxLogs() {
|
|
228
|
-
try {
|
|
229
|
-
execSync(`journalctl --user -u ${SERVICE_NAME} -f --no-pager -n 100`, { stdio: 'inherit' });
|
|
230
|
-
} catch {
|
|
231
|
-
// Fallback to log files
|
|
232
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
233
|
-
if (existsSync(logFile)) {
|
|
234
|
-
execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
|
|
235
|
-
} else {
|
|
236
|
-
console.log('No logs found.');
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ─── macOS (launchd) ─────────────────────────────────────────
|
|
242
|
-
|
|
243
|
-
function getLaunchdPlistPath() {
|
|
244
|
-
const dir = join(homedir(), 'Library', 'LaunchAgents');
|
|
245
|
-
mkdirSync(dir, { recursive: true });
|
|
246
|
-
return join(dir, 'com.yeaft.agent.plist');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function generateLaunchdPlist(config) {
|
|
250
|
-
const nodePath = getNodePath();
|
|
251
|
-
const cliPath = getCliPath();
|
|
252
|
-
const logDir = getLogDir();
|
|
253
|
-
|
|
254
|
-
const envDict = [];
|
|
255
|
-
if (config.serverUrl) envDict.push(` <key>SERVER_URL</key>\n <string>${config.serverUrl}</string>`);
|
|
256
|
-
if (config.agentName) envDict.push(` <key>AGENT_NAME</key>\n <string>${config.agentName}</string>`);
|
|
257
|
-
if (config.agentSecret) envDict.push(` <key>AGENT_SECRET</key>\n <string>${config.agentSecret}</string>`);
|
|
258
|
-
if (config.workDir) envDict.push(` <key>WORK_DIR</key>\n <string>${config.workDir}</string>`);
|
|
259
|
-
|
|
260
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
261
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
262
|
-
<plist version="1.0">
|
|
263
|
-
<dict>
|
|
264
|
-
<key>Label</key>
|
|
265
|
-
<string>com.yeaft.agent</string>
|
|
266
|
-
<key>ProgramArguments</key>
|
|
267
|
-
<array>
|
|
268
|
-
<string>${nodePath}</string>
|
|
269
|
-
<string>${cliPath}</string>
|
|
270
|
-
</array>
|
|
271
|
-
<key>WorkingDirectory</key>
|
|
272
|
-
<string>${config.workDir || homedir()}</string>
|
|
273
|
-
<key>EnvironmentVariables</key>
|
|
274
|
-
<dict>
|
|
275
|
-
${envDict.join('\n')}
|
|
276
|
-
</dict>
|
|
277
|
-
<key>RunAtLoad</key>
|
|
278
|
-
<true/>
|
|
279
|
-
<key>KeepAlive</key>
|
|
280
|
-
<dict>
|
|
281
|
-
<key>SuccessfulExit</key>
|
|
282
|
-
<false/>
|
|
283
|
-
</dict>
|
|
284
|
-
<key>ThrottleInterval</key>
|
|
285
|
-
<integer>10</integer>
|
|
286
|
-
<key>StandardOutPath</key>
|
|
287
|
-
<string>${logDir}/out.log</string>
|
|
288
|
-
<key>StandardErrorPath</key>
|
|
289
|
-
<string>${logDir}/error.log</string>
|
|
290
|
-
</dict>
|
|
291
|
-
</plist>
|
|
292
|
-
`;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function macInstall(config) {
|
|
296
|
-
const plistPath = getLaunchdPlistPath();
|
|
297
|
-
// Unload first if exists
|
|
298
|
-
if (existsSync(plistPath)) {
|
|
299
|
-
try { execSync(`launchctl unload ${plistPath} 2>/dev/null`); } catch {}
|
|
300
|
-
}
|
|
301
|
-
writeFileSync(plistPath, generateLaunchdPlist(config));
|
|
302
|
-
execSync(`launchctl load ${plistPath}`);
|
|
303
|
-
console.log('Service installed and started.');
|
|
304
|
-
console.log(`\nManage with:`);
|
|
305
|
-
console.log(` yeaft-agent status`);
|
|
306
|
-
console.log(` yeaft-agent logs`);
|
|
307
|
-
console.log(` yeaft-agent restart`);
|
|
308
|
-
console.log(` yeaft-agent uninstall`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function macUninstall() {
|
|
312
|
-
const plistPath = getLaunchdPlistPath();
|
|
313
|
-
if (existsSync(plistPath)) {
|
|
314
|
-
try { execSync(`launchctl unload ${plistPath}`); } catch {}
|
|
315
|
-
unlinkSync(plistPath);
|
|
316
|
-
}
|
|
317
|
-
console.log('Service uninstalled.');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function macStart() {
|
|
321
|
-
const plistPath = getLaunchdPlistPath();
|
|
322
|
-
if (!existsSync(plistPath)) {
|
|
323
|
-
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
324
|
-
process.exit(1);
|
|
325
|
-
}
|
|
326
|
-
execSync(`launchctl load ${plistPath}`);
|
|
327
|
-
console.log('Service started.');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function macStop() {
|
|
331
|
-
const plistPath = getLaunchdPlistPath();
|
|
332
|
-
if (existsSync(plistPath)) {
|
|
333
|
-
execSync(`launchctl unload ${plistPath}`);
|
|
334
|
-
}
|
|
335
|
-
console.log('Service stopped.');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function macRestart() {
|
|
339
|
-
macStop();
|
|
340
|
-
macStart();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function macStatus() {
|
|
344
|
-
try {
|
|
345
|
-
const output = execSync(`launchctl list | grep com.yeaft.agent`, { encoding: 'utf-8' });
|
|
346
|
-
if (output.trim()) {
|
|
347
|
-
const parts = output.trim().split(/\s+/);
|
|
348
|
-
const pid = parts[0];
|
|
349
|
-
const exitCode = parts[1];
|
|
350
|
-
if (pid !== '-') {
|
|
351
|
-
console.log(`Service is running (PID: ${pid})`);
|
|
352
|
-
} else {
|
|
353
|
-
console.log(`Service is stopped (last exit code: ${exitCode})`);
|
|
354
|
-
}
|
|
355
|
-
} else {
|
|
356
|
-
console.log('Service is not installed.');
|
|
357
|
-
}
|
|
358
|
-
} catch {
|
|
359
|
-
console.log('Service is not installed.');
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function macLogs() {
|
|
364
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
365
|
-
if (existsSync(logFile)) {
|
|
366
|
-
execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
|
|
367
|
-
} else {
|
|
368
|
-
console.log('No logs found.');
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// ─── Windows (Task Scheduler) ────────────────────────────────
|
|
373
|
-
|
|
374
|
-
const WIN_TASK_NAME = 'YeaftAgent';
|
|
375
|
-
const PM2_APP_NAME = 'yeaft-agent';
|
|
376
|
-
|
|
377
|
-
// Legacy paths for cleanup
|
|
378
|
-
function getWinWrapperPath() { return join(getConfigDir(), 'run.vbs'); }
|
|
379
|
-
function getWinBatPath() { return join(getConfigDir(), 'run.bat'); }
|
|
380
|
-
|
|
381
|
-
function ensurePm2() {
|
|
382
|
-
try {
|
|
383
|
-
execSync('pm2 --version', { stdio: 'pipe' });
|
|
384
|
-
} catch {
|
|
385
|
-
console.log('Installing pm2...');
|
|
386
|
-
execSync('npm install -g pm2', { stdio: 'inherit' });
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function getEcosystemPath() {
|
|
391
|
-
return join(getConfigDir(), 'ecosystem.config.cjs');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function generateEcosystem(config) {
|
|
395
|
-
const nodePath = getNodePath();
|
|
396
|
-
const cliPath = getCliPath();
|
|
397
|
-
const cliDir = dirname(cliPath);
|
|
398
|
-
const logDir = getLogDir();
|
|
399
|
-
|
|
400
|
-
const env = {};
|
|
401
|
-
if (config.serverUrl) env.SERVER_URL = config.serverUrl;
|
|
402
|
-
if (config.agentName) env.AGENT_NAME = config.agentName;
|
|
403
|
-
if (config.agentSecret) env.AGENT_SECRET = config.agentSecret;
|
|
404
|
-
if (config.workDir) env.WORK_DIR = config.workDir;
|
|
405
|
-
|
|
406
|
-
return `module.exports = {
|
|
407
|
-
apps: [{
|
|
408
|
-
name: '${PM2_APP_NAME}',
|
|
409
|
-
script: '${cliPath.replace(/\\/g, '\\\\')}',
|
|
410
|
-
interpreter: '${nodePath.replace(/\\/g, '\\\\')}',
|
|
411
|
-
cwd: '${cliDir.replace(/\\/g, '\\\\')}',
|
|
412
|
-
env: ${JSON.stringify(env, null, 6)},
|
|
413
|
-
autorestart: true,
|
|
414
|
-
watch: false,
|
|
415
|
-
max_restarts: 10,
|
|
416
|
-
restart_delay: 5000,
|
|
417
|
-
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
|
418
|
-
error_file: '${join(logDir, 'error.log').replace(/\\/g, '\\\\')}',
|
|
419
|
-
out_file: '${join(logDir, 'out.log').replace(/\\/g, '\\\\')}',
|
|
420
|
-
merge_logs: true,
|
|
421
|
-
max_memory_restart: '500M',
|
|
422
|
-
}]
|
|
423
|
-
};
|
|
424
|
-
`;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function winInstall(config) {
|
|
428
|
-
ensurePm2();
|
|
429
|
-
const logDir = getLogDir();
|
|
430
|
-
mkdirSync(logDir, { recursive: true });
|
|
431
|
-
|
|
432
|
-
// Generate ecosystem config
|
|
433
|
-
const ecoPath = getEcosystemPath();
|
|
434
|
-
writeFileSync(ecoPath, generateEcosystem(config));
|
|
435
|
-
|
|
436
|
-
// Stop existing instance if any
|
|
437
|
-
try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
|
|
438
|
-
|
|
439
|
-
// Start with pm2
|
|
440
|
-
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
441
|
-
|
|
442
|
-
// Save pm2 process list for resurrection
|
|
443
|
-
execSync('pm2 save', { stdio: 'pipe' });
|
|
444
|
-
|
|
445
|
-
// Setup auto-start: create startup script in Windows Startup folder
|
|
446
|
-
// pm2-startup doesn't work well on Windows, use Startup folder approach
|
|
447
|
-
const trayScript = join(__dirname, 'scripts', 'agent-tray.ps1');
|
|
448
|
-
const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
449
|
-
const startupBat = join(startupDir, `${PM2_APP_NAME}.bat`);
|
|
450
|
-
// Resurrect pm2 processes + launch tray icon
|
|
451
|
-
let batContent = `@echo off\r\npm2 resurrect\r\n`;
|
|
452
|
-
if (existsSync(trayScript)) {
|
|
453
|
-
batContent += `start "" powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "${trayScript}"\r\n`;
|
|
454
|
-
}
|
|
455
|
-
writeFileSync(startupBat, batContent);
|
|
456
|
-
|
|
457
|
-
// Launch tray now
|
|
458
|
-
if (existsSync(trayScript)) {
|
|
459
|
-
spawn('powershell', ['-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', trayScript], {
|
|
460
|
-
detached: true, stdio: 'ignore'
|
|
461
|
-
}).unref();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
console.log(`\nService installed and started.`);
|
|
465
|
-
console.log(` Ecosystem: ${ecoPath}`);
|
|
466
|
-
console.log(` Startup: ${startupBat}`);
|
|
467
|
-
console.log(`\nManage with:`);
|
|
468
|
-
console.log(` yeaft-agent status`);
|
|
469
|
-
console.log(` yeaft-agent logs`);
|
|
470
|
-
console.log(` yeaft-agent restart`);
|
|
471
|
-
console.log(` yeaft-agent uninstall`);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function winUninstall() {
|
|
475
|
-
try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
|
|
476
|
-
try { execSync('pm2 save', { stdio: 'pipe' }); } catch {}
|
|
477
|
-
// Clean up ecosystem config
|
|
478
|
-
const ecoPath = getEcosystemPath();
|
|
479
|
-
if (existsSync(ecoPath)) unlinkSync(ecoPath);
|
|
480
|
-
// Clean up Startup bat
|
|
481
|
-
const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${PM2_APP_NAME}.bat`);
|
|
482
|
-
if (existsSync(startupBat)) unlinkSync(startupBat);
|
|
483
|
-
// Clean up legacy files
|
|
484
|
-
const vbsPath = getWinWrapperPath();
|
|
485
|
-
const batPath = getWinBatPath();
|
|
486
|
-
if (existsSync(vbsPath)) unlinkSync(vbsPath);
|
|
487
|
-
if (existsSync(batPath)) unlinkSync(batPath);
|
|
488
|
-
const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
|
|
489
|
-
if (existsSync(startupVbs)) unlinkSync(startupVbs);
|
|
490
|
-
console.log('Service uninstalled.');
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function winStart() {
|
|
494
|
-
try {
|
|
495
|
-
execSync(`pm2 start ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
496
|
-
} catch {
|
|
497
|
-
// Try ecosystem file
|
|
498
|
-
const ecoPath = getEcosystemPath();
|
|
499
|
-
if (existsSync(ecoPath)) {
|
|
500
|
-
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
501
|
-
} else {
|
|
502
|
-
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
503
|
-
process.exit(1);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function winStop() {
|
|
509
|
-
try {
|
|
510
|
-
execSync(`pm2 stop ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
511
|
-
} catch {
|
|
512
|
-
console.error('Service not running or not installed.');
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function winRestart() {
|
|
517
|
-
try {
|
|
518
|
-
execSync(`pm2 restart ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
519
|
-
} catch {
|
|
520
|
-
console.error('Service not running. Use "yeaft-agent start" to start.');
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function winStatus() {
|
|
525
|
-
try {
|
|
526
|
-
execSync(`pm2 describe ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
527
|
-
} catch {
|
|
528
|
-
console.log('Service is not installed.');
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function winLogs() {
|
|
533
|
-
const child = spawn('pm2', ['logs', PM2_APP_NAME, '--lines', '100'], {
|
|
534
|
-
stdio: 'inherit',
|
|
535
|
-
shell: true
|
|
536
|
-
});
|
|
537
|
-
child.on('error', () => {
|
|
538
|
-
// Fallback to reading log file directly
|
|
539
|
-
const logFile = join(getLogDir(), 'out.log');
|
|
540
|
-
if (existsSync(logFile)) {
|
|
541
|
-
console.log(readFileSync(logFile, 'utf-8'));
|
|
542
|
-
} else {
|
|
543
|
-
console.log('No logs found.');
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// ─── Platform dispatcher ─────────────────────────────────────
|
|
549
|
-
|
|
550
|
-
const os = platform();
|
|
551
|
-
|
|
552
|
-
function ensureInstalled() {
|
|
553
|
-
if (os === 'linux') {
|
|
554
|
-
if (!existsSync(getSystemdServicePath())) {
|
|
555
|
-
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
556
|
-
process.exit(1);
|
|
557
|
-
}
|
|
558
|
-
} else if (os === 'darwin') {
|
|
559
|
-
if (!existsSync(getLaunchdPlistPath())) {
|
|
560
|
-
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
561
|
-
process.exit(1);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
// Windows check is done inside individual functions
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
export function install(args) {
|
|
568
|
-
const config = parseServiceArgs(args);
|
|
569
|
-
validateConfig(config);
|
|
570
|
-
saveServiceConfig(config);
|
|
571
|
-
|
|
572
|
-
console.log(`Installing ${SERVICE_NAME} service...`);
|
|
573
|
-
console.log(` Server: ${config.serverUrl}`);
|
|
574
|
-
console.log(` Name: ${config.agentName || '(auto)'}`);
|
|
575
|
-
console.log(` WorkDir: ${config.workDir || '(home)'}`);
|
|
576
|
-
console.log('');
|
|
577
|
-
|
|
578
|
-
if (os === 'linux') linuxInstall(config);
|
|
579
|
-
else if (os === 'darwin') macInstall(config);
|
|
580
|
-
else if (os === 'win32') winInstall(config);
|
|
581
|
-
else {
|
|
582
|
-
console.error(`Unsupported platform: ${os}`);
|
|
583
|
-
console.log('You can run the agent directly: yeaft-agent --server <url> --secret <secret>');
|
|
584
|
-
process.exit(1);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export function uninstall() {
|
|
589
|
-
console.log(`Uninstalling ${SERVICE_NAME} service...`);
|
|
590
|
-
if (os === 'linux') linuxUninstall();
|
|
591
|
-
else if (os === 'darwin') macUninstall();
|
|
592
|
-
else if (os === 'win32') winUninstall();
|
|
593
|
-
else { console.error(`Unsupported platform: ${os}`); process.exit(1); }
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export function start() {
|
|
597
|
-
ensureInstalled();
|
|
598
|
-
if (os === 'linux') linuxStart();
|
|
599
|
-
else if (os === 'darwin') macStart();
|
|
600
|
-
else if (os === 'win32') winStart();
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
export function stop() {
|
|
604
|
-
ensureInstalled();
|
|
605
|
-
if (os === 'linux') linuxStop();
|
|
606
|
-
else if (os === 'darwin') macStop();
|
|
607
|
-
else if (os === 'win32') winStop();
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export function restart() {
|
|
611
|
-
ensureInstalled();
|
|
612
|
-
if (os === 'linux') linuxRestart();
|
|
613
|
-
else if (os === 'darwin') macRestart();
|
|
614
|
-
else if (os === 'win32') winRestart();
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
export function status() {
|
|
618
|
-
if (os === 'linux') linuxStatus();
|
|
619
|
-
else if (os === 'darwin') macStatus();
|
|
620
|
-
else if (os === 'win32') winStatus();
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
export function logs() {
|
|
624
|
-
if (os === 'linux') linuxLogs();
|
|
625
|
-
else if (os === 'darwin') macLogs();
|
|
626
|
-
else if (os === 'win32') winLogs();
|
|
627
|
-
}
|
|
12
|
+
export {
|
|
13
|
+
getConfigDir,
|
|
14
|
+
getLogDir,
|
|
15
|
+
getConfigPath,
|
|
16
|
+
saveServiceConfig,
|
|
17
|
+
loadServiceConfig,
|
|
18
|
+
parseServiceArgs,
|
|
19
|
+
install,
|
|
20
|
+
uninstall,
|
|
21
|
+
start,
|
|
22
|
+
stop,
|
|
23
|
+
restart,
|
|
24
|
+
status,
|
|
25
|
+
logs
|
|
26
|
+
} from './service/index.js';
|