opencode-top 3.2.2 → 3.3.1

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 (2) hide show
  1. package/dist/cli.mjs +81 -42
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -18427,29 +18427,19 @@ var TABS = [
18427
18427
  { id: "overview", label: "Overview", key: "3" }
18428
18428
  ];
18429
18429
  function TabBarInner({ activeScreen, lastRefresh }) {
18430
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { paddingX: 1, borderStyle: "single", borderColor: colors.border, flexDirection: "row", children: [
18431
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: colors.accent, bold: true, children: [
18432
- "OCMonitor",
18433
- " "
18434
- ] }),
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 " }),
18435
18432
  TABS.map((tab2) => {
18436
18433
  const isActive = tab2.id === activeScreen;
18437
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
18438
- Text,
18439
- {
18440
- color: isActive ? colors.accent : colors.textDim,
18441
- bold: isActive,
18442
- children: [
18443
- "[",
18444
- tab2.key,
18445
- "]",
18446
- isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: colors.text, children: [
18447
- " ",
18448
- tab2.label
18449
- ] }) : ` ${tab2.label}`
18450
- ]
18451
- }
18452
- ) }, tab2.id);
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);
18453
18443
  }),
18454
18444
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1 }),
18455
18445
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: colors.textDim, children: lastRefresh.toLocaleTimeString() })
@@ -18464,7 +18454,7 @@ var import_react28 = __toESM(require_react(), 1);
18464
18454
  var import_react23 = __toESM(require_react(), 1);
