@beastmode-develeap/beastmode 0.1.110 → 0.1.111

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.
@@ -15,7 +15,7 @@
15
15
  }
16
16
  </script>
17
17
  <!--BOARD_DATA-->
18
- <script>window.__BUILD_STAMP__ = "20260418-082837-7de1d16";</script>
18
+ <script>window.__BUILD_STAMP__ = "20260418-125234-6f01092";</script>
19
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
21
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -2149,6 +2149,115 @@ input[type="range"]::-webkit-slider-thumb {
2149
2149
  gap: 8px;
2150
2150
  }
2151
2151
 
2152
+ /* ================================================================
2153
+ COSTS PAGE
2154
+ ================================================================ */
2155
+
2156
+ .costs-table-wrapper {
2157
+ overflow-x: auto;
2158
+ -webkit-overflow-scrolling: touch;
2159
+ }
2160
+
2161
+ .costs-table {
2162
+ width: 100%;
2163
+ border-collapse: collapse;
2164
+ font-family: var(--font-sans);
2165
+ font-size: 13px;
2166
+ }
2167
+
2168
+ .costs-th {
2169
+ text-align: left;
2170
+ padding: 10px 12px;
2171
+ font-size: 11px;
2172
+ font-weight: 600;
2173
+ text-transform: uppercase;
2174
+ letter-spacing: 0.5px;
2175
+ color: var(--text-muted);
2176
+ border-bottom: 1px solid var(--border);
2177
+ cursor: pointer;
2178
+ user-select: none;
2179
+ white-space: nowrap;
2180
+ }
2181
+
2182
+ .costs-th:hover {
2183
+ color: var(--accent);
2184
+ }
2185
+
2186
+ .costs-tr {
2187
+ cursor: pointer;
2188
+ transition: background 0.15s ease;
2189
+ }
2190
+
2191
+ .costs-tr:hover {
2192
+ background: var(--bg-card-hover);
2193
+ }
2194
+
2195
+ .costs-td {
2196
+ padding: 10px 12px;
2197
+ border-bottom: 1px solid var(--border-subtle);
2198
+ color: var(--text);
2199
+ vertical-align: middle;
2200
+ }
2201
+
2202
+ .costs-td-mono {
2203
+ font-family: var(--font-mono);
2204
+ font-size: 13px;
2205
+ }
2206
+
2207
+ .costs-td-name {
2208
+ font-weight: 500;
2209
+ max-width: 300px;
2210
+ overflow: hidden;
2211
+ text-overflow: ellipsis;
2212
+ white-space: nowrap;
2213
+ }
2214
+
2215
+ .costs-phase-chart {
2216
+ padding: 16px 0;
2217
+ }
2218
+
2219
+ .costs-phase-row {
2220
+ display: flex;
2221
+ align-items: center;
2222
+ gap: 12px;
2223
+ padding: 6px 16px;
2224
+ }
2225
+
2226
+ .costs-phase-label {
2227
+ width: 100px;
2228
+ font-size: 13px;
2229
+ font-weight: 500;
2230
+ color: var(--text-secondary);
2231
+ text-transform: capitalize;
2232
+ flex-shrink: 0;
2233
+ }
2234
+
2235
+ .costs-phase-bar-bg {
2236
+ flex: 1;
2237
+ height: 24px;
2238
+ background: var(--accent-subtle);
2239
+ border-radius: var(--radius-xs);
2240
+ overflow: hidden;
2241
+ }
2242
+
2243
+ .costs-phase-bar-fill {
2244
+ height: 100%;
2245
+ background: var(--accent);
2246
+ border-radius: var(--radius-xs);
2247
+ transition: width 0.4s ease;
2248
+ min-width: 2px;
2249
+ }
2250
+
2251
+ .costs-phase-amount {
2252
+ width: 80px;
2253
+ text-align: right;
2254
+ font-family: var(--font-mono);
2255
+ font-size: 13px;
2256
+ font-weight: 500;
2257
+ color: var(--text);
2258
+ flex-shrink: 0;
2259
+ }
2260
+
2152
2261
  /* ================================================================
2153
2262
  RESPONSIVE
2154
2263
  ================================================================ */
