@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,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a number of tokens for display (e.g., 2300 -> "2.3k")
|
|
3
|
+
*/
|
|
4
|
+
export function formatTokens(tokens: number): string {
|
|
5
|
+
if (tokens >= 1_000_000) {
|
|
6
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`
|
|
7
|
+
}
|
|
8
|
+
if (tokens >= 1000) {
|
|
9
|
+
return `${(tokens / 1000).toFixed(1)}k`
|
|
10
|
+
}
|
|
11
|
+
return String(tokens)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format a date as relative time (e.g., "3d ago", "2h ago")
|
|
16
|
+
*/
|
|
17
|
+
export function formatRelativeTime(date: Date): string {
|
|
18
|
+
const now = new Date()
|
|
19
|
+
const diffMs = now.getTime() - date.getTime()
|
|
20
|
+
|
|
21
|
+
if (diffMs < 0) {
|
|
22
|
+
return "in the future"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const diffSecs = Math.floor(diffMs / 1000)
|
|
26
|
+
const diffMins = Math.floor(diffSecs / 60)
|
|
27
|
+
const diffHours = Math.floor(diffMins / 60)
|
|
28
|
+
const diffDays = Math.floor(diffHours / 24)
|
|
29
|
+
const diffWeeks = Math.floor(diffDays / 7)
|
|
30
|
+
const diffMonths = Math.floor(diffDays / 30)
|
|
31
|
+
|
|
32
|
+
if (diffMonths > 0) return `${diffMonths}mo ago`
|
|
33
|
+
if (diffWeeks > 0) return `${diffWeeks}w ago`
|
|
34
|
+
if (diffDays > 0) return `${diffDays}d ago`
|
|
35
|
+
if (diffHours > 0) return `${diffHours}h ago`
|
|
36
|
+
if (diffMins > 0) return `${diffMins}m ago`
|
|
37
|
+
return "just now"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Truncate a string to a maximum length, adding ellipsis if truncated
|
|
42
|
+
*/
|
|
43
|
+
export function truncate(str: string, maxLength: number): string {
|
|
44
|
+
if (str.length <= maxLength) return str
|
|
45
|
+
return `${str.slice(0, maxLength - 1)}…`
|
|
46
|
+
}
|
package/src/utils/id.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a unique identifier (UUID v4)
|
|
5
|
+
*/
|
|
6
|
+
export function generateId(): string {
|
|
7
|
+
return uuidv4()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a short ID (first 8 characters of UUID)
|
|
12
|
+
*/
|
|
13
|
+
export function generateShortId(): string {
|
|
14
|
+
return uuidv4().slice(0, 8)
|
|
15
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utility for OpenSWE
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { LogLevel } from "../config/types"
|
|
6
|
+
|
|
7
|
+
// Re-export LogLevel for convenience
|
|
8
|
+
export type { LogLevel }
|
|
9
|
+
|
|
10
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
11
|
+
debug: 0,
|
|
12
|
+
info: 1,
|
|
13
|
+
warn: 2,
|
|
14
|
+
error: 3,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Can be overridden by config/env
|
|
18
|
+
let currentLevel: LogLevel = "info"
|
|
19
|
+
let logFileSink: any = null
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set the current log level
|
|
23
|
+
*/
|
|
24
|
+
export function setLogLevel(level: LogLevel) {
|
|
25
|
+
currentLevel = level
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize file logging
|
|
30
|
+
* @param path - Absolute path to the log file
|
|
31
|
+
*/
|
|
32
|
+
export function initFileLogging(path: string) {
|
|
33
|
+
try {
|
|
34
|
+
const file = Bun.file(path)
|
|
35
|
+
logFileSink = file.writer()
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("Failed to initialize file logging:", error)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the current log level
|
|
43
|
+
*/
|
|
44
|
+
export function getLogLevel(): LogLevel {
|
|
45
|
+
return currentLevel
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shouldLog(level: LogLevel): boolean {
|
|
49
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatTimestamp(): string {
|
|
53
|
+
return new Date().toISOString()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatValue(value: unknown): string {
|
|
57
|
+
if (value === null) return "null"
|
|
58
|
+
if (value === undefined) return "undefined"
|
|
59
|
+
if (typeof value === "string") return value
|
|
60
|
+
if (value instanceof Error) {
|
|
61
|
+
return `${value.name}: ${value.message}${value.stack ? `\n${value.stack}` : ""}`
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(value, null, 2)
|
|
65
|
+
} catch {
|
|
66
|
+
return String(value)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatMessage(level: LogLevel, args: unknown[]): string {
|
|
71
|
+
const timestamp = formatTimestamp()
|
|
72
|
+
const levelStr = level.toUpperCase().padEnd(5)
|
|
73
|
+
const message = args.map(formatValue).join(" ")
|
|
74
|
+
return `[${timestamp}] [${levelStr}] ${message}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function log(level: LogLevel, args: unknown[]) {
|
|
78
|
+
if (shouldLog(level)) {
|
|
79
|
+
const msg = formatMessage(level, args)
|
|
80
|
+
|
|
81
|
+
// Console output
|
|
82
|
+
switch (level) {
|
|
83
|
+
case "debug":
|
|
84
|
+
console.debug(msg)
|
|
85
|
+
break
|
|
86
|
+
case "info":
|
|
87
|
+
console.log(msg)
|
|
88
|
+
break
|
|
89
|
+
case "warn":
|
|
90
|
+
console.warn(msg)
|
|
91
|
+
break
|
|
92
|
+
case "error":
|
|
93
|
+
console.error(msg)
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// File output
|
|
98
|
+
if (logFileSink) {
|
|
99
|
+
logFileSink.write(msg + "\n")
|
|
100
|
+
logFileSink.flush()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const logger = {
|
|
106
|
+
debug: (...args: unknown[]) => log("debug", args),
|
|
107
|
+
info: (...args: unknown[]) => log("info", args),
|
|
108
|
+
warn: (...args: unknown[]) => log("warn", args),
|
|
109
|
+
error: (...args: unknown[]) => log("error", args),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default logger
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prerequisites checker for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Verifies required tools are installed before starting the TUI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { checkGitInstalled } from "../git"
|
|
8
|
+
import { checkGhCli } from "../github"
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/** Result of prerequisite checks */
|
|
15
|
+
export interface PrerequisiteResult {
|
|
16
|
+
/** Whether all prerequisites are met */
|
|
17
|
+
success: boolean
|
|
18
|
+
/** List of error messages for unmet prerequisites */
|
|
19
|
+
errors: string[]
|
|
20
|
+
/** List of warning messages (non-fatal) */
|
|
21
|
+
warnings: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Prerequisite Checks
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check all prerequisites for running OpenSWE
|
|
30
|
+
*
|
|
31
|
+
* Verifies:
|
|
32
|
+
* - Git is installed
|
|
33
|
+
* - GitHub CLI (gh) is installed and authenticated
|
|
34
|
+
*
|
|
35
|
+
* @returns Result indicating whether prerequisites are met
|
|
36
|
+
*/
|
|
37
|
+
export async function checkPrerequisites(): Promise<PrerequisiteResult> {
|
|
38
|
+
const errors: string[] = []
|
|
39
|
+
const warnings: string[] = []
|
|
40
|
+
|
|
41
|
+
// Check git installation
|
|
42
|
+
const gitInstalled = await checkGitInstalled()
|
|
43
|
+
if (!gitInstalled) {
|
|
44
|
+
errors.push("Git is not installed. Install it from https://git-scm.com/")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check gh CLI installation and authentication
|
|
48
|
+
const ghResult = await checkGhCli()
|
|
49
|
+
if (!ghResult.installed) {
|
|
50
|
+
errors.push(
|
|
51
|
+
"GitHub CLI (gh) is not installed. Install it from https://cli.github.com/"
|
|
52
|
+
)
|
|
53
|
+
} else if (!ghResult.authenticated) {
|
|
54
|
+
errors.push(
|
|
55
|
+
ghResult.error ?? "Not authenticated with GitHub. Run: gh auth login"
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if opencode is available (warning only, not required for all operations)
|
|
60
|
+
const opencodeAvailable = await isCommandAvailable("opencode")
|
|
61
|
+
if (!opencodeAvailable) {
|
|
62
|
+
warnings.push(
|
|
63
|
+
"opencode command not found in PATH. Sessions won't be able to start until it's installed."
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
success: errors.length === 0,
|
|
69
|
+
errors,
|
|
70
|
+
warnings,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a command is available in PATH
|
|
76
|
+
*
|
|
77
|
+
* @param command - Command name to check
|
|
78
|
+
* @returns True if command is available
|
|
79
|
+
*/
|
|
80
|
+
async function isCommandAvailable(command: string): Promise<boolean> {
|
|
81
|
+
try {
|
|
82
|
+
const proc = Bun.spawn(["which", command], {
|
|
83
|
+
stdout: "pipe",
|
|
84
|
+
stderr: "pipe",
|
|
85
|
+
})
|
|
86
|
+
const exitCode = await proc.exited
|
|
87
|
+
return exitCode === 0
|
|
88
|
+
} catch {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Format prerequisite errors for display
|
|
95
|
+
*
|
|
96
|
+
* @param result - Prerequisite check result
|
|
97
|
+
* @returns Formatted string for console output
|
|
98
|
+
*/
|
|
99
|
+
export function formatPrerequisiteErrors(result: PrerequisiteResult): string {
|
|
100
|
+
const lines: string[] = []
|
|
101
|
+
|
|
102
|
+
if (result.errors.length > 0) {
|
|
103
|
+
lines.push("Prerequisites not met:")
|
|
104
|
+
for (const error of result.errors) {
|
|
105
|
+
lines.push(` - ${error}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.warnings.length > 0) {
|
|
110
|
+
if (lines.length > 0) lines.push("")
|
|
111
|
+
lines.push("Warnings:")
|
|
112
|
+
for (const warning of result.warnings) {
|
|
113
|
+
lines.push(` - ${warning}`)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return lines.join("\n")
|
|
118
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quote a string for use in a shell command.
|
|
3
|
+
* Wraps the string in single quotes and escapes any single quotes within it.
|
|
4
|
+
* This preserves all other characters (including newlines) literally.
|
|
5
|
+
*/
|
|
6
|
+
export function shellQuote(s: string): string {
|
|
7
|
+
// Replace ' with '\'' (close quote, literal quote, open quote)
|
|
8
|
+
return "'" + s.replace(/'/g, "'\\''") + "'"
|
|
9
|
+
}
|