luckerr 0.41.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 (156) hide show
  1. package/README.md +267 -0
  2. package/README.zh-CN.md +237 -0
  3. package/dashboard/app.css +3022 -0
  4. package/dashboard/dist/app.js +30137 -0
  5. package/dashboard/dist/app.js.map +1 -0
  6. package/dashboard/dist/vendor-hljs.css +10 -0
  7. package/dashboard/dist/vendor-uplot.css +1 -0
  8. package/dashboard/index.html +19 -0
  9. package/data/deepseek-tokenizer.json.gz +0 -0
  10. package/dist/cli/acp-EOOAI4F5.js +712 -0
  11. package/dist/cli/acp-EOOAI4F5.js.map +1 -0
  12. package/dist/cli/chat-7J6GJXL2.js +51 -0
  13. package/dist/cli/chat-7J6GJXL2.js.map +1 -0
  14. package/dist/cli/chunk-2425HK6U.js +54 -0
  15. package/dist/cli/chunk-2425HK6U.js.map +1 -0
  16. package/dist/cli/chunk-25T6CVUP.js +172 -0
  17. package/dist/cli/chunk-25T6CVUP.js.map +1 -0
  18. package/dist/cli/chunk-2UQP6H6T.js +31 -0
  19. package/dist/cli/chunk-2UQP6H6T.js.map +1 -0
  20. package/dist/cli/chunk-56OAJILV.js +47 -0
  21. package/dist/cli/chunk-56OAJILV.js.map +1 -0
  22. package/dist/cli/chunk-5FTI4KXH.js +150 -0
  23. package/dist/cli/chunk-5FTI4KXH.js.map +1 -0
  24. package/dist/cli/chunk-5TWQD73O.js +2846 -0
  25. package/dist/cli/chunk-5TWQD73O.js.map +1 -0
  26. package/dist/cli/chunk-653BOCMK.js +40 -0
  27. package/dist/cli/chunk-653BOCMK.js.map +1 -0
  28. package/dist/cli/chunk-6ALJTWWQ.js +2663 -0
  29. package/dist/cli/chunk-6ALJTWWQ.js.map +1 -0
  30. package/dist/cli/chunk-6DRKA2IL.js +341 -0
  31. package/dist/cli/chunk-6DRKA2IL.js.map +1 -0
  32. package/dist/cli/chunk-6LV63NJV.js +634 -0
  33. package/dist/cli/chunk-6LV63NJV.js.map +1 -0
  34. package/dist/cli/chunk-74EX7SUH.js +25293 -0
  35. package/dist/cli/chunk-74EX7SUH.js.map +1 -0
  36. package/dist/cli/chunk-74U5RKTX.js +60611 -0
  37. package/dist/cli/chunk-74U5RKTX.js.map +1 -0
  38. package/dist/cli/chunk-ANJSUESV.js +143 -0
  39. package/dist/cli/chunk-ANJSUESV.js.map +1 -0
  40. package/dist/cli/chunk-DB2Z3DKZ.js +54 -0
  41. package/dist/cli/chunk-DB2Z3DKZ.js.map +1 -0
  42. package/dist/cli/chunk-DDIH3ZAA.js +400 -0
  43. package/dist/cli/chunk-DDIH3ZAA.js.map +1 -0
  44. package/dist/cli/chunk-ELN3Z3B2.js +621 -0
  45. package/dist/cli/chunk-ELN3Z3B2.js.map +1 -0
  46. package/dist/cli/chunk-F6BSQJGV.js +200 -0
  47. package/dist/cli/chunk-F6BSQJGV.js.map +1 -0
  48. package/dist/cli/chunk-FET2UAG5.js +246 -0
  49. package/dist/cli/chunk-FET2UAG5.js.map +1 -0
  50. package/dist/cli/chunk-FFJ342IJ.js +190 -0
  51. package/dist/cli/chunk-FFJ342IJ.js.map +1 -0
  52. package/dist/cli/chunk-GB3247B6.js +130 -0
  53. package/dist/cli/chunk-GB3247B6.js.map +1 -0
  54. package/dist/cli/chunk-HC2J4U3G.js +373 -0
  55. package/dist/cli/chunk-HC2J4U3G.js.map +1 -0
  56. package/dist/cli/chunk-HRUZAIHQ.js +42 -0
  57. package/dist/cli/chunk-HRUZAIHQ.js.map +1 -0
  58. package/dist/cli/chunk-J3ZJFUDL.js +308 -0
  59. package/dist/cli/chunk-J3ZJFUDL.js.map +1 -0
  60. package/dist/cli/chunk-J5XJHLWM.js +55 -0
  61. package/dist/cli/chunk-J5XJHLWM.js.map +1 -0
  62. package/dist/cli/chunk-JFGLMRZ6.js +160 -0
  63. package/dist/cli/chunk-JFGLMRZ6.js.map +1 -0
  64. package/dist/cli/chunk-JMBMLOBP.js +26 -0
  65. package/dist/cli/chunk-JMBMLOBP.js.map +1 -0
  66. package/dist/cli/chunk-JMWHXZEL.js +551 -0
  67. package/dist/cli/chunk-JMWHXZEL.js.map +1 -0
  68. package/dist/cli/chunk-KEQGPJBO.js +209 -0
  69. package/dist/cli/chunk-KEQGPJBO.js.map +1 -0
  70. package/dist/cli/chunk-M4K6U37F.js +232 -0
  71. package/dist/cli/chunk-M4K6U37F.js.map +1 -0
  72. package/dist/cli/chunk-MIJI2WMN.js +95 -0
  73. package/dist/cli/chunk-MIJI2WMN.js.map +1 -0
  74. package/dist/cli/chunk-MPAO3JNR.js +128 -0
  75. package/dist/cli/chunk-MPAO3JNR.js.map +1 -0
  76. package/dist/cli/chunk-PZOFBEDC.js +873 -0
  77. package/dist/cli/chunk-PZOFBEDC.js.map +1 -0
  78. package/dist/cli/chunk-RAILYQLN.js +46 -0
  79. package/dist/cli/chunk-RAILYQLN.js.map +1 -0
  80. package/dist/cli/chunk-RR35VQVT.js +90 -0
  81. package/dist/cli/chunk-RR35VQVT.js.map +1 -0
  82. package/dist/cli/chunk-RRA7VPW4.js +417 -0
  83. package/dist/cli/chunk-RRA7VPW4.js.map +1 -0
  84. package/dist/cli/chunk-RU36QVN3.js +452 -0
  85. package/dist/cli/chunk-RU36QVN3.js.map +1 -0
  86. package/dist/cli/chunk-RUBIINXR.js +1819 -0
  87. package/dist/cli/chunk-RUBIINXR.js.map +1 -0
  88. package/dist/cli/chunk-S4XVGLRW.js +499 -0
  89. package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
  90. package/dist/cli/chunk-TUK7OWJA.js +51 -0
  91. package/dist/cli/chunk-TUK7OWJA.js.map +1 -0
  92. package/dist/cli/chunk-VALDDV76.js +580 -0
  93. package/dist/cli/chunk-VALDDV76.js.map +1 -0
  94. package/dist/cli/chunk-WQOGPYGN.js +11390 -0
  95. package/dist/cli/chunk-WQOGPYGN.js.map +1 -0
  96. package/dist/cli/chunk-WREKDFXT.js +34320 -0
  97. package/dist/cli/chunk-WREKDFXT.js.map +1 -0
  98. package/dist/cli/chunk-Y7XQU2EL.js +270 -0
  99. package/dist/cli/chunk-Y7XQU2EL.js.map +1 -0
  100. package/dist/cli/chunk-YBVCZJU4.js +54 -0
  101. package/dist/cli/chunk-YBVCZJU4.js.map +1 -0
  102. package/dist/cli/chunk-YLIHDXUQ.js +749 -0
  103. package/dist/cli/chunk-YLIHDXUQ.js.map +1 -0
  104. package/dist/cli/chunk-YV5XXFD7.js +767 -0
  105. package/dist/cli/chunk-YV5XXFD7.js.map +1 -0
  106. package/dist/cli/chunk-ZRCNIYRQ.js +101 -0
  107. package/dist/cli/chunk-ZRCNIYRQ.js.map +1 -0
  108. package/dist/cli/code-CRKVCMFZ.js +155 -0
  109. package/dist/cli/code-CRKVCMFZ.js.map +1 -0
  110. package/dist/cli/commands-QLMD3T7B.js +356 -0
  111. package/dist/cli/commands-QLMD3T7B.js.map +1 -0
  112. package/dist/cli/commit-53PP32NC.js +293 -0
  113. package/dist/cli/commit-53PP32NC.js.map +1 -0
  114. package/dist/cli/desktop-R6W5CLJ5.js +1046 -0
  115. package/dist/cli/desktop-R6W5CLJ5.js.map +1 -0
  116. package/dist/cli/devtools-YECO25QO.js +3719 -0
  117. package/dist/cli/devtools-YECO25QO.js.map +1 -0
  118. package/dist/cli/diff-LYNRCJZE.js +166 -0
  119. package/dist/cli/diff-LYNRCJZE.js.map +1 -0
  120. package/dist/cli/doctor-5IBP4R5J.js +28 -0
  121. package/dist/cli/doctor-5IBP4R5J.js.map +1 -0
  122. package/dist/cli/events-QN6KLN2V.js +340 -0
  123. package/dist/cli/events-QN6KLN2V.js.map +1 -0
  124. package/dist/cli/index.js +3500 -0
  125. package/dist/cli/index.js.map +1 -0
  126. package/dist/cli/mcp-FGKEH7RG.js +277 -0
  127. package/dist/cli/mcp-FGKEH7RG.js.map +1 -0
  128. package/dist/cli/mcp-browse-YCND4NWT.js +178 -0
  129. package/dist/cli/mcp-browse-YCND4NWT.js.map +1 -0
  130. package/dist/cli/mcp-inspect-V34J3VX5.js +143 -0
  131. package/dist/cli/mcp-inspect-V34J3VX5.js.map +1 -0
  132. package/dist/cli/package.json +3 -0
  133. package/dist/cli/prompt-I775PNKT.js +16 -0
  134. package/dist/cli/prompt-I775PNKT.js.map +1 -0
  135. package/dist/cli/prune-sessions-KGIIYD3P.js +44 -0
  136. package/dist/cli/prune-sessions-KGIIYD3P.js.map +1 -0
  137. package/dist/cli/replay-RDXLUAOE.js +292 -0
  138. package/dist/cli/replay-RDXLUAOE.js.map +1 -0
  139. package/dist/cli/run-RCAC2RYW.js +223 -0
  140. package/dist/cli/run-RCAC2RYW.js.map +1 -0
  141. package/dist/cli/server-FFU6TLYJ.js +3658 -0
  142. package/dist/cli/server-FFU6TLYJ.js.map +1 -0
  143. package/dist/cli/sessions-QT26MQAE.js +107 -0
  144. package/dist/cli/sessions-QT26MQAE.js.map +1 -0
  145. package/dist/cli/setup-VV4WKXHV.js +767 -0
  146. package/dist/cli/setup-VV4WKXHV.js.map +1 -0
  147. package/dist/cli/stats-JVZPQWAN.js +15 -0
  148. package/dist/cli/stats-JVZPQWAN.js.map +1 -0
  149. package/dist/cli/update-KYI3OVJP.js +15 -0
  150. package/dist/cli/update-KYI3OVJP.js.map +1 -0
  151. package/dist/cli/version-ANYORXTI.js +34 -0
  152. package/dist/cli/version-ANYORXTI.js.map +1 -0
  153. package/dist/index.d.ts +2557 -0
  154. package/dist/index.js +15000 -0
  155. package/dist/index.js.map +1 -0
  156. package/package.json +106 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/retry.ts","../../src/client.ts"],"sourcesContent":["/** No retry on aborts or mid-stream body errors — re-billing the user for desynced output is worse than failing. */\n\nexport interface RetryOptions {\n /** Maximum total attempts (including the first). Default 4. */\n maxAttempts?: number;\n /** Initial backoff in ms. Doubles each retry, with jitter. Default 500. */\n initialBackoffMs?: number;\n /** Upper bound on any single backoff delay. Default 10000 (10s). */\n maxBackoffMs?: number;\n /** HTTP statuses to treat as retryable. Default [408, 429, 500, 502, 503, 504]. */\n retryableStatuses?: readonly number[];\n /** Abort signal; we do NOT retry once aborted. */\n signal?: AbortSignal;\n /** Telemetry hook — called before each wait. */\n onRetry?: (info: RetryInfo) => void;\n}\n\nexport interface RetryInfo {\n attempt: number;\n reason: string;\n waitMs: number;\n}\n\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504] as const;\n\nexport async function fetchWithRetry(\n fetchFn: typeof fetch,\n url: string,\n init: RequestInit,\n opts: RetryOptions = {},\n): Promise<Response> {\n const maxAttempts = opts.maxAttempts ?? 4;\n const initial = opts.initialBackoffMs ?? 500;\n const cap = opts.maxBackoffMs ?? 10_000;\n const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (opts.signal?.aborted) throw new Error(\"aborted\");\n\n try {\n const resp = await fetchFn(url, init);\n\n // Success or non-retryable failure: return as-is.\n if (resp.ok || !retryable.has(resp.status)) return resp;\n\n // Retryable but out of attempts: return the last response so the caller\n // can surface the status to the user.\n if (attempt === maxAttempts - 1) return resp;\n\n // Drain the body so the connection can be reused on the next attempt.\n await resp.text().catch(() => undefined);\n\n const waitMs = computeWait(attempt, initial, cap, resp.headers.get(\"Retry-After\"));\n opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });\n await sleep(waitMs, opts.signal);\n } catch (err) {\n lastError = err;\n // Respect explicit aborts — do not retry.\n if (isAbortError(err) || opts.signal?.aborted) throw err;\n if (attempt === maxAttempts - 1) throw err;\n\n const waitMs = computeWait(attempt, initial, cap, null);\n opts.onRetry?.({\n attempt: attempt + 1,\n reason: `network: ${messageOf(err)}`,\n waitMs,\n });\n await sleep(waitMs, opts.signal);\n }\n }\n\n throw lastError ?? new Error(\"fetchWithRetry: loop exited unexpectedly\");\n}\n\nfunction computeWait(\n attempt: number,\n initial: number,\n cap: number,\n retryAfter: string | null,\n): number {\n if (retryAfter) {\n const seconds = Number.parseFloat(retryAfter);\n if (Number.isFinite(seconds) && seconds > 0) {\n return Math.min(seconds * 1000, cap);\n }\n }\n const exp = initial * 2 ** attempt;\n // Jitter range [75%, 125%] to spread retries out when many clients hit 429 together.\n const jitter = exp * (0.75 + Math.random() * 0.5);\n return Math.min(Math.max(jitter, 0), cap);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n if (signal) {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new Error(\"aborted\"));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const name = (err as { name?: unknown }).name;\n return name === \"AbortError\";\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return \"unknown error\";\n }\n}\n","import { type EventSourceMessage, createParser } from \"eventsource-parser\";\nimport { type RetryOptions, fetchWithRetry } from \"./retry.js\";\nimport { defaultProvider, resolveProvider } from \"./providers/registry.js\";\nimport type { ProviderProfile } from \"./providers/types.js\";\nimport type { ChatMessage, ChatRequestOptions, RawUsage, ToolCall, ToolSpec } from \"./types.js\";\n\nexport class Usage {\n constructor(\n public promptTokens = 0,\n public completionTokens = 0,\n public totalTokens = 0,\n public promptCacheHitTokens = 0,\n public promptCacheMissTokens = 0,\n ) {}\n\n get cacheHitRatio(): number {\n const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;\n return denom > 0 ? this.promptCacheHitTokens / denom : 0;\n }\n\n static fromApi(raw: RawUsage | undefined | null): Usage {\n const u = raw ?? {};\n return new Usage(\n u.prompt_tokens ?? 0,\n u.completion_tokens ?? 0,\n u.total_tokens ?? 0,\n u.prompt_cache_hit_tokens ?? 0,\n u.prompt_cache_miss_tokens ?? 0,\n );\n }\n}\n\nexport interface ChatResponse {\n content: string;\n reasoningContent: string | null;\n toolCalls: ToolCall[];\n usage: Usage;\n raw: unknown;\n}\n\nexport interface StreamChunk {\n contentDelta?: string;\n reasoningDelta?: string;\n toolCallDelta?: { index: number; id?: string; name?: string; argumentsDelta?: string };\n usage?: Usage;\n finishReason?: string;\n raw: any;\n}\n\nexport interface BalanceInfo {\n currency: string;\n total_balance: string;\n granted_balance?: string;\n topped_up_balance?: string;\n}\n\nexport interface UserBalance {\n is_available: boolean;\n balance_infos: BalanceInfo[];\n}\n\n/** Largest `total_balance` wins — the wallet the user actually paid for and expects to see ticking down. */\nexport function pickPrimaryBalance(infos: ReadonlyArray<BalanceInfo>): BalanceInfo | null {\n if (infos.length === 0) return null;\n let best = infos[0]!;\n for (let i = 1; i < infos.length; i++) {\n if (Number(infos[i]!.total_balance) > Number(best.total_balance)) best = infos[i]!;\n }\n return best;\n}\n\nexport interface ModelInfo {\n id: string;\n object: \"model\";\n owned_by: string;\n}\n\nexport interface ModelList {\n object: \"list\";\n data: ModelInfo[];\n}\n\n// ---------------------------------------------------------------------------\n// OpenAICompatClient — provider-agnostic chat client\n// ---------------------------------------------------------------------------\n\nexport interface OpenAICompatClientOptions {\n /**\n * API key. Falls back to the provider's env var, then to\n * the legacy `DEEPSEEK_API_KEY` env var for backwards compat.\n */\n apiKey?: string;\n /**\n * Base URL override. Falls back to the provider's endpoint,\n * then to the legacy `DEEPSEEK_BASE_URL` env var.\n */\n baseUrl?: string;\n /** Request timeout in ms. Default: 660_000 (11 min). */\n timeoutMs?: number;\n /** Custom fetch implementation. */\n fetch?: typeof fetch;\n /** Retry configuration. `{ maxAttempts: 1 }` disables retries. */\n retry?: RetryOptions;\n /**\n * Provider profile. If not given, we resolve it from the\n * default model or fall back to the DeepSeek profile.\n */\n provider?: ProviderProfile;\n /**\n * If you know the model id upfront, we'll auto-resolve the\n * provider. Only used when `provider` is not passed.\n */\n model?: string;\n}\n\n/**\n * Generic OpenAI-compatible chat client.\n *\n * Works with any provider that speaks `/chat/completions` with\n * Bearer-auth and SSE streaming (DeepSeek, SiliconFlow, ModelScope,\n * Zhipu, Moonshot, Qwen, OpenAI, Groq, OpenRouter, etc.).\n *\n * For Anthropic's non-OpenAI-compatible Messages API, a separate\n * adapter will be needed.\n */\nexport class OpenAICompatClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly retry: RetryOptions;\n readonly provider: ProviderProfile;\n private readonly _fetch: typeof fetch;\n\n constructor(opts: OpenAICompatClientOptions = {}) {\n // Resolve the provider profile.\n let provider: ProviderProfile | undefined = opts.provider;\n if (!provider && opts.model) {\n provider = resolveProvider(opts.model);\n }\n if (!provider) {\n provider = defaultProvider();\n }\n this.provider = provider;\n\n // API key resolution order:\n // 1. Explicit `apiKey` option\n // 2. Provider-specific env var (e.g. SILICONFLOW_API_KEY)\n // 3. Legacy DEEPSEEK_API_KEY (backwards compat)\n const apiKey =\n opts.apiKey ??\n process.env[provider.auth.envKey] ??\n process.env.DEEPSEEK_API_KEY;\n\n if (!apiKey) {\n throw new Error(\n `${provider.auth.envKey} is not set. Put it in .env or pass apiKey to the client.`,\n );\n }\n this.apiKey = apiKey;\n\n // Base URL resolution:\n // 1. Explicit `baseUrl` option\n // 2. Legacy DEEPSEEK_BASE_URL env var (backwards compat)\n // 3. Provider's default chat endpoint\n let url = opts.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? provider.endpoints.chat;\n while (url.endsWith(\"/\")) url = url.slice(0, -1);\n this.baseUrl = url;\n\n this.timeoutMs = opts.timeoutMs ?? 660_000;\n this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);\n this.retry = opts.retry ?? {};\n }\n\n // -----------------------------------------------------------------------\n // Auth header construction\n // -----------------------------------------------------------------------\n\n private authHeader(): Record<string, string> {\n const auth = this.provider.auth;\n const header = auth.header ?? \"Authorization\";\n const prefix = auth.prefix ?? \"Bearer \";\n const value = prefix ? `${prefix}${this.apiKey}` : this.apiKey;\n\n const headers: Record<string, string> = { [header]: value };\n\n // Extra provider-level headers (e.g. Anthropic version, OpenRouter referer).\n if (this.provider.extraHeaders) {\n for (const [k, v] of Object.entries(this.provider.extraHeaders)) {\n headers[k] = v;\n }\n }\n\n return headers;\n }\n\n // -----------------------------------------------------------------------\n // Payload construction — respects the provider's thinking transport\n // -----------------------------------------------------------------------\n\n private buildPayload(opts: ChatRequestOptions, stream: boolean): Record<string, unknown> {\n const payload: Record<string, unknown> = {\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.tools?.length) payload.tools = opts.tools;\n if (opts.temperature !== undefined) payload.temperature = opts.temperature;\n if (opts.maxTokens !== undefined) payload.max_tokens = opts.maxTokens;\n if (opts.responseFormat) payload.response_format = opts.responseFormat;\n\n // Thinking / reasoning mode injection.\n // Only add thinking fields when the model is known to support it,\n // OR when the toggle is explicitly \"disabled\" (deepseek-chat needs\n // thinking=disabled to opt out of the default).\n const thinking = this.provider.thinking;\n const modelSupportsThinking =\n !thinking.thinkingModels || thinking.thinkingModels.includes(opts.model);\n\n if (\n thinking.transport === \"extra_body\" &&\n opts.thinking &&\n (modelSupportsThinking || opts.thinking === \"disabled\")\n ) {\n // DeepSeek-style: extra_body.thinking.type\n payload.extra_body = {\n ...((payload.extra_body as Record<string, unknown>) ?? {}),\n thinking: { type: opts.thinking },\n };\n }\n if (thinking.transport === \"reasoning_effort\" && opts.reasoningEffort && modelSupportsThinking) {\n // OpenAI-style: top-level reasoning_effort\n payload.reasoning_effort = opts.reasoningEffort;\n }\n if (thinking.transport === \"include\" && opts.thinking === \"enabled\" && modelSupportsThinking) {\n // Anthropic-style: thinking: { type: \"enabled\", budget_tokens: 16000 }\n payload.thinking = { type: \"enabled\", budget_tokens: 16000 };\n }\n\n // DeepSeek reasoning_effort also works through extra_body.\n if (opts.reasoningEffort && thinking.transport !== \"reasoning_effort\") {\n payload.reasoning_effort = opts.reasoningEffort;\n }\n\n // Merge provider-level extra body fields.\n if (this.provider.extraBody) {\n for (const [k, v] of Object.entries(this.provider.extraBody)) {\n if (!(k in payload)) payload[k] = v;\n }\n }\n\n return payload;\n }\n\n // -----------------------------------------------------------------------\n // Balance\n // -----------------------------------------------------------------------\n\n /** Returns null on failure so callers can degrade — session must keep working without balance UI. */\n async getBalance(opts: { signal?: AbortSignal } = {}): Promise<UserBalance | null> {\n const ep = this.provider.endpoints.balance;\n if (!ep) return null;\n\n try {\n const resp = await this._fetch(`${this.baseUrl}${ep}`, {\n method: \"GET\",\n headers: this.authHeader(),\n signal: opts.signal,\n });\n if (!resp.ok) return null;\n const data = (await resp.json()) as UserBalance;\n if (!data || !Array.isArray(data.balance_infos)) return null;\n return data;\n } catch {\n return null;\n }\n }\n\n // -----------------------------------------------------------------------\n // Model listing\n // -----------------------------------------------------------------------\n\n /** Returns null on failure — callers fall back to the provider's static model list. */\n async listModels(opts: { signal?: AbortSignal } = {}): Promise<ModelList | null> {\n const ep = this.provider.endpoints.models;\n if (!ep) return null;\n\n try {\n const resp = await this._fetch(`${this.baseUrl}${ep}`, {\n method: \"GET\",\n headers: this.authHeader(),\n signal: opts.signal,\n });\n if (!resp.ok) return null;\n const data = (await resp.json()) as ModelList;\n if (!data || !Array.isArray(data.data)) return null;\n return data;\n } catch {\n return null;\n }\n }\n\n // -----------------------------------------------------------------------\n // Chat (non-streaming)\n // -----------------------------------------------------------------------\n\n async chat(opts: ChatRequestOptions): Promise<ChatResponse> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n try {\n const resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n ...this.authHeader(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(this.buildPayload(opts, false)),\n signal,\n },\n { ...this.retry, signal },\n );\n if (!resp.ok) {\n throw new Error(\n `${this.provider.label} ${resp.status}: ${await resp.text()}`,\n );\n }\n const data: any = await resp.json();\n const choice = data.choices?.[0]?.message ?? {};\n return {\n content: choice.content ?? \"\",\n reasoningContent: choice.reasoning_content ?? null,\n toolCalls: choice.tool_calls ?? [],\n usage: Usage.fromApi(data.usage),\n raw: data,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n // -----------------------------------------------------------------------\n // Chat (streaming)\n // -----------------------------------------------------------------------\n\n async *stream(opts: ChatRequestOptions): AsyncGenerator<StreamChunk> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n let resp: Response;\n try {\n resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n ...this.authHeader(),\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify(this.buildPayload(opts, true)),\n signal,\n },\n { ...this.retry, signal },\n );\n } catch (err) {\n clearTimeout(timer);\n throw err;\n }\n if (!resp.ok || !resp.body) {\n clearTimeout(timer);\n throw new Error(\n `${this.provider.label} ${resp.status}: ${await resp.text().catch(() => \"\")}`,\n );\n }\n\n const queue: StreamChunk[] = [];\n let done = false;\n const parser = createParser({\n onEvent: (ev: EventSourceMessage) => {\n if (!ev.data || ev.data === \"[DONE]\") {\n done = true;\n return;\n }\n try {\n const json = JSON.parse(ev.data);\n const delta = json.choices?.[0]?.delta ?? {};\n const finishReason = json.choices?.[0]?.finish_reason ?? undefined;\n const chunk: StreamChunk = { raw: json, finishReason };\n if (typeof delta.content === \"string\" && delta.content.length > 0) {\n chunk.contentDelta = delta.content;\n }\n if (\n typeof delta.reasoning_content === \"string\" &&\n delta.reasoning_content.length > 0\n ) {\n chunk.reasoningDelta = delta.reasoning_content;\n }\n if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {\n const tc = delta.tool_calls[0];\n chunk.toolCallDelta = {\n index: tc.index ?? 0,\n id: tc.id,\n name: tc.function?.name,\n argumentsDelta: tc.function?.arguments,\n };\n }\n if (json.usage) {\n chunk.usage = Usage.fromApi(json.usage);\n }\n queue.push(chunk);\n } catch {\n /* skip malformed sse frame */\n }\n },\n });\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n try {\n while (true) {\n if (queue.length > 0) {\n yield queue.shift()!;\n continue;\n }\n if (done) break;\n const { value, done: streamDone } = await reader.read();\n if (streamDone) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n while (queue.length > 0) yield queue.shift()!;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Backwards-compatible DeepSeekClient alias\n// ---------------------------------------------------------------------------\n\nexport interface DeepSeekClientOptions {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n retry?: RetryOptions;\n}\n\n/**\n * @deprecated Use `OpenAICompatClient` with a provider profile instead.\n * Kept for backwards compatibility — all existing code that constructs\n * `new DeepSeekClient()` continues to work.\n */\nexport class DeepSeekClient extends OpenAICompatClient {\n constructor(opts: DeepSeekClientOptions = {}) {\n // Preserve the old error message for backwards compat.\n const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient.\",\n );\n }\n super({\n apiKey,\n baseUrl: opts.baseUrl,\n timeoutMs: opts.timeoutMs,\n fetch: opts.fetch,\n retry: opts.retry,\n provider: defaultProvider(),\n });\n }\n}\n\nexport type { ChatMessage, ToolCall, ToolSpec };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,621 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
3
+ import {
4
+ useColor
5
+ } from "./chunk-JFGLMRZ6.js";
6
+ import {
7
+ Box_default,
8
+ Text,
9
+ require_react,
10
+ use_input_default
11
+ } from "./chunk-WREKDFXT.js";
12
+ import {
13
+ __toESM
14
+ } from "./chunk-TUK7OWJA.js";
15
+
16
+ // src/cli/ui/keystroke-context.tsx
17
+ var import_react = __toESM(require_react(), 1);
18
+
19
+ // src/cli/ui/stdin-reader.ts
20
+ import { stdin } from "process";
21
+ var ESC_TIMEOUT_MS = 250;
22
+ var PASTE_START = "\x1B[200~";
23
+ var PASTE_END = "\x1B[201~";
24
+ var PASTE_START_BARE = "[200~";
25
+ var PASTE_END_BARE = "[201~";
26
+ var CSI_TAIL_MAP = [
27
+ { tail: "A", ev: { input: "", upArrow: true } },
28
+ { tail: "B", ev: { input: "", downArrow: true } },
29
+ { tail: "C", ev: { input: "", rightArrow: true } },
30
+ { tail: "D", ev: { input: "", leftArrow: true } },
31
+ { tail: "H", ev: { input: "", home: true } },
32
+ { tail: "F", ev: { input: "", end: true } },
33
+ { tail: "1~", ev: { input: "", home: true } },
34
+ { tail: "4~", ev: { input: "", end: true } },
35
+ { tail: "5~", ev: { input: "", pageUp: true } },
36
+ { tail: "6~", ev: { input: "", pageDown: true } },
37
+ { tail: "3~", ev: { input: "", delete: true } },
38
+ { tail: "Z", ev: { input: "", shift: true, tab: true } },
39
+ // Some Windows hosts (PowerShell 7.x conhost path) emit the
40
+ // modifier-encoded back-tab `\x1b[1;2Z` instead of bare `\x1b[Z`.
41
+ // Issue #373 — without this entry Shift+Tab is silently dropped.
42
+ { tail: "1;2Z", ev: { input: "", shift: true, tab: true } },
43
+ // modifyOtherKeys (xterm CSI > 4 ; 2 m) sequences for Enter / Tab
44
+ // with modifiers. Only fired when App.tsx has enabled the mode at
45
+ // startup; otherwise Shift+Enter stays indistinguishable from Enter.
46
+ // Modifier encoding: 2=shift, 3=alt, 4=alt+shift, 5=ctrl,
47
+ // 6=ctrl+shift, 7=ctrl+alt, 8=ctrl+alt+shift. Keycodes: 9=Tab, 13=Enter.
48
+ { tail: "27;2;9~", ev: { input: "", tab: true, shift: true } },
49
+ { tail: "27;2;13~", ev: { input: "", return: true, shift: true } },
50
+ { tail: "27;5;13~", ev: { input: "", return: true, ctrl: true } },
51
+ { tail: "27;6;13~", ev: { input: "", return: true, ctrl: true, shift: true } },
52
+ // Kitty keyboard protocol — same idea, different envelope:
53
+ // `\x1b[<keycode>;<mod>u`. Some terminals (kitty, recent Windows
54
+ // Terminal previews) prefer this shape. Harmless to map here too.
55
+ { tail: "9;2u", ev: { input: "", tab: true, shift: true } },
56
+ { tail: "13;2u", ev: { input: "", return: true, shift: true } },
57
+ { tail: "13;5u", ev: { input: "", return: true, ctrl: true } },
58
+ { tail: "13;6u", ev: { input: "", return: true, ctrl: true, shift: true } }
59
+ ];
60
+ var SS3_MAP = {
61
+ A: { input: "", upArrow: true },
62
+ B: { input: "", downArrow: true },
63
+ C: { input: "", rightArrow: true },
64
+ D: { input: "", leftArrow: true },
65
+ H: { input: "", home: true },
66
+ F: { input: "", end: true }
67
+ };
68
+ function tryEscapelessCsi(chunk, i) {
69
+ if (chunk[i] !== "[") return null;
70
+ for (const entry of CSI_TAIL_MAP) {
71
+ const candidate = `[${entry.tail}`;
72
+ if (chunk.slice(i, i + candidate.length) === candidate) {
73
+ return { advance: candidate.length, ev: entry.ev };
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ function isCsiFinal(ch) {
79
+ const code = ch.charCodeAt(0);
80
+ return code >= 64 && code <= 126;
81
+ }
82
+ function lookupCsi(tail) {
83
+ for (const entry of CSI_TAIL_MAP) {
84
+ if (entry.tail === tail) return entry.ev;
85
+ }
86
+ return null;
87
+ }
88
+ function decodeModifiedKey(cp, mod) {
89
+ if (mod < 1 || mod > 8) return null;
90
+ const bits = mod - 1;
91
+ const shift = (bits & 1) !== 0;
92
+ const alt = (bits & 2) !== 0;
93
+ const ctrl = (bits & 4) !== 0;
94
+ if (cp >= 32 && cp <= 126 && !ctrl && !alt) {
95
+ const ev = { input: String.fromCharCode(cp) };
96
+ if (shift) ev.shift = true;
97
+ return ev;
98
+ }
99
+ if (cp >= 32 && cp <= 126 && alt && !ctrl) {
100
+ const ev = { input: String.fromCharCode(cp), meta: true };
101
+ if (shift) ev.shift = true;
102
+ return ev;
103
+ }
104
+ if (cp >= 65 && cp <= 122 && ctrl && !alt) {
105
+ const ev = { input: String.fromCharCode(cp).toLowerCase(), ctrl: true };
106
+ if (shift) ev.shift = true;
107
+ return ev;
108
+ }
109
+ return null;
110
+ }
111
+ function tryDecodeGenericCsi(seq) {
112
+ let m = /^27;(\d+);(\d+)~$/.exec(seq);
113
+ if (m) return decodeModifiedKey(Number.parseInt(m[2], 10), Number.parseInt(m[1], 10));
114
+ m = /^(\d+);(\d+)u$/.exec(seq);
115
+ if (m) return decodeModifiedKey(Number.parseInt(m[1], 10), Number.parseInt(m[2], 10));
116
+ m = /^(\d+)u$/.exec(seq);
117
+ if (m) return decodeModifiedKey(Number.parseInt(m[1], 10), 1);
118
+ return null;
119
+ }
120
+ function looksLikeUnbracketedPaste(chunk) {
121
+ if (chunk.length < 2) return false;
122
+ if (chunk.includes(PASTE_START) || chunk.includes(PASTE_START_BARE)) return false;
123
+ if (chunk.includes(PASTE_END) || chunk.includes(PASTE_END_BARE)) return false;
124
+ if (chunk.includes("\x1B")) return false;
125
+ const norm = chunk.replace(/\r\n/g, "\n");
126
+ if (norm === "\r" || norm === "\n") return false;
127
+ let breaks = 0;
128
+ let firstBreakIdx = -1;
129
+ for (let i = 0; i < norm.length; i++) {
130
+ const c = norm[i];
131
+ if (c === "\r" || c === "\n") {
132
+ if (firstBreakIdx < 0) firstBreakIdx = i;
133
+ breaks++;
134
+ }
135
+ }
136
+ if (breaks >= 2) return true;
137
+ if (breaks === 1) return firstBreakIdx > 0 && firstBreakIdx < norm.length - 1;
138
+ return false;
139
+ }
140
+ var StdinReader = class {
141
+ subscribers = /* @__PURE__ */ new Set();
142
+ state = "idle";
143
+ /** Buffer for partial sequences across chunks. */
144
+ csiBuf = "";
145
+ /** Buffer for paste content. */
146
+ pasteBuf = "";
147
+ escTimer = null;
148
+ // Deferred-dispatch handle paired with `escTimer`. The timer
149
+ // queues an Immediate that runs in the event loop's CHECK phase —
150
+ // i.e. AFTER the POLL phase where stdin 'data' events fire — so
151
+ // a multi-byte sequence whose chunks queued up while the loop was
152
+ // blocked (heavy render, etc.) gets a chance to be processed
153
+ // BEFORE we emit a bogus standalone-Esc. Fixes the "I didn't press
154
+ // Esc but it aborted the turn" class of bug: previously the timer's
155
+ // setTimeout callback ran in the timers phase ahead of poll, so a
156
+ // split sequence like `\x1b` + `[A` would dispatch escape+upArrow
157
+ // even though the user only pressed Up.
158
+ escImmediate = null;
159
+ started = false;
160
+ /** The actual `data` listener — kept as a field so `stop()` can detach it. */
161
+ listener = null;
162
+ start() {
163
+ if (this.started) return;
164
+ try {
165
+ stdin.setRawMode(true);
166
+ } catch {
167
+ return;
168
+ }
169
+ stdin.setEncoding("utf8");
170
+ stdin.resume();
171
+ this.listener = (chunk) => this.handleChunk(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
172
+ stdin.on("data", this.listener);
173
+ this.started = true;
174
+ }
175
+ stop() {
176
+ if (!this.started) return;
177
+ if (this.listener) {
178
+ stdin.off("data", this.listener);
179
+ this.listener = null;
180
+ }
181
+ try {
182
+ stdin.setRawMode(false);
183
+ } catch {
184
+ }
185
+ stdin.pause();
186
+ this.cancelEscTimer();
187
+ this.state = "idle";
188
+ this.csiBuf = "";
189
+ this.pasteBuf = "";
190
+ this.started = false;
191
+ }
192
+ subscribe(fn) {
193
+ this.subscribers.add(fn);
194
+ return () => {
195
+ this.subscribers.delete(fn);
196
+ };
197
+ }
198
+ /** Test seam — drives the parser without a real TTY. */
199
+ feed(chunk) {
200
+ this.handleChunk(chunk);
201
+ }
202
+ dispatch(ev) {
203
+ for (const sub of this.subscribers) sub(ev);
204
+ }
205
+ cancelEscTimer() {
206
+ if (this.escTimer) {
207
+ clearTimeout(this.escTimer);
208
+ this.escTimer = null;
209
+ }
210
+ if (this.escImmediate) {
211
+ clearImmediate(this.escImmediate);
212
+ this.escImmediate = null;
213
+ }
214
+ }
215
+ scheduleEscTimer() {
216
+ this.cancelEscTimer();
217
+ this.escTimer = setTimeout(() => {
218
+ this.escTimer = null;
219
+ this.escImmediate = setImmediate(() => {
220
+ this.escImmediate = null;
221
+ if (this.state === "esc") {
222
+ this.state = "idle";
223
+ this.dispatch({ input: "", escape: true });
224
+ }
225
+ });
226
+ }, ESC_TIMEOUT_MS);
227
+ }
228
+ handleChunk(rawChunk) {
229
+ this.cancelEscTimer();
230
+ const chunk = this.state === "idle" && looksLikeUnbracketedPaste(rawChunk) ? PASTE_START + rawChunk + PASTE_END : rawChunk;
231
+ let i = 0;
232
+ while (i < chunk.length) {
233
+ if (this.state === "paste") {
234
+ const endA = chunk.indexOf(PASTE_END, i);
235
+ const endB = chunk.indexOf(PASTE_END_BARE, i);
236
+ let endIdx = -1;
237
+ let endLen = 0;
238
+ if (endA !== -1 && (endB === -1 || endA <= endB)) {
239
+ endIdx = endA;
240
+ endLen = PASTE_END.length;
241
+ } else if (endB !== -1) {
242
+ endIdx = endB;
243
+ endLen = PASTE_END_BARE.length;
244
+ }
245
+ if (endIdx === -1) {
246
+ this.pasteBuf += chunk.slice(i);
247
+ i = chunk.length;
248
+ break;
249
+ }
250
+ this.pasteBuf += chunk.slice(i, endIdx);
251
+ this.dispatch({ input: this.pasteBuf, paste: true });
252
+ this.pasteBuf = "";
253
+ this.state = "idle";
254
+ i = endIdx + endLen;
255
+ continue;
256
+ }
257
+ if (this.state === "csi") {
258
+ const ch2 = chunk[i];
259
+ this.csiBuf += ch2;
260
+ if (isCsiFinal(ch2)) {
261
+ this.dispatchCsi(this.csiBuf);
262
+ this.csiBuf = "";
263
+ if (this.state === "csi") this.state = "idle";
264
+ }
265
+ i++;
266
+ continue;
267
+ }
268
+ if (this.state === "ss3") {
269
+ const ev = SS3_MAP[chunk[i]];
270
+ if (ev) this.dispatch(ev);
271
+ this.state = "idle";
272
+ i++;
273
+ continue;
274
+ }
275
+ if (this.state === "esc") {
276
+ const ch2 = chunk[i];
277
+ if (ch2 === "[") {
278
+ this.state = "csi";
279
+ this.csiBuf = "";
280
+ i++;
281
+ continue;
282
+ }
283
+ if (ch2 === "O") {
284
+ this.state = "ss3";
285
+ i++;
286
+ continue;
287
+ }
288
+ if (ch2 === "\r" || ch2 === "\n") {
289
+ this.dispatch({ input: "", return: true, meta: true });
290
+ this.state = "idle";
291
+ i++;
292
+ continue;
293
+ }
294
+ this.dispatch({ input: ch2, meta: true });
295
+ this.state = "idle";
296
+ i++;
297
+ continue;
298
+ }
299
+ const ch = chunk[i];
300
+ if (ch === "\x1B") {
301
+ this.state = "esc";
302
+ i++;
303
+ continue;
304
+ }
305
+ if (chunk.slice(i, i + PASTE_START_BARE.length) === PASTE_START_BARE) {
306
+ this.state = "paste";
307
+ this.pasteBuf = "";
308
+ i += PASTE_START_BARE.length;
309
+ continue;
310
+ }
311
+ const escapeless = tryEscapelessCsi(chunk, i);
312
+ if (escapeless) {
313
+ this.dispatch(escapeless.ev);
314
+ i += escapeless.advance;
315
+ continue;
316
+ }
317
+ if (ch === "\r") {
318
+ this.dispatch({ input: "", return: true });
319
+ i++;
320
+ continue;
321
+ }
322
+ if (ch === "\n") {
323
+ this.dispatch({ input: "j", ctrl: true });
324
+ i++;
325
+ continue;
326
+ }
327
+ if (ch === " ") {
328
+ this.dispatch({ input: "", tab: true });
329
+ i++;
330
+ continue;
331
+ }
332
+ if (ch === "\x7F" || ch === "\b") {
333
+ this.dispatch({ input: "", backspace: true });
334
+ i++;
335
+ continue;
336
+ }
337
+ if (ch === "") {
338
+ this.dispatch({ input: "c", ctrl: true });
339
+ i++;
340
+ continue;
341
+ }
342
+ const code = ch.charCodeAt(0);
343
+ if (code >= 1 && code <= 26) {
344
+ const letter = String.fromCharCode(96 + code);
345
+ this.dispatch({ input: letter, ctrl: true });
346
+ i++;
347
+ continue;
348
+ }
349
+ let end = i + 1;
350
+ while (end < chunk.length) {
351
+ const c = chunk[end];
352
+ if (c === "\x1B" || c === "\r" || c === "\n" || c === " ") break;
353
+ if (c === "\x7F" || c === "\b" || c === "") break;
354
+ const cc = c.charCodeAt(0);
355
+ if (cc >= 1 && cc <= 26) break;
356
+ if (c === "[" && tryEscapelessCsi(chunk, end)) break;
357
+ if (chunk.slice(end, end + PASTE_START_BARE.length) === PASTE_START_BARE) break;
358
+ end++;
359
+ }
360
+ this.dispatch({ input: chunk.slice(i, end) });
361
+ i = end;
362
+ }
363
+ if (this.state === "esc") {
364
+ this.scheduleEscTimer();
365
+ }
366
+ }
367
+ dispatchCsi(seq) {
368
+ if (seq === "200~") {
369
+ this.state = "paste";
370
+ this.pasteBuf = "";
371
+ return;
372
+ }
373
+ if (seq === "201~") {
374
+ return;
375
+ }
376
+ if (seq.length > 1 && seq.charCodeAt(0) === 60) {
377
+ const tail = seq[seq.length - 1];
378
+ if (tail === "M" || tail === "m") {
379
+ const body = seq.slice(1, -1);
380
+ const parts = body.split(";");
381
+ if (parts.length === 3) {
382
+ const btn = Number.parseInt(parts[0], 10);
383
+ const col = Number.parseInt(parts[1], 10);
384
+ const row = Number.parseInt(parts[2], 10);
385
+ if (Number.isFinite(btn) && Number.isFinite(col) && Number.isFinite(row)) {
386
+ if (tail === "M" && btn === 64) {
387
+ this.dispatch({ input: "", mouseScrollUp: true, mouseRow: row, mouseCol: col });
388
+ return;
389
+ }
390
+ if (tail === "M" && btn === 65) {
391
+ this.dispatch({ input: "", mouseScrollDown: true, mouseRow: row, mouseCol: col });
392
+ return;
393
+ }
394
+ if (tail === "M" && btn === 0) {
395
+ this.dispatch({ input: "", mouseClick: true, mouseRow: row, mouseCol: col });
396
+ return;
397
+ }
398
+ if (tail === "M" && btn === 32) {
399
+ this.dispatch({ input: "", mouseDrag: true, mouseRow: row, mouseCol: col });
400
+ return;
401
+ }
402
+ if (tail === "m") {
403
+ this.dispatch({ input: "", mouseRelease: true, mouseRow: row, mouseCol: col });
404
+ return;
405
+ }
406
+ return;
407
+ }
408
+ }
409
+ }
410
+ }
411
+ const ev = lookupCsi(seq);
412
+ if (ev) {
413
+ this.dispatch(ev);
414
+ return;
415
+ }
416
+ const generic = tryDecodeGenericCsi(seq);
417
+ if (generic) {
418
+ this.dispatch(generic);
419
+ return;
420
+ }
421
+ }
422
+ };
423
+ var singleton = null;
424
+ function getStdinReader() {
425
+ if (!singleton) singleton = new StdinReader();
426
+ return singleton;
427
+ }
428
+
429
+ // src/cli/ui/keystroke-context.tsx
430
+ var KeystrokeContext = (0, import_react.createContext)(null);
431
+ function KeystrokeProvider({
432
+ children,
433
+ reader: providedReader
434
+ }) {
435
+ const handlersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
436
+ const busRef = (0, import_react.useRef)(null);
437
+ if (busRef.current === null) {
438
+ busRef.current = {
439
+ subscribe(handler) {
440
+ handlersRef.current.add(handler);
441
+ return () => {
442
+ handlersRef.current.delete(handler);
443
+ };
444
+ }
445
+ };
446
+ }
447
+ (0, import_react.useEffect)(() => {
448
+ const reader = providedReader ?? getStdinReader();
449
+ reader.start();
450
+ const unsubscribe = reader.subscribe((ev) => {
451
+ for (const fn of [...handlersRef.current]) fn(ev);
452
+ });
453
+ return () => {
454
+ unsubscribe();
455
+ };
456
+ }, [providedReader]);
457
+ return /* @__PURE__ */ import_react.default.createElement(KeystrokeContext.Provider, { value: busRef.current }, children);
458
+ }
459
+ function useKeystroke(handler, isActive = true) {
460
+ const bus = (0, import_react.useContext)(KeystrokeContext);
461
+ const handlerRef = (0, import_react.useRef)(handler);
462
+ handlerRef.current = handler;
463
+ (0, import_react.useEffect)(() => {
464
+ if (!bus || !isActive) return void 0;
465
+ return bus.subscribe((ev) => handlerRef.current(ev));
466
+ }, [bus, isActive]);
467
+ use_input_default(
468
+ (input, key) => {
469
+ if (bus) return;
470
+ handlerRef.current({
471
+ input,
472
+ upArrow: key.upArrow,
473
+ downArrow: key.downArrow,
474
+ leftArrow: key.leftArrow,
475
+ rightArrow: key.rightArrow,
476
+ return: key.return,
477
+ escape: key.escape,
478
+ backspace: key.backspace,
479
+ delete: key.delete,
480
+ tab: key.tab,
481
+ shift: key.shift,
482
+ ctrl: key.ctrl,
483
+ meta: key.meta,
484
+ pageUp: key.pageUp,
485
+ pageDown: key.pageDown
486
+ });
487
+ },
488
+ { isActive: !bus && isActive }
489
+ );
490
+ }
491
+
492
+ // src/cli/ui/Select.tsx
493
+ var import_react2 = __toESM(require_react(), 1);
494
+ function SingleSelect({
495
+ items,
496
+ initialValue,
497
+ onSubmit,
498
+ onTab,
499
+ onCancel,
500
+ footer,
501
+ inlineHints = false,
502
+ ignoreKey
503
+ }) {
504
+ const color = useColor();
505
+ const initialIndex = Math.max(
506
+ 0,
507
+ items.findIndex((i) => i.value === initialValue && !i.disabled)
508
+ );
509
+ const [index, setIndex] = (0, import_react2.useState)(initialIndex === -1 ? 0 : initialIndex);
510
+ useKeystroke((ev) => {
511
+ if (ev.paste || ignoreKey?.(ev)) return;
512
+ if (ev.upArrow) {
513
+ setIndex((i) => findNextEnabled(items, i, -1));
514
+ } else if (ev.downArrow) {
515
+ setIndex((i) => findNextEnabled(items, i, 1));
516
+ } else if (ev.return) {
517
+ const chosen = items[index];
518
+ if (chosen && !chosen.disabled) onSubmit(chosen.value);
519
+ } else if (ev.tab) {
520
+ const chosen = items[index];
521
+ if (chosen && !chosen.disabled) onTab?.(chosen.value);
522
+ } else if (ev.escape && onCancel) {
523
+ onCancel();
524
+ }
525
+ });
526
+ return /* @__PURE__ */ import_react2.default.createElement(Box_default, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ import_react2.default.createElement(
527
+ SelectRow,
528
+ {
529
+ key: item.value,
530
+ item,
531
+ active: i === index,
532
+ marker: i === index ? "\u25B8" : " ",
533
+ color,
534
+ inlineHint: inlineHints
535
+ }
536
+ )), footer ? /* @__PURE__ */ import_react2.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react2.default.createElement(Text, { dimColor: true }, footer)) : null);
537
+ }
538
+ function MultiSelect({
539
+ items,
540
+ initialSelected = [],
541
+ onSubmit,
542
+ onCancel,
543
+ footer,
544
+ inlineHints = false,
545
+ ignoreKey
546
+ }) {
547
+ const color = useColor();
548
+ const [index, setIndex] = (0, import_react2.useState)(() => {
549
+ const first = items.findIndex((i) => !i.disabled);
550
+ return first === -1 ? 0 : first;
551
+ });
552
+ const [selected, setSelected] = (0, import_react2.useState)(new Set(initialSelected));
553
+ useKeystroke((ev) => {
554
+ if (ev.paste || ignoreKey?.(ev)) return;
555
+ if (ev.upArrow) {
556
+ setIndex((i) => findNextEnabled(items, i, -1));
557
+ } else if (ev.downArrow) {
558
+ setIndex((i) => findNextEnabled(items, i, 1));
559
+ } else if (ev.input === " ") {
560
+ const item = items[index];
561
+ if (!item || item.disabled) return;
562
+ setSelected((prev) => {
563
+ const next = new Set(prev);
564
+ if (next.has(item.value)) next.delete(item.value);
565
+ else next.add(item.value);
566
+ return next;
567
+ });
568
+ } else if (ev.return) {
569
+ const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
570
+ onSubmit(ordered);
571
+ } else if (ev.escape && onCancel) {
572
+ onCancel();
573
+ }
574
+ });
575
+ return /* @__PURE__ */ import_react2.default.createElement(Box_default, { flexDirection: "column" }, items.map((item, i) => {
576
+ const checked = selected.has(item.value);
577
+ const marker = checked ? "[x]" : "[ ]";
578
+ return /* @__PURE__ */ import_react2.default.createElement(
579
+ SelectRow,
580
+ {
581
+ key: item.value,
582
+ item,
583
+ active: i === index,
584
+ marker: `${i === index ? "\u25B8" : " "} ${marker}`,
585
+ color,
586
+ inlineHint: inlineHints
587
+ }
588
+ );
589
+ }), footer ? /* @__PURE__ */ import_react2.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react2.default.createElement(Text, { dimColor: true }, footer)) : null);
590
+ }
591
+ function SelectRow({
592
+ item,
593
+ active,
594
+ marker,
595
+ color,
596
+ inlineHint = false
597
+ }) {
598
+ const rowColor = item.disabled ? color.info : active ? color.primary : void 0;
599
+ const labelText = `${marker} ${item.label}`;
600
+ if (inlineHint) {
601
+ return /* @__PURE__ */ import_react2.default.createElement(Box_default, { flexDirection: "row", flexWrap: "nowrap", minHeight: 1 }, /* @__PURE__ */ import_react2.default.createElement(Text, { color: rowColor, bold: active, dimColor: item.disabled, wrap: "truncate" }, labelText), item.hint ? /* @__PURE__ */ import_react2.default.createElement(Text, { dimColor: true, wrap: "truncate" }, ` ${item.hint}`) : null);
602
+ }
603
+ return /* @__PURE__ */ import_react2.default.createElement(Box_default, { flexDirection: "column" }, /* @__PURE__ */ import_react2.default.createElement(Box_default, null, /* @__PURE__ */ import_react2.default.createElement(Text, { color: rowColor, bold: active, dimColor: item.disabled }, labelText)), item.hint ? /* @__PURE__ */ import_react2.default.createElement(Box_default, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ import_react2.default.createElement(Text, { dimColor: true }, item.hint)) : null);
604
+ }
605
+ function findNextEnabled(items, from, step) {
606
+ if (items.length === 0) return 0;
607
+ let i = from;
608
+ for (let tries = 0; tries < items.length; tries++) {
609
+ i = (i + step + items.length) % items.length;
610
+ if (!items[i]?.disabled) return i;
611
+ }
612
+ return from;
613
+ }
614
+
615
+ export {
616
+ KeystrokeProvider,
617
+ useKeystroke,
618
+ SingleSelect,
619
+ MultiSelect
620
+ };
621
+ //# sourceMappingURL=chunk-ELN3Z3B2.js.map