@scrylog/cli 0.3.0 → 0.3.2

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,49 +0,0 @@
1
- import type { DashboardView } from "./model";
2
-
3
- export type BindingScope = "global" | DashboardView;
4
-
5
- export interface KeyBinding {
6
- key: string;
7
- description: string;
8
- scope: BindingScope;
9
- essential?: boolean;
10
- footer?: boolean;
11
- }
12
-
13
- export const KEYBINDINGS: KeyBinding[] = [
14
- { key: "Tab", description: "Switch tab", scope: "global", essential: true },
15
- { key: "q", description: "Quit", scope: "global", essential: true },
16
- { key: "r", description: "Refresh", scope: "global" },
17
- { key: "s", description: "Open the sessions tab", scope: "global", essential: true, footer: false },
18
- { key: "u", description: "Open the usage tab", scope: "global", essential: true, footer: false },
19
- { key: "?", description: "Keybindings", scope: "global", essential: true },
20
-
21
- { key: "j / Down", description: "Next session", scope: "sessions" },
22
- { key: "k / Up", description: "Prev session", scope: "sessions" },
23
- { key: "Enter", description: "Detail", scope: "sessions" },
24
- { key: "t", description: "Attach", scope: "sessions" },
25
- { key: "/", description: "Search", scope: "sessions" },
26
- { key: "a", description: "Actionable filter", scope: "sessions" },
27
- { key: "f", description: "Cycle filters", scope: "sessions" },
28
- { key: "Esc", description: "Clear selection or close overlay", scope: "sessions" },
29
-
30
- { key: "Click", description: "Select a session", scope: "sessions", footer: false },
31
- { key: "2x Click", description: "Open session detail", scope: "sessions", footer: false },
32
- { key: "Scroll", description: "Select next or previous session", scope: "sessions", footer: false },
33
- { key: "Click tab", description: "Switch tabs", scope: "global", footer: false },
34
- ];
35
-
36
- export function contextBindings(view: DashboardView): KeyBinding[] {
37
- return KEYBINDINGS.filter((binding) => binding.scope === "global" || binding.scope === view);
38
- }
39
-
40
- export function essentialBindings(view: DashboardView): KeyBinding[] {
41
- return contextBindings(view).filter((binding) => binding.essential);
42
- }
43
-
44
- export function bindingsForModal(): { scope: BindingScope; bindings: KeyBinding[] }[] {
45
- const scopes: BindingScope[] = ["global", "sessions", "usage"];
46
- return scopes
47
- .map((scope) => ({ scope, bindings: KEYBINDINGS.filter((binding) => binding.scope === scope) }))
48
- .filter((group) => group.bindings.length > 0);
49
- }
package/src/tui/model.ts DELETED
@@ -1,295 +0,0 @@
1
- import type { DetectionReason, ProcessInfo, SessionModel, SessionState, SessionTool } from "../shared/types";
2
- import type { SessionListItem } from "../shared/session-view";
3
- import { formatDuration, truncate } from "../shared/utils";
4
- import { BRAND } from "./theme";
5
-
6
- export type DashboardView = "sessions" | "usage";
7
- export type DashboardMode = "list" | "search" | "detail" | "message" | "bindings";
8
- export type SessionToolFilter = SessionTool | "all";
9
- export type SessionStateFilter = SessionState | "all";
10
-
11
- export interface DashboardFilters {
12
- query: string;
13
- actionableOnly: boolean;
14
- tool: SessionToolFilter;
15
- state: SessionStateFilter;
16
- }
17
-
18
- export type DashboardRow = ProjectHeaderRow | SessionRow | GapRow;
19
-
20
- export interface ProjectHeaderRow {
21
- kind: "project-header";
22
- projectKey: string;
23
- projectName: string;
24
- }
25
-
26
- export interface SessionRow {
27
- kind: "session";
28
- projectKey: string;
29
- session: SessionListItem;
30
- }
31
-
32
- export interface GapRow {
33
- kind: "gap";
34
- projectKey: string;
35
- }
36
-
37
- export interface SessionColumnWidths {
38
- session: number;
39
- state: number;
40
- tool: number;
41
- summary: number;
42
- }
43
-
44
- export const TOOL_FILTERS: SessionToolFilter[] = ["all", "opencode", "claude", "codex", "copilotcli", "shell", "unknown"];
45
- export const STATE_FILTERS: SessionStateFilter[] = ["all", "working", "waiting-input", "idle", "shell", "unknown"];
46
- export const WORKING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
47
-
48
- export const STATE_COLORS: Record<string, string> = {
49
- working: BRAND.stateWorking,
50
- idle: BRAND.stateIdle,
51
- "waiting-input": BRAND.violet,
52
- shell: BRAND.accentBlue,
53
- error: BRAND.danger,
54
- unknown: BRAND.grey,
55
- };
56
-
57
- export function applySessionFilters(
58
- sessions: SessionListItem[],
59
- filters: DashboardFilters,
60
- standaloneMaxAgeHours: number,
61
- ): SessionListItem[] {
62
- const query = filters.query.toLowerCase();
63
- const cutoff = standaloneMaxAgeHours > 0 ? Date.now() - standaloneMaxAgeHours * 3_600_000 : 0;
64
- return sessions.filter((session) => {
65
- if (session.parentSessionId) {
66
- if (session.state !== "working" && session.state !== "waiting-input") return false;
67
- } else if (cutoff > 0 && session.source === "standalone" && session.lastActivity < cutoff) {
68
- return false;
69
- }
70
- if (filters.actionableOnly && !(session.hasAttention ?? session.state === "waiting-input")) return false;
71
- if (filters.tool !== "all" && session.tool !== filters.tool) return false;
72
- if (filters.state !== "all" && session.state !== filters.state) return false;
73
- if (!query) return true;
74
- const searchable = [
75
- session.id,
76
- session.tool,
77
- session.state,
78
- session.summary,
79
- session.project?.displayName,
80
- session.project?.compactLabel,
81
- session.project?.branch,
82
- session.displayName,
83
- session.tmux?.session,
84
- session.herdr?.workspace,
85
- session.herdr?.name,
86
- session.herdr?.tab,
87
- session.herdr?.agent,
88
- session.currentPath,
89
- ].filter(Boolean).join(" ").toLowerCase();
90
- return searchable.includes(query);
91
- });
92
- }
93
-
94
- export function cycleFilters(filters: DashboardFilters): DashboardFilters {
95
- const nextTool = TOOL_FILTERS[(TOOL_FILTERS.indexOf(filters.tool) + 1) % TOOL_FILTERS.length] ?? "all";
96
- if (nextTool !== "all") return { ...filters, tool: nextTool, state: "all" };
97
- const nextState = STATE_FILTERS[(STATE_FILTERS.indexOf(filters.state) + 1) % STATE_FILTERS.length] ?? "all";
98
- return { ...filters, tool: "all", state: nextState };
99
- }
100
-
101
- function sessionSortWeight(session: SessionListItem): number {
102
- // Active work first (working/waiting), then idle workloads.
103
- const statePenalty = session.state === "working" ? 0 : session.state === "waiting-input" ? 20 : 60;
104
- const toolPenalty = session.tool === "unknown" ? 30 : session.tool === "shell" ? 20 : 0;
105
- return statePenalty + toolPenalty;
106
- }
107
-
108
- function groupSortWeight(group: { sessions: SessionListItem[] }): number {
109
- const minWeight = Math.min(...group.sessions.map(sessionSortWeight));
110
- return group.sessions.every((session) => !session.project) ? 1000 + minWeight : minWeight;
111
- }
112
-
113
- export function buildRows(sessions: SessionListItem[], narrow = false): DashboardRow[] {
114
- const roots = sessions.filter((session) => !session.parentSessionId);
115
- const childrenByParent = new Map<string, SessionListItem[]>();
116
- for (const session of sessions) {
117
- if (!session.parentSessionId) continue;
118
- const children = childrenByParent.get(session.parentSessionId) ?? [];
119
- children.push(session);
120
- childrenByParent.set(session.parentSessionId, children);
121
- }
122
-
123
- const groups = new Map<string, { projectName: string; sessions: SessionListItem[] }>();
124
- for (const session of roots) {
125
- const key = getProjectGroupKey(session);
126
- const group = groups.get(key);
127
- if (group) {
128
- group.sessions.push(session);
129
- } else {
130
- groups.set(key, {
131
- projectName: session.project?.displayName ?? (session.source === "standalone"
132
- ? (session.tool !== "unknown" ? session.tool : "standalone")
133
- : session.source === "herdr"
134
- ? session.herdr?.workspace ?? "herdr"
135
- : session.tmux?.session ?? key),
136
- sessions: [session],
137
- });
138
- }
139
- }
140
-
141
- const recency = (group: { sessions: SessionListItem[] }) => Math.max(...group.sessions.map((s) => s.lastActivity));
142
- const sortedGroups = [...groups.entries()].sort(
143
- ([, left], [, right]) => groupSortWeight(left) - groupSortWeight(right) || recency(right) - recency(left),
144
- );
145
- const rows: DashboardRow[] = sortedGroups.length > 0 ? [{ kind: "gap", projectKey: "__initial-gap__" }] : [];
146
- sortedGroups.forEach(([projectKey, group], groupIndex) => {
147
- if (groupIndex > 0) rows.push({ kind: "gap", projectKey });
148
- group.sessions.sort((left, right) => sessionSortWeight(left) - sessionSortWeight(right) || right.lastActivity - left.lastActivity);
149
- rows.push({ kind: "project-header", projectKey, projectName: group.projectName });
150
- for (const session of group.sessions) {
151
- rows.push({ kind: "session", projectKey, session });
152
- if (!narrow) {
153
- const children = (childrenByParent.get(session.id) ?? []).sort(
154
- (left, right) => sessionSortWeight(left) - sessionSortWeight(right) || right.lastActivity - left.lastActivity,
155
- );
156
- for (const child of children) rows.push({ kind: "session", projectKey, session: child });
157
- }
158
- }
159
- });
160
- return rows;
161
- }
162
-
163
- function getProjectGroupKey(session: SessionListItem): string {
164
- return session.project?.repositoryIdentityPath
165
- ?? session.project?.gitRootPath
166
- ?? session.project?.sourcePath
167
- ?? session.project?.displayName
168
- ?? (session.source === "standalone"
169
- ? `standalone:${session.tool}`
170
- : session.source === "herdr"
171
- ? `herdr:${session.herdr?.workspaceId ?? session.herdr?.workspace ?? session.herdr?.paneId ?? session.id}`
172
- : session.tmux?.session ?? session.id);
173
- }
174
-
175
- export function moveSelection(rows: DashboardRow[], selectedId: string | undefined, direction: 1 | -1): string | undefined {
176
- if (rows.length === 0) return selectedId;
177
- if (!selectedId) {
178
- const candidates = direction > 0 ? rows : [...rows].reverse();
179
- return candidates.find((row): row is SessionRow => row.kind === "session")?.session.id;
180
- }
181
- const current = rows.findIndex((row) => row.kind === "session" && row.session.id === selectedId);
182
- if (current < 0) return undefined;
183
- for (let index = current + direction; index >= 0 && index < rows.length; index += direction) {
184
- const row = rows[index];
185
- if (row?.kind === "session") return row.session.id;
186
- }
187
- return selectedId;
188
- }
189
-
190
- export function visibleRowWindow(rows: DashboardRow[], selectedId: string | undefined, limit: number): DashboardRow[] {
191
- if (rows.length <= limit) return rows;
192
- const selected = rows.findIndex((row) => row.kind === "session" && row.session.id === selectedId);
193
- const start = Math.max(0, Math.min(selected - Math.floor(limit / 2), rows.length - limit));
194
- return rows.slice(start, start + limit);
195
- }
196
-
197
- export function stateGlyph(state: string, spinnerFrame = 0): string {
198
- if (state === "working") return WORKING_FRAMES[spinnerFrame % WORKING_FRAMES.length] ?? WORKING_FRAMES[0]!;
199
- if (state === "idle") return "○";
200
- if (state === "waiting-input") return "?";
201
- if (state === "shell") return ">";
202
- if (state === "error") return "✗";
203
- return "?";
204
- }
205
-
206
- function formatBranchLabel(session: SessionListItem): string {
207
- if (!session.project?.isGitRepository) return "";
208
- return session.project.branch || "detached";
209
- }
210
-
211
- export function formatTargetLabel(session: SessionListItem, narrow = false): string {
212
- let base: string;
213
- if (session.parentSessionId) {
214
- base = ` ↳ ${truncate(session.summary || session.id, narrow ? 22 : 26)}`;
215
- } else if (session.tmux) {
216
- const label = session.displayName?.trim()
217
- || session.tmux.session?.trim()
218
- || session.id;
219
- base = ` ▸ ${label}`;
220
- } else if (session.herdr) {
221
- const label = session.displayName?.trim()
222
- || session.herdr.tab?.trim()
223
- || session.herdr.agent?.trim()
224
- || session.herdr.paneId;
225
- base = ` ▹ ${label}`;
226
- } else {
227
- const placeholder = session.activeTool
228
- ? `[${session.activeTool}]`
229
- : (session.id.includes(":") ? session.id.split(":")[1]!.slice(0, 8) : session.id.slice(0, 8));
230
- base = ` ◇ ${placeholder}`;
231
- }
232
- const branch = formatBranchLabel(session);
233
- return branch ? `${base}@${branch}` : base;
234
- }
235
-
236
- export type SessionToolMode = "text" | "monogram" | "icons";
237
-
238
- export function sessionColumnWidths(width: number, toolMode: SessionToolMode = "text", toolContentWidth = 0): SessionColumnWidths {
239
- const narrow = width < 110;
240
- const available = Math.max(36, width - 9);
241
- if (narrow) {
242
- const enhanced = toolMode !== "text";
243
- const state = 11;
244
- // On very narrow widths the summary column is a few truncated characters;
245
- // drop it entirely and hand its space to the session column. The budget
246
- // matches the pane's real content width (borders + padding + gutter) so
247
- // rows fill from border to border.
248
- if (width < 100) {
249
- const filledAvailable = Math.max(36, width - 6);
250
- const tool = Math.max(5, (toolContentWidth || (toolMode === "text" ? 10 : 7)) + 1);
251
- return { session: Math.max(14, filledAvailable - state - tool), state, tool, summary: 0 };
252
- }
253
- const tool = toolMode === "text"
254
- ? Math.min(12, Math.max(9, Math.floor(available * 0.18)))
255
- : toolMode === "icons" ? 7 : 8;
256
- const session = Math.min(enhanced ? 32 : 28, Math.max(14, Math.floor(available * (enhanced ? 0.4 : 0.34))));
257
- return { session, state, tool, summary: Math.max(8, available - session - state - tool) };
258
- }
259
- const state = 11;
260
- const tool = toolMode === "text" ? 13 : toolMode === "icons" ? 3 : 5;
261
- if (toolMode !== "text") {
262
- const remaining = available - state - tool;
263
- const session = Math.min(72, Math.max(30, Math.floor(remaining * 0.55)));
264
- return { session, state, tool, summary: Math.max(8, available - session - state - tool) };
265
- }
266
- const session = Math.min(40, Math.max(30, available - state - tool - 39));
267
- return { session, state, tool, summary: Math.max(8, available - session - state - tool) };
268
- }
269
-
270
- export function formatStatusCell(session: SessionListItem, spinnerFrame: number): string {
271
- const glyph = stateGlyph(session.state, spinnerFrame);
272
- return `${glyph} (${formatDuration(session.stableForMs ?? 0)})`;
273
- }
274
-
275
- export function activeChildCounts(sessions: SessionListItem[]): Map<string, number> {
276
- const counts = new Map<string, number>();
277
- for (const session of sessions) {
278
- if (!session.parentSessionId || (session.state !== "working" && session.state !== "waiting-input")) continue;
279
- counts.set(session.parentSessionId, (counts.get(session.parentSessionId) ?? 0) + 1);
280
- }
281
- return counts;
282
- }
283
-
284
- export function sessionAttachCommand(session: SessionListItem, insideTmux: boolean): string[] | null {
285
- if (session.source === "tmux" && session.tmux) {
286
- return insideTmux
287
- ? ["tmux", "switch-client", "-t", session.tmux.session]
288
- : ["tmux", "attach-session", "-t", session.tmux.session];
289
- }
290
- if (session.source === "herdr" && session.herdr?.terminalId) {
291
- return ["herdr", "terminal", "attach", session.herdr.terminalId];
292
- }
293
- return null;
294
- }
295
-
package/src/tui/state.ts DELETED
@@ -1,77 +0,0 @@
1
- import type { SessionModel } from "../shared/types";
2
- import type { SessionListItem, SessionViewCounts } from "../shared/session-view";
3
- import type { UsageState } from "../usage/types";
4
- import { cycleFilters, type DashboardFilters, type DashboardMode, type DashboardView } from "./model";
5
-
6
- export interface DashboardState {
7
- view: DashboardView;
8
- mode: DashboardMode;
9
- sessions: SessionListItem[];
10
- counts: SessionViewCounts | null;
11
- usage: UsageState;
12
- selectedSessionId?: string;
13
- detail?: SessionModel;
14
- message?: string;
15
- daemonConnected: boolean;
16
- filters: DashboardFilters;
17
- }
18
-
19
- export const INITIAL_DASHBOARD_STATE: DashboardState = {
20
- view: "sessions",
21
- mode: "list",
22
- sessions: [],
23
- counts: null,
24
- usage: { providers: [], lastRefreshedAt: 0 },
25
- daemonConnected: true,
26
- filters: { query: "", actionableOnly: false, tool: "all", state: "all" },
27
- };
28
-
29
- export type DashboardAction =
30
- | { type: "sessions-loaded"; sessions: SessionListItem[]; counts: SessionViewCounts }
31
- | { type: "usage-loaded"; usage: UsageState }
32
- | { type: "connection"; connected: boolean }
33
- | { type: "select"; id?: string }
34
- | { type: "view"; view: DashboardView }
35
- | { type: "search" }
36
- | { type: "query"; query: string }
37
- | { type: "toggle-actionable" }
38
- | { type: "cycle-filters" }
39
- | { type: "detail"; session: SessionModel }
40
- | { type: "message"; message: string }
41
- | { type: "bindings" }
42
- | { type: "close-overlay" };
43
-
44
- export function dashboardReducer(state: DashboardState, action: DashboardAction): DashboardState {
45
- switch (action.type) {
46
- case "sessions-loaded": {
47
- const selectedSessionId = action.sessions.some((session) => session.id === state.selectedSessionId)
48
- ? state.selectedSessionId
49
- : undefined;
50
- return { ...state, sessions: action.sessions, counts: action.counts, selectedSessionId, daemonConnected: true };
51
- }
52
- case "usage-loaded":
53
- return { ...state, usage: action.usage };
54
- case "connection":
55
- return { ...state, daemonConnected: action.connected };
56
- case "select":
57
- return { ...state, selectedSessionId: action.id };
58
- case "view":
59
- return { ...state, view: action.view, mode: "list", detail: undefined, message: undefined };
60
- case "search":
61
- return { ...state, mode: "search" };
62
- case "query":
63
- return { ...state, filters: { ...state.filters, query: action.query } };
64
- case "toggle-actionable":
65
- return { ...state, filters: { ...state.filters, actionableOnly: !state.filters.actionableOnly } };
66
- case "cycle-filters":
67
- return { ...state, filters: cycleFilters(state.filters) };
68
- case "detail":
69
- return { ...state, mode: "detail", detail: action.session, message: undefined };
70
- case "message":
71
- return { ...state, mode: "message", message: action.message, detail: undefined };
72
- case "bindings":
73
- return { ...state, mode: "bindings", detail: undefined, message: undefined };
74
- case "close-overlay":
75
- return { ...state, mode: "list", detail: undefined, message: undefined };
76
- }
77
- }
package/src/tui/theme.ts DELETED
@@ -1,110 +0,0 @@
1
- /**
2
- * Scrylog brand palette (source: brand guide, OpenTUI-compatible ANSI sheet).
3
- *
4
- * Single source of truth for every color used by the TUI: change a value here
5
- * (or swap the whole object for an alternate theme) and the whole UI follows.
6
- */
7
- export const BRAND = {
8
- /** Main Teal/Cyan — primary brand color (xterm 80). */
9
- primary: "#5CCCCC",
10
- /** Accent Light Blue (xterm 159). */
11
- accentBlue: "#A1E6FF",
12
- /** Highlight Violet (xterm 177). */
13
- violet: "#CFA6FF",
14
- /** Detail Grey — secondary text (xterm 246). */
15
- grey: "#A0A0A0",
16
- /** Background Dark (xterm 234). */
17
- bgDark: "#1A1A1A",
18
- /** Modal / floating panel surface (near-black, kept from legacy scheme). */
19
- surface: "#11151c",
20
- /** Functional colors kept unchanged (no palette equivalent). */
21
- danger: "#f05252",
22
- warning: "#f5a524",
23
-
24
- /** Legacy chrome visuals preserved by design: pane borders and separators. */
25
- border: "#495365",
26
- /** Selected session row background (legacy slate). */
27
- selection: "#263244",
28
- /** Fallback foreground for rows whose state has no mapping. */
29
- fallback: "#d8dee9",
30
-
31
- /** Session state colors (palette-family tints). */
32
- stateWorking: "#E8BC5D",
33
- stateIdle: "#6BD9A4",
34
- /** Usage semaphore ≥90% (soft coral, danger family). */
35
- usageHigh: "#E28686",
36
- } as const;
37
-
38
- export interface ProviderVisual {
39
- name: string;
40
- short: string;
41
- brandColor: string;
42
- borderColor: string;
43
- /** Alternative brand color readable on dark terminal backgrounds. */
44
- onDarkColor?: string;
45
- tileColor?: string;
46
- terminalAsset?: URL;
47
- graphicsAsset?: URL;
48
- }
49
-
50
- /** Third-party provider brand colors: data, not brand palette. */
51
- export const PROVIDER_BRANDS: Record<string, ProviderVisual> = {
52
- claude: {
53
- name: "Claude",
54
- short: "CL",
55
- brandColor: "#d97757",
56
- borderColor: "#d97757",
57
- terminalAsset: new URL("./assets/providers/claude-terminal.png", import.meta.url),
58
- graphicsAsset: new URL("./assets/providers/claude.png", import.meta.url),
59
- },
60
- codex: {
61
- name: "Codex",
62
- short: "CX",
63
- brandColor: "#539af8",
64
- borderColor: "#539af8",
65
- terminalAsset: new URL("./assets/providers/openai-terminal.png", import.meta.url),
66
- graphicsAsset: new URL("./assets/providers/openai.png", import.meta.url),
67
- },
68
- "opencode-go": {
69
- name: "OpenCode Go",
70
- short: "OC",
71
- brandColor: "#111111",
72
- onDarkColor: "#c8cdd8",
73
- borderColor: BRAND.grey,
74
- tileColor: "#f3f3f3",
75
- terminalAsset: new URL("./assets/providers/opencode-terminal.png", import.meta.url),
76
- graphicsAsset: new URL("./assets/providers/opencode.png", import.meta.url),
77
- },
78
- copilot: {
79
- name: "Copilot",
80
- short: "CP",
81
- brandColor: "#c8cdd8",
82
- borderColor: BRAND.grey,
83
- graphicsAsset: new URL("./assets/providers/copilot.png", import.meta.url),
84
- },
85
- shell: {
86
- name: "Shell",
87
- short: "SH",
88
- brandColor: "#c8cdd8",
89
- borderColor: BRAND.grey,
90
- graphicsAsset: new URL("./assets/providers/shell.png", import.meta.url),
91
- },
92
- unknown: {
93
- name: "Unknown",
94
- short: "?",
95
- brandColor: BRAND.grey,
96
- borderColor: BRAND.grey,
97
- graphicsAsset: new URL("./assets/providers/unknown.png", import.meta.url),
98
- },
99
- };
100
-
101
- export const BRAND_MARK = {
102
- /** Blocks: front-view crystal ball + prompt + circuit pedestal, brand palette. */
103
- art: [
104
- { fg: BRAND.accentBlue, content: " ▗▄▄▄▄▄▄▖" },
105
- { fg: BRAND.primary, content: " ▐▓▒░░░░▒▓▌" },
106
- { fg: BRAND.primary, content: " ░ ◉◈◉ ›_ ░" },
107
- { fg: BRAND.violet, content: " ▜▓▒░░░░▒▓▛" },
108
- { fg: BRAND.violet, content: " ▐█╿██╵█▌" },
109
- ],
110
- } as const;
package/src/types.ts DELETED
@@ -1,113 +0,0 @@
1
- export type ToolKind = "opencode" | "claude" | "codex" | "copilotcli" | "shell" | "unknown";
2
-
3
- export type PaneState = "working" | "idle" | "waiting-input" | "shell" | "unknown";
4
-
5
- export interface SessionInfo {
6
- name: string;
7
- attached: boolean;
8
- windows: number;
9
- }
10
-
11
- export interface TmuxPaneRecord {
12
- sessionName: string;
13
- windowIndex: number;
14
- windowName: string;
15
- paneIndex: number;
16
- paneId: string;
17
- panePid: number;
18
- currentCommand: string;
19
- currentPath: string;
20
- active: boolean;
21
- tty: string;
22
- title: string;
23
- width: number;
24
- height: number;
25
- }
26
-
27
- export interface ProjectInfo {
28
- displayName: string;
29
- compactLabel: string;
30
- sourcePath: string;
31
- isGitRepository: boolean;
32
- gitRootPath?: string;
33
- repositoryIdentityPath?: string;
34
- branch?: string;
35
- isWorktree?: boolean;
36
- remoteName?: string;
37
- remoteUrl?: string;
38
- }
39
-
40
- export interface EnrichedPaneRecord extends TmuxPaneRecord {
41
- project: ProjectInfo;
42
- }
43
-
44
- export interface ProcessInfo {
45
- pid: number;
46
- ppid: number;
47
- state: string;
48
- elapsed: string;
49
- command: string;
50
- comm: string;
51
- }
52
-
53
- export interface DetectionReason {
54
- label: string;
55
- detail: string;
56
- }
57
-
58
- export interface PaneSample {
59
- paneId: string;
60
- lines: string[];
61
- preview: string;
62
- }
63
-
64
- export interface PaneModel extends EnrichedPaneRecord {
65
- processTree: ProcessInfo[];
66
- processChain: string[];
67
- sample: PaneSample | null;
68
- tool: ToolKind;
69
- state: PaneState;
70
- toolReasons: DetectionReason[];
71
- stateReasons: DetectionReason[];
72
- summary: string;
73
- lastChangedAt: number;
74
- stableForMs: number;
75
- hasAttention: boolean;
76
- }
77
-
78
- export interface SnapshotError {
79
- source: string;
80
- message: string;
81
- }
82
-
83
- export interface DashboardSnapshot {
84
- sessions: SessionInfo[];
85
- panes: PaneModel[];
86
- generatedAt: number;
87
- errors: SnapshotError[];
88
- }
89
-
90
- export interface FilterOptions {
91
- query: string;
92
- actionableOnly: boolean;
93
- tool: ToolKind | "all";
94
- state: PaneState | "all";
95
- }
96
-
97
- export interface SnapshotHistory {
98
- tool: ToolKind;
99
- state: PaneState;
100
- lastFingerprint: string;
101
- lastChangedAt: number;
102
- stableCount: number;
103
- }
104
-
105
- export interface CollectorOptions {
106
- previewLines: number;
107
- detailLines: number;
108
- refreshMs: number;
109
- }
110
-
111
- export interface AppOptions extends CollectorOptions {
112
- selectedPaneId?: string;
113
- }