skillhelper 0.4.2

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/bin/lib/atomic-write.mjs +72 -0
  4. package/bin/lib/paths.mjs +65 -0
  5. package/bin/lib/port.mjs +23 -0
  6. package/bin/skillhelper.mjs +720 -0
  7. package/build/build-site.mjs +35 -0
  8. package/build/build-web.mjs +29 -0
  9. package/build/pack-smoke.mjs +195 -0
  10. package/build/prepare-publish.mjs +3 -0
  11. package/build/verify.mjs +157 -0
  12. package/config/sources.example.yaml +126 -0
  13. package/docs/Frontend-Spec.md +1207 -0
  14. package/docs/scan_skills_rules.md +432 -0
  15. package/package.json +95 -0
  16. package/packages/scanner/package.json +15 -0
  17. package/packages/scanner/src/adapters/claude-agents.mjs +134 -0
  18. package/packages/scanner/src/adapters/claude-plugin.mjs +260 -0
  19. package/packages/scanner/src/adapters/codex-plugin.mjs +330 -0
  20. package/packages/scanner/src/adapters/directory-skill.mjs +239 -0
  21. package/packages/scanner/src/adapters/file-docs.mjs +158 -0
  22. package/packages/scanner/src/adapters/hermes-plugin.mjs +149 -0
  23. package/packages/scanner/src/adapters/markdown-skill.mjs +229 -0
  24. package/packages/scanner/src/adapters/mcp-config.mjs +190 -0
  25. package/packages/scanner/src/adapters/scan-tier.mjs +99 -0
  26. package/packages/scanner/src/adapters/skill-adapter.mjs +345 -0
  27. package/packages/scanner/src/adapters/tier1-editor-skills.mjs +278 -0
  28. package/packages/scanner/src/adapters/tier2-user-skills.mjs +218 -0
  29. package/packages/scanner/src/adapters/tier3-other-skills.mjs +225 -0
  30. package/packages/scanner/src/config/editor-tiers.mjs +191 -0
  31. package/packages/scanner/src/core/atomic-write.mjs +11 -0
  32. package/packages/scanner/src/core/descriptor.mjs +143 -0
  33. package/packages/scanner/src/core/path-hash.mjs +89 -0
  34. package/packages/scanner/src/core/registry.mjs +128 -0
  35. package/packages/scanner/src/hash/path-hash.mjs +8 -0
  36. package/packages/scanner/src/icon/brand-map.mjs +268 -0
  37. package/packages/scanner/src/icon/icon-extractor.mjs +466 -0
  38. package/packages/scanner/src/index.mjs +450 -0
  39. package/packages/scanner/src/types.d.ts +144 -0
  40. package/packages/scanner/src/utils.mjs +194 -0
  41. package/packages/server/package.json +15 -0
  42. package/packages/server/src/index.mjs +1071 -0
  43. package/packages/server/src/labels.mjs +64 -0
  44. package/packages/server/src/langdetect.mjs +34 -0
  45. package/packages/server/src/llm-client.mjs +42 -0
  46. package/packages/server/src/translate-cache.mjs +132 -0
  47. package/packages/server/src/translator.mjs +53 -0
  48. package/packages/web/README.md +36 -0
  49. package/packages/web/dist/assets/index-CGt04vt0.js +304 -0
  50. package/packages/web/dist/assets/index-CLSDoa3H.css +1 -0
  51. package/packages/web/dist/brand-icon.png +0 -0
  52. package/packages/web/dist/brand-logo.png +0 -0
  53. package/packages/web/dist/favicon-16x16.png +0 -0
  54. package/packages/web/dist/favicon-192x192.png +0 -0
  55. package/packages/web/dist/favicon-32x32.png +0 -0
  56. package/packages/web/dist/favicon-512x512.png +0 -0
  57. package/packages/web/dist/favicon-source.svg +5 -0
  58. package/packages/web/dist/favicon.ico +0 -0
  59. package/packages/web/dist/favicon.svg +5 -0
  60. package/packages/web/dist/icons/hermes-128.png +0 -0
  61. package/packages/web/dist/icons/hermes-192.png +0 -0
  62. package/packages/web/dist/icons/hermes-256.png +0 -0
  63. package/packages/web/dist/icons/hermes-32.png +0 -0
  64. package/packages/web/dist/icons/hermes-512.png +0 -0
  65. package/packages/web/dist/icons/hermes-64.png +0 -0
  66. package/packages/web/dist/icons/hermes.png +0 -0
  67. package/packages/web/dist/index.html +37 -0
  68. package/packages/web/dist/robots.txt +3 -0
  69. package/packages/web/dist/site.webmanifest +24 -0
  70. package/packages/web/public/brand-icon.png +0 -0
  71. package/packages/web/public/brand-logo.png +0 -0
  72. package/packages/web/public/favicon-16x16.png +0 -0
  73. package/packages/web/public/favicon-192x192.png +0 -0
  74. package/packages/web/public/favicon-32x32.png +0 -0
  75. package/packages/web/public/favicon-512x512.png +0 -0
  76. package/packages/web/public/favicon-source.svg +5 -0
  77. package/packages/web/public/favicon.ico +0 -0
  78. package/packages/web/public/favicon.svg +5 -0
  79. package/packages/web/public/icons/hermes-128.png +0 -0
  80. package/packages/web/public/icons/hermes-192.png +0 -0
  81. package/packages/web/public/icons/hermes-256.png +0 -0
  82. package/packages/web/public/icons/hermes-32.png +0 -0
  83. package/packages/web/public/icons/hermes-512.png +0 -0
  84. package/packages/web/public/icons/hermes-64.png +0 -0
  85. package/packages/web/public/icons/hermes.png +0 -0
  86. package/packages/web/public/robots.txt +3 -0
  87. package/packages/web/public/site.webmanifest +24 -0
  88. package/scripts/install-and-sync.sh +209 -0
  89. package/scripts/sync-skills.sh +605 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * @file tier2-user-skills.mjs