@@ -2176,6 +2285,10 @@ input[type="range"]::-webkit-slider-thumb {
2176
2285
  /* Detail sidebar: full-width on tablets */
2177
2286
  .detail-sidebar { width: 100vw !important; max-width: 100vw; }
2178
2287
  .detail-resize-handle { display: none; }
2288
+ /* Costs page */
2289
+ .costs-phase-label { width: 70px; font-size: 12px; }
2290
+ .costs-phase-amount { width: 60px; font-size: 12px; }
2291
+ .costs-td-name { max-width: 150px; }
2179
2292
  }
2180
2293
 
2181
2294
  @media (max-width: 600px) {
@@ -2192,6 +2305,9 @@ input[type="range"]::-webkit-slider-thumb {
2192
2305
  .detail-sidebar { padding: 16px; }
2193
2306
  .detail-header h3 { font-size: 14px; }
2194
2307
  .update-body { font-size: 12px; }
2308
+ /* Costs page */
2309
+ .costs-table { font-size: 12px; }
2310
+ .costs-th, .costs-td { padding: 8px 6px; }
2195
2311
  }
2196
2312
 
2197
2313
  /* ================================================================
@@ -2516,6 +2632,7 @@ function Icon({ name, size = 18, className = '' }) {
2516
2632
  lightbulb: html`<path d="M8 1a5 5 0 00-3 9c.5.6.8 1.2 1 1.8V13h4v-1.2c.2-.6.5-1.2 1-1.8A5 5 0 008 1z"/><line x1="6" y1="13.5" x2="10" y2="13.5"/><line x1="6.5" y1="15" x2="9.5" y2="15"/>`,
2517
2633
  help: html`<circle cx="8" cy="8" r="6.5"/><path d="M6 6.5a2 2 0 013.7 1c0 1.5-1.7 1.5-1.7 3"/><circle cx="8" cy="12" r="0.5" fill="currentColor" stroke="none"/>`,
2518
2634
  filter: html`<polygon points="1.5,2 14.5,2 9.5,8.5 9.5,13 6.5,14.5 6.5,8.5"/>`,
2635
+ costs: html`<circle cx="8" cy="8" r="6.5"/><path d="M8 3.5v1m0 7v1M6 10.5c0 .8.9 1.5 2 1.5s2-.7 2-1.5S9.1 9 8 9s-2-.7-2-1.5S6.9 6 8 6s2 .7 2 1.5"/>`,
2519
2636
  };
2520
2637
 
2521
2638
  return html`
@@ -2606,6 +2723,7 @@ function Sidebar({ currentHash, factoryName, theme, onToggleTheme, selectedProje
2606
2723
  ...(selectedProject !== 'all' ? [{ path: '#/board', icon: 'board', label: 'Board' }] : []),
2607
2724
  ...(selectedProject !== 'all' ? [{ path: '#/strategy', icon: 'strategy', label: 'Strategy' }] : []),
2608
2725
  { path: '#/runs', icon: 'runs', label: 'Runs' },
2726
+ { path: '#/costs', icon: 'costs', label: 'Costs' },
2609
2727
  { path: '#/learnings', icon: 'lightbulb', label: 'Learnings' },
2610
2728
  { path: '#/projects', icon: 'projects', label: 'Projects' },
2611
2729
  { path: '#/extensions', icon: 'extensions', label: 'Extensions' },
@@ -6895,6 +7013,216 @@ function StrategyPage({ selectedProject }) {
6895
7013
  `;
6896
7014
  }
6897
7015
 
7016
+ // ================================================================
7017
+ // Costs Page
7018
+ // ================================================================
7019
+
7020
+ function CostsPage({ selectedProject }) {
7021
+ const [costsByItems, setCostsByItems] = useState({});
7022
+ const [boardItems, setBoardItems] = useState([]);
7023
+ const [phaseData, setPhaseData] = useState([]);
7024
+ const [loading, setLoading] = useState(true);
7025
+ const [error, setError] = useState(null);
7026
+ const [sortCol, setSortCol] = useState('cost');
7027
+ const [sortDir, setSortDir] = useState('desc');
7028
+
7029
+ useEffect(() => {
7030
+ let cancelled = false;
7031
+
7032
+ async function fetchCosts() {
7033
+ try {
7034
+ const costQs = (selectedProject && selectedProject !== 'all')
7035
+ ? '?board=' + encodeURIComponent(selectedProject)
7036
+ : '';
7037
+
7038
+ const [costsData, boardData] = await Promise.all([
7039
+ api('GET', '/api/costs/by-items' + costQs),
7040
+ api('GET', '/api/board/items'),
7041
+ ]);
7042
+
7043
+ if (cancelled) return;
7044
+ setCostsByItems(costsData || {});
7045
+ setBoardItems((boardData && boardData.items) || []);
7046
+
7047
+ const itemIds = Object.keys(costsData || {});
7048
+ if (itemIds.length > 0) {
7049
+ const phaseMap = {};
7050
+ const summaryResults = await Promise.allSettled(
7051
+ itemIds.map(id => {
7052
+ const qs = (selectedProject && selectedProject !== 'all')
7053
+ ? '?board=' + encodeURIComponent(selectedProject)
7054
+ : '';
7055
+ return api('GET', '/api/items/' + id + '/costs/summary' + qs);
7056
+ })
7057
+ );
7058
+ summaryResults.forEach(result => {
7059
+ if (result.status === 'fulfilled' && result.value && result.value.phases) {
7060
+ for (const [phaseName, phaseInfo] of Object.entries(result.value.phases)) {
7061
+ phaseMap[phaseName] = (phaseMap[phaseName] || 0) + (phaseInfo.cost_usd || 0);
7062
+ }
7063
+ }
7064
+ });
7065
+ const sorted = Object.entries(phaseMap)
7066
+ .map(([phase, cost]) => ({ phase, cost }))
7067
+ .sort((a, b) => b.cost - a.cost);
7068
+ if (!cancelled) setPhaseData(sorted);
7069
+ }
7070
+ } catch (e) {
7071
+ if (!cancelled) setError(e.message || 'Failed to load cost data');
7072
+ } finally {
7073
+ if (!cancelled) setLoading(false);
7074
+ }
7075
+ }
7076
+
7077
+ setLoading(true);
7078
+ setError(null);
7079
+ fetchCosts();
7080
+ const interval = setInterval(fetchCosts, 30000);
7081
+ return () => { cancelled = true; clearInterval(interval); };
7082
+ }, [selectedProject]);
7083
+
7084
+ function formatTokens(n) {
7085
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
7086
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
7087
+ return String(n);
7088
+ }
7089
+
7090
+ function handleSort(col) {
7091
+ if (sortCol === col) {
7092
+ setSortDir(sortDir === 'asc' ? 'desc' : 'asc');
7093
+ } else {
7094
+ setSortCol(col);
7095
+ setSortDir(col === 'name' || col === 'status' ? 'asc' : 'desc');
7096
+ }
7097
+ }
7098
+
7099
+ if (loading) return html`<${SkeletonLoader} type="stats" />`;
7100
+ if (error) return html`<div class="page-content"><div class="error-msg">${error}</div></div>`;
7101
+
7102
+ const mergedRows = Object.entries(costsByItems).map(([itemId, costs]) => {
7103
+ const item = boardItems.find(i => String(i.id) === String(itemId));
7104
+ return {
7105
+ id: itemId,
7106
+ name: item ? item.name : 'Item #' + itemId,
7107
+ status: item ? item.status : 'Unknown',
7108
+ totalTokens: (costs.total_input_tokens || 0) + (costs.total_output_tokens || 0),
7109
+ costUsd: costs.total_cost_usd || 0,
7110
+ records: costs.record_count || 0,
7111
+ };
7112
+ });
7113
+
7114
+ if (mergedRows.length === 0) {
7115
+ return html`<div class="page-content">
7116
+ <${EmptyState} icon="costs" title="No Cost Data" description="Cost data will appear here as tasks are processed by the pipeline." />
7117
+ </div>`;
7118
+ }
7119
+
7120
+ const totalSpend = mergedRows.reduce((sum, r) => sum + r.costUsd, 0);
7121
+ const totalTokens = mergedRows.reduce((sum, r) => sum + r.totalTokens, 0);
7122
+ const tasksTracked = mergedRows.length;
7123
+ const avgCostPerTask = tasksTracked > 0 ? totalSpend / tasksTracked : 0;
7124
+ const maxPhaseCost = phaseData.length > 0 ? Math.max(...phaseData.map(p => p.cost)) : 0;
7125
+
7126
+ const sortedRows = [...mergedRows].sort((a, b) => {
7127
+ let cmp = 0;
7128
+ switch (sortCol) {
7129
+ case 'id': cmp = Number(a.id) - Number(b.id); break;
7130
+ case 'name': cmp = a.name.localeCompare(b.name); break;
7131
+ case 'status': cmp = a.status.localeCompare(b.status); break;
7132
+ case 'tokens': cmp = a.totalTokens - b.totalTokens; break;
7133
+ case 'cost': cmp = a.costUsd - b.costUsd; break;
7134
+ case 'records': cmp = a.records - b.records; break;
7135
+ }
7136
+ return sortDir === 'asc' ? cmp : -cmp;
7137
+ });
7138
+
7139
+ const sortIndicator = (col) => sortCol === col ? (sortDir === 'asc' ? ' \u25B2' : ' \u25BC') : '';
7140
+
7141
+ return html`
7142
+ <div class="page-content">
7143
+ <div class="page-header">
7144
+ <h2>Factory Costs</h2>
7145
+ <p>Token usage and cost analytics across all tasks</p>
7146
+ </div>
7147
+
7148
+ <div class="stat-grid mb-24">
7149
+ <div class="stat-card">
7150
+ <div class="stat-value">$${totalSpend.toFixed(2)}</div>
7151
+ <div class="stat-label">Total Spend</div>
7152
+ <div class="stat-dot"></div>
7153
+ </div>
7154
+ <div class="stat-card">
7155
+ <div class="stat-value">${formatTokens(totalTokens)}</div>
7156
+ <div class="stat-label">Total Tokens</div>
7157
+ <div class="stat-dot"></div>
7158
+ </div>
7159
+ <div class="stat-card">
7160
+ <div class="stat-value">${tasksTracked}</div>
7161
+ <div class="stat-label">Tasks Tracked</div>
7162
+ <div class="stat-dot"></div>
7163
+ </div>
7164
+ <div class="stat-card">
7165
+ <div class="stat-value">$${avgCostPerTask.toFixed(2)}</div>
7166
+ <div class="stat-label">Avg Cost / Task</div>
7167
+ <div class="stat-dot"></div>
7168
+ </div>
7169
+ </div>
7170
+
7171
+ <div class="card">
7172
+ <div class="card-header">
7173
+ <h3>Cost by Task</h3>
7174
+ <span class="badge badge-accent">${sortedRows.length} tasks</span>
7175
+ </div>
7176
+ <div class="costs-table-wrapper">
7177
+ <table class="costs-table">
7178
+ <thead>
7179
+ <tr>
7180
+ <th class="costs-th" onClick=${() => handleSort('id')}>ID${sortIndicator('id')}</th>
7181
+ <th class="costs-th" onClick=${() => handleSort('name')}>Task${sortIndicator('name')}</th>
7182
+ <th class="costs-th" onClick=${() => handleSort('status')}>Status${sortIndicator('status')}</th>
7183
+ <th class="costs-th" onClick=${() => handleSort('tokens')}>Tokens${sortIndicator('tokens')}</th>
7184
+ <th class="costs-th" onClick=${() => handleSort('cost')}>Cost (USD)${sortIndicator('cost')}</th>
7185
+ <th class="costs-th" onClick=${() => handleSort('records')}>Records${sortIndicator('records')}</th>
7186
+ </tr>
7187
+ </thead>
7188
+ <tbody>
7189
+ ${sortedRows.map(row => html`
7190
+ <tr key=${row.id} class="costs-tr" onClick=${() => navigate('#/board?item=' + row.id)}>
7191
+ <td class="costs-td costs-td-mono">#${row.id}</td>
7192
+ <td class="costs-td costs-td-name">${row.name}</td>
7193
+ <td class="costs-td"><span class=${'badge ' + statusBadgeClass(row.status)}>${row.status}</span></td>
7194
+ <td class="costs-td costs-td-mono">${formatTokens(row.totalTokens)}</td>
7195
+ <td class="costs-td costs-td-mono">$${row.costUsd.toFixed(2)}</td>
7196
+ <td class="costs-td costs-td-mono">${row.records}</td>
7197
+ </tr>
7198
+ `)}
7199
+ </tbody>
7200
+ </table>
7201
+ </div>
7202
+ </div>
7203
+
7204
+ ${phaseData.length > 0 && html`
7205
+ <div class="card" style="margin-top:24px;">
7206
+ <div class="card-header">
7207
+ <h3>Cost by Phase</h3>
7208
+ </div>
7209
+ <div class="costs-phase-chart">
7210
+ ${phaseData.map(p => html`
7211
+ <div key=${p.phase} class="costs-phase-row">
7212
+ <div class="costs-phase-label">${p.phase}</div>
7213
+ <div class="costs-phase-bar-bg">
7214
+ <div class="costs-phase-bar-fill" style=${'width:' + (maxPhaseCost > 0 ? (p.cost / maxPhaseCost * 100) : 0) + '%'}></div>
7215
+ </div>
7216
+ <div class="costs-phase-amount">$${p.cost.toFixed(2)}</div>
7217
+ </div>
7218
+ `)}
7219
+ </div>
7220
+ </div>
7221
+ `}
7222
+ </div>
7223
+ `;
7224
+ }
7225
+
6898
7226
  // ================================================================
6899
7227
  // Learnings Page
6900
7228
  // ================================================================
@@ -7152,6 +7480,7 @@ function App() {
7152
7480
  case '#/board': page = html`<${BoardPage} selectedProject=${selectedProject} />`; break;
7153
7481
  case '#/strategy': page = html`<${StrategyPage} selectedProject=${selectedProject} />`; break;
7154
7482
  case '#/runs': page = html`<${RunsPage} selectedProject=${selectedProject} />`; break;
7483
+ case '#/costs': page = html`<${CostsPage} selectedProject=${selectedProject} />`; break;
7155
7484
  case '#/learnings': page = html`<${LearningsPage} selectedProject=${selectedProject} />`; break;
7156
7485
  case '#/projects': page = html`<${ProjectsPage} selectedProject=${selectedProject} onProjectChange=${setSelectedProject} />`; break;
7157
7486
  case '#/extensions': page = html`<${ExtensionsPage} selectedProject=${selectedProject} />`; break;
@@ -1 +1 @@
1
- 20260418-082837-7de1d16
1
+ 20260418-125234-6f01092
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beastmode-develeap/beastmode",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "description": "BeastMode Dark Factory — turn intent into verified software",
5
5
  "type": "module",
6
6
  "bin": {