diff --git a/src/frontend/analytics.js b/src/frontend/analytics.js index ddc81d5..0d7dc99 100644 --- a/src/frontend/analytics.js +++ b/src/frontend/analytics.js @@ -90,6 +90,11 @@ async function renderAnalytics(container) { coverageparts.push('Claude Extension \u2713'); if (byAgent['codex'] && byAgent['codex'].sessions > 0) coverageparts.push('Codex ~est.'); + if (byAgent['qwen'] && byAgent['qwen'].sessions > 0) { + coverageparts.push(byAgent['qwen'].unavailable + ? 'Qwen tokens only' + : 'Qwen Code \u2713'); + } if (byAgent['opencode'] && byAgent['opencode'].sessions > 0) coverageparts.push(byAgent['opencode'].estimated ? 'OpenCode ~est.' @@ -115,8 +120,10 @@ async function renderAnalytics(container) { agentEntriesOv.forEach(function(entry) { var name = entry[0]; var info = entry[1]; var pct = maxAgentCostOv > 0 ? (info.cost / maxAgentCostOv * 100) : 0; - var label = { 'claude': 'Claude Code', 'claude-ext': 'Claude Ext', 'codex': 'Codex', 'opencode': 'OpenCode', 'cursor': 'Cursor', 'kiro': 'Kiro' }[name] || name; - var estMark = info.estimated ? ' ~est.' : ''; + var label = getToolLabel(name); + var estMark = info.unavailable + ? ' tokens only' + : (info.estimated ? ' ~est.' : ''); html += '
'; diff --git a/src/frontend/app.js b/src/frontend/app.js index 3c6c318..844cee7 100644 --- a/src/frontend/app.js +++ b/src/frontend/app.js @@ -103,6 +103,33 @@ function getSessionDisplayName(session) { || ''; } +var TOOL_META = { + claude: { label: 'Claude Code', shortLabel: 'claude', color: '#60a5fa' }, + 'claude-ext': { label: 'Claude Ext', shortLabel: 'claude ext', color: '#60a5fa' }, + codex: { label: 'Codex', shortLabel: 'codex', color: '#22d3ee' }, + qwen: { label: 'Qwen Code', shortLabel: 'qwen', color: '#fbbf24' }, + cursor: { label: 'Cursor', shortLabel: 'cursor', color: '#4a9eff' }, + opencode: { label: 'OpenCode', shortLabel: 'opencode', color: '#c084fc' }, + kiro: { label: 'Kiro', shortLabel: 'kiro', color: '#fb923c' } +}; + +function getToolLabel(tool, shortLabel) { + var meta = TOOL_META[tool] || { label: tool || 'unknown', shortLabel: tool || 'unknown' }; + return shortLabel ? meta.shortLabel : meta.label; +} + +function getResumeCommand(tool, sessionId, project) { + if (tool === 'codex') return 'codex resume ' + sessionId; + if (tool === 'qwen') return 'qwen -r ' + sessionId; + if (tool === 'cursor') return 'cursor ' + (project ? '"' + project + '"' : '.'); + return 'claude --resume ' + sessionId; +} + +function getConvertTargets(tool) { + if (tool !== 'claude' && tool !== 'codex' && tool !== 'qwen') return []; + return ['claude', 'codex', 'qwen'].filter(function(target) { return target !== tool; }); +} + // ── Utilities ────────────────────────────────────────────────── function timeAgo(dateStr) { @@ -185,6 +212,11 @@ function estimateCost(fileSize) { return tokens * 0.3 * (3.0 / 1e6) + tokens * 0.7 * (15.0 / 1e6); } +function getEstimatedSessionCost(session) { + if (!session || session.tool === 'qwen') return 0; + return estimateCost(session.file_size); +} + // ── Subscription service plans (pricing as of 2025) ───────────── var SERVICE_PLANS = { 'Claude': { label: 'Claude (Anthropic)', plans: [ @@ -714,12 +746,12 @@ function renderCard(s, idx) { var isSelected = selectedIds.has(s.id); var isFocused = focusedIndex === idx; var sessionTags = tags[s.id] || []; - var cost = estimateCost(s.file_size); + var cost = getEstimatedSessionCost(s); var costStr = cost > 0 ? '~$' + cost.toFixed(2) : ''; var projName = getProjectName(s.project); var projColor = getProjectColor(projName); var toolClass = 'tool-' + s.tool; - var toolLabel = s.tool === 'claude-ext' ? 'claude ext' : s.tool; + var toolLabel = getToolLabel(s.tool, true); var classes = 'card'; if (isSelected) classes += ' selected'; @@ -820,7 +852,7 @@ function renderListCard(s, idx) { if (isFocused) classes += ' focused'; var html = '