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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.32",
|
|
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": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test scripts/*.test.js",
|
|
15
15
|
"start": "node index.js",
|
|
16
|
-
"sync:plugin": "cp 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-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-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
16
|
+
"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-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-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
17
|
+
"sync:readme": "node scripts/sync-readme.js",
|
|
17
18
|
"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'",
|
|
18
19
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
|
19
20
|
},
|
|
@@ -633,7 +633,10 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
|
|
|
633
633
|
}, 4000);
|
|
634
634
|
|
|
635
635
|
// Agent nickname routing: "贾维斯" / "小美,帮我..." → switch project session
|
|
636
|
-
|
|
636
|
+
// Strict chats (chat_agent_map bound groups) must NOT switch agents via nickname
|
|
637
|
+
const _strictAgentMap = { ...(config.telegram ? config.telegram.chat_agent_map : {}), ...(config.feishu ? config.feishu.chat_agent_map : {}) };
|
|
638
|
+
const _isStrictChatSession = !!(_strictAgentMap[String(chatId)] || projectKeyFromVirtualChatId(String(chatId)));
|
|
639
|
+
const agentMatch = _isStrictChatSession ? null : routeAgent(prompt, config);
|
|
637
640
|
if (agentMatch) {
|
|
638
641
|
const { key, proj, rest } = agentMatch;
|
|
639
642
|
const projCwd = normalizeCwd(proj.cwd);
|
|
@@ -835,7 +838,7 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
|
|
|
835
838
|
Use these before answering complex questions about MetaMe architecture or past decisions.
|
|
836
839
|
4. Active memory: After confirming a new insight, bug root cause, or user preference, persist it with:
|
|
837
840
|
node ~/.metame/memory-write.js "Entity.sub" "relation_type" "value (20-300 chars)"
|
|
838
|
-
Valid relations: tech_decision, bug_lesson, arch_convention, config_fact, config_change,
|
|
841
|
+
Valid relations: tech_decision, bug_lesson, arch_convention, config_fact, config_change, workflow_rule, project_milestone
|
|
839
842
|
Only write verified facts. Do not write speculative or process-description entries.
|
|
840
843
|
When you observe the user is clearly expert or beginner in a domain, note it in your response and suggest: "要不要把你的 {domain} 水平 ({level}) 记录到能力雷达?"
|
|
841
844
|
5. Task handoff: When suspending a multi-step task or handing off to another agent, write current status to ~/.metame/memory/NOW.md using:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { sleepSync } = require('./platform');
|
|
4
|
+
|
|
3
5
|
function createPidManager(deps) {
|
|
4
|
-
const { fs,
|
|
6
|
+
const { fs, PID_FILE, log } = deps;
|
|
5
7
|
|
|
6
8
|
function killExistingDaemon() {
|
|
7
9
|
if (!fs.existsSync(PID_FILE)) return;
|
|
@@ -12,7 +14,7 @@ function createPidManager(deps) {
|
|
|
12
14
|
log('INFO', `Killed existing daemon (PID: ${oldPid})`);
|
|
13
15
|
for (let i = 0; i < 10; i++) {
|
|
14
16
|
try { process.kill(oldPid, 0); } catch { break; }
|
|
15
|
-
|
|
17
|
+
sleepSync(500);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
} catch {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { classifyTaskUsage } = require('./usage-classifier');
|
|
5
|
+
const { IS_WIN } = require('./platform');
|
|
5
6
|
|
|
6
7
|
const WEEKDAY_INDEX = Object.freeze({
|
|
7
8
|
sun: 0,
|
|
@@ -192,7 +193,30 @@ function createTaskScheduler(deps) {
|
|
|
192
193
|
if (!task.precondition) return { pass: true, context: '' };
|
|
193
194
|
|
|
194
195
|
try {
|
|
195
|
-
|
|
196
|
+
let cmd = task.precondition;
|
|
197
|
+
|
|
198
|
+
// Cross-platform: expand ~ to HOME and handle `test -s` (Unix-only) via Node.js
|
|
199
|
+
cmd = cmd.replace(/^~|(?<=\s)~/g, HOME);
|
|
200
|
+
if (IS_WIN) {
|
|
201
|
+
// `test -s <file>` checks file exists and is non-empty — do it in JS
|
|
202
|
+
const testMatch = cmd.match(/^test\s+-s\s+(.+)$/);
|
|
203
|
+
if (testMatch) {
|
|
204
|
+
const filePath = testMatch[1].trim().replace(/["']/g, '');
|
|
205
|
+
const fs = require('fs');
|
|
206
|
+
try {
|
|
207
|
+
const stat = fs.statSync(filePath);
|
|
208
|
+
if (stat.size > 0) {
|
|
209
|
+
const content = fs.readFileSync(filePath, 'utf8').trim();
|
|
210
|
+
log('INFO', `Precondition passed for ${task.name} (${content.split('\n').length} lines)`);
|
|
211
|
+
return { pass: true, context: content };
|
|
212
|
+
}
|
|
213
|
+
} catch { /* file doesn't exist */ }
|
|
214
|
+
log('INFO', `Precondition failed for ${task.name}: file empty or missing`);
|
|
215
|
+
return { pass: false, context: '' };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const output = execSync(cmd, {
|
|
196
220
|
encoding: 'utf8',
|
|
197
221
|
timeout: 15000,
|
|
198
222
|
maxBuffer: 64 * 1024,
|
|
@@ -296,7 +320,8 @@ function createTaskScheduler(deps) {
|
|
|
296
320
|
|
|
297
321
|
// Script tasks: run a local script directly (e.g. distill.js), no claude -p
|
|
298
322
|
if (task.type === 'script') {
|
|
299
|
-
|
|
323
|
+
const scriptCmd = task.command.replace(/^~|(?<=\s)~/g, HOME);
|
|
324
|
+
log('INFO', `Executing script task: ${task.name} → ${scriptCmd}`);
|
|
300
325
|
try {
|
|
301
326
|
const scriptEnv = {
|
|
302
327
|
...process.env,
|
|
@@ -304,7 +329,7 @@ function createTaskScheduler(deps) {
|
|
|
304
329
|
METAME_INTERNAL_PROMPT: '1',
|
|
305
330
|
};
|
|
306
331
|
delete scriptEnv.CLAUDECODE;
|
|
307
|
-
const output = execSync(
|
|
332
|
+
const output = execSync(scriptCmd, {
|
|
308
333
|
encoding: 'utf8',
|
|
309
334
|
timeout: resolveTimeoutMs(task.timeout, 120),
|
|
310
335
|
maxBuffer: 1024 * 1024,
|
package/scripts/daemon.js
CHANGED
|
@@ -16,6 +16,22 @@
|
|
|
16
16
|
// Suppress Node.js experimental warnings (e.g. SQLite)
|
|
17
17
|
process.removeAllListeners('warning');
|
|
18
18
|
|
|
19
|
+
// Global error handlers — prevent silent event-loop death
|
|
20
|
+
process.on('unhandledRejection', (reason) => {
|
|
21
|
+
try {
|
|
22
|
+
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
23
|
+
const line = `[${new Date().toISOString()}] [ERROR] [UNHANDLED_REJECTION] ${msg}\n`;
|
|
24
|
+
fs.appendFileSync(path.join(os.homedir(), '.metame', 'daemon.log'), line);
|
|
25
|
+
} catch { /* last resort: don't crash the crash handler */ }
|
|
26
|
+
});
|
|
27
|
+
process.on('uncaughtException', (err) => {
|
|
28
|
+
try {
|
|
29
|
+
const line = `[${new Date().toISOString()}] [FATAL] [UNCAUGHT_EXCEPTION] ${err.stack || err.message}\n`;
|
|
30
|
+
fs.appendFileSync(path.join(os.homedir(), '.metame', 'daemon.log'), line);
|
|
31
|
+
} catch { /* last resort */ }
|
|
32
|
+
// Don't exit — let the daemon survive and self-heal via watchdog
|
|
33
|
+
});
|
|
34
|
+
|
|
19
35
|
const fs = require('fs');
|
|
20
36
|
const path = require('path');
|
|
21
37
|
const os = require('os');
|
|
@@ -30,7 +46,8 @@ const LOG_FILE = path.join(METAME_DIR, 'daemon.log');
|
|
|
30
46
|
const BRAIN_FILE = path.join(HOME, '.claude_profile.yaml');
|
|
31
47
|
const DISPATCH_DIR = path.join(METAME_DIR, 'dispatch');
|
|
32
48
|
const DISPATCH_LOG = path.join(DISPATCH_DIR, 'dispatch-log.jsonl');
|
|
33
|
-
const
|
|
49
|
+
const { socketPath, needsSocketCleanup } = require('./platform');
|
|
50
|
+
const SOCK_PATH = socketPath(METAME_DIR);
|
|
34
51
|
|
|
35
52
|
// Resolve claude binary path (daemon may not inherit user's full PATH)
|
|
36
53
|
const CLAUDE_BIN = (() => {
|
|
@@ -975,7 +992,7 @@ function handleDispatchItem(item, config) {
|
|
|
975
992
|
*/
|
|
976
993
|
function startDispatchSocket(getConfig) {
|
|
977
994
|
const net = require('net');
|
|
978
|
-
try { fs.unlinkSync(SOCK_PATH); } catch { /* ok */ }
|
|
995
|
+
if (needsSocketCleanup()) { try { fs.unlinkSync(SOCK_PATH); } catch { /* ok */ } }
|
|
979
996
|
const server = net.createServer((conn) => {
|
|
980
997
|
let buf = '';
|
|
981
998
|
conn.on('data', d => { buf += d; });
|
|
@@ -1611,7 +1628,6 @@ const { startTelegramBridge, startFeishuBridge } = createBridgeStarter({
|
|
|
1611
1628
|
|
|
1612
1629
|
const { killExistingDaemon, writePid, cleanPid } = createPidManager({
|
|
1613
1630
|
fs,
|
|
1614
|
-
execSync,
|
|
1615
1631
|
PID_FILE,
|
|
1616
1632
|
log,
|
|
1617
1633
|
});
|
|
@@ -1820,6 +1836,27 @@ async function main() {
|
|
|
1820
1836
|
process.on('SIGTERM', () => { shutdown().catch(() => process.exit(0)); });
|
|
1821
1837
|
process.on('SIGINT', () => { shutdown().catch(() => process.exit(0)); });
|
|
1822
1838
|
|
|
1839
|
+
// Watchdog: detect heartbeat stall and self-restart
|
|
1840
|
+
const WATCHDOG_INTERVAL = 5 * 60 * 1000; // check every 5 min
|
|
1841
|
+
const HEARTBEAT_STALL_THRESHOLD = 5 * 60 * 1000; // 5 min without heartbeat = stalled
|
|
1842
|
+
setInterval(() => {
|
|
1843
|
+
try {
|
|
1844
|
+
const st = loadState();
|
|
1845
|
+
const lastAlive = st.last_alive ? new Date(st.last_alive).getTime() : 0;
|
|
1846
|
+
const elapsed = Date.now() - lastAlive;
|
|
1847
|
+
if (lastAlive > 0 && elapsed > HEARTBEAT_STALL_THRESHOLD) {
|
|
1848
|
+
log('FATAL', `[WATCHDOG] Heartbeat stalled for ${Math.round(elapsed / 1000)}s — forcing restart`);
|
|
1849
|
+
// Write state before exit so next launch knows why
|
|
1850
|
+
st.watchdog_restart = new Date().toISOString();
|
|
1851
|
+
st.watchdog_stall_seconds = Math.round(elapsed / 1000);
|
|
1852
|
+
saveState(st);
|
|
1853
|
+
process.exit(1); // caffeinate or launchd will restart us
|
|
1854
|
+
}
|
|
1855
|
+
} catch (e) {
|
|
1856
|
+
log('WARN', `[WATCHDOG] Check failed: ${e.message}`);
|
|
1857
|
+
}
|
|
1858
|
+
}, WATCHDOG_INTERVAL).unref();
|
|
1859
|
+
|
|
1823
1860
|
// Keep alive
|
|
1824
1861
|
log('INFO', 'Daemon running. Send SIGTERM to stop.');
|
|
1825
1862
|
}
|
|
@@ -401,7 +401,9 @@ function createBot(config) {
|
|
|
401
401
|
|
|
402
402
|
if (text || fileInfo) {
|
|
403
403
|
// Fire-and-forget: don't block the event loop (SDK needs fast ack)
|
|
404
|
-
Promise.resolve().then(() => onMessage(chatId, text, data, fileInfo, senderId)).catch(() => {
|
|
404
|
+
Promise.resolve().then(() => onMessage(chatId, text, data, fileInfo, senderId)).catch((err) => {
|
|
405
|
+
try { console.error(`[feishu-adapter] onMessage error: ${err && err.message || err}`); } catch { }
|
|
406
|
+
});
|
|
405
407
|
}
|
|
406
408
|
} catch (e) {
|
|
407
409
|
// Non-fatal
|
|
@@ -417,7 +419,9 @@ function createBot(config) {
|
|
|
417
419
|
if (action && chatId) {
|
|
418
420
|
const cmd = action.value && action.value.cmd;
|
|
419
421
|
if (cmd) {
|
|
420
|
-
Promise.resolve().then(() => onMessage(chatId, cmd, data)).catch(() => {
|
|
422
|
+
Promise.resolve().then(() => onMessage(chatId, cmd, data)).catch((err) => {
|
|
423
|
+
try { console.error(`[feishu-adapter] card action error: ${err && err.message || err}`); } catch { }
|
|
424
|
+
});
|
|
421
425
|
}
|
|
422
426
|
}
|
|
423
427
|
} catch (e) {
|
|
@@ -29,8 +29,7 @@ const FACT_EXTRACTION_PROMPT = `你是精准的知识提取引擎。从以下会
|
|
|
29
29
|
- arch_convention(架构约定:系统组件的行为边界)
|
|
30
30
|
- config_fact(配置事实:某个值的真实含义,尤其反直觉的)
|
|
31
31
|
- config_change(配置变更:用户选择/确认了某个具体配置值,如”字体选了x-large”、”间隔改为2h”)
|
|
32
|
-
-
|
|
33
|
-
- workflow_rule(工作流戒律:如”不要在某情况下做某事”的反常识流)
|
|
32
|
+
- workflow_rule(工作流戒律/用户红线:如”不要在某情况下做某事”的反常识流、用户明确表达的项目级红线)
|
|
34
33
|
- project_milestone(项目里程碑:主要架构重构、版本发布等跨会话级成果)
|
|
35
34
|
|
|
36
35
|
绝对不提取:
|
|
@@ -38,6 +37,7 @@ const FACT_EXTRACTION_PROMPT = `你是精准的知识提取引擎。从以下会
|
|
|
38
37
|
- 临时状态("当前正在..."、"这次会话...")
|
|
39
38
|
- 未经验证的猜测("可能是因为..."、"也许...")
|
|
40
39
|
- 显而易见的常识
|
|
40
|
+
- 泛化偏好(沟通风格、代码风格、语言偏好等)——这些由认知Profile自动采集,memory不重复记录
|
|
41
41
|
|
|
42
42
|
输出 JSON 对象,包含会话名称和提取的事实:
|
|
43
43
|
{
|
package/scripts/memory-gc.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 2. search_count < 3
|
|
12
12
|
* 3. superseded_by IS NULL (already-superseded facts excluded)
|
|
13
13
|
* 4. conflict_status IS NULL OR conflict_status = 'OK' (skip CONFLICT/ARCHIVED)
|
|
14
|
-
* 5. relation NOT IN protected set (
|
|
14
|
+
* 5. relation NOT IN protected set (workflow_rule, arch_convention, config_fact never archived)
|
|
15
15
|
*
|
|
16
16
|
* Protected relations are permanently excluded — they are high-value guardrails
|
|
17
17
|
* that must survive regardless of search frequency.
|
|
@@ -32,7 +32,7 @@ const LOCK_FILE = path.join(METAME_DIR, 'memory-gc.lock');
|
|
|
32
32
|
const GC_LOG_FILE = path.join(METAME_DIR, 'memory_gc_log.jsonl');
|
|
33
33
|
|
|
34
34
|
// Relations that are permanently protected from archival
|
|
35
|
-
const PROTECTED_RELATIONS = ['
|
|
35
|
+
const PROTECTED_RELATIONS = ['workflow_rule', 'arch_convention', 'config_fact'];
|
|
36
36
|
|
|
37
37
|
// GC threshold: facts older than this many days are candidates
|
|
38
38
|
const STALE_DAYS = 30;
|
package/scripts/memory-write.js
CHANGED
package/scripts/memory.js
CHANGED
|
@@ -226,7 +226,7 @@ function saveSession({ sessionId, project, scope = null, summary, keywords = '',
|
|
|
226
226
|
|
|
227
227
|
// Relations with "current state" semantics: new value replaces old.
|
|
228
228
|
// Historical relations (tech_decision, bug_lesson, arch_convention, project_milestone) keep all versions.
|
|
229
|
-
const STATEFUL_RELATIONS = new Set(['
|
|
229
|
+
const STATEFUL_RELATIONS = new Set(['config_fact', 'config_change', 'workflow_rule']);
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
232
|
* Save atomic facts extracted from a session.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const IS_WIN = process.platform === 'win32';
|
|
8
|
+
const IS_MAC = process.platform === 'darwin';
|
|
9
|
+
const IS_LINUX = process.platform === 'linux';
|
|
10
|
+
|
|
11
|
+
const HOME = os.homedir();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* IPC socket path: Named Pipe on Windows, Unix Domain Socket elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
function socketPath(metameDir) {
|
|
17
|
+
if (IS_WIN) return `\\\\.\\pipe\\metame-daemon-${os.userInfo().username}`;
|
|
18
|
+
return path.join(metameDir, 'daemon.sock');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cross-platform synchronous sleep.
|
|
23
|
+
* On Windows, sleep command doesn't exist; use a busy-wait with Atomics for short durations.
|
|
24
|
+
*/
|
|
25
|
+
function sleepSync(ms) {
|
|
26
|
+
if (IS_WIN) {
|
|
27
|
+
// Atomics.wait on a SharedArrayBuffer — blocks the thread without shell dependency
|
|
28
|
+
const buf = new SharedArrayBuffer(4);
|
|
29
|
+
const arr = new Int32Array(buf);
|
|
30
|
+
Atomics.wait(arr, 0, 0, ms);
|
|
31
|
+
} else {
|
|
32
|
+
const seconds = ms / 1000;
|
|
33
|
+
execSync(`sleep ${seconds}`, { stdio: 'ignore' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find PIDs matching a command-line pattern.
|
|
39
|
+
* Returns array of PID numbers (excluding current process).
|
|
40
|
+
*/
|
|
41
|
+
function findProcessesByPattern(pattern) {
|
|
42
|
+
const pids = [];
|
|
43
|
+
// Sanitize pattern to prevent command injection
|
|
44
|
+
const safe = pattern.replace(/['"%;$`\\]/g, '');
|
|
45
|
+
try {
|
|
46
|
+
let output;
|
|
47
|
+
if (IS_WIN) {
|
|
48
|
+
// wmic is deprecated but widely available; fallback to tasklist
|
|
49
|
+
try {
|
|
50
|
+
output = execSync(
|
|
51
|
+
`wmic process where "CommandLine like '%${safe}%'" get ProcessId /FORMAT:LIST`,
|
|
52
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
53
|
+
);
|
|
54
|
+
const matches = output.match(/ProcessId=(\d+)/g) || [];
|
|
55
|
+
for (const m of matches) {
|
|
56
|
+
const pid = parseInt(m.split('=')[1], 10);
|
|
57
|
+
if (pid && pid !== process.pid) pids.push(pid);
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// wmic not available, try PowerShell
|
|
61
|
+
output = execSync(
|
|
62
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*${safe}*' } | Select-Object -ExpandProperty ProcessId"`,
|
|
63
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
64
|
+
);
|
|
65
|
+
for (const line of output.trim().split('\n')) {
|
|
66
|
+
const pid = parseInt(line.trim(), 10);
|
|
67
|
+
if (pid && pid !== process.pid) pids.push(pid);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
output = execSync(`pgrep -f '${safe}' 2>/dev/null || true`, { encoding: 'utf8' });
|
|
72
|
+
for (const line of output.trim().split('\n')) {
|
|
73
|
+
const pid = parseInt(line.trim(), 10);
|
|
74
|
+
if (pid && pid !== process.pid) pids.push(pid);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch { /* ignore errors */ }
|
|
78
|
+
return pids;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the socket path needs fs.unlinkSync before server.listen().
|
|
83
|
+
* Named Pipes on Windows are kernel-managed — no file to unlink.
|
|
84
|
+
*/
|
|
85
|
+
function needsSocketCleanup() {
|
|
86
|
+
return !IS_WIN;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* GBK-safe icon mapping for Windows terminals.
|
|
91
|
+
* Chinese Windows terminals use GBK encoding by default,
|
|
92
|
+
* which cannot render emoji — replace with ASCII equivalents.
|
|
93
|
+
*/
|
|
94
|
+
const _icons = IS_WIN ? {
|
|
95
|
+
// status
|
|
96
|
+
ok: '[OK]',
|
|
97
|
+
fail: '[FAIL]',
|
|
98
|
+
warn: '[!]',
|
|
99
|
+
info: '[i]',
|
|
100
|
+
// objects
|
|
101
|
+
pkg: '[PKG]',
|
|
102
|
+
brain: '[*]',
|
|
103
|
+
dna: '[~]',
|
|
104
|
+
new: '[NEW]',
|
|
105
|
+
magic: '[>]',
|
|
106
|
+
bot: '[BOT]',
|
|
107
|
+
green: '[ON]',
|
|
108
|
+
red: '[OFF]',
|
|
109
|
+
hook: '[HOOK]',
|
|
110
|
+
search: '[?]',
|
|
111
|
+
mirror: '[M]',
|
|
112
|
+
plug: '[PLUG]',
|
|
113
|
+
book: '[DOC]',
|
|
114
|
+
pin: '[PIN]',
|
|
115
|
+
arrow: '->',
|
|
116
|
+
down: '[DL]',
|
|
117
|
+
reload: '[R]',
|
|
118
|
+
stop: '[X]',
|
|
119
|
+
chart: '[#]',
|
|
120
|
+
thought: '[.]',
|
|
121
|
+
phone: '[TEL]',
|
|
122
|
+
feishu: '[FS]',
|
|
123
|
+
check: '[v]',
|
|
124
|
+
} : {
|
|
125
|
+
ok: '\u2705', // ✅
|
|
126
|
+
fail: '\u274C', // ❌
|
|
127
|
+
warn: '\u26A0\uFE0F', // ⚠️
|
|
128
|
+
info: '\u2139\uFE0F', // ℹ️
|
|
129
|
+
pkg: '\uD83D\uDCE6', // 📦
|
|
130
|
+
brain: '\uD83E\uDDE0', // 🧠
|
|
131
|
+
dna: '\uD83E\uDDEC', // 🧬
|
|
132
|
+
new: '\uD83C\uDD95', // 🆕
|
|
133
|
+
magic: '\uD83D\uDD2E', // 🔮
|
|
134
|
+
bot: '\uD83E\uDD16', // 🤖
|
|
135
|
+
green: '\uD83D\uDFE2', // 🟢
|
|
136
|
+
red: '\uD83D\uDD34', // 🔴
|
|
137
|
+
hook: '\uD83E\uDE9D', // 🪝
|
|
138
|
+
search: '\uD83D\uDD0D', // 🔍
|
|
139
|
+
mirror: '\uD83E\uDE9E', // 🪞
|
|
140
|
+
plug: '\uD83D\uDD0C', // 🔌
|
|
141
|
+
book: '\uD83D\uDCD6', // 📖
|
|
142
|
+
pin: '\uD83D\uDCCC', // 📌
|
|
143
|
+
arrow: '\u2192', // →
|
|
144
|
+
down: '\u2B07\uFE0F', // ⬇️
|
|
145
|
+
reload: '\uD83D\uDD04', // 🔄
|
|
146
|
+
stop: '\uD83D\uDEAB', // 🚫
|
|
147
|
+
chart: '\uD83D\uDCCA', // 📊
|
|
148
|
+
thought: '\uD83D\uDCAD', // 💭
|
|
149
|
+
phone: '\uD83D\uDCF1', // 📱
|
|
150
|
+
feishu: '\uD83D\uDCD8', // 📘
|
|
151
|
+
check: '\u2714', // ✔
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get a platform-appropriate icon by name.
|
|
156
|
+
* Usage: icon('ok') → '✅' on macOS, '[OK]' on Windows
|
|
157
|
+
*/
|
|
158
|
+
function icon(name) {
|
|
159
|
+
return _icons[name] || name;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
IS_WIN,
|
|
164
|
+
IS_MAC,
|
|
165
|
+
IS_LINUX,
|
|
166
|
+
HOME,
|
|
167
|
+
socketPath,
|
|
168
|
+
sleepSync,
|
|
169
|
+
findProcessesByPattern,
|
|
170
|
+
needsSocketCleanup,
|
|
171
|
+
icon,
|
|
172
|
+
};
|
|
@@ -13,10 +13,17 @@ function mkHome(prefix = 'metame-reliability-') {
|
|
|
13
13
|
return home;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function homeEnv(home) {
|
|
17
|
+
// On Windows, os.homedir() reads USERPROFILE, not HOME
|
|
18
|
+
return process.platform === 'win32'
|
|
19
|
+
? { HOME: home, USERPROFILE: home }
|
|
20
|
+
: { HOME: home };
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
function runNode(home, code, extraEnv = {}) {
|
|
17
24
|
return execFileSync(process.execPath, ['-e', code], {
|
|
18
25
|
cwd: ROOT,
|
|
19
|
-
env: { ...process.env,
|
|
26
|
+
env: { ...process.env, ...homeEnv(home), ...extraEnv },
|
|
20
27
|
encoding: 'utf8',
|
|
21
28
|
timeout: 30000,
|
|
22
29
|
});
|
|
@@ -25,17 +32,22 @@ function runNode(home, code, extraEnv = {}) {
|
|
|
25
32
|
function installFakeClaude(home, body) {
|
|
26
33
|
const bin = path.join(home, 'bin');
|
|
27
34
|
fs.mkdirSync(bin, { recursive: true });
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
const cli = path.join(bin, 'claude.cmd');
|
|
37
|
+
fs.writeFileSync(cli, `@echo off\n${body}\n`, 'utf8');
|
|
38
|
+
return { ...homeEnv(home), PATH: `${bin};${process.env.PATH}` };
|
|
39
|
+
}
|
|
28
40
|
const cli = path.join(bin, 'claude');
|
|
29
41
|
fs.writeFileSync(cli, `#!/bin/sh\n${body}\n`, 'utf8');
|
|
30
42
|
fs.chmodSync(cli, 0o755);
|
|
31
|
-
return { PATH: `${bin}:${process.env.PATH}` };
|
|
43
|
+
return { ...homeEnv(home), PATH: `${bin}:${process.env.PATH}` };
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
function sendSignal(home, prompt, extraEnv = {}) {
|
|
35
47
|
return new Promise((resolve, reject) => {
|
|
36
48
|
const child = spawn(process.execPath, [path.join(ROOT, 'scripts', 'signal-capture.js')], {
|
|
37
49
|
cwd: ROOT,
|
|
38
|
-
env: { ...process.env,
|
|
50
|
+
env: { ...process.env, ...homeEnv(home), ...extraEnv },
|
|
39
51
|
stdio: ['pipe', 'ignore', 'ignore'],
|
|
40
52
|
});
|
|
41
53
|
const timer = setTimeout(() => {
|
package/scripts/schema.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Tiers:
|
|
11
11
|
* T1 — Identity (LOCKED, never auto-modify)
|
|
12
|
-
* T2 —
|
|
12
|
+
* T2 — Soul (LOCKED, 6-dimension personality model)
|
|
13
13
|
* T3 — Preferences (auto-writable, needs confidence)
|
|
14
14
|
* T5 — Evolution (system-managed, strict limits)
|
|
15
15
|
*
|
|
@@ -23,14 +23,47 @@ const SCHEMA = {
|
|
|
23
23
|
'identity.role': { tier: 'T1', type: 'string', locked: false },
|
|
24
24
|
'identity.locale': { tier: 'T1', type: 'string', locked: true },
|
|
25
25
|
|
|
26
|
-
// === T2:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
// === T2: Soul (6-Dimension Model, LOCKED) ===
|
|
27
|
+
|
|
28
|
+
// Dim 1: Values (Schwartz Value Theory)
|
|
29
|
+
'soul.values.primary': { tier: 'T2', type: 'string', locked: true, maxChars: 40 },
|
|
30
|
+
'soul.values.secondary': { tier: 'T2', type: 'string', locked: true, maxChars: 40 },
|
|
31
|
+
'soul.values.anti_value': { tier: 'T2', type: 'string', locked: true, maxChars: 40 },
|
|
32
|
+
|
|
33
|
+
// Dim 2: Drive (Self-Determination Theory)
|
|
34
|
+
'soul.drive.primary_need': { tier: 'T2', type: 'enum', locked: true,
|
|
35
|
+
values: ['autonomy', 'mastery', 'connection', 'impact', 'security', 'novelty', 'meaning'] },
|
|
36
|
+
'soul.drive.flow_trigger': { tier: 'T2', type: 'string', locked: true, maxChars: 60 },
|
|
37
|
+
'soul.drive.north_star.aspiration': { tier: 'T2', type: 'string', locked: true, maxChars: 80 },
|
|
38
|
+
'soul.drive.north_star.realistic': { tier: 'T2', type: 'string', locked: true, maxChars: 80 },
|
|
39
|
+
|
|
40
|
+
// Dim 3: Cognition Style (Jung + Kahneman)
|
|
41
|
+
'soul.cognition_style.thinking_axis': { tier: 'T2', type: 'enum', locked: true,
|
|
42
|
+
values: ['systematic', 'intuitive', 'dialectical'] },
|
|
43
|
+
'soul.cognition_style.learning_mode': { tier: 'T2', type: 'enum', locked: true,
|
|
44
|
+
values: ['by_doing', 'by_modeling', 'by_abstracting', 'by_debating', 'by_reflecting'] },
|
|
45
|
+
'soul.cognition_style.complexity_appetite': { tier: 'T2', type: 'enum', locked: true,
|
|
46
|
+
values: ['reductionist', 'comfortable_with_ambiguity', 'complexity_seeker'] },
|
|
47
|
+
|
|
48
|
+
// Dim 4: Stress & Shadow (Jung Shadow + Resilience Theory)
|
|
49
|
+
'soul.stress.crisis_reflex': { tier: 'T2', type: 'enum', locked: true,
|
|
50
|
+
values: ['fight', 'flight', 'freeze', 'analyze'] },
|
|
51
|
+
'soul.stress.shadow': { tier: 'T2', type: 'string', locked: true, maxChars: 80 },
|
|
52
|
+
'soul.stress.recovery_pattern': { tier: 'T2', type: 'enum', locked: true,
|
|
53
|
+
values: ['solitude', 'social_support', 'physical_action', 'intellectual_distraction', 'sleep_reset'] },
|
|
54
|
+
|
|
55
|
+
// Dim 5: Relational (Attachment Theory + FIRO-B)
|
|
56
|
+
'soul.relational.trust_formation': { tier: 'T2', type: 'enum', locked: true,
|
|
57
|
+
values: ['competence_first', 'character_first', 'shared_experience', 'slow_incremental'] },
|
|
58
|
+
'soul.relational.conflict_style': { tier: 'T2', type: 'enum', locked: true,
|
|
59
|
+
values: ['direct_confrontation', 'strategic_avoidance', 'diplomatic_mediation', 'withdrawal'] },
|
|
60
|
+
'soul.relational.authority_stance': { tier: 'T2', type: 'enum', locked: true,
|
|
61
|
+
values: ['challenge_authority', 'respect_hierarchy', 'pragmatic_compliance', 'build_own_authority'] },
|
|
62
|
+
|
|
63
|
+
// Dim 6: Identity Narrative (McAdams Narrative Identity)
|
|
64
|
+
'soul.identity_narrative.self_in_one_line': { tier: 'T2', type: 'string', locked: true, maxChars: 100 },
|
|
65
|
+
'soul.identity_narrative.core_contradiction': { tier: 'T2', type: 'string', locked: true, maxChars: 80 },
|
|
66
|
+
'soul.identity_narrative.feared_self': { tier: 'T2', type: 'string', locked: true, maxChars: 60 },
|
|
34
67
|
|
|
35
68
|
// === T3: Preferences ===
|
|
36
69
|
'preferences.code_style': { tier: 'T3', type: 'enum', values: ['concise', 'verbose', 'documented'] },
|
|
@@ -80,7 +113,7 @@ const SCHEMA = {
|
|
|
80
113
|
|
|
81
114
|
/**
|
|
82
115
|
* Check if a dotted key matches the schema.
|
|
83
|
-
* Supports wildcard entries
|
|
116
|
+
* Supports wildcard entries (e.g. 'namespace.*') and exact dotted keys.
|
|
84
117
|
*/
|
|
85
118
|
function hasKey(key) {
|
|
86
119
|
if (SCHEMA[key]) return true;
|
|
@@ -12,10 +12,16 @@ function mkHome() {
|
|
|
12
12
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'metame-skill-evo-'));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function homeEnv(home) {
|
|
16
|
+
return process.platform === 'win32'
|
|
17
|
+
? { HOME: home, USERPROFILE: home }
|
|
18
|
+
: { HOME: home };
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
function runWithHome(home, code) {
|
|
16
22
|
return execFileSync(process.execPath, ['-e', code], {
|
|
17
23
|
cwd: ROOT,
|
|
18
|
-
env: { ...process.env,
|
|
24
|
+
env: { ...process.env, ...homeEnv(home) },
|
|
19
25
|
encoding: 'utf8',
|
|
20
26
|
});
|
|
21
27
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* sync-readme.js — Translate README.md (English) → README中文版.md
|
|
6
|
+
*
|
|
7
|
+
* Usage: node scripts/sync-readme.js
|
|
8
|
+
* Or: npm run sync:readme
|
|
9
|
+
*
|
|
10
|
+
* Uses claude CLI to translate. English README is the source of truth.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
18
|
+
const SRC = path.join(ROOT, 'README.md');
|
|
19
|
+
const DST = path.join(ROOT, 'README中文版.md');
|
|
20
|
+
|
|
21
|
+
const english = fs.readFileSync(SRC, 'utf8');
|
|
22
|
+
|
|
23
|
+
const prompt = `You are a professional translator. Translate the following GitHub README from English to Chinese (简体中文).
|
|
24
|
+
|
|
25
|
+
Rules:
|
|
26
|
+
- Keep ALL markdown formatting, links, code blocks, HTML tags, and badges EXACTLY as-is
|
|
27
|
+
- Keep all technical terms, CLI commands, file paths, and config examples in English
|
|
28
|
+
- Translate prose, descriptions, and comments naturally — not word-by-word
|
|
29
|
+
- The first tagline should be: > **住在你电脑里的数字分身。**
|
|
30
|
+
- Change "Your machine, your data" to "不上云。你的机器,你的数据。"
|
|
31
|
+
- Keep the <p align="center"> header block unchanged
|
|
32
|
+
- Output ONLY the translated markdown, no extra explanation
|
|
33
|
+
|
|
34
|
+
Here is the README to translate:
|
|
35
|
+
|
|
36
|
+
${english}`;
|
|
37
|
+
|
|
38
|
+
console.log('Translating README.md → README中文版.md ...');
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const result = execSync(
|
|
42
|
+
`claude -p --model haiku --output-format text`,
|
|
43
|
+
{
|
|
44
|
+
input: prompt,
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
maxBuffer: 1024 * 1024,
|
|
47
|
+
timeout: 120000,
|
|
48
|
+
cwd: ROOT,
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const translated = result.trim();
|
|
53
|
+
|
|
54
|
+
if (translated.length < 500) {
|
|
55
|
+
console.error('Translation too short, likely failed. Aborting.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(DST, translated + '\n', 'utf8');
|
|
60
|
+
console.log(`✅ README中文版.md updated (${translated.length} chars)`);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error('Translation failed:', e.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|