huhaa-myskills 0.2.13 → 0.3.4
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 +9 -375
- package/bin/huhaa-myskills.mjs +194 -12
- package/{scripts → build}/verify.mjs +3 -1
- package/docs/Frontend-Spec.md +1149 -0
- package/docs/assets/layout-wireframe.png +0 -0
- package/docs/assets/theme-reference.png +0 -0
- package/docs/scan_rules.md +172 -0
- package/package.json +33 -9
- package/packages/scanner/src/adapters/directory-skill.mjs +239 -0
- package/packages/scanner/src/adapters/skill-adapter.mjs +345 -0
- package/packages/scanner/src/icon/brand-map.mjs +84 -0
- package/packages/scanner/src/icon/icon-extractor.mjs +246 -0
- package/packages/scanner/src/index.mjs +107 -82
- package/packages/scanner/src/types.d.ts +18 -0
- package/packages/scanner/test/icon.test.mjs +61 -0
- package/packages/scanner/test/scanner.test.mjs +37 -41
- package/packages/scanner/test/skill-adapter.test.mjs +70 -0
- package/packages/scanner/tests/directory-skill.test.mjs +93 -0
- package/packages/scanner/tests/scanner-integration.test.mjs +139 -0
- package/packages/server/src/index.mjs +164 -12
- package/packages/server/src/labels.mjs +62 -0
- package/packages/server/test/server.test.mjs +1 -1
- package/packages/web/README.md +33 -2
- package/packages/web/dist/assets/index-BCY2cRaO.js +175 -0
- package/packages/web/dist/assets/index-DALKjJPi.css +1 -0
- package/packages/web/dist/favicon-16x16.png +0 -0
- package/packages/web/dist/favicon-192x192.png +0 -0
- package/packages/web/dist/favicon-32x32.png +0 -0
- package/packages/web/dist/favicon-512x512.png +0 -0
- package/packages/web/dist/favicon-source.svg +5 -0
- package/packages/web/dist/index.html +23 -9
- package/packages/web/index.html +22 -8
- package/packages/web/package-lock.json +2989 -0
- package/packages/web/package.json +21 -8
- package/packages/web/postcss.config.js +6 -0
- package/packages/web/public/favicon-16x16.png +0 -0
- package/packages/web/public/favicon-192x192.png +0 -0
- package/packages/web/public/favicon-32x32.png +0 -0
- package/packages/web/public/favicon-512x512.png +0 -0
- package/packages/web/public/favicon-source.svg +5 -0
- package/packages/web/src/App.test.ts +40 -0
- package/packages/web/src/App.tsx +177 -0
- package/packages/web/src/components/ComingSoon.tsx +14 -0
- package/packages/web/src/components/layout/Sidebar.test.tsx +74 -0
- package/packages/web/src/components/layout/Sidebar.tsx +114 -0
- package/packages/web/src/components/layout/Topbar.tsx +74 -0
- package/packages/web/src/components/ui/ActionButton.tsx +48 -0
- package/packages/web/src/components/ui/button.tsx +48 -0
- package/packages/web/src/components/ui/card.tsx +45 -0
- package/packages/web/src/components/views/DashboardView.tsx +122 -0
- package/packages/web/src/components/views/OtherSkillsView.test.tsx +126 -0
- package/packages/web/src/components/views/OtherSkillsView.tsx +306 -0
- package/packages/web/src/components/views/SettingsView.tsx +95 -0
- package/packages/web/src/components/views/SkillDetail.test.tsx +57 -0
- package/packages/web/src/components/views/SkillDetail.tsx +81 -0
- package/packages/web/src/components/views/SkillsView.test.ts +28 -0
- package/packages/web/src/components/views/SkillsView.tsx +226 -0
- package/packages/web/src/hooks/QUICK_REFERENCE.md +165 -0
- package/packages/web/src/hooks/README.md +378 -0
- package/packages/web/src/hooks/__tests__/useOtherSkills.test.ts +422 -0
- package/packages/web/src/hooks/__tests__/useSkillIcons.test.ts +272 -0
- package/packages/web/src/hooks/useLiveReload.ts +20 -0
- package/packages/web/src/hooks/useOtherSkills.ts +387 -0
- package/packages/web/src/hooks/useSkillIcons.ts +248 -0
- package/packages/web/src/hooks/useTheme.ts +36 -0
- package/packages/web/src/index.css +161 -0
- package/packages/web/src/lib/api.test.ts +47 -0
- package/packages/web/src/lib/api.ts +77 -0
- package/packages/web/src/lib/cn.ts +7 -0
- package/packages/web/src/lib/editors.test.ts +30 -0
- package/packages/web/src/lib/editors.ts +70 -0
- package/packages/web/src/lib/markdown.test.ts +27 -0
- package/packages/web/src/lib/markdown.ts +14 -0
- package/packages/web/src/main.tsx +13 -0
- package/packages/web/src/types/index.ts +62 -0
- package/packages/web/src/types/other-skill.ts +88 -0
- package/packages/web/src/types/skill.ts +136 -0
- package/packages/web/src/types.ts +47 -0
- package/packages/web/src/vite-env.d.ts +1 -0
- package/packages/web/tailwind.config.ts +66 -0
- package/packages/web/tsconfig.json +28 -0
- package/packages/web/vite.config.ts +36 -0
- package/packages/web/vitest.config.ts +21 -0
- package/packages/web/vitest.setup.ts +1 -0
- package/scripts/activate-data-sources.sh +223 -0
- package/scripts/brand-completion.py +586 -0
- package/scripts/init-nextjs-project.sh +421 -0
- package/scripts/install-and-sync.sh +209 -0
- package/scripts/start-dev.sh +72 -0
- package/docs/GUIDE.md +0 -190
- package/docs/PLAN.md +0 -359
- package/docs/RULES.md +0 -329
- package/docs/RUNBOOK-myskills.md +0 -258
- package/docs/SYNC-SKILLS.md +0 -259
- package/docs/releases/README.md +0 -47
- package/docs/releases/v0.1.0.md +0 -256
- package/docs/releases/v0.1.1.md +0 -297
- package/docs/releases/v0.1.2.md +0 -207
- package/docs/releases/v0.1.3.md +0 -269
- package/docs/todo/v0.1.2.md +0 -98
- package/docs/todo/v0.1.3.md +0 -40
- package/docs/todo/v0.1.4.md +0 -36
- package/docs/todo/v0.1.5.md +0 -41
- package/docs/todo/v0.1.6.md +0 -43
- package/docs/todo/v0.1.7.md +0 -38
- package/docs/todo/v0.1.8.md +0 -36
- package/docs/todo/v0.1.9.md +0 -54
- package/packages/web/dist/assets/index-BPepSb3q.js +0 -36
- package/packages/web/dist/assets/index-CIGkgT5w.css +0 -1
- package/packages/web/src/App.vue +0 -483
- package/packages/web/src/components/AppTree.vue +0 -230
- package/packages/web/src/components/DirectoryTree.vue +0 -263
- package/packages/web/src/components/SkillTree.vue +0 -208
- package/packages/web/src/lib/api.js +0 -43
- package/packages/web/src/main.js +0 -6
- package/packages/web/src/stores/i18n.js +0 -161
- package/packages/web/src/stores/skills.js +0 -287
- package/packages/web/src/styles.css +0 -365
- package/packages/web/vite.config.js +0 -17
- /package/{scripts → build}/build-web.mjs +0 -0
- /package/{scripts → build}/prepare-publish.mjs +0 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
品牌自动识别和补全脚本 (v2 - 增强版)
|
|
4
|
+
扫描所有 Hermes 技能,基于技能名称、描述和类别自动识别品牌
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
# 增强的品牌识别规则
|
|
16
|
+
BRAND_RULES = {
|
|
17
|
+
'GitHub': {
|
|
18
|
+
'patterns': [r'\bgithub\b', r'git-hub', r'gh-action', r'release-automation', r'github-pr'],
|
|
19
|
+
'aliases': ['github']
|
|
20
|
+
},
|
|
21
|
+
'Docker': {
|
|
22
|
+
'patterns': [r'\bdocker\b', r'docker-compose', r'dockerfile', r'containerization'],
|
|
23
|
+
'aliases': ['docker']
|
|
24
|
+
},
|
|
25
|
+
'Kubernetes': {
|
|
26
|
+
'patterns': [r'\bk8s\b', r'kubernetes', r'kubes', r'helm', r'k8s-private'],
|
|
27
|
+
'aliases': ['kubernetes', 'k8s']
|
|
28
|
+
},
|
|
29
|
+
'AWS': {
|
|
30
|
+
'patterns': [r'\baws\b', r'amazon', r'ec2', r's3', r'lambda', r'cloudformation'],
|
|
31
|
+
'aliases': ['aws']
|
|
32
|
+
},
|
|
33
|
+
'Linux': {
|
|
34
|
+
'patterns': [r'\blinux\b', r'ubuntu', r'debian', r'centos', r'rhel', r'fedora', r'gnu/linux', r'bash', r'systemd'],
|
|
35
|
+
'aliases': ['linux', 'ubuntu', 'debian', 'centos']
|
|
36
|
+
},
|
|
37
|
+
'Python': {
|
|
38
|
+
'patterns': [r'\bpython\b', r'py\d', r'\bpy\b', r'django', r'flask', r'fastapi', r'pip', r'peft', r'jupyter'],
|
|
39
|
+
'aliases': ['python', 'py']
|
|
40
|
+
},
|
|
41
|
+
'Node.js': {
|
|
42
|
+
'patterns': [r'node\.?js', r'nodejs', r'npm\b', r'express', r'vite'],
|
|
43
|
+
'aliases': ['nodejs', 'node', 'npm']
|
|
44
|
+
},
|
|
45
|
+
'Go': {
|
|
46
|
+
'patterns': [r'\bgo\b(?!ogle)', r'golang', r'goroutine'],
|
|
47
|
+
'aliases': ['go', 'golang']
|
|
48
|
+
},
|
|
49
|
+
'Rust': {
|
|
50
|
+
'patterns': [r'(\brust\b|cargo|\.rs\b)(?![a-z])'],
|
|
51
|
+
'aliases': ['rust', 'cargo']
|
|
52
|
+
},
|
|
53
|
+
'Java': {
|
|
54
|
+
'patterns': [r'\bjava\b', r'jvm', r'spring', r'maven'],
|
|
55
|
+
'aliases': ['java', 'jvm']
|
|
56
|
+
},
|
|
57
|
+
'OpenAI': {
|
|
58
|
+
'patterns': [r'\bopenai\b', r'chatgpt', r'gpt-\d', r'davinci', r'whisper'],
|
|
59
|
+
'aliases': ['openai', 'gpt', 'chatgpt', 'whisper']
|
|
60
|
+
},
|
|
61
|
+
'Anthropic': {
|
|
62
|
+
'patterns': [r'\banthrop\w+', r'\bclaude\b', r'custom-anthropic'],
|
|
63
|
+
'aliases': ['anthropic', 'claude']
|
|
64
|
+
},
|
|
65
|
+
'Google': {
|
|
66
|
+
'patterns': [r'\bgoogle\b', r'gemini', r'bard', r'vertex', r'colab', r'youtube'],
|
|
67
|
+
'aliases': ['google', 'gemini']
|
|
68
|
+
},
|
|
69
|
+
'Meta': {
|
|
70
|
+
'patterns': [r'\bmeta\b', r'\bllama\b', r'facebook', r'llama-cpp', r'gguf'],
|
|
71
|
+
'aliases': ['meta', 'llama']
|
|
72
|
+
},
|
|
73
|
+
'TensorFlow': {
|
|
74
|
+
'patterns': [r'tensorflow', r'keras\b'],
|
|
75
|
+
'aliases': ['tensorflow']
|
|
76
|
+
},
|
|
77
|
+
'PyTorch': {
|
|
78
|
+
'patterns': [r'pytorch', r'\btorch\b', r'fsdp'],
|
|
79
|
+
'aliases': ['pytorch', 'torch']
|
|
80
|
+
},
|
|
81
|
+
'Apple': {
|
|
82
|
+
'patterns': [r'\bapple\b', r'macos', r'ios\b', r'swift', r'xcode', r'reminders', r'imessage', r'findmy'],
|
|
83
|
+
'aliases': ['apple', 'ios', 'macos', 'swift']
|
|
84
|
+
},
|
|
85
|
+
'React': {
|
|
86
|
+
'patterns': [r'\breact\b', r'reactjs', r'jsx', r'next\.js', r'react-router', r'react-multi'],
|
|
87
|
+
'aliases': ['react', 'reactjs']
|
|
88
|
+
},
|
|
89
|
+
'Vue': {
|
|
90
|
+
'patterns': [r'\bvue\b', r'vuejs', r'nuxt', r'vue3-', r'vue-vite', r'pinia'],
|
|
91
|
+
'aliases': ['vue', 'vuejs']
|
|
92
|
+
},
|
|
93
|
+
'Svelte': {
|
|
94
|
+
'patterns': [r'\bsvelte\b', r'astro-vercel'],
|
|
95
|
+
'aliases': ['svelte']
|
|
96
|
+
},
|
|
97
|
+
'TypeScript': {
|
|
98
|
+
'patterns': [r'typescript', r'\.ts\b', r'tsc'],
|
|
99
|
+
'aliases': ['typescript', 'ts']
|
|
100
|
+
},
|
|
101
|
+
'Terraform': {
|
|
102
|
+
'patterns': [r'terraform', r'\.tf\b'],
|
|
103
|
+
'aliases': ['terraform']
|
|
104
|
+
},
|
|
105
|
+
'Ansible': {
|
|
106
|
+
'patterns': [r'\bansible\b'],
|
|
107
|
+
'aliases': ['ansible']
|
|
108
|
+
},
|
|
109
|
+
'Jenkins': {
|
|
110
|
+
'patterns': [r'\bjenkins\b', r'ci/cd', r'continuous-integration'],
|
|
111
|
+
'aliases': ['jenkins']
|
|
112
|
+
},
|
|
113
|
+
'GitLab': {
|
|
114
|
+
'patterns': [r'\bgitlab\b'],
|
|
115
|
+
'aliases': ['gitlab']
|
|
116
|
+
},
|
|
117
|
+
'Figma': {
|
|
118
|
+
'patterns': [r'\bfigma\b'],
|
|
119
|
+
'aliases': ['figma']
|
|
120
|
+
},
|
|
121
|
+
'Slack': {
|
|
122
|
+
'patterns': [r'\bslack\b'],
|
|
123
|
+
'aliases': ['slack']
|
|
124
|
+
},
|
|
125
|
+
'Notion': {
|
|
126
|
+
'patterns': [r'\bnotion\b'],
|
|
127
|
+
'aliases': ['notion']
|
|
128
|
+
},
|
|
129
|
+
'Jira': {
|
|
130
|
+
'patterns': [r'\bjira\b', r'atlassian'],
|
|
131
|
+
'aliases': ['jira']
|
|
132
|
+
},
|
|
133
|
+
'PostgreSQL': {
|
|
134
|
+
'patterns': [r'postgres', r'postgresql', r'psql'],
|
|
135
|
+
'aliases': ['postgres', 'postgresql']
|
|
136
|
+
},
|
|
137
|
+
'MySQL': {
|
|
138
|
+
'patterns': [r'\bmysql\b'],
|
|
139
|
+
'aliases': ['mysql', 'sql']
|
|
140
|
+
},
|
|
141
|
+
'MongoDB': {
|
|
142
|
+
'patterns': [r'mongodb', r'mongo\b'],
|
|
143
|
+
'aliases': ['mongodb', 'mongo']
|
|
144
|
+
},
|
|
145
|
+
'Redis': {
|
|
146
|
+
'patterns': [r'\bredis\b'],
|
|
147
|
+
'aliases': ['redis']
|
|
148
|
+
},
|
|
149
|
+
'Elasticsearch': {
|
|
150
|
+
'patterns': [r'elasticsearch', r'elk'],
|
|
151
|
+
'aliases': ['elasticsearch']
|
|
152
|
+
},
|
|
153
|
+
'Prometheus': {
|
|
154
|
+
'patterns': [r'prometheus'],
|
|
155
|
+
'aliases': ['prometheus']
|
|
156
|
+
},
|
|
157
|
+
'Grafana': {
|
|
158
|
+
'patterns': [r'grafana'],
|
|
159
|
+
'aliases': ['grafana']
|
|
160
|
+
},
|
|
161
|
+
'Hermes': {
|
|
162
|
+
'patterns': [r'\bhermes\b', r'hermes-agent', r'huhaa-myskills'],
|
|
163
|
+
'aliases': ['hermes']
|
|
164
|
+
},
|
|
165
|
+
'Hugging Face': {
|
|
166
|
+
'patterns': [r'huggingface', r'hugging.face', r'huggingface-hub'],
|
|
167
|
+
'aliases': ['huggingface']
|
|
168
|
+
},
|
|
169
|
+
'ComfyUI': {
|
|
170
|
+
'patterns': [r'comfyui', r'comfy'],
|
|
171
|
+
'aliases': ['comfyui', 'comfy']
|
|
172
|
+
},
|
|
173
|
+
'Suno': {
|
|
174
|
+
'patterns': [r'suno\b', r'heartmula', r'music.generation', r'audiocraft'],
|
|
175
|
+
'aliases': ['suno', 'heartmula']
|
|
176
|
+
},
|
|
177
|
+
'Midjourney': {
|
|
178
|
+
'patterns': [r'midjourney'],
|
|
179
|
+
'aliases': ['midjourney']
|
|
180
|
+
},
|
|
181
|
+
'Stable Diffusion': {
|
|
182
|
+
'patterns': [r'stable.diffusion', r'diffusion(?!s)', r'axolotl'],
|
|
183
|
+
'aliases': ['stable-diffusion']
|
|
184
|
+
},
|
|
185
|
+
'dspy': {
|
|
186
|
+
'patterns': [r'\bdspy\b'],
|
|
187
|
+
'aliases': ['dspy']
|
|
188
|
+
},
|
|
189
|
+
'LangChain': {
|
|
190
|
+
'patterns': [r'langchain'],
|
|
191
|
+
'aliases': ['langchain']
|
|
192
|
+
},
|
|
193
|
+
'LlamaIndex': {
|
|
194
|
+
'patterns': [r'llamaindex', r'gpt-index'],
|
|
195
|
+
'aliases': ['llamaindex']
|
|
196
|
+
},
|
|
197
|
+
'Modal': {
|
|
198
|
+
'patterns': [r'\bmodal\b'],
|
|
199
|
+
'aliases': ['modal']
|
|
200
|
+
},
|
|
201
|
+
'Jupyter': {
|
|
202
|
+
'patterns': [r'jupyter', r'ipython'],
|
|
203
|
+
'aliases': ['jupyter']
|
|
204
|
+
},
|
|
205
|
+
'Obsidian': {
|
|
206
|
+
'patterns': [r'obsidian\b'],
|
|
207
|
+
'aliases': ['obsidian']
|
|
208
|
+
},
|
|
209
|
+
'Excalidraw': {
|
|
210
|
+
'patterns': [r'excalidraw'],
|
|
211
|
+
'aliases': ['excalidraw']
|
|
212
|
+
},
|
|
213
|
+
'Manim': {
|
|
214
|
+
'patterns': [r'manim\b'],
|
|
215
|
+
'aliases': ['manim']
|
|
216
|
+
},
|
|
217
|
+
'Blender': {
|
|
218
|
+
'patterns': [r'blender'],
|
|
219
|
+
'aliases': ['blender']
|
|
220
|
+
},
|
|
221
|
+
'GIMP': {
|
|
222
|
+
'patterns': [r'\bgimp\b'],
|
|
223
|
+
'aliases': ['gimp']
|
|
224
|
+
},
|
|
225
|
+
'Krita': {
|
|
226
|
+
'patterns': [r'krita\b'],
|
|
227
|
+
'aliases': ['krita']
|
|
228
|
+
},
|
|
229
|
+
'P5.js': {
|
|
230
|
+
'patterns': [r'p5js', r'p5\.js'],
|
|
231
|
+
'aliases': ['p5js']
|
|
232
|
+
},
|
|
233
|
+
'Unity': {
|
|
234
|
+
'patterns': [r'\bunity\b', r'gamedev'],
|
|
235
|
+
'aliases': ['unity']
|
|
236
|
+
},
|
|
237
|
+
'Godot': {
|
|
238
|
+
'patterns': [r'\bgodot\b'],
|
|
239
|
+
'aliases': ['godot']
|
|
240
|
+
},
|
|
241
|
+
'Unreal': {
|
|
242
|
+
'patterns': [r'unreal\b', r'ue\d'],
|
|
243
|
+
'aliases': ['unreal']
|
|
244
|
+
},
|
|
245
|
+
'Minecraft': {
|
|
246
|
+
'patterns': [r'minecraft', r'mcporter'],
|
|
247
|
+
'aliases': ['minecraft']
|
|
248
|
+
},
|
|
249
|
+
'OpenStreetMap': {
|
|
250
|
+
'patterns': [r'openstreetmap', r'osrm', r'maps'],
|
|
251
|
+
'aliases': ['osm', 'openstreetmap']
|
|
252
|
+
},
|
|
253
|
+
'RSS': {
|
|
254
|
+
'patterns': [r'rss\b', r'feed', r'atom\b', r'blogwatch'],
|
|
255
|
+
'aliases': ['rss', 'feed']
|
|
256
|
+
},
|
|
257
|
+
'Email': {
|
|
258
|
+
'patterns': [r'\bemail\b', r'smtp\b', r'imap\b', r'himalaya', r'powerpoint'],
|
|
259
|
+
'aliases': ['email', 'smtp']
|
|
260
|
+
},
|
|
261
|
+
'Telegram': {
|
|
262
|
+
'patterns': [r'telegram', r'dingtalk'],
|
|
263
|
+
'aliases': ['telegram', 'dingtalk']
|
|
264
|
+
},
|
|
265
|
+
'OpenHue': {
|
|
266
|
+
'patterns': [r'hue\b', r'openhue'],
|
|
267
|
+
'aliases': ['hue', 'openhue']
|
|
268
|
+
},
|
|
269
|
+
'Airtable': {
|
|
270
|
+
'patterns': [r'airtable'],
|
|
271
|
+
'aliases': ['airtable']
|
|
272
|
+
},
|
|
273
|
+
'TouchDesigner': {
|
|
274
|
+
'patterns': [r'touchdesigner'],
|
|
275
|
+
'aliases': ['touchdesigner']
|
|
276
|
+
},
|
|
277
|
+
'Ollama': {
|
|
278
|
+
'patterns': [r'ollama'],
|
|
279
|
+
'aliases': ['ollama']
|
|
280
|
+
},
|
|
281
|
+
'HuHaa': {
|
|
282
|
+
'patterns': [r'huhaa', r'myskills'],
|
|
283
|
+
'aliases': ['huhaa']
|
|
284
|
+
},
|
|
285
|
+
'X': {
|
|
286
|
+
'patterns': [r'xitter', r'xurl'],
|
|
287
|
+
'aliases': ['twitter', 'x']
|
|
288
|
+
},
|
|
289
|
+
'Discord': {
|
|
290
|
+
'patterns': [r'discord', r'team.communication'],
|
|
291
|
+
'aliases': ['discord']
|
|
292
|
+
},
|
|
293
|
+
'Segment Anything': {
|
|
294
|
+
'patterns': [r'segment.anything', r'sam\b'],
|
|
295
|
+
'aliases': ['sam', 'segment-anything']
|
|
296
|
+
},
|
|
297
|
+
'CLIP': {
|
|
298
|
+
'patterns': [r'\bclip\b(?!py)'],
|
|
299
|
+
'aliases': ['clip']
|
|
300
|
+
},
|
|
301
|
+
'VLLM': {
|
|
302
|
+
'patterns': [r'vllm'],
|
|
303
|
+
'aliases': ['vllm']
|
|
304
|
+
},
|
|
305
|
+
'TRL': {
|
|
306
|
+
'patterns': [r'trl\b', r'trl-fine-tuning'],
|
|
307
|
+
'aliases': ['trl']
|
|
308
|
+
},
|
|
309
|
+
'PEFT': {
|
|
310
|
+
'patterns': [r'\bpeft\b'],
|
|
311
|
+
'aliases': ['peft']
|
|
312
|
+
},
|
|
313
|
+
'Unsloth': {
|
|
314
|
+
'patterns': [r'unsloth'],
|
|
315
|
+
'aliases': ['unsloth']
|
|
316
|
+
},
|
|
317
|
+
'Weights & Biases': {
|
|
318
|
+
'patterns': [r'weights.and.biases', r'wandb'],
|
|
319
|
+
'aliases': ['wandb']
|
|
320
|
+
},
|
|
321
|
+
'Guidance': {
|
|
322
|
+
'patterns': [r'\bguidance\b(?!-?)'],
|
|
323
|
+
'aliases': ['guidance']
|
|
324
|
+
},
|
|
325
|
+
'Outlines': {
|
|
326
|
+
'patterns': [r'\boutlines\b'],
|
|
327
|
+
'aliases': ['outlines']
|
|
328
|
+
},
|
|
329
|
+
'MCP': {
|
|
330
|
+
'patterns': [r'\bmcp\b', r'native-mcp'],
|
|
331
|
+
'aliases': ['mcp']
|
|
332
|
+
},
|
|
333
|
+
'Proxy': {
|
|
334
|
+
'patterns': [r'proxy', r'clash', r'sing-box', r'hysteria', r'webhook'],
|
|
335
|
+
'aliases': ['proxy']
|
|
336
|
+
},
|
|
337
|
+
'Development': {
|
|
338
|
+
'patterns': [r'code-review', r'debugg', r'test-driven', r'phased-engineering', r'systematic'],
|
|
339
|
+
'aliases': ['development']
|
|
340
|
+
},
|
|
341
|
+
'Design': {
|
|
342
|
+
'patterns': [r'design-md', r'architecture-diagram', r'pretext', r'web-design', r'svg'],
|
|
343
|
+
'aliases': ['design']
|
|
344
|
+
},
|
|
345
|
+
'Creativity': {
|
|
346
|
+
'patterns': [r'creative-ideation', r'humanizer', r'ascii-art', r'ascii-video', r'baoyu-infographic'],
|
|
347
|
+
'aliases': ['creative']
|
|
348
|
+
},
|
|
349
|
+
'OCR': {
|
|
350
|
+
'patterns': [r'ocr\b', r'nano-pdf', r'pdf'],
|
|
351
|
+
'aliases': ['ocr']
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# 基于类别的默认品牌
|
|
356
|
+
CATEGORY_BRAND_MAP = {
|
|
357
|
+
'devops': 'Docker',
|
|
358
|
+
'software-development': 'Development',
|
|
359
|
+
'creative': 'Design',
|
|
360
|
+
'gaming': 'Unity',
|
|
361
|
+
'media': 'Google',
|
|
362
|
+
'research': 'Google',
|
|
363
|
+
'productivity': 'Notion',
|
|
364
|
+
'mlops': 'PyTorch',
|
|
365
|
+
'models': 'Hugging Face',
|
|
366
|
+
'training': 'PyTorch',
|
|
367
|
+
'evaluation': 'Weights & Biases',
|
|
368
|
+
'inference': 'LlamaIndex',
|
|
369
|
+
'red-teaming': 'Anthropic',
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
def clean_text(text):
|
|
373
|
+
"""清理文本用于匹配"""
|
|
374
|
+
if not text:
|
|
375
|
+
return ""
|
|
376
|
+
return re.sub(r'[^a-z0-9\s\-_.]', '', text.lower())
|
|
377
|
+
|
|
378
|
+
def identify_brand(skill_name, skill_description="", category=""):
|
|
379
|
+
"""基于技能名称、描述和类别识别品牌"""
|
|
380
|
+
text_to_search = clean_text(f"{skill_name} {skill_description}")
|
|
381
|
+
|
|
382
|
+
matched_brands = []
|
|
383
|
+
for brand, rule in BRAND_RULES.items():
|
|
384
|
+
for pattern in rule['patterns']:
|
|
385
|
+
try:
|
|
386
|
+
if re.search(pattern, text_to_search, re.IGNORECASE):
|
|
387
|
+
matched_brands.append(brand)
|
|
388
|
+
break
|
|
389
|
+
except re.error:
|
|
390
|
+
pass
|
|
391
|
+
|
|
392
|
+
# 优先返回第一个匹配的品牌
|
|
393
|
+
if matched_brands:
|
|
394
|
+
return matched_brands[0]
|
|
395
|
+
|
|
396
|
+
# 如果没有匹配,使用类别默认品牌
|
|
397
|
+
if category and category in CATEGORY_BRAND_MAP:
|
|
398
|
+
return CATEGORY_BRAND_MAP[category]
|
|
399
|
+
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def read_skill_metadata(skill_path):
|
|
403
|
+
"""读取 SKILL.md 文件的 frontmatter"""
|
|
404
|
+
try:
|
|
405
|
+
with open(skill_path, 'r', encoding='utf-8') as f:
|
|
406
|
+
content = f.read()
|
|
407
|
+
|
|
408
|
+
match = re.match(r'^---\n(.*?)\n---\n', content, re.DOTALL)
|
|
409
|
+
if match:
|
|
410
|
+
fm_text = match.group(1)
|
|
411
|
+
metadata = {}
|
|
412
|
+
for line in fm_text.split('\n'):
|
|
413
|
+
if ':' in line and not line.startswith(' '):
|
|
414
|
+
key, val = line.split(':', 1)
|
|
415
|
+
key = key.strip()
|
|
416
|
+
val = val.strip()
|
|
417
|
+
if val.startswith('"') and val.endswith('"'):
|
|
418
|
+
val = val[1:-1]
|
|
419
|
+
elif val.startswith("'") and val.endswith("'"):
|
|
420
|
+
val = val[1:-1]
|
|
421
|
+
metadata[key] = val
|
|
422
|
+
return metadata
|
|
423
|
+
except:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
return {}
|
|
427
|
+
|
|
428
|
+
def update_skill_metadata(skill_path, brand):
|
|
429
|
+
"""更新 SKILL.md 的 frontmatter 添加 brand 字段"""
|
|
430
|
+
try:
|
|
431
|
+
with open(skill_path, 'r', encoding='utf-8') as f:
|
|
432
|
+
content = f.read()
|
|
433
|
+
|
|
434
|
+
match = re.match(r'^(---\n.*?\n---\n)', content, re.DOTALL)
|
|
435
|
+
if match:
|
|
436
|
+
frontmatter = match.group(1)
|
|
437
|
+
rest = content[len(frontmatter):]
|
|
438
|
+
|
|
439
|
+
if 'brand:' not in frontmatter:
|
|
440
|
+
fm_lines = frontmatter.rstrip('-\n').rstrip() + f'\nbrand: {brand}\n---\n'
|
|
441
|
+
new_content = fm_lines + rest
|
|
442
|
+
|
|
443
|
+
with open(skill_path, 'w', encoding='utf-8') as f:
|
|
444
|
+
f.write(new_content)
|
|
445
|
+
return True
|
|
446
|
+
except:
|
|
447
|
+
pass
|
|
448
|
+
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
def scan_skills(skills_dir):
|
|
452
|
+
"""扫描所有技能并识别品牌"""
|
|
453
|
+
results = {
|
|
454
|
+
'total': 0,
|
|
455
|
+
'with_brand': 0,
|
|
456
|
+
'newly_added': 0,
|
|
457
|
+
'brands': defaultdict(int),
|
|
458
|
+
'skills_by_brand': defaultdict(list),
|
|
459
|
+
'failures': []
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for root, dirs, files in os.walk(skills_dir):
|
|
463
|
+
dirs[:] = [d for d in dirs if not d.startswith('.')]
|
|
464
|
+
|
|
465
|
+
if 'SKILL.md' in files:
|
|
466
|
+
skill_path = os.path.join(root, 'SKILL.md')
|
|
467
|
+
skill_name = os.path.basename(root)
|
|
468
|
+
category = os.path.basename(os.path.dirname(root))
|
|
469
|
+
|
|
470
|
+
results['total'] += 1
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
metadata = read_skill_metadata(skill_path)
|
|
474
|
+
|
|
475
|
+
if 'brand' in metadata:
|
|
476
|
+
results['with_brand'] += 1
|
|
477
|
+
brand = metadata['brand']
|
|
478
|
+
else:
|
|
479
|
+
description = metadata.get('description', '')
|
|
480
|
+
brand = identify_brand(skill_name, description, category)
|
|
481
|
+
|
|
482
|
+
if brand:
|
|
483
|
+
if update_skill_metadata(skill_path, brand):
|
|
484
|
+
results['newly_added'] += 1
|
|
485
|
+
else:
|
|
486
|
+
results['failures'].append({
|
|
487
|
+
'skill': skill_name,
|
|
488
|
+
'reason': 'Failed to update metadata'
|
|
489
|
+
})
|
|
490
|
+
continue
|
|
491
|
+
else:
|
|
492
|
+
continue
|
|
493
|
+
|
|
494
|
+
if brand:
|
|
495
|
+
results['brands'][brand] += 1
|
|
496
|
+
results['skills_by_brand'][brand].append(skill_name)
|
|
497
|
+
|
|
498
|
+
except Exception as e:
|
|
499
|
+
results['failures'].append({
|
|
500
|
+
'skill': skill_name,
|
|
501
|
+
'reason': str(e)
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
return results
|
|
505
|
+
|
|
506
|
+
def generate_report(results, output_file=None):
|
|
507
|
+
"""生成补全报告"""
|
|
508
|
+
report = []
|
|
509
|
+
report.append("=" * 70)
|
|
510
|
+
report.append("品牌补全报告 (Brand Completion Report)")
|
|
511
|
+
report.append("=" * 70)
|
|
512
|
+
report.append(f"\n生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
513
|
+
|
|
514
|
+
report.append("📊 补全统计:\n")
|
|
515
|
+
report.append(f" 总技能数: {results['total']:3d} 个")
|
|
516
|
+
report.append(f" 已有品牌标记: {results['with_brand']:3d} 个")
|
|
517
|
+
report.append(f" 新增品牌标记: {results['newly_added']:3d} 个")
|
|
518
|
+
|
|
519
|
+
before_percentage = (results['with_brand'] / results['total'] * 100) if results['total'] > 0 else 0
|
|
520
|
+
after_percentage = ((results['with_brand'] + results['newly_added']) / results['total'] * 100) if results['total'] > 0 else 0
|
|
521
|
+
|
|
522
|
+
report.append(f"\n 补全前: {results['with_brand']}/{results['total']} ({before_percentage:.1f}%)")
|
|
523
|
+
report.append(f" 补全后: {results['with_brand'] + results['newly_added']}/{results['total']} ({after_percentage:.1f}%)")
|
|
524
|
+
report.append(f" 提升: +{results['newly_added']} 个 ({after_percentage - before_percentage:+.1f}%)\n")
|
|
525
|
+
|
|
526
|
+
if results['brands']:
|
|
527
|
+
report.append("🏷️ Top 25 品牌分布:\n")
|
|
528
|
+
|
|
529
|
+
sorted_brands = sorted(results['brands'].items(), key=lambda x: x[1], reverse=True)
|
|
530
|
+
for i, (brand, count) in enumerate(sorted_brands[:25], 1):
|
|
531
|
+
report.append(f" {i:2d}. {brand:25s} : {count:3d} 个")
|
|
532
|
+
|
|
533
|
+
report.append(f"\n 总计: {len(results['brands'])} 个不同品牌\n")
|
|
534
|
+
|
|
535
|
+
if results['failures']:
|
|
536
|
+
report.append(f"❌ 失败: {len(results['failures'])} 项\n")
|
|
537
|
+
for fail in results['failures'][:10]:
|
|
538
|
+
report.append(f" - {fail['skill']}: {fail['reason']}")
|
|
539
|
+
if len(results['failures']) > 10:
|
|
540
|
+
report.append(f" ... 还有 {len(results['failures']) - 10} 项")
|
|
541
|
+
report.append()
|
|
542
|
+
|
|
543
|
+
report.append("=" * 70)
|
|
544
|
+
|
|
545
|
+
report_text = '\n'.join(report)
|
|
546
|
+
print(report_text)
|
|
547
|
+
|
|
548
|
+
if output_file:
|
|
549
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
550
|
+
f.write(report_text)
|
|
551
|
+
|
|
552
|
+
return report_text
|
|
553
|
+
|
|
554
|
+
def main():
|
|
555
|
+
skills_dir = os.path.expanduser('~/.hermes/skills')
|
|
556
|
+
|
|
557
|
+
if not os.path.exists(skills_dir):
|
|
558
|
+
print(f"❌ 技能目录不存在: {skills_dir}")
|
|
559
|
+
sys.exit(1)
|
|
560
|
+
|
|
561
|
+
print(f"📁 正在扫描技能目录: {skills_dir}")
|
|
562
|
+
print("⏳ 这可能需要几秒钟...\n")
|
|
563
|
+
|
|
564
|
+
results = scan_skills(skills_dir)
|
|
565
|
+
|
|
566
|
+
output_file = os.path.expanduser('~/Project/HuHaa-MySkills/BRANDING_COMPLETION_REPORT.txt')
|
|
567
|
+
generate_report(results, output_file)
|
|
568
|
+
|
|
569
|
+
json_output = os.path.expanduser('~/Project/HuHaa-MySkills/branding-results.json')
|
|
570
|
+
json_data = {
|
|
571
|
+
'timestamp': datetime.now().isoformat(),
|
|
572
|
+
'total': results['total'],
|
|
573
|
+
'with_brand': results['with_brand'],
|
|
574
|
+
'newly_added': results['newly_added'],
|
|
575
|
+
'brands': dict(results['brands']),
|
|
576
|
+
'skills_by_brand': {k: sorted(v) for k, v in results['skills_by_brand'].items()},
|
|
577
|
+
'failures': results['failures']
|
|
578
|
+
}
|
|
579
|
+
with open(json_output, 'w', encoding='utf-8') as f:
|
|
580
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
581
|
+
|
|
582
|
+
print(f"\n✅ 报告已保存到: {output_file}")
|
|
583
|
+
print(f"✅ JSON 数据已保存到: {json_output}")
|
|
584
|
+
|
|
585
|
+
if __name__ == '__main__':
|
|
586
|
+
main()
|