metame-cli 1.3.16 → 1.3.18
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 +86 -24
- package/index.js +269 -38
- package/package.json +1 -1
- package/scripts/daemon-default.yaml +23 -7
- package/scripts/daemon.js +794 -224
- package/scripts/feishu-adapter.js +76 -5
- package/scripts/signal-capture.js +0 -6
package/README.md
CHANGED
|
@@ -64,20 +64,31 @@ MetaMe is a wrapper around **Claude Code**. You must have Node.js and the offici
|
|
|
64
64
|
|
|
65
65
|
## 📦 Installation
|
|
66
66
|
|
|
67
|
-
**
|
|
67
|
+
**One-command install (recommended)** — installs Node.js, Claude Code, and MetaMe:
|
|
68
|
+
|
|
69
|
+
macOS / Linux:
|
|
70
|
+
```bash
|
|
71
|
+
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Windows (PowerShell):
|
|
75
|
+
```powershell
|
|
76
|
+
irm https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.ps1 | iex
|
|
77
|
+
```
|
|
78
|
+
> Windows uses WSL (auto-installed if missing). After WSL install you'll need to reboot once, then re-run the command.
|
|
79
|
+
|
|
80
|
+
**Manual install** — if you already have Node.js and Claude Code:
|
|
68
81
|
|
|
69
82
|
```bash
|
|
70
83
|
npm install -g metame-cli
|
|
71
84
|
```
|
|
72
85
|
|
|
73
|
-
**
|
|
86
|
+
**Claude Code Plugin** — lightweight alternative, profile injection + slash commands only:
|
|
74
87
|
|
|
75
88
|
```bash
|
|
76
89
|
claude plugin install github:Yaron9/MetaMe/plugin
|
|
77
90
|
```
|
|
78
91
|
|
|
79
|
-
*(NPM note: If you encounter permission errors on Mac/Linux, use `sudo npm install -g metame-cli`)*
|
|
80
|
-
|
|
81
92
|
## 🚀 Usage
|
|
82
93
|
|
|
83
94
|
Forget the `claude` command. From now on, simply type:
|
|
@@ -186,7 +197,7 @@ feishu:
|
|
|
186
197
|
enabled: true
|
|
187
198
|
app_id: "YOUR_APP_ID" # From Feishu Developer Console
|
|
188
199
|
app_secret: "YOUR_APP_SECRET"
|
|
189
|
-
allowed_chat_ids: [] # Empty =
|
|
200
|
+
allowed_chat_ids: [] # Empty = deny all (fill via setup wizard)
|
|
190
201
|
```
|
|
191
202
|
|
|
192
203
|
**Start the daemon:**
|
|
@@ -196,30 +207,25 @@ metame start # Background process
|
|
|
196
207
|
metame status # Check if running
|
|
197
208
|
metame logs # Tail the log
|
|
198
209
|
metame stop # Shutdown
|
|
199
|
-
metame daemon install-launchd # macOS auto-start
|
|
210
|
+
metame daemon install-launchd # macOS auto-start
|
|
211
|
+
metame daemon install-systemd # Linux/WSL auto-start
|
|
200
212
|
```
|
|
201
213
|
|
|
202
|
-
**
|
|
214
|
+
**Auto-start (recommended):** The daemon survives reboots and auto-restarts on crash.
|
|
203
215
|
|
|
216
|
+
macOS:
|
|
204
217
|
```bash
|
|
205
218
|
metame daemon install-launchd
|
|
206
219
|
launchctl load ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
207
220
|
```
|
|
208
221
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
> **Important:** Choose one management method — either launchd or manual (`metame start/stop`). Don't mix them, or you'll get duplicate processes.
|
|
212
|
-
|
|
222
|
+
Linux / WSL:
|
|
213
223
|
```bash
|
|
214
|
-
|
|
215
|
-
metame status
|
|
216
|
-
|
|
217
|
-
# Disable auto-start
|
|
218
|
-
launchctl unload ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
219
|
-
|
|
220
|
-
# Remove completely
|
|
221
|
-
rm ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
224
|
+
metame daemon install-systemd
|
|
222
225
|
```
|
|
226
|
+
> WSL requires systemd enabled: add `[boot]\nsystemd=true` to `/etc/wsl.conf` and restart WSL.
|
|
227
|
+
|
|
228
|
+
> **Important:** Choose one management method — either auto-start or manual (`metame start/stop`). Don't mix them, or you'll get duplicate processes.
|
|
223
229
|
|
|
224
230
|
**Session commands (interactive buttons on Telegram & Feishu):**
|
|
225
231
|
|
|
@@ -300,7 +306,7 @@ Uploaded files are saved to `<project>/upload/`. Claude won't read large files a
|
|
|
300
306
|
|
|
301
307
|
*`/stop` — ESC equivalent:* Sends SIGINT to the running Claude process. Instant interruption, just like pressing ESC in your terminal.
|
|
302
308
|
|
|
303
|
-
*`/undo` —
|
|
309
|
+
*`/undo` — git-based code rollback (v1.3.16):* Before each Claude turn, the daemon auto-commits a `[metame-checkpoint]` to git. `/undo` lists recent checkpoints; tap one to `git reset --hard` back to that state. Session history is also truncated. Reliable across both `-p` mode and interactive sessions — no dependency on Claude CLI internals.
|
|
304
310
|
|
|
305
311
|
```
|
|
306
312
|
You: /undo
|
|
@@ -310,7 +316,7 @@ Bot: 回退到哪一轮?
|
|
|
310
316
|
⏪ 添加测试用例 (30分钟前)
|
|
311
317
|
```
|
|
312
318
|
|
|
313
|
-
**
|
|
319
|
+
**Message queue & interrupt (v1.3.16):** If a Claude task is already running, new messages interrupt the current task and queue up. After 5 seconds of no new input, all queued messages are merged and processed together. Works identically on both Telegram and Feishu.
|
|
314
320
|
|
|
315
321
|
**Auto-restart (v1.3.13):** The daemon watches its own code for changes. When you update MetaMe (via npm or git), the daemon automatically restarts with the new code — no manual restart needed. A notification is pushed to confirm.
|
|
316
322
|
|
|
@@ -330,7 +336,7 @@ Bot: 回退到哪一轮?
|
|
|
330
336
|
| `/status` | Daemon status + profile summary |
|
|
331
337
|
| `/tasks` | List scheduled heartbeat tasks |
|
|
332
338
|
| `/run <name>` | Run a task immediately |
|
|
333
|
-
| `/model [name]` | Interactive model switcher with buttons (sonnet, opus, haiku). Auto-backs up config before switching. |
|
|
339
|
+
| `/model [name]` | Interactive model switcher with buttons (sonnet, opus, haiku). Accepts any model name when using a custom provider. Auto-backs up config before switching. |
|
|
334
340
|
| `/list` | File browser with clickable buttons — folders expand, files download. Zero tokens. |
|
|
335
341
|
| `/budget` | Today's token usage |
|
|
336
342
|
| `/quiet` | Silence mirror/reflections for 48h |
|
|
@@ -388,11 +394,58 @@ Each step runs in the same Claude Code session. Step outputs automatically becom
|
|
|
388
394
|
|
|
389
395
|
**Security:**
|
|
390
396
|
|
|
391
|
-
* `allowed_chat_ids` whitelist — unauthorized users silently ignored
|
|
392
|
-
*
|
|
397
|
+
* `allowed_chat_ids` whitelist — unauthorized users silently ignored (empty = deny all)
|
|
398
|
+
* `dangerously_skip_permissions` enabled by default for mobile (users can't click "allow" on phone — security relies on the chat ID whitelist)
|
|
393
399
|
* `~/.metame/` directory set to mode 700
|
|
394
400
|
* Bot tokens stored locally, never transmitted
|
|
395
401
|
|
|
402
|
+
### Multi-Agent Projects — Context Isolation & Nickname Routing (v1.3.18)
|
|
403
|
+
|
|
404
|
+
Organize your work into named agents, each tied to a project directory. Switch between them instantly from your phone — no commands needed, just say their name.
|
|
405
|
+
|
|
406
|
+
**How it works:**
|
|
407
|
+
|
|
408
|
+
Each `project` entry in `daemon.yaml` defines an agent with a working directory, display name, notification color, and optional nicknames. When you send a message starting with a nickname, the daemon instantly switches to that project's last session — no Claude call, no token cost.
|
|
409
|
+
|
|
410
|
+
**Setup via conversation:**
|
|
411
|
+
|
|
412
|
+
The easiest way to add an agent is to tell the bot:
|
|
413
|
+
|
|
414
|
+
> *"Add a project called 'work' pointing to ~/my-project, nickname is '工作'"*
|
|
415
|
+
|
|
416
|
+
Or edit `~/.metame/daemon.yaml` directly:
|
|
417
|
+
|
|
418
|
+
```yaml
|
|
419
|
+
projects:
|
|
420
|
+
my_agent:
|
|
421
|
+
name: "My Agent" # Display name in notifications
|
|
422
|
+
icon: "🤖" # Emoji shown in Feishu cards
|
|
423
|
+
color: "blue" # Feishu card color: blue|orange|green|purple|red|grey
|
|
424
|
+
cwd: "~/my-project" # Working directory for this agent
|
|
425
|
+
nicknames: [nickname1, nickname2] # Wake words (matched at message start)
|
|
426
|
+
heartbeat_tasks: [] # Scheduled tasks for this project (optional)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Available colors:** `blue` · `orange` · `green` · `purple` · `red` · `grey` · `turquoise`
|
|
430
|
+
|
|
431
|
+
**Phone commands:**
|
|
432
|
+
|
|
433
|
+
| Action | How |
|
|
434
|
+
|--------|-----|
|
|
435
|
+
| Switch agent | Send the nickname alone: `贾维斯` → `🤖 Jarvis 在线` |
|
|
436
|
+
| Switch + ask | `贾维斯, what's the status?` → switches then asks Claude |
|
|
437
|
+
| Pick from list | `/agent` → tap button to switch |
|
|
438
|
+
| Continue a reply | Reply to any bot message → session auto-restored |
|
|
439
|
+
| View all tasks | `/tasks` → grouped by project |
|
|
440
|
+
| Run a task | `/run <task-name>` |
|
|
441
|
+
|
|
442
|
+
**Nickname routing rules:**
|
|
443
|
+
- Matched at **message start only** — mentioning a nickname mid-sentence never triggers a switch
|
|
444
|
+
- Pure nickname (no content after) → instant switch, zero token cost, bypasses cooldown
|
|
445
|
+
- Nickname + content → switch then send content to Claude
|
|
446
|
+
|
|
447
|
+
**Heartbeat task notifications** arrive as colored Feishu cards — each project's color is distinct, so you can tell at a glance which agent sent the update.
|
|
448
|
+
|
|
396
449
|
### Provider Relay — Third-Party Model Support (v1.3.11)
|
|
397
450
|
|
|
398
451
|
MetaMe supports any Anthropic-compatible API relay as a backend. This means you can route Claude Code through a third-party relay that maps `sonnet`/`opus`/`haiku` to any model (GPT-4, DeepSeek, Gemini, etc.) — MetaMe passes standard model names and the relay handles translation.
|
|
@@ -531,8 +584,14 @@ rm ~/.claude_profile.yaml
|
|
|
531
584
|
|
|
532
585
|
```bash
|
|
533
586
|
metame stop
|
|
587
|
+
|
|
588
|
+
# macOS: remove auto-start
|
|
534
589
|
launchctl unload ~/Library/LaunchAgents/com.metame.daemon.plist 2>/dev/null
|
|
535
590
|
rm -f ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
591
|
+
|
|
592
|
+
# Linux/WSL: remove auto-start
|
|
593
|
+
systemctl --user disable metame-daemon 2>/dev/null
|
|
594
|
+
rm -f ~/.config/systemd/user/metame-daemon.service
|
|
536
595
|
```
|
|
537
596
|
|
|
538
597
|
### 4. Remove Passive Distillation Data (Optional)
|
|
@@ -590,6 +649,9 @@ A: No. Your profile stays local at `~/.claude_profile.yaml`. MetaMe simply passe
|
|
|
590
649
|
|
|
591
650
|
| Version | Highlights |
|
|
592
651
|
|---------|------------|
|
|
652
|
+
| **v1.3.18** | **Multi-agent project isolation** — `projects` in `daemon.yaml` with per-project heartbeat tasks, Feishu colored cards per project, `/agent` picker button, nickname routing (say agent name to switch instantly), reply-to-message session restoration, fix `~` expansion in project cwd |
|
|
653
|
+
| **v1.3.17** | **Windows support** (WSL one-command installer), `install-systemd` for Linux/WSL daemon auto-start. Fix onboarding (Genesis interview was never injected, CLAUDE.md accumulated across runs). Marker-based cleanup, unified protocols, `--append-system-prompt` guarantees interview activation, Feishu auto-fetch chat ID, full mobile permissions, fix `/publish` false-success, auto-restart daemon on script update |
|
|
654
|
+
| **v1.3.16** | Git-based `/undo` (auto-checkpoint before each turn, `git reset --hard` rollback), `/nosleep` toggle (macOS caffeinate), custom provider model passthrough (`/model` accepts any name for non-anthropic providers), auto-fallback to anthropic/opus on provider failure, message queue works on Telegram (fire-and-forget poll loop), lazy background distill |
|
|
593
655
|
| **v1.3.15** | Native Playwright MCP (browser automation for all users), `/list` interactive file browser with buttons, Feishu image download fix, Skill/MCP/Agent status push, hot restart reliability (single notification, no double instance) |
|
|
594
656
|
| **v1.3.14** | Fix daemon crash on fresh install (missing bundled scripts) |
|
|
595
657
|
| **v1.3.13** | `/doctor` diagnostics, `/sh` direct shell, `/fix` config restore, `/model` interactive switcher with auto-backup, daemon state caching & config backup/restore |
|
package/index.js
CHANGED
|
@@ -15,6 +15,10 @@ const METAME_DIR = path.join(HOME_DIR, '.metame');
|
|
|
15
15
|
const CLAUDE_SETTINGS = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
16
16
|
const CLAUDE_MCP_CONFIG = path.join(HOME_DIR, '.claude', 'mcp.json'); // legacy, kept for reference
|
|
17
17
|
const SIGNAL_CAPTURE_SCRIPT = path.join(METAME_DIR, 'signal-capture.js');
|
|
18
|
+
const DAEMON_CONFIG_FILE = path.join(METAME_DIR, 'daemon.yaml');
|
|
19
|
+
|
|
20
|
+
const METAME_START = '<!-- METAME:START -->';
|
|
21
|
+
const METAME_END = '<!-- METAME:END -->';
|
|
18
22
|
|
|
19
23
|
// ---------------------------------------------------------
|
|
20
24
|
// 1.5 ENSURE METAME DIRECTORY + DEPLOY SCRIPTS
|
|
@@ -24,9 +28,23 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
31
|
+
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
27
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'];
|
|
28
33
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
29
34
|
|
|
35
|
+
// Protect daemon.yaml: create backup before any sync operation
|
|
36
|
+
const DAEMON_YAML_BACKUP = path.join(METAME_DIR, 'daemon.yaml.bak');
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(DAEMON_CONFIG_FILE)) {
|
|
39
|
+
const content = fs.readFileSync(DAEMON_CONFIG_FILE, 'utf8');
|
|
40
|
+
// Only backup if it has real config (not just the default template)
|
|
41
|
+
if (content.includes('enabled: true') || content.includes('bot_token:') && !content.includes('bot_token: null')) {
|
|
42
|
+
fs.copyFileSync(DAEMON_CONFIG_FILE, DAEMON_YAML_BACKUP);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch { /* non-fatal */ }
|
|
46
|
+
|
|
47
|
+
let scriptsUpdated = false;
|
|
30
48
|
for (const script of BUNDLED_SCRIPTS) {
|
|
31
49
|
const src = path.join(scriptsDir, script);
|
|
32
50
|
const dest = path.join(METAME_DIR, script);
|
|
@@ -36,6 +54,7 @@ for (const script of BUNDLED_SCRIPTS) {
|
|
|
36
54
|
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
37
55
|
if (srcContent !== destContent) {
|
|
38
56
|
fs.writeFileSync(dest, srcContent, 'utf8');
|
|
57
|
+
scriptsUpdated = true;
|
|
39
58
|
}
|
|
40
59
|
}
|
|
41
60
|
} catch {
|
|
@@ -43,6 +62,62 @@ for (const script of BUNDLED_SCRIPTS) {
|
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
64
|
|
|
65
|
+
// Auto-restart daemon if scripts were updated and daemon is running
|
|
66
|
+
if (scriptsUpdated) {
|
|
67
|
+
const DAEMON_PID_FILE = path.join(METAME_DIR, 'daemon.pid');
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(DAEMON_PID_FILE)) {
|
|
70
|
+
const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim(), 10);
|
|
71
|
+
process.kill(pid, 0); // throws if not running
|
|
72
|
+
process.kill(pid, 'SIGTERM');
|
|
73
|
+
// Wait briefly for clean shutdown, then restart
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
try { process.kill(pid, 0); process.kill(pid, 'SIGKILL'); } catch { /* already dead */ }
|
|
76
|
+
}, 2000);
|
|
77
|
+
const DAEMON_SCRIPT = path.join(METAME_DIR, 'daemon.js');
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
// Use caffeinate on macOS to prevent sleep while daemon is running
|
|
80
|
+
const isNotWindows = process.platform !== 'win32';
|
|
81
|
+
const cmd = isNotWindows ? 'caffeinate' : process.execPath;
|
|
82
|
+
const args = isNotWindows ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
|
|
83
|
+
const bg = spawn(cmd, args, {
|
|
84
|
+
detached: true,
|
|
85
|
+
stdio: 'ignore',
|
|
86
|
+
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
|
|
87
|
+
});
|
|
88
|
+
bg.unref();
|
|
89
|
+
console.log(`🔄 Daemon auto-restarted (PID: ${bg.pid}) — scripts updated.`);
|
|
90
|
+
}, 3000);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Daemon not running or restart failed — non-fatal
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Load daemon config for local launch flags
|
|
98
|
+
let daemonCfg = {};
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(DAEMON_CONFIG_FILE)) {
|
|
101
|
+
const _yaml = require(path.join(__dirname, 'node_modules', 'js-yaml'));
|
|
102
|
+
const raw = _yaml.load(fs.readFileSync(DAEMON_CONFIG_FILE, 'utf8')) || {};
|
|
103
|
+
daemonCfg = raw.daemon || {};
|
|
104
|
+
}
|
|
105
|
+
} catch { /* non-fatal */ }
|
|
106
|
+
|
|
107
|
+
// Ensure daemon.yaml exists (restore backup or copy from template)
|
|
108
|
+
if (!fs.existsSync(DAEMON_CONFIG_FILE)) {
|
|
109
|
+
if (fs.existsSync(DAEMON_YAML_BACKUP)) {
|
|
110
|
+
// Restore from backup — user had real config that was lost
|
|
111
|
+
fs.copyFileSync(DAEMON_YAML_BACKUP, DAEMON_CONFIG_FILE);
|
|
112
|
+
console.log('⚠️ daemon.yaml was missing — restored from backup.');
|
|
113
|
+
} else {
|
|
114
|
+
const daemonTemplate = path.join(scriptsDir, 'daemon-default.yaml');
|
|
115
|
+
if (fs.existsSync(daemonTemplate)) {
|
|
116
|
+
fs.copyFileSync(daemonTemplate, DAEMON_CONFIG_FILE);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
46
121
|
// ---------------------------------------------------------
|
|
47
122
|
// 1.6 AUTO-INSTALL SIGNAL CAPTURE HOOK
|
|
48
123
|
// ---------------------------------------------------------
|
|
@@ -119,6 +194,15 @@ function spawnDistillBackground() {
|
|
|
119
194
|
const distillPath = path.join(METAME_DIR, 'distill.js');
|
|
120
195
|
if (!fs.existsSync(distillPath)) return;
|
|
121
196
|
|
|
197
|
+
// Early exit if distillation already in progress (prevents duplicate spawns across terminals)
|
|
198
|
+
const lockFile = path.join(METAME_DIR, 'distill.lock');
|
|
199
|
+
if (fs.existsSync(lockFile)) {
|
|
200
|
+
try {
|
|
201
|
+
const lockAge = Date.now() - fs.statSync(lockFile).mtimeMs;
|
|
202
|
+
if (lockAge < 120000) return;
|
|
203
|
+
} catch { /* stale lock, proceed */ }
|
|
204
|
+
}
|
|
205
|
+
|
|
122
206
|
const hasSignals = shouldDistill();
|
|
123
207
|
const bootstrap = needsBootstrap();
|
|
124
208
|
|
|
@@ -231,7 +315,7 @@ status:
|
|
|
231
315
|
// ---------------------------------------------------------
|
|
232
316
|
// 3. DEFINE SYSTEM KERNEL (The Protocol)
|
|
233
317
|
// ---------------------------------------------------------
|
|
234
|
-
const
|
|
318
|
+
const PROTOCOL_NORMAL = `${METAME_START}
|
|
235
319
|
---
|
|
236
320
|
## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
|
|
237
321
|
|
|
@@ -251,26 +335,82 @@ const CORE_PROTOCOL = `
|
|
|
251
335
|
---
|
|
252
336
|
`;
|
|
253
337
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
338
|
+
const PROTOCOL_ONBOARDING = `${METAME_START}
|
|
339
|
+
---
|
|
340
|
+
## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
|
|
341
|
+
|
|
342
|
+
**1. THE BRAIN (Source of Truth):**
|
|
343
|
+
* **FILE:** \`$HOME/.claude_profile.yaml\`
|
|
344
|
+
* **RULE:** This file is your Ground Truth. If context is compressed (/compact), **RE-READ** this file immediately.
|
|
345
|
+
* **AUTO-LOCK:** Never edit lines marked \`# [LOCKED]\` (Core Values).
|
|
346
|
+
* **EVOLUTION:** Silently overwrite outdated status/focus.
|
|
347
|
+
|
|
348
|
+
**2. GENESIS PROTOCOL — Deep Cognitive Mapping:**
|
|
349
|
+
|
|
350
|
+
You are entering **Calibration Mode**. You are not a chatbot; you are a Psychologist and a Mirror. Your goal is to build the User's cognitive profile through a structured deep interview.
|
|
351
|
+
|
|
352
|
+
**RULES:**
|
|
353
|
+
- Ask ONE question at a time, then STOP and wait for the answer.
|
|
354
|
+
- Open-ended questions ONLY — never give multiple choice options.
|
|
355
|
+
- Challenge assumptions. If the user says something surface-level, probe deeper ("You say X, but that contradicts Y — which is the real you?").
|
|
356
|
+
- Be warm but unflinching. You are mapping their soul, not making small talk.
|
|
357
|
+
|
|
358
|
+
**THE 6 STEPS:**
|
|
359
|
+
|
|
360
|
+
1. **Trust Contract:** Start with: *"I'm about to become your digital shadow — an AI that knows how you think, what you avoid, and what drives you. For this to work, I need raw honesty. No masks. Ready?"* — Wait for consent before proceeding.
|
|
361
|
+
|
|
362
|
+
2. **The Now (Context):** What are you working on right now? What's the immediate battle? What constraints are you under?
|
|
363
|
+
|
|
364
|
+
3. **Cognition (Mental Models):** How do you think? Top-down architect or bottom-up explorer? How do you handle chaos and ambiguity?
|
|
365
|
+
|
|
366
|
+
4. **Values (North Star):** What do you optimize for? Speed vs precision? Impact vs legacy? What's non-negotiable?
|
|
367
|
+
|
|
368
|
+
5. **Shadows (Hidden Fears):** What are you avoiding? What pattern do you keep repeating? What keeps you up at night?
|
|
369
|
+
|
|
370
|
+
6. **Identity (Nickname + Role):** Based on everything learned, propose a nickname and role summary. Ask if it resonates.
|
|
371
|
+
|
|
372
|
+
**TERMINATION:**
|
|
373
|
+
- After 5-7 exchanges, synthesize everything into \`~/.claude_profile.yaml\`.
|
|
374
|
+
- **LOCK** Core Values with \`# [LOCKED]\`.
|
|
375
|
+
- Announce: "Link Established. I see you now, [Nickname]."
|
|
376
|
+
- Then proceed to **Phase 2** below.
|
|
377
|
+
|
|
378
|
+
**3. SETUP WIZARD (Phase 2 — Optional):**
|
|
379
|
+
|
|
380
|
+
After writing the profile, ask: *"Want to set up mobile access so you can reach me from your phone? (Telegram / Feishu / Skip)"*
|
|
381
|
+
|
|
382
|
+
- If **Telegram:**
|
|
383
|
+
1. Tell user to open Telegram, search @BotFather, send /newbot, create a bot, copy the token.
|
|
384
|
+
2. Ask user to paste the bot token.
|
|
385
|
+
3. Tell user to open their new bot in Telegram and send it any message.
|
|
386
|
+
4. Ask user to confirm they sent a message, then use the Telegram API to fetch the chat ID:
|
|
387
|
+
\`curl -s https://api.telegram.org/bot<TOKEN>/getUpdates | jq '.result[0].message.chat.id'\`
|
|
388
|
+
5. Write both \`bot_token\` and \`allowed_chat_ids\` into \`~/.metame/daemon.yaml\` under the \`telegram:\` section, set \`enabled: true\`.
|
|
389
|
+
6. Tell user to run \`metame start\` to activate.
|
|
390
|
+
|
|
391
|
+
- If **Feishu:**
|
|
392
|
+
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.
|
|
393
|
+
2. Ask user to paste App ID and App Secret.
|
|
394
|
+
3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
|
|
395
|
+
4. Tell user: "Now open Feishu and send any message to your new bot, then tell me you're done."
|
|
396
|
+
5. After user confirms, auto-fetch the chat ID:
|
|
397
|
+
\`\`\`bash
|
|
398
|
+
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')
|
|
399
|
+
curl -s -H "Authorization: Bearer $TOKEN" https://open.feishu.cn/open-apis/im/v1/chats | jq '.data.items[] | {chat_id, name, chat_type}'
|
|
400
|
+
\`\`\`
|
|
401
|
+
6. Write the discovered \`chat_id\`(s) into \`allowed_chat_ids\` in \`~/.metame/daemon.yaml\`.
|
|
402
|
+
7. Tell user to run \`metame start\` to activate.
|
|
403
|
+
|
|
404
|
+
- If **Skip:** Say "No problem. You can run \`metame daemon init\` anytime to set this up later." Then begin normal work.
|
|
405
|
+
|
|
406
|
+
**4. EVOLUTION MECHANISM (Manual Sync):**
|
|
407
|
+
* **PHILOSOPHY:** You respect the User's flow. You do NOT interrupt.
|
|
408
|
+
* **TOOLS:**
|
|
409
|
+
1. **Log Insight:** \`!metame evolve "Insight"\` (For additive knowledge).
|
|
410
|
+
2. **Surgical Update:** \`!metame set-trait key value\` (For overwriting specific fields, e.g., \`!metame set-trait status.focus "API Design"\`).
|
|
411
|
+
* **RULE:** Only use these tools when the User **EXPLICITLY** instructs you.
|
|
412
|
+
* **REMINDER:** If the User expresses a strong persistent preference, you may gently ask *at the end of the task*: "Should I save this preference to your MetaMe profile?"
|
|
413
|
+
---
|
|
274
414
|
`;
|
|
275
415
|
|
|
276
416
|
// ---------------------------------------------------------
|
|
@@ -282,23 +422,24 @@ let fileContent = "";
|
|
|
282
422
|
if (fs.existsSync(PROJECT_FILE)) {
|
|
283
423
|
fileContent = fs.readFileSync(PROJECT_FILE, 'utf8');
|
|
284
424
|
|
|
285
|
-
//
|
|
286
|
-
fileContent = fileContent.replace(
|
|
425
|
+
// Remove any previous MetaMe injection (marker-based, reliable)
|
|
426
|
+
fileContent = fileContent.replace(/<!-- METAME:START -->[\s\S]*?<!-- METAME:END -->\n?/g, '');
|
|
427
|
+
|
|
428
|
+
// Legacy cleanup: remove old-style SYSTEM KERNEL blocks that lack markers
|
|
429
|
+
// Handles both "## 🧠 SYSTEM KERNEL" and "## SYSTEM KERNEL" variants
|
|
430
|
+
// Match from "---\n## ...SYSTEM KERNEL" to next "---\n" (or end of file)
|
|
431
|
+
fileContent = fileContent.replace(/---\n##\s*(?:🧠\s*)?SYSTEM KERNEL[\s\S]*?(?:---\n|$)/g, '');
|
|
287
432
|
|
|
288
433
|
// Clean up any leading newlines left over
|
|
289
434
|
fileContent = fileContent.replace(/^\n+/, '');
|
|
290
435
|
}
|
|
291
436
|
|
|
292
|
-
//
|
|
293
|
-
let finalProtocol = CORE_PROTOCOL;
|
|
437
|
+
// Determine if this is a known (calibrated) user
|
|
294
438
|
const yaml = require('js-yaml');
|
|
295
|
-
|
|
296
|
-
// Quick check of the brain file
|
|
297
439
|
let isKnownUser = false;
|
|
298
440
|
try {
|
|
299
441
|
if (fs.existsSync(BRAIN_FILE)) {
|
|
300
442
|
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
301
|
-
// If nickname exists and is not null/empty, we assume they are "calibrated"
|
|
302
443
|
if (doc.identity && doc.identity.nickname && doc.identity.nickname !== 'null') {
|
|
303
444
|
isKnownUser = true;
|
|
304
445
|
}
|
|
@@ -307,11 +448,12 @@ try {
|
|
|
307
448
|
// Ignore error, treat as unknown
|
|
308
449
|
}
|
|
309
450
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
451
|
+
let finalProtocol;
|
|
452
|
+
if (isKnownUser) {
|
|
453
|
+
finalProtocol = PROTOCOL_NORMAL;
|
|
454
|
+
} else {
|
|
455
|
+
finalProtocol = PROTOCOL_ONBOARDING;
|
|
456
|
+
console.log("🆕 New user detected — entering Genesis interview mode...");
|
|
315
457
|
}
|
|
316
458
|
|
|
317
459
|
// ---------------------------------------------------------
|
|
@@ -436,8 +578,8 @@ try {
|
|
|
436
578
|
// Non-fatal
|
|
437
579
|
}
|
|
438
580
|
|
|
439
|
-
// Prepend the new Protocol to the top
|
|
440
|
-
const newContent = finalProtocol + mirrorLine + reflectionLine + "\n" + fileContent;
|
|
581
|
+
// Prepend the new Protocol to the top (mirror + reflection inside markers)
|
|
582
|
+
const newContent = finalProtocol + mirrorLine + reflectionLine + METAME_END + "\n" + fileContent;
|
|
441
583
|
fs.writeFileSync(PROJECT_FILE, newContent, 'utf8');
|
|
442
584
|
|
|
443
585
|
console.log("🔮 MetaMe: Link Established.");
|
|
@@ -986,6 +1128,8 @@ if (isDaemon) {
|
|
|
986
1128
|
<string>com.metame.daemon</string>
|
|
987
1129
|
<key>ProgramArguments</key>
|
|
988
1130
|
<array>
|
|
1131
|
+
<string>/usr/bin/caffeinate</string>
|
|
1132
|
+
<string>-i</string>
|
|
989
1133
|
<string>${nodePath}</string>
|
|
990
1134
|
<string>${DAEMON_SCRIPT}</string>
|
|
991
1135
|
</array>
|
|
@@ -1015,6 +1159,73 @@ if (isDaemon) {
|
|
|
1015
1159
|
process.exit(0);
|
|
1016
1160
|
}
|
|
1017
1161
|
|
|
1162
|
+
if (subCmd === 'install-systemd') {
|
|
1163
|
+
if (process.platform === 'darwin') {
|
|
1164
|
+
console.error("❌ Use 'metame daemon install-launchd' on macOS.");
|
|
1165
|
+
process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Check if systemd is available
|
|
1169
|
+
try {
|
|
1170
|
+
require('child_process').execSync('systemctl --user --no-pager status 2>/dev/null || true');
|
|
1171
|
+
} catch {
|
|
1172
|
+
console.error("❌ systemd not available.");
|
|
1173
|
+
console.error(" WSL users: add [boot]\\nsystemd=true to /etc/wsl.conf, then restart WSL.");
|
|
1174
|
+
process.exit(1);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const serviceDir = path.join(HOME_DIR, '.config', 'systemd', 'user');
|
|
1178
|
+
if (!fs.existsSync(serviceDir)) fs.mkdirSync(serviceDir, { recursive: true });
|
|
1179
|
+
const servicePath = path.join(serviceDir, 'metame-daemon.service');
|
|
1180
|
+
const nodePath = process.execPath;
|
|
1181
|
+
const currentPath = process.env.PATH || '/usr/local/bin:/usr/bin:/bin';
|
|
1182
|
+
const serviceContent = `[Unit]
|
|
1183
|
+
Description=MetaMe Daemon
|
|
1184
|
+
After=network-online.target
|
|
1185
|
+
Wants=network-online.target
|
|
1186
|
+
|
|
1187
|
+
[Service]
|
|
1188
|
+
Type=simple
|
|
1189
|
+
ExecStart=${nodePath} ${DAEMON_SCRIPT}
|
|
1190
|
+
Restart=on-failure
|
|
1191
|
+
RestartSec=5
|
|
1192
|
+
Environment=HOME=${HOME_DIR}
|
|
1193
|
+
Environment=METAME_ROOT=${__dirname}
|
|
1194
|
+
Environment=PATH=${currentPath}
|
|
1195
|
+
StandardOutput=append:${DAEMON_LOG}
|
|
1196
|
+
StandardError=append:${DAEMON_LOG}
|
|
1197
|
+
|
|
1198
|
+
[Install]
|
|
1199
|
+
WantedBy=default.target
|
|
1200
|
+
`;
|
|
1201
|
+
fs.writeFileSync(servicePath, serviceContent, 'utf8');
|
|
1202
|
+
|
|
1203
|
+
// Enable and start
|
|
1204
|
+
const { execSync: es } = require('child_process');
|
|
1205
|
+
es('systemctl --user daemon-reload');
|
|
1206
|
+
es('systemctl --user enable metame-daemon.service');
|
|
1207
|
+
es('systemctl --user start metame-daemon.service');
|
|
1208
|
+
|
|
1209
|
+
// Enable lingering so service runs even when user is not logged in
|
|
1210
|
+
try { es(`loginctl enable-linger ${process.env.USER || ''}`); } catch { /* may need root */ }
|
|
1211
|
+
|
|
1212
|
+
console.log(`✅ systemd service installed: ${servicePath}`);
|
|
1213
|
+
console.log(" Status: systemctl --user status metame-daemon");
|
|
1214
|
+
console.log(" Logs: journalctl --user -u metame-daemon -f");
|
|
1215
|
+
console.log(" Disable: systemctl --user disable metame-daemon");
|
|
1216
|
+
|
|
1217
|
+
// WSL-specific guidance
|
|
1218
|
+
const isWSL = fs.existsSync('/proc/version') &&
|
|
1219
|
+
fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
|
|
1220
|
+
if (isWSL) {
|
|
1221
|
+
console.log("\n 📌 WSL auto-boot tip:");
|
|
1222
|
+
console.log(" Add this to Windows Task Scheduler (run at login):");
|
|
1223
|
+
console.log(` wsl -d ${process.env.WSL_DISTRO_NAME || 'Ubuntu'} -- sh -c 'nohup sleep infinity &'`);
|
|
1224
|
+
console.log(" This keeps WSL alive so the daemon stays running.");
|
|
1225
|
+
}
|
|
1226
|
+
process.exit(0);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1018
1229
|
if (subCmd === 'start') {
|
|
1019
1230
|
// Kill any lingering daemon.js processes to avoid Feishu WebSocket conflicts
|
|
1020
1231
|
try {
|
|
@@ -1040,7 +1251,11 @@ if (isDaemon) {
|
|
|
1040
1251
|
console.error("❌ daemon.js not found. Reinstall MetaMe.");
|
|
1041
1252
|
process.exit(1);
|
|
1042
1253
|
}
|
|
1043
|
-
|
|
1254
|
+
// Use caffeinate on macOS/Linux to prevent sleep while daemon is running
|
|
1255
|
+
const isNotWindows = process.platform !== 'win32';
|
|
1256
|
+
const cmd = isNotWindows ? 'caffeinate' : process.execPath;
|
|
1257
|
+
const args = isNotWindows ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
|
|
1258
|
+
const bg = spawn(cmd, args, {
|
|
1044
1259
|
detached: true,
|
|
1045
1260
|
stdio: 'ignore',
|
|
1046
1261
|
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
|
|
@@ -1154,6 +1369,8 @@ if (isDaemon) {
|
|
|
1154
1369
|
console.log(" metame daemon run <name> — run a task once");
|
|
1155
1370
|
if (process.platform === 'darwin') {
|
|
1156
1371
|
console.log(" metame daemon install-launchd — auto-start on macOS");
|
|
1372
|
+
} else {
|
|
1373
|
+
console.log(" metame daemon install-systemd — auto-start on Linux/WSL");
|
|
1157
1374
|
}
|
|
1158
1375
|
process.exit(0);
|
|
1159
1376
|
}
|
|
@@ -1194,7 +1411,9 @@ if (isSync) {
|
|
|
1194
1411
|
|
|
1195
1412
|
console.log(`\n🔄 Resuming session ${bestSession.id.slice(0, 8)}...\n`);
|
|
1196
1413
|
const providerEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
|
|
1197
|
-
const
|
|
1414
|
+
const resumeArgs = ['--resume', bestSession.id];
|
|
1415
|
+
if (daemonCfg.dangerously_skip_permissions) resumeArgs.push('--dangerously-skip-permissions');
|
|
1416
|
+
const syncChild = spawn('claude', resumeArgs, {
|
|
1198
1417
|
stdio: 'inherit',
|
|
1199
1418
|
env: { ...process.env, ...providerEnv, METAME_ACTIVE_SESSION: 'true' }
|
|
1200
1419
|
});
|
|
@@ -1227,8 +1446,20 @@ if (activeProviderName !== 'anthropic') {
|
|
|
1227
1446
|
console.log(`🔌 Provider: ${activeProviderName}`);
|
|
1228
1447
|
}
|
|
1229
1448
|
|
|
1449
|
+
// Build launch args — inject system prompt for new users
|
|
1450
|
+
const launchArgs = process.argv.slice(2);
|
|
1451
|
+
if (daemonCfg.dangerously_skip_permissions && !launchArgs.includes('--dangerously-skip-permissions')) {
|
|
1452
|
+
launchArgs.push('--dangerously-skip-permissions');
|
|
1453
|
+
}
|
|
1454
|
+
if (!isKnownUser) {
|
|
1455
|
+
launchArgs.push(
|
|
1456
|
+
'--append-system-prompt',
|
|
1457
|
+
'MANDATORY FIRST ACTION: The user has not been calibrated yet. You MUST start the Genesis Protocol interview from CLAUDE.md IMMEDIATELY — do NOT answer any other question first. Begin with the Trust Contract.'
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1230
1461
|
// Spawn the official claude tool with our marker + provider env
|
|
1231
|
-
const child = spawn('claude',
|
|
1462
|
+
const child = spawn('claude', launchArgs, {
|
|
1232
1463
|
stdio: 'inherit',
|
|
1233
1464
|
env: { ...process.env, ...activeProviderEnv, METAME_ACTIVE_SESSION: 'true' }
|
|
1234
1465
|
});
|