agentdog 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +244 -0
  3. package/dist/adapters/adapter-factory.d.ts +21 -0
  4. package/dist/adapters/adapter-factory.d.ts.map +1 -0
  5. package/dist/adapters/adapter-factory.js +157 -0
  6. package/dist/adapters/adapter-factory.js.map +1 -0
  7. package/dist/adapters/http-sse-adapter.d.ts +49 -0
  8. package/dist/adapters/http-sse-adapter.d.ts.map +1 -0
  9. package/dist/adapters/http-sse-adapter.js +485 -0
  10. package/dist/adapters/http-sse-adapter.js.map +1 -0
  11. package/dist/adapters/stdio-adapter.d.ts +69 -0
  12. package/dist/adapters/stdio-adapter.d.ts.map +1 -0
  13. package/dist/adapters/stdio-adapter.js +664 -0
  14. package/dist/adapters/stdio-adapter.js.map +1 -0
  15. package/dist/adapters/streamable-http-adapter.d.ts +38 -0
  16. package/dist/adapters/streamable-http-adapter.d.ts.map +1 -0
  17. package/dist/adapters/streamable-http-adapter.js +321 -0
  18. package/dist/adapters/streamable-http-adapter.js.map +1 -0
  19. package/dist/ai-gateway/anthropic-errors.d.ts +4 -0
  20. package/dist/ai-gateway/anthropic-errors.d.ts.map +1 -0
  21. package/dist/ai-gateway/anthropic-errors.js +17 -0
  22. package/dist/ai-gateway/anthropic-errors.js.map +1 -0
  23. package/dist/ai-gateway/auth.d.ts +3 -0
  24. package/dist/ai-gateway/auth.d.ts.map +1 -0
  25. package/dist/ai-gateway/auth.js +14 -0
  26. package/dist/ai-gateway/auth.js.map +1 -0
  27. package/dist/ai-gateway/auth.test.d.ts +2 -0
  28. package/dist/ai-gateway/auth.test.d.ts.map +1 -0
  29. package/dist/ai-gateway/auth.test.js +57 -0
  30. package/dist/ai-gateway/auth.test.js.map +1 -0
  31. package/dist/ai-gateway/count-tokens.d.ts +5 -0
  32. package/dist/ai-gateway/count-tokens.d.ts.map +1 -0
  33. package/dist/ai-gateway/count-tokens.js +37 -0
  34. package/dist/ai-gateway/count-tokens.js.map +1 -0
  35. package/dist/ai-gateway/count-tokens.test.d.ts +2 -0
  36. package/dist/ai-gateway/count-tokens.test.d.ts.map +1 -0
  37. package/dist/ai-gateway/count-tokens.test.js +19 -0
  38. package/dist/ai-gateway/count-tokens.test.js.map +1 -0
  39. package/dist/ai-gateway/dialects.d.ts +4 -0
  40. package/dist/ai-gateway/dialects.d.ts.map +1 -0
  41. package/dist/ai-gateway/dialects.js +15 -0
  42. package/dist/ai-gateway/dialects.js.map +1 -0
  43. package/dist/ai-gateway/gateway-server.d.ts +15 -0
  44. package/dist/ai-gateway/gateway-server.d.ts.map +1 -0
  45. package/dist/ai-gateway/gateway-server.js +121 -0
  46. package/dist/ai-gateway/gateway-server.js.map +1 -0
  47. package/dist/ai-gateway/integration.test.d.ts +2 -0
  48. package/dist/ai-gateway/integration.test.d.ts.map +1 -0
  49. package/dist/ai-gateway/integration.test.js +205 -0
  50. package/dist/ai-gateway/integration.test.js.map +1 -0
  51. package/dist/ai-gateway/ir/anthropic-in.d.ts +3 -0
  52. package/dist/ai-gateway/ir/anthropic-in.d.ts.map +1 -0
  53. package/dist/ai-gateway/ir/anthropic-in.js +121 -0
  54. package/dist/ai-gateway/ir/anthropic-in.js.map +1 -0
  55. package/dist/ai-gateway/ir/anthropic-in.test.d.ts +2 -0
  56. package/dist/ai-gateway/ir/anthropic-in.test.d.ts.map +1 -0
  57. package/dist/ai-gateway/ir/anthropic-in.test.js +93 -0
  58. package/dist/ai-gateway/ir/anthropic-in.test.js.map +1 -0
  59. package/dist/ai-gateway/ir/anthropic-sse.d.ts +22 -0
  60. package/dist/ai-gateway/ir/anthropic-sse.d.ts.map +1 -0
  61. package/dist/ai-gateway/ir/anthropic-sse.js +200 -0
  62. package/dist/ai-gateway/ir/anthropic-sse.js.map +1 -0
  63. package/dist/ai-gateway/ir/anthropic-sse.test.d.ts +2 -0
  64. package/dist/ai-gateway/ir/anthropic-sse.test.d.ts.map +1 -0
  65. package/dist/ai-gateway/ir/anthropic-sse.test.js +101 -0
  66. package/dist/ai-gateway/ir/anthropic-sse.test.js.map +1 -0
  67. package/dist/ai-gateway/ir/gemini-out.d.ts +20 -0
  68. package/dist/ai-gateway/ir/gemini-out.d.ts.map +1 -0
  69. package/dist/ai-gateway/ir/gemini-out.js +218 -0
  70. package/dist/ai-gateway/ir/gemini-out.js.map +1 -0
  71. package/dist/ai-gateway/ir/gemini-out.test.d.ts +2 -0
  72. package/dist/ai-gateway/ir/gemini-out.test.d.ts.map +1 -0
  73. package/dist/ai-gateway/ir/gemini-out.test.js +125 -0
  74. package/dist/ai-gateway/ir/gemini-out.test.js.map +1 -0
  75. package/dist/ai-gateway/ir/openai-out.d.ts +17 -0
  76. package/dist/ai-gateway/ir/openai-out.d.ts.map +1 -0
  77. package/dist/ai-gateway/ir/openai-out.js +226 -0
  78. package/dist/ai-gateway/ir/openai-out.js.map +1 -0
  79. package/dist/ai-gateway/ir/openai-out.test.d.ts +2 -0
  80. package/dist/ai-gateway/ir/openai-out.test.d.ts.map +1 -0
  81. package/dist/ai-gateway/ir/openai-out.test.js +144 -0
  82. package/dist/ai-gateway/ir/openai-out.test.js.map +1 -0
  83. package/dist/ai-gateway/ir/types.d.ts +77 -0
  84. package/dist/ai-gateway/ir/types.d.ts.map +1 -0
  85. package/dist/ai-gateway/ir/types.js +4 -0
  86. package/dist/ai-gateway/ir/types.js.map +1 -0
  87. package/dist/ai-gateway/ir-channel.d.ts +15 -0
  88. package/dist/ai-gateway/ir-channel.d.ts.map +1 -0
  89. package/dist/ai-gateway/ir-channel.js +120 -0
  90. package/dist/ai-gateway/ir-channel.js.map +1 -0
  91. package/dist/ai-gateway/model-router.d.ts +11 -0
  92. package/dist/ai-gateway/model-router.d.ts.map +1 -0
  93. package/dist/ai-gateway/model-router.js +27 -0
  94. package/dist/ai-gateway/model-router.js.map +1 -0
  95. package/dist/ai-gateway/model-router.test.d.ts +2 -0
  96. package/dist/ai-gateway/model-router.test.d.ts.map +1 -0
  97. package/dist/ai-gateway/model-router.test.js +43 -0
  98. package/dist/ai-gateway/model-router.test.js.map +1 -0
  99. package/dist/ai-gateway/passthrough.d.ts +9 -0
  100. package/dist/ai-gateway/passthrough.d.ts.map +1 -0
  101. package/dist/ai-gateway/passthrough.js +81 -0
  102. package/dist/ai-gateway/passthrough.js.map +1 -0
  103. package/dist/ai-gateway/passthrough.test.d.ts +2 -0
  104. package/dist/ai-gateway/passthrough.test.d.ts.map +1 -0
  105. package/dist/ai-gateway/passthrough.test.js +108 -0
  106. package/dist/ai-gateway/passthrough.test.js.map +1 -0
  107. package/dist/ai-gateway/upstream/model-lister.d.ts +13 -0
  108. package/dist/ai-gateway/upstream/model-lister.d.ts.map +1 -0
  109. package/dist/ai-gateway/upstream/model-lister.js +61 -0
  110. package/dist/ai-gateway/upstream/model-lister.js.map +1 -0
  111. package/dist/ai-gateway/upstream/provider-client.d.ts +20 -0
  112. package/dist/ai-gateway/upstream/provider-client.d.ts.map +1 -0
  113. package/dist/ai-gateway/upstream/provider-client.js +76 -0
  114. package/dist/ai-gateway/upstream/provider-client.js.map +1 -0
  115. package/dist/ai-gateway/upstream/provider-client.test.d.ts +2 -0
  116. package/dist/ai-gateway/upstream/provider-client.test.d.ts.map +1 -0
  117. package/dist/ai-gateway/upstream/provider-client.test.js +69 -0
  118. package/dist/ai-gateway/upstream/provider-client.test.js.map +1 -0
  119. package/dist/cli/cli-main.d.ts +7 -0
  120. package/dist/cli/cli-main.d.ts.map +1 -0
  121. package/dist/cli/cli-main.js +116 -0
  122. package/dist/cli/cli-main.js.map +1 -0
  123. package/dist/cli/cli-router.d.ts +20 -0
  124. package/dist/cli/cli-router.d.ts.map +1 -0
  125. package/dist/cli/cli-router.js +271 -0
  126. package/dist/cli/cli-router.js.map +1 -0
  127. package/dist/cli/cli-utils.d.ts +30 -0
  128. package/dist/cli/cli-utils.d.ts.map +1 -0
  129. package/dist/cli/cli-utils.js +219 -0
  130. package/dist/cli/cli-utils.js.map +1 -0
  131. package/dist/cli/commands/audit-commands.d.ts +30 -0
  132. package/dist/cli/commands/audit-commands.d.ts.map +1 -0
  133. package/dist/cli/commands/audit-commands.js +417 -0
  134. package/dist/cli/commands/audit-commands.js.map +1 -0
  135. package/dist/cli/commands/autostart-command.d.ts +21 -0
  136. package/dist/cli/commands/autostart-command.d.ts.map +1 -0
  137. package/dist/cli/commands/autostart-command.js +243 -0
  138. package/dist/cli/commands/autostart-command.js.map +1 -0
  139. package/dist/cli/commands/autostart-command.test.d.ts +2 -0
  140. package/dist/cli/commands/autostart-command.test.d.ts.map +1 -0
  141. package/dist/cli/commands/autostart-command.test.js +35 -0
  142. package/dist/cli/commands/autostart-command.test.js.map +1 -0
  143. package/dist/cli/commands/config-commands.d.ts +30 -0
  144. package/dist/cli/commands/config-commands.d.ts.map +1 -0
  145. package/dist/cli/commands/config-commands.js +1013 -0
  146. package/dist/cli/commands/config-commands.js.map +1 -0
  147. package/dist/cli/commands/daemon-commands.d.ts +76 -0
  148. package/dist/cli/commands/daemon-commands.d.ts.map +1 -0
  149. package/dist/cli/commands/daemon-commands.js +451 -0
  150. package/dist/cli/commands/daemon-commands.js.map +1 -0
  151. package/dist/cli/commands/detect-commands.d.ts +17 -0
  152. package/dist/cli/commands/detect-commands.d.ts.map +1 -0
  153. package/dist/cli/commands/detect-commands.js +293 -0
  154. package/dist/cli/commands/detect-commands.js.map +1 -0
  155. package/dist/cli/commands/diagnose-commands.d.ts +19 -0
  156. package/dist/cli/commands/diagnose-commands.d.ts.map +1 -0
  157. package/dist/cli/commands/diagnose-commands.js +326 -0
  158. package/dist/cli/commands/diagnose-commands.js.map +1 -0
  159. package/dist/cli/commands/optimize-commands.d.ts +14 -0
  160. package/dist/cli/commands/optimize-commands.d.ts.map +1 -0
  161. package/dist/cli/commands/optimize-commands.js +189 -0
  162. package/dist/cli/commands/optimize-commands.js.map +1 -0
  163. package/dist/cli/commands/proxy-command.d.ts +27 -0
  164. package/dist/cli/commands/proxy-command.d.ts.map +1 -0
  165. package/dist/cli/commands/proxy-command.js +212 -0
  166. package/dist/cli/commands/proxy-command.js.map +1 -0
  167. package/dist/cli/commands/start-command.d.ts +22 -0
  168. package/dist/cli/commands/start-command.d.ts.map +1 -0
  169. package/dist/cli/commands/start-command.js +275 -0
  170. package/dist/cli/commands/start-command.js.map +1 -0
  171. package/dist/config/config-manager.d.ts +125 -0
  172. package/dist/config/config-manager.d.ts.map +1 -0
  173. package/dist/config/config-manager.js +569 -0
  174. package/dist/config/config-manager.js.map +1 -0
  175. package/dist/config/config-manager.test.d.ts +2 -0
  176. package/dist/config/config-manager.test.d.ts.map +1 -0
  177. package/dist/config/config-manager.test.js +209 -0
  178. package/dist/config/config-manager.test.js.map +1 -0
  179. package/dist/core/auto-config-generator.d.ts +55 -0
  180. package/dist/core/auto-config-generator.d.ts.map +1 -0
  181. package/dist/core/auto-config-generator.js +278 -0
  182. package/dist/core/auto-config-generator.js.map +1 -0
  183. package/dist/core/mcpdog-server.d.ts +49 -0
  184. package/dist/core/mcpdog-server.d.ts.map +1 -0
  185. package/dist/core/mcpdog-server.js +552 -0
  186. package/dist/core/mcpdog-server.js.map +1 -0
  187. package/dist/core/protocol-detector.d.ts +56 -0
  188. package/dist/core/protocol-detector.d.ts.map +1 -0
  189. package/dist/core/protocol-detector.js +365 -0
  190. package/dist/core/protocol-detector.js.map +1 -0
  191. package/dist/daemon/daemon-client.d.ts +34 -0
  192. package/dist/daemon/daemon-client.d.ts.map +1 -0
  193. package/dist/daemon/daemon-client.js +130 -0
  194. package/dist/daemon/daemon-client.js.map +1 -0
  195. package/dist/daemon/daemon-client.test.d.ts +2 -0
  196. package/dist/daemon/daemon-client.test.d.ts.map +1 -0
  197. package/dist/daemon/daemon-client.test.js +77 -0
  198. package/dist/daemon/daemon-client.test.js.map +1 -0
  199. package/dist/daemon/daemon-info.d.ts +7 -0
  200. package/dist/daemon/daemon-info.d.ts.map +1 -0
  201. package/dist/daemon/daemon-info.js +34 -0
  202. package/dist/daemon/daemon-info.js.map +1 -0
  203. package/dist/daemon/daemon-info.test.d.ts +2 -0
  204. package/dist/daemon/daemon-info.test.d.ts.map +1 -0
  205. package/dist/daemon/daemon-info.test.js +57 -0
  206. package/dist/daemon/daemon-info.test.js.map +1 -0
  207. package/dist/daemon/daemon-web-server.ai.test.d.ts +2 -0
  208. package/dist/daemon/daemon-web-server.ai.test.d.ts.map +1 -0
  209. package/dist/daemon/daemon-web-server.ai.test.js +119 -0
  210. package/dist/daemon/daemon-web-server.ai.test.js.map +1 -0
  211. package/dist/daemon/daemon-web-server.d.ts +59 -0
  212. package/dist/daemon/daemon-web-server.d.ts.map +1 -0
  213. package/dist/daemon/daemon-web-server.js +1236 -0
  214. package/dist/daemon/daemon-web-server.js.map +1 -0
  215. package/dist/daemon/daemon-web-server.test.d.ts +2 -0
  216. package/dist/daemon/daemon-web-server.test.d.ts.map +1 -0
  217. package/dist/daemon/daemon-web-server.test.js +93 -0
  218. package/dist/daemon/daemon-web-server.test.js.map +1 -0
  219. package/dist/daemon/mcpdog-daemon.d.ts +42 -0
  220. package/dist/daemon/mcpdog-daemon.d.ts.map +1 -0
  221. package/dist/daemon/mcpdog-daemon.js +302 -0
  222. package/dist/daemon/mcpdog-daemon.js.map +1 -0
  223. package/dist/daemon/stdio-proxy.d.ts +17 -0
  224. package/dist/daemon/stdio-proxy.d.ts.map +1 -0
  225. package/dist/daemon/stdio-proxy.js +143 -0
  226. package/dist/daemon/stdio-proxy.js.map +1 -0
  227. package/dist/index.d.ts +14 -0
  228. package/dist/index.d.ts.map +1 -0
  229. package/dist/index.js +198 -0
  230. package/dist/index.js.map +1 -0
  231. package/dist/intent-analyzer.d.ts +27 -0
  232. package/dist/intent-analyzer.d.ts.map +1 -0
  233. package/dist/intent-analyzer.js +140 -0
  234. package/dist/intent-analyzer.js.map +1 -0
  235. package/dist/logging/server-log-manager.d.ts +75 -0
  236. package/dist/logging/server-log-manager.d.ts.map +1 -0
  237. package/dist/logging/server-log-manager.js +185 -0
  238. package/dist/logging/server-log-manager.js.map +1 -0
  239. package/dist/mcp-registry.d.ts +35 -0
  240. package/dist/mcp-registry.d.ts.map +1 -0
  241. package/dist/mcp-registry.js +166 -0
  242. package/dist/mcp-registry.js.map +1 -0
  243. package/dist/middleware/auth.d.ts +5 -0
  244. package/dist/middleware/auth.d.ts.map +1 -0
  245. package/dist/middleware/auth.js +81 -0
  246. package/dist/middleware/auth.js.map +1 -0
  247. package/dist/mock-tool-database.d.ts +41 -0
  248. package/dist/mock-tool-database.d.ts.map +1 -0
  249. package/dist/mock-tool-database.js +461 -0
  250. package/dist/mock-tool-database.js.map +1 -0
  251. package/dist/proxy-server-manager.d.ts +11 -0
  252. package/dist/proxy-server-manager.d.ts.map +1 -0
  253. package/dist/proxy-server-manager.js +144 -0
  254. package/dist/proxy-server-manager.js.map +1 -0
  255. package/dist/router/tool-router.d.ts +61 -0
  256. package/dist/router/tool-router.d.ts.map +1 -0
  257. package/dist/router/tool-router.js +539 -0
  258. package/dist/router/tool-router.js.map +1 -0
  259. package/dist/router/tool-router.test.d.ts +2 -0
  260. package/dist/router/tool-router.test.d.ts.map +1 -0
  261. package/dist/router/tool-router.test.js +113 -0
  262. package/dist/router/tool-router.test.js.map +1 -0
  263. package/dist/streamable-http-server.d.ts +27 -0
  264. package/dist/streamable-http-server.d.ts.map +1 -0
  265. package/dist/streamable-http-server.js +302 -0
  266. package/dist/streamable-http-server.js.map +1 -0
  267. package/dist/tool-finder.d.ts +21 -0
  268. package/dist/tool-finder.d.ts.map +1 -0
  269. package/dist/tool-finder.js +86 -0
  270. package/dist/tool-finder.js.map +1 -0
  271. package/dist/tool-recommender.d.ts +23 -0
  272. package/dist/tool-recommender.d.ts.map +1 -0
  273. package/dist/tool-recommender.js +200 -0
  274. package/dist/tool-recommender.js.map +1 -0
  275. package/dist/types/index.d.ts +145 -0
  276. package/dist/types/index.d.ts.map +1 -0
  277. package/dist/types/index.js +2 -0
  278. package/dist/types/index.js.map +1 -0
  279. package/dist/utils/server-name-validator.d.ts +47 -0
  280. package/dist/utils/server-name-validator.d.ts.map +1 -0
  281. package/dist/utils/server-name-validator.js +156 -0
  282. package/dist/utils/server-name-validator.js.map +1 -0
  283. package/dist/web/web-server.d.ts +44 -0
  284. package/dist/web/web-server.d.ts.map +1 -0
  285. package/dist/web/web-server.js +500 -0
  286. package/dist/web/web-server.js.map +1 -0
  287. package/package.json +68 -0
  288. package/web/dist/assets/index-DMVhVtNU.css +1 -0
  289. package/web/dist/assets/index-VMXIgcOH.js +92 -0
  290. package/web/dist/assets/index-VMXIgcOH.js.map +1 -0
  291. package/web/dist/index.html +14 -0
