foliko 1.1.42 → 1.1.45

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.
@@ -210,7 +210,14 @@
210
210
  "Bash(node -c plugins/qq-plugin.js 2>&1)",
211
211
  "Bash(node -c cli/src/commands/daemon.js 2>&1 && node -c cli/src/index.js 2>&1)",
212
212
  "Bash(node -c examples/daemon.js 2>&1)",
213
- "Bash(node -c cli/src/commands/daemon.js 2>&1)"
213
+ "Bash(node -c cli/src/commands/daemon.js 2>&1)",
214
+ "Bash(node -c cli/src/index.js 2>&1)",
215
+ "Bash(node -c cli/src/daemon.js 2>&1 && node -c cli/src/commands/daemon.js 2>&1)",
216
+ "Bash(cd cli/src && node -e \"console.log\\(require.resolve\\('../src'\\)\\)\")",
217
+ "Bash(cd cli/src && node -e \"console.log\\(require.resolve\\('../../src'\\)\\)\")",
218
+ "Bash(node -e \"console.log\\(require.resolve\\('./src'\\)\\)\")",
219
+ "Bash(cd /d/Code/foliko/cli/src && node -e \"console.log\\(require.resolve\\('../../src'\\)\\)\")",
220
+ "Bash(node -c cli/src/daemon.js 2>&1)"
214
221
  ]
215
222
  }
216
223
  }
@@ -1,24 +1,37 @@
1
1
  /**
2
2
  * Foliko Daemon 命令
3
- * Usage: foliko start | stop | status
3
+ * Usage: foliko start | stop | status [--path=<dir>]
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const { spawn } = require('child_process');
9
+ const dotenv = require('dotenv');
9
10
 
10
- // 使用当前工作目录
11
- const CWD = process.cwd();
12
- const DAEMON_SCRIPT = path.join(__dirname, '../../../examples/daemon.js');
13
- const PID_FILE = path.join(CWD, '.foliko.pid');
14
- const LOG_FILE = path.join(CWD, '.foliko.log');
11
+ // Daemon 脚本路径
12
+ const DAEMON_SCRIPT = path.join(__dirname, '../daemon.js');
13
+
14
+ /**
15
+ * 获取指定目录的 PID 文件路径
16
+ */
17
+ function getPidFile(dir) {
18
+ return path.join(dir, '.foliko.pid');
19
+ }
20
+
21
+ /**
22
+ * 获取指定目录的日志文件路径
23
+ */
24
+ function getLogFile(dir) {
25
+ return path.join(dir, '.foliko.log');
26
+ }
15
27
 
16
28
  /**
17
29
  * 获取 PID 文件中的进程 ID
18
30
  */
