@spacek33z/autoauto 0.0.1

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 (67) hide show
  1. package/README.md +197 -0
  2. package/package.json +51 -0
  3. package/src/App.tsx +224 -0
  4. package/src/cli.ts +772 -0
  5. package/src/components/AgentPanel.tsx +254 -0
  6. package/src/components/Chat.test.tsx +71 -0
  7. package/src/components/Chat.tsx +308 -0
  8. package/src/components/CycleField.tsx +23 -0
  9. package/src/components/ModelPicker.tsx +97 -0
  10. package/src/components/PostUpdatePrompt.tsx +46 -0
  11. package/src/components/ResultsTable.tsx +172 -0
  12. package/src/components/RunCompletePrompt.tsx +90 -0
  13. package/src/components/RunSettingsOverlay.tsx +49 -0
  14. package/src/components/RunsTable.tsx +219 -0
  15. package/src/components/StatsHeader.tsx +100 -0
  16. package/src/daemon.ts +264 -0
  17. package/src/index.tsx +8 -0
  18. package/src/lib/agent/agent-provider.test.ts +133 -0
  19. package/src/lib/agent/claude-provider.ts +277 -0
  20. package/src/lib/agent/codex-provider.ts +413 -0
  21. package/src/lib/agent/default-providers.ts +10 -0
  22. package/src/lib/agent/index.ts +32 -0
  23. package/src/lib/agent/mock-provider.ts +61 -0
  24. package/src/lib/agent/opencode-provider.ts +424 -0
  25. package/src/lib/agent/types.ts +73 -0
  26. package/src/lib/auth.ts +11 -0
  27. package/src/lib/config.ts +152 -0
  28. package/src/lib/daemon-callbacks.ts +59 -0
  29. package/src/lib/daemon-client.ts +16 -0
  30. package/src/lib/daemon-lifecycle.ts +368 -0
  31. package/src/lib/daemon-spawn.ts +122 -0
  32. package/src/lib/daemon-status.ts +189 -0
  33. package/src/lib/daemon-watcher.ts +192 -0
  34. package/src/lib/experiment-loop.ts +679 -0
  35. package/src/lib/experiment.ts +356 -0
  36. package/src/lib/finalize.test.ts +143 -0
  37. package/src/lib/finalize.ts +511 -0
  38. package/src/lib/format.test.ts +32 -0
  39. package/src/lib/format.ts +44 -0
  40. package/src/lib/git.ts +176 -0
  41. package/src/lib/ideas-backlog.test.ts +54 -0
  42. package/src/lib/ideas-backlog.ts +109 -0
  43. package/src/lib/measure.ts +472 -0
  44. package/src/lib/model-options.ts +24 -0
  45. package/src/lib/programs.ts +247 -0
  46. package/src/lib/push-stream.ts +48 -0
  47. package/src/lib/run-context.ts +112 -0
  48. package/src/lib/run-setup.ts +34 -0
  49. package/src/lib/run.ts +383 -0
  50. package/src/lib/syntax-theme.ts +39 -0
  51. package/src/lib/system-prompts/experiment.ts +77 -0
  52. package/src/lib/system-prompts/finalize.ts +90 -0
  53. package/src/lib/system-prompts/index.ts +7 -0
  54. package/src/lib/system-prompts/setup.ts +516 -0
  55. package/src/lib/system-prompts/update.ts +188 -0
  56. package/src/lib/tool-events.ts +99 -0
  57. package/src/lib/validate-measurement.ts +326 -0
  58. package/src/lib/worktree.ts +40 -0
  59. package/src/screens/AuthErrorScreen.tsx +31 -0
  60. package/src/screens/ExecutionScreen.tsx +851 -0
  61. package/src/screens/FirstSetupScreen.tsx +168 -0
  62. package/src/screens/HomeScreen.tsx +406 -0
  63. package/src/screens/PreRunScreen.tsx +206 -0
  64. package/src/screens/SettingsScreen.tsx +189 -0
  65. package/src/screens/SetupScreen.tsx +226 -0
  66. package/src/tui.tsx +17 -0
  67. package/tsconfig.json +17 -0
