glad-web 1.0.46 → 2.0.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 (74) hide show
  1. package/README.md +4 -192
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/bin/glad.cjs +56 -0
  4. package/package.json +19 -61
  5. package/README.zh-CN.md +0 -198
  6. package/assets/logo.svg +0 -43
  7. package/bin/cli.js +0 -65
  8. package/lib/ai-tools/demo/enhanced-demo.js +0 -625
  9. package/lib/ai-tools/demo/index.js +0 -24
  10. package/lib/ai-tools/demo/responses.js +0 -88
  11. package/lib/ai-tools/detector.js +0 -76
  12. package/lib/ai-tools/registry.js +0 -300
  13. package/lib/claude/cli-usage.js +0 -95
  14. package/lib/claude/config.js +0 -82
  15. package/lib/claude/structured-session.js +0 -884
  16. package/lib/claude/transcript-repository.js +0 -216
  17. package/lib/codex/image-store.js +0 -174
  18. package/lib/codex/structured-session.js +0 -1590
  19. package/lib/commands/config.js +0 -78
  20. package/lib/commands/tools.js +0 -128
  21. package/lib/commands/web.js +0 -605
  22. package/lib/config/constants.js +0 -17
  23. package/lib/config/manager.js +0 -108
  24. package/lib/git/service.js +0 -83
  25. package/lib/notifications/message-formatter.js +0 -94
  26. package/lib/notifications/notification-service.js +0 -143
  27. package/lib/notifications/serverchan-client.js +0 -58
  28. package/lib/notifications/serverchan-settings-store.js +0 -115
  29. package/lib/schedule/job-runner.js +0 -162
  30. package/lib/schedule/job-store.js +0 -167
  31. package/lib/schedule/key-sequences.js +0 -49
  32. package/lib/schedule/scheduler-service.js +0 -39
  33. package/lib/server/routes/notifications.js +0 -52
  34. package/lib/server/routes/providers.js +0 -114
  35. package/lib/server/routes/schedules.js +0 -54
  36. package/lib/server/routes/skillhub.js +0 -104
  37. package/lib/server/routes/usage.js +0 -23
  38. package/lib/server/routes/workspace.js +0 -77
  39. package/lib/session/buffer.js +0 -102
  40. package/lib/session/file-attachment-store.js +0 -168
  41. package/lib/session/pty-manager.js +0 -255
  42. package/lib/session/rendered-history.js +0 -225
  43. package/lib/session/session-manager.js +0 -1032
  44. package/lib/session/text-history.js +0 -274
  45. package/lib/skillhub/client.js +0 -121
  46. package/lib/skillhub/settings-store.js +0 -168
  47. package/lib/skillhub/skill-installer.js +0 -320
  48. package/lib/usage/ccusage-runner.js +0 -128
  49. package/lib/usage/source-catalog.js +0 -26
  50. package/lib/usage/usage-service.js +0 -226
  51. package/lib/utils/logger.js +0 -74
  52. package/lib/utils/pid.js +0 -67
  53. package/lib/utils/validation.js +0 -53
  54. package/lib/web/bootstrap.js +0 -34
  55. package/lib/web/claude.js +0 -1150
  56. package/lib/web/codex.js +0 -1045
  57. package/lib/web/composer.js +0 -493
  58. package/lib/web/core.js +0 -385
  59. package/lib/web/git.js +0 -535
  60. package/lib/web/gitgraph.js +0 -293
  61. package/lib/web/index.html +0 -547
  62. package/lib/web/layout.js +0 -69
  63. package/lib/web/notifications.js +0 -164
  64. package/lib/web/schedules.js +0 -245
  65. package/lib/web/session.js +0 -361
  66. package/lib/web/shell.js +0 -74
  67. package/lib/web/skillhub.js +0 -197
  68. package/lib/web/styles.css +0 -932
  69. package/lib/web/terminal-scroll.js +0 -81
  70. package/lib/web/theme.js +0 -60
  71. package/lib/web/timed-inputs.js +0 -216
  72. package/lib/web/usage.js +0 -323
  73. package/lib/workspace/service.js +0 -77
  74. package/scripts/check-syntax.js +0 -26
