@agent-webui/ai-desk-daemon 1.0.20 → 1.0.22

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/README.md CHANGED
@@ -18,7 +18,48 @@
18
18
 
19
19
  ## 📦 安装
20
20
 
21
- ### macOS
21
+ ### 方式 1: npm 安装(纯 CLI 工具)
22
+
23
+ **适合场景**:开发者、服务器环境、CI/CD、命令行用户
24
+
25
+ ```bash
26
+ # 全局安装
27
+ npm install -g agent-webui/ai-desk-daemon
28
+
29
+ # 启动 daemon
30
+ aidesk start
31
+
32
+ # 查看状态
33
+ aidesk status
34
+ ```
35
+
36
+ **包含内容**:
37
+ - ✅ AI Desk Daemon 后台服务
38
+ - ✅ CLI 命令行管理工具
39
+ - ✅ HTTP API (http://localhost:9527)
40
+
41
+ **不包含**:
42
+ - ❌ 系统托盘应用 (Tray)
43
+ - ❌ 桌面 GUI 应用
44
+
45
+ **可用命令**:
46
+ - `aidesk start` - 启动守护进程(后台运行)
47
+ - `aidesk start --log` - 启动守护进程(前台运行,跟随日志)
48
+ - `aidesk stop` - 停止守护进程
49
+ - `aidesk restart` - 重启守护进程
50
+ - `aidesk status` - 查看状态
51
+ - `aidesk logs` - 查看日志
52
+ - `aidesk logs -f` - 实时查看日志(不会停止守护进程)
53
+
54
+ 📖 详细使用说明:[NPM_CLI.md](NPM_CLI.md)
55
+
56
+ ---
57
+
58
+ ### 方式 2: 完整安装(Daemon + Tray)
59
+
60
+ **适合场景**:桌面用户、需要系统托盘图标、完整 GUI 体验
61
+
62
+ #### macOS
22
63
 
23
64
  ```bash
24
65
  # 下载并安装
@@ -28,7 +69,7 @@ curl -fsSL https://github.com/your-repo/ai-desk-desktop/releases/latest/download
28
69
  ./scripts/install-macos.sh
29
70
  ```
30
71
 
31
- ### Linux
72
+ #### Linux
32
73
 
33
74
  ```bash
34
75
  # Ubuntu/Debian
@@ -39,7 +80,7 @@ systemctl --user enable ai-desk-daemon
39
80
  systemctl --user start ai-desk-daemon
40
81
  ```
41
82
 
42
- ### Windows
83
+ #### Windows
43
84
 
44
85
  ```powershell
45
86
  # 以管理员身份运行 PowerShell
package/bin/cli.js ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AI Desk Daemon CLI
5
+ */
6
+
7
+ const { program } = require('commander');
8
+ const chalk = require('chalk');
9
+ const fs = require('fs');
10
+ const { start, stop, restart, status } = require('../lib/daemon-manager');
11
+ const { getLogPath } = require('../lib/platform');
12
+ const { VERSION } = require('../lib/platform');
13
+
14
+ program
15
+ .name('ai-desk-daemon')
16
+ .description('AI Desk Daemon - CLI tool for managing the AI Desk daemon service')
17
+ .version(VERSION);
18
+
19
+ // Start command
20
+ program
21
+ .command('start')
22
+ .description('Start the daemon')
23
+ .option('--log', 'Follow daemon logs in foreground (Ctrl+C to stop daemon)')
24
+ .action(async (options) => {
25
+ try {
26
+ const { getPidPath } = require('../lib/platform');
27
+ const daemonPid = start();
28
+ console.log(chalk.green('✓ Daemon started successfully'));
29
+
30
+ // Only follow logs if --log is specified
31
+ if (options.log) {
32
+ console.log(chalk.cyan('\n📋 Following daemon logs (Ctrl+C to stop daemon)...\n'));
33
+
34
+ // Wait a moment for daemon to start writing logs
35
+ await new Promise(resolve => setTimeout(resolve, 1000));
36
+
37
+ const logPath = getLogPath();
38
+ if (!fs.existsSync(logPath)) {
39
+ console.log(chalk.yellow('Waiting for logs...'));
40
+ await new Promise(resolve => setTimeout(resolve, 2000));
41
+ }
42
+
43
+ // Follow logs (tail -f) - works on Unix-like systems
44
+ if (process.platform !== 'win32') {
45
+ const { spawn } = require('child_process');
46
+ const tail = spawn('tail', ['-f', logPath]);
47
+
48
+ tail.stdout.on('data', (data) => {
49
+ process.stdout.write(data);
50
+ });
51
+
52
+ tail.stderr.on('data', (data) => {
53
+ process.stderr.write(data);
54
+ });
55
+
56
+ tail.on('error', (error) => {
57
+ console.error(chalk.red('Failed to follow logs:'), error.message);
58
+ process.exit(1);
59
+ });
60
+
61
+ // Handle Ctrl+C - stop the daemon
62
+ process.on('SIGINT', () => {
63
+ console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
64
+ tail.kill();
65
+
66
+ try {
67
+ // Stop the daemon
68
+ const { stop } = require('../lib/daemon-manager');
69
+ stop();
70
+ console.log(chalk.green('✓ Daemon stopped successfully'));
71
+ } catch (error) {
72
+ console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
73
+ }
74
+
75
+ process.exit(0);
76
+ });
77
+ } else {
78
+ // Windows: use polling to read log file
79
+ console.log(chalk.yellow('Log following on Windows - press Ctrl+C to stop daemon\n'));
80
+
81
+ let lastSize = 0;
82
+ const pollInterval = setInterval(() => {
83
+ try {
84
+ const stats = fs.statSync(logPath);
85
+ if (stats.size > lastSize) {
86
+ const stream = fs.createReadStream(logPath, {
87
+ start: lastSize,
88
+ end: stats.size
89
+ });
90
+ stream.on('data', (chunk) => {
91
+ process.stdout.write(chunk);
92
+ });
93
+ lastSize = stats.size;
94
+ }
95
+ } catch (error) {
96
+ // Ignore errors
97
+ }
98
+ }, 500);
99
+
100
+ process.on('SIGINT', () => {
101
+ clearInterval(pollInterval);
102
+ console.log(chalk.yellow('\n\n⏹ Stopping daemon...'));
103
+
104
+ try {
105
+ // Stop the daemon
106
+ const { stop } = require('../lib/daemon-manager');
107
+ stop();
108
+ console.log(chalk.green('✓ Daemon stopped successfully'));
109
+ } catch (error) {
110
+ console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
111
+ }
112
+
113
+ process.exit(0);
114
+ });
115
+ }
116
+ }
117
+ } catch (error) {
118
+ console.error(chalk.red('✗ Failed to start daemon:'), error.message);
119
+ process.exit(1);
120
+ }
121
+ });
122
+
123
+ // Stop command
124
+ program
125
+ .command('stop')
126
+ .description('Stop the daemon')
127
+ .action(async () => {
128
+ try {
129
+ stop();
130
+ console.log(chalk.green('✓ Daemon stopped successfully'));
131
+ } catch (error) {
132
+ console.error(chalk.red('✗ Failed to stop daemon:'), error.message);
133
+ process.exit(1);
134
+ }
135
+ });
136
+
137
+ // Restart command
138
+ program
139
+ .command('restart')
140
+ .description('Restart the daemon')
141
+ .action(async () => {
142
+ try {
143
+ restart();
144
+ console.log(chalk.green('✓ Daemon restarted successfully'));
145
+ } catch (error) {
146
+ console.error(chalk.red('✗ Failed to restart daemon:'), error.message);
147
+ process.exit(1);
148
+ }
149
+ });
150
+
151
+ // Status command
152
+ program
153
+ .command('status')
154
+ .description('Check daemon status')
155
+ .action(async () => {
156
+ try {
157
+ const statusInfo = await status();
158
+
159
+ console.log('\n' + chalk.bold('AI Desk Daemon Status'));
160
+ console.log('─'.repeat(40));
161
+
162
+ if (statusInfo.running) {
163
+ console.log(chalk.green('● Running'));
164
+
165
+ if (statusInfo.health && statusInfo.health.data) {
166
+ const { status: healthStatus, version } = statusInfo.health.data;
167
+ console.log(`Version: ${version || 'unknown'}`);
168
+ console.log(`Status: ${healthStatus}`);
169
+ }
170
+
171
+ console.log(`Port: ${statusInfo.port}`);
172
+ console.log(`Dashboard: http://localhost:${statusInfo.port}/daemon-dashboard.html`);
173
+ } else {
174
+ console.log(chalk.red('○ Stopped'));
175
+ }
176
+
177
+ console.log(`Logs: ${statusInfo.logPath}`);
178
+ console.log('');
179
+ } catch (error) {
180
+ console.error(chalk.red('✗ Failed to get status:'), error.message);
181
+ process.exit(1);
182
+ }
183
+ });
184
+
185
+ // Logs command
186
+ program
187
+ .command('logs')
188
+ .description('View daemon logs')
189
+ .option('-f, --follow', 'Follow log output')
190
+ .option('-n, --lines <number>', 'Number of lines to show', '50')
191
+ .action((options) => {
192
+ const logPath = getLogPath();
193
+
194
+ if (!fs.existsSync(logPath)) {
195
+ console.log(chalk.yellow('No logs found'));
196
+ return;
197
+ }
198
+
199
+ if (options.follow) {
200
+ // Follow logs (tail -f)
201
+ const { spawn } = require('child_process');
202
+ const tail = spawn('tail', ['-f', logPath]);
203
+
204
+ tail.stdout.on('data', (data) => {
205
+ process.stdout.write(data);
206
+ });
207
+
208
+ tail.on('error', (error) => {
209
+ console.error(chalk.red('Failed to follow logs:'), error.message);
210
+ process.exit(1);
211
+ });
212
+ } else {
213
+ // Show last N lines
214
+ const { execSync } = require('child_process');
215
+ try {
216
+ const output = execSync(`tail -n ${options.lines} "${logPath}"`, { encoding: 'utf8' });
217
+ console.log(output);
218
+ } catch (error) {
219
+ console.error(chalk.red('Failed to read logs:'), error.message);
220
+ process.exit(1);
221
+ }
222
+ }
223
+ });
224
+
225
+ program.parse(process.argv);
226
+
227
+ // Show help if no command provided
228
+ if (!process.argv.slice(2).length) {
229
+ program.outputHelp();
230
+ }
231
+
Binary file
Binary file
Binary file
Binary file
package/lib/config.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Configuration management
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { getConfigDir, getConfigPath } = require('./platform');
8
+
9
+ const DEFAULT_PORT = 9527;
10
+
11
+ /**
12
+ * Ensure config directory exists
13
+ */
14
+ function ensureConfigDir() {
15
+ const configDir = getConfigDir();
16
+ const logsDir = path.join(configDir, 'logs');
17
+
18
+ if (!fs.existsSync(configDir)) {
19
+ fs.mkdirSync(configDir, { recursive: true });
20
+ }
21
+
22
+ if (!fs.existsSync(logsDir)) {
23
+ fs.mkdirSync(logsDir, { recursive: true });
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Load config from file
29
+ */
30
+ function loadConfig() {
31
+ ensureConfigDir();
32
+
33
+ const configPath = getConfigPath();
34
+
35
+ if (!fs.existsSync(configPath)) {
36
+ // Create default config
37
+ const defaultConfig = {
38
+ port: DEFAULT_PORT,
39
+ logLevel: 'info',
40
+ autoStart: true
41
+ };
42
+ saveConfig(defaultConfig);
43
+ return defaultConfig;
44
+ }
45
+
46
+ try {
47
+ const content = fs.readFileSync(configPath, 'utf8');
48
+ return JSON.parse(content);
49
+ } catch (error) {
50
+ console.error('Failed to load config:', error.message);
51
+ return { port: DEFAULT_PORT };
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Save config to file
57
+ */
58
+ function saveConfig(config) {
59
+ ensureConfigDir();
60
+
61
+ const configPath = getConfigPath();
62
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
63
+ }
64
+
65
+ /**
66
+ * Get port from config
67
+ */
68
+ function getPort() {
69
+ const config = loadConfig();
70
+ const port = config.port || DEFAULT_PORT;
71
+ // Ensure port is a valid integer
72
+ const portNum = parseInt(port, 10);
73
+ if (isNaN(portNum) || portNum <= 0 || portNum > 65535) {
74
+ console.warn(`Invalid port ${port}, using default ${DEFAULT_PORT}`);
75
+ return DEFAULT_PORT;
76
+ }
77
+ return portNum;
78
+ }
79
+
80
+ /**
81
+ * Set port in config
82
+ */
83
+ function setPort(port) {
84
+ const config = loadConfig();
85
+ // Ensure port is stored as integer
86
+ const portNum = parseInt(port, 10);
87
+ if (isNaN(portNum) || portNum <= 0 || portNum > 65535) {
88
+ throw new Error(`Invalid port number: ${port}`);
89
+ }
90
+ config.port = portNum;
91
+ saveConfig(config);
92
+ }
93
+
94
+ module.exports = {
95
+ ensureConfigDir,
96
+ loadConfig,
97
+ saveConfig,
98
+ getPort,
99
+ setPort,
100
+ getConfigPath, // Re-export from platform.js
101
+ DEFAULT_PORT
102
+ };
103
+
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Daemon process management
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const { spawn, execSync } = require('child_process');
7
+ const http = require('http');
8
+ const { getDaemonBinaryPath, getPidPath, getLogPath } = require('./platform');
9
+ const { getPort, getConfigPath } = require('./config');
10
+
11
+ /**
12
+ * Check if daemon is running
13
+ */
14
+ function isRunning() {
15
+ const pidPath = getPidPath();
16
+
17
+ if (!fs.existsSync(pidPath)) {
18
+ return false;
19
+ }
20
+
21
+ try {
22
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim());
23
+
24
+ // Check if process exists
25
+ process.kill(pid, 0);
26
+ return true;
27
+ } catch (error) {
28
+ // Process doesn't exist
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Get daemon health status
35
+ */
36
+ function getHealth() {
37
+ return new Promise((resolve) => {
38
+ const port = getPort();
39
+ const options = {
40
+ hostname: 'localhost',
41
+ port: port,
42
+ path: '/health',
43
+ method: 'GET',
44
+ timeout: 2000
45
+ };
46
+
47
+ const req = http.request(options, (res) => {
48
+ let data = '';
49
+ res.on('data', (chunk) => { data += chunk; });
50
+ res.on('end', () => {
51
+ try {
52
+ const health = JSON.parse(data);
53
+ resolve(health);
54
+ } catch (error) {
55
+ resolve(null);
56
+ }
57
+ });
58
+ });
59
+
60
+ req.on('error', () => resolve(null));
61
+ req.on('timeout', () => {
62
+ req.destroy();
63
+ resolve(null);
64
+ });
65
+
66
+ req.end();
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Start daemon
72
+ */
73
+ function start() {
74
+ if (isRunning()) {
75
+ throw new Error('Daemon is already running');
76
+ }
77
+
78
+ const binaryPath = getDaemonBinaryPath();
79
+ if (!fs.existsSync(binaryPath)) {
80
+ throw new Error(
81
+ `Daemon binary not found at: ${binaryPath}\n` +
82
+ `This might be a corrupted installation. Try reinstalling:\n` +
83
+ ` npm install -g @ringcentral/ai-desk-daemon --force`
84
+ );
85
+ }
86
+
87
+ // Ensure binary is executable (Unix-like systems)
88
+ if (process.platform !== 'win32') {
89
+ try {
90
+ fs.chmodSync(binaryPath, 0o755);
91
+ } catch (error) {
92
+ // Ignore permission errors
93
+ }
94
+ }
95
+
96
+ const port = getPort();
97
+ const configPath = getConfigPath();
98
+ const logPath = getLogPath();
99
+
100
+ // Ensure port is a number
101
+ const portNumber = typeof port === 'number' ? port : parseInt(port, 10);
102
+ if (isNaN(portNumber)) {
103
+ throw new Error(`Invalid port number: ${port}`);
104
+ }
105
+
106
+ // Ensure log directory exists
107
+ const logDir = require('path').dirname(logPath);
108
+ if (!fs.existsSync(logDir)) {
109
+ fs.mkdirSync(logDir, { recursive: true });
110
+ }
111
+
112
+ // The daemon handles its own logging to file (see daemon/logger.go)
113
+ // It uses io.MultiWriter to write to both stdout and the log file
114
+ // So we should ignore stdout/stderr here to avoid duplication
115
+ const child = spawn(binaryPath, ['--port', portNumber.toString(), '--config', configPath], {
116
+ detached: true,
117
+ stdio: ['ignore', 'ignore', 'ignore'] // Daemon writes to its own log file
118
+ });
119
+
120
+ child.unref();
121
+
122
+ // Save PID
123
+ fs.writeFileSync(getPidPath(), child.pid.toString());
124
+
125
+ console.log(`Daemon started (PID: ${child.pid})`);
126
+ console.log(`Port: ${portNumber}`);
127
+ console.log(`Logs: ${logPath}`);
128
+
129
+ return child.pid;
130
+ }
131
+
132
+ /**
133
+ * Stop daemon
134
+ */
135
+ function stop() {
136
+ const pidPath = getPidPath();
137
+
138
+ if (!fs.existsSync(pidPath)) {
139
+ throw new Error('Daemon is not running');
140
+ }
141
+
142
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim());
143
+
144
+ try {
145
+ process.kill(pid, 'SIGTERM');
146
+ fs.unlinkSync(pidPath);
147
+ console.log('Daemon stopped');
148
+ } catch (error) {
149
+ fs.unlinkSync(pidPath);
150
+ throw new Error(`Failed to stop daemon: ${error.message}`);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Restart daemon
156
+ */
157
+ function restart() {
158
+ if (isRunning()) {
159
+ stop();
160
+ // Wait a bit for graceful shutdown
161
+ execSync('sleep 1');
162
+ }
163
+ start();
164
+ }
165
+
166
+ /**
167
+ * Get daemon status
168
+ */
169
+ async function status() {
170
+ const running = isRunning();
171
+ const health = running ? await getHealth() : null;
172
+
173
+ return {
174
+ running,
175
+ health,
176
+ port: getPort(),
177
+ logPath: getLogPath()
178
+ };
179
+ }
180
+
181
+ module.exports = {
182
+ isRunning,
183
+ getHealth,
184
+ start,
185
+ stop,
186
+ restart,
187
+ status
188
+ };
189
+
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Platform detection and binary path resolution
3
+ */
4
+
5
+ const os = require('os');
6
+ const path = require('path');
7
+
8
+ const VERSION = '1.0.22';
9
+
10
+ /**
11
+ * Detect current platform and architecture
12
+ */
13
+ function detectPlatform() {
14
+ const platform = os.platform();
15
+ const arch = os.arch();
16
+
17
+ let platformKey;
18
+
19
+ // Map Node.js platform to binary directory name
20
+ switch (platform) {
21
+ case 'darwin':
22
+ platformKey = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
23
+ break;
24
+ case 'linux':
25
+ platformKey = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
26
+ break;
27
+ case 'win32':
28
+ platformKey = 'win32-x64';
29
+ break;
30
+ default:
31
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
32
+ }
33
+
34
+ return { platform, arch, platformKey };
35
+ }
36
+
37
+ /**
38
+ * Get daemon binary path from npm package
39
+ */
40
+ function getDaemonBinaryPath() {
41
+ const { platform, platformKey } = detectPlatform();
42
+
43
+ // Binary is in the npm package under dist/<platform>/
44
+ const binaryName = platform === 'win32' ? 'ai-desk-daemon.exe' : 'ai-desk-daemon';
45
+ const binaryPath = path.join(__dirname, '..', 'dist', platformKey, binaryName);
46
+
47
+ return binaryPath;
48
+ }
49
+
50
+ /**
51
+ * Get config directory
52
+ */
53
+ function getConfigDir() {
54
+ return path.join(os.homedir(), '.aidesktop');
55
+ }
56
+
57
+ /**
58
+ * Get config file path
59
+ */
60
+ function getConfigPath() {
61
+ return path.join(getConfigDir(), 'daemon-config.json');
62
+ }
63
+
64
+ /**
65
+ * Get log file path
66
+ */
67
+ function getLogPath() {
68
+ return path.join(getConfigDir(), 'logs', 'daemon.log');
69
+ }
70
+
71
+ /**
72
+ * Get PID file path
73
+ */
74
+ function getPidPath() {
75
+ return path.join(getConfigDir(), 'daemon.pid');
76
+ }
77
+
78
+ module.exports = {
79
+ detectPlatform,
80
+ getDaemonBinaryPath,
81
+ getConfigDir,
82
+ getConfigPath,
83
+ getLogPath,
84
+ getPidPath,
85
+ VERSION
86
+ };
87
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-webui/ai-desk-daemon",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "AI Desk Daemon - CLI tool for managing the AI Desk daemon service",
5
5
  "main": "lib/daemon-manager.js",
6
6
  "bin": {
@@ -24,6 +24,11 @@
24
24
  "files": [
25
25
  "bin/",
26
26
  "lib/",
27
+ "dist/darwin-arm64/",
28
+ "dist/darwin-x64/",
29
+ "dist/linux-arm64/",
30
+ "dist/linux-x64/",
31
+ "dist/win32-x64/",
27
32
  "scripts/postinstall.js",
28
33
  "README.md"
29
34
  ],
@@ -2,12 +2,15 @@
2
2
 
3
3
  /**
4
4
  * Post-install script for @agent-webui/ai-desk-daemon
5
- * This script does nothing - binaries are managed separately
6
5
  *
7
- * In the future, this could download platform-specific binaries from GitHub Releases
6
+ * This script runs after npm install to verify the installation
8
7
  */
9
8
 
10
- console.log('✓ @agent-webui/ai-desk-daemon installed successfully');
9
+ const chalk = require('chalk');
10
+
11
+ console.log('');
12
+ console.log(chalk.green('✓ @agent-webui/ai-desk-daemon installed successfully'));
11
13
  console.log('');
12
- console.log('Note: This is a CLI wrapper. The actual daemon binaries are managed separately.');
14
+ console.log('Run ' + chalk.cyan('aidesk --help') + ' to get started');
13
15
  console.log('');
16
+