@yeaft/webchat-agent 0.0.10 → 0.0.12
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/package.json +3 -2
- package/scripts/agent-tray.ps1 +134 -0
- package/service.js +111 -120
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"workbench.js",
|
|
45
45
|
"history.js",
|
|
46
46
|
"encryption.js",
|
|
47
|
-
"sdk/"
|
|
47
|
+
"sdk/",
|
|
48
|
+
"scripts/agent-tray.ps1"
|
|
48
49
|
],
|
|
49
50
|
"license": "MIT",
|
|
50
51
|
"author": "Yeaft",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Claude Agent 托盘管理器
|
|
2
|
+
# 右键托盘图标可以查看日志、重启、停止等
|
|
3
|
+
|
|
4
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
5
|
+
Add-Type -AssemblyName System.Drawing
|
|
6
|
+
|
|
7
|
+
$PM2AppName = "yeaft-agent"
|
|
8
|
+
|
|
9
|
+
# 创建托盘图标
|
|
10
|
+
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
11
|
+
$notifyIcon.Text = "Yeaft Agent"
|
|
12
|
+
$notifyIcon.Visible = $true
|
|
13
|
+
|
|
14
|
+
# 创建 Claude 风格图标(橙色圆形 + 白色 C)
|
|
15
|
+
function Create-ClaudeIcon {
|
|
16
|
+
$size = 32
|
|
17
|
+
$bitmap = New-Object System.Drawing.Bitmap($size, $size)
|
|
18
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
19
|
+
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
20
|
+
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
|
|
21
|
+
|
|
22
|
+
# 橙色背景圆形 (Claude 的品牌色)
|
|
23
|
+
$orangeBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255, 204, 102, 51))
|
|
24
|
+
$graphics.FillEllipse($orangeBrush, 1, 1, $size - 2, $size - 2)
|
|
25
|
+
|
|
26
|
+
# 白色 "C" 字母
|
|
27
|
+
$whiteBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
28
|
+
$font = New-Object System.Drawing.Font("Arial", 16, [System.Drawing.FontStyle]::Bold)
|
|
29
|
+
# 测量文字大小并精确居中
|
|
30
|
+
$textSize = $graphics.MeasureString("C", $font)
|
|
31
|
+
$x = ($size - $textSize.Width) / 2
|
|
32
|
+
$y = ($size - $textSize.Height) / 2
|
|
33
|
+
$graphics.DrawString("C", $font, $whiteBrush, $x, $y)
|
|
34
|
+
|
|
35
|
+
$graphics.Dispose()
|
|
36
|
+
|
|
37
|
+
# 转换为图标
|
|
38
|
+
$hIcon = $bitmap.GetHicon()
|
|
39
|
+
$icon = [System.Drawing.Icon]::FromHandle($hIcon)
|
|
40
|
+
return $icon
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$notifyIcon.Icon = Create-ClaudeIcon
|
|
44
|
+
|
|
45
|
+
# 创建右键菜单
|
|
46
|
+
$contextMenu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
47
|
+
|
|
48
|
+
# 查看状态
|
|
49
|
+
$menuStatus = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
50
|
+
$menuStatus.Text = "View Status"
|
|
51
|
+
$menuStatus.Add_Click({
|
|
52
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 status`""
|
|
53
|
+
})
|
|
54
|
+
$contextMenu.Items.Add($menuStatus)
|
|
55
|
+
|
|
56
|
+
# 查看日志
|
|
57
|
+
$menuLogs = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
58
|
+
$menuLogs.Text = "View Logs"
|
|
59
|
+
$menuLogs.Add_Click({
|
|
60
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 logs $PM2AppName --lines 100`""
|
|
61
|
+
})
|
|
62
|
+
$contextMenu.Items.Add($menuLogs)
|
|
63
|
+
|
|
64
|
+
# 分隔线
|
|
65
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
66
|
+
|
|
67
|
+
# 重启
|
|
68
|
+
$menuRestart = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
69
|
+
$menuRestart.Text = "Restart Agent"
|
|
70
|
+
$menuRestart.Add_Click({
|
|
71
|
+
pm2 restart $PM2AppName
|
|
72
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent restarted", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
73
|
+
})
|
|
74
|
+
$contextMenu.Items.Add($menuRestart)
|
|
75
|
+
|
|
76
|
+
# 停止
|
|
77
|
+
$menuStop = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
78
|
+
$menuStop.Text = "Stop Agent"
|
|
79
|
+
$menuStop.Add_Click({
|
|
80
|
+
pm2 stop $PM2AppName
|
|
81
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent stopped", [System.Windows.Forms.ToolTipIcon]::Warning)
|
|
82
|
+
})
|
|
83
|
+
$contextMenu.Items.Add($menuStop)
|
|
84
|
+
|
|
85
|
+
# 启动
|
|
86
|
+
$menuStart = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
87
|
+
$menuStart.Text = "Start Agent"
|
|
88
|
+
$menuStart.Add_Click({
|
|
89
|
+
pm2 start $PM2AppName
|
|
90
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent started", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
91
|
+
})
|
|
92
|
+
$contextMenu.Items.Add($menuStart)
|
|
93
|
+
|
|
94
|
+
# 分隔线
|
|
95
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
96
|
+
|
|
97
|
+
# 打开日志文件夹
|
|
98
|
+
$menuOpenLogs = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
99
|
+
$menuOpenLogs.Text = "Open Logs Folder"
|
|
100
|
+
$menuOpenLogs.Add_Click({
|
|
101
|
+
$logDir = Join-Path $env:APPDATA "yeaft-agent\logs"
|
|
102
|
+
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
|
|
103
|
+
Start-Process "explorer" -ArgumentList $logDir
|
|
104
|
+
})
|
|
105
|
+
$contextMenu.Items.Add($menuOpenLogs)
|
|
106
|
+
|
|
107
|
+
# 分隔线
|
|
108
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
109
|
+
|
|
110
|
+
# 退出(停止 agent + 关闭托盘)
|
|
111
|
+
$menuExit = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
112
|
+
$menuExit.Text = "Exit"
|
|
113
|
+
$menuExit.Add_Click({
|
|
114
|
+
pm2 stop $PM2AppName 2>$null
|
|
115
|
+
$notifyIcon.ShowBalloonTip(1000, "Yeaft Agent", "Agent stopped", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
116
|
+
Start-Sleep -Milliseconds 500
|
|
117
|
+
$notifyIcon.Visible = $false
|
|
118
|
+
$notifyIcon.Dispose()
|
|
119
|
+
[System.Windows.Forms.Application]::Exit()
|
|
120
|
+
})
|
|
121
|
+
$contextMenu.Items.Add($menuExit)
|
|
122
|
+
|
|
123
|
+
$notifyIcon.ContextMenuStrip = $contextMenu
|
|
124
|
+
|
|
125
|
+
# 双击打开日志
|
|
126
|
+
$notifyIcon.Add_DoubleClick({
|
|
127
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 logs $PM2AppName`""
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
# 显示启动提示
|
|
131
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Tray manager started. Right-click for options.", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
132
|
+
|
|
133
|
+
# 保持运行
|
|
134
|
+
[System.Windows.Forms.Application]::Run()
|
package/service.js
CHANGED
|
@@ -371,80 +371,98 @@ function macLogs() {
|
|
|
371
371
|
// ─── Windows (Task Scheduler) ────────────────────────────────
|
|
372
372
|
|
|
373
373
|
const WIN_TASK_NAME = 'YeaftAgent';
|
|
374
|
+
const PM2_APP_NAME = 'yeaft-agent';
|
|
374
375
|
|
|
375
|
-
|
|
376
|
-
|
|
376
|
+
// Legacy paths for cleanup
|
|
377
|
+
function getWinWrapperPath() { return join(getConfigDir(), 'run.vbs'); }
|
|
378
|
+
function getWinBatPath() { return join(getConfigDir(), 'run.bat'); }
|
|
379
|
+
|
|
380
|
+
function ensurePm2() {
|
|
381
|
+
try {
|
|
382
|
+
execSync('pm2 --version', { stdio: 'pipe' });
|
|
383
|
+
} catch {
|
|
384
|
+
console.log('Installing pm2...');
|
|
385
|
+
execSync('npm install -g pm2', { stdio: 'inherit' });
|
|
386
|
+
}
|
|
377
387
|
}
|
|
378
388
|
|
|
379
|
-
function
|
|
380
|
-
return join(getConfigDir(), '
|
|
389
|
+
function getEcosystemPath() {
|
|
390
|
+
return join(getConfigDir(), 'ecosystem.config.cjs');
|
|
381
391
|
}
|
|
382
392
|
|
|
383
|
-
function
|
|
393
|
+
function generateEcosystem(config) {
|
|
384
394
|
const nodePath = getNodePath();
|
|
385
395
|
const cliPath = getCliPath();
|
|
396
|
+
const cliDir = dirname(cliPath);
|
|
386
397
|
const logDir = getLogDir();
|
|
387
398
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (config.
|
|
391
|
-
if (config.
|
|
392
|
-
if (config.
|
|
393
|
-
|
|
399
|
+
const env = {};
|
|
400
|
+
if (config.serverUrl) env.SERVER_URL = config.serverUrl;
|
|
401
|
+
if (config.agentName) env.AGENT_NAME = config.agentName;
|
|
402
|
+
if (config.agentSecret) env.AGENT_SECRET = config.agentSecret;
|
|
403
|
+
if (config.workDir) env.WORK_DIR = config.workDir;
|
|
404
|
+
|
|
405
|
+
return `module.exports = {
|
|
406
|
+
apps: [{
|
|
407
|
+
name: '${PM2_APP_NAME}',
|
|
408
|
+
script: '${cliPath.replace(/\\/g, '\\\\')}',
|
|
409
|
+
interpreter: '${nodePath.replace(/\\/g, '\\\\')}',
|
|
410
|
+
cwd: '${cliDir.replace(/\\/g, '\\\\')}',
|
|
411
|
+
env: ${JSON.stringify(env, null, 6)},
|
|
412
|
+
autorestart: true,
|
|
413
|
+
watch: false,
|
|
414
|
+
max_restarts: 10,
|
|
415
|
+
restart_delay: 5000,
|
|
416
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
|
417
|
+
error_file: '${join(logDir, 'error.log').replace(/\\/g, '\\\\')}',
|
|
418
|
+
out_file: '${join(logDir, 'out.log').replace(/\\/g, '\\\\')}',
|
|
419
|
+
merge_logs: true,
|
|
420
|
+
max_memory_restart: '500M',
|
|
421
|
+
}]
|
|
422
|
+
};
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
394
425
|
|
|
395
|
-
|
|
426
|
+
function winInstall(config) {
|
|
427
|
+
ensurePm2();
|
|
428
|
+
const logDir = getLogDir();
|
|
396
429
|
mkdirSync(logDir, { recursive: true });
|
|
397
|
-
const logFile = join(logDir, 'out.log');
|
|
398
|
-
const cliDir = dirname(cliPath);
|
|
399
|
-
const batContent = `@echo off\r\ncd /d "${cliDir}"\r\n${envLines.join('\r\n')}\r\n"${nodePath}" "${cliPath}" >> "${logFile}" 2>&1\r\n`;
|
|
400
|
-
const batPath = getWinBatPath();
|
|
401
|
-
writeFileSync(batPath, batContent);
|
|
402
430
|
|
|
403
|
-
//
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
writeFileSync(vbsPath, vbsContent);
|
|
431
|
+
// Generate ecosystem config
|
|
432
|
+
const ecoPath = getEcosystemPath();
|
|
433
|
+
writeFileSync(ecoPath, generateEcosystem(config));
|
|
407
434
|
|
|
408
|
-
//
|
|
409
|
-
try { execSync(`
|
|
435
|
+
// Stop existing instance if any
|
|
436
|
+
try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
|
|
410
437
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// schtasks not available (no admin) — use Startup folder
|
|
427
|
-
const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
428
|
-
const startupVbs = join(startupDir, `${WIN_TASK_NAME}.vbs`);
|
|
429
|
-
writeFileSync(startupVbs, vbsContent);
|
|
430
|
-
usedStartupFolder = true;
|
|
431
|
-
console.log(' (Using Startup folder for auto-start — no admin required)');
|
|
432
|
-
}
|
|
438
|
+
// Start with pm2
|
|
439
|
+
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
440
|
+
|
|
441
|
+
// Save pm2 process list for resurrection
|
|
442
|
+
execSync('pm2 save', { stdio: 'pipe' });
|
|
443
|
+
|
|
444
|
+
// Setup auto-start: create startup script in Windows Startup folder
|
|
445
|
+
// pm2-startup doesn't work well on Windows, use Startup folder approach
|
|
446
|
+
const trayScript = join(__dirname, 'scripts', 'agent-tray.ps1');
|
|
447
|
+
const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
448
|
+
const startupBat = join(startupDir, `${PM2_APP_NAME}.bat`);
|
|
449
|
+
// Resurrect pm2 processes + launch tray icon
|
|
450
|
+
let batContent = `@echo off\r\npm2 resurrect\r\n`;
|
|
451
|
+
if (existsSync(trayScript)) {
|
|
452
|
+
batContent += `start "" powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "${trayScript}"\r\n`;
|
|
433
453
|
}
|
|
454
|
+
writeFileSync(startupBat, batContent);
|
|
434
455
|
|
|
435
|
-
//
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
456
|
+
// Launch tray now
|
|
457
|
+
if (existsSync(trayScript)) {
|
|
458
|
+
spawn('powershell', ['-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', trayScript], {
|
|
459
|
+
detached: true, stdio: 'ignore'
|
|
460
|
+
}).unref();
|
|
440
461
|
}
|
|
441
462
|
|
|
442
|
-
console.log(
|
|
443
|
-
console.log(`
|
|
444
|
-
console.log(`
|
|
445
|
-
console.log(` Log: ${logFile}`);
|
|
446
|
-
console.log(` Node: ${nodePath}`);
|
|
447
|
-
console.log(` CLI: ${cliPath}`);
|
|
463
|
+
console.log(`\nService installed and started.`);
|
|
464
|
+
console.log(` Ecosystem: ${ecoPath}`);
|
|
465
|
+
console.log(` Startup: ${startupBat}`);
|
|
448
466
|
console.log(`\nManage with:`);
|
|
449
467
|
console.log(` yeaft-agent status`);
|
|
450
468
|
console.log(` yeaft-agent logs`);
|
|
@@ -453,14 +471,19 @@ function winInstall(config) {
|
|
|
453
471
|
}
|
|
454
472
|
|
|
455
473
|
function winUninstall() {
|
|
456
|
-
try {
|
|
457
|
-
try { execSync(
|
|
458
|
-
// Clean up
|
|
474
|
+
try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
|
|
475
|
+
try { execSync('pm2 save', { stdio: 'pipe' }); } catch {}
|
|
476
|
+
// Clean up ecosystem config
|
|
477
|
+
const ecoPath = getEcosystemPath();
|
|
478
|
+
if (existsSync(ecoPath)) unlinkSync(ecoPath);
|
|
479
|
+
// Clean up Startup bat
|
|
480
|
+
const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${PM2_APP_NAME}.bat`);
|
|
481
|
+
if (existsSync(startupBat)) unlinkSync(startupBat);
|
|
482
|
+
// Clean up legacy files
|
|
459
483
|
const vbsPath = getWinWrapperPath();
|
|
460
484
|
const batPath = getWinBatPath();
|
|
461
485
|
if (existsSync(vbsPath)) unlinkSync(vbsPath);
|
|
462
486
|
if (existsSync(batPath)) unlinkSync(batPath);
|
|
463
|
-
// Clean up Startup folder shortcut
|
|
464
487
|
const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
|
|
465
488
|
if (existsSync(startupVbs)) unlinkSync(startupVbs);
|
|
466
489
|
console.log('Service uninstalled.');
|
|
@@ -468,14 +491,12 @@ function winUninstall() {
|
|
|
468
491
|
|
|
469
492
|
function winStart() {
|
|
470
493
|
try {
|
|
471
|
-
execSync(`
|
|
472
|
-
console.log('Service started.');
|
|
494
|
+
execSync(`pm2 start ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
473
495
|
} catch {
|
|
474
|
-
//
|
|
475
|
-
const
|
|
476
|
-
if (existsSync(
|
|
477
|
-
execSync(`
|
|
478
|
-
console.log('Service started.');
|
|
496
|
+
// Try ecosystem file
|
|
497
|
+
const ecoPath = getEcosystemPath();
|
|
498
|
+
if (existsSync(ecoPath)) {
|
|
499
|
+
execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
|
|
479
500
|
} else {
|
|
480
501
|
console.error('Service not installed. Run "yeaft-agent install" first.');
|
|
481
502
|
process.exit(1);
|
|
@@ -484,73 +505,43 @@ function winStart() {
|
|
|
484
505
|
}
|
|
485
506
|
|
|
486
507
|
function winStop() {
|
|
487
|
-
// Find and kill the node process running our cli.js
|
|
488
508
|
try {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
for (const line of output.split('\n')) {
|
|
494
|
-
if (line.includes('cli.js') && (line.includes(SERVICE_NAME) || line.includes('webchat-agent'))) {
|
|
495
|
-
const pid = line.trim().split(',').pop();
|
|
496
|
-
if (pid && /^\d+$/.test(pid)) {
|
|
497
|
-
execSync(`taskkill /pid ${pid} /f`, { stdio: 'pipe' });
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
} catch {}
|
|
502
|
-
// Also try to end the task
|
|
503
|
-
try { execSync(`schtasks /end /tn "${WIN_TASK_NAME}"`, { stdio: 'pipe' }); } catch {}
|
|
504
|
-
console.log('Service stopped.');
|
|
509
|
+
execSync(`pm2 stop ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
510
|
+
} catch {
|
|
511
|
+
console.error('Service not running or not installed.');
|
|
512
|
+
}
|
|
505
513
|
}
|
|
506
514
|
|
|
507
515
|
function winRestart() {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
516
|
+
try {
|
|
517
|
+
execSync(`pm2 restart ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
518
|
+
} catch {
|
|
519
|
+
console.error('Service not running. Use "yeaft-agent start" to start.');
|
|
520
|
+
}
|
|
512
521
|
}
|
|
513
522
|
|
|
514
523
|
function winStatus() {
|
|
515
524
|
try {
|
|
516
|
-
|
|
517
|
-
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
|
|
518
|
-
});
|
|
519
|
-
const lines = output.trim().split('\n');
|
|
520
|
-
if (lines.length >= 2) {
|
|
521
|
-
// Parse CSV header + data
|
|
522
|
-
const headers = lines[0].split('","').map(h => h.replace(/"/g, ''));
|
|
523
|
-
const values = lines[1].split('","').map(v => v.replace(/"/g, ''));
|
|
524
|
-
const statusIdx = headers.indexOf('Status');
|
|
525
|
-
const status = statusIdx >= 0 ? values[statusIdx] : 'Unknown';
|
|
526
|
-
console.log(`Service status: ${status}`);
|
|
527
|
-
console.log(`Task name: ${WIN_TASK_NAME}`);
|
|
528
|
-
}
|
|
525
|
+
execSync(`pm2 describe ${PM2_APP_NAME}`, { stdio: 'inherit' });
|
|
529
526
|
} catch {
|
|
530
|
-
|
|
531
|
-
const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
|
|
532
|
-
if (existsSync(startupVbs)) {
|
|
533
|
-
console.log('Service installed via Startup folder (no admin).');
|
|
534
|
-
console.log(`Startup script: ${startupVbs}`);
|
|
535
|
-
} else {
|
|
536
|
-
console.log('Service is not installed.');
|
|
537
|
-
}
|
|
527
|
+
console.log('Service is not installed.');
|
|
538
528
|
}
|
|
539
529
|
}
|
|
540
530
|
|
|
541
531
|
function winLogs() {
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
532
|
+
const child = spawn('pm2', ['logs', PM2_APP_NAME, '--lines', '100'], {
|
|
533
|
+
stdio: 'inherit',
|
|
534
|
+
shell: true
|
|
535
|
+
});
|
|
536
|
+
child.on('error', () => {
|
|
537
|
+
// Fallback to reading log file directly
|
|
538
|
+
const logFile = join(getLogDir(), 'out.log');
|
|
539
|
+
if (existsSync(logFile)) {
|
|
549
540
|
console.log(readFileSync(logFile, 'utf-8'));
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
541
|
+
} else {
|
|
542
|
+
console.log('No logs found.');
|
|
543
|
+
}
|
|
544
|
+
});
|
|
554
545
|
}
|
|
555
546
|
|
|
556
547
|
// ─── Platform dispatcher ─────────────────────────────────────
|