@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,317 @@
1
+ /**
2
+ * Git worktree management
3
+ *
4
+ * Provides functions for creating, listing, and removing git worktrees.
5
+ * Worktrees allow multiple working directories from a single repository,
6
+ * enabling parallel work on different issues.
7
+ */
8
+
9
+ import { getWorktreesDir, getWorktreePath, generateBranchName, WORKTREES_DIR } from "../workspace/paths"
10
+ import { logger } from "../utils/logger"
11
+
12
+ // ============================================================================
13
+ // Types
14
+ // ============================================================================
15
+
16
+ /** Information about a git worktree */
17
+ export interface Worktree {
18
+ /** Absolute path to the worktree directory */
19
+ path: string
20
+ /** Current HEAD commit SHA */
21
+ head: string
22
+ /** Branch name (null for detached HEAD) */
23
+ branch: string | null
24
+ /** Whether this is the main worktree */
25
+ isMain: boolean
26
+ }
27
+
28
+ /** Result of a worktree operation */
29
+ export interface WorktreeResult {
30
+ /** Whether the operation succeeded */
31
+ success: boolean
32
+ /** Error message if operation failed */
33
+ error?: string
34
+ /** Path to the created worktree (for create operations) */
35
+ worktreePath?: string
36
+ /** Branch name (for create operations) */
37
+ branchName?: string
38
+ }
39
+
40
+ // ============================================================================
41
+ // Directory Setup
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Ensure the .worktrees directory exists
46
+ *
47
+ * @param projectRoot - Absolute path to the project root
48
+ */
49
+ export async function ensureWorktreesDir(projectRoot: string): Promise<void> {
50
+ const worktreesDir = getWorktreesDir(projectRoot)
51
+
52
+ // Create directory if it doesn't exist
53
+ const { mkdir } = await import("fs/promises")
54
+ await mkdir(worktreesDir, { recursive: true })
55
+ }
56
+
57
+ // ============================================================================
58
+ // Worktree Operations
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Create a new git worktree with a new branch
63
+ *
64
+ * Creates a worktree at `.worktrees/{name}` with branch `openswe/{name}`.
65
+ *
66
+ * @param projectRoot - Absolute path to the project root
67
+ * @param name - Worktree name (issue number or custom name)
68
+ * @returns Result indicating success or failure
69
+ */
70
+ export async function createWorktree(
71
+ projectRoot: string,
72
+ name: string | number
73
+ ): Promise<WorktreeResult> {
74
+ // Ensure .worktrees directory exists
75
+ await ensureWorktreesDir(projectRoot)
76
+
77
+ const worktreePath = getWorktreePath(projectRoot, name)
78
+ const branchName = generateBranchName(name)
79
+
80
+ logger.debug("Creating worktree", {
81
+ projectRoot,
82
+ worktreePath,
83
+ branchName,
84
+ })
85
+
86
+ // Check if worktree already exists
87
+ const exists = await worktreeExists(projectRoot, name)
88
+ if (exists) {
89
+ logger.warn("Worktree already exists", worktreePath)
90
+ return {
91
+ success: false,
92
+ error: `Worktree already exists: ${worktreePath}`,
93
+ }
94
+ }
95
+
96
+ try {
97
+ // Create the worktree with a new branch
98
+ // git worktree add <path> -b <branch>
99
+ const proc = Bun.spawn(
100
+ ["git", "worktree", "add", worktreePath, "-b", branchName],
101
+ {
102
+ cwd: projectRoot,
103
+ stdout: "pipe",
104
+ stderr: "pipe",
105
+ }
106
+ )
107
+
108
+ const stderr = await new Response(proc.stderr).text()
109
+ const exitCode = await proc.exited
110
+
111
+ if (exitCode !== 0) {
112
+ // Parse common errors
113
+ if (stderr.includes("already exists")) {
114
+ // Branch might exist, try without -b flag
115
+ logger.warn("Branch exists, retrying worktree creation", branchName)
116
+ const retryProc = Bun.spawn(
117
+ ["git", "worktree", "add", worktreePath, branchName],
118
+ {
119
+ cwd: projectRoot,
120
+ stdout: "pipe",
121
+ stderr: "pipe",
122
+ }
123
+ )
124
+
125
+ const retryStderr = await new Response(retryProc.stderr).text()
126
+ const retryExitCode = await retryProc.exited
127
+
128
+ if (retryExitCode !== 0) {
129
+ logger.warn("Worktree creation retry failed", retryStderr.trim())
130
+ return {
131
+ success: false,
132
+ error: retryStderr.trim() || "Failed to create worktree",
133
+ }
134
+ }
135
+ } else {
136
+ logger.warn("Worktree creation failed", stderr.trim())
137
+ return {
138
+ success: false,
139
+ error: stderr.trim() || "Failed to create worktree",
140
+ }
141
+ }
142
+ }
143
+
144
+ return {
145
+ success: true,
146
+ worktreePath,
147
+ branchName,
148
+ }
149
+ } catch (err) {
150
+ return {
151
+ success: false,
152
+ error: err instanceof Error ? err.message : "Failed to create worktree",
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * List all worktrees for a repository
159
+ *
160
+ * @param projectRoot - Absolute path to the project root
161
+ * @returns Array of worktree information
162
+ */
163
+ export async function listWorktrees(projectRoot: string): Promise<Worktree[]> {
164
+ try {
165
+ const proc = Bun.spawn(
166
+ ["git", "worktree", "list", "--porcelain"],
167
+ {
168
+ cwd: projectRoot,
169
+ stdout: "pipe",
170
+ stderr: "pipe",
171
+ }
172
+ )
173
+
174
+ const stdout = await new Response(proc.stdout).text()
175
+ const exitCode = await proc.exited
176
+
177
+ if (exitCode !== 0) {
178
+ return []
179
+ }
180
+
181
+ // Parse porcelain output
182
+ // Format:
183
+ // worktree /path/to/worktree
184
+ // HEAD <sha>
185
+ // branch refs/heads/branch-name
186
+ // <blank line>
187
+ const worktrees: Worktree[] = []
188
+ const entries = stdout.split("\n\n").filter((e) => e.trim())
189
+
190
+ for (const entry of entries) {
191
+ const lines = entry.split("\n")
192
+ let path = ""
193
+ let head = ""
194
+ let branch: string | null = null
195
+ let isMain = false
196
+
197
+ for (const line of lines) {
198
+ if (line.startsWith("worktree ")) {
199
+ path = line.slice(9)
200
+ } else if (line.startsWith("HEAD ")) {
201
+ head = line.slice(5)
202
+ } else if (line.startsWith("branch ")) {
203
+ // refs/heads/branch-name -> branch-name
204
+ branch = line.slice(7).replace("refs/heads/", "")
205
+ } else if (line === "bare") {
206
+ isMain = true
207
+ }
208
+ }
209
+
210
+ // Main worktree is the one that's not in .worktrees
211
+ if (path && !path.includes(`/${WORKTREES_DIR}/`)) {
212
+ isMain = true
213
+ }
214
+
215
+ if (path && head) {
216
+ worktrees.push({ path, head, branch, isMain })
217
+ }
218
+ }
219
+
220
+ return worktrees
221
+ } catch {
222
+ return []
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Remove a git worktree
228
+ *
229
+ * @param projectRoot - Absolute path to the project root
230
+ * @param name - Worktree name (issue number or custom name)
231
+ * @param options - Remove options
232
+ * @returns Result indicating success or failure
233
+ */
234
+ export async function removeWorktree(
235
+ projectRoot: string,
236
+ name: string | number,
237
+ options?: {
238
+ /** Force removal even if there are uncommitted changes */
239
+ force?: boolean
240
+ /** Also delete the associated branch */
241
+ deleteBranch?: boolean
242
+ }
243
+ ): Promise<WorktreeResult> {
244
+ const worktreePath = getWorktreePath(projectRoot, name)
245
+ const branchName = generateBranchName(name)
246
+ const { force = false, deleteBranch = true } = options ?? {}
247
+
248
+ try {
249
+ // Remove the worktree
250
+ const args = ["git", "worktree", "remove", worktreePath]
251
+ if (force) {
252
+ args.push("--force")
253
+ }
254
+
255
+ const proc = Bun.spawn(args, {
256
+ cwd: projectRoot,
257
+ stdout: "pipe",
258
+ stderr: "pipe",
259
+ })
260
+
261
+ const stderr = await new Response(proc.stderr).text()
262
+ const exitCode = await proc.exited
263
+
264
+ if (exitCode !== 0) {
265
+ // If directory doesn't exist, that's fine
266
+ if (stderr.includes("is not a working tree") || stderr.includes("No such file")) {
267
+ // Continue to branch deletion if requested
268
+ } else {
269
+ return {
270
+ success: false,
271
+ error: stderr.trim() || "Failed to remove worktree",
272
+ }
273
+ }
274
+ }
275
+
276
+ // Delete the branch if requested
277
+ if (deleteBranch) {
278
+ const branchArgs = ["git", "branch", "-D", branchName]
279
+ const branchProc = Bun.spawn(branchArgs, {
280
+ cwd: projectRoot,
281
+ stdout: "pipe",
282
+ stderr: "pipe",
283
+ })
284
+
285
+ // Don't fail if branch doesn't exist
286
+ await branchProc.exited
287
+ }
288
+
289
+ return { success: true }
290
+ } catch (err) {
291
+ return {
292
+ success: false,
293
+ error: err instanceof Error ? err.message : "Failed to remove worktree",
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Check if a worktree exists
300
+ *
301
+ * @param projectRoot - Absolute path to the project root
302
+ * @param name - Worktree name (issue number or custom name)
303
+ * @returns True if worktree exists
304
+ */
305
+ export async function worktreeExists(
306
+ projectRoot: string,
307
+ name: string | number
308
+ ): Promise<boolean> {
309
+ const worktreePath = getWorktreePath(projectRoot, name)
310
+
311
+ try {
312
+ const worktrees = await listWorktrees(projectRoot)
313
+ return worktrees.some((wt) => wt.path === worktreePath)
314
+ } catch {
315
+ return false
316
+ }
317
+ }
File without changes
@@ -0,0 +1,208 @@
1
+ /**
2
+ * GitHub CLI (gh) wrapper
3
+ *
4
+ * Provides validation and authentication checking via the gh CLI.
5
+ */
6
+
7
+ // ============================================================================
8
+ // Types
9
+ // ============================================================================
10
+
11
+ /** Result of checking gh CLI installation and authentication */
12
+ export interface GhCheckResult {
13
+ /** Whether gh CLI is installed */
14
+ installed: boolean
15
+ /** Whether user is authenticated */
16
+ authenticated: boolean
17
+ /** Error message if check failed */
18
+ error?: string
19
+ }
20
+
21
+ /** Result of validating a GitHub repository */
22
+ export interface RepoValidation {
23
+ /** Whether the repository exists */
24
+ exists: boolean
25
+ /** Whether the repository is accessible to the user */
26
+ accessible: boolean
27
+ /** Default branch name (e.g., "main") */
28
+ defaultBranch?: string
29
+ /** Whether the repository is private */
30
+ isPrivate?: boolean
31
+ /** Error message if validation failed */
32
+ error?: string
33
+ }
34
+
35
+ // ============================================================================
36
+ // gh CLI Check
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Check if gh CLI is installed and authenticated
41
+ */
42
+ export async function checkGhCli(): Promise<GhCheckResult> {
43
+ // First, check if gh is installed
44
+ try {
45
+ const versionProc = Bun.spawn(["gh", "--version"], {
46
+ stdout: "pipe",
47
+ stderr: "pipe",
48
+ })
49
+ const versionExit = await versionProc.exited
50
+
51
+ if (versionExit !== 0) {
52
+ return {
53
+ installed: false,
54
+ authenticated: false,
55
+ error: "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/",
56
+ }
57
+ }
58
+ } catch {
59
+ return {
60
+ installed: false,
61
+ authenticated: false,
62
+ error: "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/",
63
+ }
64
+ }
65
+
66
+ // Check authentication status
67
+ try {
68
+ const authProc = Bun.spawn(["gh", "auth", "status"], {
69
+ stdout: "pipe",
70
+ stderr: "pipe",
71
+ })
72
+ const stderr = await new Response(authProc.stderr).text()
73
+ const authExit = await authProc.exited
74
+
75
+ if (authExit !== 0) {
76
+ // Check for specific error messages
77
+ if (stderr.includes("not logged in")) {
78
+ return {
79
+ installed: true,
80
+ authenticated: false,
81
+ error: "Not authenticated with GitHub. Run: gh auth login",
82
+ }
83
+ }
84
+ return {
85
+ installed: true,
86
+ authenticated: false,
87
+ error: stderr.trim() || "Not authenticated with GitHub. Run: gh auth login",
88
+ }
89
+ }
90
+
91
+ return {
92
+ installed: true,
93
+ authenticated: true,
94
+ }
95
+ } catch (err) {
96
+ return {
97
+ installed: true,
98
+ authenticated: false,
99
+ error: err instanceof Error ? err.message : "Failed to check authentication status",
100
+ }
101
+ }
102
+ }
103
+
104
+ // ============================================================================
105
+ // Repository Validation
106
+ // ============================================================================
107
+
108
+ /**
109
+ * Validate that a GitHub repository exists and is accessible
110
+ *
111
+ * @param ownerRepo - Repository in "owner/repo" format
112
+ */
113
+ export async function validateGhRepo(ownerRepo: string): Promise<RepoValidation> {
114
+ try {
115
+ const proc = Bun.spawn(
116
+ ["gh", "repo", "view", ownerRepo, "--json", "name,defaultBranchRef,isPrivate"],
117
+ {
118
+ stdout: "pipe",
119
+ stderr: "pipe",
120
+ }
121
+ )
122
+
123
+ const stdout = await new Response(proc.stdout).text()
124
+ const stderr = await new Response(proc.stderr).text()
125
+ const exitCode = await proc.exited
126
+
127
+ if (exitCode !== 0) {
128
+ // Parse common error messages
129
+ if (stderr.includes("Could not resolve to a Repository")) {
130
+ return {
131
+ exists: false,
132
+ accessible: false,
133
+ error: `Repository '${ownerRepo}' not found. Check the name and try again.`,
134
+ }
135
+ }
136
+ if (stderr.includes("HTTP 404")) {
137
+ return {
138
+ exists: false,
139
+ accessible: false,
140
+ error: `Repository '${ownerRepo}' not found or you don't have access.`,
141
+ }
142
+ }
143
+ if (stderr.includes("HTTP 403") || stderr.includes("must have push access")) {
144
+ return {
145
+ exists: true,
146
+ accessible: false,
147
+ error: `You don't have access to '${ownerRepo}'. Check your permissions.`,
148
+ }
149
+ }
150
+ if (stderr.includes("not logged in")) {
151
+ return {
152
+ exists: false,
153
+ accessible: false,
154
+ error: "Not authenticated with GitHub. Run: gh auth login",
155
+ }
156
+ }
157
+
158
+ return {
159
+ exists: false,
160
+ accessible: false,
161
+ error: stderr.trim() || "Failed to validate repository",
162
+ }
163
+ }
164
+
165
+ // Parse JSON response
166
+ try {
167
+ const data = JSON.parse(stdout) as {
168
+ name?: string
169
+ defaultBranchRef?: { name?: string }
170
+ isPrivate?: boolean
171
+ }
172
+
173
+ return {
174
+ exists: true,
175
+ accessible: true,
176
+ defaultBranch: data.defaultBranchRef?.name,
177
+ isPrivate: data.isPrivate,
178
+ }
179
+ } catch {
180
+ // JSON parse failed but command succeeded - repo exists
181
+ return {
182
+ exists: true,
183
+ accessible: true,
184
+ }
185
+ }
186
+ } catch (err) {
187
+ return {
188
+ exists: false,
189
+ accessible: false,
190
+ error: err instanceof Error ? err.message : "Failed to validate repository",
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Check if gh CLI is available (quick check, no auth validation)
197
+ */
198
+ export async function isGhInstalled(): Promise<boolean> {
199
+ try {
200
+ const proc = Bun.spawn(["gh", "--version"], {
201
+ stdout: "pipe",
202
+ stderr: "pipe",
203
+ })
204
+ return (await proc.exited) === 0
205
+ } catch {
206
+ return false
207
+ }
208
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * GitHub module
3
+ *
4
+ * Re-exports GitHub CLI wrapper functions.
5
+ */
6
+
7
+ export * from "./client"
8
+ export * from "./issues"