metame-cli 1.4.6 → 1.4.8
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 +2 -2
- package/index.js +115 -0
- package/package.json +1 -1
- package/scripts/daemon.js +19 -6
- package/scripts/test_daemon.js +0 -3818
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ metame daemon install-launchd # Auto-start on boot + crash recovery
|
|
|
205
205
|
|
|
206
206
|
Done. Open Telegram, message your bot.
|
|
207
207
|
|
|
208
|
-
> **First message?** New chats aren't whitelisted yet. The bot will reply with a one-step setup command — just send `/bind personal ~/` and you're in.
|
|
208
|
+
> **First message?** New chats aren't whitelisted yet. The bot will reply with a one-step setup command — just send `/agent bind personal ~/` and you're in.
|
|
209
209
|
|
|
210
210
|
---
|
|
211
211
|
|
|
@@ -343,7 +343,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
343
343
|
## Security
|
|
344
344
|
|
|
345
345
|
- All data stays on your machine. No cloud, no telemetry.
|
|
346
|
-
- `allowed_chat_ids` whitelist — unauthorized users get a one-step `/bind` guide instead of silent rejection.
|
|
346
|
+
- `allowed_chat_ids` whitelist — unauthorized users get a one-step `/agent bind` guide instead of silent rejection.
|
|
347
347
|
- `operator_ids` for shared groups — non-operators get read-only mode.
|
|
348
348
|
- `~/.metame/` directory is mode 700.
|
|
349
349
|
- Bot tokens stored locally, never transmitted.
|
package/index.js
CHANGED
|
@@ -405,6 +405,7 @@ After writing the profile, ask: *"Want to set up mobile access so you can reach
|
|
|
405
405
|
|
|
406
406
|
- If **Feishu:**
|
|
407
407
|
1. Guide through: open.feishu.cn/app → create app → get App ID + Secret → enable bot → add event subscription (long connection mode) → add permissions (im:message, im:message.p2p_msg:readonly, im:message.group_at_msg:readonly, im:message:send_as_bot, im:resource) → publish.
|
|
408
|
+
**⚠️ 重要:** 在「事件订阅」页面,必须开启「接收消息 im.message.receive_v1」事件。然后在该事件的配置中,勾选「获取群组中所有消息」(否则 bot 在群聊中只能收到 @它 的消息,无法接收普通群消息)。
|
|
408
409
|
2. Ask user to paste App ID and App Secret.
|
|
409
410
|
3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
|
|
410
411
|
4. Tell user: "Now open Feishu and send any message to your new bot, then tell me you're done."
|
|
@@ -418,6 +419,23 @@ After writing the profile, ask: *"Want to set up mobile access so you can reach
|
|
|
418
419
|
|
|
419
420
|
- If **Skip:** Say "No problem. You can run \`metame daemon init\` anytime to set this up later." Then begin normal work.
|
|
420
421
|
|
|
422
|
+
**After setup, teach the user about Agent Dispatch:**
|
|
423
|
+
|
|
424
|
+
Tell the user: *"You now have multiple AI agents that can collaborate. Here's how to send messages between them:"*
|
|
425
|
+
|
|
426
|
+
- **From mobile (Telegram/Feishu):** Each chat group is bound to a specific agent (project). To send a message to another agent, use:
|
|
427
|
+
\`/dispatch <project_key> <message>\`
|
|
428
|
+
Example: \`/dispatch desktop 帮我检查一下桌面端的日志\`
|
|
429
|
+
|
|
430
|
+
- **From Claude Code terminal:** Use the dispatch command:
|
|
431
|
+
\`~/.metame/bin/dispatch_to <project_key> "message"\`
|
|
432
|
+
|
|
433
|
+
- **Natural language shortcut:** Just say "告诉小美…" or "让老马…" — the AI will automatically route the message to the right agent.
|
|
434
|
+
|
|
435
|
+
- **Available agents** are configured in \`~/.metame/daemon.yaml\` under \`projects:\`. Each project maps to an agent with its own working directory and Claude session.
|
|
436
|
+
|
|
437
|
+
- **To add a new agent:** Send \`/agent bind <name> <working_directory>\` from any chat. This creates a new project and binds it to that chat.
|
|
438
|
+
|
|
421
439
|
**4. EVOLUTION MECHANISM (Manual Sync):**
|
|
422
440
|
* **PHILOSOPHY:** You respect the User's flow. You do NOT interrupt.
|
|
423
441
|
* **TOOLS:**
|
|
@@ -597,6 +615,71 @@ try {
|
|
|
597
615
|
const newContent = finalProtocol + mirrorLine + reflectionLine + METAME_END + "\n" + fileContent;
|
|
598
616
|
fs.writeFileSync(PROJECT_FILE, newContent, 'utf8');
|
|
599
617
|
|
|
618
|
+
// ---------------------------------------------------------
|
|
619
|
+
// 4.7 GLOBAL CLAUDE.MD INJECTION (Agent capabilities)
|
|
620
|
+
// ---------------------------------------------------------
|
|
621
|
+
// Inject MetaMe capabilities into ~/.claude/CLAUDE.md so ALL projects' agents
|
|
622
|
+
// automatically know about dispatch, memory, and skill systems.
|
|
623
|
+
const GLOBAL_CLAUDE_MD = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
624
|
+
const GLOBAL_MARKER_START = '<!-- METAME-GLOBAL:START -->';
|
|
625
|
+
const GLOBAL_MARKER_END = '<!-- METAME-GLOBAL:END -->';
|
|
626
|
+
|
|
627
|
+
// Sections to inject (each only injected if not already present in user's manual content)
|
|
628
|
+
const GLOBAL_SECTIONS = [
|
|
629
|
+
{ detect: /dispatch_to|Agent Dispatch/i, text: [
|
|
630
|
+
'## Agent Dispatch',
|
|
631
|
+
'"告诉X/让X"→ `~/.metame/bin/dispatch_to <project_key> "内容"`,手机端 `/dispatch <key> <消息>`。',
|
|
632
|
+
'新增 Agent:`/agent bind <名称> <工作目录>`',
|
|
633
|
+
]},
|
|
634
|
+
{ detect: /memory-search\.js|跨会话记忆/i, text: [
|
|
635
|
+
'## 跨会话记忆',
|
|
636
|
+
'用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
|
|
637
|
+
'一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
|
|
638
|
+
]},
|
|
639
|
+
{ detect: /skill-manager|Skills.*技能/i, text: [
|
|
640
|
+
'## Skills',
|
|
641
|
+
'能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
|
|
642
|
+
]},
|
|
643
|
+
{ detect: /\[\[FILE:|手机端文件/i, text: [
|
|
644
|
+
'## 手机端文件交互',
|
|
645
|
+
'**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
|
|
646
|
+
'**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
|
|
647
|
+
]},
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
const globalDir = path.join(os.homedir(), '.claude');
|
|
652
|
+
if (!fs.existsSync(globalDir)) fs.mkdirSync(globalDir, { recursive: true });
|
|
653
|
+
|
|
654
|
+
let globalContent = '';
|
|
655
|
+
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
656
|
+
globalContent = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf8');
|
|
657
|
+
// Remove previous injection
|
|
658
|
+
globalContent = globalContent.replace(new RegExp(
|
|
659
|
+
GLOBAL_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
660
|
+
'[\\s\\S]*?' +
|
|
661
|
+
GLOBAL_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\n?'
|
|
662
|
+
), '');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Only inject sections not already present in user's manual content
|
|
666
|
+
const contentOutsideMarkers = globalContent;
|
|
667
|
+
const needed = GLOBAL_SECTIONS.filter(s => !s.detect.test(contentOutsideMarkers));
|
|
668
|
+
|
|
669
|
+
if (needed.length > 0) {
|
|
670
|
+
const injection = GLOBAL_MARKER_START + '\n\n# MetaMe 能力注入(自动生成,勿手动编辑)\n\n' +
|
|
671
|
+
needed.map(s => s.text.join('\n')).join('\n\n') + '\n\n' + GLOBAL_MARKER_END;
|
|
672
|
+
const finalGlobal = globalContent.trimEnd() + '\n\n' + injection + '\n';
|
|
673
|
+
fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
|
|
674
|
+
} else {
|
|
675
|
+
// All sections already present, just clean up stale marker block
|
|
676
|
+
fs.writeFileSync(GLOBAL_CLAUDE_MD, globalContent.trimEnd() + '\n', 'utf8');
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
// Non-fatal: global CLAUDE.md injection is best-effort
|
|
680
|
+
console.error(`⚠️ Failed to inject global CLAUDE.md: ${e.message}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
600
683
|
console.log("🔮 MetaMe: Link Established.");
|
|
601
684
|
console.log("🧬 Protocol: Dynamic Handshake Active");
|
|
602
685
|
|
|
@@ -618,6 +701,38 @@ try {
|
|
|
618
701
|
}
|
|
619
702
|
} catch { /* non-fatal */ }
|
|
620
703
|
|
|
704
|
+
// ---------------------------------------------------------
|
|
705
|
+
// 4.9 AUTO-UPDATE CHECK (non-blocking)
|
|
706
|
+
// ---------------------------------------------------------
|
|
707
|
+
const CURRENT_VERSION = require('./package.json').version;
|
|
708
|
+
|
|
709
|
+
// Fire-and-forget: check npm for newer version and auto-update
|
|
710
|
+
(async () => {
|
|
711
|
+
try {
|
|
712
|
+
const https = require('https');
|
|
713
|
+
const latest = await new Promise((resolve, reject) => {
|
|
714
|
+
https.get('https://registry.npmjs.org/metame-cli/latest', { timeout: 5000 }, res => {
|
|
715
|
+
let data = '';
|
|
716
|
+
res.on('data', c => data += c);
|
|
717
|
+
res.on('end', () => {
|
|
718
|
+
try { resolve(JSON.parse(data).version); } catch { reject(); }
|
|
719
|
+
});
|
|
720
|
+
}).on('error', reject).on('timeout', function() { this.destroy(); reject(); });
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
if (latest && latest !== CURRENT_VERSION) {
|
|
724
|
+
console.log(`📦 MetaMe ${latest} 可用(当前 ${CURRENT_VERSION}),正在自动更新...`);
|
|
725
|
+
const { execSync } = require('child_process');
|
|
726
|
+
try {
|
|
727
|
+
execSync('npm update -g metame-cli', { stdio: 'pipe', timeout: 30000 });
|
|
728
|
+
console.log(`✅ 已更新到 ${latest},下次启动生效。`);
|
|
729
|
+
} catch (e) {
|
|
730
|
+
console.log(`⚠️ 自动更新失败,请手动执行: npm update -g metame-cli`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} catch { /* network unavailable, skip silently */ }
|
|
734
|
+
})();
|
|
735
|
+
|
|
621
736
|
// ---------------------------------------------------------
|
|
622
737
|
// 5. LAUNCH CLAUDE (OR HOT RELOAD)
|
|
623
738
|
// ---------------------------------------------------------
|
package/package.json
CHANGED
package/scripts/daemon.js
CHANGED
|
@@ -29,6 +29,19 @@ const DISPATCH_DIR = path.join(METAME_DIR, 'dispatch');
|
|
|
29
29
|
const DISPATCH_LOG = path.join(DISPATCH_DIR, 'dispatch-log.jsonl');
|
|
30
30
|
const SOCK_PATH = path.join(METAME_DIR, 'daemon.sock');
|
|
31
31
|
|
|
32
|
+
// Resolve claude binary path (daemon may not inherit user's full PATH)
|
|
33
|
+
const CLAUDE_BIN = (() => {
|
|
34
|
+
const candidates = [
|
|
35
|
+
path.join(HOME, '.local', 'bin', 'claude'), // npm global (Linux/Mac)
|
|
36
|
+
path.join(HOME, '.npm-global', 'bin', 'claude'), // custom npm prefix
|
|
37
|
+
'/usr/local/bin/claude',
|
|
38
|
+
'/opt/homebrew/bin/claude',
|
|
39
|
+
];
|
|
40
|
+
try { return execSync('which claude 2>/dev/null', { encoding: 'utf8' }).trim(); } catch {}
|
|
41
|
+
for (const p of candidates) { if (fs.existsSync(p)) return p; }
|
|
42
|
+
return 'claude'; // fallback: hope it's in PATH
|
|
43
|
+
})();
|
|
44
|
+
|
|
32
45
|
// Skill evolution module (hot path + cold path)
|
|
33
46
|
let skillEvolution = null;
|
|
34
47
|
try { skillEvolution = require('./skill-evolution'); } catch { /* graceful fallback */ }
|
|
@@ -395,7 +408,7 @@ function executeTask(task, config) {
|
|
|
395
408
|
const asyncEnv = { ...process.env, ...getDaemonProviderEnv(), CLAUDECODE: undefined };
|
|
396
409
|
|
|
397
410
|
return new Promise((resolve) => {
|
|
398
|
-
const child = spawn(
|
|
411
|
+
const child = spawn(CLAUDE_BIN, asyncArgs, {
|
|
399
412
|
cwd: cwd || undefined,
|
|
400
413
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
401
414
|
detached: true, // own process group — kills sub-agents on timeout too
|
|
@@ -1129,7 +1142,7 @@ async function startTelegramBridge(config, executeTaskByName) {
|
|
|
1129
1142
|
const isBindCmd = trimmedText && (trimmedText.startsWith('/bind') || trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
1130
1143
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
1131
1144
|
log('WARN', `Rejected message from unauthorized chat: ${chatId}`);
|
|
1132
|
-
bot.sendMessage(chatId, `⚠️ This chat
|
|
1145
|
+
bot.sendMessage(chatId, `⚠️ This chat is not authorized.\n\nCopy and send this command to register:\n\n/agent bind personal`).catch(() => {});
|
|
1133
1146
|
continue;
|
|
1134
1147
|
}
|
|
1135
1148
|
|
|
@@ -3709,7 +3722,7 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
|
|
|
3709
3722
|
*/
|
|
3710
3723
|
function spawnClaudeAsync(args, input, cwd, timeoutMs = 300000) {
|
|
3711
3724
|
return new Promise((resolve) => {
|
|
3712
|
-
const child = spawn(
|
|
3725
|
+
const child = spawn(CLAUDE_BIN, args, {
|
|
3713
3726
|
cwd,
|
|
3714
3727
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3715
3728
|
env: { ...process.env, ...getActiveProviderEnv(), CLAUDECODE: undefined },
|
|
@@ -4005,7 +4018,7 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
4005
4018
|
// Add stream-json output format (requires --verbose)
|
|
4006
4019
|
const streamArgs = [...args, '--output-format', 'stream-json', '--verbose'];
|
|
4007
4020
|
|
|
4008
|
-
const child = spawn(
|
|
4021
|
+
const child = spawn(CLAUDE_BIN, streamArgs, {
|
|
4009
4022
|
cwd,
|
|
4010
4023
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
4011
4024
|
detached: true, // Create new process group so killing -pid kills all sub-agents too
|
|
@@ -4613,7 +4626,7 @@ async function startFeishuBridge(config, executeTaskByName) {
|
|
|
4613
4626
|
const isBindCmd = trimmedText && (trimmedText.startsWith('/bind') || trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
4614
4627
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
4615
4628
|
log('WARN', `Feishu: rejected message from ${chatId}`);
|
|
4616
|
-
(bot.sendMarkdown ? bot.sendMarkdown(chatId, `⚠️ **此会话未授权**\n\n
|
|
4629
|
+
(bot.sendMarkdown ? bot.sendMarkdown(chatId, `⚠️ **此会话未授权**\n\n复制下方命令发送即可注册:\n\`\`\`\n/agent bind personal\n\`\`\``) : bot.sendMessage(chatId, `⚠️ 此会话未授权。\n\n复制下方命令发送即可注册:\n/agent bind personal`)).catch(() => {});
|
|
4617
4630
|
return;
|
|
4618
4631
|
}
|
|
4619
4632
|
|
|
@@ -4623,7 +4636,7 @@ async function startFeishuBridge(config, executeTaskByName) {
|
|
|
4623
4636
|
log('INFO', `Feishu: read-only message from non-operator ${senderId} in ${chatId}: ${(text || '').slice(0, 50)}`);
|
|
4624
4637
|
// Block slash commands for non-operators
|
|
4625
4638
|
if (text && text.startsWith('/')) {
|
|
4626
|
-
await bot.sendMessage(chatId, '⚠️ 该操作需要授权,请联系管理员。');
|
|
4639
|
+
await (bot.sendMarkdown ? bot.sendMarkdown(chatId, '⚠️ 该操作需要授权,请联系管理员。') : bot.sendMessage(chatId, '⚠️ 该操作需要授权,请联系管理员。'));
|
|
4627
4640
|
return;
|
|
4628
4641
|
}
|
|
4629
4642
|
// Allow read-only chat (query/answer only, no write/edit/execute)
|