huhaa-myskills 0.2.5 → 0.2.7
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/bin/huhaa-myskills.mjs +34 -13
- package/package.json +2 -2
- package/packages/scanner/src/adapters/markdown-skill.mjs +11 -1
- package/packages/scanner/src/index.mjs +12 -0
- package/packages/scanner/src/utils.mjs +9 -1
- package/packages/server/src/index.mjs +18 -0
- package/packages/server/src/translator.mjs +71 -0
- package/packages/web/dist/assets/index-BAV6YnyA.js +36 -0
- package/packages/web/dist/assets/index-BmAshZIL.css +1 -0
- package/packages/web/dist/favicon.svg +7 -0
- package/packages/web/dist/index.html +13 -2
- package/packages/web/dist/robots.txt +3 -0
- package/packages/web/dist/site.webmanifest +24 -0
- package/packages/web/index.html +11 -0
- package/packages/web/public/favicon.svg +7 -0
- package/packages/web/public/robots.txt +3 -0
- package/packages/web/public/site.webmanifest +24 -0
- package/packages/web/src/App.vue +81 -13
- package/packages/web/src/components/AppTree.vue +228 -0
- package/packages/web/src/components/DirectoryTree.vue +248 -0
- package/packages/web/src/components/SkillTree.vue +205 -0
- package/packages/web/src/stores/i18n.js +13 -1
- package/packages/web/src/stores/skills.js +19 -8
- package/packages/web/src/styles.css +34 -1
- package/scripts/sync-skills.sh +1 -1
- package/packages/web/dist/assets/index-CTh5OdBd.js +0 -36
- package/packages/web/dist/assets/index-CtoiGnQ7.css +0 -1
package/bin/huhaa-myskills.mjs
CHANGED
|
@@ -325,7 +325,12 @@ async function cmdSync() {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
async function syncToEditors(skills, editors) {
|
|
328
|
-
const supported = [
|
|
328
|
+
const supported = [
|
|
329
|
+
'cursor', 'vscode', 'vscode-insiders', 'windsurf', 'zed', 'helix',
|
|
330
|
+
'neovim', 'vim', 'emacs', 'sublime', 'sublime4', 'textmate',
|
|
331
|
+
'bbedit', 'atom', 'kate', 'gedit', 'jetbrains', 'openclaw',
|
|
332
|
+
'herems', 'trae', 'trae-cn', 'codex', 'claude'
|
|
333
|
+
];
|
|
329
334
|
const validEditors = editors.filter(e => supported.includes(e));
|
|
330
335
|
|
|
331
336
|
if (validEditors.length === 0) {
|
|
@@ -368,19 +373,35 @@ async function interactiveSync(skills) {
|
|
|
368
373
|
|
|
369
374
|
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
370
375
|
|
|
376
|
+
const allEditors = [
|
|
377
|
+
'cursor', 'vscode', 'vscode-insiders', 'windsurf', 'zed', 'helix',
|
|
378
|
+
'neovim', 'vim', 'emacs', 'sublime', 'sublime4', 'textmate',
|
|
379
|
+
'bbedit', 'atom', 'kate', 'gedit', 'jetbrains', 'openclaw',
|
|
380
|
+
'herems', 'trae', 'trae-cn', 'codex', 'claude'
|
|
381
|
+
];
|
|
382
|
+
|
|
371
383
|
console.log(`\n[sync] found ${skills.length} skills`);
|
|
372
384
|
console.log('\nSupported editors:');
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
385
|
+
|
|
386
|
+
// Display editors in 3 columns
|
|
387
|
+
for (let i = 0; i < allEditors.length; i += 3) {
|
|
388
|
+
const line = [];
|
|
389
|
+
for (let j = 0; j < 3; j++) {
|
|
390
|
+
const idx = i + j + 1;
|
|
391
|
+
const editor = allEditors[i + j];
|
|
392
|
+
if (editor) {
|
|
393
|
+
line.push(`[${idx.toString().padEnd(2)}] ${editor.padEnd(16)}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
console.log(' ' + line.join(' '));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(' [0] all editors [q] quit\n');
|
|
400
|
+
|
|
401
|
+
const editorMap = {};
|
|
402
|
+
allEditors.forEach((editor, i) => {
|
|
403
|
+
editorMap[(i + 1).toString()] = editor;
|
|
404
|
+
});
|
|
384
405
|
|
|
385
406
|
const input = await question('Select editors (0/1/2/... or q): ');
|
|
386
407
|
rl.close();
|
|
@@ -392,7 +413,7 @@ async function interactiveSync(skills) {
|
|
|
392
413
|
|
|
393
414
|
let editors = [];
|
|
394
415
|
if (input === '0') {
|
|
395
|
-
editors =
|
|
416
|
+
editors = allEditors;
|
|
396
417
|
} else {
|
|
397
418
|
const selected = input.split(/[,\s]+/).filter(s => s);
|
|
398
419
|
editors = selected.map(s => editorMap[s]).filter(Boolean);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huhaa-myskills",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local skill / plugin / MCP aggregation hub. Browse all your AI agent skills at http://localhost:11520.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"init": "node bin/huhaa-myskills.mjs init",
|
|
37
37
|
"purge": "node bin/huhaa-myskills.mjs purge",
|
|
38
38
|
"dev": "node bin/huhaa-myskills.mjs dev",
|
|
39
|
-
"sync": "
|
|
39
|
+
"sync": "node bin/huhaa-myskills.mjs sync",
|
|
40
40
|
"build:web": "node scripts/build-web.mjs",
|
|
41
41
|
"test": "node --test packages/scanner/test/*.test.mjs packages/server/test/*.test.mjs",
|
|
42
42
|
"verify": "node scripts/verify.mjs",
|
|
@@ -102,7 +102,7 @@ function parseSkillFile({ abs, root, source, limits }) {
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const { data: fm, body, parseError } = parseFrontmatter(text);
|
|
105
|
+
const { data: fm, body, i18n, parseError } = parseFrontmatter(text);
|
|
106
106
|
|
|
107
107
|
const name = (fm.name || dirName).toString().trim();
|
|
108
108
|
const title = (fm.title || fm.name || dirName).toString().trim();
|
|
@@ -140,6 +140,16 @@ function parseSkillFile({ abs, root, source, limits }) {
|
|
|
140
140
|
updatedAt: new Date(mtime).toISOString(),
|
|
141
141
|
product,
|
|
142
142
|
};
|
|
143
|
+
|
|
144
|
+
// Include i18n translations if present
|
|
145
|
+
if (i18n) {
|
|
146
|
+
item.i18n = {
|
|
147
|
+
...i18n,
|
|
148
|
+
translatedAt: new Date().toISOString(),
|
|
149
|
+
translationModel: 'frontmatter',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
item.brand = inferBrand({
|
|
144
154
|
name: item.name,
|
|
145
155
|
description: item.description,
|
|
@@ -132,6 +132,18 @@ export async function scan() {
|
|
|
132
132
|
|
|
133
133
|
const out = dedupeSemantic(all);
|
|
134
134
|
|
|
135
|
+
// Optional: translate skills to Chinese via LLM
|
|
136
|
+
if (process.env.HUHAA_TRANSLATE === '1') {
|
|
137
|
+
console.log('[scan] translating skills to Chinese...');
|
|
138
|
+
try {
|
|
139
|
+
const { translateSkill } = await import('../../../packages/server/src/translator.mjs');
|
|
140
|
+
const translated = await Promise.all(out.map(s => translateSkill(s, 'zh')));
|
|
141
|
+
return translated;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn('[scan] translation failed:', e.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
if (process.env.HUHAA_DEBUG) {
|
|
136
148
|
console.error('[scan] stats:', JSON.stringify(stats, null, 2));
|
|
137
149
|
}
|
|
@@ -82,7 +82,7 @@ const FM_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* Parse YAML frontmatter at the very top of a markdown file.
|
|
85
|
-
* Returns { data, body, parseError? }. `data` is always an object (possibly
|
|
85
|
+
* Returns { data, body, i18n?, parseError? }. `data` is always an object (possibly
|
|
86
86
|
* empty). Body never has the frontmatter block.
|
|
87
87
|
*/
|
|
88
88
|
export function parseFrontmatter(text) {
|
|
@@ -96,9 +96,17 @@ export function parseFrontmatter(text) {
|
|
|
96
96
|
const [, yamlBlock, body] = m;
|
|
97
97
|
try {
|
|
98
98
|
const data = YAML.parse(yamlBlock) || {};
|
|
99
|
+
|
|
100
|
+
// Extract i18n fields if present
|
|
101
|
+
const i18n = {};
|
|
102
|
+
for (const key of ['name_zh', 'description_zh', 'category_zh']) {
|
|
103
|
+
if (data[key]) i18n[key] = data[key];
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
return {
|
|
100
107
|
data: typeof data === 'object' && !Array.isArray(data) ? data : {},
|
|
101
108
|
body: body || '',
|
|
109
|
+
...(Object.keys(i18n).length > 0 && { i18n }),
|
|
102
110
|
};
|
|
103
111
|
} catch (e) {
|
|
104
112
|
return { data: {}, body: body || text, parseError: `frontmatter: ${e.message}` };
|
|
@@ -232,6 +232,24 @@ export async function startServer({ port = 11520 } = {}) {
|
|
|
232
232
|
}
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
app.post('/api/translate', async (req, reply) => {
|
|
236
|
+
const { id } = req.body || {};
|
|
237
|
+
const item = irById.get(id);
|
|
238
|
+
if (!item) {
|
|
239
|
+
reply.code(404);
|
|
240
|
+
return { error: 'not found' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { translateSkill } = await import('./translator.mjs');
|
|
244
|
+
try {
|
|
245
|
+
const translated = await translateSkill(item, 'zh');
|
|
246
|
+
return translated;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
reply.code(500);
|
|
249
|
+
return { error: e.message };
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
235
253
|
app.get('/assets/*', async (req, reply) => {
|
|
236
254
|
if (!hasSpa) {
|
|
237
255
|
reply.code(404);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export async function translateSkill(skill, targetLang = 'zh') {
|
|
2
|
+
if (targetLang !== 'zh' || skill.i18n?.name_zh) return skill;
|
|
3
|
+
|
|
4
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
console.warn('[translator] ANTHROPIC_API_KEY not set, skipping translation');
|
|
7
|
+
return skill;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const prompt = `Translate these skill metadata to Chinese (简体中文). Return JSON only, no markdown.
|
|
11
|
+
|
|
12
|
+
Current metadata:
|
|
13
|
+
- name: ${skill.name}
|
|
14
|
+
- description: ${skill.description || '(none)'}
|
|
15
|
+
- category: ${skill.category || '(none)'}
|
|
16
|
+
|
|
17
|
+
Return JSON format (translate only the values):
|
|
18
|
+
{
|
|
19
|
+
"name_zh": "中文名称",
|
|
20
|
+
"description_zh": "中文描述",
|
|
21
|
+
"category_zh": "中文分类"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Guidelines:
|
|
25
|
+
- Keep technical terms like "API", "MCP", "LLM" in English when appropriate
|
|
26
|
+
- Use common Chinese equivalents for concepts (e.g., "skill" → "技能", "rule" → "规则")
|
|
27
|
+
- Keep descriptions concise (under 100 chars)
|
|
28
|
+
- Preserve the technical meaning
|
|
29
|
+
- Return valid JSON only`;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'x-api-key': apiKey,
|
|
37
|
+
'anthropic-version': '2023-06-01',
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
41
|
+
max_tokens: 500,
|
|
42
|
+
messages: [{
|
|
43
|
+
role: 'user',
|
|
44
|
+
content: prompt,
|
|
45
|
+
}],
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const error = await response.text();
|
|
51
|
+
throw new Error(`Claude API: ${response.status} ${error}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
const translated = JSON.parse(data.content[0].text);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...skill,
|
|
59
|
+
i18n: {
|
|
60
|
+
name_zh: translated.name_zh,
|
|
61
|
+
description_zh: translated.description_zh,
|
|
62
|
+
category_zh: translated.category_zh,
|
|
63
|
+
translatedAt: new Date().toISOString(),
|
|
64
|
+
translationModel: 'claude-3.5-sonnet',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error(`[translator] error: ${e.message}`);
|
|
69
|
+
return skill;
|
|
70
|
+
}
|
|
71
|
+
}
|