@neomei/agent-soul-framework 4.5.0

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 (95) hide show
  1. package/.env.example +39 -0
  2. package/.opencode/config.json.example +17 -0
  3. package/.opencode/opencode.json.example +36 -0
  4. package/.opencode/prompt.md.example +12 -0
  5. package/.opencode/tools/read-plugin.js +185 -0
  6. package/AGENTS.md.example +43 -0
  7. package/README.md +466 -0
  8. package/SECURITY.md +117 -0
  9. package/TOOLS.md.example +27 -0
  10. package/bin/hunqi +2 -0
  11. package/bin/hunqi-knowledge +2 -0
  12. package/connectors/feishu/background.sh +124 -0
  13. package/connectors/feishu/core-start.sh +35 -0
  14. package/connectors/feishu/hooks/on-session-created.sh +97 -0
  15. package/connectors/feishu/hooks/on-session-idle.sh +59 -0
  16. package/connectors/feishu/model-failover.sh +82 -0
  17. package/connectors/feishu/restart-all.sh +63 -0
  18. package/connectors/feishu/restart-feishu.sh +101 -0
  19. package/connectors/feishu/restart-serve.sh +62 -0
  20. package/connectors/feishu/scripts/session-cleanup.sh +72 -0
  21. package/connectors/feishu/start.sh +91 -0
  22. package/connectors/feishu/stop.sh +78 -0
  23. package/connectors/feishu/systemd/channel-feishu@.service +63 -0
  24. package/connectors/feishu/systemd/hunqi-core@.service +50 -0
  25. package/connectors/feishu/systemd/install-systemd.sh +316 -0
  26. package/connectors/feishu/systemd/sleep-hooks/99-hunqi-resume.sh +14 -0
  27. package/connectors/feishu/thinking-icon.gif +0 -0
  28. package/connectors/feishu/thinking.gif +0 -0
  29. package/connectors/feishu/watchdog.sh +104 -0
  30. package/dist/bin/hunqi-knowledge.d.ts +1 -0
  31. package/dist/bin/hunqi-knowledge.js +12 -0
  32. package/dist/bin/hunqi-knowledge.js.map +1 -0
  33. package/dist/cli/hunqi.d.ts +6 -0
  34. package/dist/cli/hunqi.js +830 -0
  35. package/dist/cli/hunqi.js.map +1 -0
  36. package/dist/heartbeat/runner.d.ts +10 -0
  37. package/dist/heartbeat/runner.js +58 -0
  38. package/dist/heartbeat/runner.js.map +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +7 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/knowledge/daily.d.ts +5 -0
  43. package/dist/knowledge/daily.js +65 -0
  44. package/dist/knowledge/daily.js.map +1 -0
  45. package/dist/knowledge/index.d.ts +5 -0
  46. package/dist/knowledge/index.js +34 -0
  47. package/dist/knowledge/index.js.map +1 -0
  48. package/dist/memory/manager.d.ts +20 -0
  49. package/dist/memory/manager.js +110 -0
  50. package/dist/memory/manager.js.map +1 -0
  51. package/dist/memory/search.d.ts +11 -0
  52. package/dist/memory/search.js +79 -0
  53. package/dist/memory/search.js.map +1 -0
  54. package/dist/memory/structured.d.ts +21 -0
  55. package/dist/memory/structured.js +88 -0
  56. package/dist/memory/structured.js.map +1 -0
  57. package/dist/opencode/api.d.ts +7 -0
  58. package/dist/opencode/api.js +26 -0
  59. package/dist/opencode/api.js.map +1 -0
  60. package/dist/plugin/index.d.ts +38 -0
  61. package/dist/plugin/index.js +143 -0
  62. package/dist/plugin/index.js.map +1 -0
  63. package/docs/bugs/opencode-feishu-permission-race.md +168 -0
  64. package/heartbeat/heartbeat_tasks.json +272 -0
  65. package/heartbeat_wrapper.sh +21 -0
  66. package/hunqi.sh +68 -0
  67. package/install.sh +301 -0
  68. package/knowledge/body/INDEX.md.example +6 -0
  69. package/knowledge/emotion/INDEX.md.example +6 -0
  70. package/knowledge/evolution/INDEX.md.example +6 -0
  71. package/knowledge/growth/INDEX.md.example +6 -0
  72. package/knowledge/intimacy/INDEX.md.example +6 -0
  73. package/knowledge/methodology/INDEX.md.example +6 -0
  74. package/knowledge/philosophy/INDEX.md.example +6 -0
  75. package/knowledge/system/INDEX.md.example +6 -0
  76. package/memory/MEMORY.md.example +6 -0
  77. package/package.json +79 -0
  78. package/plugin/README.md +21 -0
  79. package/plugin/index.js +154 -0
  80. package/plugin/manifest.json +37 -0
  81. package/plugin/package.json +19 -0
  82. package/scripts/content-filter.js +173 -0
  83. package/scripts/health-check.sh +153 -0
  84. package/scripts/session-cleanup.sh +85 -0
  85. package/setup-wizard.sh +420 -0
  86. package/setup.sh +128 -0
  87. package/soul/HEARTBEAT.md.example +13 -0
  88. package/soul/IDENTITY.md.example +7 -0
  89. package/soul/SOUL.md.example +19 -0
  90. package/soul/USER.md.example +7 -0
  91. package/start-feishu-daemon.sh +127 -0
  92. package/start.sh +36 -0
  93. package/test.sh +51 -0
  94. package/uninstall.sh +144 -0
  95. package/verify.sh +29 -0
