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
@@ -1,24 +1,24 @@
1
1
  const bcrypt = require('bcryptjs');
2
2
  const { v4: uuidv4 } = require('uuid');
3
3
 
4
- // 密码哈希(10 rounds
4
+ // Password hash (10 rounds)
5
5
  function hashPassword(password) {
6
6
  return bcrypt.hashSync(password, 10);
7
7
  }
8
8
 
9
- // 验证密码
9
+ // Verify password
10
10
  function verifyPassword(password, hash) {
11
11
  return bcrypt.compareSync(password, hash);
12
12
  }
13
13
 
14
- // 生成 PAT Token,格式:sk-base-{uuid去掉横线}
14
+ // Generate PAT Token, format: sk-base-{uuid without dashes}
15
15
  function generatePAT() {
16
16
  return 'sk-base-' + uuidv4().replace(/-/g, '');
17
17
  }
18
18
 
19
- // 生成 CLI 验证码,格式:XXXX-XXXX(大写字母+数字)
19
+ // Generate CLI verification code, format: XXXX-XXXX (uppercase letters + numbers)
20
20
  function generateCliCode() {
21
- const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 排除易混淆字符
21
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing characters
22
22
  let code = '';
23
23
  for (let i = 0; i < 8; i++) {
24
24
  if (i === 4) code += '-';
@@ -27,7 +27,7 @@ function generateCliCode() {
27
27
  return code;
28
28
  }
29
29
 
30
- // 生成 Session ID
30
+ // Generate Session ID
31
31
  function generateSessionId() {
32
32
  return uuidv4();
33
33
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * For Cappy / CLI: zh vs en.
3
+ * Order: explicit locale env vars → (macOS only) system region → Intl.
4
+ */
5
+
6
+ const { execFileSync } = require('child_process');
7
+
8
+ function parseLocaleTag(raw) {
9
+ if (!raw || typeof raw !== 'string') return null;
10
+ const first = raw.trim().split(':')[0].split('.')[0].split('@')[0];
11
+ if (!first || first === 'C' || first === 'POSIX') return null;
12
+ const norm = first.toLowerCase().replace(/_/g, '-');
13
+ if (norm.startsWith('zh')) return 'zh';
14
+ if (norm.startsWith('en')) return 'en';
15
+ return null;
16
+ }
17
+
18
+ /**
19
+ * Terminals often set LANG=C.UTF-8; Intl resolves to en-US, but the OS UI language may still be Chinese.
20
+ * AppleLocale matches System Settings and needs no extra npm packages.
21
+ */
22
+ function detectDarwinLocalePreference() {
23
+ if (process.platform !== 'darwin') return null;
24
+ try {
25
+ const out = execFileSync('/usr/bin/defaults', ['read', '-g', 'AppleLocale'], {
26
+ encoding: 'utf8',
27
+ maxBuffer: 256,
28
+ timeout: 3000
29
+ });
30
+ return parseLocaleTag(out);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function detectSystemLanguage() {
37
+ const envKeys = ['LC_ALL', 'LC_MESSAGES', 'LANG', 'LANGUAGE'];
38
+ for (const k of envKeys) {
39
+ const v = process.env[k];
40
+ if (!v) continue;
41
+ const tag = parseLocaleTag(v);
42
+ if (tag) return tag;
43
+ }
44
+
45
+ const fromDarwin = detectDarwinLocalePreference();
46
+ if (fromDarwin) return fromDarwin;
47
+
48
+ try {
49
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale || '';
50
+ return locale.toLowerCase().startsWith('zh') ? 'zh' : 'en';
51
+ } catch {
52
+ return 'en';
53
+ }
54
+ }
55
+
56
+ module.exports = { detectSystemLanguage };
@@ -1,14 +1,14 @@
1
1
  const db = require('../database');
2
2
 
3
3
  /**
4
- * 检查用户是否有 Skill 的指定权限
5
- * @param {object} user - request.user 对象
4
+ * Check if user has specified permission for a Skill
5
+ * @param {object} user - request.user object
6
6
  * @param {string} skillId - Skill ID
7
7
  * @param {string} requiredRole - 'owner' | 'collaborator' | 'any'
8
8
  * @returns {boolean}
9
9
  */
10
10
  function hasSkillPermission(user, skillId, requiredRole = 'any') {
11
- // 管理员拥有所有权限
11
+ // Admin has all permissions
12
12
  if (user.role === 'admin') return true;
13
13
 
14
14
  const collaborator = db.prepare(
@@ -18,21 +18,21 @@ function hasSkillPermission(user, skillId, requiredRole = 'any') {
18
18
  if (!collaborator) return false;
19
19
  if (requiredRole === 'any') return true;
20
20
  if (requiredRole === 'owner') return collaborator.role === 'owner';
21
- if (requiredRole === 'collaborator') return true; // owner 也有 collaborator 权限
21
+ if (requiredRole === 'collaborator') return true; // owner also has collaborator permission
22
22
  return false;
23
23
  }
24
24
 
25
25
  /**
26
- * 检查用户是否可以发布 Skill
26
+ * Check if user can publish Skill
27
27
  */
28
28
  function canPublishSkill(user, skillId) {
29
29
  const skill = db.prepare('SELECT id FROM skills WHERE id = ?').get(skillId);
30
- if (!skill) return true; // Skill,任何登录用户都可创建
30
+ if (!skill) return true; // New Skill, any logged-in user can create
31
31
  return hasSkillPermission(user, skillId, 'any');
32
32
  }
33
33
 
34
34
  /**
35
- * 检查用户是否可以管理协作者/删除 Skill
35
+ * Check if user can manage collaborators / delete Skill
36
36
  */
37
37
  function canManageSkill(user, skillId) {
38
38
  return hasSkillPermission(user, skillId, 'owner');
package/src/utils/zip.js CHANGED
@@ -1,12 +1,12 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
3
 
4
- // 获取 zip 存储目录
4
+ // Get zip storage directory
5
5
  function getDataDir() {
6
6
  return process.env.DATA_DIR || path.join(__dirname, '../../data');
7
7
  }
8
8
 
9
- // 确保 skill 的存储目录存在
9
+ // Ensure skill storage directory exists
10
10
  function ensureSkillDir(skillId) {
11
11
  const dir = path.join(getDataDir(), 'skills', skillId);
12
12
  if (!fs.existsSync(dir)) {
@@ -15,19 +15,19 @@ function ensureSkillDir(skillId) {
15
15
  return dir;
16
16
  }
17
17
 
18
- // 生成时间戳版本号 vYYYYMMDD.HHMMSS
18
+ // Generate timestamp version number vYYYYMMDD.HHMMSS
19
19
  function generateVersionNumber() {
20
20
  const now = new Date();
21
21
  const pad = (n) => String(n).padStart(2, '0');
22
22
  return `v${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}.${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
23
23
  }
24
24
 
25
- // 获取 zip 文件的完整路径
25
+ // Get full path of zip file
26
26
  function getZipPath(skillId, version) {
27
27
  return path.join(getDataDir(), 'skills', skillId, `${version}.zip`);
28
28
  }
29
29
 
30
- // 根据数据库中的 zip_path 解析实际文件路径,兼容历史相对路径格式
30
+ // Resolve actual file path from zip_path in database, compatible with historical relative path format
31
31
  function resolveZipPath(zipPath, skillId, version) {
32
32
  if (zipPath) {
33
33
  if (path.isAbsolute(zipPath)) {
@@ -38,7 +38,7 @@ function resolveZipPath(zipPath, skillId, version) {
38
38
  return getZipPath(skillId, version);
39
39
  }
40
40
 
41
- // 获取 zip 文件相对路径(存入数据库)
41
+ // Get relative path of zip file (stored in database)
42
42
  function getZipRelativePath(skillId, version) {
43
43
  return `skills/${skillId}/${version}.zip`;
44
44
  }