@birdie_moblie/plugin-marketplace-cli 1.0.1 → 1.3.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +103 -6
  3. package/dist/adapters/agent-plugins.d.ts +11 -0
  4. package/dist/adapters/agent-plugins.js +66 -0
  5. package/dist/adapters/agent-plugins.js.map +1 -0
  6. package/dist/adapters/claude.d.ts +32 -1
  7. package/dist/adapters/claude.js +119 -14
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/codex.d.ts +7 -0
  10. package/dist/adapters/codex.js +10 -14
  11. package/dist/adapters/codex.js.map +1 -1
  12. package/dist/adapters/marketplace-registration.d.ts +17 -0
  13. package/dist/adapters/marketplace-registration.js +367 -0
  14. package/dist/adapters/marketplace-registration.js.map +1 -0
  15. package/dist/adapters/openspec.d.ts +66 -0
  16. package/dist/adapters/openspec.js +232 -0
  17. package/dist/adapters/openspec.js.map +1 -0
  18. package/dist/application/marketplace-registration-contract.d.ts +51 -0
  19. package/dist/application/marketplace-registration-contract.js +11 -0
  20. package/dist/application/marketplace-registration-contract.js.map +1 -0
  21. package/dist/application/marketplace-registration.d.ts +20 -0
  22. package/dist/application/marketplace-registration.js +304 -0
  23. package/dist/application/marketplace-registration.js.map +1 -0
  24. package/dist/cli.d.ts +2 -1
  25. package/dist/cli.js +25 -5
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/build.d.ts +1 -1
  28. package/dist/commands/build.js +27 -5
  29. package/dist/commands/build.js.map +1 -1
  30. package/dist/commands/index-gen.d.ts +10 -0
  31. package/dist/commands/index-gen.js +81 -20
  32. package/dist/commands/index-gen.js.map +1 -1
  33. package/dist/commands/init-repo.js +11 -2
  34. package/dist/commands/init-repo.js.map +1 -1
  35. package/dist/commands/init.d.ts +1 -1
  36. package/dist/commands/init.js +61 -9
  37. package/dist/commands/init.js.map +1 -1
  38. package/dist/commands/install-repo.d.ts +20 -0
  39. package/dist/commands/install-repo.js +200 -0
  40. package/dist/commands/install-repo.js.map +1 -0
  41. package/dist/commands/release-local.d.ts +1 -0
  42. package/dist/commands/release-local.js +310 -7
  43. package/dist/commands/release-local.js.map +1 -1
  44. package/dist/commands/validate.js +399 -16
  45. package/dist/commands/validate.js.map +1 -1
  46. package/dist/core/errors.d.ts +5 -0
  47. package/dist/core/errors.js +12 -0
  48. package/dist/core/errors.js.map +1 -0
  49. package/dist/generated/skill-content.js +1 -1
  50. package/dist/generated/skill-content.js.map +1 -1
  51. package/dist/infrastructure/process-runner.d.ts +29 -0
  52. package/dist/infrastructure/process-runner.js +130 -0
  53. package/dist/infrastructure/process-runner.js.map +1 -0
  54. package/dist/infrastructure/vscode-user-settings.d.ts +42 -0
  55. package/dist/infrastructure/vscode-user-settings.js +509 -0
  56. package/dist/infrastructure/vscode-user-settings.js.map +1 -0
  57. package/dist/schema/agent-plugins.d.ts +221 -0
  58. package/dist/schema/agent-plugins.js +155 -0
  59. package/dist/schema/agent-plugins.js.map +1 -0
  60. package/dist/schema/plugin-yaml.d.ts +37 -3
  61. package/dist/schema/plugin-yaml.js +89 -4
  62. package/dist/schema/plugin-yaml.js.map +1 -1
  63. package/dist/utils/helpers.d.ts +1 -0
  64. package/dist/utils/helpers.js +1 -0
  65. package/dist/utils/helpers.js.map +1 -1
  66. package/dist/validation/agent-plugins.d.ts +3 -0
  67. package/dist/validation/agent-plugins.js +418 -0
  68. package/dist/validation/agent-plugins.js.map +1 -0
  69. package/package.json +14 -5
