@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
@@ -1,302 +0,0 @@
1
- import type { ScrylogConfig } from "./config";
2
- import type { ProjectInfo, SessionModel, SessionState, SessionTool } from "./types";
3
-
4
- export interface SessionListProjectInfo {
5
- displayName: string;
6
- compactLabel: string;
7
- branch?: string;
8
- isGitRepository?: boolean;
9
- sourcePath?: string;
10
- gitRootPath?: string;
11
- repositoryIdentityPath?: string;
12
- }
13
-
14
- export interface SessionListTmuxInfo {
15
- session: string;
16
- window: string;
17
- pane: string;
18
- windowName?: string;
19
- }
20
-
21
- export interface SessionListHerdrInfo {
22
- paneId: string;
23
- terminalId?: string;
24
- workspaceId?: string;
25
- workspace?: string;
26
- tabId?: string;
27
- tab?: string;
28
- name?: string;
29
- agent?: string;
30
- }
31
-
32
- export interface SessionListItem {
33
- id: string;
34
- source: SessionModel["source"];
35
- tool: SessionTool;
36
- state: SessionState;
37
- summary: string;
38
- lastActivity: number;
39
- stableForMs?: number;
40
- hasAttention?: boolean;
41
- detectionSource?: SessionModel["detectionSource"];
42
- parentSessionId?: string;
43
- /** Human label for the session row: pane title > window/tab name > tmux session name */
44
- displayName?: string;
45
- project?: SessionListProjectInfo;
46
- tmux?: SessionListTmuxInfo;
47
- herdr?: SessionListHerdrInfo;
48
- currentPath?: string;
49
- activeTool?: string;
50
- childCount?: number;
51
- activeChildCount?: number;
52
- }
53
-
54
- export interface SessionTreeItem extends SessionListItem {
55
- children: SessionListItem[];
56
- }
57
-
58
- export interface SessionViewCounts {
59
- total: number;
60
- visible: number;
61
- rootsVisible: number;
62
- childrenVisible: number;
63
- byState: Record<SessionState, number>;
64
- byTool: Record<SessionTool, number>;
65
- }
66
-
67
- export interface SessionView {
68
- counts: SessionViewCounts;
69
- sessions: SessionTreeItem[];
70
- }
71
-
72
- export interface SessionViewOptions {
73
- includeUnknown?: boolean;
74
- }
75
-
76
- function makeStateCounts(): Record<SessionState, number> {
77
- return {
78
- working: 0,
79
- idle: 0,
80
- "waiting-input": 0,
81
- error: 0,
82
- shell: 0,
83
- unknown: 0,
84
- };
85
- }
86
-
87
- function makeToolCounts(): Record<SessionTool, number> {
88
- return {
89
- opencode: 0,
90
- claude: 0,
91
- codex: 0,
92
- copilotcli: 0,
93
- shell: 0,
94
- unknown: 0,
95
- };
96
- }
97
-
98
- const CODEX_ROOT_EPHEMERAL_GRACE_MS = 30_000;
99
-
100
- function isEphemeralCodexRootSession(session: SessionModel, now: number): boolean {
101
- return session.tool === "codex"
102
- && session.source === "standalone"
103
- && session.detectionSource === "codex-hook"
104
- && session.state === "idle"
105
- && session.currentPath === "/"
106
- && !session.codex?.transcriptPath
107
- && !session.parentSessionId
108
- && (session.childSessionIds?.length ?? 0) === 0
109
- && now - session.lastActivity > CODEX_ROOT_EPHEMERAL_GRACE_MS;
110
- }
111
-
112
- function isCanonicallyVisible(
113
- session: SessionModel,
114
- config: ScrylogConfig,
115
- now: number,
116
- options: SessionViewOptions,
117
- ): boolean {
118
- if (session.tool === "unknown" && !options.includeUnknown) {
119
- return false;
120
- }
121
-
122
- if (session.parentSessionId) {
123
- return session.state === "working" || session.state === "waiting-input";
124
- }
125
-
126
- if (isEphemeralCodexRootSession(session, now)) {
127
- return false;
128
- }
129
-
130
- const cutoff = config.tui.standaloneMaxAgeHours > 0
131
- ? now - config.tui.standaloneMaxAgeHours * 3_600_000
132
- : 0;
133
-
134
- if (cutoff > 0 && session.source === "standalone" && session.lastActivity < cutoff) {
135
- return false;
136
- }
137
-
138
- return true;
139
- }
140
-
141
- function toProject(project?: ProjectInfo): SessionListProjectInfo | undefined {
142
- if (!project) return undefined;
143
- return {
144
- displayName: project.displayName,
145
- compactLabel: project.compactLabel,
146
- branch: project.branch,
147
- isGitRepository: project.isGitRepository,
148
- sourcePath: project.sourcePath,
149
- gitRootPath: project.gitRootPath,
150
- repositoryIdentityPath: project.repositoryIdentityPath,
151
- };
152
- }
153
-
154
- const GRACE_PERIOD_MS = 1500;
155
-
156
- function resolveDisplayName(session: SessionModel): string | undefined {
157
- if (session.tmux) {
158
- return session.paneTitle?.trim()
159
- || session.tmux.windowName?.trim()
160
- || session.tmux.session?.trim()
161
- || undefined;
162
- }
163
- if (session.herdr) {
164
- return session.herdr.name?.trim()
165
- || session.herdr.tab?.trim()
166
- || session.herdr.agent?.trim()
167
- || session.herdr.paneId
168
- || undefined;
169
- }
170
- return undefined;
171
- }
172
-
173
- function effectiveState(session: SessionModel, activeChildCount: number, now: number): SessionState {
174
- if (session.state === "idle" && !session.parentSessionId) {
175
- if (activeChildCount > 0) {
176
- return "working";
177
- }
178
- if (session.lastChildActiveAt && (now - session.lastChildActiveAt) < GRACE_PERIOD_MS) {
179
- return "working";
180
- }
181
- }
182
- return session.state;
183
- }
184
-
185
- function stableDuration(session: SessionModel, effective: SessionState, now: number): number {
186
- if (effective === "working" && session.lastWorkingChangedAt) {
187
- return Math.max(0, now - session.lastWorkingChangedAt);
188
- }
189
- if (session.stateChangedAt) {
190
- return Math.max(0, now - session.stateChangedAt);
191
- }
192
- return session.stableForMs ?? Math.max(0, now - session.lastActivity);
193
- }
194
-
195
- function toListItem(session: SessionModel, childCount: number, activeChildCount: number, now: number): SessionListItem {
196
- const effective = effectiveState(session, activeChildCount, now);
197
- return {
198
- id: session.id,
199
- source: session.source,
200
- tool: session.tool,
201
- state: effective,
202
- summary: session.summary,
203
- lastActivity: session.lastActivity,
204
- stableForMs: stableDuration(session, effective, now),
205
- hasAttention: session.hasAttention,
206
- detectionSource: session.detectionSource,
207
- parentSessionId: session.parentSessionId,
208
- displayName: resolveDisplayName(session),
209
- project: toProject(session.project),
210
- tmux: session.tmux ? { ...session.tmux } : undefined,
211
- herdr: session.herdr ? {
212
- paneId: session.herdr.paneId,
213
- terminalId: session.herdr.terminalId,
214
- workspaceId: session.herdr.workspaceId,
215
- workspace: session.herdr.workspace,
216
- tabId: session.herdr.tabId,
217
- tab: session.herdr.tab,
218
- name: session.herdr.name,
219
- agent: session.herdr.agent,
220
- } : undefined,
221
- currentPath: session.currentPath,
222
- activeTool: session.activeTool,
223
- childCount,
224
- activeChildCount,
225
- };
226
- }
227
-
228
- export function flattenSessionTree(sessions: SessionTreeItem[]): SessionListItem[] {
229
- const flat: SessionListItem[] = [];
230
- for (const session of sessions) {
231
- const { children: _children, ...root } = session;
232
- flat.push(root);
233
- flat.push(...session.children);
234
- }
235
- return flat;
236
- }
237
-
238
- export function buildSessionView(
239
- sessions: SessionModel[],
240
- config: ScrylogConfig,
241
- now = Date.now(),
242
- options: SessionViewOptions = {},
243
- ): SessionView {
244
- const byId = new Map(sessions.map((session) => [session.id, session]));
245
- const allChildren = new Map<string, SessionModel[]>();
246
- const visibleChildren = new Map<string, SessionModel[]>();
247
- const visibleRoots = new Map<string, SessionModel>();
248
-
249
- for (const session of sessions) {
250
- if (session.parentSessionId) {
251
- const arr = allChildren.get(session.parentSessionId) ?? [];
252
- arr.push(session);
253
- allChildren.set(session.parentSessionId, arr);
254
-
255
- if (isCanonicallyVisible(session, config, now, options)) {
256
- const visibleArr = visibleChildren.get(session.parentSessionId) ?? [];
257
- visibleArr.push(session);
258
- visibleChildren.set(session.parentSessionId, visibleArr);
259
-
260
- const parent = byId.get(session.parentSessionId);
261
- if (parent && isCanonicallyVisible(parent, config, now, options)) {
262
- visibleRoots.set(parent.id, parent);
263
- }
264
- }
265
- continue;
266
- }
267
-
268
- if (isCanonicallyVisible(session, config, now, options)) {
269
- visibleRoots.set(session.id, session);
270
- }
271
- }
272
-
273
- const sortedRoots = [...visibleRoots.values()].sort((a, b) => b.lastActivity - a.lastActivity);
274
- const tree: SessionTreeItem[] = sortedRoots.map((root) => {
275
- const activeChildren = (visibleChildren.get(root.id) ?? []).sort((a, b) => b.lastActivity - a.lastActivity);
276
- const allDirectChildren = allChildren.get(root.id) ?? [];
277
- return {
278
- ...toListItem(root, allDirectChildren.length, activeChildren.length, now),
279
- children: activeChildren.map((child) => toListItem(child, 0, 0, now)),
280
- };
281
- });
282
-
283
- const flatVisible = flattenSessionTree(tree);
284
- const byState = makeStateCounts();
285
- const byTool = makeToolCounts();
286
- for (const session of flatVisible) {
287
- byState[session.state] += 1;
288
- byTool[session.tool] += 1;
289
- }
290
-
291
- return {
292
- counts: {
293
- total: sessions.length,
294
- visible: flatVisible.length,
295
- rootsVisible: tree.length,
296
- childrenVisible: flatVisible.filter((session) => Boolean(session.parentSessionId)).length,
297
- byState,
298
- byTool,
299
- },
300
- sessions: tree,
301
- };
302
- }
@@ -1,130 +0,0 @@
1
- import { stdin as input, stdout as output } from "node:process";
2
-
3
- /**
4
- * Non-invasive terminal capability probes used by `scrylog doctor` to explain
5
- * how provider icons negotiate graphics. Mirrors the response formats OpenTUI's
6
- * detector expects, but never enters the alternate screen and always restores
7
- * the terminal to its original state (`finally`).
8
- */
9
-
10
- export interface TerminalGraphicsCapabilities {
11
- /** Terminal identification from XTVERSION (e.g. "ghostty(1.2.3)"), if answered. */
12
- terminalName: string | null;
13
- /** Outer multiplexer detected from the environment ("tmux" | "zellij" | "none"). */
14
- multiplexer: "tmux" | "zellij" | "none";
15
- /** Terminal answered a kitty graphics protocol query. */
16
- kittyGraphics: boolean;
17
- /** DA1 primary response advertises sixel (attribute 4). */
18
- sixel: boolean;
19
- /** Window pixel resolution from `CSI 14t`. */
20
- pixelResolution: { width: number; height: number } | null;
21
- /** stdin was not a TTY (SSH without tty, pipes) or raw mode was unavailable. */
22
- probeFailed: boolean;
23
- /** Why the probe did not run or answer. */
24
- probeFailedReason: "not-a-tty" | "raw-unavailable" | "no-answer" | null;
25
- /** TERM value, useful for manual triage. */
26
- term: string | null;
27
- }
28
-
29
- const PROBE_TIMEOUT_MS = 300;
30
-
31
- const XTVERSION_PROBE = "\x1bP>0q\x1b\\";
32
- const DA1_PROBE = "\x1b[c";
33
- const KITTY_PROBE = "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\";
34
- const PIXEL_RES_PROBE = "\x1b[14t";
35
-
36
- /** Visible attributes in a DA1 primary response (e.g. `ESC[?6;22;4c`). Sixel is attribute 4. */
37
- const DA1_RESPONSE = /\x1b\[\?(\d+(?:;\d+)*)c/g;
38
- const XTVERSION_RESPONSE = /\x1bP>[|]([^\x1b]+)\x1b\\/;
39
- const KITTY_RESPONSE = /\x1b_Gi=31;([^\x1b]*)\x1b\\/;
40
- const PIXEL_RES_RESPONSE = /\x1b\[4;(\d+);(\d+)t/;
41
-
42
- export function detectMultiplexer(): "tmux" | "zellij" | "none" {
43
- if (process.env["TMUX"]) return "tmux";
44
- if (process.env["ZELLIJ"]) return "zellij";
45
- return "none";
46
- }
47
-
48
- export function parseResponses(sequence: string): Partial<TerminalGraphicsCapabilities> {
49
- const result: Partial<TerminalGraphicsCapabilities> = {};
50
- const version = sequence.match(XTVERSION_RESPONSE)?.[1];
51
- if (version) result.terminalName = version.trim();
52
- for (const da1 of sequence.matchAll(DA1_RESPONSE)) {
53
- if (da1[1]!.split(";").includes("4")) result.sixel = true;
54
- }
55
- const kitty = sequence.match(KITTY_RESPONSE);
56
- if (kitty) result.kittyGraphics = !(kitty[1] ?? "").startsWith("E:");
57
- const res = sequence.match(PIXEL_RES_RESPONSE);
58
- if (res) result.pixelResolution = { width: Number(res[2]), height: Number(res[1]) };
59
- return result;
60
- }
61
-
62
- export function probeTerminalGraphics(): Promise<TerminalGraphicsCapabilities> {
63
- const probe: TerminalGraphicsCapabilities = {
64
- terminalName: null,
65
- multiplexer: detectMultiplexer(),
66
- kittyGraphics: false,
67
- sixel: false,
68
- pixelResolution: null,
69
- probeFailed: true,
70
- probeFailedReason: null,
71
- term: process.env["TERM"] ?? null,
72
- };
73
-
74
- if (!input.isTTY || typeof input.setRawMode !== "function") {
75
- return Promise.resolve({ ...probe, probeFailedReason: "not-a-tty" });
76
- }
77
-
78
- return new Promise((resolve) => {
79
- const rawActive = input.isRaw === true;
80
- const restore = () => {
81
- try { input.setRawMode(rawActive); } catch { /* restore best effort */ }
82
- };
83
- let buffer = "";
84
- let settleTimeout: ReturnType<typeof setTimeout> | null = null;
85
- let done = false;
86
- const stop = () => {
87
- if (done) return;
88
- done = true;
89
- clearTimeout(settleTimeout!);
90
- input.removeListener("data", onData);
91
- restore();
92
- };
93
- const settle = () => {
94
- stop();
95
- const parsed = parseResponses(buffer);
96
- const failed = Object.keys(parsed).length === 0;
97
- resolve({
98
- ...probe,
99
- ...parsed,
100
- probeFailed: failed,
101
- probeFailedReason: failed ? "no-answer" : null,
102
- });
103
- };
104
- const onData = (chunk: Buffer | string) => {
105
- buffer += typeof chunk === "string" ? chunk : chunk.toString("latin1");
106
- clearTimeout(settleTimeout!);
107
- settleTimeout = setTimeout(settle, 80);
108
- };
109
- try {
110
- input.setRawMode(true);
111
- } catch {
112
- resolve({ ...probe, probeFailedReason: "raw-unavailable" });
113
- return;
114
- }
115
- input.on("data", onData);
116
- settleTimeout = setTimeout(settle, PROBE_TIMEOUT_MS);
117
- output.write(XTVERSION_PROBE + DA1_PROBE + KITTY_PROBE + PIXEL_RES_PROBE);
118
- });
119
- }
120
-
121
- export type ProviderIconAdvice = "graphics" | "kitty" | "sixel" | "blocks";
122
-
123
- /** Maps probed capabilities to the recommended `tui.providerIcons` value. */
124
- export function adviseProviderIcons(capabilities: TerminalGraphicsCapabilities): ProviderIconAdvice {
125
- if (capabilities.multiplexer === "tmux" || capabilities.multiplexer === "zellij") return "blocks";
126
- if (capabilities.kittyGraphics) return "graphics";
127
- if (capabilities.sixel && capabilities.pixelResolution) return "graphics";
128
- if (capabilities.sixel) return "sixel";
129
- return "blocks";
130
- }