18465
18455
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
18466
18456
  function StatusBarInner({ hints, info }) {
18467
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { paddingX: 1, borderStyle: "single", borderColor: colors.border, flexDirection: "row", children: [
18457
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { paddingX: 1, height: 1, flexDirection: "row", children: [
18468
18458
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: colors.textDim, children: hints }),
18469
18459
  info && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
18470
18460
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { flexGrow: 1 }),
@@ -20955,6 +20945,15 @@ function formatCost(d) {
20955
20945
  if (d.lessThan(0.01)) return `$${d.toFixed(4)}`;
20956
20946
  return `$${d.toFixed(2)}`;
20957
20947
  }
20948
+ function formatDate(ts) {
20949
+ if (!ts) return "";
20950
+ const d = new Date(ts);
20951
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
20952
+ const dd = String(d.getDate()).padStart(2, "0");
20953
+ const hh = String(d.getHours()).padStart(2, "0");
20954
+ const min2 = String(d.getMinutes()).padStart(2, "0");
20955
+ return `${mm}/${dd} ${hh}:${min2}`;
20956
+ }
20958
20957
  function AgentTreeInner({ workflows, selectedId, flatNodes, maxHeight = 20 }) {
20959
20958
  const headerHeight = 2;
20960
20959
  const visibleCount = Math.max(1, maxHeight - headerHeight);
@@ -20980,7 +20979,8 @@ function AgentTreeInner({ workflows, selectedId, flatNodes, maxHeight = 20 }) {
20980
20979
  const indent = " ".repeat(node.depth);
20981
20980
  const prefix = node.depth === 0 ? node.hasChildren ? "\u25B6 " : "\u25CF " : node.hasChildren ? "\u251C\u25B6 " : "\u251C\u2500 ";
20982
20981
  const label = node.depth === 0 ? truncate2(node.session.title ?? node.session.projectName ?? "Untitled", 22) : truncate2(`[${agentName ?? "?"}] ${node.session.title ?? ""}`, 20);
20983
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "row", children: [
20982
+ const date = formatDate(node.session.timeCreated);
20983
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "row", height: 1, children: [
20984
20984
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: 1 }),
20985
20985
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
20986
20986
  Text,
@@ -20996,6 +20996,10 @@ function AgentTreeInner({ workflows, selectedId, flatNodes, maxHeight = 20 }) {
20996
20996
  }
20997
20997
  ),
20998
20998
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1 }),
20999
+ date && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: colors.textDim, children: [
21000
+ date,
21001
+ " "
21002
+ ] }),
20999
21003
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.textDim, dimColor: true, children: formatTokens(tokens.total) }),
21000
21004
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: 1 }),
21001
21005
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: colors.success, children: formatCost(cost) }),
@@ -21252,16 +21256,23 @@ var import_react27 = __toESM(require_react(), 1);
21252
21256
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
21253
21257
  function buildLines(session, contentWidth, expandedIds) {
21254
21258
  const lines = [];
21259
+ let cumTokens = 0;
21255
21260
  for (const interaction of session.interactions) {
21256
21261
  if (interaction.role !== "assistant") continue;
21262
+ cumTokens += interaction.tokens.input + interaction.tokens.cacheRead + interaction.tokens.output;
21257
21263
  const dur = interaction.time.completed && interaction.time.created ? formatDuration2(interaction.time.completed - interaction.time.created) : "";
21264
+ const tokensIn = interaction.tokens.input + interaction.tokens.cacheRead;
21265
+ const tokensOut = interaction.tokens.output;
21258
21266
  lines.push({
21259
21267
  id: `h-${interaction.id}`,
21260
21268
  kind: "header",
21261
21269
  modelId: interaction.modelId,
21262
21270
  agent: interaction.agent ?? null,
21263
21271
  duration: dur,
21264
- time: formatTime(interaction.time.created)
21272
+ time: formatTime(interaction.time.created),
21273
+ tokensIn,
21274
+ tokensOut,
21275
+ cumTokens
21265
21276
  });
21266
21277
  for (const part of interaction.parts) {
21267
21278
  if (part.type === "tool") {
@@ -21280,24 +21291,25 @@ function buildLines(session, contentWidth, expandedIds) {
21280
21291
  name: truncate4(p.toolName, 18),
21281
21292
  title: p.title ? truncate4(p.title, 28) : "",
21282
21293
  right: exitStr + dur2,
21283
- expanded
21294
+ expanded,
21295
+ cumTokens
21284
21296
  });
21285
21297
  if (expanded) {
21286
21298
  const inputKeys = Object.keys(p.input);
21287
21299
  if (inputKeys.length > 0) {
21288
- lines.push({ id: `td-${p.callId}-in`, kind: "tool-detail", label: "input", value: "", isSection: true });
21300
+ lines.push({ id: `td-${p.callId}-in`, kind: "tool-detail", label: "input", value: "", isSection: true, cumTokens });
21289
21301
  for (const key of inputKeys) {
21290
21302
  const val = formatParamValue(p.input[key], contentWidth - key.length - 6);
21291
- lines.push({ id: `td-${p.callId}-in-${key}`, kind: "tool-detail", label: key, value: val, isSection: false });
21303
+ lines.push({ id: `td-${p.callId}-in-${key}`, kind: "tool-detail", label: key, value: val, isSection: false, cumTokens });
21292
21304
  }
21293
21305
  }
21294
21306
  if (p.output?.trim()) {
21295
- lines.push({ id: `td-${p.callId}-out`, kind: "tool-detail", label: "output", value: "", isSection: true });
21307
+ lines.push({ id: `td-${p.callId}-out`, kind: "tool-detail", label: "output", value: "", isSection: true, cumTokens });
21296
21308
  const outLines = p.output.trim().split("\n").slice(0, 40);
21297
21309
  let outIdx = 0;
21298
21310
  for (const ol of outLines) {
21299
21311
  for (const wrapped of wrapText2(ol === "" ? " " : ol, contentWidth - 5)) {
21300
- lines.push({ id: `td-${p.callId}-out-${outIdx++}`, kind: "tool-detail", label: "", value: wrapped, isSection: false });
21312
+ lines.push({ id: `td-${p.callId}-out-${outIdx++}`, kind: "tool-detail", label: "", value: wrapped, isSection: false, cumTokens });
21301
21313
  }
21302
21314
  }
21303
21315
  }
@@ -21305,12 +21317,12 @@ function buildLines(session, contentWidth, expandedIds) {
21305
21317
  } else if (part.type === "text" && part.text.trim()) {
21306
21318
  let txtIdx = 0;
21307
21319
  for (const row of wrapText2(part.text.trim(), contentWidth - 3)) {
21308
- lines.push({ id: `tx-${interaction.id}-${txtIdx++}`, kind: "text", text: row });
21320
+ lines.push({ id: `tx-${interaction.id}-${txtIdx++}`, kind: "text", text: row, cumTokens });
21309
21321
  }
21310
21322
  } else if (part.type === "reasoning" && part.text.trim()) {
21311
21323
  let rIdx = 0;
21312
21324
  for (const row of wrapText2(part.text.trim(), contentWidth - 5)) {
21313
- lines.push({ id: `r-${interaction.id}-${rIdx++}`, kind: "reasoning", text: row });
21325
+ lines.push({ id: `r-${interaction.id}-${rIdx++}`, kind: "reasoning", text: row, cumTokens });
21314
21326
  }
21315
21327
  }
21316
21328
  }
@@ -21338,6 +21350,11 @@ function wrapText2(text, maxLen) {
21338
21350
  }
21339
21351
  return out;
21340
21352
  }
21353
+ function formatTokens4(n) {
21354
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
21355
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
21356
+ return String(n);
21357
+ }
21341
21358
  function formatTime(ts) {
21342
21359
  if (!ts) return "";
21343
21360
  return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
@@ -21437,6 +21454,9 @@ function MessagesPanelInner({ session, maxHeight, isActive }) {
21437
21454
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { height: maxHeight, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.textDim, children: "No messages" }) });
21438
21455
  }
