@scrylog/cli 0.3.0 → 0.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 (195) hide show
  1. package/dist/assets/highlights-eq9cgrbb.scm +604 -0
  2. package/dist/assets/highlights-ghv9g403.scm +205 -0
  3. package/dist/assets/highlights-hk7bwhj4.scm +284 -0
  4. package/dist/assets/highlights-r812a2qc.scm +150 -0
  5. package/dist/assets/highlights-x6tmsnaa.scm +115 -0
  6. package/dist/assets/injections-73j83es3.scm +27 -0
  7. package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
  8. package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
  9. package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/dist/cli/index.js +499 -0
  15. package/dist/cli/index.js.map +61 -0
  16. package/dist/daemon/index.js +670 -0
  17. package/dist/daemon/index.js.map +1475 -0
  18. package/dist/hooks/claude.js +6 -0
  19. package/dist/hooks/claude.js.map +11 -0
  20. package/dist/hooks/codex.js +10 -0
  21. package/dist/hooks/codex.js.map +11 -0
  22. package/dist/mcp/index.js +100 -0
  23. package/dist/mcp/index.js.map +231 -0
  24. package/dist/oc/index.js +21 -0
  25. package/dist/oc/index.js.map +10 -0
  26. package/package.json +16 -13
  27. package/src/api/docs/sessions.keys.ts +0 -6
  28. package/src/api/docs/sessions.ts +0 -82
  29. package/src/api/docs/system.keys.ts +0 -5
  30. package/src/api/docs/system.ts +0 -69
  31. package/src/api/docs/usage.keys.ts +0 -3
  32. package/src/api/docs/usage.ts +0 -24
  33. package/src/api/docs/webhooks.keys.ts +0 -5
  34. package/src/api/docs/webhooks.ts +0 -78
  35. package/src/api/index.ts +0 -18
  36. package/src/api/lib/auth.ts +0 -43
  37. package/src/api/lib/errors.ts +0 -28
  38. package/src/api/lib/route.ts +0 -18
  39. package/src/api/models/claude.ts +0 -4
  40. package/src/api/models/codex.ts +0 -4
  41. package/src/api/models/opencode.ts +0 -11
  42. package/src/api/routes/sessions.ts +0 -51
  43. package/src/api/routes/system.ts +0 -106
  44. package/src/api/routes/usage.ts +0 -15
  45. package/src/api/routes/webhooks/claude.ts +0 -149
  46. package/src/api/routes/webhooks/codex.ts +0 -175
  47. package/src/api/routes/webhooks/opencode.ts +0 -112
  48. package/src/api/routes.ts +0 -93
  49. package/src/api/schemas/common.ts +0 -29
  50. package/src/api/schemas/sessions.ts +0 -86
  51. package/src/api/schemas/usage.ts +0 -51
  52. package/src/api/schemas/webhooks/claude.ts +0 -32
  53. package/src/api/schemas/webhooks/codex.ts +0 -34
  54. package/src/api/schemas/webhooks/opencode.ts +0 -29
  55. package/src/api/services/claude-session.ts +0 -9
  56. package/src/api/services/codex-session.ts +0 -8
  57. package/src/api/services/opencode-session.ts +0 -73
  58. package/src/classifier.ts +0 -206
  59. package/src/cli/commands.ts +0 -714
  60. package/src/cli/index.ts +0 -18
  61. package/src/cli/program.ts +0 -175
  62. package/src/cli/prompt.ts +0 -44
  63. package/src/daemon/cli.ts +0 -27
  64. package/src/daemon/collectors/claude/README.md +0 -108
  65. package/src/daemon/collectors/claude/collector.ts +0 -423
  66. package/src/daemon/collectors/claude/discovery.ts +0 -127
  67. package/src/daemon/collectors/claude/hooks.ts +0 -181
  68. package/src/daemon/collectors/claude/index.ts +0 -8
  69. package/src/daemon/collectors/claude/jsonl.ts +0 -85
  70. package/src/daemon/collectors/claude/transcript.ts +0 -246
  71. package/src/daemon/collectors/codex/README.md +0 -147
  72. package/src/daemon/collectors/codex/collector.ts +0 -661
  73. package/src/daemon/collectors/codex/discovery.ts +0 -104
  74. package/src/daemon/collectors/codex/hooks.ts +0 -179
  75. package/src/daemon/collectors/codex/index.ts +0 -7
  76. package/src/daemon/collectors/codex/jsonl.ts +0 -108
  77. package/src/daemon/collectors/codex/transcript.ts +0 -299
  78. package/src/daemon/collectors/git.ts +0 -353
  79. package/src/daemon/collectors/herdr/collector.ts +0 -128
  80. package/src/daemon/collectors/herdr/index.ts +0 -2
  81. package/src/daemon/collectors/herdr/normalization.ts +0 -111
  82. package/src/daemon/collectors/opencode/README.md +0 -106
  83. package/src/daemon/collectors/opencode/collector.ts +0 -545
  84. package/src/daemon/collectors/opencode/discovery.ts +0 -257
  85. package/src/daemon/collectors/opencode/index.ts +0 -5
  86. package/src/daemon/collectors/opencode/session-builder.ts +0 -59
  87. package/src/daemon/collectors/opencode-db.ts +0 -294
  88. package/src/daemon/collectors/process.ts +0 -201
  89. package/src/daemon/collectors/tmux/README.md +0 -128
  90. package/src/daemon/collectors/tmux/collector.ts +0 -492
  91. package/src/daemon/collectors/tmux/enrichment.ts +0 -242
  92. package/src/daemon/collectors/tmux/index.ts +0 -4
  93. package/src/daemon/collectors/tmux/parse.ts +0 -128
  94. package/src/daemon/collectors/usage.ts +0 -259
  95. package/src/daemon/index.ts +0 -209
  96. package/src/daemon/scheduler.ts +0 -86
  97. package/src/daemon/store.ts +0 -630
  98. package/src/detect.ts +0 -143
  99. package/src/hooks/claude.ts +0 -596
  100. package/src/hooks/codex.ts +0 -517
  101. package/src/legacy/classifier.ts +0 -215
  102. package/src/legacy/detect.ts +0 -143
  103. package/src/mcp/client-server.ts +0 -21
  104. package/src/mcp/http-handler.ts +0 -126
  105. package/src/mcp/index.ts +0 -61
  106. package/src/mcp/resources/config-current.ts +0 -19
  107. package/src/mcp/resources/index.ts +0 -11
  108. package/src/mcp/resources/sessions-active.ts +0 -19
  109. package/src/mcp/resources/sessions-all.ts +0 -19
  110. package/src/mcp/schemas/sessions.ts +0 -3
  111. package/src/mcp/server.ts +0 -17
  112. package/src/mcp/services/api-adapter.ts +0 -48
  113. package/src/mcp/services/data-source.ts +0 -22
  114. package/src/mcp/services/store-adapter.ts +0 -40
  115. package/src/mcp/tools/get-active-sessions.ts +0 -16
  116. package/src/mcp/tools/get-session-children.ts +0 -24
  117. package/src/mcp/tools/get-session-messages.ts +0 -24
  118. package/src/mcp/tools/get-session.ts +0 -23
  119. package/src/mcp/tools/index.ts +0 -15
  120. package/src/mcp/tools/list-sessions.ts +0 -16
  121. package/src/oc/index.ts +0 -302
  122. package/src/service/manager.ts +0 -284
  123. package/src/shared/config.ts +0 -511
  124. package/src/shared/ipc.ts +0 -80
  125. package/src/shared/logger.ts +0 -248
  126. package/src/shared/paths.ts +0 -73
  127. package/src/shared/platform.ts +0 -110
  128. package/src/shared/session-exclusions.ts +0 -5
  129. package/src/shared/session-view.ts +0 -302
  130. package/src/shared/terminal-capabilities.ts +0 -130
  131. package/src/shared/types.ts +0 -330
  132. package/src/shared/utils.ts +0 -339
  133. package/src/shared/version.ts +0 -44
  134. package/src/tui/App.tsx +0 -240
  135. package/src/tui/client.ts +0 -163
  136. package/src/tui/components/BindingsModal.tsx +0 -82
  137. package/src/tui/components/DashboardHeader.tsx +0 -256
  138. package/src/tui/components/KeyBar.tsx +0 -67
  139. package/src/tui/components/SessionDetailModal.tsx +0 -169
  140. package/src/tui/components/SessionsView.tsx +0 -184
  141. package/src/tui/components/TabBar.tsx +0 -40
  142. package/src/tui/components/UsageSummaryPane.tsx +0 -272
  143. package/src/tui/components/UsageView.tsx +0 -242
  144. package/src/tui/components/tool-icons.ts +0 -24
  145. package/src/tui/index.tsx +0 -106
  146. package/src/tui/keybindings.ts +0 -49
  147. package/src/tui/model.ts +0 -295
  148. package/src/tui/state.ts +0 -77
  149. package/src/tui/theme.ts +0 -110
  150. package/src/types.ts +0 -113
  151. package/src/usage/README.md +0 -323
  152. package/src/usage/api.ts +0 -99
  153. package/src/usage/client.ts +0 -3
  154. package/src/usage/clients/anthropic-oauth-usage.ts +0 -258
  155. package/src/usage/clients/anthropic-web-usage.ts +0 -329
  156. package/src/usage/clients/claude-cli-probe.ts +0 -513
  157. package/src/usage/clients/codex-app-server.ts +0 -354
  158. package/src/usage/clients/codex-oauth-usage.ts +0 -240
  159. package/src/usage/clients/opencode-go-api.ts +0 -100
  160. package/src/usage/clients/opencode-go-usage.ts +0 -577
  161. package/src/usage/errors.ts +0 -33
  162. package/src/usage/index.ts +0 -20
  163. package/src/usage/local/opencode-go-local.ts +0 -194
  164. package/src/usage/mappers.ts +0 -282
  165. package/src/usage/planner.ts +0 -118
  166. package/src/usage/providers/claude.ts +0 -233
  167. package/src/usage/providers/codex.ts +0 -115
  168. package/src/usage/providers/opencode-go.ts +0 -115
  169. package/src/usage/resolver.ts +0 -4
  170. package/src/usage/resolvers/browser-cookies.ts +0 -808
  171. package/src/usage/resolvers/claude-auth.ts +0 -208
  172. package/src/usage/resolvers/claude-cli.ts +0 -24
  173. package/src/usage/resolvers/claude-desktop-auth.ts +0 -170
  174. package/src/usage/resolvers/claude-web.ts +0 -82
  175. package/src/usage/resolvers/codex-auth.ts +0 -174
  176. package/src/usage/resolvers/keychain-coordinator.ts +0 -397
  177. package/src/usage/resolvers/opencode-go-web.ts +0 -55
  178. package/src/usage/types.ts +0 -225
  179. package/src/usage/web-session-cache.ts +0 -37
  180. /package/{src/tui → dist/cli}/assets/providers/SOURCES.md +0 -0
  181. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.png +0 -0
  182. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.svg +0 -0
  183. /package/{src/tui → dist/cli}/assets/providers/claude.png +0 -0
  184. /package/{src/tui → dist/cli}/assets/providers/copilot.png +0 -0
  185. /package/{src/tui → dist/cli}/assets/providers/copilot.svg +0 -0
  186. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.png +0 -0
  187. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.svg +0 -0
  188. /package/{src/tui → dist/cli}/assets/providers/openai.png +0 -0
  189. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.png +0 -0
  190. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.svg +0 -0
  191. /package/{src/tui → dist/cli}/assets/providers/opencode.png +0 -0
  192. /package/{src/tui → dist/cli}/assets/providers/shell.png +0 -0
  193. /package/{src/tui → dist/cli}/assets/providers/shell.svg +0 -0
  194. /package/{src/tui → dist/cli}/assets/providers/unknown.png +0 -0
  195. /package/{src/tui → dist/cli}/assets/providers/unknown.svg +0 -0
