@vladimirven/openswe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/AGENTS.md +203 -0
  2. package/CLAUDE.md +203 -0
  3. package/README.md +166 -0
  4. package/bun.lock +447 -0
  5. package/bunfig.toml +4 -0
  6. package/package.json +42 -0
  7. package/src/app.tsx +84 -0
  8. package/src/components/App.tsx +526 -0
  9. package/src/components/ConfirmDialog.tsx +88 -0
  10. package/src/components/Footer.tsx +50 -0
  11. package/src/components/HelpModal.tsx +136 -0
  12. package/src/components/IssueSelectorModal.tsx +701 -0
  13. package/src/components/ManualSessionModal.tsx +191 -0
  14. package/src/components/PhaseProgress.tsx +45 -0
  15. package/src/components/Preview.tsx +249 -0
  16. package/src/components/ProviderSwitcherModal.tsx +156 -0
  17. package/src/components/ScrollableText.tsx +120 -0
  18. package/src/components/SessionCard.tsx +60 -0
  19. package/src/components/SessionList.tsx +79 -0
  20. package/src/components/SessionTerminal.tsx +89 -0
  21. package/src/components/StatusBar.tsx +84 -0
  22. package/src/components/ThemeSwitcherModal.tsx +237 -0
  23. package/src/components/index.ts +58 -0
  24. package/src/components/session-utils.ts +337 -0
  25. package/src/components/theme.ts +206 -0
  26. package/src/components/types.ts +215 -0
  27. package/src/config/defaults.ts +44 -0
  28. package/src/config/env.ts +67 -0
  29. package/src/config/global.ts +252 -0
  30. package/src/config/index.ts +171 -0
  31. package/src/config/types.ts +131 -0
  32. package/src/core/.gitkeep +0 -0
  33. package/src/core/index.ts +5 -0
  34. package/src/core/parser.ts +62 -0
  35. package/src/core/process-manager.ts +52 -0
  36. package/src/core/session.ts +423 -0
  37. package/src/core/tmux.ts +206 -0
  38. package/src/git/.gitkeep +0 -0
  39. package/src/git/index.ts +8 -0
  40. package/src/git/repo.ts +443 -0
  41. package/src/git/worktree.ts +317 -0
  42. package/src/github/.gitkeep +0 -0
  43. package/src/github/client.ts +208 -0
  44. package/src/github/index.ts +8 -0
  45. package/src/github/issues.ts +351 -0
  46. package/src/index.ts +369 -0
  47. package/src/prompts/.gitkeep +0 -0
  48. package/src/prompts/index.ts +1 -0
  49. package/src/prompts/swe-system.ts +22 -0
  50. package/src/providers/claude.ts +103 -0
  51. package/src/providers/index.ts +21 -0
  52. package/src/providers/opencode.ts +98 -0
  53. package/src/providers/registry.ts +53 -0
  54. package/src/providers/types.ts +117 -0
  55. package/src/store/buffers.ts +234 -0
  56. package/src/store/db.test.ts +579 -0
  57. package/src/store/db.ts +249 -0
  58. package/src/store/index.ts +101 -0
  59. package/src/store/project.ts +119 -0
  60. package/src/store/schema.sql +71 -0
  61. package/src/store/sessions.ts +454 -0
  62. package/src/store/types.ts +194 -0
  63. package/src/theme/context.tsx +170 -0
  64. package/src/theme/custom.ts +134 -0
  65. package/src/theme/index.ts +58 -0
  66. package/src/theme/loader.ts +264 -0
  67. package/src/theme/themes/aura.json +69 -0
  68. package/src/theme/themes/ayu.json +80 -0
  69. package/src/theme/themes/carbonfox.json +248 -0
  70. package/src/theme/themes/catppuccin-frappe.json +233 -0
  71. package/src/theme/themes/catppuccin-macchiato.json +233 -0
  72. package/src/theme/themes/catppuccin.json +112 -0
  73. package/src/theme/themes/cobalt2.json +228 -0
  74. package/src/theme/themes/cursor.json +249 -0
  75. package/src/theme/themes/dracula.json +219 -0
  76. package/src/theme/themes/everforest.json +241 -0
  77. package/src/theme/themes/flexoki.json +237 -0
  78. package/src/theme/themes/github.json +233 -0
  79. package/src/theme/themes/gruvbox.json +242 -0
  80. package/src/theme/themes/kanagawa.json +77 -0
  81. package/src/theme/themes/lucent-orng.json +237 -0
  82. package/src/theme/themes/material.json +235 -0
  83. package/src/theme/themes/matrix.json +77 -0
  84. package/src/theme/themes/mercury.json +252 -0
  85. package/src/theme/themes/monokai.json +221 -0
  86. package/src/theme/themes/nightowl.json +221 -0
  87. package/src/theme/themes/nord.json +223 -0
  88. package/src/theme/themes/one-dark.json +84 -0
  89. package/src/theme/themes/opencode.json +245 -0
  90. package/src/theme/themes/orng.json +249 -0
  91. package/src/theme/themes/osaka-jade.json +93 -0
  92. package/src/theme/themes/palenight.json +222 -0
  93. package/src/theme/themes/rosepine.json +234 -0
  94. package/src/theme/themes/solarized.json +223 -0
  95. package/src/theme/themes/synthwave84.json +226 -0
  96. package/src/theme/themes/tokyonight.json +243 -0
  97. package/src/theme/themes/vercel.json +245 -0
  98. package/src/theme/themes/vesper.json +218 -0
  99. package/src/theme/themes/zenburn.json +223 -0
  100. package/src/theme/types.ts +225 -0
  101. package/src/types/sql.d.ts +4 -0
  102. package/src/utils/ansi-parser.ts +225 -0
  103. package/src/utils/format.ts +46 -0
  104. package/src/utils/id.ts +15 -0
  105. package/src/utils/logger.ts +112 -0
  106. package/src/utils/prerequisites.ts +118 -0
  107. package/src/utils/shell.ts +9 -0
  108. package/src/wizard/flows.ts +419 -0
  109. package/src/wizard/index.ts +37 -0
  110. package/src/wizard/prompts.ts +190 -0
  111. package/src/workspace/detect.test.ts +51 -0
  112. package/src/workspace/detect.ts +223 -0
  113. package/src/workspace/index.ts +71 -0
  114. package/src/workspace/init.ts +131 -0
  115. package/src/workspace/paths.ts +143 -0
  116. package/src/workspace/project.ts +164 -0
  117. package/tsconfig.json +22 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Components module - OpenSWE TUI components
