@theokit/sdk 4.53.0 → 4.53.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 (373) hide show
  1. package/CHANGELOG.md +8 -8
  2. package/LICENSE +2 -2
  3. package/README.md +5 -5
  4. package/bin/theokit-migrate-config.mjs +11 -2
  5. package/dist/{agent-BiCINq25.d.ts → agent-B9HXlwID.d.ts} +21 -8
  6. package/dist/agent-FFVV4U5N.js +50 -0
  7. package/dist/{agent-ZAGG6ZBS.js.map → agent-FFVV4U5N.js.map} +1 -1
  8. package/dist/agent-YLAHVHEH.cjs +59 -0
  9. package/dist/{agent-JX5SBYDE.cjs.map → agent-YLAHVHEH.cjs.map} +1 -1
  10. package/dist/{agent-Zta1kvGH.d.cts → agent-nLqeXKIX.d.cts} +21 -8
  11. package/dist/agent-session-store-DUGBAX5V.cjs +23 -0
  12. package/dist/{agent-session-store-EPX2WQI4.cjs.map → agent-session-store-DUGBAX5V.cjs.map} +1 -1
  13. package/dist/agent-session-store-TNCGL5RN.js +6 -0
  14. package/dist/{agent-session-store-JMU7ASDB.js.map → agent-session-store-TNCGL5RN.js.map} +1 -1
  15. package/dist/agent.d.ts +16 -1
  16. package/dist/auth/index.cjs +18 -18
  17. package/dist/auth/index.cjs.map +1 -1
  18. package/dist/auth/index.d.cts +1 -1
  19. package/dist/auth/index.d.ts +1 -1
  20. package/dist/auth/index.js +6 -6
  21. package/dist/auth/index.js.map +1 -1
  22. package/dist/{batch-NLIS4QTW.js → batch-UPKN4SG6.js} +9 -9
  23. package/dist/{batch-NLIS4QTW.js.map → batch-UPKN4SG6.js.map} +1 -1
  24. package/dist/{batch-WQ3AJCDV.cjs → batch-ZEFN5SO7.cjs} +26 -26
  25. package/dist/{batch-WQ3AJCDV.cjs.map → batch-ZEFN5SO7.cjs.map} +1 -1
  26. package/dist/{chunk-2QMK627M.cjs → chunk-2D43RD5D.cjs} +8 -8
  27. package/dist/{chunk-2QMK627M.cjs.map → chunk-2D43RD5D.cjs.map} +1 -1
  28. package/dist/{chunk-DM6Y5B2G.cjs → chunk-2FYEVT2B.cjs} +121 -121
  29. package/dist/{chunk-DM6Y5B2G.cjs.map → chunk-2FYEVT2B.cjs.map} +1 -1
  30. package/dist/{chunk-BBZYXVLZ.cjs → chunk-2ZPEDVLM.cjs} +2 -2
  31. package/dist/chunk-2ZPEDVLM.cjs.map +1 -0
  32. package/dist/{chunk-T6QUCG7L.cjs → chunk-3BK6YR5P.cjs} +4 -4
  33. package/dist/{chunk-T6QUCG7L.cjs.map → chunk-3BK6YR5P.cjs.map} +1 -1
  34. package/dist/{chunk-23VZBRDQ.js → chunk-3V7UJLQT.js} +3 -3
  35. package/dist/chunk-3V7UJLQT.js.map +1 -0
  36. package/dist/{chunk-ITQ4NO4P.js → chunk-3WVMJY7F.js} +3 -3
  37. package/dist/{chunk-ITQ4NO4P.js.map → chunk-3WVMJY7F.js.map} +1 -1
  38. package/dist/{chunk-RAACTJ7C.cjs → chunk-3X6PM7DU.cjs} +4 -4
  39. package/dist/{chunk-RAACTJ7C.cjs.map → chunk-3X6PM7DU.cjs.map} +1 -1
  40. package/dist/{chunk-DEZ75ET5.js → chunk-4Q4K4WB5.js} +3 -3
  41. package/dist/{chunk-DEZ75ET5.js.map → chunk-4Q4K4WB5.js.map} +1 -1
  42. package/dist/{chunk-4X3SBHPK.js → chunk-5F5NWKPN.js} +20 -4
  43. package/dist/chunk-5F5NWKPN.js.map +1 -0
  44. package/dist/{chunk-NUEL6RCZ.cjs → chunk-5YKNEMXR.cjs} +4 -4
  45. package/dist/chunk-5YKNEMXR.cjs.map +1 -0
  46. package/dist/{chunk-3YYDHUNH.cjs → chunk-665SFFFR.cjs} +11 -11
  47. package/dist/{chunk-3YYDHUNH.cjs.map → chunk-665SFFFR.cjs.map} +1 -1
  48. package/dist/{chunk-CHVSMEKM.js → chunk-6AZDM6LK.js} +3 -3
  49. package/dist/chunk-6AZDM6LK.js.map +1 -0
  50. package/dist/{chunk-AMYY3JHQ.cjs → chunk-ADLLFY3H.cjs} +9 -9
  51. package/dist/{chunk-AMYY3JHQ.cjs.map → chunk-ADLLFY3H.cjs.map} +1 -1
  52. package/dist/{chunk-634YVZLU.js → chunk-B6HOIWQ6.js} +5 -5
  53. package/dist/{chunk-634YVZLU.js.map → chunk-B6HOIWQ6.js.map} +1 -1
  54. package/dist/{chunk-EQMLJ52C.cjs → chunk-BDTVFIPB.cjs} +26 -26
  55. package/dist/{chunk-EQMLJ52C.cjs.map → chunk-BDTVFIPB.cjs.map} +1 -1
  56. package/dist/{chunk-UJ3SXRZZ.js → chunk-BERBYXFH.js} +3 -3
  57. package/dist/{chunk-UJ3SXRZZ.js.map → chunk-BERBYXFH.js.map} +1 -1
  58. package/dist/{chunk-KZBB4YKU.js → chunk-BFYGJGC5.js} +3 -3
  59. package/dist/{chunk-KZBB4YKU.js.map → chunk-BFYGJGC5.js.map} +1 -1
  60. package/dist/{chunk-SXSPMRSV.cjs → chunk-BM6N4KHO.cjs} +21 -3
  61. package/dist/chunk-BM6N4KHO.cjs.map +1 -0
  62. package/dist/{chunk-2CFEET3Y.cjs → chunk-C4H6PB5J.cjs} +4 -4
  63. package/dist/chunk-C4H6PB5J.cjs.map +1 -0
  64. package/dist/{chunk-NFATC6ZF.cjs → chunk-C5DEO6OI.cjs} +4 -4
  65. package/dist/{chunk-NFATC6ZF.cjs.map → chunk-C5DEO6OI.cjs.map} +1 -1
  66. package/dist/{chunk-EUBGGYPH.cjs → chunk-CZ2GKKYL.cjs} +5 -5
  67. package/dist/{chunk-EUBGGYPH.cjs.map → chunk-CZ2GKKYL.cjs.map} +1 -1
  68. package/dist/{chunk-I732VEDW.js → chunk-D24WJWN2.js} +3 -3
  69. package/dist/{chunk-I732VEDW.js.map → chunk-D24WJWN2.js.map} +1 -1
  70. package/dist/{chunk-3R4ZCQAZ.cjs → chunk-D75S2X6I.cjs} +5 -5
  71. package/dist/{chunk-3R4ZCQAZ.cjs.map → chunk-D75S2X6I.cjs.map} +1 -1
  72. package/dist/{chunk-5XXUYU3M.js → chunk-DGOG5NXR.js} +3 -3
  73. package/dist/{chunk-5XXUYU3M.js.map → chunk-DGOG5NXR.js.map} +1 -1
  74. package/dist/{chunk-RTLUZKDL.cjs → chunk-E7FJOO4G.cjs} +11 -11
  75. package/dist/{chunk-RTLUZKDL.cjs.map → chunk-E7FJOO4G.cjs.map} +1 -1
  76. package/dist/{chunk-BWGXYOBZ.cjs → chunk-ELKO4ZLR.cjs} +4 -4
  77. package/dist/{chunk-BWGXYOBZ.cjs.map → chunk-ELKO4ZLR.cjs.map} +1 -1
  78. package/dist/{chunk-FBZMSLDC.cjs → chunk-EZ2YEF4F.cjs} +4 -4
  79. package/dist/{chunk-FBZMSLDC.cjs.map → chunk-EZ2YEF4F.cjs.map} +1 -1
  80. package/dist/{chunk-2VFZQEDW.cjs → chunk-F77UENR6.cjs} +343 -324
  81. package/dist/chunk-F77UENR6.cjs.map +1 -0
  82. package/dist/{chunk-OZXPA3ME.js → chunk-FFM3SJTQ.js} +4 -4
  83. package/dist/{chunk-OZXPA3ME.js.map → chunk-FFM3SJTQ.js.map} +1 -1
  84. package/dist/{chunk-PLUCC4N5.js → chunk-FIOCJYLA.js} +3 -3
  85. package/dist/{chunk-PLUCC4N5.js.map → chunk-FIOCJYLA.js.map} +1 -1
  86. package/dist/{chunk-Z5U2JGEK.cjs → chunk-FOXKFTVZ.cjs} +11 -11
  87. package/dist/{chunk-Z5U2JGEK.cjs.map → chunk-FOXKFTVZ.cjs.map} +1 -1
  88. package/dist/{chunk-K42QGAKM.cjs → chunk-FPYT5WZP.cjs} +10 -10
  89. package/dist/{chunk-K42QGAKM.cjs.map → chunk-FPYT5WZP.cjs.map} +1 -1
  90. package/dist/{chunk-NZOR3N4E.js → chunk-G3JWXOGY.js} +78 -58
  91. package/dist/chunk-G3JWXOGY.js.map +1 -0
  92. package/dist/{chunk-DHVNJYTO.js → chunk-GBBDINFK.js} +3 -3
  93. package/dist/{chunk-DHVNJYTO.js.map → chunk-GBBDINFK.js.map} +1 -1
  94. package/dist/{chunk-V45L2OYP.cjs → chunk-GD63JPGC.cjs} +9 -9
  95. package/dist/{chunk-V45L2OYP.cjs.map → chunk-GD63JPGC.cjs.map} +1 -1
  96. package/dist/{chunk-I5OTK2RP.js → chunk-GOVJO4YE.js} +5 -5
  97. package/dist/{chunk-I5OTK2RP.js.map → chunk-GOVJO4YE.js.map} +1 -1
  98. package/dist/{chunk-CZHPR2G7.cjs → chunk-HAWE74GQ.cjs} +8 -8
  99. package/dist/{chunk-CZHPR2G7.cjs.map → chunk-HAWE74GQ.cjs.map} +1 -1
  100. package/dist/{chunk-KIX45IKP.js → chunk-HE4BIAOS.js} +4 -4
  101. package/dist/{chunk-KIX45IKP.js.map → chunk-HE4BIAOS.js.map} +1 -1
  102. package/dist/{chunk-AM2CNBKE.js → chunk-HKCKOAIO.js} +2 -2
  103. package/dist/chunk-HKCKOAIO.js.map +1 -0
  104. package/dist/{chunk-2YWWPCGX.js → chunk-HSQ5Q3I5.js} +10 -12
  105. package/dist/chunk-HSQ5Q3I5.js.map +1 -0
  106. package/dist/{chunk-QLAWEGTZ.cjs → chunk-JIQRXKTA.cjs} +7 -7
  107. package/dist/chunk-JIQRXKTA.cjs.map +1 -0
  108. package/dist/{chunk-EFXJ5C7X.js → chunk-KSWHTR54.js} +5 -5
  109. package/dist/{chunk-EFXJ5C7X.js.map → chunk-KSWHTR54.js.map} +1 -1
  110. package/dist/{chunk-6PTCKXD3.js → chunk-LD4MOBFX.js} +8 -8
  111. package/dist/{chunk-6PTCKXD3.js.map → chunk-LD4MOBFX.js.map} +1 -1
  112. package/dist/{chunk-2YGIBOUH.cjs → chunk-LPVIRJVF.cjs} +9 -9
  113. package/dist/chunk-LPVIRJVF.cjs.map +1 -0
  114. package/dist/{chunk-2Y5NO2SY.js → chunk-LS3LMQ23.js} +9 -9
  115. package/dist/{chunk-2Y5NO2SY.js.map → chunk-LS3LMQ23.js.map} +1 -1
  116. package/dist/{chunk-N43XLTHZ.js → chunk-LTSTGCL6.js} +3 -3
  117. package/dist/{chunk-N43XLTHZ.js.map → chunk-LTSTGCL6.js.map} +1 -1
  118. package/dist/{chunk-W4FV7JBH.js → chunk-MSFLYJDY.js} +4 -4
  119. package/dist/chunk-MSFLYJDY.js.map +1 -0
  120. package/dist/{chunk-C4EU627W.js → chunk-N5CR3TJZ.js} +4 -4
  121. package/dist/{chunk-C4EU627W.js.map → chunk-N5CR3TJZ.js.map} +1 -1
  122. package/dist/{chunk-HTK54K2J.js → chunk-NZL4BNFQ.js} +3 -3
  123. package/dist/{chunk-HTK54K2J.js.map → chunk-NZL4BNFQ.js.map} +1 -1
  124. package/dist/{chunk-T5ZWI3MC.cjs → chunk-OUOXM36O.cjs} +11 -11
  125. package/dist/{chunk-T5ZWI3MC.cjs.map → chunk-OUOXM36O.cjs.map} +1 -1
  126. package/dist/{chunk-BXVSBYGW.js → chunk-OY7O3Q6R.js} +5 -5
  127. package/dist/{chunk-BXVSBYGW.js.map → chunk-OY7O3Q6R.js.map} +1 -1
  128. package/dist/{chunk-L7EGRCKJ.js → chunk-PBZ7HMBP.js} +4 -4
  129. package/dist/{chunk-L7EGRCKJ.js.map → chunk-PBZ7HMBP.js.map} +1 -1
  130. package/dist/{chunk-OIBHY6JQ.js → chunk-PGP7IOJ2.js} +4 -4
  131. package/dist/{chunk-OIBHY6JQ.js.map → chunk-PGP7IOJ2.js.map} +1 -1
  132. package/dist/{chunk-R7TKOQMJ.js → chunk-POWRZSK4.js} +3 -3
  133. package/dist/{chunk-R7TKOQMJ.js.map → chunk-POWRZSK4.js.map} +1 -1
  134. package/dist/{chunk-R3CKCRK3.cjs → chunk-Q4RESANI.cjs} +4 -4
  135. package/dist/{chunk-R3CKCRK3.cjs.map → chunk-Q4RESANI.cjs.map} +1 -1
  136. package/dist/{chunk-QN5N3ZVT.cjs → chunk-QBTVATSF.cjs} +25 -24
  137. package/dist/chunk-QBTVATSF.cjs.map +1 -0
  138. package/dist/{chunk-ZSJPRPN7.js → chunk-R5YZACGK.js} +3 -3
  139. package/dist/{chunk-ZSJPRPN7.js.map → chunk-R5YZACGK.js.map} +1 -1
  140. package/dist/{chunk-ADVZIC43.cjs → chunk-RAHSWE5C.cjs} +10 -10
  141. package/dist/{chunk-ADVZIC43.cjs.map → chunk-RAHSWE5C.cjs.map} +1 -1
  142. package/dist/{chunk-NOS7PTKP.js → chunk-SEL2ZQMT.js} +7 -7
  143. package/dist/{chunk-NOS7PTKP.js.map → chunk-SEL2ZQMT.js.map} +1 -1
  144. package/dist/{chunk-XWCTBGYK.js → chunk-SK4QLUBK.js} +3 -3
  145. package/dist/chunk-SK4QLUBK.js.map +1 -0
  146. package/dist/{chunk-XRI6DXPZ.js → chunk-SSDD676S.js} +3 -3
  147. package/dist/chunk-SSDD676S.js.map +1 -0
  148. package/dist/{chunk-E3OCRJU6.cjs → chunk-SVO5WPZX.cjs} +11 -11
  149. package/dist/{chunk-E3OCRJU6.cjs.map → chunk-SVO5WPZX.cjs.map} +1 -1
  150. package/dist/{chunk-44IISBRZ.cjs → chunk-SZHU7A5B.cjs} +4 -4
  151. package/dist/{chunk-44IISBRZ.cjs.map → chunk-SZHU7A5B.cjs.map} +1 -1
  152. package/dist/{chunk-TR4V2LHV.cjs → chunk-TTJ54H3E.cjs} +5 -5
  153. package/dist/{chunk-TR4V2LHV.cjs.map → chunk-TTJ54H3E.cjs.map} +1 -1
  154. package/dist/{chunk-HI4ZW62S.js → chunk-U4UJHOSH.js} +4 -4
  155. package/dist/chunk-U4UJHOSH.js.map +1 -0
  156. package/dist/{chunk-XGYI2KQH.js → chunk-UFIXH26P.js} +4 -4
  157. package/dist/{chunk-XGYI2KQH.js.map → chunk-UFIXH26P.js.map} +1 -1
  158. package/dist/{chunk-JJZ4NIAG.js → chunk-V26QVGUG.js} +7 -6
  159. package/dist/chunk-V26QVGUG.js.map +1 -0
  160. package/dist/{chunk-3SHW7XKK.js → chunk-V6HWJQQV.js} +6 -5
  161. package/dist/chunk-V6HWJQQV.js.map +1 -0
  162. package/dist/{chunk-MV4TOCUK.js → chunk-VC5ABJXC.js} +3 -3
  163. package/dist/{chunk-MV4TOCUK.js.map → chunk-VC5ABJXC.js.map} +1 -1
  164. package/dist/{chunk-33L2NVTJ.cjs → chunk-VF7X6HDR.cjs} +8 -8
  165. package/dist/chunk-VF7X6HDR.cjs.map +1 -0
  166. package/dist/{chunk-7HQVDLFI.cjs → chunk-VGPXHHF3.cjs} +11 -9
  167. package/dist/chunk-VGPXHHF3.cjs.map +1 -0
  168. package/dist/{chunk-BC5EUG7R.cjs → chunk-VUKQL3L2.cjs} +18 -20
  169. package/dist/chunk-VUKQL3L2.cjs.map +1 -0
  170. package/dist/{chunk-PVBANCWU.cjs → chunk-VW6M4B3Z.cjs} +5 -5
  171. package/dist/{chunk-PVBANCWU.cjs.map → chunk-VW6M4B3Z.cjs.map} +1 -1
  172. package/dist/{chunk-KNZU4YO5.cjs → chunk-WQ53UQL4.cjs} +4 -4
  173. package/dist/{chunk-KNZU4YO5.cjs.map → chunk-WQ53UQL4.cjs.map} +1 -1
  174. package/dist/{chunk-6DCTL32L.cjs → chunk-XPR366DL.cjs} +17 -17
  175. package/dist/{chunk-6DCTL32L.cjs.map → chunk-XPR366DL.cjs.map} +1 -1
  176. package/dist/{chunk-7TB5U7RK.js → chunk-XROIW6BH.js} +6 -6
  177. package/dist/{chunk-7TB5U7RK.js.map → chunk-XROIW6BH.js.map} +1 -1
  178. package/dist/{chunk-ECPL5RV6.cjs → chunk-Y4AXQOT4.cjs} +8 -8
  179. package/dist/{chunk-ECPL5RV6.cjs.map → chunk-Y4AXQOT4.cjs.map} +1 -1
  180. package/dist/{chunk-E6T264NS.js → chunk-Y4I4EQFK.js} +3 -3
  181. package/dist/{chunk-E6T264NS.js.map → chunk-Y4I4EQFK.js.map} +1 -1
  182. package/dist/{chunk-FUASIT3E.cjs → chunk-ZN2DGEY3.cjs} +15 -15
  183. package/dist/{chunk-FUASIT3E.cjs.map → chunk-ZN2DGEY3.cjs.map} +1 -1
  184. package/dist/{chunk-YTO5BRBD.cjs → chunk-ZT57WTUJ.cjs} +8 -8
  185. package/dist/chunk-ZT57WTUJ.cjs.map +1 -0
  186. package/dist/compact-session-4MNKNMO2.js +21 -0
  187. package/dist/{compact-session-EJ36VH5I.js.map → compact-session-4MNKNMO2.js.map} +1 -1
  188. package/dist/compact-session-EPFBQ46Z.cjs +58 -0
  189. package/dist/{compact-session-6GKIS4SD.cjs.map → compact-session-EPFBQ46Z.cjs.map} +1 -1
  190. package/dist/compaction.cjs +16 -16
  191. package/dist/compaction.js +3 -3
  192. package/dist/concurrency.cjs +6 -6
  193. package/dist/concurrency.js +4 -4
  194. package/dist/context-7USNN6LP.cjs +22 -0
  195. package/dist/{context-4HGPCOH6.cjs.map → context-7USNN6LP.cjs.map} +1 -1
  196. package/dist/context-ENXZNF6J.js +5 -0
  197. package/dist/{context-DCECDKWN.js.map → context-ENXZNF6J.js.map} +1 -1
  198. package/dist/{cron-DyWQsEG6.d.ts → cron-BMjgFH-_.d.ts} +1 -1
  199. package/dist/{cron-DgHJnMAK.d.cts → cron-DFDdCdMM.d.cts} +1 -1
  200. package/dist/cron.cjs +33 -33
  201. package/dist/cron.d.cts +2 -2
  202. package/dist/cron.d.ts +2 -2
  203. package/dist/cron.js +32 -32
  204. package/dist/errors.cjs +21 -21
  205. package/dist/errors.js +2 -2
  206. package/dist/eval.cjs +43 -43
  207. package/dist/eval.js +33 -33
  208. package/dist/{executor-4SW7QGZ4.cjs → executor-2Z3LGG3I.cjs} +24 -24
  209. package/dist/{executor-4SW7QGZ4.cjs.map → executor-2Z3LGG3I.cjs.map} +1 -1
  210. package/dist/{executor-3CGPLVJL.js → executor-2Z6XJJJG.js} +7 -7
  211. package/dist/{executor-3CGPLVJL.js.map → executor-2Z6XJJJG.js.map} +1 -1
  212. package/dist/filesystem/index.cjs +7 -7
  213. package/dist/filesystem/index.js +3 -3
  214. package/dist/fs-session-store-R5FD3EN6.js +10 -0
  215. package/dist/{fs-session-store-LIKYT24K.js.map → fs-session-store-R5FD3EN6.js.map} +1 -1
  216. package/dist/fs-session-store-VVY2AHWG.cjs +19 -0
  217. package/dist/{fs-session-store-VXXDIBGM.cjs.map → fs-session-store-VVY2AHWG.cjs.map} +1 -1
  218. package/dist/generate-object-AVXOJK2I.cjs +20 -0
  219. package/dist/{generate-object-E465DX5I.cjs.map → generate-object-AVXOJK2I.cjs.map} +1 -1
  220. package/dist/generate-object-WQRCXFD2.js +7 -0
  221. package/dist/{generate-object-TVQCW2IZ.js.map → generate-object-WQRCXFD2.js.map} +1 -1
  222. package/dist/index-manager-VRU3A2YL.cjs +21 -0
  223. package/dist/{index-manager-HGGL4DD5.cjs.map → index-manager-VRU3A2YL.cjs.map} +1 -1
  224. package/dist/index-manager-WS7TVKUW.js +12 -0
  225. package/dist/{index-manager-EA6FGIQG.js.map → index-manager-WS7TVKUW.js.map} +1 -1
  226. package/dist/index.cjs +143 -135
  227. package/dist/index.d.cts +64 -8
  228. package/dist/index.d.ts +64 -8
  229. package/dist/index.js +45 -45
  230. package/dist/{inject-session-67SF65FC.cjs → inject-session-AUQLBDRS.cjs} +9 -9
  231. package/dist/{inject-session-67SF65FC.cjs.map → inject-session-AUQLBDRS.cjs.map} +1 -1
  232. package/dist/{inject-session-HO7FYVCX.js → inject-session-GMBMJVNZ.js} +7 -7
  233. package/dist/{inject-session-HO7FYVCX.js.map → inject-session-GMBMJVNZ.js.map} +1 -1
  234. package/dist/internal/auth/auth-types.d.ts +1 -1
  235. package/dist/internal/auth/oauth-device.d.ts +2 -2
  236. package/dist/internal/local-agent/mcp-pool.d.ts +2 -4
  237. package/dist/internal/mcp/oauth.d.ts +14 -1
  238. package/dist/internal/mcp/token-storage.d.ts +18 -1
  239. package/dist/internal/memory/adapters/index.cjs +8 -8
  240. package/dist/internal/memory/adapters/index.js +7 -7
  241. package/dist/internal/persistence/atomic-write.d.cts +93 -0
  242. package/dist/internal/persistence/atomic-write.d.ts +93 -0
  243. package/dist/internal/persistence/file-lock.d.cts +13 -1
  244. package/dist/internal/persistence/file-lock.d.ts +13 -1
  245. package/dist/internal/persistence/fts5-sanitize.d.cts +9 -1
  246. package/dist/internal/persistence/fts5-sanitize.d.ts +9 -1
  247. package/dist/internal/persistence/index.cjs +25 -25
  248. package/dist/internal/persistence/index.js +6 -6
  249. package/dist/internal/persistence/session-dir.d.cts +1 -0
  250. package/dist/internal/persistence/session-dir.d.ts +1 -0
  251. package/dist/internal/persistence/session-writer.d.cts +13 -0
  252. package/dist/internal/persistence/session-writer.d.ts +13 -0
  253. package/dist/internal/persistence/sqlite-wal.d.cts +9 -1
  254. package/dist/internal/persistence/sqlite-wal.d.ts +9 -1
  255. package/dist/internal/providers/builtin/cerebras.d.ts +2 -2
  256. package/dist/internal/providers/builtin/deepinfra.d.ts +1 -1
  257. package/dist/internal/providers/catalog-source-models-dev.d.ts +1 -1
  258. package/dist/internal/runtime/lifecycle/env-policy.d.ts +1 -3
  259. package/dist/internal/security/index.cjs +13 -13
  260. package/dist/internal/security/index.js +3 -3
  261. package/dist/internal/security/path-guard.d.cts +61 -0
  262. package/dist/internal/security/path-guard.d.ts +61 -0
  263. package/dist/internal/session/compact-session.d.ts +2 -2
  264. package/dist/internal/telemetry/tracer.d.ts +24 -0
  265. package/dist/judge-call-G5SGV2M2.cjs +22 -0
  266. package/dist/{judge-call-GVIJWQVE.cjs.map → judge-call-G5SGV2M2.cjs.map} +1 -1
  267. package/dist/judge-call-MDIPTBJI.js +5 -0
  268. package/dist/{judge-call-QMKGC2ZK.js.map → judge-call-MDIPTBJI.js.map} +1 -1
  269. package/dist/mcp-auth.cjs +14 -14
  270. package/dist/mcp-auth.cjs.map +1 -1
  271. package/dist/mcp-auth.js +4 -4
  272. package/dist/mcp-auth.js.map +1 -1
  273. package/dist/models.cjs +22 -22
  274. package/dist/models.cjs.map +1 -1
  275. package/dist/models.js +7 -7
  276. package/dist/models.js.map +1 -1
  277. package/dist/path-safety.cjs +10 -10
  278. package/dist/path-safety.js +3 -3
  279. package/dist/persistence.cjs +28 -28
  280. package/dist/persistence.js +8 -8
  281. package/dist/project.cjs +5 -5
  282. package/dist/project.js +3 -3
  283. package/dist/registry-63ASMXT4.cjs +46 -0
  284. package/dist/{registry-UBY26R4I.cjs.map → registry-63ASMXT4.cjs.map} +1 -1
  285. package/dist/registry-O7FJ2MPM.js +9 -0
  286. package/dist/{registry-GC7BQMSV.js.map → registry-O7FJ2MPM.js.map} +1 -1
  287. package/dist/retry.cjs +4 -4
  288. package/dist/retry.js +3 -3
  289. package/dist/sandbox/bwrap.d.cts +1 -2
  290. package/dist/sandbox/bwrap.d.ts +1 -2
  291. package/dist/sandbox/index.cjs +20 -20
  292. package/dist/sandbox/index.cjs.map +1 -1
  293. package/dist/sandbox/index.js +5 -5
  294. package/dist/sandbox/index.js.map +1 -1
  295. package/dist/server/errors-envelope.cjs +14 -14
  296. package/dist/server/errors-envelope.js +3 -3
  297. package/dist/session-transcript-DOM3UTXR.cjs +50 -0
  298. package/dist/{session-transcript-TNAJ4O3M.cjs.map → session-transcript-DOM3UTXR.cjs.map} +1 -1
  299. package/dist/session-transcript-RAKEK76V.js +5 -0
  300. package/dist/{session-transcript-SKIRBEJE.js.map → session-transcript-RAKEK76V.js.map} +1 -1
  301. package/dist/skills.cjs +7 -7
  302. package/dist/skills.js +5 -5
  303. package/dist/stream-object-L3Q7PBMF.cjs +20 -0
  304. package/dist/{stream-object-T4DAMNVB.cjs.map → stream-object-L3Q7PBMF.cjs.map} +1 -1
  305. package/dist/stream-object-ZYXWS4B2.js +7 -0
  306. package/dist/{stream-object-QMNR3YFF.js.map → stream-object-ZYXWS4B2.js.map} +1 -1
  307. package/dist/subagents-loader-I56GSKAQ.js +6 -0
  308. package/dist/{subagents-loader-WN2SB6PV.js.map → subagents-loader-I56GSKAQ.js.map} +1 -1
  309. package/dist/subagents-loader-VRW73QQT.cjs +15 -0
  310. package/dist/{subagents-loader-3CXBQWWY.cjs.map → subagents-loader-VRW73QQT.cjs.map} +1 -1
  311. package/dist/subagents-loader.cjs +6 -6
  312. package/dist/subagents-loader.d.cts +1 -1
  313. package/dist/subagents-loader.d.ts +1 -1
  314. package/dist/subagents-loader.js +4 -4
  315. package/dist/subscription/index.cjs +3 -3
  316. package/dist/subscription/index.js +2 -2
  317. package/dist/task-store.cjs +7 -7
  318. package/dist/task-store.js +4 -4
  319. package/dist/types/agent.d.ts +19 -6
  320. package/dist/types/provider-profile.d.ts +2 -2
  321. package/dist/workflow.cjs +27 -27
  322. package/dist/workflow.js +6 -6
  323. package/package.json +18 -18
  324. package/dist/agent-JX5SBYDE.cjs +0 -59
  325. package/dist/agent-ZAGG6ZBS.js +0 -50
  326. package/dist/agent-session-store-EPX2WQI4.cjs +0 -23
  327. package/dist/agent-session-store-JMU7ASDB.js +0 -6
  328. package/dist/chunk-23VZBRDQ.js.map +0 -1
  329. package/dist/chunk-2CFEET3Y.cjs.map +0 -1
  330. package/dist/chunk-2VFZQEDW.cjs.map +0 -1
  331. package/dist/chunk-2YGIBOUH.cjs.map +0 -1
  332. package/dist/chunk-2YWWPCGX.js.map +0 -1
  333. package/dist/chunk-33L2NVTJ.cjs.map +0 -1
  334. package/dist/chunk-3SHW7XKK.js.map +0 -1
  335. package/dist/chunk-4X3SBHPK.js.map +0 -1
  336. package/dist/chunk-7HQVDLFI.cjs.map +0 -1
  337. package/dist/chunk-AM2CNBKE.js.map +0 -1
  338. package/dist/chunk-BBZYXVLZ.cjs.map +0 -1
  339. package/dist/chunk-BC5EUG7R.cjs.map +0 -1
  340. package/dist/chunk-CHVSMEKM.js.map +0 -1
  341. package/dist/chunk-HI4ZW62S.js.map +0 -1
  342. package/dist/chunk-JJZ4NIAG.js.map +0 -1
  343. package/dist/chunk-NUEL6RCZ.cjs.map +0 -1
  344. package/dist/chunk-NZOR3N4E.js.map +0 -1
  345. package/dist/chunk-QLAWEGTZ.cjs.map +0 -1
  346. package/dist/chunk-QN5N3ZVT.cjs.map +0 -1
  347. package/dist/chunk-SXSPMRSV.cjs.map +0 -1
  348. package/dist/chunk-W4FV7JBH.js.map +0 -1
  349. package/dist/chunk-XRI6DXPZ.js.map +0 -1
  350. package/dist/chunk-XWCTBGYK.js.map +0 -1
  351. package/dist/chunk-YTO5BRBD.cjs.map +0 -1
  352. package/dist/compact-session-6GKIS4SD.cjs +0 -58
  353. package/dist/compact-session-EJ36VH5I.js +0 -21
  354. package/dist/context-4HGPCOH6.cjs +0 -22
  355. package/dist/context-DCECDKWN.js +0 -5
  356. package/dist/fs-session-store-LIKYT24K.js +0 -10
  357. package/dist/fs-session-store-VXXDIBGM.cjs +0 -19
  358. package/dist/generate-object-E465DX5I.cjs +0 -20
  359. package/dist/generate-object-TVQCW2IZ.js +0 -7
  360. package/dist/index-manager-EA6FGIQG.js +0 -12
  361. package/dist/index-manager-HGGL4DD5.cjs +0 -21
  362. package/dist/judge-call-GVIJWQVE.cjs +0 -22
  363. package/dist/judge-call-QMKGC2ZK.js +0 -5
  364. package/dist/registry-GC7BQMSV.js +0 -9
  365. package/dist/registry-UBY26R4I.cjs +0 -46
  366. package/dist/session-transcript-SKIRBEJE.js +0 -5
  367. package/dist/session-transcript-TNAJ4O3M.cjs +0 -50
  368. package/dist/stream-object-QMNR3YFF.js +0 -7
  369. package/dist/stream-object-T4DAMNVB.cjs +0 -20
  370. package/dist/subagents-loader-3CXBQWWY.cjs +0 -15
  371. package/dist/subagents-loader-WN2SB6PV.js +0 -6
  372. package/docs/error-codes.md +0 -179
  373. package/docs/harness-capability-map.md +0 -304
