work-agent 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 (245) hide show
  1. package/README.md +234 -0
  2. package/app/(admin)/approvals/page.tsx +16 -0
  3. package/app/(admin)/audit/page.tsx +18 -0
  4. package/app/(admin)/layout.tsx +47 -0
  5. package/app/(admin)/scheduled-tasks/page.tsx +17 -0
  6. package/app/(admin)/settings/page.tsx +46 -0
  7. package/app/(admin)/skills/[name]/page.tsx +378 -0
  8. package/app/(admin)/skills/page.tsx +406 -0
  9. package/app/(admin)/statistics/page.tsx +416 -0
  10. package/app/(admin)/tickets/[id]/page.tsx +348 -0
  11. package/app/(admin)/tickets/new/page.tsx +309 -0
  12. package/app/(admin)/tickets/page.tsx +27 -0
  13. package/app/api/audit/route.ts +30 -0
  14. package/app/api/auth/feishu/callback/route.ts +72 -0
  15. package/app/api/auth/feishu/login/route.ts +17 -0
  16. package/app/api/auth/feishu/sso/route.ts +78 -0
  17. package/app/api/auth/login/route.ts +85 -0
  18. package/app/api/auth/oauth/route.ts +168 -0
  19. package/app/api/config/providers/route.ts +105 -0
  20. package/app/api/config/route.ts +115 -0
  21. package/app/api/config/status/route.ts +56 -0
  22. package/app/api/config/test/route.ts +212 -0
  23. package/app/api/documents/[id]/route.ts +88 -0
  24. package/app/api/documents/route.ts +53 -0
  25. package/app/api/health/route.ts +32 -0
  26. package/app/api/knowledge/[id]/route.ts +152 -0
  27. package/app/api/knowledge/from-session/route.ts +27 -0
  28. package/app/api/knowledge/route.ts +100 -0
  29. package/app/api/market/knowledge/[id]/route.ts +92 -0
  30. package/app/api/market/knowledge/route.ts +130 -0
  31. package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
  32. package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
  33. package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
  34. package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
  35. package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
  36. package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
  37. package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
  38. package/app/api/marketplace/skills/[id]/route.ts +177 -0
  39. package/app/api/marketplace/skills/route.ts +235 -0
  40. package/app/api/memory/route.ts +40 -0
  41. package/app/api/my/files/[id]/route.ts +52 -0
  42. package/app/api/my/files/route.ts +230 -0
  43. package/app/api/my/knowledge/route.ts +36 -0
  44. package/app/api/pi-chat/route.ts +443 -0
  45. package/app/api/recommend/route.ts +38 -0
  46. package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
  47. package/app/api/scheduled-tasks/[id]/route.ts +165 -0
  48. package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
  49. package/app/api/scheduled-tasks/route.ts +101 -0
  50. package/app/api/sessions/[id]/messages/route.ts +212 -0
  51. package/app/api/sessions/route.ts +101 -0
  52. package/app/api/share/file/[id]/route.ts +37 -0
  53. package/app/api/skills/[name]/execute/route.ts +121 -0
  54. package/app/api/skills/[name]/route.ts +167 -0
  55. package/app/api/skills/create/route.ts +65 -0
  56. package/app/api/skills/generate/route.ts +405 -0
  57. package/app/api/skills/installed/route.ts +151 -0
  58. package/app/api/skills/route.ts +174 -0
  59. package/app/api/skills/translate/route.ts +40 -0
  60. package/app/api/skills/user/[name]/route.ts +159 -0
  61. package/app/api/skills/user/route.ts +90 -0
  62. package/app/api/statistics/route.ts +94 -0
  63. package/app/api/task-executions/[id]/route.ts +34 -0
  64. package/app/api/task-executions/route.ts +29 -0
  65. package/app/api/tickets/[id]/approve/route.ts +129 -0
  66. package/app/api/tickets/[id]/execute/route.ts +201 -0
  67. package/app/api/tickets/[id]/route.ts +127 -0
  68. package/app/api/tickets/route.ts +103 -0
  69. package/app/api/user/skills/route.ts +175 -0
  70. package/app/api/users/route.ts +80 -0
  71. package/app/chat/page.tsx +5 -0
  72. package/app/globals.css +84 -0
  73. package/app/h5/layout.tsx +5 -0
  74. package/app/h5/mobile-approvals-page.tsx +167 -0
  75. package/app/h5/mobile-chat-page.tsx +951 -0
  76. package/app/h5/mobile-profile-page.tsx +147 -0
  77. package/app/h5/mobile-tickets-page.tsx +121 -0
  78. package/app/h5/page.tsx +23 -0
  79. package/app/h5/ticket-action-buttons.tsx +80 -0
  80. package/app/layout.tsx +26 -0
  81. package/app/login/page.tsx +318 -0
  82. package/app/market/knowledge/[id]/page.tsx +77 -0
  83. package/app/market/knowledge/page.tsx +358 -0
  84. package/app/market/layout.tsx +29 -0
  85. package/app/market/page.tsx +18 -0
  86. package/app/market/skills/page.tsx +397 -0
  87. package/app/my/files/page.tsx +511 -0
  88. package/app/my/knowledge/[id]/page.tsx +271 -0
  89. package/app/my/knowledge/new/page.tsx +234 -0
  90. package/app/my/knowledge/page.tsx +248 -0
  91. package/app/my/layout.tsx +32 -0
  92. package/app/my/memory/page.tsx +164 -0
  93. package/app/my/page.tsx +18 -0
  94. package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
  95. package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
  96. package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
  97. package/app/my/scheduled-tasks/new/page.tsx +230 -0
  98. package/app/my/scheduled-tasks/page.tsx +27 -0
  99. package/app/my/skills/[name]/page.tsx +320 -0
  100. package/app/my/skills/new/page.tsx +394 -0
  101. package/app/my/skills/page.tsx +303 -0
  102. package/app/page.tsx +2288 -0
  103. package/app/share/[sessionId]/page.tsx +226 -0
  104. package/app/share/file/[id]/page.tsx +140 -0
  105. package/bin/README.md +63 -0
  106. package/bin/generate-api-system +300 -0
  107. package/bin/postinstall.js +95 -0
  108. package/bin/work-agent.js +173 -0
  109. package/components/ai-elements/agent.tsx +142 -0
  110. package/components/ai-elements/artifact.tsx +149 -0
  111. package/components/ai-elements/attachments.tsx +427 -0
  112. package/components/ai-elements/audio-player.tsx +232 -0
  113. package/components/ai-elements/canvas.tsx +26 -0
  114. package/components/ai-elements/chain-of-thought.tsx +223 -0
  115. package/components/ai-elements/checkpoint.tsx +72 -0
  116. package/components/ai-elements/code-block.tsx +555 -0
  117. package/components/ai-elements/commit.tsx +449 -0
  118. package/components/ai-elements/confirmation.tsx +173 -0
  119. package/components/ai-elements/connection.tsx +28 -0
  120. package/components/ai-elements/context.tsx +410 -0
  121. package/components/ai-elements/controls.tsx +19 -0
  122. package/components/ai-elements/conversation.tsx +167 -0
  123. package/components/ai-elements/edge.tsx +144 -0
  124. package/components/ai-elements/environment-variables.tsx +325 -0
  125. package/components/ai-elements/file-tree.tsx +298 -0
  126. package/components/ai-elements/image.tsx +25 -0
  127. package/components/ai-elements/inline-citation.tsx +294 -0
  128. package/components/ai-elements/jsx-preview.tsx +250 -0
  129. package/components/ai-elements/message.tsx +367 -0
  130. package/components/ai-elements/mic-selector.tsx +372 -0
  131. package/components/ai-elements/model-selector.tsx +214 -0
  132. package/components/ai-elements/node.tsx +72 -0
  133. package/components/ai-elements/open-in-chat.tsx +367 -0
  134. package/components/ai-elements/package-info.tsx +235 -0
  135. package/components/ai-elements/panel.tsx +16 -0
  136. package/components/ai-elements/persona.tsx +280 -0
  137. package/components/ai-elements/plan.tsx +144 -0
  138. package/components/ai-elements/prompt-input.tsx +1341 -0
  139. package/components/ai-elements/queue.tsx +275 -0
  140. package/components/ai-elements/reasoning.tsx +355 -0
  141. package/components/ai-elements/sandbox.tsx +133 -0
  142. package/components/ai-elements/schema-display.tsx +473 -0
  143. package/components/ai-elements/shimmer.tsx +78 -0
  144. package/components/ai-elements/snippet.tsx +141 -0
  145. package/components/ai-elements/sources.tsx +78 -0
  146. package/components/ai-elements/speech-input.tsx +324 -0
  147. package/components/ai-elements/stack-trace.tsx +531 -0
  148. package/components/ai-elements/suggestion.tsx +58 -0
  149. package/components/ai-elements/task.tsx +88 -0
  150. package/components/ai-elements/terminal.tsx +277 -0
  151. package/components/ai-elements/test-results.tsx +497 -0
  152. package/components/ai-elements/tool.tsx +174 -0
  153. package/components/ai-elements/toolbar.tsx +17 -0
  154. package/components/ai-elements/transcription.tsx +126 -0
  155. package/components/ai-elements/voice-selector.tsx +525 -0
  156. package/components/ai-elements/web-preview.tsx +282 -0
  157. package/components/audit-log-list.tsx +114 -0
  158. package/components/chat/EmptyPreviewState.tsx +12 -0
  159. package/components/chat/KnowledgePickerDialog.tsx +464 -0
  160. package/components/chat/KnowledgePreview.tsx +70 -0
  161. package/components/chat/KnowledgePreviewPanel.tsx +86 -0
  162. package/components/chat/MentionInput.tsx +309 -0
  163. package/components/chat/OrganizeDialog.tsx +258 -0
  164. package/components/chat/RecommendationBanner.tsx +94 -0
  165. package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
  166. package/components/chat/SkillSelector.tsx +305 -0
  167. package/components/chat/SkillSwitcher.tsx +163 -0
  168. package/components/client-layout.tsx +15 -0
  169. package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
  170. package/components/layout-wrapper.tsx +18 -0
  171. package/components/mobile-layout.tsx +62 -0
  172. package/components/scheduled-task-list.tsx +356 -0
  173. package/components/setup-guide.tsx +484 -0
  174. package/components/sub-nav.tsx +54 -0
  175. package/components/ticket-detail-content.tsx +383 -0
  176. package/components/ticket-list.tsx +366 -0
  177. package/components/top-nav.tsx +132 -0
  178. package/components/ui/accordion.tsx +58 -0
  179. package/components/ui/alert.tsx +59 -0
  180. package/components/ui/avatar.tsx +50 -0
  181. package/components/ui/badge.tsx +36 -0
  182. package/components/ui/button-group.tsx +83 -0
  183. package/components/ui/button.tsx +57 -0
  184. package/components/ui/card.tsx +91 -0
  185. package/components/ui/carousel.tsx +262 -0
  186. package/components/ui/collapsible.tsx +11 -0
  187. package/components/ui/command.tsx +153 -0
  188. package/components/ui/dialog.tsx +122 -0
  189. package/components/ui/dropdown-menu.tsx +200 -0
  190. package/components/ui/hover-card.tsx +29 -0
  191. package/components/ui/input-group.tsx +170 -0
  192. package/components/ui/input.tsx +22 -0
  193. package/components/ui/label.tsx +26 -0
  194. package/components/ui/popover.tsx +31 -0
  195. package/components/ui/progress.tsx +28 -0
  196. package/components/ui/scroll-area.tsx +48 -0
  197. package/components/ui/select.tsx +174 -0
  198. package/components/ui/separator.tsx +31 -0
  199. package/components/ui/spinner.tsx +16 -0
  200. package/components/ui/switch.tsx +29 -0
  201. package/components/ui/table.tsx +120 -0
  202. package/components/ui/tabs.tsx +55 -0
  203. package/components/ui/textarea.tsx +22 -0
  204. package/components/ui/tooltip.tsx +30 -0
  205. package/components/welcome-guide.tsx +182 -0
  206. package/components.json +24 -0
  207. package/lib/command-parser.ts +331 -0
  208. package/lib/dangerous-commands.ts +672 -0
  209. package/lib/db.ts +2250 -0
  210. package/lib/feishu-auth.ts +135 -0
  211. package/lib/file-storage.ts +306 -0
  212. package/lib/file-tool.ts +583 -0
  213. package/lib/knowledge-tool.ts +152 -0
  214. package/lib/knowledge-types.ts +66 -0
  215. package/lib/market-client.ts +313 -0
  216. package/lib/market-db.ts +736 -0
  217. package/lib/market-types.ts +51 -0
  218. package/lib/memory-tool.ts +211 -0
  219. package/lib/memory.ts +197 -0
  220. package/lib/pi-config.ts +436 -0
  221. package/lib/pi-session.ts +799 -0
  222. package/lib/pinyin.ts +13 -0
  223. package/lib/recommendation.ts +227 -0
  224. package/lib/risk-estimator.ts +350 -0
  225. package/lib/scheduled-task-tool.ts +184 -0
  226. package/lib/scheduler-init.ts +43 -0
  227. package/lib/scheduler.ts +416 -0
  228. package/lib/secure-bash-tool.ts +413 -0
  229. package/lib/skill-engine.ts +396 -0
  230. package/lib/skill-generator.ts +269 -0
  231. package/lib/skill-loader.ts +234 -0
  232. package/lib/skill-tool.ts +188 -0
  233. package/lib/skill-types.ts +82 -0
  234. package/lib/skills-init.ts +58 -0
  235. package/lib/ticket-tool.ts +246 -0
  236. package/lib/user-skill-types.ts +30 -0
  237. package/lib/user-skills.ts +362 -0
  238. package/lib/utils.ts +6 -0
  239. package/lib/workflow.ts +154 -0
  240. package/lib/zip-tool.ts +191 -0
  241. package/next.config.js +8 -0
  242. package/package.json +106 -0
  243. package/public/.gitkeep +1 -0
  244. package/public/icon.svg +1 -0
  245. package/tsconfig.json +42 -0
