metame-cli 1.2.2 โ 1.3.1
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 +147 -29
- package/index.js +250 -1
- package/package.json +3 -2
- package/scripts/daemon-default.yaml +49 -0
- package/scripts/daemon.js +1195 -0
- package/scripts/feishu-adapter.js +189 -0
- package/scripts/telegram-adapter.js +196 -0
package/README.md
CHANGED
|
@@ -18,14 +18,13 @@ 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
|
-
*
|
|
27
|
-
*
|
|
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
|
+
* **๐ช Metacognition Layer (v1.3):** MetaMe now observes *how* you think, not just *what* you say. Behavioral pattern detection runs inside the existing Haiku distill call (zero extra cost). It tracks decision patterns, cognitive load, comfort zones, and avoidance topics across sessions. When persistent patterns emerge, MetaMe injects a one-line mirror observation โ e.g., *"You tend to avoid testing until forced"* โ with a 14-day cooldown per pattern. Conditional reflection prompts appear only when triggered (every 7th distill or 3x consecutive comfort zone). All injection logic runs in Node.js; Claude receives only pre-decided directives, never rules to self-evaluate.
|
|
26
|
+
* **๐ฑ 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.
|
|
27
|
+
* **๐ Workflow Engine (v1.3):** Define multi-step skill chains as heartbeat tasks. Each workflow runs in a single Claude Code session via `--resume`, so step outputs flow as context to the next step. Example: `deep-research` โ `tech-writing` โ `wechat-publisher` โ fully automated content pipeline.
|
|
29
28
|
|
|
30
29
|
## ๐ Prerequisites
|
|
31
30
|
|
|
@@ -91,41 +90,152 @@ metame interview
|
|
|
91
90
|
```
|
|
92
91
|
(Command to be implemented in v1.3 - currently you can manually edit `~/.claude_profile.yaml` or use `set-trait`)
|
|
93
92
|
|
|
94
|
-
###
|
|
93
|
+
### Cognitive Evolution
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
MetaMe learns who you are through two paths:
|
|
97
96
|
|
|
98
|
-
**
|
|
97
|
+
**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
98
|
|
|
100
99
|
```
|
|
100
|
+
๐ง MetaMe: Distilling 7 moments in background...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Manual:** Update a specific trait directly:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
101
106
|
metame set-trait status.focus "Learning Rust"
|
|
107
|
+
metame evolve "I prefer functional programming patterns"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Anti-bias safeguards:** single observations โ traits, contradictions are tracked not overwritten, pending traits expire after 30 days, context fields auto-clear on staleness.
|
|
111
|
+
|
|
112
|
+
**Metacognition controls:**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
metame quiet # Silence mirror observations & reflections for 48h
|
|
116
|
+
metame insights # Show detected behavioral patterns
|
|
117
|
+
metame mirror on|off # Toggle mirror injection
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Remote Claude Code โ Telegram & Feishu (v1.3)
|
|
121
|
+
|
|
122
|
+
Full Claude Code from your phone โ stateful sessions with conversation history, tool use, and file editing. Supports both Telegram and Feishu (Lark).
|
|
123
|
+
|
|
124
|
+
**Setup:**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
metame daemon init # Create config + setup guide
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Edit `~/.metame/daemon.yaml`:
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
telegram:
|
|
134
|
+
enabled: true
|
|
135
|
+
bot_token: "YOUR_BOT_TOKEN" # From @BotFather
|
|
136
|
+
allowed_chat_ids:
|
|
137
|
+
- 123456789 # Your Telegram chat ID
|
|
138
|
+
|
|
139
|
+
feishu:
|
|
140
|
+
enabled: true
|
|
141
|
+
app_id: "YOUR_APP_ID" # From Feishu Developer Console
|
|
142
|
+
app_secret: "YOUR_APP_SECRET"
|
|
143
|
+
allowed_chat_ids: [] # Empty = allow all
|
|
102
144
|
```
|
|
103
145
|
|
|
104
|
-
|
|
146
|
+
**Start the daemon:**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
metame daemon start # Background process
|
|
150
|
+
metame daemon status # Check if running
|
|
151
|
+
metame daemon logs # Tail the log
|
|
152
|
+
metame daemon stop # Shutdown
|
|
153
|
+
metame daemon install-launchd # macOS auto-start (RunAtLoad + KeepAlive)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Session commands (interactive buttons on Telegram & Feishu):**
|
|
157
|
+
|
|
158
|
+
| Command | Description |
|
|
159
|
+
|---------|-------------|
|
|
160
|
+
| `/new` | Start new session โ pick project directory from button list |
|
|
161
|
+
| `/resume` | Resume a session โ clickable list scoped to current workdir |
|
|
162
|
+
| `/continue` | Continue the most recent terminal session |
|
|
163
|
+
| `/cd` | Change working directory โ with directory browser |
|
|
164
|
+
| `/session` | Current session info |
|
|
105
165
|
|
|
106
|
-
|
|
166
|
+
Just type naturally for conversation โ every message stays in the same Claude Code session with full context.
|
|
107
167
|
|
|
108
168
|
**How it works:**
|
|
109
169
|
|
|
110
|
-
|
|
111
|
-
2. On your next `metame` launch, a background Haiku model analyzes the buffer and extracts cognitive traits and preferences.
|
|
112
|
-
3. **High-confidence** traits write directly to your profile. **Normal-confidence** traits enter a pending queue (`~/.metame/pending_traits.yaml`) and only promote after 3+ consistent observations.
|
|
113
|
-
4. All writes are validated against a **41-field schema whitelist** โ unknown keys are silently dropped, enum fields are type-checked, and a **token budget** (800 max) prevents bloat.
|
|
114
|
-
5. The buffer is cleared, and Claude starts with a clean context.
|
|
170
|
+
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.
|
|
115
171
|
|
|
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
|
|
172
|
+
**Other commands:**
|
|
121
173
|
|
|
122
|
-
|
|
174
|
+
| Command | Description |
|
|
175
|
+
|---------|-------------|
|
|
176
|
+
| `/status` | Daemon status + profile summary |
|
|
177
|
+
| `/tasks` | List scheduled heartbeat tasks |
|
|
178
|
+
| `/run <name>` | Run a task immediately |
|
|
179
|
+
| `/budget` | Today's token usage |
|
|
180
|
+
| `/quiet` | Silence mirror/reflections for 48h |
|
|
181
|
+
| `/reload` | Manually reload daemon.yaml (also auto-reloads on file change) |
|
|
123
182
|
|
|
183
|
+
**Heartbeat Tasks:**
|
|
184
|
+
|
|
185
|
+
Define scheduled tasks in `daemon.yaml`:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
heartbeat:
|
|
189
|
+
tasks:
|
|
190
|
+
- name: "morning-news"
|
|
191
|
+
prompt: "Summarize today's top 3 AI news stories."
|
|
192
|
+
interval: "24h"
|
|
193
|
+
model: "haiku"
|
|
194
|
+
notify: true
|
|
195
|
+
precondition: "curl -s -o /dev/null -w '%{http_code}' https://news.ycombinator.com | grep 200"
|
|
124
196
|
```
|
|
125
|
-
|
|
197
|
+
|
|
198
|
+
* `precondition`: Shell command โ empty output โ task skipped, zero tokens.
|
|
199
|
+
* `type: "script"`: Run a local script directly instead of `claude -p`.
|
|
200
|
+
* `notify: true`: Push results to Telegram/Feishu.
|
|
201
|
+
|
|
202
|
+
**Workflow tasks** (multi-step skill chains):
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
heartbeat:
|
|
206
|
+
tasks:
|
|
207
|
+
- name: "daily-wechat"
|
|
208
|
+
type: "workflow"
|
|
209
|
+
interval: "24h"
|
|
210
|
+
model: "sonnet"
|
|
211
|
+
notify: true
|
|
212
|
+
steps:
|
|
213
|
+
- skill: "deep-research"
|
|
214
|
+
prompt: "Today's top 3 AI news stories"
|
|
215
|
+
- skill: "tech-writing"
|
|
216
|
+
prompt: "Write a WeChat article based on the research above"
|
|
217
|
+
- skill: "wechat-publisher"
|
|
218
|
+
prompt: "Publish the article"
|
|
219
|
+
optional: true
|
|
126
220
|
```
|
|
127
221
|
|
|
128
|
-
|
|
222
|
+
Each step runs in the same Claude Code session. Step outputs automatically become context for the next step. Set `optional: true` on steps that may fail without aborting the workflow.
|
|
223
|
+
|
|
224
|
+
**Auto-reload:** The daemon watches `daemon.yaml` for changes. When Claude (or you) edits the config file, the daemon automatically reloads โ no restart or `/reload` needed. A notification is pushed to confirm.
|
|
225
|
+
|
|
226
|
+
**Token efficiency:**
|
|
227
|
+
|
|
228
|
+
* Polling, slash commands, directory browsing: **zero tokens**
|
|
229
|
+
* Stateful sessions: same cost as using Claude Code in terminal (conversation history managed by Claude CLI)
|
|
230
|
+
* Budget tracking with daily limit (default 50k tokens)
|
|
231
|
+
* 10-second cooldown between Claude calls
|
|
232
|
+
|
|
233
|
+
**Security:**
|
|
234
|
+
|
|
235
|
+
* `allowed_chat_ids` whitelist โ unauthorized users silently ignored
|
|
236
|
+
* No `--dangerously-skip-permissions` โ standard `-p` mode permissions
|
|
237
|
+
* `~/.metame/` directory set to mode 700
|
|
238
|
+
* Bot tokens stored locally, never transmitted
|
|
129
239
|
|
|
130
240
|
### Hot Reload (Refresh)
|
|
131
241
|
|
|
@@ -226,7 +336,15 @@ If you want to delete your stored profile data:
|
|
|
226
336
|
rm ~/.claude_profile.yaml
|
|
227
337
|
```
|
|
228
338
|
|
|
229
|
-
### 3.
|
|
339
|
+
### 3. Stop the Daemon (if running)
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
metame daemon stop
|
|
343
|
+
launchctl unload ~/Library/LaunchAgents/com.metame.daemon.plist 2>/dev/null
|
|
344
|
+
rm -f ~/Library/LaunchAgents/com.metame.daemon.plist
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 4. Remove Passive Distillation Data (Optional)
|
|
230
348
|
|
|
231
349
|
Remove the signal capture scripts:
|
|
232
350
|
|
|
@@ -236,7 +354,7 @@ Remove the signal capture scripts:
|
|
|
236
354
|
rm -rf ~/.metame
|
|
237
355
|
```
|
|
238
356
|
|
|
239
|
-
###
|
|
357
|
+
### 5. Remove the Signal Capture Hook (Optional)
|
|
240
358
|
|
|
241
359
|
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
360
|
|
|
@@ -253,7 +371,7 @@ console.log('Hook removed.');
|
|
|
253
371
|
"
|
|
254
372
|
```
|
|
255
373
|
|
|
256
|
-
###
|
|
374
|
+
### 6. Cleanup Project Files (Optional)
|
|
257
375
|
|
|
258
376
|
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
377
|
|
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.1",
|
|
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
|