skill-base 2.0.3 → 2.0.6

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 (46) hide show
  1. package/README.md +189 -85
  2. package/bin/skill-base.js +33 -7
  3. package/package.json +3 -1
  4. package/src/cappy.js +416 -0
  5. package/src/database.js +11 -0
  6. package/src/index.js +87 -24
  7. package/src/middleware/auth.js +96 -32
  8. package/src/routes/auth.js +1 -1
  9. package/src/routes/skills.js +10 -5
  10. package/src/utils/zip.js +15 -4
  11. package/static/android-chrome-192x192.png +0 -0
  12. package/static/android-chrome-512x512.png +0 -0
  13. package/static/apple-touch-icon.png +0 -0
  14. package/static/assets/index-BgwubB87.css +1 -0
  15. package/static/assets/index-DBHCo8Mz.js +230 -0
  16. package/static/favicon-16x16.png +0 -0
  17. package/static/favicon-32x32.png +0 -0
  18. package/static/favicon.ico +0 -0
  19. package/static/favicon.svg +14 -0
  20. package/static/index.html +18 -248
  21. package/static/site.webmanifest +1 -0
  22. package/static/admin/users.html +0 -593
  23. package/static/cli-code.html +0 -203
  24. package/static/css/.gitkeep +0 -0
  25. package/static/css/style.css +0 -1567
  26. package/static/diff.html +0 -466
  27. package/static/file.html +0 -443
  28. package/static/js/.gitkeep +0 -0
  29. package/static/js/admin/users.js +0 -346
  30. package/static/js/app.js +0 -508
  31. package/static/js/auth.js +0 -151
  32. package/static/js/cli-code.js +0 -184
  33. package/static/js/collaborators.js +0 -283
  34. package/static/js/diff.js +0 -540
  35. package/static/js/file.js +0 -619
  36. package/static/js/i18n.js +0 -739
  37. package/static/js/index.js +0 -168
  38. package/static/js/publish.js +0 -718
  39. package/static/js/settings.js +0 -124
  40. package/static/js/setup.js +0 -157
  41. package/static/js/skill.js +0 -808
  42. package/static/login.html +0 -82
  43. package/static/publish.html +0 -459
  44. package/static/settings.html +0 -163
  45. package/static/setup.html +0 -101
  46. package/static/skill.html +0 -851