3
+ *
4
+ * Exports all components and types for the TUI interface.
5
+ */
6
+
7
+ // ============================================================================
8
+ // Components
9
+ // ============================================================================
10
+
11
+ export { App } from "./App"
12
+ export { StatusBar } from "./StatusBar"
13
+ export { SessionList } from "./SessionList"
14
+ export { SessionCard } from "./SessionCard"
15
+ export { Preview } from "./Preview"
16
+ export { PhaseProgress } from "./PhaseProgress"
17
+ export { HelpModal } from "./HelpModal"
18
+ export { ConfirmDialog } from "./ConfirmDialog"
19
+ export { ManualSessionModal } from "./ManualSessionModal"
20
+ export { ProviderSwitcherModal } from "./ProviderSwitcherModal"
21
+ export { ThemeSwitcherModal } from "./ThemeSwitcherModal"
22
+ export { SessionTerminal } from "./SessionTerminal"
23
+ export { ScrollableText } from "./ScrollableText"
24
+
25
+ // ============================================================================
26
+ // Types
27
+ // ============================================================================
28
+
29
+ export type {
30
+ AppProps,
31
+ StatusBarProps,
32
+ SessionListProps,
33
+ SessionCardProps,
34
+ PreviewProps,
35
+ PhaseProgressProps,
36
+ HelpModalProps,
37
+ ConfirmDialogProps,
38
+ ManualSessionModalProps,
39
+ ProviderSwitcherModalProps,
40
+ ThemeSwitcherModalProps,
41
+ PendingAction,
42
+ ModalType,
43
+ ProjectInfo,
44
+ } from "./types"
45
+
46
+ export {
47
+ STATUS_ICONS,
48
+ PHASE_ORDER,
49
+ PHASE_DISPLAY_NAMES,
50
+ getPhaseProgress,
51
+ generateProgressBar,
52
+ } from "./types"
53
+
54
+ // ============================================================================
55
+ // Theme
56
+ // ============================================================================
57
+
58
+ export { colors, borders, layout, typography, keybindings } from "./theme"
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Session utility functions
3
+ *
4
+ * Bridge functions connecting GitHub issues, git worktrees, and sessions.
5
+ * These functions handle the complete workflow of creating and deleting sessions.
6
+ */
7
+
8
+ import { createWorktree, removeWorktree, worktreeExists } from "../git"
9
+ import { createSession, deleteSession, getSession } from "../store"
10
+ import { getWorktreePath, generateBranchName, sanitizeWorktreeName } from "../workspace/paths"
11
+ import type { GitHubIssue } from "../github"
12
+ import type { Session, AISessionData } from "../store"
13
+ import { logger } from "../utils/logger"
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ /** Options for creating a session from an issue */
20
+ export interface CreateSessionOptions {
21
+ /**
22
+ * Custom worktree name to use instead of the issue number.
23
+ * Useful when resolving conflicts by appending a suffix.
24
+ */
25
+ worktreeNameOverride?: string
26
+
27
+ /**
28
+ * Whether to overwrite an existing worktree if it exists.
29
+ * If false (default), will fail if worktree exists.
30
+ */
31
+ overwriteWorktreeChoice?: OverwriteWorktreeChoice
32
+
33
+ /**
34
+ * Initial AI session data (e.g. backend provider)
35
+ */
36
+ aiSessionData?: AISessionData
37
+ }
38
+
39
+ export enum OverwriteWorktreeChoice {
40
+ use_existing = 0,
41
+ create_new = 1,
42
+ overwrite = 2
43
+ }
44
+
45
+ /** Result of creating a session */
46
+ export interface CreateSessionResult {
47
+ /** Whether the operation succeeded */
48
+ success: boolean
49
+ /** Created session (null if failed) */
50
+ session: Session | null
51
+ /** Error message if operation failed */
52
+ error?: string
53
+ }
54
+
55
+ /** Result of deleting a session with worktree */
56
+ export interface DeleteSessionResult {
57
+ /** Whether the operation succeeded */
58
+ success: boolean
59
+ /** Error message if operation failed */
60
+ error?: string
61
+ }
62
+
63
+ // ============================================================================
64
+ // Session Creation
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Find the next available worktree name by appending a counter
69
+ *
70
+ * @param projectRoot - Absolute path to the project root
71
+ * @param baseName - Base name (e.g. issue number)
72
+ * @returns Available name (e.g. "issue-123", "issue-123-1")
73
+ */
74
+ export async function findNextAvailableWorktreeName(
75
+ projectRoot: string,
76
+ baseName: string | number
77
+ ): Promise<string> {
78
+ // Get the canonical folder name first (e.g. 123 -> "issue-123")
79
+ const safeBase = sanitizeWorktreeName(baseName)
80
+
81
+ // First check the base name
82
+ if (!(await worktreeExists(projectRoot, safeBase))) {
83
+ return safeBase
84
+ }
85
+
86
+ // Try appending counters until we find a free one
87
+ let counter = 1
88
+ while (true) {
89
+ const candidate = `${safeBase}-${counter}`
90
+ if (!(await worktreeExists(projectRoot, candidate))) {
91
+ return candidate
92
+ }
93
+ counter++
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Create a session from a GitHub issue
99
+ *
100
+ * This function:
101
+ * 1. Creates a git worktree at `.worktrees/issue-{number}`
102
+ * 2. Creates a session in the database linked to the issue
103
+ *
104
+ * @param projectRoot - Absolute path to the project root
105
+ * @param issue - GitHub issue data
106
+ * @param options - Creation options (optional)
107
+ * @returns Result with the created session or error
108
+ */
109
+ export async function createSessionFromIssue(
110
+ projectRoot: string,
111
+ issue: GitHubIssue,
112
+ options: CreateSessionOptions = {}
113
+ ): Promise<CreateSessionResult> {
114
+ const worktreeName = options.worktreeNameOverride ?? issue.number
115
+
116
+ logger.debug("Creating worktree for issue", {
117
+ issueNumber: issue.number,
118
+ worktreeName,
119
+ projectRoot,
120
+ overwriteChoice: options.overwriteWorktreeChoice,
121
+ })
122
+ logger.info(options)
123
+ logger.info(options.overwriteWorktreeChoice)
124
+
125
+ let worktreePath: string
126
+ let branchName: string
127
+
128
+ if (options.overwriteWorktreeChoice === OverwriteWorktreeChoice.use_existing) {
129
+ // Continue with existing worktree
130
+ worktreePath = getWorktreePath(projectRoot, worktreeName)
131
+ branchName = generateBranchName(worktreeName)
132
+ } else {
133
+ // Handle overwrite if requested
134
+ if (options.overwriteWorktreeChoice === OverwriteWorktreeChoice.overwrite) {
135
+ const exists = await worktreeExists(projectRoot, worktreeName)
136
+ if (exists) {
137
+ logger.info("Overwriting existing worktree", { worktreeName })
138
+ await removeWorktree(projectRoot, worktreeName, { force: true, deleteBranch: true })
139
+ }
140
+ }
141
+
142
+ // Create the worktree
143
+ const worktreeResult = await createWorktree(projectRoot, worktreeName)
144
+ if (!worktreeResult.success) {
145
+ logger.warn("Worktree creation failed", worktreeResult.error)
146
+ return {
147
+ success: false,
148
+ session: null,
149
+ error: worktreeResult.error ?? "Failed to create worktree",
150
+ }
151
+ }
152
+
153
+ worktreePath = worktreeResult.worktreePath!
154
+ branchName = worktreeResult.branchName!
155
+ }
156
+
157
+ // Create the session in the database
158
+ try {
159
+ const session = createSession({
160
+ name: `#${issue.number}: ${issue.title}`,
161
+ issueNumber: issue.number,
162
+ issueTitle: issue.title,
163
+ issueBody: issue.body ?? undefined,
164
+ issueUrl: issue.url,
165
+ worktreePath,
166
+ branchName,
167
+ aiSessionData: options.aiSessionData,
168
+ })
169
+
170
+ logger.debug("Session created", {
171
+ sessionId: session.id,
172
+ issueNumber: issue.number,
173
+ })
174
+
175
+ return {
176
+ success: true,
177
+ session,
178
+ }
179
+ } catch (err) {
180
+ // Clean up the worktree if session creation fails
181
+ logger.warn("Session creation failed, cleaning up worktree", err)
182
+ await removeWorktree(projectRoot, worktreeName, { force: true, deleteBranch: true })
183
+
184
+ return {
185
+ success: false,
186
+ session: null,
187
+ error: err instanceof Error ? err.message : "Failed to create session",
188
+ }
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Create a manual session (not linked to a GitHub issue)
194
+ *
195
+ * This function:
196
+ * 1. Creates a git worktree at `.worktrees/{name}`
197
+ * 2. Creates a session in the database
198
+ *
199
+ * @param projectRoot - Absolute path to the project root
200
+ * @param name - Session name (will be sanitized for worktree)
201
+ * @param options - Creation options (optional)
202
+ * @returns Result with the created session or error
203
+ */
204
+ export async function createManualSession(
205
+ projectRoot: string,
206
+ name: string,
207
+ options: { aiSessionData?: AISessionData } = {}
208
+ ): Promise<CreateSessionResult> {
209
+ const sanitizedName = sanitizeWorktreeName(name)
210
+
211
+ logger.debug("Creating manual session", {
212
+ name,
213
+ sanitizedName,
214
+ projectRoot,
215
+ })
216
+
217
+ // Create the worktree
218
+ const worktreeResult = await createWorktree(projectRoot, sanitizedName)
219
+ if (!worktreeResult.success) {
220
+ logger.warn("Worktree creation failed", worktreeResult.error)
221
+ return {
222
+ success: false,
223
+ session: null,
224
+ error: worktreeResult.error ?? "Failed to create worktree",
225
+ }
226
+ }
227
+
228
+ // Create the session in the database
229
+ try {
230
+ const session = createSession({
231
+ name,
232
+ worktreePath: worktreeResult.worktreePath!,
233
+ branchName: worktreeResult.branchName!,
234
+ aiSessionData: options.aiSessionData,
235
+ })
236
+
237
+ logger.debug("Manual session created", { sessionId: session.id })
238
+
239
+ return {
240
+ success: true,
241
+ session,
242
+ }
243
+ } catch (err) {
244
+ // Clean up the worktree if session creation fails
245
+ logger.warn("Manual session creation failed, cleaning up worktree", err)
246
+ await removeWorktree(projectRoot, sanitizedName, { force: true, deleteBranch: true })
247
+
248
+ return {
249
+ success: false,
250
+ session: null,
251
+ error: err instanceof Error ? err.message : "Failed to create session",
252
+ }
253
+ }
254
+ }
255
+
256
+ // ============================================================================
257
+ // Session Deletion
258
+ // ============================================================================
259
+
260
+ /**
261
+ * Delete a session and its associated worktree
262
+ *
263
+ * This function:
264
+ * 1. Gets the session to find the worktree path
265
+ * 2. Removes the git worktree
266
+ * 3. Deletes the session from the database
267
+ *
268
+ * @param projectRoot - Absolute path to the project root
269
+ * @param sessionId - Session UUID
270
+ * @returns Result indicating success or failure
271
+ */
272
+ export async function deleteSessionWithWorktree(
273
+ projectRoot: string,
274
+ sessionId: string
275
+ ): Promise<DeleteSessionResult> {
276
+ // Get the session to find worktree info
277
+ const session = getSession(sessionId)
278
+ if (!session) {
279
+ return {
280
+ success: false,
281
+ error: "Session not found",
282
+ }
283
+ }
284
+
285
+ // Determine worktree name from session
286
+ let worktreeName: string | number
287
+ if (session.issueNumber !== null) {
288
+ worktreeName = session.issueNumber
289
+ } else {
290
+ // Extract from worktree path or branch name
291
+ const pathParts = session.worktreePath.split("/")
292
+ worktreeName = pathParts[pathParts.length - 1] ?? session.branchName.replace("openswe/", "")
293
+ }
294
+
295
+ // Remove the worktree (force to handle uncommitted changes)
296
+ const worktreeResult = await removeWorktree(projectRoot, worktreeName, {
297
+ force: true,
298
+ deleteBranch: true,
299
+ })
300
+
301
+ if (!worktreeResult.success) {
302
+ // Log the error but continue to delete the session
303
+ // The worktree might already be gone
304
+ console.error(`Warning: Failed to remove worktree: ${worktreeResult.error}`)
305
+ }
306
+
307
+ // Delete the session from the database
308
+ try {
309
+ deleteSession(sessionId)
310
+ return { success: true }
311
+ } catch (err) {
312
+ return {
313
+ success: false,
314
+ error: err instanceof Error ? err.message : "Failed to delete session",
315
+ }
316
+ }
317
+ }
318
+
319
+ // ============================================================================
320
+ // Helpers
321
+ // ============================================================================
322
+
323
+ /**
324
+ * Extract worktree name from a session
325
+ *
326
+ * @param session - Session to extract name from
327
+ * @returns Worktree name (issue number or sanitized name)
328
+ */
329
+ export function getWorktreeNameFromSession(session: Session): string | number {
330
+ if (session.issueNumber !== null) {
331
+ return session.issueNumber
332
+ }
333
+
334
+ // Extract from worktree path
335
+ const pathParts = session.worktreePath.split("/")
336
+ return pathParts[pathParts.length - 1] ?? session.branchName.replace("openswe/", "")
337
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Theme configuration for OpenSWE TUI
3
+ *
4
+ * Defines colors, border styles, and layout constants
5
+ * Colors are derived from the theme system for consistency
6
+ */
7
+
8
+ import { createMemo, type Accessor } from "solid-js"
9
+ import type { Status } from "../store"
10
+ import type { ResolvedTheme } from "../theme/types"
11
+ import { getDefaultTheme, useTheme } from "../theme/context"
12
+
13
+ // ============================================================================
14
+ // Color Palette - Derived from Theme System
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Create colors object from a resolved theme
19
+ * This allows components to use either the reactive theme or static defaults
20
+ */
21
+ export function createColors(theme: ResolvedTheme) {
22
+ return {
23
+ // Background colors
24
+ bg: {
25
+ primary: theme.background,
26
+ secondary: theme.backgroundPanel,
27
+ card: theme.backgroundElement,
28
+ cardSelected: theme.borderSubtle,
29
+ },
30
+
31
+ // Text colors
32
+ text: {
33
+ primary: theme.text,
34
+ secondary: theme.textMuted,
35
+ muted: theme.borderSubtle,
36
+ inverse: theme.background,
37
+ },
38
+
39
+ // Status colors (semantic colors)
40
+ status: {
41
+ active: theme.success,
42
+ queued: theme.borderSubtle,
43
+ needs_attention: theme.warning,
44
+ completed: theme.success,
45
+ paused: theme.info,
46
+ failed: theme.error,
47
+ } as Record<Status, string>,
48
+
49
+ // Border colors
50
+ border: {
51
+ primary: theme.border,
52
+ secondary: theme.borderSubtle,
53
+ accent: theme.accent,
54
+ },
55
+
56
+ // Progress bar colors
57
+ progress: {
58
+ filled: theme.primary,
59
+ empty: theme.backgroundElement,
60
+ text: theme.accent,
61
+ },
62
+
63
+ // Accent colors
64
+ accent: {
65
+ primary: theme.primary,
66
+ secondary: theme.secondary,
67
+ success: theme.success,
68
+ warning: theme.warning,
69
+ error: theme.error,
70
+ info: theme.info,
71
+ },
72
+ }
73
+ }
74
+
75
+ /** Type for the colors object */
76
+ export type Colors = ReturnType<typeof createColors>
77
+
78
+ /** Default colors using the default theme (tokyonight) */
79
+ export const colors = createColors(getDefaultTheme())
80
+
81
+ /**
82
+ * Reactive hook to get colors that update when the theme changes
83
+ *
84
+ * Must be used within a component that is inside the ThemeProvider
85
+ *
86
+ * @returns Accessor to the current colors object
87
+ */
88
+ export function useColors(): Accessor<Colors> {
89
+ const { theme } = useTheme()
90
+ return createMemo(() => createColors(theme()))
91
+ }
92
+
93
+ // ============================================================================
94
+ // Border Styles
95
+ // ============================================================================
96
+
97
+ export const borders = {
98
+ // Rounded borders for main panels
99
+ panel: {
100
+ topLeft: "╭",
101
+ topRight: "╮",
102
+ bottomLeft: "╰",
103
+ bottomRight: "╯",
104
+ horizontal: "─",
105
+ vertical: "│",
106
+ },
107
+
108
+ // Single line borders for cards
109
+ card: {
110
+ topLeft: "┌",
111
+ topRight: "┐",
112
+ bottomLeft: "└",
113
+ bottomRight: "┘",
114
+ horizontal: "─",
115
+ vertical: "│",
116
+ },
117
+
118
+ // Double borders for emphasis
119
+ double: {
120
+ topLeft: "╔",
121
+ topRight: "╗",
122
+ bottomLeft: "╚",
123
+ bottomRight: "╝",
124
+ horizontal: "═",
125
+ vertical: "║",
126
+ },
127
+ }
128
+
129
+ // ============================================================================
130
+ // Layout Constants
131
+ // ============================================================================
132
+
133
+ export const layout = {
134
+ // Pane widths (percentages)
135
+ sessionListWidth: "30%",
136
+ previewWidth: "70%",
137
+
138
+ // Padding values
139
+ padding: {
140
+ none: 0,
141
+ small: 1,
142
+ medium: 2,
143
+ large: 3,
144
+ },
145
+
146
+ // Card dimensions
147
+ card: {
148
+ minHeight: 4,
149
+ padding: 1,
150
+ },
151
+
152
+ // Status bar heights
153
+ statusBar: {
154
+ height: 1,
155
+ },
156
+
157
+ // Minimum dimensions
158
+ minWidth: 80,
159
+ minHeight: 24,
160
+ }
161
+
162
+ // ============================================================================
163
+ // Typography
164
+ // ============================================================================
165
+
166
+ export const typography = {
167
+ // Truncation lengths
168
+ maxSessionNameLength: 25,
169
+ maxIssueNumberWidth: 8,
170
+ maxTokensWidth: 8,
171
+ }
172
+
173
+ // ============================================================================
174
+ // Modal Dimensions
175
+ // ============================================================================
176
+
177
+ export const modals = {
178
+ issueSelector: {
179
+ width: 70,
180
+ height: 24,
181
+ },
182
+ help: {
183
+ width: 44,
184
+ height: 24,
185
+ },
186
+ confirm: {
187
+ width: 45,
188
+ },
189
+ manualSession: {
190
+ width: 50,
191
+ },
192
+ }
193
+
194
+ // ============================================================================
195
+ // Keybinding Display
196
+ // ============================================================================
197
+
198
+ export const keybindings = {
199
+ navigate: "j/k or ↑/↓",
200
+ newSession: "n",
201
+ issues: "i",
202
+ theme: "t",
203
+ select: "Enter",
204
+ help: "?",
205
+ quit: "q",
206
+ }