19
- function getPid() {
20
- if (fs.existsSync(PID_FILE)) {
21
- return parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
31
+ function getPid(dir) {
32
+ const pidFile = getPidFile(dir);
33
+ if (fs.existsSync(pidFile)) {
34
+ return parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
22
35
  }
23
36
  return null;
24
37
  }
@@ -36,48 +49,88 @@ function isRunning(pid) {
36
49
  }
37
50
  }
38
51
 
52
+ /**
53
+ * 读取 .env 文件并解析为对象
54
+ */
55
+ function parseEnvFile(envPath) {
56
+ if (!fs.existsSync(envPath)) {
57
+ return {};
58
+ }
59
+ const content = fs.readFileSync(envPath, 'utf8');
60
+ const env = {};
61
+ content.split('\n').forEach((line) => {
62
+ line = line.trim();
63
+ if (!line || line.startsWith('#')) return;
64
+ const eqIndex = line.indexOf('=');
65
+ if (eqIndex > 0) {
66
+ const key = line.substring(0, eqIndex).trim();
67
+ let value = line.substring(eqIndex + 1).trim();
68
+ // 去除引号
69
+ if ((value.startsWith('"') && value.endsWith('"')) ||
70
+ (value.startsWith("'") && value.endsWith("'"))) {
71
+ value = value.slice(1, -1);
72
+ }
73
+ env[key] = value;
74
+ }
75
+ });
76
+ return env;
77
+ }
78
+
39
79
  /**
40
80
  * 启动守护进程
41
81
  */
42
- async function startDaemon() {
43
- const existingPid = getPid();
82
+ async function startDaemon(targetDir) {
83
+ const pidFile = getPidFile(targetDir);
84
+ const logFile = getLogFile(targetDir);
85
+ const existingPid = getPid(targetDir);
44
86
 
45
87
  if (existingPid && isRunning(existingPid)) {
46
88
  console.log(`Foliko 已在运行 (PID: ${existingPid})`);
47
89
  return;
48
90
  }
49
91
 
92
+ // 读取 .env 文件(从当前目录)
93
+ const envPath = path.join(process.cwd(), '.env');
94
+ const envVars = parseEnvFile(envPath);
95
+
96
+ // 合并环境变量
97
+ const childEnv = { ...process.env, ...envVars };
98
+ console.log(`已读取环境变量文件: ${envPath}`);
99
+ console.log(`QQ_APP_ID=${envVars.QQ_APP_ID ? '已设置' : '未设置'}`);
100
+
50
101
  // 启动子进程
51
- const child = spawn(process.execPath, [DAEMON_SCRIPT], {
102
+ const child = spawn(process.execPath, [DAEMON_SCRIPT, '--cwd', targetDir], {
52
103
  detached: true,
53
104
  stdio: 'ignore',
54
- cwd: CWD,
105
+ cwd: targetDir,
106
+ env: childEnv, // 传递完整环境变量(包括 .env 中的变量)
55
107
  });
56
108
 
57
- // 重定向输出到日志文件
58
- const outFd = fs.openSync(LOG_FILE, 'a');
59
- fs.writeSync(outFd, `\n=== Foliko started at ${new Date().toISOString()} ===\n`);
109
+ // 写入启动日志
110
+ fs.writeFileSync(logFile, `\n=== Foliko started at ${new Date().toISOString()} ===\n`);
60
111
 
61
112
  // 保存 PID
62
- fs.writeFileSync(PID_FILE, child.pid.toString());
113
+ fs.writeFileSync(pidFile, child.pid.toString());
63
114
 
64
- // 脱离父进程(在 pipe 之前调用)
115
+ // 脱离父进程
65
116
  child.unref();
66
117
 
67
118
  console.log(`Foliko 已启动 (PID: ${child.pid})`);
68
- console.log(`日志文件: ${LOG_FILE}`);
119
+ console.log(`工作目录: ${targetDir}`);
120
+ console.log(`日志文件: ${logFile}`);
69
121
  }
70
122
 
71
123
  /**
72
124
  * 停止守护进程
73
125
  */
74
- async function stopDaemon() {
75
- const pid = getPid();
126
+ async function stopDaemon(targetDir) {
127
+ const pidFile = getPidFile(targetDir);
128
+ const pid = getPid(targetDir);
76
129
 
77
130
  if (!pid || !isRunning(pid)) {
78
131
  console.log('Foliko 未在运行');
79
- if (fs.existsSync(PID_FILE)) {
80
- fs.unlinkSync(PID_FILE);
132
+ if (fs.existsSync(pidFile)) {
133
+ fs.unlinkSync(pidFile);
81
134
  }
82
135
  return;
83
136
  }
@@ -88,15 +141,15 @@ async function stopDaemon() {
88
141
 
89
142
  // 等待进程结束
90
143
  setTimeout(() => {
91
- if (fs.existsSync(PID_FILE)) {
92
- fs.unlinkSync(PID_FILE);
144
+ if (fs.existsSync(pidFile)) {
145
+ fs.unlinkSync(pidFile);
93
146
  }
94
147
  console.log('Foliko 已停止');
95
148
  }, 2000);
96
149
  } catch (err) {
97
150
  console.error(`停止失败: ${err.message}`);
98
- if (fs.existsSync(PID_FILE)) {
99
- fs.unlinkSync(PID_FILE);
151
+ if (fs.existsSync(pidFile)) {
152
+ fs.unlinkSync(pidFile);
100
153
  }
101
154
  }
102
155
  }
@@ -104,8 +157,8 @@ async function stopDaemon() {
104
157
  /**
105
158
  * 查看状态
106
159
  */
107
- async function statusDaemon() {
108
- const pid = getPid();
160
+ async function statusDaemon(targetDir) {
161
+ const pid = getPid(targetDir);
109
162
 
110
163
  if (!pid || !isRunning(pid)) {
111
164
  console.log('Foliko 未在运行');
@@ -113,28 +166,40 @@ async function statusDaemon() {
113
166
  }
114
167
 
115
168
  console.log(`Foliko 正在运行 (PID: ${pid})`);
169
+ console.log(`工作目录: ${targetDir}`);
116
170
  }
117
171
 
118
172
  /**
119
173
  * Daemon 命令入口
120
- * @param {string[]} fullArgs - 完整参数 [command, subCommand]
121
174
  */
122
- async function daemonCommand(fullArgs) {
123
- const command = fullArgs[0] || 'start';
175
+ async function daemonCommand(args) {
176
+ const command = args[0] || 'start';
177
+
178
+ // 解析 --path 参数
179
+ let targetDir = process.cwd();
180
+ for (let i = 1; i < args.length; i++) {
181
+ const arg = args[i];
182
+ if (arg === '--path' && args[i + 1]) {
183
+ targetDir = path.resolve(args[i + 1]);
184
+ i++;
185
+ } else if (arg.startsWith('--path=')) {
186
+ targetDir = path.resolve(arg.split('=')[1]);
187
+ }
188
+ }
124
189
 
125
190
  switch (command) {
126
191
  case 'start':
127
- await startDaemon();
192
+ await startDaemon(targetDir);
128
193
  break;
129
194
  case 'stop':
130
- await stopDaemon();
195
+ await stopDaemon(targetDir);
131
196
  break;
132
197
  case 'status':
133
- await statusDaemon();
198
+ await statusDaemon(targetDir);
134
199
  break;
135
200
  default:
136
- console.log('用法: foliko start | stop | status');
201
+ console.log('用法: foliko start | stop | status [--path=<dir>]');
137
202
  }
138
203
  }
139
204
 
140
- module.exports = { daemonCommand };
205
+ module.exports = { daemonCommand };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Foliko 后台服务入口
3
+ * 使用 framework.bootstrap() 自动加载 .agent/ 目录配置
4
+ * 持续运行,不退出
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // 解析命令行参数 --cwd <path>
11
+ const cwdArg = process.argv.find((arg) => arg === '--cwd');
12
+ const daemonCwd = cwdArg ? process.argv[process.argv.indexOf(cwdArg) + 1] : process.cwd();
13
+
14
+ // 切换到工作目录
15
+ if (daemonCwd !== process.cwd()) {
16
+ process.chdir(daemonCwd);
17
+ }
18
+
19
+ // 日志文件
20
+ const LOG_FILE = path.join(process.cwd(), '.foliko.log');
21
+
22
+ // 覆盖 console.log,同时写日志和终端
23
+ const originalLog = console.log;
24
+ const originalError = console.error;
25
+ console.log = (...args) => {
26
+ const msg = `[${new Date().toISOString()}] ` + args.join(' ');
27
+ fs.appendFileSync(LOG_FILE, msg + '\n');
28
+ originalLog.apply(console, args);
29
+ };
30
+ console.error = (...args) => {
31
+ const msg = `[${new Date().toISOString()}] ERROR ` + args.join(' ');
32
+ fs.appendFileSync(LOG_FILE, msg + '\n');
33
+ originalError.apply(console, args);
34
+ };
35
+
36
+ // 覆盖 process.stdout.write,只写日志(捕获框架日志)
37
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
38
+ process.stdout.write = (chunk) => {
39
+ fs.appendFileSync(LOG_FILE, chunk.toString());
40
+ return originalStdoutWrite(chunk);
41
+ };
42
+
43
+ // 覆盖 process.stderr.write
44
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
45
+ process.stderr.write = (chunk) => {
46
+ fs.appendFileSync(LOG_FILE, chunk.toString());
47
+ return originalStderrWrite(chunk);
48
+ };
49
+
50
+ // 环境变量已在 commands/daemon.js 中通过 spawn 传递
51
+ const { Framework } = require('../../src');
52
+
53
+ async function main() {
54
+ console.log('[Foliko] 启动后台服务...');
55
+
56
+ // 创建框架
57
+ const framework = new Framework({ debug: false });
58
+
59
+ // 使用 bootstrap 自动加载所有默认插件
60
+ await framework.bootstrap({
61
+ agentDir: './.agent',
62
+ });
63
+
64
+ console.log('[Foliko] 框架已就绪');
65
+ console.log('[Foliko] 已加载插件:', framework.pluginManager.getAll().map((p) => p.name));
66
+ console.log('[Foliko] 后台服务运行中 (PID: ' + process.pid + ')');
67
+
68
+ // 优雅退出
69
+ const shutdown = async () => {
70
+ console.log('[Foliko] 正在关闭...');
71
+ await framework.destroy();
72
+ console.log('[Foliko] 已关闭');
73
+ process.exit(0);
74
+ };
75
+
76
+ process.on('SIGINT', shutdown);
77
+ process.on('SIGTERM', shutdown);
78
+ }
79
+
80
+ main().catch((err) => {
81
+ console.log('[Foliko] 启动失败:', err.message);
82
+ process.exit(1);
83
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.42",
3
+ "version": "1.1.45",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -1,51 +0,0 @@
1
- /**
2
- * Foliko 后台服务入口
3
- * 使用 framework.bootstrap() 自动加载 .agent/ 目录配置
4
- * 持续运行,不退出
5
- */
6
-
7
- const { Framework } = require('../src');
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- // 重定向输出到日志文件
12
- const LOG_FILE = path.join(process.cwd(), '.foliko.log');
13
- const outFd = fs.openSync(LOG_FILE, 'a');
14
- function log(...args) {
15
- const msg = `[${new Date().toISOString()}] ` + args.join(' ') + '\n';
16
- fs.writeSync(outFd, msg);
17
- }
18
- process.stdout.write = (chunk) => { log(chunk.toString()); return true; };
19
- process.stderr.write = (chunk) => { log(chunk.toString()); return true; };
20
-
21
- async function main() {
22
- log('[Foliko] 启动后台服务...');
23
-
24
- // 创建框架
25
- const framework = new Framework({ debug: false });
26
-
27
- // 使用 bootstrap 自动加载所有默认插件
28
- await framework.bootstrap({
29
- agentDir: './.agent',
30
- });
31
-
32
- log('[Foliko] 框架已就绪');
33
- log('[Foliko] 已加载插件:', framework.pluginManager.getAll().map((p) => p.name));
34
- log('[Foliko] 后台服务运行中 (PID: ' + process.pid + ')');
35
-
36
- // 优雅退出
37
- const shutdown = async () => {
38
- log('[Foliko] 正在关闭...');
39
- await framework.destroy();
40
- log('[Foliko] 已关闭');
41
- process.exit(0);
42
- };
43
-
44
- process.on('SIGINT', shutdown);
45
- process.on('SIGTERM', shutdown);
46
- }
47
-
48
- main().catch((err) => {
49
- log('[Foliko] 启动失败:', err.message);
50
- process.exit(1);
51
- });