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