agent-plugkit 0.1.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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/dist/adapters/agent-plugins.d.ts +35 -0
  4. package/dist/adapters/agent-plugins.js +57 -0
  5. package/dist/adapters/agent-plugins.js.map +1 -0
  6. package/dist/adapters/claude.d.ts +64 -0
  7. package/dist/adapters/claude.js +123 -0
  8. package/dist/adapters/claude.js.map +1 -0
  9. package/dist/adapters/cli-validation.d.ts +12 -0
  10. package/dist/adapters/cli-validation.js +31 -0
  11. package/dist/adapters/cli-validation.js.map +1 -0
  12. package/dist/adapters/codex.d.ts +62 -0
  13. package/dist/adapters/codex.js +91 -0
  14. package/dist/adapters/codex.js.map +1 -0
  15. package/dist/adapters/marketplace-registration.d.ts +17 -0
  16. package/dist/adapters/marketplace-registration.js +369 -0
  17. package/dist/adapters/marketplace-registration.js.map +1 -0
  18. package/dist/adapters/marketplace.d.ts +11 -0
  19. package/dist/adapters/marketplace.js +96 -0
  20. package/dist/adapters/marketplace.js.map +1 -0
  21. package/dist/adapters/mcp.d.ts +17 -0
  22. package/dist/adapters/mcp.js +28 -0
  23. package/dist/adapters/mcp.js.map +1 -0
  24. package/dist/application/artifact-generation.d.ts +58 -0
  25. package/dist/application/artifact-generation.js +344 -0
  26. package/dist/application/artifact-generation.js.map +1 -0
  27. package/dist/application/local-release.d.ts +122 -0
  28. package/dist/application/local-release.js +940 -0
  29. package/dist/application/local-release.js.map +1 -0
  30. package/dist/application/marketplace-document-contract.d.ts +81 -0
  31. package/dist/application/marketplace-document-contract.js +2 -0
  32. package/dist/application/marketplace-document-contract.js.map +1 -0
  33. package/dist/application/marketplace-document.d.ts +45 -0
  34. package/dist/application/marketplace-document.js +557 -0
  35. package/dist/application/marketplace-document.js.map +1 -0
  36. package/dist/application/marketplace-registration-contract.d.ts +51 -0
  37. package/dist/application/marketplace-registration-contract.js +11 -0
  38. package/dist/application/marketplace-registration-contract.js.map +1 -0
  39. package/dist/application/marketplace-registration.d.ts +20 -0
  40. package/dist/application/marketplace-registration.js +304 -0
  41. package/dist/application/marketplace-registration.js.map +1 -0
  42. package/dist/application/marketplace.d.ts +9 -0
  43. package/dist/application/marketplace.js +32 -0
  44. package/dist/application/marketplace.js.map +1 -0
  45. package/dist/application/plugin-components-contract.d.ts +135 -0
  46. package/dist/application/plugin-components-contract.js +2 -0
  47. package/dist/application/plugin-components-contract.js.map +1 -0
  48. package/dist/application/plugin-components.d.ts +17 -0
  49. package/dist/application/plugin-components.js +1000 -0
  50. package/dist/application/plugin-components.js.map +1 -0
  51. package/dist/application/plugin-document-contract.d.ts +115 -0
  52. package/dist/application/plugin-document-contract.js +2 -0
  53. package/dist/application/plugin-document-contract.js.map +1 -0
  54. package/dist/application/plugin-document.d.ts +69 -0
  55. package/dist/application/plugin-document.js +1037 -0
  56. package/dist/application/plugin-document.js.map +1 -0
  57. package/dist/application/plugin-lifecycle-contract.d.ts +230 -0
  58. package/dist/application/plugin-lifecycle-contract.js +3 -0
  59. package/dist/application/plugin-lifecycle-contract.js.map +1 -0
  60. package/dist/application/plugin-lifecycle.d.ts +8 -0
  61. package/dist/application/plugin-lifecycle.js +1375 -0
  62. package/dist/application/plugin-lifecycle.js.map +1 -0
  63. package/dist/application/plugin-scaffold.d.ts +18 -0
  64. package/dist/application/plugin-scaffold.js +188 -0
  65. package/dist/application/plugin-scaffold.js.map +1 -0
  66. package/dist/application/plugin-source.d.ts +39 -0
  67. package/dist/application/plugin-source.js +59 -0
  68. package/dist/application/plugin-source.js.map +1 -0
  69. package/dist/application/skill-document-contract.d.ts +92 -0
  70. package/dist/application/skill-document-contract.js +8 -0
  71. package/dist/application/skill-document-contract.js.map +1 -0
  72. package/dist/application/skill-document-guided.d.ts +3 -0
  73. package/dist/application/skill-document-guided.js +53 -0
  74. package/dist/application/skill-document-guided.js.map +1 -0
  75. package/dist/application/skill-document.d.ts +19 -0
  76. package/dist/application/skill-document.js +368 -0
  77. package/dist/application/skill-document.js.map +1 -0
  78. package/dist/application/workspace-create-contract.d.ts +50 -0
  79. package/dist/application/workspace-create-contract.js +16 -0
  80. package/dist/application/workspace-create-contract.js.map +1 -0
  81. package/dist/application/workspace-create.d.ts +8 -0
  82. package/dist/application/workspace-create.js +656 -0
  83. package/dist/application/workspace-create.js.map +1 -0
  84. package/dist/application/workspace-health-contract.d.ts +133 -0
  85. package/dist/application/workspace-health-contract.js +272 -0
  86. package/dist/application/workspace-health-contract.js.map +1 -0
  87. package/dist/application/workspace-health.d.ts +10 -0
  88. package/dist/application/workspace-health.js +1114 -0
  89. package/dist/application/workspace-health.js.map +1 -0
  90. package/dist/application/workspace-operation-contract.d.ts +99 -0
  91. package/dist/application/workspace-operation-contract.js +450 -0
  92. package/dist/application/workspace-operation-contract.js.map +1 -0
  93. package/dist/application/workspace-operation.d.ts +42 -0
  94. package/dist/application/workspace-operation.js +517 -0
  95. package/dist/application/workspace-operation.js.map +1 -0
  96. package/dist/cli.d.ts +16 -0
  97. package/dist/cli.js +136 -0
  98. package/dist/cli.js.map +1 -0
  99. package/dist/commands/add.d.ts +8 -0
  100. package/dist/commands/add.js +77 -0
  101. package/dist/commands/add.js.map +1 -0
  102. package/dist/commands/build.d.ts +7 -0
  103. package/dist/commands/build.js +29 -0
  104. package/dist/commands/build.js.map +1 -0
  105. package/dist/commands/component-scaffold.d.ts +4 -0
  106. package/dist/commands/component-scaffold.js +14 -0
  107. package/dist/commands/component-scaffold.js.map +1 -0
  108. package/dist/commands/import-skill.d.ts +15 -0
  109. package/dist/commands/import-skill.js +41 -0
  110. package/dist/commands/import-skill.js.map +1 -0
  111. package/dist/commands/index-gen.d.ts +12 -0
  112. package/dist/commands/index-gen.js +33 -0
  113. package/dist/commands/index-gen.js.map +1 -0
  114. package/dist/commands/init-repo.d.ts +8 -0
  115. package/dist/commands/init-repo.js +53 -0
  116. package/dist/commands/init-repo.js.map +1 -0
  117. package/dist/commands/init.d.ts +9 -0
  118. package/dist/commands/init.js +33 -0
  119. package/dist/commands/init.js.map +1 -0
  120. package/dist/commands/install-repo.d.ts +20 -0
  121. package/dist/commands/install-repo.js +200 -0
  122. package/dist/commands/install-repo.js.map +1 -0
  123. package/dist/commands/release-local.d.ts +9 -0
  124. package/dist/commands/release-local.js +27 -0
  125. package/dist/commands/release-local.js.map +1 -0
  126. package/dist/commands/validate.d.ts +8 -0
  127. package/dist/commands/validate.js +45 -0
  128. package/dist/commands/validate.js.map +1 -0
  129. package/dist/core/errors.d.ts +5 -0
  130. package/dist/core/errors.js +12 -0
  131. package/dist/core/errors.js.map +1 -0
  132. package/dist/generated/skill-content.d.ts +3 -0
  133. package/dist/generated/skill-content.js +9 -0
  134. package/dist/generated/skill-content.js.map +1 -0
  135. package/dist/infrastructure/authorized-path.d.ts +20 -0
  136. package/dist/infrastructure/authorized-path.js +85 -0
  137. package/dist/infrastructure/authorized-path.js.map +1 -0
  138. package/dist/infrastructure/document-commit.d.ts +165 -0
  139. package/dist/infrastructure/document-commit.js +1352 -0
  140. package/dist/infrastructure/document-commit.js.map +1 -0
  141. package/dist/infrastructure/process-runner.d.ts +29 -0
  142. package/dist/infrastructure/process-runner.js +130 -0
  143. package/dist/infrastructure/process-runner.js.map +1 -0
  144. package/dist/infrastructure/vscode-user-settings.d.ts +42 -0
  145. package/dist/infrastructure/vscode-user-settings.js +509 -0
  146. package/dist/infrastructure/vscode-user-settings.js.map +1 -0
  147. package/dist/schema/plugin-yaml.d.ts +89 -0
  148. package/dist/schema/plugin-yaml.js +241 -0
  149. package/dist/schema/plugin-yaml.js.map +1 -0
  150. package/dist/utils/frontmatter.d.ts +30 -0
  151. package/dist/utils/frontmatter.js +60 -0
  152. package/dist/utils/frontmatter.js.map +1 -0
  153. package/dist/utils/helpers.d.ts +44 -0
  154. package/dist/utils/helpers.js +94 -0
  155. package/dist/utils/helpers.js.map +1 -0
  156. package/package.json +56 -0
