opencode-top 3.3.6 → 3.4.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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/cli.mjs +202 -144
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -73,3 +73,18 @@ cd opencode-top
73
73
  npm install
74
74
  npm start live # run from source with tsx
75
75
  ```
76
+
77
+ ## Maintenance
78
+
79
+ ### Publishing a new version
80
+
81
+ 1. Make and commit your changes
82
+ 2. Bump the version in `package.json`
83
+ 3. Build and publish:
84
+
85
+ ```bash
86
+ npm publish --access public
87
+ ```
88
+
89
+ The `prepublishOnly` script runs the build automatically before publishing.
90
+ The bin entry must point to `bin/octop.js` (not `bin/octop.mjs`) — `octop.js` loads the pre-built `dist/cli.mjs` and has no runtime dependencies.
package/dist/cli.mjs CHANGED
@@ -18403,19 +18403,32 @@ var import_react32 = __toESM(require_react(), 1);
18403
18403
 
18404
18404
  // src/ui/theme.ts
18405
18405
  var colors = {
18406
- bg: "#1a1a2e",
18407
- bgSecondary: "#16213e",
18408
- border: "#0f3460",
18409
- accent: "#e94560",
18410
- accentDim: "#533483",
18411
- text: "#eaeaea",
18412
- textDim: "#888",
18406
+ bg: "#0a0d0a",
18407
+ bgSecondary: "#0f130f",
18408
+ bgHighlight: "#162016",
18409
+ border: "#1e2e1e",
18410
+ borderBright: "#2d4a2d",
18411
+ accent: "#4ade80",
18412
+ // bright green — primary accent
18413
+ accentDim: "#22543d",
18414
+ accentAlt: "#86efac",
18415
+ // light green
18416
+ text: "#d4e8d4",
18417
+ // slightly green-tinted white
18418
+ textDim: "#527a52",
18419
+ textMuted: "#2d4a2d",
18413
18420
  success: "#4ade80",
18414
18421
  warning: "#fbbf24",
18415
18422
  error: "#f87171",
18416
- info: "#60a5fa",
18417
- purple: "#a855f7",
18418
- cyan: "#22d3ee"
18423
+ info: "#6ee7b7",
18424
+ // mint/emerald
18425
+ purple: "#a3e635",
18426
+ // lime — replaces purple for section headers
18427
+ cyan: "#34d399",
18428
+ // emerald
18429
+ teal: "#2dd4bf",
18430
+ peach: "#86efac"
18431
+ // light green instead of orange
18419
18432
  };
18420
18433
 
18421
18434
  // src/ui/components/TabBar.tsx
@@ -18427,22 +18440,19 @@ var TABS = [
18427
18440
  { id: "overview", label: "Overview", key: "3" }
18428
18441
  ];
18429
18442
  function TabBarInner({ activeScreen, lastRefresh }) {
18430
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", children: [
18431
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.accent, bold: true, children: "oc-top " }),
18432
- TABS.map((tab2) => {
18433
- const isActive = tab2.id === activeScreen;
18434
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: isActive ? colors.accent : colors.textDim, bold: isActive, children: [
18435
- "[",
18436
- tab2.key,
18437
- "]",
18438
- isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: colors.text, children: [
18439
- " ",
18440
- tab2.label
18441
- ] }) : ` ${tab2.label}`
18442
- ] }) }, tab2.id);
18443
- }),
18444
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1 }),
18445
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textDim, children: lastRefresh.toLocaleTimeString() })
18443
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", height: 2, children: [
18444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", alignItems: "center", children: [
18445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.accent, bold: true, children: "\u25C6 oc-top" }),
18446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textMuted, children: " \u2502 " }),
18447
+ TABS.map((tab2, i) => {
18448
+ const isActive = tab2.id === activeScreen;
18449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginRight: 1, children: isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { backgroundColor: colors.bgHighlight, color: colors.accent, bold: true, children: ` ${tab2.key}:${tab2.label} ` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textDim, children: ` ${tab2.key}:${tab2.label} ` }) }, tab2.id);
18450
+ }),
18451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1 }),
18452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textMuted, children: "\u21BB " }),
18453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textDim, children: lastRefresh.toLocaleTimeString() })
18454
+ ] }),
18455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { paddingX: 0, height: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.border, children: "\u2500".repeat(200) }) })
18446
18456
  ] });
18447
18457
  }
18448
18458
  var TabBar = (0, import_react22.memo)(TabBarInner);
@@ -18454,11 +18464,14 @@ var import_react28 = __toESM(require_react(), 1);
18454
18464
  var import_react23 = __toESM(require_react(), 1);
18455
18465
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
18456
18466
  function StatusBarInner({ hints, info }) {
18457
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", children: [
18458
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.textDim, children: hints }),
18459
- info && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
18460
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { flexGrow: 1 }),
18461
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.info, children: info })
18467
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", height: 2, children: [
18468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { paddingX: 0, height: 1, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.border, children: "\u2500".repeat(200) }) }),
18469
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", children: [
18470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.textMuted, children: hints }),
18471
+ info && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
18472
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { flexGrow: 1 }),
18473
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.info, children: info })
18474
+ ] })
18462
18475
  ] })
18463
18476
  ] });
18464
18477
  }
@@ -20966,9 +20979,12 @@ function AgentTreeInner({ workflows, selectedId, flatNodes, maxHeight = 20 }) {
20966
20979
  const visibleNodes = flatNodes.slice(startIndex, startIndex + visibleCount);
20967
20980
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, height: maxHeight, overflow: "hidden", children: [
20968
20981
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { marginBottom: 1, flexDirection: "row", children: [
20969
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.accent, bold: true, children: "Sessions" }),
20982
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.purple, bold: true, children: "SESSIONS" }),
20970
20983
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1 }),
20971
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.textDim, children: workflows.length })
20984
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: colors.textMuted, children: [
20985
+ workflows.length,
20986
+ " workflows"
20987
+ ] })
20972
20988
  ] }),
20973
20989
  visibleNodes.map((node) => {
20974
20990
  const isSelected = node.id === selectedId;
@@ -20977,32 +20993,19 @@ function AgentTreeInner({ workflows, selectedId, flatNodes, maxHeight = 20 }) {
20977
20993
  const cost = getSessionCostSingle(node.session, pricing);
20978
20994
  const agentName = node.session.interactions[0]?.agent ?? null;
20979
20995
  const indent = " ".repeat(node.depth);
20980
- const prefix = node.depth === 0 ? node.hasChildren ? "\u25B6 " : "\u25CF " : node.hasChildren ? "\u251C\u25B6 " : "\u251C\u2500 ";
20996
+ const prefix = node.depth === 0 ? node.hasChildren ? "\u25B8 " : " " : node.hasChildren ? "\u2570\u25B8 " : "\u2570\u2500 ";
20981
20997
  const label = node.depth === 0 ? truncate2(node.session.title ?? node.session.projectName ?? "Untitled", 22) : truncate2(`[${agentName ?? "?"}] ${node.session.title ?? ""}`, 20);
20982
20998
  const date = formatDate(node.session.timeCreated);
20983
20999
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "row", height: 1, children: [
20984
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: 1 }),
20985
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
20986
- Text,
20987
- {
20988
- color: isSelected ? void 0 : colors.text,
20989
- backgroundColor: isSelected ? colors.accent : void 0,
20990
- bold: isSelected,
20991
- children: [
20992
- indent,
20993
- prefix,
20994
- label
20995
- ]
20996
- }
20997
- ),
21000
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.bgHighlight, backgroundColor: colors.accent, bold: true, children: `\u25B6 ${indent}${prefix}${label}` }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: node.depth === 0 ? colors.text : colors.textDim, children: ` ${indent}${prefix}${label}` }),
20998
21001
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1 }),
20999
- date && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: colors.textDim, children: [
21002
+ date && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: colors.textMuted, children: [
21000
21003
  date,
21001
21004
  " "
21002
21005
  ] }),
21003
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.textDim, dimColor: true, children: formatTokens(tokens.total) }),
21006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.textDim, children: formatTokens(tokens.total) }),
21004
21007
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: 1 }),
21005
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.success, children: formatCost(cost) }),
21008
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: cost.greaterThan(0) ? colors.success : colors.textMuted, children: formatCost(cost) }),
21006
21009
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: 1 })
21007
21010
  ] }, node.id);
21008
21011
  }),
@@ -21148,8 +21151,8 @@ function ProgressBar({
21148
21151
  const filled = Math.round(pct * width);
21149
21152
  const empty = width - filled;
21150
21153
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
21151
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color, children: "\u2588".repeat(filled) }),
21152
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.border, children: "\u2591".repeat(empty) }),
21154
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color, children: "\u2593".repeat(filled) }),
21155
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textMuted, children: "\u2591".repeat(empty) }),
21153
21156
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: colors.textDim, children: [
21154
21157
  " ",
21155
21158
  Math.round(pct * 100),
@@ -21204,35 +21207,45 @@ function DetailsPanelInner({ workflow, height }) {
21204
21207
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexDirection: "column", paddingX: 1, height, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textDim, children: "Select a session" }) });
21205
21208
  }
21206
21209
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, height, overflow: "hidden", children: [
21207
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.accent, bold: true, children: "Details" }) }),
21208
21210
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
21209
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.cyan, bold: true, children: data.title }),
21210
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textDim, children: data.project }),
21211
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.accent, bold: true, children: data.title }),
21212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: colors.textMuted, children: [
21213
+ "\u25CE ",
21214
+ data.project
21215
+ ] })
21216
+ ] }),
21217
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
21218
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 STATS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
21211
21219
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Tokens", value: formatTokens3(data.tokens) }),
21212
21220
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Cost", value: `$${data.cost.toFixed(4)}`, color: colors.success }),
21213
21221
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Duration", value: formatDuration(data.duration) }),
21214
21222
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Rate", value: `${data.outputRate.toFixed(0)} tok/s` }),
21215
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Calls", value: data.calls.toString() }),
21216
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textDim, children: "Context" }) }),
21223
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Calls", value: data.calls.toString() })
21224
+ ] }),
21225
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
21226
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textMuted, children: "context" }),
21217
21227
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
21218
21228
  ProgressBar,
21219
21229
  {
21220
21230
  value: data.contextUsage,
21221
21231
  max: data.contextWindow,
21222
- color: data.contextPct > 0.8 ? colors.warning : colors.accent
21232
+ color: data.contextPct > 0.8 ? colors.warning : colors.teal
21223
21233
  }
21224
21234
  )
21225
21235
  ] }),
21226
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "Models" }) }),
21236
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 MODELS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
21227
21237
  Array.from(data.modelBreakdown.entries()).slice(0, 3).map(([model, stats]) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "row", children: [
21228
21238
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.text, children: model.slice(0, 25) }),
21229
21239
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexGrow: 1 }),
21230
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.textDim, children: stats.count }),
21240
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: colors.textMuted, children: [
21241
+ stats.count,
21242
+ "\xD7"
21243
+ ] }),
21231
21244
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { width: 1 }),
21232
21245
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.info, children: formatTokens3(stats.tokens) })
21233
21246
  ] }, model)),
21234
21247
  data.topTools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
21235
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "Top Tools" }) }),
21248
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 TOOLS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
21236
21249
  data.topTools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "row", children: [
21237
21250
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.text, children: tool.name.slice(0, 20) }),
21238
21251
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexGrow: 1 }),
@@ -21244,7 +21257,7 @@ function DetailsPanelInner({ workflow, height }) {
21244
21257
  ] }, tool.name))
21245
21258
  ] }),
21246
21259
  data.hasSubAgents && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
21247
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "Agent Chain" }) }),
21260
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 AGENT CHAIN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
21248
21261
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AgentChainGraph, { agentTree: data.agentTree })
21249
21262
  ] })
21250
21263
  ] });
@@ -21618,7 +21631,7 @@ function SessionsScreenInner({
21618
21631
  }, [selectedNode, workflows]);
21619
21632
  const leftWidth = Math.floor(terminalWidth * 0.35);
21620
21633
  const rightWidth = terminalWidth - leftWidth - 2;
21621
- const statusBarHeight = 1;
21634
+ const statusBarHeight = 2;
21622
21635
  const borderRows = 2;
21623
21636
  const innerHeight = contentHeight - statusBarHeight - borderRows;
21624
21637
  const panelHeight = contentHeight - statusBarHeight;
@@ -21669,8 +21682,8 @@ function SessionsScreenInner({
21669
21682
  {
21670
21683
  width: leftWidth,
21671
21684
  height: panelHeight,
21672
- borderStyle: "single",
21673
- borderColor: colors.border,
21685
+ borderStyle: "round",
21686
+ borderColor: colors.borderBright,
21674
21687
  flexDirection: "column",
21675
21688
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
21676
21689
  AgentTree,
@@ -21692,16 +21705,16 @@ function SessionsScreenInner({
21692
21705
  {
21693
21706
  width: rightWidth,
21694
21707
  height: panelHeight,
21695
- borderStyle: "single",
21696
- borderColor: colors.border,
21708
+ borderStyle: "round",
21709
+ borderColor: colors.borderBright,
21697
21710
  flexDirection: "column",
21698
21711
  children: [
21699
21712
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", children: [
21700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: rightMode === "stats" ? colors.accent : colors.textDim, bold: rightMode === "stats", children: "[Stats]" }),
21701
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textDim, children: " " }),
21702
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: rightMode === "messages" ? colors.accent : colors.textDim, bold: rightMode === "messages", children: "[Messages]" }),
21713
+ rightMode === "stats" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: colors.bgHighlight, color: colors.accent, bold: true, children: " Stats " }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textDim, children: " Stats " }),
21714
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textMuted, children: "\u2502" }),
21715
+ rightMode === "messages" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: colors.bgHighlight, color: colors.accent, bold: true, children: " Messages " }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textDim, children: " Messages " }),
21703
21716
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { flexGrow: 1 }),
21704
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textDim, children: "Tab:switch" })
21717
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: colors.textMuted, children: "Tab:switch" })
21705
21718
  ] }),
21706
21719
  rightMode === "stats" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DetailsPanel, { workflow: selectedWorkflow, height: innerHeight - 1 }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
21707
21720
  MessagesPanel,
@@ -21766,10 +21779,10 @@ function SuccessBar({ successes, calls, width = 12 }) {
21766
21779
  const pct = calls > 0 ? successes / calls : 0;
21767
21780
  const filled = Math.round(pct * width);
21768
21781
  const empty = width - filled;
21769
- const color = pct >= 0.9 ? colors.success : pct >= 0.7 ? colors.warning : colors.error;
21782
+ const color = pct >= 0.9 ? colors.teal : pct >= 0.7 ? colors.warning : colors.error;
21770
21783
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
21771
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color, children: "\u2588".repeat(filled) }),
21772
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.border, children: "\u2591".repeat(empty) }),
21784
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color, children: "\u2593".repeat(filled) }),
21785
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "\u2591".repeat(empty) }),
21773
21786
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: colors.textDim, children: [
21774
21787
  " ",
21775
21788
  Math.round(pct * 100),
@@ -21797,7 +21810,8 @@ function ToolsScreenInner({ workflows, isActive, contentHeight, terminalWidth })
21797
21810
  return copy.sort((a, b) => b.avgDurationMs - a.avgDurationMs);
21798
21811
  }
21799
21812
  }, [allTools, sortKey]);
21800
- const listHeight = contentHeight - 4;
21813
+ const statusBarHeight = 2;
21814
+ const listHeight = contentHeight - statusBarHeight - 3;
21801
21815
  const clampedIndex = Math.min(selectedIndex, Math.max(0, sortedTools.length - 1));
21802
21816
  const selectedTool = sortedTools[clampedIndex] ?? null;
21803
21817
  const startIndex = clampedIndex >= listHeight ? clampedIndex - listHeight + 1 : 0;
@@ -21824,84 +21838,70 @@ function ToolsScreenInner({ workflows, isActive, contentHeight, terminalWidth })
21824
21838
  { isActive }
21825
21839
  );
21826
21840
  const sortLabels = {
21827
- calls: "Calls",
21828
- failures: "Failures",
21829
- avgTime: "Avg Time"
21841
+ calls: "calls",
21842
+ failures: "failures",
21843
+ avgTime: "avg-time"
21830
21844
  };
21831
21845
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", width: terminalWidth, height: contentHeight, children: [
21832
21846
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { paddingX: 1, flexDirection: "row", children: [
21833
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.accent, bold: true, children: "Tools" }),
21847
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.accent, bold: true, children: "\u25C6 TOOLS" }),
21834
21848
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1 }),
21835
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: colors.textDim, children: [
21836
- "sort:",
21837
- " ",
21838
- ["calls", "failures", "avgTime"].map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: sortKey === k ? colors.accent : colors.textDim, children: [
21839
- "[",
21840
- k === sortKey ? sortLabels[k] : k,
21841
- "]",
21842
- " "
21843
- ] }, k)),
21844
- "Tab:cycle"
21845
- ] })
21849
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "sort: " }),
21850
+ ["calls", "failures", "avgTime"].map((k) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: sortKey === k ? colors.teal : colors.textMuted, children: [
21851
+ sortKey === k ? `[${sortLabels[k]}]` : sortLabels[k],
21852
+ " "
21853
+ ] }, k)),
21854
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "Tab:cycle" })
21846
21855
  ] }),
21847
21856
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", flexGrow: 1, children: [
21848
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { width: 36, flexDirection: "column", borderStyle: "single", borderColor: colors.border, children: [
21857
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { width: 36, flexDirection: "column", borderStyle: "round", borderColor: colors.borderBright, children: [
21849
21858
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { paddingX: 1, flexDirection: "row", children: [
21850
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, bold: true, children: "Tool" }),
21859
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.purple, bold: true, children: "TOOL" }),
21851
21860
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1 }),
21852
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "calls " }),
21853
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "err" })
21861
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "calls " }),
21862
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "err" })
21854
21863
  ] }),
21855
- visibleTools.map((tool, i) => {
21864
+ visibleTools.map((tool) => {
21856
21865
  const isSelected = tool.name === selectedTool?.name;
21857
21866
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", paddingX: 1, children: [
21858
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
21859
- Text,
21860
- {
21861
- color: isSelected ? colors.accent : colors.textDim,
21862
- bold: isSelected,
21863
- children: [
21864
- isSelected ? "\u25B6 " : " ",
21865
- truncate5(tool.name, 20)
21866
- ]
21867
- }
21868
- ),
21867
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.bgHighlight, backgroundColor: colors.accent, bold: true, children: `\u25B6 ${truncate5(tool.name, 20)}` }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: ` ${truncate5(tool.name, 20)}` }),
21869
21868
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1 }),
21870
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.info, children: tool.calls }),
21871
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: " " }),
21872
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: tool.failures > 0 ? colors.error : colors.textDim, children: tool.failures })
21869
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.accentAlt, children: tool.calls }),
21870
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: " " }),
21871
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: tool.failures > 0 ? colors.error : colors.textMuted, children: tool.failures })
21873
21872
  ] }, tool.name);
21874
21873
  }),
21875
21874
  allTools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "No tool data yet" }) })
21876
21875
  ] }),
21877
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1, flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, children: selectedTool ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
21878
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.cyan, bold: true, children: selectedTool.name }),
21876
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1, flexDirection: "column", borderStyle: "round", borderColor: colors.borderBright, paddingX: 1, children: selectedTool ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
21877
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.accent, bold: true, children: selectedTool.name }),
21879
21878
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
21879
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 STATS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
21880
21880
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", children: [
21881
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "Total calls" }) }),
21881
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "Total calls" }) }),
21882
21882
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.text, children: selectedTool.calls })
21883
21883
  ] }),
21884
21884
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", children: [
21885
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "Successes" }) }),
21885
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "Successes" }) }),
21886
21886
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.success, children: selectedTool.successes })
21887
21887
  ] }),
21888
21888
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", children: [
21889
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "Failures" }) }),
21890
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: selectedTool.failures > 0 ? colors.error : colors.textDim, children: selectedTool.failures })
21889
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "Failures" }) }),
21890
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: selectedTool.failures > 0 ? colors.error : colors.textMuted, children: selectedTool.failures })
21891
21891
  ] }),
21892
21892
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", children: [
21893
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "Avg time" }) }),
21893
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "Avg time" }) }),
21894
21894
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.info, children: formatDuration3(selectedTool.avgDurationMs) })
21895
21895
  ] })
21896
21896
  ] }),
21897
21897
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { marginTop: 1, children: [
21898
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textDim, children: "Success rate " }),
21898
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.textMuted, children: "success rate " }),
21899
21899
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SuccessBar, { successes: selectedTool.successes, calls: selectedTool.calls })
21900
21900
  ] }),
21901
21901
  selectedTool.recentErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
21902
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.warning, bold: true, children: "Recent Errors" }) }),
21902
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.purple, bold: true, children: "\u2500\u2500 RECENT ERRORS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
21903
21903
  selectedTool.recentErrors.map((err, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "row", children: [
21904
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.error, children: "\u2022 " }),
21904
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.error, children: "\u2717 " }),
21905
21905
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: colors.text, children: truncate5(err, 60) })
21906
21906
  ] }, i))
21907
21907
  ] })
@@ -21937,6 +21937,7 @@ var SparkLine = (0, import_react30.memo)(SparkLineInner);
21937
21937
 
21938
21938
  // src/ui/screens/OverviewScreen.tsx
21939
21939
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
21940
+ var TIME_FILTER_OPTIONS = [1, 7, 30, 90, 0];
21940
21941
  function formatTokens5(n) {
21941
21942
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
21942
21943
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
@@ -21949,7 +21950,11 @@ function StatRow2({ label, value, color = colors.text }) {
21949
21950
  ] });
21950
21951
  }
21951
21952
  function SectionHeader({ title }) {
21952
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.purple, bold: true, children: title }) });
21953
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.purple, bold: true, children: [
21954
+ "\u2500\u2500 ",
21955
+ title.toUpperCase(),
21956
+ " "
21957
+ ] }) });
21953
21958
  }
21954
21959
  function ErrorBar({
21955
21960
  label,
@@ -21964,17 +21969,16 @@ function ErrorBar({
21964
21969
  const errPct = calls > 0 ? Math.round(errors / calls * 100) : 0;
21965
21970
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
21966
21971
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: labelWidth, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.text, children: truncate6(label, labelWidth - 1) }) }),
21967
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.success, children: "\u2588".repeat(okFilled) }),
21968
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: errors > 0 ? colors.error : colors.border, children: "\u2588".repeat(errFilled) }),
21972
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.teal, children: "\u2593".repeat(okFilled) }),
21973
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: errors > 0 ? colors.error : colors.textMuted, children: "\u2593".repeat(errFilled) }),
21969
21974
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
21970
21975
  " ",
21971
- calls,
21972
- " calls"
21976
+ calls
21973
21977
  ] }),
21974
21978
  errors > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.error, children: [
21975
- " ",
21979
+ " \u2717",
21976
21980
  errors,
21977
- " err (",
21981
+ " (",
21978
21982
  errPct,
21979
21983
  "%)"
21980
21984
  ] })
@@ -21982,7 +21986,50 @@ function ErrorBar({
21982
21986
  }
21983
21987
  function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth }) {
21984
21988
  const pricing = (0, import_react31.useMemo)(() => getAllPricing(), []);
21985
- const stats = (0, import_react31.useMemo)(() => computeOverviewStats(workflows, pricing), [workflows, pricing]);
21989
+ const [timeFilterIdx, setTimeFilterIdx] = (0, import_react31.useState)(0);
21990
+ const [projectFilterIdx, setProjectFilterIdx] = (0, import_react31.useState)(0);
21991
+ const timeFilter = TIME_FILTER_OPTIONS[timeFilterIdx];
21992
+ const allProjects = (0, import_react31.useMemo)(() => {
21993
+ const projects = /* @__PURE__ */ new Set();
21994
+ for (const w of workflows) {
21995
+ const p = w.mainSession.projectName ?? "Unknown";
21996
+ projects.add(p);
21997
+ }
21998
+ return Array.from(projects).sort();
21999
+ }, [workflows]);
22000
+ const selectedProject = projectFilterIdx === 0 ? null : allProjects[projectFilterIdx - 1] ?? null;
22001
+ const filteredWorkflows = (0, import_react31.useMemo)(() => {
22002
+ let cutoff = 0;
22003
+ if (timeFilter === 1) {
22004
+ const today = /* @__PURE__ */ new Date();
22005
+ today.setHours(0, 0, 0, 0);
22006
+ cutoff = today.getTime();
22007
+ } else if (timeFilter > 1) {
22008
+ cutoff = Date.now() - timeFilter * 864e5;
22009
+ }
22010
+ return workflows.filter((w) => {
22011
+ const ts = w.mainSession.timeCreated;
22012
+ if (cutoff > 0 && (ts === null || ts < cutoff)) return false;
22013
+ if (selectedProject !== null) {
22014
+ const p = w.mainSession.projectName ?? "Unknown";
22015
+ if (p !== selectedProject) return false;
22016
+ }
22017
+ return true;
22018
+ });
22019
+ }, [workflows, timeFilter, selectedProject]);
22020
+ use_input_default((input) => {
22021
+ if (!isActive) return;
22022
+ if (input === "t") {
22023
+ setTimeFilterIdx((i) => (i + 1) % TIME_FILTER_OPTIONS.length);
22024
+ }
22025
+ if (input === "p") {
22026
+ setProjectFilterIdx((i) => (i + 1) % (allProjects.length + 1));
22027
+ }
22028
+ if (input === "P") {
22029
+ setProjectFilterIdx((i) => (i - 1 + allProjects.length + 1) % (allProjects.length + 1));
22030
+ }
22031
+ }, { isActive });
22032
+ const stats = (0, import_react31.useMemo)(() => computeOverviewStats(filteredWorkflows, pricing), [filteredWorkflows, pricing]);
21986
22033
  const topModels = (0, import_react31.useMemo)(
21987
22034
  () => Array.from(stats.modelBreakdown.entries()).sort((a, b) => b[1].calls - a[1].calls).slice(0, 4),
21988
22035
  [stats]
@@ -22013,14 +22060,27 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22013
22060
  const midW = Math.max(26, Math.floor(terminalWidth * 0.3));
22014
22061
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: terminalWidth, height: contentHeight, children: [
22015
22062
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { paddingX: 1, flexDirection: "row", children: [
22016
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.accent, bold: true, children: "Overview" }),
22063
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.accent, bold: true, children: "\u25C6 OVERVIEW" }),
22017
22064
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
22018
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
22065
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textMuted, children: [
22066
+ filteredWorkflows.length,
22067
+ "/",
22019
22068
  workflows.length,
22020
- " workflows ",
22021
- stats.totalTokens.total > 0 ? formatTokens5(stats.totalTokens.total) + " tokens" : ""
22069
+ " workflows"
22070
+ ] }),
22071
+ stats.totalTokens.total > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textMuted, children: [
22072
+ " ",
22073
+ formatTokens5(stats.totalTokens.total),
22074
+ " tokens"
22022
22075
  ] })
22023
22076
  ] }),
22077
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { paddingX: 1, flexDirection: "row", children: [
22078
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textMuted, children: "filter: " }),
22079
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: timeFilter === 0 ? colors.accent : colors.teal, bold: true, children: timeFilter === 0 ? "all time" : timeFilter === 1 ? "today" : `last ${timeFilter}d` }),
22080
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textMuted, children: " \xB7 " }),
22081
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: selectedProject ? colors.peach : colors.textMuted, children: selectedProject ? truncate6(selectedProject, 24) : "all projects" }),
22082
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textMuted, children: " (t/p)" })
22083
+ ] }),
22024
22084
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", flexGrow: 1, paddingX: 1, children: [
22025
22085
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: leftW, children: [
22026
22086
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Totals" }),
@@ -22030,7 +22090,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22030
22090
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " cache r/w", value: `${formatTokens5(stats.totalTokens.cacheRead)} / ${formatTokens5(stats.totalTokens.cacheWrite)}`, color: colors.textDim }),
22031
22091
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: "Total cost", value: `$${stats.totalCost.toFixed(4)}`, color: colors.success }),
22032
22092
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Token trend (7d)" }),
22033
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "row", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SparkLine, { values: weeklyTokenValues, color: colors.cyan }) }),
22093
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "row", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SparkLine, { values: weeklyTokenValues, color: colors.accentAlt }) }),
22034
22094
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
22035
22095
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: stats.weeklyTokens[0]?.date.slice(3) ?? "" }),
22036
22096
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
@@ -22042,7 +22102,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22042
22102
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: stats.weeklyTokens[6]?.date.slice(3) ?? "" })
22043
22103
  ] }),
22044
22104
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Sessions (7d)" }),
22045
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "row", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SparkLine, { values: weeklySessionValues, color: colors.accent }) }),
22105
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "row", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SparkLine, { values: weeklySessionValues, color: colors.purple }) }),
22046
22106
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
22047
22107
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: stats.weeklySessions[0]?.date.slice(3) ?? "" }),
22048
22108
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
@@ -22055,7 +22115,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22055
22115
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: stats.weeklySessions[6]?.date.slice(3) ?? "" })
22056
22116
  ] }),
22057
22117
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Hourly activity" }),
22058
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.warning, children: hourSpark }),
22118
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.teal, children: hourSpark }),
22059
22119
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
22060
22120
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: "00" }),
22061
22121
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
@@ -22084,8 +22144,8 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22084
22144
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Top tools (calls / errors)" }),
22085
22145
  topTools.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: "No tool data" }) : topTools.map(([name, data]) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
22086
22146
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 10, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.text, children: truncate6(name, 9) }) }),
22087
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: data.errors > 0 ? colors.warning : colors.info, children: "\u2588".repeat(Math.max(1, Math.round(data.calls / maxToolCalls * 12))) }),
22088
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.border, children: "\u2591".repeat(Math.max(0, 12 - Math.max(1, Math.round(data.calls / maxToolCalls * 12)))) }),
22147
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: data.errors > 0 ? colors.peach : colors.accentAlt, children: "\u2593".repeat(Math.max(1, Math.round(data.calls / maxToolCalls * 12))) }),
22148
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textMuted, children: "\u2591".repeat(Math.max(0, 12 - Math.max(1, Math.round(data.calls / maxToolCalls * 12)))) }),
22089
22149
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
22090
22150
  " ",
22091
22151
  data.calls
@@ -22119,8 +22179,8 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22119
22179
  const durStr = avg < 1e3 ? `${avg.toFixed(0)}ms` : `${(avg / 1e3).toFixed(1)}s`;
22120
22180
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
22121
22181
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 12, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.text, children: truncate6(name, 10) }) }),
22122
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.warning, children: "\u2588".repeat(filled) }),
22123
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.border, children: "\u2591".repeat(barW - filled) }),
22182
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.peach, children: "\u2593".repeat(filled) }),
22183
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textMuted, children: "\u2591".repeat(barW - filled) }),
22124
22184
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
22125
22185
  " ",
22126
22186
  durStr
@@ -22139,7 +22199,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22139
22199
  ] }, model))
22140
22200
  ] })
22141
22201
  ] }),
22142
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusBar, { hints: "1:sessions 2:tools r:refresh q:quit" })
22202
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusBar, { hints: "1:sessions 2:tools t:time-filter p/P:project r:refresh q:quit" })
22143
22203
  ] });
22144
22204
  }
22145
22205
  var OverviewScreen = (0, import_react31.memo)(OverviewScreenInner);
@@ -22472,7 +22532,7 @@ function App2({ refreshInterval = 2e3 }) {
22472
22532
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: colors.textDim, children: "Press q to quit" }) })
22473
22533
  ] });
22474
22534
  }
22475
- const contentHeight = terminalHeight - 2;
22535
+ const contentHeight = terminalHeight - 3;
22476
22536
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: terminalWidth, height: terminalHeight - 1, children: [
22477
22537
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TabBar, { activeScreen: screen, lastRefresh }),
22478
22538
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { width: terminalWidth, height: contentHeight, children: [
@@ -22508,9 +22568,7 @@ function App2({ refreshInterval = 2e3 }) {
22508
22568
  }
22509
22569
 
22510
22570
  // src/cli.ts
22511
- import { createRequire } from "node:module";
22512
- var require2 = createRequire(import.meta.url);
22513
- var pkg = require2("../package.json");
22571
+ var pkg = true ? { version: "3.4.0", name: "opencode-top" } : devPkg();
22514
22572
  program.name(pkg.name).version(pkg.version).description("Monitor OpenCode AI coding sessions");
22515
22573
  program.command("live").description("Start live monitoring dashboard").option("-i, --interval <ms>", "Refresh interval in milliseconds", "2000").action((options) => {
22516
22574
  const refreshInterval = Number.parseInt(options.interval, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-top",
3
- "version": "3.3.6",
3
+ "version": "3.4.0",
4
4
  "description": "Monitor OpenCode AI coding sessions - Token usage, costs, and agent analytics",
5
5
  "type": "module",
6
6
  "bin": {