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
package/lib/web/core.js DELETED
@@ -1,385 +0,0 @@
1
- let currentSocket = null, term = null, fitAddon = null, activeSessionId = null, activeToolKey = null;
2
- let appConfig = null;
3
- let sessionPollTimer = null;
4
- let lobbyTab = 'sessions';
5
- let scheduleTools = [];
6
- let editingScheduleId = null;
7
- let editingSteps = [];
8
- let selectedWeekdays = [1, 2, 3, 4, 5];
9
- let timedSendRefreshTimer = null;
10
- let timedTagTimer = null;
11
- let editingTimedInputId = null;
12
- function setActionButtonLabel(button, label) {
13
- if (!button) return;
14
- const labelNode = button.querySelector('.action-label');
15
- if (labelNode) {
16
- labelNode.textContent = label;
17
- return;
18
- }
19
- button.textContent = label;
20
- }
21
- function renderSessionAttention(parts = []) {
22
- const strip = document.getElementById('session-status-strip');
23
- const rail = document.getElementById('session-attention-rail');
24
- if (!strip || !rail) return;
25
- rail.innerHTML = parts.join('');
26
- strip.style.display = parts.length ? 'block' : 'none';
27
- }
28
- let claudeMessages = [];
29
- let claudePendingPermissions = [];
30
- let claudeStatus = 'idle';
31
- let claudeRuntimeConfig = null;
32
- let claudePickerOpen = null;
33
- let claudeUsagePending = false;
34
- let claudeContextPending = false;
35
- let claudeState = {
36
- permissionMode: 'default',
37
- model: 'default',
38
- effort: 'medium',
39
- claudeSessionId: null,
40
- resumeSessionId: null,
41
- canAbort: false,
42
- pendingPermissionCount: 0
43
- };
44
- let claudeResumePanelOpen = false;
45
- let claudeForkPanelOpen = false;
46
- let claudeResumeItemsLoaded = false;
47
- let claudeRenderFrame = null;
48
- let claudeApprovalJumpIndex = 0;
49
- function createDefaultCodexState() {
50
- return {
51
- permissionMode: 'default', sandboxMode: 'default',
52
- effectivePermissionMode: null, effectiveSandboxMode: null,
53
- model: null, effort: null, status: 'idle', threadId: null,
54
- models: [], aborting: false, resuming: false,
55
- canAbort: false, canCompact: false, compacting: false
56
- };
57
- }
58
- let codexMessages = [];
59
- let codexPendingPermissions = [];
60
- let codexState = createDefaultCodexState();
61
- let codexModelPanelOpen = false;
62
- let codexModelCandidate = null;
63
- let codexResumePanelOpen = false;
64
- let codexResumeInFlight = false;
65
- let codexForkPanelOpen = false;
66
- let codexPromptPanelOpen = false;
67
- let codexPromptItems = [];
68
- let codexPromptNextOffset = 0;
69
- let codexPromptHasMore = false;
70
- let codexPromptTotal = 0;
71
- let codexPromptLoading = false;
72
- let codexExpandedPrompts = new Set();
73
- let codexSkillPanelOpen = false;
74
- let codexSkillItems = [];
75
- let codexSkillLoading = false;
76
- let codexSkillQuery = '';
77
- let codexSkillError = '';
78
- let selectedCodexSkill = null;
79
- let codexRenderFrame = null;
80
- let codexApprovalJumpIndex = 0;
81
- let codexDetailRequestSeq = 0;
82
- let codexDetailRequests = new Map();
83
- let codexDetailRevisions = new Map();
84
- let codexDetailRefreshTimer = null;
85
- let codexDetailRefreshIds = new Set();
86
- const modifiers = { ctrl: false };
87
-
88
- function log(msg) {
89
- const el = document.getElementById('debug-log');
90
- const entry = document.createElement('div');
91
- entry.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;
92
- el.appendChild(entry);
93
- console.log(msg);
94
- }
95
-
96
- async function fetchWithTimeout(url, options = {}, timeout = 5000) {
97
- const controller = new AbortController();
98
- const id = setTimeout(() => controller.abort(), timeout);
99
- try {
100
- const response = await fetch(url, { ...options, signal: controller.signal });
101
- clearTimeout(id);
102
- return response;
103
- } catch (e) {
104
- clearTimeout(id);
105
- if (e && e.name === 'AbortError') {
106
- throw new Error(`Request timed out after ${Math.round(timeout / 1000)}s`);
107
- }
108
- throw e;
109
- }
110
- }
111
-
112
- function escapeHtml(value = '') {
113
- return String(value)
114
- .replace(/&/g, '&')
115
- .replace(/</g, '&lt;')
116
- .replace(/>/g, '&gt;')
117
- .replace(/"/g, '&quot;')
118
- .replace(/'/g, '&#39;');
119
- }
120
-
121
- function encodePathValue(path = '') {
122
- return encodeURIComponent(path).replace(/'/g, '%27');
123
- }
124
-
125
- function decodePathValue(path = '') {
126
- return decodeURIComponent(path);
127
- }
128
-
129
- async function copyTextToClipboard(text) {
130
- if (navigator.clipboard && window.isSecureContext) {
131
- await navigator.clipboard.writeText(text);
132
- return;
133
- }
134
- const el = document.createElement('textarea');
135
- el.value = text;
136
- el.setAttribute('readonly', '');
137
- el.style.position = 'fixed';
138
- el.style.opacity = '0';
139
- document.body.appendChild(el);
140
- el.select();
141
- document.execCommand('copy');
142
- document.body.removeChild(el);
143
- }
144
-
145
- async function copySessionDirectory(directory, event) {
146
- event.stopPropagation();
147
- const btn = event.currentTarget;
148
- try {
149
- await copyTextToClipboard(directory);
150
- const oldTitle = btn.title;
151
- btn.title = 'Copied';
152
- btn.style.color = '#fff';
153
- setTimeout(() => {
154
- btn.title = oldTitle || 'Copy directory';
155
- btn.style.color = '';
156
- }, 1200);
157
- } catch (e) {
158
- alert('Copy failed');
159
- }
160
- }
161
-
162
- function formatWeekdays(days = []) {
163
- const labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
164
- return days.map(day => labels[day]).filter(Boolean).join(', ') || 'No days';
165
- }
166
-
167
- function formatDateTime(value) {
168
- return value ? new Date(value).toLocaleString() : 'Not scheduled';
169
- }
170
-
171
- function switchLobbyTab(tab) {
172
- lobbyTab = tab;
173
- document.getElementById('lobby-tab-sessions').classList.toggle('active', tab === 'sessions');
174
- document.getElementById('lobby-tab-schedules').classList.toggle('active', tab === 'schedules');
175
- document.getElementById('sessions-list').style.display = tab === 'sessions' ? '' : 'none';
176
- document.getElementById('schedules-list').style.display = tab === 'schedules' ? '' : 'none';
177
- if (tab === 'sessions') refreshSessionsNow();
178
- if (tab === 'schedules') loadSchedules();
179
- }
180
-
181
- async function loadSchedules() {
182
- const list = document.getElementById('schedules-list');
183
- list.innerHTML = '<p style="color:#888; text-align:center; margin-top:50px;">Loading schedules...</p>';
184
- try {
185
- await ensureScheduleTools().catch(() => {});
186
- const res = await fetchWithTimeout('/api/schedules');
187
- if (!res.ok) throw new Error('HTTP ' + res.status);
188
- const schedules = await res.json();
189
- if (!schedules.length) {
190
- list.innerHTML = '<p style="color:#888; text-align:center; margin-top:50px;">No scheduled tasks</p>';
191
- return;
192
- }
193
-
194
- let html = '';
195
- schedules.forEach(job => {
196
- const encodedId = encodePathValue(job.id);
197
- const tool = scheduleTools.find(t => t.key === job.target.toolKey);
198
- const statusColor = job.lastRunStatus === 'failed' ? '#ff6b61' : job.running ? '#5ac8fa' : 'var(--text-dim)';
199
- html += `<div class="schedule-card">
200
- <div class="schedule-row">
201
- <div style="flex:1; min-width:0;">
202
- <div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;">
203
- <strong>${escapeHtml(job.name)}</strong>
204
- <span style="font-size:11px; color:${job.enabled ? '#34c759' : '#8e8e93'}; border:1px solid currentColor; border-radius:10px; padding:1px 7px;">${job.enabled ? 'ON' : 'OFF'}</span>
205
- </div>
206
- <div class="schedule-meta">
207
- ${escapeHtml(job.schedule.time)} | ${escapeHtml(formatWeekdays(job.schedule.weekdays))}<br>
208
- ${escapeHtml(tool ? tool.displayName : job.target.toolKey)} | ${escapeHtml(job.target.workingDirectory || 'Default directory')}<br>
209
- Next: ${escapeHtml(formatDateTime(job.nextRunAt))}<br>
210
- <span style="color:${statusColor};">Last: ${escapeHtml(job.lastRunStatus || 'idle')}${job.lastRunMessage ? ' - ' + escapeHtml(job.lastRunMessage) : ''}</span>
211
- </div>
212
- </div>
213
- <div class="schedule-actions">
214
- <button class="small-btn primary" onclick="simulateSchedule(decodePathValue('${encodedId}'))">Test</button>
215
- <button class="small-btn" onclick="editSchedule(decodePathValue('${encodedId}'))">Edit</button>
216
- <button class="small-btn" onclick="duplicateSchedule(decodePathValue('${encodedId}'))">Copy</button>
217
- <button class="small-btn" onclick="toggleSchedule(decodePathValue('${encodedId}'), ${job.enabled ? 'false' : 'true'})">${job.enabled ? 'Disable' : 'Enable'}</button>
218
- <button class="small-btn danger" onclick="deleteSchedule(decodePathValue('${encodedId}'))">Delete</button>
219
- </div>
220
- </div>
221
- </div>`;
222
- });
223
- list.innerHTML = html;
224
- } catch (e) {
225
- list.innerHTML = `<div style="color:#ff3b30; text-align:center; margin-top:50px;"><p>Failed to load: ${escapeHtml(e.message)}</p><button class="btn-retry" onclick="loadSchedules()">Retry</button></div>`;
226
- }
227
- }
228
-
229
- function renderSessionCard(session) {
230
- const workingDirectory = session.workingDirectory || 'Unknown directory';
231
- const encodedId = encodePathValue(session.id);
232
- const encodedName = encodePathValue(session.name);
233
- const encodedDir = encodePathValue(workingDirectory);
234
- const encodedToolKey = encodePathValue(session.toolKey || '');
235
- const timedInputCount = Number(session.timedInputCount) || 0;
236
- const timerBadge = timedInputCount > 0
237
- ? `<span class="timer-count-badge" title="${timedInputCount} scheduled timer${timedInputCount > 1 ? 's' : ''}">
238
- <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>
239
- ${timedInputCount}
240
- </span>`
241
- : '';
242
- return `<div class="session-card${session.id === activeSessionId ? ' selected' : ''}" data-session-id="${escapeHtml(session.id)}">
243
- <span class="active-session-dot" title="Current session" aria-label="Current session"></span>
244
- <div class="session-info">
245
- <h3><span class="session-name" title="${escapeHtml(session.name)}">${escapeHtml(session.name)}</span>${session.hasUnreadCompletion ? '<span class="completion-dot" title="Completed"></span>' : ''}${timerBadge} <button type="button" class="icon-btn session-edit-btn" title="Rename session" aria-label="Rename session" onclick="renameSession(decodePathValue('${encodedId}'), decodePathValue('${encodedName}'), event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button></h3>
246
- <p>${escapeHtml(session.tool)}</p>
247
- <p>${new Date(session.startTime).toLocaleTimeString()}</p>
248
- </div>
249
- <div class="session-actions">
250
- ${renderServerChanSessionAction(session)}
251
- <button class="btn-join" onclick="joinSession(decodePathValue('${encodedId}'), decodePathValue('${encodedName}'), decodePathValue('${encodedToolKey}'))">Connect</button>
252
- <button type="button" class="icon-btn btn-delete session-delete-btn" title="Delete session" aria-label="Delete session" onclick="deleteSession(decodePathValue('${encodedId}'), event)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button>
253
- </div>
254
- <div class="session-dir-row">
255
- <button class="copy-dir-btn" title="Copy directory" onclick="copySessionDirectory(decodePathValue('${encodedDir}'), event)">
256
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
257
- </button>
258
- <p title="${escapeHtml(workingDirectory)}">${escapeHtml(workingDirectory)}</p>
259
- </div>
260
- </div>`;
261
- }
262
-
263
- async function loadSessions() {
264
- log('Loading sessions...');
265
- const list = document.getElementById('sessions-list');
266
- try {
267
- const res = await fetchWithTimeout('/api/sessions');
268
- if (!res.ok) throw new Error('HTTP ' + res.status);
269
- const sessions = await res.json();
270
- if (!sessions || sessions.length === 0) {
271
- list.innerHTML = '<p style="color:#888; text-align:center; margin-top:50px;">No active sessions</p>';
272
- return;
273
- }
274
-
275
- list.innerHTML = sessions.map(renderSessionCard).join('');
276
- } catch (e) {
277
- list.innerHTML = `<div style="color:#ff3b30; text-align:center; margin-top:50px;"><p>Failed to load: ${e.message}</p><button class="btn-retry" onclick="loadSessions()">Retry</button></div>`;
278
- }
279
- }
280
-
281
- async function renameSession(id, oldName, e) {
282
- e.stopPropagation();
283
- const newName = prompt('Rename session', oldName);
284
- if (newName && newName !== oldName) {
285
- try {
286
- await fetchWithTimeout('/api/sessions/' + id, {
287
- method: 'PATCH',
288
- headers: { 'Content-Type': 'application/json' },
289
- body: JSON.stringify({ name: newName })
290
- });
291
- refreshSessionsNow();
292
- } catch (e) { alert('Rename failed'); }
293
- }
294
- }
295
-
296
- async function showToolModal(skill = null) {
297
- window.pendingSkillHubSkill = skill || null;
298
- document.getElementById('modal-overlay').style.display = 'flex';
299
- const list = document.getElementById('tools-list');
300
- const title = document.getElementById('tool-modal-title');
301
- const skillLabel = document.getElementById('tool-modal-skill');
302
- if (title) title.textContent = skill ? 'Start Skill Session' : 'Create Session';
303
- if (skillLabel) skillLabel.textContent = skill ? String(skill.displayName || skill.name || '') : '';
304
- loadAppConfig();
305
- try {
306
- const res = await fetchWithTimeout('/api/tools');
307
- let tools = await res.json();
308
- if (skill) tools = tools.filter(tool => tool.key === 'codex');
309
-
310
- let html = '';
311
- tools.forEach(t => {
312
- const versionLabel = t.version && t.version !== 'unknown' ? `v${t.version}` : 'version unknown';
313
- html += `<div class="tool-item" onclick='createSession(${JSON.stringify(t.key)}, ${JSON.stringify(t.displayName)})'>
314
- <div class="tool-icon">${t.displayName[0]}</div>
315
- <div>
316
- <div style="font-weight:600">${t.displayName}</div>
317
- <div style="font-size:12px; color:#888">${versionLabel}</div>
318
- </div>
319
- </div>`;
320
- });
321
- list.innerHTML = html;
322
- } catch (e) {
323
- list.innerHTML = `<p style="color:#ff3b30">Detection failed: ${e.message}</p>`;
324
- }
325
- }
326
-
327
- async function loadAppConfig() {
328
- if (appConfig) {
329
- document.getElementById('default-cwd').textContent = appConfig.defaultWorkingDirectory || 'Current folder';
330
- return;
331
- }
332
- try {
333
- const res = await fetchWithTimeout('/api/config');
334
- if (!res.ok) throw new Error('HTTP ' + res.status);
335
- appConfig = await res.json();
336
- document.getElementById('default-cwd').textContent = appConfig.defaultWorkingDirectory || 'Current folder';
337
- } catch (e) {
338
- document.getElementById('default-cwd').textContent = 'Current folder';
339
- }
340
- }
341
-
342
- function closeToolModal(e) {
343
- if (!e || e.target.id === 'modal-overlay') {
344
- document.getElementById('modal-overlay').style.display = 'none';
345
- window.pendingSkillHubSkill = null;
346
- }
347
- }
348
-
349
- function isLobbyVisible() {
350
- return document.getElementById('lobby-view').classList.contains('active')
351
- || (typeof isSplitLayout === 'function' && isSplitLayout());
352
- }
353
-
354
- function scheduleSessionPolling() {
355
- clearTimeout(sessionPollTimer);
356
- sessionPollTimer = null;
357
- if (!isLobbyVisible()) return;
358
-
359
- const delay = document.hidden ? 30000 : 10000;
360
- sessionPollTimer = setTimeout(async () => {
361
- await loadSessions();
362
- scheduleSessionPolling();
363
- }, delay);
364
- }
365
-
366
- async function refreshSessionsNow() {
367
- clearTimeout(sessionPollTimer);
368
- sessionPollTimer = null;
369
- if (!isLobbyVisible()) return;
370
- if (lobbyTab === 'schedules') {
371
- await loadSchedules();
372
- scheduleSessionPolling();
373
- return;
374
- }
375
- await loadSessions();
376
- scheduleSessionPolling();
377
- }
378
-
379
- async function markCompletionRead(id) {
380
- try {
381
- await fetchWithTimeout('/api/sessions/' + id + '/completion/read', { method: 'POST' });
382
- } catch (e) {
383
- log('Failed to mark completion read: ' + e.message);
384
- }
385
- }