@@ -18,6 +18,15 @@
18
18
  * @internal
19
19
  */
20
20
  import { ConfigurationError } from "../../errors.js";
21
+ /**
22
+ * Thrown when a path operation would escape its allowed base directory.
23
+ * Extends `ConfigurationError` (no new error hierarchy per ADR D65).
24
+ *
25
+ */
26
+ export declare class PathTraversalError extends ConfigurationError {
27
+ readonly name: string;
28
+ constructor(input: string, resolvedPath: string);
29
+ }
21
30
  /**
22
31
  * Thrown when an agent tool is asked to read or write a sensitive path
23
32
  * that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,
@@ -32,6 +41,33 @@ export declare class ForbiddenPathError extends ConfigurationError {
32
41
  readonly name: string;
33
42
  constructor(path: string);
34
43
  }
44
+ /**
45
+ * Join `base` with `...parts` and ensure the resolved absolute path stays
46
+ * under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents
47
+ * normalized-escape bypasses like `subdir/.\\./bar`.
48
+ *
49
+ * Returns the safe absolute path. Throws `PathTraversalError` if escape.
50
+ *
51
+ */
52
+ export declare function safePathJoin(base: string, ...parts: string[]): string;
53
+ /**
54
+ * Assert that `path` — including every directory component in the chain —
55
+ * stays under `base` after symlink resolution. No-op when nothing on the
56
+ * path exists yet.
57
+ *
58
+ * Two-bug history:
59
+ * 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C
60
+ * must be resolved end-to-end. `realpathSync` does this in 1 syscall.
61
+ * 2. **Defence-in-depth** (added v1.x): the previous implementation only
62
+ * called `lstatSync(path)` on the terminal component. If an INTERMEDIATE
63
+ * directory was a symlink (`base/inner-symlink → /outside`), `lstat` on
64
+ * `base/inner-symlink/file.txt` followed the symlink and reported the
65
+ * regular file — escape went undetected. Fix: walk up to the nearest
66
+ * existing ancestor and `realpath` THAT, then re-attach the suffix and
67
+ * check the result against the canonical base.
68
+ *
69
+ */
70
+ export declare function assertNoSymlinkEscape(path: string, base: string): void;
35
71
  /**
36
72
  * Decide whether a project-relative path points to a known-sensitive file
37
73
  * that a coding agent must not read or write.
@@ -57,3 +93,28 @@ export declare function isForbiddenPath(input: string): boolean;
57
93
  export declare function sanitizeIdentifier(input: string, options?: {
58
94
  maxLen?: number;
59
95
  }): string;
96
+ /**
97
+ * Convert ANY opaque id (agent id, run id, conversation id, namespace, email,
98
+ * arbitrary string) into a deterministic, filesystem-safe filename component.
99
+ *
100
+ * Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),
101
+ * this is a total function: it NEVER throws on a non-empty string. It returns
102
+ * the lowercased id verbatim when it already matches the safe grammar
103
+ * `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay
104
+ * human-readable), otherwise a deterministic `h-<16 hex>` sha256 token
105
+ * (collision-resistant and always a valid filename). The output charset is
106
+ * always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.
107
+ *
108
+ * @param id - any opaque identifier (must be a non-empty string)
109
+ * @param options.maxLen - max length for the passthrough branch (default 128).
110
+ * Ids longer than this are hashed; the hash token itself is always short.
111
+ * @throws ConfigurationError (code `invalid_filename_id`) only on empty input.
112
+ *
113
+ * @example
114
+ * safeFilenameForId("550e8400-e29b-41d4-a716-446655440000") // passthrough
115
+ * safeFilenameForId("user@example.com") // "h-<16hex>"
116
+ *
117
+ */
118
+ export declare function safeFilenameForId(id: string, options?: {
119
+ maxLen?: number;
120
+ }): string;
@@ -18,6 +18,15 @@
18
18
  * @internal