@@ -0,0 +1,405 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { execSync } from 'child_process';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
4
+ import { join, basename } from 'path';
5
+ import { toDirName } from '@/lib/pinyin';
6
+ import { getDb } from '@/lib/db';
7
+
8
+ export async function POST(request: NextRequest) {
9
+ try {
10
+ const body = await request.json();
11
+ const { swaggerUrl, systemName, outputDir, baseUrl, token, userId } = body;
12
+
13
+ if (!swaggerUrl) {
14
+ return NextResponse.json({ error: 'swaggerUrl is required' }, { status: 400 });
15
+ }
16
+
17
+ const displayName = systemName || 'API System';
18
+
19
+ // 生成目录名
20
+ let name: string;
21
+ let username = '';
22
+ if (userId) {
23
+ const db = getDb();
24
+ const user = db.prepare('SELECT username FROM users WHERE id = ?').get(userId) as { username: string } | undefined;
25
+ username = user?.username || userId;
26
+ name = toDirName(displayName, username);
27
+ } else {
28
+ name = outputDir || displayName?.toLowerCase().replace(/\s+/g, '-') || 'new-api-system';
29
+ }
30
+
31
+ // 如果提供了 userId,则创建到用户目录;否则创建到系统目录
32
+ let skillPath: string;
33
+ if (userId) {
34
+ skillPath = join(process.cwd(), 'data', 'skills', 'users', userId, name);
35
+ } else {
36
+ skillPath = join(process.cwd(), '.pi', 'skills', name);
37
+ }
38
+ const scriptsDir = join(skillPath, 'scripts');
39
+ const apisDir = join(skillPath, 'APIs');
40
+ const curlDir = join(skillPath, 'curl_examples');
41
+
42
+ if (existsSync(skillPath)) {
43
+ return NextResponse.json({ error: `Skill '${name}' already exists` }, { status: 409 });
44
+ }
45
+
46
+ // 先获取并验证 swagger 数据,确保能成功访问后再创建文件夹
47
+ let swaggerData;
48
+ try {
49
+ const response = await fetch(swaggerUrl);
50
+ if (!response.ok) {
51
+ return NextResponse.json({ error: `Failed to fetch swagger: ${response.status} ${response.statusText}` }, { status: 400 });
52
+ }
53
+ swaggerData = await response.json();
54
+ // 验证 swagger 数据格式
55
+ if (!swaggerData || (!swaggerData.paths && !swaggerData.info)) {
56
+ return NextResponse.json({ error: 'Invalid swagger document format' }, { status: 400 });
57
+ }
58
+ } catch (error) {
59
+ return NextResponse.json({ error: `Failed to fetch swagger document: ${error instanceof Error ? error.message : 'Unknown error'}` }, { status: 400 });
60
+ }
61
+
62
+ // 只有在 swagger 数据验证通过后才创建文件夹
63
+ mkdirSync(skillPath, { recursive: true });
64
+ mkdirSync(scriptsDir, { recursive: true });
65
+ mkdirSync(apisDir, { recursive: true });
66
+ mkdirSync(curlDir, { recursive: true });
67
+
68
+ const paths = swaggerData.paths || {};
69
+ const tags: Record<string, Array<{ method: string; path: string; operation: Record<string, unknown> }>> = {};
70
+
71
+ for (const [path, methods] of Object.entries(paths) as [string, Record<string, unknown>][]) {
72
+ for (const [method, operation] of Object.entries(methods) as [string, Record<string, unknown>][]) {
73
+ const op = operation as { summary?: string; description?: string; tags?: string[] };
74
+ const tagName = op.tags?.[0] || 'default';
75
+
76
+ if (!tags[tagName]) {
77
+ tags[tagName] = [];
78
+ }
79
+
80
+ tags[tagName].push({ method, path, operation });
81
+ }
82
+ }
83
+
84
+ const readOnlyMethods = ['GET', 'HEAD', 'OPTIONS'];
85
+ const allApis: Array<{ method: string; path: string; tag: string; isReadOnly: boolean; summary: string; action: string }> = [];
86
+ let scriptCount = 0;
87
+
88
+ for (const [tag, apis] of Object.entries(tags)) {
89
+ const tagDir = join(apisDir, tag.toLowerCase().replace(/\s+/g, '-'));
90
+ mkdirSync(tagDir, { recursive: true });
91
+
92
+ for (const api of apis) {
93
+ const op = api.operation as { summary?: string; description?: string; operationId?: string };
94
+ const action = (api.operation as { operationId?: string }).operationId ||
95
+ `${api.method.toLowerCase()}_${api.path.replace(/[^a-zA-Z0-9]/g, '_').replace(/__+/g, '_').replace(/^_|_$/g, '')}`;
96
+ const isReadOnly = readOnlyMethods.includes(api.method);
97
+ const description = op.summary || op.description || action;
98
+ allApis.push({ method: api.method, path: api.path, tag, isReadOnly, summary: description, action });
99
+
100
+ const bashSource = '${BASH_SOURCE[0]}';
101
+ const scriptContent = `#!/bin/bash
102
+ # ${action} - ${description}
103
+ # 分类: ${isReadOnly ? '只读操作 (read_only)' : '修改操作 (modify)'}
104
+ # API: ${api.method.toUpperCase()} ${api.path}
105
+
106
+ SCRIPT_DIR="$(cd "$(dirname "${bashSource}")" && pwd)"
107
+ source "\${SCRIPT_DIR}/../config.env"
108
+
109
+ # 检查必要配置
110
+ if [[ -z "$BASE_URL" ]]; then
111
+ echo "错误: 请在 config.env 中配置 BASE_URL"
112
+ exit 1
113
+ fi
114
+
115
+ ${isReadOnly ? '' : `# 修改操作需要审批后才能执行
116
+ echo "此操作需要创建工单进行审批"
117
+ exit 1
118
+ `}
119
+
120
+ # 构建完整 URL
121
+ API_URL="\${BASE_URL}${api.path}"
122
+
123
+ # 执行请求
124
+ curl -s -X ${api.method.toUpperCase()} "\${API_URL}" \\
125
+ -H "Authorization: Bearer $TOKEN" \\
126
+ -H "Content-Type: application/json" \\
127
+ \${TIMEOUT:+--max-time "$TIMEOUT"} | jq .
128
+ `;
129
+ const scriptPath = join(scriptsDir, action);
130
+ writeFileSync(scriptPath, scriptContent);
131
+ execSync(`chmod +x "${scriptPath}"`);
132
+ scriptCount++;
133
+
134
+ const apiDoc = `# ${api.method} ${api.path}
135
+
136
+ **分类**: ${isReadOnly ? '只读操作' : '修改操作'}
137
+
138
+ **Summary**: ${description}
139
+
140
+ **Tag**: ${tag}
141
+
142
+ ## 请求信息
143
+
144
+ - **Method**: \`${api.method}\`
145
+ - **Path**: \`${api.path}\`
146
+
147
+ ## 脚本
148
+
149
+ \`\`\`bash
150
+ ./scripts/${action}
151
+ \`\`\`
152
+ `;
153
+ writeFileSync(join(tagDir, `${api.method.toLowerCase()}_${action.replace(`${api.method.toLowerCase()}_`, '')}.md`), apiDoc);
154
+
155
+ const curlContent = `#!/bin/bash
156
+ # ${action} - ${description}
157
+ # 分类: ${isReadOnly ? '只读操作' : '修改操作'}
158
+
159
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
160
+ source "\${SCRIPT_DIR}/../config.env"
161
+
162
+ curl -s -X ${api.method.toUpperCase()} "\${BASE_URL}${api.path}" \\
163
+ -H "Authorization: Bearer $TOKEN" \\
164
+ -H "Content-Type: application/json"
165
+ `;
166
+ writeFileSync(join(curlDir, `${action}.sh`), curlContent);
167
+ }
168
+ }
169
+
170
+ const skillJson = {
171
+ name,
172
+ displayName,
173
+ description: swaggerData.info?.description || `API System generated from ${swaggerUrl}`,
174
+ version: swaggerData.info?.version || '1.0.0',
175
+ author: userId || 'xuanwu',
176
+ license: 'MIT',
177
+ keywords: ['generated', 'api', 'swagger'],
178
+ categories: Object.keys(tags),
179
+ capabilities: { swaggerParsing: true, codeGeneration: true, scriptGeneration: true },
180
+ parameters: {
181
+ swaggerUrl: { type: 'string', description: 'Swagger/OpenAPI document URL', required: true },
182
+ token: { type: 'string', description: 'API authentication token' },
183
+ },
184
+ operationClassification: {
185
+ readOnly: readOnlyMethods,
186
+ modify: ['POST', 'PUT', 'PATCH', 'DELETE'],
187
+ },
188
+ generatedAt: new Date().toISOString(),
189
+ };
190
+ writeFileSync(join(skillPath, 'SKILL.json'), JSON.stringify(skillJson, null, 2));
191
+
192
+ const skillMd = `---
193
+ name: ${name}
194
+ description: ${swaggerData.info?.description || `API System for ${displayName}`}
195
+ version: 1.0.0
196
+ author: ${userId || 'xuanwu'}
197
+ engines:
198
+ pi-coding-agent: ">=1.0.0"
199
+ allowed-tools:
200
+ - bash
201
+ ---
202
+
203
+ # ${displayName}
204
+
205
+ ${swaggerData.info?.title || displayName} - API Skill generated from Swagger/OpenAPI specification.
206
+
207
+ ## Overview
208
+
209
+ - **Version**: ${swaggerData.info?.version || '1.0.0'}
210
+ - **APIs**: ${allApis.length} endpoints
211
+ - **Read-only**: ${allApis.filter(a => a.isReadOnly).length}
212
+ - **Modify**: ${allApis.filter(a => !a.isReadOnly).length}
213
+
214
+ ## Configuration Files
215
+
216
+ 此 Skill 目录包含以下配置文件:
217
+
218
+ ### config.env
219
+ 环境变量配置文件,脚本运行时读取。
220
+
221
+ **重要配置项:**
222
+ - \`BASE_URL\`: API 基础地址(必需)
223
+ - \`TOKEN\`: API 认证令牌(执行 API 调用时需要)
224
+ - \`TIMEOUT\`: 请求超时时间(秒)
225
+
226
+ **使用方式:**
227
+ 1. 编辑 \`config.env\` 文件
228
+ 2. 设置 \`BASE_URL\` 为实际的 API 地址
229
+ 3. 执行 API 调用时,需要先设置 \`TOKEN\` 环境变量或通过其他方式提供
230
+
231
+ **示例:**
232
+ \`\`\`bash
233
+ # 在 config.env 中配置
234
+ BASE_URL="https://api.example.com"
235
+ TOKEN="your-api-token-here"
236
+ TIMEOUT=30
237
+ \`\`\`
238
+
239
+ ### config.yaml
240
+ API 配置说明文档(参考用),包含:
241
+ - base_url: API 基础地址
242
+ - api_version: API 版本
243
+ - auth: 认证配置
244
+ - request: 请求配置(超时、重试等)
245
+ - read_only_operations: 只读操作方法列表
246
+ - modify_operations: 修改操作方法列表
247
+
248
+ ## Directory Structure
249
+
250
+ \`\`\`
251
+ ${name}/
252
+ ├── SKILL.md # 本文件(AI 技能说明)
253
+ ├── SKILL.json # 技能元数据
254
+ ├── config.env # 环境配置(脚本读取)
255
+ ├── config.yaml # 配置说明(文档参考)
256
+ ├── scripts/ # 可执行脚本
257
+ │ ├── ${allApis[0]?.action || 'example_action'}
258
+ │ └── ...
259
+ ├── APIs/ # API 文档
260
+ │ ├── tag1/
261
+ │ └── tag2/
262
+ └── curl_examples/ # curl 命令示例
263
+ \`\`\`
264
+
265
+ ## API Endpoints
266
+
267
+ ${Object.entries(tags).map(([tagName, apiList]) => `### ${tagName}
268
+
269
+ | Method | Path | Description |
270
+ |--------|------|-------------|
271
+ ${apiList.map(a => {
272
+ const op = a.operation as { summary?: string; description?: string };
273
+ const desc = op.summary || op.description || '';
274
+ return `| \`${a.method}\` | \`${a.path}\` | ${desc} |`;
275
+ }).join('\n')}`).join('\n')}
276
+
277
+ ## Usage
278
+
279
+ ### 方式一:直接执行脚本
280
+
281
+ 1. 首先配置 \`config.env\`:
282
+ \`\`\`bash
283
+ # 编辑配置文件
284
+ vi config.env
285
+
286
+ # 设置 BASE_URL 和 TOKEN
287
+ BASE_URL="https://api.example.com"
288
+ TOKEN="your-token"
289
+ \`\`\`
290
+
291
+ 2. 执行脚本:
292
+ \`\`\`bash
293
+ # 只读操作可以直接执行
294
+ ./scripts/${allApis.find(a => a.isReadOnly)?.action || 'get_health'}
295
+
296
+ # 修改操作需要创建工单审批
297
+ \`\`\`
298
+
299
+ ### 方式二:AI 执行
300
+
301
+ AI 可以通过 bash 工具执行 scripts 目录下的脚本,执行前会:
302
+ 1. 检查 \`config.env\` 配置是否完整
303
+ 2. 对于修改操作,创建工单进行审批
304
+
305
+ ## Important Notes for AI
306
+
307
+ 1. **配置优先级**:执行任何脚本前,先检查 \`config.env\` 中的配置是否正确
308
+ 2. **修改操作**:对于 POST/PUT/PATCH/DELETE 操作,系统会创建工单,需要用户审批
309
+ 3. **只读操作**:GET/HEAD/OPTIONS 操作可以直接执行
310
+ 4. **错误处理**:如果 curl 返回非 0,检查 BASE_URL、TOKEN 配置和网络连接
311
+ `;
312
+ writeFileSync(join(skillPath, 'SKILL.md'), skillMd);
313
+
314
+ const configYaml = `base_url: "${baseUrl || ''}"
315
+ api_version: "${swaggerData.swagger || swaggerData.openapi || '2.0'}"
316
+
317
+ auth:
318
+ type: "bearer"
319
+ token_env: "TOKEN"
320
+ header: "Authorization"
321
+
322
+ request:
323
+ timeout: 30
324
+ retry: 3
325
+
326
+ output:
327
+ format: "json"
328
+ pretty: true
329
+
330
+ read_only_operations: [${readOnlyMethods.join(', ')}]
331
+ modify_operations: [POST, PUT, PATCH, DELETE]
332
+ `;
333
+ writeFileSync(join(skillPath, 'config.yaml'), configYaml);
334
+
335
+ const configEnv = `# 环境配置文件
336
+ # 复制此文件为 .env 并填写实际值
337
+
338
+ # API Base URL
339
+ BASE_URL="${baseUrl || ''}"
340
+
341
+ # API 认证 Token
342
+ TOKEN=
343
+
344
+ # 请求超时时间(秒)
345
+ TIMEOUT=30
346
+ `;
347
+ writeFileSync(join(skillPath, 'config.env'), configEnv);
348
+
349
+ const readmeMd = `# ${displayName}
350
+
351
+ ${swaggerData.info?.description || ''}
352
+
353
+ ## Quick Start
354
+
355
+ \`\`\`bash
356
+ # Install dependencies
357
+ npm install
358
+
359
+ # Configure API token
360
+ cp config.env .env
361
+ # Edit .env and set your TOKEN
362
+
363
+ # List available scripts
364
+ ls -la scripts/
365
+
366
+ # Execute a read-only operation
367
+ ./scripts/get_health
368
+
369
+ # Execute a modify operation (creates ticket for approval)
370
+ ./scripts/create_resource
371
+ \`\`\`
372
+
373
+ ## Documentation
374
+
375
+ See [SKILL.md](./SKILL.md) for full documentation.
376
+ `;
377
+ writeFileSync(join(skillPath, 'README.md'), readmeMd);
378
+
379
+ // 如果提供了 userId,则在数据库中注册技能
380
+ if (userId) {
381
+ const { installUserSkill } = require('@/lib/db');
382
+ // skill_name 应该存显示名称,dir_name 存目录名
383
+ installUserSkill(userId, displayName, 'personal', {
384
+ displayName: displayName,
385
+ description: swaggerData.info?.description || `API System for ${displayName}`,
386
+ });
387
+ }
388
+
389
+ const resultPath = userId ? `data/users/${userId}/skills/${name}` : `.pi/skills/${name}`;
390
+
391
+ return NextResponse.json({
392
+ success: true,
393
+ skillName: name,
394
+ displayName,
395
+ scriptCount,
396
+ apiCount: allApis.length,
397
+ readOnlyCount: allApis.filter(a => a.isReadOnly).length,
398
+ modifyCount: allApis.filter(a => !a.isReadOnly).length,
399
+ path: resultPath,
400
+ });
401
+ } catch (error) {
402
+ console.error('Error generating skill:', error);
403
+ return NextResponse.json({ error: 'Failed to generate skill' }, { status: 500 });
404
+ }
405
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * 用户已安装技能 API
3
+ *
4
+ * GET /api/skills/installed?userId=xxx - 获取用户已安装的技能列表
5
+ * DELETE /api/skills/installed?userId=xxx&skillId=xxx - 卸载技能
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import {
10
+ getUserInstalledSkills,
11
+ deleteUserInstalledSkill,
12
+ getMarketSkillById,
13
+ } from '@/lib/market-db';
14
+ import { getUserById } from '@/lib/db';
15
+ import { existsSync, rmSync } from 'fs';
16
+ import { join } from 'path';
17
+
18
+ // 用户技能目录
19
+ const USER_SKILLS_BASE = join(process.cwd(), 'data', 'skills', 'users');
20
+
21
+ /**
22
+ * GET /api/skills/installed?userId=xxx
23
+ *
24
+ * 返回用户已安装的市场技能列表(包含技能详情)
25
+ */
26
+ export async function GET(request: NextRequest) {
27
+ try {
28
+ const searchParams = request.nextUrl.searchParams;
29
+ const userId = searchParams.get('userId');
30
+
31
+ if (!userId) {
32
+ return NextResponse.json(
33
+ { error: '缺少必要参数: userId' },
34
+ { status: 400 },
35
+ );
36
+ }
37
+
38
+ // 验证用户存在
39
+ const user = getUserById(userId);
40
+ if (!user) {
41
+ return NextResponse.json(
42
+ { error: '用户不存在' },
43
+ { status: 404 },
44
+ );
45
+ }
46
+
47
+ // 获取已安装技能ID列表
48
+ const installedSkillIds = getUserInstalledSkills(userId);
49
+
50
+ // 获取每个技能的详情
51
+ const skills = installedSkillIds
52
+ .map((skillId) => getMarketSkillById(skillId))
53
+ .filter((skill) => skill !== null);
54
+
55
+ return NextResponse.json({
56
+ skills,
57
+ total: skills.length,
58
+ });
59
+ } catch (error) {
60
+ console.error('[skills/installed] GET failed:', error);
61
+ return NextResponse.json(
62
+ { error: error instanceof Error ? error.message : '获取已安装技能列表失败' },
63
+ { status: 500 },
64
+ );
65
+ }
66
+ }
67
+
68
+ /**
69
+ * DELETE /api/skills/installed?userId=xxx&skillId=xxx
70
+ *
71
+ * 卸载技能:
72
+ * 1. 删除用户本地技能文件
73
+ * 2. 移除数据库安装记录
74
+ */
75
+ export async function DELETE(request: NextRequest) {
76
+ try {
77
+ const searchParams = request.nextUrl.searchParams;
78
+ const userId = searchParams.get('userId');
79
+ const skillId = searchParams.get('skillId');
80
+
81
+ if (!userId || !skillId) {
82
+ return NextResponse.json(
83
+ { error: '缺少必要参数: userId, skillId' },
84
+ { status: 400 },
85
+ );
86
+ }
87
+
88
+ // 验证用户存在
89
+ const user = getUserById(userId);
90
+ if (!user) {
91
+ return NextResponse.json(
92
+ { error: '用户不存在' },
93
+ { status: 404 },
94
+ );
95
+ }
96
+
97
+ // 验证技能存在
98
+ const skill = getMarketSkillById(skillId);
99
+ if (!skill) {
100
+ return NextResponse.json(
101
+ { error: '技能不存在' },
102
+ { status: 404 },
103
+ );
104
+ }
105
+
106
+ // 验证已安装
107
+ if (!getUserInstalledSkills(userId).includes(skillId)) {
108
+ return NextResponse.json(
109
+ { error: '技能未安装' },
110
+ { status: 404 },
111
+ );
112
+ }
113
+
114
+ // 删除用户本地技能文件
115
+ const userSkillDir = join(USER_SKILLS_BASE, userId, skill.name);
116
+ if (existsSync(userSkillDir)) {
117
+ try {
118
+ rmSync(userSkillDir, { recursive: true });
119
+ console.log(`[skills/installed] Deleted skill files: ${userSkillDir}`);
120
+ } catch (error) {
121
+ console.error('[skills/installed] Failed to delete skill files:', error);
122
+ return NextResponse.json(
123
+ { error: '删除技能文件失败' },
124
+ { status: 500 },
125
+ );
126
+ }
127
+ }
128
+
129
+ // 移除数据库安装记录
130
+ const deleted = deleteUserInstalledSkill(userId, skillId);
131
+ if (!deleted) {
132
+ return NextResponse.json(
133
+ { error: '移除安装记录失败' },
134
+ { status: 500 },
135
+ );
136
+ }
137
+
138
+ return NextResponse.json({
139
+ success: true,
140
+ message: '技能卸载成功',
141
+ skillId,
142
+ skillName: skill.name,
143
+ });
144
+ } catch (error) {
145
+ console.error('[skills/installed] DELETE failed:', error);
146
+ return NextResponse.json(
147
+ { error: error instanceof Error ? error.message : '卸载技能失败' },
148
+ { status: 500 },
149
+ );
150
+ }
151
+ }