@@ -0,0 +1,81 @@
1
+ import { sendUpstream } from './upstream/provider-client.js';
2
+ import { anthropicError, mapUpstreamStatus } from './anthropic-errors.js';
3
+ export const UPSTREAM_HEADER_TIMEOUT_MS = 30_000;
4
+ export const STREAM_WATCHDOG_MS = 5 * 60_000;
5
+ export async function passthroughMessages(req, res, provider, model) {
6
+ const body = { ...req.body, model };
7
+ const headers = {
8
+ 'content-type': 'application/json',
9
+ 'x-api-key': provider.apiKey,
10
+ 'anthropic-version': req.headers['anthropic-version'] || '2023-06-01',
11
+ ...(provider.headers || {}),
12
+ };
13
+ if (req.headers['anthropic-beta']) {
14
+ headers['anthropic-beta'] = req.headers['anthropic-beta'];
15
+ }
16
+ const abort = new AbortController();
17
+ res.on('close', () => abort.abort());
18
+ const url = trimTrailingSlash(provider.baseUrl) + '/v1/messages';
19
+ let upstream;
20
+ try {
21
+ upstream = await sendUpstream({
22
+ url,
23
+ method: 'POST',
24
+ headers,
25
+ body: JSON.stringify(body),
26
+ timeoutMs: UPSTREAM_HEADER_TIMEOUT_MS,
27
+ signal: abort.signal,
28
+ });
29
+ }
30
+ catch (error) {
31
+ if (abort.signal.aborted) {
32
+ return; // 客户端已断连
33
+ }
34
+ const message = error.message;
35
+ const isTimeout = /timeout/i.test(message);
36
+ anthropicError(res, isTimeout ? 504 : 502, 'api_error', `upstream request failed: ${message}`);
37
+ return;
38
+ }
39
+ // 直通通道例外:上游响应头一到即转发(含错误响应,格式原生一致)
40
+ const contentType = upstream.headers['content-type'] || '';
41
+ if (!contentType.includes('text/event-stream')) {
42
+ const text = await upstream.text();
43
+ if (res.writableEnded) {
44
+ return;
45
+ }
46
+ res.status(upstream.status).set('content-type', contentType || 'application/json').send(text);
47
+ return;
48
+ }
49
+ // 流式:SSE 原样 pipe 回传 + 无字节 watchdog
50
+ res.status(200).set({
51
+ 'content-type': 'text/event-stream',
52
+ 'cache-control': 'no-cache',
53
+ connection: 'keep-alive',
54
+ });
55
+ let lastByteAt = Date.now();
56
+ const watchdog = setInterval(() => {
57
+ if (Date.now() - lastByteAt > STREAM_WATCHDOG_MS) {
58
+ clearInterval(watchdog);
59
+ res.write(`event: error\ndata: ${JSON.stringify({ type: 'error', error: { type: 'api_error', message: 'upstream stream stalled' } })}\n\n`);
60
+ res.end();
61
+ abort.abort();
62
+ }
63
+ }, 30_000);
64
+ try {
65
+ await upstream.stream(line => {
66
+ lastByteAt = Date.now();
67
+ res.write(line + '\n');
68
+ });
69
+ }
70
+ finally {
71
+ clearInterval(watchdog);
72
+ if (!res.writableEnded) {
73
+ res.end();
74
+ }
75
+ }
76
+ }
77
+ export function trimTrailingSlash(s) {
78
+ return s.endsWith('/') ? s.slice(0, -1) : s;
79
+ }
80
+ export { mapUpstreamStatus };
81
+ //# sourceMappingURL=passthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passthrough.js","sourceRoot":"","sources":["../../src/ai-gateway/passthrough.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=passthrough.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passthrough.test.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/passthrough.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // mock 上游客户端
3
+ vi.mock('./upstream/provider-client.js', () => ({
4
+ sendUpstream: vi.fn(),
5
+ }));
6
+ import { sendUpstream } from './upstream/provider-client.js';
7
+ import { passthroughMessages } from './passthrough.js';
8
+ const provider = {
9
+ id: '1', slug: 'anthropic-official', dialect: 'anthropic',
10
+ baseUrl: 'https://api.anthropic.com', apiKey: 'sk-upstream', enabled: true,
11
+ };
12
+ function mockReqRes(body, headers = {}) {
13
+ const writes = [];
14
+ const res = {
15
+ statusCode: 0,
16
+ headers: {},
17
+ writableEnded: false,
18
+ sentText: undefined,
19
+ on: vi.fn(),
20
+ status(c) { this.statusCode = c; return this; },
21
+ set(h) { Object.assign(this.headers, h); return this; },
22
+ send(t) { this.sentText = t; this.writableEnded = true; return this; },
23
+ json(b) { this.sentText = JSON.stringify(b); this.writableEnded = true; return this; },
24
+ write(chunk) { writes.push(chunk); return true; },
25
+ end() { this.writableEnded = true; return this; },
26
+ };
27
+ return { req: { body, headers }, res, writes };
28
+ }
29
+ beforeEach(() => {
30
+ vi.mocked(sendUpstream).mockReset();
31
+ });
32
+ describe('passthroughMessages', () => {
33
+ it('转发 body 剥离 slug 前缀、保留未知原生字段、带上游认证头', async () => {
34
+ vi.mocked(sendUpstream).mockResolvedValue({
35
+ status: 200,
36
+ headers: { 'content-type': 'application/json' },
37
+ text: async () => '{"id":"msg_1"}',
38
+ stream: async () => { },
39
+ });
40
+ const { req, res } = mockReqRes({
41
+ model: 'anthropic-official:claude-sonnet-4',
42
+ max_tokens: 100,
43
+ thinking: { type: 'enabled', budget_tokens: 50 },
44
+ metadata: { user_id: 'u1' },
45
+ }, { 'anthropic-version': '2023-06-01' });
46
+ await passthroughMessages(req, res, provider, 'claude-sonnet-4');
47
+ const call = vi.mocked(sendUpstream).mock.calls[0];
48
+ expect(call[0].url).toBe('https://api.anthropic.com/v1/messages');
49
+ expect(call[0].headers['x-api-key']).toBe('sk-upstream');
50
+ const sentBody = JSON.parse(call[0].body);
51
+ expect(sentBody.model).toBe('claude-sonnet-4');
52
+ expect(sentBody.thinking).toEqual({ type: 'enabled', budget_tokens: 50 });
53
+ expect(sentBody.metadata).toEqual({ user_id: 'u1' });
54
+ expect(res.statusCode).toBe(200);
55
+ expect(res.sentText).toBe('{"id":"msg_1"}');
56
+ });
57
+ it('上游 401 原样透传状态码与响应体', async () => {
58
+ vi.mocked(sendUpstream).mockResolvedValue({
59
+ status: 401,
60
+ headers: { 'content-type': 'application/json' },
61
+ text: async () => '{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}',
62
+ stream: async () => { },
63
+ });
64
+ const { req, res } = mockReqRes({ model: 'x:y', max_tokens: 1 });
65
+ await passthroughMessages(req, res, provider, 'claude-sonnet-4');
66
+ expect(res.statusCode).toBe(401);
67
+ expect(JSON.parse(res.sentText).error.message).toBe('invalid x-api-key');
68
+ });
69
+ it('流式响应原样 pipe 回传 SSE 行', async () => {
70
+ const sseLines = ['event: message_start', 'data: {"type":"message_start"}', '', 'event: message_stop', ''];
71
+ vi.mocked(sendUpstream).mockResolvedValue({
72
+ status: 200,
73
+ headers: { 'content-type': 'text/event-stream' },
74
+ text: async () => { throw new Error('not called'); },
75
+ stream: async (onLine) => { sseLines.forEach(onLine); },
76
+ });
77
+ const { req, res, writes } = mockReqRes({ model: 'x:y', stream: true, max_tokens: 1 });
78
+ await passthroughMessages(req, res, provider, 'claude-sonnet-4');
79
+ expect(res.statusCode).toBe(200);
80
+ expect(res.headers['content-type']).toBe('text/event-stream');
81
+ expect(writes.join('')).toBe(sseLines.map(l => l + '\n').join(''));
82
+ expect(res.writableEnded).toBe(true);
83
+ });
84
+ it('baseUrl 带尾斜杠不产生双斜杠', async () => {
85
+ vi.mocked(sendUpstream).mockResolvedValue({
86
+ status: 200, headers: {}, text: async () => '{}', stream: async () => { },
87
+ });
88
+ const { req, res } = mockReqRes({ model: 'x:y', max_tokens: 1 });
89
+ await passthroughMessages(req, res, { ...provider, baseUrl: 'https://api.anthropic.com/' }, 'm');
90
+ expect(vi.mocked(sendUpstream).mock.calls[0][0].url).toBe('https://api.anthropic.com/v1/messages');
91
+ });
92
+ it('上游连接失败返回 502 Anthropic 错误体', async () => {
93
+ vi.mocked(sendUpstream).mockRejectedValue(new Error('ECONNREFUSED'));
94
+ const { req, res } = mockReqRes({ model: 'x:y', max_tokens: 1 });
95
+ await passthroughMessages(req, res, provider, 'm');
96
+ expect(res.statusCode).toBe(502);
97
+ expect(JSON.parse(res.sentText).error.message).toContain('ECONNREFUSED');
98
+ });
99
+ it('provider 自定义 headers 合并进上游请求', async () => {
100
+ vi.mocked(sendUpstream).mockResolvedValue({
101
+ status: 200, headers: {}, text: async () => '{}', stream: async () => { },
102
+ });
103
+ const { req, res } = mockReqRes({ model: 'x:y', max_tokens: 1 });
104
+ await passthroughMessages(req, res, { ...provider, headers: { 'x-custom': 'v' } }, 'm');
105
+ expect(vi.mocked(sendUpstream).mock.calls[0][0].headers['x-custom']).toBe('v');
106
+ });
107
+ });
108
+ //# sourceMappingURL=passthrough.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passthrough.test.js","sourceRoot":"","sources":["../../src/ai-gateway/passthrough.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { AIProviderConfig } from '../../types/index.js';
2
+ export declare function openaiChatUrl(baseUrl: string): string;
3
+ export declare function openaiModelsUrl(baseUrl: string): string;
4
+ export declare function anthropicMessagesUrl(baseUrl: string): string;
5
+ export declare function geminiGenerateUrl(baseUrl: string, model: string, stream: boolean): string;
6
+ export declare function geminiModelsUrl(baseUrl: string): string;
7
+ export declare function listUpstreamModels(provider: AIProviderConfig): Promise<{
8
+ ok: boolean;
9
+ status: number;
10
+ models: string[];
11
+ message: string;
12
+ }>;
13
+ //# sourceMappingURL=model-lister.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-lister.d.ts","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/model-lister.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAaxD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAGzF;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BhJ"}
@@ -0,0 +1,61 @@
1
+ import { sendUpstream } from './provider-client.js';
2
+ function trimSlash(s) {
3
+ return s.endsWith('/') ? s.slice(0, -1) : s;
4
+ }
5
+ // openai 兼容:baseUrl 末尾已含 /v1 则直接拼,否则补 /v1
6
+ function openaiBase(baseUrl) {
7
+ const base = trimSlash(baseUrl);
8
+ return /\/v\d+$/.test(base) ? base : base + '/v1';
9
+ }
10
+ export function openaiChatUrl(baseUrl) {
11
+ return openaiBase(baseUrl) + '/chat/completions';
12
+ }
13
+ export function openaiModelsUrl(baseUrl) {
14
+ return openaiBase(baseUrl) + '/models';
15
+ }
16
+ export function anthropicMessagesUrl(baseUrl) {
17
+ return trimSlash(baseUrl) + '/v1/messages';
18
+ }
19
+ export function geminiGenerateUrl(baseUrl, model, stream) {
20
+ const action = stream ? 'streamGenerateContent?alt=sse' : 'generateContent';
21
+ return `${trimSlash(baseUrl)}/v1beta/models/${encodeURIComponent(model)}:${action}`;
22
+ }
23
+ export function geminiModelsUrl(baseUrl) {
24
+ return `${trimSlash(baseUrl)}/v1beta/models`;
25
+ }
26
+ export async function listUpstreamModels(provider) {
27
+ try {
28
+ let url;
29
+ let headers;
30
+ if (provider.dialect === 'openai') {
31
+ url = openaiModelsUrl(provider.baseUrl);
32
+ headers = { authorization: `Bearer ${provider.apiKey}`, ...(provider.headers || {}) };
33
+ }
34
+ else if (provider.dialect === 'anthropic') {
35
+ url = `${trimSlash(provider.baseUrl)}/v1/models`;
36
+ headers = { 'x-api-key': provider.apiKey, 'anthropic-version': '2023-06-01', ...(provider.headers || {}) };
37
+ }
38
+ else {
39
+ url = geminiModelsUrl(provider.baseUrl);
40
+ headers = { 'x-goog-api-key': provider.apiKey, ...(provider.headers || {}) };
41
+ }
42
+ const resp = await sendUpstream({ url, method: 'GET', headers, timeoutMs: 15_000 });
43
+ const text = await resp.text();
44
+ if (resp.status !== 200) {
45
+ return { ok: false, status: resp.status, models: [], message: text.slice(0, 500) };
46
+ }
47
+ const json = JSON.parse(text);
48
+ let models = [];
49
+ if (Array.isArray(json.data)) {
50
+ models = json.data.map((m) => m.id).filter(Boolean); // openai
51
+ }
52
+ else if (Array.isArray(json.models)) {
53
+ models = json.models.map((m) => (m.name || '').replace(/^models\//, '')).filter(Boolean); // gemini
54
+ }
55
+ return { ok: true, status: 200, models, message: 'ok' };
56
+ }
57
+ catch (error) {
58
+ return { ok: false, status: 0, models: [], message: error.message };
59
+ }
60
+ }
61
+ //# sourceMappingURL=model-lister.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-lister.js","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/model-lister.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface UpstreamRequest {
2
+ url: string;
3
+ method: 'POST' | 'GET';
4
+ headers: Record<string, string>;
5
+ body?: string;
6
+ timeoutMs: number;
7
+ signal?: AbortSignal;
8
+ }
9
+ export interface UpstreamResponse {
10
+ status: number;
11
+ headers: Record<string, string>;
12
+ text(): Promise<string>;
13
+ stream(onLine: (line: string) => void): Promise<void>;
14
+ }
15
+ export declare class UpstreamTimeoutError extends Error {
16
+ constructor(url: string);
17
+ }
18
+ export declare function sendUpstream(req: UpstreamRequest): Promise<UpstreamResponse>;
19
+ export declare function parseSSEStream(body: ReadableStream<Uint8Array>, onLine: (line: string) => void): Promise<void>;
20
+ //# sourceMappingURL=provider-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-client.d.ts","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/provider-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,GAAG,EAAE,MAAM;CAIxB;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyClF;AAGD,wBAAsB,cAAc,CAClC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC7B,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,76 @@
1
+ export class UpstreamTimeoutError extends Error {
2
+ constructor(url) {
3
+ super(`upstream timeout: no response headers within limit from ${url}`);
4
+ this.name = 'UpstreamTimeoutError';
5
+ }
6
+ }
7
+ export async function sendUpstream(req) {
8
+ const timeoutController = new AbortController();
9
+ const timer = setTimeout(() => timeoutController.abort(), req.timeoutMs);
10
+ // 外部 abort 联动
11
+ const onExternalAbort = () => timeoutController.abort();
12
+ req.signal?.addEventListener('abort', onExternalAbort, { once: true });
13
+ let response;
14
+ try {
15
+ response = await fetch(req.url, {
16
+ method: req.method,
17
+ headers: req.headers,
18
+ body: req.body,
19
+ signal: timeoutController.signal,
20
+ });
21
+ }
22
+ catch (error) {
23
+ // 区分超时 abort 与外部 abort
24
+ if (timeoutController.signal.aborted && !req.signal?.aborted) {
25
+ throw new UpstreamTimeoutError(req.url);
26
+ }
27
+ throw error;
28
+ }
29
+ finally {
30
+ clearTimeout(timer);
31
+ req.signal?.removeEventListener('abort', onExternalAbort);
32
+ }
33
+ const headers = {};
34
+ response.headers.forEach((value, key) => { headers[key] = value; });
35
+ return {
36
+ status: response.status,
37
+ headers,
38
+ text: () => response.text(),
39
+ stream: async (onLine) => {
40
+ if (!response.body) {
41
+ return;
42
+ }
43
+ // 响应头已到,后续流读取不再受 timeoutMs 约束(无字节 watchdog 由调用方实现)
44
+ await parseSSEStream(response.body, onLine);
45
+ },
46
+ };
47
+ }
48
+ // 增量逐行解析:按 \n 分行缓冲,跨 chunk 行不撕裂
49
+ export async function parseSSEStream(body, onLine) {
50
+ const reader = body.getReader();
51
+ const decoder = new TextDecoder();
52
+ let buffer = '';
53
+ try {
54
+ for (;;) {
55
+ const { done, value } = await reader.read();
56
+ if (done) {
57
+ break;
58
+ }
59
+ buffer += decoder.decode(value, { stream: true });
60
+ let newlineIndex;
61
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
62
+ const line = buffer.slice(0, newlineIndex).replace(/\r$/, '');
63
+ buffer = buffer.slice(newlineIndex + 1);
64
+ onLine(line);
65
+ }
66
+ }
67
+ buffer += decoder.decode();
68
+ if (buffer) {
69
+ onLine(buffer.replace(/\r$/, ''));
70
+ }
71
+ }
72
+ finally {
73
+ reader.releaseLock();
74
+ }
75
+ }
76
+ //# sourceMappingURL=provider-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-client.js","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/provider-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provider-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-client.test.d.ts","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/provider-client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { parseSSEStream, sendUpstream } from './provider-client.js';
3
+ function sseChunks(lines) {
4
+ // 模拟跨 chunk 半行的字节流切分
5
+ return lines.map(l => new TextEncoder().encode(l));
6
+ }
7
+ describe('parseSSEStream', () => {
8
+ it('逐帧回调完整 data 行,跨 chunk 行不撕裂', async () => {
9
+ const lines = [];
10
+ const stream = new ReadableStream({
11
+ start(controller) {
12
+ controller.enqueue(new TextEncoder().encode('event: message\n'));
13
+ controller.enqueue(new TextEncoder().encode('data: {"a"')); // 半行撕裂点
14
+ controller.enqueue(new TextEncoder().encode(':1}\n\n'));
15
+ controller.enqueue(new TextEncoder().encode('data: {"b":2}\n\n'));
16
+ controller.close();
17
+ },
18
+ });
19
+ await parseSSEStream(stream, line => lines.push(line));
20
+ expect(lines).toEqual(['event: message', 'data: {"a":1}', '', 'data: {"b":2}', '']);
21
+ });
22
+ it('空行与注释行原样回调(由调用方按需忽略)', async () => {
23
+ const lines = [];
24
+ const stream = new ReadableStream({
25
+ start(controller) {
26
+ controller.enqueue(new TextEncoder().encode(': ping\n\ndata: x\n\n'));
27
+ controller.close();
28
+ },
29
+ });
30
+ await parseSSEStream(stream, line => lines.push(line));
31
+ expect(lines).toEqual([': ping', '', 'data: x', '']);
32
+ });
33
+ });
34
+ describe('sendUpstream', () => {
35
+ afterEach(() => { vi.unstubAllGlobals(); });
36
+ it('返回状态/头与 text()', async () => {
37
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
38
+ status: 200,
39
+ headers: new Headers({ 'content-type': 'application/json' }),
40
+ text: async () => '{"ok":true}',
41
+ body: null,
42
+ }));
43
+ const res = await sendUpstream({
44
+ url: 'https://up.test/v1/messages', method: 'POST',
45
+ headers: { 'x-api-key': 'k' }, body: '{}', timeoutMs: 5000,
46
+ });
47
+ expect(res.status).toBe(200);
48
+ expect(res.headers['content-type']).toContain('application/json');
49
+ expect(await res.text()).toBe('{"ok":true}');
50
+ });
51
+ it('响应头超时抛 TimeoutError 语义错误', async () => {
52
+ vi.stubGlobal('fetch', vi.fn().mockImplementation((_url, init) => new Promise((_resolve, reject) => {
53
+ init.signal.addEventListener('abort', () => reject(new Error('aborted')));
54
+ })));
55
+ await expect(sendUpstream({
56
+ url: 'https://up.test', method: 'POST', headers: {}, timeoutMs: 50,
57
+ })).rejects.toThrow(/timeout/i);
58
+ });
59
+ it('透传外部 signal abort', async () => {
60
+ const ac = new AbortController();
61
+ vi.stubGlobal('fetch', vi.fn().mockImplementation((_url, init) => new Promise((_resolve, reject) => {
62
+ init.signal.addEventListener('abort', () => reject(new Error('aborted')));
63
+ })));
64
+ const p = sendUpstream({ url: 'https://up.test', method: 'POST', headers: {}, timeoutMs: 5000, signal: ac.signal });
65
+ ac.abort();
66
+ await expect(p).rejects.toThrow();
67
+ });
68
+ });
69
+ //# sourceMappingURL=provider-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-client.test.js","sourceRoot":"","sources":["../../../src/ai-gateway/upstream/provider-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCPDog CLI - Main entry point for the command-line interface
4
+ * Supports server management, protocol detection, configuration optimization, etc.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=cli-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-main.d.ts","sourceRoot":"","sources":["../../src/cli/cli-main.ts"],"names":[],"mappings":";AACA;;;GAGG"}
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCPDog CLI - Main entry point for the command-line interface
4
+ * Supports server management, protocol detection, configuration optimization, etc.
5
+ */
6
+ import { parseArgs } from 'util';
7
+ import { CLICommandRouter } from './cli-router.js';
8
+ import { CLIUtils } from './cli-utils.js';
9
+ async function main() {
10
+ try {
11
+ // Parse command line arguments - only parse global options, let subcommands parse their specific options
12
+ const { values, positionals } = parseArgs({
13
+ args: process.argv.slice(2),
14
+ options: {
15
+ help: { type: 'boolean', short: 'h' },
16
+ version: { type: 'boolean', short: 'v' },
17
+ config: { type: 'string', short: 'c' },
18
+ verbose: { type: 'boolean' },
19
+ json: { type: 'boolean' },
20
+ 'no-color': { type: 'boolean' },
21
+ 'dashboard-port': { type: 'string' },
22
+ 'mcp-http-port': { type: 'string' },
23
+ 'web-port': { type: 'string' }, // deprecated, kept for backward compatibility
24
+ 'gateway-port': { type: 'string' }, // AI API gateway port (daemon start)
25
+ 'daemon-port': { type: 'string' },
26
+ 'pid-file': { type: 'string' },
27
+ 'stdio-only': { type: 'boolean' },
28
+ 'http-only': { type: 'boolean' },
29
+ 'no-dashboard': { type: 'boolean' },
30
+ port: { type: 'string', short: 'p' },
31
+ // Config command options
32
+ endpoint: { type: 'string' },
33
+ transport: { type: 'string' },
34
+ timeout: { type: 'string' },
35
+ retries: { type: 'string' },
36
+ description: { type: 'string' },
37
+ // Add command options
38
+ 'auto-detect': { type: 'boolean' },
39
+ headers: { type: 'string' },
40
+ args: { type: 'string' },
41
+ yes: { type: 'boolean', short: 'y' },
42
+ // Audit command options
43
+ performance: { type: 'boolean' },
44
+ security: { type: 'boolean' },
45
+ compliance: { type: 'boolean' },
46
+ export: { type: 'string' },
47
+ // Detect command options
48
+ all: { type: 'boolean' },
49
+ detailed: { type: 'boolean' },
50
+ 'no-add': { type: 'boolean' },
51
+ // Diagnose command options
52
+ 'health-check': { type: 'boolean' },
53
+ fix: { type: 'boolean' },
54
+ // Optimize command options
55
+ apply: { type: 'boolean' },
56
+ preview: { type: 'boolean' },
57
+ // Daemon autostart command options
58
+ enable: { type: 'boolean' },
59
+ disable: { type: 'boolean' }
60
+ },
61
+ allowPositionals: true,
62
+ strict: false
63
+ });
64
+ // Set up CLI environment
65
+ CLIUtils.setupEnvironment({
66
+ verbose: Boolean(values.verbose),
67
+ noColor: Boolean(values['no-color']),
68
+ json: Boolean(values.json)
69
+ });
70
+ // Handle global options
71
+ if (values.version) {
72
+ CLIUtils.showVersion();
73
+ process.exit(0);
74
+ }
75
+ if (values.help && positionals.length === 0) {
76
+ CLIUtils.showGlobalHelp();
77
+ process.exit(0);
78
+ }
79
+ // Get command and arguments
80
+ const [command, ...args] = positionals;
81
+ // If no command is provided, default to 'proxy' (auto-start mode)
82
+ const finalCommand = command || 'proxy';
83
+ const finalArgs = command ? args : [];
84
+ // In auto mode (no command specified), suppress all output to avoid MCP client errors
85
+ if (!command) {
86
+ // Don't output anything in auto mode to avoid MCP client parsing errors
87
+ // The proxy command will handle all the necessary startup logic silently
88
+ }
89
+ if (!command && values.help) {
90
+ CLIUtils.showGlobalHelp();
91
+ process.exit(0);
92
+ }
93
+ // Create router and execute command
94
+ const router = new CLICommandRouter(values.config);
95
+ await router.executeCommand(finalCommand, finalArgs, values);
96
+ }
97
+ catch (error) {
98
+ CLIUtils.error('CLI execution failed:', error.message);
99
+ if (CLIUtils.isVerbose()) {
100
+ console.error(error.stack);
101
+ }
102
+ process.exit(1);
103
+ }
104
+ }
105
+ // Handle uncaught exceptions
106
+ process.on('uncaughtException', (error) => {
107
+ CLIUtils.error('Uncaught exception:', error.message);
108
+ process.exit(1);
109
+ });
110
+ process.on('unhandledRejection', (reason, promise) => {
111
+ CLIUtils.error('Unhandled rejection at:', promise, 'reason:', reason);
112
+ process.exit(1);
113
+ });
114
+ // Start the CLI
115
+ main();
116
+ //# sourceMappingURL=cli-main.js.map