@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,249 @@
1
+ /**
2
+ * Database connection and initialization
3
+ *
4
+ * Manages the SQLite database connection using Bun's native bun:sqlite.
5
+ * Uses WAL mode for better concurrency.
6
+ */
7
+
8
+ import { Database } from "bun:sqlite"
9
+ import { dirname } from "path"
10
+ import { getStateDatabasePath } from "../workspace/paths"
11
+
12
+ // ============================================================================
13
+ // Schema
14
+ // ============================================================================
15
+
16
+ // Import schema as raw text
17
+ import schema from "./schema.sql" with { type: "text" }
18
+
19
+ // ============================================================================
20
+ // Manager Types
21
+ // ============================================================================
22
+
23
+ export interface DatabaseManager {
24
+ getDatabase: () => Database
25
+ initDatabase: (projectRoot: string) => Promise<void>
26
+ initDatabaseWithPath: (dbPath: string) => Promise<void>
27
+ closeDatabase: () => void
28
+ isDatabaseInitialized: () => boolean
29
+ getDatabasePath: () => string | null
30
+ getSchemaVersion: () => number
31
+ runMigrations: () => void
32
+ withTransaction: <T>(fn: () => T) => T
33
+ }
34
+
35
+ // ============================================================================
36
+ // Database Manager Factory
37
+ // ============================================================================
38
+
39
+ export function createDatabaseManager(): DatabaseManager {
40
+ let db: Database | null = null
41
+ let currentDbPath: string | null = null
42
+
43
+ const getDatabase = (): Database => {
44
+ if (!db) {
45
+ throw new Error(
46
+ "Database not initialized. Call initDatabase() first."
47
+ )
48
+ }
49
+ return db
50
+ }
51
+
52
+ const applySchema = (): void => {
53
+ if (!db) {
54
+ throw new Error("Database not initialized")
55
+ }
56
+
57
+ // Execute schema as a transaction
58
+ db.exec(schema)
59
+ }
60
+
61
+ const initDatabase = async (projectRoot: string): Promise<void> => {
62
+ const dbPath = getStateDatabasePath(projectRoot)
63
+
64
+ // If already connected to this database, skip
65
+ if (db && currentDbPath === dbPath) {
66
+ return
67
+ }
68
+
69
+ // Close existing connection if any
70
+ if (db) {
71
+ closeDatabase()
72
+ }
73
+
74
+ // Ensure directory exists
75
+ const dir = dirname(dbPath)
76
+ await Bun.write(dir + "/.keep", "")
77
+
78
+ // Open database connection
79
+ db = new Database(dbPath, { create: true })
80
+ currentDbPath = dbPath
81
+
82
+ // Enable WAL mode for better concurrency
83
+ db.exec("PRAGMA journal_mode = WAL")
84
+
85
+ // Enable foreign keys
86
+ db.exec("PRAGMA foreign_keys = ON")
87
+
88
+ // Apply schema
89
+ applySchema()
90
+ runMigrations()
91
+ }
92
+
93
+ const initDatabaseWithPath = async (dbPath: string): Promise<void> => {
94
+ // Close existing connection if any
95
+ if (db) {
96
+ closeDatabase()
97
+ }
98
+
99
+ // For in-memory databases, skip directory creation
100
+ if (dbPath !== ":memory:") {
101
+ const dir = dirname(dbPath)
102
+ await Bun.write(dir + "/.keep", "")
103
+ }
104
+
105
+ // Open database connection
106
+ db = new Database(dbPath, { create: true })
107
+ currentDbPath = dbPath
108
+
109
+ // Enable WAL mode for better concurrency (skip for in-memory)
110
+ if (dbPath !== ":memory:") {
111
+ db.exec("PRAGMA journal_mode = WAL")
112
+ }
113
+
114
+ // Enable foreign keys
115
+ db.exec("PRAGMA foreign_keys = ON")
116
+
117
+ // Apply schema
118
+ applySchema()
119
+ runMigrations()
120
+ }
121
+
122
+ const closeDatabase = (): void => {
123
+ if (db) {
124
+ db.close()
125
+ db = null
126
+ currentDbPath = null
127
+ }
128
+ }
129
+
130
+ const isDatabaseInitialized = (): boolean => {
131
+ return db !== null
132
+ }
133
+
134
+ const getDatabasePath = (): string | null => {
135
+ return currentDbPath
136
+ }
137
+
138
+ const getSchemaVersion = (): number => {
139
+ const database = getDatabase()
140
+ const result = database
141
+ .query<{ version: number }, []>(
142
+ "SELECT MAX(version) as version FROM schema_version"
143
+ )
144
+ .get()
145
+ return result?.version ?? 0
146
+ }
147
+
148
+ const runMigrations = (): void => {
149
+ const version = getSchemaVersion()
150
+ const database = getDatabase()
151
+
152
+ // Migration to version 2: Add ai_session_data column
153
+ if (version < 2) {
154
+ // Check if column exists (for databases created with new schema)
155
+ const columns = database
156
+ .query<{ name: string }, []>("PRAGMA table_info(sessions)")
157
+ .all()
158
+ const hasColumn = columns.some((col) => col.name === "ai_session_data")
159
+
160
+ if (!hasColumn) {
161
+ database.exec("ALTER TABLE sessions ADD COLUMN ai_session_data TEXT")
162
+ }
163
+
164
+ // Update version
165
+ database.exec("INSERT OR REPLACE INTO schema_version (version) VALUES (2)")
166
+ }
167
+
168
+ // Add future migrations here:
169
+ // if (version < 3) { migrateTo3() }
170
+ }
171
+
172
+ const withTransaction = <T>(fn: () => T): T => {
173
+ const database = getDatabase()
174
+ return database.transaction(fn)()
175
+ }
176
+
177
+ return {
178
+ getDatabase,
179
+ initDatabase,
180
+ initDatabaseWithPath,
181
+ closeDatabase,
182
+ isDatabaseInitialized,
183
+ getDatabasePath,
184
+ getSchemaVersion,
185
+ runMigrations,
186
+ withTransaction,
187
+ }
188
+ }
189
+
190
+ // ============================================================================
191
+ // Active Database Manager
192
+ // ============================================================================
193
+
194
+ let activeManager = createDatabaseManager()
195
+
196
+ export function setDatabaseManager(manager: DatabaseManager): void {
197
+ activeManager = manager
198
+ }
199
+
200
+ // ============================================================================
201
+ // Default Manager Exports
202
+ // ============================================================================
203
+
204
+ export function getDatabase(): Database {
205
+ return activeManager.getDatabase()
206
+ }
207
+
208
+ export async function initDatabase(projectRoot: string): Promise<void> {
209
+ return activeManager.initDatabase(projectRoot)
210
+ }
211
+
212
+ export async function initDatabaseWithPath(dbPath: string): Promise<void> {
213
+ return activeManager.initDatabaseWithPath(dbPath)
214
+ }
215
+
216
+ export function closeDatabase(): void {
217
+ return activeManager.closeDatabase()
218
+ }
219
+
220
+ export function isDatabaseInitialized(): boolean {
221
+ return activeManager.isDatabaseInitialized()
222
+ }
223
+
224
+ export function getDatabasePath(): string | null {
225
+ return activeManager.getDatabasePath()
226
+ }
227
+
228
+ export function getSchemaVersion(): number {
229
+ return activeManager.getSchemaVersion()
230
+ }
231
+
232
+ export function runMigrations(): void {
233
+ return activeManager.runMigrations()
234
+ }
235
+
236
+ export function withTransaction<T>(fn: () => T): T {
237
+ return activeManager.withTransaction(fn)
238
+ }
239
+
240
+ // ============================================================================
241
+ // Utility Functions
242
+ // ============================================================================
243
+
244
+ /**
245
+ * Generate an ISO timestamp for the current time
246
+ */
247
+ export function nowISO(): string {
248
+ return new Date().toISOString()
249
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Store module - SQLite database layer for OpenSWE
3
+ *
4
+ * Provides persistent storage for sessions, human tasks, project state,
5
+ * and output buffers using Bun's native bun:sqlite.
6
+ */
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ export type {
13
+ Phase,
14
+ Status,
15
+ AISessionData,
16
+ Session,
17
+ CreateSessionInput,
18
+ UpdateSessionInput,
19
+ ProjectState,
20
+ CreateProjectInput,
21
+ OutputBuffer,
22
+ } from "./types"
23
+
24
+ export {
25
+ isValidPhase,
26
+ isValidStatus,
27
+ isValidAISessionData,
28
+ } from "./types"
29
+
30
+ // ============================================================================
31
+ // Database Connection
32
+ // ============================================================================
33
+
34
+ export {
35
+ createDatabaseManager,
36
+ setDatabaseManager,
37
+ getDatabase,
38
+ initDatabase,
39
+ initDatabaseWithPath,
40
+ closeDatabase,
41
+ isDatabaseInitialized,
42
+ getDatabasePath,
43
+ getSchemaVersion,
44
+ runMigrations,
45
+ withTransaction,
46
+ nowISO,
47
+ } from "./db"
48
+
49
+ // ============================================================================
50
+ // Project Operations
51
+ // ============================================================================
52
+
53
+ export {
54
+ getProject,
55
+ createProject,
56
+ updateLastOpened,
57
+ projectExists,
58
+ deleteProject,
59
+ } from "./project"
60
+
61
+ // ============================================================================
62
+ // Session Operations
63
+ // ============================================================================
64
+
65
+ export {
66
+ getAllSessions,
67
+ getSession,
68
+ getSessionsByStatus,
69
+ getSessionsByPhase,
70
+ getActiveSessionCount,
71
+ getSessionCount,
72
+ createSession,
73
+ updateSession,
74
+ updateSessionPhase,
75
+ updateSessionStatus,
76
+ incrementRetryCount,
77
+ updateTokensUsed,
78
+ setPid,
79
+ setAISessionData,
80
+ getAISessionData,
81
+ deleteSession,
82
+ deleteAllSessions,
83
+ } from "./sessions"
84
+
85
+ // ============================================================================
86
+ // Buffer Operations
87
+ // ============================================================================
88
+
89
+ export {
90
+ MAX_BUFFER_LINES,
91
+ getBuffer,
92
+ getRecentLines,
93
+ getAllLines,
94
+ createBuffer,
95
+ ensureBuffer,
96
+ appendLines,
97
+ appendLine,
98
+ setLines,
99
+ clearBuffer,
100
+ deleteBuffer,
101
+ } from "./buffers"
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Project state database operations
3
+ *
4
+ * Manages the project singleton in the database.
5
+ * This mirrors essential fields from ProjectConfig for querying.
6
+ */
7
+
8
+ import { getDatabase, nowISO } from "./db"
9
+ import type { ProjectState, CreateProjectInput } from "./types"
10
+
11
+ // ============================================================================
12
+ // Database Row Type
13
+ // ============================================================================
14
+
15
+ interface ProjectRow {
16
+ id: number
17
+ repo_full_name: string
18
+ repo_url: string
19
+ created_at: string
20
+ last_opened_at: string
21
+ }
22
+
23
+ // ============================================================================
24
+ // Row Mapping
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Convert database row to ProjectState
29
+ */
30
+ function rowToProject(row: ProjectRow): ProjectState {
31
+ return {
32
+ id: 1,
33
+ repoFullName: row.repo_full_name,
34
+ repoUrl: row.repo_url,
35
+ createdAt: row.created_at,
36
+ lastOpenedAt: row.last_opened_at,
37
+ }
38
+ }
39
+
40
+ // ============================================================================
41
+ // CRUD Operations
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Get the project state from the database
46
+ *
47
+ * @returns ProjectState or null if not created
48
+ */
49
+ export function getProject(): ProjectState | null {
50
+ const db = getDatabase()
51
+ const row = db
52
+ .query<ProjectRow, []>("SELECT * FROM project WHERE id = 1")
53
+ .get()
54
+
55
+ return row ? rowToProject(row) : null
56
+ }
57
+
58
+ /**
59
+ * Create the project state (singleton)
60
+ *
61
+ * @param data - Project creation data
62
+ * @returns The created ProjectState
63
+ * @throws Error if project already exists
64
+ */
65
+ export function createProject(data: CreateProjectInput): ProjectState {
66
+ const db = getDatabase()
67
+ const now = nowISO()
68
+
69
+ // Check if project already exists
70
+ const existing = getProject()
71
+ if (existing) {
72
+ throw new Error("Project already exists. Use updateProject instead.")
73
+ }
74
+
75
+ db.query(
76
+ `INSERT INTO project (id, repo_full_name, repo_url, created_at, last_opened_at)
77
+ VALUES (1, ?, ?, ?, ?)`
78
+ ).run(
79
+ data.repoFullName,
80
+ data.repoUrl,
81
+ now,
82
+ now
83
+ )
84
+
85
+ return {
86
+ id: 1,
87
+ repoFullName: data.repoFullName,
88
+ repoUrl: data.repoUrl,
89
+ createdAt: now,
90
+ lastOpenedAt: now,
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Update the lastOpenedAt timestamp
96
+ */
97
+ export function updateLastOpened(): void {
98
+ const db = getDatabase()
99
+ const now = nowISO()
100
+
101
+ db.query("UPDATE project SET last_opened_at = ? WHERE id = 1").run(now)
102
+ }
103
+
104
+ /**
105
+ * Check if project exists in the database
106
+ */
107
+ export function projectExists(): boolean {
108
+ return getProject() !== null
109
+ }
110
+
111
+ /**
112
+ * Delete the project state
113
+ *
114
+ * Warning: This also cascades to delete all sessions and tasks.
115
+ */
116
+ export function deleteProject(): void {
117
+ const db = getDatabase()
118
+ db.query("DELETE FROM project WHERE id = 1").run()
119
+ }
@@ -0,0 +1,71 @@
1
+ -- OpenSWE Database Schema
2
+ -- SQLite database for persistent storage of sessions, tasks, and project state
3
+
4
+ -- ============================================================================
5
+ -- Schema Version
6
+ -- ============================================================================
7
+
8
+ -- Track schema version for migrations
9
+ CREATE TABLE IF NOT EXISTS schema_version (
10
+ version INTEGER PRIMARY KEY,
11
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
12
+ );
13
+
14
+ -- Insert initial version if not exists
15
+ INSERT OR IGNORE INTO schema_version (version) VALUES (1);
16
+
17
+ -- ============================================================================
18
+ -- Project Table (Singleton)
19
+ -- ============================================================================
20
+
21
+ -- Project metadata - only one row (id = 1)
22
+ CREATE TABLE IF NOT EXISTS project (
23
+ id INTEGER PRIMARY KEY CHECK (id = 1),
24
+ repo_full_name TEXT NOT NULL,
25
+ repo_url TEXT NOT NULL,
26
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
27
+ last_opened_at TEXT NOT NULL DEFAULT (datetime('now'))
28
+ );
29
+
30
+ -- ============================================================================
31
+ -- Sessions Table
32
+ -- ============================================================================
33
+
34
+ -- Sessions represent work on specific issues or tasks
35
+ CREATE TABLE IF NOT EXISTS sessions (
36
+ id TEXT PRIMARY KEY,
37
+ name TEXT NOT NULL,
38
+ issue_number INTEGER,
39
+ issue_title TEXT,
40
+ issue_body TEXT,
41
+ issue_url TEXT,
42
+ worktree_path TEXT NOT NULL,
43
+ branch_name TEXT NOT NULL,
44
+ phase TEXT NOT NULL DEFAULT 'pending',
45
+ status TEXT NOT NULL DEFAULT 'queued',
46
+ attention_reason TEXT,
47
+ retry_count INTEGER NOT NULL DEFAULT 0,
48
+ tokens_used INTEGER NOT NULL DEFAULT 0,
49
+ pid INTEGER,
50
+ ai_session_data TEXT, -- JSON: {backend, sessionId, ...}
51
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
52
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
53
+ );
54
+
55
+ -- Index for querying sessions by status
56
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
57
+
58
+ -- Index for querying sessions by phase
59
+ CREATE INDEX IF NOT EXISTS idx_sessions_phase ON sessions(phase);
60
+
61
+ -- ============================================================================
62
+ -- Output Buffers Table
63
+ -- ============================================================================
64
+
65
+ -- Circular buffers for session output (stored as JSON array)
66
+ CREATE TABLE IF NOT EXISTS output_buffers (
67
+ session_id TEXT PRIMARY KEY,
68
+ lines TEXT NOT NULL DEFAULT '[]',
69
+ last_updated TEXT NOT NULL DEFAULT (datetime('now')),
70
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
71
+ );