19
19
  */
20
20
  import { ConfigurationError } from "../../errors.js";
21
+ /**
22
+ * Thrown when a path operation would escape its allowed base directory.
23
+ * Extends `ConfigurationError` (no new error hierarchy per ADR D65).
24
+ *
25
+ */
26
+ export declare class PathTraversalError extends ConfigurationError {
27
+ readonly name: string;
28
+ constructor(input: string, resolvedPath: string);
29
+ }
21
30
  /**
22
31
  * Thrown when an agent tool is asked to read or write a sensitive path
23
32
  * that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,
@@ -32,6 +41,33 @@ export declare class ForbiddenPathError extends ConfigurationError {
32
41
  readonly name: string;
33
42
  constructor(path: string);
34
43
  }
44
+ /**
45
+ * Join `base` with `...parts` and ensure the resolved absolute path stays
46
+ * under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents
47
+ * normalized-escape bypasses like `subdir/.\\./bar`.
48
+ *
49
+ * Returns the safe absolute path. Throws `PathTraversalError` if escape.
50
+ *
51
+ */
52
+ export declare function safePathJoin(base: string, ...parts: string[]): string;
53
+ /**
54
+ * Assert that `path` — including every directory component in the chain —
55
+ * stays under `base` after symlink resolution. No-op when nothing on the
56
+ * path exists yet.
57
+ *
58
+ * Two-bug history:
59
+ * 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C
60
+ * must be resolved end-to-end. `realpathSync` does this in 1 syscall.
61
+ * 2. **Defence-in-depth** (added v1.x): the previous implementation only
62
+ * called `lstatSync(path)` on the terminal component. If an INTERMEDIATE
63
+ * directory was a symlink (`base/inner-symlink → /outside`), `lstat` on
64
+ * `base/inner-symlink/file.txt` followed the symlink and reported the
65
+ * regular file — escape went undetected. Fix: walk up to the nearest
66
+ * existing ancestor and `realpath` THAT, then re-attach the suffix and
67
+ * check the result against the canonical base.
68
+ *
69
+ */
70
+ export declare function assertNoSymlinkEscape(path: string, base: string): void;
35
71
  /**
36
72
  * Decide whether a project-relative path points to a known-sensitive file
37
73
  * that a coding agent must not read or write.
@@ -57,3 +93,28 @@ export declare function isForbiddenPath(input: string): boolean;
57
93
  export declare function sanitizeIdentifier(input: string, options?: {
58
94
  maxLen?: number;
59
95
  }): string;
96
+ /**
97
+ * Convert ANY opaque id (agent id, run id, conversation id, namespace, email,
98
+ * arbitrary string) into a deterministic, filesystem-safe filename component.
99
+ *
100
+ * Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),
101
+ * this is a total function: it NEVER throws on a non-empty string. It returns
102
+ * the lowercased id verbatim when it already matches the safe grammar
103
+ * `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay
104
+ * human-readable), otherwise a deterministic `h-<16 hex>` sha256 token
105
+ * (collision-resistant and always a valid filename). The output charset is
106
+ * always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.
107
+ *
108
+ * @param id - any opaque identifier (must be a non-empty string)
109
+ * @param options.maxLen - max length for the passthrough branch (default 128).
110
+ * Ids longer than this are hashed; the hash token itself is always short.
111
+ * @throws ConfigurationError (code `invalid_filename_id`) only on empty input.
112
+ *
113
+ * @example
114
+ * safeFilenameForId("550e8400-e29b-41d4-a716-446655440000") // passthrough
115
+ * safeFilenameForId("user@example.com") // "h-<16hex>"
116
+ *
117
+ */
118
+ export declare function safeFilenameForId(id: string, options?: {
119
+ maxLen?: number;
120
+ }): string;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * M50 (agent-builder) — session-transcript compaction, Codex-faithful.
2
+ * M50 (agent-builder) — session-transcript compaction.
3
3
  *