3
+ * 扫描第2层:用户根目录 ~/skills/
4
+ * 与第1层的 pathHashes 进行去重
5
+ */
6
+
7
+ import path from 'node:path';
8
+ import fs from 'node:fs';
9
+ import fg from 'fast-glob';
10
+ import { expandTilde, readFileSafe, sha1Id } from '../utils.mjs';
11
+ import { USER_TIER_2_CONFIG } from '../config/editor-tiers.mjs';
12
+ import { pathHashCache } from '../hash/path-hash.mjs';
13
+ import { ensureRegistered, getDescriptor } from '../core/registry.mjs';
14
+ import { computeConfidence } from '../core/descriptor.mjs';
15
+
16
+ /**
17
+ * 扫描第2层:用户根目录 ~/skills/
18
+ * 与第1层的 pathHashes 进行去重
19
+ *
20
+ * @param {Object} options
21
+ * @param {Set<string>} options.tier1PathHashes - 第1层的所有 pathHash 集合
22
+ * @param {Object} [options.limits] - { maxFiles, maxFileBytes }
23
+ * @returns {Promise<{items: SkillItem[], pathHashes: Set<string>}>}
24
+ */
25
+ export async function scanTier2UserSkills(options = {}) {
26
+ const { tier1PathHashes = new Set(), limits = {} } = options;
27
+ const mergedLimits = {
28
+ maxFiles: limits.maxFiles ?? 5000,
29
+ maxFileBytes: limits.maxFileBytes ?? 1024 * 1024,
30
+ };
31
+
32
+ // descriptor registry 化(渐进迁移)
33
+ ensureRegistered();
34
+ const desc = getDescriptor('tier2:user-skills');
35
+ const relPatterns = desc?.detect?.globPatterns || [`**/SKILL.md`, `**/skill.md`];
36
+ const ignore = desc?.detect?.ignore || ['**/node_modules/**', '**/.git/**', '**/dist/**'];
37
+ const deep = desc?.detect?.deep ?? 10;
38
+
39
+ const items = [];
40
+ const pathHashes = new Set();
41
+
42
+ // 置信度统计
43
+ const basePath = expandTilde(USER_TIER_2_CONFIG.globalPath);
44
+ const dirExists = safeDirExists(basePath);
45
+ let matchedFiles = 0;
46
+ let parsedValid = 0;
47
+ let metadataComplete = 0;
48
+
49
+ try {
50
+ const patterns = relPatterns.map(p => `${basePath}/${p}`);
51
+
52
+ const seen = new Set();
53
+
54
+ for (const pattern of patterns) {
55
+ if (items.length >= mergedLimits.maxFiles) break;
56
+
57
+ try {
58
+ const matches = await fg(pattern, {
59
+ absolute: true,
60
+ onlyFiles: true,
61
+ dot: true,
62
+ followSymbolicLinks: true,
63
+ deep,
64
+ ignore,
65
+ });
66
+
67
+ for (const filePath of matches) {
68
+ if (items.length >= mergedLimits.maxFiles) break;
69
+ if (seen.has(filePath)) continue;
70
+ seen.add(filePath);
71
+ matchedFiles++;
72
+
73
+ // 路径去重:检查是否已在 Tier 1 中
74
+ const hash = pathHashCache.getOrCompute(filePath);
75
+ if (tier1PathHashes.has(hash)) {
76
+ continue; // 跳过重复项
77
+ }
78
+
79
+ try {
80
+ const skill = parseSkillFile({
81
+ abs: filePath,
82
+ editor: 'my-skills',
83
+ limits: mergedLimits,
84
+ });
85
+ if (skill) {
86
+ skill.tierId = 'tier-2';
87
+ skill.pathHash = hash;
88
+ if (skill.name) parsedValid++;
89
+ if (skill.name && skill.description) metadataComplete++;
90
+ items.push(skill);
91
+ pathHashes.add(hash);
92
+ }
93
+ } catch (e) {
94
+ console.warn(`[scanTier2] parse failed: ${filePath}`, e.message);
95
+ }
96
+ }
97
+ } catch (e) {
98
+ // glob 模式失败,继续下一个
99
+ }
100
+ }
101
+ } catch (e) {
102
+ console.warn(`[scanTier2] scan failed:`, e.message);
103
+ }
104
+
105
+ const confidence = computeConfidence({ dirExists, matchedFiles, parsedValid, metadataComplete });
106
+ if (confidence) {
107
+ for (const item of items) item.confidence = confidence;
108
+ }
109
+
110
+ return { items, pathHashes, confidence };
111
+ }
112
+
113
+ function safeDirExists(p) {
114
+ try {
115
+ return fs.existsSync(p);
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 解析单个技能文件
123
+ * @private
124
+ */
125
+ function parseSkillFile({ abs, editor, limits }) {
126
+ const { text, error } = readFileSafe(abs, limits.maxFileBytes);
127
+ if (error) return null;
128
+
129
+ const id = sha1Id(abs);
130
+ const dirName = path.basename(path.dirname(abs));
131
+
132
+ let frontmatter = {};
133
+ let body = text;
134
+ let parseError = null;
135
+
136
+ // 简单的 frontmatter 解析
137
+ if (text.startsWith('---\n')) {
138
+ const endIdx = text.indexOf('\n---\n', 4);
139
+ if (endIdx > 0) {
140
+ const fm = text.slice(4, endIdx);
141
+ try {
142
+ frontmatter = parseYAML(fm);
143
+ body = text.slice(endIdx + 5);
144
+ } catch (e) {
145
+ parseError = `Frontmatter parse error: ${e.message}`;
146
+ }
147
+ }
148
+ }
149
+
150
+ const name = frontmatter.name || dirName;
151
+ const description = frontmatter.description || '';
152
+ const category = frontmatter.category;
153
+
154
+ return {
155
+ id,
156
+ kind: 'skill',
157
+ source: 'tier2-user',
158
+ editor,
159
+ name,
160
+ description,
161
+ category,
162
+ paths: {
163
+ abs,
164
+ rootKind: 'home',
165
+ },
166
+ preview: makePreview(body),
167
+ raw: body,
168
+ updatedAt: new Date().toISOString(),
169
+ parseError,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * 极简 YAML 解析
175
+ * @private
176
+ */
177
+ function parseYAML(yamlStr) {
178
+ const obj = {};
179
+ const lines = yamlStr.split('\n');
180
+
181
+ for (const line of lines) {
182
+ const trimmed = line.trim();
183
+ if (!trimmed || trimmed.startsWith('#')) continue;
184
+
185
+ const colonIdx = trimmed.indexOf(':');
186
+ if (colonIdx <= 0) continue;
187
+
188
+ const key = trimmed.slice(0, colonIdx).trim();
189
+ const value = trimmed.slice(colonIdx + 1).trim();
190
+
191
+ const unquoted = value.startsWith('"')
192
+ ? value.slice(1, -1)
193
+ : value.startsWith("'")
194
+ ? value.slice(1, -1)
195
+ : value;
196
+
197
+ obj[key] = unquoted || null;
198
+ }
199
+
200
+ return obj;
201
+ }
202
+
203
+ /**
204
+ * 生成预览文本
205
+ * @private
206
+ */
207
+ function makePreview(text, maxLen = 200) {
208
+ const lines = text.split('\n').filter(l => l.trim());
209
+ let preview = '';
210
+ for (const line of lines) {
211
+ if ((preview + line).length > maxLen) {
212
+ preview += '...';
213
+ break;
214
+ }
215
+ preview += (preview ? ' ' : '') + line;
216
+ }
217
+ return preview;
218
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @file tier3-other-skills.mjs
3
+ * 扫描第3层:其他位置的 skill.md 文件
4
+ * 仅在 scanTier3=true 时启用
5
+ * 与第1、2层的 pathHashes 进行去重
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import fs from 'node:fs';
10
+ import fg from 'fast-glob';
11
+ import { expandTilde, readFileSafe, sha1Id } from '../utils.mjs';
12
+ import { OTHER_TIER_3_CONFIGS } from '../config/editor-tiers.mjs';
13
+ import { pathHashCache } from '../hash/path-hash.mjs';
14
+ import { ensureRegistered, getDescriptor } from '../core/registry.mjs';
15
+ import { computeConfidence } from '../core/descriptor.mjs';
16
+
17
+ /**
18
+ * 扫描第3层:其他位置
19
+ * 仅扫描小写 skill.md(精确大小写敏感)
20
+ * 与第1、2层的 pathHashes 进行去重
21
+ *
22
+ * @param {Object} options
23
+ * @param {Set<string>} options.tier1PathHashes - 第1层的所有 pathHash 集合
24
+ * @param {Set<string>} options.tier2PathHashes - 第2层的所有 pathHash 集合
25
+ * @param {Object} [options.limits] - { maxFiles, maxFileBytes }
26
+ * @returns {Promise<{items: SkillItem[]}>}
27
+ */
28
+ export async function scanTier3OtherSkills(options = {}) {
29
+ const { tier1PathHashes = new Set(), tier2PathHashes = new Set(), limits = {} } = options;
30
+ const mergedLimits = {
31
+ maxFiles: limits.maxFiles ?? 5000,
32
+ maxFileBytes: limits.maxFileBytes ?? 1024 * 1024,
33
+ };
34
+
35
+ // descriptor registry 化(渐进迁移)
36
+ ensureRegistered();
37
+
38
+ const items = [];
39
+
40
+ // 合并第1、2层的 pathHashes
41
+ const existingHashes = new Set([...tier1PathHashes, ...tier2PathHashes]);
42
+
43
+ for (const location of OTHER_TIER_3_CONFIGS) {
44
+ if (items.length >= mergedLimits.maxFiles) break;
45
+
46
+ const desc = getDescriptor(`tier3:${location.name}`);
47
+ const relPatterns = desc?.detect?.globPatterns || [`**/skill.md`];
48
+ const ignore = desc?.detect?.ignore || ['**/node_modules/**', '**/.git/**', '**/dist/**'];
49
+ const deep = desc?.detect?.deep ?? 10;
50
+
51
+ const basePath = expandTilde(location.path);
52
+ const dirExists = safeDirExists(basePath);
53
+ let matchedFiles = 0;
54
+ let parsedValid = 0;
55
+ let metadataComplete = 0;
56
+
57
+ try {
58
+ const patterns = relPatterns.map(p => `${basePath}/${p}`);
59
+
60
+ const seen = new Set();
61
+
62
+ for (const pattern of patterns) {
63
+ const matches = await fg(pattern, {
64
+ absolute: true,
65
+ onlyFiles: true,
66
+ dot: true,
67
+ followSymbolicLinks: true,
68
+ deep,
69
+ ignore,
70
+ });
71
+
72
+ for (const filePath of matches) {
73
+ if (items.length >= mergedLimits.maxFiles) break;
74
+ if (seen.has(filePath)) continue;
75
+ seen.add(filePath);
76
+ matchedFiles++;
77
+
78
+ // 路径去重
79
+ const hash = pathHashCache.getOrCompute(filePath);
80
+ if (existingHashes.has(hash)) {
81
+ continue; // 跳过重复项
82
+ }
83
+
84
+ try {
85
+ const skill = parseSkillFile({
86
+ abs: filePath,
87
+ editor: 'other-skills',
88
+ limits: mergedLimits,
89
+ location: location.name,
90
+ });
91
+ if (skill) {
92
+ skill.tierId = 'tier-3';
93
+ skill.pathHash = hash;
94
+ if (skill.name) parsedValid++;
95
+ if (skill.name && skill.description) metadataComplete++;
96
+ items.push(skill);
97
+ existingHashes.add(hash);
98
+ }
99
+ } catch (e) {
100
+ console.warn(`[scanTier3] parse failed: ${filePath}`, e.message);
101
+ }
102
+ }
103
+ }
104
+ } catch (e) {
105
+ console.warn(`[scanTier3] scan ${location.name} failed:`, e.message);
106
+ }
107
+
108
+ // 每个 location 独立计算置信度
109
+ const confidence = computeConfidence({ dirExists, matchedFiles, parsedValid, metadataComplete });
110
+ if (confidence) {
111
+ for (const item of items) {
112
+ if (item.tierId === 'tier-3' && !item.confidence) item.confidence = confidence;
113
+ }
114
+ }
115
+ }
116
+
117
+ return { items };
118
+ }
119
+
120
+ function safeDirExists(p) {
121
+ try {
122
+ return fs.existsSync(p);
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * 解析单个技能文件
130
+ * @private
131
+ */
132
+ function parseSkillFile({ abs, editor, limits, location }) {
133
+ const { text, error } = readFileSafe(abs, limits.maxFileBytes);
134
+ if (error) return null;
135
+
136
+ const id = sha1Id(abs);
137
+ const dirName = path.basename(path.dirname(abs));
138
+
139
+ let frontmatter = {};
140
+ let body = text;
141
+ let parseError = null;
142
+
143
+ // 简单的 frontmatter 解析
144
+ if (text.startsWith('---\n')) {
145
+ const endIdx = text.indexOf('\n---\n', 4);
146
+ if (endIdx > 0) {
147
+ const fm = text.slice(4, endIdx);
148
+ try {
149
+ frontmatter = parseYAML(fm);
150
+ body = text.slice(endIdx + 5);
151
+ } catch (e) {
152
+ parseError = `Frontmatter parse error: ${e.message}`;
153
+ }
154
+ }
155
+ }
156
+
157
+ const name = frontmatter.name || dirName;
158
+ const description = frontmatter.description || '';
159
+ const category = frontmatter.category;
160
+
161
+ return {
162
+ id,
163
+ kind: 'skill',
164
+ source: 'tier3-other',
165
+ editor,
166
+ name,
167
+ description,
168
+ category,
169
+ paths: {
170
+ abs,
171
+ rootKind: 'home',
172
+ },
173
+ preview: makePreview(body),
174
+ raw: body,
175
+ updatedAt: new Date().toISOString(),
176
+ parseError,
177
+ };
178
+ }
179
+
180
+ /**
181
+ * 极简 YAML 解析
182
+ * @private
183
+ */
184
+ function parseYAML(yamlStr) {
185
+ const obj = {};
186
+ const lines = yamlStr.split('\n');
187
+
188
+ for (const line of lines) {
189
+ const trimmed = line.trim();
190
+ if (!trimmed || trimmed.startsWith('#')) continue;
191
+
192
+ const colonIdx = trimmed.indexOf(':');
193
+ if (colonIdx <= 0) continue;
194
+
195
+ const key = trimmed.slice(0, colonIdx).trim();
196
+ const value = trimmed.slice(colonIdx + 1).trim();
197
+
198
+ const unquoted = value.startsWith('"')
199
+ ? value.slice(1, -1)
200
+ : value.startsWith("'")
201
+ ? value.slice(1, -1)
202
+ : value;
203
+
204
+ obj[key] = unquoted || null;
205
+ }
206
+
207
+ return obj;
208
+ }
209
+
210
+ /**
211
+ * 生成预览文本
212
+ * @private
213
+ */
214
+ function makePreview(text, maxLen = 200) {
215
+ const lines = text.split('\n').filter(l => l.trim());
216
+ let preview = '';
217
+ for (const line of lines) {
218
+ if ((preview + line).length > maxLen) {
219
+ preview += '...';
220
+ break;
221
+ }
222
+ preview += (preview ? ' ' : '') + line;
223
+ }
224
+ return preview;
225
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @file editor-tiers.mjs
3
+ * 22个编辑器工具的配置,按优先级排序
4
+ * 用于第1层扫描的路径和品牌映射
5
+ */
6
+
7
+ /**
8
+ * Tier 1:22 个编辑器工具(按优先级)
9
+ */
10
+ export const EDITOR_TIER_1_CONFIGS = [
11
+ {
12
+ id: 1,
13
+ brand: 'hermes',
14
+ name: 'Hermes',
15
+ globalPath: '~/.hermes/skills',
16
+ projectPath: '.hermes/skills',
17
+ },
18
+ {
19
+ id: 2,
20
+ brand: 'claude',
21
+ name: 'Claude',
22
+ globalPath: '~/.claude/skills',
23
+ projectPath: '.claude/skills',
24
+ },
25
+ {
26
+ id: 3,
27
+ brand: 'cursor',
28
+ name: 'Cursor',
29
+ globalPath: '~/.cursor/skills',
30
+ projectPath: '.cursor/skills',
31
+ },
32
+ {
33
+ id: 4,
34
+ brand: 'vscode',
35
+ name: 'VSCode',
36
+ globalPath: '~/.vscode/skills',
37
+ projectPath: '.vscode/skills',
38
+ },
39
+ {
40
+ id: 5,
41
+ brand: 'codeium',
42
+ name: 'Codeium',
43
+ globalPath: '~/.codeium/skills',
44
+ projectPath: '.codeium/skills',
45
+ },
46
+ {
47
+ id: 6,
48
+ brand: 'windsurf',
49
+ name: 'Windsurf',
50
+ globalPath: '~/.windsurf/skills',
51
+ projectPath: '.windsurf/skills',
52
+ },
53
+ {
54
+ id: 7,
55
+ brand: 'continue',
56
+ name: 'Continue',
57
+ globalPath: '~/.continue/skills',
58
+ projectPath: '.continue/skills',
59
+ },
60
+ {
61
+ id: 8,
62
+ brand: 'tauri',
63
+ name: 'Tauri',
64
+ globalPath: '~/.tauri/skills',
65
+ projectPath: '.tauri/skills',
66
+ },
67
+ {
68
+ id: 9,
69
+ brand: 'trae',
70
+ name: 'Trae',
71
+ globalPath: '~/.trae/skills',
72
+ projectPath: '.trae/skills',
73
+ },
74
+ {
75
+ id: 10,
76
+ brand: 'trae-cn',
77
+ name: 'Trae-CN',
78
+ globalPath: '~/.trae-cn/skills',
79
+ projectPath: '.trae-cn/skills',
80
+ },
81
+ {
82
+ id: 11,
83
+ brand: 'qoder',
84
+ name: 'QoDer',
85
+ globalPath: '~/.qoder/skills',
86
+ projectPath: '.qoder/skills',
87
+ },
88
+ {
89
+ id: 12,
90
+ brand: 'codex',
91
+ name: 'Codex',
92
+ globalPath: '~/.codex/skills',
93
+ projectPath: '.codex/skills',
94
+ },
95
+ {
96
+ id: 13,
97
+ brand: 'vim',
98
+ name: 'Vim',
99
+ globalPath: '~/.vim/skills',
100
+ projectPath: '.vim/skills',
101
+ },
102
+ {
103
+ id: 14,
104
+ brand: 'neovim',
105
+ name: 'Neovim',
106
+ globalPath: '~/.config/nvim/skills',
107
+ projectPath: '.nvim/skills',
108
+ },
109
+ {
110
+ id: 15,
111
+ brand: 'emacs',
112
+ name: 'Emacs',
113
+ globalPath: '~/.emacs.d/skills',
114
+ projectPath: '.emacs.d/skills',
115
+ },
116
+ {
117
+ id: 16,
118
+ brand: 'sublime',
119
+ name: 'Sublime',
120
+ globalPath: '~/.config/sublime-text/skills',
121
+ projectPath: '.sublime-text/skills',
122
+ },
123
+ {
124
+ id: 17,
125
+ brand: 'jetbrains',
126
+ name: 'Jetbrains IDE',
127
+ globalPath: '~/.config/JetBrains/skills',
128
+ projectPath: '.jetbrains/skills',
129
+ },
130
+ {
131
+ id: 18,
132
+ brand: 'nova',
133
+ name: 'Nova',
134
+ globalPath: '~/.nova/skills',
135
+ projectPath: '.nova/skills',
136
+ },
137
+ {
138
+ id: 19,
139
+ brand: 'zed',
140
+ name: 'Zed',
141
+ globalPath: '~/.config/zed/skills',
142
+ projectPath: '.zed/skills',
143
+ },
144
+ {
145
+ id: 20,
146
+ brand: 'copilot',
147
+ name: 'GitHub Copilot',
148
+ globalPath: '~/.copilot/skills',
149
+ projectPath: '.copilot/skills',
150
+ },
151
+ {
152
+ id: 21,
153
+ brand: 'replit',
154
+ name: 'Replit',
155
+ globalPath: '~/.replit/skills',
156
+ projectPath: '.replit/skills',
157
+ },
158
+ {
159
+ id: 22,
160
+ brand: 'glot',
161
+ name: 'Glot.io',
162
+ globalPath: '~/.glot/skills',
163
+ projectPath: '.glot/skills',
164
+ },
165
+ ];
166
+
167
+ /**
168
+ * Tier 2:用户根目录技能库
169
+ */
170
+ export const USER_TIER_2_CONFIG = {
171
+ name: 'My Skills Library',
172
+ globalPath: '~/skills', // 支持 ~/Skills / ~/SKILLS 等大小写变体
173
+ };
174
+
175
+ /**
176
+ * Tier 3:其他位置(可选扫描)
177
+ */
178
+ export const OTHER_TIER_3_CONFIGS = [
179
+ {
180
+ name: 'System Opt',
181
+ path: '/opt',
182
+ },
183
+ {
184
+ name: 'Homebrew',
185
+ path: '/usr/local',
186
+ },
187
+ {
188
+ name: 'User Opt',
189
+ path: '~/opt',
190
+ },
191
+ ];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file atomic-write.mjs
3
+ * 2026-07 阶段三:实现已迁入 bin/lib/atomic-write.mjs(底层公共模块)。
4
+ * 本文件保留为 re-export,保持 scanner 包内现有引用不变。
5
+ * 新代码可直接引用 ../../../bin/lib/atomic-write.mjs。
6
+ *
7
+ * 原子写工具:临时文件 + rename,保证进程中断不产生半截文件。
8
+ * 详见 bin/lib/atomic-write.mjs。
9
+ */
10
+
11
+ export { atomicWriteText, atomicWriteBytes, atomicWriteJson } from '../../../../bin/lib/atomic-write.mjs';