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.
- package/README.md +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- 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
|
+
}
|