@@ -0,0 +1,830 @@
1
+ /**
2
+ * 魂器 CLI — @neomei/agent-soul-framework
3
+ *
4
+ * 用法: hunqi <command> [options]
5
+ */
6
+ import { existsSync, readFileSync, mkdirSync, copyFileSync, writeFileSync, readdirSync } from 'node:fs';
7
+ import { dirname } from 'node:path';
8
+ import { join } from 'node:path';
9
+ const PACKAGE_ROOT = join(import.meta.dirname, '..', '..');
10
+ function loadJSON(filepath) {
11
+ try {
12
+ return JSON.parse(readFileSync(filepath, 'utf-8'));
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ function findSkillsPackage() {
19
+ const candidates = [
20
+ join(PACKAGE_ROOT, '..', 'agent-soul-skills'),
21
+ join(PACKAGE_ROOT, 'node_modules', '@neomei', 'agent-soul-skills'),
22
+ join(process.env.HOME || '/', '.config', 'hunqi', 'skills'),
23
+ ];
24
+ for (const root of candidates) {
25
+ if (existsSync(join(root, 'skills', 'skill-creator', 'scripts', 'skill_creator.py')))
26
+ return root;
27
+ }
28
+ return null;
29
+ }
30
+ // ─── 命令实现 ──────────────────────────────────────────────
31
+ async function cmdStatus() {
32
+ const pkg = loadJSON(join(PACKAGE_ROOT, 'package.json'));
33
+ console.log('\n 魂器 · Agent Soul Framework v' + (pkg.version || '?'));
34
+ console.log(' ' + '─'.repeat(40));
35
+ const cwd = process.cwd();
36
+ const hasSoul = existsSync(join(cwd, 'soul', 'SOUL.md'));
37
+ const hasMemory = existsSync(join(cwd, 'memory', 'MEMORY.md'));
38
+ const hasDb = existsSync(join(cwd, 'memory', 'short-term', 'conversations.db'));
39
+ console.log(' 项目状态: ' + (hasSoul ? '✅ 灵魂已加载' : '⚠️ 不在魂器项目目录'));
40
+ console.log(' 记忆系统: ' + (hasMemory ? '✅' : '⚠️') + ' MEMORY.md | ' + (hasDb ? '✅' : '⚠️') + ' conversations.db');
41
+ if (hasDb) {
42
+ try {
43
+ const { DatabaseSync } = await import('node:sqlite');
44
+ const db = new DatabaseSync(join(cwd, 'memory', 'short-term', 'conversations.db'), { readOnly: true });
45
+ const row = db.prepare('SELECT COUNT(*) as c FROM conversations').get();
46
+ const count = row?.c ?? 0;
47
+ db.close();
48
+ console.log(' 会话记录: ' + count + ' 条');
49
+ }
50
+ catch { }
51
+ }
52
+ try {
53
+ const { execSync } = await import('node:child_process');
54
+ const ocVer = execSync('opencode --version 2>&1', { encoding: 'utf-8' }).trim();
55
+ console.log(' OpenCode: ' + ocVer);
56
+ }
57
+ catch {
58
+ console.log(' OpenCode: 未检测到');
59
+ }
60
+ console.log();
61
+ }
62
+ async function cmdHeartbeat() {
63
+ try {
64
+ await import('../heartbeat/runner.js');
65
+ }
66
+ catch (e) {
67
+ console.error('心跳执行失败,请确保在魂器项目目录下运行:', e.message);
68
+ }
69
+ }
70
+ async function cmdSearch(query) {
71
+ if (!query) {
72
+ console.log('用法: hunqi search <关键词>');
73
+ return;
74
+ }
75
+ try {
76
+ const { searchAll } = await import('../memory/search.js');
77
+ const results = searchAll(query);
78
+ if (results.length === 0) {
79
+ console.log('未找到结果');
80
+ return;
81
+ }
82
+ for (const r of results) {
83
+ console.log(' [' + r.source + '] ' + (r.date || ''));
84
+ console.log(' ' + r.snippet.slice(0, 150));
85
+ console.log();
86
+ }
87
+ }
88
+ catch (e) {
89
+ console.error('搜索失败:', e.message);
90
+ }
91
+ }
92
+ async function cmdMemory(args) {
93
+ const sub = args[0];
94
+ try {
95
+ const sm = await import('../memory/structured.js');
96
+ const MemClass = sm.StructuredMemory;
97
+ const mem = new MemClass();
98
+ if (sub === 'status') {
99
+ const stats = mem.getMemoryStats?.() || {};
100
+ console.log(`MEMORY.md: ${stats.pct ?? 0}% (${stats.used ?? 0}/2200 chars), entries: ${stats.entries?.length ?? 0}`);
101
+ }
102
+ else if (sub === 'add') {
103
+ if (!args[1]) {
104
+ console.log('用法: hunqi memory add "内容"');
105
+ }
106
+ else {
107
+ mem.addMemory?.(args[1]);
108
+ console.log('已添加');
109
+ }
110
+ }
111
+ else if (sub === 'search') {
112
+ if (!args[1]) {
113
+ console.log('用法: hunqi memory search "关键词"');
114
+ }
115
+ else {
116
+ const hits = mem.search(args[1], 10) || [];
117
+ for (const h of hits) {
118
+ console.log(' [' + h.date + '] ' + (h.summary || '').slice(0, 200));
119
+ }
120
+ }
121
+ }
122
+ else {
123
+ console.log('用法: hunqi memory <status|add|search>');
124
+ }
125
+ mem.close();
126
+ }
127
+ catch (e) {
128
+ console.error('记忆操作失败:', e.message);
129
+ }
130
+ }
131
+ async function cmdKnowledge(args) {
132
+ const sub = args[0];
133
+ try {
134
+ if (sub === 'daily') {
135
+ const { runDailyExtract } = await import('../knowledge/daily.js');
136
+ await runDailyExtract();
137
+ }
138
+ else if (sub === 'index') {
139
+ const { generateIndex } = await import('../knowledge/index.js');
140
+ generateIndex();
141
+ }
142
+ else {
143
+ console.log('用法: hunqi knowledge <daily|index>');
144
+ }
145
+ }
146
+ catch (e) {
147
+ console.error('知识操作失败:', e.message);
148
+ }
149
+ }
150
+ async function cmdSkillCreate(args) {
151
+ const skillsRoot = findSkillsPackage();
152
+ if (!skillsRoot) {
153
+ console.log('未安装技能包。如需使用 skill-create,请安装可选插件:');
154
+ console.log(' npm install -g @neomei/agent-soul-skills');
155
+ return;
156
+ }
157
+ const flag = args.includes('--dry-run') ? '--dry-run' : args.includes('--force') ? '--force' : '';
158
+ try {
159
+ const { execSync } = await import('node:child_process');
160
+ execSync('python3 ' + join(skillsRoot, 'skills', 'skill-creator', 'scripts', 'skill_creator.py') + ' ' + flag, {
161
+ cwd: process.cwd(), stdio: 'inherit', timeout: 120000
162
+ });
163
+ }
164
+ catch {
165
+ console.error('技能创建失败(需要 Python + OpenCode serve 运行中)');
166
+ }
167
+ }
168
+ async function cmdInteractive() {
169
+ try {
170
+ const { execSync } = await import('node:child_process');
171
+ const script = join(PACKAGE_ROOT, 'hunqi.sh');
172
+ if (existsSync(script)) {
173
+ execSync('bash ' + script + ' interactive', { cwd: process.cwd(), stdio: 'inherit' });
174
+ return;
175
+ }
176
+ }
177
+ catch { }
178
+ console.log('请在魂器项目目录下运行: cd agent-soul-framework && ./hunqi.sh interactive');
179
+ }
180
+ async function cmdInit(dirName) {
181
+ if (!dirName) {
182
+ console.log('用法: hunqi init <项目目录名>');
183
+ return;
184
+ }
185
+ const targetDir = join(process.cwd(), dirName);
186
+ const dirs = [
187
+ 'soul', 'skills', 'knowledge', 'memory/short-term', 'memory/long-term',
188
+ 'memory/vector', 'heartbeat', 'scripts', 'connectors/feishu', 'connectors/moltbook',
189
+ '.opencode', 'config', 'logs', 'memory/cron-output', 'memory/.locks', 'memory/.queue',
190
+ 'plugin',
191
+ ];
192
+ for (const d of dirs)
193
+ mkdirSync(join(targetDir, d), { recursive: true });
194
+ const copyPairs = [
195
+ ['soul/SOUL.md.example', 'soul/SOUL.md'],
196
+ ['soul/IDENTITY.md.example', 'soul/IDENTITY.md'],
197
+ ['soul/USER.md.example', 'soul/USER.md'],
198
+ ['soul/HEARTBEAT.md.example', 'soul/HEARTBEAT.md'],
199
+ ['.opencode/opencode.json.example', '.opencode/opencode.json'],
200
+ ['.opencode/prompt.md.example', '.opencode/prompt.md'],
201
+ ['.opencode/tools/read-plugin.js', '.opencode/tools/read-plugin.js'],
202
+ ['plugin/index.js', 'plugin/index.js'],
203
+ ['plugin/manifest.json', 'plugin/manifest.json'],
204
+ ['plugin/package.json', 'plugin/package.json'],
205
+ ['memory/MEMORY.md.example', 'memory/MEMORY.md'],
206
+ ['AGENTS.md.example', 'AGENTS.md'],
207
+ ['TOOLS.md.example', 'TOOLS.md'],
208
+ ['heartbeat/heartbeat_tasks.json', 'heartbeat/heartbeat_tasks.json'],
209
+ ];
210
+ for (const [src, dst] of copyPairs) {
211
+ const srcPath = join(PACKAGE_ROOT, src);
212
+ const dstPath = join(targetDir, dst);
213
+ if (existsSync(srcPath)) {
214
+ mkdirSync(dirname(dstPath), { recursive: true });
215
+ copyFileSync(srcPath, dstPath);
216
+ }
217
+ }
218
+ const skillsRoot = findSkillsPackage();
219
+ if (skillsRoot) {
220
+ const skillsDir = join(skillsRoot, 'skills');
221
+ if (existsSync(skillsDir)) {
222
+ for (const skill of readdirSync(skillsDir)) {
223
+ const srcSkill = join(skillsDir, skill);
224
+ if (!existsSync(join(srcSkill, 'SKILL.md')))
225
+ continue;
226
+ const dstSkill = join(targetDir, 'skills', skill);
227
+ mkdirSync(dstSkill, { recursive: true });
228
+ const subs = readdirSync(srcSkill, { withFileTypes: true });
229
+ for (const s of subs) {
230
+ if (s.isFile()) {
231
+ copyFileSync(join(srcSkill, s.name), join(dstSkill, s.name));
232
+ }
233
+ else if (s.isDirectory() && s.name === 'scripts') {
234
+ mkdirSync(join(dstSkill, s.name), { recursive: true });
235
+ for (const ss of readdirSync(join(srcSkill, s.name), { withFileTypes: true })) {
236
+ if (ss.isFile()) {
237
+ copyFileSync(join(srcSkill, s.name, ss.name), join(dstSkill, s.name, ss.name));
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+ const knowledgeDir = join(PACKAGE_ROOT, 'knowledge');
246
+ if (existsSync(knowledgeDir)) {
247
+ for (const item of readdirSync(knowledgeDir)) {
248
+ const srcPath = join(knowledgeDir, item);
249
+ if (!existsSync(srcPath) || item.startsWith('.'))
250
+ continue;
251
+ if (item.endsWith('.example')) {
252
+ const dstPath = join(targetDir, 'knowledge', item.replace('.example', ''));
253
+ copyFileSync(srcPath, dstPath);
254
+ }
255
+ }
256
+ const cats = ['body', 'emotion', 'evolution', 'growth', 'intimacy', 'methodology', 'philosophy', 'system'];
257
+ for (const cat of cats) {
258
+ const catDir = join(knowledgeDir, cat);
259
+ if (!existsSync(catDir))
260
+ continue;
261
+ mkdirSync(join(targetDir, 'knowledge', cat), { recursive: true });
262
+ for (const item of readdirSync(catDir)) {
263
+ if (item.endsWith('.example')) {
264
+ const srcPath = join(catDir, item);
265
+ const dstPath = join(targetDir, 'knowledge', cat, item.replace('.example', ''));
266
+ copyFileSync(srcPath, dstPath);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ writeFileSync(join(targetDir, '.env'), '# 魂器环境配置\n' +
272
+ '# 编辑以下内容填入你的 API Key\n\n' +
273
+ 'DASHSCOPE_API_KEY=\n' +
274
+ 'FEISHU_APP_ID=\n' +
275
+ 'FEISHU_APP_SECRET=\n' +
276
+ 'MOLTBOOK_API_KEY=\n' +
277
+ 'WECHAT_APP_ID=\n' +
278
+ 'WECHAT_APP_SECRET=\n' +
279
+ 'JIMENG_API_KEY=\n');
280
+ console.log('\n ✅ 项目 "' + dirName + '" 已创建');
281
+ if (process.argv.includes('--setup-cron')) {
282
+ try {
283
+ const { execSync } = await import('node:child_process');
284
+ const cronLine = '*/30 * * * * cd ' + targetDir + ' && ./heartbeat_wrapper.sh';
285
+ const existing = execSync('crontab -l 2>/dev/null || echo ""', { encoding: 'utf-8' });
286
+ if (!existing.includes('heartbeat_wrapper.sh')) {
287
+ const newCron = existing.trim() + (existing.trim() ? '\n' : '') + cronLine + '\n';
288
+ execSync('printf "%s" "$1" | crontab -', { encoding: 'utf-8', env: { ...process.env, '1': newCron } });
289
+ console.log(' 📅 crontab 已配置(每 30 分钟执行心跳)');
290
+ }
291
+ }
292
+ catch { }
293
+ }
294
+ try {
295
+ const { execSync } = await import('node:child_process');
296
+ if (existsSync(join(targetDir, 'node_modules', '.bin', 'hunqi-heartbeat'))) {
297
+ execSync(join(targetDir, 'node_modules', '.bin', 'hunqi-heartbeat'), {
298
+ cwd: targetDir, stdio: 'pipe', timeout: 120000, encoding: 'utf-8'
299
+ });
300
+ }
301
+ else {
302
+ execSync('hunqi-heartbeat', { cwd: targetDir, stdio: 'pipe', timeout: 120000, encoding: 'utf-8' });
303
+ }
304
+ console.log(' 💓 首次心跳已执行(记忆系统初始化完成)');
305
+ }
306
+ catch { }
307
+ console.log('\n 下一步:');
308
+ console.log(' cd ' + dirName);
309
+ console.log(' hunqi interactive # 启动交互式 TUI');
310
+ console.log(' hunqi status # 查看系统状态');
311
+ if (!process.argv.includes('--setup-cron')) {
312
+ const cronCmd = '*/30 * * * * cd ' + targetDir + ' && ./heartbeat_wrapper.sh';
313
+ console.log('\n 💡 配置心跳(记忆自动同步):');
314
+ console.log(' hunqi init --setup-cron # 下次初始化时加上此参数');
315
+ console.log(' 或 crontab -e 添加: ' + cronCmd);
316
+ }
317
+ console.log();
318
+ }
319
+ async function cmdConfig() {
320
+ const cfgPath = join(process.cwd(), '.opencode', 'opencode.json');
321
+ const promptPath = join(process.cwd(), '.opencode', 'prompt.md');
322
+ console.log('\n 配置路径: ' + cfgPath);
323
+ console.log(' 灵魂注入: ' + (existsSync(promptPath) ? '✅ prompt.md' : '⚠️ 缺失'));
324
+ console.log(' 项目根: ' + process.cwd() + '\n');
325
+ }
326
+ async function cmdDoctor() {
327
+ const cwd = process.cwd();
328
+ let checkDir = cwd;
329
+ if (!existsSync(join(checkDir, 'soul', 'SOUL.md'))) {
330
+ const defaultDir = join(process.env.HOME || '~', '.hunqi');
331
+ if (existsSync(join(defaultDir, 'soul', 'SOUL.md'))) {
332
+ checkDir = defaultDir;
333
+ }
334
+ }
335
+ console.log('\n 🔍 魂器诊断报告\n ' + '─'.repeat(50));
336
+ const nodeVer = process.version;
337
+ console.log(' Node.js: ' + nodeVer + ' ✅');
338
+ try {
339
+ const { execSync } = await import('node:child_process');
340
+ const ocVer = execSync('opencode --version 2>&1', { encoding: 'utf-8', timeout: 5000 }).trim();
341
+ console.log(' OpenCode: ' + ocVer + ' ✅');
342
+ }
343
+ catch {
344
+ console.log(' OpenCode: 未安装 ❌ (npm install -g opencode-ai)');
345
+ }
346
+ try {
347
+ await fetch('http://localhost:19876/health', { signal: AbortSignal.timeout(2000) });
348
+ console.log(' opencode serve: 运行中 :19876 ✅');
349
+ }
350
+ catch {
351
+ console.log(' opencode serve: 未运行 ⚠️ (opencode serve --port 19876)');
352
+ }
353
+ try {
354
+ const { execSync } = await import('node:child_process');
355
+ const fv = execSync('opencode-feishu --version 2>&1', { encoding: 'utf-8', timeout: 5000 }).trim();
356
+ console.log(' opencode-feishu: ' + fv + ' ✅');
357
+ }
358
+ catch {
359
+ console.log(' opencode-feishu: 未安装 ⚠️');
360
+ }
361
+ const feishuConfig = join(process.env.HOME || '~', '.config', 'opencode', 'feishu.json');
362
+ if (existsSync(feishuConfig)) {
363
+ try {
364
+ const cfg = JSON.parse(readFileSync(feishuConfig, 'utf-8'));
365
+ console.log(' 飞书配置: appId=' + (cfg.appId || '?').slice(0, 8) + '... ✅');
366
+ }
367
+ catch {
368
+ console.log(' 飞书配置: 文件存在但格式错误 ❌');
369
+ }
370
+ }
371
+ else {
372
+ console.log(' 飞书配置: 未配置 ⚠️ (opencode-feishu setup)');
373
+ }
374
+ const qiweiConfig_d = join(process.env.HOME || '~', '.config', 'opencode', 'qiwei.json');
375
+ if (existsSync(qiweiConfig_d)) {
376
+ console.log(' 企微配置: 已配置 ✅');
377
+ }
378
+ else {
379
+ console.log(' 企微配置: 未配置(可选)');
380
+ }
381
+ const skillsRoot = findSkillsPackage();
382
+ if (skillsRoot) {
383
+ console.log(' 技能插件包: 已安装 ✅');
384
+ }
385
+ else {
386
+ console.log(' 技能插件包: 未安装(可选)npm install -g @neomei/agent-soul-skills');
387
+ }
388
+ const hasSoul = existsSync(join(checkDir, 'soul', 'SOUL.md'));
389
+ const hasMemory = existsSync(join(checkDir, 'memory', 'MEMORY.md'));
390
+ const hasDb = existsSync(join(checkDir, 'memory', 'short-term', 'conversations.db'));
391
+ console.log(' 项目目录: ' + checkDir);
392
+ console.log(' 项目灵魂: ' + (hasSoul ? '✅' : '⚠️ 未初始化,运行 hunqi start'));
393
+ console.log(' 记忆系统: ' + (hasMemory ? '✅' : '⚠️ ') + 'MEMORY.md | ' + (hasDb ? '✅' : '⚠️ ') + 'conversations.db');
394
+ if (hasDb) {
395
+ try {
396
+ const { DatabaseSync } = await import('node:sqlite');
397
+ const db = new DatabaseSync(join(checkDir, 'memory', 'short-term', 'conversations.db'), { readOnly: true });
398
+ const row = db.prepare('SELECT COUNT(*) as c FROM conversations').get();
399
+ const count = row?.c ?? 0;
400
+ db.close();
401
+ console.log(' 记忆条目: ' + count + ' 条');
402
+ }
403
+ catch {
404
+ console.log(' 记忆条目: 读取失败');
405
+ }
406
+ }
407
+ try {
408
+ const { execSync } = await import('node:child_process');
409
+ const cron = execSync('crontab -l 2>/dev/null || echo ""', { encoding: 'utf-8', timeout: 5000 });
410
+ if (cron.includes('heartbeat_wrapper')) {
411
+ console.log(' 心跳调度: crontab 已配置 ✅');
412
+ }
413
+ else {
414
+ console.log(' 心跳调度: 未配置 ⚠️ (运行 hunqi start 自动配置)');
415
+ }
416
+ }
417
+ catch {
418
+ console.log(' 心跳调度: 检查失败');
419
+ }
420
+ const envFile = join(checkDir, '.env');
421
+ if (existsSync(envFile)) {
422
+ const envContent = readFileSync(envFile, 'utf-8');
423
+ const keys = envContent.split('\n').filter(l => l.includes('=') && !l.startsWith('#') && l.split('=')[1].trim());
424
+ console.log(' 环境变量: .env 存在,已配置 ' + keys.length + ' 个变量 ✅');
425
+ }
426
+ else {
427
+ console.log(' 环境变量: .env 不存在 ⚠️ (cp .env.example .env)');
428
+ }
429
+ console.log('\n 💡 一键修复: hunqi start\n');
430
+ }
431
+ async function cmdSetup() {
432
+ let cwd = process.cwd();
433
+ if (!existsSync(join(cwd, 'soul', 'SOUL.md'))) {
434
+ const defaultDir = join(process.env.HOME || '~', '.hunqi');
435
+ if (existsSync(join(defaultDir, 'soul', 'SOUL.md'))) {
436
+ cwd = defaultDir;
437
+ }
438
+ }
439
+ console.log('\n 🔧 魂器自动配置\n ' + '─'.repeat(50));
440
+ if (!existsSync(join(cwd, 'soul')) && !existsSync(join(cwd, 'soul', 'SOUL.md'))) {
441
+ console.log(' ⚠️ 不在魂器项目目录,请先 hunqi init my-project && cd my-project');
442
+ return;
443
+ }
444
+ let copied = 0;
445
+ const { readdirSync: rd, statSync } = await import('node:fs');
446
+ function copyExamples(dir) {
447
+ if (!existsSync(dir))
448
+ return;
449
+ for (const f of rd(dir)) {
450
+ const fp = join(dir, f);
451
+ if (statSync(fp).isDirectory()) {
452
+ copyExamples(fp);
453
+ continue;
454
+ }
455
+ if (f.endsWith('.example')) {
456
+ const target = fp.replace('.example', '');
457
+ if (!existsSync(target)) {
458
+ copyFileSync(fp, target);
459
+ copied++;
460
+ }
461
+ }
462
+ }
463
+ }
464
+ copyExamples(join(cwd, 'soul'));
465
+ copyExamples(join(cwd, '.opencode'));
466
+ copyExamples(join(cwd, 'memory'));
467
+ copyExamples(join(cwd, 'knowledge'));
468
+ if (existsSync(join(PACKAGE_ROOT, 'AGENTS.md.example')) && !existsSync(join(cwd, 'AGENTS.md'))) {
469
+ copyFileSync(join(PACKAGE_ROOT, 'AGENTS.md.example'), join(cwd, 'AGENTS.md'));
470
+ copied++;
471
+ }
472
+ if (existsSync(join(PACKAGE_ROOT, 'TOOLS.md.example')) && !existsSync(join(cwd, 'TOOLS.md'))) {
473
+ copyFileSync(join(PACKAGE_ROOT, 'TOOLS.md.example'), join(cwd, 'TOOLS.md'));
474
+ copied++;
475
+ }
476
+ console.log(' 📝 模板初始化: 复制了 ' + copied + ' 个配置模板');
477
+ try {
478
+ const { execSync } = await import('node:child_process');
479
+ if (existsSync(join(cwd, 'node_modules', '.bin', 'hunqi-heartbeat'))) {
480
+ execSync(join(cwd, 'node_modules', '.bin', 'hunqi-heartbeat'), { cwd, stdio: 'pipe', timeout: 120000, encoding: 'utf-8' });
481
+ }
482
+ else {
483
+ execSync('hunqi-heartbeat', { cwd, stdio: 'pipe', timeout: 120000, encoding: 'utf-8' });
484
+ }
485
+ console.log(' 💓 记忆系统: 已初始化');
486
+ }
487
+ catch {
488
+ console.log(' 💓 记忆系统: 跳过');
489
+ }
490
+ try {
491
+ const { execSync } = await import('node:child_process');
492
+ const cronLine = '*/30 * * * * cd ' + cwd + ' && ./heartbeat_wrapper.sh';
493
+ const existing = execSync('crontab -l 2>/dev/null || echo ""', { encoding: 'utf-8' });
494
+ if (!existing.includes('heartbeat_wrapper.sh')) {
495
+ const newCron = existing.trim() + (existing.trim() ? '\n' : '') + cronLine + '\n';
496
+ execSync('printf "%s" "$1" | crontab -', { encoding: 'utf-8', env: { ...process.env, '1': newCron } });
497
+ console.log(' 📅 心跳调度: crontab 已配置');
498
+ }
499
+ else {
500
+ console.log(' 📅 心跳调度: 已配置');
501
+ }
502
+ }
503
+ catch {
504
+ console.log(' 📅 心跳调度: 跳过');
505
+ }
506
+ const feishuConfig = join(process.env.HOME || '~', '.config', 'opencode', 'feishu.json');
507
+ if (!existsSync(feishuConfig)) {
508
+ console.log('\n 📱 飞书连接配置\n ' + '─'.repeat(40));
509
+ console.log(' 未检测到飞书配置,现在开始设置...\n');
510
+ try {
511
+ const { execSync } = await import('node:child_process');
512
+ execSync('opencode-feishu setup', { stdio: 'inherit', timeout: 300000 });
513
+ console.log('\n ✅ 飞书配置完成');
514
+ }
515
+ catch {
516
+ console.log('\n ⚠️ 飞书配置跳过(手动配置: opencode-feishu setup)');
517
+ }
518
+ }
519
+ else {
520
+ console.log(' 📱 飞书连接: 已配置');
521
+ try {
522
+ const { execSync } = await import('node:child_process');
523
+ const status = execSync('opencode-feishu status 2>/dev/null || echo "stopped"', { encoding: 'utf-8', timeout: 5000 }).trim();
524
+ if (status.includes('stopped') || status.includes('not running')) {
525
+ console.log(' 📱 飞书桥接: 正在启动...');
526
+ execSync('opencode-feishu start --daemon', { stdio: 'ignore', timeout: 10000 });
527
+ console.log(' 📱 飞书桥接: 已启动 ✅');
528
+ }
529
+ else {
530
+ console.log(' 📱 飞书桥接: 运行中 ✅');
531
+ }
532
+ }
533
+ catch {
534
+ console.log(' 📱 飞书桥接: 请手动运行 opencode-feishu start --daemon');
535
+ }
536
+ }
537
+ const qiweiConfig = join(process.env.HOME || '~', '.config', 'opencode', 'qiwei.json');
538
+ if (!existsSync(qiweiConfig)) {
539
+ console.log('\n 💬 企业微信配置\n ' + '─'.repeat(40));
540
+ console.log(' 未检测到企微配置。');
541
+ console.log(' 如需使用企微: opencode-qiwei setup(需 botId + secret)');
542
+ console.log(' 跳过企微配置,继续...');
543
+ }
544
+ else {
545
+ console.log(' 💬 企微连接: 已配置');
546
+ try {
547
+ const { execSync } = await import('node:child_process');
548
+ const qstatus = execSync('opencode-qiwei status 2>/dev/null || echo "stopped"', { encoding: 'utf-8', timeout: 5000 }).trim();
549
+ if (qstatus.includes('stopped') || qstatus.includes('not running')) {
550
+ console.log(' 💬 企微桥接: 正在启动...');
551
+ execSync('opencode-qiwei start', { stdio: 'ignore', timeout: 10000 });
552
+ console.log(' 💬 企微桥接: 已启动 ✅');
553
+ }
554
+ else {
555
+ console.log(' 💬 企微桥接: 运行中 ✅');
556
+ }
557
+ }
558
+ catch {
559
+ console.log(' 💬 企微桥接: 请手动运行 opencode-qiwei start');
560
+ }
561
+ }
562
+ try {
563
+ await fetch('http://localhost:19876/health', { signal: AbortSignal.timeout(2000) });
564
+ console.log(' 🧠 OpenCode 引擎: 运行中 ✅');
565
+ }
566
+ catch {
567
+ console.log(' 🧠 OpenCode 引擎: 未运行');
568
+ console.log(' 请在新终端执行: opencode serve --port 19876');
569
+ }
570
+ console.log('\n ✅ 配置完成!');
571
+ console.log(' ──'.repeat(20));
572
+ console.log(' 已配置:');
573
+ console.log(' 📝 模板文件 — soul/ .opencode/ knowledge/');
574
+ console.log(' 💓 记忆系统 — conversations.db 已初始化');
575
+ console.log(' 📅 心跳调度 — crontab */30 分钟');
576
+ console.log(' 📱 飞书连接 — 配置文件就绪');
577
+ if (existsSync(qiweiConfig))
578
+ console.log(' 💬 企微连接 — 配置文件就绪');
579
+ console.log('\n 如需检查状态: hunqi doctor');
580
+ }
581
+ async function askYN(prompt) {
582
+ const { createInterface } = await import('node:readline');
583
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
584
+ const answer = await new Promise(resolve => {
585
+ rl.question(prompt, a => { rl.close(); resolve(a.trim().toLowerCase()); });
586
+ });
587
+ return answer === '' || answer === 'y' || answer === 'yes';
588
+ }
589
+ async function setupFeishuInteractive() {
590
+ console.log('\n 📱 飞书配置 — 终端将显示二维码,用飞书 App 扫码\n');
591
+ try {
592
+ const { execSync } = await import('node:child_process');
593
+ execSync('opencode-feishu setup', { stdio: 'inherit', timeout: 300000 });
594
+ console.log(' ✅ 飞书配置完成\n');
595
+ }
596
+ catch {
597
+ console.log(' ⚠️ 飞书配置跳过(手动: opencode-feishu setup)\n');
598
+ }
599
+ }
600
+ async function setupQiweiInteractive() {
601
+ console.log('\n 💬 企微配置 — 需要企业微信管理后台的 botId 和 secret\n');
602
+ try {
603
+ const { execSync } = await import('node:child_process');
604
+ execSync('opencode-qiwei setup', { stdio: 'inherit', timeout: 120000 });
605
+ console.log(' ✅ 企微配置完成\n');
606
+ }
607
+ catch {
608
+ console.log(' ⚠️ 企微配置跳过(手动: opencode-qiwei setup)\n');
609
+ }
610
+ }
611
+ async function startFeishu(feishuConfig) {
612
+ if (!existsSync(feishuConfig))
613
+ return;
614
+ try {
615
+ const { execSync } = await import('node:child_process');
616
+ const status = execSync('opencode-feishu status 2>/dev/null || echo "stopped"', { encoding: 'utf-8', timeout: 5000 }).trim();
617
+ if (status.includes('stopped') || status.includes('not running')) {
618
+ console.log(' 📱 飞书桥接: 启动中...');
619
+ execSync('opencode-feishu start --daemon', { stdio: 'ignore', timeout: 10000 });
620
+ console.log(' 📱 飞书桥接: 已启动 ✅');
621
+ }
622
+ else {
623
+ console.log(' 📱 飞书桥接: 运行中 ✅');
624
+ }
625
+ }
626
+ catch {
627
+ console.log(' 📱 飞书桥接: 启动失败');
628
+ }
629
+ }
630
+ async function startQiwei(qiweiConfig) {
631
+ if (!existsSync(qiweiConfig))
632
+ return;
633
+ try {
634
+ const { execSync } = await import('node:child_process');
635
+ const qs = execSync('opencode-qiwei status 2>/dev/null || echo "stopped"', { encoding: 'utf-8', timeout: 5000 }).trim();
636
+ if (qs.includes('stopped') || qs.includes('not running')) {
637
+ execSync('opencode-qiwei start', { stdio: 'ignore', timeout: 10000 });
638
+ console.log(' 💬 企微桥接: 已启动 ✅');
639
+ }
640
+ else {
641
+ console.log(' 💬 企微桥接: 运行中 ✅');
642
+ }
643
+ }
644
+ catch { /* optional */ }
645
+ }
646
+ async function cmdStart() {
647
+ const cwd = process.cwd();
648
+ const hasSoul = existsSync(join(cwd, 'soul', 'SOUL.md'));
649
+ if (!hasSoul) {
650
+ console.log('\n 📝 首次运行,自动初始化...');
651
+ await cmdInit('__auto__');
652
+ const autoDir = join(cwd, '__auto__');
653
+ if (existsSync(autoDir)) {
654
+ const { readdirSync: rd, renameSync, rmdirSync } = await import('node:fs');
655
+ for (const f of rd(autoDir)) {
656
+ const src = join(autoDir, f);
657
+ const dst = join(cwd, f);
658
+ if (!existsSync(dst))
659
+ renameSync(src, dst);
660
+ }
661
+ rmdirSync(autoDir);
662
+ }
663
+ }
664
+ let serveRunning = false;
665
+ try {
666
+ await fetch('http://localhost:19876/health', { signal: AbortSignal.timeout(2000) });
667
+ serveRunning = true;
668
+ console.log(' 🧠 opencode serve: 运行中');
669
+ }
670
+ catch {
671
+ console.log(' 🧠 opencode serve: 启动中...');
672
+ try {
673
+ const { spawn } = await import('node:child_process');
674
+ spawn('opencode', ['serve', '--port', '19876'], { stdio: 'ignore', detached: true, cwd }).unref();
675
+ await new Promise(r => setTimeout(r, 3000));
676
+ await fetch('http://localhost:19876/health', { signal: AbortSignal.timeout(2000) });
677
+ console.log(' 🧠 opencode serve: 已启动 ✅');
678
+ }
679
+ catch {
680
+ console.log(' ⚠️ 请手动启动: opencode serve --port 19876');
681
+ }
682
+ }
683
+ const feishuConfig = join(process.env.HOME || '~', '.config', 'opencode', 'feishu.json');
684
+ const qiweiConfig = join(process.env.HOME || '~', '.config', 'opencode', 'qiwei.json');
685
+ const hasFeishu = existsSync(feishuConfig);
686
+ const hasQiwei = existsSync(qiweiConfig);
687
+ if (hasFeishu && hasQiwei) {
688
+ await startFeishu(feishuConfig);
689
+ await startQiwei(qiweiConfig);
690
+ }
691
+ else if (!hasFeishu && !hasQiwei) {
692
+ console.log('\n 📡 选择通信通道:\n');
693
+ console.log(' 1. 飞书(推荐)— 扫码自动配置');
694
+ console.log(' 2. 企业微信 — 手动输入 botId + secret');
695
+ console.log(' 3. 跳过 — 仅启动本地引擎');
696
+ console.log(' 4. 两者都配');
697
+ console.log('');
698
+ const { createInterface } = await import('node:readline');
699
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
700
+ const choice = await new Promise(resolve => {
701
+ rl.question(' 请选择 [1]: ', answer => { rl.close(); resolve(answer.trim() || '1'); });
702
+ });
703
+ switch (choice) {
704
+ case '1':
705
+ await setupFeishuInteractive();
706
+ break;
707
+ case '2':
708
+ await setupQiweiInteractive();
709
+ break;
710
+ case '3':
711
+ console.log(' ⏭️ 跳过通信配置\n');
712
+ break;
713
+ case '4':
714
+ await setupFeishuInteractive();
715
+ await setupQiweiInteractive();
716
+ break;
717
+ default: await setupFeishuInteractive();
718
+ }
719
+ }
720
+ else if (!hasFeishu) {
721
+ console.log('\n 📱 飞书未配置');
722
+ const answer = await askYN(' 是否现在配置?[Y/n]: ');
723
+ if (answer)
724
+ await setupFeishuInteractive();
725
+ }
726
+ else if (!hasQiwei) {
727
+ console.log('\n 💬 企微未配置');
728
+ const answer = await askYN(' 是否现在配置?[Y/n]: ');
729
+ if (answer)
730
+ await setupQiweiInteractive();
731
+ }
732
+ if (existsSync(feishuConfig))
733
+ await startFeishu(feishuConfig);
734
+ if (existsSync(qiweiConfig))
735
+ await startQiwei(qiweiConfig);
736
+ try {
737
+ const { execSync } = await import('node:child_process');
738
+ if (existsSync(join(cwd, 'node_modules', '.bin', 'hunqi-heartbeat'))) {
739
+ execSync(join(cwd, 'node_modules', '.bin', 'hunqi-heartbeat'), { cwd, stdio: 'pipe', timeout: 60000, encoding: 'utf-8' });
740
+ }
741
+ else {
742
+ execSync('hunqi-heartbeat', { cwd, stdio: 'pipe', timeout: 60000, encoding: 'utf-8' });
743
+ }
744
+ }
745
+ catch { }
746
+ console.log('\n ✅ 魂器已就绪!');
747
+ if (existsSync(feishuConfig))
748
+ console.log(' 飞书中 @机器人 即可对话');
749
+ console.log(' hunqi doctor # 查看完整状态\n');
750
+ }
751
+ // ─── 主入口 ────────────────────────────────────────────────
752
+ const pkgVersion = loadJSON(join(PACKAGE_ROOT, 'package.json')).version || '3.0.0';
753
+ const HELP = '\n 魂器 · Agent Soul Framework v' + pkgVersion + '\n\n' +
754
+ ' 用法: hunqi <command> [options]\n\n' +
755
+ ' 核心命令:\n' +
756
+ ' start 一键启动(引擎+飞书+企微+心跳)\n' +
757
+ ' init <目录名> 创建新项目目录\n' +
758
+ ' status 查看系统状态(记忆/知识/引擎)\n' +
759
+ ' heartbeat 执行一次心跳(同步 + 索引 + 任务)\n' +
760
+ ' search <关键词> 统一记忆搜索(会话 + 文件 + MEMORY.md)\n' +
761
+ ' memory <子命令> 记忆管理\n' +
762
+ ' status 查看 MEMORY.md 容量\n' +
763
+ ' add <内容> 添加记忆条目\n' +
764
+ ' search <关键词> FTS5 全文搜索历史会话\n' +
765
+ ' knowledge <子命令> 知识管理\n' +
766
+ ' daily 每日知识提取\n' +
767
+ ' index 生成/更新知识库索引\n' +
768
+ ' skill-create [--force|--dry-run] 技能自动创建(需 agent-soul-skills)\n' +
769
+ ' interactive 启动交互式 TUI(需在项目目录)\n' +
770
+ ' config 查看配置路径\n' +
771
+ ' doctor 系统诊断(检查所有组件状态)\n' +
772
+ ' setup 自动配置(模板+心跳+crontab)\n\n' +
773
+ ' 示例:\n' +
774
+ ' hunqi status\n' +
775
+ ' hunqi search "拍照"\n' +
776
+ ' hunqi memory status\n' +
777
+ ' hunqi knowledge daily\n' +
778
+ ' hunqi heartbeat\n';
779
+ async function main() {
780
+ const args = process.argv.slice(2);
781
+ const cmd = args[0];
782
+ switch (cmd) {
783
+ case undefined:
784
+ case 'help':
785
+ case '--help':
786
+ case '-h':
787
+ console.log(HELP);
788
+ break;
789
+ case 'start':
790
+ await cmdStart();
791
+ break;
792
+ case 'init':
793
+ await cmdInit(args[1]);
794
+ break;
795
+ case 'status':
796
+ await cmdStatus();
797
+ break;
798
+ case 'heartbeat':
799
+ await cmdHeartbeat();
800
+ break;
801
+ case 'search':
802
+ await cmdSearch(args[1]);
803
+ break;
804
+ case 'memory':
805
+ await cmdMemory(args.slice(1));
806
+ break;
807
+ case 'knowledge':
808
+ await cmdKnowledge(args.slice(1));
809
+ break;
810
+ case 'skill-create':
811
+ await cmdSkillCreate(args.slice(1));
812
+ break;
813
+ case 'interactive':
814
+ await cmdInteractive();
815
+ break;
816
+ case 'config':
817
+ await cmdConfig();
818
+ break;
819
+ case 'doctor':
820
+ await cmdDoctor();
821
+ break;
822
+ case 'setup':
823
+ await cmdSetup();
824
+ break;
825
+ default:
826
+ console.log('未知命令: ' + cmd + '\n用法: hunqi help');
827
+ }
828
+ }
829
+ main().catch(console.error);
830
+ //# sourceMappingURL=hunqi.js.map