@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.
- package/AGENTS.md +203 -0
- package/CLAUDE.md +203 -0
- package/README.md +166 -0
- package/bun.lock +447 -0
- package/bunfig.toml +4 -0
- package/package.json +42 -0
- package/src/app.tsx +84 -0
- package/src/components/App.tsx +526 -0
- package/src/components/ConfirmDialog.tsx +88 -0
- package/src/components/Footer.tsx +50 -0
- package/src/components/HelpModal.tsx +136 -0
- package/src/components/IssueSelectorModal.tsx +701 -0
- package/src/components/ManualSessionModal.tsx +191 -0
- package/src/components/PhaseProgress.tsx +45 -0
- package/src/components/Preview.tsx +249 -0
- package/src/components/ProviderSwitcherModal.tsx +156 -0
- package/src/components/ScrollableText.tsx +120 -0
- package/src/components/SessionCard.tsx +60 -0
- package/src/components/SessionList.tsx +79 -0
- package/src/components/SessionTerminal.tsx +89 -0
- package/src/components/StatusBar.tsx +84 -0
- package/src/components/ThemeSwitcherModal.tsx +237 -0
- package/src/components/index.ts +58 -0
- package/src/components/session-utils.ts +337 -0
- package/src/components/theme.ts +206 -0
- package/src/components/types.ts +215 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/env.ts +67 -0
- package/src/config/global.ts +252 -0
- package/src/config/index.ts +171 -0
- package/src/config/types.ts +131 -0
- package/src/core/.gitkeep +0 -0
- package/src/core/index.ts +5 -0
- package/src/core/parser.ts +62 -0
- package/src/core/process-manager.ts +52 -0
- package/src/core/session.ts +423 -0
- package/src/core/tmux.ts +206 -0
- package/src/git/.gitkeep +0 -0
- package/src/git/index.ts +8 -0
- package/src/git/repo.ts +443 -0
- package/src/git/worktree.ts +317 -0
- package/src/github/.gitkeep +0 -0
- package/src/github/client.ts +208 -0
- package/src/github/index.ts +8 -0
- package/src/github/issues.ts +351 -0
- package/src/index.ts +369 -0
- package/src/prompts/.gitkeep +0 -0
- package/src/prompts/index.ts +1 -0
- package/src/prompts/swe-system.ts +22 -0
- package/src/providers/claude.ts +103 -0
- package/src/providers/index.ts +21 -0
- package/src/providers/opencode.ts +98 -0
- package/src/providers/registry.ts +53 -0
- package/src/providers/types.ts +117 -0
- package/src/store/buffers.ts +234 -0
- package/src/store/db.test.ts +579 -0
- package/src/store/db.ts +249 -0
- package/src/store/index.ts +101 -0
- package/src/store/project.ts +119 -0
- package/src/store/schema.sql +71 -0
- package/src/store/sessions.ts +454 -0
- package/src/store/types.ts +194 -0
- package/src/theme/context.tsx +170 -0
- package/src/theme/custom.ts +134 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/loader.ts +264 -0
- package/src/theme/themes/aura.json +69 -0
- package/src/theme/themes/ayu.json +80 -0
- package/src/theme/themes/carbonfox.json +248 -0
- package/src/theme/themes/catppuccin-frappe.json +233 -0
- package/src/theme/themes/catppuccin-macchiato.json +233 -0
- package/src/theme/themes/catppuccin.json +112 -0
- package/src/theme/themes/cobalt2.json +228 -0
- package/src/theme/themes/cursor.json +249 -0
- package/src/theme/themes/dracula.json +219 -0
- package/src/theme/themes/everforest.json +241 -0
- package/src/theme/themes/flexoki.json +237 -0
- package/src/theme/themes/github.json +233 -0
- package/src/theme/themes/gruvbox.json +242 -0
- package/src/theme/themes/kanagawa.json +77 -0
- package/src/theme/themes/lucent-orng.json +237 -0
- package/src/theme/themes/material.json +235 -0
- package/src/theme/themes/matrix.json +77 -0
- package/src/theme/themes/mercury.json +252 -0
- package/src/theme/themes/monokai.json +221 -0
- package/src/theme/themes/nightowl.json +221 -0
- package/src/theme/themes/nord.json +223 -0
- package/src/theme/themes/one-dark.json +84 -0
- package/src/theme/themes/opencode.json +245 -0
- package/src/theme/themes/orng.json +249 -0
- package/src/theme/themes/osaka-jade.json +93 -0
- package/src/theme/themes/palenight.json +222 -0
- package/src/theme/themes/rosepine.json +234 -0
- package/src/theme/themes/solarized.json +223 -0
- package/src/theme/themes/synthwave84.json +226 -0
- package/src/theme/themes/tokyonight.json +243 -0
- package/src/theme/themes/vercel.json +245 -0
- package/src/theme/themes/vesper.json +218 -0
- package/src/theme/themes/zenburn.json +223 -0
- package/src/theme/types.ts +225 -0
- package/src/types/sql.d.ts +4 -0
- package/src/utils/ansi-parser.ts +225 -0
- package/src/utils/format.ts +46 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/logger.ts +112 -0
- package/src/utils/prerequisites.ts +118 -0
- package/src/utils/shell.ts +9 -0
- package/src/wizard/flows.ts +419 -0
- package/src/wizard/index.ts +37 -0
- package/src/wizard/prompts.ts +190 -0
- package/src/workspace/detect.test.ts +51 -0
- package/src/workspace/detect.ts +223 -0
- package/src/workspace/index.ts +71 -0
- package/src/workspace/init.ts +131 -0
- package/src/workspace/paths.ts +143 -0
- package/src/workspace/project.ts +164 -0
- 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
|
+
}
|