skill-base 2.0.16 → 2.0.18

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 (109) hide show
  1. package/README.md +8 -2
  2. package/bin/skill-base.js +73 -28
  3. package/package.json +2 -2
  4. package/src/cappy.js +162 -50
  5. package/src/database.js +17 -17
  6. package/src/index.js +75 -22
  7. package/src/middleware/admin.js +3 -3
  8. package/src/middleware/auth.js +22 -22
  9. package/src/middleware/error.js +4 -4
  10. package/src/models/skill.js +6 -6
  11. package/src/models/user.js +10 -10
  12. package/src/models/version.js +6 -6
  13. package/src/routes/auth.js +17 -17
  14. package/src/routes/collaborators.js +28 -28
  15. package/src/routes/init.js +7 -7
  16. package/src/routes/publish.js +15 -15
  17. package/src/routes/skills.js +13 -13
  18. package/src/routes/users.js +9 -9
  19. package/src/utils/crypto.js +6 -6
  20. package/src/utils/detect-language.js +56 -0
  21. package/src/utils/permission.js +7 -7
  22. package/src/utils/zip.js +6 -6
  23. package/static/assets/{index-BHB0vddE.js → index-BVgsNsqr.js} +43 -43
  24. package/static/assets/index-ByONPaqz.css +1 -0
  25. package/static/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  26. package/static/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  27. package/static/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  28. package/static/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  29. package/static/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  30. package/static/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  31. package/static/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
  32. package/static/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
  33. package/static/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  34. package/static/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  35. package/static/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  36. package/static/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  37. package/static/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  38. package/static/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  39. package/static/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
  40. package/static/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
  41. package/static/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  42. package/static/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  43. package/static/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  44. package/static/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  45. package/static/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  46. package/static/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  47. package/static/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
  48. package/static/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
  49. package/static/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  50. package/static/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  51. package/static/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  52. package/static/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  53. package/static/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  54. package/static/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  55. package/static/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
  56. package/static/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
  57. package/static/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  58. package/static/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  59. package/static/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  60. package/static/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  61. package/static/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  62. package/static/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  63. package/static/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  64. package/static/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  65. package/static/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  66. package/static/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  67. package/static/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  68. package/static/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  69. package/static/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  70. package/static/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  71. package/static/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
  72. package/static/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
  73. package/static/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  74. package/static/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  75. package/static/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  76. package/static/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  77. package/static/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  78. package/static/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  79. package/static/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
  80. package/static/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
  81. package/static/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  82. package/static/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  83. package/static/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  84. package/static/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  85. package/static/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  86. package/static/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  87. package/static/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  88. package/static/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  89. package/static/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  90. package/static/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  91. package/static/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  92. package/static/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  93. package/static/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  94. package/static/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  95. package/static/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  96. package/static/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  97. package/static/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  98. package/static/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  99. package/static/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  100. package/static/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  101. package/static/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  102. package/static/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  103. package/static/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  104. package/static/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  105. package/static/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  106. package/static/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  107. package/static/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  108. package/static/index.html +2 -5
  109. package/static/assets/index-EVWfLxoq.css +0 -1
package/src/index.js CHANGED
@@ -13,13 +13,36 @@ const fastify = require('fastify')({
13
13
  bodyLimit: 100 * 1024 * 1024
14
14
  });
15
15
  const CappyMascot = require('./cappy');
16
+ const appLanguage = CappyMascot.detectSystemLanguage();
17
+
18
+ function pickMessage(message) {
19
+ if (typeof message === 'string') return message;
20
+ if (!message || typeof message !== 'object') return '';
21
+ return message[appLanguage] || message.en || message.zh || '';
22
+ }
23
+
24
+ function infoLog(message, ...args) {
25
+ console.log(pickMessage(message), ...args);
26
+ }
27
+
28
+ function debugLog(message, ...args) {
29
+ if (!isDebug) return;
30
+ console.log(`DEBUG: ${pickMessage(message)}`, ...args);
31
+ }
32
+
33
+ function errorLog(message, ...args) {
34
+ console.error(pickMessage(message), ...args);
35
+ }
16
36
 
