Skip to content

Commit 74df273

Browse files
committed
Refactor CLI bootstrap flow
1 parent 2a822dc commit 74df273

File tree

1 file changed

+122
-72
lines changed

1 file changed

+122
-72
lines changed

cli/src/index.tsx

Lines changed: 122 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
#!/usr/bin/env node
2+
3+
const cliEntryPoint =
4+
(typeof Bun !== 'undefined' && typeof Bun.main === 'string' && Bun.main) ||
5+
(typeof process !== 'undefined' &&
6+
Array.isArray(process.argv) &&
7+
process.argv[1]) ||
8+
''
9+
10+
if (cliEntryPoint && typeof globalThis !== 'undefined') {
11+
const globalScope = globalThis as Record<string, unknown>
12+
if (!('__CLI_ENTRY_POINT' in globalScope)) {
13+
Object.defineProperty(globalScope, '__CLI_ENTRY_POINT', {
14+
value: cliEntryPoint,
15+
enumerable: false,
16+
writable: false,
17+
configurable: false,
18+
})
19+
}
20+
}
21+
222
import './polyfills/bun-strip-ansi'
323
import { createRequire } from 'module'
424

@@ -18,6 +38,31 @@ import { initializeThemeStore } from './state/theme-store'
1838

1939
const require = createRequire(import.meta.url)
2040

