metame-cli 1.4.10 → 1.4.13
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 +19 -30
- package/index.js +218 -72
- package/package.json +3 -3
- package/scripts/daemon-admin-commands.js +365 -0
- package/scripts/daemon-agent-commands.js +467 -0
- package/scripts/daemon-agent-tools.js +256 -0
- package/scripts/daemon-bridges.js +236 -0
- package/scripts/daemon-checkpoints.js +89 -0
- package/scripts/daemon-claude-engine.js +808 -0
- package/scripts/daemon-command-router.js +395 -0
- package/scripts/daemon-default.yaml +2 -2
- package/scripts/daemon-exec-commands.js +290 -0
- package/scripts/daemon-file-browser.js +219 -0
- package/scripts/daemon-notify.js +64 -0
- package/scripts/daemon-ops-commands.js +275 -0
- package/scripts/daemon-runtime-lifecycle.js +133 -0
- package/scripts/daemon-session-commands.js +436 -0
- package/scripts/daemon-session-store.js +423 -0
- package/scripts/daemon-task-scheduler.js +539 -0
- package/scripts/daemon.js +421 -3986
- package/scripts/distill.js +19 -7
- package/scripts/memory-extract.js +20 -13
- package/scripts/memory.js +92 -6
- package/scripts/pending-traits.js +15 -2
- package/scripts/self-reflect.js +169 -0
- package/scripts/session-analytics.js +116 -0
- package/scripts/skill-evolution.js +56 -2
- package/scripts/test_daemon.js +1407 -0
- package/scripts/utils.js +57 -0
package/README.md
CHANGED
|
@@ -172,40 +172,26 @@ Task fails → skill-scout finds a skill → installs → retries → succeeds
|
|
|
172
172
|
|
|
173
173
|
## Quick Start
|
|
174
174
|
|
|
175
|
-
### Install
|
|
176
|
-
|
|
177
175
|
```bash
|
|
178
|
-
|
|
179
|
-
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
180
|
-
|
|
181
|
-
# Or if you already have Claude Code
|
|
182
|
-
npm install -g metame-cli
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### First Run
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
metame
|
|
176
|
+
npm install -g metame-cli && metame
|
|
189
177
|
```
|
|
190
178
|
|
|
191
|
-
|
|
179
|
+
**3 minutes to full setup:**
|
|
192
180
|
|
|
193
|
-
|
|
181
|
+
| Step | Command | What happens |
|
|
182
|
+
|------|---------|-------------|
|
|
183
|
+
| 1. Install & profile | `metame` | First run: cognitive interview → builds `~/.claude_profile.yaml` |
|
|
184
|
+
| 2. Connect phone | Follow the setup wizard | Bot token + app credentials → `~/.metame/daemon.yaml` |
|
|
185
|
+
| 3. Start daemon | `metame start` | Background daemon launches, bot goes online |
|
|
186
|
+
| 4. Auto-start | `metame daemon install-launchd` | Survives reboot + crash recovery |
|
|
194
187
|
|
|
195
|
-
|
|
196
|
-
metame daemon init # Creates config with setup guide
|
|
197
|
-
metame start # Launches background daemon
|
|
198
|
-
```
|
|
188
|
+
**Create your first Agent:**
|
|
199
189
|
|
|
200
|
-
|
|
190
|
+
1. Create a group chat in Telegram/Feishu, add your bot
|
|
191
|
+
2. Send `/agent bind <name>` in the group (e.g. `/agent bind personal`)
|
|
192
|
+
3. Pick a working directory from the buttons, or type a path directly — non-existent directories are created automatically → done
|
|
201
193
|
|
|
202
|
-
|
|
203
|
-
metame daemon install-launchd # Auto-start on boot + crash recovery
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Done. Open Telegram, message your bot.
|
|
207
|
-
|
|
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.
|
|
194
|
+
> Want more Agents? Repeat: new group → add bot → `/agent bind <name>`. Each group = independent AI workspace.
|
|
209
195
|
|
|
210
196
|
---
|
|
211
197
|
|
|
@@ -234,7 +220,7 @@ The easiest way. Open any Telegram/Feishu group and use the `/agent` wizard:
|
|
|
234
220
|
|
|
235
221
|
| Command | What it does |
|
|
236
222
|
|---------|-------------|
|
|
237
|
-
| `/agent new` | Step-by-step wizard: pick a directory → name the agent → describe its role. MetaMe writes the role into `CLAUDE.md` automatically. |
|
|
223
|
+
| `/agent new` | Step-by-step wizard: pick a directory → name the agent → describe its role. MetaMe writes the role into `CLAUDE.md` automatically. You can also type a path directly in chat — if it doesn't exist, MetaMe creates it for you. |
|
|
238
224
|
| `/agent bind <name> [dir]` | Quick bind: register this group as a named agent, optionally set working directory. |
|
|
239
225
|
| `/agent list` | Show all configured agents. |
|
|
240
226
|
| `/agent edit` | Update the current agent's role description (rewrites its `CLAUDE.md` section). |
|
|
@@ -245,14 +231,17 @@ Example flow:
|
|
|
245
231
|
You: /agent new
|
|
246
232
|
Bot: Please select a working directory:
|
|
247
233
|
📁 ~/AGI 📁 ~/projects 📁 ~/Desktop
|
|
248
|
-
You: ~/AGI/MyProject
|
|
249
|
-
Bot:
|
|
234
|
+
You: ~/AGI/MyProject/NewDir
|
|
235
|
+
Bot: ✅ 已新建目录:~/AGI/MyProject/NewDir
|
|
236
|
+
What should we name this agent?
|
|
250
237
|
You: 小美
|
|
251
238
|
Bot: Describe 小美's role and responsibilities:
|
|
252
239
|
You: Personal assistant. Manages my calendar, drafts messages, and tracks todos.
|
|
253
240
|
Bot: ✅ Agent「小美」created. CLAUDE.md updated with role definition.
|
|
254
241
|
```
|
|
255
242
|
|
|
243
|
+
You can tap a button to pick an existing directory, or type any path directly in chat. If the path doesn't exist, it's created automatically. All entry points (`/agent new` wizard and `/agent bind`) validate that the directory is real before saving.
|
|
244
|
+
|
|
256
245
|
### From config file (for power users)
|
|
257
246
|
|
|
258
247
|
```yaml
|
package/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
-
const { spawn } = require('child_process');
|
|
6
|
+
const { spawn, execSync } = require('child_process');
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------
|
|
9
9
|
// 1. CONFIGURATION
|
|
@@ -29,8 +29,16 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
29
29
|
|
|
30
30
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
31
31
|
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
32
|
-
const BUNDLED_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'qmd-client.js', 'session-summarize.js'];
|
|
33
32
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
33
|
+
const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'qmd-client.js', 'session-summarize.js'];
|
|
34
|
+
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
35
|
+
try {
|
|
36
|
+
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
const BUNDLED_SCRIPTS = [...new Set([...BUNDLED_BASE_SCRIPTS, ...DAEMON_MODULE_SCRIPTS])];
|
|
34
42
|
|
|
35
43
|
// Protect daemon.yaml: create backup before any sync operation
|
|
36
44
|
const DAEMON_YAML_BACKUP = path.join(METAME_DIR, 'daemon.yaml.bak');
|
|
@@ -213,15 +221,12 @@ function spawnDistillBackground() {
|
|
|
213
221
|
|
|
214
222
|
if (!hasSignals && !bootstrap) return;
|
|
215
223
|
|
|
216
|
-
|
|
217
|
-
const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
|
|
218
|
-
const lines = fs.readFileSync(bufferFile, 'utf8').trim().split('\n').filter(l => l.trim());
|
|
219
|
-
console.log(`🧠 MetaMe: Distilling ${lines.length} moment${lines.length > 1 ? 's' : ''} in background...`);
|
|
220
|
-
}
|
|
224
|
+
// Note: status display is handled separately in startup output — no log here
|
|
221
225
|
if (bootstrap) {
|
|
222
|
-
|
|
226
|
+
// Background bootstrap — silent, no need to inform user
|
|
223
227
|
}
|
|
224
228
|
|
|
229
|
+
|
|
225
230
|
// Spawn as detached background process — won't block Claude launch
|
|
226
231
|
// Remove CLAUDECODE env var so distill.js can call `claude -p` without nested-session rejection
|
|
227
232
|
const distillEnvClean = { ...process.env };
|
|
@@ -390,10 +395,14 @@ You are entering **Calibration Mode**. You are not a chatbot; you are a Psycholo
|
|
|
390
395
|
- Announce: "Link Established. Profile calibrated."
|
|
391
396
|
- Then proceed to **Phase 2** below.
|
|
392
397
|
|
|
393
|
-
**3. SETUP WIZARD (Phase 2 —
|
|
398
|
+
**3. SETUP WIZARD (Phase 2 — Mobile Access):**
|
|
394
399
|
|
|
395
400
|
After writing the profile, ask: *"Want to set up mobile access so you can reach me from your phone? (Telegram / Feishu / Skip)"*
|
|
396
401
|
|
|
402
|
+
**Step A: Create Bot & Connect Private Chat (必做)**
|
|
403
|
+
|
|
404
|
+
This step connects the bot to the user's PRIVATE chat — this is the admin channel.
|
|
405
|
+
|
|
397
406
|
- If **Telegram:**
|
|
398
407
|
1. Tell user to open Telegram, search @BotFather, send /newbot, create a bot, copy the token.
|
|
399
408
|
2. Ask user to paste the bot token.
|
|
@@ -408,7 +417,7 @@ After writing the profile, ask: *"Want to set up mobile access so you can reach
|
|
|
408
417
|
**⚠️ 重要:** 在「事件订阅」页面,必须开启「接收消息 im.message.receive_v1」事件。然后在该事件的配置中,勾选「获取群组中所有消息」(否则 bot 在群聊中只能收到 @它 的消息,无法接收普通群消息)。
|
|
409
418
|
2. Ask user to paste App ID and App Secret.
|
|
410
419
|
3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
|
|
411
|
-
4. Tell user: "Now open Feishu and send any message to your new bot, then tell me you're done."
|
|
420
|
+
4. Tell user: "Now open Feishu and send any message to your new bot (private chat), then tell me you're done."
|
|
412
421
|
5. After user confirms, auto-fetch the chat ID:
|
|
413
422
|
\`\`\`bash
|
|
414
423
|
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal -H "Content-Type: application/json" -d '{"app_id":"<APP_ID>","app_secret":"<APP_SECRET>"}' | jq -r '.tenant_access_token')
|
|
@@ -419,22 +428,16 @@ After writing the profile, ask: *"Want to set up mobile access so you can reach
|
|
|
419
428
|
|
|
420
429
|
- If **Skip:** Say "No problem. You can run \`metame daemon init\` anytime to set this up later." Then begin normal work.
|
|
421
430
|
|
|
422
|
-
**
|
|
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 帮我检查一下桌面端的日志\`
|
|
431
|
+
**Step B: Create Your First Agent (引导用户建立第一个 Agent)**
|
|
429
432
|
|
|
430
|
-
|
|
431
|
-
\`~/.metame/bin/dispatch_to <project_key> "message"\`
|
|
433
|
+
After bot is connected, explain the Agent concept and guide the user to create their first one:
|
|
432
434
|
|
|
433
|
-
|
|
435
|
+
Tell the user: *"Now let's create your first AI Agent. Each Agent is an independent AI workspace bound to a specific project folder and chat group."*
|
|
434
436
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
1. Tell user to create a new group chat in Telegram/Feishu, add the bot to the group, and name it (e.g. "Personal Assistant" or "My Project").
|
|
438
|
+
2. Tell user to send \`/agent bind <name>\` in that group (e.g. \`/agent bind personal\`). This will show a directory picker — user taps to select the working directory.
|
|
439
|
+
3. Once bound, that group becomes a dedicated Agent channel — messages there go to that Agent's Claude session.
|
|
440
|
+
4. Tell user: *"Want to create more Agents? Just repeat: create a group → add bot → send /agent bind <name>. Each group becomes an independent Agent."*
|
|
438
441
|
|
|
439
442
|
**4. EVOLUTION MECHANISM (Manual Sync):**
|
|
440
443
|
* **PHILOSOPHY:** You respect the User's flow. You do NOT interrupt.
|
|
@@ -611,41 +614,72 @@ try {
|
|
|
611
614
|
// Non-fatal
|
|
612
615
|
}
|
|
613
616
|
|
|
614
|
-
//
|
|
615
|
-
|
|
617
|
+
// Project-level CLAUDE.md: KERNEL has moved to global ~/.claude/CLAUDE.md.
|
|
618
|
+
// Only inject dynamic per-session observations (mirror / reflection).
|
|
619
|
+
// If nothing dynamic, write the cleaned file with no METAME block at all.
|
|
620
|
+
const dynamicContent = mirrorLine + reflectionLine;
|
|
621
|
+
const newContent = dynamicContent.trim()
|
|
622
|
+
? METAME_START + '\n' + dynamicContent + METAME_END + '\n' + fileContent
|
|
623
|
+
: fileContent;
|
|
616
624
|
fs.writeFileSync(PROJECT_FILE, newContent, 'utf8');
|
|
617
625
|
|
|
618
626
|
// ---------------------------------------------------------
|
|
619
|
-
// 4.7 GLOBAL CLAUDE.MD INJECTION (
|
|
627
|
+
// 4.7 GLOBAL CLAUDE.MD INJECTION (Full Kernel + Capabilities)
|
|
620
628
|
// ---------------------------------------------------------
|
|
621
|
-
// Inject MetaMe
|
|
622
|
-
//
|
|
629
|
+
// Inject the full MetaMe KERNEL into ~/.claude/CLAUDE.md.
|
|
630
|
+
// This file is read by ALL Claude Code sessions regardless of working directory,
|
|
631
|
+
// so every project (小美, 3D, MetaMe, etc.) gets the system automatically.
|
|
632
|
+
// Project-level CLAUDE.md only needs role definitions — no kernel duplication.
|
|
623
633
|
const GLOBAL_CLAUDE_MD = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
624
634
|
const GLOBAL_MARKER_START = '<!-- METAME-GLOBAL:START -->';
|
|
625
635
|
const GLOBAL_MARKER_END = '<!-- METAME-GLOBAL:END -->';
|
|
626
636
|
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
637
|
+
// Build dynamic Agent dispatch table from daemon.yaml projects.
|
|
638
|
+
// Only include agents whose cwd actually exists on disk — test/stale agents
|
|
639
|
+
// with deleted paths are automatically excluded, no manual cleanup needed.
|
|
640
|
+
let dispatchTable = '';
|
|
641
|
+
try {
|
|
642
|
+
const daemonYamlPath = path.join(os.homedir(), '.metame', 'daemon.yaml');
|
|
643
|
+
if (fs.existsSync(daemonYamlPath)) {
|
|
644
|
+
const daemonCfg = yaml.load(fs.readFileSync(daemonYamlPath, 'utf8')) || {};
|
|
645
|
+
const projects = daemonCfg.projects || {};
|
|
646
|
+
const rows = Object.entries(projects)
|
|
647
|
+
.filter(([, p]) => {
|
|
648
|
+
if (!p || !p.name || !p.cwd) return false;
|
|
649
|
+
// Expand ~ to home directory
|
|
650
|
+
const expandedCwd = String(p.cwd).replace(/^~/, os.homedir());
|
|
651
|
+
return fs.existsSync(expandedCwd);
|
|
652
|
+
})
|
|
653
|
+
.map(([key, p]) => `| \`${key}\` | ${p.name} |`);
|
|
654
|
+
if (rows.length > 0) {
|
|
655
|
+
dispatchTable = '\n\n| project_key | 昵称 |\n|-------------|------|\n' + rows.join('\n') + '\n\n`--new` 强制新建会话(用户说"新开会话"时加此参数)。';
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} catch { /* daemon.yaml missing or invalid — skip dispatch table */ }
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
// Full kernel body: reuse PROTOCOL_NORMAL, strip project-level marker
|
|
662
|
+
const KERNEL_BODY = PROTOCOL_NORMAL
|
|
663
|
+
.replace(/^<!-- METAME:START -->\n/, '') // remove project-level marker
|
|
664
|
+
.trimEnd();
|
|
665
|
+
|
|
666
|
+
const CAPABILITY_SECTIONS = [
|
|
667
|
+
'## Agent Dispatch',
|
|
668
|
+
`"告诉X/让X" → \`~/.metame/bin/dispatch_to <project_key> "内容"\`,手机端 \`/dispatch to <key> <消息>\`。` + dispatchTable,
|
|
669
|
+
'新增 Agent:`/agent bind <名称> <工作目录>`',
|
|
670
|
+
'',
|
|
671
|
+
'## 跨会话记忆',
|
|
672
|
+
'用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
|
|
673
|
+
'一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
|
|
674
|
+
'',
|
|
675
|
+
'## Skills',
|
|
676
|
+
'能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
|
|
677
|
+
'',
|
|
678
|
+
'## 手机端文件交互',
|
|
679
|
+
'**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
|
|
680
|
+
'**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
|
|
681
|
+
|
|
682
|
+
].join('\n');
|
|
649
683
|
|
|
650
684
|
try {
|
|
651
685
|
const globalDir = path.join(os.homedir(), '.claude');
|
|
@@ -654,34 +688,32 @@ try {
|
|
|
654
688
|
let globalContent = '';
|
|
655
689
|
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
656
690
|
globalContent = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf8');
|
|
657
|
-
// Remove previous injection
|
|
691
|
+
// Remove previous global injection (always replace with latest)
|
|
658
692
|
globalContent = globalContent.replace(new RegExp(
|
|
659
|
-
GLOBAL_MARKER_START.replace(/[.*+?^${}()|[
|
|
693
|
+
GLOBAL_MARKER_START.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') +
|
|
660
694
|
'[\\s\\S]*?' +
|
|
661
|
-
GLOBAL_MARKER_END.replace(/[.*+?^${}()|[
|
|
695
|
+
GLOBAL_MARKER_END.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') + '\\n?'
|
|
662
696
|
), '');
|
|
663
697
|
}
|
|
664
698
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
699
|
+
const injection =
|
|
700
|
+
GLOBAL_MARKER_START + '\n' +
|
|
701
|
+
KERNEL_BODY + '\n\n' +
|
|
702
|
+
'# MetaMe 能力注入(自动生成,勿手动编辑)\n\n' +
|
|
703
|
+
CAPABILITY_SECTIONS + '\n\n' +
|
|
704
|
+
GLOBAL_MARKER_END;
|
|
668
705
|
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
}
|
|
706
|
+
const finalGlobal = globalContent.trimEnd() + (globalContent.trim() ? '\n\n' : '') + injection + '\n';
|
|
707
|
+
fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
|
|
678
708
|
} catch (e) {
|
|
679
709
|
// Non-fatal: global CLAUDE.md injection is best-effort
|
|
680
710
|
console.error(`⚠️ Failed to inject global CLAUDE.md: ${e.message}`);
|
|
681
711
|
}
|
|
682
712
|
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
|
|
683
716
|
console.log("🔮 MetaMe: Link Established.");
|
|
684
|
-
console.log("🧬 Protocol: Dynamic Handshake Active");
|
|
685
717
|
|
|
686
718
|
// Memory system status — show live stats without blocking launch
|
|
687
719
|
try {
|
|
@@ -701,6 +733,39 @@ try {
|
|
|
701
733
|
}
|
|
702
734
|
} catch { /* non-fatal */ }
|
|
703
735
|
|
|
736
|
+
// Cognitive distillation status — always show so user knows the system's state
|
|
737
|
+
try {
|
|
738
|
+
const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
|
|
739
|
+
const pendingCount = fs.existsSync(bufferFile)
|
|
740
|
+
? fs.readFileSync(bufferFile, 'utf8').trim().split('\n').filter(l => l.trim()).length
|
|
741
|
+
: 0;
|
|
742
|
+
|
|
743
|
+
if (pendingCount > 0) {
|
|
744
|
+
console.log(`🧬 Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
|
|
745
|
+
} else {
|
|
746
|
+
// Show last distill time
|
|
747
|
+
let lastDistillStr = '从未';
|
|
748
|
+
try {
|
|
749
|
+
const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
|
|
750
|
+
if (fs.existsSync(profilePath)) {
|
|
751
|
+
const _yaml = require('js-yaml');
|
|
752
|
+
const profile = _yaml.load(fs.readFileSync(profilePath, 'utf8'));
|
|
753
|
+
const distillLog = profile && profile.evolution && profile.evolution.auto_distill;
|
|
754
|
+
if (Array.isArray(distillLog) && distillLog.length > 0) {
|
|
755
|
+
const lastTs = new Date(distillLog[distillLog.length - 1].ts).getTime();
|
|
756
|
+
const diffMs = Date.now() - lastTs;
|
|
757
|
+
const diffH = Math.floor(diffMs / 3600000);
|
|
758
|
+
const diffM = Math.floor((diffMs % 3600000) / 60000);
|
|
759
|
+
lastDistillStr = diffH > 0 ? `${diffH}h${diffM}m 前` : `${diffM}m 前`;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
} catch { /* non-fatal */ }
|
|
763
|
+
console.log(`🧬 Cognition: 无新信号 · 上次蒸馏 ${lastDistillStr}`);
|
|
764
|
+
}
|
|
765
|
+
} catch { /* non-fatal */ }
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
|
|
704
769
|
// ---------------------------------------------------------
|
|
705
770
|
// 4.9 AUTO-UPDATE CHECK (non-blocking)
|
|
706
771
|
// ---------------------------------------------------------
|
|
@@ -717,7 +782,7 @@ const CURRENT_VERSION = require('./package.json').version;
|
|
|
717
782
|
res.on('end', () => {
|
|
718
783
|
try { resolve(JSON.parse(data).version); } catch { reject(); }
|
|
719
784
|
});
|
|
720
|
-
}).on('error', reject).on('timeout', function() { this.destroy(); reject(); });
|
|
785
|
+
}).on('error', reject).on('timeout', function () { this.destroy(); reject(); });
|
|
721
786
|
});
|
|
722
787
|
|
|
723
788
|
if (latest && latest !== CURRENT_VERSION) {
|
|
@@ -733,6 +798,71 @@ const CURRENT_VERSION = require('./package.json').version;
|
|
|
733
798
|
} catch { /* network unavailable, skip silently */ }
|
|
734
799
|
})();
|
|
735
800
|
|
|
801
|
+
// ---------------------------------------------------------
|
|
802
|
+
// 4.95 QMD OPTIONAL INSTALL PROMPT (one-time)
|
|
803
|
+
// ---------------------------------------------------------
|
|
804
|
+
// Only prompt when: TTY environment + QMD not installed + never asked before.
|
|
805
|
+
// Uses synchronous fs.readSync on stdin — no async/readline complexity.
|
|
806
|
+
// Writes a flag file after asking so this prompt never appears again.
|
|
807
|
+
(function maybeOfferQmd() {
|
|
808
|
+
const QMD_OFFERED_FILE = path.join(METAME_DIR, '.qmd_offered');
|
|
809
|
+
const isTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
810
|
+
if (!isTTY) return; // non-interactive env: CI, pipe, etc.
|
|
811
|
+
if (fs.existsSync(QMD_OFFERED_FILE)) return; // already offered before
|
|
812
|
+
|
|
813
|
+
// Check if QMD already installed
|
|
814
|
+
try { execSync('which qmd', { stdio: 'pipe', timeout: 2000 }); return; } catch { }
|
|
815
|
+
|
|
816
|
+
// Mark as offered NOW — so crash/ctrl-c won't re-ask
|
|
817
|
+
try { fs.writeFileSync(QMD_OFFERED_FILE, new Date().toISOString(), 'utf8'); } catch { }
|
|
818
|
+
|
|
819
|
+
// Check bun availability
|
|
820
|
+
let bunAvailable = false;
|
|
821
|
+
try { execSync('which bun', { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
|
|
822
|
+
|
|
823
|
+
console.log('');
|
|
824
|
+
console.log('┌─ 🔍 记忆搜索增强(可选,免费)');
|
|
825
|
+
console.log('│');
|
|
826
|
+
console.log('│ 当前模式:基础全文搜索(FTS5)');
|
|
827
|
+
console.log('│ 安装 QMD 后:BM25 + 向量语义 + 重排序 混合搜索');
|
|
828
|
+
console.log('│ 效果:召回质量约 5x,模糊描述也能精准命中历史记忆');
|
|
829
|
+
if (!bunAvailable) {
|
|
830
|
+
console.log('│');
|
|
831
|
+
console.log('│ ⚠️ 未检测到 bun,无法自动安装。');
|
|
832
|
+
console.log('│ 手动安装:curl -fsSL https://bun.sh/install | bash');
|
|
833
|
+
console.log('│ bun install -g github:tobi/qmd');
|
|
834
|
+
console.log('└────────────────────────────────────────────────');
|
|
835
|
+
console.log('');
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
console.log('│ 耗时:约 30 秒');
|
|
839
|
+
console.log('│');
|
|
840
|
+
|
|
841
|
+
// Synchronous prompt — read one character from stdin
|
|
842
|
+
try {
|
|
843
|
+
process.stdout.write('└─ 立即安装?(y/N) › ');
|
|
844
|
+
const buf = Buffer.alloc(8);
|
|
845
|
+
const n = fs.readSync(0, buf, 0, 8);
|
|
846
|
+
const answer = buf.slice(0, n).toString().trim().toLowerCase();
|
|
847
|
+
process.stdout.write('\n');
|
|
848
|
+
|
|
849
|
+
if (answer === 'y' || answer === 'yes') {
|
|
850
|
+
console.log(' ⬇️ 正在安装 QMD...');
|
|
851
|
+
try {
|
|
852
|
+
execSync('bun install -g github:tobi/qmd', { stdio: 'inherit', timeout: 120000 });
|
|
853
|
+
console.log(' ✅ QMD 已安装,下次记忆搜索自动启用向量模式。');
|
|
854
|
+
} catch {
|
|
855
|
+
console.log(' ⚠️ 安装失败,可手动执行:bun install -g github:tobi/qmd');
|
|
856
|
+
}
|
|
857
|
+
} else {
|
|
858
|
+
console.log(' 跳过。如需日后安装:bun install -g github:tobi/qmd');
|
|
859
|
+
}
|
|
860
|
+
console.log('');
|
|
861
|
+
} catch {
|
|
862
|
+
// stdin not readable (edge case) — silent skip
|
|
863
|
+
}
|
|
864
|
+
})();
|
|
865
|
+
|
|
736
866
|
// ---------------------------------------------------------
|
|
737
867
|
// 5. LAUNCH CLAUDE (OR HOT RELOAD)
|
|
738
868
|
// ---------------------------------------------------------
|
|
@@ -1233,8 +1363,8 @@ if (isDaemon) {
|
|
|
1233
1363
|
cfg.feishu.app_secret = feishuSecret;
|
|
1234
1364
|
if (!cfg.feishu.allowed_chat_ids) cfg.feishu.allowed_chat_ids = [];
|
|
1235
1365
|
console.log(" ✅ Feishu configured!");
|
|
1236
|
-
console.log(" Note: allowed_chat_ids is empty =
|
|
1237
|
-
console.log("
|
|
1366
|
+
console.log(" Note: allowed_chat_ids is empty = deny all users.");
|
|
1367
|
+
console.log(" Add chat IDs to daemon.yaml or use /agent bind from target chat.\n");
|
|
1238
1368
|
}
|
|
1239
1369
|
} else {
|
|
1240
1370
|
console.log(" Skipped.\n");
|
|
@@ -1465,13 +1595,29 @@ WantedBy=default.target
|
|
|
1465
1595
|
|
|
1466
1596
|
// Tasks
|
|
1467
1597
|
const tasks = state.tasks || {};
|
|
1468
|
-
|
|
1598
|
+
const configuredTaskNames = new Set();
|
|
1599
|
+
for (const t of ((config.heartbeat && config.heartbeat.tasks) || [])) {
|
|
1600
|
+
if (t && t.name) configuredTaskNames.add(t.name);
|
|
1601
|
+
}
|
|
1602
|
+
for (const proj of Object.values(config.projects || {})) {
|
|
1603
|
+
for (const t of ((proj && proj.heartbeat_tasks) || [])) {
|
|
1604
|
+
if (t && t.name) configuredTaskNames.add(t.name);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const taskEntries = Object.entries(tasks).filter(([name]) =>
|
|
1608
|
+
configuredTaskNames.size === 0 || configuredTaskNames.has(name)
|
|
1609
|
+
);
|
|
1610
|
+
if (taskEntries.length > 0) {
|
|
1469
1611
|
console.log(" Recent tasks:");
|
|
1470
|
-
for (const [name, info] of
|
|
1612
|
+
for (const [name, info] of taskEntries) {
|
|
1471
1613
|
const icon = info.status === 'success' ? '✅' : '❌';
|
|
1472
1614
|
console.log(` ${icon} ${name}: ${info.last_run || 'unknown'}`);
|
|
1473
1615
|
if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
|
|
1474
1616
|
}
|
|
1617
|
+
const hiddenStale = Object.keys(tasks).length - taskEntries.length;
|
|
1618
|
+
if (hiddenStale > 0) {
|
|
1619
|
+
console.log(` … ${hiddenStale} stale task record(s) hidden`);
|
|
1620
|
+
}
|
|
1475
1621
|
}
|
|
1476
1622
|
process.exit(0);
|
|
1477
1623
|
}
|
|
@@ -1643,4 +1789,4 @@ child.on('error', () => {
|
|
|
1643
1789
|
child.on('close', (code) => process.exit(code || 0));
|
|
1644
1790
|
|
|
1645
1791
|
// Launch background distillation AFTER Claude starts — no blocking
|
|
1646
|
-
spawnDistillBackground();
|
|
1792
|
+
spawnDistillBackground();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.13",
|
|
4
4
|
"description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test scripts/*.test.js",
|
|
15
15
|
"start": "node index.js",
|
|
16
|
-
"sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
16
|
+
"sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-command-router.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
17
17
|
"restart:daemon": "node index.js stop 2>/dev/null; sleep 1; node index.js start 2>/dev/null || echo '⚠️ Daemon not running or restart failed'",
|
|
18
18
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
|
19
19
|
},
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=22.5"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|