metame-cli 1.4.31 ā 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/README.md +41 -18
- package/index.js +180 -128
- package/package.json +3 -2
- package/scripts/daemon-claude-engine.js +5 -2
- package/scripts/daemon-runtime-lifecycle.js +4 -2
- package/scripts/daemon-task-scheduler.js +28 -3
- package/scripts/daemon.js +40 -3
- package/scripts/feishu-adapter.js +6 -2
- package/scripts/memory-extract.js +2 -2
- package/scripts/memory-gc.js +2 -2
- package/scripts/memory-write.js +0 -1
- package/scripts/memory.js +1 -1
- package/scripts/platform.js +172 -0
- package/scripts/reliability-core.test.js +15 -3
- package/scripts/schema.js +43 -10
- package/scripts/skill-evolution.test.js +7 -1
- package/scripts/sync-readme.js +64 -0
- package/scripts/utils.test.js +12 -5
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.
|
|
@@ -52,7 +53,7 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
52
53
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
53
54
|
// IMPORTANT: daemon.yaml is USER CONFIG ā never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
54
55
|
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'];
|
|
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'];
|
|
56
57
|
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
57
58
|
try {
|
|
58
59
|
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
@@ -97,7 +98,7 @@ for (const script of BUNDLED_SCRIPTS) {
|
|
|
97
98
|
// and has defer logic (waits for active Claude tasks to finish before restarting).
|
|
98
99
|
// Killing here bypasses that and interrupts ongoing conversations.
|
|
99
100
|
if (scriptsUpdated) {
|
|
100
|
-
console.log(
|
|
101
|
+
console.log(`${icon("pkg")} Scripts synced to ~/.metame/ ā daemon will auto-restart when idle.`);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// ---------------------------------------------------------
|
|
@@ -131,7 +132,7 @@ if (fs.existsSync(bundledSkillsDir)) {
|
|
|
131
132
|
skillsInstalled.push(skillName);
|
|
132
133
|
}
|
|
133
134
|
if (skillsInstalled.length > 0) {
|
|
134
|
-
console.log(
|
|
135
|
+
console.log(`${icon("brain")} Skills installed: ${skillsInstalled.join(', ')}`);
|
|
135
136
|
}
|
|
136
137
|
} catch {
|
|
137
138
|
// Non-fatal
|
|
@@ -153,7 +154,7 @@ if (!fs.existsSync(DAEMON_CONFIG_FILE)) {
|
|
|
153
154
|
if (fs.existsSync(DAEMON_YAML_BACKUP)) {
|
|
154
155
|
// Restore from backup ā user had real config that was lost
|
|
155
156
|
fs.copyFileSync(DAEMON_YAML_BACKUP, DAEMON_CONFIG_FILE);
|
|
156
|
-
console.log(
|
|
157
|
+
console.log(`${icon("warn")} daemon.yaml was missing ā restored from backup.`);
|
|
157
158
|
} else {
|
|
158
159
|
const daemonTemplate = path.join(scriptsDir, 'daemon-default.yaml');
|
|
159
160
|
if (fs.existsSync(daemonTemplate)) {
|
|
@@ -179,13 +180,34 @@ function ensureHookInstalled() {
|
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
// Check if our hook is already configured
|
|
182
|
-
|
|
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}"`;
|
|
183
186
|
const existing = settings.hooks?.UserPromptSubmit || [];
|
|
184
187
|
const alreadyInstalled = existing.some(entry =>
|
|
185
|
-
entry.hooks?.some(h => h.command
|
|
188
|
+
entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
|
|
186
189
|
);
|
|
187
190
|
|
|
188
|
-
|
|
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) {
|
|
189
211
|
if (!settings.hooks) settings.hooks = {};
|
|
190
212
|
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
191
213
|
|
|
@@ -197,11 +219,11 @@ function ensureHookInstalled() {
|
|
|
197
219
|
});
|
|
198
220
|
|
|
199
221
|
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
|
|
200
|
-
console.log("
|
|
222
|
+
console.log(`${icon("hook")} MetaMe: Signal capture hook installed.`);
|
|
201
223
|
}
|
|
202
224
|
} catch (e) {
|
|
203
225
|
// Non-fatal: hook install failure shouldn't block launch
|
|
204
|
-
console.error("
|
|
226
|
+
console.error(`${icon("warn")} Hook install skipped:`, e.message);
|
|
205
227
|
}
|
|
206
228
|
}
|
|
207
229
|
|
|
@@ -264,7 +286,7 @@ function spawnDistillBackground() {
|
|
|
264
286
|
// 4-hour cooldown: check last distill timestamp from profile
|
|
265
287
|
const cooldownMs = 4 * 60 * 60 * 1000;
|
|
266
288
|
try {
|
|
267
|
-
const profilePath = path.join(
|
|
289
|
+
const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
|
|
268
290
|
if (fs.existsSync(profilePath)) {
|
|
269
291
|
const yaml = require('js-yaml');
|
|
270
292
|
const profile = yaml.load(fs.readFileSync(profilePath, 'utf8'));
|
|
@@ -391,7 +413,7 @@ status:
|
|
|
391
413
|
// ---------------------------------------------------------
|
|
392
414
|
const PROTOCOL_NORMAL = `${METAME_START}
|
|
393
415
|
---
|
|
394
|
-
##
|
|
416
|
+
## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
|
|
395
417
|
|
|
396
418
|
**1. THE BRAIN (Source of Truth):**
|
|
397
419
|
* **FILE:** \`$HOME/.claude_profile.yaml\`
|
|
@@ -417,7 +439,7 @@ const PROTOCOL_NORMAL = `${METAME_START}
|
|
|
417
439
|
|
|
418
440
|
const PROTOCOL_ONBOARDING = `${METAME_START}
|
|
419
441
|
---
|
|
420
|
-
##
|
|
442
|
+
## ${icon("brain")} SYSTEM KERNEL: SHADOW_MODE (Active)
|
|
421
443
|
|
|
422
444
|
**1. THE BRAIN (Source of Truth):**
|
|
423
445
|
* **FILE:** \`$HOME/.claude_profile.yaml\`
|
|
@@ -474,7 +496,7 @@ This step connects the bot to the user's PRIVATE chat ā this is the admin chan
|
|
|
474
496
|
|
|
475
497
|
- If **Feishu:**
|
|
476
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.
|
|
477
|
-
|
|
499
|
+
**${icon("warn")} éč¦ļ¼** åØćäŗä»¶č®¢é
ć锵é¢ļ¼åæ
é”»å¼åÆćę„ę¶ę¶ęÆ im.message.receive_v1ćäŗä»¶ćē¶ååØčÆ„äŗä»¶ēé
ē½®äøļ¼å¾éćč·å群ē»äøęęę¶ęÆćļ¼å¦å bot åØē¾¤čäøåŖč½ę¶å° @å® ēę¶ęÆļ¼ę ę³ę„ę¶ę®é群ę¶ęÆļ¼ć
|
|
478
500
|
2. Ask user to paste App ID and App Secret.
|
|
479
501
|
3. Write \`app_id\` and \`app_secret\` into \`~/.metame/daemon.yaml\` under \`feishu:\` section, set \`enabled: true\`.
|
|
480
502
|
4. Tell user: "Now open Feishu and send any message to your new bot (private chat), then tell me you're done."
|
|
@@ -549,7 +571,7 @@ if (isKnownUser) {
|
|
|
549
571
|
finalProtocol = PROTOCOL_NORMAL;
|
|
550
572
|
} else {
|
|
551
573
|
finalProtocol = PROTOCOL_ONBOARDING;
|
|
552
|
-
console.log("
|
|
574
|
+
console.log(`${icon("new")} New user detected ā entering Genesis interview mode...`);
|
|
553
575
|
}
|
|
554
576
|
|
|
555
577
|
// ---------------------------------------------------------
|
|
@@ -659,11 +681,11 @@ try {
|
|
|
659
681
|
if (triggerDrift || triggerComfort || trigger7th) {
|
|
660
682
|
let hint = '';
|
|
661
683
|
if (triggerDrift) {
|
|
662
|
-
hint = `ęčæå äøŖsessionēę¹åå"${driftDeclaredFocus}"
|
|
684
|
+
hint = `ęčæå äøŖsessionēę¹åå"${driftDeclaredFocus}"ęåå·®ć请åØåƹčÆå¼å§ę¶ęø©åå°é®ļ¼${icon("mirror")} ęÆę¹åęęč°ę“äŗļ¼čæęÆäøå°åæåäŗļ¼`;
|
|
663
685
|
} else if (triggerComfort) {
|
|
664
|
-
hint =
|
|
686
|
+
hint = `čæē»å ꬔé½åØēęé¢åćå¦ęēØę·åØsessionē»ęę¶čŖē¶å锿ļ¼åÆä»„ęø©åå°é®ļ¼${icon("mirror")} åå¤å„½ę¢ē“¢ęä¼øåŗäŗåļ¼`;
|
|
665
687
|
} else {
|
|
666
|
-
hint = 'čæęÆē¬¬' + distillCount +
|
|
688
|
+
hint = 'čæęÆē¬¬' + distillCount + `ꬔsessionćå¦ęsessiončŖē¶ē»ęļ¼åÆä»„éå äøå„ļ¼${icon("mirror")} äøäøŖčÆå½¢å®¹čæę¬”sessionēęåļ¼`;
|
|
667
689
|
}
|
|
668
690
|
const timing = triggerDrift ? 'åØåƹčÆå¼å§ę¶å°±é®äøę¬”' : 'åŖåØsessionå³å°ē»ęę¶čÆ“äøę¬”';
|
|
669
691
|
reflectionLine = `\n[MetaMe reflection: ${hint} ${timing}ćå¦ęēØę·ę²”ååŗå°±äøč¦čæ½é®ć]\n`;
|
|
@@ -767,13 +789,13 @@ try {
|
|
|
767
789
|
fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
|
|
768
790
|
} catch (e) {
|
|
769
791
|
// Non-fatal: global CLAUDE.md injection is best-effort
|
|
770
|
-
console.error(
|
|
792
|
+
console.error(`${icon("warn")} Failed to inject global CLAUDE.md: ${e.message}`);
|
|
771
793
|
}
|
|
772
794
|
|
|
773
795
|
|
|
774
796
|
|
|
775
797
|
|
|
776
|
-
console.log(
|
|
798
|
+
console.log(`${icon("magic")} MetaMe v${pkgVersion}: Link Established.`);
|
|
777
799
|
|
|
778
800
|
// Memory system status ā show live stats without blocking launch
|
|
779
801
|
try {
|
|
@@ -789,7 +811,7 @@ try {
|
|
|
789
811
|
memMod.close();
|
|
790
812
|
} catch { /* memory.js not available or DB not ready */ }
|
|
791
813
|
if (factCount > 0 || tagCount > 0) {
|
|
792
|
-
console.log(
|
|
814
|
+
console.log(`${icon("brain")} Memory: ${factCount} facts Ā· ${tagCount} sessions tagged`);
|
|
793
815
|
}
|
|
794
816
|
} catch { /* non-fatal */ }
|
|
795
817
|
|
|
@@ -801,12 +823,12 @@ try {
|
|
|
801
823
|
: 0;
|
|
802
824
|
|
|
803
825
|
if (pendingCount > 0) {
|
|
804
|
-
console.log(
|
|
826
|
+
console.log(`${icon("dna")} Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
|
|
805
827
|
} else {
|
|
806
828
|
// Show last distill time
|
|
807
829
|
let lastDistillStr = 'ä»ęŖ';
|
|
808
830
|
try {
|
|
809
|
-
const profilePath = path.join(
|
|
831
|
+
const profilePath = path.join(HOME_DIR, '.claude_profile.yaml');
|
|
810
832
|
if (fs.existsSync(profilePath)) {
|
|
811
833
|
const _yaml = require('js-yaml');
|
|
812
834
|
const profile = _yaml.load(fs.readFileSync(profilePath, 'utf8'));
|
|
@@ -820,7 +842,7 @@ try {
|
|
|
820
842
|
}
|
|
821
843
|
}
|
|
822
844
|
} catch { /* non-fatal */ }
|
|
823
|
-
console.log(
|
|
845
|
+
console.log(`${icon("dna")} Cognition: ę ę°äæ”å· Ā· äøę¬”čøé¦ ${lastDistillStr}`);
|
|
824
846
|
}
|
|
825
847
|
} catch { /* non-fatal */ }
|
|
826
848
|
|
|
@@ -846,7 +868,7 @@ const CURRENT_VERSION = pkgVersion;
|
|
|
846
868
|
});
|
|
847
869
|
|
|
848
870
|
if (latest && latest !== CURRENT_VERSION) {
|
|
849
|
-
console.log(
|
|
871
|
+
console.log(`${icon("pkg")} MetaMe ${latest} available (current ${CURRENT_VERSION}), updating...`);
|
|
850
872
|
const { execSync } = require('child_process');
|
|
851
873
|
try {
|
|
852
874
|
execSync('npm install -g metame-cli@latest', {
|
|
@@ -854,10 +876,10 @@ const CURRENT_VERSION = pkgVersion;
|
|
|
854
876
|
timeout: 60000,
|
|
855
877
|
...(process.platform === 'win32' ? { shell: process.env.COMSPEC || true } : {}),
|
|
856
878
|
});
|
|
857
|
-
console.log(
|
|
879
|
+
console.log(`${icon("ok")} Updated to ${latest}. Restart metame to use the new version.`);
|
|
858
880
|
} catch (e) {
|
|
859
881
|
const msg = e.stderr ? e.stderr.toString().trim().split('\n').pop() : '';
|
|
860
|
-
console.log(
|
|
882
|
+
console.log(`${icon("warn")} Auto-update failed${msg ? ': ' + msg : ''}. Run manually: npm install -g metame-cli`);
|
|
861
883
|
}
|
|
862
884
|
}
|
|
863
885
|
} catch { /* network unavailable, skip silently */ }
|
|
@@ -887,14 +909,14 @@ const CURRENT_VERSION = pkgVersion;
|
|
|
887
909
|
try { execSync(`${whichCmd} bun`, { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
|
|
888
910
|
|
|
889
911
|
console.log('');
|
|
890
|
-
console.log(
|
|
912
|
+
console.log(`āā ${icon("search")} č®°åæęē“¢å¢å¼ŗļ¼åÆéļ¼å
蓹ļ¼`);
|
|
891
913
|
console.log('ā');
|
|
892
914
|
console.log('ā å½å樔å¼ļ¼åŗē”å
Øęęē“¢ļ¼FTS5ļ¼');
|
|
893
915
|
console.log('ā å®č£
QMD åļ¼BM25 + åéčÆä¹ + éęåŗ ę··åęē“¢');
|
|
894
916
|
console.log('ā ęęļ¼å¬å蓨éēŗ¦ 5xļ¼ęØ”ē³ęčæ°ä¹č½ē²¾åå½äøåå²č®°åæ');
|
|
895
917
|
if (!bunAvailable) {
|
|
896
918
|
console.log('ā');
|
|
897
|
-
console.log(
|
|
919
|
+
console.log(`ā ${icon("warn")} ęŖę£ęµå° bunļ¼ę ę³čŖåØå®č£
ć`);
|
|
898
920
|
console.log('ā ęåØå®č£
ļ¼curl -fsSL https://bun.sh/install | bash');
|
|
899
921
|
console.log('ā bun install -g github:tobi/qmd');
|
|
900
922
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
@@ -913,12 +935,12 @@ const CURRENT_VERSION = pkgVersion;
|
|
|
913
935
|
process.stdout.write('\n');
|
|
914
936
|
|
|
915
937
|
if (answer === 'y' || answer === 'yes') {
|
|
916
|
-
console.log(
|
|
938
|
+
console.log(` ${icon("down")} ę£åØå®č£
QMD...`);
|
|
917
939
|
try {
|
|
918
940
|
execSync('bun install -g github:tobi/qmd', { stdio: 'inherit', timeout: 120000 });
|
|
919
|
-
console.log(
|
|
941
|
+
console.log(` ${icon("ok")} QMD å·²å®č£
ļ¼äøę¬”č®°åæęē“¢čŖåØåÆēØåé樔å¼ć`);
|
|
920
942
|
} catch {
|
|
921
|
-
console.log(
|
|
943
|
+
console.log(` ${icon("warn")} å®č£
失蓄ļ¼åÆęåØę§č”ļ¼bun install -g github:tobi/qmd`);
|
|
922
944
|
}
|
|
923
945
|
} else {
|
|
924
946
|
console.log(' č·³čæćå¦éę„åå®č£
ļ¼bun install -g github:tobi/qmd');
|
|
@@ -937,7 +959,7 @@ const CURRENT_VERSION = pkgVersion;
|
|
|
937
959
|
const isRefresh = process.argv.includes('refresh') || process.argv.includes('--refresh');
|
|
938
960
|
|
|
939
961
|
if (isRefresh) {
|
|
940
|
-
console.log("
|
|
962
|
+
console.log(`${icon("ok")} MetaMe configuration re-injected.`);
|
|
941
963
|
console.log(" Ask Claude to 'read CLAUDE.md' to apply the changes.");
|
|
942
964
|
process.exit(0);
|
|
943
965
|
}
|
|
@@ -953,7 +975,7 @@ if (isEvolve) {
|
|
|
953
975
|
const insight = process.argv.slice(evolveIndex + 1).join(' ').trim();
|
|
954
976
|
|
|
955
977
|
if (!insight) {
|
|
956
|
-
console.error("
|
|
978
|
+
console.error(`${icon("fail")} Error: Missing insight.`);
|
|
957
979
|
console.error(" Usage: metame evolve \"I realized I prefer functional programming\"");
|
|
958
980
|
process.exit(1);
|
|
959
981
|
}
|
|
@@ -975,14 +997,14 @@ if (isEvolve) {
|
|
|
975
997
|
// Save back to file
|
|
976
998
|
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
|
|
977
999
|
|
|
978
|
-
console.log("
|
|
1000
|
+
console.log(`${icon("brain")} MetaMe Brain Updated.`);
|
|
979
1001
|
console.log(` Added insight: "${insight}"`);
|
|
980
1002
|
console.log(" (Run 'metame refresh' to apply this to the current session)");
|
|
981
1003
|
} else {
|
|
982
|
-
console.error("
|
|
1004
|
+
console.error(`${icon("fail")} Error: No profile found. Run 'metame' first to initialize.`);
|
|
983
1005
|
}
|
|
984
1006
|
} catch (e) {
|
|
985
|
-
console.error("
|
|
1007
|
+
console.error(`${icon("fail")} Error updating profile:`, e.message);
|
|
986
1008
|
}
|
|
987
1009
|
process.exit(0);
|
|
988
1010
|
}
|
|
@@ -1002,7 +1024,7 @@ if (isSetTrait) {
|
|
|
1002
1024
|
const value = process.argv.slice(setIndex + 2).join(' ').trim();
|
|
1003
1025
|
|
|
1004
1026
|
if (!key || !value) {
|
|
1005
|
-
console.error("
|
|
1027
|
+
console.error(`${icon("fail")} Error: Missing key or value.`);
|
|
1006
1028
|
console.error(" Usage: metame set-trait identity.role \"New Role\"");
|
|
1007
1029
|
process.exit(1);
|
|
1008
1030
|
}
|
|
@@ -1028,14 +1050,14 @@ if (isSetTrait) {
|
|
|
1028
1050
|
|
|
1029
1051
|
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc), 'utf8');
|
|
1030
1052
|
|
|
1031
|
-
console.log(
|
|
1053
|
+
console.log(`${icon("brain")} MetaMe Brain Surgically Updated.`);
|
|
1032
1054
|
console.log(` Set \`${key}\` = "${value}"`);
|
|
1033
1055
|
console.log(" (Run 'metame refresh' to apply this to the current session)");
|
|
1034
1056
|
} else {
|
|
1035
|
-
console.error("
|
|
1057
|
+
console.error(`${icon("fail")} Error: No profile found.`);
|
|
1036
1058
|
}
|
|
1037
1059
|
} catch (e) {
|
|
1038
|
-
console.error("
|
|
1060
|
+
console.error(`${icon("fail")} Error updating profile:`, e.message);
|
|
1039
1061
|
}
|
|
1040
1062
|
process.exit(0);
|
|
1041
1063
|
}
|
|
@@ -1052,9 +1074,9 @@ if (isQuiet) {
|
|
|
1052
1074
|
if (!doc.growth) doc.growth = {};
|
|
1053
1075
|
doc.growth.quiet_until = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();
|
|
1054
1076
|
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
|
|
1055
|
-
console.log("
|
|
1077
|
+
console.log(`${icon("brain")} MetaMe: Mirror & reflections silenced for 48 hours.`);
|
|
1056
1078
|
} catch (e) {
|
|
1057
|
-
console.error("
|
|
1079
|
+
console.error(`${icon("fail")} Error:`, e.message);
|
|
1058
1080
|
}
|
|
1059
1081
|
process.exit(0);
|
|
1060
1082
|
}
|
|
@@ -1068,26 +1090,26 @@ if (isInsights) {
|
|
|
1068
1090
|
const zoneHistory = (doc.growth && doc.growth.zone_history) || [];
|
|
1069
1091
|
|
|
1070
1092
|
if (patterns.length === 0) {
|
|
1071
|
-
console.log("
|
|
1093
|
+
console.log(`${icon("search")} MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.`);
|
|
1072
1094
|
} else {
|
|
1073
|
-
console.log("
|
|
1095
|
+
console.log(`${icon("mirror")} MetaMe Insights:\n`);
|
|
1074
1096
|
patterns.forEach((p, i) => {
|
|
1075
|
-
const
|
|
1076
|
-
console.log(` ${
|
|
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)}%)`);
|
|
1077
1099
|
console.log(` Detected: ${p.detected}${p.surfaced ? `, Last shown: ${p.surfaced}` : ''}`);
|
|
1078
1100
|
});
|
|
1079
1101
|
if (zoneHistory.length > 0) {
|
|
1080
|
-
console.log(`\n
|
|
1102
|
+
console.log(`\n ${icon("chart")} Recent zone history: ${zoneHistory.join(' ā ')}`);
|
|
1081
1103
|
console.log(` (C=Comfort, S=Stretch, P=Panic)`);
|
|
1082
1104
|
}
|
|
1083
1105
|
const answered = (doc.growth && doc.growth.reflections_answered) || 0;
|
|
1084
1106
|
const skipped = (doc.growth && doc.growth.reflections_skipped) || 0;
|
|
1085
1107
|
if (answered + skipped > 0) {
|
|
1086
|
-
console.log(`\n
|
|
1108
|
+
console.log(`\n ${icon("thought")} Reflections: ${answered} answered, ${skipped} skipped`);
|
|
1087
1109
|
}
|
|
1088
1110
|
}
|
|
1089
1111
|
} catch (e) {
|
|
1090
|
-
console.error("
|
|
1112
|
+
console.error(`${icon("fail")} Error:`, e.message);
|
|
1091
1113
|
}
|
|
1092
1114
|
process.exit(0);
|
|
1093
1115
|
}
|
|
@@ -1098,7 +1120,7 @@ if (isMirror) {
|
|
|
1098
1120
|
const mirrorIndex = process.argv.indexOf('mirror');
|
|
1099
1121
|
const toggle = process.argv[mirrorIndex + 1];
|
|
1100
1122
|
if (toggle !== 'on' && toggle !== 'off') {
|
|
1101
|
-
console.error("
|
|
1123
|
+
console.error(`${icon("fail")} Usage: metame mirror on|off`);
|
|
1102
1124
|
process.exit(1);
|
|
1103
1125
|
}
|
|
1104
1126
|
try {
|
|
@@ -1106,9 +1128,9 @@ if (isMirror) {
|
|
|
1106
1128
|
if (!doc.growth) doc.growth = {};
|
|
1107
1129
|
doc.growth.mirror_enabled = (toggle === 'on');
|
|
1108
1130
|
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
|
|
1109
|
-
console.log(
|
|
1131
|
+
console.log(`${icon("mirror")} MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
|
|
1110
1132
|
} catch (e) {
|
|
1111
|
-
console.error("
|
|
1133
|
+
console.error(`${icon("fail")} Error:`, e.message);
|
|
1112
1134
|
}
|
|
1113
1135
|
process.exit(0);
|
|
1114
1136
|
}
|
|
@@ -1124,7 +1146,7 @@ if (isProvider) {
|
|
|
1124
1146
|
|
|
1125
1147
|
if (!subCmd || subCmd === 'list') {
|
|
1126
1148
|
const active = providers.getActiveProvider();
|
|
1127
|
-
console.log(
|
|
1149
|
+
console.log(`${icon("plug")} MetaMe Providers (active: ${active ? active.name : 'anthropic'})`);
|
|
1128
1150
|
console.log(providers.listFormatted());
|
|
1129
1151
|
process.exit(0);
|
|
1130
1152
|
}
|
|
@@ -1132,18 +1154,18 @@ if (isProvider) {
|
|
|
1132
1154
|
if (subCmd === 'use') {
|
|
1133
1155
|
const name = process.argv[providerIndex + 2];
|
|
1134
1156
|
if (!name) {
|
|
1135
|
-
console.error("
|
|
1157
|
+
console.error(`${icon("fail")} Usage: metame provider use <name>`);
|
|
1136
1158
|
process.exit(1);
|
|
1137
1159
|
}
|
|
1138
1160
|
try {
|
|
1139
1161
|
providers.setActive(name);
|
|
1140
1162
|
const p = providers.getActiveProvider();
|
|
1141
|
-
console.log(
|
|
1163
|
+
console.log(`${icon("ok")} Provider switched ā ${name} (${p.label || name})`);
|
|
1142
1164
|
if (name !== 'anthropic') {
|
|
1143
1165
|
console.log(` Base URL: ${p.base_url || 'not set'}`);
|
|
1144
1166
|
}
|
|
1145
1167
|
} catch (e) {
|
|
1146
|
-
console.error(
|
|
1168
|
+
console.error(`${icon("fail")} ${e.message}`);
|
|
1147
1169
|
process.exit(1);
|
|
1148
1170
|
}
|
|
1149
1171
|
process.exit(0);
|
|
@@ -1152,7 +1174,7 @@ if (isProvider) {
|
|
|
1152
1174
|
if (subCmd === 'add') {
|
|
1153
1175
|
const name = process.argv[providerIndex + 2];
|
|
1154
1176
|
if (!name) {
|
|
1155
|
-
console.error("
|
|
1177
|
+
console.error(`${icon("fail")} Usage: metame provider add <name>`);
|
|
1156
1178
|
process.exit(1);
|
|
1157
1179
|
}
|
|
1158
1180
|
const readline = require('readline');
|
|
@@ -1160,7 +1182,7 @@ if (isProvider) {
|
|
|
1160
1182
|
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
1161
1183
|
|
|
1162
1184
|
(async () => {
|
|
1163
|
-
console.log(`\n
|
|
1185
|
+
console.log(`\n${icon("plug")} Add Provider: ${name}\n`);
|
|
1164
1186
|
console.log("The relay must accept Anthropic Messages API format.");
|
|
1165
1187
|
console.log("(Most quality relays like OpenRouter, OneAPI, etc. support this.)\n");
|
|
1166
1188
|
|
|
@@ -1169,7 +1191,7 @@ if (isProvider) {
|
|
|
1169
1191
|
const api_key = (await ask("API Key: ")).trim();
|
|
1170
1192
|
|
|
1171
1193
|
if (!base_url) {
|
|
1172
|
-
console.error("
|
|
1194
|
+
console.error(`${icon("fail")} Base URL is required.`);
|
|
1173
1195
|
rl.close();
|
|
1174
1196
|
process.exit(1);
|
|
1175
1197
|
}
|
|
@@ -1180,10 +1202,10 @@ if (isProvider) {
|
|
|
1180
1202
|
|
|
1181
1203
|
try {
|
|
1182
1204
|
providers.addProvider(name, config);
|
|
1183
|
-
console.log(`\n
|
|
1205
|
+
console.log(`\n${icon("ok")} Provider "${name}" added.`);
|
|
1184
1206
|
console.log(` Switch to it: metame provider use ${name}`);
|
|
1185
1207
|
} catch (e) {
|
|
1186
|
-
console.error(
|
|
1208
|
+
console.error(`${icon("fail")} ${e.message}`);
|
|
1187
1209
|
}
|
|
1188
1210
|
rl.close();
|
|
1189
1211
|
process.exit(0);
|
|
@@ -1194,14 +1216,14 @@ if (isProvider) {
|
|
|
1194
1216
|
if (subCmd === 'remove') {
|
|
1195
1217
|
const name = process.argv[providerIndex + 2];
|
|
1196
1218
|
if (!name) {
|
|
1197
|
-
console.error("
|
|
1219
|
+
console.error(`${icon("fail")} Usage: metame provider remove <name>`);
|
|
1198
1220
|
process.exit(1);
|
|
1199
1221
|
}
|
|
1200
1222
|
try {
|
|
1201
1223
|
providers.removeProvider(name);
|
|
1202
|
-
console.log(
|
|
1224
|
+
console.log(`${icon("ok")} Provider "${name}" removed.`);
|
|
1203
1225
|
} catch (e) {
|
|
1204
|
-
console.error(
|
|
1226
|
+
console.error(`${icon("fail")} ${e.message}`);
|
|
1205
1227
|
}
|
|
1206
1228
|
process.exit(0);
|
|
1207
1229
|
}
|
|
@@ -1210,15 +1232,15 @@ if (isProvider) {
|
|
|
1210
1232
|
const role = process.argv[providerIndex + 2]; // distill | daemon
|
|
1211
1233
|
const name = process.argv[providerIndex + 3]; // provider name or empty to clear
|
|
1212
1234
|
if (!role) {
|
|
1213
|
-
console.error("
|
|
1235
|
+
console.error(`${icon("fail")} Usage: metame provider set-role <distill|daemon> [provider-name]`);
|
|
1214
1236
|
console.error(" Omit provider name to reset to active provider.");
|
|
1215
1237
|
process.exit(1);
|
|
1216
1238
|
}
|
|
1217
1239
|
try {
|
|
1218
1240
|
providers.setRole(role, name || null);
|
|
1219
|
-
console.log(
|
|
1241
|
+
console.log(`${icon("ok")} ${role} provider ${name ? `set to "${name}"` : 'reset to active'}.`);
|
|
1220
1242
|
} catch (e) {
|
|
1221
|
-
console.error(
|
|
1243
|
+
console.error(`${icon("fail")} ${e.message}`);
|
|
1222
1244
|
}
|
|
1223
1245
|
process.exit(0);
|
|
1224
1246
|
}
|
|
@@ -1229,11 +1251,11 @@ if (isProvider) {
|
|
|
1229
1251
|
const name = targetName || prov.active;
|
|
1230
1252
|
const p = prov.providers[name];
|
|
1231
1253
|
if (!p) {
|
|
1232
|
-
console.error(
|
|
1254
|
+
console.error(`${icon("fail")} Provider "${name}" not found.`);
|
|
1233
1255
|
process.exit(1);
|
|
1234
1256
|
}
|
|
1235
1257
|
|
|
1236
|
-
console.log(
|
|
1258
|
+
console.log(`${icon("search")} Testing provider: ${name} (${p.label || name})`);
|
|
1237
1259
|
if (name === 'anthropic') {
|
|
1238
1260
|
console.log(" Using official Anthropic endpoint ā testing via claude CLI...");
|
|
1239
1261
|
} else {
|
|
@@ -1257,18 +1279,18 @@ if (isProvider) {
|
|
|
1257
1279
|
const elapsed = Date.now() - start;
|
|
1258
1280
|
|
|
1259
1281
|
if (result.includes('PROVIDER_OK')) {
|
|
1260
|
-
console.log(`
|
|
1282
|
+
console.log(` ${icon("ok")} Connected (${elapsed}ms)`);
|
|
1261
1283
|
} else {
|
|
1262
|
-
console.log(`
|
|
1284
|
+
console.log(` ${icon("warn")} Response received (${elapsed}ms) but unexpected: ${result.slice(0, 80)}`);
|
|
1263
1285
|
}
|
|
1264
1286
|
} catch (e) {
|
|
1265
|
-
console.error(`
|
|
1287
|
+
console.error(` ${icon("fail")} Failed: ${e.message.split('\n')[0]}`);
|
|
1266
1288
|
}
|
|
1267
1289
|
process.exit(0);
|
|
1268
1290
|
}
|
|
1269
1291
|
|
|
1270
1292
|
// Unknown subcommand ā show help
|
|
1271
|
-
console.log("
|
|
1293
|
+
console.log(`${icon("plug")} MetaMe Provider Commands:`);
|
|
1272
1294
|
console.log(" metame provider ā list providers");
|
|
1273
1295
|
console.log(" metame provider use <name> ā switch active provider");
|
|
1274
1296
|
console.log(" metame provider add <name> ā add a new provider");
|
|
@@ -1312,20 +1334,20 @@ if (isDaemon) {
|
|
|
1312
1334
|
if (fs.existsSync(templateSrc)) {
|
|
1313
1335
|
fs.copyFileSync(templateSrc, DAEMON_CONFIG);
|
|
1314
1336
|
} else {
|
|
1315
|
-
console.error("
|
|
1337
|
+
console.error(`${icon("fail")} Template not found. Reinstall MetaMe.`);
|
|
1316
1338
|
process.exit(1);
|
|
1317
1339
|
}
|
|
1318
1340
|
try { fs.chmodSync(METAME_DIR, 0o700); } catch { /* ignore on Windows */ }
|
|
1319
|
-
console.log("
|
|
1341
|
+
console.log(`${icon("ok")} Config created: ~/.metame/daemon.yaml\n`);
|
|
1320
1342
|
} else {
|
|
1321
|
-
console.log("
|
|
1343
|
+
console.log(`${icon("ok")} Config exists: ~/.metame/daemon.yaml\n`);
|
|
1322
1344
|
}
|
|
1323
1345
|
|
|
1324
1346
|
const yaml = require(path.join(__dirname, 'node_modules', 'js-yaml'));
|
|
1325
1347
|
let cfg = yaml.load(fs.readFileSync(DAEMON_CONFIG, 'utf8')) || {};
|
|
1326
1348
|
|
|
1327
1349
|
// --- Telegram Setup ---
|
|
1328
|
-
console.log("
|
|
1350
|
+
console.log(`āāā ${icon("phone")} Telegram Setup āāā`);
|
|
1329
1351
|
console.log("");
|
|
1330
1352
|
console.log("Step 1: Create a Bot");
|
|
1331
1353
|
console.log(" ⢠Open Telegram app on your phone or desktop");
|
|
@@ -1370,21 +1392,21 @@ if (isDaemon) {
|
|
|
1370
1392
|
|
|
1371
1393
|
if (chatIds.length > 0) {
|
|
1372
1394
|
cfg.telegram.allowed_chat_ids = chatIds;
|
|
1373
|
-
console.log(`
|
|
1395
|
+
console.log(` ${icon("ok")} Found chat ID(s): ${chatIds.join(', ')}`);
|
|
1374
1396
|
} else {
|
|
1375
|
-
console.log("
|
|
1397
|
+
console.log(` ${icon("warn")} No messages found. Make sure you messaged the bot.`);
|
|
1376
1398
|
console.log(" You can set allowed_chat_ids manually in daemon.yaml later.");
|
|
1377
1399
|
}
|
|
1378
1400
|
} catch {
|
|
1379
|
-
console.log("
|
|
1401
|
+
console.log(` ${icon("warn")} Could not fetch chat ID. Set it manually in daemon.yaml.`);
|
|
1380
1402
|
}
|
|
1381
|
-
console.log("
|
|
1403
|
+
console.log(` ${icon("ok")} Telegram configured!\n`);
|
|
1382
1404
|
} else {
|
|
1383
1405
|
console.log(" Skipped.\n");
|
|
1384
1406
|
}
|
|
1385
1407
|
|
|
1386
1408
|
// --- Feishu Setup ---
|
|
1387
|
-
console.log("
|
|
1409
|
+
console.log(`āāā ${icon("feishu")} Feishu (Lark) Setup āāā`);
|
|
1388
1410
|
console.log("");
|
|
1389
1411
|
console.log("Step 1: Create an App");
|
|
1390
1412
|
console.log(" ⢠Go to: https://open.feishu.cn/app");
|
|
@@ -1428,7 +1450,7 @@ if (isDaemon) {
|
|
|
1428
1450
|
cfg.feishu.app_id = feishuAppId;
|
|
1429
1451
|
cfg.feishu.app_secret = feishuSecret;
|
|
1430
1452
|
if (!cfg.feishu.allowed_chat_ids) cfg.feishu.allowed_chat_ids = [];
|
|
1431
|
-
console.log("
|
|
1453
|
+
console.log(` ${icon("ok")} Feishu configured!`);
|
|
1432
1454
|
console.log(" Note: allowed_chat_ids is empty = deny all users.");
|
|
1433
1455
|
console.log(" Add chat IDs to daemon.yaml or use /agent bind from target chat.\n");
|
|
1434
1456
|
}
|
|
@@ -1438,7 +1460,7 @@ if (isDaemon) {
|
|
|
1438
1460
|
|
|
1439
1461
|
// Write config
|
|
1440
1462
|
fs.writeFileSync(DAEMON_CONFIG, yaml.dump(cfg, { lineWidth: -1 }), 'utf8');
|
|
1441
|
-
console.log("
|
|
1463
|
+
console.log(`āāā ${icon("ok")} Setup Complete āāā`);
|
|
1442
1464
|
console.log(`Config saved: ${DAEMON_CONFIG}`);
|
|
1443
1465
|
console.log("\nNext steps:");
|
|
1444
1466
|
console.log(" metame start ā start the daemon");
|
|
@@ -1455,7 +1477,7 @@ if (isDaemon) {
|
|
|
1455
1477
|
|
|
1456
1478
|
if (subCmd === 'install-launchd') {
|
|
1457
1479
|
if (process.platform !== 'darwin') {
|
|
1458
|
-
console.error("
|
|
1480
|
+
console.error(`${icon("fail")} launchd is macOS-only.`);
|
|
1459
1481
|
process.exit(1);
|
|
1460
1482
|
}
|
|
1461
1483
|
const plistDir = path.join(HOME_DIR, 'Library', 'LaunchAgents');
|
|
@@ -1497,7 +1519,7 @@ if (isDaemon) {
|
|
|
1497
1519
|
</dict>
|
|
1498
1520
|
</plist>`;
|
|
1499
1521
|
fs.writeFileSync(plistPath, plistContent, 'utf8');
|
|
1500
|
-
console.log(
|
|
1522
|
+
console.log(`${icon("ok")} launchd plist installed: ${plistPath}`);
|
|
1501
1523
|
console.log(" Load now: launchctl load " + plistPath);
|
|
1502
1524
|
console.log(" Unload: launchctl unload " + plistPath);
|
|
1503
1525
|
process.exit(0);
|
|
@@ -1505,7 +1527,7 @@ if (isDaemon) {
|
|
|
1505
1527
|
|
|
1506
1528
|
if (subCmd === 'install-systemd') {
|
|
1507
1529
|
if (process.platform === 'darwin') {
|
|
1508
|
-
console.error("
|
|
1530
|
+
console.error(`${icon("fail")} Use 'metame daemon install-launchd' on macOS.`);
|
|
1509
1531
|
process.exit(1);
|
|
1510
1532
|
}
|
|
1511
1533
|
|
|
@@ -1513,7 +1535,7 @@ if (isDaemon) {
|
|
|
1513
1535
|
try {
|
|
1514
1536
|
require('child_process').execSync('systemctl --user --no-pager status 2>/dev/null || true');
|
|
1515
1537
|
} catch {
|
|
1516
|
-
console.error("
|
|
1538
|
+
console.error(`${icon("fail")} systemd not available.`);
|
|
1517
1539
|
console.error(" WSL users: add [boot]\\nsystemd=true to /etc/wsl.conf, then restart WSL.");
|
|
1518
1540
|
process.exit(1);
|
|
1519
1541
|
}
|
|
@@ -1553,7 +1575,7 @@ WantedBy=default.target
|
|
|
1553
1575
|
// Enable lingering so service runs even when user is not logged in
|
|
1554
1576
|
try { es(`loginctl enable-linger ${process.env.USER || ''}`); } catch { /* may need root */ }
|
|
1555
1577
|
|
|
1556
|
-
console.log(
|
|
1578
|
+
console.log(`${icon("ok")} systemd service installed: ${servicePath}`);
|
|
1557
1579
|
console.log(" Status: systemctl --user status metame-daemon");
|
|
1558
1580
|
console.log(" Logs: journalctl --user -u metame-daemon -f");
|
|
1559
1581
|
console.log(" Disable: systemctl --user disable metame-daemon");
|
|
@@ -1562,7 +1584,7 @@ WantedBy=default.target
|
|
|
1562
1584
|
const isWSL = fs.existsSync('/proc/version') &&
|
|
1563
1585
|
fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
|
|
1564
1586
|
if (isWSL) {
|
|
1565
|
-
console.log(
|
|
1587
|
+
console.log(`\n ${icon("pin")} WSL auto-boot tip:`);
|
|
1566
1588
|
console.log(" Add this to Windows Task Scheduler (run at login):");
|
|
1567
1589
|
console.log(` wsl -d ${process.env.WSL_DISTRO_NAME || 'Ubuntu'} -- sh -c 'nohup sleep infinity &'`);
|
|
1568
1590
|
console.log(" This keeps WSL alive so the daemon stays running.");
|
|
@@ -1570,17 +1592,44 @@ WantedBy=default.target
|
|
|
1570
1592
|
process.exit(0);
|
|
1571
1593
|
}
|
|
1572
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
|
+
|
|
1573
1624
|
if (subCmd === 'start') {
|
|
1574
1625
|
// Kill any lingering daemon.js processes to avoid Feishu WebSocket conflicts
|
|
1575
1626
|
try {
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const n = parseInt(p, 10);
|
|
1581
|
-
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 { /* */ }
|
|
1582
1631
|
}
|
|
1583
|
-
|
|
1632
|
+
sleepSync(1000);
|
|
1584
1633
|
}
|
|
1585
1634
|
} catch { /* ignore */ }
|
|
1586
1635
|
// Check if already running
|
|
@@ -1588,25 +1637,26 @@ WantedBy=default.target
|
|
|
1588
1637
|
try { fs.unlinkSync(DAEMON_PID); } catch { /* */ }
|
|
1589
1638
|
}
|
|
1590
1639
|
if (!fs.existsSync(DAEMON_CONFIG)) {
|
|
1591
|
-
console.error("
|
|
1640
|
+
console.error(`${icon("fail")} No config found. Run: metame daemon init`);
|
|
1592
1641
|
process.exit(1);
|
|
1593
1642
|
}
|
|
1594
1643
|
if (!fs.existsSync(DAEMON_SCRIPT)) {
|
|
1595
|
-
console.error("
|
|
1644
|
+
console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
|
|
1596
1645
|
process.exit(1);
|
|
1597
1646
|
}
|
|
1598
|
-
// Use caffeinate on macOS
|
|
1599
|
-
const
|
|
1600
|
-
const
|
|
1601
|
-
const
|
|
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];
|
|
1602
1652
|
const bg = spawn(cmd, args, {
|
|
1603
|
-
detached:
|
|
1653
|
+
detached: !isWin,
|
|
1604
1654
|
stdio: 'ignore',
|
|
1605
1655
|
windowsHide: true,
|
|
1606
1656
|
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
|
|
1607
1657
|
});
|
|
1608
1658
|
bg.unref();
|
|
1609
|
-
console.log(
|
|
1659
|
+
console.log(`${icon("ok")} MetaMe daemon started (PID: ${bg.pid})`);
|
|
1610
1660
|
console.log(" Logs: metame logs");
|
|
1611
1661
|
console.log(" Stop: metame stop");
|
|
1612
1662
|
process.exit(0);
|
|
@@ -1614,7 +1664,7 @@ WantedBy=default.target
|
|
|
1614
1664
|
|
|
1615
1665
|
if (subCmd === 'stop') {
|
|
1616
1666
|
if (!fs.existsSync(DAEMON_PID)) {
|
|
1617
|
-
console.log("
|
|
1667
|
+
console.log(`${icon("info")} No daemon running (no PID file).`);
|
|
1618
1668
|
process.exit(0);
|
|
1619
1669
|
}
|
|
1620
1670
|
const pid = parseInt(fs.readFileSync(DAEMON_PID, 'utf8').trim(), 10);
|
|
@@ -1623,16 +1673,15 @@ WantedBy=default.target
|
|
|
1623
1673
|
// Wait for process to die (up to 3s), then force kill
|
|
1624
1674
|
let dead = false;
|
|
1625
1675
|
for (let i = 0; i < 6; i++) {
|
|
1626
|
-
|
|
1627
|
-
es('sleep 0.5');
|
|
1676
|
+
sleepSync(500);
|
|
1628
1677
|
try { process.kill(pid, 0); } catch { dead = true; break; }
|
|
1629
1678
|
}
|
|
1630
1679
|
if (!dead) {
|
|
1631
1680
|
try { process.kill(pid, 'SIGKILL'); } catch { /* already gone */ }
|
|
1632
1681
|
}
|
|
1633
|
-
console.log(
|
|
1682
|
+
console.log(`${icon("ok")} Daemon stopped (PID: ${pid})`);
|
|
1634
1683
|
} catch (e) {
|
|
1635
|
-
console.log(
|
|
1684
|
+
console.log(`${icon("warn")} Process ${pid} not found (may have already exited).`);
|
|
1636
1685
|
}
|
|
1637
1686
|
try { fs.unlinkSync(DAEMON_PID); } catch { /* ignore */ }
|
|
1638
1687
|
process.exit(0);
|
|
@@ -1649,7 +1698,7 @@ WantedBy=default.target
|
|
|
1649
1698
|
try { process.kill(pid, 0); isRunning = true; } catch { /* dead */ }
|
|
1650
1699
|
}
|
|
1651
1700
|
|
|
1652
|
-
console.log(
|
|
1701
|
+
console.log(`${icon("bot")} MetaMe Daemon: ${isRunning ? icon("green") + ' Running' : icon("red") + ' Stopped'}`);
|
|
1653
1702
|
if (state.started_at) console.log(` Started: ${state.started_at}`);
|
|
1654
1703
|
if (state.pid) console.log(` PID: ${state.pid}`);
|
|
1655
1704
|
|
|
@@ -1677,8 +1726,8 @@ WantedBy=default.target
|
|
|
1677
1726
|
if (taskEntries.length > 0) {
|
|
1678
1727
|
console.log(" Recent tasks:");
|
|
1679
1728
|
for (const [name, info] of taskEntries) {
|
|
1680
|
-
const
|
|
1681
|
-
console.log(` ${
|
|
1729
|
+
const sym = info.status === 'success' ? icon("ok") : icon("fail");
|
|
1730
|
+
console.log(` ${sym} ${name}: ${info.last_run || 'unknown'}`);
|
|
1682
1731
|
if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
|
|
1683
1732
|
}
|
|
1684
1733
|
const hiddenStale = Object.keys(tasks).length - taskEntries.length;
|
|
@@ -1691,7 +1740,7 @@ WantedBy=default.target
|
|
|
1691
1740
|
|
|
1692
1741
|
if (subCmd === 'logs') {
|
|
1693
1742
|
if (!fs.existsSync(DAEMON_LOG)) {
|
|
1694
|
-
console.log("
|
|
1743
|
+
console.log(`${icon("info")} No log file yet. Start the daemon first.`);
|
|
1695
1744
|
process.exit(0);
|
|
1696
1745
|
}
|
|
1697
1746
|
const content = fs.readFileSync(DAEMON_LOG, 'utf8');
|
|
@@ -1704,11 +1753,11 @@ WantedBy=default.target
|
|
|
1704
1753
|
if (subCmd === 'run') {
|
|
1705
1754
|
const taskName = process.argv[daemonIndex + 2];
|
|
1706
1755
|
if (!taskName) {
|
|
1707
|
-
console.error("
|
|
1756
|
+
console.error(`${icon("fail")} Usage: metame daemon run <task-name>`);
|
|
1708
1757
|
process.exit(1);
|
|
1709
1758
|
}
|
|
1710
1759
|
if (!fs.existsSync(DAEMON_SCRIPT)) {
|
|
1711
|
-
console.error("
|
|
1760
|
+
console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
|
|
1712
1761
|
process.exit(1);
|
|
1713
1762
|
}
|
|
1714
1763
|
// Run in foreground using daemon.js --run
|
|
@@ -1721,7 +1770,7 @@ WantedBy=default.target
|
|
|
1721
1770
|
}
|
|
1722
1771
|
|
|
1723
1772
|
// Unknown subcommand
|
|
1724
|
-
console.log("
|
|
1773
|
+
console.log(`${icon("book")} MetaMe Daemon Commands:`);
|
|
1725
1774
|
console.log(" metame start ā start background daemon");
|
|
1726
1775
|
console.log(" metame stop ā stop daemon");
|
|
1727
1776
|
console.log(" metame status ā show status & budget");
|
|
@@ -1729,9 +1778,11 @@ WantedBy=default.target
|
|
|
1729
1778
|
console.log(" metame daemon init ā initialize config");
|
|
1730
1779
|
console.log(" metame daemon run <name> ā run a task once");
|
|
1731
1780
|
if (process.platform === 'darwin') {
|
|
1732
|
-
console.log(" metame daemon install-launchd
|
|
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");
|
|
1733
1784
|
} else {
|
|
1734
|
-
console.log(" metame daemon install-systemd
|
|
1785
|
+
console.log(" metame daemon install-systemd ā auto-start on Linux/WSL");
|
|
1735
1786
|
}
|
|
1736
1787
|
process.exit(0);
|
|
1737
1788
|
}
|
|
@@ -1780,7 +1831,7 @@ if (isSync) {
|
|
|
1780
1831
|
process.exit(1);
|
|
1781
1832
|
}
|
|
1782
1833
|
|
|
1783
|
-
console.log(`\n
|
|
1834
|
+
console.log(`\n${icon("reload")} Resuming session ${bestSession.id.slice(0, 8)}...\n`);
|
|
1784
1835
|
const providerEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
|
|
1785
1836
|
const resumeArgs = ['--resume', bestSession.id];
|
|
1786
1837
|
if (daemonCfg.dangerously_skip_permissions) resumeArgs.push('--dangerously-skip-permissions');
|
|
@@ -1801,7 +1852,7 @@ if (isSync) {
|
|
|
1801
1852
|
// We rely on our own scoped variable to detect nesting,
|
|
1802
1853
|
// ignoring the leaky CLAUDE_CODE_SSE_PORT from IDEs.
|
|
1803
1854
|
if (process.env.METAME_ACTIVE_SESSION === 'true') {
|
|
1804
|
-
console.error("
|
|
1855
|
+
console.error(`\n${icon("stop")} ACTION BLOCKED: Nested Session Detected`);
|
|
1805
1856
|
console.error(" You are actively running inside a MetaMe session.");
|
|
1806
1857
|
console.error(" To reload configuration, use: \x1b[36m!metame refresh\x1b[0m\n");
|
|
1807
1858
|
process.exit(1);
|
|
@@ -1814,7 +1865,7 @@ if (process.env.METAME_ACTIVE_SESSION === 'true') {
|
|
|
1814
1865
|
const activeProviderEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
|
|
1815
1866
|
const activeProviderName = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).getActiveName(); } catch { return 'anthropic'; } })();
|
|
1816
1867
|
if (activeProviderName !== 'anthropic') {
|
|
1817
|
-
console.log(
|
|
1868
|
+
console.log(`${icon("plug")} Provider: ${activeProviderName}`);
|
|
1818
1869
|
}
|
|
1819
1870
|
|
|
1820
1871
|
// Build launch args ā inject system prompt for new users
|
|
@@ -1873,17 +1924,18 @@ try {
|
|
|
1873
1924
|
} catch { /* PID file stale, daemon not running */ }
|
|
1874
1925
|
}
|
|
1875
1926
|
if (!daemonRunning) {
|
|
1876
|
-
const
|
|
1877
|
-
const
|
|
1878
|
-
const
|
|
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];
|
|
1879
1931
|
const bg = spawn(dCmd, dArgs, {
|
|
1880
|
-
detached:
|
|
1932
|
+
detached: !_isWin,
|
|
1881
1933
|
stdio: 'ignore',
|
|
1882
1934
|
windowsHide: true,
|
|
1883
1935
|
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname },
|
|
1884
1936
|
});
|
|
1885
1937
|
bg.unref();
|
|
1886
|
-
console.log(
|
|
1938
|
+
console.log(`${icon("bot")} Daemon auto-started (PID: ${bg.pid})`);
|
|
1887
1939
|
}
|
|
1888
1940
|
}
|
|
1889
1941
|
} catch { /* non-fatal */ }
|
|
@@ -1895,7 +1947,7 @@ const child = spawnClaude(launchArgs, {
|
|
|
1895
1947
|
});
|
|
1896
1948
|
|
|
1897
1949
|
child.on('error', () => {
|
|
1898
|
-
console.error("
|
|
1950
|
+
console.error(`\n${icon("fail")} Error: Could not launch 'claude'.`);
|
|
1899
1951
|
console.error(" Please make sure Claude Code is installed globally:");
|
|
1900
1952
|
console.error(" npm install -g @anthropic-ai/claude-code");
|
|
1901
1953
|
});
|