@@ -0,0 +1,32 @@
1
+ export type {
2
+ AgentEvent,
3
+ AgentCost,
4
+ AgentSessionConfig,
5
+ AgentSession,
6
+ AuthResult,
7
+ AgentProvider,
8
+ AgentProviderID,
9
+ AgentModelOption,
10
+ } from "./types.ts"
11
+
12
+ import type { AgentProvider, AgentProviderID } from "./types.ts"
13
+
14
+ const providers = new Map<AgentProviderID, AgentProvider>()
15
+
16
+ /** Set the active agent provider. Must be called before app render. */
17
+ export function setProvider(id: AgentProviderID, p: AgentProvider): void {
18
+ providers.set(id, p)
19
+ }
20
+
21
+ /** Get the active agent provider. Throws if not yet configured. */
22
+ export function getProvider(id: AgentProviderID = "claude"): AgentProvider {
23
+ const provider = providers.get(id)
24
+ if (!provider) {
25
+ throw new Error(`No ${id} agent provider configured — call setProvider() before use`)
26
+ }
27
+ return provider
28
+ }
29
+
30
+ export async function closeProviders(): Promise<void> {
31
+ await Promise.all([...providers.values()].map((provider) => provider.close?.()))
32
+ }
@@ -0,0 +1,61 @@
1
+ import type {
2
+ AgentProvider,
3
+ AgentSession,
4
+ AgentSessionConfig,
5
+ AgentEvent,
6
+ AuthResult,
7
+ } from "./types.ts"
8
+
9
+ /**
10
+ * Mock provider for contract tests.
11
+ * Emits a scripted sequence of AgentEvent values, independent of any real SDK.
12
+ */
13
+ export class MockProvider implements AgentProvider {
14
+ readonly name = "mock"
15
+
16
+ constructor(
17
+ private events: AgentEvent[] = [],
18
+ private authResult: AuthResult = { authenticated: true, account: { email: "test@example.com" } },
19
+ ) {}
20
+
21
+ createSession(_config: AgentSessionConfig): AgentSession {
22
+ return new MockSession(this.events)
23
+ }
24
+
25
+ runOnce(_prompt: string, config: AgentSessionConfig): AgentSession {
26
+ const session = this.createSession(config)
27
+ session.endInput()
28
+ return session
29
+ }
30
+
31
+ async checkAuth(): Promise<AuthResult> {
32
+ return this.authResult
33
+ }
34
+ }
35
+
36
+ class MockSession implements AgentSession {
37
+ private messages: string[] = []
38
+ private ended = false
39
+ private closed = false
40
+
41
+ constructor(private events: AgentEvent[]) {}
42
+
43
+ pushMessage(content: string): void {
44
+ this.messages.push(content)
45
+ }
46
+
47
+ endInput(): void {
48
+ this.ended = true
49
+ }
50
+
51
+ close(): void {
52
+ this.closed = true
53
+ }
54
+
55
+ async *[Symbol.asyncIterator](): AsyncIterator<AgentEvent> {
56
+ for (const event of this.events) {
57
+ if (this.closed) break
58
+ yield event
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,424 @@
1
+ import { createOpencode, type OpencodeClient } from "@opencode-ai/sdk/v2"
2
+ import type {
3
+ AgentCost,
4
+ AgentEvent,
5
+ AgentModelOption,
6
+ AgentProvider,
7
+ AgentSession,
8
+ AgentSessionConfig,
9
+ AuthResult,
10
+ } from "./types.ts"
11
+ import { createPushStream } from "../push-stream.ts"
12
+
13
+ type OpenCodeServer = Awaited<ReturnType<typeof createOpencode>>["server"]
14
+ type OpenCodeInstance = { client: OpencodeClient; server: OpenCodeServer }
15
+
16
+ type OpenCodeEvent = {
17
+ type: string
18
+ properties?: Record<string, unknown>
19
+ }
20
+
21
+ type OpenCodePart = {
22
+ id?: string
23
+ sessionID?: string
24
+ messageID?: string
25
+ type?: string
26
+ text?: string
27
+ tool?: string
28
+ state?: {
29
+ status?: string
30
+ input?: Record<string, unknown>
31
+ }
32
+ }
33
+
34
+ type OpenCodeAssistantMessage = {
35
+ cost?: number
36
+ tokens?: {
37
+ input?: number
38
+ output?: number
39
+ }
40
+ }
41
+
42
+ function parseModel(model: string | undefined): { providerID: string; modelID: string } | undefined {
43
+ if (!model) return undefined
44
+ const slash = model.indexOf("/")
45
+ if (slash <= 0 || slash === model.length - 1) {
46
+ throw new Error(`OpenCode model must be in provider/model form, got "${model}"`)
47
+ }
48
+ return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) }
49
+ }
50
+
51
+ function mapPermission(tool: string): string[] {
52
+ switch (tool.toLowerCase()) {
53
+ case "read":
54
+ return ["read", "list"]
55
+ case "write":
56
+ case "edit":
57
+ return ["edit"]
58
+ case "bash":
59
+ return ["bash"]
60
+ case "glob":
61
+ return ["glob", "list"]
62
+ case "grep":
63
+ return ["grep"]
64
+ default:
65
+ return []
66
+ }
67
+ }
68
+
69
+ function buildPermissionRules(config: AgentSessionConfig) {
70
+ const tools = config.allowedTools ?? config.tools ?? []
71
+ const permissions = new Set<string>()
72
+ for (const tool of tools) {
73
+ for (const permission of mapPermission(tool)) {
74
+ permissions.add(permission)
75
+ }
76
+ }
77
+
78
+ return [
79
+ ...[...permissions].toSorted().map((permission) => ({
80
+ permission,
81
+ pattern: "*",
82
+ action: "allow" as const,
83
+ })),
84
+ { permission: "external_directory", pattern: "*", action: "deny" as const },
85
+ ]
86
+ }
87
+
88
+ function getTextFromParts(parts: unknown): string {
89
+ if (!Array.isArray(parts)) return ""
90
+ return parts
91
+ .flatMap((part) => {
92
+ if (typeof part === "object" && part !== null && (part as OpenCodePart).type === "text") {
93
+ const text = (part as OpenCodePart).text
94
+ return typeof text === "string" ? [text] : []
95
+ }
96
+ return []
97
+ })
98
+ .join("")
99
+ }
100
+
101
+ function extractCost(info: unknown, startedAt: number): AgentCost {
102
+ const message = (typeof info === "object" && info !== null ? info : {}) as OpenCodeAssistantMessage
103
+ return {
104
+ total_cost_usd: message.cost ?? 0,
105
+ duration_ms: Date.now() - startedAt,
106
+ duration_api_ms: 0,
107
+ num_turns: 1,
108
+ input_tokens: message.tokens?.input ?? 0,
109
+ output_tokens: message.tokens?.output ?? 0,
110
+ }
111
+ }
112
+
113
+ class OpenCodeSession implements AgentSession {
114
+ private input = createPushStream<string>()
115
+ private events = createPushStream<AgentEvent>()
116
+ private abortController = new AbortController()
117
+ private externalSignal?: AbortSignal
118
+ private signalHandler?: () => void
119
+ private closed = false
120
+ private sessionID: string | null = null
121
+
122
+ constructor(
123
+ private provider: OpenCodeProvider,
124
+ private config: AgentSessionConfig,
125
+ ) {
126
+ if (config.signal) {
127
+ if (config.signal.aborted) {
128
+ this.abortController.abort()
129
+ } else {
130
+ this.externalSignal = config.signal
131
+ this.signalHandler = () => this.close()
132
+ config.signal.addEventListener("abort", this.signalHandler, { once: true })
133
+ }
134
+ }
135
+
136
+ this.run().catch((err: unknown) => {
137
+ if (!this.closed) {
138
+ this.events.push({
139
+ type: "error",
140
+ error: err instanceof Error ? err.message : String(err),
141
+ retriable: false,
142
+ })
143
+ this.events.push({
144
+ type: "result",
145
+ success: false,
146
+ error: err instanceof Error ? err.message : String(err),
147
+ })
148
+ }
149
+ this.events.end()
150
+ })
151
+ }
152
+
153
+ pushMessage(content: string): void {
154
+ if (this.closed) return
155
+ this.input.push(content)
156
+ }
157
+
158
+ endInput(): void {
159
+ this.input.end()
160
+ }
161
+
162
+ close(): void {
163
+ if (this.closed) return
164
+ this.closed = true
165
+ if (this.externalSignal && this.signalHandler) {
166
+ this.externalSignal.removeEventListener("abort", this.signalHandler)
167
+ }
168
+ this.abortController.abort()
169
+ this.input.end()
170
+ this.events.end()
171
+ const sessionID = this.sessionID
172
+ if (sessionID) {
173
+ this.provider.abortSession(sessionID, this.config.cwd).catch(() => {})
174
+ }
175
+ }
176
+
177
+ async *[Symbol.asyncIterator](): AsyncIterator<AgentEvent> {
178
+ yield* this.events
179
+ }
180
+
181
+ private async run(): Promise<void> {
182
+ const { client } = await this.provider.getInstance()
183
+ const directory = this.config.cwd
184
+ const created = await client.session.create({
185
+ directory,
186
+ title: "AutoAuto",
187
+ permission: buildPermissionRules(this.config),
188
+ })
189
+ if (created.error) {
190
+ throw new Error(`Failed to create OpenCode session: ${JSON.stringify(created.error)}`)
191
+ }
192
+ if (!created.data) throw new Error("Failed to create OpenCode session")
193
+
194
+ this.sessionID = created.data.id
195
+
196
+ const eventAbort = new AbortController()
197
+ const subscription = await client.event.subscribe(
198
+ { directory },
199
+ { signal: eventAbort.signal },
200
+ )
201
+ const consumeEvents = this.consumeEvents(
202
+ subscription.stream as AsyncIterable<OpenCodeEvent>,
203
+ created.data.id,
204
+ eventAbort.signal,
205
+ )
206
+
207
+ try {
208
+ for await (const prompt of this.input) {
209
+ if (this.closed || this.abortController.signal.aborted) break
210
+
211
+ const model = parseModel(this.config.model)
212
+ const startedAt = Date.now()
213
+ const result = await client.session.prompt(
214
+ {
215
+ sessionID: created.data.id,
216
+ directory,
217
+ agent: "build",
218
+ system: this.config.systemPrompt,
219
+ model,
220
+ parts: [{ type: "text", text: prompt }],
221
+ },
222
+ { signal: this.abortController.signal },
223
+ )
224
+
225
+ if (result.error) {
226
+ const error = JSON.stringify(result.error)
227
+ this.events.push({ type: "error", error, retriable: false })
228
+ this.events.push({ type: "result", success: false, error })
229
+ continue
230
+ }
231
+
232
+ const text = getTextFromParts(result.data?.parts)
233
+ if (text.trim()) {
234
+ this.events.push({ type: "assistant_complete", text })
235
+ }
236
+ this.events.push({
237
+ type: "result",
238
+ success: true,
239
+ cost: extractCost(result.data?.info, startedAt),
240
+ })
241
+ }
242
+ } finally {
243
+ eventAbort.abort()
244
+ await consumeEvents.catch(() => {})
245
+ this.events.end()
246
+ }
247
+ }
248
+
249
+ private async consumeEvents(
250
+ stream: AsyncIterable<OpenCodeEvent>,
251
+ sessionID: string,
252
+ signal: AbortSignal,
253
+ ): Promise<void> {
254
+ const textByPartID = new Map<string, string>()
255
+ const emittedTools = new Set<string>()
256
+
257
+ for await (const event of stream) {
258
+ if (this.closed || signal.aborted) break
259
+ const properties = event.properties ?? {}
260
+ if (properties.sessionID !== sessionID) continue
261
+
262
+ if (event.type === "message.part.updated") {
263
+ const part = properties.part as OpenCodePart | undefined
264
+ if (!part?.id) continue
265
+
266
+ if (part.type === "text" && typeof part.text === "string") {
267
+ const previous = textByPartID.get(part.id) ?? ""
268
+ const delta = part.text.startsWith(previous)
269
+ ? part.text.slice(previous.length)
270
+ : part.text
271
+ textByPartID.set(part.id, part.text)
272
+ if (delta) this.events.push({ type: "text_delta", text: delta })
273
+ }
274
+
275
+ if (part.type === "tool" && part.tool) {
276
+ const status = part.state?.status ?? "unknown"
277
+ const key = `${part.id}:${status}`
278
+ if (!emittedTools.has(key)) {
279
+ emittedTools.add(key)
280
+ const state = part.state as {
281
+ status?: string
282
+ input?: Record<string, unknown>
283
+ title?: string
284
+ } | undefined
285
+ const input: Record<string, unknown> = { ...state?.input }
286
+ // Forward provider-supplied title for richer tool status display
287
+ if (state?.title) input.__title = state.title
288
+ this.events.push({
289
+ type: "tool_use",
290
+ tool: part.tool,
291
+ input,
292
+ })
293
+ }
294
+ }
295
+ }
296
+
297
+ if (event.type === "session.error") {
298
+ this.events.push({
299
+ type: "error",
300
+ error: JSON.stringify(properties.error ?? "OpenCode session error"),
301
+ retriable: false,
302
+ })
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ export class OpenCodeProvider implements AgentProvider {
309
+ readonly name = "opencode"
310
+ private instance: Promise<OpenCodeInstance> | null = null
311
+ private resolvedInstance: OpenCodeInstance | null = null
312
+ private modelCache: AgentModelOption[] | null = null
313
+
314
+ async getInstance(): Promise<OpenCodeInstance> {
315
+ if (!this.instance) {
316
+ this.instance = this.startInstance()
317
+ }
318
+ return this.instance
319
+ }
320
+
321
+ createSession(config: AgentSessionConfig): AgentSession {
322
+ return new OpenCodeSession(this, config)
323
+ }
324
+
325
+ runOnce(prompt: string, config: AgentSessionConfig): AgentSession {
326
+ const session = this.createSession(config)
327
+ session.pushMessage(prompt)
328
+ session.endInput()
329
+ return session
330
+ }
331
+
332
+ async checkAuth(): Promise<AuthResult> {
333
+ try {
334
+ const { client } = await this.getInstance()
335
+ const health = await client.global.health({ throwOnError: true })
336
+ return {
337
+ authenticated: true,
338
+ account: {
339
+ provider: "opencode",
340
+ version: health.data.version,
341
+ },
342
+ }
343
+ } catch (err) {
344
+ return {
345
+ authenticated: false,
346
+ error: err instanceof Error ? err.message : String(err),
347
+ }
348
+ }
349
+ }
350
+
351
+ async listModels(cwd?: string, forceRefresh = false): Promise<AgentModelOption[]> {
352
+ if (this.modelCache && !forceRefresh) return this.modelCache
353
+
354
+ const { client } = await this.getInstance()
355
+ const response = await client.provider.list({ directory: cwd }, { throwOnError: true })
356
+ const connected = new Set(response.data.connected)
357
+ const defaults = response.data.default
358
+ const options: AgentModelOption[] = []
359
+
360
+ for (const provider of response.data.all) {
361
+ if (!connected.has(provider.id)) continue
362
+ const defaultModel = defaults[provider.id]
363
+ for (const [modelID, model] of Object.entries(provider.models)) {
364
+ const fullModel = `${provider.id}/${modelID}`
365
+ const isDefault = defaultModel === modelID
366
+ options.push({
367
+ provider: "opencode",
368
+ model: fullModel,
369
+ label: `OpenCode / ${provider.name} / ${model.name}`,
370
+ description: isDefault ? "Configured OpenCode default" : fullModel,
371
+ isDefault,
372
+ })
373
+ }
374
+ }
375
+
376
+ options.sort((a, b) => {
377
+ if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1
378
+ return a.label.localeCompare(b.label)
379
+ })
380
+
381
+ this.modelCache = options
382
+ return options
383
+ }
384
+
385
+ async getDefaultModel(cwd?: string): Promise<string | null> {
386
+ const options = await this.listModels(cwd)
387
+ return options.find((option) => option.isDefault)?.model ?? options[0]?.model ?? null
388
+ }
389
+
390
+ async abortSession(sessionID: string, cwd?: string): Promise<void> {
391
+ const { client } = await this.getInstance()
392
+ await client.session.abort({ sessionID, directory: cwd }).catch(() => {})
393
+ }
394
+
395
+ close(): void {
396
+ this.resolvedInstance?.server.close()
397
+ this.resolvedInstance = null
398
+ this.instance = null
399
+ this.modelCache = null
400
+ }
401
+
402
+ private async startInstance(): Promise<OpenCodeInstance> {
403
+ const instance = await createOpencode({
404
+ port: await getFreePort(),
405
+ timeout: 10_000,
406
+ config: { autoupdate: false },
407
+ })
408
+ this.resolvedInstance = instance
409
+ return instance
410
+ }
411
+ }
412
+
413
+ async function getFreePort(): Promise<number> {
414
+ const server = Bun.serve({
415
+ port: 0,
416
+ fetch() {
417
+ return new Response("ok")
418
+ },
419
+ })
420
+ const port = server.port
421
+ if (port == null) throw new Error("Failed to allocate local OpenCode port")
422
+ await server.stop()
423
+ return port
424
+ }
@@ -0,0 +1,73 @@
1
+ /** Normalized event types emitted by all agent providers. */
2
+ export type AgentEvent =
3
+ | { type: "text_delta"; text: string }
4
+ | { type: "tool_use"; tool: string; input?: Record<string, unknown> }
5
+ | { type: "assistant_complete"; text: string }
6
+ | { type: "error"; error: string; retriable: boolean }
7
+ | { type: "result"; success: boolean; error?: string; cost?: AgentCost }
8
+
9
+ /** Cost and usage data from a completed agent session. */
10
+ export interface AgentCost {
11
+ total_cost_usd: number
12
+ duration_ms: number
13
+ duration_api_ms: number
14
+ num_turns: number
15
+ input_tokens: number
16
+ output_tokens: number
17
+ }
18
+
19
+ export type AgentProviderID = "claude" | "opencode" | "codex"
20
+
21
+ export interface AgentModelOption {
22
+ provider: AgentProviderID
23
+ model: string
24
+ label: string
25
+ description?: string
26
+ isDefault?: boolean
27
+ }
28
+
29
+ /** Configuration for creating an agent session. */
30
+ export interface AgentSessionConfig {
31
+ systemPrompt?: string
32
+ tools?: string[]
33
+ allowedTools?: string[]
34
+ maxTurns?: number
35
+ cwd?: string
36
+ model?: string
37
+ effort?: string
38
+ signal?: AbortSignal
39
+ /** Escape hatch for provider-specific options (e.g. temperature, reasoning_effort). */
40
+ providerOptions?: Record<string, unknown>
41
+ }
42
+
43
+ /** A running agent session that yields events and accepts user messages. */
44
+ export interface AgentSession extends AsyncIterable<AgentEvent> {
45
+ /** Push a user message into the conversation. */
46
+ pushMessage(content: string): void
47
+ /** Signal that no more input will be sent (one-shot mode). */
48
+ endInput(): void
49
+ /** Close the session and release resources. Idempotent. */
50
+ close(): void
51
+ }
52
+
53
+ /** Result of an authentication check. */
54
+ export type AuthResult =
55
+ | { authenticated: true; account: Record<string, unknown> & { email?: string } }
56
+ | { authenticated: false; error: string }
57
+
58
+ /** Interface that all agent SDK providers must implement. */
59
+ export interface AgentProvider {
60
+ readonly name: AgentProviderID | string
61
+ /** Create an interactive multi-turn session. */
62
+ createSession(config: AgentSessionConfig): AgentSession
63
+ /** Convenience: create a one-shot session with a single prompt. */
64
+ runOnce(prompt: string, config: AgentSessionConfig): AgentSession
65
+ /** Verify authentication with the provider. */
66
+ checkAuth(): Promise<AuthResult>
67
+ /** List models available through this provider. */
68
+ listModels?(cwd?: string, forceRefresh?: boolean): Promise<AgentModelOption[]>
69
+ /** Return the provider's configured default model, if available. */
70
+ getDefaultModel?(cwd?: string): Promise<string | null>
71
+ /** Release provider-owned resources. */
72
+ close?(): void | Promise<void>
73
+ }
@@ -0,0 +1,11 @@
1
+ import { getProvider, type AgentProviderID } from "./agent/index.ts"
2
+ import type { AuthResult } from "./agent/types.ts"
3
+
4
+ export type { AuthResult }
5
+
6
+ /**
7
+ * Check if the user is authenticated with an agent provider.
8
+ */
9
+ export async function checkAuth(provider: AgentProviderID = "claude"): Promise<AuthResult> {
10
+ return getProvider(provider).checkAuth()
11
+ }