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 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 for your identity, storing your nickname, stress status, and cognitive traits.
22
- * **๐Ÿงฌ Evolution Mechanism:** You are in control. Use `!metame evolve` to manually teach Claude about your new preferences or constraints, ensuring it gets smarter with every interaction.
23
- * **๐Ÿค Dynamic Handshake Protocol:** The "Canary Test." MetaMe verifies its connection to your profile by addressing you by your chosen **Codename** in the very first sentence. If it doesn't, you know the link is broken.
24
- * **๐Ÿ›ก๏ธ Auto-Lock Mechanism:** Mark any value in your profile with `# [LOCKED]`, and MetaMe will treat it as a constitution that cannot be overwritten.
25
- * **๐Ÿ”Œ Smart Injection:** Automatically injects your profile context into the `CLAUDE.md` of any project you enter, ensuring seamless context switching.
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
+ * **๐Ÿชž 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
- ### Surgical Update (Manual Override)
93
+ ### Cognitive Evolution
95
94
 
96
- If you need to update a specific trait without editing the file manually:
95
+ MetaMe learns who you are through two paths:
97
96
 
98
- **Bash**
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
- ### Passive Distillation (Automatic)
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
- MetaMe automatically learns your cognitive patterns from conversations โ€” no action needed.
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
- 1. A global Claude Code hook captures every message, tagging each with a **confidence level** (high for strong directives like "always"/"ไปฅๅŽไธ€ๅพ‹", normal otherwise).
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
- **Anti-bias safeguards:**
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
- You'll see this in the startup log:
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
- ๐Ÿง  MetaMe: Distilling 7 moments in background...
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
- The hook is installed automatically on first run to `~/.claude/settings.json` (global scope โ€” works across all projects).
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. Remove Passive Distillation Data (Optional)
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
- ### 4. Remove the Signal Capture Hook (Optional)
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
- ### 5. Cleanup Project Files (Optional)
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.2.2",
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