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.
- package/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -61
- package/README.zh-CN.md +0 -198
- package/assets/logo.svg +0 -43
- package/bin/cli.js +0 -65
- package/lib/ai-tools/demo/enhanced-demo.js +0 -625
- package/lib/ai-tools/demo/index.js +0 -24
- package/lib/ai-tools/demo/responses.js +0 -88
- package/lib/ai-tools/detector.js +0 -76
- package/lib/ai-tools/registry.js +0 -300
- package/lib/claude/cli-usage.js +0 -95
- package/lib/claude/config.js +0 -82
- package/lib/claude/structured-session.js +0 -884
- package/lib/claude/transcript-repository.js +0 -216
- package/lib/codex/image-store.js +0 -174
- package/lib/codex/structured-session.js +0 -1590
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -605
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -108
- package/lib/git/service.js +0 -83
- package/lib/notifications/message-formatter.js +0 -94
- package/lib/notifications/notification-service.js +0 -143
- package/lib/notifications/serverchan-client.js +0 -58
- package/lib/notifications/serverchan-settings-store.js +0 -115
- package/lib/schedule/job-runner.js +0 -162
- package/lib/schedule/job-store.js +0 -167
- package/lib/schedule/key-sequences.js +0 -49
- package/lib/schedule/scheduler-service.js +0 -39
- package/lib/server/routes/notifications.js +0 -52
- package/lib/server/routes/providers.js +0 -114
- package/lib/server/routes/schedules.js +0 -54
- package/lib/server/routes/skillhub.js +0 -104
- package/lib/server/routes/usage.js +0 -23
- package/lib/server/routes/workspace.js +0 -77
- package/lib/session/buffer.js +0 -102
- package/lib/session/file-attachment-store.js +0 -168
- package/lib/session/pty-manager.js +0 -255
- package/lib/session/rendered-history.js +0 -225
- package/lib/session/session-manager.js +0 -1032
- package/lib/session/text-history.js +0 -274
- package/lib/skillhub/client.js +0 -121
- package/lib/skillhub/settings-store.js +0 -168
- package/lib/skillhub/skill-installer.js +0 -320
- package/lib/usage/ccusage-runner.js +0 -128
- package/lib/usage/source-catalog.js +0 -26
- package/lib/usage/usage-service.js +0 -226
- package/lib/utils/logger.js +0 -74
- package/lib/utils/pid.js +0 -67
- package/lib/utils/validation.js +0 -53
- package/lib/web/bootstrap.js +0 -34
- package/lib/web/claude.js +0 -1150
- package/lib/web/codex.js +0 -1045
- package/lib/web/composer.js +0 -493
- package/lib/web/core.js +0 -385
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -547
- package/lib/web/layout.js +0 -69
- package/lib/web/notifications.js +0 -164
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -361
- package/lib/web/shell.js +0 -74
- package/lib/web/skillhub.js +0 -197
- package/lib/web/styles.css +0 -932
- package/lib/web/terminal-scroll.js +0 -81
- package/lib/web/theme.js +0 -60
- package/lib/web/timed-inputs.js +0 -216
- package/lib/web/usage.js +0 -323
- package/lib/workspace/service.js +0 -77
- package/scripts/check-syntax.js +0 -26
package/lib/web/usage.js
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
const usageState = {
|
|
2
|
-
source: null,
|
|
3
|
-
scope: 'weekly',
|
|
4
|
-
selectedPeriod: null,
|
|
5
|
-
sources: [],
|
|
6
|
-
requestSequence: 0
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const usageModelColors = [
|
|
10
|
-
'#0a84ff', '#30d158', '#bf5af2', '#ff9f0a', '#ff453a', '#64d2ff',
|
|
11
|
-
'#ffd60a', '#5e5ce6', '#ff375f', '#66d4cf', '#ac8e68', '#8e8e93'
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
function formatExactTokens(value) {
|
|
15
|
-
return new Intl.NumberFormat().format(Number(value) || 0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function formatCompactNumber(value) {
|
|
19
|
-
const number = Number(value) || 0;
|
|
20
|
-
if (number < 1000) return formatExactTokens(number);
|
|
21
|
-
return new Intl.NumberFormat(undefined, {
|
|
22
|
-
notation: 'compact',
|
|
23
|
-
maximumFractionDigits: number >= 1000000 ? 2 : 1
|
|
24
|
-
}).format(number);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function formatEstimatedCost(value, compact = false) {
|
|
28
|
-
if (value === null || value === undefined) return '—';
|
|
29
|
-
const amount = Number(value) || 0;
|
|
30
|
-
return new Intl.NumberFormat(undefined, {
|
|
31
|
-
style: 'currency',
|
|
32
|
-
currency: 'USD',
|
|
33
|
-
notation: compact && amount >= 1000 ? 'compact' : 'standard',
|
|
34
|
-
minimumFractionDigits: compact ? 2 : (amount < 1 ? 3 : 2),
|
|
35
|
-
maximumFractionDigits: compact ? 2 : (amount < 1 ? 4 : 2)
|
|
36
|
-
}).format(amount);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function closeUsageSourceModal(event) {
|
|
40
|
-
if (event && event.target.id !== 'usage-source-overlay') return;
|
|
41
|
-
document.getElementById('usage-source-overlay').style.display = 'none';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function showUsageSourceModal(options = {}) {
|
|
45
|
-
const overlay = document.getElementById('usage-source-overlay');
|
|
46
|
-
overlay.style.display = 'flex';
|
|
47
|
-
const hasCachedSources = usageState.sources.length > 0;
|
|
48
|
-
if (hasCachedSources && !options.refresh) {
|
|
49
|
-
renderUsageSources();
|
|
50
|
-
} else {
|
|
51
|
-
document.getElementById('usage-sources-list').innerHTML = '<p class="usage-modal-state">Reading local usage data...</p>';
|
|
52
|
-
}
|
|
53
|
-
const list = document.getElementById('usage-sources-list');
|
|
54
|
-
try {
|
|
55
|
-
const suffix = options.refresh ? '?refresh=1' : '';
|
|
56
|
-
const response = await fetchWithTimeout(`/api/usage/sources${suffix}`, {}, 60000);
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
|
|
59
|
-
usageState.sources = Array.isArray(data.sources) ? data.sources : [];
|
|
60
|
-
renderUsageSources();
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (hasCachedSources) return;
|
|
63
|
-
list.innerHTML = `<div class="usage-modal-state">Unable to read usage data.<br>${escapeHtml(error.message)}<br><button class="btn-retry" type="button" onclick="showUsageSourceModal({ refresh: true })">Retry</button></div>`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function renderUsageSources() {
|
|
68
|
-
const list = document.getElementById('usage-sources-list');
|
|
69
|
-
if (!usageState.sources.length) {
|
|
70
|
-
list.innerHTML = '<p class="usage-modal-state">No supported local CLI usage history was found.</p>';
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
list.innerHTML = usageState.sources.map(source => `
|
|
74
|
-
<button class="usage-source-item" type="button" onclick="openUsageDashboard('${escapeHtml(source.id)}')">
|
|
75
|
-
<span class="usage-source-badge">${escapeHtml(source.badge)}</span>
|
|
76
|
-
<span class="usage-source-copy"><strong>${escapeHtml(source.label)}</strong><span>Local token history</span></span>
|
|
77
|
-
<span class="usage-source-arrow">›</span>
|
|
78
|
-
</button>`).join('');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function openUsageDashboard(sourceId) {
|
|
82
|
-
const source = usageState.sources.find(item => item.id === sourceId);
|
|
83
|
-
usageState.source = source || { id: sourceId, label: sourceId };
|
|
84
|
-
usageState.selectedPeriod = null;
|
|
85
|
-
closeUsageSourceModal();
|
|
86
|
-
document.querySelectorAll('.view').forEach(view => view.classList.remove('active'));
|
|
87
|
-
document.getElementById('usage-view').classList.add('active');
|
|
88
|
-
document.getElementById('usage-source-title').textContent = `${usageState.source.label} Usage`;
|
|
89
|
-
await loadUsageDashboard();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function setUsageScope(scope) {
|
|
93
|
-
if (!['weekly', 'monthly'].includes(scope) || usageState.scope === scope) return;
|
|
94
|
-
usageState.scope = scope;
|
|
95
|
-
usageState.selectedPeriod = null;
|
|
96
|
-
updateUsageScopeButtons();
|
|
97
|
-
if (usageState.source) await loadUsageDashboard();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function updateUsageScopeButtons() {
|
|
101
|
-
for (const scope of ['weekly', 'monthly']) {
|
|
102
|
-
document.getElementById(`usage-scope-${scope}`).classList.toggle('active', usageState.scope === scope);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function selectUsagePeriod(period) {
|
|
107
|
-
if (!period || usageState.selectedPeriod === period) return;
|
|
108
|
-
usageState.selectedPeriod = period;
|
|
109
|
-
await loadUsageDashboard();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function refreshUsageDashboard() {
|
|
113
|
-
if (!usageState.source) return;
|
|
114
|
-
await loadUsageDashboard(true);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function setUsageLoading(loading, message = 'Loading usage data...') {
|
|
118
|
-
const state = document.getElementById('usage-loading');
|
|
119
|
-
const dashboard = document.getElementById('usage-dashboard');
|
|
120
|
-
const refresh = document.getElementById('usage-refresh-button');
|
|
121
|
-
state.classList.remove('error');
|
|
122
|
-
state.textContent = message;
|
|
123
|
-
state.hidden = !loading;
|
|
124
|
-
dashboard.hidden = loading;
|
|
125
|
-
refresh.classList.toggle('loading', loading);
|
|
126
|
-
refresh.disabled = loading;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function loadUsageDashboard(refresh = false) {
|
|
130
|
-
const requestSequence = ++usageState.requestSequence;
|
|
131
|
-
setUsageLoading(true);
|
|
132
|
-
try {
|
|
133
|
-
const query = new URLSearchParams({ source: usageState.source.id, scope: usageState.scope });
|
|
134
|
-
if (usageState.selectedPeriod) query.set('period', usageState.selectedPeriod);
|
|
135
|
-
if (refresh) query.set('refresh', '1');
|
|
136
|
-
const response = await fetchWithTimeout(`/api/usage/report?${query}`, {}, 60000);
|
|
137
|
-
const data = await response.json();
|
|
138
|
-
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
|
|
139
|
-
if (requestSequence !== usageState.requestSequence) return;
|
|
140
|
-
renderUsageDashboard(data);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
if (requestSequence !== usageState.requestSequence) return;
|
|
143
|
-
const state = document.getElementById('usage-loading');
|
|
144
|
-
state.classList.add('error');
|
|
145
|
-
state.innerHTML = `Unable to load usage: ${escapeHtml(error.message)}<br><button class="btn-retry" type="button" onclick="loadUsageDashboard(true)">Retry</button>`;
|
|
146
|
-
state.hidden = false;
|
|
147
|
-
document.getElementById('usage-dashboard').hidden = true;
|
|
148
|
-
} finally {
|
|
149
|
-
if (requestSequence !== usageState.requestSequence) return;
|
|
150
|
-
const refreshButton = document.getElementById('usage-refresh-button');
|
|
151
|
-
refreshButton.classList.remove('loading');
|
|
152
|
-
refreshButton.disabled = false;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function renderPeriodPicker(report) {
|
|
157
|
-
usageState.selectedPeriod = report.selectedPeriod;
|
|
158
|
-
const select = document.getElementById('usage-period-select');
|
|
159
|
-
select.innerHTML = (report.availablePeriods || []).map(period =>
|
|
160
|
-
`<option value="${escapeHtml(period)}"${period === report.selectedPeriod ? ' selected' : ''}>${escapeHtml(period)}</option>`
|
|
161
|
-
).join('');
|
|
162
|
-
select.disabled = !report.availablePeriods || report.availablePeriods.length === 0;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function totalSummaryCard(label, value, cost = false) {
|
|
166
|
-
const display = cost ? formatEstimatedCost(value, true) : formatCompactNumber(value);
|
|
167
|
-
const exact = cost ? formatEstimatedCost(value) : `${formatExactTokens(value)} tokens`;
|
|
168
|
-
return `<article class="usage-summary-card" title="${escapeHtml(exact)}">
|
|
169
|
-
<span class="label"><i class="dot ${cost ? 'cost' : 'tokens'}"></i>${escapeHtml(label)}</span>
|
|
170
|
-
<strong class="value">${escapeHtml(display)}</strong>
|
|
171
|
-
<span class="exact">${escapeHtml(exact)}</span>
|
|
172
|
-
</article>`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function renderAllModelTotals(report) {
|
|
176
|
-
const totals = report.summary && report.summary.totals ? report.summary.totals : {};
|
|
177
|
-
const cards = [totalSummaryCard('All-model tokens', totals.totalTokens)];
|
|
178
|
-
if (totals.estimatedCostUSD !== null && totals.estimatedCostUSD !== undefined) {
|
|
179
|
-
cards.push(totalSummaryCard('All GPT cost', totals.estimatedCostUSD, true));
|
|
180
|
-
}
|
|
181
|
-
document.getElementById('usage-summary').innerHTML = cards.join('');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function renderModelSummary(report) {
|
|
185
|
-
const models = report.summary && Array.isArray(report.summary.models) ? report.summary.models : [];
|
|
186
|
-
const totals = report.summary && report.summary.totals ? report.summary.totals : {};
|
|
187
|
-
const hasCost = totals.estimatedCostUSD !== null && totals.estimatedCostUSD !== undefined;
|
|
188
|
-
const container = document.getElementById('usage-model-summary');
|
|
189
|
-
if (!models.length) {
|
|
190
|
-
container.innerHTML = '<div class="usage-empty">No usage in this period.</div>';
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
container.innerHTML = `<table class="usage-table">
|
|
194
|
-
<thead><tr><th>Model</th><th>Uncached input</th><th>Cached input</th><th>Output</th><th>Total tokens</th>${hasCost ? '<th>Cost</th>' : ''}</tr></thead>
|
|
195
|
-
<tbody>${models.map(model => `<tr>
|
|
196
|
-
<td class="usage-models" title="${escapeHtml(model.modelName)}">${escapeHtml(model.modelName)}</td>
|
|
197
|
-
<td>${escapeHtml(formatExactTokens(model.uncachedInputTokens))}</td>
|
|
198
|
-
<td>${escapeHtml(formatExactTokens(model.cachedInputTokens))}</td>
|
|
199
|
-
<td>${escapeHtml(formatExactTokens(model.outputTokens))}</td>
|
|
200
|
-
<td>${escapeHtml(formatExactTokens(model.totalTokens))}</td>
|
|
201
|
-
${hasCost ? `<td>${escapeHtml(formatEstimatedCost(model.estimatedCostUSD))}</td>` : ''}
|
|
202
|
-
</tr>`).join('')}</tbody>
|
|
203
|
-
<tfoot><tr><td>All models</td><td>${escapeHtml(formatExactTokens(totals.uncachedInputTokens))}</td><td>${escapeHtml(formatExactTokens(totals.cachedInputTokens))}</td><td>${escapeHtml(formatExactTokens(totals.outputTokens))}</td><td>${escapeHtml(formatExactTokens(totals.totalTokens))}</td>${hasCost ? `<td>${escapeHtml(formatEstimatedCost(totals.estimatedCostUSD))}</td>` : ''}</tr></tfoot>
|
|
204
|
-
</table>`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function collectChartModels(days, metric) {
|
|
208
|
-
const names = [];
|
|
209
|
-
const seen = new Set();
|
|
210
|
-
for (const day of days) {
|
|
211
|
-
for (const model of day.models || []) {
|
|
212
|
-
const value = model[metric];
|
|
213
|
-
if ((value === null || value === undefined || Number(value) <= 0) || seen.has(model.modelName)) continue;
|
|
214
|
-
seen.add(model.modelName);
|
|
215
|
-
names.push(model.modelName);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return names.sort((a, b) => a.localeCompare(b));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function modelColorMap(modelNames) {
|
|
222
|
-
return new Map(modelNames.map((name, index) => [
|
|
223
|
-
name,
|
|
224
|
-
usageModelColors[index] || `hsl(${(index * 47) % 360} 75% 58%)`
|
|
225
|
-
]));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function renderModelLegend(elementId, modelNames, colors) {
|
|
229
|
-
document.getElementById(elementId).innerHTML = modelNames.map(name =>
|
|
230
|
-
`<span title="${escapeHtml(name)}"><i style="background:${colors.get(name)}"></i>${escapeHtml(name)}</span>`
|
|
231
|
-
).join('');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function renderStackedModelChart(report, options) {
|
|
235
|
-
const days = report.days || [];
|
|
236
|
-
const modelNames = collectChartModels(days, options.metric);
|
|
237
|
-
const colors = modelColorMap(modelNames);
|
|
238
|
-
const container = document.getElementById(options.containerId);
|
|
239
|
-
renderModelLegend(options.legendId, modelNames, colors);
|
|
240
|
-
if (!days.length || !modelNames.length) {
|
|
241
|
-
container.innerHTML = `<div class="usage-empty">${escapeHtml(options.emptyText)}</div>`;
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
const dayTotals = days.map(day => (day.models || []).reduce((sum, model) => {
|
|
245
|
-
const value = model[options.metric];
|
|
246
|
-
return sum + (value === null || value === undefined ? 0 : Number(value) || 0);
|
|
247
|
-
}, 0));
|
|
248
|
-
const maximum = Math.max(...dayTotals, 1);
|
|
249
|
-
container.innerHTML = days.map((day, dayIndex) => {
|
|
250
|
-
const segments = modelNames.map(name => {
|
|
251
|
-
const model = (day.models || []).find(item => item.modelName === name);
|
|
252
|
-
const value = model && model[options.metric] !== null ? Number(model[options.metric]) || 0 : 0;
|
|
253
|
-
if (value <= 0) return '';
|
|
254
|
-
const title = `${name}: ${options.formatExact(value)}`;
|
|
255
|
-
return `<span title="${escapeHtml(title)}" style="width:${value / maximum * 100}%;background:${colors.get(name)}"></span>`;
|
|
256
|
-
}).join('');
|
|
257
|
-
return `<div class="usage-chart-row">
|
|
258
|
-
<span class="usage-chart-label">${escapeHtml(day.period)}</span>
|
|
259
|
-
<div class="usage-chart-track">${segments}</div>
|
|
260
|
-
<span class="usage-chart-total">${escapeHtml(options.formatCompact(dayTotals[dayIndex]))}</span>
|
|
261
|
-
</div>`;
|
|
262
|
-
}).join('');
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function renderDailyTable(report) {
|
|
267
|
-
const days = (report.days || []).slice().reverse();
|
|
268
|
-
const hasCost = days.some(day => day.totals && day.totals.estimatedCostUSD !== null);
|
|
269
|
-
const container = document.getElementById('usage-daily-table');
|
|
270
|
-
if (!days.length) {
|
|
271
|
-
container.innerHTML = '<div class="usage-empty">No daily usage in this period.</div>';
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
container.innerHTML = `<table class="usage-table">
|
|
275
|
-
<thead><tr><th>Date</th><th>Total tokens</th>${hasCost ? '<th>Cost</th>' : ''}<th>Models</th></tr></thead>
|
|
276
|
-
<tbody>${days.map(day => `<tr>
|
|
277
|
-
<td>${escapeHtml(day.period)}</td>
|
|
278
|
-
<td>${escapeHtml(formatExactTokens(day.totals.totalTokens))}</td>
|
|
279
|
-
${hasCost ? `<td>${escapeHtml(formatEstimatedCost(day.totals.estimatedCostUSD))}</td>` : ''}
|
|
280
|
-
<td class="usage-models" title="${escapeHtml(day.models.map(model => model.modelName).join(', '))}">${escapeHtml(day.models.map(model => model.modelName).join(', ') || '—')}</td>
|
|
281
|
-
</tr>`).join('')}</tbody>
|
|
282
|
-
</table>`;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function renderEngineNote(report) {
|
|
286
|
-
const engine = report.engine || { name: 'ccusage', version: 'unknown' };
|
|
287
|
-
const pricingMode = engine.pricingMode === 'embedded' ? 'embedded pricing' : 'pricing';
|
|
288
|
-
const parts = [`Statistics and ${pricingMode} calculated by ${engine.name} ${engine.version}`];
|
|
289
|
-
if (report.cost) parts.push(report.cost.note);
|
|
290
|
-
else parts.push('Cost is only shown for GPT models used by Codex.');
|
|
291
|
-
document.getElementById('usage-engine-note').textContent = parts.join(' · ');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function renderUsageDashboard(report) {
|
|
295
|
-
setUsageLoading(false);
|
|
296
|
-
updateUsageScopeButtons();
|
|
297
|
-
renderPeriodPicker(report);
|
|
298
|
-
const updated = report.generatedAt ? new Date(report.generatedAt) : null;
|
|
299
|
-
document.getElementById('usage-updated-at').textContent = updated && !Number.isNaN(updated.getTime())
|
|
300
|
-
? `Updated ${updated.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
|
301
|
-
: '';
|
|
302
|
-
renderAllModelTotals(report);
|
|
303
|
-
renderModelSummary(report);
|
|
304
|
-
renderStackedModelChart(report, {
|
|
305
|
-
containerId: 'usage-token-chart',
|
|
306
|
-
legendId: 'usage-token-legend',
|
|
307
|
-
metric: 'totalTokens',
|
|
308
|
-
emptyText: 'No token data in this period.',
|
|
309
|
-
formatExact: value => `${formatExactTokens(value)} tokens`,
|
|
310
|
-
formatCompact: formatCompactNumber
|
|
311
|
-
});
|
|
312
|
-
const hasCostChart = renderStackedModelChart(report, {
|
|
313
|
-
containerId: 'usage-cost-chart',
|
|
314
|
-
legendId: 'usage-cost-legend',
|
|
315
|
-
metric: 'estimatedCostUSD',
|
|
316
|
-
emptyText: 'No Codex GPT cost estimate in this period.',
|
|
317
|
-
formatExact: formatEstimatedCost,
|
|
318
|
-
formatCompact: value => formatEstimatedCost(value, true)
|
|
319
|
-
});
|
|
320
|
-
document.getElementById('usage-cost-panel').hidden = !hasCostChart;
|
|
321
|
-
renderDailyTable(report);
|
|
322
|
-
renderEngineNote(report);
|
|
323
|
-
}
|
package/lib/workspace/service.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
class WorkspaceService {
|
|
5
|
-
constructor({ gitService } = {}) {
|
|
6
|
-
this.gitService = gitService || null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
resolveInside(rootDir, targetPath = '') {
|
|
10
|
-
const root = fs.realpathSync(path.resolve(rootDir));
|
|
11
|
-
const requestedPath = path.resolve(root, String(targetPath || ''));
|
|
12
|
-
const fullPath = fs.realpathSync(requestedPath);
|
|
13
|
-
const relative = path.relative(root, fullPath);
|
|
14
|
-
|
|
15
|
-
if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) {
|
|
16
|
-
return fullPath;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const error = new Error('Access denied');
|
|
20
|
-
error.statusCode = 403;
|
|
21
|
-
throw error;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
readFile(rootDir, filePath) {
|
|
25
|
-
const fullPath = this.resolveInside(rootDir, filePath);
|
|
26
|
-
return fs.readFileSync(fullPath, 'utf8');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async listDirectory(rootDir, dirPath = '') {
|
|
30
|
-
const fullPath = this.resolveInside(rootDir, dirPath);
|
|
31
|
-
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
32
|
-
let files = entries.map(entry => ({
|
|
33
|
-
name: entry.name,
|
|
34
|
-
isDirectory: entry.isDirectory()
|
|
35
|
-
})).sort((a, b) => {
|
|
36
|
-
if (a.isDirectory && !b.isDirectory) return -1;
|
|
37
|
-
if (!a.isDirectory && b.isDirectory) return 1;
|
|
38
|
-
return a.name.localeCompare(b.name);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (!this.gitService) return files;
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const gitResult = await this.gitService.status(rootDir);
|
|
45
|
-
if (!gitResult.success || !Array.isArray(gitResult.files)) return files;
|
|
46
|
-
|
|
47
|
-
const gitMap = new Map();
|
|
48
|
-
gitResult.files.forEach(entry => {
|
|
49
|
-
gitMap.set(entry.path, entry.status);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
files = files.map(file => {
|
|
53
|
-
const relativePath = dirPath ? `${dirPath}/${file.name}` : file.name;
|
|
54
|
-
let gitStatus = null;
|
|
55
|
-
|
|
56
|
-
if (file.isDirectory) {
|
|
57
|
-
for (const [gitFile] of gitMap.entries()) {
|
|
58
|
-
if (gitFile.startsWith(relativePath + '/')) {
|
|
59
|
-
gitStatus = 'M';
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} else if (gitMap.has(relativePath)) {
|
|
64
|
-
gitStatus = gitMap.get(relativePath);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { ...file, gitStatus };
|
|
68
|
-
});
|
|
69
|
-
} catch (_) {
|
|
70
|
-
return files;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return files;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = WorkspaceService;
|
package/scripts/check-syntax.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
|
|
5
|
-
const projectRoot = path.resolve(__dirname, '..');
|
|
6
|
-
const sourceRoots = ['bin', 'lib', 'scripts', 'tests'];
|
|
7
|
-
const standaloneFiles = ['playwright.config.js'];
|
|
8
|
-
|
|
9
|
-
function collectJavaScriptFiles(directory) {
|
|
10
|
-
return fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
|
|
11
|
-
const fullPath = path.join(directory, entry.name);
|
|
12
|
-
if (entry.isDirectory()) return collectJavaScriptFiles(fullPath);
|
|
13
|
-
return entry.isFile() && entry.name.endsWith('.js') ? [fullPath] : [];
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const files = [
|
|
18
|
-
...sourceRoots.flatMap(sourceRoot => collectJavaScriptFiles(path.join(projectRoot, sourceRoot))),
|
|
19
|
-
...standaloneFiles.map(file => path.join(projectRoot, file))
|
|
20
|
-
];
|
|
21
|
-
for (const file of files) {
|
|
22
|
-
const result = spawnSync(process.execPath, ['--check', file], { stdio: 'inherit' });
|
|
23
|
-
if (result.status !== 0) process.exit(result.status || 1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
console.log(`Checked ${files.length} JavaScript files.`);
|