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
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
+ }