@worca/ui 0.1.1 → 0.2.0
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/app/main.bundle.js +400 -345
- package/app/main.bundle.js.map +3 -3
- package/app/styles.css +25 -2
- package/package.json +1 -1
- package/server/beads-reader.js +21 -19
- package/server/project-routes.js +25 -58
- package/server/versions.js +11 -9
- package/server/ws-beads-watcher.js +2 -2
- package/server/ws-message-router.js +5 -5
package/app/styles.css
CHANGED
|
@@ -1735,10 +1735,20 @@ sl-details.log-history-panel::part(content) {
|
|
|
1735
1735
|
}
|
|
1736
1736
|
|
|
1737
1737
|
.pricing-table sl-input {
|
|
1738
|
-
min-width:
|
|
1739
|
-
max-width:
|
|
1738
|
+
min-width: 72px;
|
|
1739
|
+
max-width: 104px;
|
|
1740
|
+
display: inline-block;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
sl-input.pricing-input::part(input) {
|
|
1744
|
+
text-align: right;
|
|
1740
1745
|
}
|
|
1741
1746
|
|
|
1747
|
+
.pricing-table--auto {
|
|
1748
|
+
width: auto;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
|
|
1742
1752
|
.pricing-info {
|
|
1743
1753
|
display: flex;
|
|
1744
1754
|
gap: 16px;
|
|
@@ -3160,6 +3170,19 @@ sl-details.learnings-panel::part(content) {
|
|
|
3160
3170
|
color: var(--muted);
|
|
3161
3171
|
}
|
|
3162
3172
|
|
|
3173
|
+
.cost-badge {
|
|
3174
|
+
display: inline-flex;
|
|
3175
|
+
align-items: center;
|
|
3176
|
+
gap: 2px;
|
|
3177
|
+
font-size: 0.7rem;
|
|
3178
|
+
padding: 1px 4px;
|
|
3179
|
+
border-radius: 4px;
|
|
3180
|
+
background: var(--bg-tertiary);
|
|
3181
|
+
color: var(--muted);
|
|
3182
|
+
margin-left: 4px;
|
|
3183
|
+
vertical-align: middle;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3163
3186
|
/* Preflight checks view */
|
|
3164
3187
|
|
|
3165
3188
|
.preflight-checks-view {
|
package/package.json
CHANGED
package/server/beads-reader.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
async function runBd(args, dbPath) {
|
|
5
8
|
const fullArgs = [...args, '--json', '--db', dbPath, '--readonly'];
|
|
6
|
-
const stdout =
|
|
9
|
+
const { stdout } = await execFileAsync('bd', fullArgs, {
|
|
7
10
|
encoding: 'utf8',
|
|
8
11
|
timeout: 10000,
|
|
9
12
|
maxBuffer: 10 * 1024 * 1024,
|
|
10
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
13
|
});
|
|
12
14
|
return JSON.parse(stdout);
|
|
13
15
|
}
|
|
@@ -30,12 +32,12 @@ function transformIssue(issue, deps) {
|
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function enrichWithDeps(issues, dbPath) {
|
|
35
|
+
async function enrichWithDeps(issues, dbPath) {
|
|
34
36
|
const needDeps = issues.filter((i) => i.dependency_count > 0);
|
|
35
37
|
if (needDeps.length === 0) {
|
|
36
38
|
return issues.map((i) => transformIssue(i, []));
|
|
37
39
|
}
|
|
38
|
-
const detailed = runBd(['show', ...needDeps.map((i) => i.id)], dbPath);
|
|
40
|
+
const detailed = await runBd(['show', ...needDeps.map((i) => i.id)], dbPath);
|
|
39
41
|
const depMap = new Map(detailed.map((d) => [d.id, d.dependencies || []]));
|
|
40
42
|
return issues.map((i) => transformIssue(i, depMap.get(i.id) || []));
|
|
41
43
|
}
|
|
@@ -44,18 +46,18 @@ export function dbExists(beadsDb) {
|
|
|
44
46
|
return existsSync(beadsDb);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
export function listIssues(beadsDb) {
|
|
49
|
+
export async function listIssues(beadsDb) {
|
|
48
50
|
try {
|
|
49
|
-
const issues = runBd(['list', '--limit', '0'], beadsDb);
|
|
51
|
+
const issues = await runBd(['list', '--limit', '0'], beadsDb);
|
|
50
52
|
return enrichWithDeps(issues, beadsDb);
|
|
51
53
|
} catch {
|
|
52
54
|
return [];
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
export function listIssuesByLabel(beadsDb, label) {
|
|
58
|
+
export async function listIssuesByLabel(beadsDb, label) {
|
|
57
59
|
try {
|
|
58
|
-
const issues = runBd(
|
|
60
|
+
const issues = await runBd(
|
|
59
61
|
['list', '--label-any', label, '--all', '--limit', '0'],
|
|
60
62
|
beadsDb,
|
|
61
63
|
);
|
|
@@ -65,12 +67,12 @@ export function listIssuesByLabel(beadsDb, label) {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export function listUnlinkedIssues(beadsDb) {
|
|
70
|
+
export async function listUnlinkedIssues(beadsDb) {
|
|
69
71
|
try {
|
|
70
|
-
const issues = runBd(['list', '--limit', '0'], beadsDb);
|
|
72
|
+
const issues = await runBd(['list', '--limit', '0'], beadsDb);
|
|
71
73
|
if (issues.length === 0) return [];
|
|
72
74
|
// bd list doesn't include labels — use bd show to get them
|
|
73
|
-
const detailed = runBd(['show', ...issues.map((i) => i.id)], beadsDb);
|
|
75
|
+
const detailed = await runBd(['show', ...issues.map((i) => i.id)], beadsDb);
|
|
74
76
|
const detailMap = new Map(detailed.map((d) => [d.id, d]));
|
|
75
77
|
const unlinked = issues.filter((i) => {
|
|
76
78
|
const d = detailMap.get(i.id);
|
|
@@ -87,9 +89,9 @@ export function listUnlinkedIssues(beadsDb) {
|
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
export function countIssuesByRunLabel(beadsDb) {
|
|
92
|
+
export async function countIssuesByRunLabel(beadsDb) {
|
|
91
93
|
try {
|
|
92
|
-
const rows = runBd(['label', 'list-all'], beadsDb);
|
|
94
|
+
const rows = await runBd(['label', 'list-all'], beadsDb);
|
|
93
95
|
const counts = {};
|
|
94
96
|
for (const row of rows) {
|
|
95
97
|
if (row.label.startsWith('run:')) {
|
|
@@ -102,18 +104,18 @@ export function countIssuesByRunLabel(beadsDb) {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
export function listDistinctRunLabels(beadsDb) {
|
|
107
|
+
export async function listDistinctRunLabels(beadsDb) {
|
|
106
108
|
try {
|
|
107
|
-
const rows = runBd(['label', 'list-all'], beadsDb);
|
|
109
|
+
const rows = await runBd(['label', 'list-all'], beadsDb);
|
|
108
110
|
return rows.filter((r) => r.label.startsWith('run:')).map((r) => r.label);
|
|
109
111
|
} catch {
|
|
110
112
|
return [];
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
export function getIssue(beadsDb, id) {
|
|
116
|
+
export async function getIssue(beadsDb, id) {
|
|
115
117
|
try {
|
|
116
|
-
const results = runBd(['show', id], beadsDb);
|
|
118
|
+
const results = await runBd(['show', id], beadsDb);
|
|
117
119
|
if (!results || results.length === 0) return null;
|
|
118
120
|
const issue = results[0];
|
|
119
121
|
return transformIssue(issue, issue.dependencies || []);
|
package/server/project-routes.js
CHANGED
|
@@ -1174,76 +1174,43 @@ export function createProjectScopedRoutes() {
|
|
|
1174
1174
|
});
|
|
1175
1175
|
|
|
1176
1176
|
// GET /api/projects/:projectId/costs — token & cost data
|
|
1177
|
+
// Reads per-iteration token_usage from each run's status.json.
|
|
1177
1178
|
router.get('/costs', requireWorcaDir, (req, res) => {
|
|
1178
1179
|
const { worcaDir } = req.project;
|
|
1179
|
-
const
|
|
1180
|
-
if (!existsSync(resultsDir)) return res.json({ ok: true, tokenData: {} });
|
|
1181
|
-
|
|
1180
|
+
const runs = discoverRuns(worcaDir);
|
|
1182
1181
|
const tokenData = {};
|
|
1183
1182
|
|
|
1184
|
-
for (const
|
|
1185
|
-
|
|
1186
|
-
const
|
|
1187
|
-
const stageNames = [];
|
|
1188
|
-
try {
|
|
1189
|
-
for (const sub of readdirSync(runDir, { withFileTypes: true })) {
|
|
1190
|
-
if (sub.isDirectory()) stageNames.push(sub.name);
|
|
1191
|
-
}
|
|
1192
|
-
} catch {
|
|
1193
|
-
continue;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (stageNames.length === 0) continue;
|
|
1197
|
-
tokenData[entry.name] = {};
|
|
1183
|
+
for (const run of runs) {
|
|
1184
|
+
const stages = run.stages || {};
|
|
1185
|
+
const runEntry = {};
|
|
1198
1186
|
|
|
1199
|
-
for (const stage of
|
|
1200
|
-
const
|
|
1187
|
+
for (const [stageName, stage] of Object.entries(stages)) {
|
|
1188
|
+
const iterations = stage.iterations || [];
|
|
1201
1189
|
const iters = [];
|
|
1202
|
-
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
cacheReadInputTokens = 0,
|
|
1215
|
-
cacheCreationInputTokens = 0;
|
|
1216
|
-
const models = [];
|
|
1217
|
-
for (const [model, usage] of Object.entries(mu)) {
|
|
1218
|
-
inputTokens += usage.inputTokens || 0;
|
|
1219
|
-
outputTokens += usage.outputTokens || 0;
|
|
1220
|
-
cacheReadInputTokens += usage.cacheReadInputTokens || 0;
|
|
1221
|
-
cacheCreationInputTokens += usage.cacheCreationInputTokens || 0;
|
|
1222
|
-
models.push(model);
|
|
1223
|
-
}
|
|
1224
|
-
iters.push({
|
|
1225
|
-
inputTokens,
|
|
1226
|
-
outputTokens,
|
|
1227
|
-
cacheReadInputTokens,
|
|
1228
|
-
cacheCreationInputTokens,
|
|
1229
|
-
models,
|
|
1230
|
-
});
|
|
1231
|
-
} catch {
|
|
1232
|
-
/* skip bad files */
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
} catch {
|
|
1236
|
-
/* skip */
|
|
1190
|
+
for (const iter of iterations) {
|
|
1191
|
+
const tu = iter.token_usage || {};
|
|
1192
|
+
iters.push({
|
|
1193
|
+
inputTokens: tu.input_tokens || 0,
|
|
1194
|
+
outputTokens: tu.output_tokens || 0,
|
|
1195
|
+
cacheReadInputTokens: tu.cache_read_input_tokens || 0,
|
|
1196
|
+
cacheCreationInputTokens: tu.cache_creation_input_tokens || 0,
|
|
1197
|
+
webSearchRequests: tu.web_search_requests || 0,
|
|
1198
|
+
cacheEphemeral1hTokens: tu.cache_ephemeral_1h_tokens || 0,
|
|
1199
|
+
cacheEphemeral5mTokens: tu.cache_ephemeral_5m_tokens || 0,
|
|
1200
|
+
models: tu.model ? [tu.model] : [],
|
|
1201
|
+
});
|
|
1237
1202
|
}
|
|
1238
|
-
if (iters.length > 0)
|
|
1203
|
+
if (iters.length > 0) runEntry[stageName] = iters;
|
|
1239
1204
|
}
|
|
1205
|
+
|
|
1206
|
+
if (Object.keys(runEntry).length > 0) tokenData[run.id] = runEntry;
|
|
1240
1207
|
}
|
|
1241
1208
|
|
|
1242
1209
|
res.json({ ok: true, tokenData });
|
|
1243
1210
|
});
|
|
1244
1211
|
|
|
1245
1212
|
// ─── Beads (project-scoped) ─────────────────────────────────────────
|
|
1246
|
-
router.get('/beads/issues', requireWorcaDir, (req, res) => {
|
|
1213
|
+
router.get('/beads/issues', requireWorcaDir, async (req, res) => {
|
|
1247
1214
|
const beadsDbPath = join(req.project.worcaDir, '..', '.beads', 'beads.db');
|
|
1248
1215
|
if (!dbExists(beadsDbPath)) {
|
|
1249
1216
|
return res.json({
|
|
@@ -1254,7 +1221,7 @@ export function createProjectScopedRoutes() {
|
|
|
1254
1221
|
});
|
|
1255
1222
|
}
|
|
1256
1223
|
try {
|
|
1257
|
-
const issues = listIssues(beadsDbPath);
|
|
1224
|
+
const issues = await listIssues(beadsDbPath);
|
|
1258
1225
|
res.json({ ok: true, issues, dbExists: true, dbPath: beadsDbPath });
|
|
1259
1226
|
} catch (err) {
|
|
1260
1227
|
res.status(500).json({ ok: false, error: err.message });
|
|
@@ -1269,7 +1236,7 @@ export function createProjectScopedRoutes() {
|
|
|
1269
1236
|
.json({ ok: false, error: 'Issue ID must be a positive integer' });
|
|
1270
1237
|
}
|
|
1271
1238
|
const beadsDbPath = join(req.project.worcaDir, '..', '.beads', 'beads.db');
|
|
1272
|
-
const issue = getIssue(beadsDbPath, issueId);
|
|
1239
|
+
const issue = await getIssue(beadsDbPath, issueId);
|
|
1273
1240
|
if (!issue) {
|
|
1274
1241
|
return res
|
|
1275
1242
|
.status(404)
|
package/server/versions.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
// server/versions.js — version fetching + caching for worca-cc and @worca/ui
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
5
|
import { readPreferences } from './preferences.js';
|
|
6
6
|
|
|
7
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
|
|
9
7
|
/** Cache: { data, timestamp } */
|
|
10
8
|
let _cache = null;
|
|
11
9
|
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
@@ -173,15 +171,19 @@ export function getDevPathVersions(sourceRepo) {
|
|
|
173
171
|
}
|
|
174
172
|
|
|
175
173
|
/**
|
|
176
|
-
* Get installed @worca/ui version
|
|
174
|
+
* Get globally installed @worca/ui version via npm.
|
|
175
|
+
* Falls back to own package.json if npm query fails.
|
|
177
176
|
* @returns {string|null}
|
|
178
177
|
*/
|
|
179
178
|
function getInstalledUiVersion() {
|
|
180
179
|
try {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
const output = execFileSync('npm', ['list', '-g', '@worca/ui', '--json'], {
|
|
181
|
+
encoding: 'utf8',
|
|
182
|
+
timeout: 5000,
|
|
183
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
184
|
+
});
|
|
185
|
+
const data = JSON.parse(output);
|
|
186
|
+
return data.dependencies?.['@worca/ui']?.version || null;
|
|
185
187
|
} catch {
|
|
186
188
|
return null;
|
|
187
189
|
}
|
|
@@ -21,10 +21,10 @@ export function createBeadsWatcher({ worcaDir, broadcaster, projectId }) {
|
|
|
21
21
|
|
|
22
22
|
function scheduleBeadsRefresh() {
|
|
23
23
|
if (BEADS_REFRESH_TIMER) clearTimeout(BEADS_REFRESH_TIMER);
|
|
24
|
-
BEADS_REFRESH_TIMER = setTimeout(() => {
|
|
24
|
+
BEADS_REFRESH_TIMER = setTimeout(async () => {
|
|
25
25
|
BEADS_REFRESH_TIMER = null;
|
|
26
26
|
try {
|
|
27
|
-
const issues = listIssues(beadsDbPath);
|
|
27
|
+
const issues = await listIssues(beadsDbPath);
|
|
28
28
|
broadcaster.broadcast(
|
|
29
29
|
'beads-update',
|
|
30
30
|
{
|
|
@@ -531,7 +531,7 @@ export function createMessageRouter({
|
|
|
531
531
|
);
|
|
532
532
|
return;
|
|
533
533
|
}
|
|
534
|
-
const issues = listIssues(beadsDbPath);
|
|
534
|
+
const issues = await listIssues(beadsDbPath);
|
|
535
535
|
ws.send(
|
|
536
536
|
JSON.stringify(
|
|
537
537
|
makeOk(req, { issues, dbExists: true, dbPath: beadsDbPath }),
|
|
@@ -552,7 +552,7 @@ export function createMessageRouter({
|
|
|
552
552
|
ws.send(JSON.stringify(makeOk(req, { issues: [], dbExists: false })));
|
|
553
553
|
return;
|
|
554
554
|
}
|
|
555
|
-
const issues = listUnlinkedIssues(beadsDbPath);
|
|
555
|
+
const issues = await listUnlinkedIssues(beadsDbPath);
|
|
556
556
|
ws.send(JSON.stringify(makeOk(req, { issues, dbExists: true })));
|
|
557
557
|
return;
|
|
558
558
|
}
|
|
@@ -569,7 +569,7 @@ export function createMessageRouter({
|
|
|
569
569
|
ws.send(JSON.stringify(makeOk(req, { refs: [] })));
|
|
570
570
|
return;
|
|
571
571
|
}
|
|
572
|
-
const refs = listDistinctRunLabels(beadsDbPath);
|
|
572
|
+
const refs = await listDistinctRunLabels(beadsDbPath);
|
|
573
573
|
ws.send(JSON.stringify(makeOk(req, { refs })));
|
|
574
574
|
return;
|
|
575
575
|
}
|
|
@@ -586,7 +586,7 @@ export function createMessageRouter({
|
|
|
586
586
|
ws.send(JSON.stringify(makeOk(req, { counts: {} })));
|
|
587
587
|
return;
|
|
588
588
|
}
|
|
589
|
-
const counts = countIssuesByRunLabel(beadsDbPath);
|
|
589
|
+
const counts = await countIssuesByRunLabel(beadsDbPath);
|
|
590
590
|
ws.send(JSON.stringify(makeOk(req, { counts })));
|
|
591
591
|
return;
|
|
592
592
|
}
|
|
@@ -612,7 +612,7 @@ export function createMessageRouter({
|
|
|
612
612
|
ws.send(JSON.stringify(makeOk(req, { issues: [], runId })));
|
|
613
613
|
return;
|
|
614
614
|
}
|
|
615
|
-
const issues = listIssuesByLabel(beadsDbPath, `run:${runId}`);
|
|
615
|
+
const issues = await listIssuesByLabel(beadsDbPath, `run:${runId}`);
|
|
616
616
|
ws.send(JSON.stringify(makeOk(req, { issues, runId })));
|
|
617
617
|
return;
|
|
618
618
|
}
|