4
- * Mirrors the vendored Codex mechanism (`codex-rs/core/src/compact.rs`):
4
+ * The mechanism:
5
5
  * - the replacement history = recent USER messages verbatim (newest→oldest under a token budget;
6
6
  * prior summaries filtered by marker) + ONE summary message with a textual marker prefix,
7
7
  * injected as `role:"user"` (`build_compacted_history`, compact.rs:589-663);
@@ -14,6 +14,30 @@ export interface OTelSpan {
14
14
  };
15
15
  isRecording(): boolean;
16
16
  }
17
+ /**
18
+ * Telemetry handle returned by {@link createTelemetry}. Callers use this to
19
+ * start spans, add events, etc. When telemetry is disabled OR OTel is not
20
+ * installed, every method is a safe no-op.
21
+ *
22
+ */
23
+ export interface TelemetryHandle {
24
+ readonly enabled: boolean;
25
+ readonly includeContent: boolean;
26
+ /** Start a span. Returns a no-op span if telemetry is disabled. */
27
+ startSpan(name: string, attrs?: Record<string, string | number | boolean>): OTelSpan;
28
+ /** Start a child span nested under `parent` (M3 #64). `undefined` parent →
29
+ * a root span (parentless callers, or telemetry with no run span). */
30
+ startChildSpan(parent: OTelSpan | undefined, name: string, attrs?: Record<string, string | number | boolean>): OTelSpan;
31
+ /**
32
+ * Record a histogram observation. T0.1 ships
33
+ * `theokit_memory_recall_duration_ms`; downstream tasks add more.
34
+ * No-op when telemetry is disabled or when `@opentelemetry/api`'s metrics
35
+ * namespace is unavailable. ADR D34 — exporter errors are swallowed by `safe()`.
36
+ */
37
+ recordHistogram(name: string, valueMs: number, attrs?: Record<string, string | number | boolean>): void;
38
+ /** End all open spans on this handle (best-effort, used by Agent.dispose). */
39
+ endAll(): void;
40
+ }
17
41
  export declare const NOOP_SPAN: OTelSpan;