41+
const INTERNAL_OSC_FLAG = '--internal-osc-detect'
42+
43+
function isOscDetectionRun(): boolean {
44+
return process.argv.includes(INTERNAL_OSC_FLAG)
45+
}
46+
47+
async function runOscDetectionSubprocess(): Promise<void> {
48+
// Set env vars to keep subprocess quiet
49+
process.env.__INTERNAL_OSC_DETECT = '1'
50+
process.env.CODEBUFF_GITHUB_ACTIONS = 'true'
51+
52+
// Avoid importing logger or other modules that produce output
53+
const { detectTerminalTheme } = await import('./utils/terminal-color-detection')
54+
try {
55+
const theme = await detectTerminalTheme()
56+
console.log(JSON.stringify({ theme }))
57+
await new Promise((resolve) => setImmediate(resolve))
58+
} catch {
59+
console.log(JSON.stringify({ theme: null }))
60+
await new Promise((resolve) => setImmediate(resolve))
61+
}
62+
63+
process.exit(0)
64+
}
65+
2166
function loadPackageVersion(): string {
2267
if (process.env.CODEBUFF_CLI_VERSION) {
2368
return process.env.CODEBUFF_CLI_VERSION
@@ -37,6 +82,24 @@ function loadPackageVersion(): string {
3782

3883
const VERSION = loadPackageVersion()
3984

85+
function createQueryClient(): QueryClient {
86+
return new QueryClient({
87+
defaultOptions: {
88+
queries: {
89+
staleTime: 5 * 60 * 1000, // 5 minutes - auth tokens don't change frequently
90+
gcTime: 10 * 60 * 1000, // 10 minutes - keep cached data a bit longer
91+
retry: false, // Don't retry failed auth queries automatically
92+
refetchOnWindowFocus: false, // CLI doesn't have window focus
93+
refetchOnReconnect: true, // Refetch when network reconnects
94+
refetchOnMount: false, // Don't refetch on every mount
95+
},
96+
mutations: {
97+
retry: 1, // Retry mutations once on failure
98+
},
99+
},
100+
})
101+
}
102+
40103
type ParsedArgs = {
41104
initialPrompt: string | null
42105
agent?: string
@@ -70,85 +133,63 @@ function parseArgs(): ParsedArgs {
70133
}
71134
}
72135

73-
const { initialPrompt, agent, clearLogs } = parseArgs()
136+
async function bootstrapCli(): Promise<void> {
137+
const { initialPrompt, agent, clearLogs } = parseArgs()
74138

75-
// Initialize theme store and watchers
76-
initializeThemeStore()
139+
initializeThemeStore()
77140

78-
if (clearLogs) {
79-
clearLogFile()
80-
}
141+
if (clearLogs) {
142+
clearLogFile()
143+
}
81144

82-
const loadedAgentsData = getLoadedAgentsData()
145+
const loadedAgentsData = getLoadedAgentsData()
83146

84-
// Validate local agents and capture any errors
85-
let validationErrors: Array<{ id: string; message: string }> = []
86-
if (loadedAgentsData) {
87-
const agentDefinitions = loadAgentDefinitions()
88-
const validationResult = await validateAgents(agentDefinitions, {
89-
remote: true, // Use remote validation to ensure spawnable agents exist
90-
})
147+
let validationErrors: Array<{ id: string; message: string }> = []
148+
if (loadedAgentsData) {
149+
const agentDefinitions = loadAgentDefinitions()
150+
const validationResult = await validateAgents(agentDefinitions, {
151+
remote: true,
152+
})
91153

92-
if (!validationResult.success) {
93-
validationErrors = validationResult.validationErrors
94-
}
95-
}
96-
97-
// Create QueryClient instance with CLI-optimized defaults
98-
const queryClient = new QueryClient({
99-
defaultOptions: {
100-
queries: {
101-
staleTime: 5 * 60 * 1000, // 5 minutes - auth tokens don't change frequently
102-
gcTime: 10 * 60 * 1000, // 10 minutes - keep cached data a bit longer
103-
retry: false, // Don't retry failed auth queries automatically
104-
refetchOnWindowFocus: false, // CLI doesn't have window focus
105-
refetchOnReconnect: true, // Refetch when network reconnects
106-
refetchOnMount: false, // Don't refetch on every mount
107-
},
108-
mutations: {
109-
retry: 1, // Retry mutations once on failure
110-
},
111-
},
112-
})
113-
114-
// Wrapper component to handle async auth check
115-
const AppWithAsyncAuth = () => {
116-
const [requireAuth, setRequireAuth] = React.useState<boolean | null>(null)
117-
const [hasInvalidCredentials, setHasInvalidCredentials] =
118-
React.useState(false)
119-
120-
React.useEffect(() => {
121-
// Check authentication asynchronously
122-
const userCredentials = getUserCredentials()
123-
const apiKey =
124-
userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || ''
125-
126-
if (!apiKey) {
127-
// No credentials, require auth
128-
setRequireAuth(true)
129-
setHasInvalidCredentials(false)
130-
return
154+
if (!validationResult.success) {
155+
validationErrors = validationResult.validationErrors
131156
}
157+
}
132158

133-
// We have credentials - require auth but show invalid credentials banner until validation succeeds
134-
setHasInvalidCredentials(true)
135-
setRequireAuth(false)
136-
}, [])
137-
138-
return (
139-
<App
140-
initialPrompt={initialPrompt}
141-
agentId={agent}
142-
requireAuth={requireAuth}
143-
hasInvalidCredentials={hasInvalidCredentials}
144-
loadedAgentsData={loadedAgentsData}
145-
validationErrors={validationErrors}
146-
/>
147-
)
148-
}
159+
const queryClient = createQueryClient()
160+
161+
const AppWithAsyncAuth = () => {
162+
const [requireAuth, setRequireAuth] = React.useState<boolean | null>(null)
163+
const [hasInvalidCredentials, setHasInvalidCredentials] =
164+
React.useState(false)
165+
166+
React.useEffect(() => {
167+
const userCredentials = getUserCredentials()
168+
const apiKey =
169+
userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || ''
170+
171+
if (!apiKey) {
172+
setRequireAuth(true)
173+
setHasInvalidCredentials(false)
174+
return
175+
}
176+
177+
setHasInvalidCredentials(true)
178+
setRequireAuth(false)
179+
}, [])
180+
181+
return (
182+
<App
183+
initialPrompt={initialPrompt}
184+
agentId={agent}
185+
requireAuth={requireAuth}
186+
hasInvalidCredentials={hasInvalidCredentials}
187+
loadedAgentsData={loadedAgentsData}
188+
validationErrors={validationErrors}
189+
/>
190+
)
191+
}
149192

150-
// Start app immediately with QueryClientProvider
151-
function startApp() {
152193
render(
153194
<QueryClientProvider client={queryClient}>
154195
<AppWithAsyncAuth />
@@ -160,4 +201,13 @@ function startApp() {
160201
)
161202
}
162203

163-
startApp()
204+
async function main(): Promise<void> {
205+
if (isOscDetectionRun()) {
206+
await runOscDetectionSubprocess()
207+
return
208+
}
209+
210+
await bootstrapCli()
211+
}
212+
213+
void main()

0 commit comments

Comments
 (0)