@egain/egain-mcp-server 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (693) hide show
  1. package/.speakeasy/templates/src/lib/env.ts +1 -0
  2. package/README.md +119 -0
  3. package/bin/mcp-server.js +47817 -0
  4. package/bin/mcp-server.js.map +371 -0
  5. package/esm/src/cloudflare-worker/cloudflare-worker.d.ts +17 -0
  6. package/esm/src/cloudflare-worker/cloudflare-worker.d.ts.map +1 -0
  7. package/esm/src/cloudflare-worker/cloudflare-worker.js +72 -0
  8. package/esm/src/cloudflare-worker/cloudflare-worker.js.map +1 -0
  9. package/esm/src/cloudflare-worker/landing-page.d.ts +2 -0
  10. package/esm/src/cloudflare-worker/landing-page.d.ts.map +1 -0
  11. package/esm/src/cloudflare-worker/landing-page.js +330 -0
  12. package/esm/src/cloudflare-worker/landing-page.js.map +1 -0
  13. package/esm/src/core.d.ts +10 -0
  14. package/esm/src/core.d.ts.map +1 -0
  15. package/esm/src/core.js +13 -0
  16. package/esm/src/core.js.map +1 -0
  17. package/esm/src/funcs/getAnnouncements.d.ts +25 -0
  18. package/esm/src/funcs/getAnnouncements.d.ts.map +1 -0
  19. package/esm/src/funcs/getAnnouncements.js +111 -0
  20. package/esm/src/funcs/getAnnouncements.js.map +1 -0
  21. package/esm/src/funcs/getAnswers.d.ts +25 -0
  22. package/esm/src/funcs/getAnswers.d.ts.map +1 -0
  23. package/esm/src/funcs/getAnswers.js +115 -0
  24. package/esm/src/funcs/getAnswers.js.map +1 -0
  25. package/esm/src/funcs/getArticle.d.ts +28 -0
  26. package/esm/src/funcs/getArticle.d.ts.map +1 -0
  27. package/esm/src/funcs/getArticle.js +118 -0
  28. package/esm/src/funcs/getArticle.js.map +1 -0
  29. package/esm/src/funcs/getPopularArticles.d.ts +25 -0
  30. package/esm/src/funcs/getPopularArticles.d.ts.map +1 -0
  31. package/esm/src/funcs/getPopularArticles.js +111 -0
  32. package/esm/src/funcs/getPopularArticles.js.map +1 -0
  33. package/esm/src/funcs/getPortals.d.ts +23 -0
  34. package/esm/src/funcs/getPortals.d.ts.map +1 -0
  35. package/esm/src/funcs/getPortals.js +106 -0
  36. package/esm/src/funcs/getPortals.js.map +1 -0
  37. package/esm/src/funcs/makeSuggestion.d.ts +25 -0
  38. package/esm/src/funcs/makeSuggestion.d.ts.map +1 -0
  39. package/esm/src/funcs/makeSuggestion.js +104 -0
  40. package/esm/src/funcs/makeSuggestion.js.map +1 -0
  41. package/esm/src/funcs/queryAnswers.d.ts +25 -0
  42. package/esm/src/funcs/queryAnswers.d.ts.map +1 -0
  43. package/esm/src/funcs/queryAnswers.js +109 -0
  44. package/esm/src/funcs/queryAnswers.js.map +1 -0
  45. package/esm/src/funcs/queryRetrieve.d.ts +27 -0
  46. package/esm/src/funcs/queryRetrieve.d.ts.map +1 -0
  47. package/esm/src/funcs/queryRetrieve.js +115 -0
  48. package/esm/src/funcs/queryRetrieve.js.map +1 -0
  49. package/esm/src/funcs/querySearch.d.ts +16 -0
  50. package/esm/src/funcs/querySearch.d.ts.map +1 -0
  51. package/esm/src/funcs/querySearch.js +98 -0
  52. package/esm/src/funcs/querySearch.js.map +1 -0
  53. package/esm/src/funcs/retrieveChunks.d.ts +27 -0
  54. package/esm/src/funcs/retrieveChunks.d.ts.map +1 -0
  55. package/esm/src/funcs/retrieveChunks.js +117 -0
  56. package/esm/src/funcs/retrieveChunks.js.map +1 -0
  57. package/esm/src/funcs/searchAiSearch.d.ts +16 -0
  58. package/esm/src/funcs/searchAiSearch.d.ts.map +1 -0
  59. package/esm/src/funcs/searchAiSearch.js +102 -0
  60. package/esm/src/funcs/searchAiSearch.js.map +1 -0
  61. package/esm/src/hooks/auth-hook.d.ts +100 -0
  62. package/esm/src/hooks/auth-hook.d.ts.map +1 -0
  63. package/esm/src/hooks/auth-hook.js +1565 -0
  64. package/esm/src/hooks/auth-hook.js.map +1 -0
  65. package/esm/src/hooks/hooks.d.ts +25 -0
  66. package/esm/src/hooks/hooks.d.ts.map +1 -0
  67. package/esm/src/hooks/hooks.js +82 -0
  68. package/esm/src/hooks/hooks.js.map +1 -0
  69. package/esm/src/hooks/portal-cache-hook.d.ts +34 -0
  70. package/esm/src/hooks/portal-cache-hook.d.ts.map +1 -0
  71. package/esm/src/hooks/portal-cache-hook.js +441 -0
  72. package/esm/src/hooks/portal-cache-hook.js.map +1 -0
  73. package/esm/src/hooks/portal-lookup-hook.d.ts +17 -0
  74. package/esm/src/hooks/portal-lookup-hook.d.ts.map +1 -0
  75. package/esm/src/hooks/portal-lookup-hook.js +374 -0
  76. package/esm/src/hooks/portal-lookup-hook.js.map +1 -0
  77. package/esm/src/hooks/registration.d.ts +3 -0
  78. package/esm/src/hooks/registration.d.ts.map +1 -0
  79. package/esm/src/hooks/registration.js +36 -0
  80. package/esm/src/hooks/registration.js.map +1 -0
  81. package/esm/src/hooks/server-routing-hook.d.ts +19 -0
  82. package/esm/src/hooks/server-routing-hook.d.ts.map +1 -0
  83. package/esm/src/hooks/server-routing-hook.js +43 -0
  84. package/esm/src/hooks/server-routing-hook.js.map +1 -0
  85. package/esm/src/hooks/types.d.ts +76 -0
  86. package/esm/src/hooks/types.d.ts.map +1 -0
  87. package/esm/src/hooks/types.js +5 -0
  88. package/esm/src/hooks/types.js.map +1 -0
  89. package/esm/src/lib/base64.d.ts +10 -0
  90. package/esm/src/lib/base64.d.ts.map +1 -0
  91. package/esm/src/lib/base64.js +29 -0
  92. package/esm/src/lib/base64.js.map +1 -0
  93. package/esm/src/lib/config.d.ts +57 -0
  94. package/esm/src/lib/config.d.ts.map +1 -0
  95. package/esm/src/lib/config.js +46 -0
  96. package/esm/src/lib/config.js.map +1 -0
  97. package/esm/src/lib/dlv.d.ts +14 -0
  98. package/esm/src/lib/dlv.d.ts.map +1 -0
  99. package/esm/src/lib/dlv.js +46 -0
  100. package/esm/src/lib/dlv.js.map +1 -0
  101. package/esm/src/lib/encodings.d.ts +52 -0
  102. package/esm/src/lib/encodings.d.ts.map +1 -0
  103. package/esm/src/lib/encodings.js +354 -0
  104. package/esm/src/lib/encodings.js.map +1 -0
  105. package/esm/src/lib/env.d.ts +15 -0
  106. package/esm/src/lib/env.d.ts.map +1 -0
  107. package/esm/src/lib/env.js +27 -0
  108. package/esm/src/lib/env.js.map +1 -0
  109. package/esm/src/lib/files.d.ts +13 -0
  110. package/esm/src/lib/files.d.ts.map +1 -0
  111. package/esm/src/lib/files.js +73 -0
  112. package/esm/src/lib/files.js.map +1 -0
  113. package/esm/src/lib/http.d.ts +67 -0
  114. package/esm/src/lib/http.d.ts.map +1 -0
  115. package/esm/src/lib/http.js +209 -0
  116. package/esm/src/lib/http.js.map +1 -0
  117. package/esm/src/lib/is-plain-object.d.ts +2 -0
  118. package/esm/src/lib/is-plain-object.d.ts.map +1 -0
  119. package/esm/src/lib/is-plain-object.js +38 -0
  120. package/esm/src/lib/is-plain-object.js.map +1 -0
  121. package/esm/src/lib/logger.d.ts +6 -0
  122. package/esm/src/lib/logger.d.ts.map +1 -0
  123. package/esm/src/lib/logger.js +5 -0
  124. package/esm/src/lib/logger.js.map +1 -0
  125. package/esm/src/lib/matchers.d.ts +66 -0
  126. package/esm/src/lib/matchers.d.ts.map +1 -0
  127. package/esm/src/lib/matchers.js +209 -0
  128. package/esm/src/lib/matchers.js.map +1 -0
  129. package/esm/src/lib/primitives.d.ts +11 -0
  130. package/esm/src/lib/primitives.d.ts.map +1 -0
  131. package/esm/src/lib/primitives.js +81 -0
  132. package/esm/src/lib/primitives.js.map +1 -0
  133. package/esm/src/lib/result.d.ts +31 -0
  134. package/esm/src/lib/result.d.ts.map +1 -0
  135. package/esm/src/lib/result.js +31 -0
  136. package/esm/src/lib/result.js.map +1 -0
  137. package/esm/src/lib/retries.d.ts +38 -0
  138. package/esm/src/lib/retries.d.ts.map +1 -0
  139. package/esm/src/lib/retries.js +150 -0
  140. package/esm/src/lib/retries.js.map +1 -0
  141. package/esm/src/lib/schemas.d.ts +19 -0
  142. package/esm/src/lib/schemas.d.ts.map +1 -0
  143. package/esm/src/lib/schemas.js +57 -0
  144. package/esm/src/lib/schemas.js.map +1 -0
  145. package/esm/src/lib/sdks.d.ts +63 -0
  146. package/esm/src/lib/sdks.d.ts.map +1 -0
  147. package/esm/src/lib/sdks.js +258 -0
  148. package/esm/src/lib/sdks.js.map +1 -0
  149. package/esm/src/lib/security.d.ts +83 -0
  150. package/esm/src/lib/security.d.ts.map +1 -0
  151. package/esm/src/lib/security.js +138 -0
  152. package/esm/src/lib/security.js.map +1 -0
  153. package/esm/src/lib/url.d.ts +5 -0
  154. package/esm/src/lib/url.d.ts.map +1 -0
  155. package/esm/src/lib/url.js +22 -0
  156. package/esm/src/lib/url.js.map +1 -0
  157. package/esm/src/mcp-server/cli/start/command.d.ts +2 -0
  158. package/esm/src/mcp-server/cli/start/command.d.ts.map +1 -0
  159. package/esm/src/mcp-server/cli/start/command.js +96 -0
  160. package/esm/src/mcp-server/cli/start/command.js.map +1 -0
  161. package/esm/src/mcp-server/cli/start/impl.d.ts +17 -0
  162. package/esm/src/mcp-server/cli/start/impl.d.ts.map +1 -0
  163. package/esm/src/mcp-server/cli/start/impl.js +98 -0
  164. package/esm/src/mcp-server/cli/start/impl.js.map +1 -0
  165. package/esm/src/mcp-server/cli.d.ts +6 -0
  166. package/esm/src/mcp-server/cli.d.ts.map +1 -0
  167. package/esm/src/mcp-server/cli.js +7 -0
  168. package/esm/src/mcp-server/cli.js.map +1 -0
  169. package/esm/src/mcp-server/console-logger.d.ts +9 -0
  170. package/esm/src/mcp-server/console-logger.d.ts.map +1 -0
  171. package/esm/src/mcp-server/console-logger.js +56 -0
  172. package/esm/src/mcp-server/console-logger.js.map +1 -0
  173. package/esm/src/mcp-server/extensions.d.ts +11 -0
  174. package/esm/src/mcp-server/extensions.d.ts.map +1 -0
  175. package/esm/src/mcp-server/extensions.js +5 -0
  176. package/esm/src/mcp-server/extensions.js.map +1 -0
  177. package/esm/src/mcp-server/mcp-server.d.ts +2 -0
  178. package/esm/src/mcp-server/mcp-server.d.ts.map +1 -0
  179. package/esm/src/mcp-server/mcp-server.js +23 -0
  180. package/esm/src/mcp-server/mcp-server.js.map +1 -0
  181. package/esm/src/mcp-server/prompts.d.ts +26 -0
  182. package/esm/src/mcp-server/prompts.d.ts.map +1 -0
  183. package/esm/src/mcp-server/prompts.js +47 -0
  184. package/esm/src/mcp-server/prompts.js.map +1 -0
  185. package/esm/src/mcp-server/resources.d.ts +32 -0
  186. package/esm/src/mcp-server/resources.d.ts.map +1 -0
  187. package/esm/src/mcp-server/resources.js +82 -0
  188. package/esm/src/mcp-server/resources.js.map +1 -0
  189. package/esm/src/mcp-server/scopes.d.ts +3 -0
  190. package/esm/src/mcp-server/scopes.d.ts.map +1 -0
  191. package/esm/src/mcp-server/scopes.js +5 -0
  192. package/esm/src/mcp-server/scopes.js.map +1 -0
  193. package/esm/src/mcp-server/server.d.ts +16 -0
  194. package/esm/src/mcp-server/server.d.ts.map +1 -0
  195. package/esm/src/mcp-server/server.js +53 -0
  196. package/esm/src/mcp-server/server.js.map +1 -0
  197. package/esm/src/mcp-server/shared.d.ts +8 -0
  198. package/esm/src/mcp-server/shared.d.ts.map +1 -0
  199. package/esm/src/mcp-server/shared.js +61 -0
  200. package/esm/src/mcp-server/shared.js.map +1 -0
  201. package/esm/src/mcp-server/tools/getAnnouncements.d.ts +7 -0
  202. package/esm/src/mcp-server/tools/getAnnouncements.d.ts.map +1 -0
  203. package/esm/src/mcp-server/tools/getAnnouncements.js +44 -0
  204. package/esm/src/mcp-server/tools/getAnnouncements.js.map +1 -0
  205. package/esm/src/mcp-server/tools/getAnswers.d.ts +7 -0
  206. package/esm/src/mcp-server/tools/getAnswers.d.ts.map +1 -0
  207. package/esm/src/mcp-server/tools/getAnswers.js +44 -0
  208. package/esm/src/mcp-server/tools/getAnswers.js.map +1 -0
  209. package/esm/src/mcp-server/tools/getArticle.d.ts +7 -0
  210. package/esm/src/mcp-server/tools/getArticle.d.ts.map +1 -0
  211. package/esm/src/mcp-server/tools/getArticle.js +47 -0
  212. package/esm/src/mcp-server/tools/getArticle.js.map +1 -0
  213. package/esm/src/mcp-server/tools/getPopularArticles.d.ts +7 -0
  214. package/esm/src/mcp-server/tools/getPopularArticles.d.ts.map +1 -0
  215. package/esm/src/mcp-server/tools/getPopularArticles.js +44 -0
  216. package/esm/src/mcp-server/tools/getPopularArticles.js.map +1 -0
  217. package/esm/src/mcp-server/tools/getPortals.d.ts +7 -0
  218. package/esm/src/mcp-server/tools/getPortals.d.ts.map +1 -0
  219. package/esm/src/mcp-server/tools/getPortals.js +42 -0
  220. package/esm/src/mcp-server/tools/getPortals.js.map +1 -0
  221. package/esm/src/mcp-server/tools/makeSuggestion.d.ts +7 -0
  222. package/esm/src/mcp-server/tools/makeSuggestion.d.ts.map +1 -0
  223. package/esm/src/mcp-server/tools/makeSuggestion.js +44 -0
  224. package/esm/src/mcp-server/tools/makeSuggestion.js.map +1 -0
  225. package/esm/src/mcp-server/tools/queryAnswers.d.ts +7 -0
  226. package/esm/src/mcp-server/tools/queryAnswers.d.ts.map +1 -0
  227. package/esm/src/mcp-server/tools/queryAnswers.js +44 -0
  228. package/esm/src/mcp-server/tools/queryAnswers.js.map +1 -0
  229. package/esm/src/mcp-server/tools/queryRetrieve.d.ts +7 -0
  230. package/esm/src/mcp-server/tools/queryRetrieve.d.ts.map +1 -0
  231. package/esm/src/mcp-server/tools/queryRetrieve.js +46 -0
  232. package/esm/src/mcp-server/tools/queryRetrieve.js.map +1 -0
  233. package/esm/src/mcp-server/tools/querySearch.d.ts +7 -0
  234. package/esm/src/mcp-server/tools/querySearch.d.ts.map +1 -0
  235. package/esm/src/mcp-server/tools/querySearch.js +35 -0
  236. package/esm/src/mcp-server/tools/querySearch.js.map +1 -0
  237. package/esm/src/mcp-server/tools/retrieveChunks.d.ts +7 -0
  238. package/esm/src/mcp-server/tools/retrieveChunks.d.ts.map +1 -0
  239. package/esm/src/mcp-server/tools/retrieveChunks.js +46 -0
  240. package/esm/src/mcp-server/tools/retrieveChunks.js.map +1 -0
  241. package/esm/src/mcp-server/tools/searchAiSearch.d.ts +7 -0
  242. package/esm/src/mcp-server/tools/searchAiSearch.d.ts.map +1 -0
  243. package/esm/src/mcp-server/tools/searchAiSearch.js +35 -0
  244. package/esm/src/mcp-server/tools/searchAiSearch.js.map +1 -0
  245. package/esm/src/mcp-server/tools.d.ts +37 -0
  246. package/esm/src/mcp-server/tools.d.ts.map +1 -0
  247. package/esm/src/mcp-server/tools.js +78 -0
  248. package/esm/src/mcp-server/tools.js.map +1 -0
  249. package/esm/src/models/acceptlanguage.d.ts +7 -0
  250. package/esm/src/models/acceptlanguage.d.ts.map +1 -0
  251. package/esm/src/models/acceptlanguage.js +25 -0
  252. package/esm/src/models/acceptlanguage.js.map +1 -0
  253. package/esm/src/models/accessibleportal.d.ts +12 -0
  254. package/esm/src/models/accessibleportal.d.ts.map +1 -0
  255. package/esm/src/models/accessibleportal.js +14 -0
  256. package/esm/src/models/accessibleportal.js.map +1 -0
  257. package/esm/src/models/additionalsnippets.d.ts +17 -0
  258. package/esm/src/models/additionalsnippets.d.ts.map +1 -0
  259. package/esm/src/models/additionalsnippets.js +23 -0
  260. package/esm/src/models/additionalsnippets.js.map +1 -0
  261. package/esm/src/models/aisearchop.d.ts +23 -0
  262. package/esm/src/models/aisearchop.d.ts.map +1 -0
  263. package/esm/src/models/aisearchop.js +24 -0
  264. package/esm/src/models/aisearchop.js.map +1 -0
  265. package/esm/src/models/allaccessibleportals.d.ts +12 -0
  266. package/esm/src/models/allaccessibleportals.d.ts.map +1 -0
  267. package/esm/src/models/allaccessibleportals.js +11 -0
  268. package/esm/src/models/allaccessibleportals.js.map +1 -0
  269. package/esm/src/models/answersrequest.d.ts +28 -0
  270. package/esm/src/models/answersrequest.d.ts.map +1 -0
  271. package/esm/src/models/answersrequest.js +28 -0
  272. package/esm/src/models/answersrequest.js.map +1 -0
  273. package/esm/src/models/answersresponse.d.ts +34 -0
  274. package/esm/src/models/answersresponse.d.ts.map +1 -0
  275. package/esm/src/models/answersresponse.js +38 -0
  276. package/esm/src/models/answersresponse.js.map +1 -0
  277. package/esm/src/models/article.d.ts +59 -0
  278. package/esm/src/models/article.d.ts.map +1 -0
  279. package/esm/src/models/article.js +59 -0
  280. package/esm/src/models/article.js.map +1 -0
  281. package/esm/src/models/articleadditionalattributes.d.ts +4 -0
  282. package/esm/src/models/articleadditionalattributes.d.ts.map +1 -0
  283. package/esm/src/models/articleadditionalattributes.js +22 -0
  284. package/esm/src/models/articleadditionalattributes.js.map +1 -0
  285. package/esm/src/models/articleattachment.d.ts +16 -0
  286. package/esm/src/models/articleattachment.d.ts.map +1 -0
  287. package/esm/src/models/articleattachment.js +20 -0
  288. package/esm/src/models/articleattachment.js.map +1 -0
  289. package/esm/src/models/articleresult.d.ts +43 -0
  290. package/esm/src/models/articleresult.d.ts.map +1 -0
  291. package/esm/src/models/articleresult.js +42 -0
  292. package/esm/src/models/articleresult.js.map +1 -0
  293. package/esm/src/models/articleresultadditionalattributes.d.ts +4 -0
  294. package/esm/src/models/articleresultadditionalattributes.d.ts.map +1 -0
  295. package/esm/src/models/articleresultadditionalattributes.js +21 -0
  296. package/esm/src/models/articleresultadditionalattributes.js.map +1 -0
  297. package/esm/src/models/articleresults.d.ts +12 -0
  298. package/esm/src/models/articleresults.d.ts.map +1 -0
  299. package/esm/src/models/articleresults.js +11 -0
  300. package/esm/src/models/articleresults.js.map +1 -0
  301. package/esm/src/models/articlesearchresult.d.ts +57 -0
  302. package/esm/src/models/articlesearchresult.d.ts.map +1 -0
  303. package/esm/src/models/articlesearchresult.js +62 -0
  304. package/esm/src/models/articlesearchresult.js.map +1 -0
  305. package/esm/src/models/articletype.d.ts +17 -0
  306. package/esm/src/models/articletype.d.ts.map +1 -0
  307. package/esm/src/models/articletype.js +24 -0
  308. package/esm/src/models/articletype.js.map +1 -0
  309. package/esm/src/models/attachmentforcreatesuggestion.d.ts +9 -0
  310. package/esm/src/models/attachmentforcreatesuggestion.d.ts.map +1 -0
  311. package/esm/src/models/attachmentforcreatesuggestion.js +8 -0
  312. package/esm/src/models/attachmentforcreatesuggestion.js.map +1 -0
  313. package/esm/src/models/bookmarkstatus.d.ts +12 -0
  314. package/esm/src/models/bookmarkstatus.d.ts.map +1 -0
  315. package/esm/src/models/bookmarkstatus.js +11 -0
  316. package/esm/src/models/bookmarkstatus.js.map +1 -0
  317. package/esm/src/models/complianceforarticle.d.ts +24 -0
  318. package/esm/src/models/complianceforarticle.d.ts.map +1 -0
  319. package/esm/src/models/complianceforarticle.js +15 -0
  320. package/esm/src/models/complianceforarticle.js.map +1 -0
  321. package/esm/src/models/createdby.d.ts +10 -0
  322. package/esm/src/models/createdby.d.ts.map +1 -0
  323. package/esm/src/models/createdby.js +12 -0
  324. package/esm/src/models/createdby.js.map +1 -0
  325. package/esm/src/models/createsuggestion.d.ts +43 -0
  326. package/esm/src/models/createsuggestion.d.ts.map +1 -0
  327. package/esm/src/models/createsuggestion.js +47 -0
  328. package/esm/src/models/createsuggestion.js.map +1 -0
  329. package/esm/src/models/customattribute.d.ts +13 -0
  330. package/esm/src/models/customattribute.d.ts.map +1 -0
  331. package/esm/src/models/customattribute.js +19 -0
  332. package/esm/src/models/customattribute.js.map +1 -0
  333. package/esm/src/models/department.d.ts +7 -0
  334. package/esm/src/models/department.d.ts.map +1 -0
  335. package/esm/src/models/department.js +9 -0
  336. package/esm/src/models/department.js.map +1 -0
  337. package/esm/src/models/edition.d.ts +22 -0
  338. package/esm/src/models/edition.d.ts.map +1 -0
  339. package/esm/src/models/edition.js +18 -0
  340. package/esm/src/models/edition.js.map +1 -0
  341. package/esm/src/models/errors/apierror.d.ts +13 -0
  342. package/esm/src/models/errors/apierror.d.ts.map +1 -0
  343. package/esm/src/models/errors/apierror.js +12 -0
  344. package/esm/src/models/errors/apierror.js.map +1 -0
  345. package/esm/src/models/errors/httpclienterrors.d.ts +44 -0
  346. package/esm/src/models/errors/httpclienterrors.d.ts.map +1 -0
  347. package/esm/src/models/errors/httpclienterrors.js +56 -0
  348. package/esm/src/models/errors/httpclienterrors.js.map +1 -0
  349. package/esm/src/models/errors/sdkvalidationerror.d.ts +20 -0
  350. package/esm/src/models/errors/sdkvalidationerror.d.ts.map +1 -0
  351. package/esm/src/models/errors/sdkvalidationerror.js +85 -0
  352. package/esm/src/models/errors/sdkvalidationerror.js.map +1 -0
  353. package/esm/src/models/folderbreadcrumb.d.ts +10 -0
  354. package/esm/src/models/folderbreadcrumb.d.ts.map +1 -0
  355. package/esm/src/models/folderbreadcrumb.js +9 -0
  356. package/esm/src/models/folderbreadcrumb.js.map +1 -0
  357. package/esm/src/models/foldersummary.d.ts +12 -0
  358. package/esm/src/models/foldersummary.d.ts.map +1 -0
  359. package/esm/src/models/foldersummary.js +11 -0
  360. package/esm/src/models/foldersummary.js.map +1 -0
  361. package/esm/src/models/getannouncementarticlesop.d.ts +25 -0
  362. package/esm/src/models/getannouncementarticlesop.d.ts.map +1 -0
  363. package/esm/src/models/getannouncementarticlesop.js +26 -0
  364. package/esm/src/models/getannouncementarticlesop.js.map +1 -0
  365. package/esm/src/models/getarticlebyidop.d.ts +57 -0
  366. package/esm/src/models/getarticlebyidop.d.ts.map +1 -0
  367. package/esm/src/models/getarticlebyidop.js +130 -0
  368. package/esm/src/models/getarticlebyidop.js.map +1 -0
  369. package/esm/src/models/getmyportalsop.d.ts +28 -0
  370. package/esm/src/models/getmyportalsop.d.ts.map +1 -0
  371. package/esm/src/models/getmyportalsop.js +29 -0
  372. package/esm/src/models/getmyportalsop.js.map +1 -0
  373. package/esm/src/models/getpopulararticlesop.d.ts +24 -0
  374. package/esm/src/models/getpopulararticlesop.d.ts.map +1 -0
  375. package/esm/src/models/getpopulararticlesop.js +25 -0
  376. package/esm/src/models/getpopulararticlesop.js.map +1 -0
  377. package/esm/src/models/l10nstring.d.ts +7 -0
  378. package/esm/src/models/l10nstring.d.ts.map +1 -0
  379. package/esm/src/models/l10nstring.js +9 -0
  380. package/esm/src/models/l10nstring.js.map +1 -0
  381. package/esm/src/models/languagecode.d.ts +7 -0
  382. package/esm/src/models/languagecode.d.ts.map +1 -0
  383. package/esm/src/models/languagecode.js +40 -0
  384. package/esm/src/models/languagecode.js.map +1 -0
  385. package/esm/src/models/languagecodeparameter.d.ts +7 -0
  386. package/esm/src/models/languagecodeparameter.d.ts.map +1 -0
  387. package/esm/src/models/languagecodeparameter.js +41 -0
  388. package/esm/src/models/languagecodeparameter.js.map +1 -0
  389. package/esm/src/models/languagequeryparameter.d.ts +7 -0
  390. package/esm/src/models/languagequeryparameter.d.ts.map +1 -0
  391. package/esm/src/models/languagequeryparameter.js +41 -0
  392. package/esm/src/models/languagequeryparameter.js.map +1 -0
  393. package/esm/src/models/link.d.ts +10 -0
  394. package/esm/src/models/link.d.ts.map +1 -0
  395. package/esm/src/models/link.js +9 -0
  396. package/esm/src/models/link.js.map +1 -0
  397. package/esm/src/models/makesuggestionop.d.ts +18 -0
  398. package/esm/src/models/makesuggestionop.d.ts.map +1 -0
  399. package/esm/src/models/makesuggestionop.js +19 -0
  400. package/esm/src/models/makesuggestionop.js.map +1 -0
  401. package/esm/src/models/mandatorylanguagequeryparameter.d.ts +7 -0
  402. package/esm/src/models/mandatorylanguagequeryparameter.d.ts.map +1 -0
  403. package/esm/src/models/mandatorylanguagequeryparameter.js +40 -0
  404. package/esm/src/models/mandatorylanguagequeryparameter.js.map +1 -0
  405. package/esm/src/models/milestone.d.ts +10 -0
  406. package/esm/src/models/milestone.d.ts.map +1 -0
  407. package/esm/src/models/milestone.js +9 -0
  408. package/esm/src/models/milestone.js.map +1 -0
  409. package/esm/src/models/milestonename.d.ts +10 -0
  410. package/esm/src/models/milestonename.d.ts.map +1 -0
  411. package/esm/src/models/milestonename.js +9 -0
  412. package/esm/src/models/milestonename.js.map +1 -0
  413. package/esm/src/models/modifiedby.d.ts +10 -0
  414. package/esm/src/models/modifiedby.d.ts.map +1 -0
  415. package/esm/src/models/modifiedby.js +12 -0
  416. package/esm/src/models/modifiedby.js.map +1 -0
  417. package/esm/src/models/order.d.ts +4 -0
  418. package/esm/src/models/order.d.ts.map +1 -0
  419. package/esm/src/models/order.js +9 -0
  420. package/esm/src/models/order.js.map +1 -0
  421. package/esm/src/models/ownedby.d.ts +10 -0
  422. package/esm/src/models/ownedby.d.ts.map +1 -0
  423. package/esm/src/models/ownedby.js +13 -0
  424. package/esm/src/models/ownedby.js.map +1 -0
  425. package/esm/src/models/paginationinfo.d.ts +10 -0
  426. package/esm/src/models/paginationinfo.d.ts.map +1 -0
  427. package/esm/src/models/paginationinfo.js +12 -0
  428. package/esm/src/models/paginationinfo.js.map +1 -0
  429. package/esm/src/models/personalization.d.ts +25 -0
  430. package/esm/src/models/personalization.d.ts.map +1 -0
  431. package/esm/src/models/personalization.js +22 -0
  432. package/esm/src/models/personalization.js.map +1 -0
  433. package/esm/src/models/postportalidanswersop.d.ts +24 -0
  434. package/esm/src/models/postportalidanswersop.d.ts.map +1 -0
  435. package/esm/src/models/postportalidanswersop.js +25 -0
  436. package/esm/src/models/postportalidanswersop.js.map +1 -0
  437. package/esm/src/models/postportalidretrieveop.d.ts +26 -0
  438. package/esm/src/models/postportalidretrieveop.d.ts.map +1 -0
  439. package/esm/src/models/postportalidretrieveop.js +27 -0
  440. package/esm/src/models/postportalidretrieveop.js.map +1 -0
  441. package/esm/src/models/publishview.d.ts +13 -0
  442. package/esm/src/models/publishview.d.ts.map +1 -0
  443. package/esm/src/models/publishview.js +15 -0
  444. package/esm/src/models/publishview.js.map +1 -0
  445. package/esm/src/models/referenceresponse.d.ts +25 -0
  446. package/esm/src/models/referenceresponse.d.ts.map +1 -0
  447. package/esm/src/models/referenceresponse.js +30 -0
  448. package/esm/src/models/referenceresponse.js.map +1 -0
  449. package/esm/src/models/retrieverequest.d.ts +18 -0
  450. package/esm/src/models/retrieverequest.d.ts.map +1 -0
  451. package/esm/src/models/retrieverequest.js +21 -0
  452. package/esm/src/models/retrieverequest.js.map +1 -0
  453. package/esm/src/models/retrieveresponse.d.ts +37 -0
  454. package/esm/src/models/retrieveresponse.d.ts.map +1 -0
  455. package/esm/src/models/retrieveresponse.js +37 -0
  456. package/esm/src/models/retrieveresponse.js.map +1 -0
  457. package/esm/src/models/schemaslink.d.ts +8 -0
  458. package/esm/src/models/schemaslink.d.ts.map +1 -0
  459. package/esm/src/models/schemaslink.js +10 -0
  460. package/esm/src/models/schemaslink.js.map +1 -0
  461. package/esm/src/models/searchresponse.d.ts +14 -0
  462. package/esm/src/models/searchresponse.d.ts.map +1 -0
  463. package/esm/src/models/searchresponse.js +12 -0
  464. package/esm/src/models/searchresponse.js.map +1 -0
  465. package/esm/src/models/searchresult.d.ts +27 -0
  466. package/esm/src/models/searchresult.d.ts.map +1 -0
  467. package/esm/src/models/searchresult.js +32 -0
  468. package/esm/src/models/searchresult.js.map +1 -0
  469. package/esm/src/models/security.d.ts +6 -0
  470. package/esm/src/models/security.d.ts.map +1 -0
  471. package/esm/src/models/security.js +9 -0
  472. package/esm/src/models/security.js.map +1 -0
  473. package/esm/src/models/shorturl.d.ts +9 -0
  474. package/esm/src/models/shorturl.d.ts.map +1 -0
  475. package/esm/src/models/shorturl.js +12 -0
  476. package/esm/src/models/shorturl.js.map +1 -0
  477. package/esm/src/models/sortidnamedepartment.d.ts +4 -0
  478. package/esm/src/models/sortidnamedepartment.d.ts.map +1 -0
  479. package/esm/src/models/sortidnamedepartment.js +10 -0
  480. package/esm/src/models/sortidnamedepartment.js.map +1 -0
  481. package/esm/src/models/stage.d.ts +11 -0
  482. package/esm/src/models/stage.d.ts.map +1 -0
  483. package/esm/src/models/stage.js +11 -0
  484. package/esm/src/models/stage.js.map +1 -0
  485. package/esm/src/models/structuredauthoringfields.d.ts +10 -0
  486. package/esm/src/models/structuredauthoringfields.d.ts.map +1 -0
  487. package/esm/src/models/structuredauthoringfields.js +12 -0
  488. package/esm/src/models/structuredauthoringfields.js.map +1 -0
  489. package/esm/src/models/tag.d.ts +7 -0
  490. package/esm/src/models/tag.d.ts.map +1 -0
  491. package/esm/src/models/tag.js +9 -0
  492. package/esm/src/models/tag.js.map +1 -0
  493. package/esm/src/models/tagcategory.d.ts +14 -0
  494. package/esm/src/models/tagcategory.d.ts.map +1 -0
  495. package/esm/src/models/tagcategory.js +13 -0
  496. package/esm/src/models/tagcategory.js.map +1 -0
  497. package/esm/src/models/taggroup.d.ts +7 -0
  498. package/esm/src/models/taggroup.d.ts.map +1 -0
  499. package/esm/src/models/taggroup.js +10 -0
  500. package/esm/src/models/taggroup.js.map +1 -0
  501. package/esm/src/models/taggroups.d.ts +7 -0
  502. package/esm/src/models/taggroups.d.ts.map +1 -0
  503. package/esm/src/models/taggroups.js +9 -0
  504. package/esm/src/models/taggroups.js.map +1 -0
  505. package/esm/src/models/tags.d.ts +7 -0
  506. package/esm/src/models/tags.d.ts.map +1 -0
  507. package/esm/src/models/tags.js +9 -0
  508. package/esm/src/models/tags.js.map +1 -0
  509. package/esm/src/models/topicbreadcrumb.d.ts +10 -0
  510. package/esm/src/models/topicbreadcrumb.d.ts.map +1 -0
  511. package/esm/src/models/topicbreadcrumb.js +9 -0
  512. package/esm/src/models/topicbreadcrumb.js.map +1 -0
  513. package/esm/src/models/topicsummary.d.ts +10 -0
  514. package/esm/src/models/topicsummary.d.ts.map +1 -0
  515. package/esm/src/models/topicsummary.js +9 -0
  516. package/esm/src/models/topicsummary.js.map +1 -0
  517. package/esm/src/models/workflow.d.ts +10 -0
  518. package/esm/src/models/workflow.d.ts.map +1 -0
  519. package/esm/src/models/workflow.js +10 -0
  520. package/esm/src/models/workflow.js.map +1 -0
  521. package/esm/src/models/workflowmilestone.d.ts +4 -0
  522. package/esm/src/models/workflowmilestone.d.ts.map +1 -0
  523. package/esm/src/models/workflowmilestone.js +10 -0
  524. package/esm/src/models/workflowmilestone.js.map +1 -0
  525. package/esm/src/models/wserrorcommon.d.ts +17 -0
  526. package/esm/src/models/wserrorcommon.d.ts.map +1 -0
  527. package/esm/src/models/wserrorcommon.js +16 -0
  528. package/esm/src/models/wserrorcommon.js.map +1 -0
  529. package/esm/src/types/async.d.ts +23 -0
  530. package/esm/src/types/async.d.ts.map +1 -0
  531. package/esm/src/types/async.js +28 -0
  532. package/esm/src/types/async.js.map +1 -0
  533. package/esm/src/types/blobs.d.ts +2 -0
  534. package/esm/src/types/blobs.d.ts.map +1 -0
  535. package/esm/src/types/blobs.js +20 -0
  536. package/esm/src/types/blobs.js.map +1 -0
  537. package/esm/src/types/enums.d.ts +12 -0
  538. package/esm/src/types/enums.d.ts.map +1 -0
  539. package/esm/src/types/enums.js +7 -0
  540. package/esm/src/types/enums.js.map +1 -0
  541. package/esm/src/types/fp.d.ts +31 -0
  542. package/esm/src/types/fp.d.ts.map +1 -0
  543. package/esm/src/types/fp.js +31 -0
  544. package/esm/src/types/fp.js.map +1 -0
  545. package/esm/src/types/rfcdate.d.ts +21 -0
  546. package/esm/src/types/rfcdate.d.ts.map +1 -0
  547. package/esm/src/types/rfcdate.js +43 -0
  548. package/esm/src/types/rfcdate.js.map +1 -0
  549. package/esm/src/types/streams.d.ts +2 -0
  550. package/esm/src/types/streams.d.ts.map +1 -0
  551. package/esm/src/types/streams.js +15 -0
  552. package/esm/src/types/streams.js.map +1 -0
  553. package/esm/worker-configuration.d.ts +4 -0
  554. package/esm/worker-configuration.d.ts.map +1 -0
  555. package/esm/worker-configuration.js +5 -0
  556. package/esm/worker-configuration.js.map +1 -0
  557. package/manifest.json +96 -0
  558. package/package.json +41 -0
  559. package/scripts/login.js +93 -0
  560. package/scripts/logout.js +53 -0
  561. package/src/cloudflare-worker/cloudflare-worker.ts +98 -0
  562. package/src/cloudflare-worker/landing-page.ts +337 -0
  563. package/src/core.ts +13 -0
  564. package/src/funcs/getAnnouncements.ts +202 -0
  565. package/src/funcs/getArticle.ts +208 -0
  566. package/src/funcs/getPopularArticles.ts +198 -0
  567. package/src/funcs/getPortals.ts +190 -0
  568. package/src/funcs/makeSuggestion.ts +191 -0
  569. package/src/funcs/queryAnswers.ts +191 -0
  570. package/src/funcs/queryRetrieve.ts +197 -0
  571. package/src/funcs/querySearch.ts +181 -0
  572. package/src/hooks/auth-hook.ts +1752 -0
  573. package/src/hooks/auth-pages/config-page.js +531 -0
  574. package/src/hooks/hooks.ts +132 -0
  575. package/src/hooks/portal-cache-hook.ts +505 -0
  576. package/src/hooks/portal-lookup-hook.ts +429 -0
  577. package/src/hooks/registration.ts +43 -0
  578. package/src/hooks/server-routing-hook.ts +58 -0
  579. package/src/hooks/types.ts +107 -0
  580. package/src/lib/base64.ts +37 -0
  581. package/src/lib/config.ts +89 -0
  582. package/src/lib/dlv.ts +53 -0
  583. package/src/lib/encodings.ts +483 -0
  584. package/src/lib/env.ts +41 -0
  585. package/src/lib/files.ts +82 -0
  586. package/src/lib/http.ts +323 -0
  587. package/src/lib/is-plain-object.ts +43 -0
  588. package/src/lib/logger.ts +9 -0
  589. package/src/lib/matchers.ts +350 -0
  590. package/src/lib/primitives.ts +113 -0
  591. package/src/lib/result.ts +50 -0
  592. package/src/lib/retries.ts +218 -0
  593. package/src/lib/schemas.ts +91 -0
  594. package/src/lib/sdks.ts +409 -0
  595. package/src/lib/security.ts +260 -0
  596. package/src/lib/url.ts +33 -0
  597. package/src/mcp-server/cli/start/command.ts +100 -0
  598. package/src/mcp-server/cli/start/impl.ts +133 -0
  599. package/src/mcp-server/cli.ts +13 -0
  600. package/src/mcp-server/console-logger.ts +76 -0
  601. package/src/mcp-server/extensions.ts +17 -0
  602. package/src/mcp-server/mcp-server.ts +26 -0
  603. package/src/mcp-server/prompts.ts +121 -0
  604. package/src/mcp-server/resources.ts +176 -0
  605. package/src/mcp-server/scopes.ts +7 -0
  606. package/src/mcp-server/server.ts +91 -0
  607. package/src/mcp-server/shared.ts +78 -0
  608. package/src/mcp-server/tools/getAnnouncements.ts +53 -0
  609. package/src/mcp-server/tools/getArticle.ts +56 -0
  610. package/src/mcp-server/tools/getPopularArticles.ts +53 -0
  611. package/src/mcp-server/tools/getPortals.ts +51 -0
  612. package/src/mcp-server/tools/makeSuggestion.ts +53 -0
  613. package/src/mcp-server/tools/queryAnswers.ts +53 -0
  614. package/src/mcp-server/tools/queryRetrieve.ts +55 -0
  615. package/src/mcp-server/tools/querySearch.ts +44 -0
  616. package/src/mcp-server/tools.ts +156 -0
  617. package/src/models/acceptlanguage.ts +30 -0
  618. package/src/models/accessibleportal.ts +27 -0
  619. package/src/models/additionalsnippets.ts +43 -0
  620. package/src/models/aisearchop.ts +62 -0
  621. package/src/models/allaccessibleportals.ts +27 -0
  622. package/src/models/answersrequest.ts +67 -0
  623. package/src/models/answersresponse.ts +88 -0
  624. package/src/models/article.ts +120 -0
  625. package/src/models/articleadditionalattributes.ts +27 -0
  626. package/src/models/articleattachment.ts +38 -0
  627. package/src/models/articleresult.ts +91 -0
  628. package/src/models/articleresults.ts +24 -0
  629. package/src/models/articlesearchresult.ts +139 -0
  630. package/src/models/articletype.ts +42 -0
  631. package/src/models/attachmentforcreatesuggestion.ts +18 -0
  632. package/src/models/bookmarkstatus.ts +28 -0
  633. package/src/models/complianceforarticle.ts +48 -0
  634. package/src/models/createdby.ts +22 -0
  635. package/src/models/createsuggestion.ts +108 -0
  636. package/src/models/customattribute.ts +33 -0
  637. package/src/models/department.ts +16 -0
  638. package/src/models/edition.ts +40 -0
  639. package/src/models/errors/apierror.ts +21 -0
  640. package/src/models/errors/httpclienterrors.ts +62 -0
  641. package/src/models/errors/sdkvalidationerror.ts +97 -0
  642. package/src/models/folderbreadcrumb.ts +21 -0
  643. package/src/models/foldersummary.ts +27 -0
  644. package/src/models/getannouncementarticlesop.ts +68 -0
  645. package/src/models/getarticlebyidop.ts +182 -0
  646. package/src/models/getmyportalsop.ts +78 -0
  647. package/src/models/getpopulararticlesop.ts +66 -0
  648. package/src/models/l10nstring.ts +19 -0
  649. package/src/models/languagecode.ts +43 -0
  650. package/src/models/languagecodeparameter.ts +48 -0
  651. package/src/models/languagequeryparameter.ts +48 -0
  652. package/src/models/link.ts +17 -0
  653. package/src/models/makesuggestionop.ts +47 -0
  654. package/src/models/mandatorylanguagequeryparameter.ts +47 -0
  655. package/src/models/milestone.ts +16 -0
  656. package/src/models/milestonename.ts +22 -0
  657. package/src/models/modifiedby.ts +25 -0
  658. package/src/models/order.ts +12 -0
  659. package/src/models/ownedby.ts +22 -0
  660. package/src/models/paginationinfo.ts +24 -0
  661. package/src/models/personalization.ts +55 -0
  662. package/src/models/postportalidanswersop.ts +62 -0
  663. package/src/models/postportalidretrieveop.ts +68 -0
  664. package/src/models/publishview.ts +35 -0
  665. package/src/models/referenceresponse.ts +60 -0
  666. package/src/models/retrieverequest.ts +47 -0
  667. package/src/models/retrieveresponse.ts +92 -0
  668. package/src/models/schemaslink.ts +21 -0
  669. package/src/models/searchresponse.ts +34 -0
  670. package/src/models/searchresult.ts +64 -0
  671. package/src/models/security.ts +14 -0
  672. package/src/models/shorturl.ts +20 -0
  673. package/src/models/sortidnamedepartment.ts +15 -0
  674. package/src/models/stage.ts +20 -0
  675. package/src/models/structuredauthoringfields.ts +24 -0
  676. package/src/models/tagcategory.ts +28 -0
  677. package/src/models/taggroup.ts +13 -0
  678. package/src/models/taggroups.ts +13 -0
  679. package/src/models/tags.ts +15 -0
  680. package/src/models/topicbreadcrumb.ts +21 -0
  681. package/src/models/topicsummary.ts +24 -0
  682. package/src/models/workflow.ts +16 -0
  683. package/src/models/workflowmilestone.ts +13 -0
  684. package/src/models/wserrorcommon.ts +34 -0
  685. package/src/types/async.ts +68 -0
  686. package/src/types/blobs.ts +23 -0
  687. package/src/types/enums.ts +16 -0
  688. package/src/types/fp.ts +50 -0
  689. package/src/types/rfcdate.ts +54 -0
  690. package/src/types/streams.ts +21 -0
  691. package/tsconfig.json +36 -0
  692. package/worker-configuration.d.ts +8489 -0
  693. package/worker-configuration.ts +11 -0