@@ -0,0 +1,200 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { executeMarketplaceRegistration, inspectMarketplaceRegistration, normalizeTargetIds, } from '../application/marketplace-registration.js';
3
+ import { AGENT_TARGET_IDS, } from '../application/marketplace-registration-contract.js';
4
+ import { CommandError } from '../core/errors.js';
5
+ const INSPECTION_LABELS = {
6
+ ready: '可自动',
7
+ 'missing-cli': '缺少 CLI',
8
+ 'manual-required': '需手动',
9
+ failed: '不可用',
10
+ interrupted: '中断',
11
+ };
12
+ const RESULT_LABELS = {
13
+ completed: '完成',
14
+ 'missing-cli': '缺少 CLI',
15
+ 'manual-required': '需手动',
16
+ failed: '失败',
17
+ interrupted: '中断',
18
+ };
19
+ function defaultWrite(text) {
20
+ process.stdout.write(text);
21
+ }
22
+ async function defaultReadSelection(interruptController) {
23
+ if (interruptController.signal.aborted) {
24
+ return undefined;
25
+ }
26
+ const terminal = createInterface({ input: process.stdin, output: process.stdout });
27
+ const onSigint = () => {
28
+ interruptController.abort('SIGINT');
29
+ };
30
+ terminal.on('SIGINT', onSigint);
31
+ try {
32
+ if (interruptController.signal.aborted) {
33
+ return undefined;
34
+ }
35
+ return await terminal.question('> ', { signal: interruptController.signal });
36
+ }
37
+ catch (error) {
38
+ if (interruptController.signal.aborted ||
39
+ error?.name === 'AbortError') {
40
+ return undefined;
41
+ }
42
+ throw error;
43
+ }
44
+ finally {
45
+ terminal.removeListener('SIGINT', onSigint);
46
+ terminal.close();
47
+ }
48
+ }
49
+ export function parseTargetSelection(input, availableTargetIds, defaultTargetIds) {
50
+ const value = input.trim();
51
+ if (value.length === 0) {
52
+ if (defaultTargetIds.length === 0) {
53
+ throw new CommandError('当前没有默认可自动处理的 agent;请输入明确编号或 all。');
54
+ }
55
+ return normalizeTargetIds(defaultTargetIds);
56
+ }
57
+ if (value.toLowerCase() === 'all') {
58
+ return normalizeTargetIds(availableTargetIds);
59
+ }
60
+ const tokens = value.split(',').map((token) => token.trim());
61
+ if (tokens.some((token) => !/^\d+$/u.test(token))) {
62
+ throw new CommandError('请选择逗号分隔的 agent 编号,或输入 all。');
63
+ }
64
+ const selected = [];
65
+ for (const token of tokens) {
66
+ const index = Number(token) - 1;
67
+ const target = availableTargetIds[index];
68
+ if (!target) {
69
+ throw new CommandError(`agent 编号超出范围: ${token}`);
70
+ }
71
+ selected.push(target);
72
+ }
73
+ return normalizeTargetIds(selected);
74
+ }
75
+ function renderPrompt(inspection, write) {
76
+ write('请选择要注册 Marketplace 的 agent:\n');
77
+ inspection.targets.forEach((target, index) => {
78
+ write(` ${index + 1}. ${target.label.padEnd(18)} [${INSPECTION_LABELS[target.status]}]\n`);
79
+ });
80
+ const defaults = inspection.targets
81
+ .filter((target) => target.status === 'ready')
82
+ .map((target) => target.id);
83
+ const defaultNumbers = defaults.map((id) => inspection.targets.findIndex((target) => target.id === id) + 1);
84
+ write(`\n默认:${defaultNumbers.length > 0 ? defaultNumbers.join(',') : '无'}\n`);
85
+ write('输入编号(逗号分隔),输入 all 选择全部,直接回车接受默认:\n');
86
+ return defaults;
87
+ }
88
+ function formatInvocation(target) {
89
+ if (!target.invocation) {
90
+ return target.message;
91
+ }
92
+ return [target.invocation.executable, ...target.invocation.args.map((arg) => JSON.stringify(arg))]
93
+ .join(' ');
94
+ }
95
+ function renderTargetStart(target, write) {
96
+ if (target.status === 'ready') {
97
+ write(`→ ${target.label}: ${formatInvocation(target)}\n`);
98
+ }
99
+ }
100
+ function renderResultLine(result, write) {
101
+ write(` [${RESULT_LABELS[result.status]}] ${result.label}:${result.message}\n`);
102
+ if (result.status !== 'completed' && result.recovery) {
103
+ write(` 下一步:${result.recovery}\n`);
104
+ }
105
+ }
106
+ function renderReport(report, write) {
107
+ const completed = report.results.filter((result) => result.status === 'completed');
108
+ const incomplete = report.results.filter((result) => result.status !== 'completed');
109
+ write('\n注册结果:\n');
110
+ write('已完成:\n');
111
+ if (completed.length === 0) {
112
+ write(' 无\n');
113
+ }
114
+ else {
115
+ completed.forEach((result) => renderResultLine(result, write));
116
+ }
117
+ write('未完成:\n');
118
+ if (incomplete.length === 0) {
119
+ write(' 无\n');
120
+ }
121
+ else {
122
+ incomplete.forEach((result) => renderResultLine(result, write));
123
+ }
124
+ write(`退出码:${report.exitCode}\n`);
125
+ }
126
+ function selectedInspection(inspection, targetIds) {
127
+ const selected = new Set(targetIds);
128
+ return {
129
+ source: inspection.source,
130
+ targets: inspection.targets.filter((target) => selected.has(target.id)),
131
+ };
132
+ }
133
+ export async function runInstallRepo(source, options, dependencies = {}) {
134
+ const requested = [...(options.agent ?? [])];
135
+ if (options.all && requested.length > 0) {
136
+ throw new CommandError('不能同时使用 --agent 与 --all。');
137
+ }
138
+ const interactive = dependencies.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
139
+ const explicitTargetIds = options.all
140
+ ? [...AGENT_TARGET_IDS]
141
+ : requested.length > 0
142
+ ? normalizeTargetIds(requested)
143
+ : undefined;
144
+ if (!explicitTargetIds && !interactive) {
145
+ throw new CommandError('非交互终端必须显式传入至少一个 --agent,或使用 --all。');
146
+ }
147
+ const write = dependencies.write ?? defaultWrite;
148
+ const interruptController = new AbortController();
149
+ const onSigint = () => interruptController.abort('SIGINT');
150
+ const runtime = {
151
+ ...dependencies.runtime,
152
+ signal: interruptController.signal,
153
+ };
154
+ process.on('SIGINT', onSigint);
155
+ try {
156
+ let inspection = await inspectMarketplaceRegistration(source, {
157
+ baseDir: dependencies.baseDir,
158
+ homeDir: dependencies.homeDir,
159
+ targetIds: explicitTargetIds ?? AGENT_TARGET_IDS,
160
+ runtime,
161
+ });
162
+ if (!explicitTargetIds) {
163
+ const interrupted = inspection.targets.some((target) => target.status === 'interrupted');
164
+ if (interrupted) {
165
+ const report = await executeMarketplaceRegistration(inspection, {
166
+ runtime,
167
+ onTargetResult: (result) => renderResultLine(result, write),
168
+ });
169
+ renderReport(report, write);
170
+ return { exitCode: 130, report };
171
+ }
172
+ const defaultTargetIds = renderPrompt(inspection, write);
173
+ if (interruptController.signal.aborted) {
174
+ write('\n[中断] 用户取消了 Marketplace 注册。\n');
175
+ return { exitCode: 130 };
176
+ }
177
+ const answer = dependencies.readSelection
178
+ ? await dependencies.readSelection(interruptController.signal)
179
+ : await defaultReadSelection(interruptController);
180
+ if (answer === undefined || interruptController.signal.aborted) {
181
+ write('\n[中断] 用户取消了 Marketplace 注册。\n');
182
+ return { exitCode: 130 };
183
+ }
184
+ const selected = parseTargetSelection(answer, inspection.targets.map((target) => target.id), defaultTargetIds);
185
+ inspection = selectedInspection(inspection, selected);
186
+ }
187
+ write(`Marketplace 来源:${inspection.source.displayValue}\n`);
188
+ const report = await executeMarketplaceRegistration(inspection, {
189
+ runtime,
190
+ onTargetStart: (target) => renderTargetStart(target, write),
191
+ onTargetResult: (result) => renderResultLine(result, write),
192
+ });
193
+ renderReport(report, write);
194
+ return { exitCode: report.exitCode, report };
195
+ }
196
+ finally {
197
+ process.removeListener('SIGINT', onSigint);
198
+ }
199
+ }
200
+ //# sourceMappingURL=install-repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-repo.js","sourceRoot":"","sources":["../../src/commands/install-repo.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * 该命令假定调用前已执行完整构建流程。
5
5
  */
