metame-cli 1.4.6 → 1.4.7
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 +130 -0
- package/package.json +1 -1
- package/scripts/daemon.js +3 -3
- 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,86 @@ 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(跨 Agent 通信)',
|
|
631
|
+
'',
|
|
632
|
+
'识别到"告诉X/让X/通知X"且 X 为已知 Agent 昵称时,直接调用 dispatch,无需确认:',
|
|
633
|
+
'',
|
|
634
|
+
'```bash',
|
|
635
|
+
'~/.metame/bin/dispatch_to <project_key> "内容"',
|
|
636
|
+
'```',
|
|
637
|
+
'',
|
|
638
|
+
'手机端用户可发送 `/dispatch <project_key> <消息>` 跨 agent 通信。',
|
|
639
|
+
'',
|
|
640
|
+
'新增 Agent:`/agent bind <名称> <工作目录>`',
|
|
641
|
+
]},
|
|
642
|
+
{ detect: /memory-search\.js|跨会话记忆/i, text: [
|
|
643
|
+
'## 跨会话记忆',
|
|
644
|
+
'',
|
|
645
|
+
'搜索命令(支持多关键词并行 + QMD 向量混合搜索):',
|
|
646
|
+
'```bash',
|
|
647
|
+
'node ~/.metame/memory-search.js "关键词1" "keyword2" "函数名"',
|
|
648
|
+
'# 只搜事实:--facts;只搜会话:--sessions;最近会话:--recent',
|
|
649
|
+
'```',
|
|
650
|
+
'',
|
|
651
|
+
'**搜索策略**:一次传 3-4 个关键词(中文描述 + 英文术语 + 函数/配置名)。',
|
|
652
|
+
'**触发场景**:用户说"上次/之前/以前"、涉及已有决策/踩过的坑、排查"之前能用现在不行"。',
|
|
653
|
+
]},
|
|
654
|
+
{ detect: /skill-manager|Skills.*技能/i, text: [
|
|
655
|
+
'## Skills(技能扩展)',
|
|
656
|
+
'',
|
|
657
|
+
'遇到任何能力不足、工具缺失、任务失败,**第一步永远是查阅 skill-manager**:',
|
|
658
|
+
'```bash',
|
|
659
|
+
'cat ~/.claude/skills/skill-manager/SKILL.md',
|
|
660
|
+
'```',
|
|
661
|
+
'skill-manager 掌握完整技能清单和决策流程(调用现有 → 商城安装 → GitHub 包装 → 从零创建 → 任务完成后进化)。不要绕过它自己猜。',
|
|
662
|
+
]},
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const globalDir = path.join(os.homedir(), '.claude');
|
|
667
|
+
if (!fs.existsSync(globalDir)) fs.mkdirSync(globalDir, { recursive: true });
|
|
668
|
+
|
|
669
|
+
let globalContent = '';
|
|
670
|
+
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
671
|
+
globalContent = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf8');
|
|
672
|
+
// Remove previous injection
|
|
673
|
+
globalContent = globalContent.replace(new RegExp(
|
|
674
|
+
GLOBAL_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
675
|
+
'[\\s\\S]*?' +
|
|
676
|
+
GLOBAL_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\n?'
|
|
677
|
+
), '');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Only inject sections not already present in user's manual content
|
|
681
|
+
const contentOutsideMarkers = globalContent;
|
|
682
|
+
const needed = GLOBAL_SECTIONS.filter(s => !s.detect.test(contentOutsideMarkers));
|
|
683
|
+
|
|
684
|
+
if (needed.length > 0) {
|
|
685
|
+
const injection = GLOBAL_MARKER_START + '\n\n# MetaMe 能力注入(自动生成,勿手动编辑)\n\n' +
|
|
686
|
+
needed.map(s => s.text.join('\n')).join('\n\n') + '\n\n' + GLOBAL_MARKER_END;
|
|
687
|
+
const finalGlobal = globalContent.trimEnd() + '\n\n' + injection + '\n';
|
|
688
|
+
fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
|
|
689
|
+
} else {
|
|
690
|
+
// All sections already present, just clean up stale marker block
|
|
691
|
+
fs.writeFileSync(GLOBAL_CLAUDE_MD, globalContent.trimEnd() + '\n', 'utf8');
|
|
692
|
+
}
|
|
693
|
+
} catch (e) {
|
|
694
|
+
// Non-fatal: global CLAUDE.md injection is best-effort
|
|
695
|
+
console.error(`⚠️ Failed to inject global CLAUDE.md: ${e.message}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
600
698
|
console.log("🔮 MetaMe: Link Established.");
|
|
601
699
|
console.log("🧬 Protocol: Dynamic Handshake Active");
|
|
602
700
|
|
|
@@ -618,6 +716,38 @@ try {
|
|
|
618
716
|
}
|
|
619
717
|
} catch { /* non-fatal */ }
|
|
620
718
|
|
|
719
|
+
// ---------------------------------------------------------
|
|
720
|
+
// 4.9 AUTO-UPDATE CHECK (non-blocking)
|
|
721
|
+
// ---------------------------------------------------------
|
|
722
|
+
const CURRENT_VERSION = require('./package.json').version;
|
|
723
|
+
|
|
724
|
+
// Fire-and-forget: check npm for newer version and auto-update
|
|
725
|
+
(async () => {
|
|
726
|
+
try {
|
|
727
|
+
const https = require('https');
|
|
728
|
+
const latest = await new Promise((resolve, reject) => {
|
|
729
|
+
https.get('https://registry.npmjs.org/metame-cli/latest', { timeout: 5000 }, res => {
|
|
730
|
+
let data = '';
|
|
731
|
+
res.on('data', c => data += c);
|
|
732
|
+
res.on('end', () => {
|
|
733
|
+
try { resolve(JSON.parse(data).version); } catch { reject(); }
|
|
734
|
+
});
|
|
735
|
+
}).on('error', reject).on('timeout', function() { this.destroy(); reject(); });
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
if (latest && latest !== CURRENT_VERSION) {
|
|
739
|
+
console.log(`📦 MetaMe ${latest} 可用(当前 ${CURRENT_VERSION}),正在自动更新...`);
|
|
740
|
+
const { execSync } = require('child_process');
|
|
741
|
+
try {
|
|
742
|
+
execSync('npm update -g metame-cli', { stdio: 'pipe', timeout: 30000 });
|
|
743
|
+
console.log(`✅ 已更新到 ${latest},下次启动生效。`);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
console.log(`⚠️ 自动更新失败,请手动执行: npm update -g metame-cli`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} catch { /* network unavailable, skip silently */ }
|
|
749
|
+
})();
|
|
750
|
+
|
|
621
751
|
// ---------------------------------------------------------
|
|
622
752
|
// 5. LAUNCH CLAUDE (OR HOT RELOAD)
|
|
623
753
|
// ---------------------------------------------------------
|
package/package.json
CHANGED
package/scripts/daemon.js
CHANGED
|
@@ -1129,7 +1129,7 @@ async function startTelegramBridge(config, executeTaskByName) {
|
|
|
1129
1129
|
const isBindCmd = trimmedText && (trimmedText.startsWith('/bind') || trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
1130
1130
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
1131
1131
|
log('WARN', `Rejected message from unauthorized chat: ${chatId}`);
|
|
1132
|
-
bot.sendMessage(chatId, `⚠️ This chat (ID: ${chatId}) is not authorized.\n\nTo get started, send:\n/bind personal ~/\n\nThis will register this chat and bind it to your home directory.`).catch(() => {});
|
|
1132
|
+
bot.sendMessage(chatId, `⚠️ This chat (ID: ${chatId}) is not authorized.\n\nTo get started, send:\n/agent bind personal ~/\n\nThis will register this chat and bind it to your home directory.`).catch(() => {});
|
|
1133
1133
|
continue;
|
|
1134
1134
|
}
|
|
1135
1135
|
|
|
@@ -4613,7 +4613,7 @@ async function startFeishuBridge(config, executeTaskByName) {
|
|
|
4613
4613
|
const isBindCmd = trimmedText && (trimmedText.startsWith('/bind') || trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
4614
4614
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
4615
4615
|
log('WARN', `Feishu: rejected message from ${chatId}`);
|
|
4616
|
-
(bot.sendMarkdown ? bot.sendMarkdown(chatId, `⚠️ **此会话未授权**\n\n会话 ID: \`${chatId}\`\n\n发送以下命令注册:\n\`/bind personal ~/\`\n\n这会将此会话绑定到你的主目录。`) : bot.sendMessage(chatId, `⚠️ 此会话 (ID: ${chatId}) 未授权。\n\n发送以下命令注册:\n/bind personal ~/\n\n这会将此会话绑定到你的主目录。`)).catch(() => {});
|
|
4616
|
+
(bot.sendMarkdown ? bot.sendMarkdown(chatId, `⚠️ **此会话未授权**\n\n会话 ID: \`${chatId}\`\n\n发送以下命令注册:\n\`/agent bind personal ~/\`\n\n这会将此会话绑定到你的主目录。`) : bot.sendMessage(chatId, `⚠️ 此会话 (ID: ${chatId}) 未授权。\n\n发送以下命令注册:\n/agent bind personal ~/\n\n这会将此会话绑定到你的主目录。`)).catch(() => {});
|
|
4617
4617
|
return;
|
|
4618
4618
|
}
|
|
4619
4619
|
|
|
@@ -4623,7 +4623,7 @@ async function startFeishuBridge(config, executeTaskByName) {
|
|
|
4623
4623
|
log('INFO', `Feishu: read-only message from non-operator ${senderId} in ${chatId}: ${(text || '').slice(0, 50)}`);
|
|
4624
4624
|
// Block slash commands for non-operators
|
|
4625
4625
|
if (text && text.startsWith('/')) {
|
|
4626
|
-
await bot.sendMessage(chatId, '⚠️ 该操作需要授权,请联系管理员。');
|
|
4626
|
+
await (bot.sendMarkdown ? bot.sendMarkdown(chatId, '⚠️ 该操作需要授权,请联系管理员。') : bot.sendMessage(chatId, '⚠️ 该操作需要授权,请联系管理员。'));
|
|
4627
4627
|
return;
|
|
4628
4628
|
}
|
|
4629
4629
|
// Allow read-only chat (query/answer only, no write/edit/execute)
|