21439
21456
  const visibleLines = allLines.slice(clampedOffset, clampedOffset + viewHeight);
21457
+ const cursorLine = allLines[clampedCursor];
21458
+ const cursorCumTokens = cursorLine?.cumTokens ?? 0;
21459
+ const totalTokens = allLines[allLines.length - 1]?.cumTokens ?? 0;
21440
21460
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", height: maxHeight, paddingX: 1, children: [
21441
21461
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "row", height: 1, children: [
21442
21462
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.textDim, children: [
@@ -21446,6 +21466,15 @@ function MessagesPanelInner({ session, maxHeight, isActive }) {
21446
21466
  "/",
21447
21467
  allLines.length
21448
21468
  ] }),
21469
+ totalTokens > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
21470
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.textDim, children: " \xB7 " }),
21471
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.info, children: formatTokens4(cursorCumTokens) }),
21472
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.textDim, children: [
21473
+ "/",
21474
+ formatTokens4(totalTokens),
21475
+ " tok"
21476
+ ] })
21477
+ ] }),
21449
21478
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { flexGrow: 1 }),
21450
21479
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.textDim, children: [
21451
21480
  session.interactions.filter((i) => i.role === "assistant").length,
@@ -21462,14 +21491,24 @@ function MessagesPanelInner({ session, maxHeight, isActive }) {
21462
21491
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "row", height: 1, children: [
21463
21492
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: isCursor ? colors.accent : colors.textDim, children: isCursor ? "\u203A" : " " }),
21464
21493
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: isCursor ? colors.accent : colors.purple, bold: true, children: "\u25C6 " }),
21465
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: isCursor ? colors.accent : colors.info, children: truncate4(line.modelId, 26) }),
21494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: isCursor ? colors.accent : colors.info, children: truncate4(line.modelId, 22) }),
21466
21495
  line.agent && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.cyan, children: [
21467
21496
  " [",
21468
21497
  line.agent,
21469
21498
  "]"
21470
21499
  ] }),
21471
21500
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { flexGrow: 1 }),
21472
- line.duration && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.textDim, children: [
21501
+ line.tokensIn > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
21502
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.textDim, children: "\u2193" }),
21503
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.warning, children: formatTokens4(line.tokensIn) }),
21504
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " })
21505
+ ] }),
21506
+ line.tokensOut > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
21507
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.textDim, children: "\u2191" }),
21508
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: colors.success, children: formatTokens4(line.tokensOut) }),
21509
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " })
21510
+ ] }),
21511
+ line.duration && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { color: colors.cyan, children: [
21473
21512
  line.duration,
21474
21513
  " "
21475
21514
  ] }),
