@yeaft/webchat-agent 0.0.234 → 0.0.236

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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Service — Linux (systemd) platform implementation
3
+ */
4
+ import { execSync } from 'child_process';
5
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { homedir } from 'os';
8
+ import { SERVICE_NAME, getLogDir, getNodePath, getCliPath } from './config.js';
9
+
10
+ export function getSystemdServicePath() {
11
+ const dir = join(homedir(), '.config', 'systemd', 'user');
12
+ mkdirSync(dir, { recursive: true });
13
+ return join(dir, `${SERVICE_NAME}.service`);
14
+ }
15
+
16
+ function generateSystemdUnit(config) {
17
+ const nodePath = getNodePath();
18
+ const cliPath = getCliPath();
19
+ const envLines = [];
20
+ if (config.serverUrl) envLines.push(`Environment=SERVER_URL=${config.serverUrl}`);
21
+ if (config.agentName) envLines.push(`Environment=AGENT_NAME=${config.agentName}`);
22
+ if (config.agentSecret) envLines.push(`Environment=AGENT_SECRET=${config.agentSecret}`);
23
+ if (config.workDir) envLines.push(`Environment=WORK_DIR=${config.workDir}`);
24
+
25
+ // Include node's bin dir in PATH for claude CLI access
26
+ const nodeBinDir = dirname(nodePath);
27
+
28
+ return `[Unit]
29
+ Description=Yeaft WebChat Agent
30
+ After=network-online.target
31
+ Wants=network-online.target
32
+
33
+ [Service]
34
+ Type=simple
35
+ ExecStart=${nodePath} ${cliPath}
36
+ WorkingDirectory=${config.workDir || homedir()}
37
+ Restart=on-failure
38
+ RestartSec=10
39
+ KillMode=process
40
+ ${envLines.join('\n')}
41
+ Environment=PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
42
+
43
+ StandardOutput=append:${getLogDir()}/out.log
44
+ StandardError=append:${getLogDir()}/error.log
45
+
46
+ [Install]
47
+ WantedBy=default.target
48
+ `;
49
+ }
50
+
51
+ export function linuxInstall(config) {
52
+ const servicePath = getSystemdServicePath();
53
+ writeFileSync(servicePath, generateSystemdUnit(config));
54
+ execSync('systemctl --user daemon-reload');
55
+ execSync(`systemctl --user enable ${SERVICE_NAME}`);
56
+ execSync(`systemctl --user start ${SERVICE_NAME}`);
57
+ console.log(`Service installed and started.`);
58
+ console.log(`\nManage with:`);
59
+ console.log(` yeaft-agent status`);
60
+ console.log(` yeaft-agent logs`);
61
+ console.log(` yeaft-agent restart`);
62
+ console.log(` yeaft-agent uninstall`);
63
+ console.log(`\nTo run when not logged in:`);
64
+ console.log(` sudo loginctl enable-linger $(whoami)`);
65
+ }
66
+
67
+ export function linuxUninstall() {
68
+ try { execSync(`systemctl --user stop ${SERVICE_NAME} 2>/dev/null`); } catch {}
69
+ try { execSync(`systemctl --user disable ${SERVICE_NAME} 2>/dev/null`); } catch {}
70
+ const servicePath = getSystemdServicePath();
71
+ if (existsSync(servicePath)) unlinkSync(servicePath);
72
+ try { execSync('systemctl --user daemon-reload'); } catch {}
73
+ console.log('Service uninstalled.');
74
+ }
75
+
76
+ export function linuxStart() {
77
+ execSync(`systemctl --user start ${SERVICE_NAME}`, { stdio: 'inherit' });
78
+ console.log('Service started.');
79
+ }
80
+
81
+ export function linuxStop() {
82
+ execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'inherit' });
83
+ console.log('Service stopped.');
84
+ }
85
+
86
+ export function linuxRestart() {
87
+ execSync(`systemctl --user restart ${SERVICE_NAME}`, { stdio: 'inherit' });
88
+ console.log('Service restarted.');
89
+ }
90
+
91
+ export function linuxStatus() {
92
+ try {
93
+ execSync(`systemctl --user status ${SERVICE_NAME}`, { stdio: 'inherit' });
94
+ } catch {
95
+ // systemctl status returns non-zero when service is stopped
96
+ }
97
+ }
98
+
99
+ export function linuxLogs() {
100
+ try {
101
+ execSync(`journalctl --user -u ${SERVICE_NAME} -f --no-pager -n 100`, { stdio: 'inherit' });
102
+ } catch {
103
+ // Fallback to log files
104
+ const logFile = join(getLogDir(), 'out.log');
105
+ if (existsSync(logFile)) {
106
+ execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
107
+ } else {
108
+ console.log('No logs found.');
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Service — macOS (launchd) platform implementation
3
+ */
4
+ import { execSync } from 'child_process';
5
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { homedir } from 'os';
8
+ import { getLogDir, getNodePath, getCliPath } from './config.js';
9
+
10
+ export function getLaunchdPlistPath() {
11
+ const dir = join(homedir(), 'Library', 'LaunchAgents');
12
+ mkdirSync(dir, { recursive: true });
13
+ return join(dir, 'com.yeaft.agent.plist');
14
+ }
15
+
16
+ function generateLaunchdPlist(config) {
17
+ const nodePath = getNodePath();
18
+ const cliPath = getCliPath();
19
+ const logDir = getLogDir();
20
+
21
+ const envDict = [];
22
+ if (config.serverUrl) envDict.push(` <key>SERVER_URL</key>\n <string>${config.serverUrl}</string>`);
23
+ if (config.agentName) envDict.push(` <key>AGENT_NAME</key>\n <string>${config.agentName}</string>`);
24
+ if (config.agentSecret) envDict.push(` <key>AGENT_SECRET</key>\n <string>${config.agentSecret}</string>`);
25
+ if (config.workDir) envDict.push(` <key>WORK_DIR</key>\n <string>${config.workDir}</string>`);
26
+
27
+ return `<?xml version="1.0" encoding="UTF-8"?>
28
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
29
+ <plist version="1.0">
30
+ <dict>
31
+ <key>Label</key>
32
+ <string>com.yeaft.agent</string>
33
+ <key>ProgramArguments</key>
34
+ <array>
35
+ <string>${nodePath}</string>
36
+ <string>${cliPath}</string>
37
+ </array>
38
+ <key>WorkingDirectory</key>
39
+ <string>${config.workDir || homedir()}</string>
40
+ <key>EnvironmentVariables</key>
41
+ <dict>
42
+ ${envDict.join('\n')}
43
+ </dict>
44
+ <key>RunAtLoad</key>
45
+ <true/>
46
+ <key>KeepAlive</key>
47
+ <dict>
48
+ <key>SuccessfulExit</key>
49
+ <false/>
50
+ </dict>
51
+ <key>ThrottleInterval</key>
52
+ <integer>10</integer>
53
+ <key>StandardOutPath</key>
54
+ <string>${logDir}/out.log</string>
55
+ <key>StandardErrorPath</key>
56
+ <string>${logDir}/error.log</string>
57
+ </dict>
58
+ </plist>
59
+ `;
60
+ }
61
+
62
+ export function macInstall(config) {
63
+ const plistPath = getLaunchdPlistPath();
64
+ // Unload first if exists
65
+ if (existsSync(plistPath)) {
66
+ try { execSync(`launchctl unload ${plistPath} 2>/dev/null`); } catch {}
67
+ }
68
+ writeFileSync(plistPath, generateLaunchdPlist(config));
69
+ execSync(`launchctl load ${plistPath}`);
70
+ console.log('Service installed and started.');
71
+ console.log(`\nManage with:`);
72
+ console.log(` yeaft-agent status`);
73
+ console.log(` yeaft-agent logs`);
74
+ console.log(` yeaft-agent restart`);
75
+ console.log(` yeaft-agent uninstall`);
76
+ }
77
+
78
+ export function macUninstall() {
79
+ const plistPath = getLaunchdPlistPath();
80
+ if (existsSync(plistPath)) {
81
+ try { execSync(`launchctl unload ${plistPath}`); } catch {}
82
+ unlinkSync(plistPath);
83
+ }
84
+ console.log('Service uninstalled.');
85
+ }
86
+
87
+ export function macStart() {
88
+ const plistPath = getLaunchdPlistPath();
89
+ if (!existsSync(plistPath)) {
90
+ console.error('Service not installed. Run "yeaft-agent install" first.');
91
+ process.exit(1);
92
+ }
93
+ execSync(`launchctl load ${plistPath}`);
94
+ console.log('Service started.');
95
+ }
96
+
97
+ export function macStop() {
98
+ const plistPath = getLaunchdPlistPath();
99
+ if (existsSync(plistPath)) {
100
+ execSync(`launchctl unload ${plistPath}`);
101
+ }
102
+ console.log('Service stopped.');
103
+ }
104
+
105
+ export function macRestart() {
106
+ macStop();
107
+ macStart();
108
+ }
109
+
110
+ export function macStatus() {
111
+ try {
112
+ const output = execSync(`launchctl list | grep com.yeaft.agent`, { encoding: 'utf-8' });
113
+ if (output.trim()) {
114
+ const parts = output.trim().split(/\s+/);
115
+ const pid = parts[0];
116
+ const exitCode = parts[1];
117
+ if (pid !== '-') {
118
+ console.log(`Service is running (PID: ${pid})`);
119
+ } else {
120
+ console.log(`Service is stopped (last exit code: ${exitCode})`);
121
+ }
122
+ } else {
123
+ console.log('Service is not installed.');
124
+ }
125
+ } catch {
126
+ console.log('Service is not installed.');
127
+ }
128
+ }
129
+
130
+ export function macLogs() {
131
+ const logFile = join(getLogDir(), 'out.log');
132
+ if (existsSync(logFile)) {
133
+ execSync(`tail -f -n 100 ${logFile}`, { stdio: 'inherit' });
134
+ } else {
135
+ console.log('No logs found.');
136
+ }
137
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Service — Windows (pm2) platform implementation
3
+ */
4
+ import { execSync, spawn } from 'child_process';
5
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { SERVICE_NAME, getConfigDir, getLogDir, getNodePath, getCliPath } from './config.js';
8
+
9
+ const WIN_TASK_NAME = 'YeaftAgent';
10
+ const PM2_APP_NAME = 'yeaft-agent';
11
+
12
+ // Legacy paths for cleanup
13
+ function getWinWrapperPath() { return join(getConfigDir(), 'run.vbs'); }
14
+ function getWinBatPath() { return join(getConfigDir(), 'run.bat'); }
15
+
16
+ function ensurePm2() {
17
+ try {
18
+ execSync('pm2 --version', { stdio: 'pipe' });
19
+ } catch {
20
+ console.log('Installing pm2...');
21
+ execSync('npm install -g pm2', { stdio: 'inherit' });
22
+ }
23
+ }
24
+
25
+ function getEcosystemPath() {
26
+ return join(getConfigDir(), 'ecosystem.config.cjs');
27
+ }
28
+
29
+ function generateEcosystem(config) {
30
+ const nodePath = getNodePath();
31
+ const cliPath = getCliPath();
32
+ const cliDir = dirname(cliPath);
33
+ const logDir = getLogDir();
34
+
35
+ const env = {};
36
+ if (config.serverUrl) env.SERVER_URL = config.serverUrl;
37
+ if (config.agentName) env.AGENT_NAME = config.agentName;
38
+ if (config.agentSecret) env.AGENT_SECRET = config.agentSecret;
39
+ if (config.workDir) env.WORK_DIR = config.workDir;
40
+
41
+ return `module.exports = {
42
+ apps: [{
43
+ name: '${PM2_APP_NAME}',
44
+ script: '${cliPath.replace(/\\/g, '\\\\')}',
45
+ interpreter: '${nodePath.replace(/\\/g, '\\\\')}',
46
+ cwd: '${cliDir.replace(/\\/g, '\\\\')}',
47
+ env: ${JSON.stringify(env, null, 6)},
48
+ autorestart: true,
49
+ watch: false,
50
+ max_restarts: 10,
51
+ restart_delay: 5000,
52
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
53
+ error_file: '${join(logDir, 'error.log').replace(/\\/g, '\\\\')}',
54
+ out_file: '${join(logDir, 'out.log').replace(/\\/g, '\\\\')}',
55
+ merge_logs: true,
56
+ max_memory_restart: '500M',
57
+ }]
58
+ };
59
+ `;
60
+ }
61
+
62
+ export function winInstall(config) {
63
+ ensurePm2();
64
+ const logDir = getLogDir();
65
+ mkdirSync(logDir, { recursive: true });
66
+
67
+ // Generate ecosystem config
68
+ const ecoPath = getEcosystemPath();
69
+ writeFileSync(ecoPath, generateEcosystem(config));
70
+
71
+ // Stop existing instance if any
72
+ try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
73
+
74
+ // Start with pm2
75
+ execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
76
+
77
+ // Save pm2 process list for resurrection
78
+ execSync('pm2 save', { stdio: 'pipe' });
79
+
80
+ // Setup auto-start: create startup script in Windows Startup folder
81
+ // pm2-startup doesn't work well on Windows, use Startup folder approach
82
+ const trayScript = join(dirname(getCliPath()), 'scripts', 'agent-tray.ps1');
83
+ const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
84
+ const startupBat = join(startupDir, `${PM2_APP_NAME}.bat`);
85
+ // Resurrect pm2 processes + launch tray icon
86
+ let batContent = `@echo off\r\npm2 resurrect\r\n`;
87
+ if (existsSync(trayScript)) {
88
+ batContent += `start "" powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "${trayScript}"\r\n`;
89
+ }
90
+ writeFileSync(startupBat, batContent);
91
+
92
+ // Launch tray now
93
+ if (existsSync(trayScript)) {
94
+ spawn('powershell', ['-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', trayScript], {
95
+ detached: true, stdio: 'ignore'
96
+ }).unref();
97
+ }
98
+
99
+ console.log(`\nService installed and started.`);
100
+ console.log(` Ecosystem: ${ecoPath}`);
101
+ console.log(` Startup: ${startupBat}`);
102
+ console.log(`\nManage with:`);
103
+ console.log(` yeaft-agent status`);
104
+ console.log(` yeaft-agent logs`);
105
+ console.log(` yeaft-agent restart`);
106
+ console.log(` yeaft-agent uninstall`);
107
+ }
108
+
109
+ export function winUninstall() {
110
+ try { execSync(`pm2 delete ${PM2_APP_NAME}`, { stdio: 'pipe' }); } catch {}
111
+ try { execSync('pm2 save', { stdio: 'pipe' }); } catch {}
112
+ // Clean up ecosystem config
113
+ const ecoPath = getEcosystemPath();
114
+ if (existsSync(ecoPath)) unlinkSync(ecoPath);
115
+ // Clean up Startup bat
116
+ const startupBat = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${PM2_APP_NAME}.bat`);
117
+ if (existsSync(startupBat)) unlinkSync(startupBat);
118
+ // Clean up legacy files
119
+ const vbsPath = getWinWrapperPath();
120
+ const batPath = getWinBatPath();
121
+ if (existsSync(vbsPath)) unlinkSync(vbsPath);
122
+ if (existsSync(batPath)) unlinkSync(batPath);
123
+ const startupVbs = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${WIN_TASK_NAME}.vbs`);
124
+ if (existsSync(startupVbs)) unlinkSync(startupVbs);
125
+ console.log('Service uninstalled.');
126
+ }
127
+
128
+ export function winStart() {
129
+ try {
130
+ execSync(`pm2 start ${PM2_APP_NAME}`, { stdio: 'inherit' });
131
+ } catch {
132
+ // Try ecosystem file
133
+ const ecoPath = getEcosystemPath();
134
+ if (existsSync(ecoPath)) {
135
+ execSync(`pm2 start "${ecoPath}"`, { stdio: 'inherit' });
136
+ } else {
137
+ console.error('Service not installed. Run "yeaft-agent install" first.');
138
+ process.exit(1);
139
+ }
140
+ }
141
+ }
142
+
143
+ export function winStop() {
144
+ try {
145
+ execSync(`pm2 stop ${PM2_APP_NAME}`, { stdio: 'inherit' });
146
+ } catch {
147
+ console.error('Service not running or not installed.');
148
+ }
149
+ }
150
+
151
+ export function winRestart() {
152
+ try {
153
+ execSync(`pm2 restart ${PM2_APP_NAME}`, { stdio: 'inherit' });
154
+ } catch {
155
+ console.error('Service not running. Use "yeaft-agent start" to start.');
156
+ }
157
+ }
158
+
159
+ export function winStatus() {
160
+ try {
161
+ execSync(`pm2 describe ${PM2_APP_NAME}`, { stdio: 'inherit' });
162
+ } catch {
163
+ console.log('Service is not installed.');
164
+ }
165
+ }
166
+
167
+ export function winLogs() {
168
+ const child = spawn('pm2', ['logs', PM2_APP_NAME, '--lines', '100'], {
169
+ stdio: 'inherit',
170
+ shell: true
171
+ });
172
+ child.on('error', () => {
173
+ // Fallback to reading log file directly
174
+ const logFile = join(getLogDir(), 'out.log');
175
+ if (existsSync(logFile)) {
176
+ console.log(readFileSync(logFile, 'utf-8'));
177
+ } else {
178
+ console.log('No logs found.');
179
+ }
180
+ });
181
+ }