18
42
  /** Exported for tests — resets the OTel cache and the warning-once latch. */
19
43
  export declare function _resetTelemetryCacheForTests(): void;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ var chunkELKO4ZLR_cjs = require('./chunk-ELKO4ZLR.cjs');
4
+ require('./chunk-LPVIRJVF.cjs');
5
+ require('./chunk-BM6N4KHO.cjs');
6
+
7
+
8
+
9
+ Object.defineProperty(exports, "JudgeCredentialError", {
10
+ enumerable: true,
11
+ get: function () { return chunkELKO4ZLR_cjs.JudgeCredentialError; }
12
+ });
13
+ Object.defineProperty(exports, "composeJudgePrompt", {
14
+ enumerable: true,
15
+ get: function () { return chunkELKO4ZLR_cjs.composeJudgePrompt; }
16
+ });
17
+ Object.defineProperty(exports, "judgeCallImpl", {
18
+ enumerable: true,
19
+ get: function () { return chunkELKO4ZLR_cjs.judgeCallImpl; }
20
+ });
21
+ //# sourceMappingURL=judge-call-G5SGV2M2.cjs.map
22
+ //# sourceMappingURL=judge-call-G5SGV2M2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-GVIJWQVE.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-G5SGV2M2.cjs"}
@@ -0,0 +1,5 @@
1
+ export { JudgeCredentialError, composeJudgePrompt, judgeCallImpl } from './chunk-LTSTGCL6.js';
2
+ import './chunk-U4UJHOSH.js';
3
+ import './chunk-5F5NWKPN.js';
4
+ //# sourceMappingURL=judge-call-MDIPTBJI.js.map
5
+ //# sourceMappingURL=judge-call-MDIPTBJI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-QMKGC2ZK.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-MDIPTBJI.js"}
package/dist/mcp-auth.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunk2CFEET3Y_cjs = require('./chunk-2CFEET3Y.cjs');
4
- var chunkNUEL6RCZ_cjs = require('./chunk-NUEL6RCZ.cjs');
5
- var chunk2YGIBOUH_cjs = require('./chunk-2YGIBOUH.cjs');
6
- var chunkSXSPMRSV_cjs = require('./chunk-SXSPMRSV.cjs');
3
+ var chunkC4H6PB5J_cjs = require('./chunk-C4H6PB5J.cjs');
4
+ var chunk5YKNEMXR_cjs = require('./chunk-5YKNEMXR.cjs');
5
+ var chunkLPVIRJVF_cjs = require('./chunk-LPVIRJVF.cjs');
6
+ var chunkBM6N4KHO_cjs = require('./chunk-BM6N4KHO.cjs');
7
7
  var crypto = require('crypto');
