opencode-top 3.1.1 → 3.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.
Files changed (103) hide show
  1. package/bin/octop.js +2 -9
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +22432 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cli.mjs +22570 -0
  7. package/dist/core/agents.d.ts +11 -0
  8. package/dist/core/agents.d.ts.map +1 -0
  9. package/dist/core/agents.js +58 -0
  10. package/dist/core/agents.js.map +1 -0
  11. package/dist/core/session.d.ts +19 -0
  12. package/dist/core/session.d.ts.map +1 -0
  13. package/dist/core/session.js +261 -0
  14. package/dist/core/session.js.map +1 -0
  15. package/dist/core/types.d.ts +140 -0
  16. package/dist/core/types.d.ts.map +1 -0
  17. package/dist/core/types.js +29 -0
  18. package/dist/core/types.js.map +1 -0
  19. package/dist/data/pricing.d.ts +4 -0
  20. package/dist/data/pricing.d.ts.map +1 -0
  21. package/dist/data/pricing.js +76 -0
  22. package/dist/data/pricing.js.map +1 -0
  23. package/dist/data/sqlite.d.ts +5 -0
  24. package/dist/data/sqlite.d.ts.map +1 -0
  25. package/dist/data/sqlite.js +222 -0
  26. package/dist/data/sqlite.js.map +1 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/{src/index.ts → dist/index.js} +1 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/ui/App.d.ts +6 -0
  32. package/dist/ui/App.d.ts.map +1 -0
  33. package/dist/ui/App.js +101 -0
  34. package/dist/ui/App.js.map +1 -0
  35. package/dist/ui/components/AgentChainGraph.d.ts +9 -0
  36. package/dist/ui/components/AgentChainGraph.d.ts.map +1 -0
  37. package/dist/ui/components/AgentChainGraph.js +41 -0
  38. package/dist/ui/components/AgentChainGraph.js.map +1 -0
  39. package/dist/ui/components/AgentTree.d.ts +13 -0
  40. package/dist/ui/components/AgentTree.d.ts.map +1 -0
  41. package/dist/ui/components/AgentTree.js +50 -0
  42. package/dist/ui/components/AgentTree.js.map +1 -0
  43. package/dist/ui/components/DetailsPanel.d.ts +9 -0
  44. package/dist/ui/components/DetailsPanel.d.ts.map +1 -0
  45. package/dist/ui/components/DetailsPanel.js +82 -0
  46. package/dist/ui/components/DetailsPanel.js.map +1 -0
  47. package/dist/ui/components/MessagesPanel.d.ts +12 -0
  48. package/dist/ui/components/MessagesPanel.d.ts.map +1 -0
  49. package/dist/ui/components/MessagesPanel.js +107 -0
  50. package/dist/ui/components/MessagesPanel.js.map +1 -0
  51. package/dist/ui/components/SparkLine.d.ts +10 -0
  52. package/dist/ui/components/SparkLine.d.ts.map +1 -0
  53. package/dist/ui/components/SparkLine.js +12 -0
  54. package/dist/ui/components/SparkLine.js.map +1 -0
  55. package/dist/ui/components/StatusBar.d.ts +9 -0
  56. package/dist/ui/components/StatusBar.d.ts.map +1 -0
  57. package/dist/ui/components/StatusBar.js +9 -0
  58. package/dist/ui/components/StatusBar.js.map +1 -0
  59. package/dist/ui/components/TabBar.d.ts +10 -0
  60. package/dist/ui/components/TabBar.d.ts.map +1 -0
  61. package/dist/ui/components/TabBar.js +17 -0
  62. package/dist/ui/components/TabBar.js.map +1 -0
  63. package/dist/ui/screens/OverviewScreen.d.ts +12 -0
  64. package/dist/ui/screens/OverviewScreen.d.ts.map +1 -0
  65. package/dist/ui/screens/OverviewScreen.js +94 -0
  66. package/dist/ui/screens/OverviewScreen.js.map +1 -0
  67. package/dist/ui/screens/SessionsScreen.d.ts +12 -0
  68. package/dist/ui/screens/SessionsScreen.d.ts.map +1 -0
  69. package/dist/ui/screens/SessionsScreen.js +98 -0
  70. package/dist/ui/screens/SessionsScreen.js.map +1 -0
  71. package/dist/ui/screens/TimelineScreen.d.ts +11 -0
  72. package/dist/ui/screens/TimelineScreen.d.ts.map +1 -0
  73. package/dist/ui/screens/TimelineScreen.js +128 -0
  74. package/dist/ui/screens/TimelineScreen.js.map +1 -0
  75. package/dist/ui/screens/ToolsScreen.d.ts +12 -0
  76. package/dist/ui/screens/ToolsScreen.d.ts.map +1 -0
  77. package/dist/ui/screens/ToolsScreen.js +113 -0
  78. package/dist/ui/screens/ToolsScreen.js.map +1 -0
  79. package/dist/ui/theme.d.ts +21 -0
  80. package/dist/ui/theme.d.ts.map +1 -0
  81. package/dist/ui/theme.js +21 -0
  82. package/dist/ui/theme.js.map +1 -0
  83. package/package.json +2 -1
  84. package/bin/octop.mjs +0 -13
  85. package/src/cli.ts +0 -60
  86. package/src/core/agents.ts +0 -78
  87. package/src/core/session.ts +0 -315
  88. package/src/core/types.ts +0 -156
  89. package/src/data/pricing.ts +0 -82
  90. package/src/data/sqlite.ts +0 -347
  91. package/src/ui/App.tsx +0 -141
  92. package/src/ui/components/AgentChainGraph.tsx +0 -95
  93. package/src/ui/components/AgentTree.tsx +0 -101
  94. package/src/ui/components/DetailsPanel.tsx +0 -211
  95. package/src/ui/components/MessagesPanel.tsx +0 -323
  96. package/src/ui/components/SparkLine.tsx +0 -18
  97. package/src/ui/components/StatusBar.tsx +0 -24
  98. package/src/ui/components/TabBar.tsx +0 -42
  99. package/src/ui/screens/OverviewScreen.tsx +0 -327
  100. package/src/ui/screens/SessionsScreen.tsx +0 -168
  101. package/src/ui/screens/TimelineScreen.tsx +0 -222
  102. package/src/ui/screens/ToolsScreen.tsx +0 -260
  103. package/src/ui/theme.ts +0 -21
