metame-cli 1.2.2 โ 1.3.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.
- package/README.md +113 -30
- package/index.js +250 -1
- package/package.json +3 -2
- package/scripts/daemon-default.yaml +49 -0
- package/scripts/daemon.js +1082 -0
- package/scripts/feishu-adapter.js +189 -0
- package/scripts/telegram-adapter.js +196 -0
package/README.md
CHANGED
|
@@ -18,14 +18,11 @@ It is not a memory system; it is a **Cognitive Mirror** .
|
|
|
18
18
|
|
|
19
19
|
## โจ Key Features
|
|
20
20
|
|
|
21
|
-
* **๐ง Global Brain (`~/.claude_profile.yaml`):** A single source of truth
|
|
22
|
-
* **๐งฌ Evolution
|
|
23
|
-
* **๐ค Dynamic Handshake
|
|
24
|
-
* **๐ก๏ธ Auto-Lock
|
|
25
|
-
*
|
|
26
|
-
* **๐ง Passive Distillation:** MetaMe silently captures your messages via Claude Code hooks and, on next launch, uses a lightweight LLM (Haiku) to extract cognitive traits and preferences โ automatically merging them into your profile with confidence-based upsert. Zero manual effort required.
|
|
27
|
-
* **๐ Schema-Enforced Profile:** A 41-field whitelist across 5 tiers (T1-T5) prevents profile bloat. Fields have type validation, enum constraints, and token budget limits (800 tokens max).
|
|
28
|
-
* **๐ฏ Confidence-Based Learning:** Strong directives ("always"/"ไปฅๅไธๅพ") write directly. Normal observations accumulate in a pending queue and only promote to the profile after 3 consistent observations โ preventing single-session bias.
|
|
21
|
+
* **๐ง Global Brain (`~/.claude_profile.yaml`):** A single, portable source of truth โ your identity, cognitive traits, and preferences travel with you across every project.
|
|
22
|
+
* **๐งฌ Cognitive Evolution Engine:** MetaMe learns how you think through three channels: (1) **Passive** โ silently captures your messages and distills cognitive traits via Haiku on next launch; (2) **Manual** โ `!metame evolve` for explicit teaching; (3) **Confidence gates** โ strong directives ("always"/"ไปฅๅไธๅพ") write immediately, normal observations need 3+ consistent sightings before promotion. Schema-enforced (41 fields, 5 tiers, 800 token budget) to prevent bloat.
|
|
23
|
+
* **๐ค Dynamic Handshake:** The "Canary Test." Claude must address you by your **Codename** in the first sentence. If it doesn't, the link is broken.
|
|
24
|
+
* **๐ก๏ธ Auto-Lock:** Mark any value with `# [LOCKED]` โ treated as a constitution, never auto-modified.
|
|
25
|
+
* **๐ฑ Remote Claude Code (v1.3):** Full Claude Code from your phone via Telegram or Feishu (Lark). Stateful sessions with `--resume` โ same conversation history, tool use, and file editing as your terminal. Interactive buttons for project/session picking, directory browser, and macOS launchd auto-start.
|
|
29
26
|
|
|
30
27
|
## ๐ Prerequisites
|
|
31
28
|
|
|
@@ -91,41 +88,119 @@ metame interview
|
|
|
91
88
|
```
|
|
92
89
|
(Command to be implemented in v1.3 - currently you can manually edit `~/.claude_profile.yaml` or use `set-trait`)
|
|
93
90
|
|
|
94
|
-
###
|
|
91
|
+
### Cognitive Evolution
|
|
95
92
|
|
|
96
|
-
|
|
93
|
+
MetaMe learns who you are through two paths:
|
|
97
94
|
|
|
98
|
-
**
|
|
95
|
+
**Automatic (zero effort):** A global hook captures your messages. On next launch, Haiku distills cognitive traits in the background. Strong directives ("always"/"ไปฅๅไธๅพ") write immediately; normal observations need 3+ consistent sightings. All writes are schema-validated (41 fields, 800 token budget). You'll see:
|
|
99
96
|
|
|
100
97
|
```
|
|
98
|
+
๐ง MetaMe: Distilling 7 moments in background...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Manual:** Update a specific trait directly:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
101
104
|
metame set-trait status.focus "Learning Rust"
|
|
105
|
+
metame evolve "I prefer functional programming patterns"
|
|
102
106
|
```
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
**Anti-bias safeguards:** single observations โ traits, contradictions are tracked not overwritten, pending traits expire after 30 days, context fields auto-clear on staleness.
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
### Remote Claude Code โ Telegram & Feishu (v1.3)
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
Full Claude Code from your phone โ stateful sessions with conversation history, tool use, and file editing. Supports both Telegram and Feishu (Lark).
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
**Setup:**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
metame daemon init # Create config + setup guide
|
|
118
|
+
```
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
- Single observations are treated as states, not traits
|
|
118
|
-
- Contradictions are tracked, not blindly overwritten
|
|
119
|
-
- Pending traits expire after 30 days without re-observation
|
|
120
|
-
- Context fields (focus, energy) auto-expire on staleness
|
|
120
|
+
Edit `~/.metame/daemon.yaml`:
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
```yaml
|
|
123
|
+
telegram:
|
|
124
|
+
enabled: true
|
|
125
|
+
bot_token: "YOUR_BOT_TOKEN" # From @BotFather
|
|
126
|
+
allowed_chat_ids:
|
|
127
|
+
- 123456789 # Your Telegram chat ID
|
|
123
128
|
|
|
129
|
+
feishu:
|
|
130
|
+
enabled: true
|
|
131
|
+
app_id: "YOUR_APP_ID" # From Feishu Developer Console
|
|
132
|
+
app_secret: "YOUR_APP_SECRET"
|
|
133
|
+
allowed_chat_ids: [] # Empty = allow all
|
|
124
134
|
```
|
|
125
|
-
|
|
135
|
+
|
|
136
|
+
**Start the daemon:**
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
metame daemon start # Background process
|
|
140
|
+
metame daemon status # Check if running
|
|
141
|
+
metame daemon logs # Tail the log
|
|
142
|
+
metame daemon stop # Shutdown
|
|
143
|
+
metame daemon install-launchd # macOS auto-start (RunAtLoad + KeepAlive)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Session commands (interactive buttons on Telegram & Feishu):**
|
|
147
|
+
|
|
148
|
+
| Command | Description |
|
|
149
|
+
|---------|-------------|
|
|
150
|
+
| `/new` | Start new session โ pick project directory from button list |
|
|
151
|
+
| `/resume` | Resume a session โ clickable list scoped to current workdir |
|
|
152
|
+
| `/continue` | Continue the most recent terminal session |
|
|
153
|
+
| `/cd` | Change working directory โ with directory browser |
|
|
154
|
+
| `/session` | Current session info |
|
|
155
|
+
|
|
156
|
+
Just type naturally for conversation โ every message stays in the same Claude Code session with full context.
|
|
157
|
+
|
|
158
|
+
**How it works:**
|
|
159
|
+
|
|
160
|
+
Each chat gets a persistent session via `claude -p --resume <session-id>`. This is the same Claude Code engine as your terminal โ same tools (file editing, bash, code search), same conversation history. You can start work on your computer and `/resume` from your phone, or vice versa.
|
|
161
|
+
|
|
162
|
+
**Other commands:**
|
|
163
|
+
|
|
164
|
+
| Command | Description |
|
|
165
|
+
|---------|-------------|
|
|
166
|
+
| `/status` | Daemon status + profile summary |
|
|
167
|
+
| `/tasks` | List scheduled heartbeat tasks |
|
|
168
|
+
| `/run <name>` | Run a task immediately |
|
|
169
|
+
| `/budget` | Today's token usage |
|
|
170
|
+
| `/quiet` | Silence mirror/reflections for 48h |
|
|
171
|
+
|
|
172
|
+
**Heartbeat Tasks:**
|
|
173
|
+
|
|
174
|
+
Define scheduled tasks in `daemon.yaml`:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
heartbeat:
|
|
178
|
+
tasks:
|
|
179
|
+
- name: "morning-news"
|
|
180
|
+
prompt: "Summarize today's top 3 AI news stories."
|
|
181
|
+
interval: "24h"
|
|
182
|
+
model: "haiku"
|
|
183
|
+
notify: true
|
|
184
|
+
precondition: "curl -s -o /dev/null -w '%{http_code}' https://news.ycombinator.com | grep 200"
|
|
126
185
|
```
|
|
127
186
|
|
|
128
|
-
|
|
187
|
+
* `precondition`: Shell command โ empty output โ task skipped, zero tokens.
|
|
188
|
+
* `type: "script"`: Run a local script directly instead of `claude -p`.
|
|
189
|
+
* `notify: true`: Push results to Telegram/Feishu.
|
|
190
|
+
|
|
191
|
+
**Token efficiency:**
|
|
192
|
+
|
|
193
|
+
* Polling, slash commands, directory browsing: **zero tokens**
|
|
194
|
+
* Stateful sessions: same cost as using Claude Code in terminal (conversation history managed by Claude CLI)
|
|
195
|
+
* Budget tracking with daily limit (default 50k tokens)
|
|
196
|
+
* 10-second cooldown between Claude calls
|
|
197
|
+
|
|
198
|
+
**Security:**
|
|
199
|
+
|
|
200
|
+
* `allowed_chat_ids` whitelist โ unauthorized users silently ignored
|
|
201
|
+
* No `--dangerously-skip-permissions` โ standard `-p` mode permissions
|
|
202
|
+
* `~/.metame/` directory set to mode 700
|
|
203
|
+
* Bot tokens stored locally, never transmitted
|
|
129
204
|
|
|
130
205
|
### Hot Reload (Refresh)
|
|
131
206
|
|
|
@@ -226,7 +301,15 @@ If you want to delete your stored profile data:
|
|
|
226
301
|
rm ~/.claude_profile.yaml
|
|
227
302
|
```
|
|
228
303
|
|
|
229
|
-
### 3.
|
|
304
|
+
### 3. Stop the Daemon (if running)
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
metame daemon stop
|
|
308
|
+
launchctl unload ~/Library/LaunchAgents/com.metame.daemon.plist 2>/dev/null
|
|
309
|
+
rm -f ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 4. Remove Passive Distillation Data (Optional)
|
|
230
313
|
|
|
231
314
|
Remove the signal capture scripts:
|
|
232
315
|
|
|
@@ -236,7 +319,7 @@ Remove the signal capture scripts:
|
|
|
236
319
|
rm -rf ~/.metame
|
|
237
320
|
```
|
|
238
321
|
|
|
239
|
-
###
|
|
322
|
+
### 5. Remove the Signal Capture Hook (Optional)
|
|
240
323
|
|
|
241
324
|
MetaMe installs a global hook in `~/.claude/settings.json`. To remove it, edit the file and delete the `UserPromptSubmit` entry under `hooks`, or run:
|
|
242
325
|
|
|
@@ -253,7 +336,7 @@ console.log('Hook removed.');
|
|
|
253
336
|
"
|
|
254
337
|
```
|
|
255
338
|
|
|
256
|
-
###
|
|
339
|
+
### 6. Cleanup Project Files (Optional)
|
|
257
340
|
|
|
258
341
|
MetaMe adds a header to `CLAUDE.md` files in your projects. To restore them to their original state (if you have many), you can use a text editor to remove the block starting with `## ๐ง SYSTEM KERNEL`.
|
|
259
342
|
|
package/index.js
CHANGED
|
@@ -23,7 +23,7 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
26
|
-
const BUNDLED_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js'];
|
|
26
|
+
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'];
|
|
27
27
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
28
28
|
|
|
29
29
|
for (const script of BUNDLED_SCRIPTS) {
|
|
@@ -563,6 +563,255 @@ if (isMirror) {
|
|
|
563
563
|
process.exit(0);
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
+
// ---------------------------------------------------------
|
|
567
|
+
// 5.6 DAEMON SUBCOMMANDS
|
|
568
|
+
// ---------------------------------------------------------
|
|
569
|
+
const isDaemon = process.argv.includes('daemon');
|
|
570
|
+
if (isDaemon) {
|
|
571
|
+
const daemonIndex = process.argv.indexOf('daemon');
|
|
572
|
+
const subCmd = process.argv[daemonIndex + 1];
|
|
573
|
+
const DAEMON_CONFIG = path.join(METAME_DIR, 'daemon.yaml');
|
|
574
|
+
const DAEMON_STATE = path.join(METAME_DIR, 'daemon_state.json');
|
|
575
|
+
const DAEMON_PID = path.join(METAME_DIR, 'daemon.pid');
|
|
576
|
+
const DAEMON_LOG = path.join(METAME_DIR, 'daemon.log');
|
|
577
|
+
const DAEMON_DEFAULT = path.join(__dirname, 'scripts', 'daemon-default.yaml');
|
|
578
|
+
const DAEMON_SCRIPT = path.join(METAME_DIR, 'daemon.js');
|
|
579
|
+
|
|
580
|
+
if (subCmd === 'init') {
|
|
581
|
+
// Create config from template
|
|
582
|
+
if (fs.existsSync(DAEMON_CONFIG)) {
|
|
583
|
+
console.log("โ ๏ธ daemon.yaml already exists at ~/.metame/daemon.yaml");
|
|
584
|
+
console.log(" Delete it first if you want to re-initialize.");
|
|
585
|
+
} else {
|
|
586
|
+
const templateSrc = fs.existsSync(DAEMON_DEFAULT)
|
|
587
|
+
? DAEMON_DEFAULT
|
|
588
|
+
: path.join(METAME_DIR, 'daemon-default.yaml');
|
|
589
|
+
if (fs.existsSync(templateSrc)) {
|
|
590
|
+
fs.copyFileSync(templateSrc, DAEMON_CONFIG);
|
|
591
|
+
} else {
|
|
592
|
+
console.error("โ Template not found. Reinstall MetaMe.");
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
// Ensure directory permissions (700)
|
|
596
|
+
try { fs.chmodSync(METAME_DIR, 0o700); } catch { /* ignore on Windows */ }
|
|
597
|
+
console.log("โ
MetaMe daemon initialized.");
|
|
598
|
+
console.log(` Config: ${DAEMON_CONFIG}`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
console.log("\n๐ฑ Telegram Setup (optional):");
|
|
602
|
+
console.log(" 1. Message @BotFather on Telegram โ /newbot");
|
|
603
|
+
console.log(" 2. Copy the bot token");
|
|
604
|
+
console.log(" 3. Edit ~/.metame/daemon.yaml:");
|
|
605
|
+
console.log(" telegram:");
|
|
606
|
+
console.log(" enabled: true");
|
|
607
|
+
console.log(" bot_token: \"YOUR_TOKEN\"");
|
|
608
|
+
console.log(" allowed_chat_ids: [YOUR_CHAT_ID]");
|
|
609
|
+
console.log(" 4. To find your chat_id: message your bot, then run:");
|
|
610
|
+
console.log(" curl https://api.telegram.org/botYOUR_TOKEN/getUpdates");
|
|
611
|
+
console.log("\n๐ Feishu Setup (optional):");
|
|
612
|
+
console.log(" 1. Go to open.feishu.cn โ Create App โ get app_id & app_secret");
|
|
613
|
+
console.log(" 2. Enable Bot capability + im:message events");
|
|
614
|
+
console.log(" 3. Enable 'Long Connection' (้ฟ่ฟๆฅ) mode in Event Subscription");
|
|
615
|
+
console.log(" 4. Edit ~/.metame/daemon.yaml:");
|
|
616
|
+
console.log(" feishu:");
|
|
617
|
+
console.log(" enabled: true");
|
|
618
|
+
console.log(" app_id: \"YOUR_APP_ID\"");
|
|
619
|
+
console.log(" app_secret: \"YOUR_APP_SECRET\"");
|
|
620
|
+
console.log(" allowed_chat_ids: [CHAT_ID]");
|
|
621
|
+
|
|
622
|
+
console.log("\n Then: metame daemon start");
|
|
623
|
+
|
|
624
|
+
// Optional launchd setup (macOS only)
|
|
625
|
+
if (process.platform === 'darwin') {
|
|
626
|
+
const plistDir = path.join(HOME_DIR, 'Library', 'LaunchAgents');
|
|
627
|
+
const plistPath = path.join(plistDir, 'com.metame.daemon.plist');
|
|
628
|
+
console.log("\n๐ Auto-start on macOS (optional):");
|
|
629
|
+
console.log(" To start daemon automatically on login:");
|
|
630
|
+
console.log(` metame daemon start (first time to verify it works)`);
|
|
631
|
+
console.log(` Then create: ${plistPath}`);
|
|
632
|
+
console.log(" Or run: metame daemon install-launchd");
|
|
633
|
+
}
|
|
634
|
+
process.exit(0);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (subCmd === 'install-launchd') {
|
|
638
|
+
if (process.platform !== 'darwin') {
|
|
639
|
+
console.error("โ launchd is macOS-only.");
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
const plistDir = path.join(HOME_DIR, 'Library', 'LaunchAgents');
|
|
643
|
+
if (!fs.existsSync(plistDir)) fs.mkdirSync(plistDir, { recursive: true });
|
|
644
|
+
const plistPath = path.join(plistDir, 'com.metame.daemon.plist');
|
|
645
|
+
const nodePath = process.execPath;
|
|
646
|
+
// Capture current PATH so launchd can find `claude` and other tools
|
|
647
|
+
const currentPath = process.env.PATH || '/usr/local/bin:/usr/bin:/bin';
|
|
648
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
649
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
650
|
+
<plist version="1.0">
|
|
651
|
+
<dict>
|
|
652
|
+
<key>Label</key>
|
|
653
|
+
<string>com.metame.daemon</string>
|
|
654
|
+
<key>ProgramArguments</key>
|
|
655
|
+
<array>
|
|
656
|
+
<string>${nodePath}</string>
|
|
657
|
+
<string>${DAEMON_SCRIPT}</string>
|
|
658
|
+
</array>
|
|
659
|
+
<key>RunAtLoad</key>
|
|
660
|
+
<true/>
|
|
661
|
+
<key>KeepAlive</key>
|
|
662
|
+
<true/>
|
|
663
|
+
<key>StandardOutPath</key>
|
|
664
|
+
<string>${DAEMON_LOG}</string>
|
|
665
|
+
<key>StandardErrorPath</key>
|
|
666
|
+
<string>${DAEMON_LOG}</string>
|
|
667
|
+
<key>EnvironmentVariables</key>
|
|
668
|
+
<dict>
|
|
669
|
+
<key>METAME_ROOT</key>
|
|
670
|
+
<string>${__dirname}</string>
|
|
671
|
+
<key>PATH</key>
|
|
672
|
+
<string>${currentPath}</string>
|
|
673
|
+
<key>HOME</key>
|
|
674
|
+
<string>${HOME_DIR}</string>
|
|
675
|
+
</dict>
|
|
676
|
+
</dict>
|
|
677
|
+
</plist>`;
|
|
678
|
+
fs.writeFileSync(plistPath, plistContent, 'utf8');
|
|
679
|
+
console.log(`โ
launchd plist installed: ${plistPath}`);
|
|
680
|
+
console.log(" Load now: launchctl load " + plistPath);
|
|
681
|
+
console.log(" Unload: launchctl unload " + plistPath);
|
|
682
|
+
process.exit(0);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (subCmd === 'start') {
|
|
686
|
+
// Check if already running
|
|
687
|
+
if (fs.existsSync(DAEMON_PID)) {
|
|
688
|
+
const existingPid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
|
|
689
|
+
try {
|
|
690
|
+
process.kill(existingPid, 0); // test if alive
|
|
691
|
+
console.log(`โ ๏ธ Daemon already running (PID: ${existingPid})`);
|
|
692
|
+
console.log(" Use 'metame daemon stop' first.");
|
|
693
|
+
process.exit(1);
|
|
694
|
+
} catch {
|
|
695
|
+
// Stale PID file โ clean up
|
|
696
|
+
fs.unlinkSync(DAEMON_PID);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (!fs.existsSync(DAEMON_CONFIG)) {
|
|
700
|
+
console.error("โ No config found. Run: metame daemon init");
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
if (!fs.existsSync(DAEMON_SCRIPT)) {
|
|
704
|
+
console.error("โ daemon.js not found. Reinstall MetaMe.");
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
const bg = spawn(process.execPath, [DAEMON_SCRIPT], {
|
|
708
|
+
detached: true,
|
|
709
|
+
stdio: 'ignore',
|
|
710
|
+
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
|
|
711
|
+
});
|
|
712
|
+
bg.unref();
|
|
713
|
+
console.log(`โ
MetaMe daemon started (PID: ${bg.pid})`);
|
|
714
|
+
console.log(" Logs: metame daemon logs");
|
|
715
|
+
console.log(" Stop: metame daemon stop");
|
|
716
|
+
process.exit(0);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (subCmd === 'stop') {
|
|
720
|
+
if (!fs.existsSync(DAEMON_PID)) {
|
|
721
|
+
console.log("โน๏ธ No daemon running (no PID file).");
|
|
722
|
+
process.exit(0);
|
|
723
|
+
}
|
|
724
|
+
const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
|
|
725
|
+
try {
|
|
726
|
+
process.kill(pid, 'SIGTERM');
|
|
727
|
+
console.log(`โ
Daemon stopped (PID: ${pid})`);
|
|
728
|
+
} catch (e) {
|
|
729
|
+
console.log(`โ ๏ธ Process ${pid} not found (may have already exited).`);
|
|
730
|
+
fs.unlinkSync(DAEMON_PID);
|
|
731
|
+
}
|
|
732
|
+
process.exit(0);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (subCmd === 'status') {
|
|
736
|
+
let state = {};
|
|
737
|
+
try { state = JSON.parse(fs.readFileSync(DAEMON_STATE, 'utf8')); } catch { /* empty */ }
|
|
738
|
+
|
|
739
|
+
// Check if running
|
|
740
|
+
let isRunning = false;
|
|
741
|
+
if (fs.existsSync(DAEMON_PID)) {
|
|
742
|
+
const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
|
|
743
|
+
try { process.kill(pid, 0); isRunning = true; } catch { /* dead */ }
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
console.log(`๐ค MetaMe Daemon: ${isRunning ? '๐ข Running' : '๐ด Stopped'}`);
|
|
747
|
+
if (state.started_at) console.log(` Started: ${state.started_at}`);
|
|
748
|
+
if (state.pid) console.log(` PID: ${state.pid}`);
|
|
749
|
+
|
|
750
|
+
// Budget
|
|
751
|
+
const budget = state.budget || {};
|
|
752
|
+
const config = {};
|
|
753
|
+
try { Object.assign(config, yaml.load(fs.readFileSync(DAEMON_CONFIG, 'utf8'))); } catch { /* empty */ }
|
|
754
|
+
const limit = (config.budget && config.budget.daily_limit) || 50000;
|
|
755
|
+
console.log(` Budget: ${budget.tokens_used || 0}/${limit} tokens (${budget.date || 'no data'})`);
|
|
756
|
+
|
|
757
|
+
// Tasks
|
|
758
|
+
const tasks = state.tasks || {};
|
|
759
|
+
if (Object.keys(tasks).length > 0) {
|
|
760
|
+
console.log(" Recent tasks:");
|
|
761
|
+
for (const [name, info] of Object.entries(tasks)) {
|
|
762
|
+
const icon = info.status === 'success' ? 'โ
' : 'โ';
|
|
763
|
+
console.log(` ${icon} ${name}: ${info.last_run || 'unknown'}`);
|
|
764
|
+
if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
process.exit(0);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (subCmd === 'logs') {
|
|
771
|
+
if (!fs.existsSync(DAEMON_LOG)) {
|
|
772
|
+
console.log("โน๏ธ No log file yet. Start the daemon first.");
|
|
773
|
+
process.exit(0);
|
|
774
|
+
}
|
|
775
|
+
const content = fs.readFileSync(DAEMON_LOG, 'utf8');
|
|
776
|
+
const lines = content.split('\n');
|
|
777
|
+
const tail = lines.slice(-50).join('\n');
|
|
778
|
+
console.log(tail);
|
|
779
|
+
process.exit(0);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (subCmd === 'run') {
|
|
783
|
+
const taskName = process.argv[daemonIndex + 2];
|
|
784
|
+
if (!taskName) {
|
|
785
|
+
console.error("โ Usage: metame daemon run <task-name>");
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
if (!fs.existsSync(DAEMON_SCRIPT)) {
|
|
789
|
+
console.error("โ daemon.js not found. Reinstall MetaMe.");
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
// Run in foreground using daemon.js --run
|
|
793
|
+
const result = require('child_process').spawnSync(
|
|
794
|
+
process.execPath,
|
|
795
|
+
[DAEMON_SCRIPT, '--run', taskName],
|
|
796
|
+
{ stdio: 'inherit', env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname } }
|
|
797
|
+
);
|
|
798
|
+
process.exit(result.status || 0);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Unknown subcommand
|
|
802
|
+
console.log("๐ MetaMe Daemon Commands:");
|
|
803
|
+
console.log(" metame daemon init โ initialize config");
|
|
804
|
+
console.log(" metame daemon start โ start background daemon");
|
|
805
|
+
console.log(" metame daemon stop โ stop daemon");
|
|
806
|
+
console.log(" metame daemon status โ show status & budget");
|
|
807
|
+
console.log(" metame daemon logs โ tail log file");
|
|
808
|
+
console.log(" metame daemon run <name> โ run a task once");
|
|
809
|
+
if (process.platform === 'darwin') {
|
|
810
|
+
console.log(" metame daemon install-launchd โ auto-start on macOS");
|
|
811
|
+
}
|
|
812
|
+
process.exit(0);
|
|
813
|
+
}
|
|
814
|
+
|
|
566
815
|
// ---------------------------------------------------------
|
|
567
816
|
// ---------------------------------------------------------
|
|
568
817
|
// 6. SAFETY GUARD: RECURSION PREVENTION (v2)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
"author": "Yaron",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@larksuiteoapi/node-sdk": "^1.58.0",
|
|
26
27
|
"js-yaml": "^4.1.1"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=14"
|
|
30
31
|
}
|
|
31
|
-
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# MetaMe Daemon Configuration
|
|
2
|
+
# Generated by: metame daemon init
|
|
3
|
+
|
|
4
|
+
telegram:
|
|
5
|
+
enabled: false
|
|
6
|
+
bot_token: null
|
|
7
|
+
allowed_chat_ids: []
|
|
8
|
+
|
|
9
|
+
feishu:
|
|
10
|
+
enabled: false
|
|
11
|
+
app_id: null
|
|
12
|
+
app_secret: null
|
|
13
|
+
allowed_chat_ids: []
|
|
14
|
+
|
|
15
|
+
heartbeat:
|
|
16
|
+
tasks: []
|
|
17
|
+
# Examples โ uncomment or add your own:
|
|
18
|
+
#
|
|
19
|
+
# Scheduled task (calls claude -p with your profile context):
|
|
20
|
+
# - name: "morning-news"
|
|
21
|
+
# prompt: "ๆๅไปๅคฉ็AI้ขๅ้่ฆๆฐ้ป๏ผๆด็ๆ3ๆกๆ่ฆ๏ผไธญ่ฑๅ่ฏญใ"
|
|
22
|
+
# interval: "24h"
|
|
23
|
+
# model: "haiku"
|
|
24
|
+
# notify: true
|
|
25
|
+
#
|
|
26
|
+
# - name: "weekly-review"
|
|
27
|
+
# prompt: "ๅบไบๆ็profile๏ผ็ๆๆฌๅจไธชไบบๆ้ฟๅ้กพๅไธๅจๅปบ่ฎฎใ"
|
|
28
|
+
# interval: "7d"
|
|
29
|
+
# model: "haiku"
|
|
30
|
+
# notify: true
|
|
31
|
+
#
|
|
32
|
+
# Script task (runs a local script directly, no claude -p):
|
|
33
|
+
# - name: "distill"
|
|
34
|
+
# type: "script"
|
|
35
|
+
# command: "node ~/.metame/distill.js"
|
|
36
|
+
# interval: "6h"
|
|
37
|
+
# precondition: "test -s ~/.metame/raw_signals.jsonl"
|
|
38
|
+
# notify: false
|
|
39
|
+
#
|
|
40
|
+
# precondition: shell command, empty output โ skip (zero tokens)
|
|
41
|
+
# type: "script" โ runs command directly instead of claude -p
|
|
42
|
+
|
|
43
|
+
budget:
|
|
44
|
+
daily_limit: 50000
|
|
45
|
+
warning_threshold: 0.8
|
|
46
|
+
|
|
47
|
+
daemon:
|
|
48
|
+
log_max_size: 1048576
|
|
49
|
+
heartbeat_check_interval: 60
|