8
8
  var http = require('http');
9
9
  var promises = require('readline/promises');
@@ -32,7 +32,7 @@ function tryRequireKeytar() {
32
32
  cachedKeytar = r("keytar");
33
33
  } catch {
34
34
  if (!keytarWarned) {
35
- chunkSXSPMRSV_cjs.diag(
35
+ chunkBM6N4KHO_cjs.diag(
36
36
  "[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\n"
37
37
  );
38
38
  keytarWarned = true;
@@ -59,7 +59,7 @@ async function setTokens(serverName, tokens) {
59
59
  }
60
60
  }
61
61
  allTokens[serverName] = tokens;
62
- await chunk2CFEET3Y_cjs.atomicWriteJson(FILE_PATH, allTokens);
62
+ await chunkC4H6PB5J_cjs.atomicWriteJson(FILE_PATH, allTokens);
63
63
  try {
64
64
  fs.chmodSync(FILE_PATH, 384);
65
65
  } catch {
@@ -77,7 +77,7 @@ async function getTokens(serverName) {
77
77
  }
78
78
  }
79
79
  if (!fs.existsSync(FILE_PATH)) return void 0;
80
- chunkNUEL6RCZ_cjs.assertSecureModes(path.dirname(FILE_PATH), FILE_PATH);
80
+ chunk5YKNEMXR_cjs.assertSecureModes(path.dirname(FILE_PATH), FILE_PATH);
81
81
  try {
82
82
  const all = JSON.parse(fs.readFileSync(FILE_PATH, "utf8"));
83
83
  return all[serverName];
@@ -118,7 +118,7 @@ async function refreshAccessToken(serverName, clientId, refreshToken, oauth) {
118
118
  body: body.toString()
119
119
  });
120
120
  if (!response.ok) {
121
- throw new chunk2YGIBOUH_cjs.ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {
121
+ throw new chunkLPVIRJVF_cjs.ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {
122
122
  code: "oauth_refresh_failed"
123
123
  });
124
124
  }
@@ -164,7 +164,7 @@ async function runPkceFlow(serverName, clientId, scopes, oauth) {
164
164
  authUrl.searchParams.set("code_challenge", challenge);
165
165
  authUrl.searchParams.set("code_challenge_method", "S256");
166
166
  authUrl.searchParams.set("state", state);
167
- chunkSXSPMRSV_cjs.diag(
167
+ chunkBM6N4KHO_cjs.diag(
168
168
  `
169
169
  [theokit-sdk] OAuth required for MCP server "${serverName}".
170
170
  Open this URL to authenticate:
@@ -174,7 +174,7 @@ Open this URL to authenticate:
174
174
  );
175
175
  const { code, state: returnedState } = await codeWaiter;
176
176
  if (returnedState !== state) {
177
- throw new chunk2YGIBOUH_cjs.ConfigurationError(
177
+ throw new chunkLPVIRJVF_cjs.ConfigurationError(
178
178
  "OAuth state mismatch \u2014 possible CSRF attempt; aborting token exchange.",
179
179
  { code: "oauth_state_mismatch" }
180
180
  );
@@ -193,7 +193,7 @@ Open this URL to authenticate:
193
193
  });
194
194
  if (!response.ok) {
195
195
  const errText = await response.text();
196
- throw new chunk2YGIBOUH_cjs.ConfigurationError(
196
+ throw new chunkLPVIRJVF_cjs.ConfigurationError(
197
197
  `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,
198
198
  { code: "oauth_token_exchange_failed" }
199
199
  );
@@ -239,7 +239,7 @@ async function openLocalhostCallback(expectedState, timeoutMs) {
239
239
  });
240
240
  server.listen(0, "127.0.0.1");
241
241
  const timeoutHandle = setTimeout(() => {
242
- rejectCode?.(new chunk2YGIBOUH_cjs.ConfigurationError("OAuth flow timed out", { code: "oauth_timeout" }));
242
+ rejectCode?.(new chunkLPVIRJVF_cjs.ConfigurationError("OAuth flow timed out", { code: "oauth_timeout" }));
243
243
  }, timeoutMs);
244
244
  await new Promise((r) => {
245
245
  if (server.listening) r();
@@ -248,7 +248,7 @@ async function openLocalhostCallback(expectedState, timeoutMs) {
248
248
  const address = server.address();
249
249
  if (typeof address !== "object" || address === null) {
250
250
  server.close();
251
- throw new chunk2YGIBOUH_cjs.ConfigurationError("OAuth localhost callback failed to bind", {
251
+ throw new chunkLPVIRJVF_cjs.ConfigurationError("OAuth localhost callback failed to bind", {
252
252
  code: "oauth_bind_failed"
253
253
  });
254
254
  }
@@ -284,7 +284,7 @@ async function readCodeFromStdin(expectedState, timeoutMs) {
284
284
  }).catch(reject);
285
285
  setTimeout(() => {
286
286
  rl.close();
287
- reject(new chunk2YGIBOUH_cjs.ConfigurationError("OAuth manual input timed out", { code: "oauth_timeout" }));
287
+ reject(new chunkLPVIRJVF_cjs.ConfigurationError("OAuth manual input timed out", { code: "oauth_timeout" }));
288
288
  }, timeoutMs);
289
289
  });
290
290
  return promise;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":["join","homedir","dirname","mkdirSync","chmodSync","createRequire","diag","existsSync","readFileSync","atomicWriteJson","assertSecureModes","randomBytes","createHash","ConfigurationError","createServer","createInterface"],"mappings":";;;;;;;;;;;;;;;AAqBA,IAAM,cAAA,GAAiB,aAAA;AACvB,IAAM,SAAA,GAAYA,SAAA,CAAKC,UAAA,EAAQ,EAAG,YAAY,iBAAiB,CAAA;AAgB/D,SAAS,qBAAA,GAA8B;AACrC,EAAA,MAAM,GAAA,GAAMC,aAAQ,SAAS,CAAA;AAC7B,EAAA,IAAI;AACF,IAAAC,YAAA,CAAU,KAAK,EAAE,SAAA,EAAW,IAAA,EAAM,IAAA,EAAM,KAAO,CAAA;AAC/C,IAAAC,YAAA,CAAU,KAAK,GAAK,CAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AAAA,EAGR;AACF;AAQA,IAAI,YAAA,GAAe,KAAA;AACnB,IAAI,YAAA;AACJ,SAAS,gBAAA,GAAsC;AAC7C,EAAA,IAAI,YAAA,KAAiB,QAAW,OAAO,YAAA;AACvC,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAIC,sBAAA,CAAc,8PAAe,CAAA;AACvC,IAAA,YAAA,GAAe,EAAE,QAAQ,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAAC,sBAAA;AAAA,QACE;AAAA,OACF;AACA,MAAA,YAAA,GAAe,IAAA;AAAA,IACjB;AACA,IAAA,YAAA,GAAe,IAAA;AAAA,EACjB;AACA,EAAA,OAAO,YAAA;AACT;AAIA,IAAM,eAAA,uBAAsB,GAAA,EAAkC;AAQ9D,eAAsB,SAAA,CAAU,YAAoB,MAAA,EAAoC;AACtF,EAAA,MAAM,KAAK,gBAAA,EAAiB;AAC5B,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AACrC,EAAA,IAAI,EAAA,KAAO,IAAA,IAAQ,EAAA,KAAO,MAAA,EAAW;AACnC,IAAA,MAAM,EAAA,CAAG,WAAA,CAAY,cAAA,EAAgB,UAAA,EAAY,OAAO,CAAA;AACxD,IAAA;AAAA,EACF;AAEA,EAAA,qBAAA,EAAsB;AACtB,EAAA,IAAI,YAAyC,EAAC;AAC9C,EAAA,IAAIC,aAAA,CAAW,SAAS,CAAA,EAAG;AACzB,IAAA,IAAI;AACF,MAAA,SAAA,GAAY,IAAA,CAAK,KAAA,CAAMC,eAAA,CAAa,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,IACxD,CAAA,CAAA,MAAQ;AAEN,MAAA,SAAA,GAAY,EAAC;AAAA,IACf;AAAA,EACF;AACA,EAAA,SAAA,CAAU,UAAU,CAAA,GAAI,MAAA;AACxB,EAAA,MAAMC,iCAAA,CAAgB,WAAW,SAAS,CAAA;AAC1C,EAAA,IAAI;AACF,IAAAL,YAAA,CAAU,WAAW,GAAK,CAAA;AAAA,EAC5B,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAOA,eAAsB,UAAU,UAAA,EAAsD;AACpF,EAAA,MAAM,KAAK,gBAAA,EAAiB;AAC5B,EAAA,IAAI,EAAA,KAAO,IAAA,IAAQ,EAAA,KAAO,MAAA,EAAW;AACnC,IAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,WAAA,CAAY,gBAAgB,UAAU,CAAA;AAC7D,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,MAAA;AACtC,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,IACzB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,IAAI,CAACG,aAAA,CAAW,SAAS,CAAA,EAAG,OAAO,MAAA;AAMnC,EAAAG,mCAAA,CAAkBR,YAAA,CAAQ,SAAS,CAAA,EAAG,SAAS,CAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAMM,eAAA,CAAa,SAAA,EAAW,MAAM,CAAC,CAAA;AACtD,IAAA,OAAO,IAAI,UAAU,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AASO,SAAS,aAAA,CACd,YACA,SAAA,EACsB;AACtB,EAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,GAAA,CAAI,UAAU,CAAA;AAC/C,EAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AACnC,EAAA,MAAM,WAAW,YAAY;AAC3B,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,SAAA,EAAU;AAAA,IACzB,CAAA,SAAE;AACA,MAAA,eAAA,CAAgB,OAAO,UAAU,CAAA;AAAA,IACnC;AAAA,EACF,CAAA,GAAG;AACH,EAAA,eAAA,CAAgB,GAAA,CAAI,YAAY,OAAO,CAAA;AACvC,EAAA,OAAO,OAAA;AACT;;;ACjJA,SAAS,UAAU,CAAA,EAAmB;AACpC,EAAA,OAAOG,kBAAA,CAAY,CAAC,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA;AAC5C;AAKA,SAAS,aAAa,QAAA,EAA0B;AAC9C,EAAA,OAAOC,kBAAW,QAAQ,CAAA,CAAE,OAAO,QAAQ,CAAA,CAAE,OAAO,WAAW,CAAA;AACjE;AAOA,eAAsB,kBAAA,CACpB,UAAA,EACA,QAAA,EACA,YAAA,EACA,KAAA,EACsB;AACtB,EAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB;AAAA,IAC/B,UAAA,EAAY,eAAA;AAAA,IACZ,aAAA,EAAe,YAAA;AAAA,IACf,SAAA,EAAW;AAAA,GACZ,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,KAAA,CAAM,aAAA,EAAe;AAAA,IAChD,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,IAC/D,IAAA,EAAM,KAAK,QAAA;AAAS,GACrB,CAAA;AACD,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAIC,oCAAA,CAAmB,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI;AAAA,MAC5E,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACA,EAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAMlC,EAAA,MAAM,UAAA,GAAa,KAAK,GAAA,EAAI;AAC5B,EAAA,MAAM,MAAA,GAAsB;AAAA,IAC1B,aAAa,IAAA,CAAK,YAAA;AAAA,IAClB,YAAA,EAAc,KAAK,aAAA,IAAiB,YAAA;AAAA;AAAA,IAEpC,SAAA,EAAW,UAAA,GAAA,CAAc,IAAA,CAAK,UAAA,IAAc,IAAA,IAAQ,GAAA;AAAA,IACpD,UAAA;AAAA,IACA,GAAI,KAAK,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAM,GAAI;AAAC,GAC1D;AACA,EAAA,MAAM,SAAA,CAAU,YAAY,MAAM,CAAA;AAClC,EAAA,OAAO,MAAA;AACT;AAUA,eAAsB,WAAA,CACpB,UAAA,EACA,QAAA,EACA,MAAA,EACA,KAAA,EACsB;AACtB,EAAA,MAAM,QAAA,GAAW,UAAU,EAAE,CAAA;AAC7B,EAAA,MAAM,SAAA,GAAY,aAAa,QAAQ,CAAA;AACvC,EAAA,MAAM,KAAA,GAAQ,UAAU,EAAE,CAAA;AAC1B,EAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,GAAA;AAErC,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,UAAsB,MAAM;AAAA,EAAC,CAAA;AAEjC,EAAA,IAAI,KAAA,CAAM,iBAAiB,WAAA,EAAa;AACtC,IAAA,MAAM,MAAA,GAAS,MAAM,qBAAA,CAAsB,KAAA,EAAO,SAAS,CAAA;AAC3D,IAAA,WAAA,GAAc,MAAA,CAAO,WAAA;AACrB,IAAA,UAAA,GAAa,MAAA,CAAO,OAAA;AACpB,IAAA,OAAA,GAAU,MAAA,CAAO,KAAA;AAAA,EACnB,CAAA,MAAO;AACL,IAAA,WAAA,GAAc,2BAAA;AACd,IAAA,UAAA,GAAa,iBAAA,CAAkB,OAAO,SAAS,CAAA;AAAA,EACjD;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,KAAA,CAAM,qBAAqB,CAAA;AACnD,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,eAAA,EAAiB,MAAM,CAAA;AAChD,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,WAAA,EAAa,QAAQ,CAAA;AAC9C,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,cAAA,EAAgB,WAAW,CAAA;AACpD,IAAA,IAAI,MAAA,KAAW,KAAA,CAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC7C,MAAA,OAAA,CAAQ,aAAa,GAAA,CAAI,OAAA,EAAS,MAAA,CAAO,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,IACpD;AACA,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,gBAAA,EAAkB,SAAS,CAAA;AACpD,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,uBAAA,EAAyB,MAAM,CAAA;AACxD,IAAA,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,OAAA,EAAS,KAAK,CAAA;AAEvC,IAAAP,sBAAA;AAAA,MACE;AAAA,6CAAA,EAAkD,UAAU,CAAA;AAAA;AAAA,EAAA,EACrB,OAAA,CAAQ,UAAU;;AAAA;AAAA,KAC3D;AAEA,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,aAAA,KAAkB,MAAM,UAAA;AAE7C,IAAA,IAAI,kBAAkB,KAAA,EAAO;AAC3B,MAAA,MAAM,IAAIO,oCAAA;AAAA,QACR,6EAAA;AAAA,QACA,EAAE,MAAM,sBAAA;AAAuB,OACjC;AAAA,IACF;AAGA,IAAA,MAAM,SAAA,GAAY,IAAI,eAAA,CAAgB;AAAA,MACpC,UAAA,EAAY,oBAAA;AAAA,MACZ,IAAA;AAAA,MACA,SAAA,EAAW,QAAA;AAAA,MACX,aAAA,EAAe,QAAA;AAAA,MACf,YAAA,EAAc;AAAA,KACf,CAAA;AACD,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,KAAA,CAAM,aAAA,EAAe;AAAA,MAChD,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,MAC/D,IAAA,EAAM,UAAU,QAAA;AAAS,KAC1B,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AACpC,MAAA,MAAM,IAAIA,oCAAA;AAAA,QACR,CAAA,kCAAA,EAAqC,SAAS,MAAM,CAAA,EAAA,EAAK,QAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAAA,QAC9E,EAAE,MAAM,6BAAA;AAA8B,OACxC;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAMlC,IAAA,MAAM,UAAA,GAAa,KAAK,GAAA,EAAI;AAC5B,IAAA,OAAO;AAAA,MACL,aAAa,IAAA,CAAK,YAAA;AAAA,MAClB,GAAI,KAAK,aAAA,KAAkB,KAAA,CAAA,GAAY,EAAE,YAAA,EAAc,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA;AAAA,MAE/E,SAAA,EAAW,UAAA,GAAA,CAAc,IAAA,CAAK,UAAA,IAAc,IAAA,IAAQ,GAAA;AAAA,MACpD,UAAA;AAAA,MACA,GAAI,KAAK,KAAA,KAAU,KAAA,CAAA,GAAY,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAM,GAAI;AAAC,KAC1D;AAAA,EACF,CAAA,SAAE;AACA,IAAA,OAAA,EAAQ;AAAA,EACV;AACF;AASA,eAAe,qBAAA,CACb,eACA,SAAA,EAKC;AACD,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,UAAA;AACJ,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAyC,CAAC,KAAK,GAAA,KAAQ;AACzE,IAAA,WAAA,GAAc,GAAA;AACd,IAAA,UAAA,GAAa,GAAA;AAAA,EACf,CAAC,CAAA;AACD,EAAA,MAAM,MAAA,GAASC,iBAAA,CAAa,CAAC,GAAA,EAAsB,GAAA,KAAwB;AACzE,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAA,CAAI,GAAA,IAAO,KAAK,kBAAkB,CAAA;AACtD,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACxC,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAC1C,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,UAAU,QAAA,EAAU;AACzD,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,IAAI,uBAAuB,CAAA;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,UAAU,aAAA,EAAe;AAC3B,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,IAAI,yCAAoC,CAAA;AAC5C,MAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,IAAA,GAAA,CAAI,IAAI,+EAA+E,CAAA;AACvF,IAAA,WAAA,GAAc,EAAE,IAAA,EAAM,KAAA,EAAO,CAAA;AAAA,EAC/B,CAAC,CAAA;AACD,EAAA,MAAA,CAAO,MAAA,CAAO,GAAG,WAAW,CAAA;AAC5B,EAAA,MAAM,aAAA,GAAgB,WAAW,MAAM;AACrC,IAAA,UAAA,GAAa,IAAID,oCAAA,CAAmB,sBAAA,EAAwB,EAAE,IAAA,EAAM,eAAA,EAAiB,CAAC,CAAA;AAAA,EACxF,GAAG,SAAS,CAAA;AAEZ,EAAA,MAAM,IAAI,OAAA,CAAc,CAAC,CAAA,KAAM;AAC7B,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAE;AAAA,SACnB,MAAA,CAAO,IAAA,CAAK,WAAA,EAAa,MAAM,GAAG,CAAA;AAAA,EACzC,CAAC,CAAA;AACD,EAAA,MAAM,OAAA,GAAU,OAAO,OAAA,EAAQ;AAC/B,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,IAAA,EAAM;AACnD,IAAA,MAAA,CAAO,KAAA,EAAM;AACb,IAAA,MAAM,IAAIA,qCAAmB,yCAAA,EAA2C;AAAA,MACtE,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACA,EAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,EAAA,OAAO;AAAA,IACL,WAAA,EAAa,oBAAoB,IAAI,CAAA,SAAA,CAAA;AAAA,IACrC,OAAA;AAAA,IACA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,aAAa,CAAA;AAC1B,MAAA,MAAA,CAAO,KAAA,EAAM;AAAA,IACf;AAAA,GACF;AACF;AAQA,eAAe,iBAAA,CACb,eACA,SAAA,EAC0C;AAC1C,EAAA,MAAM,EAAA,GAAKE,yBAAgB,EAAE,KAAA,EAAO,QAAQ,KAAA,EAAO,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAC3E,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAyC,CAAC,SAAS,MAAA,KAAW;AAChF,IAAA,EAAA,CAAG,QAAA,CAAS,kDAAkD,CAAA,CAE3D,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,MAAA,EAAA,CAAG,KAAA,EAAM;AACT,MAAA,MAAM,OAAA,GAAU,OAAO,IAAA,EAAK;AAE5B,MAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG;AACzB,QAAA,IAAI;AACF,UAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,OAAO,CAAA;AAC9B,UAAA,MAAM,IAAA,GAAO,MAAA,CAAO,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AAC3C,UAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA,IAAK,EAAA;AAClD,UAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,YAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,KAAA,EAAO,CAAA;AACvB,YAAA;AAAA,UACF;AAAA,QACF,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AAIA,MAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,eAAe,CAAA;AAAA,IACjD,CAAC,CAAA,CACA,KAAA,CAAM,MAAM,CAAA;AACf,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,EAAA,CAAG,KAAA,EAAM;AACT,MAAA,MAAA,CAAO,IAAIF,oCAAA,CAAmB,8BAAA,EAAgC,EAAE,IAAA,EAAM,eAAA,EAAiB,CAAC,CAAA;AAAA,IAC1F,GAAG,SAAS,CAAA;AAAA,EACd,CAAC,CAAA;AACD,EAAA,OAAO,OAAA;AACT","file":"mcp-auth.cjs","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n * @internal\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n * @internal\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n * @internal\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n * @internal\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":["join","homedir","dirname","mkdirSync","chmodSync","createRequire","diag","existsSync","readFileSync","atomicWriteJson","assertSecureModes","randomBytes","createHash","ConfigurationError","createServer","createInterface"],"mappings":"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","file":"mcp-auth.cjs","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
package/dist/mcp-auth.js CHANGED
@@ -1,7 +1,7 @@
1
- import { atomicWriteJson } from './chunk-CHVSMEKM.js';
2
- import { assertSecureModes } from './chunk-23VZBRDQ.js';
3
- import { ConfigurationError } from './chunk-HI4ZW62S.js';
4
- import { diag } from './chunk-4X3SBHPK.js';
1
+ import { atomicWriteJson } from './chunk-6AZDM6LK.js';
2
+ import { assertSecureModes } from './chunk-3V7UJLQT.js';
3
+ import { ConfigurationError } from './chunk-U4UJHOSH.js';
4
+ import { diag } from './chunk-5F5NWKPN.js';
5
5
  import { randomBytes, createHash } from 'crypto';
6
6
  import { createServer } from 'http';
7
7
  import { createInterface } from 'readline/promises';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":[],"mappings":"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","file":"mcp-auth.js","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n * @internal\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n * @internal\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n * @internal\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n * @internal\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":[],"mappings":"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","file":"mcp-auth.js","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}