inspiration-agent 0.0.1
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 +368 -0
- package/dist/.env.example +53 -0
- package/dist/README.md +368 -0
- package/dist/agent/ai.agent.js +712 -0
- package/dist/channel/feishu-connection.js +238 -0
- package/dist/channel/feishu.service.js +222 -0
- package/dist/cli.js +659 -0
- package/dist/config/system.prompt.txt +312 -0
- package/dist/index.js +176 -0
- package/dist/model/base-llm.service.js +320 -0
- package/dist/model/deepseek.service.js +18 -0
- package/dist/model/kimi.service.js +19 -0
- package/dist/model/minimax.service.js +35 -0
- package/dist/model/zhipu.service.js +26 -0
- package/dist/services/database.service.js +697 -0
- package/dist/services/memory.manager.js +486 -0
- package/dist/services/message.queue.js +157 -0
- package/dist/tools/browser.tools.js +814 -0
- package/dist/tools/command.tools.js +361 -0
- package/dist/tools/file.tools.js +222 -0
- package/dist/tools/memory.tools.js +393 -0
- package/dist/tools/scheduler.tools.js +559 -0
- package/dist/tools/search.tools.js +208 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/markdown-renderer.js +207 -0
- package/package.json +51 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, exec, fork } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const readline = require("readline");
|
|
8
|
+
const net = require("net");
|
|
9
|
+
|
|
10
|
+
const PACKAGE_JSON = require("../package.json");
|
|
11
|
+
const PID_FILE = path.join(__dirname, "..", "inspiration.pid");
|
|
12
|
+
const CONFIG_DIR = path.join(os.homedir(), ".inspiration");
|
|
13
|
+
const ENV_FILE = path.join(CONFIG_DIR, ".env");
|
|
14
|
+
|
|
15
|
+
function parseArgs() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const command = args[0] || "help";
|
|
18
|
+
const flags = {};
|
|
19
|
+
|
|
20
|
+
for (let i = 1; i < args.length; i++) {
|
|
21
|
+
if (args[i].startsWith("-")) {
|
|
22
|
+
const flag = args[i].replace(/^-+/, "");
|
|
23
|
+
if (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
24
|
+
flags[flag] = args[i + 1];
|
|
25
|
+
i++;
|
|
26
|
+
} else {
|
|
27
|
+
flags[flag] = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { command, flags, args };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureConfigDir() {
|
|
36
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
37
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getPid() {
|
|
42
|
+
if (fs.existsSync(PID_FILE)) {
|
|
43
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
44
|
+
if (Number.isInteger(pid)) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return pid;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
fs.unlinkSync(PID_FILE);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function removePid() {
|
|
58
|
+
if (fs.existsSync(PID_FILE)) {
|
|
59
|
+
fs.unlinkSync(PID_FILE);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isRunning() {
|
|
64
|
+
return getPid() !== null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getEnvPath() {
|
|
68
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
69
|
+
return ENV_FILE;
|
|
70
|
+
}
|
|
71
|
+
const localEnv = path.join(process.cwd(), ".env");
|
|
72
|
+
if (fs.existsSync(localEnv)) {
|
|
73
|
+
return localEnv;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function startDaemon() {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const indexPath = path.join(__dirname, "index.js");
|
|
81
|
+
const envPath = getEnvPath();
|
|
82
|
+
const logDir = path.join(__dirname, "..", "logs");
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(logDir)) {
|
|
85
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const logFile = path.join(logDir, "daemon.log");
|
|
89
|
+
const logFd = fs.openSync(logFile, "a");
|
|
90
|
+
|
|
91
|
+
const env = { ...process.env };
|
|
92
|
+
if (envPath) {
|
|
93
|
+
env.DOTENV_CONFIG_PATH = envPath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const child = spawn(process.execPath, [indexPath], {
|
|
97
|
+
detached: true,
|
|
98
|
+
stdio: ["ignore", logFd, logFd],
|
|
99
|
+
env: env,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
child.unref();
|
|
103
|
+
|
|
104
|
+
child.on("error", (err) => {
|
|
105
|
+
reject(err);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
const timeout = 10000;
|
|
110
|
+
|
|
111
|
+
const checkPid = () => {
|
|
112
|
+
const pid = getPid();
|
|
113
|
+
if (pid) {
|
|
114
|
+
resolve(pid);
|
|
115
|
+
} else if (Date.now() - startTime > timeout) {
|
|
116
|
+
reject(new Error(`启动超时,请检查日志: ${logFile}`));
|
|
117
|
+
} else {
|
|
118
|
+
setTimeout(checkPid, 100);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
setTimeout(checkPid, 300);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function startForeground() {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const indexPath = path.join(__dirname, "index.js");
|
|
129
|
+
const envPath = getEnvPath();
|
|
130
|
+
|
|
131
|
+
const env = { ...process.env };
|
|
132
|
+
if (envPath) {
|
|
133
|
+
env.DOTENV_CONFIG_PATH = envPath;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const child = spawn(process.execPath, [indexPath], {
|
|
137
|
+
stdio: "inherit",
|
|
138
|
+
env: env,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
child.on("error", (err) => {
|
|
142
|
+
reject(err);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
child.on("exit", (code) => {
|
|
146
|
+
resolve(code);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function stopProcess() {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const pid = getPid();
|
|
154
|
+
if (!pid) {
|
|
155
|
+
resolve(false);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
process.kill(pid, "SIGTERM");
|
|
161
|
+
|
|
162
|
+
let attempts = 0;
|
|
163
|
+
const checkStopped = () => {
|
|
164
|
+
try {
|
|
165
|
+
process.kill(pid, 0);
|
|
166
|
+
attempts++;
|
|
167
|
+
if (attempts > 20) {
|
|
168
|
+
process.kill(pid, "SIGKILL");
|
|
169
|
+
removePid();
|
|
170
|
+
resolve(true);
|
|
171
|
+
} else {
|
|
172
|
+
setTimeout(checkStopped, 100);
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
removePid();
|
|
176
|
+
resolve(true);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
setTimeout(checkStopped, 100);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
removePid();
|
|
182
|
+
resolve(false);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function showHelp() {
|
|
188
|
+
console.log(`
|
|
189
|
+
\x1b[1minspiration-agent\x1b[0m - Personal AI Assistant
|
|
190
|
+
|
|
191
|
+
\x1b[1m用法:\x1b[0m
|
|
192
|
+
inspiration <command> [options]
|
|
193
|
+
|
|
194
|
+
\x1b[1m命令:\x1b[0m
|
|
195
|
+
start 启动服务
|
|
196
|
+
-d, --daemon 后台运行
|
|
197
|
+
stop 停止服务
|
|
198
|
+
restart 重启服务
|
|
199
|
+
status 查看服务状态
|
|
200
|
+
version 查看版本号
|
|
201
|
+
onboard 配置向导
|
|
202
|
+
cli 打开命令行交互(需要服务运行中)
|
|
203
|
+
help 显示帮助信息
|
|
204
|
+
|
|
205
|
+
\x1b[1m示例:\x1b[0m
|
|
206
|
+
inspiration start # 前台启动
|
|
207
|
+
inspiration start -d # 后台启动
|
|
208
|
+
inspiration onboard # 运行配置向导
|
|
209
|
+
inspiration status # 查看状态
|
|
210
|
+
inspiration cli # 进入交互模式
|
|
211
|
+
|
|
212
|
+
\x1b[1m配置文件:\x1b[0m
|
|
213
|
+
${ENV_FILE}
|
|
214
|
+
|
|
215
|
+
\x1b[1m更多信息:\x1b[0m
|
|
216
|
+
https://github.com/your-repo/inspiration-agent
|
|
217
|
+
`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function showVersion() {
|
|
221
|
+
console.log(`inspiration-agent v${PACKAGE_JSON.version}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function showStatus() {
|
|
225
|
+
const pid = getPid();
|
|
226
|
+
if (pid) {
|
|
227
|
+
console.log(`\n✅ inspiration-agent 运行中 (PID: ${pid})\n`);
|
|
228
|
+
} else {
|
|
229
|
+
console.log("\n❌ inspiration-agent 未运行");
|
|
230
|
+
console.log(" 使用 'inspiration start' 启动服务\n");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runOnboard() {
|
|
235
|
+
console.log("\n\x1b[1m🔧 inspiration-agent 配置向导\x1b[0m\n");
|
|
236
|
+
|
|
237
|
+
ensureConfigDir();
|
|
238
|
+
|
|
239
|
+
const existingEnv = fs.existsSync(ENV_FILE)
|
|
240
|
+
? fs.readFileSync(ENV_FILE, "utf-8")
|
|
241
|
+
: "";
|
|
242
|
+
|
|
243
|
+
const getExistingValue = (key) => {
|
|
244
|
+
const match = existingEnv.match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
245
|
+
return match ? match[1] : "";
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const rl = readline.createInterface({
|
|
249
|
+
input: process.stdin,
|
|
250
|
+
output: process.stdout,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const question = (prompt, defaultValue = "", required = false) => {
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
const displayPrompt = defaultValue
|
|
256
|
+
? `${prompt} [${defaultValue}]: `
|
|
257
|
+
: required
|
|
258
|
+
? `${prompt} (必填): `
|
|
259
|
+
: `${prompt}: `;
|
|
260
|
+
rl.question(displayPrompt, (answer) => {
|
|
261
|
+
const trimmed = answer.trim();
|
|
262
|
+
if (required && !trimmed && !defaultValue) {
|
|
263
|
+
console.log("此项为必填项,请输入有效值");
|
|
264
|
+
question(prompt, defaultValue, required).then(resolve);
|
|
265
|
+
} else {
|
|
266
|
+
resolve(trimmed || defaultValue);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
let envContent = "";
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
console.log("\x1b[1m========== LLM 提供商配置 ==========\x1b[0m\n");
|
|
276
|
+
|
|
277
|
+
const validProviders = ["deepseek", "kimi", "zhipu", "minimax"];
|
|
278
|
+
let llmProvider = await question(
|
|
279
|
+
"LLM 提供商 (deepseek/kimi/zhipu/minimax)",
|
|
280
|
+
getExistingValue("LLM_PROVIDER") || "deepseek",
|
|
281
|
+
true
|
|
282
|
+
);
|
|
283
|
+
while (!validProviders.includes(llmProvider.toLowerCase())) {
|
|
284
|
+
console.log("无效的提供商,请选择: deepseek, kimi, zhipu, minimax");
|
|
285
|
+
llmProvider = await question(
|
|
286
|
+
"LLM 提供商 (deepseek/kimi/zhipu/minimax)",
|
|
287
|
+
getExistingValue("LLM_PROVIDER") || "deepseek",
|
|
288
|
+
true
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
envContent += `LLM_PROVIDER=${llmProvider}\n\n`;
|
|
292
|
+
|
|
293
|
+
if (llmProvider === "deepseek") {
|
|
294
|
+
console.log("\n\x1b[36mDeepSeek 配置:\x1b[0m");
|
|
295
|
+
const apiKey = await question(
|
|
296
|
+
"API Key",
|
|
297
|
+
getExistingValue("DEEPSEEK_API_KEY"),
|
|
298
|
+
true
|
|
299
|
+
);
|
|
300
|
+
const apiUrl = await question(
|
|
301
|
+
"API URL",
|
|
302
|
+
getExistingValue("DEEPSEEK_API_URL") || "https://api.deepseek.com/v1/chat/completions"
|
|
303
|
+
);
|
|
304
|
+
const model = await question(
|
|
305
|
+
"模型名称",
|
|
306
|
+
getExistingValue("DEEPSEEK_MODEL") || "deepseek-chat"
|
|
307
|
+
);
|
|
308
|
+
envContent += `DEEPSEEK_API_KEY=${apiKey}\n`;
|
|
309
|
+
envContent += `DEEPSEEK_API_URL=${apiUrl}\n`;
|
|
310
|
+
envContent += `DEEPSEEK_MODEL=${model}\n\n`;
|
|
311
|
+
} else if (llmProvider === "kimi") {
|
|
312
|
+
console.log("\n\x1b[36mKimi (月之暗面) 配置:\x1b[0m");
|
|
313
|
+
const apiKey = await question(
|
|
314
|
+
"API Key",
|
|
315
|
+
getExistingValue("KIMI_API_KEY"),
|
|
316
|
+
true
|
|
317
|
+
);
|
|
318
|
+
const apiUrl = await question(
|
|
319
|
+
"API URL",
|
|
320
|
+
getExistingValue("KIMI_API_URL") || "https://api.moonshot.cn/v1/chat/completions"
|
|
321
|
+
);
|
|
322
|
+
const model = await question(
|
|
323
|
+
"模型名称",
|
|
324
|
+
getExistingValue("KIMI_MODEL") || "moonshot-v1-8k"
|
|
325
|
+
);
|
|
326
|
+
envContent += `KIMI_API_KEY=${apiKey}\n`;
|
|
327
|
+
envContent += `KIMI_API_URL=${apiUrl}\n`;
|
|
328
|
+
envContent += `KIMI_MODEL=${model}\n\n`;
|
|
329
|
+
} else if (llmProvider === "zhipu") {
|
|
330
|
+
console.log("\n\x1b[36m智谱 GLM 配置:\x1b[0m");
|
|
331
|
+
const apiKey = await question(
|
|
332
|
+
"API Key",
|
|
333
|
+
getExistingValue("ZHIPU_API_KEY"),
|
|
334
|
+
true
|
|
335
|
+
);
|
|
336
|
+
const apiUrl = await question(
|
|
337
|
+
"API URL",
|
|
338
|
+
getExistingValue("ZHIPU_API_URL") || "https://open.bigmodel.cn/api/paas/v4/chat/completions"
|
|
339
|
+
);
|
|
340
|
+
const model = await question(
|
|
341
|
+
"模型名称",
|
|
342
|
+
getExistingValue("ZHIPU_MODEL") || "glm-4-flash"
|
|
343
|
+
);
|
|
344
|
+
envContent += `ZHIPU_API_KEY=${apiKey}\n`;
|
|
345
|
+
envContent += `ZHIPU_API_URL=${apiUrl}\n`;
|
|
346
|
+
envContent += `ZHIPU_MODEL=${model}\n\n`;
|
|
347
|
+
} else if (llmProvider === "minimax") {
|
|
348
|
+
console.log("\n\x1b[36mMiniMax 配置:\x1b[0m");
|
|
349
|
+
const apiKey = await question(
|
|
350
|
+
"API Key",
|
|
351
|
+
getExistingValue("MINIMAX_API_KEY"),
|
|
352
|
+
true
|
|
353
|
+
);
|
|
354
|
+
const groupId = await question(
|
|
355
|
+
"Group ID",
|
|
356
|
+
getExistingValue("MINIMAX_GROUP_ID"),
|
|
357
|
+
true
|
|
358
|
+
);
|
|
359
|
+
const apiUrl = await question(
|
|
360
|
+
"API URL",
|
|
361
|
+
getExistingValue("MINIMAX_API_URL") || "https://api.minimax.chat/v1/chat/completions"
|
|
362
|
+
);
|
|
363
|
+
const model = await question(
|
|
364
|
+
"模型名称",
|
|
365
|
+
getExistingValue("MINIMAX_MODEL") || "abab6.5s-chat"
|
|
366
|
+
);
|
|
367
|
+
envContent += `MINIMAX_API_KEY=${apiKey}\n`;
|
|
368
|
+
envContent += `MINIMAX_GROUP_ID=${groupId}\n`;
|
|
369
|
+
envContent += `MINIMAX_API_URL=${apiUrl}\n`;
|
|
370
|
+
envContent += `MINIMAX_MODEL=${model}\n\n`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log("\x1b[1m========== 飞书应用配置 ==========\x1b[0m\n");
|
|
374
|
+
|
|
375
|
+
const feishuAppId = await question(
|
|
376
|
+
"飞书 App ID",
|
|
377
|
+
getExistingValue("FEISHU_APP_ID"),
|
|
378
|
+
true
|
|
379
|
+
);
|
|
380
|
+
const feishuAppSecret = await question(
|
|
381
|
+
"飞书 App Secret",
|
|
382
|
+
getExistingValue("FEISHU_APP_SECRET"),
|
|
383
|
+
true
|
|
384
|
+
);
|
|
385
|
+
const feishuVerificationToken = await question(
|
|
386
|
+
"飞书 Verification Token",
|
|
387
|
+
getExistingValue("FEISHU_VERIFICATION_TOKEN"),
|
|
388
|
+
true
|
|
389
|
+
);
|
|
390
|
+
const feishuEncryptKey = await question(
|
|
391
|
+
"飞书 Encrypt Key (可选)",
|
|
392
|
+
getExistingValue("FEISHU_ENCRYPT_KEY")
|
|
393
|
+
);
|
|
394
|
+
const feishuDefaultChatId = await question(
|
|
395
|
+
"飞书默认 Chat ID (可选)",
|
|
396
|
+
getExistingValue("FEISHU_DEFAULT_CHAT_ID")
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
envContent += `FEISHU_APP_ID=${feishuAppId}\n`;
|
|
400
|
+
envContent += `FEISHU_APP_SECRET=${feishuAppSecret}\n`;
|
|
401
|
+
envContent += `FEISHU_VERIFICATION_TOKEN=${feishuVerificationToken}\n`;
|
|
402
|
+
if (feishuEncryptKey) envContent += `FEISHU_ENCRYPT_KEY=${feishuEncryptKey}\n`;
|
|
403
|
+
if (feishuDefaultChatId) envContent += `FEISHU_DEFAULT_CHAT_ID=${feishuDefaultChatId}\n\n`;
|
|
404
|
+
|
|
405
|
+
console.log("\x1b[1m========== 服务配置 ==========\x1b[0m\n");
|
|
406
|
+
|
|
407
|
+
const port = await question(
|
|
408
|
+
"服务端口",
|
|
409
|
+
getExistingValue("PORT") || "3000"
|
|
410
|
+
);
|
|
411
|
+
let enableStream = await question(
|
|
412
|
+
"启用流式输出 (true/false)",
|
|
413
|
+
getExistingValue("ENABLE_STREAM") || "true"
|
|
414
|
+
);
|
|
415
|
+
while (!["true", "false"].includes(enableStream.toLowerCase())) {
|
|
416
|
+
console.log("请输入 true 或 false");
|
|
417
|
+
enableStream = await question(
|
|
418
|
+
"启用流式输出 (true/false)",
|
|
419
|
+
getExistingValue("ENABLE_STREAM") || "true"
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
envContent += `PORT=${port}\n`;
|
|
424
|
+
envContent += `ENABLE_STREAM=${enableStream}\n\n`;
|
|
425
|
+
|
|
426
|
+
console.log("\x1b[1m========== 命令安全配置 ==========\x1b[0m\n");
|
|
427
|
+
|
|
428
|
+
let whitelistEnabled = await question(
|
|
429
|
+
"启用命令白名单 (true/false)",
|
|
430
|
+
getExistingValue("COMMAND_WHITELIST_ENABLED") || "true"
|
|
431
|
+
);
|
|
432
|
+
while (!["true", "false"].includes(whitelistEnabled.toLowerCase())) {
|
|
433
|
+
console.log("请输入 true 或 false");
|
|
434
|
+
whitelistEnabled = await question(
|
|
435
|
+
"启用命令白名单 (true/false)",
|
|
436
|
+
getExistingValue("COMMAND_WHITELIST_ENABLED") || "true"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
envContent += `COMMAND_WHITELIST_ENABLED=${whitelistEnabled}\n`;
|
|
440
|
+
envContent += "\n";
|
|
441
|
+
|
|
442
|
+
console.log("\x1b[1m========== 数据库配置 ==========\x1b[0m\n");
|
|
443
|
+
|
|
444
|
+
const databasePath = await question(
|
|
445
|
+
"数据库路径",
|
|
446
|
+
getExistingValue("DATABASE_PATH") || "./data/ai_agent.db"
|
|
447
|
+
);
|
|
448
|
+
envContent += `DATABASE_PATH=${databasePath}\n`;
|
|
449
|
+
|
|
450
|
+
fs.writeFileSync(ENV_FILE, envContent);
|
|
451
|
+
|
|
452
|
+
console.log("\n\x1b[32m✅ 配置已保存到:\x1b[0m", ENV_FILE);
|
|
453
|
+
console.log("\n现在可以使用 'inspiration start' 启动服务\n");
|
|
454
|
+
} finally {
|
|
455
|
+
rl.close();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const SOCKET_FILE = path.join(__dirname, "..", "inspiration.sock");
|
|
460
|
+
|
|
461
|
+
function sendIPC(request) {
|
|
462
|
+
return new Promise((resolve, reject) => {
|
|
463
|
+
if (!fs.existsSync(SOCKET_FILE)) {
|
|
464
|
+
reject(new Error("服务未运行"));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const client = net.createConnection(SOCKET_FILE, () => {
|
|
469
|
+
client.write(JSON.stringify(request) + "\n");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
let response = "";
|
|
473
|
+
let resolved = false;
|
|
474
|
+
|
|
475
|
+
const timeout = setTimeout(() => {
|
|
476
|
+
if (!resolved) {
|
|
477
|
+
client.destroy();
|
|
478
|
+
reject(new Error("连接超时"));
|
|
479
|
+
}
|
|
480
|
+
}, 300000);
|
|
481
|
+
|
|
482
|
+
client.on("data", (data) => {
|
|
483
|
+
response += data.toString();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
client.on("end", () => {
|
|
487
|
+
resolved = true;
|
|
488
|
+
clearTimeout(timeout);
|
|
489
|
+
try {
|
|
490
|
+
const result = JSON.parse(response.trim());
|
|
491
|
+
resolve(result);
|
|
492
|
+
} catch (e) {
|
|
493
|
+
reject(new Error("解析响应失败"));
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
client.on("error", (err) => {
|
|
498
|
+
resolved = true;
|
|
499
|
+
clearTimeout(timeout);
|
|
500
|
+
reject(new Error(`连接失败: ${err.message}`));
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function runCli() {
|
|
506
|
+
const pid = getPid();
|
|
507
|
+
if (!pid) {
|
|
508
|
+
console.log("\n❌ inspiration-agent 未运行");
|
|
509
|
+
console.log(" 请先使用 'inspiration start' 启动服务\n");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
console.log("\n\x1b[1m💬 inspiration-agent 命令行交互\x1b[0m");
|
|
514
|
+
console.log("输入消息与 AI 交互,输入 'exit' 或 'quit' 退出\n");
|
|
515
|
+
|
|
516
|
+
const TerminalMarkdownRenderer = require("./utils/markdown-renderer");
|
|
517
|
+
const markdownRenderer = new TerminalMarkdownRenderer();
|
|
518
|
+
|
|
519
|
+
const rl = readline.createInterface({
|
|
520
|
+
input: process.stdin,
|
|
521
|
+
output: process.stdout,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const question = (prompt) => {
|
|
525
|
+
return new Promise((resolve) => {
|
|
526
|
+
rl.question(prompt, resolve);
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
while (true) {
|
|
532
|
+
const input = await question("你: ");
|
|
533
|
+
if (!input.trim()) continue;
|
|
534
|
+
if (input.toLowerCase() === "exit" || input.toLowerCase() === "quit") {
|
|
535
|
+
console.log("\n再见!\n");
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (input.startsWith("/")) {
|
|
540
|
+
try {
|
|
541
|
+
const result = await sendIPC({ type: "command", content: input });
|
|
542
|
+
|
|
543
|
+
if (result.type === "response") {
|
|
544
|
+
if (typeof result.content === "object") {
|
|
545
|
+
console.log("\n" + JSON.stringify(result.content, null, 2) + "\n");
|
|
546
|
+
} else {
|
|
547
|
+
console.log(`\n${result.content}\n`);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (input.trim().toLowerCase() === "/shutdown") {
|
|
551
|
+
console.log("CLI 已关闭\n");
|
|
552
|
+
rl.close();
|
|
553
|
+
process.exit(0);
|
|
554
|
+
}
|
|
555
|
+
} else if (result.type === "error") {
|
|
556
|
+
console.error(`\n错误: ${result.message}\n`);
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error(`\n错误: ${error.message}\n`);
|
|
560
|
+
}
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
const result = await sendIPC({ type: "message", content: input });
|
|
566
|
+
|
|
567
|
+
if (result.type === "response") {
|
|
568
|
+
const rendered = markdownRenderer.render(result.content);
|
|
569
|
+
console.log(`\nAI:\n${rendered}\n`);
|
|
570
|
+
} else if (result.type === "error") {
|
|
571
|
+
console.error(`\n错误: ${result.message}\n`);
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.error(`\n错误: ${error.message}\n`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} finally {
|
|
578
|
+
rl.close();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function main() {
|
|
583
|
+
const { command, flags } = parseArgs();
|
|
584
|
+
|
|
585
|
+
switch (command) {
|
|
586
|
+
case "start":
|
|
587
|
+
if (isRunning()) {
|
|
588
|
+
console.log("\n⚠️ inspiration-agent 已在运行中");
|
|
589
|
+
console.log(` 使用 'inspiration status' 查看状态\n`);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (flags.d || flags.daemon) {
|
|
594
|
+
console.log("\n🚀 启动 inspiration-agent (后台模式)...");
|
|
595
|
+
try {
|
|
596
|
+
const pid = await startDaemon();
|
|
597
|
+
console.log(`✅ 服务已启动 (PID: ${pid})\n`);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
console.error(`❌ 启动失败: ${err.message}\n`);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
console.log("\n🚀 启动 inspiration-agent...\n");
|
|
604
|
+
await startForeground();
|
|
605
|
+
}
|
|
606
|
+
break;
|
|
607
|
+
|
|
608
|
+
case "stop":
|
|
609
|
+
if (await stopProcess()) {
|
|
610
|
+
console.log("\n✅ inspiration-agent 已停止\n");
|
|
611
|
+
} else {
|
|
612
|
+
console.log("\n⚠️ inspiration-agent 未运行\n");
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
|
|
616
|
+
case "restart":
|
|
617
|
+
console.log("\n🔄 重启 inspiration-agent...");
|
|
618
|
+
await stopProcess();
|
|
619
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
620
|
+
if (flags.d || flags.daemon) {
|
|
621
|
+
const pid = await startDaemon();
|
|
622
|
+
console.log(`✅ 服务已重启 (PID: ${pid})\n`);
|
|
623
|
+
} else {
|
|
624
|
+
await startForeground();
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
|
|
628
|
+
case "status":
|
|
629
|
+
showStatus();
|
|
630
|
+
console.log();
|
|
631
|
+
break;
|
|
632
|
+
|
|
633
|
+
case "version":
|
|
634
|
+
case "-v":
|
|
635
|
+
case "--version":
|
|
636
|
+
showVersion();
|
|
637
|
+
break;
|
|
638
|
+
|
|
639
|
+
case "onboard":
|
|
640
|
+
await runOnboard();
|
|
641
|
+
break;
|
|
642
|
+
|
|
643
|
+
case "cli":
|
|
644
|
+
await runCli();
|
|
645
|
+
break;
|
|
646
|
+
|
|
647
|
+
case "help":
|
|
648
|
+
case "-h":
|
|
649
|
+
case "--help":
|
|
650
|
+
default:
|
|
651
|
+
showHelp();
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
main().catch((err) => {
|
|
657
|
+
console.error(`错误: ${err.message}`);
|
|
658
|
+
process.exit(1);
|
|
659
|
+
});
|