@lesliechan721/aw 0.1.0-beta.34 → 0.1.0-beta.36
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/dist/scripts/test-plugin-scripts.js +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +1 -0
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/operation-plan.d.ts +19 -0
- package/dist/src/commands/operation-plan.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +1 -0
- package/dist/src/commands/rebuild-command.js +890 -8
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +12 -0
- package/dist/src/commands/rebuild-integration.js +88 -2
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/status-v6.js +109 -19
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/wizard.d.ts +5 -1
- package/dist/src/commands/wizard.js +4 -2
- package/dist/src/commands/wizard.js.map +1 -1
- package/dist/src/commands/write-commands.js +3 -0
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/index.js +57 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/portable/materializer.js +19 -3
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/review/review-profile.js +19 -2
- package/dist/src/review/review-profile.js.map +1 -1
- package/dist/src/tui/components.d.ts +39 -0
- package/dist/src/tui/components.js +51 -18
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/config-array-draft.d.ts +29 -0
- package/dist/src/tui/config-array-draft.js +48 -0
- package/dist/src/tui/config-array-draft.js.map +1 -0
- package/dist/src/tui/config-value-presentation.d.ts +1 -0
- package/dist/src/tui/config-value-presentation.js +17 -1
- package/dist/src/tui/config-value-presentation.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +1742 -1368
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/content-item-card.d.ts +13 -0
- package/dist/src/tui/content-item-card.js +51 -0
- package/dist/src/tui/content-item-card.js.map +1 -0
- package/dist/src/tui/controller-runtime.js +45 -4
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +18 -0
- package/dist/src/tui/controller.js +168 -12
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph-view.d.ts +39 -0
- package/dist/src/tui/dependency-graph-view.js +121 -0
- package/dist/src/tui/dependency-graph-view.js.map +1 -0
- package/dist/src/tui/dependency-graph.js +16 -19
- package/dist/src/tui/dependency-graph.js.map +1 -1
- package/dist/src/tui/entity-cards.d.ts +55 -0
- package/dist/src/tui/entity-cards.js +220 -0
- package/dist/src/tui/entity-cards.js.map +1 -0
- package/dist/src/tui/input-display.js +10 -1
- package/dist/src/tui/input-display.js.map +1 -1
- package/dist/src/tui/model.d.ts +11 -0
- package/dist/src/tui/model.js +20 -4
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.d.ts +2 -18
- package/dist/src/tui/operation-diagnostic-screens.js +16 -69
- package/dist/src/tui/operation-diagnostic-screens.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +44 -3
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-content.d.ts +8 -0
- package/dist/src/tui/plugin-content.js +409 -65
- package/dist/src/tui/plugin-content.js.map +1 -1
- package/dist/src/tui/plugin-detail.d.ts +52 -0
- package/dist/src/tui/plugin-detail.js +165 -0
- package/dist/src/tui/plugin-detail.js.map +1 -0
- package/dist/src/tui/project-inventory.d.ts +68 -0
- package/dist/src/tui/project-inventory.js +203 -0
- package/dist/src/tui/project-inventory.js.map +1 -0
- package/dist/src/tui/query-header.d.ts +30 -0
- package/dist/src/tui/query-header.js +40 -0
- package/dist/src/tui/query-header.js.map +1 -0
- package/dist/src/tui/reader.d.ts +37 -0
- package/dist/src/tui/reader.js +47 -0
- package/dist/src/tui/reader.js.map +1 -0
- package/dist/src/tui/router.d.ts +26 -3
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +9 -1
- package/dist/src/tui/screen-registry.js +1 -1
- package/dist/src/tui/screen-registry.js.map +1 -1
- package/dist/src/tui/source-detail-card.d.ts +29 -0
- package/dist/src/tui/source-detail-card.js +45 -0
- package/dist/src/tui/source-detail-card.js.map +1 -0
- package/dist/src/tui/theme.d.ts +12 -0
- package/dist/src/tui/theme.js +12 -0
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/wizard.js +26 -4
- package/dist/src/tui/wizard.js.map +1 -1
- package/dist/src/types.d.ts +2 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/catalog-source-references.js +2 -2
- package/dist/src/utils/local-marketplace.d.ts +1 -0
- package/dist/src/utils/local-marketplace.js +22 -0
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/plugin-config-write.js +3 -1
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +5 -5
- package/dist/src/utils/plugin-config.js +47 -23
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { open } from 'node:fs/promises';
|
|
3
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
3
4
|
import fs from 'fs-extra';
|
|
5
|
+
import YAML from 'yaml';
|
|
4
6
|
import { parseSkillFrontmatter } from '../utils/skill-validation.js';
|
|
7
|
+
import { resolveSupportedPlatforms } from '../utils/platform-support.js';
|
|
5
8
|
const MAX_FRONTMATTER_BYTES = 16 * 1024;
|
|
6
9
|
const TOOL_ORDER = ['Codex', 'Claude Code', 'Agent Skills'];
|
|
10
|
+
function safeSummary(description, short) {
|
|
11
|
+
const safeDescription = stripVTControlCharacters(description)
|
|
12
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu, ' ')
|
|
13
|
+
.replace(/\s+/gu, ' ').trim();
|
|
14
|
+
const sentence = short ? safeDescription
|
|
15
|
+
: safeDescription.match(/^.*?(?:[。!?]|[.!?](?=\s|$)|$)/u)?.[0] ?? safeDescription;
|
|
16
|
+
const points = [...sentence];
|
|
17
|
+
return points.length > 120 ? `${points.slice(0, 119).join('')}…` : sentence || null;
|
|
18
|
+
}
|
|
7
19
|
function sortTools(tools) {
|
|
8
20
|
return [...new Set(tools)].sort((left, right) => TOOL_ORDER.indexOf(left) - TOOL_ORDER.indexOf(right));
|
|
9
21
|
}
|
|
@@ -37,35 +49,164 @@ async function regularPathWithin(root, candidate, kind) {
|
|
|
37
49
|
const real = await fs.realpath(lexical).catch(() => undefined);
|
|
38
50
|
return real && within(root, real) ? real : null;
|
|
39
51
|
}
|
|
52
|
+
async function unconfirmedPathCode(root, candidate) {
|
|
53
|
+
const lexical = path.resolve(candidate);
|
|
54
|
+
if (!within(root, lexical))
|
|
55
|
+
return 'unsafePath';
|
|
56
|
+
let current = root;
|
|
57
|
+
for (const segment of path.relative(root, lexical).split(path.sep).filter(Boolean)) {
|
|
58
|
+
current = path.join(current, segment);
|
|
59
|
+
let stats;
|
|
60
|
+
try {
|
|
61
|
+
stats = await fs.lstat(current);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return error.code === 'ENOENT'
|
|
65
|
+
? 'incompleteList' : 'unreadable';
|
|
66
|
+
}
|
|
67
|
+
if (stats.isSymbolicLink() || (!stats.isDirectory() && current !== lexical))
|
|
68
|
+
return 'unsafePath';
|
|
69
|
+
}
|
|
70
|
+
return 'unsafePath';
|
|
71
|
+
}
|
|
40
72
|
async function readFrontmatterDescription(root, skillFile) {
|
|
41
73
|
const safeFile = await regularPathWithin(root, skillFile, 'file');
|
|
42
74
|
if (!safeFile)
|
|
43
75
|
return { exists: false, description: null };
|
|
44
|
-
|
|
76
|
+
let handle;
|
|
77
|
+
try {
|
|
78
|
+
handle = await open(safeFile, 'r');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return { exists: true, description: null, issue: {
|
|
82
|
+
code: 'unreadable', message: '简介文件无法读取。请检查目录内容后重试。',
|
|
83
|
+
} };
|
|
84
|
+
}
|
|
45
85
|
try {
|
|
46
86
|
const chunks = [];
|
|
47
87
|
let total = 0;
|
|
48
88
|
let frontmatterEnd = -1;
|
|
89
|
+
let reachedEof = false;
|
|
49
90
|
while (total < MAX_FRONTMATTER_BYTES && frontmatterEnd === -1) {
|
|
50
91
|
const buffer = Buffer.alloc(Math.min(512, MAX_FRONTMATTER_BYTES - total));
|
|
51
92
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, total);
|
|
52
|
-
if (bytesRead === 0)
|
|
93
|
+
if (bytesRead === 0) {
|
|
94
|
+
reachedEof = true;
|
|
53
95
|
break;
|
|
96
|
+
}
|
|
54
97
|
chunks.push(buffer.subarray(0, bytesRead));
|
|
55
98
|
total += bytesRead;
|
|
56
99
|
frontmatterEnd = Buffer.concat(chunks).toString('utf8').replace(/\r\n/gu, '\n').indexOf('\n---\n', 4);
|
|
57
100
|
}
|
|
58
|
-
if (frontmatterEnd === -1)
|
|
59
|
-
return { exists: true, description: null };
|
|
60
101
|
const normalized = Buffer.concat(chunks).toString('utf8').replace(/\r\n/gu, '\n');
|
|
61
|
-
const
|
|
102
|
+
const closingAtEnd = frontmatterEnd === -1 && normalized.endsWith('\n---')
|
|
103
|
+
&& (reachedEof || (await handle.stat()).size === total);
|
|
104
|
+
if (frontmatterEnd === -1 && !closingAtEnd)
|
|
105
|
+
return { exists: true, description: null, issue: {
|
|
106
|
+
code: 'invalidMetadata', message: '简介元信息无效或超过读取预算。请检查内容文件。',
|
|
107
|
+
} };
|
|
108
|
+
const header = closingAtEnd ? normalized : normalized.slice(0, frontmatterEnd + '\n---\n'.length);
|
|
62
109
|
const parsed = parseSkillFrontmatter(header, 'SKILL.md');
|
|
63
|
-
|
|
110
|
+
if (parsed.errors.length > 0)
|
|
111
|
+
return { exists: true, description: null, issue: {
|
|
112
|
+
code: 'invalidMetadata', message: '简介元信息无法解析。请检查内容文件。',
|
|
113
|
+
} };
|
|
114
|
+
const metadata = parsed.frontmatter?.metadata;
|
|
115
|
+
const short = isRecord(metadata) ? metadata['short-description'] : undefined;
|
|
116
|
+
if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
|
|
117
|
+
return { exists: true, description: null, issue: {
|
|
118
|
+
code: 'invalidMetadata', message: '简短用途元信息无效。请检查内容文件。',
|
|
119
|
+
} };
|
|
120
|
+
}
|
|
121
|
+
const description = typeof short === 'string' && short.trim()
|
|
122
|
+
? short : parsed.frontmatter?.description;
|
|
123
|
+
if (description !== undefined && typeof description !== 'string') {
|
|
124
|
+
return { exists: true, description: null, issue: {
|
|
125
|
+
code: 'invalidMetadata', message: '简介元信息类型无效。请检查内容文件。',
|
|
126
|
+
} };
|
|
127
|
+
}
|
|
64
128
|
return {
|
|
65
129
|
exists: true,
|
|
66
|
-
description: typeof description === 'string'
|
|
130
|
+
description: typeof description === 'string' ? safeSummary(description, Boolean(short)) : null,
|
|
67
131
|
};
|
|
68
132
|
}
|
|
133
|
+
catch {
|
|
134
|
+
return { exists: true, description: null, issue: {
|
|
135
|
+
code: 'unreadable', message: '简介文件无法读取。请检查目录内容后重试。',
|
|
136
|
+
} };
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await handle.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function readCodexCommandDescription(root, skillRoot) {
|
|
143
|
+
const metadataFile = path.join(skillRoot, 'agents', 'openai.yaml');
|
|
144
|
+
const safeFile = await regularPathWithin(root, metadataFile, 'file');
|
|
145
|
+
if (!safeFile) {
|
|
146
|
+
const code = await unconfirmedPathCode(root, metadataFile);
|
|
147
|
+
return { description: null, issue: {
|
|
148
|
+
code, message: 'Slash command 简介文件不可安全确认。请检查内容目录。',
|
|
149
|
+
} };
|
|
150
|
+
}
|
|
151
|
+
let handle;
|
|
152
|
+
try {
|
|
153
|
+
handle = await open(safeFile, 'r');
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return { description: null, issue: {
|
|
157
|
+
code: 'unreadable', message: 'Slash command 简介文件无法读取。请检查内容目录。',
|
|
158
|
+
} };
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const size = (await handle.stat()).size;
|
|
162
|
+
if (size > MAX_FRONTMATTER_BYTES)
|
|
163
|
+
return { description: null, issue: {
|
|
164
|
+
code: 'invalidMetadata', message: 'Slash command 简介元信息超过读取预算。请检查内容文件。',
|
|
165
|
+
} };
|
|
166
|
+
const buffer = Buffer.alloc(size);
|
|
167
|
+
let total = 0;
|
|
168
|
+
while (total < size) {
|
|
169
|
+
const { bytesRead } = await handle.read(buffer, total, size - total, total);
|
|
170
|
+
if (bytesRead === 0)
|
|
171
|
+
return { description: null, issue: {
|
|
172
|
+
code: 'unreadable', message: 'Slash command 简介文件无法完整读取。请检查内容目录。',
|
|
173
|
+
} };
|
|
174
|
+
total += bytesRead;
|
|
175
|
+
}
|
|
176
|
+
let metadata;
|
|
177
|
+
try {
|
|
178
|
+
const document = YAML.parseDocument(buffer.toString('utf8'), { prettyErrors: false });
|
|
179
|
+
if (document.errors.length > 0)
|
|
180
|
+
throw new Error('Invalid YAML');
|
|
181
|
+
metadata = document.toJS({ maxAliasCount: 0 });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return { description: null, issue: {
|
|
185
|
+
code: 'invalidMetadata', message: 'Slash command 简介元信息无法解析。请检查内容文件。',
|
|
186
|
+
} };
|
|
187
|
+
}
|
|
188
|
+
if (!isRecord(metadata) || (metadata.interface !== undefined && !isRecord(metadata.interface))) {
|
|
189
|
+
return { description: null, issue: {
|
|
190
|
+
code: 'invalidMetadata', message: 'Slash command 简介元信息无效。请检查内容文件。',
|
|
191
|
+
} };
|
|
192
|
+
}
|
|
193
|
+
const short = isRecord(metadata.interface) ? metadata.interface.short_description : undefined;
|
|
194
|
+
if (short === undefined)
|
|
195
|
+
return { description: null };
|
|
196
|
+
if (typeof short !== 'string' || !short.trim())
|
|
197
|
+
return { description: null, issue: {
|
|
198
|
+
code: 'invalidMetadata', message: 'Slash command 简短用途元信息无效。请检查内容文件。',
|
|
199
|
+
} };
|
|
200
|
+
const description = safeSummary(short, true);
|
|
201
|
+
return description ? { description } : { description: null, issue: {
|
|
202
|
+
code: 'invalidMetadata', message: 'Slash command 简短用途元信息无效。请检查内容文件。',
|
|
203
|
+
} };
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return { description: null, issue: {
|
|
207
|
+
code: 'unreadable', message: 'Slash command 简介文件无法读取。请检查内容目录。',
|
|
208
|
+
} };
|
|
209
|
+
}
|
|
69
210
|
finally {
|
|
70
211
|
await handle.close();
|
|
71
212
|
}
|
|
@@ -80,38 +221,108 @@ function addItem(items, item) {
|
|
|
80
221
|
current.tools = sortTools([...current.tools, ...item.tools]);
|
|
81
222
|
if (current.description === null && item.description !== null)
|
|
82
223
|
current.description = item.description;
|
|
224
|
+
if (item.issue) {
|
|
225
|
+
current.descriptionState = 'issue';
|
|
226
|
+
current.issue = item.issue;
|
|
227
|
+
}
|
|
228
|
+
else if (current.descriptionState !== 'issue' && current.description !== null) {
|
|
229
|
+
current.descriptionState = 'available';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function addCoverageIssue(issues, issue) {
|
|
233
|
+
issues.push(issue);
|
|
83
234
|
}
|
|
84
235
|
async function addSkillDirectory(options) {
|
|
85
236
|
const directory = await regularPathWithin(options.root, path.join(options.root, options.relativePath), 'directory');
|
|
86
237
|
if (!directory) {
|
|
87
|
-
options.
|
|
238
|
+
const code = await unconfirmedPathCode(options.root, path.join(options.root, options.relativePath));
|
|
239
|
+
addCoverageIssue(options.coverageIssues, {
|
|
240
|
+
code, message: `${options.tool} Skill 清单无法确认。请检查目录。`,
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
let entries;
|
|
245
|
+
try {
|
|
246
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
addCoverageIssue(options.coverageIssues, {
|
|
250
|
+
code: 'unreadable', message: `${options.tool} Skill 清单无法读取。请检查目录后重试。`,
|
|
251
|
+
});
|
|
88
252
|
return;
|
|
89
253
|
}
|
|
90
|
-
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
91
254
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
92
|
-
if (
|
|
255
|
+
if (entry.isSymbolicLink()) {
|
|
256
|
+
addCoverageIssue(options.coverageIssues, {
|
|
257
|
+
code: 'unsafePath', message: `${options.tool} Skill 清单含不安全路径。请检查目录。`,
|
|
258
|
+
});
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!entry.isDirectory())
|
|
93
262
|
continue;
|
|
94
263
|
const skillRoot = await regularPathWithin(options.root, path.join(directory, entry.name), 'directory');
|
|
95
|
-
if (!skillRoot)
|
|
264
|
+
if (!skillRoot) {
|
|
265
|
+
const code = await unconfirmedPathCode(options.root, path.join(directory, entry.name));
|
|
266
|
+
addCoverageIssue(options.coverageIssues, {
|
|
267
|
+
code, message: `${options.tool} Skill 清单含无法确认的条目。请检查目录。`,
|
|
268
|
+
});
|
|
96
269
|
continue;
|
|
97
|
-
|
|
98
|
-
if (
|
|
270
|
+
}
|
|
271
|
+
if (options.tool === 'Codex'
|
|
272
|
+
&& options.items.get(`command:${entry.name}`)?.tools.includes('Codex'))
|
|
99
273
|
continue;
|
|
274
|
+
const skillFile = path.join(skillRoot, 'SKILL.md');
|
|
275
|
+
const hasClaudeCommand = options.items.has(`command:${entry.name}`);
|
|
276
|
+
const hasClaudeSkill = options.checkClaudeCommand
|
|
277
|
+
&& options.items.get(`skill:${entry.name}`)?.tools.includes('Claude Code')
|
|
278
|
+
&& await regularPathWithin(options.root, path.join(options.root, 'claude', 'skills', entry.name, 'SKILL.md'), 'file');
|
|
279
|
+
const uncertainSource = options.checkClaudeCommand && !hasClaudeCommand && !hasClaudeSkill;
|
|
280
|
+
const skill = options.readDescription === false || (uncertainSource && options.type !== 'command')
|
|
281
|
+
? { exists: Boolean(await regularPathWithin(options.root, skillFile, 'file')), description: null }
|
|
282
|
+
: await readFrontmatterDescription(options.root, skillFile);
|
|
283
|
+
let issue = skill.issue;
|
|
284
|
+
if (!skill.exists) {
|
|
285
|
+
const code = await unconfirmedPathCode(options.root, skillFile);
|
|
286
|
+
if (code === 'incompleteList') {
|
|
287
|
+
addCoverageIssue(options.coverageIssues, {
|
|
288
|
+
code, message: `${options.tool} Skill 清单有无法确认的条目。请检查目录。`,
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
issue = { code, message: 'Skill 元信息文件不可安全读取。请检查内容目录后重试。' };
|
|
293
|
+
}
|
|
294
|
+
const type = options.type === 'command' && issue?.code === 'unsafePath'
|
|
295
|
+
? 'skill' : options.type ?? 'skill';
|
|
296
|
+
if (uncertainSource) {
|
|
297
|
+
addCoverageIssue(options.coverageIssues, {
|
|
298
|
+
code: 'incompleteList',
|
|
299
|
+
message: options.type === 'command'
|
|
300
|
+
? 'Slash command 的 Claude Code 组件无法确认。请检查内容目录。'
|
|
301
|
+
: 'Codex 内容名称已确认,但缺少可安全确认的 Claude Code 对应项,不能区分普通 Skill 与 Slash command。请检查内容目录。',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const command = type === 'command' && skill.exists
|
|
305
|
+
? await readCodexCommandDescription(options.root, skillRoot) : undefined;
|
|
306
|
+
issue ??= command?.issue;
|
|
307
|
+
const description = command?.description ?? skill.description;
|
|
100
308
|
addItem(options.items, {
|
|
101
|
-
id:
|
|
309
|
+
id: `${type}:${entry.name}`,
|
|
102
310
|
name: entry.name,
|
|
103
|
-
type
|
|
104
|
-
description
|
|
311
|
+
type,
|
|
312
|
+
description,
|
|
105
313
|
tools: [options.tool],
|
|
314
|
+
descriptionState: issue ? 'issue' : description ? 'available' : 'absent',
|
|
315
|
+
...(issue ? { issue } : {}),
|
|
106
316
|
});
|
|
107
|
-
if (skill.description === null)
|
|
108
|
-
options.notices.push(`Skill ${entry.name} 没有安全可读的简短用途。`);
|
|
109
317
|
}
|
|
110
318
|
}
|
|
111
319
|
async function addJsonComponents(options) {
|
|
112
320
|
const file = await regularPathWithin(options.root, path.join(options.root, options.relativePath), 'file');
|
|
113
321
|
if (!file) {
|
|
114
|
-
options.
|
|
322
|
+
const code = await unconfirmedPathCode(options.root, path.join(options.root, options.relativePath));
|
|
323
|
+
addCoverageIssue(options.coverageIssues, {
|
|
324
|
+
code, message: `${options.tool} ${options.type} 清单无法确认。请检查目录。`,
|
|
325
|
+
});
|
|
115
326
|
return;
|
|
116
327
|
}
|
|
117
328
|
let value;
|
|
@@ -119,14 +330,19 @@ async function addJsonComponents(options) {
|
|
|
119
330
|
value = await fs.readJson(file);
|
|
120
331
|
}
|
|
121
332
|
catch {
|
|
122
|
-
|
|
333
|
+
addCoverageIssue(options.coverageIssues, {
|
|
334
|
+
code: 'unreadable', message: `${options.tool} ${options.type} 清单无法读取。请检查内容文件。`,
|
|
335
|
+
});
|
|
123
336
|
return;
|
|
124
337
|
}
|
|
125
338
|
const collection = isRecord(value) ? value[options.property] : undefined;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
339
|
+
if (!isRecord(collection)) {
|
|
340
|
+
addCoverageIssue(options.coverageIssues, {
|
|
341
|
+
code: 'incompleteList', message: `${options.tool} ${options.type} 清单格式无效。请检查内容文件。`,
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
129
344
|
}
|
|
345
|
+
const entries = Object.keys(collection).sort();
|
|
130
346
|
for (const name of entries) {
|
|
131
347
|
addItem(options.items, {
|
|
132
348
|
id: `${options.type}:${name}`,
|
|
@@ -134,35 +350,83 @@ async function addJsonComponents(options) {
|
|
|
134
350
|
type: options.type,
|
|
135
351
|
description: null,
|
|
136
352
|
tools: [options.tool],
|
|
353
|
+
descriptionState: 'absent',
|
|
137
354
|
});
|
|
138
355
|
}
|
|
139
356
|
}
|
|
140
357
|
async function addSlashCommands(options) {
|
|
141
358
|
const directory = await regularPathWithin(options.root, path.join(options.root, 'claude', 'commands'), 'directory');
|
|
142
359
|
if (!directory) {
|
|
143
|
-
options.
|
|
360
|
+
const code = await unconfirmedPathCode(options.root, path.join(options.root, 'claude', 'commands'));
|
|
361
|
+
addCoverageIssue(options.coverageIssues, {
|
|
362
|
+
code, message: 'Slash command 清单无法确认。请检查目录。',
|
|
363
|
+
});
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
let entries;
|
|
367
|
+
try {
|
|
368
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
addCoverageIssue(options.coverageIssues, {
|
|
372
|
+
code: 'unreadable', message: 'Slash command 清单无法读取。请检查目录后重试。',
|
|
373
|
+
});
|
|
144
374
|
return;
|
|
145
375
|
}
|
|
146
|
-
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
147
376
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
148
|
-
if (
|
|
377
|
+
if (path.extname(entry.name) !== '.md')
|
|
149
378
|
continue;
|
|
379
|
+
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
380
|
+
addCoverageIssue(options.coverageIssues, {
|
|
381
|
+
code: 'unsafePath', message: 'Slash command 清单含无法确认的条目。请检查目录。',
|
|
382
|
+
});
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!await regularPathWithin(options.root, path.join(directory, entry.name), 'file')) {
|
|
386
|
+
addCoverageIssue(options.coverageIssues, {
|
|
387
|
+
code: 'unsafePath', message: 'Slash command 清单含不安全路径。请检查目录。',
|
|
388
|
+
});
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
150
391
|
const name = path.basename(entry.name, '.md');
|
|
151
392
|
const tools = ['Claude Code'];
|
|
152
393
|
const codexSkillFile = path.join(options.root, 'codex', 'skills', name, 'SKILL.md');
|
|
153
394
|
const codexCommand = options.hasCodex
|
|
154
395
|
&& await regularPathWithin(options.root, codexSkillFile, 'file');
|
|
396
|
+
if (options.hasCodex && !codexCommand) {
|
|
397
|
+
const code = await unconfirmedPathCode(options.root, codexSkillFile);
|
|
398
|
+
addCoverageIssue(options.coverageIssues, {
|
|
399
|
+
code, message: 'Slash command 的 Codex 组件无法安全确认。请检查内容目录。',
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
if (options.hasCodex && !codexCommand && await fs.lstat(codexSkillFile).catch(() => undefined)) {
|
|
403
|
+
if (await regularPathWithin(options.root, path.dirname(codexSkillFile), 'directory')) {
|
|
404
|
+
addItem(options.items, {
|
|
405
|
+
id: `skill:${name}`,
|
|
406
|
+
name,
|
|
407
|
+
type: 'skill',
|
|
408
|
+
description: null,
|
|
409
|
+
tools: ['Codex'],
|
|
410
|
+
descriptionState: 'issue',
|
|
411
|
+
issue: { code: 'unsafePath', message: 'Skill 元信息文件不可安全读取。请检查内容目录后重试。' },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const command = await readFrontmatterDescription(options.root, path.join(directory, entry.name));
|
|
416
|
+
const codexDescription = codexCommand
|
|
417
|
+
? await readCodexCommandDescription(options.root, path.dirname(codexSkillFile)) : undefined;
|
|
418
|
+
const description = codexDescription?.description ?? command.description;
|
|
419
|
+
const issue = codexDescription?.issue ?? command.issue;
|
|
155
420
|
if (codexCommand)
|
|
156
421
|
tools.unshift('Codex');
|
|
157
|
-
options.items.delete(`skill:${name}`);
|
|
158
422
|
addItem(options.items, {
|
|
159
423
|
id: `command:${name}`,
|
|
160
424
|
name,
|
|
161
425
|
type: 'command',
|
|
162
|
-
description
|
|
163
|
-
? (await readFrontmatterDescription(options.root, codexSkillFile)).description
|
|
164
|
-
: null,
|
|
426
|
+
description,
|
|
165
427
|
tools,
|
|
428
|
+
descriptionState: issue ? 'issue' : description ? 'available' : 'absent',
|
|
429
|
+
...(issue ? { issue } : {}),
|
|
166
430
|
});
|
|
167
431
|
}
|
|
168
432
|
}
|
|
@@ -202,35 +466,105 @@ async function projectedPlatforms(root) {
|
|
|
202
466
|
return tools;
|
|
203
467
|
}
|
|
204
468
|
export async function projectPluginContentOverview(options) {
|
|
205
|
-
|
|
469
|
+
let root;
|
|
470
|
+
try {
|
|
471
|
+
root = await fs.realpath(options.plugin.rootDir);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
const message = '当前无法安全读取这个插件的目录内容。请检查来源状态后重试。';
|
|
475
|
+
return {
|
|
476
|
+
pluginId: options.pluginId,
|
|
477
|
+
pluginVersion: options.plugin.manifest.version,
|
|
478
|
+
catalogVersion: options.catalogVersion,
|
|
479
|
+
evidence: 'unavailable',
|
|
480
|
+
items: [],
|
|
481
|
+
notices: [message],
|
|
482
|
+
coverage: 'unavailable',
|
|
483
|
+
coverageIssue: { code: 'unreadable', message },
|
|
484
|
+
};
|
|
485
|
+
}
|
|
206
486
|
const items = new Map();
|
|
207
|
-
const
|
|
487
|
+
const coverageIssues = [];
|
|
208
488
|
const platforms = await projectedPlatforms(root);
|
|
489
|
+
const supportedPlatforms = resolveSupportedPlatforms(options.plugin.manifest);
|
|
490
|
+
for (const [platform, tool] of [
|
|
491
|
+
['codex', 'Codex'], ['claude', 'Claude Code'],
|
|
492
|
+
]) {
|
|
493
|
+
if (!supportedPlatforms.has(platform) || platforms.includes(tool))
|
|
494
|
+
continue;
|
|
495
|
+
const marker = path.join(root, `.${platform}-plugin`, 'plugin.json');
|
|
496
|
+
const code = await unconfirmedPathCode(root, marker);
|
|
497
|
+
addCoverageIssue(coverageIssues, {
|
|
498
|
+
code, message: `${tool} 平台清单无法安全确认。请检查目录。`,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const codexOnlyCommands = Boolean(options.plugin.manifest.slashCommands)
|
|
502
|
+
&& platforms.includes('Codex') && !platforms.includes('Claude Code');
|
|
503
|
+
const mixedCodexSkills = Boolean(options.plugin.manifest.slashCommands)
|
|
504
|
+
&& platforms.includes('Codex')
|
|
505
|
+
&& Boolean(options.plugin.manifest.skills || options.plugin.manifest.buildConfig?.fileRules?.codex);
|
|
506
|
+
if (mixedCodexSkills && codexOnlyCommands) {
|
|
507
|
+
addCoverageIssue(coverageIssues, {
|
|
508
|
+
code: 'incompleteList',
|
|
509
|
+
message: 'Codex 内容名称已确认,但当前目录不能安全区分普通 Skill 与 Slash command。请查看来源定义。',
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (options.plugin.manifest.slashCommands && platforms.includes('Claude Code')) {
|
|
513
|
+
await addSlashCommands({
|
|
514
|
+
root,
|
|
515
|
+
hasCodex: platforms.includes('Codex'),
|
|
516
|
+
items,
|
|
517
|
+
coverageIssues,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
209
520
|
if (options.plugin.manifest.skills) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
521
|
+
if (platforms.includes('Claude Code')) {
|
|
522
|
+
await addSkillDirectory({
|
|
523
|
+
root, relativePath: 'claude/skills', tool: 'Claude Code', items, coverageIssues,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (platforms.includes('Codex')) {
|
|
527
|
+
await addSkillDirectory({
|
|
528
|
+
root, relativePath: 'codex/skills', tool: 'Codex', items, coverageIssues,
|
|
529
|
+
...(codexOnlyCommands ? { readDescription: false } : {}),
|
|
530
|
+
checkClaudeCommand: Boolean(options.plugin.manifest.slashCommands)
|
|
531
|
+
&& platforms.includes('Claude Code'),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (codexOnlyCommands && !options.plugin.manifest.skills) {
|
|
536
|
+
await addSkillDirectory({
|
|
537
|
+
root, relativePath: 'codex/skills', tool: 'Codex', items, coverageIssues,
|
|
538
|
+
type: mixedCodexSkills ? 'skill' : 'command',
|
|
539
|
+
readDescription: false,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (options.plugin.manifest.slashCommands && platforms.includes('Codex')
|
|
543
|
+
&& platforms.includes('Claude Code') && !options.plugin.manifest.skills) {
|
|
544
|
+
await addSkillDirectory({
|
|
545
|
+
root, relativePath: 'codex/skills', tool: 'Codex', items, coverageIssues,
|
|
546
|
+
type: mixedCodexSkills ? 'skill' : 'command',
|
|
547
|
+
readDescription: false,
|
|
548
|
+
checkClaudeCommand: true,
|
|
549
|
+
});
|
|
218
550
|
}
|
|
219
551
|
if (options.plugin.manifest.distributions?.agentSkills?.skills) {
|
|
220
552
|
await addSkillDirectory({
|
|
221
|
-
root, relativePath: 'agent-skills/skills', tool: 'Agent Skills', items,
|
|
553
|
+
root, relativePath: 'agent-skills/skills', tool: 'Agent Skills', items, coverageIssues,
|
|
222
554
|
});
|
|
223
555
|
}
|
|
224
556
|
if (options.plugin.manifest.mcpServers) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
557
|
+
for (const tool of platforms) {
|
|
558
|
+
await addJsonComponents({
|
|
559
|
+
root,
|
|
560
|
+
relativePath: `${tool === 'Codex' ? 'codex' : 'claude'}/${options.plugin.manifest.mcpServers.replace(/^\.\//u, '')}`,
|
|
561
|
+
property: 'mcpServers',
|
|
562
|
+
type: 'mcpServer',
|
|
563
|
+
tool,
|
|
564
|
+
items,
|
|
565
|
+
coverageIssues,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
234
568
|
}
|
|
235
569
|
if (options.plugin.manifest.apps && platforms.includes('Codex')) {
|
|
236
570
|
await addJsonComponents({
|
|
@@ -240,15 +574,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
240
574
|
type: 'app',
|
|
241
575
|
tool: 'Codex',
|
|
242
576
|
items,
|
|
243
|
-
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
if (options.plugin.manifest.slashCommands) {
|
|
247
|
-
await addSlashCommands({
|
|
248
|
-
root,
|
|
249
|
-
hasCodex: platforms.includes('Codex'),
|
|
250
|
-
items,
|
|
251
|
-
notices,
|
|
577
|
+
coverageIssues,
|
|
252
578
|
});
|
|
253
579
|
}
|
|
254
580
|
for (const agent of options.plugin.manifest.extensions?.agents ?? []) {
|
|
@@ -258,6 +584,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
258
584
|
type: 'agent',
|
|
259
585
|
description: agent.description.trim() || null,
|
|
260
586
|
tools: toolsForOverrides(platforms, agent.agentOverrides),
|
|
587
|
+
descriptionState: agent.description.trim() ? 'available' : 'absent',
|
|
261
588
|
});
|
|
262
589
|
}
|
|
263
590
|
for (const instruction of options.plugin.manifest.extensions?.instructions ?? []) {
|
|
@@ -267,6 +594,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
267
594
|
type: 'instruction',
|
|
268
595
|
description: null,
|
|
269
596
|
tools: toolsForOverrides(platforms, instruction.agentOverrides),
|
|
597
|
+
descriptionState: 'absent',
|
|
270
598
|
});
|
|
271
599
|
}
|
|
272
600
|
for (const permission of options.plugin.manifest.extensions?.permissions ?? []) {
|
|
@@ -276,6 +604,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
276
604
|
type: 'permission',
|
|
277
605
|
description: null,
|
|
278
606
|
tools: toolsForOverrides(platforms, permission.agentOverrides),
|
|
607
|
+
descriptionState: 'absent',
|
|
279
608
|
});
|
|
280
609
|
}
|
|
281
610
|
for (const hook of options.plugin.manifest.extensions?.hooks ?? []) {
|
|
@@ -286,6 +615,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
286
615
|
type: 'hook',
|
|
287
616
|
description: null,
|
|
288
617
|
tools: toolsForOverrides(platforms, hook.agentOverrides),
|
|
618
|
+
descriptionState: 'absent',
|
|
289
619
|
});
|
|
290
620
|
}
|
|
291
621
|
for (const [platform, hooks] of Object.entries(options.plugin.manifest.extensions?.nativeHooks ?? {})) {
|
|
@@ -298,6 +628,7 @@ export async function projectPluginContentOverview(options) {
|
|
|
298
628
|
type: 'hook',
|
|
299
629
|
description: null,
|
|
300
630
|
tools: [tool],
|
|
631
|
+
descriptionState: 'absent',
|
|
301
632
|
});
|
|
302
633
|
}
|
|
303
634
|
}
|
|
@@ -308,8 +639,18 @@ export async function projectPluginContentOverview(options) {
|
|
|
308
639
|
continue;
|
|
309
640
|
const name = target.skillKey ?? target.roleKey ?? target.id;
|
|
310
641
|
let description = null;
|
|
642
|
+
let issue;
|
|
311
643
|
if (type === 'skill' && target.platform) {
|
|
312
|
-
|
|
644
|
+
const skill = await readFrontmatterDescription(root, path.join(root, '.agents-plugin', 'artifact-sources', target.platform, target.id, '0', 'SKILL.md'));
|
|
645
|
+
description = skill.description;
|
|
646
|
+
issue = skill.issue;
|
|
647
|
+
if (!skill.exists) {
|
|
648
|
+
const code = await unconfirmedPathCode(root, path.join(root, '.agents-plugin', 'artifact-sources', target.platform, target.id, '0', 'SKILL.md'));
|
|
649
|
+
if (code !== 'incompleteList')
|
|
650
|
+
issue = {
|
|
651
|
+
code, message: '简介文件不可安全读取。请检查内容目录。',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
313
654
|
}
|
|
314
655
|
addItem(items, {
|
|
315
656
|
id: `artifact:${target.id}`,
|
|
@@ -317,20 +658,23 @@ export async function projectPluginContentOverview(options) {
|
|
|
317
658
|
type,
|
|
318
659
|
description,
|
|
319
660
|
tools: [tool],
|
|
661
|
+
descriptionState: issue ? 'issue' : description ? 'available' : 'absent',
|
|
662
|
+
...(issue ? { issue } : {}),
|
|
320
663
|
});
|
|
321
|
-
if (description === null)
|
|
322
|
-
notices.push(`${type} ${name} 没有安全可读的简短用途。`);
|
|
323
664
|
}
|
|
324
665
|
const result = [...items.values()].sort((left, right) => left.type.localeCompare(right.type) || left.name.localeCompare(right.name));
|
|
325
|
-
|
|
326
|
-
|
|
666
|
+
const coverage = coverageIssues.length === 0
|
|
667
|
+
? 'complete' : result.length > 0 ? 'partial' : 'unavailable';
|
|
668
|
+
const notices = [...new Set(coverageIssues.map((issue) => issue.message))];
|
|
327
669
|
return {
|
|
328
670
|
pluginId: options.pluginId,
|
|
329
671
|
pluginVersion: options.plugin.manifest.version,
|
|
330
672
|
catalogVersion: options.catalogVersion,
|
|
331
673
|
evidence: 'catalogBundle',
|
|
332
674
|
items: result,
|
|
333
|
-
notices
|
|
675
|
+
notices,
|
|
676
|
+
coverage,
|
|
677
|
+
...(coverageIssues[0] ? { coverageIssue: coverageIssues[0] } : {}),
|
|
334
678
|
};
|
|
335
679
|
}
|
|
336
680
|
//# sourceMappingURL=plugin-content.js.map
|