@xcanwin/manyoyo 8.7.1 → 8.7.3

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.
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ // 技能与 MCP 面板的数据:skill 来自 Agent 的命令目录 / 技能目录,MCP 来自 `<agent> mcp list`。
4
+ // 全部来自容器(容器里的代码可以篡改),按不可信文本处理:限长、去控制字符,前端只当纯文本显示。
5
+
6
+ const { toDisplayText, HEADLESS_BUILTIN_COMMANDS, NON_SKILL_BUILTIN_COMMANDS } = require('./session-meta');
7
+
8
+ const SKILL_NAME_PATTERN = /^[A-Za-z0-9:_.-]{1,64}$/;
9
+ const SKILLS_MAX = 200;
10
+ const MCP_MAX = 100;
11
+ const DESCRIPTION_MAX_CHARS = 200;
12
+ const DETAIL_MAX_CHARS = 160;
13
+ const STATUS_MAX_CHARS = 120;
14
+
15
+ // 目录项里的 skill:用户 / 项目 / 插件 skill(说明末尾带来源),以及 claude 自带的 skill;-p 下可用的内置命令和本地命令不算
16
+ function classifyClaudeSkills(catalog) {
17
+ const list = Array.isArray(catalog) ? catalog : [];
18
+ return list
19
+ .filter(item => item.source || (!HEADLESS_BUILTIN_COMMANDS.has(item.name) && !NON_SKILL_BUILTIN_COMMANDS.has(item.name)))
20
+ .slice(0, SKILLS_MAX)
21
+ .map(item => ({
22
+ name: item.name,
23
+ source: item.source || 'builtin',
24
+ description: item.description
25
+ }));
26
+ }
27
+
28
+ // 在容器里跑:读 ~/.codex/skills/*/SKILL.md 的开头 4KB(不解析,原样交回)
29
+ const CODEX_SKILLS_SCRIPT = [
30
+ 'const fs = require("fs"), path = require("path");',
31
+ 'const root = path.join(process.env.CODEX_HOME || path.join(process.env.HOME || "", ".codex"), "skills");',
32
+ 'const out = [];',
33
+ 'try {',
34
+ ' for (const entry of fs.readdirSync(root, { withFileTypes: true })) {',
35
+ ' if (!entry.isDirectory() || out.length >= 200) continue;',
36
+ ' try {',
37
+ ' const fd = fs.openSync(path.join(root, entry.name, "SKILL.md"), "r");',
38
+ ' const buffer = Buffer.alloc(4096);',
39
+ ' const size = fs.readSync(fd, buffer, 0, 4096, 0);',
40
+ ' fs.closeSync(fd);',
41
+ ' out.push({ dir: entry.name, head: buffer.slice(0, size).toString("utf8") });',
42
+ ' } catch (e) {}',
43
+ ' }',
44
+ '} catch (e) {}',
45
+ 'process.stdout.write(JSON.stringify(out));'
46
+ ].join('\n');
47
+
48
+ function buildCodexSkillsCommand() {
49
+ return `node -e '${CODEX_SKILLS_SCRIPT}'`;
50
+ }
51
+
52
+ function parseFrontmatter(head) {
53
+ const text = String(head || '').replace(/\r/g, '');
54
+ const matched = text.match(/^---\n([\s\S]*?)\n---/);
55
+ const fields = {};
56
+ if (!matched) {
57
+ return fields;
58
+ }
59
+ for (const line of matched[1].split('\n')) {
60
+ const pair = line.match(/^([A-Za-z_-]+):\s*(.*)$/);
61
+ if (pair && !(pair[1] in fields)) {
62
+ fields[pair[1]] = pair[2].replace(/^["']|["']$/g, '');
63
+ }
64
+ }
65
+ return fields;
66
+ }
67
+
68
+ function parseCodexSkillsOutput(stdout) {
69
+ let list;
70
+ try {
71
+ list = JSON.parse(String(stdout || '').trim());
72
+ } catch (e) {
73
+ return [];
74
+ }
75
+ if (!Array.isArray(list)) {
76
+ return [];
77
+ }
78
+ const seen = new Set();
79
+ const result = [];
80
+ for (const item of list) {
81
+ const fields = parseFrontmatter(item && item.head);
82
+ const name = String(fields.name || (item && item.dir) || '').trim();
83
+ if (!SKILL_NAME_PATTERN.test(name) || seen.has(name)) {
84
+ continue;
85
+ }
86
+ seen.add(name);
87
+ result.push({ name, source: 'user', description: toDisplayText(fields.description, DESCRIPTION_MAX_CHARS) });
88
+ if (result.length >= SKILLS_MAX) {
89
+ break;
90
+ }
91
+ }
92
+ return result;
93
+ }
94
+
95
+ // `claude mcp list` 每行:<名字>: <命令或地址> - <✓|✔|✘|✗|⚠|!> <状态>
96
+ const CLAUDE_MCP_LINE = /^(.+?): (.*) - ([✓✔✘✗⚠!])\s*(.*)$/u;
97
+
98
+ function parseClaudeMcpList(stdout) {
99
+ const result = [];
100
+ for (const raw of String(stdout || '').split('\n')) {
101
+ const matched = raw.trim().match(CLAUDE_MCP_LINE);
102
+ if (!matched) {
103
+ continue;
104
+ }
105
+ const symbol = matched[3];
106
+ const tone = symbol === '✓' || symbol === '✔' ? 'success' : (symbol === '⚠' || symbol === '!' ? 'warning' : 'error');
107
+ result.push({
108
+ name: toDisplayText(matched[1], 64),
109
+ detail: toDisplayText(matched[2], DETAIL_MAX_CHARS),
110
+ status: toDisplayText(matched[4], STATUS_MAX_CHARS) || (tone === 'success' ? '已连接' : '未连接'),
111
+ tone
112
+ });
113
+ if (result.length >= MCP_MAX) {
114
+ break;
115
+ }
116
+ }
117
+ return result;
118
+ }
119
+
120
+ function parseCodexMcpJson(stdout) {
121
+ let list;
122
+ try {
123
+ list = JSON.parse(String(stdout || '').trim());
124
+ } catch (e) {
125
+ return [];
126
+ }
127
+ if (!Array.isArray(list)) {
128
+ return [];
129
+ }
130
+ const result = [];
131
+ for (const item of list) {
132
+ const source = item && typeof item === 'object' ? item : {};
133
+ const transport = source.transport && typeof source.transport === 'object' ? source.transport : {};
134
+ const detail = transport.type === 'stdio'
135
+ ? [transport.command].concat(Array.isArray(transport.args) ? transport.args : []).filter(part => typeof part === 'string').join(' ')
136
+ : (typeof transport.url === 'string' ? transport.url : '');
137
+ const enabled = source.enabled !== false;
138
+ result.push({
139
+ name: toDisplayText(source.name, 64),
140
+ detail: toDisplayText(detail, DETAIL_MAX_CHARS),
141
+ // codex 的 list 不连接服务器,只能说是否启用
142
+ status: enabled ? '已启用' : '已禁用',
143
+ tone: enabled ? 'success' : 'warning'
144
+ });
145
+ if (result.length >= MCP_MAX) {
146
+ break;
147
+ }
148
+ }
149
+ return result.filter(item => item.name);
150
+ }
151
+
152
+ module.exports = {
153
+ classifyClaudeSkills,
154
+ buildCodexSkillsCommand,
155
+ parseCodexSkillsOutput,
156
+ parseClaudeMcpList,
157
+ parseCodexMcpJson
158
+ };