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