@yeaft/webchat-agent 0.0.9 → 0.0.11

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 (2) hide show
  1. package/package.json +1 -1
  2. package/service.js +102 -115
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/service.js CHANGED
@@ -371,74 +371,87 @@ 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
- function getWinWrapperPath() {
376
- return join(getConfigDir(), 'run.vbs');
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 getWinBatPath() {
380
- return join(getConfigDir(), 'run.bat');
389
+ function getEcosystemPath() {
390
+ return join(getConfigDir(), 'ecosystem.config.cjs');
381
391
  }
382
392
 
383
- function winInstall(config) {
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
- // Build environment variable settings for the batch file
389
- const envLines = [];
390
- if (config.serverUrl) envLines.push(`set "SERVER_URL=${config.serverUrl}"`);
391
- if (config.agentName) envLines.push(`set "AGENT_NAME=${config.agentName}"`);
392
- if (config.agentSecret) envLines.push(`set "AGENT_SECRET=${config.agentSecret}"`);
393
- if (config.workDir) envLines.push(`set "WORK_DIR=${config.workDir}"`);
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
- // Create a batch file that sets env vars and starts node (with log redirection)
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 batContent = `@echo off\r\n${envLines.join('\r\n')}\r\n"${nodePath}" "${cliPath}" >> "${logFile}" 2>&1\r\n`;
399
- const batPath = getWinBatPath();
400
- writeFileSync(batPath, batContent);
401
430
 
402
- // Create VBS wrapper to run hidden (no console window)
403
- const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\r\nWshShell.Run """${batPath}""", 0, False\r\n`;
404
- const vbsPath = getWinWrapperPath();
405
- writeFileSync(vbsPath, vbsContent);
431
+ // Generate ecosystem config
432
+ const ecoPath = getEcosystemPath();
433
+ writeFileSync(ecoPath, generateEcosystem(config));
406
434
 
407
- // Remove existing task if any
408
- try { execSync(`schtasks /delete /tn "${WIN_TASK_NAME}" /f 2>nul`, { stdio: 'pipe' }); } catch {}
435
+ // Stop existing instance if any
436
+ try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
409
437
 
410
- // Create scheduled task that runs at logon
411
- // Try schtasks first (highest limited), fall back to Startup folder
412
- let usedStartupFolder = false;
413
- try {
414
- execSync(
415
- `schtasks /create /tn "${WIN_TASK_NAME}" /tr "wscript.exe \\"${vbsPath}\\"" /sc onlogon /rl highest /f`,
416
- { stdio: 'pipe' }
417
- );
418
- } catch {
419
- try {
420
- execSync(
421
- `schtasks /create /tn "${WIN_TASK_NAME}" /tr "wscript.exe \\"${vbsPath}\\"" /sc onlogon /rl limited /f`,
422
- { stdio: 'pipe' }
423
- );
424
- } catch {
425
- // schtasks not available (no admin) — use Startup folder
426
- const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
427
- const startupVbs = join(startupDir, `${WIN_TASK_NAME}.vbs`);
428
- writeFileSync(startupVbs, vbsContent);
429
- usedStartupFolder = true;
430
- console.log(' (Using Startup folder for auto-start — no admin required)');
431
- }
432
- }
438
+ // Start with pm2
439
+ execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
433
440
 
434
- // Also start it now
435
- if (usedStartupFolder) {
436
- execSync(`wscript.exe "${vbsPath}"`, { stdio: 'pipe' });
437
- } else {
438
- execSync(`schtasks /run /tn "${WIN_TASK_NAME}"`, { stdio: 'pipe' });
439
- }
441
+ // Save pm2 process list for resurrection
442
+ execSync('pm2 save', { stdio: 'pipe' });
440
443
 
441
- console.log('Service installed and started.');
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 startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
447
+ const startupBat = join(startupDir, `${PM2_APP_NAME}.bat`);
448
+ // pm2 is in PATH (ensured by ensurePm2), so just call it directly
449
+ const batContent = `@echo off\r\npm2 resurrect\r\n`;
450
+ writeFileSync(startupBat, batContent);
451
+
452
+ console.log(`\nService installed and started.`);
453
+ console.log(` Ecosystem: ${ecoPath}`);
454
+ console.log(` Startup: ${startupBat}`);
442
455
  console.log(`\nManage with:`);
443
456
  console.log(` yeaft-agent status`);
444
457
  console.log(` yeaft-agent logs`);
@@ -447,14 +460,19 @@ function winInstall(config) {
447
460
  }
448
461
 
449
462
  function winUninstall() {
450
- try { winStop(); } catch {}
451
- try { execSync(`schtasks /delete /tn "${WIN_TASK_NAME}" /f`, { stdio: 'pipe' }); } catch {}
452
- // Clean up wrapper files
463
+ try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
464
+ try { execSync('pm2 save', { stdio: 'pipe' }); } catch {}
465
+ // Clean up ecosystem config
466
+ const ecoPath = getEcosystemPath();
467
+ if (existsSync(ecoPath)) unlinkSync(ecoPath);
468
+ // Clean up Startup bat
469
+ const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${PM2_APP_NAME}.bat`);
470
+ if (existsSync(startupBat)) unlinkSync(startupBat);
471
+ // Clean up legacy files
453
472
  const vbsPath = getWinWrapperPath();
454
473
  const batPath = getWinBatPath();
455
474
  if (existsSync(vbsPath)) unlinkSync(vbsPath);
456
475
  if (existsSync(batPath)) unlinkSync(batPath);
457
- // Clean up Startup folder shortcut
458
476
  const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
459
477
  if (existsSync(startupVbs)) unlinkSync(startupVbs);
460
478
  console.log('Service uninstalled.');
@@ -462,14 +480,12 @@ function winUninstall() {
462
480
 
463
481
  function winStart() {
464
482
  try {
465
- execSync(`schtasks /run /tn "${WIN_TASK_NAME}"`, { stdio: 'pipe' });
466
- console.log('Service started.');
483
+ execSync(`pm2 start ${PM2_APP_NAME}`, { stdio: 'inherit' });
467
484
  } catch {
468
- // No schtasks — try direct launch via VBS
469
- const vbsPath = getWinWrapperPath();
470
- if (existsSync(vbsPath)) {
471
- execSync(`wscript.exe "${vbsPath}"`, { stdio: 'pipe' });
472
- console.log('Service started.');
485
+ // Try ecosystem file
486
+ const ecoPath = getEcosystemPath();
487
+ if (existsSync(ecoPath)) {
488
+ execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
473
489
  } else {
474
490
  console.error('Service not installed. Run "yeaft-agent install" first.');
475
491
  process.exit(1);
@@ -478,72 +494,43 @@ function winStart() {
478
494
  }
479
495
 
480
496
  function winStop() {
481
- // Find and kill the node process running cli.js
482
497
  try {
483
- const output = execSync('wmic process where "name=\'node.exe\'" get processid,commandline /format:csv', {
484
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
485
- });
486
- for (const line of output.split('\n')) {
487
- if (line.includes('cli.js') && line.includes(SERVICE_NAME)) {
488
- const pid = line.trim().split(',').pop();
489
- if (pid && /^\d+$/.test(pid)) {
490
- execSync(`taskkill /pid ${pid} /f`, { stdio: 'pipe' });
491
- }
492
- }
493
- }
494
- } catch {}
495
- // Also try to end the task
496
- try { execSync(`schtasks /end /tn "${WIN_TASK_NAME}"`, { stdio: 'pipe' }); } catch {}
497
- console.log('Service stopped.');
498
+ execSync(`pm2 stop ${PM2_APP_NAME}`, { stdio: 'inherit' });
499
+ } catch {
500
+ console.error('Service not running or not installed.');
501
+ }
498
502
  }
499
503
 
500
504
  function winRestart() {
501
- winStop();
502
- // Brief pause to ensure cleanup
503
- execSync('ping -n 2 127.0.0.1 >nul', { stdio: 'pipe' });
504
- winStart();
505
+ try {
506
+ execSync(`pm2 restart ${PM2_APP_NAME}`, { stdio: 'inherit' });
507
+ } catch {
508
+ console.error('Service not running. Use "yeaft-agent start" to start.');
509
+ }
505
510
  }
506
511
 
507
512
  function winStatus() {
508
513
  try {
509
- const output = execSync(`schtasks /query /tn "${WIN_TASK_NAME}" /fo csv /v`, {
510
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
511
- });
512
- const lines = output.trim().split('\n');
513
- if (lines.length >= 2) {
514
- // Parse CSV header + data
515
- const headers = lines[0].split('","').map(h => h.replace(/"/g, ''));
516
- const values = lines[1].split('","').map(v => v.replace(/"/g, ''));
517
- const statusIdx = headers.indexOf('Status');
518
- const status = statusIdx >= 0 ? values[statusIdx] : 'Unknown';
519
- console.log(`Service status: ${status}`);
520
- console.log(`Task name: ${WIN_TASK_NAME}`);
521
- }
514
+ execSync(`pm2 describe ${PM2_APP_NAME}`, { stdio: 'inherit' });
522
515
  } catch {
523
- // Check Startup folder fallback
524
- const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
525
- if (existsSync(startupVbs)) {
526
- console.log('Service installed via Startup folder (no admin).');
527
- console.log(`Startup script: ${startupVbs}`);
528
- } else {
529
- console.log('Service is not installed.');
530
- }
516
+ console.log('Service is not installed.');
531
517
  }
532
518
  }
533
519
 
534
520
  function winLogs() {
535
- const logFile = join(getLogDir(), 'out.log');
536
- if (existsSync(logFile)) {
537
- // Windows: use PowerShell Get-Content -Wait (like tail -f)
538
- const child = spawn('powershell', ['-Command', `Get-Content -Path "${logFile}" -Tail 100 -Wait`], {
539
- stdio: 'inherit'
540
- });
541
- child.on('error', () => {
521
+ const child = spawn('pm2', ['logs', PM2_APP_NAME, '--lines', '100'], {
522
+ stdio: 'inherit',
523
+ shell: true
524
+ });
525
+ child.on('error', () => {
526
+ // Fallback to reading log file directly
527
+ const logFile = join(getLogDir(), 'out.log');
528
+ if (existsSync(logFile)) {
542
529
  console.log(readFileSync(logFile, 'utf-8'));
543
- });
544
- } else {
545
- console.log('No logs found.');
546
- }
530
+ } else {
531
+ console.log('No logs found.');
532
+ }
533
+ });
547
534
  }
548
535
 
549
536
  // ─── Platform dispatcher ─────────────────────────────────────