metame-cli 1.4.30 → 1.4.32

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/index.js CHANGED
@@ -7,20 +7,16 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
  const { spawn, execSync } = require('child_process');
10
+ const { sleepSync, findProcessesByPattern, icon } = require('./scripts/platform');
10
11
 
11
- // On Windows, resolve the full path to claude.cmd so we can spawn it
12
- // via cmd.exe (using COMSPEC) without relying on shell:true finding cmd.exe in PATH
13
- function resolveClaudeBin() {
14
- if (process.platform !== 'win32') return 'claude';
15
- try {
16
- return execSync('where claude', { encoding: 'utf8' }).trim().split('\n')[0];
17
- } catch { return 'claude'; }
18
- }
12
+ // On Windows, .cmd files (like claude.cmd from npm global) need shell:true to spawn.
13
+ // We use COMSPEC to avoid conda/PATH issues where cmd.exe can't be found.
19
14
  function spawnClaude(args, options) {
20
15
  if (process.platform === 'win32') {
21
- const claudePath = resolveClaudeBin();
22
- const comspec = process.env.COMSPEC || 'C:\\WINDOWS\\system32\\cmd.exe';
23
- return spawn(comspec, ['/c', `"${claudePath}"`, ...args], options);
16
+ return spawn('claude', args, {
17
+ ...options,
18
+ shell: process.env.COMSPEC || true,
19
+ });
24
20
  }
25
21
  return spawn('claude', args, options);
26
22
  }