@@ -1,211 +0,0 @@
1
- import React, { memo, useMemo } from "react";
2
- import { Box, Text } from "ink";
3
- import { colors } from "../theme";
4
- import type { Workflow } from "../../core/types";
5
- import {
6
- getSessionTokens,
7
- getSessionCostSingle,
8
- getSessionDuration,
9
- getOutputRate,
10
- getToolUsage,
11
- } from "../../core/session";
12
- import { getPricing } from "../../data/pricing";
13
- import { AgentChainGraph } from "./AgentChainGraph";
14
-
15
- interface DetailsPanelProps {
16
- workflow: Workflow | null;
17
- height?: number;
18
- }
19
-
20
- function StatRow({
21
- label,
22
- value,
23
- color = colors.text,
24
- }: {
25
- label: string;
26
- value: string;
27
- color?: string;
28
- }) {
29
- return (
30
- <Box flexDirection="row">
31
- <Box width={12}>
32
- <Text color={colors.textDim}>{label}</Text>
33
- </Box>
34
- <Text color={color}>{value}</Text>
35
- </Box>
36
- );
37
- }
38
-
39
- function formatDuration(ms: number): string {
40
- if (ms === 0) return "—";
41
- const mins = Math.floor(ms / 60000);
42
- const secs = Math.floor((ms % 60000) / 1000);
43
- if (mins === 0) return `${secs}s`;
44
- return `${mins}m ${secs}s`;
45
- }
46
-
47
- function ProgressBar({
48
- value,
49
- max,
50
- width = 16,
51
- color = colors.accent,
52
- }: {
53
- value: number;
54
- max: number;
55
- width?: number;
56
- color?: string;
57
- }) {
58
- const pct = max > 0 ? Math.min(value / max, 1) : 0;
59
- const filled = Math.round(pct * width);
60
- const empty = width - filled;
61
-
62
- return (
63
- <Text>
64
- <Text color={color}>{"█".repeat(filled)}</Text>
65
- <Text color={colors.border}>{"░".repeat(empty)}</Text>
66
- <Text color={colors.textDim}> {Math.round(pct * 100)}%</Text>
67
- </Text>
68
- );
69
- }
70
-
71
- function formatTokens(n: number): string {
72
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
73
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
74
- return n.toString();
75
- }
76
-
77
- function DetailsPanelInner({ workflow, height }: DetailsPanelProps) {
78
- const data = useMemo(() => {
79
- if (!workflow) return null;
80
-
81
- const session = workflow.mainSession;
82
- const tokens = getSessionTokens(session);
83
- const modelId = session.interactions[0]?.modelId ?? "";
84
- const pricing = getPricing(modelId);
85
- const cost = getSessionCostSingle(session, pricing);
86
- const duration = getSessionDuration(session);
87
- const outputRate = getOutputRate(session);
88
-
89
- const contextUsage = tokens.input + tokens.cacheRead + tokens.cacheWrite;
90
- const contextPct = pricing.contextWindow > 0 ? contextUsage / pricing.contextWindow : 0;
91
-
92
- const modelBreakdown = new Map<string, { count: number; tokens: number }>();
93
- for (const i of session.interactions) {
94
- const existing = modelBreakdown.get(i.modelId) ?? { count: 0, tokens: 0 };
95
- existing.count++;
96
- existing.tokens += i.tokens.total;
97
- modelBreakdown.set(i.modelId, existing);
98
- }
99
-
100
- const toolUsage = getToolUsage(session);
101
- const topTools = toolUsage
102
- .sort((a, b) => b.calls - a.calls)
103
- .slice(0, 3);
104
-
105
- return {
106
- title: session.title ?? "Untitled",
107
- project: session.projectName ?? "—",
108
- tokens: tokens.total,
109
- cost,
110
- duration,
111
- outputRate,
112
- calls: session.interactions.length,
113
- contextUsage,
114
- contextWindow: pricing.contextWindow,
115
- contextPct,
116
- modelBreakdown,
117
- topTools,
118
- agentTree: workflow.agentTree,
119
- hasSubAgents: workflow.subAgentSessions.length > 0,
120
- };
121
- }, [workflow]);
122
-
123
- if (!data) {
124
- return (
125
- <Box flexDirection="column" paddingX={1} height={height}>
126
- <Text color={colors.textDim}>Select a session</Text>
127
- </Box>
128
- );
129
- }
130
-
131
- return (
132
- <Box flexDirection="column" paddingX={1} height={height} overflow="hidden">
133
- <Box marginBottom={1}>
134
- <Text color={colors.accent} bold>
135
- Details
136
- </Text>
137
- </Box>
138
-
139
- <Box flexDirection="column">
140
- <Text color={colors.cyan} bold>
141
- {data.title}
142
- </Text>
143
- <Text color={colors.textDim}>{data.project}</Text>
144
-
145
- <StatRow label="Tokens" value={formatTokens(data.tokens)} />
146
- <StatRow label="Cost" value={`$${data.cost.toFixed(4)}`} color={colors.success} />
147
- <StatRow label="Duration" value={formatDuration(data.duration)} />
148
- <StatRow label="Rate" value={`${data.outputRate.toFixed(0)} tok/s`} />
149
- <StatRow label="Calls" value={data.calls.toString()} />
150
-
151
- <Box marginTop={1}>
152
- <Text color={colors.textDim}>Context</Text>
153
- </Box>
154
- <ProgressBar
155
- value={data.contextUsage}
156
- max={data.contextWindow}
157
- color={data.contextPct > 0.8 ? colors.warning : colors.accent}
158
- />
159
- </Box>
160
-
161
- <Box marginTop={1}>
162
- <Text color={colors.purple} bold>
163
- Models
164
- </Text>
165
- </Box>
166
- {Array.from(data.modelBreakdown.entries())
167
- .slice(0, 3)
168
- .map(([model, stats]) => (
169
- <Box key={model} flexDirection="row">
170
- <Text color={colors.text}>{model.slice(0, 25)}</Text>
171
- <Box flexGrow={1} />
172
- <Text color={colors.textDim}>{stats.count}</Text>
173
- <Box width={1} />
174
- <Text color={colors.info}>{formatTokens(stats.tokens)}</Text>
175
- </Box>
176
- ))}
177
-
178
- {data.topTools.length > 0 && (
179
- <>
180
- <Box marginTop={1}>
181
- <Text color={colors.purple} bold>
182
- Top Tools
183
- </Text>
184
- </Box>
185
- {data.topTools.map((tool) => (
186
- <Box key={tool.name} flexDirection="row">
187
- <Text color={colors.text}>{tool.name.slice(0, 20)}</Text>
188
- <Box flexGrow={1} />
189
- <Text color={tool.failures > 0 ? colors.warning : colors.success}>
190
- {tool.successes}/{tool.calls}
191
- </Text>
192
- </Box>
193
- ))}
194
- </>
195
- )}
196
-
197
- {data.hasSubAgents && (
198
- <>
199
- <Box marginTop={1}>
200
- <Text color={colors.purple} bold>
201
- Agent Chain
202
- </Text>
203
- </Box>
204
- <AgentChainGraph agentTree={data.agentTree} />
205
- </>
206
- )}
207
- </Box>
208
- );
209
- }
210
-
211
- export const DetailsPanel = memo(DetailsPanelInner);
@@ -1,323 +0,0 @@
1
- import React, { useMemo, memo, useEffect, useState } from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import { colors } from "../theme";
4
- import type { Session, Interaction, MessagePart } from "../../core/types";
5
-
6
- interface MessagesPanelProps {
7
- session: Session | null;
8
- maxHeight: number;
9
- isActive: boolean;
10
- }
11
-
12
- type MsgLine =
13
- | { id: string; kind: "header"; modelId: string; agent: string | null; duration: string; time: string }
14
- | { id: string; kind: "tool"; callId: string; icon: string; iconColor: string; name: string; title: string; right: string; expanded: boolean }
15
- | { id: string; kind: "tool-detail"; label: string; value: string; isSection: boolean }
16
- | { id: string; kind: "text"; text: string }
17
- | { id: string; kind: "reasoning"; text: string };
18
-
19
- function buildLines(session: Session, contentWidth: number, expandedIds: Set<string>): MsgLine[] {
20
- const lines: MsgLine[] = [];
21
- for (const interaction of session.interactions) {
22
- if (interaction.role !== "assistant") continue;
23
-
24
- const dur =
25
- interaction.time.completed && interaction.time.created
26
- ? formatDuration(interaction.time.completed - interaction.time.created)
27
- : "";
28
-
29
- lines.push({
30
- id: `h-${interaction.id}`,
31
- kind: "header",
32
- modelId: interaction.modelId,
33
- agent: interaction.agent ?? null,
34
- duration: dur,
35
- time: formatTime(interaction.time.created),
36
- });
37
-
38
- for (const part of interaction.parts) {
39
- if (part.type === "tool") {
40
- const p = part as MessagePart & { type: "tool" };
41
- const icon = p.status === "completed" ? "✓" : p.status === "error" ? "✗" : "◌";
42
- const iconColor =
43
- p.status === "completed" ? colors.success : p.status === "error" ? colors.error : colors.warning;
44
- const dur2 = p.timeEnd > 0 && p.timeStart > 0 ? formatDuration(p.timeEnd - p.timeStart) : "";
45
- const exitStr = p.exitCode !== null && p.exitCode !== 0 ? `exit:${p.exitCode} ` : "";
46
- const expanded = expandedIds.has(p.callId);
47
-
48
- lines.push({
49
- id: `t-${p.callId}`,
50
- kind: "tool",
51
- callId: p.callId,
52
- icon,
53
- iconColor,
54
- name: truncate(p.toolName, 18),
55
- title: p.title ? truncate(p.title, 28) : "",
56
- right: exitStr + dur2,
57
- expanded,
58
- });
59
-
60
- if (expanded) {
61
- const inputKeys = Object.keys(p.input);
62
- if (inputKeys.length > 0) {
63
- lines.push({ id: `td-${p.callId}-in`, kind: "tool-detail", label: "input", value: "", isSection: true });
64
- for (const key of inputKeys) {
65
- const val = formatParamValue(p.input[key], contentWidth - key.length - 6);
66
- lines.push({ id: `td-${p.callId}-in-${key}`, kind: "tool-detail", label: key, value: val, isSection: false });
67
- }
68
- }
69
- if (p.output?.trim()) {
70
- lines.push({ id: `td-${p.callId}-out`, kind: "tool-detail", label: "output", value: "", isSection: true });
71
- const outLines = p.output.trim().split("\n").slice(0, 40);
72
- let outIdx = 0;
73
- for (const ol of outLines) {
74
- for (const wrapped of wrapText(ol === "" ? " " : ol, contentWidth - 5)) {
75
- lines.push({ id: `td-${p.callId}-out-${outIdx++}`, kind: "tool-detail", label: "", value: wrapped, isSection: false });
76
- }
77
- }
78
- }
79
- }
80
- } else if (part.type === "text" && part.text.trim()) {
81
- let txtIdx = 0;
82
- for (const row of wrapText(part.text.trim(), contentWidth - 3)) {
83
- lines.push({ id: `tx-${interaction.id}-${txtIdx++}`, kind: "text", text: row });
84
- }
85
- } else if (part.type === "reasoning" && part.text.trim()) {
86
- let rIdx = 0;
87
- for (const row of wrapText(part.text.trim(), contentWidth - 5)) {
88
- lines.push({ id: `r-${interaction.id}-${rIdx++}`, kind: "reasoning", text: row });
89
- }
90
- }
91
- }
92
- }
93
- return lines;
94
- }
95
-
96
- function formatParamValue(val: unknown, maxLen: number): string {
97
- if (val === null || val === undefined) return "null";
98
- if (typeof val === "string") return truncate(val.split("\n")[0], Math.max(20, maxLen));
99
- if (typeof val === "number" || typeof val === "boolean") return String(val);
100
- try { return truncate(JSON.stringify(val), Math.max(20, maxLen)); }
101
- catch { return String(val); }
102
- }
103
-
104
- function wrapText(text: string, maxLen: number): string[] {
105
- const out: string[] = [];
106
- for (const raw of text.split("\n")) {
107
- if (raw.length <= maxLen) { out.push(raw); }
108
- else { for (let i = 0; i < raw.length; i += maxLen) out.push(raw.slice(i, i + maxLen)); }
109
- }
110
- return out;
111
- }
112
-
113
- function formatTime(ts: number | null): string {
114
- if (!ts) return "";
115
- return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
116
- }
117
-
118
- function formatDuration(ms: number): string {
119
- if (ms <= 0) return "";
120
- if (ms < 1000) return `${ms}ms`;
121
- return `${(ms / 1000).toFixed(1)}s`;
122
- }
123
-
124
- function truncate(s: string, max: number): string {
125
- if (!s || s.length <= max) return s;
126
- return s.slice(0, max - 1) + "…";
127
- }
128
-
129
- function MessagesPanelInner({ session, maxHeight, isActive }: MessagesPanelProps) {
130
- const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
131
- const [scrollOffset, setScrollOffset] = useState(0);
132
- const [cursor, setCursor] = useState(0);
133
-
134
- // Reset all state when session changes
135
- useEffect(() => {
136
- setExpandedIds(new Set());
137
- setScrollOffset(0);
138
- setCursor(0);
139
- }, [session?.id]);
140
-
141
- const contentWidth = 80;
142
- const viewHeight = maxHeight - 1; // minus the counter row
143
-
144
- const allLines = useMemo(
145
- () => (session ? buildLines(session, contentWidth, expandedIds) : []),
146
- [session, expandedIds]
147
- );
148
-
149
- const maxOffset = Math.max(0, allLines.length - viewHeight);
150
-
151
- // Keep cursor in view: scroll to follow cursor
152
- const clampedCursor = Math.min(cursor, Math.max(0, allLines.length - 1));
153
- const clampedOffset = Math.max(
154
- 0,
155
- Math.min(
156
- scrollOffset,
157
- Math.min(maxOffset, Math.max(scrollOffset, clampedCursor - viewHeight + 1))
158
- )
159
- );
160
-
161
- useInput((input, key) => {
162
- if (key.downArrow || input === "j") {
163
- setCursor((c) => Math.min(allLines.length - 1, c + 1));
164
- setScrollOffset((o) => {
165
- const newCursor = Math.min(allLines.length - 1, clampedCursor + 1);
166
- // Scroll down if cursor goes below view
167
- if (newCursor >= o + viewHeight) return Math.min(maxOffset, o + 1);
168
- return o;
169
- });
170
- return;
171
- }
172
- if (key.upArrow || input === "k") {
173
- setCursor((c) => Math.max(0, c - 1));
174
- setScrollOffset((o) => {
175
- const newCursor = Math.max(0, clampedCursor - 1);
176
- // Scroll up if cursor goes above view
177
- if (newCursor < o) return Math.max(0, o - 1);
178
- return o;
179
- });
180
- return;
181
- }
182
- if (key.pageDown || input === "d") {
183
- const half = Math.floor(viewHeight / 2);
184
- setScrollOffset((o) => Math.min(maxOffset, o + half));
185
- setCursor((c) => Math.min(allLines.length - 1, c + half));
186
- return;
187
- }
188
- if (key.pageUp || input === "u") {
189
- const half = Math.floor(viewHeight / 2);
190
- setScrollOffset((o) => Math.max(0, o - half));
191
- setCursor((c) => Math.max(0, c - half));
192
- return;
193
- }
194
- if (input === "g") {
195
- setScrollOffset(0);
196
- setCursor(0);
197
- return;
198
- }
199
- if (input === "G") {
200
- setScrollOffset(maxOffset);
201
- setCursor(allLines.length - 1);
202
- return;
203
- }
204
- if (key.return) {
205
- const line = allLines[clampedCursor];
206
- if (line?.kind === "tool") {
207
- const id = line.callId;
208
- setExpandedIds((prev) => {
209
- const next = new Set(prev);
210
- if (next.has(id)) next.delete(id);
211
- else next.add(id);
212
- return next;
213
- });
214
- }
215
- return;
216
- }
217
- }, { isActive });
218
-
219
- if (!session) {
220
- return (
221
- <Box height={maxHeight} paddingX={1}>
222
- <Text color={colors.textDim}>Select a session</Text>
223
- </Box>
224
- );
225
- }
226
-
227
- if (allLines.length === 0) {
228
- return (
229
- <Box height={maxHeight} paddingX={1}>
230
- <Text color={colors.textDim}>No messages</Text>
231
- </Box>
232
- );
233
- }
234
-
235
- const visibleLines = allLines.slice(clampedOffset, clampedOffset + viewHeight);
236
-
237
- return (
238
- <Box flexDirection="column" height={maxHeight} paddingX={1}>
239
- {/* Counter row */}
240
- <Box flexDirection="row" height={1}>
241
- <Text color={colors.textDim}>
242
- {clampedOffset + 1}–{Math.min(clampedOffset + viewHeight, allLines.length)}/{allLines.length}
243
- </Text>
244
- <Box flexGrow={1} />
245
- <Text color={colors.textDim}>
246
- {session.interactions.filter((i) => i.role === "assistant").length} turns
247
- </Text>
248
- {clampedOffset > 0 && <Text color={colors.textDim}> ↑</Text>}
249
- {clampedOffset < maxOffset && <Text color={colors.textDim}> ↓</Text>}
250
- </Box>
251
-
252
- {visibleLines.map((line, i) => {
253
- const absIdx = clampedOffset + i;
254
- const isCursor = absIdx === clampedCursor && isActive;
255
-
256
- switch (line.kind) {
257
- case "header":
258
- return (
259
- <Box key={line.id} flexDirection="row" height={1}>
260
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
261
- <Text color={isCursor ? colors.accent : colors.purple} bold>◆ </Text>
262
- <Text color={isCursor ? colors.accent : colors.info}>{truncate(line.modelId, 26)}</Text>
263
- {line.agent && <Text color={colors.cyan}> [{line.agent}]</Text>}
264
- <Box flexGrow={1} />
265
- {line.duration && <Text color={colors.textDim}>{line.duration} </Text>}
266
- <Text color={colors.textDim}>{line.time}</Text>
267
- </Box>
268
- );
269
-
270
- case "tool":
271
- return (
272
- <Box key={line.id} flexDirection="row" height={1} paddingLeft={1}>
273
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
274
- <Text color={line.iconColor}>{line.icon} </Text>
275
- <Text color={isCursor ? colors.accent : colors.text} bold={isCursor}>{line.name}</Text>
276
- {line.title && <Text color={colors.textDim}> {line.title}</Text>}
277
- <Box flexGrow={1} />
278
- <Text color={colors.textDim}>{line.expanded ? "▼ " : "▶ "}</Text>
279
- {line.right && <Text color={colors.textDim}>{line.right}</Text>}
280
- </Box>
281
- );
282
-
283
- case "tool-detail":
284
- if (line.isSection) {
285
- return (
286
- <Box key={line.id} height={1} paddingLeft={3}>
287
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
288
- <Text color={colors.purple}>── {line.label} </Text>
289
- </Box>
290
- );
291
- }
292
- return (
293
- <Box key={line.id} height={1} paddingLeft={4}>
294
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
295
- {line.label
296
- ? <><Text color={colors.cyan}>{line.label}</Text><Text color={colors.textDim}>: </Text><Text color={isCursor ? colors.accent : colors.text}>{line.value}</Text></>
297
- : <Text color={isCursor ? colors.accent : colors.textDim}>{line.value}</Text>
298
- }
299
- </Box>
300
- );
301
-
302
- case "text":
303
- return (
304
- <Box key={line.id} flexDirection="row" height={1} paddingLeft={1}>
305
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
306
- <Text color={isCursor ? colors.accent : colors.text}> {line.text}</Text>
307
- </Box>
308
- );
309
-
310
- case "reasoning":
311
- return (
312
- <Box key={line.id} flexDirection="row" height={1} paddingLeft={1}>
313
- <Text color={isCursor ? colors.accent : colors.textDim}>{isCursor ? "›" : " "}</Text>
314
- <Text color={isCursor ? colors.accent : colors.accentDim}> ⚡ {line.text}</Text>
315
- </Box>
316
- );
317
- }
318
- })}
319
- </Box>
320
- );
321
- }
322
-
323
- export const MessagesPanel = memo(MessagesPanelInner);
@@ -1,18 +0,0 @@
1
- import React, { memo } from "react";
2
- import { Text } from "ink";
3
- import { colors } from "../theme";
4
- import { buildSparkSeries } from "../../core/session";
5
-
6
- interface SparkLineProps {
7
- values: number[];
8
- color?: string;
9
- width?: number;
10
- }
11
-
12
- function SparkLineInner({ values, color = colors.info, width }: SparkLineProps) {
13
- const data = width && values.length > width ? values.slice(-width) : values;
14
- const spark = buildSparkSeries(data);
15
- return <Text color={color}>{spark}</Text>;
16
- }
17
-
18
- export const SparkLine = memo(SparkLineInner);
@@ -1,24 +0,0 @@
1
- import React, { memo } from "react";
2
- import { Box, Text } from "ink";
3
- import { colors } from "../theme";
4
-
5
- interface StatusBarProps {
6
- hints: string;
7
- info?: string;
8
- }
9
-
10
- function StatusBarInner({ hints, info }: StatusBarProps) {
11
- return (
12
- <Box paddingX={1} borderStyle="single" borderColor={colors.border} flexDirection="row">
13
- <Text color={colors.textDim}>{hints}</Text>
14
- {info && (
15
- <>
16
- <Box flexGrow={1} />
17
- <Text color={colors.info}>{info}</Text>
18
- </>
19
- )}
20
- </Box>
21
- );
22
- }
23
-
24
- export const StatusBar = memo(StatusBarInner);
@@ -1,42 +0,0 @@
1
- import React, { memo } from "react";
2
- import { Box, Text } from "ink";
3
- import { colors } from "../theme";
4
- import type { ScreenId } from "../../core/types";
5
-
6
- interface TabBarProps {
7
- activeScreen: ScreenId;
8
- lastRefresh: Date;
9
- }
10
-
11
- const TABS: { id: ScreenId; label: string; key: string }[] = [
12
- { id: "sessions", label: "Sessions", key: "1" },
13
- { id: "tools", label: "Tools", key: "2" },
14
- { id: "overview", label: "Overview", key: "3" },
15
- ];
16
-
17
- function TabBarInner({ activeScreen, lastRefresh }: TabBarProps) {
18
- return (
19
- <Box paddingX={1} borderStyle="single" borderColor={colors.border} flexDirection="row">
20
- <Text color={colors.accent} bold>
21
- OCMonitor{" "}
22
- </Text>
23
- {TABS.map((tab) => {
24
- const isActive = tab.id === activeScreen;
25
- return (
26
- <Box key={tab.id} marginRight={1}>
27
- <Text
28
- color={isActive ? colors.accent : colors.textDim}
29
- bold={isActive}
30
- >
31
- [{tab.key}]{isActive ? <Text color={colors.text}> {tab.label}</Text> : ` ${tab.label}`}
32
- </Text>
33
- </Box>
34
- );
35
- })}
36
- <Box flexGrow={1} />
37
- <Text color={colors.textDim}>{lastRefresh.toLocaleTimeString()}</Text>
38
- </Box>
39
- );
40
- }
41
-
42
- export const TabBar = memo(TabBarInner);