package/src/tui/App.tsx DELETED
@@ -1,240 +0,0 @@
1
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
2
- import { useEffect, useReducer, useRef, useState } from "react";
3
- import type { ScrylogConfig } from "../shared/config";
4
- import type { SessionModel, StoreEvent } from "../shared/types";
5
- import type { SessionListItem } from "../shared/session-view";
6
- import { applySessionFilters, buildRows, moveSelection, sessionAttachCommand, visibleRowWindow } from "./model";
7
- import { dashboardReducer, INITIAL_DASHBOARD_STATE } from "./state";
8
- import type { DaemonClient } from "./client";
9
- import { SessionsView } from "./components/SessionsView";
10
- import { UsageView } from "./components/UsageView";
11
- import { TabBar } from "./components/TabBar";
12
- import { KeyBar } from "./components/KeyBar";
13
- import { BindingsModal } from "./components/BindingsModal";
14
- import { DashboardHeader, dashboardHeaderHeight } from "./components/DashboardHeader";
15
- import { isInsideTmux } from "../shared/utils";
16
- import type { DaemonVersionMismatch } from "../shared/version";
17
-
18
- export interface AppOptions {
19
- refreshMs: number;
20
- includeUnknown?: boolean;
21
- }
22
-
23
- export interface AppProps {
24
- client: DaemonClient;
25
- options: AppOptions;
26
- config: ScrylogConfig;
27
- versionMismatch?: DaemonVersionMismatch;
28
- onExit(): void;
29
- onAttach(command: string[], sessionId: string): void;
30
- }
31
-
32
- export function App({ client, options, config, versionMismatch, onExit, onAttach }: AppProps) {
33
- const { width, height } = useTerminalDimensions();
34
- const paneHeight = Math.max(1, height - dashboardHeaderHeight(width) - 2);
35
- const [state, dispatch] = useReducer(dashboardReducer, INITIAL_DASHBOARD_STATE);
36
- const [spinnerFrame, setSpinnerFrame] = useState(0);
37
- const stateRef = useRef(state);
38
- const rowsRef = useRef<ReturnType<typeof buildRows>>([]);
39
- const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
40
- const knownStatesRef = useRef(new Map<string, SessionModel["state"]>());
41
- stateRef.current = state;
42
-
43
- useEffect(() => {
44
- let active = true;
45
- const refreshSessions = async () => {
46
- const [status, sessionData] = await Promise.all([
47
- client.getStatusSummary(options.includeUnknown),
48
- client.getSessions(options.includeUnknown),
49
- ]);
50
- if (!active) return;
51
- if (!status || !sessionData) {
52
- dispatch({ type: "connection", connected: false });
53
- return;
54
- }
55
- knownStatesRef.current = new Map(sessionData.sessions.map((session) => [session.id, session.state]));
56
- dispatch({ type: "sessions-loaded", sessions: sessionData.sessions, counts: status.counts });
57
- };
58
- const refreshAll = async () => {
59
- const usage = await client.getUsage();
60
- if (active && usage) dispatch({ type: "usage-loaded", usage });
61
- await refreshSessions();
62
- };
63
- const scheduleRefresh = (immediate: boolean) => {
64
- if (debounceRef.current) clearTimeout(debounceRef.current);
65
- if (immediate || config.tui.pushDebounceMs <= 0) {
66
- debounceRef.current = null;
67
- void refreshSessions();
68
- return;
69
- }
70
- debounceRef.current = setTimeout(() => {
71
- debounceRef.current = null;
72
- void refreshSessions();
73
- }, config.tui.pushDebounceMs);
74
- };
75
- const onStoreEvent = (event: StoreEvent | { type: "connected" } | { type: "disconnected" }) => {
76
- if (event.type === "connected") {
77
- dispatch({ type: "connection", connected: true });
78
- void refreshAll();
79
- } else if (event.type === "disconnected") {
80
- dispatch({ type: "connection", connected: false });
81
- } else if (event.type === "usage:updated") {
82
- dispatch({ type: "usage-loaded", usage: event.usage });
83
- } else if (event.type === "session:added" || event.type === "session:removed") {
84
- scheduleRefresh(true);
85
- } else if (event.type === "session:updated") {
86
- const known = knownStatesRef.current.get(event.session.id);
87
- scheduleRefresh(known === undefined || known !== event.session.state);
88
- }
89
- };
90
-
91
- void refreshAll();
92
- const polling = setInterval(() => void refreshAll(), options.refreshMs);
93
- const unsubscribe = config.tui.usePushUpdates ? client.subscribeEvents(onStoreEvent) : undefined;
94
- return () => {
95
- active = false;
96
- clearInterval(polling);
97
- unsubscribe?.();
98
- if (debounceRef.current) clearTimeout(debounceRef.current);
99
- debounceRef.current = null;
100
- };
101
- }, [client, config.tui.pushDebounceMs, config.tui.usePushUpdates, options.includeUnknown, options.refreshMs]);
102
-
103
- useEffect(() => {
104
- if (state.view !== "sessions") return;
105
- const animation = setInterval(() => setSpinnerFrame((frame) => (frame + 1) % 10), 180);
106
- return () => clearInterval(animation);
107
- }, [state.view]);
108
-
109
- const filtered = applySessionFilters(state.sessions, state.filters, config.tui.standaloneMaxAgeHours);
110
- const rows = buildRows(filtered, width < 110);
111
- rowsRef.current = rows;
112
- const usagePaneRows = state.view === "sessions" && state.usage ? 3 : 0;
113
- const visibleRows = visibleRowWindow(rows, state.selectedSessionId, Math.max(4, height - dashboardHeaderHeight(width) - usagePaneRows - (state.mode === "search" ? 8 : 5)));
114
-
115
- const openDetail = async (session: SessionListItem | undefined) => {
116
- if (!session) return;
117
- const detail = await client.getSessionDetail(session.id) ?? ({ ...session } as SessionModel);
118
- dispatch({ type: "detail", session: detail });
119
- };
120
-
121
- const attach = (session: SessionListItem | undefined) => {
122
- if (!session) return;
123
- const command = sessionAttachCommand(session, isInsideTmux());
124
- if (!command) {
125
- dispatch({
126
- type: "message",
127
- message: session.source === "herdr"
128
- ? "Herdr attach is unavailable because this pane has no terminal ID."
129
- : "Attach is only available for sessions running inside tmux or herdr.",
130
- });
131
- return;
132
- }
133
- onAttach(command, session.id);
134
- };
135
-
136
- useKeyboard((key) => {
137
- const currentState = stateRef.current;
138
- const currentSelected = currentState.sessions.find((session) => session.id === currentState.selectedSessionId);
139
- if ((key.ctrl && key.name === "c") || (key.name === "q" && currentState.mode === "list")) return onExit();
140
- if (currentState.mode === "search") {
141
- if (key.name === "escape" || key.name === "return" || key.name === "enter") dispatch({ type: "close-overlay" });
142
- return;
143
- }
144
- if (currentState.mode === "detail" || currentState.mode === "message" || currentState.mode === "bindings") {
145
- if (key.name === "escape" || key.name === "q") dispatch({ type: "close-overlay" });
146
- if (key.name === "t" && currentState.detail) attach(currentState.detail as SessionListItem);
147
- return;
148
- }
149
- if (key.name === "tab") {
150
- dispatch({ type: "view", view: key.shift ? "sessions" : currentState.view === "sessions" ? "usage" : "sessions" });
151
- return;
152
- }
153
- if (key.name === "u") return dispatch({ type: "view", view: "usage" });
154
- if (key.name === "s") return dispatch({ type: "view", view: "sessions" });
155
- if (key.name === "r") {
156
- void Promise.all([client.getStatusSummary(options.includeUnknown), client.getSessions(options.includeUnknown), client.getUsage()])
157
- .then(([status, sessions, usage]) => {
158
- if (status && sessions) dispatch({ type: "sessions-loaded", sessions: sessions.sessions, counts: status.counts });
159
- if (usage) dispatch({ type: "usage-loaded", usage });
160
- });
161
- return;
162
- }
163
- if (key.name === "?") return dispatch({ type: "bindings" });
164
- if (currentState.view !== "sessions") return;
165
- if (key.name === "up" || key.name === "k") return dispatch({ type: "select", id: moveSelection(rowsRef.current, currentState.selectedSessionId, -1) });
166
- if (key.name === "down" || key.name === "j") return dispatch({ type: "select", id: moveSelection(rowsRef.current, currentState.selectedSessionId, 1) });
167
- if (key.name === "escape") return dispatch({ type: "select", id: undefined });
168
- if (key.name === "return" || key.name === "enter") return void openDetail(currentSelected);
169
- if (key.name === "t") return attach(currentSelected);
170
- if (key.name === "/") return dispatch({ type: "search" });
171
- if (key.name === "a") return dispatch({ type: "toggle-actionable" });
172
- if (key.name === "f") return dispatch({ type: "cycle-filters" });
173
- });
174
-
175
- const filterSummary = [
176
- state.filters.query ? `q=${state.filters.query}` : null,
177
- state.filters.actionableOnly ? "actionable" : null,
178
- state.filters.tool !== "all" ? `tool=${state.filters.tool}` : null,
179
- state.filters.state !== "all" ? `state=${state.filters.state}` : null,
180
- ].filter(Boolean).join(" ");
181
- const selectedSession = state.sessions.find((session) => session.id === state.selectedSessionId);
182
-
183
- return (
184
- <box width="100%" height="100%" flexDirection="column">
185
- <DashboardHeader
186
- sessions={state.sessions}
187
- connected={state.daemonConnected}
188
- spinnerFrame={spinnerFrame}
189
- width={width}
190
- versionMismatch={versionMismatch}
191
- />
192
- <TabBar
193
- view={state.view}
194
- onSelect={(next) => dispatch({ type: "view", view: next })}
195
- />
196
- <box
197
- position={state.view === "sessions" ? "relative" : "absolute"}
198
- top={state.view === "sessions" ? 0 : height + 2}
199
- width="100%"
200
- height={paneHeight}
201
- >
202
- <SessionsView
203
- rows={visibleRows}
204
- sessions={state.sessions}
205
- selectedSessionId={state.selectedSessionId}
206
- filters={state.filters}
207
- mode={state.mode}
208
- detail={state.detail}
209
- message={state.message}
210
- usage={state.usage}
211
- iconMode={config.tui.providerIcons}
212
- spinnerFrame={spinnerFrame}
213
- width={width}
214
- interactive={state.mode === "list" && state.view === "sessions"}
215
- onQueryChange={(query) => dispatch({ type: "query", query })}
216
- onSearchSubmit={() => dispatch({ type: "close-overlay" })}
217
- onSelectSession={(id) => dispatch({ type: "select", id })}
218
- onOpenSessionDetail={(session) => void openDetail(session)}
219
- onScrollSelection={(direction) => dispatch({ type: "select", id: moveSelection(rowsRef.current, stateRef.current.selectedSessionId, direction) })}
220
- />
221
- </box>
222
- <box
223
- position={state.view === "usage" ? "relative" : "absolute"}
224
- top={state.view === "usage" ? 0 : height + 2}
225
- width="100%"
226
- height={paneHeight}
227
- >
228
- <UsageView usage={state.usage} width={width} iconMode={config.tui.providerIcons} hidden={state.view !== "usage"} />
229
- </box>
230
- <KeyBar
231
- view={state.view}
232
- width={width}
233
- context={state.view === "sessions"
234
- ? `${selectedSession ? `${selectedSession.tool} ${selectedSession.state} ${selectedSession.project?.compactLabel ?? selectedSession.id}` : "no selection"}${filterSummary ? ` ${filterSummary}` : ""}`
235
- : undefined}
236
- />
237
- {state.mode === "bindings" && <BindingsModal width={width} iconMode={config.tui.providerIcons} />}
238
- </box>
239
- );
240
- }
package/src/tui/client.ts DELETED
@@ -1,163 +0,0 @@
1
- import { createDaemonFetch, getSocketPath } from "../shared/ipc";
2
- import { IS_WINDOWS } from "../shared/platform";
3
- import type { ScrylogConfig } from "../shared/config";
4
- import type { DaemonHealth, HealthResponse, SessionModel, StoreEvent } from "../shared/types";
5
- import type { SessionListItem, SessionTreeItem, SessionViewCounts } from "../shared/session-view";
6
- import { flattenSessionTree } from "../shared/session-view";
7
- import type { UsageState } from "../usage/types";
8
-
9
- type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
10
-
11
- export class DaemonClient {
12
- private readonly fetch: FetchFn;
13
-
14
- constructor(config: ScrylogConfig, socketPath?: string) {
15
- this.fetch = createDaemonFetch({
16
- host: config.api.host,
17
- port: config.api.port,
18
- socketPath: socketPath ?? (IS_WINDOWS ? undefined : getSocketPath(config.daemon.socketPath)),
19
- bearerToken: config.api.bearerToken,
20
- });
21
- }
22
-
23
- async getStatusSummary(includeUnknown = false): Promise<{ counts: SessionViewCounts; collectorStatus: Record<string, unknown>; generatedAt: number } | null> {
24
- try {
25
- const response = await this.fetch(includeUnknown ? "/api/status?includeUnknown=true" : "/api/status");
26
- if (!response.ok) return null;
27
- return await response.json() as { counts: SessionViewCounts; collectorStatus: Record<string, unknown>; generatedAt: number };
28
- } catch {
29
- return null;
30
- }
31
- }
32
-
33
- async getHealth(): Promise<DaemonHealth | null> {
34
- try {
35
- const response = await this.fetch("/api/health");
36
- if (!response.ok) return null;
37
- const body = await response.json() as Partial<HealthResponse>;
38
- if (body.ok !== true) return null;
39
- return body as DaemonHealth;
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- async getSessions(includeUnknown = false): Promise<{ sessions: SessionListItem[]; counts: SessionViewCounts; generatedAt: number } | null> {
46
- try {
47
- const response = await this.fetch(includeUnknown ? "/api/sessions?includeUnknown=true" : "/api/sessions");
48
- if (!response.ok) return null;
49
- const data = await response.json() as { sessions: SessionTreeItem[]; counts: SessionViewCounts; generatedAt: number };
50
- return {
51
- sessions: flattenSessionTree(data.sessions),
52
- counts: data.counts,
53
- generatedAt: data.generatedAt,
54
- };
55
- } catch {
56
- return null;
57
- }
58
- }
59
-
60
- async getSessionDetail(id: string): Promise<SessionModel | null> {
61
- try {
62
- const response = await this.fetch(`/api/sessions/${encodeURIComponent(id)}`);
63
- if (!response.ok) return null;
64
- const data = await response.json() as { session: SessionModel };
65
- return data.session;
66
- } catch {
67
- return null;
68
- }
69
- }
70
-
71
- async getUsage(): Promise<UsageState | null> {
72
- try {
73
- const response = await this.fetch("/api/usage");
74
- if (!response.ok) return null;
75
- return await response.json() as UsageState;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- /**
82
- * Subscribe to daemon SSE events with automatic reconnect.
83
- * Returns an unsubscribe function.
84
- */
85
- subscribeEvents(onEvent: (event: StoreEvent | { type: "connected" } | { type: "disconnected" }) => void): () => void {
86
- let aborted = false;
87
- const controller = new AbortController();
88
- let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
89
- let reconnectDelay = 1_000;
90
-
91
- const connect = async () => {
92
- if (aborted) return;
93
- try {
94
- const response = await this.fetch("/api/events", {
95
- signal: controller.signal,
96
- headers: { Accept: "text/event-stream" },
97
- });
98
-
99
- if (!response.ok || !response.body) {
100
- if (!aborted) {
101
- onEvent({ type: "disconnected" });
102
- }
103
- return;
104
- }
105
-
106
- onEvent({ type: "connected" });
107
- reconnectDelay = 1_000; // reset backoff on successful connect
108
-
109
- const reader = response.body.getReader();
110
- const decoder = new TextDecoder();
111
- let buffer = "";
112
-
113
- while (!aborted) {
114
- const { value, done } = await reader.read();
115
- if (done) break;
116
-
117
- buffer += decoder.decode(value, { stream: true });
118
- const lines = buffer.split("\n");
119
- buffer = lines.pop() ?? "";
120
-
121
- for (const line of lines) {
122
- if (line.startsWith("data: ")) {
123
- try {
124
- const payload = JSON.parse(line.slice(6)) as StoreEvent;
125
- onEvent(payload);
126
- } catch {
127
- // Ignore malformed events
128
- }
129
- }
130
- }
131
- }
132
- } catch (error) {
133
- if (!aborted) {
134
- onEvent({ type: "disconnected" });
135
- }
136
- }
137
- };
138
-
139
- const scheduleReconnect = () => {
140
- if (aborted || reconnectTimer) return;
141
- reconnectTimer = setTimeout(() => {
142
- reconnectTimer = null;
143
- void connect().then(() => {
144
- if (!aborted) scheduleReconnect();
145
- });
146
- }, reconnectDelay);
147
- reconnectDelay = Math.min(reconnectDelay * 1.5, 5_000);
148
- };
149
-
150
- void connect().then(() => {
151
- if (!aborted) scheduleReconnect();
152
- });
153
-
154
- return () => {
155
- aborted = true;
156
- controller.abort();
157
- if (reconnectTimer) {
158
- clearTimeout(reconnectTimer);
159
- reconnectTimer = null;
160
- }
161
- };
162
- }
163
- }
@@ -1,82 +0,0 @@
1
- import { TextAttributes } from "@opentui/core";
2
- import { bindingsForModal } from "../keybindings";
3
- import type { TuiConfig } from "../../shared/config";
4
- import { ProviderIcon } from "./UsageView";
5
- import { MINI_ICON_SIZE, isImageIconMode, toolProviderKey } from "./tool-icons";
6
- import { BRAND } from "../theme";
7
-
8
- const TOOL_LEGEND: { tool: string; label: string }[] = [
9
- { tool: "claude", label: "Claude" },
10
- { tool: "codex", label: "Codex" },
11
- { tool: "opencode", label: "OpenCode" },
12
- { tool: "copilotcli", label: "Copilot CLI" },
13
- { tool: "shell", label: "Shell" },
14
- { tool: "unknown", label: "Unknown" },
15
- ];
16
-
17
- export function BindingsModal({ width, iconMode }: { width: number; iconMode: TuiConfig["providerIcons"] }) {
18
- const groups = bindingsForModal();
19
- const showIcons = isImageIconMode(iconMode);
20
- return (
21
- <box
22
- position="absolute"
23
- top="8%"
24
- left="10%"
25
- width="80%"
26
- height="70%"
27
- title="Keybindings"
28
- border
29
- borderColor={BRAND.primary}
30
- backgroundColor={BRAND.surface}
31
- flexDirection="column"
32
- padding={1}
33
- zIndex={20}
34
- >
35
- <box flexGrow={1} overflow="hidden" flexDirection="column">
36
- <scrollbox flexGrow={1} height="100%" scrollY focused>
37
- {groups.map((group) => (
38
- <box key={group.scope} flexDirection="column">
39
- <text
40
- fg={BRAND.grey}
41
- attributes={TextAttributes.BOLD}
42
- content={`── ${group.scope === "global" ? "Global" : group.scope === "sessions" ? "Sessions tab" : "Usage tab"} ──`}
43
- />
44
- {group.bindings.map((binding) => (
45
- <box key={`${group.scope}:${binding.key}`} flexDirection="row" height={1}>
46
- <text width={Math.min(14, Math.max(10, Math.floor(width * 0.2)))} fg={BRAND.primary} attributes={TextAttributes.BOLD} content={binding.key} />
47
- <text fg={BRAND.grey} content={binding.description} />
48
- </box>
49
- ))}
50
- <text height={1} content="" />
51
- </box>
52
- ))}
53
- {showIcons && (
54
- <box flexDirection="column">
55
- <text
56
- fg={BRAND.grey}
57
- attributes={TextAttributes.BOLD}
58
- content="── Tool icons ──"
59
- />
60
- {TOOL_LEGEND.map((entry) => {
61
- const key = toolProviderKey(entry.tool);
62
- const labelWidth = Math.min(14, Math.max(10, Math.floor(width * 0.2)));
63
- return (
64
- <box key={entry.tool} flexDirection="row" height={1}>
65
- {key ? (
66
- <ProviderIcon provider={key} mode={iconMode} size={MINI_ICON_SIZE} />
67
- ) : (
68
- <box width={MINI_ICON_SIZE.width} />
69
- )}
70
- <text fg={BRAND.grey} content={` ${entry.label}`} />
71
- </box>
72
- );
73
- })}
74
- <text height={1} content="" />
75
- </box>
76
- )}
77
- </scrollbox>
78
- </box>
79
- <text height={1} flexShrink={0} fg={BRAND.grey} content="Press q or Esc to close." />
80
- </box>
81
- );
82
- }