wyxrouter 0.4.71

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 (763) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSE +21 -0
  3. package/README.md +96 -0
  4. package/README.zh-CN.md +1311 -0
  5. package/assets/pixel-router-2d.png +0 -0
  6. package/cli/LICENSE +42 -0
  7. package/cli/README.md +125 -0
  8. package/cli/app/package.json +58 -0
  9. package/cli/cli.js +806 -0
  10. package/cli/package.json +48 -0
  11. package/i18n/README.ja-JP.md +1209 -0
  12. package/i18n/README.ru.md +1311 -0
  13. package/i18n/README.vi.md +1310 -0
  14. package/i18n/README.zh-CN.md +1306 -0
  15. package/images/9router.png +0 -0
  16. package/jsconfig.json +12 -0
  17. package/next.config.mjs +71 -0
  18. package/open-sse/config/appConstants.js +203 -0
  19. package/open-sse/config/codexInstructions.js +119 -0
  20. package/open-sse/config/constants.js +4 -0
  21. package/open-sse/config/defaultThinkingSignature.js +12 -0
  22. package/open-sse/config/errorConfig.js +85 -0
  23. package/open-sse/config/googleTtsLanguages.js +62 -0
  24. package/open-sse/config/kiroConstants.js +322 -0
  25. package/open-sse/config/models.js +13 -0
  26. package/open-sse/config/ollamaModels.js +19 -0
  27. package/open-sse/config/providerModels.js +944 -0
  28. package/open-sse/config/providers.js +458 -0
  29. package/open-sse/config/runtimeConfig.js +72 -0
  30. package/open-sse/config/ttsModels.js +129 -0
  31. package/open-sse/executors/antigravity.js +504 -0
  32. package/open-sse/executors/azure.js +57 -0
  33. package/open-sse/executors/base.js +185 -0
  34. package/open-sse/executors/codex.js +469 -0
  35. package/open-sse/executors/commandcode.js +88 -0
  36. package/open-sse/executors/cursor.js +795 -0
  37. package/open-sse/executors/default.js +497 -0
  38. package/open-sse/executors/gemini-cli.js +89 -0
  39. package/open-sse/executors/github.js +379 -0
  40. package/open-sse/executors/grok-web.js +345 -0
  41. package/open-sse/executors/iflow.js +108 -0
  42. package/open-sse/executors/index.js +75 -0
  43. package/open-sse/executors/kiro.js +508 -0
  44. package/open-sse/executors/ollama-local.js +14 -0
  45. package/open-sse/executors/opencode-go.js +41 -0
  46. package/open-sse/executors/opencode.js +32 -0
  47. package/open-sse/executors/perplexity-web.js +507 -0
  48. package/open-sse/executors/qoder.js +450 -0
  49. package/open-sse/executors/qwen.js +129 -0
  50. package/open-sse/executors/vertex.js +131 -0
  51. package/open-sse/executors/xiaomi-tokenplan.js +19 -0
  52. package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
  53. package/open-sse/handlers/chatCore/requestDetail.js +102 -0
  54. package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
  55. package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
  56. package/open-sse/handlers/chatCore.js +287 -0
  57. package/open-sse/handlers/embeddingProviders/_base.js +4 -0
  58. package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
  59. package/open-sse/handlers/embeddingProviders/index.js +23 -0
  60. package/open-sse/handlers/embeddingProviders/openai.js +39 -0
  61. package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
  62. package/open-sse/handlers/embeddingsCore.js +126 -0
  63. package/open-sse/handlers/fetch/index.js +237 -0
  64. package/open-sse/handlers/imageGenerationCore.js +189 -0
  65. package/open-sse/handlers/imageProviders/_base.js +31 -0
  66. package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
  67. package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
  68. package/open-sse/handlers/imageProviders/codex.js +198 -0
  69. package/open-sse/handlers/imageProviders/comfyui.js +8 -0
  70. package/open-sse/handlers/imageProviders/falAi.js +41 -0
  71. package/open-sse/handlers/imageProviders/gemini.js +25 -0
  72. package/open-sse/handlers/imageProviders/huggingface.js +22 -0
  73. package/open-sse/handlers/imageProviders/index.js +40 -0
  74. package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
  75. package/open-sse/handlers/imageProviders/openai.js +40 -0
  76. package/open-sse/handlers/imageProviders/runwayml.js +47 -0
  77. package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
  78. package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
  79. package/open-sse/handlers/responsesHandler.js +103 -0
  80. package/open-sse/handlers/search/callers.js +371 -0
  81. package/open-sse/handlers/search/chatSearch.js +409 -0
  82. package/open-sse/handlers/search/index.js +201 -0
  83. package/open-sse/handlers/search/normalizers.js +223 -0
  84. package/open-sse/handlers/sttCore.js +194 -0
  85. package/open-sse/handlers/ttsCore.js +74 -0
  86. package/open-sse/handlers/ttsProviders/_base.js +39 -0
  87. package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
  88. package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
  89. package/open-sse/handlers/ttsProviders/gemini.js +117 -0
  90. package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
  91. package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
  92. package/open-sse/handlers/ttsProviders/index.js +50 -0
  93. package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
  94. package/open-sse/handlers/ttsProviders/minimax.js +59 -0
  95. package/open-sse/handlers/ttsProviders/openai.js +30 -0
  96. package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
  97. package/open-sse/index.js +82 -0
  98. package/open-sse/rtk/applyFilter.js +15 -0
  99. package/open-sse/rtk/autodetect.js +111 -0
  100. package/open-sse/rtk/caveman.js +100 -0
  101. package/open-sse/rtk/cavemanPrompts.js +78 -0
  102. package/open-sse/rtk/constants.js +55 -0
  103. package/open-sse/rtk/filters/buildOutput.js +127 -0
  104. package/open-sse/rtk/filters/dedupLog.js +44 -0
  105. package/open-sse/rtk/filters/find.js +49 -0
  106. package/open-sse/rtk/filters/gitDiff.js +92 -0
  107. package/open-sse/rtk/filters/gitStatus.js +117 -0
  108. package/open-sse/rtk/filters/grep.js +48 -0
  109. package/open-sse/rtk/filters/ls.js +79 -0
  110. package/open-sse/rtk/filters/readNumbered.js +27 -0
  111. package/open-sse/rtk/filters/searchList.js +52 -0
  112. package/open-sse/rtk/filters/smartTruncate.js +15 -0
  113. package/open-sse/rtk/filters/tree.js +32 -0
  114. package/open-sse/rtk/index.js +155 -0
  115. package/open-sse/rtk/registry.js +38 -0
  116. package/open-sse/services/accountFallback.js +238 -0
  117. package/open-sse/services/combo.js +198 -0
  118. package/open-sse/services/compact.js +71 -0
  119. package/open-sse/services/kiroModels.js +332 -0
  120. package/open-sse/services/model.js +261 -0
  121. package/open-sse/services/oauthCredentialManager.js +151 -0
  122. package/open-sse/services/projectId.js +306 -0
  123. package/open-sse/services/provider.js +356 -0
  124. package/open-sse/services/qoderModels.js +214 -0
  125. package/open-sse/services/tokenRefresh.js +939 -0
  126. package/open-sse/services/usage.js +1496 -0
  127. package/open-sse/transformer/responsesTransformer.js +439 -0
  128. package/open-sse/transformer/streamToJsonConverter.js +103 -0
  129. package/open-sse/translator/formats.js +36 -0
  130. package/open-sse/translator/helpers/claudeHelper.js +216 -0
  131. package/open-sse/translator/helpers/geminiHelper.js +372 -0
  132. package/open-sse/translator/helpers/imageHelper.js +34 -0
  133. package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
  134. package/open-sse/translator/helpers/openaiHelper.js +130 -0
  135. package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
  136. package/open-sse/translator/helpers/toolCallHelper.js +148 -0
  137. package/open-sse/translator/index.js +251 -0
  138. package/open-sse/translator/request/antigravity-to-openai.js +229 -0
  139. package/open-sse/translator/request/claude-to-openai.js +232 -0
  140. package/open-sse/translator/request/gemini-to-openai.js +147 -0
  141. package/open-sse/translator/request/openai-responses.js +318 -0
  142. package/open-sse/translator/request/openai-to-claude.js +401 -0
  143. package/open-sse/translator/request/openai-to-commandcode.js +170 -0
  144. package/open-sse/translator/request/openai-to-cursor.js +183 -0
  145. package/open-sse/translator/request/openai-to-gemini.js +470 -0
  146. package/open-sse/translator/request/openai-to-kiro.js +629 -0
  147. package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
  148. package/open-sse/translator/request/openai-to-ollama.js +192 -0
  149. package/open-sse/translator/request/openai-to-vertex.js +42 -0
  150. package/open-sse/translator/response/claude-to-openai.js +206 -0
  151. package/open-sse/translator/response/commandcode-to-openai.js +197 -0
  152. package/open-sse/translator/response/cursor-to-openai.js +30 -0
  153. package/open-sse/translator/response/gemini-to-openai.js +245 -0
  154. package/open-sse/translator/response/kiro-to-openai.js +195 -0
  155. package/open-sse/translator/response/ollama-to-openai.js +152 -0
  156. package/open-sse/translator/response/openai-responses.js +590 -0
  157. package/open-sse/translator/response/openai-to-antigravity.js +122 -0
  158. package/open-sse/translator/response/openai-to-claude.js +266 -0
  159. package/open-sse/utils/bypassHandler.js +298 -0
  160. package/open-sse/utils/claudeCloaking.js +155 -0
  161. package/open-sse/utils/claudeHeaderCache.js +70 -0
  162. package/open-sse/utils/clientDetector.js +63 -0
  163. package/open-sse/utils/cursorChecksum.js +149 -0
  164. package/open-sse/utils/cursorProtobuf.js +904 -0
  165. package/open-sse/utils/debugLog.js +14 -0
  166. package/open-sse/utils/error.js +147 -0
  167. package/open-sse/utils/ollamaTransform.js +85 -0
  168. package/open-sse/utils/proxyFetch.js +368 -0
  169. package/open-sse/utils/reasoningContentInjector.js +79 -0
  170. package/open-sse/utils/requestLogger.js +260 -0
  171. package/open-sse/utils/responsesStreamHelpers.js +49 -0
  172. package/open-sse/utils/sessionManager.js +82 -0
  173. package/open-sse/utils/stream.js +462 -0
  174. package/open-sse/utils/streamHandler.js +250 -0
  175. package/open-sse/utils/streamHelpers.js +122 -0
  176. package/open-sse/utils/toolDeduper.js +49 -0
  177. package/open-sse/utils/usageTracking.js +347 -0
  178. package/package.json +100 -0
  179. package/postcss.config.mjs +12 -0
  180. package/public/favicon.svg +11 -0
  181. package/public/file.svg +1 -0
  182. package/public/globe.svg +1 -0
  183. package/public/i18n/literals/ar.json +195 -0
  184. package/public/i18n/literals/bn.json +195 -0
  185. package/public/i18n/literals/cs.json +195 -0
  186. package/public/i18n/literals/da.json +195 -0
  187. package/public/i18n/literals/de.json +195 -0
  188. package/public/i18n/literals/el.json +195 -0
  189. package/public/i18n/literals/es.json +195 -0
  190. package/public/i18n/literals/fi.json +195 -0
  191. package/public/i18n/literals/fr.json +195 -0
  192. package/public/i18n/literals/he.json +195 -0
  193. package/public/i18n/literals/hi.json +195 -0
  194. package/public/i18n/literals/hu.json +195 -0
  195. package/public/i18n/literals/id.json +195 -0
  196. package/public/i18n/literals/it.json +195 -0
  197. package/public/i18n/literals/ja.json +195 -0
  198. package/public/i18n/literals/ko.json +195 -0
  199. package/public/i18n/literals/nl.json +195 -0
  200. package/public/i18n/literals/no.json +195 -0
  201. package/public/i18n/literals/pl.json +195 -0
  202. package/public/i18n/literals/pt-BR.json +195 -0
  203. package/public/i18n/literals/pt-PT.json +195 -0
  204. package/public/i18n/literals/ro.json +195 -0
  205. package/public/i18n/literals/ru.json +195 -0
  206. package/public/i18n/literals/sv.json +195 -0
  207. package/public/i18n/literals/th.json +195 -0
  208. package/public/i18n/literals/tl.json +195 -0
  209. package/public/i18n/literals/tr.json +195 -0
  210. package/public/i18n/literals/uk.json +195 -0
  211. package/public/i18n/literals/ur.json +195 -0
  212. package/public/i18n/literals/vi.json +195 -0
  213. package/public/i18n/literals/zh-CN.json +772 -0
  214. package/public/i18n/literals/zh-TW.json +195 -0
  215. package/public/icons/discord.svg +4 -0
  216. package/public/icons/icon-192.svg +4 -0
  217. package/public/icons/icon-512.svg +4 -0
  218. package/public/next.svg +1 -0
  219. package/public/providers/alicode-intl.png +0 -0
  220. package/public/providers/alicode.png +0 -0
  221. package/public/providers/amp.png +0 -0
  222. package/public/providers/anthropic-m.png +0 -0
  223. package/public/providers/anthropic.png +0 -0
  224. package/public/providers/antigravity.png +0 -0
  225. package/public/providers/assemblyai.png +0 -0
  226. package/public/providers/aws-polly.png +0 -0
  227. package/public/providers/azure.png +0 -0
  228. package/public/providers/black-forest-labs.png +0 -0
  229. package/public/providers/blackbox.png +0 -0
  230. package/public/providers/brave-search.png +0 -0
  231. package/public/providers/byteplus.png +0 -0
  232. package/public/providers/cartesia.png +0 -0
  233. package/public/providers/cerebras.png +0 -0
  234. package/public/providers/chutes.png +0 -0
  235. package/public/providers/claude.png +0 -0
  236. package/public/providers/cline.png +0 -0
  237. package/public/providers/cloudflare-ai.png +0 -0
  238. package/public/providers/codebuddy.svg +37 -0
  239. package/public/providers/codex.png +0 -0
  240. package/public/providers/cohere.png +0 -0
  241. package/public/providers/comfyui.png +0 -0
  242. package/public/providers/commandcode.png +0 -0
  243. package/public/providers/continue.png +0 -0
  244. package/public/providers/copilot.png +0 -0
  245. package/public/providers/coqui.png +0 -0
  246. package/public/providers/cursor.png +0 -0
  247. package/public/providers/deepgram.png +0 -0
  248. package/public/providers/deepseek-tui.png +0 -0
  249. package/public/providers/deepseek.png +0 -0
  250. package/public/providers/droid.png +0 -0
  251. package/public/providers/edge-tts.png +0 -0
  252. package/public/providers/elevenlabs.png +0 -0
  253. package/public/providers/exa.png +0 -0
  254. package/public/providers/fal-ai.png +0 -0
  255. package/public/providers/firecrawl.png +0 -0
  256. package/public/providers/fireworks.png +0 -0
  257. package/public/providers/gemini-cli.png +0 -0
  258. package/public/providers/gemini.png +0 -0
  259. package/public/providers/github.png +0 -0
  260. package/public/providers/glm-cn.png +0 -0
  261. package/public/providers/glm.png +0 -0
  262. package/public/providers/google-pse.png +0 -0
  263. package/public/providers/google-tts.png +0 -0
  264. package/public/providers/grok-web.png +0 -0
  265. package/public/providers/groq.png +0 -0
  266. package/public/providers/hermes.png +0 -0
  267. package/public/providers/huggingface.png +0 -0
  268. package/public/providers/hyperbolic.png +0 -0
  269. package/public/providers/iflow.png +0 -0
  270. package/public/providers/inworld.png +0 -0
  271. package/public/providers/jcode.png +0 -0
  272. package/public/providers/jina-ai.png +0 -0
  273. package/public/providers/jina-reader.png +0 -0
  274. package/public/providers/kilocode.png +0 -0
  275. package/public/providers/kimi-coding.png +0 -0
  276. package/public/providers/kimi.png +0 -0
  277. package/public/providers/kiro.png +0 -0
  278. package/public/providers/linkup.png +0 -0
  279. package/public/providers/local-device.png +0 -0
  280. package/public/providers/minimax-cn.png +0 -0
  281. package/public/providers/minimax.png +0 -0
  282. package/public/providers/mistral.png +0 -0
  283. package/public/providers/nanobanana.png +0 -0
  284. package/public/providers/nebius.png +0 -0
  285. package/public/providers/nvidia.png +0 -0
  286. package/public/providers/oai-cc.png +0 -0
  287. package/public/providers/oai-r.png +0 -0
  288. package/public/providers/ollama-local.png +0 -0
  289. package/public/providers/ollama.png +0 -0
  290. package/public/providers/openai.png +0 -0
  291. package/public/providers/openclaw.png +0 -0
  292. package/public/providers/opencode-go.png +0 -0
  293. package/public/providers/opencode.png +0 -0
  294. package/public/providers/openrouter.png +0 -0
  295. package/public/providers/perplexity-web.png +0 -0
  296. package/public/providers/perplexity.png +0 -0
  297. package/public/providers/playht.png +0 -0
  298. package/public/providers/qoder.png +0 -0
  299. package/public/providers/qwen.png +0 -0
  300. package/public/providers/recraft.png +0 -0
  301. package/public/providers/roo.png +0 -0
  302. package/public/providers/runwayml.png +0 -0
  303. package/public/providers/sdwebui.png +0 -0
  304. package/public/providers/searchapi.png +0 -0
  305. package/public/providers/searxng.png +0 -0
  306. package/public/providers/serper.png +0 -0
  307. package/public/providers/siliconflow.png +0 -0
  308. package/public/providers/stability-ai.png +0 -0
  309. package/public/providers/tavily.png +0 -0
  310. package/public/providers/together.png +0 -0
  311. package/public/providers/topaz.png +0 -0
  312. package/public/providers/tortoise.png +0 -0
  313. package/public/providers/vertex-partner.png +0 -0
  314. package/public/providers/vertex.png +0 -0
  315. package/public/providers/volcengine-ark.png +0 -0
  316. package/public/providers/voyage-ai.png +0 -0
  317. package/public/providers/xai.png +0 -0
  318. package/public/providers/xiaomi-mimo.png +0 -0
  319. package/public/providers/xiaomi-tokenplan.png +0 -0
  320. package/public/providers/youcom.png +0 -0
  321. package/public/sw.js +22 -0
  322. package/public/vercel.svg +1 -0
  323. package/public/window.svg +1 -0
  324. package/scripts/compact-request-details.mjs +71 -0
  325. package/scripts/import-codex-gptjson.mjs +342 -0
  326. package/scripts/start-standalone.mjs +25 -0
  327. package/scripts/translate-readme.js +201 -0
  328. package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
  329. package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
  330. package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
  331. package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
  332. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
  333. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
  334. package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
  335. package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
  336. package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
  337. package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
  338. package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
  339. package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
  340. package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
  341. package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
  342. package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
  343. package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
  344. package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
  345. package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
  346. package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
  347. package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
  348. package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
  349. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
  350. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
  351. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
  352. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
  353. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
  354. package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
  355. package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
  356. package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
  357. package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
  358. package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
  359. package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
  360. package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
  361. package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
  362. package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
  363. package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
  364. package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
  365. package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
  366. package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
  367. package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
  368. package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
  369. package/src/app/(dashboard)/dashboard/page.js +7 -0
  370. package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
  371. package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
  372. package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
  373. package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
  374. package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
  375. package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
  376. package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
  377. package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
  378. package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
  379. package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
  380. package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
  381. package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
  382. package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
  383. package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
  384. package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
  385. package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
  386. package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
  387. package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
  388. package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
  389. package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
  390. package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
  391. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
  392. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
  393. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
  394. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
  395. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
  396. package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
  397. package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
  398. package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
  399. package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
  400. package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
  401. package/src/app/(dashboard)/layout.js +6 -0
  402. package/src/app/api/auth/login/route.js +76 -0
  403. package/src/app/api/auth/logout/route.js +12 -0
  404. package/src/app/api/auth/oidc/callback/route.js +87 -0
  405. package/src/app/api/auth/oidc/start/route.js +52 -0
  406. package/src/app/api/auth/oidc/test/route.js +84 -0
  407. package/src/app/api/auth/status/route.js +45 -0
  408. package/src/app/api/cli-tools/all-statuses/route.js +46 -0
  409. package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
  410. package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
  411. package/src/app/api/cli-tools/claude-settings/route.js +203 -0
  412. package/src/app/api/cli-tools/cline-settings/route.js +133 -0
  413. package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
  414. package/src/app/api/cli-tools/codex-settings/route.js +239 -0
  415. package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
  416. package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
  417. package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
  418. package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
  419. package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
  420. package/src/app/api/cli-tools/droid-settings/route.js +213 -0
  421. package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
  422. package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
  423. package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
  424. package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
  425. package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
  426. package/src/app/api/combos/[id]/route.js +81 -0
  427. package/src/app/api/combos/route.js +48 -0
  428. package/src/app/api/health/route.js +15 -0
  429. package/src/app/api/init/route.js +4 -0
  430. package/src/app/api/keys/[id]/route.js +58 -0
  431. package/src/app/api/keys/route.js +42 -0
  432. package/src/app/api/locale/route.js +30 -0
  433. package/src/app/api/mcp/[plugin]/message/route.js +21 -0
  434. package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
  435. package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
  436. package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
  437. package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
  438. package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
  439. package/src/app/api/media-providers/tts/voices/route.js +99 -0
  440. package/src/app/api/models/alias/route.js +53 -0
  441. package/src/app/api/models/availability/route.js +103 -0
  442. package/src/app/api/models/custom/route.js +48 -0
  443. package/src/app/api/models/disabled/route.js +50 -0
  444. package/src/app/api/models/route.js +64 -0
  445. package/src/app/api/models/test/ping.js +191 -0
  446. package/src/app/api/models/test/route.js +14 -0
  447. package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
  448. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
  449. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
  450. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
  451. package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
  452. package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
  453. package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
  454. package/src/app/api/oauth/codex/import-token/route.js +96 -0
  455. package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
  456. package/src/app/api/oauth/cursor/import/route.js +100 -0
  457. package/src/app/api/oauth/gitlab/pat/route.js +62 -0
  458. package/src/app/api/oauth/iflow/cookie/route.js +137 -0
  459. package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
  460. package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
  461. package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
  462. package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
  463. package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
  464. package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
  465. package/src/app/api/oauth/kiro/import/route.js +110 -0
  466. package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
  467. package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
  468. package/src/app/api/pricing/route.js +134 -0
  469. package/src/app/api/provider-nodes/[id]/route.js +101 -0
  470. package/src/app/api/provider-nodes/route.js +104 -0
  471. package/src/app/api/provider-nodes/validate/route.js +201 -0
  472. package/src/app/api/providers/[id]/models/route.js +526 -0
  473. package/src/app/api/providers/[id]/route.js +189 -0
  474. package/src/app/api/providers/[id]/test/route.js +23 -0
  475. package/src/app/api/providers/[id]/test/testUtils.js +714 -0
  476. package/src/app/api/providers/[id]/test-models/route.js +66 -0
  477. package/src/app/api/providers/client/route.js +126 -0
  478. package/src/app/api/providers/kilo/free-models/route.js +55 -0
  479. package/src/app/api/providers/route.js +206 -0
  480. package/src/app/api/providers/suggested-models/filters.js +20 -0
  481. package/src/app/api/providers/suggested-models/route.js +32 -0
  482. package/src/app/api/providers/test-batch/route.js +131 -0
  483. package/src/app/api/providers/validate/route.js +637 -0
  484. package/src/app/api/proxy-pools/[id]/route.js +123 -0
  485. package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
  486. package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
  487. package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
  488. package/src/app/api/proxy-pools/route.js +93 -0
  489. package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
  490. package/src/app/api/settings/database/route.js +36 -0
  491. package/src/app/api/settings/proxy-test/route.js +23 -0
  492. package/src/app/api/settings/require-login/route.js +15 -0
  493. package/src/app/api/settings/route.js +100 -0
  494. package/src/app/api/shutdown/route.js +24 -0
  495. package/src/app/api/tags/route.js +18 -0
  496. package/src/app/api/translator/console-logs/route.js +24 -0
  497. package/src/app/api/translator/console-logs/stream/route.js +79 -0
  498. package/src/app/api/translator/load/route.js +45 -0
  499. package/src/app/api/translator/save/route.js +44 -0
  500. package/src/app/api/translator/send/route.js +94 -0
  501. package/src/app/api/translator/translate/route.js +90 -0
  502. package/src/app/api/tunnel/disable/route.js +12 -0
  503. package/src/app/api/tunnel/enable/route.js +16 -0
  504. package/src/app/api/tunnel/status/route.js +13 -0
  505. package/src/app/api/tunnel/tailscale-check/route.js +50 -0
  506. package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
  507. package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
  508. package/src/app/api/tunnel/tailscale-install/route.js +72 -0
  509. package/src/app/api/usage/[connectionId]/route.js +188 -0
  510. package/src/app/api/usage/chart/route.js +21 -0
  511. package/src/app/api/usage/history/route.js +12 -0
  512. package/src/app/api/usage/logs/route.js +12 -0
  513. package/src/app/api/usage/providers/route.js +42 -0
  514. package/src/app/api/usage/request-details/route.js +57 -0
  515. package/src/app/api/usage/request-logs/route.js +13 -0
  516. package/src/app/api/usage/stats/route.js +23 -0
  517. package/src/app/api/usage/stream/route.js +79 -0
  518. package/src/app/api/v1/api/chat/route.js +37 -0
  519. package/src/app/api/v1/audio/speech/route.js +16 -0
  520. package/src/app/api/v1/audio/transcriptions/route.js +19 -0
  521. package/src/app/api/v1/audio/voices/route.js +68 -0
  522. package/src/app/api/v1/chat/completions/route.js +35 -0
  523. package/src/app/api/v1/embeddings/route.js +21 -0
  524. package/src/app/api/v1/images/generations/route.js +16 -0
  525. package/src/app/api/v1/messages/count_tokens/route.js +52 -0
  526. package/src/app/api/v1/messages/route.js +36 -0
  527. package/src/app/api/v1/models/[kind]/route.js +55 -0
  528. package/src/app/api/v1/models/info/route.js +110 -0
  529. package/src/app/api/v1/models/route.js +451 -0
  530. package/src/app/api/v1/responses/compact/route.js +37 -0
  531. package/src/app/api/v1/responses/route.js +30 -0
  532. package/src/app/api/v1/route.js +1 -0
  533. package/src/app/api/v1/search/route.js +21 -0
  534. package/src/app/api/v1/web/fetch/route.js +21 -0
  535. package/src/app/api/v1beta/models/[...path]/route.js +328 -0
  536. package/src/app/api/v1beta/models/route.js +44 -0
  537. package/src/app/api/version/route.js +45 -0
  538. package/src/app/api/version/shutdown/route.js +15 -0
  539. package/src/app/api/version/update/route.js +21 -0
  540. package/src/app/callback/page.js +148 -0
  541. package/src/app/dashboard/settings/pricing/page.js +173 -0
  542. package/src/app/favicon.ico +0 -0
  543. package/src/app/globals.css +496 -0
  544. package/src/app/landing/components/AnimatedBackground.js +57 -0
  545. package/src/app/landing/components/Features.js +133 -0
  546. package/src/app/landing/components/FlowAnimation.js +175 -0
  547. package/src/app/landing/components/Footer.js +61 -0
  548. package/src/app/landing/components/GetStarted.js +97 -0
  549. package/src/app/landing/components/HeroSection.js +47 -0
  550. package/src/app/landing/components/HowItWorks.js +66 -0
  551. package/src/app/landing/components/Navigation.js +72 -0
  552. package/src/app/landing/page.js +106 -0
  553. package/src/app/layout.js +49 -0
  554. package/src/app/login/page.js +197 -0
  555. package/src/app/manifest.js +30 -0
  556. package/src/app/page.js +5 -0
  557. package/src/dashboardGuard.js +242 -0
  558. package/src/i18n/RuntimeI18nProvider.js +27 -0
  559. package/src/i18n/config.js +146 -0
  560. package/src/i18n/runtime.js +162 -0
  561. package/src/lib/appUpdater.js +200 -0
  562. package/src/lib/auth/dashboardSession.js +68 -0
  563. package/src/lib/auth/loginLimiter.js +52 -0
  564. package/src/lib/auth/oidc.js +234 -0
  565. package/src/lib/consoleLogBuffer.js +79 -0
  566. package/src/lib/dataDir.js +29 -0
  567. package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
  568. package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
  569. package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
  570. package/src/lib/db/adapters/sqljsAdapter.js +115 -0
  571. package/src/lib/db/backup.js +35 -0
  572. package/src/lib/db/driver.js +85 -0
  573. package/src/lib/db/helpers/jsonCol.js +9 -0
  574. package/src/lib/db/helpers/kvStore.js +39 -0
  575. package/src/lib/db/helpers/metaStore.js +22 -0
  576. package/src/lib/db/index.js +171 -0
  577. package/src/lib/db/migrate.js +286 -0
  578. package/src/lib/db/migrations/001-initial.js +14 -0
  579. package/src/lib/db/migrations/index.js +10 -0
  580. package/src/lib/db/paths.js +18 -0
  581. package/src/lib/db/repos/aliasRepo.js +62 -0
  582. package/src/lib/db/repos/apiKeysRepo.js +75 -0
  583. package/src/lib/db/repos/combosRepo.js +73 -0
  584. package/src/lib/db/repos/connectionsRepo.js +226 -0
  585. package/src/lib/db/repos/disabledModelsRepo.js +56 -0
  586. package/src/lib/db/repos/nodesRepo.js +95 -0
  587. package/src/lib/db/repos/pricingRepo.js +108 -0
  588. package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
  589. package/src/lib/db/repos/requestDetailsRepo.js +259 -0
  590. package/src/lib/db/repos/settingsRepo.js +104 -0
  591. package/src/lib/db/repos/usageRepo.js +731 -0
  592. package/src/lib/db/schema.js +157 -0
  593. package/src/lib/db/version.js +21 -0
  594. package/src/lib/disabledModelsDb.js +4 -0
  595. package/src/lib/localDb.js +21 -0
  596. package/src/lib/mcp/stdioSseBridge.js +198 -0
  597. package/src/lib/mitmAliasCache.js +46 -0
  598. package/src/lib/network/connectionProxy.js +160 -0
  599. package/src/lib/network/initOutboundProxy.js +25 -0
  600. package/src/lib/network/outboundProxy.js +68 -0
  601. package/src/lib/network/proxyTest.js +91 -0
  602. package/src/lib/oauth/constants/oauth.js +284 -0
  603. package/src/lib/oauth/constants/xai.js +61 -0
  604. package/src/lib/oauth/providers.js +1506 -0
  605. package/src/lib/oauth/services/antigravity.js +321 -0
  606. package/src/lib/oauth/services/claude.js +136 -0
  607. package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
  608. package/src/lib/oauth/services/codex.js +144 -0
  609. package/src/lib/oauth/services/cursor.js +179 -0
  610. package/src/lib/oauth/services/gemini.js +240 -0
  611. package/src/lib/oauth/services/github.js +225 -0
  612. package/src/lib/oauth/services/iflow.js +202 -0
  613. package/src/lib/oauth/services/index.js +17 -0
  614. package/src/lib/oauth/services/kiro.js +334 -0
  615. package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
  616. package/src/lib/oauth/services/kiroConnections.js +93 -0
  617. package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
  618. package/src/lib/oauth/services/oauth.js +157 -0
  619. package/src/lib/oauth/services/openai.js +123 -0
  620. package/src/lib/oauth/services/qoder.js +216 -0
  621. package/src/lib/oauth/services/qwen.js +170 -0
  622. package/src/lib/oauth/services/xai.js +238 -0
  623. package/src/lib/oauth/utils/banner.js +63 -0
  624. package/src/lib/oauth/utils/pkce.js +39 -0
  625. package/src/lib/oauth/utils/server.js +415 -0
  626. package/src/lib/oauth/utils/ui.js +48 -0
  627. package/src/lib/providerNormalization.js +45 -0
  628. package/src/lib/qoder/constants.js +64 -0
  629. package/src/lib/qoder/cosy.js +175 -0
  630. package/src/lib/qoder/encoding.js +55 -0
  631. package/src/lib/requestDetailsDb.js +4 -0
  632. package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
  633. package/src/lib/tunnel/cloudflare/config.js +9 -0
  634. package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
  635. package/src/lib/tunnel/cloudflare/manager.js +151 -0
  636. package/src/lib/tunnel/cloudflare/pid.js +23 -0
  637. package/src/lib/tunnel/index.js +48 -0
  638. package/src/lib/tunnel/shared/dnsResolver.js +17 -0
  639. package/src/lib/tunnel/shared/internetCheck.js +26 -0
  640. package/src/lib/tunnel/shared/state.js +41 -0
  641. package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
  642. package/src/lib/tunnel/tailscale/config.js +7 -0
  643. package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
  644. package/src/lib/tunnel/tailscale/manager.js +129 -0
  645. package/src/lib/tunnel/tailscale/tailscale.js +790 -0
  646. package/src/lib/updater/updater.js +235 -0
  647. package/src/lib/usage/fetcher.js +208 -0
  648. package/src/lib/usageDb.js +7 -0
  649. package/src/mitm/antigravityIdeVersion.js +50 -0
  650. package/src/mitm/cert/generate.js +32 -0
  651. package/src/mitm/cert/install.js +269 -0
  652. package/src/mitm/cert/rootCA.js +173 -0
  653. package/src/mitm/config.js +87 -0
  654. package/src/mitm/dbReader.js +22 -0
  655. package/src/mitm/dns/dnsConfig.js +266 -0
  656. package/src/mitm/handlers/antigravity.js +33 -0
  657. package/src/mitm/handlers/base.js +226 -0
  658. package/src/mitm/handlers/copilot.js +35 -0
  659. package/src/mitm/handlers/cursor.js +15 -0
  660. package/src/mitm/handlers/kiro.js +526 -0
  661. package/src/mitm/logger.js +106 -0
  662. package/src/mitm/manager.js +851 -0
  663. package/src/mitm/paths.js +32 -0
  664. package/src/mitm/server.js +435 -0
  665. package/src/mitm/winElevated.js +81 -0
  666. package/src/models/index.js +38 -0
  667. package/src/proxy.js +5 -0
  668. package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
  669. package/src/shared/components/Avatar.js +88 -0
  670. package/src/shared/components/Badge.js +54 -0
  671. package/src/shared/components/BulkAccountAutomationModal.js +508 -0
  672. package/src/shared/components/Button.js +56 -0
  673. package/src/shared/components/Card.js +116 -0
  674. package/src/shared/components/ChangelogModal.js +97 -0
  675. package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
  676. package/src/shared/components/ComboFormModal.js +176 -0
  677. package/src/shared/components/CursorAuthModal.js +212 -0
  678. package/src/shared/components/DonateModal.js +136 -0
  679. package/src/shared/components/Drawer.js +82 -0
  680. package/src/shared/components/EditConnectionModal.js +286 -0
  681. package/src/shared/components/Footer.js +132 -0
  682. package/src/shared/components/GitLabAuthModal.js +194 -0
  683. package/src/shared/components/Header.js +380 -0
  684. package/src/shared/components/HeaderLanguage.js +46 -0
  685. package/src/shared/components/HeaderMenu.js +126 -0
  686. package/src/shared/components/IFlowCookieModal.js +132 -0
  687. package/src/shared/components/Input.js +65 -0
  688. package/src/shared/components/KiroAuthModal.js +1171 -0
  689. package/src/shared/components/KiroOAuthWrapper.js +149 -0
  690. package/src/shared/components/KiroSocialOAuthModal.js +205 -0
  691. package/src/shared/components/LanguageSwitcher.js +190 -0
  692. package/src/shared/components/Loading.js +75 -0
  693. package/src/shared/components/ManualConfigModal.js +44 -0
  694. package/src/shared/components/McpMarketplaceModal.js +255 -0
  695. package/src/shared/components/Modal.js +146 -0
  696. package/src/shared/components/ModelSelectModal.js +537 -0
  697. package/src/shared/components/NineRemoteButton.js +23 -0
  698. package/src/shared/components/NineRemotePromoModal.js +99 -0
  699. package/src/shared/components/NoAuthProxyCard.js +86 -0
  700. package/src/shared/components/OAuthModal.js +682 -0
  701. package/src/shared/components/Pagination.js +150 -0
  702. package/src/shared/components/PricingModal.js +208 -0
  703. package/src/shared/components/ProviderIcon.js +63 -0
  704. package/src/shared/components/ProviderInfoCard.js +82 -0
  705. package/src/shared/components/RequestLogger.js +121 -0
  706. package/src/shared/components/SegmentedControl.js +48 -0
  707. package/src/shared/components/Select.js +67 -0
  708. package/src/shared/components/Sidebar.js +441 -0
  709. package/src/shared/components/ThemeProvider.js +15 -0
  710. package/src/shared/components/ThemeToggle.js +42 -0
  711. package/src/shared/components/Toggle.js +69 -0
  712. package/src/shared/components/Tooltip.js +25 -0
  713. package/src/shared/components/UsageStats.js +505 -0
  714. package/src/shared/components/index.js +46 -0
  715. package/src/shared/components/layouts/AuthLayout.js +29 -0
  716. package/src/shared/components/layouts/DashboardLayout.js +104 -0
  717. package/src/shared/components/layouts/index.js +4 -0
  718. package/src/shared/constants/cliTools.js +397 -0
  719. package/src/shared/constants/colors.js +77 -0
  720. package/src/shared/constants/config.js +99 -0
  721. package/src/shared/constants/coworkPlugins.js +75 -0
  722. package/src/shared/constants/index.js +4 -0
  723. package/src/shared/constants/locales.js +36 -0
  724. package/src/shared/constants/mitmToolHosts.js +12 -0
  725. package/src/shared/constants/models.js +38 -0
  726. package/src/shared/constants/pricing.js +303 -0
  727. package/src/shared/constants/providers.js +289 -0
  728. package/src/shared/constants/skills.js +78 -0
  729. package/src/shared/constants/ttsProviders.js +138 -0
  730. package/src/shared/hooks/index.js +2 -0
  731. package/src/shared/hooks/useCopyToClipboard.js +43 -0
  732. package/src/shared/hooks/useTheme.js +60 -0
  733. package/src/shared/services/bootstrap.js +12 -0
  734. package/src/shared/services/initializeApp.js +268 -0
  735. package/src/shared/utils/api.js +93 -0
  736. package/src/shared/utils/apiKey.js +98 -0
  737. package/src/shared/utils/clineAuth.js +37 -0
  738. package/src/shared/utils/cn.js +11 -0
  739. package/src/shared/utils/connectionStatus.js +162 -0
  740. package/src/shared/utils/index.js +40 -0
  741. package/src/shared/utils/machine.js +6 -0
  742. package/src/shared/utils/machineId.js +66 -0
  743. package/src/shared/utils/providerModelsFetcher.js +30 -0
  744. package/src/sse/handlers/chat.js +261 -0
  745. package/src/sse/handlers/embeddings.js +141 -0
  746. package/src/sse/handlers/fetch.js +213 -0
  747. package/src/sse/handlers/imageGeneration.js +142 -0
  748. package/src/sse/handlers/search.js +206 -0
  749. package/src/sse/handlers/stt.js +88 -0
  750. package/src/sse/handlers/tts.js +114 -0
  751. package/src/sse/services/auth.js +346 -0
  752. package/src/sse/services/codexGateway.js +215 -0
  753. package/src/sse/services/model.js +99 -0
  754. package/src/sse/services/tokenRefresh.js +319 -0
  755. package/src/sse/utils/logger.js +75 -0
  756. package/src/store/headerSearchStore.js +19 -0
  757. package/src/store/index.js +6 -0
  758. package/src/store/notificationStore.js +45 -0
  759. package/src/store/providerStore.js +55 -0
  760. package/src/store/settingsStore.js +51 -0
  761. package/src/store/themeStore.js +54 -0
  762. package/src/store/userStore.js +20 -0
  763. package/start.sh +4 -0