@@ -1,81 +0,0 @@
1
- const scrollChargeDurationMs = 6000;
2
- let isScrolling = false, scrollDir = 0, scrollSpeed = 0, rafId = null;
3
- let chargeBtn = null, chargeStartTime = 0, chargeRafId = null, chargePointerId = null, chargeCompleted = false;
4
-
5
- function getTerminalViewport() {
6
- return document.querySelector('.xterm-viewport');
7
- }
8
-
9
- function smoothScrollLoop() {
10
- if (!isScrolling) return;
11
- const viewport = getTerminalViewport();
12
- if (viewport) { viewport.scrollTop += (scrollDir * scrollSpeed); scrollSpeed = Math.min(30, scrollSpeed + 0.2); }
13
- rafId = requestAnimationFrame(smoothScrollLoop);
14
- }
15
- function startScroll(dir) { if (isScrolling) return; isScrolling = true; scrollDir = dir; scrollSpeed = 4; smoothScrollLoop(); }
16
- function stopScroll() { isScrolling = false; if (rafId) cancelAnimationFrame(rafId); }
17
-
18
- function jumpTerminalScroll(dir) {
19
- const viewport = getTerminalViewport();
20
- if (!viewport) return;
21
- viewport.scrollTop = dir < 0 ? 0 : viewport.scrollHeight;
22
- if (term) {
23
- if (dir < 0) term.scrollToTop();
24
- else term.scrollToBottom();
25
- }
26
- }
27
-
28
- function resetChargeButton(btn) {
29
- if (!btn) return;
30
- btn.classList.remove('charging');
31
- btn.style.setProperty('--charge', '0deg');
32
- }
33
-
34
- function stopScrollCharge() {
35
- stopScroll();
36
- if (chargeRafId) cancelAnimationFrame(chargeRafId);
37
- if (chargeBtn && chargePointerId !== null) {
38
- try { chargeBtn.releasePointerCapture(chargePointerId); } catch (_) {}
39
- }
40
- resetChargeButton(chargeBtn);
41
- chargeBtn = null;
42
- chargeStartTime = 0;
43
- chargeRafId = null;
44
- chargePointerId = null;
45
- chargeCompleted = false;
46
- }
47
-
48
- function updateScrollCharge() {
49
- if (!chargeBtn) return;
50
- const elapsed = performance.now() - chargeStartTime;
51
- const progress = Math.min(1, elapsed / scrollChargeDurationMs);
52
- chargeBtn.style.setProperty('--charge', (progress * 360) + 'deg');
53
- if (progress >= 1) {
54
- chargeCompleted = true;
55
- jumpTerminalScroll(scrollDir);
56
- stopScrollCharge();
57
- return;
58
- }
59
- chargeRafId = requestAnimationFrame(updateScrollCharge);
60
- }
61
-
62
- function startScrollCharge(e, dir) {
63
- e.preventDefault();
64
- stopScrollCharge();
65
- chargeBtn = e.currentTarget;
66
- chargePointerId = e.pointerId;
67
- chargeStartTime = performance.now();
68
- chargeCompleted = false;
69
- chargeBtn.classList.add('charging');
70
- chargeBtn.setPointerCapture(chargePointerId);
71
- startScroll(dir);
72
- updateScrollCharge();
73
- }
74
-
75
- document.getElementById('scroll-up').addEventListener('pointerdown', (e) => startScrollCharge(e, -1));
76
- document.getElementById('scroll-down').addEventListener('pointerdown', (e) => startScrollCharge(e, 1));
77
- window.addEventListener('pointerup', stopScrollCharge);
78
- window.addEventListener('pointercancel', stopScrollCharge);
79
- window.addEventListener('resize', syncLayout);
80
-
81
- // Git Logic
package/lib/web/theme.js DELETED
@@ -1,60 +0,0 @@
1
- const GLAD_THEME_KEY = 'glad-theme';
2
-
3
- function getGladTheme() {
4
- const stored = localStorage.getItem(GLAD_THEME_KEY);
5
- if (stored === 'light' || stored === 'dark') return stored;
6
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
7
- }
8
-
9
- function getGladTerminalTheme(theme = getGladTheme()) {
10
- return theme === 'light'
11
- ? {
12
- background: '#ffffff', foreground: '#172033', cursor: '#007aff',
13
- selectionBackground: 'rgba(0, 122, 255, 0.22)', black: '#172033', white: '#f5f6f8',
14
- brightBlack: '#667085', brightWhite: '#ffffff'
15
- }
16
- : {
17
- background: '#000000', foreground: '#ffffff', cursor: '#34c759',
18
- selectionBackground: 'rgba(0, 122, 255, 0.32)'
19
- };
20
- }
21
-
22
- function syncGladThemeControls(theme = getGladTheme()) {
23
- ['light', 'dark'].forEach(value => {
24
- const button = document.getElementById(`theme-${value}-btn`);
25
- if (button) button.setAttribute('aria-pressed', String(value === theme));
26
- });
27
- }
28
-
29
- function applyGladTheme(theme, options = {}) {
30
- const resolved = theme === 'light' ? 'light' : 'dark';
31
- document.documentElement.dataset.theme = resolved;
32
- document.documentElement.style.removeProperty('background-color');
33
- const meta = document.querySelector('meta[name="theme-color"]');
34
- if (meta) meta.content = resolved === 'light' ? '#f5f6f8' : '#000000';
35
- syncGladThemeControls(resolved);
36
- try {
37
- if (typeof term !== 'undefined' && term) {
38
- term.options.theme = getGladTerminalTheme(resolved);
39
- term.refresh(0, Math.max(0, term.rows - 1));
40
- }
41
- } catch (_) {}
42
- if (!options.silent) window.dispatchEvent(new CustomEvent('glad-theme-change', { detail: resolved }));
43
- }
44
-
45
- function setGladTheme(theme) {
46
- if (theme !== 'light' && theme !== 'dark') return;
47
- localStorage.setItem(GLAD_THEME_KEY, theme);
48
- applyGladTheme(theme);
49
- }
50
-
51
- applyGladTheme(getGladTheme(), { silent: true });
52
-
53
- const gladSystemTheme = window.matchMedia('(prefers-color-scheme: dark)');
54
- gladSystemTheme.addEventListener('change', () => {
55
- if (!localStorage.getItem(GLAD_THEME_KEY)) applyGladTheme(getGladTheme());
56
- });
57
- window.addEventListener('storage', event => {
58
- if (event.key === GLAD_THEME_KEY) applyGladTheme(getGladTheme());
59
- });
60
- document.addEventListener('DOMContentLoaded', () => syncGladThemeControls());
@@ -1,216 +0,0 @@
1
- let timedInputs = [];
2
-
3
- function initTimedDelaySelectors() {
4
- const hours = document.getElementById('timed-hours');
5
- const minutes = document.getElementById('timed-minutes');
6
- if (hours.options.length) return;
7
- for (let i = 0; i <= 23; i++) {
8
- hours.add(new Option(`${i} hr`, String(i)));
9
- }
10
- for (let i = 0; i <= 59; i++) {
11
- minutes.add(new Option(`${i} min`, String(i)));
12
- }
13
- hours.value = '0';
14
- minutes.value = '5';
15
- }
16
-
17
- function getTimedDelayMs() {
18
- const hours = Number(document.getElementById('timed-hours').value);
19
- const minutes = Number(document.getElementById('timed-minutes').value);
20
- const delayMs = ((hours * 60) + minutes) * 60 * 1000;
21
- return Number.isFinite(delayMs) && delayMs > 0 ? delayMs : 0;
22
- }
23
-
24
- function parseSendAt() {
25
- const delayMs = getTimedDelayMs();
26
- return delayMs > 0 ? Date.now() + delayMs : null;
27
- }
28
-
29
- function setTimedDelayFromMs(ms) {
30
- const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
31
- const hours = Math.min(23, Math.floor(totalMinutes / 60));
32
- const minutes = Math.min(59, totalMinutes - hours * 60);
33
- document.getElementById('timed-hours').value = String(hours);
34
- document.getElementById('timed-minutes').value = String(minutes);
35
- }
36
-
37
- function updateTimedSendPreview() {
38
- const preview = document.getElementById('timed-preview');
39
- const sendAt = parseSendAt();
40
- preview.textContent = sendAt
41
- ? `Will run at ${new Date(sendAt).toLocaleString()}`
42
- : 'Choose at least 1 minute.';
43
- }
44
-
45
- function formatCountdown(sendAt) {
46
- const remaining = Math.max(0, sendAt - Date.now());
47
- const totalSeconds = Math.ceil(remaining / 1000);
48
- const hours = Math.floor(totalSeconds / 3600);
49
- const minutes = Math.floor((totalSeconds % 3600) / 60);
50
- const seconds = totalSeconds % 60;
51
- if (hours > 0) return `${hours}h\n${String(minutes).padStart(2, '0')}m`;
52
- return `${minutes}:${String(seconds).padStart(2, '0')}`;
53
- }
54
-
55
- function renderTimedTags() {
56
- const rail = document.getElementById('timed-tag-rail');
57
- const now = Date.now();
58
- const activeItems = timedInputs.filter(item => item.sendAt > now);
59
- rail.innerHTML = activeItems.map(item => {
60
- const encodedId = encodePathValue(item.id);
61
- return `<button class="timed-tag${item.id === editingTimedInputId ? ' active' : ''}" title="${escapeHtml(item.text)}" onclick="editTimedInput(decodePathValue('${encodedId}'))">${escapeHtml(formatCountdown(item.sendAt))}</button>`;
62
- }).join('');
63
- }
64
-
65
- function closeTimedSendPanel() {
66
- document.getElementById('timed-send-panel').classList.remove('active');
67
- editingTimedInputId = null;
68
- renderTimedTags();
69
- syncComposerButtonState();
70
- updateTerminalControlsHeight();
71
- }
72
-
73
- function stopTimedInputTimers() {
74
- clearTimeout(timedSendRefreshTimer);
75
- timedSendRefreshTimer = null;
76
- if (timedTagTimer) clearInterval(timedTagTimer);
77
- timedTagTimer = null;
78
- timedInputs = [];
79
- renderTimedTags();
80
- }
81
-
82
- function resetTimedEditor(options = {}) {
83
- editingTimedInputId = null;
84
- document.getElementById('timed-save-btn').textContent = 'Add Timer';
85
- document.getElementById('timed-cancel-edit-btn').style.display = 'none';
86
- document.getElementById('timed-delete-btn').style.display = 'none';
87
- if (!options.keepInput) {
88
- inputEl.value = '';
89
- inputEl.style.height = '38px';
90
- }
91
- setTimedDelayFromMs(5 * 60 * 1000);
92
- updateTimedSendPreview();
93
- renderTimedTags();
94
- }
95
-
96
- async function loadTimedInputs() {
97
- clearTimeout(timedSendRefreshTimer);
98
- timedSendRefreshTimer = null;
99
- if (!activeSessionId) return;
100
-
101
- try {
102
- const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/timed-inputs`);
103
- if (!res.ok) throw new Error('HTTP ' + res.status);
104
- const data = await res.json();
105
- timedInputs = data.items || [];
106
- renderTimedTags();
107
- if (!timedTagTimer) timedTagTimer = setInterval(renderTimedTags, 1000);
108
- timedSendRefreshTimer = setTimeout(loadTimedInputs, 15000);
109
- } catch (e) {
110
- timedInputs = [];
111
- renderTimedTags();
112
- }
113
- }
114
-
115
- function editTimedInput(id) {
116
- const item = timedInputs.find(value => value.id === id);
117
- if (!item) return;
118
- initTimedDelaySelectors();
119
- editingTimedInputId = id;
120
- inputEl.value = item.text || '';
121
- inputEl.style.height = 'auto';
122
- inputEl.style.height = Math.min(inputEl.scrollHeight, 150) + 'px';
123
- setTimedDelayFromMs(item.sendAt - Date.now());
124
- document.getElementById('timed-save-btn').textContent = 'Update Timer';
125
- document.getElementById('timed-cancel-edit-btn').style.display = '';
126
- document.getElementById('timed-delete-btn').style.display = '';
127
- document.getElementById('timed-send-panel').classList.add('active');
128
- updateTimedSendPreview();
129
- renderTimedTags();
130
- syncComposerButtonState();
131
- updateTerminalControlsHeight();
132
- }
133
-
134
- async function saveTimedSend() {
135
- if (!activeSessionId) return alert('No active session');
136
- const text = inputEl.value;
137
- if (!text.trim()) return alert('Type a message first');
138
- const sendAt = parseSendAt();
139
- if (!sendAt) return alert('Choose at least 1 minute');
140
-
141
- try {
142
- const url = editingTimedInputId
143
- ? `/api/sessions/${activeSessionId}/timed-inputs/${editingTimedInputId}`
144
- : `/api/sessions/${activeSessionId}/timed-inputs`;
145
- const res = await fetchWithTimeout(url, {
146
- method: editingTimedInputId ? 'PATCH' : 'POST',
147
- headers: { 'Content-Type': 'application/json' },
148
- body: JSON.stringify({ text, sendAt })
149
- });
150
- const data = await res.json();
151
- if (!res.ok || !data.success) throw new Error(data.error || 'Failed to save timer');
152
- resetTimedEditor();
153
- await loadTimedInputs();
154
- } catch (e) {
155
- alert('Save failed: ' + e.message);
156
- }
157
- }
158
-
159
- async function cancelTimedInput(id) {
160
- if (!activeSessionId) return;
161
- try {
162
- await fetchWithTimeout(`/api/sessions/${activeSessionId}/timed-inputs/${id}`, { method: 'DELETE' });
163
- if (editingTimedInputId === id) resetTimedEditor();
164
- loadTimedInputs();
165
- } catch (e) {
166
- alert('Delete failed');
167
- }
168
- }
169
-
170
- function deleteEditingTimedInput() {
171
- if (editingTimedInputId) cancelTimedInput(editingTimedInputId);
172
- }
173
-
174
- document.getElementById('send-btn').addEventListener('click', performSend);
175
- document.getElementById('attachment-btn').addEventListener('click', () => {
176
- attachmentFileInput.click();
177
- });
178
- document.getElementById('schedule-send-btn').addEventListener('click', () => {
179
- if (document.getElementById('timed-send-panel').classList.contains('active')) closeTimedSendPanel();
180
- else openTimedSendPanel();
181
- });
182
- attachmentFileInput.addEventListener('change', () => {
183
- const files = attachmentFileInput.files;
184
- if (files?.length) void uploadSelectedAttachments(files);
185
- attachmentFileInput.value = '';
186
- });
187
- inputEl.addEventListener('keydown', (e) => {
188
- if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); performSend(); }
189
- else if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') {
190
- markInputEditStart();
191
- if (keepTerminalBottomForNextInput) requestAnimationFrame(restoreTerminalBottomSoon);
192
- }
193
- });
194
-
195
- document.getElementById('shortcut-rail').addEventListener('click', (e) => {
196
- const commandBtn = e.target.closest('.command-key');
197
- if (commandBtn) {
198
- const command = commandBtn.dataset.command;
199
- if (command) {
200
- sendWS(command);
201
- setTimeout(() => { sendWS('\r'); }, 1000);
202
- }
203
- return;
204
- }
205
-
206
- const keyBtn = e.target.closest('.key-btn'); if (!keyBtn) return;
207
- const key = keyBtn.dataset.key;
208
- if (key === 'ctrl') { modifiers[key] = !modifiers[key]; keyBtn.classList.toggle('active', modifiers[key]); return; }
209
- if (key === 'codex-perm') {
210
- sendWS('/perm');
211
- setTimeout(() => { sendWS('\r'); }, 1000);
212
- return;
213
- }
214
- const sequences = { up: '\x1b[A', down: '\x1b[B', left: '\x1b[D', right: '\x1b[C', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
215
- if (sequences[key]) sendWS(sequences[key]);
216
- });