metame-cli 1.4.31 → 1.4.33

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,6 +7,7 @@ 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
12
  // On Windows, .cmd files (like claude.cmd from npm global) need shell:true to spawn.
12
13
  // We use COMSPEC to avoid conda/PATH issues where cmd.exe can't be found.
@@ -49,10 +50,45 @@ if (!fs.existsSync(METAME_DIR)) {
49
50
  fs.mkdirSync(METAME_DIR, { recursive: true });
50
51
  }
51
52
 
53
+ // ---------------------------------------------------------
54
+ // DEPLOY PHASE: sync scripts, docs, bin to ~/.metame/
55
+ // ---------------------------------------------------------
56
+
57
+ /**
58
+ * Sync files from srcDir to destDir. Only writes when content differs.
59
+ * @param {string} srcDir - source directory
60
+ * @param {string} destDir - destination directory
61
+ * @param {object} [opts]
62
+ * @param {string[]} [opts.fileList] - explicit file list (skip readdirSync)
63
+ * @param {number} [opts.chmod] - chmod after write (e.g. 0o755)
64
+ * @returns {boolean} true if any file was updated
65
+ */
66
+ function syncDirFiles(srcDir, destDir, { fileList, chmod } = {}) {
67
+ if (!fs.existsSync(srcDir)) return false;
68
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
69
+ let updated = false;
70
+ const files = fileList || fs.readdirSync(srcDir).filter(f => fs.statSync(path.join(srcDir, f)).isFile());
71
+ for (const f of files) {
72
+ const src = path.join(srcDir, f);
73
+ const dest = path.join(destDir, f);
74
+ try {
75
+ if (!fs.existsSync(src)) continue;
76
+ const srcContent = fs.readFileSync(src, 'utf8');
77
+ const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
78
+ if (srcContent !== destContent) {
79
+ fs.writeFileSync(dest, srcContent, 'utf8');
80
+ if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
81
+ updated = true;
82
+ }
83
+ } catch { /* non-fatal per file */ }
84
+ }
85
+ return updated;
86
+ }
87
+
52
88
  // Auto-deploy bundled scripts to ~/.metame/
53
89
  // IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
54
90
  const scriptsDir = path.join(__dirname, 'scripts');