@@ -0,0 +1,790 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import crypto from "crypto";
5
+ import { execSync, exec, spawn } from "child_process";
6
+ import { promisify } from "util";
7
+ import { execWithPassword } from "@/mitm/dns/dnsConfig";
8
+ import { DATA_DIR } from "@/lib/dataDir.js";
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ const BIN_DIR = path.join(DATA_DIR, "bin");
13
+ const IS_MAC = os.platform() === "darwin";
14
+ const IS_LINUX = os.platform() === "linux";
15
+ const IS_WINDOWS = os.platform() === "win32";
16
+ const TAILSCALE_BIN = path.join(BIN_DIR, IS_WINDOWS ? "tailscale.exe" : "tailscale");
17
+
18
+ // Custom socket for userspace-networking mode (no root required)
19
+ const TAILSCALE_DIR = path.join(DATA_DIR, "tailscale");
20
+ export const TAILSCALE_SOCKET = path.join(TAILSCALE_DIR, "tailscaled.sock");
21
+ const SOCKET_FLAG = IS_WINDOWS ? [] : ["--socket", TAILSCALE_SOCKET];
22
+
23
+ // Well-known Windows install path
24
+ const WINDOWS_TAILSCALE_BIN = "C:\\Program Files\\Tailscale\\tailscale.exe";
25
+
26
+ // Common Unix install paths to probe synchronously (system tailscale)
27
+ const UNIX_TAILSCALE_CANDIDATES = [
28
+ "/usr/local/bin/tailscale",
29
+ "/opt/homebrew/bin/tailscale",
30
+ "/usr/bin/tailscale",
31
+ ];
32
+
33
+ // ─── Cache + background refresh (avoid blocking event loop on dead daemon) ──
34
+ const PROBE_TTL_MS = 10000;
35
+ const PROBE_TIMEOUT_MS = 1500;
36
+
37
+ const binCache = { value: undefined, fetchedAt: 0, refreshing: false };
38
+ const runningCache = { value: false, fetchedAt: 0, refreshing: false };
39
+ const funnelUrlCache = { value: null, port: null, fetchedAt: 0, refreshing: false };
40
+
41
+ function fallbackBin() {
42
+ if (fs.existsSync(TAILSCALE_BIN)) return TAILSCALE_BIN;
43
+ if (IS_WINDOWS && fs.existsSync(WINDOWS_TAILSCALE_BIN)) return WINDOWS_TAILSCALE_BIN;
44
+ if (!IS_WINDOWS) return UNIX_TAILSCALE_CANDIDATES.find((p) => fs.existsSync(p)) || null;
45
+ return null;
46
+ }
47
+
48
+ function bgRefreshBin() {
49
+ if (binCache.refreshing) return;
50
+ binCache.refreshing = true;
51
+ const cmd = IS_WINDOWS ? "where tailscale 2>nul" : "which tailscale 2>/dev/null";
52
+ execAsync(cmd, { windowsHide: true, timeout: PROBE_TIMEOUT_MS })
53
+ .then(({ stdout }) => {
54
+ const sys = stdout.trim();
55
+ binCache.value = sys || fallbackBin();
56
+ })
57
+ .catch(() => { binCache.value = fallbackBin(); })
58
+ .finally(() => {
59
+ binCache.fetchedAt = Date.now();
60
+ binCache.refreshing = false;
61
+ });
62
+ }
63
+
64
+ // Sync getter: returns cached value, triggers background refresh if stale
65
+ function getTailscaleBin() {
66
+ if (Date.now() - binCache.fetchedAt > PROBE_TTL_MS) bgRefreshBin();
67
+ // First call: synchronously probe common install paths (no exec, no event-loop block)
68
+ if (binCache.value === undefined) {
69
+ if (fs.existsSync(TAILSCALE_BIN)) binCache.value = TAILSCALE_BIN;
70
+ else if (IS_WINDOWS && fs.existsSync(WINDOWS_TAILSCALE_BIN)) binCache.value = WINDOWS_TAILSCALE_BIN;
71
+ else if (!IS_WINDOWS) {
72
+ const found = UNIX_TAILSCALE_CANDIDATES.find((p) => fs.existsSync(p));
73
+ binCache.value = found || null;
74
+ } else binCache.value = null;
75
+ }
76
+ return binCache.value;
77
+ }
78
+
79
+ export function isTailscaleInstalled() {
80
+ return getTailscaleBin() !== null;
81
+ }
82
+
83
+ /** Build tailscale CLI args with custom socket (no root needed) */
84
+ function tsArgs(...args) {
85
+ return [...SOCKET_FLAG, ...args];
86
+ }
87
+
88
+ export function isTailscaleLoggedIn() {
89
+ const bin = getTailscaleBin();
90
+ if (!bin) return false;
91
+ try {
92
+ const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
93
+ encoding: "utf8",
94
+ windowsHide: true,
95
+ env: { ...process.env, PATH: EXTENDED_PATH },
96
+ timeout: 5000
97
+ });
98
+ const json = JSON.parse(out);
99
+ // BackendState=Running + Self.Online=true → device still exists in tailnet
100
+ return json.BackendState === "Running" && json.Self?.Online === true;
101
+ } catch (e) {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function bgRefreshRunning() {
107
+ if (runningCache.refreshing) return;
108
+ const bin = getTailscaleBin();
109
+ if (!bin) {
110
+ runningCache.value = false;
111
+ runningCache.fetchedAt = Date.now();
112
+ return;
113
+ }
114
+ runningCache.refreshing = true;
115
+ execAsync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel status --json`, { windowsHide: true, timeout: PROBE_TIMEOUT_MS })
116
+ .then(({ stdout }) => {
117
+ try {
118
+ const json = JSON.parse(stdout);
119
+ runningCache.value = Object.keys(json.AllowFunnel || {}).length > 0;
120
+ } catch { runningCache.value = false; }
121
+ })
122
+ .catch(() => { runningCache.value = false; })
123
+ .finally(() => {
124
+ runningCache.fetchedAt = Date.now();
125
+ runningCache.refreshing = false;
126
+ });
127
+ }
128
+
129
+ // Sync getter: never blocks; returns last known state, refreshes in background
130
+ export function isTailscaleRunning() {
131
+ if (Date.now() - runningCache.fetchedAt > PROBE_TTL_MS) bgRefreshRunning();
132
+ return runningCache.value;
133
+ }
134
+
135
+ // Synchronous strict probe for hot user-initiated paths (enable/connect flow).
136
+ // Blocks ~PROBE_TIMEOUT_MS at most; updates cache as a side effect.
137
+ export function isTailscaleRunningStrict() {
138
+ const bin = getTailscaleBin();
139
+ if (!bin) return false;
140
+ try {
141
+ const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel status --json`, {
142
+ encoding: "utf8",
143
+ windowsHide: true,
144
+ stdio: ["ignore", "pipe", "ignore"],
145
+ timeout: PROBE_TIMEOUT_MS,
146
+ });
147
+ const json = JSON.parse(out);
148
+ const running = Object.keys(json.AllowFunnel || {}).length > 0;
149
+ runningCache.value = running;
150
+ runningCache.fetchedAt = Date.now();
151
+ return running;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+
157
+ function bgRefreshFunnelUrl(port) {
158
+ if (funnelUrlCache.refreshing) return;
159
+ const bin = getTailscaleBin();
160
+ if (!bin) return;
161
+ funnelUrlCache.refreshing = true;
162
+ execAsync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, { windowsHide: true, timeout: PROBE_TIMEOUT_MS })
163
+ .then(({ stdout }) => {
164
+ try {
165
+ const json = JSON.parse(stdout);
166
+ const dnsName = json.Self?.DNSName?.replace(/\.$/, "");
167
+ funnelUrlCache.value = dnsName ? `https://${dnsName}` : null;
168
+ } catch { /* keep prev */ }
169
+ })
170
+ .catch(() => { /* keep prev */ })
171
+ .finally(() => {
172
+ funnelUrlCache.port = port;
173
+ funnelUrlCache.fetchedAt = Date.now();
174
+ funnelUrlCache.refreshing = false;
175
+ });
176
+ }
177
+
178
+ /** Get actual funnel URL from Self.DNSName (sync, authoritative — avoids hostname-conflict suffix). */
179
+ function getActualFunnelUrl() {
180
+ const bin = getTailscaleBin();
181
+ if (!bin) return null;
182
+ try {
183
+ const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
184
+ encoding: "utf8",
185
+ windowsHide: true,
186
+ env: { ...process.env, PATH: EXTENDED_PATH },
187
+ timeout: 5000,
188
+ });
189
+ const json = JSON.parse(out);
190
+ const dnsName = json.Self?.DNSName?.replace(/\.$/, "");
191
+ return dnsName ? `https://${dnsName}` : null;
192
+ } catch { return null; }
193
+ }
194
+
195
+ /** Get funnel URL from tailscale status (cached, non-blocking) */
196
+ export function getTailscaleFunnelUrl(port) {
197
+ if (Date.now() - funnelUrlCache.fetchedAt > PROBE_TTL_MS || funnelUrlCache.port !== port) {
198
+ bgRefreshFunnelUrl(port);
199
+ }
200
+ return funnelUrlCache.value;
201
+ }
202
+
203
+ /**
204
+ * Install tailscale.
205
+ * - macOS + brew: brew install tailscale (no sudo needed)
206
+ * - macOS no brew: download .pkg then sudo installer -pkg
207
+ * - Linux: fetch install.sh, pipe to sudo -S sh via stdin
208
+ * - Windows: download MSI via UAC-elevated PowerShell
209
+ */
210
+ export async function installTailscale(sudoPassword, hostname, onProgress) {
211
+ const log = onProgress || (() => {});
212
+ if (IS_WINDOWS) {
213
+ await installTailscaleWindows(log);
214
+ return { success: true };
215
+ }
216
+ if (IS_MAC) await installTailscaleMac(sudoPassword, log);
217
+ else await installTailscaleLinux(sudoPassword, log);
218
+
219
+ log("Starting daemon...");
220
+ await startDaemonWithPassword(sudoPassword);
221
+ log("Logging in...");
222
+ return startLogin(hostname);
223
+ }
224
+
225
+ const EXTENDED_PATH = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${process.env.PATH || ""}`;
226
+
227
+ function hasBrew() {
228
+ try { execSync("which brew", { stdio: "ignore", windowsHide: true, env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
229
+ }
230
+
231
+ async function installTailscaleMac(sudoPassword, log) {
232
+ if (hasBrew()) {
233
+ log("Installing via Homebrew...");
234
+ await new Promise((resolve, reject) => {
235
+ const child = spawn("brew", ["install", "tailscale"], {
236
+ stdio: ["ignore", "pipe", "pipe"],
237
+ windowsHide: true,
238
+ env: { ...process.env, PATH: EXTENDED_PATH }
239
+ });
240
+ child.stdout.on("data", (d) => {
241
+ const line = d.toString().trim();
242
+ if (line) log(line);
243
+ });
244
+ child.stderr.on("data", (d) => {
245
+ const line = d.toString().trim();
246
+ if (line) log(line);
247
+ });
248
+ child.on("close", (c) => {
249
+ if (c === 0) resolve();
250
+ else reject(new Error(`brew install failed (code ${c})`));
251
+ });
252
+ child.on("error", reject);
253
+ });
254
+ return;
255
+ }
256
+
257
+ // No brew: download .pkg and install via sudo installer
258
+ const pkgUrl = "https://pkgs.tailscale.com/stable/tailscale-latest.pkg";
259
+ const pkgPath = path.join(os.tmpdir(), "tailscale.pkg");
260
+
261
+ log("Downloading Tailscale package...");
262
+ await new Promise((resolve, reject) => {
263
+ const child = spawn("curl", ["-fL", "--progress-bar", pkgUrl, "-o", pkgPath], {
264
+ stdio: ["ignore", "pipe", "pipe"],
265
+ windowsHide: true
266
+ });
267
+ child.stderr.on("data", (d) => {
268
+ const line = d.toString().trim();
269
+ if (line) log(line);
270
+ });
271
+ child.on("close", (c) => {
272
+ if (c === 0) resolve();
273
+ else reject(new Error("Download failed"));
274
+ });
275
+ child.on("error", reject);
276
+ });
277
+
278
+ log("Installing package...");
279
+ await new Promise((resolve, reject) => {
280
+ const child = spawn("sudo", ["-S", "installer", "-pkg", pkgPath, "-target", "/"], {
281
+ stdio: ["pipe", "pipe", "pipe"],
282
+ windowsHide: true
283
+ });
284
+ let stderr = "";
285
+ child.stderr.on("data", (d) => { stderr += d.toString(); });
286
+ child.stdout.on("data", (d) => {
287
+ const line = d.toString().trim();
288
+ if (line) log(line);
289
+ });
290
+ child.on("close", (c) => {
291
+ try { execSync(`rm -f ${pkgPath}`, { stdio: "ignore", windowsHide: true }); } catch { /* ignore */ }
292
+ if (c === 0) resolve();
293
+ else {
294
+ const msg = (stderr.includes("incorrect password") || stderr.includes("Sorry"))
295
+ ? "Wrong sudo password"
296
+ : stderr || `Exit code ${c}`;
297
+ reject(new Error(msg));
298
+ }
299
+ });
300
+ child.on("error", reject);
301
+ child.stdin.write(`${sudoPassword}\n`);
302
+ child.stdin.end();
303
+ });
304
+ }
305
+
306
+ async function installTailscaleLinux(sudoPassword, log) {
307
+ // Reject password containing newline → prevents stdin command injection
308
+ if (typeof sudoPassword !== "string" || sudoPassword.includes("\n")) {
309
+ throw new Error("Invalid sudo password");
310
+ }
311
+ log("Downloading install script...");
312
+ return new Promise((resolve, reject) => {
313
+ const curlChild = spawn("curl", ["-fsSL", "https://tailscale.com/install.sh"], {
314
+ stdio: ["ignore", "pipe", "pipe"],
315
+ windowsHide: true
316
+ });
317
+ let scriptContent = "";
318
+ let curlErr = "";
319
+ curlChild.stdout.on("data", (d) => { scriptContent += d.toString(); });
320
+ curlChild.stderr.on("data", (d) => { curlErr += d.toString(); });
321
+ curlChild.on("exit", (code) => {
322
+ if (code !== 0) return reject(new Error(`Failed to download install script: ${curlErr}`));
323
+ log("Running install script...");
324
+ // Persist script to temp file → exec by path (NOT via stdin) → sh never reads attacker-controlled stdin
325
+ const tmpScript = path.join(os.tmpdir(), `tailscale-install-${crypto.randomBytes(8).toString("hex")}.sh`);
326
+ try {
327
+ fs.writeFileSync(tmpScript, scriptContent, { mode: 0o700 });
328
+ } catch (e) {
329
+ return reject(new Error(`Failed to write install script: ${e.message}`));
330
+ }
331
+ const cleanup = () => { try { fs.unlinkSync(tmpScript); } catch {} };
332
+ const child = spawn("sudo", ["-S", "sh", tmpScript], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
333
+ let stderr = "";
334
+ child.stdout.on("data", (d) => {
335
+ const line = d.toString().trim();
336
+ if (line) log(line);
337
+ });
338
+ child.stderr.on("data", (d) => { stderr += d.toString(); });
339
+ child.on("close", (c) => {
340
+ cleanup();
341
+ if (c === 0) resolve();
342
+ else {
343
+ const msg = (stderr.includes("incorrect password") || stderr.includes("Sorry"))
344
+ ? "Wrong sudo password"
345
+ : stderr || `Exit code ${c}`;
346
+ reject(new Error(msg));
347
+ }
348
+ });
349
+ child.on("error", (e) => { cleanup(); reject(e); });
350
+ child.stdin.write(`${sudoPassword}\n`);
351
+ child.stdin.end();
352
+ });
353
+ curlChild.on("error", reject);
354
+ });
355
+ }
356
+
357
+ async function installTailscaleWindows(log) {
358
+ const msiUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest-amd64.msi";
359
+ const msiPath = path.join(os.tmpdir(), "tailscale-setup.msi");
360
+
361
+ // Download MSI via curl.exe (built-in on Win10+) — no PowerShell window, streams progress
362
+ log("Downloading Tailscale installer...");
363
+ await new Promise((resolve, reject) => {
364
+ const child = spawn("curl.exe", ["-L", "-#", "-o", msiPath, msiUrl], {
365
+ stdio: ["ignore", "pipe", "pipe"],
366
+ windowsHide: true
367
+ });
368
+ // curl outputs progress to stderr with -# flag
369
+ let lastPct = "";
370
+ child.stderr.on("data", (d) => {
371
+ const text = d.toString();
372
+ const match = text.match(/(\d+\.\d)%/);
373
+ if (match && match[1] !== lastPct) {
374
+ lastPct = match[1];
375
+ log(`Downloading... ${lastPct}%`);
376
+ }
377
+ });
378
+ child.on("close", (c) => c === 0 ? resolve() : reject(new Error("Download failed")));
379
+ child.on("error", reject);
380
+ });
381
+
382
+ // Install MSI with UAC elevation via PowerShell Start-Process -Verb RunAs
383
+ log("Installing Tailscale (UAC prompt may appear)...");
384
+ await new Promise((resolve, reject) => {
385
+ const args = `'/i','${msiPath}','TS_NOLAUNCH=true','/quiet','/norestart'`;
386
+ const child = spawn("powershell", [
387
+ "-NoProfile", "-NonInteractive", "-Command",
388
+ `Start-Process msiexec -ArgumentList ${args} -Verb RunAs -Wait`
389
+ ], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
390
+ child.stderr.on("data", (d) => { const l = d.toString().trim(); if (l) log(l); });
391
+ child.on("close", (c) => {
392
+ try { fs.unlinkSync(msiPath); } catch { /* ignore */ }
393
+ c === 0 ? resolve() : reject(new Error(`msiexec failed (code ${c})`));
394
+ });
395
+ child.on("error", reject);
396
+ });
397
+
398
+ // Verify tailscale.exe exists after install
399
+ log("Verifying installation...");
400
+ const maxWait = 10000;
401
+ const start = Date.now();
402
+ while (Date.now() - start < maxWait) {
403
+ if (fs.existsSync(WINDOWS_TAILSCALE_BIN)) {
404
+ log("Installation complete.");
405
+ return;
406
+ }
407
+ await new Promise((r) => setTimeout(r, 1000));
408
+ }
409
+ throw new Error("Installation finished but tailscale.exe not found");
410
+ }
411
+
412
+ // Self-heal: if state dir/files were previously created by root (e.g. legacy sudo daemon),
413
+ // reclaim ownership recursively so the user-mode daemon can read/write state files.
414
+ async function ensureUserOwnedDir(dir) {
415
+ try {
416
+ if (!fs.existsSync(dir)) {
417
+ fs.mkdirSync(dir, { recursive: true });
418
+ return;
419
+ }
420
+ const uid = process.getuid();
421
+ const gid = process.getgid();
422
+
423
+ // Walk dir + all entries to find any non-user-owned items
424
+ const needsChown = (() => {
425
+ const stack = [dir];
426
+ while (stack.length) {
427
+ const cur = stack.pop();
428
+ try {
429
+ const st = fs.statSync(cur);
430
+ if (st.uid !== uid) return true;
431
+ if (st.isDirectory()) {
432
+ for (const name of fs.readdirSync(cur)) stack.push(path.join(cur, name));
433
+ }
434
+ } catch { /* ignore */ }
435
+ }
436
+ return false;
437
+ })();
438
+
439
+ if (!needsChown) return;
440
+
441
+ // Try direct chown first (works if already owned). Fallback to passwordless sudo.
442
+ try {
443
+ execSync(`chown -R ${uid}:${gid} "${dir}"`, { stdio: "ignore", timeout: 3000 });
444
+ } catch {
445
+ try { execSync(`sudo -n chown -R ${uid}:${gid} "${dir}"`, { stdio: "ignore", timeout: 3000 }); } catch { /* ignore */ }
446
+ }
447
+ } catch { /* ignore */ }
448
+ }
449
+
450
+ /** Check if running daemon uses TUN mode (Funnel TLS requires TUN). */
451
+ function isDaemonTunMode() {
452
+ try {
453
+ const ps = execSync(`pgrep -af "tailscaled.*${TAILSCALE_SOCKET}"`, { encoding: "utf8", timeout: 2000 }).trim();
454
+ if (!ps) return null;
455
+ return !ps.includes("--tun=userspace-networking");
456
+ } catch { return null; }
457
+ }
458
+
459
+ /**
460
+ * Start tailscaled.
461
+ * - With sudoPassword: TUN mode (root) → Funnel TLS works
462
+ * - Without: userspace-networking fallback (no sudo, but Funnel TLS unstable)
463
+ * State always lives in ~/.9router/tailscale/ via --statedir.
464
+ */
465
+ export async function startDaemonWithPassword(sudoPassword) {
466
+ if (IS_WINDOWS) {
467
+ // Windows: tailscale runs as a Windows Service. Start it then poll BackendState
468
+ // until daemon finishes init (avoids "NoState" errors when calling funnel/up too early).
469
+ const bin = getTailscaleBin();
470
+ console.log("[Tailscale] win: net start Tailscale");
471
+ try { execSync("net start Tailscale", { stdio: "ignore", windowsHide: true, timeout: 10000 }); }
472
+ catch { /* may need admin, or already running */ }
473
+ if (!bin) return;
474
+ // Poll up to ~10s for backend to leave NoState
475
+ for (let i = 0; i < 20; i++) {
476
+ try {
477
+ const out = execSync(`"${bin}" status --json`, { encoding: "utf8", windowsHide: true, timeout: 2000 });
478
+ const j = JSON.parse(out);
479
+ if (j.BackendState && j.BackendState !== "NoState") {
480
+ console.log(`[Tailscale] win: BackendState=${j.BackendState} after ${i*500}ms`);
481
+ return;
482
+ }
483
+ } catch { /* daemon not ready */ }
484
+ await new Promise((r) => setTimeout(r, 500));
485
+ }
486
+ console.log("[Tailscale] win: BackendState still NoState after poll");
487
+ return;
488
+ }
489
+
490
+ const wantTun = !!sudoPassword;
491
+ const currentMode = isDaemonTunMode(); // true=TUN, false=userspace, null=not running
492
+
493
+ // Daemon already running in correct mode → reuse
494
+ if (currentMode !== null && currentMode === wantTun) {
495
+ try {
496
+ const bin = getTailscaleBin() || "tailscale";
497
+ execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
498
+ stdio: "ignore", windowsHide: true,
499
+ env: { ...process.env, PATH: EXTENDED_PATH }, timeout: 3000
500
+ });
501
+ return;
502
+ } catch { /* unresponsive, restart below */ }
503
+ }
504
+
505
+ // Mode mismatch or unresponsive → kill all daemons on our socket
506
+ try { execSync(`pkill -9 -f "tailscaled.*${TAILSCALE_SOCKET}"`, { stdio: "ignore", timeout: 3000 }); } catch { /* ignore */ }
507
+ if (sudoPassword) {
508
+ try { await execWithPassword(`pkill -9 -f "tailscaled.*${TAILSCALE_SOCKET}"`, sudoPassword); } catch { /* ignore */ }
509
+ } else {
510
+ try { execSync(`sudo -n pkill -9 -f "tailscaled.*${TAILSCALE_SOCKET}"`, { stdio: "ignore", timeout: 3000 }); } catch { /* ignore */ }
511
+ }
512
+ await new Promise((r) => setTimeout(r, 1500));
513
+
514
+ // Reclaim folder ownership (previous root daemon may have locked it)
515
+ await ensureUserOwnedDir(TAILSCALE_DIR);
516
+
517
+ const tailscaledBin = IS_MAC ? "/usr/local/bin/tailscaled" : "tailscaled";
518
+ const daemonArgs = [
519
+ `--socket=${TAILSCALE_SOCKET}`,
520
+ `--statedir=${TAILSCALE_DIR}`,
521
+ ];
522
+ if (!wantTun) daemonArgs.push("--tun=userspace-networking");
523
+
524
+ if (wantTun) {
525
+ // TUN mode: spawn via sudo, password via stdin. Detached so it survives parent exit.
526
+ const child = spawn("sudo", ["-S", tailscaledBin, ...daemonArgs], {
527
+ detached: true,
528
+ stdio: ["pipe", "ignore", "ignore"],
529
+ cwd: os.tmpdir(),
530
+ env: { ...process.env, PATH: EXTENDED_PATH },
531
+ });
532
+ child.stdin.write(`${sudoPassword}\n`);
533
+ child.stdin.end();
534
+ child.unref();
535
+ } else {
536
+ const child = spawn(tailscaledBin, daemonArgs, {
537
+ detached: true,
538
+ stdio: "ignore",
539
+ cwd: os.tmpdir(),
540
+ env: { ...process.env, PATH: EXTENDED_PATH },
541
+ });
542
+ child.unref();
543
+ }
544
+
545
+ // Wait for socket ready
546
+ await new Promise((r) => setTimeout(r, 3000));
547
+ }
548
+
549
+ /** Best-effort: ensure daemon running (used for login flow) */
550
+ function ensureDaemon() {
551
+ startDaemonWithPassword("").catch(() => {});
552
+ }
553
+
554
+ /** Read AuthURL from `tailscale status --json` (Win exposes it there, not stdout). */
555
+ function getAuthUrlFromStatus() {
556
+ const bin = getTailscaleBin();
557
+ if (!bin) return null;
558
+ try {
559
+ const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
560
+ encoding: "utf8", windowsHide: true, timeout: 2000
561
+ });
562
+ const j = JSON.parse(out);
563
+ if (j.AuthURL) return j.AuthURL;
564
+ return null;
565
+ } catch { return null; }
566
+ }
567
+
568
+ /**
569
+ * Run `tailscale up` and capture the auth URL for browser login.
570
+ * Resolves with { authUrl } or { alreadyLoggedIn: true }.
571
+ * On Windows, AuthURL comes from `status --json` (not stdout) — must poll status.
572
+ */
573
+ export function startLogin(hostname) {
574
+ const bin = getTailscaleBin();
575
+ if (!bin) return Promise.reject(new Error("Tailscale not installed"));
576
+
577
+ return new Promise((resolve, reject) => {
578
+ // Ensure daemon is running (best-effort, no sudo)
579
+ ensureDaemon();
580
+
581
+ // Check if already logged in
582
+ if (isTailscaleLoggedIn()) {
583
+ resolve({ alreadyLoggedIn: true });
584
+ return;
585
+ }
586
+
587
+ const args = tsArgs("up", "--accept-routes");
588
+ if (hostname) args.push(`--hostname=${hostname}`);
589
+ const child = spawn(bin, args, {
590
+ stdio: ["ignore", "pipe", "pipe"],
591
+ detached: true,
592
+ windowsHide: true
593
+ });
594
+
595
+ let resolved = false;
596
+ let output = "";
597
+
598
+ const parseAuthUrl = (text) => {
599
+ const match = text.match(/https:\/\/login\.tailscale\.com\/a\/[a-zA-Z0-9]+/);
600
+ return match ? match[0] : null;
601
+ };
602
+
603
+ const finishWithUrl = (url, source) => {
604
+ if (resolved) return;
605
+ resolved = true;
606
+ clearTimeout(timeout);
607
+ clearInterval(statusPoll);
608
+ console.log(`[Tailscale] login authUrl detected (${source})`);
609
+ child.unref();
610
+ resolve({ authUrl: url });
611
+ };
612
+
613
+ // Poll status --json every 500ms — Windows exposes AuthURL only there
614
+ const statusPoll = setInterval(() => {
615
+ if (resolved) return;
616
+ const url = getAuthUrlFromStatus();
617
+ if (url) finishWithUrl(url, "status");
618
+ }, 500);
619
+
620
+ const timeout = setTimeout(() => {
621
+ if (resolved) return;
622
+ resolved = true;
623
+ clearInterval(statusPoll);
624
+ child.unref();
625
+ const url = parseAuthUrl(output) || getAuthUrlFromStatus();
626
+ if (url) resolve({ authUrl: url });
627
+ else reject(new Error("tailscale up timed out without auth URL"));
628
+ }, 15000);
629
+
630
+ const handleData = (data) => {
631
+ output += data.toString();
632
+ const url = parseAuthUrl(output);
633
+ if (url) finishWithUrl(url, "stdout");
634
+ };
635
+
636
+ child.stdout.on("data", handleData);
637
+ child.stderr.on("data", handleData);
638
+
639
+ child.on("error", (err) => {
640
+ if (resolved) return;
641
+ resolved = true;
642
+ clearTimeout(timeout);
643
+ clearInterval(statusPoll);
644
+ console.error(`[Tailscale] login spawn error: ${err.message}`);
645
+ reject(err);
646
+ });
647
+
648
+ child.on("exit", (code) => {
649
+ if (resolved) return;
650
+ console.log(`[Tailscale] login exit code=${code}`);
651
+ // Don't trust exit code alone — Win `tailscale up` exits 0 even when not logged in.
652
+ // Let status poll continue until AuthURL appears or timeout.
653
+ const url = parseAuthUrl(output) || getAuthUrlFromStatus();
654
+ if (url) {
655
+ finishWithUrl(url, "exit");
656
+ return;
657
+ }
658
+ // Only resolve alreadyLoggedIn if status confirms BackendState=Running
659
+ if (isTailscaleLoggedIn()) {
660
+ resolved = true;
661
+ clearTimeout(timeout);
662
+ clearInterval(statusPoll);
663
+ resolve({ alreadyLoggedIn: true });
664
+ return;
665
+ }
666
+ // Otherwise keep polling — daemon may publish AuthURL shortly after exit
667
+ });
668
+ });
669
+ }
670
+
671
+ /** Start tailscale funnel for the given port */
672
+ export async function startFunnel(port) {
673
+ const bin = getTailscaleBin();
674
+ if (!bin) throw new Error("Tailscale not installed");
675
+
676
+ // Reset any existing funnel
677
+ try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore", windowsHide: true }); } catch (e) { /* ignore */ }
678
+
679
+ return new Promise((resolve, reject) => {
680
+ const child = spawn(bin, tsArgs("funnel", "--bg", `${port}`), {
681
+ stdio: ["ignore", "pipe", "pipe"],
682
+ windowsHide: true
683
+ });
684
+
685
+ let resolved = false;
686
+ let output = "";
687
+
688
+ const timeout = setTimeout(() => {
689
+ if (resolved) return;
690
+ resolved = true;
691
+ // --bg exits after setup, read actual hostname from status
692
+ const url = getActualFunnelUrl() || getTailscaleFunnelUrl(port);
693
+ if (url) resolve({ tunnelUrl: url });
694
+ else reject(new Error(`Tailscale funnel timed out: ${output.trim() || "no output"}`));
695
+ }, 30000);
696
+
697
+ // Always resolve via Self.DNSName to get the real hostname (avoids -1 suffix from conflicts)
698
+ const parseFunnelUrl = () => getActualFunnelUrl();
699
+
700
+ let funnelNotEnabled = false;
701
+
702
+ const handleData = (data) => {
703
+ output += data.toString();
704
+
705
+ if (output.includes("Funnel is not enabled")) funnelNotEnabled = true;
706
+
707
+ // Wait for the enable URL to arrive in a later chunk
708
+ if (funnelNotEnabled && !resolved) {
709
+ const enableMatch = output.match(/https:\/\/login\.tailscale\.com\/[^\s]+/);
710
+ if (enableMatch) {
711
+ resolved = true;
712
+ clearTimeout(timeout);
713
+ child.kill();
714
+ resolve({ funnelNotEnabled: true, enableUrl: enableMatch[0] });
715
+ return;
716
+ }
717
+ }
718
+
719
+ const url = parseFunnelUrl();
720
+ if (url && !resolved) {
721
+ resolved = true;
722
+ clearTimeout(timeout);
723
+ resolve({ tunnelUrl: url });
724
+ }
725
+ };
726
+
727
+ child.stdout.on("data", handleData);
728
+ child.stderr.on("data", handleData);
729
+
730
+ child.on("exit", (code) => {
731
+ if (resolved) return;
732
+ resolved = true;
733
+ clearTimeout(timeout);
734
+ console.log(`[Tailscale] funnel exit code=${code} output="${output.trim().slice(0, 200)}"`);
735
+ const url = parseFunnelUrl() || getTailscaleFunnelUrl(port);
736
+ if (url) resolve({ tunnelUrl: url });
737
+ else reject(new Error(`tailscale funnel failed (code ${code}): ${output.trim()}`));
738
+ });
739
+
740
+ child.on("error", (err) => {
741
+ if (resolved) return;
742
+ resolved = true;
743
+ clearTimeout(timeout);
744
+ reject(err);
745
+ });
746
+ });
747
+ }
748
+
749
+ /** Provision TLS cert for funnel domain (required before Funnel serves HTTPS). Best-effort. */
750
+ export async function provisionCert(hostname) {
751
+ const bin = getTailscaleBin();
752
+ if (!bin || !hostname) return;
753
+ const certsDir = path.join(TAILSCALE_DIR, "certs");
754
+ fs.mkdirSync(certsDir, { recursive: true });
755
+ const certFile = path.join(certsDir, `${hostname}.crt`);
756
+ const keyFile = path.join(certsDir, `${hostname}.key`);
757
+ try {
758
+ await execAsync(
759
+ `"${bin}" ${SOCKET_FLAG.join(" ")} cert --cert-file "${certFile}" --key-file "${keyFile}" "${hostname}"`,
760
+ { windowsHide: true, env: { ...process.env, PATH: EXTENDED_PATH }, timeout: 30000 }
761
+ );
762
+ console.log(`[Tailscale] cert provisioned for ${hostname}`);
763
+ } catch (e) {
764
+ console.warn(`[Tailscale] cert provision failed (non-fatal): ${e.message}`);
765
+ }
766
+ }
767
+
768
+ /** Stop tailscale funnel */
769
+ export function stopFunnel() {
770
+ const bin = getTailscaleBin();
771
+ if (!bin) return;
772
+ try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore", windowsHide: true }); } catch (e) { /* ignore */ }
773
+ }
774
+
775
+ /** Kill tailscaled daemon (runs as root, needs sudo) */
776
+ export async function stopDaemon(sudoPassword) {
777
+ // Try non-sudo first
778
+ try { execSync("pkill -x tailscaled", { stdio: "ignore", windowsHide: true, timeout: 3000 }); } catch { /* ignore */ }
779
+
780
+ // Check if still alive
781
+ try { execSync("pgrep -x tailscaled", { stdio: "ignore", windowsHide: true, timeout: 2000 }); } catch { return; } // Dead, done
782
+
783
+ // Kill with sudo password
784
+ if (!IS_WINDOWS) {
785
+ try { await execWithPassword("pkill -x tailscaled", sudoPassword || ""); } catch { /* ignore */ }
786
+ }
787
+
788
+ // Cleanup socket
789
+ try { if (fs.existsSync(TAILSCALE_SOCKET)) fs.unlinkSync(TAILSCALE_SOCKET); } catch { /* ignore */ }
790
+ }