@@ -21898,7 +21937,7 @@ var SparkLine = (0, import_react30.memo)(SparkLineInner);
21898
21937
 
21899
21938
  // src/ui/screens/OverviewScreen.tsx
21900
21939
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
21901
- function formatTokens4(n) {
21940
+ function formatTokens5(n) {
21902
21941
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
21903
21942
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
21904
21943
  return n.toString();
@@ -21979,16 +22018,16 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
21979
22018
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
21980
22019
  workflows.length,
21981
22020
  " workflows ",
21982
- stats.totalTokens.total > 0 ? formatTokens4(stats.totalTokens.total) + " tokens" : ""
22021
+ stats.totalTokens.total > 0 ? formatTokens5(stats.totalTokens.total) + " tokens" : ""
21983
22022
  ] })
21984
22023
  ] }),
21985
22024
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", flexGrow: 1, paddingX: 1, children: [
21986
22025
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: leftW, children: [
21987
22026
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Totals" }),
21988
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: "Total tokens", value: formatTokens4(stats.totalTokens.total) }),
21989
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " input", value: formatTokens4(stats.totalTokens.input), color: colors.info }),
21990
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " output", value: formatTokens4(stats.totalTokens.output), color: colors.cyan }),
21991
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " cache r/w", value: `${formatTokens4(stats.totalTokens.cacheRead)} / ${formatTokens4(stats.totalTokens.cacheWrite)}`, color: colors.textDim }),
22027
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: "Total tokens", value: formatTokens5(stats.totalTokens.total) }),
22028
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " input", value: formatTokens5(stats.totalTokens.input), color: colors.info }),
22029
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " output", value: formatTokens5(stats.totalTokens.output), color: colors.cyan }),
22030
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: " cache r/w", value: `${formatTokens5(stats.totalTokens.cacheRead)} / ${formatTokens5(stats.totalTokens.cacheWrite)}`, color: colors.textDim }),
21992
22031
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatRow2, { label: "Total cost", value: `$${stats.totalCost.toFixed(4)}`, color: colors.success }),
21993
22032
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHeader, { title: "Token trend (7d)" }),
21994
22033
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "row", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SparkLine, { values: weeklyTokenValues, color: colors.cyan }) }),
@@ -21997,7 +22036,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
21997
22036
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
21998
22037
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
21999
22038
  "peak ",
22000
- formatTokens4(maxWeeklyTokens)
22039
+ formatTokens5(maxWeeklyTokens)
22001
22040
  ] }),
22002
22041
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1 }),
22003
22042
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.textDim, children: stats.weeklyTokens[6]?.date.slice(3) ?? "" })
@@ -22095,7 +22134,7 @@ function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth
22095
22134
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: colors.info, children: data.calls }),
22096
22135
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: colors.textDim, children: [
22097
22136
  " ",
22098
- formatTokens4(data.tokens)
22137
+ formatTokens5(data.tokens)
22099
22138
  ] })
22100
22139
  ] }, model))
22101
22140
  ] })
@@ -22433,7 +22472,7 @@ function App2({ refreshInterval = 2e3 }) {
22433
22472
  /* @__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" }) })
22434
22473
  ] });
22435
22474
  }
22436
- const contentHeight = terminalHeight - 3;
22475
+ const contentHeight = terminalHeight - 2;
22437
22476
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: terminalWidth, height: terminalHeight - 1, children: [
22438
22477
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TabBar, { activeScreen: screen, lastRefresh }),
22439
22478
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { width: terminalWidth, height: contentHeight, children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-top",
3
- "version": "3.2.2",
3
+ "version": "3.3.1",
4
4
  "description": "Monitor OpenCode AI coding sessions - Token usage, costs, and agent analytics",
5
5
  "type": "module",
6
6
  "bin": {