55
- 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'];
91
+ 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'];
56
92
  const DAEMON_MODULE_SCRIPTS = (() => {
57
93
  try {
58
94
  return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
@@ -74,32 +110,21 @@ try {
74
110
  }
75
111
  } catch { /* non-fatal */ }
76
112
 
77
- let scriptsUpdated = false;
78
- for (const script of BUNDLED_SCRIPTS) {
79
- const src = path.join(scriptsDir, script);
80
- const dest = path.join(METAME_DIR, script);
81
- try {
82
- if (fs.existsSync(src)) {
83
- const srcContent = fs.readFileSync(src, 'utf8');
84
- const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
85
- if (srcContent !== destContent) {
86
- fs.writeFileSync(dest, srcContent, 'utf8');
87
- scriptsUpdated = true;
88
- }
89
- }
90
- } catch {
91
- // Non-fatal
92
- }
93
- }
113
+ const scriptsUpdated = syncDirFiles(scriptsDir, METAME_DIR, { fileList: BUNDLED_SCRIPTS });
94
114
 
95
115
  // Daemon restart on script update:
96
116
  // Don't kill daemon here — daemon's own file watcher detects ~/.metame/daemon.js changes
97
117
  // and has defer logic (waits for active Claude tasks to finish before restarting).
98
118
  // Killing here bypasses that and interrupts ongoing conversations.
99
119
  if (scriptsUpdated) {
100
- console.log('📦 Scripts synced to ~/.metame/ — daemon will auto-restart when idle.');
120
+ console.log(`${icon("pkg")} Scripts synced to ~/.metame/ — daemon will auto-restart when idle.`);
101
121
  }
102
122
 
123
+ // Docs: lazy-load references for CLAUDE.md pointer instructions
124
+ syncDirFiles(path.join(__dirname, 'scripts', 'docs'), path.join(METAME_DIR, 'docs'));
125
+ // Bin: CLI tools (dispatch_to etc.)
126
+ syncDirFiles(path.join(__dirname, 'scripts', 'bin'), path.join(METAME_DIR, 'bin'), { chmod: 0o755 });
127
+
103
128
  // ---------------------------------------------------------
104
129
  // Deploy bundled skills to ~/.claude/skills/
105
130
  // Only installs if not already present — never overwrites user customizations.
@@ -131,7 +156,7 @@ if (fs.existsSync(bundledSkillsDir)) {
131
156
  skillsInstalled.push(skillName);
132
157
  }
133
158
  if (skillsInstalled.length > 0) {
134
- console.log(`🧠 Skills installed: ${skillsInstalled.join(', ')}`);
159
+ console.log(`${icon("brain")} Skills installed: ${skillsInstalled.join(', ')}`);
135
160
  }
136
161
  } catch {
137
162
  // Non-fatal
@@ -153,7 +178,7 @@ if (!fs.existsSync(DAEMON_CONFIG_FILE)) {
153
178
  if (fs.existsSync(DAEMON_YAML_BACKUP)) {
154
179
  // Restore from backup — user had real config that was lost
155
180
  fs.copyFileSync(DAEMON_YAML_BACKUP, DAEMON_CONFIG_FILE);
156
- console.log('⚠️ daemon.yaml was missing — restored from backup.');
181
+ console.log(`${icon("warn")} daemon.yaml was missing — restored from backup.`);
157
182
  } else {
158
183
  const daemonTemplate = path.join(scriptsDir, 'daemon-default.yaml');
159
184
  if (fs.existsSync(daemonTemplate)) {
@@ -179,13 +204,34 @@ function ensureHookInstalled() {
179
204
  }
180
205
 
181
206
  // Check if our hook is already configured
182
- const hookCommand = `node ${SIGNAL_CAPTURE_SCRIPT}`;
207
+ // Use forward slashes + quotes — Claude Code runs hooks via bash even on Windows
208
+ const scriptPathForHook = SIGNAL_CAPTURE_SCRIPT.replace(/\\/g, '/');
209
+ const hookCommand = `node "${scriptPathForHook}"`;
183
210
  const existing = settings.hooks?.UserPromptSubmit || [];
184
211
  const alreadyInstalled = existing.some(entry =>
185
- entry.hooks?.some(h => h.command === hookCommand)
212
+ entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
186
213
  );
187
214
 
188
- if (!alreadyInstalled) {
215
+ // Remove stale hooks with backslash paths (old Windows format)
216
+ if (settings.hooks?.UserPromptSubmit) {
217
+ for (const entry of settings.hooks.UserPromptSubmit) {
218
+ if (entry.hooks) {
219
+ entry.hooks = entry.hooks.filter(h =>
220
+ !(h.command && h.command.includes('signal-capture.js') && h.command.includes('\\'))
221
+ );
222
+ }
223
+ }
224
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
225
+ entry => entry.hooks && entry.hooks.length > 0
226
+ );
227
+ }
228
+
229
+ // Re-check after cleanup
230
+ const stillInstalled = (settings.hooks?.UserPromptSubmit || []).some(entry =>
231
+ entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
232
+ );
233
+
234
+ if (!stillInstalled) {
189
235
  if (!settings.hooks) settings.hooks = {};
190
236
  if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
191
237
 
@@ -197,11 +243,11 @@ function ensureHookInstalled() {
197
243
  });
198
244
 
199
245
  fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
200
- console.log("🪝 MetaMe: Signal capture hook installed.");
246
+ console.log(`${icon("hook")} MetaMe: Signal capture hook installed.`);
201
247
  }
202
248
  } catch (e) {
203
249
  // Non-fatal: hook install failure shouldn't block launch
204
- console.error("⚠️ Hook install skipped:", e.message);
250
+ console.error(`${icon("warn")} Hook install skipped:`, e.message);
205
251
  }
206
252
  }
207
253
 
@@ -264,7 +310,7 @@ function spawnDistillBackground() {
264
310
  // 4-hour cooldown: check last distill timestamp from profile
265
311
  const cooldownMs = 4 * 60 * 60 * 1000;
266
312
  try {
267
- const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
313
+ const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
268
314
  if (fs.existsSync(profilePath)) {
269
315
  const yaml = require('js-yaml');
270
316
  const profile = yaml.load(fs.readFileSync(profilePath, 'utf8'));
@@ -391,7 +437,7 @@ status:
391
437
  // ---------------------------------------------------------
392
438
  const PROTOCOL_NORMAL = `${METAME_START}
393
439
  ---
394
- ## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
440
+ ## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
395
441
 
396
442
  **1. THE BRAIN (Source of Truth):**
397
443
  * **FILE:** \`$HOME/.claude_profile.yaml\`
@@ -417,7 +463,7 @@ const PROTOCOL_NORMAL = `${METAME_START}
417
463
 
418
464
  const PROTOCOL_ONBOARDING = `${METAME_START}
419
465
  ---
420
- ## 🧠 SYSTEM KERNEL: SHADOW_MODE (Active)
466
+ ## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
421
467
 
422
468
  **1. THE BRAIN (Source of Truth):**
423
469
  * **FILE:** \`$HOME/.claude_profile.yaml\`
@@ -474,7 +520,7 @@ This step connects the bot to the user's PRIVATE chat — this is the admin chan
474
520
 
475
521
  - If **Feishu:**
476
522
  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.
477
- **⚠️ 重要:** 在「事件订阅」页面,必须开启「接收消息 im.message.receive_v1」事件。然后在该事件的配置中,勾选「获取群组中所有消息」(否则 bot 在群聊中只能收到 @它 的消息,无法接收普通群消息)。
523
+ **${icon("warn")} 重要:** 在「事件订阅」页面,必须开启「接收消息 im.message.receive_v1」事件。然后在该事件的配置中,勾选「获取群组中所有消息」(否则 bot 在群聊中只能收到 @它 的消息,无法接收普通群消息)。
478
524
  2. Ask user to paste App ID and App Secret.
479
525
  3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
480
526
  4. Tell user: "Now open Feishu and send any message to your new bot (private chat), then tell me you're done."
@@ -549,7 +595,7 @@ if (isKnownUser) {
549
595
  finalProtocol = PROTOCOL_NORMAL;
550
596
  } else {
551
597
  finalProtocol = PROTOCOL_ONBOARDING;
552
- console.log("🆕 New user detected — entering Genesis interview mode...");
598
+ console.log(`${icon("new")} New user detected — entering Genesis interview mode...`);
553
599
  }
554
600
 
555
601
  // ---------------------------------------------------------
@@ -659,11 +705,11 @@ try {
659
705
  if (triggerDrift || triggerComfort || trigger7th) {
660
706
  let hint = '';
661
707
  if (triggerDrift) {
662
- hint = `最近几个session的方向和"${driftDeclaredFocus}"有偏差。请在对话开始时温和地问:🪞 是方向有意调整了,还是不小心偏了?`;
708
+ hint = `最近几个session的方向和"${driftDeclaredFocus}"有偏差。请在对话开始时温和地问:${icon("mirror")} 是方向有意调整了,还是不小心偏了?`;
663
709
  } else if (triggerComfort) {
664
- hint = '连续几次都在熟悉领域。如果用户在session结束时自然停顿,可以温和地问:🪞 准备好探索拉伸区了吗?';
710
+ hint = `连续几次都在熟悉领域。如果用户在session结束时自然停顿,可以温和地问:${icon("mirror")} 准备好探索拉伸区了吗?`;
665
711
  } else {
666
- hint = '这是第' + distillCount + '次session。如果session自然结束,可以附加一句:🪞 一个词形容这次session的感受?';
712
+ hint = '这是第' + distillCount + `次session。如果session自然结束,可以附加一句:${icon("mirror")} 一个词形容这次session的感受?`;
667
713
  }
668
714
  const timing = triggerDrift ? '在对话开始时就问一次' : '只在session即将结束时说一次';
669
715
  reflectionLine = `\n[MetaMe reflection: ${hint} ${timing}。如果用户没回应就不要追问。]\n`;
@@ -728,16 +774,20 @@ const CAPABILITY_SECTIONS = [
728
774
  `"告诉X/让X" → \`~/.metame/bin/dispatch_to <project_key> "内容"\`,手机端 \`/dispatch to <key> <消息>\`。` + dispatchTable,
729
775
  '新增 Agent:`/agent bind <名称> <工作目录>`',
730
776
  '',
777
+ '## Agent 创建与管理',
778
+ '用户问创建/管理/绑定 Agent 时 → 先 `cat ~/.metame/docs/agent-guide.md` 再回答。',
779
+ '',
780
+ '## 手机端文件交互',
781
+ '用户要文件("发给我"/"发过来"/"导出")→ 先 `cat ~/.metame/docs/file-transfer.md` 再执行。',
782
+ '**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
783
+ '**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
784
+ '',
731
785
  '## 跨会话记忆',
732
786
  '用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
733
787
  '一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
734
788
  '',
735
789
  '## Skills',
736
790
  '能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
737
- '',
738
- '## 手机端文件交互',
739
- '**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
740
- '**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
741
791
 
742
792
  ].join('\n');
743
793
 
@@ -756,6 +806,14 @@ try {
756
806
  ), '');
757
807
  }
758
808
 
809
+ // New user: seed with default template if CLAUDE.md is empty or missing
810
+ if (!globalContent.trim()) {
811
+ const tplPath = path.join(__dirname, 'scripts', 'templates', 'default-global-claude.md');
812
+ if (fs.existsSync(tplPath)) {
813
+ globalContent = fs.readFileSync(tplPath, 'utf8');
814
+ }
815
+ }
816
+
759
817
  const injection =
760
818
  GLOBAL_MARKER_START + '\n' +
761
819
  KERNEL_BODY + '\n\n' +
@@ -767,13 +825,13 @@ try {
767
825
  fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
768
826
  } catch (e) {
769
827
  // Non-fatal: global CLAUDE.md injection is best-effort
770
- console.error(`⚠️ Failed to inject global CLAUDE.md: ${e.message}`);
828
+ console.error(`${icon("warn")} Failed to inject global CLAUDE.md: ${e.message}`);
771
829
  }
772
830
 
773
831
 
774
832
 
775
833
 
776
- console.log(`🔮 MetaMe v${pkgVersion}: Link Established.`);
834
+ console.log(`${icon("magic")} MetaMe v${pkgVersion}: Link Established.`);
777
835
 
778
836
  // Memory system status — show live stats without blocking launch
779
837
  try {
@@ -789,7 +847,7 @@ try {
789
847
  memMod.close();
790
848
  } catch { /* memory.js not available or DB not ready */ }
791
849
  if (factCount > 0 || tagCount > 0) {
792
- console.log(`🧠 Memory: ${factCount} facts · ${tagCount} sessions tagged`);
850
+ console.log(`${icon("brain")} Memory: ${factCount} facts · ${tagCount} sessions tagged`);
793
851
  }
794
852
  } catch { /* non-fatal */ }
795
853
 
@@ -801,12 +859,12 @@ try {
801
859
  : 0;
802
860
 
803
861
  if (pendingCount > 0) {
804
- console.log(`🧬 Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
862
+ console.log(`${icon("dna")} Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
805
863
  } else {
806
864
  // Show last distill time
807
865
  let lastDistillStr = '从未';
808
866
  try {
809
- const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
867
+ const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
810
868
  if (fs.existsSync(profilePath)) {
811
869
  const _yaml = require('js-yaml');
812
870
  const profile = _yaml.load(fs.readFileSync(profilePath, 'utf8'));
@@ -820,7 +878,7 @@ try {
820
878
  }
821
879
  }
822
880
  } catch { /* non-fatal */ }
823
- console.log(`🧬 Cognition: 无新信号 · 上次蒸馏 ${lastDistillStr}`);
881
+ console.log(`${icon("dna")} Cognition: 无新信号 · 上次蒸馏 ${lastDistillStr}`);
824
882
  }
825
883
  } catch { /* non-fatal */ }
826
884
 
@@ -846,7 +904,7 @@ const CURRENT_VERSION = pkgVersion;
846
904
  });
847
905
 
848
906
  if (latest && latest !== CURRENT_VERSION) {
849
- console.log(`📦 MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
907
+ console.log(`${icon("pkg")} MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
850
908
  const { execSync } = require('child_process');
851
909
  try {
852
910
  execSync('npm install -g metame-cli@latest', {
@@ -854,10 +912,10 @@ const CURRENT_VERSION = pkgVersion;
854
912
  timeout: 60000,
855
913
  ...(process.platform === 'win32' ? { shell: process.env.COMSPEC || true } : {}),
856
914
  });
857
- console.log(`✅ Updated to ${latest}. Restart metame to use the new version.`);
915
+ console.log(`${icon("ok")} Updated to ${latest}. Restart metame to use the new version.`);
858
916
  } catch (e) {
859
917
  const msg = e.stderr ? e.stderr.toString().trim().split('\n').pop() : '';
860
- console.log(`⚠️ Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
918
+ console.log(`${icon("warn")} Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
861
919
  }
862
920
  }
863
921
  } catch { /* network unavailable, skip silently */ }
@@ -887,14 +945,14 @@ const CURRENT_VERSION = pkgVersion;
887
945
  try { execSync(`${whichCmd} bun`, { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
888
946
 
889
947
  console.log('');
890
- console.log('┌─ 🔍 记忆搜索增强(可选,免费)');
948
+ console.log(`┌─ ${icon("search")} 记忆搜索增强(可选,免费)`);
891
949
  console.log('│');
892
950
  console.log('│ 当前模式:基础全文搜索(FTS5)');
893
951
  console.log('│ 安装 QMD 后:BM25 + 向量语义 + 重排序 混合搜索');
894
952
  console.log('│ 效果:召回质量约 5x,模糊描述也能精准命中历史记忆');
895
953
  if (!bunAvailable) {
896
954
  console.log('│');
897
- console.log('│ ⚠️ 未检测到 bun,无法自动安装。');
955
+ console.log(`│ ${icon("warn")} 未检测到 bun,无法自动安装。`);
898
956
  console.log('│ 手动安装:curl -fsSL https://bun.sh/install | bash');
899
957
  console.log('│ bun install -g github:tobi/qmd');
900
958
  console.log('└────────────────────────────────────────────────');
@@ -913,12 +971,12 @@ const CURRENT_VERSION = pkgVersion;
913
971
  process.stdout.write('\n');
914
972
 
915
973
  if (answer === 'y' || answer === 'yes') {
916
- console.log(' ⬇️ 正在安装 QMD...');
974
+ console.log(` ${icon("down")} 正在安装 QMD...`);
917
975
  try {
918
976
  execSync('bun install -g github:tobi/qmd', { stdio: 'inherit', timeout: 120000 });
919
- console.log(' QMD 已安装,下次记忆搜索自动启用向量模式。');
977
+ console.log(` ${icon("ok")} QMD 已安装,下次记忆搜索自动启用向量模式。`);
920
978
  } catch {
921
- console.log(' ⚠️ 安装失败,可手动执行:bun install -g github:tobi/qmd');
979
+ console.log(` ${icon("warn")} 安装失败,可手动执行:bun install -g github:tobi/qmd`);
922
980
  }
923
981
  } else {
924
982
  console.log(' 跳过。如需日后安装:bun install -g github:tobi/qmd');
@@ -937,7 +995,7 @@ const CURRENT_VERSION = pkgVersion;
937
995
  const isRefresh = process.argv.includes('refresh') || process.argv.includes('--refresh');
938
996
 
939
997
  if (isRefresh) {
940
- console.log(" MetaMe configuration re-injected.");
998
+ console.log(`${icon("ok")} MetaMe configuration re-injected.`);
941
999
  console.log(" Ask Claude to 'read CLAUDE.md' to apply the changes.");
942
1000
  process.exit(0);
943
1001
  }
@@ -953,7 +1011,7 @@ if (isEvolve) {
953
1011
  const insight = process.argv.slice(evolveIndex + 1).join(' ').trim();
954
1012
 
955
1013
  if (!insight) {
956
- console.error(" Error: Missing insight.");
1014
+ console.error(`${icon("fail")} Error: Missing insight.`);
957
1015
  console.error(" Usage: metame evolve \"I realized I prefer functional programming\"");
958
1016
  process.exit(1);
959
1017
  }
@@ -975,14 +1033,14 @@ if (isEvolve) {
975
1033
  // Save back to file
976
1034
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
977
1035
 
978
- console.log("🧠 MetaMe Brain Updated.");
1036
+ console.log(`${icon("brain")} MetaMe Brain Updated.`);
979
1037
  console.log(` Added insight: "${insight}"`);
980
1038
  console.log(" (Run 'metame refresh' to apply this to the current session)");
981
1039
  } else {
982
- console.error(" Error: No profile found. Run 'metame' first to initialize.");
1040
+ console.error(`${icon("fail")} Error: No profile found. Run 'metame' first to initialize.`);
983
1041
  }
984
1042
  } catch (e) {
985
- console.error(" Error updating profile:", e.message);
1043
+ console.error(`${icon("fail")} Error updating profile:`, e.message);
986
1044
  }
987
1045
  process.exit(0);
988
1046
  }
@@ -1002,7 +1060,7 @@ if (isSetTrait) {
1002
1060
  const value = process.argv.slice(setIndex + 2).join(' ').trim();
1003
1061
 
1004
1062
  if (!key || !value) {
1005
- console.error(" Error: Missing key or value.");
1063
+ console.error(`${icon("fail")} Error: Missing key or value.`);
1006
1064
  console.error(" Usage: metame set-trait identity.role \"New Role\"");
1007
1065
  process.exit(1);
1008
1066
  }
@@ -1028,14 +1086,14 @@ if (isSetTrait) {
1028
1086
 
1029
1087
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
1030
1088
 
1031
- console.log(`🧠 MetaMe Brain Surgically Updated.`);
1089
+ console.log(`${icon("brain")} MetaMe Brain Surgically Updated.`);
1032
1090
  console.log(` Set \`${key}\` = "${value}"`);
1033
1091
  console.log(" (Run 'metame refresh' to apply this to the current session)");
1034
1092
  } else {
1035
- console.error(" Error: No profile found.");
1093
+ console.error(`${icon("fail")} Error: No profile found.`);
1036
1094
  }
1037
1095
  } catch (e) {
1038
- console.error(" Error updating profile:", e.message);
1096
+ console.error(`${icon("fail")} Error updating profile:`, e.message);
1039
1097
  }
1040
1098
  process.exit(0);
1041
1099
  }
@@ -1052,9 +1110,9 @@ if (isQuiet) {
1052
1110
  if (!doc.growth) doc.growth = {};
1053
1111
  doc.growth.quiet_until = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();
1054
1112
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
1055
- console.log("🤫 MetaMe: Mirror & reflections silenced for 48 hours.");
1113
+ console.log(`${icon("brain")} MetaMe: Mirror & reflections silenced for 48 hours.`);
1056
1114
  } catch (e) {
1057
- console.error(" Error:", e.message);
1115
+ console.error(`${icon("fail")} Error:`, e.message);
1058
1116
  }
1059
1117
  process.exit(0);
1060
1118
  }
@@ -1068,26 +1126,26 @@ if (isInsights) {
1068
1126
  const zoneHistory = (doc.growth && doc.growth.zone_history) || [];
1069
1127
 
1070
1128
  if (patterns.length === 0) {
1071
- console.log("🔍 MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.");
1129
+ console.log(`${icon("search")} MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.`);
1072
1130
  } else {
1073
- console.log("🪞 MetaMe Insights:\n");
1131
+ console.log(`${icon("mirror")} MetaMe Insights:\n`);
1074
1132
  patterns.forEach((p, i) => {
1075
- const icon = p.type === 'avoidance' ? '⚠️' : p.type === 'growth' ? '🌱' : p.type === 'energy' ? '' : '🔄';
1076
- console.log(` ${icon} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
1133
+ const sym = p.type === 'avoidance' ? icon("warn") : p.type === 'growth' ? '+' : p.type === 'energy' ? '*' : icon("reload");
1134
+ console.log(` ${sym} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
1077
1135
  console.log(` Detected: ${p.detected}${p.surfaced ? `, Last shown: ${p.surfaced}` : ''}`);
1078
1136
  });
1079
1137
  if (zoneHistory.length > 0) {
1080
- console.log(`\n 📊 Recent zone history: ${zoneHistory.join(' → ')}`);
1138
+ console.log(`\n ${icon("chart")} Recent zone history: ${zoneHistory.join(' → ')}`);
1081
1139
  console.log(` (C=Comfort, S=Stretch, P=Panic)`);
1082
1140
  }
1083
1141
  const answered = (doc.growth && doc.growth.reflections_answered) || 0;
1084
1142
  const skipped = (doc.growth && doc.growth.reflections_skipped) || 0;
1085
1143
  if (answered + skipped > 0) {
1086
- console.log(`\n 💭 Reflections: ${answered} answered, ${skipped} skipped`);
1144
+ console.log(`\n ${icon("thought")} Reflections: ${answered} answered, ${skipped} skipped`);
1087
1145
  }
1088
1146
  }
1089
1147
  } catch (e) {
1090
- console.error(" Error:", e.message);
1148
+ console.error(`${icon("fail")} Error:`, e.message);
1091
1149
  }
1092
1150
  process.exit(0);
1093
1151
  }
@@ -1098,7 +1156,7 @@ if (isMirror) {
1098
1156
  const mirrorIndex = process.argv.indexOf('mirror');
1099
1157
  const toggle = process.argv[mirrorIndex + 1];
1100
1158
  if (toggle !== 'on' && toggle !== 'off') {
1101
- console.error(" Usage: metame mirror on|off");
1159
+ console.error(`${icon("fail")} Usage: metame mirror on|off`);
1102
1160
  process.exit(1);
1103
1161
  }
1104
1162
  try {
@@ -1106,9 +1164,9 @@ if (isMirror) {
1106
1164
  if (!doc.growth) doc.growth = {};
1107
1165
  doc.growth.mirror_enabled = (toggle === 'on');
1108
1166
  fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
1109
- console.log(`🪞 MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
1167
+ console.log(`${icon("mirror")} MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
1110
1168
  } catch (e) {
1111
- console.error(" Error:", e.message);
1169
+ console.error(`${icon("fail")} Error:`, e.message);
1112
1170
  }
1113
1171
  process.exit(0);
1114
1172
  }
@@ -1124,7 +1182,7 @@ if (isProvider) {
1124
1182
 
1125
1183
  if (!subCmd || subCmd === 'list') {
1126
1184
  const active = providers.getActiveProvider();
1127
- console.log(`🔌 MetaMe Providers (active: ${active ? active.name : 'anthropic'})`);
1185
+ console.log(`${icon("plug")} MetaMe Providers (active: ${active ? active.name : 'anthropic'})`);
1128
1186
  console.log(providers.listFormatted());
1129
1187
  process.exit(0);
1130
1188
  }
@@ -1132,18 +1190,18 @@ if (isProvider) {
1132
1190
  if (subCmd === 'use') {
1133
1191
  const name = process.argv[providerIndex + 2];
1134
1192
  if (!name) {
1135
- console.error(" Usage: metame provider use <name>");
1193
+ console.error(`${icon("fail")} Usage: metame provider use <name>`);
1136
1194
  process.exit(1);
1137
1195
  }
1138
1196
  try {
1139
1197
  providers.setActive(name);
1140
1198
  const p = providers.getActiveProvider();
1141
- console.log(`✅ Provider switched → ${name} (${p.label || name})`);
1199
+ console.log(`${icon("ok")} Provider switched → ${name} (${p.label || name})`);
1142
1200
  if (name !== 'anthropic') {
1143
1201
  console.log(` Base URL: ${p.base_url || 'not set'}`);
1144
1202
  }
1145
1203
  } catch (e) {
1146
- console.error(`❌ ${e.message}`);
1204
+ console.error(`${icon("fail")} ${e.message}`);
1147
1205
  process.exit(1);
1148
1206
  }
1149
1207
  process.exit(0);
@@ -1152,7 +1210,7 @@ if (isProvider) {
1152
1210
  if (subCmd === 'add') {
1153
1211
  const name = process.argv[providerIndex + 2];
1154
1212
  if (!name) {
1155
- console.error(" Usage: metame provider add <name>");
1213
+ console.error(`${icon("fail")} Usage: metame provider add <name>`);
1156
1214
  process.exit(1);
1157
1215
  }
1158
1216
  const readline = require('readline');
@@ -1160,7 +1218,7 @@ if (isProvider) {
1160
1218
  const ask = (q) => new Promise(r => rl.question(q, r));
1161
1219
 
1162
1220
  (async () => {
1163
- console.log(`\n🔌 Add Provider: ${name}\n`);
1221
+ console.log(`\n${icon("plug")} Add Provider: ${name}\n`);
1164
1222
  console.log("The relay must accept Anthropic Messages API format.");
1165
1223
  console.log("(Most quality relays like OpenRouter, OneAPI, etc. support this.)\n");
1166
1224
 
@@ -1169,7 +1227,7 @@ if (isProvider) {
1169
1227
  const api_key = (await ask("API Key: ")).trim();
1170
1228
 
1171
1229
  if (!base_url) {
1172
- console.error(" Base URL is required.");
1230
+ console.error(`${icon("fail")} Base URL is required.`);
1173
1231
  rl.close();
1174
1232
  process.exit(1);
1175
1233
  }
@@ -1180,10 +1238,10 @@ if (isProvider) {
1180
1238
 
1181
1239
  try {
1182
1240
  providers.addProvider(name, config);
1183
- console.log(`\n Provider "${name}" added.`);
1241
+ console.log(`\n${icon("ok")} Provider "${name}" added.`);
1184
1242
  console.log(` Switch to it: metame provider use ${name}`);
1185
1243
  } catch (e) {
1186
- console.error(`❌ ${e.message}`);
1244
+ console.error(`${icon("fail")} ${e.message}`);
1187
1245
  }
1188
1246
  rl.close();
1189
1247
  process.exit(0);
@@ -1194,14 +1252,14 @@ if (isProvider) {
1194
1252
  if (subCmd === 'remove') {
1195
1253
  const name = process.argv[providerIndex + 2];
1196
1254
  if (!name) {
1197
- console.error(" Usage: metame provider remove <name>");
1255
+ console.error(`${icon("fail")} Usage: metame provider remove <name>`);
1198
1256
  process.exit(1);
1199
1257
  }
1200
1258
  try {
1201
1259
  providers.removeProvider(name);
1202
- console.log(`✅ Provider "${name}" removed.`);
1260
+ console.log(`${icon("ok")} Provider "${name}" removed.`);
1203
1261
  } catch (e) {
1204
- console.error(`❌ ${e.message}`);
1262
+ console.error(`${icon("fail")} ${e.message}`);
1205
1263
  }
1206
1264
  process.exit(0);
1207
1265
  }
@@ -1210,15 +1268,15 @@ if (isProvider) {
1210
1268
  const role = process.argv[providerIndex + 2]; // distill | daemon
1211
1269
  const name = process.argv[providerIndex + 3]; // provider name or empty to clear
1212
1270
  if (!role) {
1213
- console.error(" Usage: metame provider set-role <distill|daemon> [provider-name]");
1271
+ console.error(`${icon("fail")} Usage: metame provider set-role <distill|daemon> [provider-name]`);
1214
1272
  console.error(" Omit provider name to reset to active provider.");
1215
1273
  process.exit(1);
1216
1274
  }
1217
1275
  try {
1218
1276
  providers.setRole(role, name || null);
1219
- console.log(`✅ ${role} provider ${name ? `set to "${name}"` : 'reset to active'}.`);
1277
+ console.log(`${icon("ok")} ${role} provider ${name ? `set to "${name}"` : 'reset to active'}.`);
1220
1278
  } catch (e) {
1221
- console.error(`❌ ${e.message}`);
1279
+ console.error(`${icon("fail")} ${e.message}`);
1222
1280
  }
1223
1281
  process.exit(0);
1224
1282
  }
@@ -1229,11 +1287,11 @@ if (isProvider) {
1229
1287
  const name = targetName || prov.active;
1230
1288
  const p = prov.providers[name];
1231
1289
  if (!p) {
1232
- console.error(`❌ Provider "${name}" not found.`);
1290
+ console.error(`${icon("fail")} Provider "${name}" not found.`);
1233
1291
  process.exit(1);
1234
1292
  }
1235
1293
 
1236
- console.log(`🔍 Testing provider: ${name} (${p.label || name})`);
1294
+ console.log(`${icon("search")} Testing provider: ${name} (${p.label || name})`);
1237
1295
  if (name === 'anthropic') {
1238
1296
  console.log(" Using official Anthropic endpoint — testing via claude CLI...");
1239
1297
  } else {
@@ -1257,18 +1315,18 @@ if (isProvider) {
1257
1315
  const elapsed = Date.now() - start;
1258
1316
 
1259
1317
  if (result.includes('PROVIDER_OK')) {
1260
- console.log(` Connected (${elapsed}ms)`);
1318
+ console.log(` ${icon("ok")} Connected (${elapsed}ms)`);
1261
1319
  } else {
1262
- console.log(` ⚠️ Response received (${elapsed}ms) but unexpected: ${result.slice(0, 80)}`);
1320
+ console.log(` ${icon("warn")} Response received (${elapsed}ms) but unexpected: ${result.slice(0, 80)}`);
1263
1321
  }
1264
1322
  } catch (e) {
1265
- console.error(` Failed: ${e.message.split('\n')[0]}`);
1323
+ console.error(` ${icon("fail")} Failed: ${e.message.split('\n')[0]}`);
1266
1324
  }
1267
1325
  process.exit(0);
1268
1326
  }
1269
1327
 
1270
1328
  // Unknown subcommand — show help
1271
- console.log("🔌 MetaMe Provider Commands:");
1329
+ console.log(`${icon("plug")} MetaMe Provider Commands:`);
1272
1330
  console.log(" metame provider — list providers");
1273
1331
  console.log(" metame provider use <name> — switch active provider");
1274
1332
  console.log(" metame provider add <name> — add a new provider");
@@ -1312,20 +1370,20 @@ if (isDaemon) {
1312
1370
  if (fs.existsSync(templateSrc)) {
1313
1371
  fs.copyFileSync(templateSrc, DAEMON_CONFIG);
1314
1372
  } else {
1315
- console.error(" Template not found. Reinstall MetaMe.");
1373
+ console.error(`${icon("fail")} Template not found. Reinstall MetaMe.`);
1316
1374
  process.exit(1);
1317
1375
  }
1318
1376
  try { fs.chmodSync(METAME_DIR, 0o700); } catch { /* ignore on Windows */ }
1319
- console.log(" Config created: ~/.metame/daemon.yaml\n");
1377
+ console.log(`${icon("ok")} Config created: ~/.metame/daemon.yaml\n`);
1320
1378
  } else {
1321
- console.log(" Config exists: ~/.metame/daemon.yaml\n");
1379
+ console.log(`${icon("ok")} Config exists: ~/.metame/daemon.yaml\n`);
1322
1380
  }
1323
1381
 
1324
1382
  const yaml = require(path.join(__dirname, 'node_modules', 'js-yaml'));
1325
1383
  let cfg = yaml.load(fs.readFileSync(DAEMON_CONFIG, 'utf8')) || {};
1326
1384
 
1327
1385
  // --- Telegram Setup ---
1328
- console.log("━━━ 📱 Telegram Setup ━━━");
1386
+ console.log(`━━━ ${icon("phone")} Telegram Setup ━━━`);
1329
1387
  console.log("");
1330
1388
  console.log("Step 1: Create a Bot");
1331
1389
  console.log(" • Open Telegram app on your phone or desktop");
@@ -1370,21 +1428,21 @@ if (isDaemon) {
1370
1428
 
1371
1429
  if (chatIds.length > 0) {
1372
1430
  cfg.telegram.allowed_chat_ids = chatIds;
1373
- console.log(` Found chat ID(s): ${chatIds.join(', ')}`);
1431
+ console.log(` ${icon("ok")} Found chat ID(s): ${chatIds.join(', ')}`);
1374
1432
  } else {
1375
- console.log(" ⚠️ No messages found. Make sure you messaged the bot.");
1433
+ console.log(` ${icon("warn")} No messages found. Make sure you messaged the bot.`);
1376
1434
  console.log(" You can set allowed_chat_ids manually in daemon.yaml later.");
1377
1435
  }
1378
1436
  } catch {
1379
- console.log(" ⚠️ Could not fetch chat ID. Set it manually in daemon.yaml.");
1437
+ console.log(` ${icon("warn")} Could not fetch chat ID. Set it manually in daemon.yaml.`);
1380
1438
  }
1381
- console.log(" Telegram configured!\n");
1439
+ console.log(` ${icon("ok")} Telegram configured!\n`);
1382
1440
  } else {
1383
1441
  console.log(" Skipped.\n");
1384
1442
  }
1385
1443
 
1386
1444
  // --- Feishu Setup ---
1387
- console.log("━━━ 📘 Feishu (Lark) Setup ━━━");
1445
+ console.log(`━━━ ${icon("feishu")} Feishu (Lark) Setup ━━━`);
1388
1446
  console.log("");
1389
1447
  console.log("Step 1: Create an App");
1390
1448
  console.log(" • Go to: https://open.feishu.cn/app");
@@ -1428,7 +1486,7 @@ if (isDaemon) {
1428
1486
  cfg.feishu.app_id = feishuAppId;
1429
1487
  cfg.feishu.app_secret = feishuSecret;
1430
1488
  if (!cfg.feishu.allowed_chat_ids) cfg.feishu.allowed_chat_ids = [];
1431
- console.log(" Feishu configured!");
1489
+ console.log(` ${icon("ok")} Feishu configured!`);
1432
1490
  console.log(" Note: allowed_chat_ids is empty = deny all users.");
1433
1491
  console.log(" Add chat IDs to daemon.yaml or use /agent bind from target chat.\n");
1434
1492
  }
@@ -1438,7 +1496,7 @@ if (isDaemon) {
1438
1496
 
1439
1497
  // Write config
1440
1498
  fs.writeFileSync(DAEMON_CONFIG, yaml.dump(cfg, { lineWidth: -1 }), 'utf8');
1441
- console.log("━━━ Setup Complete ━━━");
1499
+ console.log(`━━━ ${icon("ok")} Setup Complete ━━━`);
1442
1500
  console.log(`Config saved: ${DAEMON_CONFIG}`);
1443
1501
  console.log("\nNext steps:");
1444
1502
  console.log(" metame start — start the daemon");
@@ -1455,7 +1513,7 @@ if (isDaemon) {
1455
1513
 
1456
1514
  if (subCmd === 'install-launchd') {
1457
1515
  if (process.platform !== 'darwin') {
1458
- console.error(" launchd is macOS-only.");
1516
+ console.error(`${icon("fail")} launchd is macOS-only.`);
1459
1517
  process.exit(1);
1460
1518
  }
1461
1519
  const plistDir = path.join(HOME_DIR, 'Library', 'LaunchAgents');
@@ -1497,7 +1555,7 @@ if (isDaemon) {
1497
1555
  </dict>
1498
1556
  </plist>`;
1499
1557
  fs.writeFileSync(plistPath, plistContent, 'utf8');
1500
- console.log(`✅ launchd plist installed: ${plistPath}`);
1558
+ console.log(`${icon("ok")} launchd plist installed: ${plistPath}`);
1501
1559
  console.log(" Load now: launchctl load " + plistPath);
1502
1560
  console.log(" Unload: launchctl unload " + plistPath);
1503
1561
  process.exit(0);
@@ -1505,7 +1563,7 @@ if (isDaemon) {
1505
1563
 
1506
1564
  if (subCmd === 'install-systemd') {
1507
1565
  if (process.platform === 'darwin') {
1508
- console.error(" Use 'metame daemon install-launchd' on macOS.");
1566
+ console.error(`${icon("fail")} Use 'metame daemon install-launchd' on macOS.`);
1509
1567
  process.exit(1);
1510
1568
  }
1511
1569
 
@@ -1513,7 +1571,7 @@ if (isDaemon) {
1513
1571
  try {
1514
1572
  require('child_process').execSync('systemctl --user --no-pager status 2>/dev/null || true');
1515
1573
  } catch {
1516
- console.error(" systemd not available.");
1574
+ console.error(`${icon("fail")} systemd not available.`);
1517
1575
  console.error(" WSL users: add [boot]\\nsystemd=true to /etc/wsl.conf, then restart WSL.");
1518
1576
  process.exit(1);
1519
1577
  }
@@ -1553,7 +1611,7 @@ WantedBy=default.target
1553
1611
  // Enable lingering so service runs even when user is not logged in
1554
1612
  try { es(`loginctl enable-linger ${process.env.USER || ''}`); } catch { /* may need root */ }
1555
1613
 
1556
- console.log(`✅ systemd service installed: ${servicePath}`);
1614
+ console.log(`${icon("ok")} systemd service installed: ${servicePath}`);
1557
1615
  console.log(" Status: systemctl --user status metame-daemon");
1558
1616
  console.log(" Logs: journalctl --user -u metame-daemon -f");
1559
1617
  console.log(" Disable: systemctl --user disable metame-daemon");
@@ -1562,7 +1620,7 @@ WantedBy=default.target
1562
1620
  const isWSL = fs.existsSync('/proc/version') &&
1563
1621
  fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
1564
1622
  if (isWSL) {
1565
- console.log("\n 📌 WSL auto-boot tip:");
1623
+ console.log(`\n ${icon("pin")} WSL auto-boot tip:`);
1566
1624
  console.log(" Add this to Windows Task Scheduler (run at login):");
1567
1625
  console.log(` wsl -d ${process.env.WSL_DISTRO_NAME || 'Ubuntu'} -- sh -c 'nohup sleep infinity &'`);
1568
1626
  console.log(" This keeps WSL alive so the daemon stays running.");
@@ -1570,17 +1628,44 @@ WantedBy=default.target
1570
1628
  process.exit(0);
1571
1629
  }
1572
1630
 
1631
+ if (subCmd === 'install-task-scheduler') {
1632
+ if (process.platform !== 'win32') {
1633
+ console.error("Task Scheduler is Windows-only. Use install-launchd (macOS) or install-systemd (Linux).");
1634
+ process.exit(1);
1635
+ }
1636
+ const nodePath = process.execPath;
1637
+ const taskName = 'MetaMe-Daemon';
1638
+ const scriptPath = DAEMON_SCRIPT.replace(/\//g, '\\');
1639
+ const nodePathWin = nodePath.replace(/\//g, '\\');
1640
+ try {
1641
+ try {
1642
+ execSync(`schtasks /delete /tn "${taskName}" /f`, { stdio: 'ignore' });
1643
+ } catch { /* task may not exist yet */ }
1644
+ execSync(
1645
+ `schtasks /create /tn "${taskName}" /tr "\\"${nodePathWin}\\" \\"${scriptPath}\\"" /sc onlogon /rl limited /f`,
1646
+ { stdio: 'inherit' }
1647
+ );
1648
+ console.log(`Task Scheduler task "${taskName}" installed.`);
1649
+ console.log(` The daemon will auto-start at login.`);
1650
+ console.log(` Remove: schtasks /delete /tn "${taskName}" /f`);
1651
+ console.log(` Query: schtasks /query /tn "${taskName}"`);
1652
+ } catch (e) {
1653
+ console.error(`Failed to create scheduled task: ${e.message}`);
1654
+ console.error(" Try running as Administrator, or create it manually in Task Scheduler.");
1655
+ process.exit(1);
1656
+ }
1657
+ process.exit(0);
1658
+ }
1659
+
1573
1660
  if (subCmd === 'start') {
1574
1661
  // Kill any lingering daemon.js processes to avoid Feishu WebSocket conflicts
1575
1662
  try {
1576
- const { execSync: es } = require('child_process');
1577
- const pids = es("pgrep -f 'node.*daemon\\.js' 2>/dev/null || true", { encoding: 'utf8' }).trim();
1578
- if (pids) {
1579
- for (const p of pids.split('\n').filter(Boolean)) {
1580
- const n = parseInt(p, 10);
1581
- if (n && n !== process.pid) try { process.kill(n, 'SIGKILL'); } catch { /* */ }
1663
+ const pids = findProcessesByPattern('node.*daemon\\.js');
1664
+ if (pids.length) {
1665
+ for (const n of pids) {
1666
+ try { process.kill(n, 'SIGKILL'); } catch { /* */ }
1582
1667
  }
1583
- es('sleep 1');
1668
+ sleepSync(1000);
1584
1669
  }
1585
1670
  } catch { /* ignore */ }
1586
1671
  // Check if already running
@@ -1588,25 +1673,26 @@ WantedBy=default.target
1588
1673
  try { fs.unlinkSync(DAEMON_PID); } catch { /* */ }
1589
1674
  }
1590
1675
  if (!fs.existsSync(DAEMON_CONFIG)) {
1591
- console.error(" No config found. Run: metame daemon init");
1676
+ console.error(`${icon("fail")} No config found. Run: metame daemon init`);
1592
1677
  process.exit(1);
1593
1678
  }
1594
1679
  if (!fs.existsSync(DAEMON_SCRIPT)) {
1595
- console.error(" daemon.js not found. Reinstall MetaMe.");
1680
+ console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
1596
1681
  process.exit(1);
1597
1682
  }
1598
- // Use caffeinate on macOS/Linux to prevent sleep while daemon is running
1599
- const isNotWindows = process.platform !== 'win32';
1600
- const cmd = isNotWindows ? 'caffeinate' : process.execPath;
1601
- const args = isNotWindows ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
1683
+ // Use caffeinate on macOS to prevent sleep while daemon is running
1684
+ const isMac = process.platform === 'darwin';
1685
+ const isWin = process.platform === 'win32';
1686
+ const cmd = isMac ? 'caffeinate' : process.execPath;
1687
+ const args = isMac ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
1602
1688
  const bg = spawn(cmd, args, {
1603
- detached: true,
1689
+ detached: !isWin,
1604
1690
  stdio: 'ignore',
1605
1691
  windowsHide: true,
1606
1692
  env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
1607
1693
  });
1608
1694
  bg.unref();
1609
- console.log(`✅ MetaMe daemon started (PID: ${bg.pid})`);
1695
+ console.log(`${icon("ok")} MetaMe daemon started (PID: ${bg.pid})`);
1610
1696
  console.log(" Logs: metame logs");
1611
1697
  console.log(" Stop: metame stop");
1612
1698
  process.exit(0);
@@ -1614,7 +1700,7 @@ WantedBy=default.target
1614
1700
 
1615
1701
  if (subCmd === 'stop') {
1616
1702
  if (!fs.existsSync(DAEMON_PID)) {
1617
- console.log("ℹ️ No daemon running (no PID file).");
1703
+ console.log(`${icon("info")} No daemon running (no PID file).`);
1618
1704
  process.exit(0);
1619
1705
  }
1620
1706
  const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
@@ -1623,16 +1709,15 @@ WantedBy=default.target
1623
1709
  // Wait for process to die (up to 3s), then force kill
1624
1710
  let dead = false;
1625
1711
  for (let i = 0; i < 6; i++) {
1626
- const { execSync: es } = require('child_process');
1627
- es('sleep 0.5');
1712
+ sleepSync(500);
1628
1713
  try { process.kill(pid, 0); } catch { dead = true; break; }
1629
1714
  }
1630
1715
  if (!dead) {
1631
1716
  try { process.kill(pid, 'SIGKILL'); } catch { /* already gone */ }
1632
1717
  }
1633
- console.log(`✅ Daemon stopped (PID: ${pid})`);
1718
+ console.log(`${icon("ok")} Daemon stopped (PID: ${pid})`);
1634
1719
  } catch (e) {
1635
- console.log(`⚠️ Process ${pid} not found (may have already exited).`);
1720
+ console.log(`${icon("warn")} Process ${pid} not found (may have already exited).`);
1636
1721
  }
1637
1722
  try { fs.unlinkSync(DAEMON_PID); } catch { /* ignore */ }
1638
1723
  process.exit(0);
@@ -1649,7 +1734,7 @@ WantedBy=default.target
1649
1734
  try { process.kill(pid, 0); isRunning = true; } catch { /* dead */ }
1650
1735
  }
1651
1736
 
1652
- console.log(`🤖 MetaMe Daemon: ${isRunning ? '🟢 Running' : '🔴 Stopped'}`);
1737
+ console.log(`${icon("bot")} MetaMe Daemon: ${isRunning ? icon("green") + ' Running' : icon("red") + ' Stopped'}`);
1653
1738
  if (state.started_at) console.log(` Started: ${state.started_at}`);
1654
1739
  if (state.pid) console.log(` PID: ${state.pid}`);
1655
1740
 
@@ -1677,8 +1762,8 @@ WantedBy=default.target
1677
1762
  if (taskEntries.length > 0) {
1678
1763
  console.log(" Recent tasks:");
1679
1764
  for (const [name, info] of taskEntries) {
1680
- const icon = info.status === 'success' ? '✅' : '❌';
1681
- console.log(` ${icon} ${name}: ${info.last_run || 'unknown'}`);
1765
+ const sym = info.status === 'success' ? icon("ok") : icon("fail");
1766
+ console.log(` ${sym} ${name}: ${info.last_run || 'unknown'}`);
1682
1767
  if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
1683
1768
  }
1684
1769
  const hiddenStale = Object.keys(tasks).length - taskEntries.length;
@@ -1691,7 +1776,7 @@ WantedBy=default.target
1691
1776
 
1692
1777
  if (subCmd === 'logs') {
1693
1778
  if (!fs.existsSync(DAEMON_LOG)) {
1694
- console.log("ℹ️ No log file yet. Start the daemon first.");
1779
+ console.log(`${icon("info")} No log file yet. Start the daemon first.`);
1695
1780
  process.exit(0);
1696
1781
  }
1697
1782
  const content = fs.readFileSync(DAEMON_LOG, 'utf8');
@@ -1704,11 +1789,11 @@ WantedBy=default.target
1704
1789
  if (subCmd === 'run') {
1705
1790
  const taskName = process.argv[daemonIndex + 2];
1706
1791
  if (!taskName) {
1707
- console.error(" Usage: metame daemon run <task-name>");
1792
+ console.error(`${icon("fail")} Usage: metame daemon run <task-name>`);
1708
1793
  process.exit(1);
1709
1794
  }
1710
1795
  if (!fs.existsSync(DAEMON_SCRIPT)) {
1711
- console.error(" daemon.js not found. Reinstall MetaMe.");
1796
+ console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
1712
1797
  process.exit(1);
1713
1798
  }
1714
1799
  // Run in foreground using daemon.js --run
@@ -1721,7 +1806,7 @@ WantedBy=default.target
1721
1806
  }
1722
1807
 
1723
1808
  // Unknown subcommand
1724
- console.log("📖 MetaMe Daemon Commands:");
1809
+ console.log(`${icon("book")} MetaMe Daemon Commands:`);
1725
1810
  console.log(" metame start — start background daemon");
1726
1811
  console.log(" metame stop — stop daemon");
1727
1812
  console.log(" metame status — show status & budget");
@@ -1729,9 +1814,11 @@ WantedBy=default.target
1729
1814
  console.log(" metame daemon init — initialize config");
1730
1815
  console.log(" metame daemon run <name> — run a task once");
1731
1816
  if (process.platform === 'darwin') {
1732
- console.log(" metame daemon install-launchd — auto-start on macOS");
1817
+ console.log(" metame daemon install-launchd — auto-start on macOS");
1818
+ } else if (process.platform === 'win32') {
1819
+ console.log(" metame daemon install-task-scheduler — auto-start on Windows");
1733
1820
  } else {
1734
- console.log(" metame daemon install-systemd — auto-start on Linux/WSL");
1821
+ console.log(" metame daemon install-systemd — auto-start on Linux/WSL");
1735
1822
  }
1736
1823
  process.exit(0);
1737
1824
  }
@@ -1780,7 +1867,7 @@ if (isSync) {
1780
1867
  process.exit(1);
1781
1868
  }
1782
1869
 
1783
- console.log(`\n🔄 Resuming session ${bestSession.id.slice(0, 8)}...\n`);
1870
+ console.log(`\n${icon("reload")} Resuming session ${bestSession.id.slice(0, 8)}...\n`);
1784
1871
  const providerEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
1785
1872
  const resumeArgs = ['--resume', bestSession.id];
1786
1873
  if (daemonCfg.dangerously_skip_permissions) resumeArgs.push('--dangerously-skip-permissions');
@@ -1801,7 +1888,7 @@ if (isSync) {
1801
1888
  // We rely on our own scoped variable to detect nesting,
1802
1889
  // ignoring the leaky CLAUDE_CODE_SSE_PORT from IDEs.
1803
1890
  if (process.env.METAME_ACTIVE_SESSION === 'true') {
1804
- console.error("\n🚫 ACTION BLOCKED: Nested Session Detected");
1891
+ console.error(`\n${icon("stop")} ACTION BLOCKED: Nested Session Detected`);
1805
1892
  console.error(" You are actively running inside a MetaMe session.");
1806
1893
  console.error(" To reload configuration, use: \x1b[36m!metame refresh\x1b[0m\n");
1807
1894
  process.exit(1);
@@ -1814,7 +1901,7 @@ if (process.env.METAME_ACTIVE_SESSION === 'true') {
1814
1901
  const activeProviderEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
1815
1902
  const activeProviderName = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).getActiveName(); } catch { return 'anthropic'; } })();
1816
1903
  if (activeProviderName !== 'anthropic') {
1817
- console.log(`🔌 Provider: ${activeProviderName}`);
1904
+ console.log(`${icon("plug")} Provider: ${activeProviderName}`);
1818
1905
  }
1819
1906
 
1820
1907
  // Build launch args — inject system prompt for new users
@@ -1873,17 +1960,18 @@ try {
1873
1960
  } catch { /* PID file stale, daemon not running */ }
1874
1961
  }
1875
1962
  if (!daemonRunning) {
1876
- const isNotWindows = process.platform !== 'win32';
1877
- const dCmd = isNotWindows ? 'caffeinate' : process.execPath;
1878
- const dArgs = isNotWindows ? ['-i', process.execPath, _daemonScript] : [_daemonScript];
1963
+ const _isMac = process.platform === 'darwin';
1964
+ const _isWin = process.platform === 'win32';
1965
+ const dCmd = _isMac ? 'caffeinate' : process.execPath;
1966
+ const dArgs = _isMac ? ['-i', process.execPath, _daemonScript] : [_daemonScript];
1879
1967
  const bg = spawn(dCmd, dArgs, {
1880
- detached: true,
1968
+ detached: !_isWin,
1881
1969
  stdio: 'ignore',
1882
1970
  windowsHide: true,
1883
1971
  env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
1884
1972
  });
1885
1973
  bg.unref();
1886
- console.log(`🤖 Daemon auto-started (PID: ${bg.pid})`);
1974
+ console.log(`${icon("bot")} Daemon auto-started (PID: ${bg.pid})`);
1887
1975
  }
1888
1976
  }
1889
1977
  } catch { /* non-fatal */ }
@@ -1895,7 +1983,7 @@ const child = spawnClaude(launchArgs, {
1895
1983
  });
1896
1984
 
1897
1985
  child.on('error', () => {
1898
- console.error("\n❌ Error: Could not launch 'claude'.");
1986
+ console.error(`\n${icon("fail")} Error: Could not launch 'claude'.`);
1899
1987
  console.error(" Please make sure Claude Code is installed globally:");
1900
1988
  console.error(" npm install -g @anthropic-ai/claude-code");
1901
1989
  });