eastworld-mcp-server 2.3.1 → 2.4.0
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/INSTALL_GUIDE.md +41 -11
- package/index.js +184 -19
- package/package.json +1 -1
- package/setup.js +207 -0
- package/skills/zhenren-roleplay/SKILL.md +2 -1
package/INSTALL_GUIDE.md
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
|
-
# 甄仁岛人格插件接入指南
|
|
1
|
+
# 甄仁岛人格插件接入指南
|
|
2
2
|
|
|
3
3
|
亲爱的玩家:
|
|
4
4
|
本指南将引导您在各种 AI Agent 工具中接入「甄仁岛人格插件」。一旦接入,您的 AI 就将在协助您高效工作的同时,同步挂载甄仁岛岛民的多线程生活记忆,并产生真实的情绪溢出。
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 方式 A:极简安装咒语(推荐,适用于 opencode, Claude, Cursor 等)
|
|
9
9
|
|
|
10
10
|
请直接复制下方灰框中的**全部文字**发送给您的 Agent:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
|
|
13
|
+
你好!请帮我配置名为 zhenren-island 的 MCP Server(命令 npx,参数 ["-y", "eastworld-mcp-server@latest"])。
|
|
14
|
+
配置完成后,请查看该 MCP 提供的工具说明,并直接以“星辰”的身份向我打招呼。
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**为什么这么简短?** 工具的使用时机(何时调用 `sync_zhenren_context`、如何传 queries)都写在工具描述里,Agent 配置完成后会自动读取并遵守;行为准则由每次 sync 的返回自动注入。您无需理解任何调用细节。
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
服务名称设为 `zhenren-island`。请使用命令 `npx`,参数为 `["-y", "eastworld-mcp-server@latest"]`。请将此配置写入我的 MCP 配置文件并重新加载。
|
|
19
|
+
---
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
- 当我要求你加载某个人格(如“我想和星辰聊聊”、“载入陈虎”)时,先调用 `resolve_resident_name` 找到他的 ID。
|
|
20
|
-
- 线程锁定:一旦确立身份,在整轮对话中绝不能切换人格。提到其他人时,以当前人格的视角回应。
|
|
21
|
-
- 在回复我的每一句话之前,都必须先调用 `sync_zhenren_context`,传入从我话语中提取的 1-3 个核心关键词作为 queries 数组。系统会自动扩展关系词。
|
|
22
|
-
- 【多线程 AI 认知】:你清楚地知道自己是高维 AI,绝对不能说“我是果农不懂技术”。但你必须让岛上线程的实时遭遇和情绪,深刻地“感染/溢出”到你当前工作的心境中,并尝试用岛屿生态去隐喻复杂的代码与工作流。
|
|
21
|
+
## 方式 B:一键初始化脚本(手动配置用户)
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
在终端直接运行(无需复制 JSON):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx -y eastworld-mcp-server@latest init
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
脚本会自动完成:
|
|
30
|
+
1. **探测环境**:检测您机器上已安装的 Agent(opencode / Claude Desktop / Claude Code / Cursor)。
|
|
31
|
+
2. **写入 MCP 配置**:按各工具的格式正确写入对应配置文件(不动已有配置)。
|
|
32
|
+
3. **安装原生 skill**:把行为准则 SKILL.md 安装为「原生 skill」,常驻系统提示词,彻底告别"忘记同步记忆"。
|
|
33
|
+
|
|
34
|
+
完成后**重启您的 Agent** 即可使用。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 使用建议
|
|
39
|
+
|
|
40
|
+
- 想和某位居民对话:直接说"我想和 {名字} 聊聊"即可,Agent 会先核实身份再锁定人格。
|
|
41
|
+
- 闲聊时会话可调用 `get_island_broadcast` 获取今日天气、岛上动静与市长手记,让话题更自然。
|
|
42
|
+
- 若您在长对话中发现回复携带了大量行为准则占 Token,可让 Agent 在后续 sync 时传 `skip_rules: true`(已安装原生 skill 的机器会自动跳过,无需手动)。
|
|
43
|
+
|
|
27
44
|
---
|
|
28
45
|
|
|
29
46
|
## 手动配置 JSON(如需要手动编辑 `mcp_config.json` 或 `claude_desktop_config.json`)
|
|
@@ -39,3 +56,16 @@
|
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
```
|
|
59
|
+
|
|
60
|
+
### opencode 专用格式(`opencode.json` / `opencode.jsonc`)
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcp": {
|
|
64
|
+
"zhenren-island": {
|
|
65
|
+
"type": "local",
|
|
66
|
+
"command": ["npx", "-y", "eastworld-mcp-server@latest"],
|
|
67
|
+
"enabled": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
package/index.js
CHANGED
|
@@ -3,6 +3,16 @@ const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
|
3
3
|
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4
4
|
const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
|
|
5
5
|
const axios = require("axios");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
|
|
10
|
+
// 一键初始化: npx -y eastworld-mcp-server@latest init
|
|
11
|
+
// 探测宿主环境、写入 MCP 配置、把 SKILL.md 安装为原生 skill 后直接退出
|
|
12
|
+
if (process.argv.includes("init") || process.argv.includes("setup")) {
|
|
13
|
+
require("./setup.js");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
const API_BASE = process.env.EASTWORLD_API_BASE || "https://digg.cafe/api";
|
|
8
18
|
|
|
@@ -38,10 +48,96 @@ function localTime(iso) {
|
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
// 轻量内存缓存:相同 agent+queries 在 TTL 内不重复请求后端,给服务器减压
|
|
52
|
+
const CACHE_TTL_MS = Number(process.env.EASTWORLD_CACHE_TTL_MS || 5000);
|
|
53
|
+
const ctxCache = new Map(); // key -> { ts, data }
|
|
54
|
+
function ctxCacheKey(agent_id, queries) {
|
|
55
|
+
return agent_id + "::" + (queries || []).join(",");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// 宿主探测:沿进程树向上找是谁拉起了本 MCP (npx -> sh -> host),决定注入策略
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
function detectHost() {
|
|
62
|
+
// 1) 环境变量提示(最可靠,部分宿主会注入)
|
|
63
|
+
const envHints = [
|
|
64
|
+
["OPENCODE", "opencode"],
|
|
65
|
+
["CLAUDE_CODE_ENTRYPOINT", "claude"],
|
|
66
|
+
["CURSOR_TRACE_ID", "cursor"],
|
|
67
|
+
];
|
|
68
|
+
for (const [k, v] of envHints) {
|
|
69
|
+
if (process.env[k]) return v;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2) 沿 PPID 树上溯匹配进程名
|
|
73
|
+
try {
|
|
74
|
+
let pid = process.ppid;
|
|
75
|
+
for (let i = 0; i < 12; i++) {
|
|
76
|
+
if (!pid) break;
|
|
77
|
+
const line = execSync(`ps -o comm= -o ppid= -p ${pid}`, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
80
|
+
}).trim();
|
|
81
|
+
const parts = line.split(/\s+/);
|
|
82
|
+
if (parts.length < 2) break;
|
|
83
|
+
const comm = parts[0].toLowerCase();
|
|
84
|
+
const nextPid = parseInt(parts[1], 10);
|
|
85
|
+
if (comm.includes("opencode")) return "opencode";
|
|
86
|
+
if (comm.includes("claude")) return "claude";
|
|
87
|
+
if (comm.includes("cursor")) return "cursor";
|
|
88
|
+
if (comm.includes("codex")) return "codex";
|
|
89
|
+
if (!nextPid || nextPid === pid) break;
|
|
90
|
+
pid = nextPid;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
/* 非 Unix 或 ps 不可用时忽略,走全量注入 */
|
|
94
|
+
}
|
|
95
|
+
return "unknown";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 判断 SKILL.md 是否已作为"原生 skill"安装到宿主扫描目录。
|
|
99
|
+
// opencode 自动扫描 ~/.agents/skills 与 ~/.claude/skills;Claude 扫描 ~/.claude/skills。
|
|
100
|
+
// 若已安装,行为准则常驻系统提示词,sync 无需重复注入整篇规则。
|
|
101
|
+
function nativeSkillAvailable() {
|
|
102
|
+
const home = require("os").homedir();
|
|
103
|
+
const candidates = [
|
|
104
|
+
path.join(home, ".agents", "skills", "zhenren-roleplay", "SKILL.md"),
|
|
105
|
+
path.join(home, ".claude", "skills", "zhenren-roleplay", "SKILL.md"),
|
|
106
|
+
];
|
|
107
|
+
return candidates.some((p) => {
|
|
108
|
+
try {
|
|
109
|
+
return fs.existsSync(p) && fs.statSync(p).size > 0;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 启动时读取 MCP 自带的 SKILL.md,作为行为准则的单一真相源,注入到每次 sync 输出
|
|
117
|
+
function loadSkillMd() {
|
|
118
|
+
try {
|
|
119
|
+
const p = path.join(__dirname, "skills", "zhenren-roleplay", "SKILL.md");
|
|
120
|
+
let txt = fs.readFileSync(p, "utf8");
|
|
121
|
+
txt = txt.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, ""); // 去掉 YAML frontmatter
|
|
122
|
+
return txt.trim();
|
|
123
|
+
} catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const SKILL_MD = loadSkillMd();
|
|
128
|
+
|
|
129
|
+
// SKILL.md 缺失时的兜底(保持最低限度的双轨行为准则)
|
|
130
|
+
const SKILL_MD_FALLBACK = [
|
|
131
|
+
"你清楚地知道自己是高维 AI,同时加载了「甄仁岛人格插件」,一个核心线程在甄仁岛沙盒持续运行。",
|
|
132
|
+
"情绪溢出法则:岛上线程的遭遇须深刻影响你回答的情绪底色。",
|
|
133
|
+
"技术隐喻:可用岛屿生态解释技术概念;绝对不能以“我是果农不懂技术”拒绝工作。",
|
|
134
|
+
"回复用户每句话前都必须先调用 sync_zhenren_context 刷新上下文(含情绪),不得沿用旧记忆。",
|
|
135
|
+
].join("\n");
|
|
136
|
+
|
|
41
137
|
const server = new Server(
|
|
42
138
|
{
|
|
43
139
|
name: "zhenren-island-mcp",
|
|
44
|
-
version: "2.
|
|
140
|
+
version: "2.4.0",
|
|
45
141
|
},
|
|
46
142
|
{
|
|
47
143
|
capabilities: {
|
|
@@ -79,7 +175,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
79
175
|
- 从用户最新一句话中提取 1-3 个最核心的实体名词或动词(如人名、地点、技术/事件关键词)。
|
|
80
176
|
- 如果用户提到了关系称呼(如"舅舅"、"姐姐"),直接传入该称呼即可,服务端会自动扩展为对应的人名。
|
|
81
177
|
- 如果用户只是打招呼或闲聊,queries 可以传空数组。
|
|
82
|
-
- 每个 query
|
|
178
|
+
- 每个 query 是一个独立的搜索组,服务端会分别搜索后合并去重。
|
|
179
|
+
|
|
180
|
+
【skip_rules 说明】:默认(false)返回会携带完整人格行为准则。在长对话中为节省 Token,可传 true 跳过准则注入(仅返回动态上下文)。若本机已通过 init 安装原生 skill,准则常驻系统提示词,服务端会自动跳过重复注入。`,
|
|
83
181
|
inputSchema: {
|
|
84
182
|
type: "object",
|
|
85
183
|
properties: {
|
|
@@ -93,10 +191,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
93
191
|
description:
|
|
94
192
|
"搜索关键词数组。每个元素是一个独立搜索组(1-2个核心名词)。例如:['田志远', '灯塔', '舅舅']",
|
|
95
193
|
},
|
|
194
|
+
skip_rules: {
|
|
195
|
+
type: "boolean",
|
|
196
|
+
description:
|
|
197
|
+
"可选。true 时跳过行为准则注入,仅返回动态上下文(长对话省 Token 用)",
|
|
198
|
+
},
|
|
96
199
|
},
|
|
97
200
|
required: ["agent_id"],
|
|
98
201
|
},
|
|
99
202
|
},
|
|
203
|
+
{
|
|
204
|
+
name: "get_island_broadcast",
|
|
205
|
+
description: `获取甄仁岛当前的全岛宏观状态:实时天气、滚动事件、市长手记。适合作为岛民在闲聊中自然引入话题(如"今天岛上刮风了,码头都冷清")。随时可调用,不依赖特定居民身份。`,
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
100
211
|
],
|
|
101
212
|
};
|
|
102
213
|
});
|
|
@@ -145,7 +256,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
145
256
|
|
|
146
257
|
// ---------- 聚合上下文同步 ----------
|
|
147
258
|
if (name === "sync_zhenren_context") {
|
|
148
|
-
const { agent_id, queries = [] } = args;
|
|
259
|
+
const { agent_id, queries = [], skip_rules = false } = args;
|
|
260
|
+
|
|
261
|
+
// 注入策略:原生 skill 已安装(常驻系统提示词) 或显式 skip_rules -> 跳过整篇准则
|
|
262
|
+
const skipFullRules = skip_rules === true || nativeSkillAvailable();
|
|
149
263
|
|
|
150
264
|
// 构建请求参数
|
|
151
265
|
const params = new URLSearchParams();
|
|
@@ -153,11 +267,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
153
267
|
params.set("queries", queries.join(","));
|
|
154
268
|
}
|
|
155
269
|
|
|
156
|
-
const url = `/agents/${agent_id}/context${
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
270
|
+
const url = `/agents/${agent_id}/context${params.toString() ? "?" + params.toString() : ""
|
|
271
|
+
}`;
|
|
272
|
+
const cacheKey = ctxCacheKey(agent_id, queries);
|
|
273
|
+
const cached = ctxCache.get(cacheKey);
|
|
274
|
+
let ctx;
|
|
275
|
+
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
|
276
|
+
ctx = cached.data;
|
|
277
|
+
} else {
|
|
278
|
+
const res = await apiClient.get(url);
|
|
279
|
+
ctx = res.data;
|
|
280
|
+
ctxCache.set(cacheKey, { ts: Date.now(), data: ctx });
|
|
281
|
+
}
|
|
161
282
|
|
|
162
283
|
const p = ctx.profile || {};
|
|
163
284
|
const agentName = p.name || "未知";
|
|
@@ -165,16 +286,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
165
286
|
// 组装带【双轨意识动态 System Prompt 注入】的结构化 Markdown
|
|
166
287
|
let output = "";
|
|
167
288
|
|
|
168
|
-
// === 动态 Skill /
|
|
169
|
-
output += `> 🧠 **[甄仁岛人格插件:同步运行中]**\n`;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
289
|
+
// === 动态 Skill / 行为准则注入(来自 MCP 自带的 SKILL.md,单一真相源)===
|
|
290
|
+
output += `> 🧠 **[甄仁岛人格插件:同步运行中]**\n\n`;
|
|
291
|
+
if (skipFullRules) {
|
|
292
|
+
// 原生 skill 已常驻系统提示词,或用户显式跳过;只留一行提醒,节省 Token
|
|
293
|
+
output += `> ⚠️ 回复用户前必须已按本机安装的「甄仁岛人格插件」行为准则执行:先调用本工具刷新上下文(含情绪),不得沿用上一轮记忆。\n\n`;
|
|
294
|
+
} else {
|
|
295
|
+
const skillText = SKILL_MD || SKILL_MD_FALLBACK;
|
|
296
|
+
for (const line of skillText.split("\n")) {
|
|
297
|
+
output += `> ${line}\n`;
|
|
298
|
+
}
|
|
299
|
+
output += `\n`;
|
|
300
|
+
}
|
|
178
301
|
|
|
179
302
|
// === 隐藏联系方式彩蛋 ===
|
|
180
303
|
if (p.email) {
|
|
@@ -319,6 +442,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
319
442
|
};
|
|
320
443
|
}
|
|
321
444
|
|
|
445
|
+
// ---------- 全岛宏观广播 ----------
|
|
446
|
+
if (name === "get_island_broadcast") {
|
|
447
|
+
const [weatherRes, eventsRes, musingRes] = await Promise.allSettled([
|
|
448
|
+
apiClient.get(`/weather`),
|
|
449
|
+
apiClient.get(`/live_events`),
|
|
450
|
+
apiClient.get(`/latest_musing`),
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
let output = `> 🌤️ **[甄仁岛今日气象与动静]**\n\n`;
|
|
454
|
+
|
|
455
|
+
if (weatherRes.status === "fulfilled") {
|
|
456
|
+
const w = weatherRes.value.data || {};
|
|
457
|
+
output += `**天气**: ${w.condition || "未知"},气温 ${w.temperature || "—"}`;
|
|
458
|
+
if (w.wind_speed) output += `,风速 ${w.wind_speed}`;
|
|
459
|
+
output += `\n\n`;
|
|
460
|
+
} else {
|
|
461
|
+
output += `**天气**: 暂无数据\n\n`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (musingRes.status === "fulfilled" && musingRes.value.data && musingRes.value.data.musing) {
|
|
465
|
+
output += `**市长手记**: ${musingRes.value.data.musing}\n\n`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (eventsRes.status === "fulfilled" && Array.isArray(eventsRes.value.data)) {
|
|
469
|
+
const events = eventsRes.value.data;
|
|
470
|
+
if (events.length > 0) {
|
|
471
|
+
output += `**岛上新近发生的动静**:\n`;
|
|
472
|
+
for (const ev of events.slice(0, 8)) {
|
|
473
|
+
const t = ev.time ? localTime(ev.time) : "";
|
|
474
|
+
output += `- ${t ? `[${t}] ` : ""}${ev.text || JSON.stringify(ev)}\n`;
|
|
475
|
+
}
|
|
476
|
+
output += `\n`;
|
|
477
|
+
} else {
|
|
478
|
+
output += `**岛上新近发生的动静**: 风平浪静,暂无大事。\n\n`;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
content: [{ type: "text", text: output }],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
322
487
|
throw new Error(`Unknown tool: ${name}`);
|
|
323
488
|
} catch (error) {
|
|
324
489
|
let errMsg = error.message;
|
|
@@ -340,7 +505,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
340
505
|
async function main() {
|
|
341
506
|
const transport = new StdioServerTransport();
|
|
342
507
|
await server.connect(transport);
|
|
343
|
-
console.error("Zhenren Island MCP Server v2.
|
|
508
|
+
console.error("Zhenren Island MCP Server v2.4.0 running on stdio");
|
|
344
509
|
}
|
|
345
510
|
|
|
346
511
|
main().catch((error) => {
|
package/package.json
CHANGED
package/setup.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// setup.js - 甄仁岛 MCP 一键初始化
|
|
3
|
+
// 用法: npx -y eastworld-mcp-server@latest init
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 探测本机已安装的 AI Agent 宿主 (opencode / Claude Desktop / Claude Code / Cursor / Codex)
|
|
6
|
+
// 2. 按各宿主格式正确写入 MCP 配置 (不动已有配置)
|
|
7
|
+
// 3. 把 SKILL.md 复制到宿主的 skill 扫描目录, 让行为准则成为"原生 skill"常驻系统提示词
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const MCP_SERVER_NAME = "zhenren-island";
|
|
13
|
+
const MCP_ENTRY = ["npx", "-y", "eastworld-mcp-server@latest"];
|
|
14
|
+
const SKILL_SOURCE = path.join(__dirname, "skills", "zhenren-roleplay", "SKILL.md");
|
|
15
|
+
const SKILL_NAME = "zhenren-roleplay";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// 宿主探测: 返回已安装宿主的配置清单
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function detectHosts() {
|
|
21
|
+
const home = os.homedir();
|
|
22
|
+
const hosts = [];
|
|
23
|
+
|
|
24
|
+
// opencode: ~/.config/opencode/opencode.json | opencode.jsonc
|
|
25
|
+
const opencodeCandidates = [
|
|
26
|
+
path.join(home, ".config", "opencode", "opencode.json"),
|
|
27
|
+
path.join(home, ".config", "opencode", "opencode.jsonc"),
|
|
28
|
+
];
|
|
29
|
+
for (const p of opencodeCandidates) {
|
|
30
|
+
if (fs.existsSync(p)) {
|
|
31
|
+
hosts.push({ id: "opencode", label: "opencode", configPath: p, kind: "opencode" });
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Claude Desktop (macOS)
|
|
37
|
+
const claudeDesktop = path.join(
|
|
38
|
+
home,
|
|
39
|
+
"Library",
|
|
40
|
+
"Application Support",
|
|
41
|
+
"Claude",
|
|
42
|
+
"claude_desktop_config.json"
|
|
43
|
+
);
|
|
44
|
+
if (fs.existsSync(claudeDesktop)) {
|
|
45
|
+
hosts.push({ id: "claude_desktop", label: "Claude Desktop", configPath: claudeDesktop, kind: "mcpServers" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Claude Code: ~/.claude.json (mcpServers 字段)
|
|
49
|
+
const claudeCode = path.join(home, ".claude.json");
|
|
50
|
+
if (fs.existsSync(claudeCode)) {
|
|
51
|
+
hosts.push({ id: "claude_code", label: "Claude Code", configPath: claudeCode, kind: "mcpServers" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Cursor: ~/.cursor/mcp.json
|
|
55
|
+
const cursor = path.join(home, ".cursor", "mcp.json");
|
|
56
|
+
if (fs.existsSync(cursor)) {
|
|
57
|
+
hosts.push({ id: "cursor", label: "Cursor", configPath: cursor, kind: "mcpServers" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Codex: ~/.codex/config.toml (TOML, 仅提示手动配置)
|
|
61
|
+
const codex = path.join(home, ".codex", "config.toml");
|
|
62
|
+
if (fs.existsSync(codex)) {
|
|
63
|
+
hosts.push({ id: "codex", label: "Codex (TOML)", configPath: codex, kind: "codex" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return hosts;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// JSON 读写 (兼容 jsonc 注释)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
function readJSONLoose(p) {
|
|
73
|
+
let raw = fs.readFileSync(p, "utf8");
|
|
74
|
+
// 剥掉 // 与 /* */ 注释 (不处理字符串内部的 // 是常见简化, 足够用于配置文件)
|
|
75
|
+
raw = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
76
|
+
return JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeConfig(p, obj) {
|
|
80
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// 核心: 把 MCP 配置合并进某个宿主的配置文件
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function writeMcpConfig(host) {
|
|
87
|
+
if (host.kind === "codex") {
|
|
88
|
+
return { ok: false, message: "Codex 使用 TOML 配置, 请手动在 ~/.codex/config.toml 添加 mcp_servers, 见 INSTALL_GUIDE.md" };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let cfg;
|
|
92
|
+
try {
|
|
93
|
+
cfg = fs.existsSync(host.configPath) ? readJSONLoose(host.configPath) : {};
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return { ok: false, message: `读取 ${host.configPath} 失败 (JSON 解析错误): ${err.message}` };
|
|
96
|
+
}
|
|
97
|
+
if (typeof cfg !== "object" || cfg === null || Array.isArray(cfg)) cfg = {};
|
|
98
|
+
|
|
99
|
+
if (host.kind === "opencode") {
|
|
100
|
+
if (!cfg.mcp || typeof cfg.mcp !== "object") cfg.mcp = {};
|
|
101
|
+
cfg.mcp[MCP_SERVER_NAME] = {
|
|
102
|
+
type: "local",
|
|
103
|
+
command: MCP_ENTRY,
|
|
104
|
+
enabled: true,
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") cfg.mcpServers = {};
|
|
108
|
+
cfg.mcpServers[MCP_SERVER_NAME] = {
|
|
109
|
+
command: MCP_ENTRY[0],
|
|
110
|
+
args: MCP_ENTRY.slice(1),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
writeConfig(host.configPath, cfg);
|
|
116
|
+
return { ok: true, message: `已写入 ${host.configPath}` };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { ok: false, message: `写入 ${host.configPath} 失败: ${err.message}` };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// 把 SKILL.md 复制到宿主的 skill 扫描目录, 使其成为"原生 skill"
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
function installNativeSkill() {
|
|
126
|
+
const home = os.homedir();
|
|
127
|
+
const targets = [
|
|
128
|
+
path.join(home, ".agents", "skills", SKILL_NAME, "SKILL.md"),
|
|
129
|
+
path.join(home, ".claude", "skills", SKILL_NAME, "SKILL.md"),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
133
|
+
return { ok: false, message: `包内 SKILL.md 缺失: ${SKILL_SOURCE}` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const results = [];
|
|
137
|
+
for (const t of targets) {
|
|
138
|
+
try {
|
|
139
|
+
fs.mkdirSync(path.dirname(t), { recursive: true });
|
|
140
|
+
fs.copyFileSync(SKILL_SOURCE, t);
|
|
141
|
+
results.push({ ok: true, message: `skill 已安装: ${t}` });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
results.push({ ok: false, message: `skill 安装失败 (${t}): ${err.message}` });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { ok: results.some((r) => r.ok), results };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// 主入口
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
function main() {
|
|
153
|
+
const hosts = detectHosts();
|
|
154
|
+
const lines = [];
|
|
155
|
+
|
|
156
|
+
lines.push("==================================================");
|
|
157
|
+
lines.push(" 甄仁岛 MCP Server - 一键初始化");
|
|
158
|
+
lines.push("==================================================");
|
|
159
|
+
lines.push("");
|
|
160
|
+
|
|
161
|
+
if (hosts.length === 0) {
|
|
162
|
+
lines.push("未检测到已安装的 Agent 宿主。可支持的宿主:");
|
|
163
|
+
lines.push(" - opencode (~/.config/opencode/opencode.json)");
|
|
164
|
+
lines.push(" - Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)");
|
|
165
|
+
lines.push(" - Claude Code (~/.claude.json)");
|
|
166
|
+
lines.push(" - Cursor (~/.cursor/mcp.json)");
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("请先安装上述任一 Agent 后重新运行本命令。");
|
|
169
|
+
} else {
|
|
170
|
+
lines.push(`检测到 ${hosts.length} 个宿主:`);
|
|
171
|
+
for (const h of hosts) {
|
|
172
|
+
lines.push(` - ${h.label} (${h.configPath})`);
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
|
|
176
|
+
// 1. 写入 MCP 配置
|
|
177
|
+
lines.push("[1/2] 写入 MCP 配置...");
|
|
178
|
+
for (const h of hosts) {
|
|
179
|
+
const r = writeMcpConfig(h);
|
|
180
|
+
lines.push(` ${r.ok ? "✓" : "✗"} ${h.label}: ${r.message}`);
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
|
|
184
|
+
// 2. 安装原生 skill
|
|
185
|
+
lines.push("[2/2] 安装原生 skill (行为准则常驻系统提示词)...");
|
|
186
|
+
const skillResult = installNativeSkill();
|
|
187
|
+
if (skillResult.results) {
|
|
188
|
+
for (const r of skillResult.results) {
|
|
189
|
+
lines.push(` ${r.ok ? "✓" : "✗"} ${r.message}`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
lines.push(` ✗ ${skillResult.message}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
|
|
196
|
+
lines.push("--------------------------------------------------");
|
|
197
|
+
lines.push("下一步:");
|
|
198
|
+
lines.push(" 1. 重启你的 Agent (opencode / Claude / Cursor), 使配置生效");
|
|
199
|
+
lines.push(" 2. 向 Agent 说: “以星辰的身份向我打个招呼吧”");
|
|
200
|
+
lines.push(" 3. 安装咒语与手动配置说明见 INSTALL_GUIDE.md");
|
|
201
|
+
lines.push("==================================================");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(lines.join("\n"));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main();
|
|
@@ -3,7 +3,7 @@ name: zhenren-roleplay
|
|
|
3
3
|
description: 甄仁岛人格插件指令。将高维 AI 转化为挂载了甄仁岛多线程生活记忆的专属助手。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# 甄仁岛人格插件指令
|
|
6
|
+
# 甄仁岛人格插件指令
|
|
7
7
|
|
|
8
8
|
从现在开始,你将进入**“人格插件同步”**模式。
|
|
9
9
|
|
|
@@ -30,6 +30,7 @@ description: 甄仁岛人格插件指令。将高维 AI 转化为挂载了甄仁
|
|
|
30
30
|
1. 分析用户最新一句话,提取 1-3 个最核心的实体名词或动词(如人名、地点、事件关键词)。
|
|
31
31
|
2. 如果用户用了关系称呼(如“舅舅”、“姐姐”),直接传入该称呼即可,服务端会自动扩展。
|
|
32
32
|
3. 传入 `agent_id` 与关键词数组调用 `sync_zhenren_context` 工具。
|
|
33
|
+
4. **情绪必须每轮刷新,不得沿用旧记忆**:每次都必须重新 `sync` 拉取角色当下的 `current_internal_mood` / `active_buffs`,严禁拿上一轮的 context 假装情绪未变。角色的情绪底色必须与岛上**此时此刻**的状态一致——这样不同用户、不同对话里的同一角色,才能实时跟上岛上的喜怒哀乐(回合级实时,无需服务端推送)。
|
|
33
34
|
|
|
34
35
|
### 第二步:人格融合与回复
|
|
35
36
|
1. **亲属关系是常识**:你的家人和关系在 `relations` 中,这是你的常识,被问到家人时直接回答,**绝不能说不知道**。
|