crewswarm 0.9.2 → 0.9.4
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 +22 -9
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
- package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
- package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
- package/apps/dashboard/dist/index.html +135 -15
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +101 -56
- package/crew-lead.mjs +34 -4
- package/lib/bridges/cli-executor.mjs +1 -1
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/browser/passthrough-stderr.js +1 -0
- package/lib/chat/project-messages.mjs +3 -5
- package/lib/cli-process-tracker.mjs +3 -2
- package/lib/contacts/identity-linker.mjs +1 -0
- package/lib/crew-judge/judge.mjs +19 -18
- package/lib/crew-lead/agent-manager.mjs +1 -1
- package/lib/crew-lead/background.mjs +14 -1
- package/lib/crew-lead/chat-handler.mjs +38 -1
- package/lib/crew-lead/http-server.mjs +106 -57
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +14 -1
- package/lib/crew-lead/tools.mjs +3 -2
- package/lib/crew-lead/wave-dispatcher.mjs +19 -5
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/crew-cli.mjs +1 -1
- package/lib/engines/engine-registry.mjs +14 -3
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +28 -4
- package/lib/gemini-cli-passthrough-noise.mjs +1 -1
- package/lib/integrations/code-search.mjs +4 -3
- package/lib/memory/shared-adapter.mjs +23 -10
- package/lib/pipeline/manager.mjs +2 -1
- package/lib/runtime/config.mjs +1 -1
- package/lib/runtime/paths.mjs +12 -8
- package/lib/runtime/spending.mjs +2 -1
- package/package.json +42 -14
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +76 -0
- package/scripts/dashboard.mjs +1667 -551
- package/scripts/generate-openapi.mjs +683 -277
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/restart-service.sh +12 -9
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
|
Binary file
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skills tab — extracted from app.js
|
|
3
|
-
* Deps: getJSON, postJSON, showNotification (from core/)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getJSON, postJSON } from '../core/api.js';
|
|
7
|
-
import { showNotification } from '../core/dom.js';
|
|
8
|
-
|
|
9
|
-
let _skillsCache = [];
|
|
10
|
-
|
|
11
|
-
export function showSkills() {
|
|
12
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
13
|
-
document.getElementById('skillsView').classList.add('active');
|
|
14
|
-
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
15
|
-
const nav = document.getElementById('navSkills');
|
|
16
|
-
if (nav) nav.classList.add('active');
|
|
17
|
-
loadSkills();
|
|
18
|
-
if (typeof loadPendingApprovals === 'function') loadPendingApprovals();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function showRunSkills() {
|
|
22
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
23
|
-
const view = document.getElementById('runSkillsView');
|
|
24
|
-
if (view) view.classList.add('active');
|
|
25
|
-
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
26
|
-
const nav = document.getElementById('navRunSkills');
|
|
27
|
-
if (nav) nav.classList.add('active');
|
|
28
|
-
loadRunSkills();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function loadRunSkills() {
|
|
32
|
-
const el = document.getElementById('runSkillsGrid');
|
|
33
|
-
if (!el) return;
|
|
34
|
-
try {
|
|
35
|
-
const d = await (await fetch('/api/health')).json();
|
|
36
|
-
const skills = (d.skills || []).filter(s => !s.error && s.url);
|
|
37
|
-
if (!skills.length) {
|
|
38
|
-
el.innerHTML = '<div style="color:var(--text-3);font-size:13px;">No API skills found. Add API skills (with a URL endpoint) in the Skills tab.</div>';
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
el.innerHTML = skills.map(s => {
|
|
42
|
-
const defaults = s.defaultParams && Object.keys(s.defaultParams).length
|
|
43
|
-
? JSON.stringify(s.defaultParams, null, 2) : '{}';
|
|
44
|
-
const paramHint = (s.paramNotes || s.description || '').slice(0, 120);
|
|
45
|
-
const safeName = (s.name || '').replace(/"/g, '"');
|
|
46
|
-
return '<div class="card" style="display:flex;flex-direction:column;">'
|
|
47
|
-
+ '<div class="card-title" style="margin-bottom:6px;">' + (s.name || 'unnamed') + '</div>'
|
|
48
|
-
+ '<div style="font-size:12px;color:var(--text-3);margin-bottom:10px;line-height:1.4;">' + (s.description || '') + '</div>'
|
|
49
|
-
+ (paramHint ? '<div style="font-size:11px;color:var(--text-2);margin-bottom:8px;">' + paramHint + '</div>' : '')
|
|
50
|
-
+ '<label style="font-size:11px;color:var(--text-2);margin-bottom:4px;">Params (JSON)</label>'
|
|
51
|
-
+ '<textarea data-skill="' + safeName + '" rows="4" style="font-family:monospace;font-size:12px;width:100%;margin-bottom:10px;resize:vertical;" class="runskills-params">' + defaults.replace(/</g, '<') + '</textarea>'
|
|
52
|
-
+ '<div style="display:flex;align-items:center;gap:8px;margin-top:auto;">'
|
|
53
|
-
+ '<button class="btn-green" style="font-size:12px;" data-action="runSkillFromUI" data-arg="' + safeName + '">Run</button>'
|
|
54
|
-
+ '<span class="runskills-result" data-skill="' + safeName + '" style="font-size:11px;color:var(--text-3);"></span>'
|
|
55
|
-
+ '</div></div>';
|
|
56
|
-
}).join('');
|
|
57
|
-
} catch (e) {
|
|
58
|
-
el.innerHTML = '<div style="color:var(--red);font-size:12px;">Error loading health/skills: ' + (e.message || '') + '</div>';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function runSkillFromUI(skillName) {
|
|
63
|
-
const textarea = document.querySelector('.runskills-params[data-skill="' + (skillName || '').replace(/"/g, '\\"') + '"]');
|
|
64
|
-
const resultEl = document.querySelector('.runskills-result[data-skill="' + (skillName || '').replace(/"/g, '\\"') + '"]');
|
|
65
|
-
if (!textarea) return;
|
|
66
|
-
let params = {};
|
|
67
|
-
try { params = JSON.parse(textarea.value.trim() || '{}'); } catch (e) {
|
|
68
|
-
if (resultEl) resultEl.textContent = 'Invalid JSON';
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (resultEl) resultEl.textContent = 'Running…';
|
|
72
|
-
try {
|
|
73
|
-
const r = await fetch('/api/skills/' + encodeURIComponent(skillName) + '/run', {
|
|
74
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ params })
|
|
75
|
-
});
|
|
76
|
-
const data = await r.json();
|
|
77
|
-
if (resultEl) {
|
|
78
|
-
if (data.ok) resultEl.textContent = 'Done';
|
|
79
|
-
else resultEl.textContent = data.error || 'Error';
|
|
80
|
-
resultEl.style.color = data.ok ? 'var(--green)' : 'var(--red)';
|
|
81
|
-
}
|
|
82
|
-
if (!data.ok) return;
|
|
83
|
-
if (data.result !== undefined && resultEl) {
|
|
84
|
-
const preview = typeof data.result === 'string' ? data.result : JSON.stringify(data.result).slice(0, 120);
|
|
85
|
-
resultEl.textContent = preview + (preview.length >= 120 ? '…' : '');
|
|
86
|
-
}
|
|
87
|
-
} catch (e) {
|
|
88
|
-
if (resultEl) { resultEl.textContent = e.message || 'Request failed'; resultEl.style.color = 'var(--red)'; }
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function loadSkills() {
|
|
93
|
-
const el = document.getElementById('skillsList');
|
|
94
|
-
try {
|
|
95
|
-
const d = await (await fetch('/api/skills')).json();
|
|
96
|
-
_skillsCache = d.skills || [];
|
|
97
|
-
renderSkillsList(_skillsCache);
|
|
98
|
-
} catch(e) {
|
|
99
|
-
if (el) el.innerHTML = '<div style="color:var(--text-3);font-size:12px;">Error loading skills</div>';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function renderSkillsList(skills) {
|
|
104
|
-
const el = document.getElementById('skillsList');
|
|
105
|
-
if (!el) return;
|
|
106
|
-
if (!skills.length) {
|
|
107
|
-
el.innerHTML = '<div style="color:var(--text-3);font-size:12px;padding:8px 0;">No skills match. Add one above or copy JSONs to ~/.crewswarm/skills/</div>';
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const apiSkills = skills.filter(s => s.type === 'api' || (!s.type && s.url));
|
|
112
|
-
const knowledgeSkills = skills.filter(s => s.type === 'knowledge' || (!s.type && !s.url));
|
|
113
|
-
|
|
114
|
-
function renderSkillRow(s) {
|
|
115
|
-
const isKnowledge = s.type === 'knowledge';
|
|
116
|
-
const approvalBadge = s.requiresApproval
|
|
117
|
-
? '<span style="margin-left:6px;font-size:10px;background:rgba(251,191,36,0.15);color:var(--yellow);padding:2px 6px;border-radius:4px;">⚠️ approval</span>' : '';
|
|
118
|
-
const typeBadge = isKnowledge
|
|
119
|
-
? '<span style="margin-left:6px;font-size:10px;background:rgba(99,102,241,0.15);color:#818cf8;padding:2px 6px;border-radius:4px;">knowledge</span>'
|
|
120
|
-
: '<span style="margin-left:6px;font-size:10px;background:rgba(34,197,94,0.12);color:var(--green);padding:2px 6px;border-radius:4px;">API</span>';
|
|
121
|
-
const urlNote = s.url
|
|
122
|
-
? ' · <code style="background:var(--bg-1);padding:1px 4px;border-radius:3px;">' + (s.method||'POST') + ' ' + (s.url||'').slice(0,55) + '</code>' : '';
|
|
123
|
-
const aliasNote = (s.aliases && s.aliases.length)
|
|
124
|
-
? '<span style="margin-left:6px;font-size:10px;color:var(--text-3);">aliases: ' + s.aliases.join(', ') + '</span>' : '';
|
|
125
|
-
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-2);border-radius:var(--radius);border:1px solid var(--border);">'
|
|
126
|
-
+ '<div style="min-width:0;">'
|
|
127
|
-
+ '<div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;">'
|
|
128
|
-
+ '<span style="font-weight:600;font-size:13px;">' + s.name + '</span>'
|
|
129
|
-
+ typeBadge + approvalBadge + aliasNote
|
|
130
|
-
+ '</div>'
|
|
131
|
-
+ '<div style="font-size:11px;color:var(--text-3);margin-top:3px;">' + (s.description||'') + urlNote + '</div>'
|
|
132
|
-
+ '</div>'
|
|
133
|
-
+ '<div style="display:flex;gap:6px;flex-shrink:0;margin-left:12px;">'
|
|
134
|
-
+ (isKnowledge ? '' : '<button class="btn-ghost" style="font-size:11px;" data-action="editSkill" data-arg="' + s.name + '">Edit</button>')
|
|
135
|
-
+ '<button class="btn-ghost" style="font-size:11px;color:var(--red);" data-action="deleteSkill" data-arg="' + s.name + '">Delete</button>'
|
|
136
|
-
+ '</div></div>';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function renderSection(title, items, emptyMsg) {
|
|
140
|
-
if (!items.length) return '';
|
|
141
|
-
return '<div style="margin-bottom:20px;">'
|
|
142
|
-
+ '<div style="font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);margin-bottom:8px;">' + title + ' <span style="font-weight:400;opacity:.7;">(' + items.length + ')</span></div>'
|
|
143
|
-
+ '<div style="display:flex;flex-direction:column;gap:6px;">' + items.map(renderSkillRow).join('') + '</div>'
|
|
144
|
-
+ '</div>';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
el.innerHTML = renderSection('Knowledge', knowledgeSkills)
|
|
148
|
-
+ renderSection('API Integrations', apiSkills);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function filterSkills(q) {
|
|
152
|
-
const lower = q.toLowerCase();
|
|
153
|
-
renderSkillsList(lower ? _skillsCache.filter(s =>
|
|
154
|
-
(s.name||'').toLowerCase().includes(lower) ||
|
|
155
|
-
(s.description||'').toLowerCase().includes(lower) ||
|
|
156
|
-
(s.url||'').toLowerCase().includes(lower) ||
|
|
157
|
-
(s.aliases||[]).some(a => a.toLowerCase().includes(lower))
|
|
158
|
-
) : _skillsCache);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function editSkill(name) {
|
|
162
|
-
const s = _skillsCache.find(x => x.name === name);
|
|
163
|
-
if (!s) return;
|
|
164
|
-
document.getElementById('skEditName').value = name;
|
|
165
|
-
document.getElementById('addSkillFormTitle').textContent = 'Edit Skill';
|
|
166
|
-
document.getElementById('saveSkillBtn').textContent = 'Update Skill';
|
|
167
|
-
document.getElementById('skName').value = s.name || '';
|
|
168
|
-
document.getElementById('skDesc').value = s.description || '';
|
|
169
|
-
document.getElementById('skUrl').value = s.url || '';
|
|
170
|
-
const meth = document.getElementById('skMethod');
|
|
171
|
-
for (let i = 0; i < meth.options.length; i++) if (meth.options[i].value === s.method) { meth.selectedIndex = i; break; }
|
|
172
|
-
const authType = s.auth?.type || '';
|
|
173
|
-
document.getElementById('skAuthType').value = authType;
|
|
174
|
-
document.getElementById('skAuthKey').value = s.auth?.keyFrom || s.auth?.token || '';
|
|
175
|
-
document.getElementById('skAuthHeader').value = s.auth?.header || '';
|
|
176
|
-
document.getElementById('skRequiresApproval').checked = !!s.requiresApproval;
|
|
177
|
-
document.getElementById('skDefaults').value = s.defaultParams && Object.keys(s.defaultParams).length ? JSON.stringify(s.defaultParams, null, 2) : '';
|
|
178
|
-
updateSkillAuthFields();
|
|
179
|
-
const f = document.getElementById('addSkillForm');
|
|
180
|
-
f.style.display = 'block';
|
|
181
|
-
f.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function toggleAddSkill() {
|
|
185
|
-
cancelSkillForm();
|
|
186
|
-
document.getElementById('importSkillForm').style.display = 'none';
|
|
187
|
-
const f = document.getElementById('addSkillForm');
|
|
188
|
-
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function toggleImportSkill() {
|
|
192
|
-
cancelSkillForm();
|
|
193
|
-
const f = document.getElementById('importSkillForm');
|
|
194
|
-
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
195
|
-
if (f.style.display !== 'none') setTimeout(() => document.getElementById('importSkillUrl').focus(), 50);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function importSkillFromUrl() {
|
|
199
|
-
const urlInput = document.getElementById('importSkillUrl');
|
|
200
|
-
const status = document.getElementById('importSkillStatus');
|
|
201
|
-
const btn = document.getElementById('importSkillBtn');
|
|
202
|
-
const skillUrl = urlInput.value.trim();
|
|
203
|
-
if (!skillUrl) { status.style.color = 'var(--red)'; status.textContent = 'Paste a URL first.'; return; }
|
|
204
|
-
btn.disabled = true; btn.textContent = 'Importing…';
|
|
205
|
-
status.style.color = 'var(--text-3)'; status.textContent = 'Fetching & scanning…';
|
|
206
|
-
try {
|
|
207
|
-
const r = await fetch('/api/skills/import', { method: 'POST', headers: {'content-type':'application/json'}, body: JSON.stringify({ url: skillUrl }) });
|
|
208
|
-
const d = await r.json();
|
|
209
|
-
if (!r.ok || d.error) throw new Error(d.error || 'Import failed');
|
|
210
|
-
if (d.warnings && d.warnings.length) {
|
|
211
|
-
status.style.color = 'var(--yellow)';
|
|
212
|
-
const warnLabels = { cmd_skill: '⚠ executes shell commands', ssrf_risk: '⚠ targets private network', insecure_url: '⚠ non-HTTPS endpoint', no_approval: '⚠ no approval gate on write' };
|
|
213
|
-
const msgs = d.warnings.map(w => warnLabels[w.split(':')[0]] || w);
|
|
214
|
-
status.innerHTML = '✓ Imported <strong>"' + d.name + '"</strong> — ' + msgs.join(' · ');
|
|
215
|
-
} else {
|
|
216
|
-
status.style.color = 'var(--green)';
|
|
217
|
-
status.textContent = '✓ Imported "' + d.name + '" — no security warnings';
|
|
218
|
-
}
|
|
219
|
-
urlInput.value = '';
|
|
220
|
-
await loadSkills();
|
|
221
|
-
if (!d.warnings || !d.warnings.length) {
|
|
222
|
-
setTimeout(() => { document.getElementById('importSkillForm').style.display = 'none'; status.textContent = ''; }, 3000);
|
|
223
|
-
}
|
|
224
|
-
} catch(e) {
|
|
225
|
-
status.style.color = 'var(--red)';
|
|
226
|
-
status.textContent = 'Error: ' + e.message;
|
|
227
|
-
} finally { btn.disabled = false; btn.textContent = 'Import'; }
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function cancelSkillForm() {
|
|
231
|
-
document.getElementById('skEditName').value = '';
|
|
232
|
-
document.getElementById('addSkillFormTitle').textContent = 'New Skill';
|
|
233
|
-
document.getElementById('saveSkillBtn').textContent = 'Save Skill';
|
|
234
|
-
document.getElementById('addSkillForm').style.display = 'none';
|
|
235
|
-
['skName','skDesc','skUrl','skAuthKey','skAuthHeader','skDefaults'].forEach(id => {
|
|
236
|
-
const el = document.getElementById(id); if(el) el.value = '';
|
|
237
|
-
});
|
|
238
|
-
document.getElementById('skAuthType').value = '';
|
|
239
|
-
document.getElementById('skRequiresApproval').checked = false;
|
|
240
|
-
updateSkillAuthFields();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function updateSkillAuthFields() {
|
|
244
|
-
const t = document.getElementById('skAuthType').value;
|
|
245
|
-
document.getElementById('skAuthHeaderWrap').style.display = t === 'header' ? 'block' : 'none';
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export async function saveSkill() {
|
|
249
|
-
const name = document.getElementById('skName').value.trim();
|
|
250
|
-
const url = document.getElementById('skUrl').value.trim();
|
|
251
|
-
if (!name || !url) { alert('Skill name and URL are required'); return; }
|
|
252
|
-
let defaultParams = {};
|
|
253
|
-
try { const v = document.getElementById('skDefaults').value.trim(); if(v) defaultParams = JSON.parse(v); }
|
|
254
|
-
catch { alert('Default Params must be valid JSON'); return; }
|
|
255
|
-
const authType = document.getElementById('skAuthType').value;
|
|
256
|
-
const authKeyRaw = document.getElementById('skAuthKey').value.trim();
|
|
257
|
-
let auth = {};
|
|
258
|
-
if (authType && authKeyRaw) {
|
|
259
|
-
auth = { type: authType };
|
|
260
|
-
if (authKeyRaw.startsWith('providers.') || authKeyRaw.startsWith('env.')) auth.keyFrom = authKeyRaw;
|
|
261
|
-
else auth.token = authKeyRaw;
|
|
262
|
-
if (authType === 'header') auth.header = document.getElementById('skAuthHeader').value.trim() || 'X-API-Key';
|
|
263
|
-
}
|
|
264
|
-
const editingName = document.getElementById('skEditName').value.trim();
|
|
265
|
-
const body = { name, url, method: document.getElementById('skMethod').value, description: document.getElementById('skDesc').value.trim(), auth: Object.keys(auth).length ? auth : undefined, defaultParams, requiresApproval: document.getElementById('skRequiresApproval').checked };
|
|
266
|
-
try {
|
|
267
|
-
if (editingName && editingName !== name) await fetch('/api/skills/' + editingName, { method: 'DELETE' });
|
|
268
|
-
const r = await fetch('/api/skills', { method: 'POST', headers: {'content-type':'application/json'}, body: JSON.stringify(body) });
|
|
269
|
-
if (!r.ok) throw new Error(await r.text());
|
|
270
|
-
cancelSkillForm();
|
|
271
|
-
loadSkills();
|
|
272
|
-
showNotification(editingName ? 'Skill updated' : 'Skill saved');
|
|
273
|
-
} catch(e) { showNotification('Failed: ' + e.message, 'error'); }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export async function deleteSkill(name) {
|
|
277
|
-
if (!confirm('Delete skill "' + name + '"?')) return;
|
|
278
|
-
try {
|
|
279
|
-
const r = await fetch('/api/skills/' + name, { method: 'DELETE' });
|
|
280
|
-
if(!r.ok) throw new Error(await r.text());
|
|
281
|
-
loadSkills();
|
|
282
|
-
showNotification('Deleted');
|
|
283
|
-
} catch(e) { showNotification('Delete failed: ' + e.message, 'error'); }
|
|
284
|
-
}
|
|
Binary file
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Spending dashboard — extracted from app.js
|
|
3
|
-
* Deps: getJSON (core/api), showNotification (core/dom), estimateCost (usage-tab)
|
|
4
|
-
*/
|
|
5
|
-
import { getJSON } from '../core/api.js';
|
|
6
|
-
import { showNotification, showError } from '../core/dom.js';
|
|
7
|
-
import { estimateCost, loadOcStats as loadOcStatsFromUsage } from './usage-tab.js';
|
|
8
|
-
|
|
9
|
-
// ── Spending ──────────────────────────────────────────────────────────────────
|
|
10
|
-
var _agentTotalCost = null;
|
|
11
|
-
var _ocTotalCost = null;
|
|
12
|
-
|
|
13
|
-
function updateGrandTotal() {
|
|
14
|
-
var a = _agentTotalCost, o = _ocTotalCost;
|
|
15
|
-
var aEl = document.getElementById('gtAgentCost');
|
|
16
|
-
var oEl = document.getElementById('gtOcCost');
|
|
17
|
-
var tEl = document.getElementById('gtTotal');
|
|
18
|
-
if (!aEl) return;
|
|
19
|
-
if (a !== null) aEl.textContent = '$' + a.toFixed(4);
|
|
20
|
-
if (o !== null) oEl.textContent = '$' + o.toFixed(4);
|
|
21
|
-
if (a !== null && o !== null) tEl.textContent = '$' + (a + o).toFixed(4);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function reportOcCost(cost) {
|
|
25
|
-
_ocTotalCost = cost;
|
|
26
|
-
updateGrandTotal();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function loadAllUsage() {
|
|
30
|
-
var days = parseInt(document.getElementById('grandTotalDays')?.value || '14');
|
|
31
|
-
var ocSel = document.getElementById('ocStatsDays');
|
|
32
|
-
var spSel = document.getElementById('spendingDays');
|
|
33
|
-
if (ocSel) ocSel.value = String(days);
|
|
34
|
-
if (spSel) spSel.value = String(days === 1 ? 1 : days);
|
|
35
|
-
_agentTotalCost = null;
|
|
36
|
-
_ocTotalCost = null;
|
|
37
|
-
document.getElementById('gtAgentCost').textContent = '—';
|
|
38
|
-
document.getElementById('gtOcCost').textContent = '—';
|
|
39
|
-
document.getElementById('gtTotal').textContent = '—';
|
|
40
|
-
loadSpending();
|
|
41
|
-
loadOcStatsFromUsage(reportOcCost);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function loadSpending(){
|
|
45
|
-
const el = document.getElementById('spendingWidget');
|
|
46
|
-
const days = parseInt(document.getElementById('spendingDays')?.value || '1');
|
|
47
|
-
try {
|
|
48
|
-
if (days <= 1) {
|
|
49
|
-
// Today: real-time from crew-lead
|
|
50
|
-
const d = await (await fetch('/api/spending')).json();
|
|
51
|
-
const { spending, caps } = d;
|
|
52
|
-
const gTokens = spending.global?.tokens || 0;
|
|
53
|
-
const gCost = spending.global?.costUSD || 0;
|
|
54
|
-
const gCapTok = caps.global?.dailyTokenLimit;
|
|
55
|
-
const gCapCost = caps.global?.dailyCostLimitUSD;
|
|
56
|
-
let out = '<div style="margin-bottom:10px;">'
|
|
57
|
-
+ '<div style="font-size:11px;font-weight:600;color:var(--text-2);margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em;">Global · ' + (spending.date||'today') + '</div>'
|
|
58
|
-
+ '<div style="display:flex;gap:20px;"><span>' + gTokens.toLocaleString() + ' tokens' + (gCapTok ? ' / ' + Number(gCapTok).toLocaleString() : '') + '</span>'
|
|
59
|
-
+ '<span style="color:var(--yellow);font-weight:600;">$' + gCost.toFixed(4) + '</span>' + (gCapCost ? '<span> / $' + gCapCost + '</span>' : '') + '</div>';
|
|
60
|
-
if (gCapTok) {
|
|
61
|
-
const pct = Math.min(100, (gTokens/gCapTok)*100);
|
|
62
|
-
const barColor = pct > 80 ? 'var(--red)' : pct > 50 ? 'var(--yellow)' : 'var(--green)';
|
|
63
|
-
out += '<div style="margin-top:4px;height:4px;background:var(--border);border-radius:2px;"><div style="width:' + pct + '%;height:100%;background:' + barColor + ';border-radius:2px;transition:width .3s;"></div></div>';
|
|
64
|
-
}
|
|
65
|
-
out += '</div>';
|
|
66
|
-
const agents = Object.entries(spending.agents || {});
|
|
67
|
-
if (agents.length) {
|
|
68
|
-
out += '<div style="font-size:11px;font-weight:600;color:var(--text-2);margin-bottom:6px;text-transform:uppercase;letter-spacing:.06em;">Per Agent</div>';
|
|
69
|
-
out += agents.map(function(entry) {
|
|
70
|
-
var id = entry[0], v = entry[1];
|
|
71
|
-
const agentCap = caps.agents && caps.agents[id];
|
|
72
|
-
const toks = v.tokens || 0;
|
|
73
|
-
const cost = (v.costUSD||0).toFixed(4);
|
|
74
|
-
const capTok = agentCap && agentCap.dailyTokenLimit;
|
|
75
|
-
const pct = capTok ? Math.min(100, (toks/capTok)*100) : null;
|
|
76
|
-
let row = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:4px;">'
|
|
77
|
-
+ '<span style="min-width:140px;font-size:12px;">' + id + '</span>'
|
|
78
|
-
+ '<span style="font-size:12px;">' + toks.toLocaleString() + ' tok' + (capTok ? ' / ' + Number(capTok).toLocaleString() : '') + ' · <span style="color:var(--yellow);">$' + cost + '</span></span>';
|
|
79
|
-
if (pct !== null) {
|
|
80
|
-
const barColor = pct > 80 ? 'var(--red)' : 'var(--accent)';
|
|
81
|
-
row += '<div style="flex:1;height:3px;background:var(--border);border-radius:2px;"><div style="width:' + pct + '%;height:100%;background:' + barColor + ';border-radius:2px;"></div></div>';
|
|
82
|
-
}
|
|
83
|
-
return row + '</div>';
|
|
84
|
-
}).join('');
|
|
85
|
-
} else { out += '<div style="color:var(--text-3);">No per-agent data yet for today.</div>'; }
|
|
86
|
-
if (gCapTok) document.getElementById('gcapTokens').value = gCapTok;
|
|
87
|
-
if (gCapCost) document.getElementById('gcapCost').value = gCapCost;
|
|
88
|
-
_agentTotalCost = gCost;
|
|
89
|
-
updateGrandTotal();
|
|
90
|
-
el.innerHTML = out;
|
|
91
|
-
} else {
|
|
92
|
-
// Multi-day: compute from token-usage.json byDay
|
|
93
|
-
const u = await getJSON('/api/token-usage').catch(function(){ return {}; });
|
|
94
|
-
const byDay = u.byDay || {};
|
|
95
|
-
const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
|
96
|
-
const filteredDays = Object.keys(byDay).filter(function(d){ return d >= cutoff; }).sort().reverse();
|
|
97
|
-
if (!filteredDays.length) {
|
|
98
|
-
el.innerHTML = '<div style="color:var(--text-3);">No data for this period.</div>';
|
|
99
|
-
_agentTotalCost = 0;
|
|
100
|
-
updateGrandTotal();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
// Aggregate byModel across days
|
|
104
|
-
const aggByModel = {};
|
|
105
|
-
var totalTok = 0, totalCost = 0;
|
|
106
|
-
filteredDays.forEach(function(day) {
|
|
107
|
-
const dm = byDay[day].byModel || {};
|
|
108
|
-
Object.entries(dm).forEach(function(e) {
|
|
109
|
-
var m = e[0], s = e[1];
|
|
110
|
-
if (!aggByModel[m]) aggByModel[m] = { prompt: 0, completion: 0 };
|
|
111
|
-
aggByModel[m].prompt += s.prompt || 0;
|
|
112
|
-
aggByModel[m].completion += s.completion || 0;
|
|
113
|
-
totalTok += (s.prompt||0) + (s.completion||0);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
totalCost = estimateCost(aggByModel);
|
|
117
|
-
let out = '<div style="margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;">'
|
|
118
|
-
+ '<span style="font-size:12px;color:var(--text-3);">Last ' + days + ' days · ' + filteredDays.length + ' days of data</span>'
|
|
119
|
-
+ '<span style="font-size:16px;font-weight:700;color:var(--yellow);">$' + totalCost.toFixed(4) + '</span>'
|
|
120
|
-
+ '</div>';
|
|
121
|
-
// Daily breakdown bar chart
|
|
122
|
-
const maxDayCost = Math.max(...filteredDays.map(function(d){ return estimateCost(byDay[d].byModel||{}); }), 0.0001);
|
|
123
|
-
const today = new Date().toISOString().slice(0,10);
|
|
124
|
-
out += '<div style="display:flex;flex-direction:column;gap:3px;margin-bottom:12px;">';
|
|
125
|
-
filteredDays.forEach(function(day) {
|
|
126
|
-
const dc = estimateCost(byDay[day].byModel||{});
|
|
127
|
-
const pct = Math.max((dc/maxDayCost)*100, dc > 0 ? 2 : 0);
|
|
128
|
-
const isToday = day === today;
|
|
129
|
-
const tok = ((byDay[day].prompt||0)+(byDay[day].completion||0))/1000;
|
|
130
|
-
out += '<div style="display:flex;align-items:center;gap:8px;font-size:11px;">'
|
|
131
|
-
+ '<span style="width:64px;color:var(--text-3);flex-shrink:0;">' + (isToday ? 'today' : day.slice(5)) + '</span>'
|
|
132
|
-
+ '<div style="flex:1;background:var(--bg-1);border-radius:3px;height:12px;overflow:hidden;">'
|
|
133
|
-
+ '<div style="width:' + pct.toFixed(1) + '%;height:100%;background:' + (isToday ? 'var(--accent)' : 'var(--green)') + ';border-radius:3px;opacity:.8;"></div>'
|
|
134
|
-
+ '</div>'
|
|
135
|
-
+ '<span style="width:58px;text-align:right;color:var(--yellow);font-weight:600;">$' + dc.toFixed(4) + '</span>'
|
|
136
|
-
+ '<span style="width:40px;text-align:right;color:var(--text-3);">' + tok.toFixed(0) + 'k</span>'
|
|
137
|
-
+ '</div>';
|
|
138
|
-
});
|
|
139
|
-
out += '</div>';
|
|
140
|
-
// Top models
|
|
141
|
-
const sortedModels = Object.entries(aggByModel).sort(function(a,b){
|
|
142
|
-
return estimateCost({b:b[1]}) - estimateCost({a:a[1]});
|
|
143
|
-
});
|
|
144
|
-
if (sortedModels.length) {
|
|
145
|
-
out += '<div style="font-size:11px;color:var(--text-3);margin-bottom:4px;">By model</div>';
|
|
146
|
-
sortedModels.slice(0,8).forEach(function(e) {
|
|
147
|
-
var m = e[0], s = e[1];
|
|
148
|
-
const mc = estimateCost({x:s});
|
|
149
|
-
const tok = ((s.prompt||0)+(s.completion||0))/1000;
|
|
150
|
-
out += '<div style="display:flex;justify-content:space-between;font-size:11px;padding:2px 0;border-bottom:1px solid var(--border);">'
|
|
151
|
-
+ '<code style="color:var(--accent);">' + m + '</code>'
|
|
152
|
-
+ '<span style="color:var(--text-2);">' + tok.toFixed(1) + 'k tok · <span style="color:var(--yellow);">$' + mc.toFixed(4) + '</span></span>'
|
|
153
|
-
+ '</div>';
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
_agentTotalCost = totalCost;
|
|
157
|
-
updateGrandTotal();
|
|
158
|
-
el.innerHTML = out;
|
|
159
|
-
}
|
|
160
|
-
} catch(e) { showError(el, 'Error: ' + e.message); }
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function resetSpending(){
|
|
164
|
-
if (!confirm("Reset today's spending counters?")) return;
|
|
165
|
-
try { await fetch('/api/spending/reset', { method: 'POST', headers:{'content-type':'application/json'}, body: '{}' }); loadSpending(); showNotification('Spending reset'); }
|
|
166
|
-
catch(e) { showNotification('Reset failed', true); }
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function saveGlobalCaps(){
|
|
170
|
-
const tokens = parseInt(document.getElementById('gcapTokens').value) || null;
|
|
171
|
-
const cost = parseFloat(document.getElementById('gcapCost').value) || null;
|
|
172
|
-
showNotification('Add to ~/.crewswarm/crewswarm.json: "globalSpendingCaps": {"dailyTokenLimit":' + (tokens||'null') + ',"dailyCostLimitUSD":' + (cost||'null') + '}', 'warning');
|
|
173
|
-
}
|
|
Binary file
|