6
+ export declare function assertPortableArchive(archivePath: string): void;
6
7
  export declare function runReleaseLocal(options?: {
7
8
  root?: string;
8
9
  }): void;
@@ -4,21 +4,296 @@
4
4
  * 该命令假定调用前已执行完整构建流程。
5
5
  */
6
6
  import { spawnSync } from 'node:child_process';
7
- import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
8
- import { join } from 'node:path';
7
+ import { gunzipSync } from 'node:zlib';
8
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
9
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path';
9
10
  import chalk from 'chalk';
10
11
  import { loadAllPlugins, loadMarketplaceMetadata, resolveRepoContext, } from '../utils/helpers.js';
12
+ import { loadOpenSpecAdapters } from '../adapters/openspec.js';
11
13
  function assertExists(path, label) {
12
14
  if (!existsSync(path)) {
13
15
  throw new Error(`${label} 不存在,请先运行 npm run ci:local`);
14
16
  }
15
17
  }
16
- function copyDirectory(from, to) {
18
+ function isGeneratedMetadataPath(path) {
19
+ const name = basename(path);
20
+ return (name === '.DS_Store' ||
21
+ name === '__pycache__' ||
22
+ name.startsWith('._') ||
23
+ /\.py[co]$/i.test(name));
24
+ }
25
+ function copyDirectory(from, to, options = {}) {
26
+ const sourceRoot = resolve(from);
17
27
  cpSync(from, to, {
18
28
  recursive: true,
19
- filter: (src) => !src.endsWith('.DS_Store'),
29
+ verbatimSymlinks: true,
30
+ filter: (src) => {
31
+ if (isGeneratedMetadataPath(src))
32
+ return false;
33
+ if (options.excludePluginTests) {
34
+ const parts = relative(sourceRoot, resolve(src)).split(/[\\/]/);
35
+ if (parts.length >= 2 && parts[1] === 'tests')
36
+ return false;
37
+ }
38
+ return true;
39
+ },
20
40
  });
21
41
  }
42
+ function isInside(root, candidate) {
43
+ const value = relative(root, candidate);
44
+ return value === '' || (!value.startsWith('..') && !isAbsolute(value));
45
+ }
46
+ function assertPortableTree(root) {
47
+ const resolvedRoot = realpathSync(root);
48
+ const visit = (directory) => {
49
+ for (const name of readdirSync(directory)) {
50
+ const path = join(directory, name);
51
+ if (isGeneratedMetadataPath(path)) {
52
+ throw new Error(`发布包包含解释器或文件系统元数据: ${relative(root, path)}`);
53
+ }
54
+ const metadata = lstatSync(path);
55
+ if (metadata.isSymbolicLink()) {
56
+ const target = readlinkSync(path);
57
+ if (isAbsolute(target)) {
58
+ throw new Error(`发布包包含绝对符号链接: ${relative(root, path)}`);
59
+ }
60
+ const lexicalTarget = resolve(dirname(path), target);
61
+ if (!isInside(resolvedRoot, lexicalTarget)) {
62
+ throw new Error(`发布包包含越界符号链接: ${relative(root, path)}`);
63
+ }
64
+ let realTarget;
65
+ try {
66
+ realTarget = realpathSync(path);
67
+ }
68
+ catch {
69
+ throw new Error(`发布包包含断链符号链接: ${relative(root, path)}`);
70
+ }
71
+ if (!isInside(resolvedRoot, realTarget)) {
72
+ throw new Error(`发布包符号链接最终目标越界: ${relative(root, path)}`);
73
+ }
74
+ continue;
75
+ }
76
+ if (metadata.isDirectory()) {
77
+ visit(path);
78
+ continue;
79
+ }
80
+ if (!metadata.isFile()) {
81
+ throw new Error(`发布包包含不支持的文件类型: ${relative(root, path)}`);
82
+ }
83
+ }
84
+ };
85
+ visit(resolvedRoot);
86
+ }
87
+ function tarString(buffer, offset, length) {
88
+ const field = buffer.subarray(offset, offset + length);
89
+ const end = field.indexOf(0);
90
+ return field.subarray(0, end < 0 ? field.length : end).toString('utf-8').trim();
91
+ }
92
+ function tarNumber(buffer, offset, length) {
93
+ const field = buffer.subarray(offset, offset + length);
94
+ if (field.length && (field[0] & 0x80) !== 0) {
95
+ let value = BigInt(field[0] & 0x7f);
96
+ for (const byte of field.subarray(1))
97
+ value = (value << 8n) | BigInt(byte);
98
+ const numeric = Number(value);
99
+ if (!Number.isSafeInteger(numeric))
100
+ throw new Error('发布包 tar 数值字段超出安全范围');
101
+ return numeric;
102
+ }
103
+ const text = field.toString('ascii').replace(/\0.*$/s, '').trim();
104
+ if (!text)
105
+ return 0;
106
+ if (!/^[0-7]+$/.test(text))
107
+ throw new Error('发布包 tar 数值字段无效');
108
+ return Number.parseInt(text, 8);
109
+ }
110
+ function parsePax(payload) {
111
+ const values = {};
112
+ let offset = 0;
113
+ while (offset < payload.length) {
114
+ const space = payload.indexOf(0x20, offset);
115
+ if (space < 0)
116
+ throw new Error('发布包 PAX header 无效');
117
+ const lengthText = payload.subarray(offset, space).toString('ascii');
118
+ if (!/^[1-9][0-9]*$/.test(lengthText))
119
+ throw new Error('发布包 PAX 长度无效');
120
+ const length = Number.parseInt(lengthText, 10);
121
+ const end = offset + length;
122
+ if (!Number.isSafeInteger(length) || end > payload.length || end <= space + 1) {
123
+ throw new Error('发布包 PAX record 越界');
124
+ }
125
+ const record = payload.subarray(space + 1, end - 1).toString('utf-8');
126
+ const equals = record.indexOf('=');
127
+ if (equals > 0)
128
+ values[record.slice(0, equals)] = record.slice(equals + 1);
129
+ offset = end;
130
+ }
131
+ return values;
132
+ }
133
+ function safeArchivePath(value, label) {
134
+ if (!value ||
135
+ value.includes('\0') ||
136
+ value.includes('\\') ||
137
+ posix.isAbsolute(value) ||
138
+ /^[A-Za-z]:[\\/]/.test(value)) {
139
+ throw new Error(`发布包包含不安全${label}: ${value}`);
140
+ }
141
+ const parts = value.split('/').filter((part) => part !== '' && part !== '.');
142
+ if (parts.includes('..'))
143
+ throw new Error(`发布包包含不安全${label}: ${value}`);
144
+ const normalized = posix.normalize(value).replace(/^\.\//, '').replace(/\/$/, '');
145
+ if (!normalized || normalized === '..' || normalized.startsWith('../')) {
146
+ throw new Error(`发布包包含不安全${label}: ${value}`);
147
+ }
148
+ return normalized;
149
+ }
150
+ function safeArchiveLinkTarget(member, value) {
151
+ if (!value ||
152
+ value.includes('\0') ||
153
+ value.includes('\\') ||
154
+ posix.isAbsolute(value) ||
155
+ /^[A-Za-z]:[\\/]/.test(value)) {
156
+ throw new Error(`发布包包含不安全链接目标: ${member.path}`);
157
+ }
158
+ const target = member.type === 'symlink'
159
+ ? posix.normalize(posix.join(posix.dirname(member.path), value))
160
+ : posix.normalize(value);
161
+ if (!target || target === '..' || target.startsWith('../')) {
162
+ throw new Error(`发布包包含越界链接目标: ${member.path}`);
163
+ }
164
+ return target.replace(/^\.\//, '').replace(/\/$/, '');
165
+ }
166
+ function parseTarMembers(tar) {
167
+ const members = [];
168
+ let offset = 0;
169
+ let pendingName;
170
+ let pendingLink;
171
+ let pendingPax = {};
172
+ let globalPax = {};
173
+ let ended = false;
174
+ while (offset + 512 <= tar.length) {
175
+ const header = tar.subarray(offset, offset + 512);
176
+ if (header.every((byte) => byte === 0)) {
177
+ const second = tar.subarray(offset + 512, offset + 1024);
178
+ if (second.length !== 512 || !second.every((byte) => byte === 0)) {
179
+ throw new Error('发布包 tar 缺少两个结束块');
180
+ }
181
+ if (!tar.subarray(offset + 1024).every((byte) => byte === 0)) {
182
+ throw new Error('发布包 tar 结束块后包含非零内容');
183
+ }
184
+ ended = true;
185
+ offset = tar.length;
186
+ break;
187
+ }
188
+ const storedChecksum = tarNumber(header, 148, 8);
189
+ let computedChecksum = 0;
190
+ for (let index = 0; index < header.length; index += 1) {
191
+ computedChecksum += index >= 148 && index < 156 ? 0x20 : header[index];
192
+ }
193
+ if (storedChecksum !== computedChecksum) {
194
+ throw new Error('发布包 tar header checksum 无效');
195
+ }
196
+ const magic = header.subarray(257, 263);
197
+ const version = header.subarray(263, 265).toString('ascii');
198
+ if (!magic.equals(Buffer.from([0x75, 0x73, 0x74, 0x61, 0x72, 0x00])) || version !== '00') {
199
+ throw new Error('发布包 tar magic/version 无效');
200
+ }
201
+ const size = tarNumber(header, 124, 12);
202
+ const typeFlag = String.fromCharCode(header[156] || 0);
203
+ const prefix = tarString(header, 345, 155);
204
+ const headerName = [prefix, tarString(header, 0, 100)].filter(Boolean).join('/');
205
+ const dataStart = offset + 512;
206
+ const dataEnd = dataStart + size;
207
+ if (dataEnd > tar.length)
208
+ throw new Error('发布包 tar member 内容越界');
209
+ const payload = tar.subarray(dataStart, dataEnd);
210
+ if (typeFlag === 'x' || typeFlag === 'g') {
211
+ const values = parsePax(payload);
212
+ if (typeFlag === 'g')
213
+ globalPax = { ...globalPax, ...values };
214
+ else
215
+ pendingPax = values;
216
+ }
217
+ else if (typeFlag === 'L') {
218
+ pendingName = payload.toString('utf-8').replace(/\0.*$/s, '').replace(/\n$/, '');
219
+ }
220
+ else if (typeFlag === 'K') {
221
+ pendingLink = payload.toString('utf-8').replace(/\0.*$/s, '').replace(/\n$/, '');
222
+ }
223
+ else {
224
+ const attributes = { ...globalPax, ...pendingPax };
225
+ const memberPath = safeArchivePath(attributes.path || pendingName || headerName, '成员路径');
226
+ const type = typeFlag === '\0' || typeFlag === '0'
227
+ ? 'file'
228
+ : typeFlag === '5'
229
+ ? 'directory'
230
+ : typeFlag === '2'
231
+ ? 'symlink'
232
+ : typeFlag === '1'
233
+ ? 'hardlink'
234
+ : null;
235
+ if (type === null) {
236
+ throw new Error(`发布包包含不支持的 tar header type ${JSON.stringify(typeFlag)}: ${memberPath}`);
237
+ }
238
+ const rawLink = attributes.linkpath || pendingLink || tarString(header, 157, 100);
239
+ members.push({ path: memberPath, type, ...(rawLink ? { linkName: rawLink } : {}) });
240
+ pendingName = undefined;
241
+ pendingLink = undefined;
242
+ pendingPax = {};
243
+ }
244
+ offset = dataStart + Math.ceil(size / 512) * 512;
245
+ }
246
+ if (offset > tar.length || !ended)
247
+ throw new Error('发布包 tar 对齐或结束区无效');
248
+ return members;
249
+ }
250
+ export function assertPortableArchive(archivePath) {
251
+ let tar;
252
+ try {
253
+ tar = gunzipSync(readFileSync(archivePath));
254
+ }
255
+ catch {
256
+ throw new Error(`发布包压缩流无效: ${archivePath}`);
257
+ }
258
+ const sensitiveText = tar.toString('utf-8');
259
+ if (/\/(?:Users|home)\/[A-Za-z0-9._-]+\//.test(sensitiveText) ||
260
+ /\/(?:private\/tmp|private\/var\/folders|var\/folders)\//.test(sensitiveText) ||
261
+ /[A-Za-z]:\\Users\\[^\\\r\n\0]+\\/i.test(sensitiveText)) {
262
+ throw new Error('发布包包含本机绝对路径');
263
+ }
264
+ const members = parseTarMembers(tar);
265
+ const memberPaths = new Set();
266
+ for (const member of members) {
267
+ if (memberPaths.has(member.path)) {
268
+ throw new Error(`发布包包含重复成员路径: ${member.path}`);
269
+ }
270
+ memberPaths.add(member.path);
271
+ }
272
+ for (const member of members) {
273
+ const parts = member.path.split('/');
274
+ if (parts.some((part) => part.startsWith('._') || part === '__pycache__' || /\.py[co]$/i.test(part))) {
275
+ throw new Error(`发布包包含解释器或文件系统元数据: ${member.path}`);
276
+ }
277
+ if (member.type === 'symlink' || member.type === 'hardlink') {
278
+ const target = safeArchiveLinkTarget(member, member.linkName || '');
279
+ if (!memberPaths.has(target)) {
280
+ throw new Error(`发布包包含越界或断开的 ${member.type}: ${member.path}`);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ function listComponentLabels(config) {
286
+ const labels = [];
287
+ if (config.components.skills?.length)
288
+ labels.push('Skill');
289
+ if (config.components.mcp?.length)
290
+ labels.push('MCP');
291
+ if (config.components.lsp?.length)
292
+ labels.push('LSP');
293
+ if (config.components.hooks?.length)
294
+ labels.push('Hook');
295
+ return labels;
296
+ }
22
297
  function writeReleaseReadme(releaseDir, marketplaceName, pluginCount) {
23
298
  const readme = `# ${marketplaceName} 本地发布包
24
299
 
@@ -26,9 +301,15 @@ function writeReleaseReadme(releaseDir, marketplaceName, pluginCount) {
26
301
 
27
302
  ## 内容
28
303
 
29
- - \`plugins/\`: 插件源文件以及生成后的 Claude Code / Codex manifest
304
+ - \`plugins/\`: 每个目录都是带根 \`plugin.json\` 的 Agent Plugins 1.0 包,同时保留 Claude Code / Codex manifest
305
+ - 插件源码中的顶层 \`tests/\` 不进入消费者发布包
306
+ - 插件内 \`mcp.json\`: Agent Plugins 1.0 MCP 配置(仅 MCP 插件)
307
+ - 插件内 \`.lsp.json\`: Claude Code LSP server 配置(仅 LSP 插件)
30
308
  - \`.claude-plugin/marketplace.json\`: Claude Code marketplace manifest
309
+ - \`.github/plugin/marketplace.json\`: GitHub Copilot / VS Code marketplace manifest
31
310
  - \`.agents/plugins/marketplace.json\`: Codex repo marketplace manifest
311
+ - \`.cursor-plugin/marketplace.json\`: Cursor team marketplace manifest(仓库导入要求 GitHub/GitHub Enterprise)
312
+ - \`.openspec-plugin/source-adapters.json\`: OpenSpec source adapter sidecar 索引
32
313
  - \`marketplace.json\`: 根目录兼容镜像
33
314
  - \`marketplace.yaml\`: marketplace 元数据
34
315
  - \`release-manifest.json\`: 本次发布包的元信息
@@ -46,10 +327,12 @@ function createArchive(distDir, marketplaceName) {
46
327
  const archivePath = join(distDir, archiveName);
47
328
  const result = spawnSync('tar', ['-czf', archivePath, '-C', distDir, 'release'], {
48
329
  stdio: 'inherit',
330
+ env: { ...process.env, COPYFILE_DISABLE: '1' },
49
331
  });
50
332
  if (result.status !== 0) {
51
333
  throw new Error(`发布包压缩失败: ${archiveName}`);
52
334
  }
335
+ assertPortableArchive(archivePath);
53
336
  return archivePath;
54
337
  }
55
338
  export function runReleaseLocal(options = {}) {
@@ -59,25 +342,39 @@ export function runReleaseLocal(options = {}) {
59
342
  const plugins = loadAllPlugins(context);
60
343
  const releaseDir = join(context.distDir, 'release');
61
344
  assertExists(join(context.rootDir, '.claude-plugin/marketplace.json'), 'Claude marketplace manifest');
345
+ assertExists(join(context.rootDir, '.github/plugin/marketplace.json'), 'GitHub Copilot marketplace manifest');
62
346
  assertExists(join(context.rootDir, '.agents/plugins/marketplace.json'), 'Codex marketplace manifest');
347
+ assertExists(join(context.rootDir, '.cursor-plugin/marketplace.json'), 'Cursor marketplace manifest');
348
+ assertExists(join(context.rootDir, '.openspec-plugin/source-adapters.json'), 'OpenSpec source adapter sidecar 索引');
63
349
  assertExists(join(context.rootDir, 'marketplace.json'), 'marketplace.json');
64
350
  assertExists(context.marketplaceYamlPath, 'marketplace.yaml');
65
351
  assertExists(join(context.pluginsDir, 'CATALOG.md'), 'plugins/CATALOG.md');
66
352
  for (const { name, dir, config } of plugins) {
353
+ assertExists(join(dir, 'plugin.json'), `${name} Agent Plugins manifest`);
67
354
  assertExists(join(dir, '.claude-plugin/plugin.json'), `${name} Claude manifest`);
68
355
  assertExists(join(dir, '.codex-plugin/plugin.json'), `${name} Codex manifest`);
69
356
  if (config.components.mcp?.length) {
357
+ assertExists(join(dir, 'mcp.json'), `${name} Agent Plugins MCP 配置`);
70
358
  assertExists(join(dir, '.mcp.json'), `${name} MCP 配置`);
71
359
  }
360
+ if (config.components.lsp?.length) {
361
+ assertExists(join(dir, '.lsp.json'), `${name} LSP 配置`);
362
+ }
72
363
  if (config.components.hooks?.length) {
73
364
  assertExists(join(dir, 'hooks/hooks.json'), `${name} hooks 配置`);
74
365
  }
366
+ if (loadOpenSpecAdapters(dir).length) {
367
+ assertExists(join(dir, '.openspec-plugin/plugin.json'), `${name} OpenSpec sidecar`);
368
+ }
75
369
  }
76
370
  rmSync(releaseDir, { recursive: true, force: true });
77
371
  mkdirSync(releaseDir, { recursive: true });
78
- copyDirectory(context.pluginsDir, join(releaseDir, 'plugins'));
372
+ copyDirectory(context.pluginsDir, join(releaseDir, 'plugins'), { excludePluginTests: true });
79
373
  copyDirectory(join(context.rootDir, '.claude-plugin'), join(releaseDir, '.claude-plugin'));
80
- copyDirectory(join(context.rootDir, '.agents'), join(releaseDir, '.agents'));
374
+ copyDirectory(join(context.rootDir, '.github', 'plugin'), join(releaseDir, '.github', 'plugin'));
375
+ copyDirectory(join(context.rootDir, '.agents', 'plugins'), join(releaseDir, '.agents', 'plugins'));
376
+ copyDirectory(join(context.rootDir, '.cursor-plugin'), join(releaseDir, '.cursor-plugin'));
377
+ copyDirectory(join(context.rootDir, '.openspec-plugin'), join(releaseDir, '.openspec-plugin'));
81
378
  cpSync(join(context.rootDir, 'marketplace.json'), join(releaseDir, 'marketplace.json'));
82
379
  cpSync(context.marketplaceYamlPath, join(releaseDir, 'marketplace.yaml'));
83
380
  const manifest = {
@@ -87,17 +384,23 @@ export function runReleaseLocal(options = {}) {
87
384
  name,
88
385
  version: config.version,
89
386
  category: config.category || 'general',
387
+ components: listComponentLabels(config),
90
388
  })),
91
389
  artifacts: {
92
390
  plugins: 'plugins/',
391
+ agentPluginRoots: 'plugins/*/',
93
392
  claudeMarketplace: '.claude-plugin/marketplace.json',
393
+ copilotMarketplace: '.github/plugin/marketplace.json',
94
394
  codexMarketplace: '.agents/plugins/marketplace.json',
395
+ cursorMarketplace: '.cursor-plugin/marketplace.json',
396
+ openSpecSourceAdapters: '.openspec-plugin/source-adapters.json',
95
397
  marketplaceMirror: 'marketplace.json',
96
398
  marketplaceMetadata: 'marketplace.yaml',
97
399
  },
98
400
  };
99
401
  writeFileSync(join(releaseDir, 'release-manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
100
402
  writeReleaseReadme(releaseDir, marketplace.name, plugins.length);
403
+ assertPortableTree(releaseDir);
101
404
  const archivePath = createArchive(context.distDir, marketplace.name);
102
405
  console.log(chalk.green(` ✓ dist/release (${plugins.length} 个插件)`));
103
406
  console.log(chalk.green(` ✓ ${archivePath}`));