@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,171 @@
1
+ /**
2
+ * Main configuration module for OpenSWE
3
+ *
4
+ * This module orchestrates loading configuration from multiple sources
5
+ * with the following precedence (highest to lowest):
6
+ * 1. CLI flags
7
+ * 2. Environment variables (OPENSWE_*)
8
+ * 3. Global config file (~/.config/openswe/config.toml)
9
+ * 4. Built-in defaults
10
+ */
11
+
12
+ // Re-export all types
13
+ export * from "./types"
14
+ export type {
15
+ GlobalConfig,
16
+ PartialConfig,
17
+ CLIOverrides,
18
+ AIBackend,
19
+ LogLevel,
20
+ AIConfig,
21
+ KeybindingsConfig,
22
+ AdvancedConfig,
23
+ } from "./types"
24
+
25
+ // Re-export defaults
26
+ export { DEFAULT_CONFIG } from "./defaults"
27
+
28
+ // Re-export global config utilities
29
+ export {
30
+ getConfigDir,
31
+ getConfigPath,
32
+ configFileExists,
33
+ saveGlobalConfig,
34
+ } from "./global"
35
+
36
+ import type { GlobalConfig, PartialConfig, CLIOverrides, DeepPartial } from "./types"
37
+ import { DEFAULT_CONFIG } from "./defaults"
38
+ import { loadEnvConfig } from "./env"
39
+ import { loadGlobalConfig } from "./global"
40
+
41
+ // ============================================================================
42
+ // Deep Merge Utility
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Deep merge two objects, with source values overriding target values
47
+ * Arrays are replaced, not merged
48
+ */
49
+ export function deepMerge<T extends object>(target: T, source: DeepPartial<T>): T {
50
+ const result = structuredClone(target)
51
+
52
+ for (const key of Object.keys(source)) {
53
+ const sourceValue = (source as Record<string, unknown>)[key]
54
+ const targetValue = (result as Record<string, unknown>)[key]
55
+
56
+ if (sourceValue === undefined) {
57
+ continue
58
+ }
59
+
60
+ if (
61
+ sourceValue !== null &&
62
+ typeof sourceValue === "object" &&
63
+ !Array.isArray(sourceValue) &&
64
+ targetValue !== null &&
65
+ typeof targetValue === "object" &&
66
+ !Array.isArray(targetValue)
67
+ ) {
68
+ // Recursively merge nested objects
69
+ ;(result as Record<string, unknown>)[key] = deepMerge(
70
+ targetValue as object,
71
+ sourceValue as DeepPartial<object>
72
+ )
73
+ } else {
74
+ // Replace value (including arrays)
75
+ ;(result as Record<string, unknown>)[key] = sourceValue
76
+ }
77
+ }
78
+
79
+ return result
80
+ }
81
+
82
+ // ============================================================================
83
+ // CLI Overrides
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Apply CLI flag overrides to config
88
+ */
89
+ function applyCLIOverrides(config: GlobalConfig, cli: CLIOverrides): GlobalConfig {
90
+ const result = structuredClone(config)
91
+
92
+ if (cli.backend !== undefined) {
93
+ result.ai.backend = cli.backend
94
+ }
95
+
96
+ if (cli.model !== undefined) {
97
+ const activeBackend = result.ai.backend
98
+ if (activeBackend === "opencode") {
99
+ result.ai.opencode.model = cli.model
100
+ } else if (activeBackend === "claude") {
101
+ result.ai.claude.model = cli.model
102
+ }
103
+ }
104
+
105
+ if (cli.debug === true) {
106
+ result.advanced.logLevel = "debug"
107
+ }
108
+
109
+ return result
110
+ }
111
+
112
+ // ============================================================================
113
+ // Main Load Function
114
+ // ============================================================================
115
+
116
+ /**
117
+ * Load and merge configuration from all sources
118
+ *
119
+ * Precedence (highest to lowest):
120
+ * 1. CLI overrides
121
+ * 2. Environment variables
122
+ * 3. Global config file
123
+ * 4. Built-in defaults
124
+ *
125
+ * @param cliOverrides - Optional CLI flag overrides
126
+ * @returns Fully resolved configuration
127
+ */
128
+ export async function loadConfig(cliOverrides?: CLIOverrides): Promise<GlobalConfig> {
129
+ // 1. Start with defaults
130
+ let config: GlobalConfig = structuredClone(DEFAULT_CONFIG)
131
+
132
+ // 2. Merge global config file (if exists)
133
+ const fileConfig = await loadGlobalConfig()
134
+ config = deepMerge(config, fileConfig)
135
+
136
+ // 3. Merge environment variables
137
+ const envConfig = loadEnvConfig()
138
+ config = deepMerge(config, envConfig)
139
+
140
+ // 4. Apply CLI overrides (highest priority)
141
+ if (cliOverrides) {
142
+ config = applyCLIOverrides(config, cliOverrides)
143
+ }
144
+
145
+ return config
146
+ }
147
+
148
+ /**
149
+ * Load configuration synchronously (for use in non-async contexts)
150
+ * Note: This skips loading the config file since file I/O is async
151
+ *
152
+ * @param cliOverrides - Optional CLI flag overrides
153
+ * @returns Configuration with defaults + env + CLI (no file)
154
+ */
155
+ export function loadConfigSync(cliOverrides?: CLIOverrides): GlobalConfig {
156
+ // 1. Start with defaults
157
+ let config: GlobalConfig = structuredClone(DEFAULT_CONFIG)
158
+
159
+ // 2. Skip file config (would require async)
160
+
161
+ // 3. Merge environment variables
162
+ const envConfig = loadEnvConfig()
163
+ config = deepMerge(config, envConfig)
164
+
165
+ // 4. Apply CLI overrides
166
+ if (cliOverrides) {
167
+ config = applyCLIOverrides(config, cliOverrides)
168
+ }
169
+
170
+ return config
171
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Configuration type definitions for OpenSWE
3
+ */
4
+
5
+ // ============================================================================
6
+ // Type Literals
7
+ // ============================================================================
8
+
9
+ /** Supported AI backends */
10
+ export type AIBackend = "opencode" | "claude"
11
+
12
+ /** Log levels in order of verbosity */
13
+ export type LogLevel = "debug" | "info" | "warn" | "error"
14
+
15
+ // ============================================================================
16
+ // Sub-Configuration Interfaces
17
+ // ============================================================================
18
+
19
+ /** OpenCode-specific AI configuration */
20
+ export interface OpenCodeConfig {
21
+ model: string
22
+ provider: string
23
+ }
24
+
25
+ /** Claude-specific AI configuration */
26
+ export interface ClaudeConfig {
27
+ model: string
28
+ }
29
+
30
+ /** AI backend configuration */
31
+ export interface AIConfig {
32
+ backend: AIBackend
33
+ opencode: OpenCodeConfig
34
+ claude: ClaudeConfig
35
+ }
36
+
37
+ /** Keyboard shortcut configuration */
38
+ export interface KeybindingsConfig {
39
+ navigateUp: string
40
+ navigateDown: string
41
+ select: string
42
+ newSession: string
43
+ deleteSession: string
44
+ pauseSession: string
45
+ taskQueue: string
46
+ issues: string
47
+ quit: string
48
+ help: string
49
+ }
50
+
51
+ /** Advanced/debug configuration */
52
+ export interface AdvancedConfig {
53
+ logLevel: LogLevel
54
+ }
55
+
56
+ /** UI/Theme configuration */
57
+ export interface UIConfig {
58
+ /** Theme name (bundled or custom) */
59
+ theme: string
60
+ /** Theme mode (dark or light) */
61
+ themeMode: "dark" | "light"
62
+ }
63
+
64
+ // ============================================================================
65
+ // Full Configuration Interface
66
+ // ============================================================================
67
+
68
+ /** Complete global configuration */
69
+ export interface GlobalConfig {
70
+ ai: AIConfig
71
+ keybindings: KeybindingsConfig
72
+ advanced: AdvancedConfig
73
+ ui: UIConfig
74
+ }
75
+
76
+ // ============================================================================
77
+ // Utility Types
78
+ // ============================================================================
79
+
80
+ /** Deep partial type - makes all nested properties optional */
81
+ export type DeepPartial<T> = T extends object
82
+ ? { [P in keyof T]?: DeepPartial<T[P]> }
83
+ : T
84
+
85
+ /** Partial configuration for merging sources */
86
+ export type PartialConfig = DeepPartial<GlobalConfig>
87
+
88
+ /** CLI flags that can override configuration */
89
+ export interface CLIOverrides {
90
+ backend?: AIBackend
91
+ model?: string
92
+ debug?: boolean
93
+ }
94
+
95
+ // ============================================================================
96
+ // Type Guards
97
+ // ============================================================================
98
+
99
+ /** Valid AI backend values */
100
+ const VALID_BACKENDS: readonly AIBackend[] = ["opencode", "claude"]
101
+
102
+ /** Valid log level values */
103
+ const VALID_LOG_LEVELS: readonly LogLevel[] = ["debug", "info", "warn", "error"]
104
+
105
+ /**
106
+ * Check if a value is a valid AI backend
107
+ */
108
+ export function isValidBackend(val: unknown): val is AIBackend {
109
+ return typeof val === "string" && VALID_BACKENDS.includes(val as AIBackend)
110
+ }
111
+
112
+ /**
113
+ * Check if a value is a valid log level
114
+ */
115
+ export function isValidLogLevel(val: unknown): val is LogLevel {
116
+ return typeof val === "string" && VALID_LOG_LEVELS.includes(val as LogLevel)
117
+ }
118
+
119
+ /**
120
+ * Check if a value is a boolean
121
+ */
122
+ export function isBoolean(val: unknown): val is boolean {
123
+ return typeof val === "boolean"
124
+ }
125
+
126
+ /**
127
+ * Check if a value is a non-empty string
128
+ */
129
+ export function isNonEmptyString(val: unknown): val is string {
130
+ return typeof val === "string" && val.length > 0
131
+ }
File without changes
@@ -0,0 +1,5 @@
1
+ export { parseOutputLine, createParser } from "./parser"
2
+ export { SessionManager } from "./session"
3
+ export { TmuxManager } from "./tmux"
4
+ export type { ProcessManager, ProcessSnapshot } from "./process-manager"
5
+ export type { ParsedEvent, ParsedEventType } from "./parser"
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Output parser for AI session markers
3
+ *
4
+ * Detects OpenSWE markers with support for provider-specific patterns.
5
+ */
6
+
7
+ import type { ParserPatterns } from "../providers/types"
8
+
9
+ export type ParsedEventType =
10
+ | "phase"
11
+ | "done"
12
+ | "working"
13
+ | "raw"
14
+
15
+ export interface ParsedEvent {
16
+ type: ParsedEventType
17
+ payload?: string
18
+ line: string
19
+ }
20
+
21
+ // ============================================================================
22
+ // Default Patterns (OpenCode compatible)
23
+ // ============================================================================
24
+
25
+ const DEFAULT_PATTERNS: ParserPatterns = {
26
+ workingRegex: /(?:starting|begin|entering).+(?:implementation|coding|execution)|(?:mode|status):\s*(?:implement|coding)/i,
27
+ doneRegex: /\[?OPENSWE:DONE\]?/i,
28
+ }
29
+
30
+ // ============================================================================
31
+ // Parser Factory
32
+ // ============================================================================
33
+
34
+ /**
35
+ * Create a parser function with custom patterns
36
+ * @param patterns - Provider-specific parser patterns
37
+ * @returns Parser function for output lines
38
+ */
39
+ export function createParser(patterns: ParserPatterns): (line: string) => ParsedEvent | null {
40
+ return function parseOutputLine(line: string): ParsedEvent | null {
41
+ // Implicitly detect working phase via keywords
42
+ if (patterns.workingRegex.test(line)) {
43
+ return { type: "working", line }
44
+ }
45
+
46
+ if (patterns.doneRegex.test(line)) {
47
+ return { type: "done", line }
48
+ }
49
+
50
+ return null
51
+ }
52
+ }
53
+
54
+ // ============================================================================
55
+ // Default Parser (backward compatible)
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Parse a single output line for known markers
60
+ * Uses default patterns for backward compatibility
61
+ */
62
+ export const parseOutputLine = createParser(DEFAULT_PATTERNS)
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Process Manager Interface
3
+ *
4
+ * Abstraction for managing background processes (sessions).
5
+ */
6
+
7
+ export interface ProcessSnapshot {
8
+ lines: string[]
9
+ cursor: { x: number, y: number } | null
10
+ }
11
+
12
+ export interface ProcessManager {
13
+ /**
14
+ * Spawn a new persistent session
15
+ */
16
+ spawn(id: string, command: string, args: string[], cwd: string, env: Record<string, string>, logPath: string): Promise<number>
17
+
18
+ /**
19
+ * Check if a session is running
20
+ */
21
+ isRunning(id: string): Promise<boolean>
22
+
23
+ /**
24
+ * Kill a session
25
+ */
26
+ kill(id: string): Promise<void>
27
+
28
+ /**
29
+ * Get the current visual state of the session
30
+ */
31
+ getSnapshot(id: string): Promise<ProcessSnapshot>
32
+
33
+ /**
34
+ * Send input to the session
35
+ */
36
+ sendInput(id: string, data: string): Promise<void>
37
+
38
+ /**
39
+ * Get list of active session IDs managed by this backend
40
+ */
41
+ listActiveSessions(): Promise<string[]>
42
+
43
+ /**
44
+ * Get the command to attach to this session interactively
45
+ */
46
+ getAttachCommand(id: string): string[]
47
+
48
+ /**
49
+ * Resize the session terminal
50
+ */
51
+ resize(id: string, cols: number, rows: number): Promise<void>
52
+ }