@@ -0,0 +1,1752 @@
1
+ /**
2
+ * Authentication Hook for eGain MCP Server
3
+ * Handles Azure B2C OAuth2 authentication with PKCE and popup window
4
+ */
5
+
6
+ import { SDKInitHook, BeforeRequestHook, HookContext } from "./types.js";
7
+ import { SDKOptions } from "../lib/config.js";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import { exec } from "child_process";
11
+ import * as http from "http";
12
+ import * as os from "os";
13
+ import { fileURLToPath } from 'url';
14
+ import * as crypto from "crypto";
15
+ import { promisify } from "util";
16
+
17
+ // ES module __dirname equivalent
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
21
+ // Get the project root directory by finding package.json
22
+ const getProjectRoot = (): string => {
23
+ // Start from current directory and walk up until we find package.json
24
+ let currentDir = __dirname;
25
+ while (currentDir !== path.dirname(currentDir)) { // Stop at filesystem root
26
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
27
+ return currentDir;
28
+ }
29
+ currentDir = path.dirname(currentDir);
30
+ }
31
+ // If package.json not found, use current working directory as fallback
32
+ return process.cwd();
33
+ };
34
+
35
+ // Get user's home directory for storing config securely
36
+ const getConfigDir = (): string => {
37
+ const homeDir = os.homedir();
38
+ const configDir = path.join(homeDir, '.egain-mcp');
39
+
40
+ // Create directory if it doesn't exist
41
+ if (!fs.existsSync(configDir)) {
42
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); // Only user can read/write
43
+ }
44
+
45
+ return configDir;
46
+ };
47
+
48
+ const getConfigPath = (): string => {
49
+ return path.join(getConfigDir(), 'config.json');
50
+ };
51
+
52
+ const execAsync = promisify(exec);
53
+
54
+ interface AuthConfig {
55
+ environmentUrl?: string;
56
+ clientId?: string;
57
+ clientSecret?: string;
58
+ redirectUri?: string;
59
+ authUrl?: string;
60
+ accessUrl?: string;
61
+ scopePrefix?: string;
62
+ }
63
+
64
+ interface PKCEValues {
65
+ codeVerifier: string;
66
+ codeChallenge: string;
67
+ }
68
+
69
+ const CONFIG_SERVER_PORT = 3333;
70
+ const CONFIG_SERVER_HOST = 'localhost';
71
+
72
+ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
73
+ private token: string | null = null;
74
+ private authConfig: AuthConfig;
75
+ private codeVerifier: string;
76
+ private codeChallenge: string;
77
+ private portalCacheHook?: any; // PortalCacheHook reference
78
+ private configServer: http.Server | null = null; // HTTP server for configuration form
79
+ private detectedBrowser: string = 'Google Chrome'; // Default fallback
80
+ private authCancelled: boolean = false; // Track if user cancelled authentication
81
+ private oauthRedirectStarted: boolean = false; // Track when OAuth redirect happens
82
+
83
+
84
+ /**
85
+ * Generates PKCE (Proof Key for Code Exchange) values for OAuth2 security
86
+ * @returns Object containing code verifier and S256 code challenge
87
+ */
88
+ private generatePKCEValues(): PKCEValues {
89
+ // Generate cryptographically secure random code verifier (43-128 characters)
90
+ // Using base64url encoding of 32 random bytes = 43 characters
91
+ const codeVerifier = crypto
92
+ .randomBytes(32)
93
+ .toString('base64url'); // base64url automatically removes padding and uses URL-safe characters
94
+
95
+ // Generate S256 code challenge from verifier
96
+ const codeChallenge = crypto
97
+ .createHash('sha256')
98
+ .update(codeVerifier)
99
+ .digest('base64url'); // base64url encoding for URL safety
100
+
101
+ console.error('🔐 Generated new PKCE values:');
102
+ console.error(` Code Verifier: ${codeVerifier.substring(0, 10)}... (${codeVerifier.length} chars)`);
103
+ console.error(` Code Challenge: ${codeChallenge.substring(0, 10)}... (${codeChallenge.length} chars)`);
104
+
105
+ return {
106
+ codeVerifier,
107
+ codeChallenge
108
+ };
109
+ }
110
+
111
+ constructor(portalCacheHook?: any) {
112
+ this.portalCacheHook = portalCacheHook;
113
+ this.authConfig = this.loadAuthConfig();
114
+
115
+ // Generate secure PKCE values instead of using hardcoded ones
116
+ const pkceValues = this.generatePKCEValues();
117
+ this.codeVerifier = pkceValues.codeVerifier;
118
+ this.codeChallenge = pkceValues.codeChallenge;
119
+ }
120
+
121
+ /**
122
+ * Detect the default browser on macOS or Windows
123
+ */
124
+ private async detectDefaultBrowser(): Promise<void> {
125
+ console.error('🔍 Attempting to detect default browser...');
126
+
127
+ const platform = process.platform;
128
+
129
+ // Force specific browser for testing if FORCE_BROWSER env var is set
130
+ const forcedBrowser = process.env['FORCE_BROWSER'] || (process.env['FORCE_SAFARI_BROWSER'] === 'true' ? 'Safari' : null);
131
+ if (forcedBrowser) {
132
+ console.error(` 🧪 TEST MODE: Forcing ${forcedBrowser} browser...`);
133
+ this.detectedBrowser = forcedBrowser;
134
+
135
+ if (forcedBrowser === 'Safari') {
136
+ console.error(`⚠️ Found Safari, but it has limited private browsing support via CLI`);
137
+ console.error(` For better security, consider installing Chrome, Firefox, or Edge`);
138
+ } else {
139
+ console.error(`✅ Using ${forcedBrowser} for testing`);
140
+ }
141
+ return;
142
+ }
143
+
144
+ if (platform === 'darwin') {
145
+ await this.detectBrowserMacOS();
146
+ } else if (platform === 'win32') {
147
+ await this.detectBrowserWindows();
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Detect browser on macOS
153
+ */
154
+ private async detectBrowserMacOS(): Promise<void> {
155
+ // Map bundle IDs to application names
156
+ const browserMap: { [key: string]: string } = {
157
+ 'com.google.chrome': 'Google Chrome',
158
+ 'com.apple.safari': 'Safari',
159
+ 'org.mozilla.firefox': 'Firefox',
160
+ 'com.microsoft.edgemac': 'Microsoft Edge',
161
+ 'com.brave.browser': 'Brave Browser',
162
+ 'com.vivaldi.vivaldi': 'Vivaldi',
163
+ 'com.operasoftware.opera': 'Opera'
164
+ };
165
+
166
+ // Method 1: Try using defaultbrowser command (if installed)
167
+ try {
168
+ console.error(' Method 1: Trying defaultbrowser command...');
169
+ const { stdout } = await execAsync('which defaultbrowser');
170
+ if (stdout.trim()) {
171
+ const { stdout: browserOutput } = await execAsync('defaultbrowser');
172
+ const bundleId = browserOutput.trim();
173
+ console.error(` Bundle ID from defaultbrowser: ${bundleId}`);
174
+
175
+ if (bundleId && browserMap[bundleId]) {
176
+ this.detectedBrowser = browserMap[bundleId];
177
+ console.error(`✅ Successfully detected: ${this.detectedBrowser}`);
178
+ return;
179
+ }
180
+ }
181
+ } catch (error) {
182
+ console.error(' Method 1 failed (defaultbrowser not installed)');
183
+ }
184
+
185
+ // Method 2: Check LaunchServices
186
+ try {
187
+ console.error(' Method 2: Checking LaunchServices...');
188
+ const { stdout } = await execAsync(`defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers 2>/dev/null | grep -B 1 -A 3 'https' | grep LSHandlerRoleAll -A 2 | grep LSHandlerContentTag | head -1 | cut -d '"' -f 2`);
189
+ const bundleId = stdout.trim();
190
+ console.error(` Bundle ID from LaunchServices: ${bundleId || '(none)'}`);
191
+
192
+ if (bundleId && browserMap[bundleId]) {
193
+ this.detectedBrowser = browserMap[bundleId];
194
+ console.error(`✅ Successfully detected: ${this.detectedBrowser}`);
195
+ return;
196
+ }
197
+ } catch (error) {
198
+ console.error(' Method 2 failed');
199
+ }
200
+
201
+ // Method 3: Scan for installed browsers
202
+ console.error(' Method 3: Scanning for installed browsers...');
203
+ const browsersToCheck = [
204
+ 'Google Chrome',
205
+ 'Microsoft Edge',
206
+ 'Firefox',
207
+ 'Brave Browser',
208
+ 'Vivaldi',
209
+ 'Opera',
210
+ 'Safari'
211
+ ];
212
+
213
+ for (const browser of browsersToCheck) {
214
+ try {
215
+ await execAsync(`osascript -e 'exists application "${browser}"'`);
216
+ this.detectedBrowser = browser;
217
+
218
+ if (browser === 'Safari') {
219
+ console.error(`⚠️ Found Safari, but it has limited private browsing support via CLI`);
220
+ console.error(` For better security, consider installing Chrome, Firefox, or Edge`);
221
+ } else {
222
+ console.error(`✅ Found installed browser: ${this.detectedBrowser}`);
223
+ }
224
+ return;
225
+ } catch (error) {
226
+ // Browser not installed, continue
227
+ }
228
+ }
229
+
230
+ console.error(`⚠️ No browsers detected, falling back to: ${this.detectedBrowser}`);
231
+ }
232
+
233
+ /**
234
+ * Detect browser on Windows
235
+ */
236
+ private async detectBrowserWindows(): Promise<void> {
237
+ console.error(' Method 1: Checking Windows registry for default browser...');
238
+
239
+ // Try to get default browser from Windows registry
240
+ try {
241
+ const { stdout } = await execAsync(
242
+ `powershell -Command "$browser = (Get-ItemProperty 'HKCU:\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice' -ErrorAction SilentlyContinue).ProgId; Write-Output $browser"`
243
+ );
244
+
245
+ const progId = stdout.trim().toLowerCase();
246
+ console.error(` ProgID detected: ${progId || '(none)'}`);
247
+
248
+ // Map Windows ProgIDs to browser names
249
+ if (progId.includes('chromehtml')) {
250
+ this.detectedBrowser = 'chrome';
251
+ console.error(`✅ Successfully detected: Google Chrome`);
252
+ return;
253
+ } else if (progId.includes('msedgehtm')) {
254
+ this.detectedBrowser = 'msedge';
255
+ console.error(`✅ Successfully detected: Microsoft Edge`);
256
+ return;
257
+ } else if (progId.includes('firefox')) {
258
+ this.detectedBrowser = 'firefox';
259
+ console.error(`✅ Successfully detected: Firefox`);
260
+ return;
261
+ } else if (progId.includes('bravehtml')) {
262
+ this.detectedBrowser = 'brave';
263
+ console.error(`✅ Successfully detected: Brave`);
264
+ return;
265
+ }
266
+ } catch (error) {
267
+ console.error(' Method 1 failed');
268
+ }
269
+
270
+ // Method 2: Scan for installed browsers in common locations
271
+ console.error(' Method 2: Scanning for installed browsers...');
272
+ const browsersToCheck = [
273
+ { name: 'chrome', paths: [
274
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
275
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
276
+ ]},
277
+ { name: 'msedge', paths: [
278
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
279
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
280
+ ]},
281
+ { name: 'firefox', paths: [
282
+ 'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
283
+ 'C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe'
284
+ ]},
285
+ { name: 'brave', paths: [
286
+ 'C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe',
287
+ 'C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe'
288
+ ]}
289
+ ];
290
+
291
+ for (const browser of browsersToCheck) {
292
+ for (const browserPath of browser.paths) {
293
+ try {
294
+ // Check if file exists using PowerShell
295
+ const { stdout } = await execAsync(
296
+ `powershell -Command "Test-Path '${browserPath}'"`
297
+ );
298
+
299
+ if (stdout.trim().toLowerCase() === 'true') {
300
+ this.detectedBrowser = browser.name;
301
+ const displayName = browser.name === 'msedge' ? 'Microsoft Edge' :
302
+ browser.name === 'chrome' ? 'Google Chrome' :
303
+ browser.name === 'firefox' ? 'Firefox' : 'Brave';
304
+ console.error(`✅ Found installed browser: ${displayName}`);
305
+ return;
306
+ }
307
+ } catch (error) {
308
+ // Continue checking other paths
309
+ }
310
+ }
311
+ }
312
+
313
+ // Default to Edge (ships with Windows 10+)
314
+ console.error(`⚠️ No browsers detected, falling back to: Microsoft Edge`);
315
+ this.detectedBrowser = 'msedge';
316
+ }
317
+
318
+ /**
319
+ * Get incognito flag for the detected browser
320
+ */
321
+ private getIncognitoFlag(): string {
322
+ const flagMap: { [key: string]: string } = {
323
+ // macOS browser names
324
+ 'Google Chrome': '--incognito',
325
+ 'Brave Browser': '--incognito',
326
+ 'Microsoft Edge': '--inprivate',
327
+ 'Vivaldi': '--incognito',
328
+ 'Opera': '--private',
329
+ 'Firefox': '--private-window',
330
+ 'Safari': '', // Safari doesn't support private browsing via CLI well
331
+ // Windows browser executable names
332
+ 'chrome': '--incognito',
333
+ 'msedge': '--inprivate',
334
+ 'firefox': '--private-window',
335
+ 'brave': '--incognito'
336
+ };
337
+
338
+ // Use 'in' operator to check if key exists, allowing empty string values
339
+ if (this.detectedBrowser in flagMap) {
340
+ return flagMap[this.detectedBrowser]!; // Non-null assertion since we just checked
341
+ }
342
+ return '--incognito';
343
+ }
344
+
345
+
346
+ /**
347
+ * Save OAuth config to user's home directory (secure file storage)
348
+ */
349
+ private saveConfigToFile(config: AuthConfig): void {
350
+ try {
351
+ const configPath = getConfigPath();
352
+
353
+ // Set restrictive permissions (only current user can read/write)
354
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), {
355
+ mode: 0o600
356
+ });
357
+
358
+ console.error(`💾 Created config file: ${configPath}`);
359
+ console.error(' (Secured with user-only read/write permissions)');
360
+ } catch (error) {
361
+ console.error('❌ Failed to save config:', error);
362
+ throw error;
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Load OAuth config from user's home directory, falling back to .env
368
+ */
369
+ private loadAuthConfig(): AuthConfig {
370
+ // Priority 1: Try loading from secure user config file
371
+ try {
372
+ const configPath = getConfigPath();
373
+ if (fs.existsSync(configPath)) {
374
+ const configContent = fs.readFileSync(configPath, 'utf8');
375
+ const config = JSON.parse(configContent) as AuthConfig;
376
+ console.error(`✅ Loaded config from: ${configPath}`);
377
+ return config;
378
+ }
379
+ } catch (error) {
380
+ console.error('⚠️ Could not load config from home directory:', error);
381
+ }
382
+
383
+ // Priority 2: Try loading from .env file (for development)
384
+ try {
385
+ // Try multiple possible locations for the .env file
386
+ const possiblePaths = [
387
+ path.join(process.cwd(), '.env'),
388
+ path.join(__dirname, '../../../.env'), // From compiled esm location in bin/
389
+ path.join(__dirname, '../../../../.env'), // From compiled bin/ to project root
390
+ path.join(__dirname, '../../.env'), // From src location
391
+ // Try finding project root by looking for package.json
392
+ ...this.findProjectRoot().map(root => path.join(root, '.env'))
393
+ ];
394
+
395
+
396
+ let envPath: string | null = null;
397
+ for (const possiblePath of possiblePaths) {
398
+ if (fs.existsSync(possiblePath)) {
399
+ envPath = possiblePath;
400
+ console.error(`✅ Found .env file at: ${envPath}`);
401
+ break;
402
+ }
403
+ }
404
+
405
+ if (envPath) {
406
+ const envContent = fs.readFileSync(envPath, 'utf8');
407
+ const config: AuthConfig = {};
408
+
409
+ envContent.split('\n').forEach(line => {
410
+ const equalIndex = line.indexOf('=');
411
+ if (equalIndex > 0) {
412
+ const key = line.substring(0, equalIndex).trim();
413
+ const value = line.substring(equalIndex + 1).trim();
414
+
415
+ if (key && value) {
416
+ const cleanKey = key;
417
+ const cleanValue = value.replace(/['"]/g, '');
418
+
419
+ switch (cleanKey) {
420
+ // New variable names (preferred)
421
+ case 'EGAIN_URL':
422
+ config.environmentUrl = cleanValue;
423
+ break;
424
+ case 'CLIENT_ID':
425
+ config.clientId = cleanValue;
426
+ break;
427
+ case 'CLIENT_SECRET':
428
+ config.clientSecret = cleanValue;
429
+ break;
430
+ case 'REDIRECT_URL':
431
+ config.redirectUri = cleanValue;
432
+ break;
433
+ case 'AUTH_URL':
434
+ config.authUrl = cleanValue;
435
+ break;
436
+ case 'ACCESS_TOKEN_URL':
437
+ config.accessUrl = cleanValue;
438
+ break;
439
+ case 'SCOPE_PREFIX':
440
+ config.scopePrefix = cleanValue;
441
+ break;
442
+
443
+ // Backward compatibility with old names
444
+ case 'EGAIN_ENVIRONMENT_URL':
445
+ config.environmentUrl = cleanValue;
446
+ break;
447
+ case 'EGAIN_CLIENT_ID':
448
+ config.clientId = cleanValue;
449
+ break;
450
+ case 'EGAIN_CLIENT_SECRET':
451
+ config.clientSecret = cleanValue;
452
+ break;
453
+ case 'EGAIN_REDIRECT_URI':
454
+ config.redirectUri = cleanValue;
455
+ break;
456
+ case 'ACCESS_URL':
457
+ config.accessUrl = cleanValue;
458
+ break;
459
+ case 'EGAIN_SCOPE_PREFIX':
460
+ config.scopePrefix = cleanValue;
461
+ break;
462
+ }
463
+ }
464
+ }
465
+ });
466
+
467
+ return config;
468
+ }
469
+ } catch (error) {
470
+ console.error('Could not load .env file');
471
+ }
472
+
473
+ return {};
474
+ }
475
+
476
+ /**
477
+ * Generate Safari warning page (Safari doesn't support private browsing via CLI)
478
+ */
479
+ private getSafariWarningPage(): string {
480
+ try {
481
+ const projectRoot = getProjectRoot();
482
+ const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'safari-warning.html');
483
+ return fs.readFileSync(htmlPath, 'utf8');
484
+ } catch (error) {
485
+ console.error('⚠️ Could not load Safari warning page:', error);
486
+ // Fallback minimal HTML
487
+ return '<html><body><h1>Safari Not Supported</h1><p>Safari does not support private browsing mode via command line.</p></body></html>';
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Load HTML page for browser-based configuration
493
+ */
494
+ private getConfigPage(): string {
495
+ try {
496
+ const projectRoot = getProjectRoot();
497
+ const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.html');
498
+ return fs.readFileSync(htmlPath, 'utf8');
499
+ } catch (error) {
500
+ console.error('⚠️ Could not load config page:', error);
501
+ // Fallback minimal HTML
502
+ return '<html><body><h1>Configuration Error</h1><p>Could not load configuration page.</p></body></html>';
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Serve JavaScript for config page
508
+ */
509
+ private getConfigPageJS(): string {
510
+ try {
511
+ const projectRoot = getProjectRoot();
512
+ const jsPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.js');
513
+ return fs.readFileSync(jsPath, 'utf8');
514
+ } catch (error) {
515
+ console.error('⚠️ Could not load config page JS:', error);
516
+ return ''; // Return empty string if JS fails to load
517
+ }
518
+ }
519
+
520
+ private buildAuthUrl(): string {
521
+ const { environmentUrl, clientId, redirectUri, authUrl, scopePrefix } = this.authConfig;
522
+
523
+ console.error('🔧 Building OAuth URL...');
524
+
525
+ if (!authUrl) {
526
+ throw new Error('Authorization URL is required');
527
+ }
528
+
529
+ // Clean and validate redirect_uri (must match exactly what's registered)
530
+ const cleanRedirectUri = redirectUri?.trim();
531
+ if (!cleanRedirectUri) {
532
+ throw new Error('Redirect URI is required');
533
+ }
534
+
535
+ // Log the redirect URI for debugging
536
+ console.error('🔗 Redirect URI:', cleanRedirectUri);
537
+ console.error('⚠️ Make sure this EXACTLY matches the redirect URI in your client application (including trailing slashes)');
538
+
539
+ // Parse the authUrl to remove any existing domain_hint parameter
540
+ let baseUrl = authUrl;
541
+ let existingParams = new URLSearchParams();
542
+
543
+ // Check if authUrl already has query parameters
544
+ const urlParts = authUrl.split('?');
545
+ if (urlParts.length > 1 && urlParts[0]) {
546
+ baseUrl = urlParts[0];
547
+ const queryString = urlParts.slice(1).join('?'); // Handle multiple ? characters
548
+ existingParams = new URLSearchParams(queryString);
549
+
550
+ // Remove domain_hint if it exists (we'll add our own)
551
+ if (existingParams.has('domain_hint')) {
552
+ existingParams.delete('domain_hint');
553
+ console.error('🔧 Removed existing domain_hint from authorization URL');
554
+ }
555
+ }
556
+
557
+ const prefix = scopePrefix || '';
558
+ const scope = `${prefix}knowledge.portalmgr.manage ${prefix}knowledge.portalmgr.read ${prefix}core.aiservices.read`;
559
+
560
+ // Add our parameters
561
+ existingParams.set('domain_hint', environmentUrl!.trim());
562
+ existingParams.set('client_id', clientId!.trim());
563
+ existingParams.set('response_type', 'code');
564
+ existingParams.set('redirect_uri', cleanRedirectUri);
565
+ existingParams.set('scope', scope);
566
+ existingParams.set('forceLogin', 'yes');
567
+ existingParams.set('prompt', 'login');
568
+
569
+ // Add PKCE only for public clients (no client_secret)
570
+ // SSO/confidential clients use client_secret only (no PKCE)
571
+ if (!this.authConfig.clientSecret) {
572
+ existingParams.set('code_challenge', this.codeChallenge);
573
+ existingParams.set('code_challenge_method', 'S256');
574
+ console.error('🔐 Using PKCE flow (public client)');
575
+ } else {
576
+ console.error('🔐 Using SSO/confidential client flow (no PKCE)');
577
+ }
578
+
579
+ // Reconstruct the full URL
580
+ const fullUrl = `${baseUrl}?${existingParams.toString()}`;
581
+ return fullUrl;
582
+ }
583
+
584
+ /**
585
+ * Monitor browser window for authorization code in URL
586
+ * Works with ANY redirect URL - detects when URL contains code= parameter
587
+ */
588
+ private async monitorBrowserForAuthCode(): Promise<string> {
589
+ const platform = process.platform;
590
+ const timeout = 120; // 2 minutes
591
+ const startTime = Date.now();
592
+
593
+ if (platform === 'darwin') {
594
+ // macOS - Monitor using AppleScript
595
+ console.error(`🔍 Monitoring ${this.detectedBrowser} for authorization code...`);
596
+ let lastUrl = '';
597
+
598
+ while ((Date.now() - startTime) < timeout * 1000) {
599
+ try {
600
+ // Get URL from browser using AppleScript
601
+ const script = `
602
+ tell application "${this.detectedBrowser}"
603
+ try
604
+ set currentURL to URL of active tab of front window
605
+ return currentURL
606
+ on error
607
+ return ""
608
+ end try
609
+ end tell
610
+ `;
611
+
612
+ const { stdout } = await execAsync(`osascript -e '${script}'`);
613
+ const currentUrl = stdout.trim();
614
+
615
+ if (currentUrl && currentUrl !== lastUrl) {
616
+ lastUrl = currentUrl;
617
+ console.error(`🔍 Current URL: ${currentUrl}`);
618
+ }
619
+
620
+ // Check if URL contains code= parameter (regardless of domain)
621
+ if (currentUrl && currentUrl.includes('code=')) {
622
+ console.error('✅ Found authorization code in URL!');
623
+
624
+ // Extract the code from the URL
625
+ const codeMatch = currentUrl.match(/[?&]code=([^&]+)/);
626
+ if (codeMatch && codeMatch[1]) {
627
+ const code = decodeURIComponent(codeMatch[1]);
628
+ console.error(`🔑 Extracted authorization code (first 20 chars): ${code.substring(0, 20)}...`);
629
+ console.error(` Code length: ${code.length} characters`);
630
+
631
+ // Close the browser window immediately (non-blocking - fire and forget)
632
+ setImmediate(async () => {
633
+ try {
634
+ await execAsync(`osascript -e 'tell application "${this.detectedBrowser}" to close front window'`);
635
+ console.error('✅ Browser window closed');
636
+ } catch (closeError) {
637
+ console.error('⚠️ Could not close browser window:', closeError);
638
+ }
639
+ });
640
+
641
+ // Return code immediately without waiting for window close
642
+ return code;
643
+ }
644
+ }
645
+
646
+ // Also check for error parameters
647
+ if (currentUrl && currentUrl.includes('error=')) {
648
+ const errorMatch = currentUrl.match(/[?&]error=([^&]+)/);
649
+ const errorDescMatch = currentUrl.match(/error_description=([^&]+)/);
650
+ const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
651
+ const errorDesc = errorDescMatch && errorDescMatch[1] ? decodeURIComponent(errorDescMatch[1]) : 'No description';
652
+
653
+ // Throw OAuth error - this will stop monitoring but window stays open so user can see the error
654
+ throw new Error(`OAuth error: ${error} - ${errorDesc}`);
655
+ }
656
+
657
+ } catch (error) {
658
+ // Re-throw OAuth errors (they should stop monitoring but window stays open)
659
+ if (error instanceof Error && error.message.includes('OAuth error:')) {
660
+ throw error;
661
+ }
662
+ // Ignore AppleScript errors and continue monitoring
663
+ }
664
+
665
+ // Wait 500ms before checking again
666
+ await new Promise(resolve => setTimeout(resolve, 500));
667
+ }
668
+
669
+ throw new Error('Authentication timeout. Please try again.');
670
+
671
+ } else if (platform === 'win32') {
672
+ // Windows - Monitor browser window title (contains URL in most browsers)
673
+ console.error(`🔍 Monitoring ${this.detectedBrowser} for authorization code...`);
674
+ let lastTitle = '';
675
+
676
+ while ((Date.now() - startTime) < timeout * 1000) {
677
+ try {
678
+ // PowerShell script to get browser window title
679
+ // Window titles often contain the URL or page title
680
+ const browserProcessName = this.detectedBrowser.replace('.exe', '');
681
+ const psScript = `
682
+ $process = Get-Process -Name "${browserProcessName}" -ErrorAction SilentlyContinue |
683
+ Where-Object { $_.MainWindowHandle -ne 0 } |
684
+ Select-Object -First 1
685
+ if ($process) {
686
+ $process.MainWindowTitle
687
+ }
688
+ `.replace(/\n\s+/g, ' ');
689
+
690
+ const { stdout } = await execAsync(`powershell -Command "${psScript}"`);
691
+ const windowTitle = stdout.trim();
692
+
693
+ if (windowTitle && windowTitle !== lastTitle) {
694
+ lastTitle = windowTitle;
695
+ console.error(`🔍 Browser window: ${windowTitle.substring(0, 100)}...`);
696
+
697
+ // Check if title or URL contains the code parameter
698
+ // Most browsers show URL in the title or we can detect redirect completion
699
+ if (windowTitle.includes('code=') || windowTitle.includes('localhost:3333')) {
700
+ console.error('✅ Detected OAuth callback!');
701
+
702
+ // Try to extract code from title if visible
703
+ const codeMatch = windowTitle.match(/code=([^&\s]+)/);
704
+ if (codeMatch && codeMatch[1]) {
705
+ const code = decodeURIComponent(codeMatch[1]);
706
+ console.error(`🔑 Extracted authorization code (first 20 chars): ${code.substring(0, 20)}...`);
707
+ console.error(` Code length: ${code.length} characters`);
708
+
709
+ // Close browser window immediately (non-blocking - fire and forget)
710
+ setImmediate(async () => {
711
+ try {
712
+ await execAsync(`powershell -Command "Stop-Process -Name '${browserProcessName}' -Force"`);
713
+ console.error('✅ Browser window closed');
714
+ } catch (closeError) {
715
+ console.error('⚠️ Could not close browser window:', closeError);
716
+ }
717
+ });
718
+
719
+ // Return code immediately without waiting for window close
720
+ return code;
721
+ }
722
+ }
723
+
724
+ // Check for OAuth error in title
725
+ if (windowTitle.includes('error=')) {
726
+ const errorMatch = windowTitle.match(/error=([^&\s]+)/);
727
+ const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
728
+ // Throw OAuth error - this will stop monitoring but window stays open so user can see the error
729
+ throw new Error(`OAuth error: ${error}`);
730
+ }
731
+ }
732
+
733
+ } catch (error) {
734
+ // Re-throw OAuth errors (they should stop monitoring but window stays open)
735
+ if (error instanceof Error && error.message.includes('OAuth error:')) {
736
+ throw error;
737
+ }
738
+ // Ignore other errors and continue monitoring
739
+ }
740
+
741
+ // Wait 500ms before checking again
742
+ await new Promise(resolve => setTimeout(resolve, 500));
743
+ }
744
+
745
+ throw new Error('Authentication timeout. The browser window title did not show the authorization code. Please ensure your redirect URL is http://localhost:3333/callback for automatic detection on Windows.');
746
+
747
+ } else {
748
+ throw new Error('Linux is not supported. Use macOS or Windows for automatic authentication.');
749
+ }
750
+ }
751
+
752
+ private async getUserAccessToken(code: string): Promise<string> {
753
+ const { clientId, clientSecret, redirectUri, accessUrl } = this.authConfig;
754
+
755
+ console.error('🔄 Starting token exchange...');
756
+
757
+ // Warn if Access Token URL doesn't look like a token endpoint
758
+ if (accessUrl && !accessUrl.toLowerCase().includes('token')) {
759
+ console.error('⚠️ WARNING: Access Token URL does not contain "token" - verify this is correct!');
760
+ }
761
+
762
+ // Temporarily disable SSL verification for development/testing
763
+ // This is a workaround for corporate networks or certificate issues
764
+ const originalRejectUnauthorized = process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
765
+ process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
766
+
767
+ const headers = {
768
+ 'Content-Type': 'application/x-www-form-urlencoded'
769
+ };
770
+
771
+ try {
772
+ // SSO/Confidential client flow: Use client_secret only (no PKCE)
773
+ // Public client flow: Use PKCE with code_verifier (no client_secret)
774
+ if (clientSecret) {
775
+ console.error('🔄 Using SSO/confidential client flow (client_secret only, no PKCE)...');
776
+
777
+ const confidentialClientBody = new URLSearchParams({
778
+ code: code,
779
+ grant_type: 'authorization_code',
780
+ redirect_uri: redirectUri!,
781
+ client_id: clientId!,
782
+ client_secret: clientSecret
783
+ });
784
+
785
+ const confidentialResponse = await fetch(accessUrl!, {
786
+ method: 'POST',
787
+ headers,
788
+ body: confidentialClientBody
789
+ });
790
+
791
+ console.error('📨 Token response received (confidential client):', confidentialResponse.status, confidentialResponse.statusText);
792
+
793
+ if (confidentialResponse.ok) {
794
+ const data = await confidentialResponse.json() as { access_token?: string; expires_in?: number };
795
+
796
+ if (data.access_token) {
797
+ console.error('✅ Token received (confidential client)');
798
+ await this.saveTokenWithExpiration(data.access_token, data.expires_in);
799
+ return data.access_token;
800
+ } else {
801
+ throw new Error('No access_token in confidential client response');
802
+ }
803
+ } else {
804
+ const errorText = await confidentialResponse.text();
805
+ console.error('❌ Confidential client failed:', confidentialResponse.status);
806
+ throw new Error(`Token request failed: ${confidentialResponse.status} - ${errorText}`);
807
+ }
808
+ } else {
809
+ // Public client flow: Use PKCE with code_verifier
810
+ console.error('🔄 Using public client flow (PKCE with code_verifier)...');
811
+
812
+ const publicClientBody = new URLSearchParams({
813
+ code: code,
814
+ grant_type: 'authorization_code',
815
+ redirect_uri: redirectUri!,
816
+ client_id: clientId!,
817
+ code_verifier: this.codeVerifier
818
+ });
819
+
820
+ const publicResponse = await fetch(accessUrl!, {
821
+ method: 'POST',
822
+ headers,
823
+ body: publicClientBody
824
+ });
825
+
826
+ console.error('📨 Token response received (public client):', publicResponse.status, publicResponse.statusText);
827
+
828
+ if (publicResponse.ok) {
829
+ const data = await publicResponse.json() as { access_token?: string; expires_in?: number };
830
+
831
+ if (data.access_token) {
832
+ console.error('✅ Token received (public client)');
833
+ await this.saveTokenWithExpiration(data.access_token, data.expires_in);
834
+ return data.access_token;
835
+ } else {
836
+ throw new Error('No access_token in public client response');
837
+ }
838
+ } else {
839
+ const errorText = await publicResponse.text();
840
+ console.error('❌ Public client failed:', publicResponse.status);
841
+ throw new Error(`Token request failed (public client): ${publicResponse.status} - ${errorText}`);
842
+ }
843
+ }
844
+
845
+ } catch (error) {
846
+ console.error('💥 Token exchange error:', error);
847
+ throw new Error(`Error getting user access token: ${error}`);
848
+ } finally {
849
+ // Restore original SSL setting
850
+ if (originalRejectUnauthorized !== undefined) {
851
+ process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = originalRejectUnauthorized;
852
+ } else {
853
+ delete process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
854
+ }
855
+ }
856
+ }
857
+
858
+
859
+ private async saveToken(token: string): Promise<void> {
860
+ try {
861
+ // Save to the project root directory (not current working directory)
862
+ const projectRoot = getProjectRoot();
863
+ const tokenPath = path.join(projectRoot, '.bearer_token');
864
+
865
+ try {
866
+ fs.writeFileSync(tokenPath, token);
867
+ console.error(`💾 Access token saved to ${tokenPath}`);
868
+ } catch (error) {
869
+ console.error(`❌ Failed to save token to ${tokenPath}:`, error);
870
+ throw error;
871
+ }
872
+ } catch (error) {
873
+ console.error('Could not save token:', error);
874
+ throw error;
875
+ }
876
+ }
877
+
878
+ /**
879
+ * Saves token with expiration metadata for fast validation
880
+ * @param token The bearer token
881
+ * @param expiresIn Expiration time in seconds (from OAuth2 response)
882
+ */
883
+ private async saveTokenWithExpiration(token: string, expiresIn?: number): Promise<void> {
884
+ try {
885
+ const now = Date.now();
886
+ const expirationTime = expiresIn ? now + (expiresIn * 1000) : now + (3600 * 1000);
887
+
888
+ const tokenData = {
889
+ token,
890
+ expiresAt: expirationTime,
891
+ createdAt: now
892
+ };
893
+
894
+ const projectRoot = getProjectRoot();
895
+ const tokenPath = path.join(projectRoot, '.bearer_token');
896
+ const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
897
+
898
+ try {
899
+ fs.writeFileSync(tokenPath, token);
900
+ fs.writeFileSync(metadataPath, JSON.stringify(tokenData, null, 2));
901
+ console.error(`💾 Token saved (expires: ${new Date(expirationTime).toLocaleString()})`);
902
+ } catch (error) {
903
+ await this.saveToken(token);
904
+ }
905
+ } catch (error) {
906
+ await this.saveToken(token);
907
+ }
908
+ }
909
+
910
+ /**
911
+ * Checks if the stored token is expired without making API calls
912
+ * @returns true if token is valid, false if expired or not found
913
+ */
914
+ private isTokenValid(): boolean {
915
+ try {
916
+ const projectRoot = getProjectRoot();
917
+ const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
918
+
919
+ if (fs.existsSync(metadataPath)) {
920
+ const metadataContent = fs.readFileSync(metadataPath, 'utf8');
921
+ const tokenData = JSON.parse(metadataContent);
922
+ const timeUntilExpiry = tokenData.expiresAt - Date.now();
923
+
924
+ if (timeUntilExpiry > 60000) { // Valid if more than 1 minute left
925
+ return true;
926
+ } else {
927
+ console.error(`⏰ AUTH: Token expires in ${Math.round(timeUntilExpiry / 1000)} seconds - treating as expired`);
928
+ return false;
929
+ }
930
+ }
931
+
932
+ return false;
933
+ } catch (error) {
934
+ return false;
935
+ }
936
+ }
937
+
938
+ // Force login mechanism removed - use `npm run logout` instead
939
+
940
+ private loadExistingToken(): string | null {
941
+ try {
942
+ // Check in the project root directory (not current working directory)
943
+ const projectRoot = getProjectRoot();
944
+ const tokenPath = path.join(projectRoot, '.bearer_token');
945
+
946
+ if (fs.existsSync(tokenPath)) {
947
+ const token = fs.readFileSync(tokenPath, 'utf8').trim();
948
+ if (token && token.length > 10) { // Basic validation
949
+ console.error(`🔑 Found existing bearer token at: ${tokenPath}`);
950
+ return token;
951
+ }
952
+ }
953
+ } catch (error) {
954
+ console.error('Could not load existing token:', error);
955
+ }
956
+ return null;
957
+ }
958
+
959
+ /**
960
+ * Clear the stored bearer token (for remote clearing)
961
+ */
962
+ public clearToken(): void {
963
+ try {
964
+ const projectRoot = getProjectRoot();
965
+ const tokenPath = path.join(projectRoot, '.bearer_token');
966
+ const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
967
+
968
+ if (fs.existsSync(tokenPath)) {
969
+ fs.unlinkSync(tokenPath);
970
+ console.error('🗑️ Deleted bearer token file');
971
+ }
972
+
973
+ if (fs.existsSync(metadataPath)) {
974
+ fs.unlinkSync(metadataPath);
975
+ console.error('🗑️ Deleted bearer token metadata file');
976
+ }
977
+
978
+ this.token = null;
979
+ console.error('✅ Token cleared successfully');
980
+ } catch (error) {
981
+ console.error('❌ Failed to clear token:', error);
982
+ throw error;
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Start the configuration HTTP server
988
+ */
989
+ private async startConfigServer(): Promise<void> {
990
+ return new Promise((resolve, reject) => {
991
+ if (this.configServer) {
992
+ console.error('⚠️ Config server already running');
993
+ resolve();
994
+ return;
995
+ }
996
+
997
+ console.error(`🌐 Starting configuration server on http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
998
+
999
+ this.configServer = http.createServer(async (req, res) => {
1000
+ const url = new URL(req.url!, `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
1001
+
1002
+ // Serve static images
1003
+ if (url.pathname && url.pathname.startsWith('/img/')) {
1004
+ // Images are in the source directory, not the compiled output
1005
+ const projectRoot = getProjectRoot();
1006
+ const imagePath = path.join(projectRoot, 'src', 'hooks', url.pathname);
1007
+ try {
1008
+ const imageData = fs.readFileSync(imagePath);
1009
+ const ext = path.extname(imagePath).toLowerCase();
1010
+ const contentType = ext === '.png' ? 'image/png' : ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
1011
+ res.writeHead(200, { 'Content-Type': contentType });
1012
+ res.end(imageData);
1013
+ return;
1014
+ } catch (error) {
1015
+ console.error(`❌ Image not found: ${imagePath}`, error);
1016
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
1017
+ res.end('Image not found');
1018
+ return;
1019
+ }
1020
+ }
1021
+
1022
+ // Serve config page JavaScript
1023
+ if (url.pathname === '/config-page.js') {
1024
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
1025
+ res.end(this.getConfigPageJS());
1026
+ return;
1027
+ }
1028
+
1029
+ // Serve configuration page
1030
+ if (url.pathname === '/' || url.pathname === '/config') {
1031
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1032
+ res.end(this.getConfigPage());
1033
+ return;
1034
+ }
1035
+
1036
+ // Serve Safari warning page
1037
+ if (url.pathname === '/safari-warning') {
1038
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1039
+ res.end(this.getSafariWarningPage());
1040
+ return;
1041
+ }
1042
+
1043
+ // Get saved configuration
1044
+ if (url.pathname === '/get-config' && req.method === 'GET') {
1045
+ try {
1046
+ // Check if we have a valid config (at least the required fields)
1047
+ const hasValidConfig = this.authConfig.environmentUrl &&
1048
+ this.authConfig.clientId &&
1049
+ this.authConfig.authUrl &&
1050
+ this.authConfig.accessUrl &&
1051
+ this.authConfig.redirectUri;
1052
+
1053
+ if (!hasValidConfig) {
1054
+ // No config exists
1055
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1056
+ res.end(JSON.stringify({ config: null }));
1057
+ return;
1058
+ }
1059
+
1060
+ // Return current authConfig (loaded from file or .env)
1061
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1062
+ res.end(JSON.stringify({
1063
+ config: {
1064
+ egainUrl: this.authConfig.environmentUrl,
1065
+ authUrl: this.authConfig.authUrl,
1066
+ accessTokenUrl: this.authConfig.accessUrl,
1067
+ clientId: this.authConfig.clientId,
1068
+ redirectUrl: this.authConfig.redirectUri,
1069
+ clientSecret: this.authConfig.clientSecret,
1070
+ scopePrefix: this.authConfig.scopePrefix
1071
+ }
1072
+ }));
1073
+ } catch (error: any) {
1074
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1075
+ res.end(JSON.stringify({ success: false, error: error.message }));
1076
+ }
1077
+ return;
1078
+ }
1079
+
1080
+ // Get OAuth URL for saved config (used when signing in with existing config)
1081
+ if (url.pathname === '/get-oauth-url' && req.method === 'POST') {
1082
+ try {
1083
+ const oauthUrl = this.buildAuthUrl();
1084
+ console.error('🔐 Generated OAuth URL for saved configuration');
1085
+ console.error('🔗 OAuth URL:', oauthUrl);
1086
+
1087
+ // Mark that OAuth redirect is about to happen (shorter timeout applies)
1088
+ this.oauthRedirectStarted = true;
1089
+
1090
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1091
+ res.end(JSON.stringify({
1092
+ success: true,
1093
+ oauthUrl: oauthUrl
1094
+ }));
1095
+
1096
+ // Start monitoring browser in background (for ANY redirect URL)
1097
+ console.error('🔍 Starting browser URL monitoring for authorization code...');
1098
+ setImmediate(async () => {
1099
+ try {
1100
+ const code = await this.monitorBrowserForAuthCode();
1101
+ console.error('✅ Authorization code detected:', code.substring(0, 10) + '...');
1102
+
1103
+ const accessToken = await this.getUserAccessToken(code);
1104
+ console.error('✅ Access token received');
1105
+
1106
+ this.token = accessToken;
1107
+
1108
+ // Trigger cache initialization if available
1109
+ if (this.portalCacheHook) {
1110
+ try {
1111
+ const fakeRequest = new Request(this.authConfig.environmentUrl!, {
1112
+ headers: { 'Authorization': `Bearer ${accessToken}` }
1113
+ });
1114
+ await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
1115
+ } catch (error) {
1116
+ // Cache init failure is non-fatal
1117
+ }
1118
+ }
1119
+
1120
+ console.error('🎉 Authentication complete! Stopping config server...');
1121
+ this.stopConfigServer();
1122
+
1123
+ } catch (authError: any) {
1124
+ console.error('❌ Authentication monitoring error:', authError);
1125
+ this.stopConfigServer();
1126
+ }
1127
+ });
1128
+
1129
+ } catch (error: any) {
1130
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1131
+ res.end(JSON.stringify({ success: false, error: error.message }));
1132
+ }
1133
+ return;
1134
+ }
1135
+
1136
+ // Clear saved configuration
1137
+ if (url.pathname === '/clear-config' && req.method === 'POST') {
1138
+ try {
1139
+ const configPath = getConfigPath();
1140
+ if (fs.existsSync(configPath)) {
1141
+ fs.unlinkSync(configPath);
1142
+ console.error(`🗑️ Deleted config file: ${configPath}`);
1143
+ }
1144
+
1145
+ // Clear in-memory config
1146
+ this.authConfig = {};
1147
+
1148
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1149
+ res.end(JSON.stringify({ success: true, message: 'Configuration cleared' }));
1150
+ } catch (error: any) {
1151
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1152
+ res.end(JSON.stringify({ success: false, error: error.message }));
1153
+ }
1154
+ return;
1155
+ }
1156
+
1157
+ // Handle authentication request
1158
+ if (url.pathname === '/authenticate' && req.method === 'POST') {
1159
+ let body = '';
1160
+ req.on('data', chunk => { body += chunk.toString(); });
1161
+ req.on('end', async () => {
1162
+ try {
1163
+ const config = JSON.parse(body);
1164
+ console.error('📝 Received configuration from browser form');
1165
+
1166
+ // Validate URLs don't contain spaces (common mistake: pasting multiple URLs)
1167
+ const urlFields = [
1168
+ { name: 'eGain Environment URL', value: config.egainUrl },
1169
+ { name: 'Authorization URL', value: config.authUrl },
1170
+ { name: 'Access Token URL', value: config.accessTokenUrl },
1171
+ { name: 'Redirect URL', value: config.redirectUrl }
1172
+ ];
1173
+
1174
+ for (const field of urlFields) {
1175
+ if (field.value && field.value.includes(' ')) {
1176
+ const errorMsg = `❌ ${field.name} contains spaces! It looks like multiple URLs were pasted together. Please enter only ONE URL for this field.`;
1177
+ console.error(errorMsg);
1178
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1179
+ res.end(JSON.stringify({
1180
+ success: false,
1181
+ error: `${field.name} is invalid - contains multiple URLs. Please enter only one URL per field.`
1182
+ }));
1183
+ return;
1184
+ }
1185
+ }
1186
+
1187
+ // Update authConfig from browser form data
1188
+ this.authConfig = {
1189
+ environmentUrl: config.egainUrl,
1190
+ authUrl: config.authUrl,
1191
+ accessUrl: config.accessTokenUrl,
1192
+ clientId: config.clientId,
1193
+ redirectUri: config.redirectUrl,
1194
+ clientSecret: config.clientSecret || undefined,
1195
+ scopePrefix: config.scopePrefix || undefined
1196
+ };
1197
+
1198
+ // Save config to secure file storage (home directory)
1199
+ try {
1200
+ this.saveConfigToFile(this.authConfig);
1201
+ console.error('✅ Configuration saved to secure file storage');
1202
+ } catch (error) {
1203
+ console.error('⚠️ Failed to save config to file:', error);
1204
+ // Continue with authentication even if file save fails
1205
+ }
1206
+
1207
+ // Generate OAuth URL and return it to frontend for redirect (single window flow)
1208
+ const oauthUrl = this.buildAuthUrl();
1209
+ console.error('🔐 Generated OAuth URL for browser redirect');
1210
+ console.error('🔗 OAuth URL:', oauthUrl);
1211
+
1212
+ // Mark that OAuth redirect is about to happen (shorter timeout applies)
1213
+ this.oauthRedirectStarted = true;
1214
+
1215
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1216
+ res.end(JSON.stringify({
1217
+ success: true,
1218
+ message: 'Configuration saved. Redirecting to login...',
1219
+ oauthUrl: oauthUrl // Frontend will redirect to this URL
1220
+ }));
1221
+
1222
+ // Start monitoring browser in background (for ANY redirect URL)
1223
+ console.error('🔍 Starting browser URL monitoring for authorization code...');
1224
+ setImmediate(async () => {
1225
+ try {
1226
+ const code = await this.monitorBrowserForAuthCode();
1227
+ console.error('✅ Authorization code detected:', code.substring(0, 10) + '...');
1228
+
1229
+ const accessToken = await this.getUserAccessToken(code);
1230
+ console.error('✅ Access token received');
1231
+
1232
+ this.token = accessToken;
1233
+
1234
+ // Trigger cache initialization if available
1235
+ if (this.portalCacheHook) {
1236
+ console.error('🔄 Triggering cache initialization...');
1237
+ try {
1238
+ const fakeRequest = new Request(this.authConfig.environmentUrl!, {
1239
+ headers: { 'Authorization': `Bearer ${accessToken}` }
1240
+ });
1241
+ await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
1242
+ console.error('✅ Cache initialization completed');
1243
+ } catch (error) {
1244
+ console.error('⚠️ Cache initialization failed:', error);
1245
+ }
1246
+ }
1247
+
1248
+ console.error('🎉 Authentication complete! Stopping config server...');
1249
+ this.stopConfigServer();
1250
+
1251
+ } catch (authError: any) {
1252
+ console.error('❌ Authentication monitoring error:', authError);
1253
+ this.stopConfigServer();
1254
+ }
1255
+ });
1256
+
1257
+ } catch (error: any) {
1258
+ console.error('❌ Error processing authentication request:', error);
1259
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1260
+ res.end(JSON.stringify({
1261
+ success: false,
1262
+ error: error.message
1263
+ }));
1264
+ }
1265
+ });
1266
+ return;
1267
+ }
1268
+
1269
+ // Handle OAuth callback (after user completes Azure B2C login)
1270
+ if (url.pathname === '/callback' && req.method === 'GET') {
1271
+ try {
1272
+ const code = url.searchParams.get('code');
1273
+ const error = url.searchParams.get('error');
1274
+
1275
+ if (error) {
1276
+ const errorDesc = url.searchParams.get('error_description') || 'No description';
1277
+ console.error('❌ OAuth error:', error, errorDesc);
1278
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1279
+ res.end(`
1280
+ <html>
1281
+ <head><title>Authentication Error</title></head>
1282
+ <body style="font-family: sans-serif; padding: 40px; text-align: center;">
1283
+ <h1>❌ Authentication Error</h1>
1284
+ <p><strong>${error}</strong></p>
1285
+ <p>${errorDesc}</p>
1286
+ <p>You can close this window and try again.</p>
1287
+ <script>setTimeout(() => window.close(), 3000);</script>
1288
+ </body>
1289
+ </html>
1290
+ `);
1291
+ return;
1292
+ }
1293
+
1294
+ if (!code) {
1295
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
1296
+ res.end('Missing authorization code');
1297
+ return;
1298
+ }
1299
+
1300
+ console.error('✅ Authorization code received from OAuth callback');
1301
+
1302
+ // Exchange code for token (async in background)
1303
+ setImmediate(async () => {
1304
+ try {
1305
+ const accessToken = await this.getUserAccessToken(code);
1306
+ console.error('✅ Access token received');
1307
+
1308
+ this.token = accessToken;
1309
+
1310
+ // Trigger cache initialization if available
1311
+ if (this.portalCacheHook) {
1312
+ try {
1313
+ const fakeRequest = new Request(this.authConfig.environmentUrl!, {
1314
+ headers: { 'Authorization': `Bearer ${accessToken}` }
1315
+ });
1316
+ await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
1317
+ } catch (error) {
1318
+ // Cache init failure is non-fatal
1319
+ }
1320
+ }
1321
+
1322
+ console.error('🎉 Authentication complete! Stopping config server...');
1323
+ // Stop server after successful authentication
1324
+ this.stopConfigServer();
1325
+
1326
+ } catch (authError: any) {
1327
+ console.error('❌ Token exchange error:', authError);
1328
+ this.stopConfigServer();
1329
+ }
1330
+ });
1331
+
1332
+ // Send success page to browser
1333
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1334
+ res.end(`
1335
+ <html>
1336
+ <head><title>Authentication Complete</title></head>
1337
+ <body style="font-family: sans-serif; padding: 40px; text-align: center;">
1338
+ <h1>✅ Authentication Complete!</h1>
1339
+ <p>You can now close this window.</p>
1340
+ <p style="color: #666; font-size: 14px;">This window will close automatically in 2 seconds...</p>
1341
+ <script>setTimeout(() => window.close(), 2000);</script>
1342
+ </body>
1343
+ </html>
1344
+ `);
1345
+
1346
+ } catch (error: any) {
1347
+ console.error('❌ Error handling OAuth callback:', error);
1348
+ res.writeHead(500, { 'Content-Type': 'text/html' });
1349
+ res.end(`
1350
+ <html>
1351
+ <head><title>Error</title></head>
1352
+ <body style="font-family: sans-serif; padding: 40px; text-align: center;">
1353
+ <h1>❌ Error</h1>
1354
+ <p>${error.message}</p>
1355
+ <script>setTimeout(() => window.close(), 3000);</script>
1356
+ </body>
1357
+ </html>
1358
+ `);
1359
+ }
1360
+ return;
1361
+ }
1362
+
1363
+ // Handle clear token request
1364
+ if (url.pathname === '/clear-token' && req.method === 'POST') {
1365
+ try {
1366
+ this.clearToken();
1367
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1368
+ res.end(JSON.stringify({ success: true, message: 'Token cleared' }));
1369
+ } catch (error: any) {
1370
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1371
+ res.end(JSON.stringify({ success: false, error: error.message }));
1372
+ }
1373
+ return;
1374
+ }
1375
+
1376
+ // Handle authentication cancellation
1377
+ if (url.pathname === '/cancel' && req.method === 'POST') {
1378
+ console.error('🚫 User cancelled authentication');
1379
+ this.authCancelled = true;
1380
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1381
+ res.end(JSON.stringify({ success: true, message: 'Authentication cancelled' }));
1382
+ return;
1383
+ }
1384
+
1385
+ // 404
1386
+ res.writeHead(404);
1387
+ res.end('Not Found');
1388
+ });
1389
+
1390
+ this.configServer.listen(CONFIG_SERVER_PORT, CONFIG_SERVER_HOST, () => {
1391
+ console.error(`✅ Configuration server started at http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
1392
+ resolve();
1393
+ });
1394
+
1395
+ this.configServer.on('error', (error: any) => {
1396
+ if (error.code === 'EADDRINUSE') {
1397
+ console.error(`❌ Port ${CONFIG_SERVER_PORT} is already in use`);
1398
+ reject(new Error(`Port ${CONFIG_SERVER_PORT} is already in use. Please close other applications using this port.`));
1399
+ } else {
1400
+ console.error('❌ Server error:', error);
1401
+ reject(error);
1402
+ }
1403
+ });
1404
+ });
1405
+ }
1406
+
1407
+ /**
1408
+ * Stop the configuration HTTP server
1409
+ */
1410
+ private stopConfigServer(): void {
1411
+ if (this.configServer) {
1412
+ console.error('🚪 Stopping configuration server...');
1413
+ this.configServer.close(() => {
1414
+ console.error('✅ Configuration server stopped');
1415
+ });
1416
+ // Immediately set to null to prevent double-close
1417
+ this.configServer = null;
1418
+ }
1419
+ }
1420
+
1421
+ /**
1422
+ * Open browser to configuration page
1423
+ */
1424
+ private async openConfigBrowser(): Promise<void> {
1425
+ const platform = process.platform;
1426
+
1427
+ // Detect browser on macOS or Windows before opening
1428
+ if (platform === 'darwin' || platform === 'win32') {
1429
+ await this.detectDefaultBrowser();
1430
+ }
1431
+
1432
+ console.error(`🌐 Browser: ${this.detectedBrowser}`);
1433
+
1434
+ // Safari shows warning page instead of config form (security limitation)
1435
+ const configUrl = this.detectedBrowser === 'Safari'
1436
+ ? `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}/safari-warning`
1437
+ : `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}/config`;
1438
+
1439
+ if (this.detectedBrowser === 'Safari') {
1440
+ console.error('⚠️ Safari detected - showing browser installation guide...');
1441
+ } else {
1442
+ console.error('📋 Opening browser for configuration...');
1443
+ }
1444
+
1445
+ const windowWidth = 600;
1446
+ const windowHeight = 800;
1447
+
1448
+ try {
1449
+ if (platform === 'darwin') {
1450
+ // macOS - Open default browser in private mode
1451
+ const incognitoFlag = this.getIncognitoFlag();
1452
+ console.error(`🕵️ Using private mode flag: ${incognitoFlag || '(none - browser limitation)'}`);
1453
+
1454
+ // Safari doesn't support --args flags well, use AppleScript for private browsing
1455
+ if (this.detectedBrowser === 'Safari') {
1456
+ console.error(` Opening Safari in Private Browsing mode via AppleScript...`);
1457
+
1458
+ // AppleScript to open Safari in private browsing mode
1459
+ const appleScript = `
1460
+ tell application "Safari"
1461
+ activate
1462
+
1463
+ -- Try to open a new private window
1464
+ -- Note: This may not work in newer macOS versions due to security restrictions
1465
+ try
1466
+ make new document
1467
+ delay 0.5
1468
+ set URL of document 1 to "${configUrl}"
1469
+
1470
+ -- Attempt to enable private browsing (may not work in all macOS versions)
1471
+ tell window 1
1472
+ try
1473
+ set private browsing to true
1474
+ end try
1475
+ end tell
1476
+ on error
1477
+ -- Fallback: just open the URL in a new window
1478
+ open location "${configUrl}"
1479
+ end try
1480
+ end tell
1481
+ `.replace(/\n\s+/g, ' ');
1482
+
1483
+ try {
1484
+ await execAsync(`osascript -e '${appleScript}'`);
1485
+ console.error(`⚠️ IMPORTANT: If Safari didn't open in Private Browsing mode:`);
1486
+ console.error(` 1. Close the Safari window`);
1487
+ console.error(` 2. Open Safari manually in Private Browsing (File > New Private Window)`);
1488
+ console.error(` 3. Navigate to: ${configUrl}`);
1489
+ } catch (error) {
1490
+ // Fallback to simple open if AppleScript fails
1491
+ console.error(` AppleScript failed, using fallback method...`);
1492
+ await execAsync(`open -a "Safari" "${configUrl}"`);
1493
+ console.error(`⚠️ IMPORTANT: Please manually enable Private Browsing in Safari!`);
1494
+ console.error(` (File > New Private Window, then go to: ${configUrl})`);
1495
+ }
1496
+ } else if (this.detectedBrowser === 'Firefox') {
1497
+ // Firefox: Use AppleScript to ensure private window opens even if Firefox is already running
1498
+ console.error(` Opening Firefox in private browsing mode...`);
1499
+
1500
+ const firefoxScript = `
1501
+ tell application "Firefox"
1502
+ activate
1503
+
1504
+ -- Check if Firefox is running
1505
+ set isRunning to true
1506
+
1507
+ -- Open new private window using Firefox's built-in command
1508
+ try
1509
+ -- Use Firefox's internal private browsing command
1510
+ do shell script "open -a Firefox --args --private-window '${configUrl}'"
1511
+ on error
1512
+ -- Fallback: try to open with just the private flag
1513
+ do shell script "open -na Firefox --args -private-window '${configUrl}'"
1514
+ end try
1515
+ end tell
1516
+ `.replace(/\n\s+/g, ' ');
1517
+
1518
+ try {
1519
+ await execAsync(`osascript -e '${firefoxScript}'`);
1520
+ } catch (error) {
1521
+ // Ultimate fallback: force new instance with -n flag
1522
+ console.error(` AppleScript failed, trying fallback...`);
1523
+ await execAsync(`open -na "Firefox" --args -private-window "${configUrl}"`);
1524
+ }
1525
+ } else {
1526
+ // Chrome, Edge, Brave, etc. support --args flags
1527
+ const args = incognitoFlag
1528
+ ? `--args ${incognitoFlag} --app="${configUrl}" --window-size=${windowWidth},${windowHeight} --window-position=100,100`
1529
+ : `"${configUrl}"`; // Fallback for browsers without good CLI support
1530
+
1531
+ const command = `open -n -a "${this.detectedBrowser}" ${args}`;
1532
+ console.error(` Executing: open -n -a "${this.detectedBrowser}" ...`);
1533
+
1534
+ await execAsync(command);
1535
+ }
1536
+ } else if (platform === 'win32') {
1537
+ // Windows - Open detected browser with private mode
1538
+ const incognitoFlag = this.getIncognitoFlag();
1539
+ console.error(`🕵️ Using private mode flag: ${incognitoFlag}`);
1540
+
1541
+ if (this.detectedBrowser === 'firefox') {
1542
+ // Firefox on Windows needs special handling
1543
+ console.error(` Executing: start firefox ${incognitoFlag} ...`);
1544
+ await execAsync(`cmd /c start "" "${this.detectedBrowser}" ${incognitoFlag} "${configUrl}"`);
1545
+ } else {
1546
+ // Chrome, Edge, Brave support --app mode
1547
+ console.error(` Executing: start ${this.detectedBrowser} ${incognitoFlag} --app ...`);
1548
+ await execAsync(`cmd /c start "" "${this.detectedBrowser}" ${incognitoFlag} --app="${configUrl}" --window-size=${windowWidth},${windowHeight} --window-position=100,100`);
1549
+ }
1550
+ } else {
1551
+ // Linux - Use default browser
1552
+ console.error(` Executing: xdg-open ...`);
1553
+ await execAsync(`xdg-open "${configUrl}"`);
1554
+ }
1555
+ console.error('✅ Browser opened successfully');
1556
+ } catch (error) {
1557
+ console.error('❌ Failed to open browser:', error instanceof Error ? error.message : error);
1558
+ console.error('⚠️ Please open manually: ' + configUrl);
1559
+ }
1560
+ }
1561
+
1562
+ public async authenticate(): Promise<string> {
1563
+ try {
1564
+ // Try to use existing token first (lazy mode)
1565
+ console.error('🔐 Starting Azure B2C OAuth2 authentication...');
1566
+
1567
+ // Fast validation using metadata (no API calls)
1568
+ if (this.isTokenValid()) {
1569
+ const existingToken = this.loadExistingToken();
1570
+ if (existingToken) {
1571
+ console.error('🎉 Using existing valid bearer token (lazy mode)');
1572
+ this.token = existingToken;
1573
+
1574
+ // Trigger cache initialization with existing token
1575
+ if (this.portalCacheHook) {
1576
+ try {
1577
+ const fakeRequest = new Request(this.authConfig.environmentUrl || 'https://api-dev9.knowledge.ai/knowledge', {
1578
+ headers: { 'Authorization': `Bearer ${existingToken}` }
1579
+ });
1580
+ await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
1581
+ } catch (error) {
1582
+ // Cache init failure is non-fatal
1583
+ }
1584
+ }
1585
+
1586
+ return this.token;
1587
+ }
1588
+ } else {
1589
+ console.error('⏰ Existing token is expired or not found, proceeding with fresh login...');
1590
+ }
1591
+
1592
+ // Check if we have configuration (from .env or file)
1593
+ const hasConfig = this.authConfig.environmentUrl && this.authConfig.clientId &&
1594
+ this.authConfig.redirectUri && this.authConfig.authUrl && this.authConfig.accessUrl;
1595
+
1596
+ if (!hasConfig) {
1597
+ console.error('📝 No configuration found, starting browser-based configuration...');
1598
+ } else {
1599
+ console.error('✅ Configuration found, opening browser for authentication...');
1600
+ }
1601
+
1602
+ console.error('🌐 A browser window will open for you to authenticate');
1603
+
1604
+ // Start config server and open browser (for both cases)
1605
+ await this.startConfigServer();
1606
+ await this.openConfigBrowser();
1607
+
1608
+ // Two-phase timeout:
1609
+ // Phase 1: Filling config form (generous time)
1610
+ // Phase 2: After OAuth redirect, logging in (shorter time)
1611
+ const configFormTimeout = 900000; // 15 minutes for filling form
1612
+ const oauthLoginTimeout = 300000; // 5 minutes after OAuth redirect
1613
+ const checkInterval = 1000; // 1 second
1614
+ const startTime = Date.now();
1615
+
1616
+ while (!this.token && !this.authCancelled) {
1617
+ const elapsed = Date.now() - startTime;
1618
+
1619
+ // Determine which timeout applies
1620
+ const currentTimeout = this.oauthRedirectStarted ? oauthLoginTimeout : configFormTimeout;
1621
+ const timeoutLabel = this.oauthRedirectStarted ? 'OAuth login' : 'configuration form';
1622
+
1623
+ if (elapsed >= currentTimeout) {
1624
+ this.stopConfigServer();
1625
+ throw new Error(`Authentication timeout (${timeoutLabel}). Please try again.`);
1626
+ }
1627
+
1628
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
1629
+ }
1630
+
1631
+ if (this.authCancelled) {
1632
+ this.stopConfigServer();
1633
+ this.authCancelled = false; // Reset for next attempt
1634
+ this.oauthRedirectStarted = false; // Reset flag
1635
+ throw new Error('Authentication cancelled by user. Authentication is required to use eGain MCP tools.');
1636
+ }
1637
+
1638
+ // Final check: ensure token was actually received
1639
+ if (!this.token) {
1640
+ this.stopConfigServer();
1641
+ throw new Error('Authentication completed but no token received. Please try again.');
1642
+ }
1643
+
1644
+ console.error('✅ Authentication completed');
1645
+ return this.token;
1646
+ } catch (error) {
1647
+ this.stopConfigServer();
1648
+ console.error('❌ Authentication failed:', error);
1649
+ throw error;
1650
+ }
1651
+ }
1652
+
1653
+ async beforeRequest(_hookCtx: HookContext, request: Request): Promise<Request> {
1654
+ // Check if request already has valid Authorization header
1655
+ const existingAuth = request.headers.get('Authorization');
1656
+ if (existingAuth && existingAuth.startsWith('Bearer ') && this.isTokenValid()) {
1657
+ return request;
1658
+ }
1659
+
1660
+ // Get or refresh token
1661
+ if (!this.token || !this.isTokenValid()) {
1662
+ this.token = await this.authenticate();
1663
+ }
1664
+
1665
+ // Clone the request and add the Authorization header
1666
+ const headers = new Headers(request.headers);
1667
+ headers.set('Authorization', `Bearer ${this.token}`);
1668
+
1669
+ const requestOptions: RequestInit = {
1670
+ method: request.method,
1671
+ headers: headers,
1672
+ signal: request.signal,
1673
+ };
1674
+
1675
+ if (request.body) {
1676
+ requestOptions.body = request.body;
1677
+ (requestOptions as any).duplex = 'half';
1678
+ }
1679
+
1680
+ return new Request(request.url, requestOptions);
1681
+ }
1682
+
1683
+ sdkInit(opts: SDKOptions): SDKOptions {
1684
+ // Check if bearer token was provided via CLI flag
1685
+ if (opts.security && typeof opts.security === 'object' && 'bearerAuth' in opts.security) {
1686
+ const providedToken = (opts.security as any).bearerAuth;
1687
+ if (providedToken && typeof providedToken === 'string' && providedToken.trim().length > 0) {
1688
+ console.error('🔑 Using bearer token from CLI flag');
1689
+ this.token = providedToken;
1690
+ return opts;
1691
+ }
1692
+ }
1693
+
1694
+ // Load existing token if available
1695
+ if (!this.token) {
1696
+ this.token = this.loadExistingToken();
1697
+ }
1698
+
1699
+ // If we have a token, set up the security provider
1700
+ if (this.token) {
1701
+ return {
1702
+ ...opts,
1703
+ security: { accessToken: this.token }
1704
+ };
1705
+ }
1706
+
1707
+ // No token available - set up async authentication
1708
+ const securityProvider = async () => {
1709
+ const token = await this.authenticate();
1710
+ return { accessToken: token };
1711
+ };
1712
+
1713
+ return {
1714
+ ...opts,
1715
+ security: securityProvider,
1716
+ };
1717
+ }
1718
+
1719
+ private findProjectRoot(): string[] {
1720
+ const roots: string[] = [];
1721
+
1722
+ // Always prioritize process.cwd() as the primary project root
1723
+ const cwd = process.cwd();
1724
+ const cwdPackageJsonPath = path.join(cwd, 'package.json');
1725
+ if (fs.existsSync(cwdPackageJsonPath)) {
1726
+ roots.push(cwd);
1727
+ // Only return the current working directory to ensure we stay within the project
1728
+ return roots;
1729
+ }
1730
+
1731
+ // If no package.json in cwd, search up from __dirname but limit to reasonable project boundaries
1732
+ let currentDir = __dirname;
1733
+ const maxLevelsUp = 5; // Reasonable limit to prevent going too far up
1734
+
1735
+ for (let i = 0; i < maxLevelsUp; i++) {
1736
+ const packageJsonPath = path.join(currentDir, 'package.json');
1737
+ if (fs.existsSync(packageJsonPath)) {
1738
+ // Only add if it's not already in roots and seems to be within a reasonable project structure
1739
+ if (!roots.includes(currentDir)) {
1740
+ roots.push(currentDir);
1741
+ }
1742
+ break;
1743
+ }
1744
+
1745
+ const parentDir = path.dirname(currentDir);
1746
+ if (parentDir === currentDir) break; // Reached filesystem root
1747
+ currentDir = parentDir;
1748
+ }
1749
+
1750
+ return roots;
1751
+ }
1752
+ }