@@ -57,7 +53,7 @@ if (!fs.existsSync(METAME_DIR)) {
57
53
  // Auto-deploy bundled scripts to ~/.metame/
58
54
  // IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
59
55
  const scriptsDir = path.join(__dirname, 'scripts');
60
- const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'memory-search.js', 'memory-gc.js', 'qmd-client.js', 'session-summarize.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js'];
56
+ const BUNDLED_BASE_SCRIPTS = ['platform.js', 'signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'memory-search.js', 'memory-gc.js', 'qmd-client.js', 'session-summarize.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js'];
61
57
  const DAEMON_MODULE_SCRIPTS = (() => {
62
58
  try {
63
59
  return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
@@ -102,7 +98,7 @@ for (const script of BUNDLED_SCRIPTS) {
102
98
  // and has defer logic (waits for active Claude tasks to finish before restarting).
103
99
  // Killing here bypasses that and interrupts ongoing conversations.
104
100
  if (scriptsUpdated) {
105
- console.log('šŸ“¦ Scripts synced to ~/.metame/ — daemon will auto-restart when idle.');
101
+ console.log(`${icon("pkg")} Scripts synced to ~/.metame/ — daemon will auto-restart when idle.`);
106
102
  }
107
103
 
108
104
  // ---------------------------------------------------------
@@ -136,7 +132,7 @@ if (fs.existsSync(bundledSkillsDir)) {
136
132
  skillsInstalled.push(skillName);
137
133
  }
138
134
  if (skillsInstalled.length > 0) {
139
- console.log(`🧠 Skills installed: ${skillsInstalled.join(', ')}`);
135
+ console.log(`${icon("brain")} Skills installed: ${skillsInstalled.join(', ')}`);
140
136
  }
141
137
  } catch {
142
138
  // Non-fatal
@@ -158,7 +154,7 @@ if (!fs.existsSync(DAEMON_CONFIG_FILE)) {
158
154
  if (fs.existsSync(DAEMON_YAML_BACKUP)) {
159
155
  // Restore from backup — user had real config that was lost
160
156
  fs.copyFileSync(DAEMON_YAML_BACKUP, DAEMON_CONFIG_FILE);
161
- console.log('āš ļø daemon.yaml was missing — restored from backup.');
157
+ console.log(`${icon("warn")} daemon.yaml was missing — restored from backup.`);
162
158
  } else {
163
159
  const daemonTemplate = path.join(scriptsDir, 'daemon-default.yaml');
164
160
  if (fs.existsSync(daemonTemplate)) {
@@ -184,13 +180,34 @@ function ensureHookInstalled() {
184
180
  }
185
181
 
186
182
  // Check if our hook is already configured
187
- const hookCommand = `node ${SIGNAL_CAPTURE_SCRIPT}`;
183
+ // Use forward slashes + quotes — Claude Code runs hooks via bash even on Windows
184
+ const scriptPathForHook = SIGNAL_CAPTURE_SCRIPT.replace(/\\/g, '/');
185
+ const hookCommand = `node "${scriptPathForHook}"`;
188
186
  const existing = settings.hooks?.UserPromptSubmit || [];
189
187
  const alreadyInstalled = existing.some(entry =>
190
- entry.hooks?.some(h => h.command === hookCommand)
188
+ entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
191
189
  );
192
190
 
193
- if (!alreadyInstalled) {
191
+ // Remove stale hooks with backslash paths (old Windows format)
192
+ if (settings.hooks?.UserPromptSubmit) {
193
+ for (const entry of settings.hooks.UserPromptSubmit) {
194
+ if (entry.hooks) {
195
+ entry.hooks = entry.hooks.filter(h =>
196
+ !(h.command && h.command.includes('signal-capture.js') && h.command.includes('\\'))
197
+ );
198
+ }
199
+ }
200
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
201
+ entry => entry.hooks && entry.hooks.length > 0
202
+ );
203
+ }
204
+
205
+ // Re-check after cleanup
206
+ const stillInstalled = (settings.hooks?.UserPromptSubmit || []).some(entry =>
207
+ entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
208
+ );
209
+
210
+ if (!stillInstalled) {
194
211
  if (!settings.hooks) settings.hooks = {};
195
212
  if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
196
213
 
@@ -202,11 +219,11 @@ function ensureHookInstalled() {
202
219
  });
203
220
 
204
221
  fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
205
- console.log("šŸŖ MetaMe: Signal capture hook installed.");
222
+ console.log(`${icon("hook")} MetaMe: Signal capture hook installed.`);
206
223
  }
207
224
  } catch (e) {
208
225
  // Non-fatal: hook install failure shouldn't block launch
209
- console.error("āš ļø Hook install skipped:", e.message);
226
+ console.error(`${icon("warn")} Hook install skipped:`, e.message);
210
227
  }
211
228
  }
212
229
 
@@ -269,7 +286,7 @@ function spawnDistillBackground() {
269
286
  // 4-hour cooldown: check last distill timestamp from profile
270
287
  const cooldownMs = 4 * 60 * 60 * 1000;
271
288
  try {
272
- const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
289
+ const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
273
290
  if (fs.existsSync(profilePath)) {
274
291
  const yaml = require('js-yaml');
275
292
  const profile = yaml.load(fs.readFileSync(profilePath, 'utf8'));
@@ -396,7 +413,7 @@ status:
396
413
  // ---------------------------------------------------------
397
414
  const PROTOCOL_NORMAL = `${METAME_START}
398
415
  ---
399
- ## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
416
+ ## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
400
417
 
401
418
  **1. THE BRAIN (Source of Truth):**
402
419
  * **FILE:** \`$HOME/.claude_profile.yaml\`
@@ -422,7 +439,7 @@ const PROTOCOL_NORMAL = `${METAME_START}
422
439
 
423
440
  const PROTOCOL_ONBOARDING = `${METAME_START}
424
441
  ---
425
- ## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
442
+ ## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
426
443
 
427
444
  **1. THE BRAIN (Source of Truth):**
428
445
  * **FILE:** \`$HOME/.claude_profile.yaml\`
@@ -479,7 +496,7 @@ This step connects the bot to the user's PRIVATE chat — this is the admin chan
479
496
 
480
497
  - If **Feishu:**
481
498
  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.
482
- **āš ļø é‡č¦ļ¼š** åœØć€Œäŗ‹ä»¶č®¢é˜…ć€é”µé¢ļ¼Œåæ…é”»å¼€åÆć€ŒęŽ„ę”¶ę¶ˆęÆ im.message.receive_v1ć€äŗ‹ä»¶ć€‚ē„¶åŽåœØčÆ„äŗ‹ä»¶ēš„é…ē½®äø­ļ¼Œå‹¾é€‰ć€ŒčŽ·å–ē¾¤ē»„äø­ę‰€ęœ‰ę¶ˆęÆć€ļ¼ˆå¦åˆ™ bot åœØē¾¤čŠäø­åŖčƒ½ę”¶åˆ° @它 ēš„ę¶ˆęÆļ¼Œę— ę³•ęŽ„ę”¶ę™®é€šē¾¤ę¶ˆęÆļ¼‰ć€‚
499
+ **${icon("warn")} é‡č¦ļ¼š** åœØć€Œäŗ‹ä»¶č®¢é˜…ć€é”µé¢ļ¼Œåæ…é”»å¼€åÆć€ŒęŽ„ę”¶ę¶ˆęÆ im.message.receive_v1ć€äŗ‹ä»¶ć€‚ē„¶åŽåœØčÆ„äŗ‹ä»¶ēš„é…ē½®äø­ļ¼Œå‹¾é€‰ć€ŒčŽ·å–ē¾¤ē»„äø­ę‰€ęœ‰ę¶ˆęÆć€ļ¼ˆå¦åˆ™ bot åœØē¾¤čŠäø­åŖčƒ½ę”¶åˆ° @它 ēš„ę¶ˆęÆļ¼Œę— ę³•ęŽ„ę”¶ę™®é€šē¾¤ę¶ˆęÆļ¼‰ć€‚
483
500
  2. Ask user to paste App ID and App Secret.
484
501
  3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
485
502
  4. Tell user: "Now open Feishu and send any message to your new bot (private chat), then tell me you're done."
@@ -554,7 +571,7 @@ if (isKnownUser) {
554
571
  finalProtocol = PROTOCOL_NORMAL;
555
572
  } else {
556
573
  finalProtocol = PROTOCOL_ONBOARDING;
557
- console.log("šŸ†• New user detected — entering Genesis interview mode...");
574
+ console.log(`${icon("new")} New user detected — entering Genesis interview mode...`);
558
575
  }
559
576
 
560
577
  // ---------------------------------------------------------
@@ -664,11 +681,11 @@ try {
664
681
  if (triggerDrift || triggerComfort || trigger7th) {
665
682
  let hint = '';
666
683
  if (triggerDrift) {
667
- hint = `ęœ€čæ‘å‡ äøŖsessionēš„ę–¹å‘å’Œ"${driftDeclaredFocus}"ęœ‰åå·®ć€‚čÆ·åœØåÆ¹čÆå¼€å§‹ę—¶ęø©å’Œåœ°é—®ļ¼ššŸŖž ę˜Æę–¹å‘ęœ‰ę„č°ƒę•“äŗ†ļ¼Œčæ˜ę˜Æäøå°åæƒåäŗ†ļ¼Ÿ`;
684
+ hint = `ęœ€čæ‘å‡ äøŖsessionēš„ę–¹å‘å’Œ"${driftDeclaredFocus}"ęœ‰åå·®ć€‚čÆ·åœØåÆ¹čÆå¼€å§‹ę—¶ęø©å’Œåœ°é—®ļ¼š${icon("mirror")} ę˜Æę–¹å‘ęœ‰ę„č°ƒę•“äŗ†ļ¼Œčæ˜ę˜Æäøå°åæƒåäŗ†ļ¼Ÿ`;
668
685
  } else if (triggerComfort) {
669
- hint = 'čæžē»­å‡ ę¬”éƒ½åœØē†Ÿę‚‰é¢†åŸŸć€‚å¦‚ęžœē”Øęˆ·åœØsessionē»“ęŸę—¶č‡Ŗē„¶åœé”æļ¼ŒåÆä»„ęø©å’Œåœ°é—®ļ¼ššŸŖž å‡†å¤‡å„½ęŽ¢ē“¢ę‹‰ä¼øåŒŗäŗ†å—ļ¼Ÿ';
686
+ hint = `čæžē»­å‡ ę¬”éƒ½åœØē†Ÿę‚‰é¢†åŸŸć€‚å¦‚ęžœē”Øęˆ·åœØsessionē»“ęŸę—¶č‡Ŗē„¶åœé”æļ¼ŒåÆä»„ęø©å’Œåœ°é—®ļ¼š${icon("mirror")} å‡†å¤‡å„½ęŽ¢ē“¢ę‹‰ä¼øåŒŗäŗ†å—ļ¼Ÿ`;
670
687
  } else {
671
- hint = 'čæ™ę˜Æē¬¬' + distillCount + 'ꬔsessionć€‚å¦‚ęžœsessionč‡Ŗē„¶ē»“ęŸļ¼ŒåÆä»„é™„åŠ äø€å„ļ¼ššŸŖž äø€äøŖčÆå½¢å®¹čæ™ę¬”sessionēš„ę„Ÿå—ļ¼Ÿ';
688
+ hint = 'čæ™ę˜Æē¬¬' + distillCount + `ꬔsessionć€‚å¦‚ęžœsessionč‡Ŗē„¶ē»“ęŸļ¼ŒåÆä»„é™„åŠ äø€å„ļ¼š${icon("mirror")} äø€äøŖčÆå½¢å®¹čæ™ę¬”sessionēš„ę„Ÿå—ļ¼Ÿ`;
672
689
  }
673
690
  const timing = triggerDrift ? 'åœØåÆ¹čÆå¼€å§‹ę—¶å°±é—®äø€ę¬”' : 'åŖåœØsessionå³å°†ē»“ęŸę—¶čÆ“äø€ę¬”';
674
691
  reflectionLine = `\n[MetaMe reflection: ${hint} ${timing}ć€‚å¦‚ęžœē”Øęˆ·ę²”å›žåŗ”å°±äøč¦čæ½é—®ć€‚]\n`;
@@ -772,13 +789,13 @@ try {
772
789
  fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
773
790
  } catch (e) {
774
791
  // Non-fatal: global CLAUDE.md injection is best-effort
775
- console.error(`āš ļø Failed to inject global CLAUDE.md: ${e.message}`);
792
+ console.error(`${icon("warn")} Failed to inject global CLAUDE.md: ${e.message}`);
776
793
  }
777
794
 
778
795
 
779
796
 
780
797
 
781
- console.log(`šŸ”® MetaMe v${pkgVersion}: Link Established.`);
798
+ console.log(`${icon("magic")} MetaMe v${pkgVersion}: Link Established.`);
782
799
 
783
800
  // Memory system status — show live stats without blocking launch
784
801
  try {
@@ -794,7 +811,7 @@ try {
794
811
  memMod.close();
795
812
  } catch { /* memory.js not available or DB not ready */ }
796
813
  if (factCount > 0 || tagCount > 0) {
797
- console.log(`🧠 Memory: ${factCount} facts · ${tagCount} sessions tagged`);
814
+ console.log(`${icon("brain")} Memory: ${factCount} facts Ā· ${tagCount} sessions tagged`);
798
815
  }
799
816
  } catch { /* non-fatal */ }
800
817
 
@@ -806,12 +823,12 @@ try {
806
823
  : 0;
807
824
 
808
825
  if (pendingCount > 0) {
809
- console.log(`🧬 Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
826
+ console.log(`${icon("dna")} Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
810
827
  } else {
811
828
  // Show last distill time
812
829
  let lastDistillStr = 'ä»ŽęœŖ';
813
830
  try {
814
- const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
831
+ const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
815
832
  if (fs.existsSync(profilePath)) {
816
833
  const _yaml = require('js-yaml');
817
834
  const profile = _yaml.load(fs.readFileSync(profilePath, 'utf8'));
@@ -825,7 +842,7 @@ try {
825
842
  }
826
843
  }
827
844
  } catch { /* non-fatal */ }
828
- console.log(`🧬 Cognition: ę— ę–°äæ”å· Ā· äøŠę¬”č’øé¦ ${lastDistillStr}`);
845
+ console.log(`${icon("dna")} Cognition: ę— ę–°äæ”å· Ā· äøŠę¬”č’øé¦ ${lastDistillStr}`);
829
846
  }
830
847
  } catch { /* non-fatal */ }
831
848
 
@@ -851,7 +868,7 @@ const CURRENT_VERSION = pkgVersion;
851
868
  });
852
869
 
853
870
  if (latest && latest !== CURRENT_VERSION) {
854
- console.log(`šŸ“¦ MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
871
+ console.log(`${icon("pkg")} MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
855
872
  const { execSync } = require('child_process');
856
873
  try {
857
874
  execSync('npm install -g metame-cli@latest', {
@@ -859,10 +876,10 @@ const CURRENT_VERSION = pkgVersion;
859
876
  timeout: 60000,
860
877
  ...(process.platform === 'win32' ? { shell: process.env.COMSPEC || true } : {}),
861
878
  });
862
- console.log(`āœ… Updated to ${latest}. Restart metame to use the new version.`);
879
+ console.log(`${icon("ok")} Updated to ${latest}. Restart metame to use the new version.`);
863
880
  } catch (e) {
864
881
  const msg = e.stderr ? e.stderr.toString().trim().split('\n').pop() : '';
865
- console.log(`āš ļø Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
882
+ console.log(`${icon("warn")} Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
866
883
  }
867
884
  }
868
885
  } catch { /* network unavailable, skip silently */ }
@@ -881,24 +898,25 @@ const CURRENT_VERSION = pkgVersion;
881
898
  if (fs.existsSync(QMD_OFFERED_FILE)) return; // already offered before
882
899
 
883
900
  // Check if QMD already installed
884
- try { execSync('which qmd', { stdio: 'pipe', timeout: 2000 }); return; } catch { }
901
+ const whichCmd = process.platform === 'win32' ? 'where' : 'which';
902
+ try { execSync(`${whichCmd} qmd`, { stdio: 'pipe', timeout: 2000 }); return; } catch { }
885
903
 
886
904
  // Mark as offered NOW — so crash/ctrl-c won't re-ask
887
905
  try { fs.writeFileSync(QMD_OFFERED_FILE, new Date().toISOString(), 'utf8'); } catch { }
888
906
 
889
907
  // Check bun availability
890
908
  let bunAvailable = false;
891
- try { execSync('which bun', { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
909
+ try { execSync(`${whichCmd} bun`, { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
892
910
 
893
911
  console.log('');
894
- console.log('ā”Œā”€ šŸ” č®°åæ†ęœē“¢å¢žå¼ŗļ¼ˆåÆé€‰ļ¼Œå…č“¹ļ¼‰');
912
+ console.log(`ā”Œā”€ ${icon("search")} č®°åæ†ęœē“¢å¢žå¼ŗļ¼ˆåÆé€‰ļ¼Œå…č“¹ļ¼‰`);
895
913
  console.log('│');
896
914
  console.log('│ å½“å‰ęØ”å¼ļ¼šåŸŗē”€å…Øę–‡ęœē“¢ļ¼ˆFTS5)');
897
915
  console.log('│ 安装 QMD åŽļ¼šBM25 + å‘é‡čÆ­ä¹‰ + é‡ęŽ’åŗ 混合搜瓢');
898
916
  console.log('│ ę•ˆęžœļ¼šå¬å›žč“Øé‡ēŗ¦ 5xļ¼ŒęØ”ē³Šęčæ°ä¹Ÿčƒ½ē²¾å‡†å‘½äø­åŽ†å²č®°åæ†');
899
917
  if (!bunAvailable) {
900
918
  console.log('│');
901
- console.log('│ āš ļø ęœŖę£€ęµ‹åˆ° bunļ¼Œę— ę³•č‡ŖåŠØå®‰č£…ć€‚');
919
+ console.log(`│ ${icon("warn")} ęœŖę£€ęµ‹åˆ° bunļ¼Œę— ę³•č‡ŖåŠØå®‰č£…ć€‚`);
902
920
  console.log('│ ę‰‹åŠØå®‰č£…ļ¼šcurl -fsSL https://bun.sh/install | bash');
903
921
  console.log('│ bun install -g github:tobi/qmd');
904
922
  console.log('└────────────────────────────────────────────────');
@@ -917,12 +935,12 @@ const CURRENT_VERSION = pkgVersion;
917
935
  process.stdout.write('\n');
918
936
 
919
937
  if (answer === 'y' || answer === 'yes') {
920
- console.log(' ā¬‡ļø ę­£åœØå®‰č£… QMD...');
938
+ console.log(` ${icon("down")} ę­£åœØå®‰č£… QMD...`);
921
939
  try {
922
940
  execSync('bun install -g github:tobi/qmd', { stdio: 'inherit', timeout: 120000 });
923
- console.log(' āœ… QMD å·²å®‰č£…ļ¼Œäø‹ę¬”č®°åæ†ęœē“¢č‡ŖåŠØåÆē”Øå‘é‡ęØ”å¼ć€‚');
941
+ console.log(` ${icon("ok")} QMD å·²å®‰č£…ļ¼Œäø‹ę¬”č®°åæ†ęœē“¢č‡ŖåŠØåÆē”Øå‘é‡ęØ”å¼ć€‚`);
924
942
  } catch {
925
- console.log(' āš ļø å®‰č£…å¤±č“„ļ¼ŒåÆę‰‹åŠØę‰§č”Œļ¼šbun install -g github:tobi/qmd');
943
+ console.log(` ${icon("warn")} å®‰č£…å¤±č“„ļ¼ŒåÆę‰‹åŠØę‰§č”Œļ¼šbun install -g github:tobi/qmd`);
926
944
  }
927
945
  } else {
928
946
  console.log(' č·³čæ‡ć€‚å¦‚éœ€ę—„åŽå®‰č£…ļ¼šbun install -g github:tobi/qmd');
@@ -941,7 +959,7 @@ const CURRENT_VERSION = pkgVersion;
941
959
  const isRefresh = process.argv.includes('refresh') || process.argv.includes('--refresh');
942
960
 
943
961
  if (isRefresh) {
944
- console.log("āœ… MetaMe configuration re-injected.");
962
+ console.log(`${icon("ok")} MetaMe configuration re-injected.`);
945
963
  console.log(" Ask Claude to 'read CLAUDE.md' to apply the changes.");
946
964
  process.exit(0);
947
965
  }
@@ -957,7 +975,7 @@ if (isEvolve) {
957
975
  const insight = process.argv.slice(evolveIndex + 1).join(' ').trim();
958
976
 
959
977
  if (!insight) {
960
- console.error("āŒ Error: Missing insight.");
978
+ console.error(`${icon("fail")} Error: Missing insight.`);
961
979
  console.error(" Usage: metame evolve \"I realized I prefer functional programming\"");
962
980
  process.exit(1);
963
981
  }
@@ -979,14 +997,14 @@ if (isEvolve) {
979
997
  // Save back to file
980
998
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
981
999
 
982
- console.log("🧠 MetaMe Brain Updated.");
1000
+ console.log(`${icon("brain")} MetaMe Brain Updated.`);
983
1001
  console.log(` Added insight: "${insight}"`);
984
1002
  console.log(" (Run 'metame refresh' to apply this to the current session)");
985
1003
  } else {
986
- console.error("āŒ Error: No profile found. Run 'metame' first to initialize.");
1004
+ console.error(`${icon("fail")} Error: No profile found. Run 'metame' first to initialize.`);
987
1005
  }
988
1006
  } catch (e) {
989
- console.error("āŒ Error updating profile:", e.message);
1007
+ console.error(`${icon("fail")} Error updating profile:`, e.message);
990
1008
  }
991
1009
  process.exit(0);
992
1010
  }
@@ -1006,7 +1024,7 @@ if (isSetTrait) {
1006
1024
  const value = process.argv.slice(setIndex + 2).join(' ').trim();
1007
1025
 
1008
1026
  if (!key || !value) {
1009
- console.error("āŒ Error: Missing key or value.");
1027
+ console.error(`${icon("fail")} Error: Missing key or value.`);
1010
1028
  console.error(" Usage: metame set-trait identity.role \"New Role\"");
1011
1029
  process.exit(1);
1012
1030
  }
@@ -1032,14 +1050,14 @@ if (isSetTrait) {
1032
1050
 
1033
1051
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
1034
1052
 
1035
- console.log(`🧠 MetaMe Brain Surgically Updated.`);
1053
+ console.log(`${icon("brain")} MetaMe Brain Surgically Updated.`);
1036
1054
  console.log(` Set \`${key}\` = "${value}"`);
1037
1055
  console.log(" (Run 'metame refresh' to apply this to the current session)");
1038
1056
  } else {
1039
- console.error("āŒ Error: No profile found.");
1057
+ console.error(`${icon("fail")} Error: No profile found.`);
1040
1058
  }
1041
1059
  } catch (e) {
1042
- console.error("āŒ Error updating profile:", e.message);
1060
+ console.error(`${icon("fail")} Error updating profile:`, e.message);
1043
1061
  }
1044
1062
  process.exit(0);
1045
1063
  }
@@ -1056,9 +1074,9 @@ if (isQuiet) {
1056
1074
  if (!doc.growth) doc.growth = {};
1057
1075
  doc.growth.quiet_until = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();
1058
1076
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
1059
- console.log("🤫 MetaMe: Mirror & reflections silenced for 48 hours.");
1077
+ console.log(`${icon("brain")} MetaMe: Mirror & reflections silenced for 48 hours.`);
1060
1078
  } catch (e) {
1061
- console.error("āŒ Error:", e.message);
1079
+ console.error(`${icon("fail")} Error:`, e.message);
1062
1080
  }
1063
1081
  process.exit(0);
1064
1082
  }
@@ -1072,26 +1090,26 @@ if (isInsights) {
1072
1090
  const zoneHistory = (doc.growth && doc.growth.zone_history) || [];
1073
1091
 
1074
1092
  if (patterns.length === 0) {
1075
- console.log("šŸ” MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.");
1093
+ console.log(`${icon("search")} MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.`);
1076
1094
  } else {
1077
- console.log("šŸŖž MetaMe Insights:\n");
1095
+ console.log(`${icon("mirror")} MetaMe Insights:\n`);
1078
1096
  patterns.forEach((p, i) => {
1079
- const icon = p.type === 'avoidance' ? 'āš ļø' : p.type === 'growth' ? '🌱' : p.type === 'energy' ? '⚔' : 'šŸ”„';
1080
- console.log(` ${icon} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
1097
+ const sym = p.type === 'avoidance' ? icon("warn") : p.type === 'growth' ? '+' : p.type === 'energy' ? '*' : icon("reload");
1098
+ console.log(` ${sym} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
1081
1099
  console.log(` Detected: ${p.detected}${p.surfaced ? `, Last shown: ${p.surfaced}` : ''}`);
1082
1100
  });
1083
1101
  if (zoneHistory.length > 0) {
1084
- console.log(`\n šŸ“Š Recent zone history: ${zoneHistory.join(' → ')}`);
1102
+ console.log(`\n ${icon("chart")} Recent zone history: ${zoneHistory.join(' → ')}`);
1085
1103
  console.log(` (C=Comfort, S=Stretch, P=Panic)`);
1086
1104
  }
1087
1105
  const answered = (doc.growth && doc.growth.reflections_answered) || 0;
1088
1106
  const skipped = (doc.growth && doc.growth.reflections_skipped) || 0;
1089
1107
  if (answered + skipped > 0) {
1090
- console.log(`\n šŸ’­ Reflections: ${answered} answered, ${skipped} skipped`);
1108
+ console.log(`\n ${icon("thought")} Reflections: ${answered} answered, ${skipped} skipped`);
1091
1109
  }
1092
1110
  }
1093
1111
  } catch (e) {
1094
- console.error("āŒ Error:", e.message);
1112
+ console.error(`${icon("fail")} Error:`, e.message);
1095
1113
  }
1096
1114
  process.exit(0);
1097
1115
  }
@@ -1102,7 +1120,7 @@ if (isMirror) {
1102
1120
  const mirrorIndex = process.argv.indexOf('mirror');
1103
1121
  const toggle = process.argv[mirrorIndex + 1];
1104
1122
  if (toggle !== 'on' && toggle !== 'off') {
1105
- console.error("āŒ Usage: metame mirror on|off");
1123
+ console.error(`${icon("fail")} Usage: metame mirror on|off`);
1106
1124
  process.exit(1);
1107
1125
  }
1108
1126
  try {
@@ -1110,9 +1128,9 @@ if (isMirror) {
1110
1128
  if (!doc.growth) doc.growth = {};
1111
1129
  doc.growth.mirror_enabled = (toggle === 'on');
1112
1130
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
1113
- console.log(`šŸŖž MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
1131
+ console.log(`${icon("mirror")} MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
1114
1132
  } catch (e) {
1115
- console.error("āŒ Error:", e.message);
1133
+ console.error(`${icon("fail")} Error:`, e.message);
1116
1134
  }
1117
1135
  process.exit(0);
1118
1136
  }
@@ -1128,7 +1146,7 @@ if (isProvider) {
1128
1146
 
1129
1147
  if (!subCmd || subCmd === 'list') {
1130
1148
  const active = providers.getActiveProvider();
1131
- console.log(`šŸ”Œ MetaMe Providers (active: ${active ? active.name : 'anthropic'})`);
1149
+ console.log(`${icon("plug")} MetaMe Providers (active: ${active ? active.name : 'anthropic'})`);
1132
1150
  console.log(providers.listFormatted());
1133
1151
  process.exit(0);
1134
1152
  }
@@ -1136,18 +1154,18 @@ if (isProvider) {
1136
1154
  if (subCmd === 'use') {
1137
1155
  const name = process.argv[providerIndex + 2];
1138
1156
  if (!name) {
1139
- console.error("āŒ Usage: metame provider use <name>");
1157
+ console.error(`${icon("fail")} Usage: metame provider use <name>`);
1140
1158
  process.exit(1);
1141
1159
  }
1142
1160
  try {
1143
1161
  providers.setActive(name);
1144
1162
  const p = providers.getActiveProvider();
1145
- console.log(`āœ… Provider switched → ${name} (${p.label || name})`);
1163
+ console.log(`${icon("ok")} Provider switched → ${name} (${p.label || name})`);
1146
1164
  if (name !== 'anthropic') {
1147
1165
  console.log(` Base URL: ${p.base_url || 'not set'}`);
1148
1166
  }
1149
1167
  } catch (e) {
1150
- console.error(`āŒ ${e.message}`);
1168
+ console.error(`${icon("fail")} ${e.message}`);
1151
1169
  process.exit(1);
1152
1170
  }
1153
1171
  process.exit(0);
@@ -1156,7 +1174,7 @@ if (isProvider) {
1156
1174
  if (subCmd === 'add') {
1157
1175
  const name = process.argv[providerIndex + 2];
1158
1176
  if (!name) {
1159
- console.error("āŒ Usage: metame provider add <name>");
1177
+ console.error(`${icon("fail")} Usage: metame provider add <name>`);
1160
1178
  process.exit(1);
1161
1179
  }
1162
1180
  const readline = require('readline');
@@ -1164,7 +1182,7 @@ if (isProvider) {
1164
1182
  const ask = (q) => new Promise(r => rl.question(q, r));
1165
1183
 
1166
1184
  (async () => {
1167
- console.log(`\nšŸ”Œ Add Provider: ${name}\n`);
1185
+ console.log(`\n${icon("plug")} Add Provider: ${name}\n`);
1168
1186
  console.log("The relay must accept Anthropic Messages API format.");
1169
1187
  console.log("(Most quality relays like OpenRouter, OneAPI, etc. support this.)\n");
1170
1188
 
@@ -1173,7 +1191,7 @@ if (isProvider) {
1173
1191
  const api_key = (await ask("API Key: ")).trim();
1174
1192
 
1175
1193
  if (!base_url) {
1176
- console.error("āŒ Base URL is required.");
1194
+ console.error(`${icon("fail")} Base URL is required.`);
1177
1195
  rl.close();
1178
1196
  process.exit(1);
1179
1197
  }
@@ -1184,10 +1202,10 @@ if (isProvider) {
1184
1202
 
1185
1203
  try {
1186
1204
  providers.addProvider(name, config);
1187
- console.log(`\nāœ… Provider "${name}" added.`);
1205
+ console.log(`\n${icon("ok")} Provider "${name}" added.`);
1188
1206
  console.log(` Switch to it: metame provider use ${name}`);
1189
1207
  } catch (e) {
1190
- console.error(`āŒ ${e.message}`);
1208
+ console.error(`${icon("fail")} ${e.message}`);
1191
1209
  }
1192
1210
  rl.close();
1193
1211
  process.exit(0);
@@ -1198,14 +1216,14 @@ if (isProvider) {
1198
1216
  if (subCmd === 'remove') {
1199
1217
  const name = process.argv[providerIndex + 2];
1200
1218
  if (!name) {
1201
- console.error("āŒ Usage: metame provider remove <name>");
1219
+ console.error(`${icon("fail")} Usage: metame provider remove <name>`);
1202
1220
  process.exit(1);
1203
1221
  }
1204
1222
  try {
1205
1223
  providers.removeProvider(name);
1206
- console.log(`āœ… Provider "${name}" removed.`);
1224
+ console.log(`${icon("ok")} Provider "${name}" removed.`);
1207
1225
  } catch (e) {
1208
- console.error(`āŒ ${e.message}`);
1226
+ console.error(`${icon("fail")} ${e.message}`);
1209
1227
  }
1210
1228
  process.exit(0);
1211
1229
  }
@@ -1214,15 +1232,15 @@ if (isProvider) {
1214
1232
  const role = process.argv[providerIndex + 2]; // distill | daemon
1215
1233
  const name = process.argv[providerIndex + 3]; // provider name or empty to clear
1216
1234
  if (!role) {
1217
- console.error("āŒ Usage: metame provider set-role <distill|daemon> [provider-name]");
1235
+ console.error(`${icon("fail")} Usage: metame provider set-role <distill|daemon> [provider-name]`);
1218
1236
  console.error(" Omit provider name to reset to active provider.");
1219
1237
  process.exit(1);
1220
1238
  }
1221
1239
  try {
1222
1240
  providers.setRole(role, name || null);
1223
- console.log(`āœ… ${role} provider ${name ? `set to "${name}"` : 'reset to active'}.`);
1241
+ console.log(`${icon("ok")} ${role} provider ${name ? `set to "${name}"` : 'reset to active'}.`);
1224
1242
  } catch (e) {
1225
- console.error(`āŒ ${e.message}`);
1243
+ console.error(`${icon("fail")} ${e.message}`);
1226
1244
  }
1227
1245
  process.exit(0);
1228
1246
  }
@@ -1233,11 +1251,11 @@ if (isProvider) {
1233
1251
  const name = targetName || prov.active;
1234
1252
  const p = prov.providers[name];
1235
1253
  if (!p) {
1236
- console.error(`āŒ Provider "${name}" not found.`);
1254
+ console.error(`${icon("fail")} Provider "${name}" not found.`);
1237
1255
  process.exit(1);
1238
1256
  }
1239
1257
 
1240
- console.log(`šŸ” Testing provider: ${name} (${p.label || name})`);
1258
+ console.log(`${icon("search")} Testing provider: ${name} (${p.label || name})`);
1241
1259
  if (name === 'anthropic') {
1242
1260
  console.log(" Using official Anthropic endpoint — testing via claude CLI...");
1243
1261
  } else {
@@ -1261,18 +1279,18 @@ if (isProvider) {
1261
1279
  const elapsed = Date.now() - start;
1262
1280
 
1263
1281
  if (result.includes('PROVIDER_OK')) {
1264
- console.log(` āœ… Connected (${elapsed}ms)`);
1282
+ console.log(` ${icon("ok")} Connected (${elapsed}ms)`);
1265
1283
  } else {
1266
- console.log(` āš ļø Response received (${elapsed}ms) but unexpected: ${result.slice(0, 80)}`);
1284
+ console.log(` ${icon("warn")} Response received (${elapsed}ms) but unexpected: ${result.slice(0, 80)}`);
1267
1285
  }
1268
1286
  } catch (e) {
1269
- console.error(` āŒ Failed: ${e.message.split('\n')[0]}`);
1287
+ console.error(` ${icon("fail")} Failed: ${e.message.split('\n')[0]}`);
1270
1288
  }
1271
1289
  process.exit(0);
1272
1290
  }
1273
1291
 
1274
1292
  // Unknown subcommand — show help
1275
- console.log("šŸ”Œ MetaMe Provider Commands:");
1293
+ console.log(`${icon("plug")} MetaMe Provider Commands:`);
1276
1294
  console.log(" metame provider — list providers");
1277
1295
  console.log(" metame provider use <name> — switch active provider");
1278
1296
  console.log(" metame provider add <name> — add a new provider");
@@ -1316,20 +1334,20 @@ if (isDaemon) {
1316
1334
  if (fs.existsSync(templateSrc)) {
1317
1335
  fs.copyFileSync(templateSrc, DAEMON_CONFIG);
1318
1336
  } else {
1319
- console.error("āŒ Template not found. Reinstall MetaMe.");
1337
+ console.error(`${icon("fail")} Template not found. Reinstall MetaMe.`);
1320
1338
  process.exit(1);
1321
1339
  }
1322
1340
  try { fs.chmodSync(METAME_DIR, 0o700); } catch { /* ignore on Windows */ }
1323
- console.log("āœ… Config created: ~/.metame/daemon.yaml\n");
1341
+ console.log(`${icon("ok")} Config created: ~/.metame/daemon.yaml\n`);
1324
1342
  } else {
1325
- console.log("āœ… Config exists: ~/.metame/daemon.yaml\n");
1343
+ console.log(`${icon("ok")} Config exists: ~/.metame/daemon.yaml\n`);
1326
1344
  }
1327
1345
 
1328
1346
  const yaml = require(path.join(__dirname, 'node_modules', 'js-yaml'));
1329
1347
  let cfg = yaml.load(fs.readFileSync(DAEMON_CONFIG, 'utf8')) || {};
1330
1348
 
1331
1349
  // --- Telegram Setup ---
1332
- console.log("━━━ šŸ“± Telegram Setup ━━━");
1350
+ console.log(`━━━ ${icon("phone")} Telegram Setup ━━━`);
1333
1351
  console.log("");
1334
1352
  console.log("Step 1: Create a Bot");
1335
1353
  console.log(" • Open Telegram app on your phone or desktop");
@@ -1374,21 +1392,21 @@ if (isDaemon) {
1374
1392
 
1375
1393
  if (chatIds.length > 0) {
1376
1394
  cfg.telegram.allowed_chat_ids = chatIds;
1377
- console.log(` āœ… Found chat ID(s): ${chatIds.join(', ')}`);
1395
+ console.log(` ${icon("ok")} Found chat ID(s): ${chatIds.join(', ')}`);
1378
1396
  } else {
1379
- console.log(" āš ļø No messages found. Make sure you messaged the bot.");
1397
+ console.log(` ${icon("warn")} No messages found. Make sure you messaged the bot.`);
1380
1398
  console.log(" You can set allowed_chat_ids manually in daemon.yaml later.");
1381
1399
  }
1382
1400
  } catch {
1383
- console.log(" āš ļø Could not fetch chat ID. Set it manually in daemon.yaml.");
1401
+ console.log(` ${icon("warn")} Could not fetch chat ID. Set it manually in daemon.yaml.`);
1384
1402
  }
1385
- console.log(" āœ… Telegram configured!\n");
1403
+ console.log(` ${icon("ok")} Telegram configured!\n`);
1386
1404
  } else {
1387
1405
  console.log(" Skipped.\n");
1388
1406
  }
1389
1407
 
1390
1408
  // --- Feishu Setup ---
1391
- console.log("━━━ šŸ“˜ Feishu (Lark) Setup ━━━");
1409
+ console.log(`━━━ ${icon("feishu")} Feishu (Lark) Setup ━━━`);
1392
1410
  console.log("");
1393
1411
  console.log("Step 1: Create an App");
1394
1412
  console.log(" • Go to: https://open.feishu.cn/app");
@@ -1432,7 +1450,7 @@ if (isDaemon) {
1432
1450
  cfg.feishu.app_id = feishuAppId;
1433
1451
  cfg.feishu.app_secret = feishuSecret;
1434
1452
  if (!cfg.feishu.allowed_chat_ids) cfg.feishu.allowed_chat_ids = [];
1435
- console.log(" āœ… Feishu configured!");
1453
+ console.log(` ${icon("ok")} Feishu configured!`);
1436
1454
  console.log(" Note: allowed_chat_ids is empty = deny all users.");
1437
1455
  console.log(" Add chat IDs to daemon.yaml or use /agent bind from target chat.\n");
1438
1456
  }
@@ -1442,7 +1460,7 @@ if (isDaemon) {
1442
1460
 
1443
1461
  // Write config
1444
1462
  fs.writeFileSync(DAEMON_CONFIG, yaml.dump(cfg, { lineWidth: -1 }), 'utf8');
1445
- console.log("━━━ āœ… Setup Complete ━━━");
1463
+ console.log(`━━━ ${icon("ok")} Setup Complete ━━━`);
1446
1464
  console.log(`Config saved: ${DAEMON_CONFIG}`);
1447
1465
  console.log("\nNext steps:");
1448
1466
  console.log(" metame start — start the daemon");
@@ -1459,7 +1477,7 @@ if (isDaemon) {
1459
1477
 
1460
1478
  if (subCmd === 'install-launchd') {
1461
1479
  if (process.platform !== 'darwin') {
1462
- console.error("āŒ launchd is macOS-only.");
1480
+ console.error(`${icon("fail")} launchd is macOS-only.`);
1463
1481
  process.exit(1);
1464
1482
  }
1465
1483
  const plistDir = path.join(HOME_DIR, 'Library', 'LaunchAgents');
@@ -1501,7 +1519,7 @@ if (isDaemon) {
1501
1519
  </dict>
1502
1520
  </plist>`;
1503
1521
  fs.writeFileSync(plistPath, plistContent, 'utf8');
1504
- console.log(`āœ… launchd plist installed: ${plistPath}`);
1522
+ console.log(`${icon("ok")} launchd plist installed: ${plistPath}`);
1505
1523
  console.log(" Load now: launchctl load " + plistPath);
1506
1524
  console.log(" Unload: launchctl unload " + plistPath);
1507
1525
  process.exit(0);
@@ -1509,7 +1527,7 @@ if (isDaemon) {
1509
1527
 
1510
1528
  if (subCmd === 'install-systemd') {
1511
1529
  if (process.platform === 'darwin') {
1512
- console.error("āŒ Use 'metame daemon install-launchd' on macOS.");
1530
+ console.error(`${icon("fail")} Use 'metame daemon install-launchd' on macOS.`);
1513
1531
  process.exit(1);
1514
1532
  }
1515
1533
 
@@ -1517,7 +1535,7 @@ if (isDaemon) {
1517
1535
  try {
1518
1536
  require('child_process').execSync('systemctl --user --no-pager status 2>/dev/null || true');
1519
1537
  } catch {
1520
- console.error("āŒ systemd not available.");
1538
+ console.error(`${icon("fail")} systemd not available.`);
1521
1539
  console.error(" WSL users: add [boot]\\nsystemd=true to /etc/wsl.conf, then restart WSL.");
1522
1540
  process.exit(1);
1523
1541
  }
@@ -1557,7 +1575,7 @@ WantedBy=default.target
1557
1575
  // Enable lingering so service runs even when user is not logged in
1558
1576
  try { es(`loginctl enable-linger ${process.env.USER || ''}`); } catch { /* may need root */ }
1559
1577
 
1560
- console.log(`āœ… systemd service installed: ${servicePath}`);
1578
+ console.log(`${icon("ok")} systemd service installed: ${servicePath}`);
1561
1579
  console.log(" Status: systemctl --user status metame-daemon");
1562
1580
  console.log(" Logs: journalctl --user -u metame-daemon -f");
1563
1581
  console.log(" Disable: systemctl --user disable metame-daemon");
@@ -1566,7 +1584,7 @@ WantedBy=default.target
1566
1584
  const isWSL = fs.existsSync('/proc/version') &&
1567
1585
  fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
1568
1586
  if (isWSL) {
1569
- console.log("\n šŸ“Œ WSL auto-boot tip:");
1587
+ console.log(`\n ${icon("pin")} WSL auto-boot tip:`);
1570
1588
  console.log(" Add this to Windows Task Scheduler (run at login):");
1571
1589
  console.log(` wsl -d ${process.env.WSL_DISTRO_NAME || 'Ubuntu'} -- sh -c 'nohup sleep infinity &'`);
1572
1590
  console.log(" This keeps WSL alive so the daemon stays running.");
@@ -1574,17 +1592,44 @@ WantedBy=default.target
1574
1592
  process.exit(0);
1575
1593
  }
1576
1594
 
1595
+ if (subCmd === 'install-task-scheduler') {
1596
+ if (process.platform !== 'win32') {
1597
+ console.error("Task Scheduler is Windows-only. Use install-launchd (macOS) or install-systemd (Linux).");
1598
+ process.exit(1);
1599
+ }
1600
+ const nodePath = process.execPath;
1601
+ const taskName = 'MetaMe-Daemon';
1602
+ const scriptPath = DAEMON_SCRIPT.replace(/\//g, '\\');
1603
+ const nodePathWin = nodePath.replace(/\//g, '\\');
1604
+ try {
1605
+ try {
1606
+ execSync(`schtasks /delete /tn "${taskName}" /f`, { stdio: 'ignore' });
1607
+ } catch { /* task may not exist yet */ }
1608
+ execSync(
1609
+ `schtasks /create /tn "${taskName}" /tr "\\"${nodePathWin}\\" \\"${scriptPath}\\"" /sc onlogon /rl limited /f`,
1610
+ { stdio: 'inherit' }
1611
+ );
1612
+ console.log(`Task Scheduler task "${taskName}" installed.`);
1613
+ console.log(` The daemon will auto-start at login.`);
1614
+ console.log(` Remove: schtasks /delete /tn "${taskName}" /f`);
1615
+ console.log(` Query: schtasks /query /tn "${taskName}"`);
1616
+ } catch (e) {
1617
+ console.error(`Failed to create scheduled task: ${e.message}`);
1618
+ console.error(" Try running as Administrator, or create it manually in Task Scheduler.");
1619
+ process.exit(1);
1620
+ }
1621
+ process.exit(0);
1622
+ }
1623
+
1577
1624
  if (subCmd === 'start') {
1578
1625
  // Kill any lingering daemon.js processes to avoid Feishu WebSocket conflicts
1579
1626
  try {
1580
- const { execSync: es } = require('child_process');
1581
- const pids = es("pgrep -f 'node.*daemon\\.js' 2>/dev/null || true", { encoding: 'utf8' }).trim();
1582
- if (pids) {
1583
- for (const p of pids.split('\n').filter(Boolean)) {
1584
- const n = parseInt(p, 10);
1585
- if (n && n !== process.pid) try { process.kill(n, 'SIGKILL'); } catch { /* */ }
1627
+ const pids = findProcessesByPattern('node.*daemon\\.js');
1628
+ if (pids.length) {
1629
+ for (const n of pids) {
1630
+ try { process.kill(n, 'SIGKILL'); } catch { /* */ }
1586
1631
  }
1587
- es('sleep 1');
1632
+ sleepSync(1000);
1588
1633
  }
1589
1634
  } catch { /* ignore */ }
1590
1635
  // Check if already running
@@ -1592,25 +1637,26 @@ WantedBy=default.target
1592
1637
  try { fs.unlinkSync(DAEMON_PID); } catch { /* */ }
1593
1638
  }
1594
1639
  if (!fs.existsSync(DAEMON_CONFIG)) {
1595
- console.error("āŒ No config found. Run: metame daemon init");
1640
+ console.error(`${icon("fail")} No config found. Run: metame daemon init`);
1596
1641
  process.exit(1);
1597
1642
  }
1598
1643
  if (!fs.existsSync(DAEMON_SCRIPT)) {
1599
- console.error("āŒ daemon.js not found. Reinstall MetaMe.");
1644
+ console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
1600
1645
  process.exit(1);
1601
1646
  }
1602
- // Use caffeinate on macOS/Linux to prevent sleep while daemon is running
1603
- const isNotWindows = process.platform !== 'win32';
1604
- const cmd = isNotWindows ? 'caffeinate' : process.execPath;
1605
- const args = isNotWindows ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
1647
+ // Use caffeinate on macOS to prevent sleep while daemon is running
1648
+ const isMac = process.platform === 'darwin';
1649
+ const isWin = process.platform === 'win32';
1650
+ const cmd = isMac ? 'caffeinate' : process.execPath;
1651
+ const args = isMac ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
1606
1652
  const bg = spawn(cmd, args, {
1607
- detached: true,
1653
+ detached: !isWin,
1608
1654
  stdio: 'ignore',
1609
1655
  windowsHide: true,
1610
1656
  env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
1611
1657
  });
1612
1658
  bg.unref();
1613
- console.log(`āœ… MetaMe daemon started (PID: ${bg.pid})`);
1659
+ console.log(`${icon("ok")} MetaMe daemon started (PID: ${bg.pid})`);
1614
1660
  console.log(" Logs: metame logs");
1615
1661
  console.log(" Stop: metame stop");
1616
1662
  process.exit(0);
@@ -1618,7 +1664,7 @@ WantedBy=default.target
1618
1664
 
1619
1665
  if (subCmd === 'stop') {
1620
1666
  if (!fs.existsSync(DAEMON_PID)) {
1621
- console.log("ā„¹ļø No daemon running (no PID file).");
1667
+ console.log(`${icon("info")} No daemon running (no PID file).`);
1622
1668
  process.exit(0);
1623
1669
  }
1624
1670
  const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
@@ -1627,16 +1673,15 @@ WantedBy=default.target
1627
1673
  // Wait for process to die (up to 3s), then force kill
1628
1674
  let dead = false;
1629
1675
  for (let i = 0; i < 6; i++) {
1630
- const { execSync: es } = require('child_process');
1631
- es('sleep 0.5');
1676
+ sleepSync(500);
1632
1677
  try { process.kill(pid, 0); } catch { dead = true; break; }
1633
1678
  }
1634
1679
  if (!dead) {
1635
1680
  try { process.kill(pid, 'SIGKILL'); } catch { /* already gone */ }
1636
1681
  }
1637
- console.log(`āœ… Daemon stopped (PID: ${pid})`);
1682
+ console.log(`${icon("ok")} Daemon stopped (PID: ${pid})`);
1638
1683
  } catch (e) {
1639
- console.log(`āš ļø Process ${pid} not found (may have already exited).`);
1684
+ console.log(`${icon("warn")} Process ${pid} not found (may have already exited).`);
1640
1685
  }
1641
1686
  try { fs.unlinkSync(DAEMON_PID); } catch { /* ignore */ }
1642
1687
  process.exit(0);
@@ -1653,7 +1698,7 @@ WantedBy=default.target
1653
1698
  try { process.kill(pid, 0); isRunning = true; } catch { /* dead */ }
1654
1699
  }
1655
1700
 
1656
- console.log(`šŸ¤– MetaMe Daemon: ${isRunning ? '🟢 Running' : 'šŸ”“ Stopped'}`);
1701
+ console.log(`${icon("bot")} MetaMe Daemon: ${isRunning ? icon("green") + ' Running' : icon("red") + ' Stopped'}`);
1657
1702
  if (state.started_at) console.log(` Started: ${state.started_at}`);
1658
1703
  if (state.pid) console.log(` PID: ${state.pid}`);
1659
1704
 
@@ -1681,8 +1726,8 @@ WantedBy=default.target
1681
1726
  if (taskEntries.length > 0) {
1682
1727
  console.log(" Recent tasks:");
1683
1728
  for (const [name, info] of taskEntries) {
1684
- const icon = info.status === 'success' ? 'āœ…' : 'āŒ';
1685
- console.log(` ${icon} ${name}: ${info.last_run || 'unknown'}`);
1729
+ const sym = info.status === 'success' ? icon("ok") : icon("fail");
1730
+ console.log(` ${sym} ${name}: ${info.last_run || 'unknown'}`);
1686
1731
  if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
1687
1732
  }
1688
1733
  const hiddenStale = Object.keys(tasks).length - taskEntries.length;
@@ -1695,7 +1740,7 @@ WantedBy=default.target
1695
1740
 
1696
1741
  if (subCmd === 'logs') {
1697
1742
  if (!fs.existsSync(DAEMON_LOG)) {
1698
- console.log("ā„¹ļø No log file yet. Start the daemon first.");
1743
+ console.log(`${icon("info")} No log file yet. Start the daemon first.`);
1699
1744
  process.exit(0);
1700
1745
  }
1701
1746
  const content = fs.readFileSync(DAEMON_LOG, 'utf8');
@@ -1708,11 +1753,11 @@ WantedBy=default.target
1708
1753
  if (subCmd === 'run') {
1709
1754
  const taskName = process.argv[daemonIndex + 2];
1710
1755
  if (!taskName) {
1711
- console.error("āŒ Usage: metame daemon run <task-name>");
1756
+ console.error(`${icon("fail")} Usage: metame daemon run <task-name>`);
1712
1757
  process.exit(1);
1713
1758
  }
1714
1759
  if (!fs.existsSync(DAEMON_SCRIPT)) {
1715
- console.error("āŒ daemon.js not found. Reinstall MetaMe.");
1760
+ console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
1716
1761
  process.exit(1);
1717
1762
  }
1718
1763
  // Run in foreground using daemon.js --run
@@ -1725,7 +1770,7 @@ WantedBy=default.target
1725
1770
  }
1726
1771
 
1727
1772
  // Unknown subcommand
1728
- console.log("šŸ“– MetaMe Daemon Commands:");
1773
+ console.log(`${icon("book")} MetaMe Daemon Commands:`);
1729
1774
  console.log(" metame start — start background daemon");
1730
1775
  console.log(" metame stop — stop daemon");
1731
1776
  console.log(" metame status — show status & budget");
@@ -1733,9 +1778,11 @@ WantedBy=default.target
1733
1778
  console.log(" metame daemon init — initialize config");
1734
1779
  console.log(" metame daemon run <name> — run a task once");
1735
1780
  if (process.platform === 'darwin') {
1736
- console.log(" metame daemon install-launchd — auto-start on macOS");
1781
+ console.log(" metame daemon install-launchd — auto-start on macOS");
1782
+ } else if (process.platform === 'win32') {
1783
+ console.log(" metame daemon install-task-scheduler — auto-start on Windows");
1737
1784
  } else {
1738
- console.log(" metame daemon install-systemd — auto-start on Linux/WSL");
1785
+ console.log(" metame daemon install-systemd — auto-start on Linux/WSL");
1739
1786
  }
1740
1787
  process.exit(0);
1741
1788
  }
@@ -1784,7 +1831,7 @@ if (isSync) {
1784
1831
  process.exit(1);
1785
1832
  }
1786
1833
 
1787
- console.log(`\nšŸ”„ Resuming session ${bestSession.id.slice(0, 8)}...\n`);
1834
+ console.log(`\n${icon("reload")} Resuming session ${bestSession.id.slice(0, 8)}...\n`);
1788
1835
  const providerEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
1789
1836
  const resumeArgs = ['--resume', bestSession.id];
1790
1837
  if (daemonCfg.dangerously_skip_permissions) resumeArgs.push('--dangerously-skip-permissions');
@@ -1805,7 +1852,7 @@ if (isSync) {
1805
1852
  // We rely on our own scoped variable to detect nesting,
1806
1853
  // ignoring the leaky CLAUDE_CODE_SSE_PORT from IDEs.
1807
1854
  if (process.env.METAME_ACTIVE_SESSION === 'true') {
1808
- console.error("\n🚫 ACTION BLOCKED: Nested Session Detected");
1855
+ console.error(`\n${icon("stop")} ACTION BLOCKED: Nested Session Detected`);
1809
1856
  console.error(" You are actively running inside a MetaMe session.");
1810
1857
  console.error(" To reload configuration, use: \x1b[36m!metame refresh\x1b[0m\n");
1811
1858
  process.exit(1);
@@ -1818,7 +1865,7 @@ if (process.env.METAME_ACTIVE_SESSION === 'true') {
1818
1865
  const activeProviderEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
1819
1866
  const activeProviderName = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).getActiveName(); } catch { return 'anthropic'; } })();
1820
1867
  if (activeProviderName !== 'anthropic') {
1821
- console.log(`šŸ”Œ Provider: ${activeProviderName}`);
1868
+ console.log(`${icon("plug")} Provider: ${activeProviderName}`);
1822
1869
  }
1823
1870
 
1824
1871
  // Build launch args — inject system prompt for new users
@@ -1842,7 +1889,7 @@ try {
1842
1889
  let repoProject = cwdProject;
1843
1890
  try {
1844
1891
  const { execSync } = require('child_process');
1845
- const remote = execSync('git remote get-url origin 2>/dev/null || true', { encoding: 'utf8', stdio: 'pipe' }).trim();
1892
+ const remote = execSync('git remote get-url origin', { encoding: 'utf8', stdio: 'pipe' }).trim();
1846
1893
  if (remote) repoProject = path.basename(remote, '.git');
1847
1894
  } catch { /* not a git repo, use dirname */ }
1848
1895
 
@@ -1864,27 +1911,31 @@ try {
1864
1911
 
1865
1912
  // Auto-start daemon if config exists but daemon is not running
1866
1913
  try {
1867
- if (fs.existsSync(DAEMON_CONFIG) && fs.existsSync(DAEMON_SCRIPT)) {
1914
+ const _daemonCfgPath = path.join(METAME_DIR, 'daemon.yaml');
1915
+ const _daemonScript = path.join(METAME_DIR, 'daemon.js');
1916
+ const _daemonPid = path.join(METAME_DIR, 'daemon.pid');
1917
+ if (fs.existsSync(_daemonCfgPath) && fs.existsSync(_daemonScript)) {
1868
1918
  let daemonRunning = false;
1869
- if (fs.existsSync(DAEMON_PID)) {
1919
+ if (fs.existsSync(_daemonPid)) {
1870
1920
  try {
1871
- const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
1921
+ const pid = parseInt(fs.readFileSync(_daemonPid, 'utf8').trim(), 10);
1872
1922
  process.kill(pid, 0); // signal 0 = check if alive
1873
1923
  daemonRunning = true;
1874
1924
  } catch { /* PID file stale, daemon not running */ }
1875
1925
  }
1876
1926
  if (!daemonRunning) {
1877
- const isNotWindows = process.platform !== 'win32';
1878
- const dCmd = isNotWindows ? 'caffeinate' : process.execPath;
1879
- const dArgs = isNotWindows ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
1927
+ const _isMac = process.platform === 'darwin';
1928
+ const _isWin = process.platform === 'win32';
1929
+ const dCmd = _isMac ? 'caffeinate' : process.execPath;
1930
+ const dArgs = _isMac ? ['-i', process.execPath, _daemonScript] : [_daemonScript];
1880
1931
  const bg = spawn(dCmd, dArgs, {
1881
- detached: true,
1932
+ detached: !_isWin,
1882
1933
  stdio: 'ignore',
1883
1934
  windowsHide: true,
1884
1935
  env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
1885
1936
  });
1886
1937
  bg.unref();
1887
- console.log(`šŸ¤– Daemon auto-started (PID: ${bg.pid})`);
1938
+ console.log(`${icon("bot")} Daemon auto-started (PID: ${bg.pid})`);
1888
1939
  }
1889
1940
  }
1890
1941
  } catch { /* non-fatal */ }
@@ -1896,7 +1947,7 @@ const child = spawnClaude(launchArgs, {
1896
1947
  });
1897
1948
 
1898
1949
  child.on('error', () => {
1899
- console.error("\nāŒ Error: Could not launch 'claude'.");
1950
+ console.error(`\n${icon("fail")} Error: Could not launch 'claude'.`);
1900
1951
  console.error(" Please make sure Claude Code is installed globally:");
1901
1952
  console.error(" npm install -g @anthropic-ai/claude-code");
1902
1953
  });