metame-cli 1.5.3 → 1.5.5
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 +60 -18
- package/index.js +352 -79
- package/package.json +2 -2
- package/scripts/agent-layer.js +4 -2
- package/scripts/bin/dispatch_to +178 -90
- package/scripts/daemon-admin-commands.js +353 -105
- package/scripts/daemon-agent-commands.js +434 -66
- package/scripts/daemon-bridges.js +477 -68
- package/scripts/daemon-claude-engine.js +1267 -674
- package/scripts/daemon-command-router.js +205 -27
- package/scripts/daemon-command-session-route.js +118 -0
- package/scripts/daemon-default.yaml +7 -0
- package/scripts/daemon-engine-runtime.js +96 -20
- package/scripts/daemon-exec-commands.js +108 -49
- package/scripts/daemon-file-browser.js +64 -7
- package/scripts/daemon-notify.js +18 -4
- package/scripts/daemon-ops-commands.js +16 -2
- package/scripts/daemon-remote-dispatch.js +55 -1
- package/scripts/daemon-runtime-lifecycle.js +87 -0
- package/scripts/daemon-session-commands.js +102 -45
- package/scripts/daemon-session-store.js +497 -66
- package/scripts/daemon-siri-bridge.js +234 -0
- package/scripts/daemon-siri-imessage.js +209 -0
- package/scripts/daemon-task-scheduler.js +10 -2
- package/scripts/daemon.js +697 -179
- package/scripts/daemon.yaml +7 -0
- package/scripts/docs/agent-guide.md +36 -3
- package/scripts/docs/hook-config.md +134 -0
- package/scripts/docs/maintenance-manual.md +162 -5
- package/scripts/docs/pointer-map.md +60 -5
- package/scripts/feishu-adapter.js +7 -15
- package/scripts/hooks/doc-router.js +29 -0
- package/scripts/hooks/hook-utils.js +61 -0
- package/scripts/hooks/intent-doc-router.js +54 -0
- package/scripts/hooks/intent-engine.js +72 -0
- package/scripts/hooks/intent-file-transfer.js +51 -0
- package/scripts/hooks/intent-memory-recall.js +35 -0
- package/scripts/hooks/intent-ops-assist.js +54 -0
- package/scripts/hooks/intent-task-create.js +35 -0
- package/scripts/hooks/intent-team-dispatch.js +106 -0
- package/scripts/hooks/team-context.js +143 -0
- package/scripts/intent-registry.js +59 -0
- package/scripts/memory-extract.js +59 -0
- package/scripts/memory-nightly-reflect.js +109 -43
- package/scripts/memory.js +55 -17
- package/scripts/mentor-engine.js +6 -0
- package/scripts/schema.js +1 -0
- package/scripts/self-reflect.js +110 -12
- package/scripts/session-analytics.js +160 -0
- package/scripts/signal-capture.js +1 -1
- package/scripts/team-dispatch.js +315 -0
package/index.js
CHANGED
|
@@ -50,6 +50,96 @@ function spawnCodex(args, options) {
|
|
|
50
50
|
return spawnViaNode('codex', args, { ...options, env });
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function mergeReflectionDisplayEntries(entries) {
|
|
54
|
+
const merged = new Map();
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const normalized = typeof entry === 'string'
|
|
57
|
+
? { summary: entry, detected: null }
|
|
58
|
+
: (entry && typeof entry.summary === 'string' ? { summary: entry.summary, detected: entry.detected || null } : null);
|
|
59
|
+
if (!normalized) continue;
|
|
60
|
+
|
|
61
|
+
const existing = merged.get(normalized.summary);
|
|
62
|
+
if (!existing) {
|
|
63
|
+
merged.set(normalized.summary, normalized);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const existingDetected = existing.detected ? new Date(existing.detected).getTime() : 0;
|
|
68
|
+
const normalizedDetected = normalized.detected ? new Date(normalized.detected).getTime() : 0;
|
|
69
|
+
const shouldReplace =
|
|
70
|
+
normalizedDetected > 0 && (
|
|
71
|
+
existingDetected === 0
|
|
72
|
+
|| normalizedDetected < existingDetected
|
|
73
|
+
);
|
|
74
|
+
if (shouldReplace) merged.set(normalized.summary, { ...existing, ...normalized });
|
|
75
|
+
}
|
|
76
|
+
return [...merged.values()];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readLatestClaudeSession(projectsRoot, cwd) {
|
|
80
|
+
let bestSession = null;
|
|
81
|
+
const findLatest = (dir) => {
|
|
82
|
+
try {
|
|
83
|
+
return fs.readdirSync(dir)
|
|
84
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
85
|
+
.map(f => ({ id: f.replace('.jsonl', ''), mtime: fs.statSync(path.join(dir, f)).mtimeMs }))
|
|
86
|
+
.sort((a, b) => b.mtime - a.mtime)[0] || null;
|
|
87
|
+
} catch { return null; }
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const projDir = path.join(projectsRoot, cwd.replace(/\//g, '-'));
|
|
92
|
+
const localBest = findLatest(projDir);
|
|
93
|
+
let globalBest = null;
|
|
94
|
+
try {
|
|
95
|
+
for (const d of fs.readdirSync(projectsRoot)) {
|
|
96
|
+
const s = findLatest(path.join(projectsRoot, d));
|
|
97
|
+
if (s && (!globalBest || s.mtime > globalBest.mtime)) globalBest = s;
|
|
98
|
+
}
|
|
99
|
+
} catch { /* ignore */ }
|
|
100
|
+
if (localBest && globalBest && globalBest.mtime > localBest.mtime) {
|
|
101
|
+
bestSession = { ...globalBest, scope: 'global' };
|
|
102
|
+
} else {
|
|
103
|
+
bestSession = localBest ? { ...localBest, scope: 'local' } : (globalBest ? { ...globalBest, scope: 'global' } : null);
|
|
104
|
+
}
|
|
105
|
+
} catch { /* ignore */ }
|
|
106
|
+
|
|
107
|
+
return bestSession ? { ...bestSession, engine: 'claude' } : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readLatestCodexSession(cwd) {
|
|
111
|
+
let db;
|
|
112
|
+
try {
|
|
113
|
+
const codeDb = path.join(HOME_DIR, '.codex', 'state_5.sqlite');
|
|
114
|
+
if (!fs.existsSync(codeDb)) return null;
|
|
115
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
116
|
+
db = new DatabaseSync(codeDb, { readonly: true });
|
|
117
|
+
const row = db.prepare(`
|
|
118
|
+
SELECT id, cwd, updated_at, created_at
|
|
119
|
+
FROM threads
|
|
120
|
+
WHERE COALESCE(has_user_event, 1) = 1
|
|
121
|
+
AND archived = 0
|
|
122
|
+
ORDER BY
|
|
123
|
+
CASE WHEN cwd = ? THEN 0 ELSE 1 END ASC,
|
|
124
|
+
COALESCE(updated_at, created_at, 0) DESC
|
|
125
|
+
LIMIT 1
|
|
126
|
+
`).get(cwd);
|
|
127
|
+
db.close();
|
|
128
|
+
db = null;
|
|
129
|
+
if (!row || !row.id) return null;
|
|
130
|
+
const ts = Number(row.updated_at || row.created_at || 0) * 1000;
|
|
131
|
+
return {
|
|
132
|
+
id: String(row.id),
|
|
133
|
+
mtime: ts || 0,
|
|
134
|
+
engine: 'codex',
|
|
135
|
+
scope: String(row.cwd || '') === String(cwd) ? 'local' : 'global',
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
if (db) { try { db.close(); } catch { /* ignore */ } }
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
53
143
|
// Quick flags (before heavy init)
|
|
54
144
|
const pkgVersion = require('./package.json').version;
|
|
55
145
|
if (process.argv.includes('-V') || process.argv.includes('--version')) {
|
|
@@ -83,8 +173,15 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
83
173
|
// DEPLOY PHASE: sync scripts, docs, bin to ~/.metame/
|
|
84
174
|
// ---------------------------------------------------------
|
|
85
175
|
|
|
176
|
+
// Dev mode: when running from git repo, symlink instead of copy.
|
|
177
|
+
// This ensures source files and runtime files are always the same,
|
|
178
|
+
// preventing agents from accidentally editing copies instead of source.
|
|
179
|
+
const IS_DEV_MODE = fs.existsSync(path.join(__dirname, '.git'));
|
|
180
|
+
|
|
86
181
|
/**
|
|
87
|
-
* Sync files from srcDir to destDir.
|
|
182
|
+
* Sync files from srcDir to destDir.
|
|
183
|
+
* - Dev mode (git repo): creates symlinks so source === runtime.
|
|
184
|
+
* - Production (npm install): copies files, only writes when content differs.
|
|
88
185
|
* @param {string} srcDir - source directory
|
|
89
186
|
* @param {string} destDir - destination directory
|
|
90
187
|
* @param {object} [opts]
|
|
@@ -102,22 +199,111 @@ function syncDirFiles(srcDir, destDir, { fileList, chmod } = {}) {
|
|
|
102
199
|
const dest = path.join(destDir, f);
|
|
103
200
|
try {
|
|
104
201
|
if (!fs.existsSync(src)) continue;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
fs.
|
|
109
|
-
|
|
110
|
-
|
|
202
|
+
|
|
203
|
+
if (IS_DEV_MODE) {
|
|
204
|
+
// Dev mode: symlink dest → src (replace copy/stale symlink if needed)
|
|
205
|
+
const srcReal = fs.realpathSync(src);
|
|
206
|
+
let needLink = true;
|
|
207
|
+
try {
|
|
208
|
+
const existing = fs.lstatSync(dest);
|
|
209
|
+
if (existing.isSymbolicLink()) {
|
|
210
|
+
if (fs.realpathSync(dest) === srcReal) needLink = false;
|
|
211
|
+
else fs.unlinkSync(dest);
|
|
212
|
+
} else {
|
|
213
|
+
// Replace regular file with symlink
|
|
214
|
+
fs.unlinkSync(dest);
|
|
215
|
+
}
|
|
216
|
+
} catch { /* dest doesn't exist */ }
|
|
217
|
+
if (needLink) {
|
|
218
|
+
fs.symlinkSync(srcReal, dest);
|
|
219
|
+
if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
|
|
220
|
+
updated = true;
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// Production: copy when content differs
|
|
224
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
225
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
226
|
+
if (srcContent !== destContent) {
|
|
227
|
+
fs.writeFileSync(dest, srcContent, 'utf8');
|
|
228
|
+
if (chmod) try { fs.chmodSync(dest, chmod); } catch { /* Windows */ }
|
|
229
|
+
updated = true;
|
|
230
|
+
}
|
|
111
231
|
}
|
|
112
232
|
} catch { /* non-fatal per file */ }
|
|
113
233
|
}
|
|
114
234
|
return updated;
|
|
115
235
|
}
|
|
116
236
|
|
|
237
|
+
function readRunningDaemonPid({ pidFile, lockFile }) {
|
|
238
|
+
if (fs.existsSync(pidFile)) {
|
|
239
|
+
try {
|
|
240
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
241
|
+
if (pid && pid !== process.pid) {
|
|
242
|
+
process.kill(pid, 0);
|
|
243
|
+
return pid;
|
|
244
|
+
}
|
|
245
|
+
} catch { /* stale pid file */ }
|
|
246
|
+
}
|
|
247
|
+
if (fs.existsSync(lockFile)) {
|
|
248
|
+
try {
|
|
249
|
+
const lock = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
250
|
+
const pid = parseInt(lock && lock.pid, 10);
|
|
251
|
+
if (pid && pid !== process.pid) {
|
|
252
|
+
process.kill(pid, 0);
|
|
253
|
+
return pid;
|
|
254
|
+
}
|
|
255
|
+
} catch { /* stale or invalid lock */ }
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function requestDaemonRestart({
|
|
261
|
+
reason = 'manual-restart',
|
|
262
|
+
daemonPidFile = path.join(METAME_DIR, 'daemon.pid'),
|
|
263
|
+
daemonLockFile = path.join(METAME_DIR, 'daemon.lock'),
|
|
264
|
+
daemonScript = path.join(METAME_DIR, 'daemon.js'),
|
|
265
|
+
} = {}) {
|
|
266
|
+
const pid = readRunningDaemonPid({ pidFile: daemonPidFile, lockFile: daemonLockFile });
|
|
267
|
+
if (!pid) return { ok: false, status: 'not_running' };
|
|
268
|
+
|
|
269
|
+
if (process.platform !== 'win32') {
|
|
270
|
+
try {
|
|
271
|
+
process.kill(pid, 'SIGUSR2');
|
|
272
|
+
return { ok: true, status: 'signaled', pid };
|
|
273
|
+
} catch (e) {
|
|
274
|
+
return { ok: false, status: 'signal_failed', pid, error: e.message };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
process.kill(pid, 'SIGTERM');
|
|
280
|
+
} catch (e) {
|
|
281
|
+
return { ok: false, status: 'stop_failed', pid, error: e.message };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let stopped = false;
|
|
285
|
+
for (let i = 0; i < 12; i++) {
|
|
286
|
+
sleepSync(500);
|
|
287
|
+
try { process.kill(pid, 0); } catch { stopped = true; break; }
|
|
288
|
+
}
|
|
289
|
+
if (!stopped) {
|
|
290
|
+
try { process.kill(pid, 'SIGKILL'); } catch { /* already gone */ }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const bg = spawn(process.execPath, [daemonScript], {
|
|
294
|
+
detached: true,
|
|
295
|
+
stdio: 'ignore',
|
|
296
|
+
windowsHide: true,
|
|
297
|
+
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname, METAME_DEPLOY_RESTART_REASON: reason },
|
|
298
|
+
});
|
|
299
|
+
bg.unref();
|
|
300
|
+
return { ok: true, status: 'restarted', pid, nextPid: bg.pid };
|
|
301
|
+
}
|
|
302
|
+
|
|
117
303
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
118
304
|
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
119
305
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
120
|
-
const BUNDLED_BASE_SCRIPTS = ['platform.js', 'signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.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-write.js', 'memory-gc.js', 'qmd-client.js', 'session-summarize.js', 'mentor-engine.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js', 'skill-changelog.js', 'agent-layer.js'];
|
|
306
|
+
const BUNDLED_BASE_SCRIPTS = ['platform.js', 'signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'daemon.js', 'daemon-notify.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-write.js', 'memory-gc.js', 'qmd-client.js', 'session-summarize.js', 'mentor-engine.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js', 'skill-changelog.js', 'agent-layer.js'];
|
|
121
307
|
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
122
308
|
try {
|
|
123
309
|
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
@@ -162,22 +348,36 @@ if (syntaxErrors.length > 0) {
|
|
|
162
348
|
console.error('Fix the errors before deploying. Daemon continues running with old code.');
|
|
163
349
|
} else {
|
|
164
350
|
scriptsUpdated = syncDirFiles(scriptsDir, METAME_DIR, { fileList: BUNDLED_SCRIPTS });
|
|
165
|
-
|
|
166
|
-
// Daemon restart on script update:
|
|
167
|
-
// Don't kill daemon here — daemon's own file watcher detects ~/.metame/daemon.js changes
|
|
168
|
-
// and has defer logic (waits for active Claude tasks to finish before restarting).
|
|
169
|
-
// Killing here bypasses that and interrupts ongoing conversations.
|
|
170
351
|
if (scriptsUpdated) {
|
|
171
|
-
console.log(`${icon("pkg")} Scripts synced to ~/.metame
|
|
352
|
+
console.log(`${icon("pkg")} Scripts ${IS_DEV_MODE ? 'symlinked' : 'synced'} to ~/.metame/.`);
|
|
172
353
|
}
|
|
173
354
|
}
|
|
174
355
|
|
|
175
356
|
// Docs: lazy-load references for CLAUDE.md pointer instructions
|
|
176
357
|
syncDirFiles(path.join(__dirname, 'scripts', 'docs'), path.join(METAME_DIR, 'docs'));
|
|
177
358
|
// Bin: CLI tools (dispatch_to etc.)
|
|
178
|
-
syncDirFiles(path.join(__dirname, 'scripts', 'bin'), path.join(METAME_DIR, 'bin'), { chmod: 0o755 });
|
|
359
|
+
const binUpdated = syncDirFiles(path.join(__dirname, 'scripts', 'bin'), path.join(METAME_DIR, 'bin'), { chmod: 0o755 });
|
|
179
360
|
// Hooks: Claude Code event hooks (Stop, PostToolUse, etc.)
|
|
180
|
-
syncDirFiles(path.join(__dirname, 'scripts', 'hooks'), path.join(METAME_DIR, 'hooks'));
|
|
361
|
+
const hooksUpdated = syncDirFiles(path.join(__dirname, 'scripts', 'hooks'), path.join(METAME_DIR, 'hooks'));
|
|
362
|
+
|
|
363
|
+
const daemonCodeUpdated = scriptsUpdated || binUpdated || hooksUpdated;
|
|
364
|
+
const shouldAutoRestartAfterDeploy = (() => {
|
|
365
|
+
const [cmd] = process.argv.slice(2);
|
|
366
|
+
if (!cmd) return true;
|
|
367
|
+
if (cmd === 'daemon') return false;
|
|
368
|
+
if (['start', 'stop', 'restart', 'status', 'logs'].includes(cmd)) return false;
|
|
369
|
+
return ['codex', 'continue', 'sync'].includes(cmd);
|
|
370
|
+
})();
|
|
371
|
+
if (daemonCodeUpdated && shouldAutoRestartAfterDeploy) {
|
|
372
|
+
const restartResult = requestDaemonRestart({ reason: 'deploy-sync' });
|
|
373
|
+
if (restartResult.ok) {
|
|
374
|
+
console.log(`${icon("reload")} Daemon restart requested after deploy${restartResult.pid ? ` (PID: ${restartResult.pid})` : ''}.`);
|
|
375
|
+
} else if (restartResult.status === 'not_running') {
|
|
376
|
+
console.log(`${icon("info")} Deploy finished. Daemon not running, so restart was skipped.`);
|
|
377
|
+
} else {
|
|
378
|
+
console.log(`${icon("warn")} Deploy finished, but daemon restart failed: ${restartResult.error || restartResult.status}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
181
381
|
|
|
182
382
|
// ---------------------------------------------------------
|
|
183
383
|
// Deploy bundled skills to ~/.claude/skills/
|
|
@@ -217,6 +417,7 @@ if (fs.existsSync(bundledSkillsDir)) {
|
|
|
217
417
|
}
|
|
218
418
|
}
|
|
219
419
|
|
|
420
|
+
|
|
220
421
|
// Ensure ~/.codex/skills and ~/.agents/skills are symlinks to ~/.claude/skills
|
|
221
422
|
// This keeps skill evolution unified across all engines.
|
|
222
423
|
for (const altDir of [
|
|
@@ -348,6 +549,41 @@ function ensureHookInstalled() {
|
|
|
348
549
|
console.log(`${icon("hook")} MetaMe: Stop session capture hook installed.`);
|
|
349
550
|
}
|
|
350
551
|
|
|
552
|
+
// Migrate: remove standalone team-context.js hook (superseded by intent-engine)
|
|
553
|
+
if (settings.hooks?.UserPromptSubmit) {
|
|
554
|
+
const before = settings.hooks.UserPromptSubmit.length;
|
|
555
|
+
for (const entry of settings.hooks.UserPromptSubmit) {
|
|
556
|
+
if (entry.hooks) {
|
|
557
|
+
entry.hooks = entry.hooks.filter(h => !(h.command && h.command.includes('team-context.js')));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
|
|
561
|
+
entry => entry.hooks && entry.hooks.length > 0
|
|
562
|
+
);
|
|
563
|
+
if (settings.hooks.UserPromptSubmit.length !== before) modified = true;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Ensure intent-engine hook (unified intent detection + hint injection)
|
|
567
|
+
const intentEngineScript = path.join(METAME_DIR, 'hooks', 'intent-engine.js').replace(/\\/g, '/');
|
|
568
|
+
const intentEngineCommand = `node "${intentEngineScript}"`;
|
|
569
|
+
const intentEngineInstalled = (settings.hooks?.UserPromptSubmit || []).some(entry =>
|
|
570
|
+
entry.hooks?.some(h => h.command && h.command.includes('intent-engine.js'))
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
if (!intentEngineInstalled) {
|
|
574
|
+
if (!settings.hooks) settings.hooks = {};
|
|
575
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
576
|
+
|
|
577
|
+
settings.hooks.UserPromptSubmit.push({
|
|
578
|
+
hooks: [{
|
|
579
|
+
type: 'command',
|
|
580
|
+
command: intentEngineCommand,
|
|
581
|
+
}]
|
|
582
|
+
});
|
|
583
|
+
modified = true;
|
|
584
|
+
console.log(`${icon("hook")} MetaMe: Intent engine hook installed.`);
|
|
585
|
+
}
|
|
586
|
+
|
|
351
587
|
if (modified) {
|
|
352
588
|
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
|
|
353
589
|
}
|
|
@@ -768,6 +1004,7 @@ try {
|
|
|
768
1004
|
|
|
769
1005
|
// Find a pattern that hasn't been surfaced in 14 days
|
|
770
1006
|
const candidate = brainDoc.growth.patterns.find(p => {
|
|
1007
|
+
if (!p || typeof p.summary !== 'string') return false;
|
|
771
1008
|
if (!p.surfaced) return true;
|
|
772
1009
|
return (now - new Date(p.surfaced).getTime()) > COOLDOWN_MS;
|
|
773
1010
|
});
|
|
@@ -872,26 +1109,11 @@ const KERNEL_BODY = PROTOCOL_NORMAL
|
|
|
872
1109
|
.replace(/^<!-- METAME:START -->\n/, '') // remove project-level marker
|
|
873
1110
|
.trimEnd();
|
|
874
1111
|
|
|
1112
|
+
// Most capability hints migrated to intent engine (on-demand injection).
|
|
1113
|
+
// Only keep Skills here — it's a fallback behavior that can't be keyword-matched.
|
|
875
1114
|
const CAPABILITY_SECTIONS = [
|
|
876
|
-
'## Agent Dispatch',
|
|
877
|
-
'识别到"告诉X/让X/通知X"等转发意图时 → 先 `cat ~/.metame/docs/dispatch-table.md` 获取路由表(昵称→project_key),再执行转发。不要凭记忆猜测昵称对应关系。',
|
|
878
|
-
'',
|
|
879
|
-
'## Agent 创建与管理',
|
|
880
|
-
'用户问创建/管理/绑定 Agent 时 → 先 `cat ~/.metame/docs/agent-guide.md` 再回答。',
|
|
881
|
-
'用户问代码结构/升级进度/脚本入口时 → 先 `cat ~/.metame/docs/pointer-map.md` 再回答。',
|
|
882
|
-
'',
|
|
883
|
-
'## 手机端文件交互',
|
|
884
|
-
'用户要文件("发给我"/"发过来"/"导出")→ 先 `cat ~/.metame/docs/file-transfer.md` 再执行。',
|
|
885
|
-
'**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
|
|
886
|
-
'**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
|
|
887
|
-
'',
|
|
888
|
-
'## 跨会话记忆',
|
|
889
|
-
'用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
|
|
890
|
-
'一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
|
|
891
|
-
'',
|
|
892
1115
|
'## Skills',
|
|
893
1116
|
'能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
|
|
894
|
-
|
|
895
1117
|
].join('\n');
|
|
896
1118
|
|
|
897
1119
|
try {
|
|
@@ -1256,17 +1478,34 @@ if (isInsights) {
|
|
|
1256
1478
|
try {
|
|
1257
1479
|
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
1258
1480
|
const patterns = (doc.growth && doc.growth.patterns) || [];
|
|
1481
|
+
const reflectionPatterns = (doc.growth && doc.growth.self_reflection_patterns) || [];
|
|
1259
1482
|
const zoneHistory = (doc.growth && doc.growth.zone_history) || [];
|
|
1483
|
+
const userPatterns = patterns.filter(p => p && typeof p.summary === 'string');
|
|
1484
|
+
const legacyReflectionPatterns = patterns
|
|
1485
|
+
.filter(p => typeof p === 'string')
|
|
1486
|
+
.map(p => ({ summary: p, detected: null }));
|
|
1487
|
+
const aiReflections = mergeReflectionDisplayEntries([...reflectionPatterns, ...legacyReflectionPatterns]);
|
|
1260
1488
|
|
|
1261
|
-
if (
|
|
1489
|
+
if (userPatterns.length === 0 && aiReflections.length === 0) {
|
|
1262
1490
|
console.log(`${icon("search")} MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.`);
|
|
1263
1491
|
} else {
|
|
1264
1492
|
console.log(`${icon("mirror")} MetaMe Insights:\n`);
|
|
1265
|
-
|
|
1493
|
+
if (userPatterns.length > 0) {
|
|
1494
|
+
console.log('User observation:');
|
|
1495
|
+
}
|
|
1496
|
+
userPatterns.forEach((p, i) => {
|
|
1266
1497
|
const sym = p.type === 'avoidance' ? icon("warn") : p.type === 'growth' ? '+' : p.type === 'energy' ? '*' : icon("reload");
|
|
1267
1498
|
console.log(` ${sym} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
|
|
1268
1499
|
console.log(` Detected: ${p.detected}${p.surfaced ? `, Last shown: ${p.surfaced}` : ''}`);
|
|
1269
1500
|
});
|
|
1501
|
+
if (aiReflections.length > 0) {
|
|
1502
|
+
if (userPatterns.length > 0) console.log('');
|
|
1503
|
+
console.log('AI self-reflection:');
|
|
1504
|
+
aiReflections.forEach((p) => {
|
|
1505
|
+
console.log(` ${icon("thought")} ${p.summary}`);
|
|
1506
|
+
if (p.detected) console.log(` Detected: ${p.detected}`);
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1270
1509
|
if (zoneHistory.length > 0) {
|
|
1271
1510
|
console.log(`\n ${icon("chart")} Recent zone history: ${zoneHistory.join(' → ')}`);
|
|
1272
1511
|
console.log(` (C=Comfort, S=Stretch, P=Panic)`);
|
|
@@ -1474,7 +1713,7 @@ if (isProvider) {
|
|
|
1474
1713
|
// 5.7 DAEMON SUBCOMMANDS
|
|
1475
1714
|
// ---------------------------------------------------------
|
|
1476
1715
|
// Shorthand aliases: `metame start` → `metame daemon start`, etc.
|
|
1477
|
-
const DAEMON_SHORTCUTS = ['start', 'stop', 'status', 'logs'];
|
|
1716
|
+
const DAEMON_SHORTCUTS = ['start', 'stop', 'restart', 'status', 'logs'];
|
|
1478
1717
|
if (DAEMON_SHORTCUTS.includes(process.argv[2])) {
|
|
1479
1718
|
process.argv.splice(2, 0, 'daemon');
|
|
1480
1719
|
}
|
|
@@ -1856,6 +2095,48 @@ WantedBy=default.target
|
|
|
1856
2095
|
process.exit(0);
|
|
1857
2096
|
}
|
|
1858
2097
|
|
|
2098
|
+
if (subCmd === 'restart') {
|
|
2099
|
+
if (!fs.existsSync(DAEMON_CONFIG)) {
|
|
2100
|
+
console.error(`${icon("fail")} No config found. Run: metame daemon init`);
|
|
2101
|
+
process.exit(1);
|
|
2102
|
+
}
|
|
2103
|
+
if (!fs.existsSync(DAEMON_SCRIPT)) {
|
|
2104
|
+
console.error(`${icon("fail")} daemon.js not found. Reinstall MetaMe.`);
|
|
2105
|
+
process.exit(1);
|
|
2106
|
+
}
|
|
2107
|
+
const result = requestDaemonRestart({
|
|
2108
|
+
reason: 'cli-restart',
|
|
2109
|
+
daemonPidFile: DAEMON_PID,
|
|
2110
|
+
daemonLockFile: DAEMON_LOCK,
|
|
2111
|
+
daemonScript: DAEMON_SCRIPT,
|
|
2112
|
+
});
|
|
2113
|
+
if (result.ok) {
|
|
2114
|
+
if (result.status === 'restarted') {
|
|
2115
|
+
console.log(`${icon("ok")} Daemon restarted (old PID: ${result.pid}, new PID: ${result.nextPid})`);
|
|
2116
|
+
} else {
|
|
2117
|
+
console.log(`${icon("ok")} Daemon graceful restart requested (PID: ${result.pid})`);
|
|
2118
|
+
}
|
|
2119
|
+
process.exit(0);
|
|
2120
|
+
}
|
|
2121
|
+
if (result.status === 'not_running') {
|
|
2122
|
+
console.log(`${icon("info")} No daemon running. Starting a fresh daemon instead.`);
|
|
2123
|
+
const isMac = process.platform === 'darwin';
|
|
2124
|
+
const cmd = isMac ? 'caffeinate' : process.execPath;
|
|
2125
|
+
const args = isMac ? ['-i', process.execPath, DAEMON_SCRIPT] : [DAEMON_SCRIPT];
|
|
2126
|
+
const bg = spawn(cmd, args, {
|
|
2127
|
+
detached: true,
|
|
2128
|
+
stdio: 'ignore',
|
|
2129
|
+
windowsHide: true,
|
|
2130
|
+
env: { ...process.env, HOME: HOME_DIR, METAME_ROOT: __dirname, METAME_DEPLOY_RESTART_REASON: 'cli-restart' },
|
|
2131
|
+
});
|
|
2132
|
+
bg.unref();
|
|
2133
|
+
console.log(`${icon("ok")} MetaMe daemon started (PID: ${bg.pid})`);
|
|
2134
|
+
process.exit(0);
|
|
2135
|
+
}
|
|
2136
|
+
console.error(`${icon("fail")} Daemon restart failed: ${result.error || result.status}`);
|
|
2137
|
+
process.exit(1);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
1859
2140
|
if (subCmd === 'status') {
|
|
1860
2141
|
let state = {};
|
|
1861
2142
|
try { state = JSON.parse(fs.readFileSync(DAEMON_STATE, 'utf8')); } catch { /* empty */ }
|
|
@@ -1959,6 +2240,7 @@ WantedBy=default.target
|
|
|
1959
2240
|
console.log(`${icon("book")} Daemon Commands:`);
|
|
1960
2241
|
console.log(" metame start — start background daemon");
|
|
1961
2242
|
console.log(" metame stop — stop daemon");
|
|
2243
|
+
console.log(" metame restart — graceful restart daemon");
|
|
1962
2244
|
console.log(" metame status — show status & budget");
|
|
1963
2245
|
console.log(" metame logs — tail log file");
|
|
1964
2246
|
console.log(" metame daemon init — initialize config");
|
|
@@ -2022,58 +2304,48 @@ if (isCodex) {
|
|
|
2022
2304
|
// ---------------------------------------------------------
|
|
2023
2305
|
// 5.9 CONTINUE/SYNC — resume latest session from terminal
|
|
2024
2306
|
// ---------------------------------------------------------
|
|
2025
|
-
// Usage: exit
|
|
2026
|
-
// Finds the most recent session and
|
|
2307
|
+
// Usage: exit current CLI first, then run `metame continue` from terminal.
|
|
2308
|
+
// Finds the most recent session across Claude/Codex and resumes with the matching engine.
|
|
2027
2309
|
const isSync = process.argv.includes('sync') || process.argv.includes('continue');
|
|
2028
2310
|
if (isSync) {
|
|
2029
2311
|
const projectsRoot = path.join(HOME_DIR, '.claude', 'projects');
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
return fs.readdirSync(dir)
|
|
2037
|
-
.filter(f => f.endsWith('.jsonl'))
|
|
2038
|
-
.map(f => ({ id: f.replace('.jsonl', ''), mtime: fs.statSync(path.join(dir, f)).mtimeMs }))
|
|
2039
|
-
.sort((a, b) => b.mtime - a.mtime)[0] || null;
|
|
2040
|
-
} catch { return null; }
|
|
2041
|
-
};
|
|
2042
|
-
const localBest = findLatest(projDir);
|
|
2043
|
-
// Always scan globally to find the absolute most recent session
|
|
2044
|
-
// (phone /continue may have worked in a different project's session)
|
|
2045
|
-
let globalBest = null;
|
|
2046
|
-
try {
|
|
2047
|
-
for (const d of fs.readdirSync(projectsRoot)) {
|
|
2048
|
-
const s = findLatest(path.join(projectsRoot, d));
|
|
2049
|
-
if (s && (!globalBest || s.mtime > globalBest.mtime)) globalBest = s;
|
|
2050
|
-
}
|
|
2051
|
-
} catch { /* ignore */ }
|
|
2052
|
-
// Use global best if it's more recent than local; prefer local otherwise
|
|
2053
|
-
if (localBest && globalBest && globalBest.mtime > localBest.mtime) {
|
|
2054
|
-
bestSession = globalBest;
|
|
2055
|
-
console.log(` (global session is newer than local — using global)`);
|
|
2056
|
-
} else {
|
|
2057
|
-
bestSession = localBest || globalBest;
|
|
2058
|
-
}
|
|
2059
|
-
} catch { }
|
|
2312
|
+
const cwd = process.cwd();
|
|
2313
|
+
const candidates = [
|
|
2314
|
+
readLatestClaudeSession(projectsRoot, cwd),
|
|
2315
|
+
readLatestCodexSession(cwd),
|
|
2316
|
+
].filter(Boolean);
|
|
2317
|
+
const bestSession = candidates.sort((a, b) => (b.mtime || 0) - (a.mtime || 0))[0] || null;
|
|
2060
2318
|
|
|
2061
2319
|
if (!bestSession) {
|
|
2062
2320
|
console.error('No session found.');
|
|
2063
2321
|
process.exit(1);
|
|
2064
2322
|
}
|
|
2065
2323
|
|
|
2324
|
+
if (bestSession.scope === 'global') {
|
|
2325
|
+
console.log(' (global session is newer than local — using global)');
|
|
2326
|
+
}
|
|
2066
2327
|
console.log(`\n${icon("reload")} Resuming session ${bestSession.id.slice(0, 8)}...\n`);
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
}
|
|
2328
|
+
let syncChild;
|
|
2329
|
+
if (bestSession.engine === 'codex') {
|
|
2330
|
+
syncChild = spawnCodex(['exec', 'resume', bestSession.id], {
|
|
2331
|
+
stdio: 'inherit',
|
|
2332
|
+
env: { ...process.env, METAME_ACTIVE_SESSION: 'true' }
|
|
2333
|
+
});
|
|
2334
|
+
syncChild.on('error', () => {
|
|
2335
|
+
console.error("Could not launch 'codex'. Is Codex CLI installed?");
|
|
2336
|
+
});
|
|
2337
|
+
} else {
|
|
2338
|
+
const providerEnv = (() => { try { return require(path.join(__dirname, 'scripts', 'providers.js')).buildActiveEnv(); } catch { return {}; } })();
|
|
2339
|
+
const resumeArgs = ['--resume', bestSession.id];
|
|
2340
|
+
if (daemonCfg.dangerously_skip_permissions) resumeArgs.push('--dangerously-skip-permissions');
|
|
2341
|
+
syncChild = spawnClaude(resumeArgs, {
|
|
2342
|
+
stdio: 'inherit',
|
|
2343
|
+
env: { ...process.env, ...providerEnv, METAME_ACTIVE_SESSION: 'true' }
|
|
2344
|
+
});
|
|
2345
|
+
syncChild.on('error', () => {
|
|
2346
|
+
console.error("Could not launch 'claude'. Is Claude Code installed?");
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2077
2349
|
syncChild.on('close', (c) => process.exit(c || 0));
|
|
2078
2350
|
return;
|
|
2079
2351
|
}
|
|
@@ -2086,7 +2358,8 @@ if (isSync) {
|
|
|
2086
2358
|
if (process.env.METAME_ACTIVE_SESSION === 'true') {
|
|
2087
2359
|
console.error(`\n${icon("stop")} ACTION BLOCKED: Nested Session Detected`);
|
|
2088
2360
|
console.error(" You are actively running inside a MetaMe session.");
|
|
2089
|
-
console.error(" To reload
|
|
2361
|
+
console.error(" To hot-reload daemon code from this session, run: \x1b[36mtouch ~/.metame/daemon.js\x1b[0m");
|
|
2362
|
+
console.error(" In this dev workspace, \x1b[36mtouch scripts/daemon.js\x1b[0m works too because ~/.metame/daemon.js is symlinked.\n");
|
|
2090
2363
|
process.exit(1);
|
|
2091
2364
|
}
|
|
2092
2365
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "node --test scripts/*.test.js",
|
|
18
18
|
"test:daemon-status": "node --test scripts/daemon-restart-status.test.js",
|
|
19
19
|
"start": "node index.js",
|
|
20
|
-
"sync:plugin": "cp scripts/platform.js scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-engine-runtime.js scripts/daemon-command-router.js scripts/daemon-user-acl.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/daemon-task-envelope.js scripts/task-board.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/usage-classifier.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-write.js scripts/memory-extract.js scripts/memory-search.js scripts/memory-gc.js scripts/memory-nightly-reflect.js scripts/memory-index.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/mentor-engine.js scripts/skill-evolution.js scripts/skill-changelog.js scripts/agent-layer.js scripts/self-reflect.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo 'Plugin scripts synced'",
|
|
20
|
+
"sync:plugin": "cp scripts/platform.js scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-command-session-route.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-engine-runtime.js scripts/daemon-command-router.js scripts/daemon-user-acl.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/daemon-task-envelope.js scripts/task-board.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/usage-classifier.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-write.js scripts/memory-extract.js scripts/memory-search.js scripts/memory-gc.js scripts/memory-nightly-reflect.js scripts/memory-index.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/mentor-engine.js scripts/skill-evolution.js scripts/skill-changelog.js scripts/agent-layer.js scripts/self-reflect.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo 'Plugin scripts synced'",
|
|
21
21
|
"sync:readme": "node scripts/sync-readme.js",
|
|
22
22
|
"restart:daemon": "node index.js stop 2>/dev/null; sleep 1; node index.js start 2>/dev/null || echo '鈿狅笍 Daemon not running or restart failed'",
|
|
23
23
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
package/scripts/agent-layer.js
CHANGED
|
@@ -127,11 +127,13 @@ function tryRemoveExisting(filePath) {
|
|
|
127
127
|
* 3. plain file copy (last resort; note: will not track future changes to target)
|
|
128
128
|
*/
|
|
129
129
|
function createLinkOrMirror(targetPath, linkPath) {
|
|
130
|
-
const relativeTarget = path.relative(path.dirname(linkPath), targetPath) || path.basename(targetPath);
|
|
131
130
|
tryRemoveExisting(linkPath);
|
|
132
131
|
|
|
133
132
|
try {
|
|
134
|
-
|
|
133
|
+
// Use absolute symlinks here: agent layer lives under ~/.metame while workspaces can
|
|
134
|
+
// sit on a different top-level tree (/var, /Volumes, etc). Relative links are brittle
|
|
135
|
+
// across those roots and have produced broken SOUL.md/MEMORY.md views.
|
|
136
|
+
fs.symlinkSync(targetPath, linkPath, 'file');
|
|
135
137
|
return { mode: 'symlink', path: linkPath };
|
|
136
138
|
} catch (symlinkErr) {
|
|
137
139
|
const sameRoot = path.parse(targetPath).root.toLowerCase() === path.parse(linkPath).root.toLowerCase();
|