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
package/lib/pinyin.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pinyin } from 'pinyin-pro';
|
|
2
|
+
|
|
3
|
+
export function toDirName(displayName: string, username: string): string {
|
|
4
|
+
const py = pinyin(displayName, { pattern: 'pinyin', toneType: 'none', separator: '' });
|
|
5
|
+
const cleanedPy = py.replace(/\s+/g, '').toLowerCase();
|
|
6
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
7
|
+
return `${cleanedPy}-${username}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function toPinyin(name: string): string {
|
|
11
|
+
const py = pinyin(name, { pattern: 'pinyin', toneType: 'none', separator: '' });
|
|
12
|
+
return py.replace(/\s+/g, '').toLowerCase();
|
|
13
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 推荐服务
|
|
3
|
+
*
|
|
4
|
+
* 基于使用统计和上下文为用户推荐合适的Skill */
|
|
5
|
+
|
|
6
|
+
import { listMarketSkills } from './market-db';
|
|
7
|
+
import { getUserSkills, getSkillUsageStats } from './db';
|
|
8
|
+
|
|
9
|
+
let marketSkillsCache: any[] | null = null;
|
|
10
|
+
let marketSkillsCacheTime = 0;
|
|
11
|
+
const MARKET_CACHE_TTL = 60000; // 1 minute cache
|
|
12
|
+
|
|
13
|
+
async function getMarketSkillsFromRemote(): Promise<any[]> {
|
|
14
|
+
try {
|
|
15
|
+
const { getMarketClient } = await import('./market-client');
|
|
16
|
+
const client = getMarketClient();
|
|
17
|
+
|
|
18
|
+
if (!client.isRemoteEnabled()) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await client.searchSkills({ status: 'approved', limit: 50 });
|
|
23
|
+
return result.skills;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn('[recommendation] Failed to fetch from remote:', error);
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getApprovedSkills(): any[] {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
|
|
33
|
+
if (marketSkillsCache && (now - marketSkillsCacheTime) < MARKET_CACHE_TTL) {
|
|
34
|
+
return marketSkillsCache;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { skills } = listMarketSkills({ status: 'approved', limit: 50 });
|
|
38
|
+
marketSkillsCache = skills;
|
|
39
|
+
marketSkillsCacheTime = now;
|
|
40
|
+
|
|
41
|
+
return skills;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RecommendedSkill {
|
|
45
|
+
name: string;
|
|
46
|
+
displayName?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
score: number;
|
|
49
|
+
reason: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 基于上下文的推荐
|
|
54
|
+
*
|
|
55
|
+
* @param userQuery 用户的查询内容
|
|
56
|
+
* @param userId 用户ID
|
|
57
|
+
* @returns 推荐的Skill列表
|
|
58
|
+
*/
|
|
59
|
+
export function recommendForContext(
|
|
60
|
+
userQuery: string,
|
|
61
|
+
userId: string
|
|
62
|
+
): RecommendedSkill[] {
|
|
63
|
+
// 获取用户已安装的Skill
|
|
64
|
+
const userSkills = getUserSkills(userId);
|
|
65
|
+
const installedSkills = new Set(userSkills.map(s => s.skillName));
|
|
66
|
+
|
|
67
|
+
// 获取所有市场Skill(只获取已审核通过的)
|
|
68
|
+
const marketSkills = getApprovedSkills();
|
|
69
|
+
|
|
70
|
+
// 基于查询关键词匹配
|
|
71
|
+
const queryLower = userQuery.toLowerCase();
|
|
72
|
+
const keywords: Record<string, string[]> = {
|
|
73
|
+
'k8s-ops': ['k8s', 'kubernetes', 'kubectl', 'pod', 'deployment', 'service', 'ingress'],
|
|
74
|
+
'docker': ['docker', 'container', '镜像', '容器'],
|
|
75
|
+
'git': ['git', 'commit', 'branch', 'merge', 'pull', 'push'],
|
|
76
|
+
'nginx': ['nginx', '反向代理', '负载均衡'],
|
|
77
|
+
'database': ['mysql', 'postgres', 'mongodb', 'redis', '数据库'],
|
|
78
|
+
'api': ['api', 'rest', 'http', '请求'],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const recommendations: RecommendedSkill[] = [];
|
|
82
|
+
|
|
83
|
+
for (const skill of marketSkills) {
|
|
84
|
+
// 跳过已安装的Skill
|
|
85
|
+
if (installedSkills.has(skill.name)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let matchScore = 0;
|
|
90
|
+
let reason = '';
|
|
91
|
+
|
|
92
|
+
// 匹配关键词
|
|
93
|
+
const skillKeywords = keywords[skill.name] || [];
|
|
94
|
+
for (const keyword of skillKeywords) {
|
|
95
|
+
if (queryLower.includes(keyword)) {
|
|
96
|
+
matchScore += 10;
|
|
97
|
+
reason = `与"${keyword}"相关`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 匹配名称和描述
|
|
102
|
+
const nameAndDesc = `${skill.name} ${skill.displayName || ''} ${skill.description || ''}`.toLowerCase();
|
|
103
|
+
for (const keyword of skillKeywords) {
|
|
104
|
+
if (nameAndDesc.includes(keyword)) {
|
|
105
|
+
matchScore += 5;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 考虑热门度
|
|
110
|
+
const stats = getSkillUsageStats(skill.name);
|
|
111
|
+
if (stats.total > 0) {
|
|
112
|
+
matchScore += Math.min(5, stats.total); // 最多加5分
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (matchScore > 0) {
|
|
116
|
+
recommendations.push({
|
|
117
|
+
name: skill.name,
|
|
118
|
+
displayName: skill.displayName,
|
|
119
|
+
description: skill.description,
|
|
120
|
+
score: matchScore,
|
|
121
|
+
reason: reason || '热门Skill推荐',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 按分数排序,返回前5个
|
|
127
|
+
return recommendations
|
|
128
|
+
.sort((a, b) => b.score - a.score)
|
|
129
|
+
.slice(0, 5);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 获取个性化推荐
|
|
134
|
+
*
|
|
135
|
+
* 基于用户历史使用习惯推荐
|
|
136
|
+
*/
|
|
137
|
+
export function getPersonalizedRecommendations(
|
|
138
|
+
userId: string,
|
|
139
|
+
limit: number = 5
|
|
140
|
+
): RecommendedSkill[] {
|
|
141
|
+
// 获取用户已安装的Skill
|
|
142
|
+
const userSkills = getUserSkills(userId);
|
|
143
|
+
const installedSkills = new Set(userSkills.map(s => s.skillName));
|
|
144
|
+
|
|
145
|
+
// 获取用户使用最多的Skill
|
|
146
|
+
const mostUsed = [...userSkills]
|
|
147
|
+
.sort((a, b) => b.useCount - a.useCount)
|
|
148
|
+
.slice(0, 3);
|
|
149
|
+
|
|
150
|
+
// 获取这些Skill的关键词
|
|
151
|
+
const usedSkillNames = mostUsed.map(s => s.skillName);
|
|
152
|
+
|
|
153
|
+
// 获取市场Skill
|
|
154
|
+
const marketSkills = getApprovedSkills();
|
|
155
|
+
|
|
156
|
+
const recommendations: RecommendedSkill[] = [];
|
|
157
|
+
|
|
158
|
+
for (const skill of marketSkills) {
|
|
159
|
+
if (installedSkills.has(skill.name)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let score = 0;
|
|
164
|
+
let reason = '';
|
|
165
|
+
|
|
166
|
+
// 如果使用了类似的Skill,给予更高分数
|
|
167
|
+
for (const usedSkill of usedSkillNames) {
|
|
168
|
+
if (skill.name.includes(usedSkill.split('-')[0]) ||
|
|
169
|
+
usedSkill.includes(skill.name.split('-')[0])) {
|
|
170
|
+
score += 15;
|
|
171
|
+
reason = '基于您常用的Skill类型';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 考虑评分
|
|
176
|
+
if (skill.ratingAvg >= 4) {
|
|
177
|
+
score += 5;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 考虑下载量
|
|
181
|
+
if (skill.downloadCount > 10) {
|
|
182
|
+
score += 3;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (score > 0) {
|
|
186
|
+
recommendations.push({
|
|
187
|
+
name: skill.name,
|
|
188
|
+
displayName: skill.displayName,
|
|
189
|
+
description: skill.description,
|
|
190
|
+
score,
|
|
191
|
+
reason: reason || '热门且高评分',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return recommendations
|
|
197
|
+
.sort((a, b) => b.score - a.score)
|
|
198
|
+
.slice(0, limit);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 获取热门推荐
|
|
203
|
+
*
|
|
204
|
+
* 基于下载量和评分
|
|
205
|
+
*/
|
|
206
|
+
export function getPopularSkills(limit: number = 10): RecommendedSkill[] {
|
|
207
|
+
const { skills: marketSkills } = listMarketSkills({
|
|
208
|
+
status: 'approved',
|
|
209
|
+
orderBy: 'downloads',
|
|
210
|
+
limit,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return marketSkills.map(skill => {
|
|
214
|
+
// 计算综合分数
|
|
215
|
+
const score = (skill.downloadCount * 0.5) + (Number(skill.ratingAvg) * skill.ratingCount * 2);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
name: skill.name,
|
|
219
|
+
displayName: skill.displayName,
|
|
220
|
+
description: skill.description,
|
|
221
|
+
score,
|
|
222
|
+
reason: `下载${skill.downloadCount}次,评分${Number(skill.ratingAvg).toFixed(1)}`,
|
|
223
|
+
};
|
|
224
|
+
})
|
|
225
|
+
.sort((a, b) => b.score - a.score)
|
|
226
|
+
.slice(0, limit);
|
|
227
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk Estimator
|
|
3
|
+
*
|
|
4
|
+
* Estimates risk level for commands based on:
|
|
5
|
+
* 1. System-wide high-risk configurations (highest priority)
|
|
6
|
+
* 2. Skill-specific risk configurations
|
|
7
|
+
*
|
|
8
|
+
* Priority: system_critical > system_high > skill_high > system_medium > skill_medium > skill_low
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
|
|
14
|
+
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
15
|
+
|
|
16
|
+
interface SystemRiskConfig {
|
|
17
|
+
version: string;
|
|
18
|
+
description: string;
|
|
19
|
+
priority: string;
|
|
20
|
+
highRiskCommands: {
|
|
21
|
+
bash: {
|
|
22
|
+
patterns: Array<{
|
|
23
|
+
pattern: string;
|
|
24
|
+
level: RiskLevel;
|
|
25
|
+
description: string;
|
|
26
|
+
}>;
|
|
27
|
+
requireApproval: boolean;
|
|
28
|
+
blockOnCritical: boolean;
|
|
29
|
+
};
|
|
30
|
+
kubectl: {
|
|
31
|
+
verbs: Record<string, {
|
|
32
|
+
level: RiskLevel;
|
|
33
|
+
requireApproval: boolean;
|
|
34
|
+
description: string;
|
|
35
|
+
}>;
|
|
36
|
+
dangerousResources: string[];
|
|
37
|
+
requireApproval: boolean;
|
|
38
|
+
};
|
|
39
|
+
docker: {
|
|
40
|
+
patterns: Array<{
|
|
41
|
+
pattern: string;
|
|
42
|
+
level: RiskLevel;
|
|
43
|
+
description: string;
|
|
44
|
+
}>;
|
|
45
|
+
requireApproval: boolean;
|
|
46
|
+
};
|
|
47
|
+
git: {
|
|
48
|
+
patterns: Array<{
|
|
49
|
+
pattern: string;
|
|
50
|
+
level: RiskLevel;
|
|
51
|
+
description: string;
|
|
52
|
+
}>;
|
|
53
|
+
requireApproval: boolean;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
generalRules: {
|
|
57
|
+
requireApprovalOnHighRisk: boolean;
|
|
58
|
+
blockOnCritical: boolean;
|
|
59
|
+
logAllCommands: boolean;
|
|
60
|
+
timeout: Record<RiskLevel, number>;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface SkillRiskConfig {
|
|
65
|
+
default: RiskLevel;
|
|
66
|
+
overrides: Record<string, RiskLevel>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RiskAssessment {
|
|
70
|
+
riskLevel: RiskLevel;
|
|
71
|
+
requiresApproval: boolean;
|
|
72
|
+
shouldBlock: boolean;
|
|
73
|
+
reason: string;
|
|
74
|
+
matchedPattern?: string;
|
|
75
|
+
skillName?: string;
|
|
76
|
+
commandType?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let systemConfig: SystemRiskConfig | null = null;
|
|
80
|
+
|
|
81
|
+
function loadSystemConfig(): SystemRiskConfig {
|
|
82
|
+
if (systemConfig) return systemConfig;
|
|
83
|
+
|
|
84
|
+
const configPath = join(process.cwd(), '.pi', 'system-commands.json');
|
|
85
|
+
if (existsSync(configPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
88
|
+
systemConfig = JSON.parse(content) as SystemRiskConfig;
|
|
89
|
+
return systemConfig;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Failed to load system risk config:', error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Return default config if file not found
|
|
96
|
+
return getDefaultSystemConfig();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getDefaultSystemConfig(): SystemRiskConfig {
|
|
100
|
+
return {
|
|
101
|
+
version: '1.0',
|
|
102
|
+
description: 'Default system risk configuration',
|
|
103
|
+
priority: 'highest',
|
|
104
|
+
highRiskCommands: {
|
|
105
|
+
bash: {
|
|
106
|
+
patterns: [
|
|
107
|
+
{ pattern: 'rm -rf', level: 'critical', description: 'Force recursive delete' },
|
|
108
|
+
],
|
|
109
|
+
requireApproval: true,
|
|
110
|
+
blockOnCritical: true,
|
|
111
|
+
},
|
|
112
|
+
kubectl: {
|
|
113
|
+
verbs: {
|
|
114
|
+
delete: { level: 'high', requireApproval: true, description: 'Delete resources' },
|
|
115
|
+
scale: { level: 'high', requireApproval: true, description: 'Scale resources' },
|
|
116
|
+
patch: { level: 'high', requireApproval: true, description: 'Patch resources' },
|
|
117
|
+
replace: { level: 'high', requireApproval: true, description: 'Replace resources' },
|
|
118
|
+
},
|
|
119
|
+
dangerousResources: ['all', 'pods', 'deployments', 'services'],
|
|
120
|
+
requireApproval: true,
|
|
121
|
+
},
|
|
122
|
+
docker: {
|
|
123
|
+
patterns: [],
|
|
124
|
+
requireApproval: true,
|
|
125
|
+
},
|
|
126
|
+
git: {
|
|
127
|
+
patterns: [],
|
|
128
|
+
requireApproval: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
generalRules: {
|
|
132
|
+
requireApprovalOnHighRisk: true,
|
|
133
|
+
blockOnCritical: true,
|
|
134
|
+
logAllCommands: true,
|
|
135
|
+
timeout: {
|
|
136
|
+
low: 30000,
|
|
137
|
+
medium: 60000,
|
|
138
|
+
high: 120000,
|
|
139
|
+
critical: 5000,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getSkillRiskConfig(skillName: string): SkillRiskConfig {
|
|
146
|
+
const skillConfigPath = join(process.cwd(), '.pi', 'skills', skillName, 'SKILL.json');
|
|
147
|
+
|
|
148
|
+
if (existsSync(skillConfigPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const content = readFileSync(skillConfigPath, 'utf-8');
|
|
151
|
+
const config = JSON.parse(content);
|
|
152
|
+
return {
|
|
153
|
+
default: config.riskConfig?.default || 'low',
|
|
154
|
+
overrides: config.riskConfig?.overrides || {},
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`Failed to load skill risk config for ${skillName}:`, error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
default: 'low',
|
|
163
|
+
overrides: {},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function estimateRisk(
|
|
168
|
+
command: string,
|
|
169
|
+
skillName?: string
|
|
170
|
+
): RiskAssessment {
|
|
171
|
+
const trimmedCommand = command.trim();
|
|
172
|
+
const config = loadSystemConfig();
|
|
173
|
+
|
|
174
|
+
// Detect command type
|
|
175
|
+
const commandType = detectCommandType(trimmedCommand);
|
|
176
|
+
|
|
177
|
+
// 1. Check system-level patterns first (highest priority)
|
|
178
|
+
const systemAssessment = checkSystemPatterns(trimmedCommand, commandType, config);
|
|
179
|
+
if (systemAssessment) {
|
|
180
|
+
return systemAssessment;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Check skill-level configurations
|
|
184
|
+
if (skillName) {
|
|
185
|
+
const skillConfig = getSkillRiskConfig(skillName);
|
|
186
|
+
const skillAssessment = checkSkillPatterns(trimmedCommand, skillConfig, skillName, commandType);
|
|
187
|
+
if (skillAssessment) {
|
|
188
|
+
return skillAssessment;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. Default to low risk if no patterns match
|
|
193
|
+
return {
|
|
194
|
+
riskLevel: 'low',
|
|
195
|
+
requiresApproval: false,
|
|
196
|
+
shouldBlock: false,
|
|
197
|
+
reason: 'Default: No risk patterns matched',
|
|
198
|
+
commandType,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function detectCommandType(command: string): string {
|
|
203
|
+
const trimmed = command.toLowerCase().trim();
|
|
204
|
+
|
|
205
|
+
if (trimmed.startsWith('kubectl')) return 'kubectl';
|
|
206
|
+
if (trimmed.startsWith('docker')) return 'docker';
|
|
207
|
+
if (trimmed.startsWith('git')) return 'git';
|
|
208
|
+
if (trimmed.startsWith('npm')) return 'npm';
|
|
209
|
+
if (trimmed.startsWith('yarn')) return 'yarn';
|
|
210
|
+
if (trimmed.startsWith('chmod')) return 'chmod';
|
|
211
|
+
if (trimmed.startsWith('rm')) return 'rm';
|
|
212
|
+
if (trimmed.startsWith('kill')) return 'kill';
|
|
213
|
+
|
|
214
|
+
return 'bash';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function checkSystemPatterns(
|
|
218
|
+
command: string,
|
|
219
|
+
commandType: string,
|
|
220
|
+
config: SystemRiskConfig
|
|
221
|
+
): RiskAssessment | null {
|
|
222
|
+
const lowerCommand = command.toLowerCase();
|
|
223
|
+
|
|
224
|
+
// Check bash patterns
|
|
225
|
+
if (commandType === 'bash' || commandType === 'rm' || commandType === 'chmod' || commandType === 'kill') {
|
|
226
|
+
for (const pattern of config.highRiskCommands.bash.patterns) {
|
|
227
|
+
if (lowerCommand.includes(pattern.pattern.toLowerCase())) {
|
|
228
|
+
return {
|
|
229
|
+
riskLevel: pattern.level,
|
|
230
|
+
requiresApproval: pattern.level === 'critical' ? true : config.highRiskCommands.bash.requireApproval,
|
|
231
|
+
shouldBlock: pattern.level === 'critical' && config.generalRules.blockOnCritical,
|
|
232
|
+
reason: `System pattern matched: ${pattern.description}`,
|
|
233
|
+
matchedPattern: pattern.pattern,
|
|
234
|
+
commandType: 'bash',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check kubectl verbs
|
|
241
|
+
if (commandType === 'kubectl') {
|
|
242
|
+
const verbs = lowerCommand.match(/kubectl\s+(\w+)/);
|
|
243
|
+
const verb = verbs?.[1];
|
|
244
|
+
|
|
245
|
+
if (verb && config.highRiskCommands.kubectl.verbs[verb]) {
|
|
246
|
+
const verbConfig = config.highRiskCommands.kubectl.verbs[verb];
|
|
247
|
+
return {
|
|
248
|
+
riskLevel: verbConfig.level,
|
|
249
|
+
requiresApproval: verbConfig.requireApproval,
|
|
250
|
+
shouldBlock: false,
|
|
251
|
+
reason: `Kubectl verb '${verb}': ${verbConfig.description}`,
|
|
252
|
+
matchedPattern: `kubectl ${verb}`,
|
|
253
|
+
commandType: 'kubectl',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check for dangerous resources (only for modifying verbs)
|
|
258
|
+
const modifyingVerbs = ['delete', 'scale', 'patch', 'replace', 'edit', 'exec', 'cp'];
|
|
259
|
+
if (modifyingVerbs.includes(verb || '') && commandType === 'kubectl') {
|
|
260
|
+
for (const resource of config.highRiskCommands.kubectl.dangerousResources) {
|
|
261
|
+
if (lowerCommand.includes(resource)) {
|
|
262
|
+
return {
|
|
263
|
+
riskLevel: 'high',
|
|
264
|
+
requiresApproval: config.highRiskCommands.kubectl.requireApproval,
|
|
265
|
+
shouldBlock: false,
|
|
266
|
+
reason: `Dangerous resource: ${resource}`,
|
|
267
|
+
commandType: 'kubectl',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check docker patterns
|
|
275
|
+
if (commandType === 'docker') {
|
|
276
|
+
for (const pattern of config.highRiskCommands.docker.patterns) {
|
|
277
|
+
if (lowerCommand.includes(pattern.pattern.toLowerCase())) {
|
|
278
|
+
return {
|
|
279
|
+
riskLevel: pattern.level,
|
|
280
|
+
requiresApproval: config.highRiskCommands.docker.requireApproval,
|
|
281
|
+
shouldBlock: pattern.level === 'critical',
|
|
282
|
+
reason: `Docker pattern matched: ${pattern.description}`,
|
|
283
|
+
matchedPattern: pattern.pattern,
|
|
284
|
+
commandType: 'docker',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check git patterns
|
|
291
|
+
if (commandType === 'git') {
|
|
292
|
+
for (const pattern of config.highRiskCommands.git.patterns) {
|
|
293
|
+
if (lowerCommand.includes(pattern.pattern.toLowerCase())) {
|
|
294
|
+
return {
|
|
295
|
+
riskLevel: pattern.level,
|
|
296
|
+
requiresApproval: config.highRiskCommands.git.requireApproval,
|
|
297
|
+
shouldBlock: false,
|
|
298
|
+
reason: `Git pattern matched: ${pattern.description}`,
|
|
299
|
+
matchedPattern: pattern.pattern,
|
|
300
|
+
commandType: 'git',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function checkSkillPatterns(
|
|
310
|
+
command: string,
|
|
311
|
+
skillConfig: SkillRiskConfig,
|
|
312
|
+
skillName: string,
|
|
313
|
+
commandType: string
|
|
314
|
+
): RiskAssessment | null {
|
|
315
|
+
const lowerCommand = command.toLowerCase();
|
|
316
|
+
|
|
317
|
+
// Check specific overrides first
|
|
318
|
+
for (const [pattern, level] of Object.entries(skillConfig.overrides)) {
|
|
319
|
+
if (lowerCommand.includes(pattern.toLowerCase())) {
|
|
320
|
+
return {
|
|
321
|
+
riskLevel: level,
|
|
322
|
+
requiresApproval: level === 'high' || level === 'critical',
|
|
323
|
+
shouldBlock: level === 'critical',
|
|
324
|
+
reason: `Skill '${skillName}' pattern matched: ${pattern}`,
|
|
325
|
+
matchedPattern: pattern,
|
|
326
|
+
skillName,
|
|
327
|
+
commandType,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Default to skill's default level
|
|
333
|
+
return {
|
|
334
|
+
riskLevel: skillConfig.default,
|
|
335
|
+
requiresApproval: skillConfig.default === 'high' || skillConfig.default === 'critical',
|
|
336
|
+
shouldBlock: skillConfig.default === 'critical',
|
|
337
|
+
reason: `Skill '${skillName}' default risk level`,
|
|
338
|
+
skillName,
|
|
339
|
+
commandType,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function getTimeoutForRisk(riskLevel: RiskLevel): number {
|
|
344
|
+
const config = loadSystemConfig();
|
|
345
|
+
return config.generalRules.timeout[riskLevel] || 30000;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function reloadSystemConfig(): void {
|
|
349
|
+
systemConfig = null;
|
|
350
|
+
}
|