17
37
  if (isDebug) {
18
- console.log('DEBUG: Debug mode is enabled.');
19
- console.log('DEBUG: PORT:', process.env.PORT);
20
- console.log('DEBUG: HOST:', process.env.HOST);
21
- console.log('DEBUG: APP_BASE_PATH:', process.env.APP_BASE_PATH);
22
- console.log('DEBUG: CACHE_MAX_MB:', process.env.CACHE_MAX_MB);
38
+ debugLog({
39
+ zh: '调试模式已启用。',
40
+ en: 'Debug mode is enabled.'
41
+ });
42
+ debugLog({ zh: '端口:', en: 'PORT:' }, process.env.PORT);
43
+ debugLog({ zh: '主机:', en: 'HOST:' }, process.env.HOST);
44
+ debugLog({ zh: '应用基础路径:', en: 'APP_BASE_PATH:' }, process.env.APP_BASE_PATH);
45
+ debugLog({ zh: '缓存上限(MB):', en: 'CACHE_MAX_MB:' }, process.env.CACHE_MAX_MB);
23
46
  }
24
47
 
25
48
  // 1. Normalize deploy prefix (APP_BASE_PATH)
@@ -47,18 +70,21 @@ function renderSpaHtml() {
47
70
  // Main bootstrap
48
71
  async function start() {
49
72
  try {
50
- if (isDebug) console.log('DEBUG: Registering core plugins...');
73
+ debugLog({
74
+ zh: '正在注册核心插件...',
75
+ en: 'Registering core plugins...'
76
+ });
51
77
  // 1. Plugins
52
78
  // @fastify/cors — CORS
53
79
  await fastify.register(require('@fastify/cors'), {
54
80
  origin: true,
55
81
  credentials: true
56
82
  });
57
- if (isDebug) console.log('DEBUG: Registered @fastify/cors');
83
+ debugLog({ zh: '已注册 @fastify/cors', en: 'Registered @fastify/cors' });
58
84
 
59
85
  // @fastify/cookie
60
86
  await fastify.register(require('@fastify/cookie'));
61
- if (isDebug) console.log('DEBUG: Registered @fastify/cookie');
87
+ debugLog({ zh: '已注册 @fastify/cookie', en: 'Registered @fastify/cookie' });
62
88
 
63
89
  // @fastify/multipart — uploads
64
90
  await fastify.register(require('@fastify/multipart'), {
@@ -66,7 +92,7 @@ async function start() {
66
92
  fileSize: 100 * 1024 * 1024 // 100MB
67
93
  }
68
94
  });
69
- if (isDebug) console.log('DEBUG: Registered @fastify/multipart');
95
+ debugLog({ zh: '已注册 @fastify/multipart', en: 'Registered @fastify/multipart' });
70
96
 
71
97
  // Before static: with index:false, directory + trailing slash can 403 in send and skip notFoundHandler
72
98
  fastify.route({
@@ -88,21 +114,33 @@ async function start() {
88
114
  wildcard: true,
89
115
  index: false
90
116
  });
91
- if (isDebug) console.log('DEBUG: Registered @fastify/static at', STATIC_ROOT);
117
+ debugLog(
118
+ { zh: '已注册 @fastify/static,目录为', en: 'Registered @fastify/static at' },
119
+ STATIC_ROOT
120
+ );
92
121
 
93
122
  // 2. Custom middleware
94
- if (isDebug) console.log('DEBUG: Registering custom middlewares...');
123
+ debugLog({
124
+ zh: '正在注册自定义中间件...',
125
+ en: 'Registering custom middlewares...'
126
+ });
95
127
  // Errors
96
128
  await fastify.register(require('./middleware/error'));
97
129
  // Auth (authenticate, createSession, …)
98
130
  await fastify.register(require('./middleware/auth'));
99
131
  // Admin (requireAdmin)
100
132
  await fastify.register(require('./middleware/admin'));
101
- if (isDebug) console.log('DEBUG: Custom middlewares registered.');
133
+ debugLog({
134
+ zh: '自定义中间件已注册。',
135
+ en: 'Custom middlewares registered.'
136
+ });
102
137
 
103
138
  // 3. API routes
104
139
  const API_PREFIX = (APP_BASE_PATH + 'api/v1').replace(/\/+/g, '/');
105
- if (isDebug) console.log('DEBUG: Registering API routes with prefix:', API_PREFIX);
140
+ debugLog(
141
+ { zh: '正在注册 API 路由,前缀为:', en: 'Registering API routes with prefix:' },
142
+ API_PREFIX
143
+ );
106
144
 
107
145
  // Health
108
146
  fastify.get(`${API_PREFIX}/health`, async () => {
@@ -123,7 +161,7 @@ async function start() {
123
161
  await fastify.register(require('./routes/publish'), { prefix: `${API_PREFIX}/skills` });
124
162
  await fastify.register(require('./routes/collaborators'), { prefix: `${API_PREFIX}/skills` });
125
163
  await fastify.register(require('./routes/users'), { prefix: `${API_PREFIX}/users` });
126
- if (isDebug) console.log('DEBUG: API routes registered.');
164
+ debugLog({ zh: 'API 路由已注册。', en: 'API routes registered.' });
127
165
 
128
166
  // 4. SPA fallback for non-API routes
129
167
  fastify.setNotFoundHandler(async (request, reply) => {
@@ -158,9 +196,9 @@ async function start() {
158
196
  let cappy = null;
159
197
 
160
198
  if (enableCappy) {
161
- if (isDebug) console.log('DEBUG: CappyMascot is enabled.');
199
+ debugLog({ zh: 'CappyMascot 已启用。', en: 'CappyMascot is enabled.' });
162
200
  // Cappy (decorate before listen)
163
- cappy = new CappyMascot(PORT, APP_BASE_PATH);
201
+ cappy = new CappyMascot(PORT, APP_BASE_PATH, appLanguage);
164
202
  fastify.decorate('cappy', cappy);
165
203
 
166
204
  // Drive Cappy from onResponse; no route changes
@@ -171,29 +209,44 @@ async function start() {
171
209
  const url = request.url.split('?')[0];
172
210
 
173
211
  if (method === 'POST' && url === `${API_PREFIX}/users`) {
174
- cappy.action('New user added. Another worker on the roster; the system stays steady.');
212
+ cappy.action({
213
+ zh: '新用户已添加。又多一个人干活,系统依旧稳定。',
214
+ en: 'New user added. Another worker on the roster; the system stays steady.'
215
+ });
175
216
  } else if (method === 'POST' && url === `${API_PREFIX}/skills/publish`) {
176
- cappy.action('New skill or version published. Hope the code stays simple.');
217
+ cappy.action({
218
+ zh: '新的 Skill 或版本已发布。希望代码继续保持简单。',
219
+ en: 'New skill or version published. Hope the code stays simple.'
220
+ });
177
221
  } else if (method === 'GET' && url.match(new RegExp(`^${API_PREFIX}/skills/[^/]+/versions/[^/]+/download/?$`))) {
178
- cappy.action('Someone downloaded a skill. Code is moving—Cappy approves.');
222
+ cappy.action({
223
+ zh: '有人下载了一个 Skill。代码开始流动了,Cappy 认可。',
224
+ en: 'Someone downloaded a skill. Code is moving—Cappy approves.'
225
+ });
179
226
  }
180
227
  }
181
228
  done();
182
229
  });
183
230
  } else {
184
- if (isDebug) console.log('DEBUG: CappyMascot is disabled.');
231
+ debugLog({ zh: 'CappyMascot 已禁用。', en: 'CappyMascot is disabled.' });
185
232
  fastify.decorate('cappy', { action: () => {} });
186
233
  }
187
234
 
188
235
  await fastify.listen({ port: PORT, host: HOST });
189
- console.log(`\n📦 Skill Base Engine Initialized at http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}${APP_BASE_PATH}\n`);
236
+ infoLog({
237
+ zh: `\n📦 Skill Base 引擎已启动: http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}${APP_BASE_PATH}\n`,
238
+ en: `\n📦 Skill Base Engine Initialized at http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}${APP_BASE_PATH}\n`
239
+ });
190
240
 
191
241
  if (enableCappy && cappy) {
192
242
  // Start Cappy loop
193
243
  cappy.start();
194
244
  }
195
245
  } catch (err) {
196
- console.error(err);
246
+ errorLog(
247
+ { zh: 'Skill Base 启动失败。', en: 'Skill Base failed to start.' },
248
+ err
249
+ );
197
250
  process.exit(1);
198
251
  }
199
252
  }
@@ -2,16 +2,16 @@ const fp = require('fastify-plugin');
2
2
 
3
3
  async function adminPlugin(fastify, options) {
4
4
  fastify.decorate('requireAdmin', async function(request, reply) {
5
- // 1. 先调用 authenticate 确保已登录
5
+ // 1. First call authenticate to ensure logged in
6
6
  await fastify.authenticate(request, reply);
7
7
  if (reply.sent) return;
8
8
 
9
- // 2. 检查管理员角色
9
+ // 2. Check admin role
10
10
  if (request.user.role !== 'admin') {
11
11
  return reply.code(403).send({
12
12
  ok: false,
13
13
  error: 'forbidden',
14
- detail: '需要管理员权限'
14
+ detail: 'Admin permission required'
15
15
  });
16
16
  }
17
17
  });
@@ -2,12 +2,12 @@ const fp = require('fastify-plugin');
2
2
  const db = require('../database');
3
3
  const { generateSessionId } = require('../utils/crypto');
4
4
 
5
- // Session 存储模式:'memory' | 'sqlite',通过环境变量配置
5
+ // Session storage mode: 'memory' | 'sqlite', configured via environment variable
6
6
  const SESSION_STORE = process.env.SESSION_STORE || 'memory';
7
- // Session 过期时间(默认 7 天)
7
+ // Session expiration time (default 7 days)
8
8
  const SESSION_EXPIRES_DAYS = parseInt(process.env.SESSION_EXPIRES_DAYS || '7', 10);
9
9
 
10
- // ============ 内存存储实现 ============
10
+ // ============ Memory storage implementation ============
11
11
  const memorySessions = new Map();
12
12
 
13
13
  const memoryStore = {
@@ -20,7 +20,7 @@ const memoryStore = {
20
20
  get(sessionId) {
21
21
  const session = memorySessions.get(sessionId);
22
22
  if (!session) return null;
23
- // 检查过期
23
+ // Check expiration
24
24
  if (Date.now() > session.expiresAt) {
25
25
  memorySessions.delete(sessionId);
26
26
  return null;
@@ -30,7 +30,7 @@ const memoryStore = {
30
30
  destroy(sessionId) {
31
31
  memorySessions.delete(sessionId);
32
32
  },
33
- // 清理过期 Session
33
+ // Clean up expired sessions
34
34
  cleanup() {
35
35
  const now = Date.now();
36
36
  for (const [id, session] of memorySessions) {
@@ -41,7 +41,7 @@ const memoryStore = {
41
41
  }
42
42
  };
43
43
 
44
- // ============ SQLite 存储实现 ============
44
+ // ============ SQLite storage implementation ============
45
45
  const sqliteStore = {
46
46
  create(userId) {
47
47
  const sessionId = generateSessionId();
@@ -62,32 +62,32 @@ const sqliteStore = {
62
62
  destroy(sessionId) {
63
63
  db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId);
64
64
  },
65
- // 清理过期 Session
65
+ // Clean up expired sessions
66
66
  cleanup() {
67
67
  db.prepare("DELETE FROM sessions WHERE expires_at <= datetime('now')").run();
68
68
  }
69
69
  };
70
70
 
71
- // 根据配置选择存储实现
71
+ // Select storage implementation based on configuration
72
72
  const sessionStore = SESSION_STORE === 'sqlite' ? sqliteStore : memoryStore;
73
73
 
74
- // 启动时清理一次过期 Session
74
+ // Clean up expired sessions on startup
75
75
  sessionStore.cleanup();
76
76
 
77
- // 定期清理过期 Session(每小时)
77
+ // Periodically clean up expired sessions (every hour)
78
78
  setInterval(() => sessionStore.cleanup(), 60 * 60 * 1000);
79
79
 
80
- // 认证中间件装饰器 —— 注册为 Fastify decorate + preHandler
81
- // 用法:在路由中通过 { preHandler: [fastify.authenticate] } 使用
80
+ // Authentication middleware decorator - registered as Fastify's decorate + preHandler
81
+ // Usage: use via { preHandler: [fastify.authenticate] } in routes
82
82
  async function authPlugin(fastify, options) {
83
- // sessionStore 暴露出去供路由使用
83
+ // Expose sessionStore for routes to use
84
84
  fastify.decorate('sessionStore', sessionStore);
85
85
  fastify.decorate('createSession', (userId) => sessionStore.create(userId));
86
86
  fastify.decorate('destroySession', (sessionId) => sessionStore.destroy(sessionId));
87
87
 
88
- // 认证装饰器
88
+ // Authentication decorator
89
89
  fastify.decorate('authenticate', async function(request, reply) {
90
- // 1. 先尝试 Cookie Session
90
+ // 1. Try Cookie Session first
91
91
  const sessionId = request.cookies?.session_id;
92
92
  if (sessionId) {
93
93
  const session = sessionStore.get(sessionId);
@@ -97,7 +97,7 @@ async function authPlugin(fastify, options) {
97
97
  return reply.code(401).send({
98
98
  ok: false,
99
99
  error: 'account_disabled',
100
- detail: '账号已被禁用'
100
+ detail: 'Account has been disabled'
101
101
  });
102
102
  }
103
103
  request.user = user;
@@ -105,7 +105,7 @@ async function authPlugin(fastify, options) {
105
105
  }
106
106
  }
107
107
 
108
- // 2. 再尝试 Bearer TokenPAT
108
+ // 2. Then try Bearer Token (PAT)
109
109
  const authHeader = request.headers.authorization;
110
110
  if (authHeader?.startsWith('Bearer ')) {
111
111
  const token = authHeader.slice(7);
@@ -116,24 +116,24 @@ async function authPlugin(fastify, options) {
116
116
  return reply.code(401).send({
117
117
  ok: false,
118
118
  error: 'account_disabled',
119
- detail: '账号已被禁用'
119
+ detail: 'Account has been disabled'
120
120
  });
121
121
  }
122
- // 更新 last_used_at
122
+ // Update last_used_at
123
123
  db.prepare('UPDATE personal_access_tokens SET last_used_at = CURRENT_TIMESTAMP WHERE token = ?').run(token);
124
124
  request.user = user;
125
125
  return;
126
126
  }
127
127
  }
128
128
 
129
- // 3. 未认证
129
+ // 3. Not authenticated
130
130
  reply.code(401).send({ detail: 'Authentication required' });
131
131
  });
132
132
 
133
- // 可选认证(不强制,有则解析)
133
+ // Optional authentication (not enforced, parse if present)
134
134
  fastify.decorate('optionalAuth', async function(request, reply) {
135
135
  try {
136
- // 复用 authenticate 逻辑,但不抛错
136
+ // Reuse authenticate logic but don't throw error
137
137
  const sessionId = request.cookies?.session_id;
138
138
  if (sessionId) {
139
139
  const session = sessionStore.get(sessionId);
@@ -1,9 +1,9 @@
1
- // Fastify 错误处理插件
1
+ // Fastify error handler plugin
2
2
  async function errorHandler(fastify, options) {
3
3
  fastify.setErrorHandler(function (error, request, reply) {
4
4
  const statusCode = error.statusCode || 500;
5
5
 
6
- // 记录错误日志
6
+ // Log error
7
7
  if (statusCode >= 500) {
8
8
  request.log.error(error);
9
9
  } else {
@@ -16,8 +16,8 @@ async function errorHandler(fastify, options) {
16
16
  });
17
17
  });
18
18
 
19
- // 注意: 404 处理统一在 index.js 中的 setNotFoundHandler 设置
20
- // 包含 API 路由返回 JSON 404 和页面路由返回对应 HTML 的逻辑
19
+ // Note: 404 handling is unified in setNotFoundHandler in index.js
20
+ // Includes logic for returning JSON 404 for API routes and corresponding HTML for page routes
21
21
  }
22
22
 
23
23
  module.exports = errorHandler;
@@ -11,7 +11,7 @@ function queryById(id) {
11
11
  }
12
12
 
13
13
  const SkillModel = {
14
- // 根据 ID 查询 Skill(附带 owner 信息)
14
+ // Find Skill by ID (includes owner info)
15
15
  findById(id) {
16
16
  return modelCache.remember(
17
17
  modelCache.keys.skill(id),
@@ -20,7 +20,7 @@ const SkillModel = {
20
20
  );
21
21
  },
22
22
 
23
- // 搜索/列出 Skills(支持关键词搜索 name description
23
+ // Search/List Skills (supports keyword search on name or description)
24
24
  search(query) {
25
25
  const normalizedQuery = query ? String(query) : '';
26
26
  return modelCache.remember(
@@ -48,7 +48,7 @@ const SkillModel = {
48
48
  );
49
49
  },
50
50
 
51
- // 创建新 Skill
51
+ // Create new Skill
52
52
  create(id, name, description, ownerId) {
53
53
  db.prepare(`
54
54
  INSERT INTO skills (id, name, description, owner_id)
@@ -58,7 +58,7 @@ const SkillModel = {
58
58
  return queryById(id);
59
59
  },
60
60
 
61
- // 更新 Skill
61
+ // Update Skill
62
62
  update(id, name, description) {
63
63
  const fields = [];
64
64
  const values = [];
@@ -83,7 +83,7 @@ const SkillModel = {
83
83
  return queryById(id);
84
84
  },
85
85
 
86
- // 更新 latest_version updated_at
86
+ // Update latest_version and updated_at
87
87
  updateLatestVersion(id, version) {
88
88
  db.prepare(`
89
89
  UPDATE skills SET latest_version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
@@ -91,7 +91,7 @@ const SkillModel = {
91
91
  modelCache.invalidateSkill(id);
92
92
  },
93
93
 
94
- // 检查 Skill 是否存在
94
+ // Check if Skill exists
95
95
  exists(id) {
96
96
  return modelCache.remember(
97
97
  modelCache.keys.skillExists(id),
@@ -6,7 +6,7 @@ function queryById(id) {
6
6
  }
7
7
 
8
8
  const UserModel = {
9
- // 根据 ID 查询用户
9
+ // Find user by ID
10
10
  findById(id) {
11
11
  return modelCache.remember(
12
12
  modelCache.keys.userBasic(id),
@@ -15,19 +15,19 @@ const UserModel = {
15
15
  );
16
16
  },
17
17
 
18
- // 根据用户名查询(含 password_hash,用于登录验证)
18
+ // Find user by username (includes password_hash for login verification)
19
19
  findByUsername(username) {
20
20
  return db.prepare('SELECT * FROM users WHERE username = ?').get(username);
21
21
  },
22
22
 
23
- // 创建用户
23
+ // Create user
24
24
  create(username, passwordHash, role = 'developer') {
25
25
  const result = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)').run(username, passwordHash, role);
26
26
  modelCache.invalidateUser(result.lastInsertRowid);
27
27
  return queryById(result.lastInsertRowid);
28
28
  },
29
29
 
30
- // 列出用户(支持分页和搜索)
30
+ // List users (supports pagination and search)
31
31
  list({ q, status, page = 1, limit = 20 } = {}) {
32
32
  let sql = 'SELECT id, username, name, role, status, created_at, updated_at FROM users WHERE 1=1';
33
33
  let countSql = 'SELECT COUNT(*) as total FROM users WHERE 1=1';
@@ -56,7 +56,7 @@ const UserModel = {
56
56
  return { users, total, page, limit };
57
57
  },
58
58
 
59
- // 更新用户名
59
+ // Update username
60
60
  updateUsername(id, username) {
61
61
  const result = db.prepare(
62
62
  "UPDATE users SET username = ?, updated_at = datetime('now') WHERE id = ?"
@@ -67,7 +67,7 @@ const UserModel = {
67
67
  return result.changes > 0;
68
68
  },
69
69
 
70
- // 更新密码
70
+ // Update password
71
71
  updatePassword(id, passwordHash) {
72
72
  const result = db.prepare(
73
73
  "UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?"
@@ -78,7 +78,7 @@ const UserModel = {
78
78
  return result.changes > 0;
79
79
  },
80
80
 
81
- // 更新用户(管理员用)
81
+ // Update user (for admin use)
82
82
  update(id, fields) {
83
83
  const allowed = ['role', 'status', 'username', 'name'];
84
84
  const sets = [];
@@ -103,7 +103,7 @@ const UserModel = {
103
103
  return result.changes > 0;
104
104
  },
105
105
 
106
- // 重置密码(管理员用)
106
+ // Reset password (for admin use)
107
107
  resetPassword(id, passwordHash) {
108
108
  const result = db.prepare(
109
109
  "UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?"
@@ -114,7 +114,7 @@ const UserModel = {
114
114
  return result.changes > 0;
115
115
  },
116
116
 
117
- // 查询用户详情(含创建者信息)
117
+ // Find user details (includes creator info)
118
118
  findByIdWithCreator(id) {
119
119
  return db.prepare(`
120
120
  SELECT u.id, u.username, u.name, u.role, u.status, u.created_at, u.updated_at,
@@ -125,7 +125,7 @@ const UserModel = {
125
125
  `).get(id);
126
126
  },
127
127
 
128
- // 更新用户名和姓名
128
+ // Update username and name
129
129
  updateProfile(id, { username, name }) {
130
130
  const sets = [];
131
131
  const params = [];
@@ -2,7 +2,7 @@ const db = require('../database');
2
2
  const modelCache = require('../utils/model-cache');
3
3
 
4
4
  const VersionModel = {
5
- // 创建新版本
5
+ // Create new version
6
6
  create(skillId, version, changelog, zipPath, uploaderId, description) {
7
7
  const result = db.prepare(`
8
8
  INSERT INTO skill_versions (skill_id, version, changelog, zip_path, uploader_id, description)
@@ -12,7 +12,7 @@ const VersionModel = {
12
12
  return this.findById(result.lastInsertRowid);
13
13
  },
14
14
 
15
- // 根据 ID 查询版本
15
+ // Find version by ID
16
16
  findById(id) {
17
17
  return db.prepare(`
18
18
  SELECT sv.*, u.username as uploader_username, u.name as uploader_name
@@ -22,7 +22,7 @@ const VersionModel = {
22
22
  `).get(id);
23
23
  },
24
24
 
25
- // 根据 skill_id version 查询
25
+ // Find by skill_id and version
26
26
  findByVersion(skillId, version) {
27
27
  return modelCache.remember(
28
28
  modelCache.keys.skillVersion(skillId, version),
@@ -36,7 +36,7 @@ const VersionModel = {
36
36
  );
37
37
  },
38
38
 
39
- // 列出某 Skill 的所有版本(按创建时间倒序)
39
+ // List all versions of a Skill (sorted by created_at descending)
40
40
  listBySkillId(skillId) {
41
41
  return modelCache.remember(
42
42
  modelCache.keys.skillVersions(skillId),
@@ -51,7 +51,7 @@ const VersionModel = {
51
51
  );
52
52
  },
53
53
 
54
- // 获取某 Skill 的最新版本
54
+ // Get the latest version of a Skill
55
55
  getLatest(skillId) {
56
56
  return modelCache.remember(
57
57
  modelCache.keys.skillLatest(skillId),
@@ -67,7 +67,7 @@ const VersionModel = {
67
67
  );
68
68
  },
69
69
 
70
- // 更新版本描述和更新日志
70
+ // Update version description and changelog
71
71
  update(id, description, changelog) {
72
72
  const existing = this.findById(id);
73
73
  db.prepare(`