@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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme context for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive theme access throughout the TUI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createContext,
|
|
9
|
+
useContext,
|
|
10
|
+
createSignal,
|
|
11
|
+
onMount,
|
|
12
|
+
createMemo,
|
|
13
|
+
type JSX,
|
|
14
|
+
type Accessor,
|
|
15
|
+
} from "solid-js"
|
|
16
|
+
import type { ResolvedTheme, ThemeJson, ThemeMode } from "./types"
|
|
17
|
+
import { BUNDLED_THEMES, DEFAULT_THEME, resolveTheme } from "./loader"
|
|
18
|
+
import { loadCustomThemes } from "./custom"
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Theme Context Type
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
interface ThemeContextValue {
|
|
25
|
+
/** Current resolved theme colors */
|
|
26
|
+
theme: Accessor<ResolvedTheme>
|
|
27
|
+
/** Current theme name */
|
|
28
|
+
themeName: Accessor<string>
|
|
29
|
+
/** Current theme mode (dark/light) */
|
|
30
|
+
themeMode: Accessor<ThemeMode>
|
|
31
|
+
/** Set the active theme by name */
|
|
32
|
+
setTheme: (name: string) => void
|
|
33
|
+
/** Set the theme mode */
|
|
34
|
+
setThemeMode: (mode: ThemeMode) => void
|
|
35
|
+
/** List of all available theme names */
|
|
36
|
+
availableThemes: Accessor<string[]>
|
|
37
|
+
/** Check if a theme is a custom (user-defined) theme */
|
|
38
|
+
isCustomTheme: (name: string) => boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Context Creation
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
const ThemeContext = createContext<ThemeContextValue>()
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Theme Provider
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
interface ThemeProviderProps {
|
|
52
|
+
children: JSX.Element
|
|
53
|
+
/** Initial theme name (default: tokyonight) */
|
|
54
|
+
initialTheme?: string
|
|
55
|
+
/** Initial theme mode (default: dark) */
|
|
56
|
+
initialMode?: ThemeMode
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Theme provider component
|
|
61
|
+
*
|
|
62
|
+
* Wraps the application and provides reactive theme access
|
|
63
|
+
*/
|
|
64
|
+
export function ThemeProvider(props: ThemeProviderProps): JSX.Element {
|
|
65
|
+
const [themeName, setThemeName] = createSignal(
|
|
66
|
+
props.initialTheme ?? DEFAULT_THEME,
|
|
67
|
+
)
|
|
68
|
+
const [themeMode, setThemeMode] = createSignal<ThemeMode>(
|
|
69
|
+
props.initialMode ?? "dark",
|
|
70
|
+
)
|
|
71
|
+
const [customThemes, setCustomThemes] = createSignal<
|
|
72
|
+
Record<string, ThemeJson>
|
|
73
|
+
>({})
|
|
74
|
+
|
|
75
|
+
// Load custom themes on mount
|
|
76
|
+
onMount(async () => {
|
|
77
|
+
const themes = await loadCustomThemes()
|
|
78
|
+
setCustomThemes(themes)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Combined themes (bundled + custom)
|
|
82
|
+
const allThemes = createMemo(() => ({
|
|
83
|
+
...BUNDLED_THEMES,
|
|
84
|
+
...customThemes(),
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
// Available theme names (sorted)
|
|
88
|
+
const availableThemes = createMemo(() =>
|
|
89
|
+
Object.keys(allThemes()).sort(),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// Check if a theme is custom
|
|
93
|
+
const isCustomTheme = (name: string): boolean => {
|
|
94
|
+
return name in customThemes()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Resolved theme (reactive)
|
|
98
|
+
const theme = createMemo(() => {
|
|
99
|
+
const name = themeName()
|
|
100
|
+
const mode = themeMode()
|
|
101
|
+
const themes = allThemes()
|
|
102
|
+
|
|
103
|
+
// Try to get the requested theme, fall back to default
|
|
104
|
+
// DEFAULT_THEME is always present in BUNDLED_THEMES
|
|
105
|
+
const themeJson = themes[name] ?? BUNDLED_THEMES[DEFAULT_THEME]!
|
|
106
|
+
return resolveTheme(themeJson, mode)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Set theme with validation
|
|
110
|
+
const setTheme = (name: string): void => {
|
|
111
|
+
const themes = allThemes()
|
|
112
|
+
if (name in themes) {
|
|
113
|
+
setThemeName(name)
|
|
114
|
+
} else {
|
|
115
|
+
// Fall back to default if theme doesn't exist
|
|
116
|
+
setThemeName(DEFAULT_THEME)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contextValue: ThemeContextValue = {
|
|
121
|
+
theme,
|
|
122
|
+
themeName,
|
|
123
|
+
themeMode,
|
|
124
|
+
setTheme,
|
|
125
|
+
setThemeMode,
|
|
126
|
+
availableThemes,
|
|
127
|
+
isCustomTheme,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<ThemeContext.Provider value={contextValue}>
|
|
132
|
+
{props.children}
|
|
133
|
+
</ThemeContext.Provider>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Hook
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Use the theme context
|
|
143
|
+
*
|
|
144
|
+
* Must be used within a ThemeProvider
|
|
145
|
+
*
|
|
146
|
+
* @returns Theme context value
|
|
147
|
+
* @throws Error if used outside ThemeProvider
|
|
148
|
+
*/
|
|
149
|
+
export function useTheme(): ThemeContextValue {
|
|
150
|
+
const context = useContext(ThemeContext)
|
|
151
|
+
if (!context) {
|
|
152
|
+
throw new Error("useTheme must be used within a ThemeProvider")
|
|
153
|
+
}
|
|
154
|
+
return context
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Static Theme Access
|
|
159
|
+
// ============================================================================
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get the default theme (for use outside of reactive context)
|
|
163
|
+
*
|
|
164
|
+
* This is useful for components that need theme colors but can't use
|
|
165
|
+
* the reactive context (e.g., during initialization)
|
|
166
|
+
*/
|
|
167
|
+
export function getDefaultTheme(): ResolvedTheme {
|
|
168
|
+
// DEFAULT_THEME is always present in BUNDLED_THEMES
|
|
169
|
+
return resolveTheme(BUNDLED_THEMES[DEFAULT_THEME]!, "dark")
|
|
170
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom theme loader for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Loads user-defined themes from ~/.config/openswe/themes/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from "os"
|
|
8
|
+
import path from "path"
|
|
9
|
+
import { Glob } from "bun"
|
|
10
|
+
import type { ThemeJson } from "./types"
|
|
11
|
+
import { logger } from "@/utils/logger"
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Custom Theme Directory
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/** Get the custom themes directory path */
|
|
18
|
+
export function getCustomThemesDir(): string {
|
|
19
|
+
return path.join(homedir(), ".config", "openswe", "themes")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Custom Theme Loader
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load all custom themes from the user's config directory
|
|
28
|
+
*
|
|
29
|
+
* Themes are loaded from ~/.config/openswe/themes/*.json
|
|
30
|
+
* Invalid themes are skipped with a warning
|
|
31
|
+
*
|
|
32
|
+
* @returns Record of theme name to theme JSON
|
|
33
|
+
*/
|
|
34
|
+
export async function loadCustomThemes(): Promise<Record<string, ThemeJson>> {
|
|
35
|
+
const themesDir = getCustomThemesDir()
|
|
36
|
+
const result: Record<string, ThemeJson> = {}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Check if themes directory exists
|
|
40
|
+
const dirFile = Bun.file(themesDir)
|
|
41
|
+
const dirExists = await dirFile.exists()
|
|
42
|
+
|
|
43
|
+
if (!dirExists) {
|
|
44
|
+
// Directory doesn't exist - that's fine, no custom themes
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Scan for JSON files
|
|
49
|
+
const glob = new Glob("*.json")
|
|
50
|
+
|
|
51
|
+
for await (const file of glob.scan({ cwd: themesDir, absolute: false })) {
|
|
52
|
+
const name = path.basename(file, ".json")
|
|
53
|
+
const filePath = path.join(themesDir, file)
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const content = await Bun.file(filePath).json()
|
|
57
|
+
|
|
58
|
+
// Basic validation - must have a theme object
|
|
59
|
+
if (!content.theme || typeof content.theme !== "object") {
|
|
60
|
+
logger.warn(`Invalid custom theme ${name}: missing 'theme' object`)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate required colors exist
|
|
65
|
+
const requiredColors = [
|
|
66
|
+
"primary",
|
|
67
|
+
"secondary",
|
|
68
|
+
"accent",
|
|
69
|
+
"error",
|
|
70
|
+
"warning",
|
|
71
|
+
"success",
|
|
72
|
+
"info",
|
|
73
|
+
"text",
|
|
74
|
+
"textMuted",
|
|
75
|
+
"background",
|
|
76
|
+
"backgroundPanel",
|
|
77
|
+
"backgroundElement",
|
|
78
|
+
"border",
|
|
79
|
+
"borderActive",
|
|
80
|
+
"borderSubtle",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
const missingColors = requiredColors.filter(
|
|
84
|
+
(c) => !(c in content.theme),
|
|
85
|
+
)
|
|
86
|
+
if (missingColors.length > 0) {
|
|
87
|
+
logger.warn(
|
|
88
|
+
`Invalid custom theme ${name}: missing colors: ${missingColors.join(", ")}`,
|
|
89
|
+
)
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
result[name] = content as ThemeJson
|
|
94
|
+
logger.debug(`Loaded custom theme: ${name}`)
|
|
95
|
+
} catch (error) {
|
|
96
|
+
logger.warn(`Failed to load custom theme ${name}:`, error)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// If we can't read the directory at all, just return empty
|
|
101
|
+
logger.debug("Could not read custom themes directory:", error)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load a single custom theme by name
|
|
109
|
+
*
|
|
110
|
+
* @param name - Theme name (without .json extension)
|
|
111
|
+
* @returns Theme JSON if found, undefined otherwise
|
|
112
|
+
*/
|
|
113
|
+
export async function loadCustomTheme(
|
|
114
|
+
name: string,
|
|
115
|
+
): Promise<ThemeJson | undefined> {
|
|
116
|
+
const themesDir = getCustomThemesDir()
|
|
117
|
+
const filePath = path.join(themesDir, `${name}.json`)
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const file = Bun.file(filePath)
|
|
121
|
+
if (!(await file.exists())) {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const content = await file.json()
|
|
126
|
+
if (!content.theme || typeof content.theme !== "object") {
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return content as ThemeJson
|
|
131
|
+
} catch {
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme module for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Provides a complete theming system with 33+ bundled themes
|
|
5
|
+
* and support for custom user themes.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // In app root
|
|
10
|
+
* import { ThemeProvider } from "@/theme"
|
|
11
|
+
*
|
|
12
|
+
* <ThemeProvider initialTheme="tokyonight">
|
|
13
|
+
* <App />
|
|
14
|
+
* </ThemeProvider>
|
|
15
|
+
*
|
|
16
|
+
* // In components
|
|
17
|
+
* import { useTheme } from "@/theme"
|
|
18
|
+
*
|
|
19
|
+
* const { theme, themeName, setTheme } = useTheme()
|
|
20
|
+
* console.log(theme().primary) // "#7aa2f7"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Types
|
|
25
|
+
export type {
|
|
26
|
+
ThemeJson,
|
|
27
|
+
ResolvedTheme,
|
|
28
|
+
ThemeMode,
|
|
29
|
+
BundledThemeName,
|
|
30
|
+
ColorValue,
|
|
31
|
+
ColorVariant,
|
|
32
|
+
HexColor,
|
|
33
|
+
RefName,
|
|
34
|
+
} from "./types"
|
|
35
|
+
export { isHexColor, isColorVariant } from "./types"
|
|
36
|
+
|
|
37
|
+
// Loader
|
|
38
|
+
export {
|
|
39
|
+
BUNDLED_THEMES,
|
|
40
|
+
DEFAULT_THEME,
|
|
41
|
+
getBundledThemeNames,
|
|
42
|
+
resolveTheme,
|
|
43
|
+
getTheme,
|
|
44
|
+
} from "./loader"
|
|
45
|
+
|
|
46
|
+
// Custom themes
|
|
47
|
+
export {
|
|
48
|
+
getCustomThemesDir,
|
|
49
|
+
loadCustomThemes,
|
|
50
|
+
loadCustomTheme,
|
|
51
|
+
} from "./custom"
|
|
52
|
+
|
|
53
|
+
// Context
|
|
54
|
+
export {
|
|
55
|
+
ThemeProvider,
|
|
56
|
+
useTheme,
|
|
57
|
+
getDefaultTheme,
|
|
58
|
+
} from "./context"
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme loader for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Loads bundled themes and resolves color references
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ThemeJson,
|
|
9
|
+
ResolvedTheme,
|
|
10
|
+
ColorValue,
|
|
11
|
+
ThemeMode,
|
|
12
|
+
HexColor,
|
|
13
|
+
RefName,
|
|
14
|
+
} from "./types"
|
|
15
|
+
import { isHexColor, isColorVariant } from "./types"
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Bundled Theme Imports
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
import aura from "./themes/aura.json"
|
|
22
|
+
import ayu from "./themes/ayu.json"
|
|
23
|
+
import carbonfox from "./themes/carbonfox.json"
|
|
24
|
+
import catppuccin from "./themes/catppuccin.json"
|
|
25
|
+
import catppuccinFrappe from "./themes/catppuccin-frappe.json"
|
|
26
|
+
import catppuccinMacchiato from "./themes/catppuccin-macchiato.json"
|
|
27
|
+
import cobalt2 from "./themes/cobalt2.json"
|
|
28
|
+
import cursor from "./themes/cursor.json"
|
|
29
|
+
import dracula from "./themes/dracula.json"
|
|
30
|
+
import everforest from "./themes/everforest.json"
|
|
31
|
+
import flexoki from "./themes/flexoki.json"
|
|
32
|
+
import github from "./themes/github.json"
|
|
33
|
+
import gruvbox from "./themes/gruvbox.json"
|
|
34
|
+
import kanagawa from "./themes/kanagawa.json"
|
|
35
|
+
import lucentOrng from "./themes/lucent-orng.json"
|
|
36
|
+
import material from "./themes/material.json"
|
|
37
|
+
import matrix from "./themes/matrix.json"
|
|
38
|
+
import mercury from "./themes/mercury.json"
|
|
39
|
+
import monokai from "./themes/monokai.json"
|
|
40
|
+
import nightowl from "./themes/nightowl.json"
|
|
41
|
+
import nord from "./themes/nord.json"
|
|
42
|
+
import oneDark from "./themes/one-dark.json"
|
|
43
|
+
import opencode from "./themes/opencode.json"
|
|
44
|
+
import orng from "./themes/orng.json"
|
|
45
|
+
import osakaJade from "./themes/osaka-jade.json"
|
|
46
|
+
import palenight from "./themes/palenight.json"
|
|
47
|
+
import rosepine from "./themes/rosepine.json"
|
|
48
|
+
import solarized from "./themes/solarized.json"
|
|
49
|
+
import synthwave84 from "./themes/synthwave84.json"
|
|
50
|
+
import tokyonight from "./themes/tokyonight.json"
|
|
51
|
+
import vercel from "./themes/vercel.json"
|
|
52
|
+
import vesper from "./themes/vesper.json"
|
|
53
|
+
import zenburn from "./themes/zenburn.json"
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Bundled Themes Registry
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/** All bundled themes, keyed by name */
|
|
60
|
+
export const BUNDLED_THEMES: Record<string, ThemeJson> = {
|
|
61
|
+
aura: aura as ThemeJson,
|
|
62
|
+
ayu: ayu as ThemeJson,
|
|
63
|
+
carbonfox: carbonfox as ThemeJson,
|
|
64
|
+
catppuccin: catppuccin as ThemeJson,
|
|
65
|
+
"catppuccin-frappe": catppuccinFrappe as ThemeJson,
|
|
66
|
+
"catppuccin-macchiato": catppuccinMacchiato as ThemeJson,
|
|
67
|
+
cobalt2: cobalt2 as ThemeJson,
|
|
68
|
+
cursor: cursor as ThemeJson,
|
|
69
|
+
dracula: dracula as ThemeJson,
|
|
70
|
+
everforest: everforest as ThemeJson,
|
|
71
|
+
flexoki: flexoki as ThemeJson,
|
|
72
|
+
github: github as ThemeJson,
|
|
73
|
+
gruvbox: gruvbox as ThemeJson,
|
|
74
|
+
kanagawa: kanagawa as ThemeJson,
|
|
75
|
+
"lucent-orng": lucentOrng as ThemeJson,
|
|
76
|
+
material: material as ThemeJson,
|
|
77
|
+
matrix: matrix as ThemeJson,
|
|
78
|
+
mercury: mercury as ThemeJson,
|
|
79
|
+
monokai: monokai as ThemeJson,
|
|
80
|
+
nightowl: nightowl as ThemeJson,
|
|
81
|
+
nord: nord as ThemeJson,
|
|
82
|
+
"one-dark": oneDark as ThemeJson,
|
|
83
|
+
opencode: opencode as ThemeJson,
|
|
84
|
+
orng: orng as ThemeJson,
|
|
85
|
+
"osaka-jade": osakaJade as ThemeJson,
|
|
86
|
+
palenight: palenight as ThemeJson,
|
|
87
|
+
rosepine: rosepine as ThemeJson,
|
|
88
|
+
solarized: solarized as ThemeJson,
|
|
89
|
+
synthwave84: synthwave84 as ThemeJson,
|
|
90
|
+
tokyonight: tokyonight as ThemeJson,
|
|
91
|
+
vercel: vercel as ThemeJson,
|
|
92
|
+
vesper: vesper as ThemeJson,
|
|
93
|
+
zenburn: zenburn as ThemeJson,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Default theme name */
|
|
97
|
+
export const DEFAULT_THEME = "tokyonight"
|
|
98
|
+
|
|
99
|
+
/** Get list of all bundled theme names */
|
|
100
|
+
export function getBundledThemeNames(): string[] {
|
|
101
|
+
return Object.keys(BUNDLED_THEMES).sort()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Theme Resolution
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a color value to a hex string
|
|
110
|
+
*
|
|
111
|
+
* @param color - The color value to resolve
|
|
112
|
+
* @param defs - Color definitions from the theme
|
|
113
|
+
* @param mode - Dark or light mode
|
|
114
|
+
* @param visited - Track visited refs to detect cycles
|
|
115
|
+
* @returns Resolved hex color string
|
|
116
|
+
*/
|
|
117
|
+
function resolveColor(
|
|
118
|
+
color: ColorValue,
|
|
119
|
+
defs: Record<string, HexColor | RefName>,
|
|
120
|
+
mode: ThemeMode,
|
|
121
|
+
visited: Set<string> = new Set(),
|
|
122
|
+
): string {
|
|
123
|
+
// If it's a variant object, extract the appropriate mode value
|
|
124
|
+
if (isColorVariant(color)) {
|
|
125
|
+
return resolveColor(color[mode], defs, mode, visited)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If it's a hex color, return it directly
|
|
129
|
+
if (isHexColor(color)) {
|
|
130
|
+
return color
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// It's a reference name - look it up in defs
|
|
134
|
+
const refName = color as RefName
|
|
135
|
+
|
|
136
|
+
// Check for circular reference
|
|
137
|
+
if (visited.has(refName)) {
|
|
138
|
+
throw new Error(`Circular color reference detected: ${refName}`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const resolved = defs[refName]
|
|
142
|
+
if (resolved === undefined) {
|
|
143
|
+
throw new Error(`Unknown color reference: ${refName}`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Add to visited set and recurse
|
|
147
|
+
visited.add(refName)
|
|
148
|
+
return resolveColor(resolved, defs, mode, visited)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve a theme JSON to a fully resolved theme object
|
|
153
|
+
*
|
|
154
|
+
* @param theme - The theme JSON to resolve
|
|
155
|
+
* @param mode - Dark or light mode (default: dark)
|
|
156
|
+
* @returns Resolved theme with all hex colors
|
|
157
|
+
*/
|
|
158
|
+
export function resolveTheme(
|
|
159
|
+
theme: ThemeJson,
|
|
160
|
+
mode: ThemeMode = "dark",
|
|
161
|
+
): ResolvedTheme {
|
|
162
|
+
const defs = theme.defs ?? {}
|
|
163
|
+
const t = theme.theme
|
|
164
|
+
|
|
165
|
+
// Helper to resolve with fallback
|
|
166
|
+
const resolve = (color: ColorValue | undefined, fallback: string): string => {
|
|
167
|
+
if (color === undefined) return fallback
|
|
168
|
+
try {
|
|
169
|
+
return resolveColor(color, defs, mode)
|
|
170
|
+
} catch {
|
|
171
|
+
return fallback
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Resolve core colors (required)
|
|
176
|
+
const primary = resolveColor(t.primary, defs, mode)
|
|
177
|
+
const secondary = resolveColor(t.secondary, defs, mode)
|
|
178
|
+
const accent = resolveColor(t.accent, defs, mode)
|
|
179
|
+
const error = resolveColor(t.error, defs, mode)
|
|
180
|
+
const warning = resolveColor(t.warning, defs, mode)
|
|
181
|
+
const success = resolveColor(t.success, defs, mode)
|
|
182
|
+
const info = resolveColor(t.info, defs, mode)
|
|
183
|
+
const text = resolveColor(t.text, defs, mode)
|
|
184
|
+
const textMuted = resolveColor(t.textMuted, defs, mode)
|
|
185
|
+
const background = resolveColor(t.background, defs, mode)
|
|
186
|
+
const backgroundPanel = resolveColor(t.backgroundPanel, defs, mode)
|
|
187
|
+
const backgroundElement = resolveColor(t.backgroundElement, defs, mode)
|
|
188
|
+
const border = resolveColor(t.border, defs, mode)
|
|
189
|
+
const borderActive = resolveColor(t.borderActive, defs, mode)
|
|
190
|
+
const borderSubtle = resolveColor(t.borderSubtle, defs, mode)
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
// Core colors
|
|
194
|
+
primary,
|
|
195
|
+
secondary,
|
|
196
|
+
accent,
|
|
197
|
+
error,
|
|
198
|
+
warning,
|
|
199
|
+
success,
|
|
200
|
+
info,
|
|
201
|
+
text,
|
|
202
|
+
textMuted,
|
|
203
|
+
background,
|
|
204
|
+
backgroundPanel,
|
|
205
|
+
backgroundElement,
|
|
206
|
+
border,
|
|
207
|
+
borderActive,
|
|
208
|
+
borderSubtle,
|
|
209
|
+
|
|
210
|
+
// Diff colors (with sensible defaults)
|
|
211
|
+
diffAdded: resolve(t.diffAdded, success),
|
|
212
|
+
diffRemoved: resolve(t.diffRemoved, error),
|
|
213
|
+
diffContext: resolve(t.diffContext, textMuted),
|
|
214
|
+
diffHunkHeader: resolve(t.diffHunkHeader, secondary),
|
|
215
|
+
diffHighlightAdded: resolve(t.diffHighlightAdded, success),
|
|
216
|
+
diffHighlightRemoved: resolve(t.diffHighlightRemoved, error),
|
|
217
|
+
diffAddedBg: resolve(t.diffAddedBg, backgroundPanel),
|
|
218
|
+
diffRemovedBg: resolve(t.diffRemovedBg, backgroundPanel),
|
|
219
|
+
diffContextBg: resolve(t.diffContextBg, backgroundPanel),
|
|
220
|
+
diffLineNumber: resolve(t.diffLineNumber, borderSubtle),
|
|
221
|
+
diffAddedLineNumberBg: resolve(t.diffAddedLineNumberBg, backgroundPanel),
|
|
222
|
+
diffRemovedLineNumberBg: resolve(t.diffRemovedLineNumberBg, backgroundPanel),
|
|
223
|
+
|
|
224
|
+
// Markdown colors (with sensible defaults)
|
|
225
|
+
markdownText: resolve(t.markdownText, text),
|
|
226
|
+
markdownHeading: resolve(t.markdownHeading, secondary),
|
|
227
|
+
markdownLink: resolve(t.markdownLink, primary),
|
|
228
|
+
markdownLinkText: resolve(t.markdownLinkText, info),
|
|
229
|
+
markdownCode: resolve(t.markdownCode, success),
|
|
230
|
+
markdownBlockQuote: resolve(t.markdownBlockQuote, warning),
|
|
231
|
+
markdownEmph: resolve(t.markdownEmph, warning),
|
|
232
|
+
markdownStrong: resolve(t.markdownStrong, accent),
|
|
233
|
+
markdownHorizontalRule: resolve(t.markdownHorizontalRule, textMuted),
|
|
234
|
+
markdownListItem: resolve(t.markdownListItem, primary),
|
|
235
|
+
markdownListEnumeration: resolve(t.markdownListEnumeration, info),
|
|
236
|
+
markdownImage: resolve(t.markdownImage, primary),
|
|
237
|
+
markdownImageText: resolve(t.markdownImageText, info),
|
|
238
|
+
markdownCodeBlock: resolve(t.markdownCodeBlock, text),
|
|
239
|
+
|
|
240
|
+
// Syntax highlighting (with sensible defaults)
|
|
241
|
+
syntaxComment: resolve(t.syntaxComment, textMuted),
|
|
242
|
+
syntaxKeyword: resolve(t.syntaxKeyword, secondary),
|
|
243
|
+
syntaxFunction: resolve(t.syntaxFunction, primary),
|
|
244
|
+
syntaxVariable: resolve(t.syntaxVariable, error),
|
|
245
|
+
syntaxString: resolve(t.syntaxString, success),
|
|
246
|
+
syntaxNumber: resolve(t.syntaxNumber, accent),
|
|
247
|
+
syntaxType: resolve(t.syntaxType, warning),
|
|
248
|
+
syntaxOperator: resolve(t.syntaxOperator, info),
|
|
249
|
+
syntaxPunctuation: resolve(t.syntaxPunctuation, text),
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get a resolved theme by name
|
|
255
|
+
*
|
|
256
|
+
* @param name - Theme name (must be a bundled theme name)
|
|
257
|
+
* @param mode - Dark or light mode (default: dark)
|
|
258
|
+
* @returns Resolved theme, or default theme if not found
|
|
259
|
+
*/
|
|
260
|
+
export function getTheme(name: string, mode: ThemeMode = "dark"): ResolvedTheme {
|
|
261
|
+
// DEFAULT_THEME is always present in BUNDLED_THEMES
|
|
262
|
+
const theme = BUNDLED_THEMES[name] ?? BUNDLED_THEMES[DEFAULT_THEME]!
|
|
263
|
+
return resolveTheme(theme, mode)
|
|
264
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/theme.json",
|
|
3
|
+
"defs": {
|
|
4
|
+
"darkBg": "#0f0f0f",
|
|
5
|
+
"darkBgPanel": "#15141b",
|
|
6
|
+
"darkBorder": "#2d2d2d",
|
|
7
|
+
"darkFgMuted": "#6d6d6d",
|
|
8
|
+
"darkFg": "#edecee",
|
|
9
|
+
"purple": "#a277ff",
|
|
10
|
+
"pink": "#f694ff",
|
|
11
|
+
"blue": "#82e2ff",
|
|
12
|
+
"red": "#ff6767",
|
|
13
|
+
"orange": "#ffca85",
|
|
14
|
+
"cyan": "#61ffca",
|
|
15
|
+
"green": "#9dff65"
|
|
16
|
+
},
|
|
17
|
+
"theme": {
|
|
18
|
+
"primary": "purple",
|
|
19
|
+
"secondary": "pink",
|
|
20
|
+
"accent": "purple",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "orange",
|
|
23
|
+
"success": "cyan",
|
|
24
|
+
"info": "purple",
|
|
25
|
+
"text": "darkFg",
|
|
26
|
+
"textMuted": "darkFgMuted",
|
|
27
|
+
"background": "darkBg",
|
|
28
|
+
"backgroundPanel": "darkBgPanel",
|
|
29
|
+
"backgroundElement": "darkBgPanel",
|
|
30
|
+
"border": "darkBorder",
|
|
31
|
+
"borderActive": "darkFgMuted",
|
|
32
|
+
"borderSubtle": "darkBorder",
|
|
33
|
+
"diffAdded": "cyan",
|
|
34
|
+
"diffRemoved": "red",
|
|
35
|
+
"diffContext": "darkFgMuted",
|
|
36
|
+
"diffHunkHeader": "darkFgMuted",
|
|
37
|
+
"diffHighlightAdded": "cyan",
|
|
38
|
+
"diffHighlightRemoved": "red",
|
|
39
|
+
"diffAddedBg": "#354933",
|
|
40
|
+
"diffRemovedBg": "#3f191a",
|
|
41
|
+
"diffContextBg": "darkBgPanel",
|
|
42
|
+
"diffLineNumber": "darkBorder",
|
|
43
|
+
"diffAddedLineNumberBg": "#162620",
|
|
44
|
+
"diffRemovedLineNumberBg": "#26161a",
|
|
45
|
+
"markdownText": "darkFg",
|
|
46
|
+
"markdownHeading": "purple",
|
|
47
|
+
"markdownLink": "pink",
|
|
48
|
+
"markdownLinkText": "purple",
|
|
49
|
+
"markdownCode": "cyan",
|
|
50
|
+
"markdownBlockQuote": "darkFgMuted",
|
|
51
|
+
"markdownEmph": "orange",
|
|
52
|
+
"markdownStrong": "purple",
|
|
53
|
+
"markdownHorizontalRule": "darkFgMuted",
|
|
54
|
+
"markdownListItem": "purple",
|
|
55
|
+
"markdownListEnumeration": "purple",
|
|
56
|
+
"markdownImage": "pink",
|
|
57
|
+
"markdownImageText": "purple",
|
|
58
|
+
"markdownCodeBlock": "darkFg",
|
|
59
|
+
"syntaxComment": "darkFgMuted",
|
|
60
|
+
"syntaxKeyword": "pink",
|
|
61
|
+
"syntaxFunction": "purple",
|
|
62
|
+
"syntaxVariable": "purple",
|
|
63
|
+
"syntaxString": "cyan",
|
|
64
|
+
"syntaxNumber": "green",
|
|
65
|
+
"syntaxType": "purple",
|
|
66
|
+
"syntaxOperator": "pink",
|
|
67
|
+
"syntaxPunctuation": "darkFg"
|
|
68
|
+
}
|
|
69
|
+
}
|