@@ -0,0 +1,656 @@
1
+ import { constants, copyFileSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, rmdirSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { basename, dirname, join, relative, resolve } from 'node:path';
4
+ import { PLUGKIT_MAINTAIN_SKILL, PLUGKIT_PLUGIN_YAML, PLUGKIT_SETUP_SKILL, } from '../generated/skill-content.js';
5
+ import { AuthorizedPathError, authorizeExistingDirectory, resolveAuthorizedPath, } from '../infrastructure/authorized-path.js';
6
+ const CLI_PACKAGE = 'agent-plugkit';
7
+ const CLI_RUNNER = `npx ${CLI_PACKAGE}`;
8
+ const DEFAULT_SCRIPTS = {
9
+ mp: CLI_RUNNER,
10
+ 'validate:plugins': `${CLI_RUNNER} validate --all`,
11
+ 'build:plugins': `${CLI_RUNNER} build --all`,
12
+ 'build:index': `${CLI_RUNNER} index`,
13
+ 'build:all': 'npm run build:plugins && npm run build:index',
14
+ 'ci:local': 'npm run build:all && npm run validate:plugins',
15
+ 'release:local': `npm run ci:local && ${CLI_RUNNER} release-local`,
16
+ };
17
+ function marketplaceYaml(name, organization) {
18
+ const title = toTitleCase(name);
19
+ return `# Marketplace 元数据
20
+
21
+ name: ${yamlScalar(name)}
22
+ description: ${yamlScalar(`${title} 插件市场`)}
23
+ organization: ${yamlScalar(organization)}
24
+
25
+ categories:
26
+ - id: tooling
27
+ label: "工具"
28
+ description: "开发工具和脚手架"
29
+ - id: integration
30
+ label: "集成"
31
+ description: "第三方服务连接器"
32
+ - id: quality
33
+ label: "质量"
34
+ description: "质量检查和审查工作流"
35
+ - id: workflow
36
+ label: "工作流"
37
+ description: "自动化工作流"
38
+ - id: general
39
+ label: "通用"
40
+ description: "其他插件"
41
+
42
+ platforms:
43
+ - name: "Agent Plugins"
44
+ manifest: "plugin.json"
45
+ - name: "Claude Code"
46
+ manifest: ".claude-plugin/plugin.json"
47
+ - name: "Codex"
48
+ manifest: ".codex-plugin/plugin.json"
49
+ `;
50
+ }
51
+ function yamlScalar(value) {
52
+ // JSON strings are valid YAML scalars and make line breaks, quotes and colons inert.
53
+ return JSON.stringify(value);
54
+ }
55
+ function toTitleCase(kebab) {
56
+ return kebab
57
+ .split('-')
58
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
59
+ .join(' ');
60
+ }
61
+ function defaultReadme(name) {
62
+ return `# ${toTitleCase(name)}
63
+
64
+ 这是一个由 \`${CLI_PACKAGE}\` 维护的 AI agent plugin marketplace 仓库。
65
+
66
+ \`plugin.yaml\` 是单一事实源;\`build\` 生成 Agent Plugins 1.0 portable 文件及 Claude Code / Codex 原生产物,\`index\` 生成 Copilot/VS Code、Cursor、Claude Code 与 Codex 的客户端索引。
67
+
68
+ ## 常用流程
69
+
70
+ \`\`\`bash
71
+ npx ${CLI_PACKAGE} init my-plugin
72
+ npx ${CLI_PACKAGE} add skill plugkit audit
73
+ npx ${CLI_PACKAGE} validate --all
74
+ npx ${CLI_PACKAGE} build --all
75
+ npx ${CLI_PACKAGE} index
76
+ \`\`\`
77
+
78
+ \`release-local\` 只创建本地发布目录与压缩包,不会执行 Git push、远端 marketplace 注册或客户端安装。
79
+ `;
80
+ }
81
+ function parsePackageJsonContent(raw) {
82
+ const parsed = JSON.parse(raw);
83
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
84
+ throw new Error('package.json 必须包含 JSON 对象');
85
+ }
86
+ return parsed;
87
+ }
88
+ function packageContent(root, name) {
89
+ const path = resolveAuthorizedPath(root, 'package.json');
90
+ const exists = existsSync(path);
91
+ const raw = exists ? readFileSync(path, 'utf8') : undefined;
92
+ const existing = raw === undefined
93
+ ? { name, version: '1.0.0', private: true }
94
+ : parsePackageJsonContent(raw);
95
+ if (existing.scripts !== undefined &&
96
+ (existing.scripts === null ||
97
+ typeof existing.scripts !== 'object' ||
98
+ Array.isArray(existing.scripts))) {
99
+ throw new Error('package.json 的 scripts 必须是 JSON 对象');
100
+ }
101
+ const existingScripts = existing.scripts ?? {};
102
+ const scripts = { ...existingScripts };
103
+ let changed = !exists;
104
+ for (const [scriptName, command] of Object.entries(DEFAULT_SCRIPTS)) {
105
+ if (!(scriptName in scripts)) {
106
+ scripts[scriptName] = command;
107
+ changed = true;
108
+ }
109
+ }
110
+ const next = { ...existing, scripts };
111
+ return {
112
+ content: `${JSON.stringify(next, null, 2)}\n`,
113
+ action: exists ? 'update' : 'create',
114
+ changed,
115
+ expectedState: raw === undefined
116
+ ? 'missing'
117
+ : `file:${createHash('sha256').update(raw).digest('hex')}`,
118
+ };
119
+ }
120
+ function fileState(root, relativePath) {
121
+ const path = resolveAuthorizedPath(root, relativePath);
122
+ if (!existsSync(path))
123
+ return 'missing';
124
+ const stat = lstatSync(path);
125
+ if (stat.isSymbolicLink()) {
126
+ throw new AuthorizedPathError('UNSAFE_SYMLINK', '创建目标不能使用符号链接', relativePath);
127
+ }
128
+ if (!stat.isFile())
129
+ return `non-file:${stat.mode}`;
130
+ return `file:${createHash('sha256').update(readFileSync(path)).digest('hex')}`;
131
+ }
132
+ function assertExistingParentsAreDirectories(root, relativePath) {
133
+ const parent = dirname(relativePath);
134
+ if (parent === '.')
135
+ return;
136
+ let cursor = '';
137
+ for (const segment of parent.split('/')) {
138
+ cursor = cursor ? `${cursor}/${segment}` : segment;
139
+ const path = resolveAuthorizedPath(root, cursor);
140
+ if (!existsSync(path))
141
+ return;
142
+ if (!lstatSync(path).isDirectory()) {
143
+ throw new AuthorizedPathError('UNSAFE_SYMLINK', `${cursor} 必须是文件夹`, cursor);
144
+ }
145
+ }
146
+ }
147
+ function assertAuthorizedRootAvailable(root) {
148
+ const current = authorizeExistingDirectory(root.canonicalPath);
149
+ if (current.canonicalPath !== root.canonicalPath) {
150
+ throw new AuthorizedPathError('PATH_UNAVAILABLE', '目标文件夹的授权位置已经变化', root.canonicalPath);
151
+ }
152
+ }
153
+ function fingerprint(request, root, paths) {
154
+ assertAuthorizedRootAvailable(root);
155
+ const states = paths
156
+ .map((path) => `${path}:${fileState(root, path)}`)
157
+ .sort()
158
+ .join('\n');
159
+ return createHash('sha256')
160
+ .update(JSON.stringify({
161
+ root: root.canonicalPath,
162
+ name: request.name,
163
+ organization: request.organization,
164
+ includePlugkit: request.includePlugkit,
165
+ }))
166
+ .update('\n')
167
+ .update(states)
168
+ .digest('hex');
169
+ }
170
+ function conflictFromError(error) {
171
+ if (error instanceof AuthorizedPathError) {
172
+ const unavailableCodes = new Set([
173
+ 'EMPTY_PATH',
174
+ 'PATH_NOT_FOUND',
175
+ 'PATH_UNAVAILABLE',
176
+ 'NOT_A_DIRECTORY',
177
+ ]);
178
+ return {
179
+ code: unavailableCodes.has(error.code)
180
+ ? 'PATH_UNAVAILABLE'
181
+ : 'UNSAFE_PATH',
182
+ title: unavailableCodes.has(error.code)
183
+ ? '目标位置当前不可用'
184
+ : '目标位置不安全',
185
+ message: error.message,
186
+ relativePath: error.path,
187
+ };
188
+ }
189
+ if (error !== null &&
190
+ typeof error === 'object' &&
191
+ 'code' in error &&
192
+ typeof error.code === 'string' &&
193
+ new Set(['ENOENT', 'EACCES', 'EPERM', 'ENOTDIR', 'EIO']).has(error.code)) {
194
+ return {
195
+ code: 'PATH_UNAVAILABLE',
196
+ title: '目标位置当前不可用',
197
+ message: '目标文件夹在检查期间发生变化或当前无法访问。',
198
+ };
199
+ }
200
+ return {
201
+ code: 'INVALID_PACKAGE_JSON',
202
+ title: '无法保留现有 package.json',
203
+ message: error instanceof Error ? error.message : String(error),
204
+ relativePath: 'package.json',
205
+ };
206
+ }
207
+ function blockedPlanInternals(request, error) {
208
+ const conflict = conflictFromError(error);
209
+ const targetPath = resolve(request.directory);
210
+ return {
211
+ desiredFiles: [],
212
+ createPluginsDirectory: false,
213
+ plan: {
214
+ status: 'blocked',
215
+ targetPath,
216
+ marketplaceName: request.name,
217
+ organization: request.organization,
218
+ includePlugkit: request.includePlugkit,
219
+ changes: [],
220
+ conflicts: [conflict],
221
+ preservationNote: '目标文件夹不可用,因此没有读取、创建或修改任何内容。',
222
+ fingerprint: createHash('sha256')
223
+ .update(JSON.stringify({
224
+ directory: targetPath,
225
+ name: request.name,
226
+ organization: request.organization,
227
+ includePlugkit: request.includePlugkit,
228
+ blocked: conflict.code,
229
+ }))
230
+ .digest('hex'),
231
+ },
232
+ };
233
+ }
234
+ function planInternalsUnsafe(request) {
235
+ let authorization;
236
+ try {
237
+ authorization = authorizeExistingDirectory(request.directory);
238
+ }
239
+ catch (error) {
240
+ return blockedPlanInternals(request, error);
241
+ }
242
+ const changes = [];
243
+ const conflicts = [];
244
+ const desiredFiles = [];
245
+ let createPluginsDirectory = false;
246
+ const trackedPaths = [
247
+ 'marketplace.yaml',
248
+ 'package.json',
249
+ 'README.md',
250
+ 'plugins',
251
+ ];
252
+ if (request.includePlugkit) {
253
+ trackedPaths.push('plugins/plugkit/plugin.yaml', 'plugins/plugkit/skills/setup/SKILL.md', 'plugins/plugkit/skills/maintain/SKILL.md');
254
+ }
255
+ try {
256
+ assertAuthorizedRootAvailable(authorization);
257
+ const pluginsPath = resolveAuthorizedPath(authorization, 'plugins');
258
+ if (existsSync(pluginsPath)) {
259
+ const pluginsStat = lstatSync(pluginsPath);
260
+ if (!pluginsStat.isDirectory()) {
261
+ throw new AuthorizedPathError('UNSAFE_SYMLINK', 'plugins 必须是文件夹', 'plugins');
262
+ }
263
+ changes.push({
264
+ relativePath: 'plugins',
265
+ action: 'preserve',
266
+ summary: '保留现有插件目录与其中的全部内容',
267
+ });
268
+ }
269
+ else {
270
+ createPluginsDirectory = true;
271
+ changes.push({
272
+ relativePath: 'plugins',
273
+ action: 'create',
274
+ summary: '创建插件目录',
275
+ });
276
+ }
277
+ const marketplacePath = resolveAuthorizedPath(authorization, 'marketplace.yaml');
278
+ if (existsSync(marketplacePath)) {
279
+ fileState(authorization, 'marketplace.yaml');
280
+ conflicts.push({
281
+ code: 'MARKETPLACE_EXISTS',
282
+ title: '这个文件夹已经是 Marketplace',
283
+ message: 'marketplace.yaml 已存在;为避免覆盖,创建已停止。',
284
+ relativePath: 'marketplace.yaml',
285
+ });
286
+ changes.push({
287
+ relativePath: 'marketplace.yaml',
288
+ action: 'preserve',
289
+ summary: '保留已有 Marketplace 配置,不覆盖',
290
+ });
291
+ }
292
+ else {
293
+ desiredFiles.push({
294
+ relativePath: 'marketplace.yaml',
295
+ content: marketplaceYaml(request.name, request.organization),
296
+ action: 'create',
297
+ expectedState: 'missing',
298
+ });
299
+ changes.push({
300
+ relativePath: 'marketplace.yaml',
301
+ action: 'create',
302
+ summary: '创建插件集合的主配置',
303
+ });
304
+ }
305
+ const packagePlan = packageContent(authorization, request.name);
306
+ if (packagePlan.changed) {
307
+ desiredFiles.push({
308
+ relativePath: 'package.json',
309
+ content: packagePlan.content,
310
+ action: packagePlan.action,
311
+ expectedState: packagePlan.expectedState,
312
+ });
313
+ changes.push({
314
+ relativePath: 'package.json',
315
+ action: packagePlan.action,
316
+ summary: packagePlan.action === 'create'
317
+ ? '创建本地维护脚本'
318
+ : '只补充缺失的维护脚本,保留现有键和值',
319
+ });
320
+ }
321
+ else {
322
+ changes.push({
323
+ relativePath: 'package.json',
324
+ action: 'preserve',
325
+ summary: '现有维护脚本完整,保持原样',
326
+ });
327
+ }
328
+ const readmePath = resolveAuthorizedPath(authorization, 'README.md');
329
+ if (existsSync(readmePath)) {
330
+ fileState(authorization, 'README.md');
331
+ changes.push({
332
+ relativePath: 'README.md',
333
+ action: 'preserve',
334
+ summary: '保留现有说明文档',
335
+ });
336
+ }
337
+ else {
338
+ desiredFiles.push({
339
+ relativePath: 'README.md',
340
+ content: defaultReadme(request.name),
341
+ action: 'create',
342
+ expectedState: 'missing',
343
+ });
344
+ changes.push({
345
+ relativePath: 'README.md',
346
+ action: 'create',
347
+ summary: '创建本地使用说明',
348
+ });
349
+ }
350
+ if (request.includePlugkit) {
351
+ const plugkitFiles = [
352
+ ['plugins/plugkit/plugin.yaml', PLUGKIT_PLUGIN_YAML, '插件维护入口'],
353
+ [
354
+ 'plugins/plugkit/skills/setup/SKILL.md',
355
+ PLUGKIT_SETUP_SKILL,
356
+ '安装与设置指引',
357
+ ],
358
+ [
359
+ 'plugins/plugkit/skills/maintain/SKILL.md',
360
+ PLUGKIT_MAINTAIN_SKILL,
361
+ '日常维护指引',
362
+ ],
363
+ ];
364
+ for (const [relativePath, content, summary] of plugkitFiles) {
365
+ assertExistingParentsAreDirectories(authorization, relativePath);
366
+ const path = resolveAuthorizedPath(authorization, relativePath);
367
+ if (existsSync(path)) {
368
+ const state = fileState(authorization, relativePath);
369
+ if (!state.startsWith('file:')) {
370
+ throw new AuthorizedPathError('UNSAFE_SYMLINK', `${relativePath} 必须是文件`, relativePath);
371
+ }
372
+ changes.push({
373
+ relativePath,
374
+ action: 'preserve',
375
+ summary: `保留已有${summary}`,
376
+ });
377
+ }
378
+ else {
379
+ desiredFiles.push({
380
+ relativePath,
381
+ content,
382
+ action: 'create',
383
+ expectedState: 'missing',
384
+ });
385
+ changes.push({ relativePath, action: 'create', summary });
386
+ }
387
+ }
388
+ }
389
+ }
390
+ catch (error) {
391
+ conflicts.push(conflictFromError(error));
392
+ }
393
+ let comparisonToken;
394
+ try {
395
+ comparisonToken = fingerprint(request, authorization, trackedPaths);
396
+ }
397
+ catch (error) {
398
+ conflicts.push(conflictFromError(error));
399
+ comparisonToken = createHash('sha256')
400
+ .update(`${authorization.canonicalPath}:blocked`)
401
+ .digest('hex');
402
+ }
403
+ const uniqueConflicts = conflicts.filter((conflict, index, all) => all.findIndex((candidate) => candidate.code === conflict.code &&
404
+ candidate.relativePath === conflict.relativePath &&
405
+ candidate.message === conflict.message) === index);
406
+ return {
407
+ authorization,
408
+ desiredFiles,
409
+ createPluginsDirectory,
410
+ plan: {
411
+ status: uniqueConflicts.length === 0 ? 'ready' : 'blocked',
412
+ targetPath: authorization.canonicalPath,
413
+ marketplaceName: request.name,
414
+ organization: request.organization,
415
+ includePlugkit: request.includePlugkit,
416
+ changes,
417
+ conflicts: uniqueConflicts,
418
+ preservationNote: '未列出的文件与文件夹保持原样;现有 README 和 plugkit 文件不会被覆盖。',
419
+ fingerprint: comparisonToken,
420
+ },
421
+ };
422
+ }
423
+ function planInternals(request) {
424
+ try {
425
+ return planInternalsUnsafe(request);
426
+ }
427
+ catch (error) {
428
+ return blockedPlanInternals(request, error);
429
+ }
430
+ }
431
+ function ensureParentDirectories(root, relativePath, createdDirectories) {
432
+ const parent = dirname(relativePath);
433
+ if (parent === '.')
434
+ return;
435
+ let cursor = '';
436
+ for (const segment of parent.split('/')) {
437
+ cursor = cursor ? `${cursor}/${segment}` : segment;
438
+ const path = resolveAuthorizedPath(root, cursor);
439
+ if (!existsSync(path)) {
440
+ mkdirSync(path);
441
+ createdDirectories.push(path);
442
+ }
443
+ else if (!lstatSync(path).isDirectory()) {
444
+ throw new Error(`${cursor} 不是文件夹`);
445
+ }
446
+ }
447
+ }
448
+ function restoreTransaction(createdFiles, backups, createdDirectories) {
449
+ let complete = true;
450
+ for (const path of [...createdFiles].reverse()) {
451
+ try {
452
+ if (existsSync(path))
453
+ unlinkSync(path);
454
+ }
455
+ catch {
456
+ complete = false;
457
+ }
458
+ }
459
+ for (const { target, backup } of [...backups].reverse()) {
460
+ try {
461
+ if (existsSync(target))
462
+ unlinkSync(target);
463
+ renameSync(backup, target);
464
+ }
465
+ catch {
466
+ complete = false;
467
+ }
468
+ }
469
+ for (const path of [...createdDirectories].reverse()) {
470
+ try {
471
+ rmdirSync(path);
472
+ }
473
+ catch {
474
+ complete = false;
475
+ }
476
+ }
477
+ return complete;
478
+ }
479
+ class StaleWorkspaceCreationError extends Error {
480
+ constructor() {
481
+ super('目标文件夹在预览后发生了变化;请重新检查再创建。');
482
+ this.name = 'StaleWorkspaceCreationError';
483
+ }
484
+ }
485
+ export function planWorkspaceCreation(request) {
486
+ return planInternals(request).plan;
487
+ }
488
+ export function executeWorkspaceCreation(request, expectedFingerprint, dependencies = {}) {
489
+ const current = planInternals(request);
490
+ if (current.plan.status === 'blocked') {
491
+ return { status: 'blocked', conflicts: current.plan.conflicts };
492
+ }
493
+ if (current.authorization === undefined) {
494
+ return {
495
+ status: 'blocked',
496
+ conflicts: [
497
+ {
498
+ code: 'PATH_UNAVAILABLE',
499
+ title: '目标位置当前不可用',
500
+ message: '目标文件夹无法获得有效授权。',
501
+ },
502
+ ],
503
+ };
504
+ }
505
+ const authorization = current.authorization;
506
+ if (current.plan.fingerprint !== expectedFingerprint) {
507
+ return {
508
+ status: 'stale',
509
+ message: '目标文件夹在预览后发生了变化;请重新检查再创建。',
510
+ };
511
+ }
512
+ let stagingRoot;
513
+ try {
514
+ stagingRoot = mkdtempSync(join(authorization.canonicalPath, '.agent-plugkit-staging-'));
515
+ }
516
+ catch (error) {
517
+ return {
518
+ status: 'failed',
519
+ message: error instanceof Error
520
+ ? `无法准备安全写入:${error.message}`
521
+ : '无法准备安全写入',
522
+ changedPaths: [],
523
+ rollbackComplete: true,
524
+ };
525
+ }
526
+ const createdFiles = [];
527
+ const createdDirectories = [];
528
+ const backups = [];
529
+ let outcome;
530
+ try {
531
+ if (current.createPluginsDirectory) {
532
+ const pluginsPath = resolveAuthorizedPath(authorization, 'plugins');
533
+ mkdirSync(pluginsPath);
534
+ createdDirectories.push(pluginsPath);
535
+ }
536
+ for (const desired of current.desiredFiles) {
537
+ const staged = join(stagingRoot, desired.relativePath);
538
+ mkdirSync(dirname(staged), { recursive: true });
539
+ writeFileSync(staged, desired.content);
540
+ }
541
+ for (const [index, desired] of current.desiredFiles.entries()) {
542
+ dependencies.beforeApplyChange?.(desired.relativePath, index);
543
+ if (fileState(authorization, desired.relativePath) !==
544
+ desired.expectedState) {
545
+ throw new StaleWorkspaceCreationError();
546
+ }
547
+ const target = resolveAuthorizedPath(authorization, desired.relativePath);
548
+ ensureParentDirectories(authorization, desired.relativePath, createdDirectories);
549
+ const staged = join(stagingRoot, desired.relativePath);
550
+ if (desired.action === 'create') {
551
+ try {
552
+ linkSync(staged, target);
553
+ createdFiles.push(target);
554
+ unlinkSync(staged);
555
+ }
556
+ catch (error) {
557
+ if (error !== null &&
558
+ typeof error === 'object' &&
559
+ 'code' in error &&
560
+ error.code === 'EEXIST') {
561
+ throw new StaleWorkspaceCreationError();
562
+ }
563
+ throw error;
564
+ }
565
+ }
566
+ else {
567
+ const backup = join(stagingRoot, '.backup', desired.relativePath);
568
+ mkdirSync(dirname(backup), { recursive: true });
569
+ copyFileSync(target, backup, constants.COPYFILE_EXCL);
570
+ if (fileState(authorization, desired.relativePath) !==
571
+ desired.expectedState) {
572
+ throw new StaleWorkspaceCreationError();
573
+ }
574
+ renameSync(staged, target);
575
+ backups.push({ target, backup });
576
+ }
577
+ }
578
+ const written = [
579
+ ...(current.createPluginsDirectory ? ['plugins'] : []),
580
+ ...current.desiredFiles.map((file) => file.relativePath),
581
+ ];
582
+ const preserved = current.plan.changes
583
+ .filter((change) => change.action === 'preserve')
584
+ .map((change) => change.relativePath);
585
+ outcome = {
586
+ status: 'created',
587
+ targetPath: authorization.canonicalPath,
588
+ written,
589
+ preserved,
590
+ };
591
+ }
592
+ catch (error) {
593
+ const rollbackComplete = restoreTransaction(createdFiles, backups, createdDirectories);
594
+ const possiblyChangedPaths = [
595
+ ...createdFiles,
596
+ ...backups.map(({ target }) => target),
597
+ ...createdDirectories,
598
+ ]
599
+ .map((path) => relative(authorization.canonicalPath, path))
600
+ .filter((path, index, all) => all.indexOf(path) === index);
601
+ outcome =
602
+ error instanceof StaleWorkspaceCreationError && rollbackComplete
603
+ ? {
604
+ status: 'stale',
605
+ message: error.message,
606
+ }
607
+ : {
608
+ status: 'failed',
609
+ message: error instanceof Error ? error.message : String(error),
610
+ changedPaths: rollbackComplete ? [] : possiblyChangedPaths,
611
+ rollbackComplete,
612
+ };
613
+ }
614
+ try {
615
+ (dependencies.cleanupStaging ??
616
+ ((path) => rmSync(path, { recursive: true, force: true })))(stagingRoot);
617
+ }
618
+ catch (error) {
619
+ const stagingPath = relative(authorization.canonicalPath, stagingRoot);
620
+ if (outcome.status === 'created') {
621
+ return {
622
+ status: 'failed',
623
+ message: 'Marketplace 已写入,但安全写入的临时目录未能清理;请勿重复创建。',
624
+ changedPaths: [...outcome.written, stagingPath],
625
+ rollbackComplete: false,
626
+ };
627
+ }
628
+ if (outcome.status === 'stale') {
629
+ return {
630
+ status: 'failed',
631
+ message: `${outcome.message};临时目录清理也未完成:${error instanceof Error ? error.message : String(error)}`,
632
+ changedPaths: [stagingPath],
633
+ rollbackComplete: false,
634
+ };
635
+ }
636
+ return {
637
+ ...outcome,
638
+ message: `${outcome.message};临时目录清理也未完成:${error instanceof Error ? error.message : String(error)}`,
639
+ changedPaths: outcome.changedPaths.includes(stagingPath)
640
+ ? outcome.changedPaths
641
+ : [...outcome.changedPaths, stagingPath],
642
+ rollbackComplete: false,
643
+ };
644
+ }
645
+ return outcome;
646
+ }
647
+ export function normalizeWorkspaceCreationRequest(directory, name, organization, includePlugkit) {
648
+ const marketplaceName = name?.trim() || basename(directory);
649
+ return {
650
+ directory,
651
+ name: marketplaceName,
652
+ organization: organization?.trim() || toTitleCase(marketplaceName),
653
+ includePlugkit,
654
+ };
655
+ }
656
+ //# sourceMappingURL=workspace-create.js.map