@@ -1,184 +0,0 @@
1
- /**
2
- * CLI 验证码页面脚本
3
- */
4
-
5
- // 当前验证码信息
6
- let currentCode = null;
7
- let expiresAt = null;
8
- let timerInterval = null;
9
-
10
- /**
11
- * 页面初始化
12
- */
13
- async function init() {
14
- // 检查是否来自 CLI login 的标记
15
- const urlParams = new URLSearchParams(window.location.search);
16
- const fromCli = urlParams.get('from') === 'cli';
17
-
18
- // 检查登录状态
19
- const user = await initPage();
20
- if (!user) {
21
- // 未登录,initPage 已处理跳转
22
- return;
23
- }
24
-
25
- // 如果是从 CLI 登录过来的,显示提示信息
26
- if (fromCli) {
27
- showToast(t('cliCode.fromCli'), 'info', 5000);
28
- }
29
-
30
- // 生成验证码
31
- await generateCode();
32
- }
33
-
34
- /**
35
- * 生成验证码
36
- */
37
- async function generateCode() {
38
- showLoading(true);
39
-
40
- try {
41
- const result = await apiPost('/auth/cli-code/generate', {});
42
-
43
- if (result.ok && result.code) {
44
- currentCode = result.code;
45
- expiresAt = new Date(result.expires_at);
46
-
47
- // 显示验证码
48
- document.getElementById('codeValue').textContent = currentCode;
49
- showLoading(false);
50
-
51
- // 启动倒计时
52
- startTimer();
53
- } else {
54
- throw new Error(result.error || t('cliCode.generateFailed'));
55
- }
56
- } catch (error) {
57
- showLoading(false);
58
- showToast(error.message || t('cliCode.generateFailed'), 'error');
59
-
60
- // 显示错误状态
61
- document.getElementById('codeValue').textContent = '----';
62
- document.getElementById('timerText').textContent = t('cliCode.genFailed');
63
- }
64
- }
65
-
66
- /**
67
- * 显示/隐藏加载状态
68
- */
69
- function showLoading(loading) {
70
- const loadingEl = document.getElementById('loadingState');
71
- const displayEl = document.getElementById('codeDisplay');
72
-
73
- if (loading) {
74
- loadingEl.classList.remove('hidden');
75
- displayEl.classList.add('hidden');
76
- } else {
77
- loadingEl.classList.add('hidden');
78
- displayEl.classList.remove('hidden');
79
- }
80
- }
81
-
82
- /**
83
- * 启动倒计时
84
- */
85
- function startTimer() {
86
- // 清除之前的定时器
87
- if (timerInterval) {
88
- clearInterval(timerInterval);
89
- }
90
-
91
- updateTimer();
92
- timerInterval = setInterval(updateTimer, 1000);
93
- }
94
-
95
- /**
96
- * 更新倒计时显示
97
- */
98
- function updateTimer() {
99
- const now = new Date();
100
- const remaining = Math.max(0, expiresAt - now);
101
- const timerEl = document.getElementById('codeTimer');
102
- const timerTextEl = document.getElementById('timerText');
103
-
104
- if (remaining <= 0) {
105
- // 已过期
106
- timerTextEl.textContent = t('cliCode.expired');
107
- timerEl.classList.remove('expiring');
108
- timerEl.classList.add('expired');
109
- clearInterval(timerInterval);
110
- return;
111
- }
112
-
113
- const minutes = Math.floor(remaining / 60000);
114
- const seconds = Math.floor((remaining % 60000) / 1000);
115
- const formattedTime = `${minutes}:${String(seconds).padStart(2, '0')}`;
116
- timerTextEl.textContent = formattedTime + t('cliCode.expires');
117
-
118
- // 最后一分钟变为警告色
119
- if (remaining <= 60000) {
120
- timerEl.classList.add('expiring');
121
- timerEl.classList.remove('expired');
122
- } else {
123
- timerEl.classList.remove('expiring');
124
- timerEl.classList.remove('expired');
125
- }
126
- }
127
-
128
- /**
129
- * 复制验证码
130
- */
131
- async function copyCode() {
132
- if (!currentCode) {
133
- showToast(t('cliCode.noCopy'), 'warning');
134
- return;
135
- }
136
-
137
- try {
138
- await navigator.clipboard.writeText(currentCode);
139
- showToast(t('cliCode.copiedToast'), 'success');
140
-
141
- // 按钮反馈
142
- const btn = document.getElementById('copyBtn');
143
- const originalText = btn.innerHTML;
144
- btn.innerHTML = `
145
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
146
- <polyline points="20 6 9 17 4 12"/>
147
- </svg>
148
- ${t('cliCode.copied')}
149
- `;
150
- btn.disabled = true;
151
-
152
- setTimeout(() => {
153
- btn.innerHTML = originalText;
154
- btn.disabled = false;
155
- }, 2000);
156
- } catch (error) {
157
- // 降级方案:选中文本
158
- const codeEl = document.getElementById('codeValue');
159
- const range = document.createRange();
160
- range.selectNodeContents(codeEl);
161
- const selection = window.getSelection();
162
- selection.removeAllRanges();
163
- selection.addRange(range);
164
- showToast(t('cliCode.pressCopy'), 'info');
165
- }
166
- }
167
-
168
- /**
169
- * 重新生成验证码
170
- */
171
- async function regenerateCode() {
172
- const btn = document.getElementById('regenerateBtn');
173
- btn.disabled = true;
174
-
175
- try {
176
- await generateCode();
177
- showToast(t('cliCode.newGenerated'), 'success');
178
- } finally {
179
- btn.disabled = false;
180
- }
181
- }
182
-
183
- // 页面加载完成后初始化
184
- document.addEventListener('DOMContentLoaded', init);
@@ -1,283 +0,0 @@
1
- /**
2
- * 协作者管理模块
3
- * 依赖:app.js (apiGet, apiPost, apiDelete, showToast, escapeHtml, getCurrentUser, currentUser)
4
- * 依赖:skill.js (currentSkill 全局变量)
5
- */
6
-
7
- // 联想搜索相关状态
8
- let selectedUserId = null; // 选中的用户 ID
9
- let searchDebounceTimer = null; // 防抖计时器
10
-
11
- // 初始化协作者面板
12
- async function initCollaboratorsPanel() {
13
- // currentSkill 由 skill.js 设置(全局变量)
14
- if (!currentSkill) return;
15
-
16
- const user = await getCurrentUser();
17
- const panel = document.getElementById('collaborators-panel');
18
- if (!panel) return;
19
-
20
- // 显示面板
21
- panel.style.display = 'block';
22
-
23
- // 加载协作者列表
24
- await loadCollaborators();
25
-
26
- // 检查当前用户是否有管理权限(owner 或 admin)
27
- if (user) {
28
- const isOwner = currentSkill.owner_id === user.id;
29
- const isAdmin = user.role === 'admin';
30
-
31
- if (isOwner || isAdmin) {
32
- // 显示添加按钮
33
- const addBtn = document.getElementById('add-collaborator-btn');
34
- if (addBtn) {
35
- addBtn.style.display = 'inline-block';
36
- addBtn.onclick = showAddCollaboratorModal;
37
- }
38
-
39
- // 显示危险操作区
40
- const dangerZone = document.getElementById('skill-danger-zone');
41
- if (dangerZone) {
42
- dangerZone.style.display = 'block';
43
- }
44
- const deleteBtn = document.getElementById('delete-skill-btn');
45
- if (deleteBtn) {
46
- deleteBtn.onclick = showDeleteSkillModal;
47
- }
48
- }
49
- }
50
- }
51
-
52
- // 加载协作者列表
53
- async function loadCollaborators() {
54
- try {
55
- const data = await apiGet(`/skills/${currentSkill.id}/collaborators`);
56
- renderCollaborators(data.collaborators);
57
- } catch (error) {
58
- console.error('Failed to load collaborators:', error);
59
- }
60
- }
61
-
62
- // 渲染协作者列表
63
- function renderCollaborators(collaborators) {
64
- const container = document.getElementById('collaborators-list');
65
- if (!container) return;
66
-
67
- // 获取当前用户用于判断是否显示移除按钮
68
- const user = currentUser; // 来自 app.js 缓存
69
- const isOwner = user && currentSkill.owner_id === user.id;
70
- const isAdmin = user && user.role === 'admin';
71
- const canManage = isOwner || isAdmin;
72
-
73
- container.innerHTML = collaborators.map(c => {
74
- const roleLabel = c.role === 'owner' ? t('collab.owner') : t('collab.collaborator');
75
- const roleClass = c.role === 'owner' ? 'owner' : '';
76
- const statusLabel = c.user.status === 'disabled' ? t('collab.disabled') : '';
77
-
78
- let removeBtn = '';
79
- if (canManage && c.role !== 'owner') {
80
- removeBtn = `<button class="btn btn-secondary btn-sm" onclick="removeCollaborator(${c.user.id})">${t('btn.remove')}</button>`;
81
- }
82
-
83
- // 显示 name(如果有)或 username
84
- const displayName = c.user.name || c.user.username;
85
- const usernameHint = c.user.name ? ` <span class="collaborator-username">@${escapeHtml(c.user.username)}</span>` : '';
86
-
87
- return `
88
- <div class="collaborator-item">
89
- <div class="collaborator-info">
90
- <span class="collaborator-role ${roleClass}">${roleLabel}</span>
91
- <span>${escapeHtml(displayName)}${usernameHint}${statusLabel}</span>
92
- </div>
93
- ${removeBtn}
94
- </div>
95
- `;
96
- }).join('');
97
- }
98
-
99
- // 显示添加协作者弹窗
100
- function showAddCollaboratorModal() {
101
- document.getElementById('add-collaborator-modal').style.display = 'flex';
102
- document.getElementById('collaborator-username').value = '';
103
- selectedUserId = null; // 重置选中状态
104
- hideSuggestions(); // 隐藏联想列表
105
- document.getElementById('collaborator-username').focus();
106
- }
107
-
108
- // 初始化联想搜索(在 DOM 加载后调用)
109
- function initUserSuggestions() {
110
- const input = document.getElementById('collaborator-username');
111
- const suggestionsEl = document.getElementById('user-suggestions');
112
- if (!input || !suggestionsEl) return;
113
-
114
- // 监听输入事件,防抖 300ms
115
- input.addEventListener('input', (e) => {
116
- const query = e.target.value.trim();
117
- selectedUserId = null; // 用户手动输入时清除选中状态
118
-
119
- if (searchDebounceTimer) {
120
- clearTimeout(searchDebounceTimer);
121
- }
122
-
123
- if (query.length < 1) {
124
- hideSuggestions();
125
- return;
126
- }
127
-
128
- searchDebounceTimer = setTimeout(() => {
129
- searchUsers(query);
130
- }, 300);
131
- });
132
-
133
- // 点击页面其他区域关闭下拉
134
- document.addEventListener('click', (e) => {
135
- if (!e.target.closest('.suggestions-container')) {
136
- hideSuggestions();
137
- }
138
- });
139
- }
140
-
141
- // 搜索用户
142
- async function searchUsers(query) {
143
- try {
144
- const data = await apiGet(`/users/search?q=${encodeURIComponent(query)}`);
145
- renderSuggestions(data.users || []);
146
- } catch (error) {
147
- console.error('Failed to search users:', error);
148
- hideSuggestions();
149
- }
150
- }
151
-
152
- // 渲染联想列表
153
- function renderSuggestions(users) {
154
- const suggestionsEl = document.getElementById('user-suggestions');
155
- if (!suggestionsEl) return;
156
-
157
- if (users.length === 0) {
158
- suggestionsEl.innerHTML = `<div class="suggestion-empty">${t('collab.noUsers')}</div>`;
159
- suggestionsEl.classList.add('is-open');
160
- return;
161
- }
162
-
163
- suggestionsEl.innerHTML = users.map(user => {
164
- const displayName = user.name
165
- ? `<span class="suggestion-item-name">${escapeHtml(user.name)}</span><span class="suggestion-item-username">@${escapeHtml(user.username)}</span>`
166
- : `<span class="suggestion-item-name">@${escapeHtml(user.username)}</span>`;
167
- return `
168
- <div class="suggestion-item" data-user-id="${user.id}" data-username="${escapeHtml(user.username)}" data-name="${escapeHtml(user.name || '')}">
169
- ${displayName}
170
- </div>
171
- `;
172
- }).join('');
173
-
174
- // 绑定点击事件
175
- suggestionsEl.querySelectorAll('.suggestion-item').forEach(item => {
176
- item.addEventListener('click', () => selectSuggestion(item));
177
- });
178
-
179
- suggestionsEl.classList.add('is-open');
180
- }
181
-
182
- // 选择联想项
183
- function selectSuggestion(item) {
184
- const userId = item.dataset.userId;
185
- const username = item.dataset.username;
186
- const name = item.dataset.name;
187
-
188
- selectedUserId = userId;
189
-
190
- // 设置输入框显示文本
191
- const input = document.getElementById('collaborator-username');
192
- input.value = name || username;
193
-
194
- hideSuggestions();
195
- }
196
-
197
- // 隐藏联想列表
198
- function hideSuggestions() {
199
- const suggestionsEl = document.getElementById('user-suggestions');
200
- if (suggestionsEl) {
201
- suggestionsEl.classList.remove('is-open');
202
- suggestionsEl.innerHTML = '';
203
- }
204
- }
205
-
206
- // 关闭添加协作者弹窗
207
- function closeCollaboratorModal() {
208
- document.getElementById('add-collaborator-modal').style.display = 'none';
209
- }
210
-
211
- // 提交添加协作者
212
- async function submitAddCollaborator() {
213
- const inputValue = document.getElementById('collaborator-username').value.trim();
214
- if (!inputValue) {
215
- showToast(t('collab.enterUsername'), 'warning');
216
- return;
217
- }
218
-
219
- try {
220
- // 如果有选中的 user_id,使用 user_id;否则使用 username(向后兼容)
221
- const payload = selectedUserId
222
- ? { user_id: selectedUserId }
223
- : { username: inputValue };
224
-
225
- await apiPost(`/skills/${currentSkill.id}/collaborators`, payload);
226
- showToast(t('collab.addSuccess'), 'success');
227
- closeCollaboratorModal();
228
- await loadCollaborators();
229
- } catch (error) {
230
- showToast(error.message || t('collab.addFailed'), 'error');
231
- }
232
- }
233
-
234
- // 移除协作者
235
- async function removeCollaborator(userId) {
236
- if (!confirm(t('collab.removeConfirm'))) return;
237
-
238
- try {
239
- await apiDelete(`/skills/${currentSkill.id}/collaborators/${userId}`);
240
- showToast(t('collab.removeSuccess'), 'success');
241
- await loadCollaborators();
242
- } catch (error) {
243
- showToast(error.message || t('collab.removeFailed'), 'error');
244
- }
245
- }
246
-
247
- // 显示删除 Skill 弹窗
248
- function showDeleteSkillModal() {
249
- document.getElementById('delete-skill-modal').style.display = 'flex';
250
- document.getElementById('delete-confirm-input').value = '';
251
- document.getElementById('delete-confirm-input').placeholder = currentSkill.id;
252
- }
253
-
254
- // 关闭删除 Skill 弹窗
255
- function closeDeleteModal() {
256
- document.getElementById('delete-skill-modal').style.display = 'none';
257
- }
258
-
259
- // 在页面加载时初始化联想搜索
260
- if (document.readyState === 'loading') {
261
- document.addEventListener('DOMContentLoaded', initUserSuggestions);
262
- } else {
263
- initUserSuggestions();
264
- }
265
-
266
- // 提交删除 Skill
267
- async function submitDeleteSkill() {
268
- const confirmValue = document.getElementById('delete-confirm-input').value.trim();
269
- if (confirmValue !== currentSkill.id) {
270
- showToast(t('collab.deleteWrongId'), 'warning');
271
- return;
272
- }
273
-
274
- try {
275
- await apiDelete(`/skills/${currentSkill.id}?confirm=${encodeURIComponent(currentSkill.id)}`);
276
- showToast(t('collab.deleteSuccess'), 'success');
277
- setTimeout(() => {
278
- window.location.href = '/';
279
- }, 1000);
280
- } catch (error) {
281
- showToast(error.message || t('collab.deleteFailed'), 'error');
282
- }
283
- }