@herbertgao/pi-extensions 2026.9.17 → 2026.9.19

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 (307) hide show
  1. package/README.md +31 -33
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +4 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +4 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +21 -6
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +15 -4
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +43 -7
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +7 -2
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/package.json +75 -75
  12. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  13. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  14. package/node_modules/@herbertgao/resume-from/package.json +4 -2
  15. package/node_modules/@herbertgao/sol-pi/package.json +6 -6
  16. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
  17. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  18. package/node_modules/@tifan/pi-handoff/package.json +1 -1
  19. package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
  20. package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
  21. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  22. package/node_modules/@tifan/pi-recap/package.json +1 -1
  23. package/node_modules/@tifan/pi-rename/package.json +1 -1
  24. package/node_modules/pi-antigravity/README.md +29 -6
  25. package/node_modules/pi-antigravity/package.json +14 -9
  26. package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
  27. package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
  28. package/node_modules/pi-antigravity/src/client/client.ts +1 -1
  29. package/node_modules/pi-antigravity/src/image/image.ts +4 -4
  30. package/node_modules/pi-antigravity/src/index.ts +203 -13
  31. package/node_modules/pi-antigravity/src/search/index.ts +1 -0
  32. package/node_modules/pi-antigravity/src/search/search.ts +325 -0
  33. package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
  34. package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
  35. package/node_modules/pi-antigravity/src/types/types.ts +5 -12
  36. package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
  37. package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
  38. package/node_modules/pi-lens/package.json +158 -158
  39. package/node_modules/pi-multi-account/package.json +2 -2
  40. package/node_modules/pi-typesafe/README.md +3 -1
  41. package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
  42. package/node_modules/pi-typesafe/dist/auth.js +7 -3
  43. package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
  44. package/node_modules/pi-typesafe/dist/backends.js +95 -1
  45. package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
  46. package/node_modules/pi-typesafe/dist/client.js +12 -9
  47. package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
  48. package/node_modules/pi-typesafe/dist/credentials.js +10 -7
  49. package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
  50. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  51. package/node_modules/pi-typesafe/dist/index.js +1 -1
  52. package/node_modules/pi-typesafe/dist/login.d.ts +2 -2
  53. package/node_modules/pi-typesafe/dist/login.js +2 -2
  54. package/node_modules/pi-typesafe/package.json +6 -5
  55. package/node_modules/pi-web-access/CHANGELOG.md +72 -0
  56. package/node_modules/pi-web-access/README.md +34 -13
  57. package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
  58. package/node_modules/pi-web-access/curator-page.ts +5 -1
  59. package/node_modules/pi-web-access/curator-server.ts +2 -0
  60. package/node_modules/pi-web-access/dist/index.js +1337 -1009
  61. package/node_modules/pi-web-access/exa.ts +20 -52
  62. package/node_modules/pi-web-access/extract.ts +41 -4
  63. package/node_modules/pi-web-access/gemini-search.ts +12 -4
  64. package/node_modules/pi-web-access/index.ts +29 -37
  65. package/node_modules/pi-web-access/openai-search.ts +35 -7
  66. package/node_modules/pi-web-access/package.json +8 -7
  67. package/node_modules/pi-web-access/page-query.ts +2 -2
  68. package/node_modules/pi-web-access/query-rewrite.ts +2 -3
  69. package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
  70. package/node_modules/pi-web-access/storage.ts +48 -77
  71. package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
  72. package/node_modules/pi-web-access/summary-review.ts +3 -4
  73. package/node_modules/pi-web-access/tavily.ts +67 -20
  74. package/node_modules/pi-web-access/tool-activation.ts +53 -33
  75. package/node_modules/pi-web-access/utils.ts +1 -1
  76. package/node_modules/pi-web-access/you.ts +164 -0
  77. package/node_modules/pi-web-access/zai.ts +161 -0
  78. package/node_modules/remote-pi/package.json +5 -1
  79. package/package.json +19 -31
  80. package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
  81. package/node_modules/pi-mcp-adapter/LICENSE +0 -21
  82. package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
  83. package/node_modules/pi-mcp-adapter/README.md +0 -1021
  84. package/node_modules/pi-mcp-adapter/abort.ts +0 -34
  85. package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
  86. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
  87. package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
  88. package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
  89. package/node_modules/pi-mcp-adapter/banner.png +0 -0
  90. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
  91. package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
  92. package/node_modules/pi-mcp-adapter/cli.js +0 -454
  93. package/node_modules/pi-mcp-adapter/commands.ts +0 -861
  94. package/node_modules/pi-mcp-adapter/config.ts +0 -1565
  95. package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
  96. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
  97. package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
  98. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
  99. package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
  100. package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
  101. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
  102. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
  103. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
  104. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
  105. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
  106. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
  107. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
  108. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
  109. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
  110. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
  111. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
  112. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
  113. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
  114. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
  115. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
  116. package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
  117. package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
  118. package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
  119. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
  120. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
  121. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
  122. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
  123. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
  124. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
  125. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
  126. package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
  127. package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
  128. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
  129. package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
  130. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
  131. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
  132. package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
  133. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
  134. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
  135. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
  136. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
  137. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
  138. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
  139. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
  140. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
  141. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
  142. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
  143. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
  144. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
  145. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
  146. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
  147. package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
  148. package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
  149. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
  150. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
  151. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
  152. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
  153. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
  154. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
  155. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
  156. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
  157. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
  158. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
  159. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
  160. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
  161. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
  162. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
  163. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
  164. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
  165. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
  166. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
  167. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
  168. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
  169. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
  170. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
  171. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
  172. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
  173. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
  174. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
  175. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
  176. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
  177. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
  178. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
  179. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
  180. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
  181. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
  182. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
  183. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
  184. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
  185. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
  186. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
  187. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
  188. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
  189. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
  190. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
  191. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
  192. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
  193. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
  194. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
  195. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
  196. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
  197. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
  198. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
  199. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
  200. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
  201. package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
  202. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
  203. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
  204. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
  205. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
  206. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
  207. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
  208. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
  209. package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
  210. package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
  211. package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
  212. package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
  213. package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
  214. package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
  215. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
  216. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
  217. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
  218. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
  219. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
  220. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
  221. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
  222. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
  223. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
  224. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
  225. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
  226. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
  227. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
  228. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
  229. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
  230. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
  231. package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
  232. package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
  233. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
  234. package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
  235. package/node_modules/pi-mcp-adapter/errors.ts +0 -364
  236. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
  237. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
  238. package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
  239. package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
  240. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
  241. package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
  242. package/node_modules/pi-mcp-adapter/index.ts +0 -2106
  243. package/node_modules/pi-mcp-adapter/init.ts +0 -721
  244. package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
  245. package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
  246. package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
  247. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
  248. package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
  249. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
  250. package/node_modules/pi-mcp-adapter/logger.ts +0 -169
  251. package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
  252. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
  253. package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
  254. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
  255. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
  256. package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
  257. package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
  258. package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
  259. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
  260. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
  261. package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
  262. package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
  263. package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
  264. package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
  265. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
  266. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
  267. package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
  268. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
  269. package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
  270. package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
  271. package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
  272. package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
  273. package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
  274. package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
  275. package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
  276. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
  277. package/node_modules/pi-mcp-adapter/package.json +0 -219
  278. package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
  279. package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
  280. package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
  281. package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
  282. package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
  283. package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
  284. package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
  285. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
  286. package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
  287. package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
  288. package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
  289. package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
  290. package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
  291. package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
  292. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
  293. package/node_modules/pi-mcp-adapter/state.ts +0 -71
  294. package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
  295. package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
  296. package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
  297. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
  298. package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
  299. package/node_modules/pi-mcp-adapter/types.ts +0 -1120
  300. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
  301. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
  302. package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
  303. package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
  304. package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
  305. package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
  306. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
  307. package/node_modules/pi-mcp-adapter/utils.ts +0 -464
@@ -1,1287 +0,0 @@
1
- /**
2
- * MCP Auth Storage Module
3
- *
4
- * Handles secure storage of OAuth credentials, tokens, client information,
5
- * and legacy PKCE state for MCP servers.
6
- *
7
- * Persistent OAuth entries are stored in the operating system credential store
8
- * unless the externally keyed encrypted-file backend is explicitly selected.
9
- * The default backend imports and removes legacy plaintext entries from
10
- * $MCP_OAUTH_DIR or <Pi agent dir>/mcp-oauth.
11
- */
12
-
13
- import { spawnSync } from 'child_process';
14
- import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
15
- import { createRequire } from 'module';
16
- import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'fs';
17
- import { dirname, join } from 'path';
18
- import { fileURLToPath } from 'url';
19
- import { getAgentPath } from './agent-dir.ts';
20
- import { resolveConfiguredOAuthDir } from './config.ts';
21
-
22
- const require = createRequire(import.meta.url);
23
- const AUTH_SECRET_SERVICE = 'pi-mcp-adapter.oauth';
24
- const TEST_AUTH_STORE_ENV = 'PI_MCP_ADAPTER_TEST_AUTH_STORE';
25
- /**
26
- * Windows Credential Manager caps one value at CRED_MAX_CREDENTIAL_BLOB_SIZE
27
- * (2560 bytes) and stores it as UTF-16, so the real ceiling is
28
- * AUTH_SECRET_VALUE_LIMIT characters. Chunks must stay below that, and so must
29
- * the threshold that decides whether to chunk at all, or oversized records still
30
- * fail to persist on Windows.
31
- */
32
- const AUTH_SECRET_CHUNK_SIZE = 1000;
33
- /** Largest single value the strictest supported credential store accepts. */
34
- const AUTH_SECRET_VALUE_LIMIT = 1280;
35
- const KEYRING_RECOVERY_DISABLED_ENV = 'PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY';
36
- const KEYRING_RECOVERY_KEYCTL_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_KEYCTL';
37
- const KEYRING_RECOVERY_NODE_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_NODE';
38
- const KEYRING_RECOVERY_HELPER_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_HELPER';
39
- const TEST_LINUX_KEYRING_RECOVERY_ENV = 'PI_MCP_ADAPTER_TEST_LINUX_KEYRING_RECOVERY';
40
- const AUTH_CACHE_DISABLED_ENV = 'PI_MCP_ADAPTER_DISABLE_AUTH_CACHE';
41
- const KEYRING_RECOVERY_TIMEOUT_MS = 10_000;
42
- const AUTH_CHUNK_MANIFEST_KEY = '__piMcpAdapterOAuthChunked';
43
- const OAUTH_FILE_KEY_ENV = 'PI_MCP_ADAPTER_OAUTH_FILE_KEY';
44
- const ENCRYPTED_FILE_AAD_CONTEXT = 'pi-mcp-adapter.oauth.encrypted-file.v1';
45
-
46
- export type OAuthAuthority = () => void;
47
-
48
- type OAuthLifecycleRecord = {
49
- generation: object;
50
- revocations: number;
51
- };
52
-
53
- const oauthLifecycleRecords = new Map<string, OAuthLifecycleRecord>();
54
-
55
- function getOAuthLifecycleRecord(serverName: string): OAuthLifecycleRecord {
56
- let record = oauthLifecycleRecords.get(serverName);
57
- if (!record) {
58
- record = { generation: {}, revocations: 0 };
59
- oauthLifecycleRecords.set(serverName, record);
60
- }
61
- return record;
62
- }
63
-
64
- /** Capture immutable process-local authority for one server's OAuth lifecycle. */
65
- export function captureOAuthAuthority(serverName: string, assertNow = true): OAuthAuthority {
66
- const record = getOAuthLifecycleRecord(serverName);
67
- const generation = record.generation;
68
- const capturedDuringRevocation = record.revocations > 0;
69
- const assertAuthority = (): void => {
70
- if (capturedDuringRevocation || record.generation !== generation || record.revocations > 0) {
71
- throw new Error('OAuth flow is no longer active');
72
- }
73
- };
74
- if (assertNow) assertAuthority();
75
- return assertAuthority;
76
- }
77
-
78
- /** Begin an overlap-safe process-local logout interval. */
79
- export function beginOAuthRevocation(serverName: string): () => void {
80
- const record = getOAuthLifecycleRecord(serverName);
81
- record.generation = {};
82
- record.revocations += 1;
83
- let released = false;
84
- return () => {
85
- if (released) return;
86
- released = true;
87
- record.revocations -= 1;
88
- };
89
- }
90
-
91
- /** OAuth token storage format */
92
- export interface StoredTokens {
93
- accessToken: string;
94
- refreshToken?: string;
95
- expiresAt?: number; // Unix timestamp in seconds
96
- scope?: string;
97
- /** SEP-2352 authorization-server issuer binding */
98
- issuer?: string;
99
- }
100
-
101
- /** OAuth client information from dynamic or static registration */
102
- export interface StoredClientInfo {
103
- clientId: string;
104
- clientSecret?: string;
105
- clientIdIssuedAt?: number;
106
- clientSecretExpiresAt?: number;
107
- redirectUris?: string[];
108
- /** SEP-2352 authorization-server issuer binding */
109
- issuer?: string;
110
- /**
111
- * True when this entry is a secretless SEP-2352 issuer stub persisted for a
112
- * config-pre-registered client (written by the config-clientId path of
113
- * saveClientInformation). Such a stub is only usable when paired with the
114
- * config that supplies the client secret; it must never be served as
115
- * standalone client information.
116
- */
117
- configPreRegistered?: boolean;
118
- }
119
-
120
- /** Complete auth entry for a server */
121
- export interface AuthEntry {
122
- tokens?: StoredTokens;
123
- clientInfo?: StoredClientInfo;
124
- codeVerifier?: string;
125
- oauthState?: string;
126
- serverUrl?: string; // Track the URL these credentials are for
127
- }
128
-
129
- export interface AuthStorageOptions {
130
- /** Legacy plaintext import directory. Persistent secrets no longer use this as their store. */
131
- baseDir?: string;
132
- credentialStore?: 'encrypted-file';
133
- }
134
-
135
- export class OAuthCredentialStoreError extends Error {
136
- readonly code = 'OAUTH_CREDENTIAL_STORE_UNAVAILABLE';
137
-
138
- constructor(
139
- message: string,
140
- readonly operation: 'read' | 'write' | 'remove',
141
- cause: unknown,
142
- readonly backend?: 'encrypted-file',
143
- ) {
144
- super(message, { cause });
145
- this.name = 'OAuthCredentialStoreError';
146
- }
147
- }
148
-
149
- export type OAuthCredentialStatus =
150
- | { status: 'present'; entry: AuthEntry }
151
- | { status: 'absent' }
152
- | { status: 'unavailable'; message: string };
153
-
154
- function causeChainContains(error: unknown, pattern: RegExp): boolean {
155
- const seen = new Set<unknown>();
156
- let current = error;
157
- while ((typeof current === 'object' && current !== null) || typeof current === 'function') {
158
- if (seen.has(current)) break;
159
- seen.add(current);
160
- const candidate = current as { name?: unknown; message?: unknown; code?: unknown; cause?: unknown };
161
- if ([candidate.name, candidate.message, candidate.code].some(value => typeof value === 'string' && pattern.test(value))) {
162
- return true;
163
- }
164
- current = candidate.cause;
165
- }
166
- return false;
167
- }
168
-
169
- export function formatOAuthCredentialStoreUnavailable(error: OAuthCredentialStoreError): string {
170
- if (error.backend === 'encrypted-file') {
171
- if (causeChainContains(error, /PI_MCP_ADAPTER_OAUTH_FILE_KEY/)) {
172
- return 'Encrypted OAuth credential file store unavailable. Set PI_MCP_ADAPTER_OAUTH_FILE_KEY to canonical base64 for exactly 32 random bytes and retry.';
173
- }
174
- return 'Encrypted OAuth credential file store unavailable. Check the encrypted credential file and key, then reauthenticate.';
175
- }
176
- if (process.platform === 'linux' && causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i)) {
177
- return 'OAuth credential store unavailable: the Linux session keyring may be revoked. Start Pi from a fresh login/keyring session and retry.';
178
- }
179
- if (causeChainContains(error, /ERROR_NO_SUCH_LOGON_SESSION|\b1312\b/i)) {
180
- return 'OAuth credential store unavailable: Windows Credential Manager is unavailable from this network logon. To opt in to encrypted file storage for OpenSSH/headless use, set settings.oauthCredentialStore to "encrypted-file" and provide PI_MCP_ADAPTER_OAUTH_FILE_KEY.';
181
- }
182
- return 'OAuth credential store unavailable. Configure or unlock the OS credential store and retry.';
183
- }
184
-
185
- interface KeyringEntry {
186
- getPassword(): string | null;
187
- setPassword(password: string): void;
188
- deleteCredential(): boolean;
189
- }
190
-
191
- type KeyringEntryConstructor = new (service: string, account: string) => KeyringEntry;
192
- type KeyringModule = { Entry: KeyringEntryConstructor };
193
- type KeyringRequire = ((id: string) => unknown) & { resolve(id: string): string };
194
-
195
- interface AuthSecretStore {
196
- readonly kind?: 'encrypted-file';
197
- read(account: string): string | undefined;
198
- write(account: string, payload: string): void;
199
- remove(account: string): void;
200
- }
201
-
202
- function authSecretStoreLabel(store: AuthSecretStore): string {
203
- return store.kind === 'encrypted-file' ? 'encrypted OAuth credential file store' : 'OS secure credential store';
204
- }
205
-
206
- interface AuthEntryChunkManifest {
207
- [AUTH_CHUNK_MANIFEST_KEY]: 1;
208
- chunkCount: number;
209
- chunkDigest: string;
210
- }
211
-
212
- let KeyringEntryClass: KeyringEntryConstructor | undefined;
213
- const keyringEntries = new Map<string, KeyringEntry>();
214
- const memoryAuthEntries = new Map<string, string>();
215
-
216
- let testAuthSecretStoreReadCount = 0;
217
- const authEntryCache = new Map<string, AuthEntry | undefined>();
218
-
219
- function isAuthEntryCacheEnabled(): boolean {
220
- return process.env[AUTH_CACHE_DISABLED_ENV] !== '1';
221
- }
222
-
223
- function cloneAuthEntry(entry: AuthEntry | undefined): AuthEntry | undefined {
224
- return entry === undefined ? undefined : structuredClone(entry);
225
- }
226
-
227
- const memoryAuthSecretStore: AuthSecretStore = {
228
- read(account) {
229
- testAuthSecretStoreReadCount++;
230
- return memoryAuthEntries.get(account);
231
- },
232
- write(account, payload) {
233
- memoryAuthEntries.set(account, payload);
234
- },
235
- remove(account) {
236
- memoryAuthEntries.delete(account);
237
- },
238
- };
239
-
240
- const keyringAuthSecretStore: AuthSecretStore = {
241
- read(account) {
242
- const cached = keyringEntries.get(account);
243
- if (cached) {
244
- try {
245
- // keyring v2 throws for provider/session failures; null means the credential is absent.
246
- return cached.getPassword() ?? undefined;
247
- } catch {
248
- // A stale native Entry may survive a keyring daemon restart. Retry once.
249
- }
250
- keyringEntries.delete(account);
251
- }
252
- const fresh = getKeyringEntry(account);
253
- const value = fresh.getPassword();
254
- keyringEntries.set(account, fresh);
255
- return value ?? undefined;
256
- },
257
- write(account, payload) {
258
- keyringEntries.delete(account);
259
- const fresh = getKeyringEntry(account);
260
- fresh.setPassword(payload);
261
- keyringEntries.set(account, fresh);
262
- },
263
- remove(account) {
264
- keyringEntries.delete(account);
265
- getKeyringEntry(account).deleteCredential();
266
- },
267
- };
268
-
269
- /** Mimics the Windows Credential Manager per-value ceiling for tests. */
270
- const sizeLimitedAuthSecretStore: AuthSecretStore = {
271
- read(account) {
272
- testAuthSecretStoreReadCount++;
273
- return memoryAuthEntries.get(account);
274
- },
275
- write(account, payload) {
276
- if (payload.length > AUTH_SECRET_VALUE_LIMIT) {
277
- throw new Error(`Value of 'password encoded as UTF-16' is longer than the platform limit of ${AUTH_SECRET_VALUE_LIMIT * 2} chars`);
278
- }
279
- memoryAuthEntries.set(account, payload);
280
- },
281
- remove(account) {
282
- memoryAuthEntries.delete(account);
283
- },
284
- };
285
-
286
- const writeFailingAuthSecretStore: AuthSecretStore = {
287
- ...memoryAuthSecretStore,
288
- write() {
289
- throw new Error('simulated secure credential store write failure');
290
- },
291
- };
292
-
293
- const unavailableAuthSecretStore: AuthSecretStore = {
294
- read() {
295
- testAuthSecretStoreReadCount++;
296
- throw new Error('simulated secure credential store unavailable');
297
- },
298
- write() {
299
- throw new Error('simulated secure credential store unavailable');
300
- },
301
- remove() {
302
- throw new Error('simulated secure credential store unavailable');
303
- },
304
- };
305
-
306
- function createKeyRevokedTestError(): Error {
307
- return new Error("Couldn't access platform storage: KeyRevoked", { cause: new Error('KeyRevoked') });
308
- }
309
-
310
- const keyRevokedAuthSecretStore: AuthSecretStore = {
311
- read() {
312
- testAuthSecretStoreReadCount++;
313
- throw createKeyRevokedTestError();
314
- },
315
- write() {
316
- throw createKeyRevokedTestError();
317
- },
318
- remove() {
319
- throw createKeyRevokedTestError();
320
- },
321
- };
322
-
323
- export function resetTestAuthSecretStore(): void {
324
- memoryAuthEntries.clear();
325
- authEntryCache.clear();
326
- keyringEntries.clear();
327
- testAuthSecretStoreReadCount = 0;
328
- }
329
-
330
- /** Install a fake native Entry for OAuth storage tests without opening a real keyring. */
331
- export function setTestKeyringEntryClass(entryClass: KeyringEntryConstructor | undefined): void {
332
- keyringEntries.clear();
333
- KeyringEntryClass = entryClass;
334
- }
335
-
336
- export function resetAuthEntryCache(): void {
337
- authEntryCache.clear();
338
- }
339
-
340
- export function getTestAuthSecretStoreReadCount(): number {
341
- return testAuthSecretStoreReadCount;
342
- }
343
-
344
- export function getTestAuthSecretStoreEntries(): [string, string][] {
345
- return [...memoryAuthEntries.entries()];
346
- }
347
-
348
- export function removeTestAuthSecretStoreEntry(account: string): void {
349
- memoryAuthEntries.delete(account);
350
- }
351
-
352
- export function setTestAuthSecretStoreEntry(account: string, payload: string): void {
353
- memoryAuthEntries.set(account, payload);
354
- }
355
-
356
- function decodeCanonicalBase64(value: unknown, expectedBytes?: number): Buffer {
357
- if (typeof value !== 'string' || value.length === 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
358
- throw new Error('value is not canonical base64');
359
- }
360
- const decoded = Buffer.from(value, 'base64');
361
- if (decoded.toString('base64') !== value || (expectedBytes !== undefined && decoded.length !== expectedBytes)) {
362
- throw new Error(expectedBytes === undefined ? 'value is not canonical base64' : `value must decode to exactly ${expectedBytes} bytes`);
363
- }
364
- return decoded;
365
- }
366
-
367
- function getEncryptedFileKey(): Buffer {
368
- const encoded = process.env[OAUTH_FILE_KEY_ENV];
369
- if (!encoded) throw new Error(`${OAUTH_FILE_KEY_ENV} is required for the encrypted OAuth credential file store`);
370
- try {
371
- return decodeCanonicalBase64(encoded, 32);
372
- } catch (error) {
373
- throw new Error(`${OAUTH_FILE_KEY_ENV} must be canonical base64 for exactly 32 random bytes`, { cause: error });
374
- }
375
- }
376
-
377
- function encryptedEntryPath(root: string, account: string): string {
378
- return join(root, account, 'credentials.json');
379
- }
380
-
381
- function validatePrivateRegularFile(path: string): boolean {
382
- let stat;
383
- try {
384
- stat = lstatSync(path);
385
- } catch (error) {
386
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;
387
- throw error;
388
- }
389
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Refusing non-regular OAuth credential file at ${path}`);
390
- if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) {
391
- throw new Error(`OAuth credential file has group or other permissions at ${path}`);
392
- }
393
- return true;
394
- }
395
-
396
- function ensurePrivateDirectory(path: string): void {
397
- mkdirSync(path, { recursive: true, mode: 0o700 });
398
- const stat = lstatSync(path);
399
- if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error(`Refusing non-directory OAuth credential path at ${path}`);
400
- if (process.platform !== 'win32') chmodSync(path, 0o700);
401
- }
402
-
403
- function createEncryptedFileAuthSecretStore(): AuthSecretStore {
404
- const root = getAgentPath('mcp-oauth-encrypted');
405
- return {
406
- kind: 'encrypted-file',
407
- read(account) {
408
- const key = getEncryptedFileKey();
409
- const path = encryptedEntryPath(root, account);
410
- if (!validatePrivateRegularFile(path)) return undefined;
411
- const envelope = parseJsonPayload(account, readFileSync(path, 'utf8'), path) as Record<string, unknown>;
412
- if (!envelope || envelope.version !== 1 || envelope.algorithm !== 'aes-256-gcm') {
413
- throw new Error(`Unsupported encrypted OAuth credential envelope at ${path}`);
414
- }
415
- const iv = decodeCanonicalBase64(envelope.iv, 12);
416
- const ciphertext = decodeCanonicalBase64(envelope.ciphertext);
417
- const tag = decodeCanonicalBase64(envelope.tag, 16);
418
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
419
- decipher.setAAD(Buffer.from(`${ENCRYPTED_FILE_AAD_CONTEXT}\0${account}`, 'utf8'));
420
- decipher.setAuthTag(tag);
421
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
422
- },
423
- write(account, payload) {
424
- const key = getEncryptedFileKey();
425
- const iv = randomBytes(12);
426
- const cipher = createCipheriv('aes-256-gcm', key, iv);
427
- cipher.setAAD(Buffer.from(`${ENCRYPTED_FILE_AAD_CONTEXT}\0${account}`, 'utf8'));
428
- const ciphertext = Buffer.concat([cipher.update(payload, 'utf8'), cipher.final()]);
429
- const envelope = JSON.stringify({
430
- version: 1,
431
- algorithm: 'aes-256-gcm',
432
- iv: iv.toString('base64'),
433
- ciphertext: ciphertext.toString('base64'),
434
- tag: cipher.getAuthTag().toString('base64'),
435
- });
436
- const dir = join(root, account);
437
- ensurePrivateDirectory(root);
438
- ensurePrivateDirectory(dir);
439
- const destination = encryptedEntryPath(root, account);
440
- validatePrivateRegularFile(destination);
441
- const temporary = join(dir, `.credentials-${randomBytes(12).toString('hex')}.tmp`);
442
- let fd: number | undefined;
443
- try {
444
- fd = openSync(temporary, 'wx', 0o600);
445
- writeFileSync(fd, envelope, 'utf8');
446
- fsyncSync(fd);
447
- closeSync(fd);
448
- fd = undefined;
449
- renameSync(temporary, destination);
450
- } catch (error) {
451
- if (fd !== undefined) {
452
- try { closeSync(fd); } catch {}
453
- }
454
- try { rmSync(temporary, { force: true }); } catch {}
455
- throw error;
456
- }
457
- },
458
- remove(account) {
459
- getEncryptedFileKey();
460
- const path = encryptedEntryPath(root, account);
461
- if (!validatePrivateRegularFile(path)) return;
462
- rmSync(path);
463
- try { rmSync(dirname(path)); } catch {}
464
- },
465
- };
466
- }
467
-
468
- function getAuthSecretStore(options: AuthStorageOptions = {}): AuthSecretStore {
469
- if (options.credentialStore === 'encrypted-file') return createEncryptedFileAuthSecretStore();
470
- if (process.env[TEST_AUTH_STORE_ENV] === 'memory') return memoryAuthSecretStore;
471
- if (process.env[TEST_AUTH_STORE_ENV] === 'sizelimited') return sizeLimitedAuthSecretStore;
472
- if (process.env[TEST_AUTH_STORE_ENV] === 'writefailing') return writeFailingAuthSecretStore;
473
- if (process.env[TEST_AUTH_STORE_ENV] === 'unavailable') return unavailableAuthSecretStore;
474
- if (process.env[TEST_AUTH_STORE_ENV] === 'keyrevoked') return keyRevokedAuthSecretStore;
475
- return keyringAuthSecretStore;
476
- }
477
-
478
- function getKeyringEntry(account: string): KeyringEntry {
479
- try {
480
- KeyringEntryClass ??= loadKeyringEntryClass();
481
- return new KeyringEntryClass(AUTH_SECRET_SERVICE, account);
482
- } catch (error) {
483
- throw new Error('OAuth secure credential storage is unavailable. Configure the OS credential store and retry authentication.', { cause: error });
484
- }
485
- }
486
-
487
- function loadKeyringEntryClass(keyringRequire: KeyringRequire = require, platform: NodeJS.Platform = process.platform, arch: NodeJS.Architecture = process.arch): KeyringEntryConstructor {
488
- try {
489
- return (keyringRequire('@napi-rs/keyring') as KeyringModule).Entry;
490
- } catch (loaderError) {
491
- try {
492
- return loadKeyringNativeBindingFallback(keyringRequire, platform, arch).Entry;
493
- } catch (fallbackError) {
494
- throw new Error(`Failed to load @napi-rs/keyring; absolute-path native binding fallback also failed: ${formatErrorMessage(fallbackError)}`, {
495
- cause: loaderError,
496
- });
497
- }
498
- }
499
- }
500
-
501
- function loadKeyringNativeBindingFallback(keyringRequire: KeyringRequire, platform: NodeJS.Platform, arch: NodeJS.Architecture): KeyringModule {
502
- const targets = getKeyringNativeBindingTargets(platform, arch);
503
- if (targets.length === 0) {
504
- throw new Error(`Unsupported @napi-rs/keyring native binding target: ${platform}-${arch}`);
505
- }
506
-
507
- let lastError: unknown;
508
- for (const target of targets) {
509
- try {
510
- const packageJsonPath = keyringRequire.resolve(`${target.packageName}/package.json`);
511
- return keyringRequire(join(dirname(packageJsonPath), target.bindingFile)) as KeyringModule;
512
- } catch (error) {
513
- lastError = error;
514
- }
515
- }
516
-
517
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
518
- }
519
-
520
- function getKeyringNativeBindingTargets(platform: NodeJS.Platform, arch: NodeJS.Architecture): { packageName: string; bindingFile: string }[] {
521
- return getKeyringNativeBindingSuffixes(platform, arch).map(suffix => ({
522
- packageName: `@napi-rs/keyring-${suffix}`,
523
- bindingFile: `keyring.${suffix}.node`,
524
- }));
525
- }
526
-
527
- function getKeyringNativeBindingSuffixes(platform: NodeJS.Platform, arch: NodeJS.Architecture): string[] {
528
- if (platform === 'darwin') {
529
- if (arch === 'arm64') return ['darwin-arm64'];
530
- if (arch === 'x64') return ['darwin-x64'];
531
- }
532
- if (platform === 'win32') {
533
- if (arch === 'arm64') return ['win32-arm64-msvc'];
534
- if (arch === 'x64') return ['win32-x64-msvc'];
535
- if (arch === 'ia32') return ['win32-ia32-msvc'];
536
- }
537
- if (platform === 'linux') {
538
- if (arch === 'arm64') return ['linux-arm64-gnu', 'linux-arm64-musl'];
539
- if (arch === 'arm') return ['linux-arm-gnueabihf'];
540
- if (arch === 'riscv64') return ['linux-riscv64-gnu'];
541
- if (arch === 'x64') return ['linux-x64-gnu', 'linux-x64-musl'];
542
- }
543
- if (platform === 'freebsd' && arch === 'x64') return ['freebsd-x64'];
544
- return [];
545
- }
546
-
547
- function formatErrorMessage(error: unknown): string {
548
- return error instanceof Error ? error.message : String(error);
549
- }
550
-
551
- type KeyringRecoveryOperation = 'read' | 'write' | 'remove';
552
-
553
- type KeyringRecoveryResponse =
554
- | { ok: true; found?: boolean; value?: string }
555
- | { ok: false; error?: string };
556
-
557
- function isLinuxKeyringRecoveryEnabled(): boolean {
558
- if (process.env[KEYRING_RECOVERY_DISABLED_ENV] === '1') return false;
559
- return process.platform === 'linux' || process.env[TEST_LINUX_KEYRING_RECOVERY_ENV] === '1';
560
- }
561
-
562
- function shouldAttemptLinuxKeyringRecovery(error: unknown): boolean {
563
- return isLinuxKeyringRecoveryEnabled()
564
- && causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i);
565
- }
566
-
567
- function runLinuxKeyringRecoveryOperation(operation: KeyringRecoveryOperation, account: string, payload?: string): KeyringRecoveryResponse {
568
- const keyctl = process.env[KEYRING_RECOVERY_KEYCTL_ENV]?.trim() || 'keyctl';
569
- const node = process.env[KEYRING_RECOVERY_NODE_ENV]?.trim() || 'node';
570
- const helper = process.env[KEYRING_RECOVERY_HELPER_ENV]?.trim()
571
- || fileURLToPath(new URL('./mcp-keyring-helper.cjs', import.meta.url));
572
- const request = JSON.stringify({ operation, service: AUTH_SECRET_SERVICE, account, payload });
573
- const result = spawnSync(keyctl, ['session', '-', node, helper], {
574
- input: `${request}\n`,
575
- encoding: 'utf8',
576
- maxBuffer: 1024 * 1024,
577
- timeout: KEYRING_RECOVERY_TIMEOUT_MS,
578
- windowsHide: true,
579
- });
580
-
581
- if (result.error) {
582
- throw new Error(`Linux keyring recovery helper could not start: ${result.error.message}`, { cause: result.error });
583
- }
584
- if (result.status !== 0) {
585
- throw new Error(`Linux keyring recovery helper failed with exit code ${result.status ?? 'unknown'}`);
586
- }
587
-
588
- let response: unknown;
589
- try {
590
- response = JSON.parse(result.stdout.trim()) as unknown;
591
- } catch (error) {
592
- throw new Error('Linux keyring recovery helper returned invalid JSON', { cause: error });
593
- }
594
- if (typeof response !== 'object' || response === null || typeof (response as { ok?: unknown }).ok !== 'boolean') {
595
- throw new Error('Linux keyring recovery helper returned an invalid response');
596
- }
597
- const typedResponse = response as KeyringRecoveryResponse;
598
- if (typedResponse.ok === false) {
599
- throw new Error(typedResponse.error || 'Linux keyring recovery helper failed');
600
- }
601
- if (operation === 'read' && typedResponse.found === true && typeof typedResponse.value !== 'string') {
602
- throw new Error('Linux keyring recovery helper returned an invalid read response');
603
- }
604
- return typedResponse;
605
- }
606
-
607
- const linuxKeyringRecoveryAuthSecretStore: AuthSecretStore = {
608
- read(account) {
609
- const response = runLinuxKeyringRecoveryOperation('read', account);
610
- return response.ok && response.found === true ? response.value : undefined;
611
- },
612
- write(account, payload) {
613
- runLinuxKeyringRecoveryOperation('write', account, payload);
614
- },
615
- remove(account) {
616
- runLinuxKeyringRecoveryOperation('remove', account);
617
- },
618
- };
619
-
620
- export function loadTestKeyringEntryClass(keyringRequire: KeyringRequire, platform: NodeJS.Platform, arch: NodeJS.Architecture): KeyringEntryConstructor {
621
- return loadKeyringEntryClass(keyringRequire, platform, arch);
622
- }
623
-
624
- export function getAuthStorageOptions(oauthDir: unknown, cwd = process.cwd(), oauthCredentialStore?: unknown): AuthStorageOptions {
625
- if (oauthCredentialStore !== undefined && oauthCredentialStore !== 'encrypted-file') {
626
- throw new Error('settings.oauthCredentialStore must be "encrypted-file" when set');
627
- }
628
- if (oauthCredentialStore === 'encrypted-file') return { credentialStore: 'encrypted-file' };
629
- const baseDir = resolveConfiguredOAuthDir(oauthDir, cwd);
630
- return baseDir ? { baseDir } : {};
631
- }
632
-
633
- export function getAuthBaseDir(options: AuthStorageOptions = {}): string {
634
- const override = process.env.MCP_OAUTH_DIR?.trim();
635
- if (override) return override;
636
- return options.baseDir ?? getAgentPath('mcp-oauth');
637
- }
638
-
639
- /**
640
- * Get the legacy server-specific directory path.
641
- */
642
- function getServerDir(serverName: string, options?: AuthStorageOptions): string {
643
- if (typeof serverName !== 'string') {
644
- throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
645
- }
646
- const storageKey = getAuthEntryAccount(serverName);
647
- return join(getAuthBaseDir(options), storageKey);
648
- }
649
-
650
- function getAuthEntryAccount(serverName: string): string {
651
- if (typeof serverName !== 'string') {
652
- throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
653
- }
654
- return `sha256-${createHash('sha256').update(serverName, 'utf8').digest('hex')}`;
655
- }
656
-
657
- /**
658
- * Get the legacy plaintext tokens file path for a server.
659
- */
660
- export function getAuthEntryFilePath(serverName: string, options?: AuthStorageOptions): string {
661
- return join(getServerDir(serverName, options), 'tokens.json');
662
- }
663
-
664
- function parseJsonPayload(serverName: string, payload: string, source: string): unknown {
665
- try {
666
- return JSON.parse(payload) as unknown;
667
- } catch (error) {
668
- throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}`, { cause: error });
669
- }
670
- }
671
-
672
- function parseAuthEntryPayload(serverName: string, payload: string, source: string): AuthEntry {
673
- const parsed = parseJsonPayload(serverName, payload, source);
674
- const entry = toAuthEntry(parsed);
675
- if (!entry) {
676
- throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}: invalid credential shape`);
677
- }
678
- return entry;
679
- }
680
-
681
- function toAuthEntry(value: unknown): AuthEntry | undefined {
682
- const entry = toRecord(value);
683
- if (!entry) return undefined;
684
-
685
- const codeVerifier = optionalString(entry.codeVerifier);
686
- const oauthState = optionalString(entry.oauthState);
687
- const serverUrl = optionalString(entry.serverUrl);
688
- if (codeVerifier === null || oauthState === null || serverUrl === null) return undefined;
689
-
690
- const tokens = entry.tokens === undefined ? undefined : toStoredTokens(entry.tokens);
691
- const clientInfo = entry.clientInfo === undefined ? undefined : toStoredClientInfo(entry.clientInfo);
692
- if ((entry.tokens !== undefined && !tokens) || (entry.clientInfo !== undefined && !clientInfo)) return undefined;
693
-
694
- const authEntry: AuthEntry = {};
695
- if (tokens) authEntry.tokens = tokens;
696
- if (clientInfo) authEntry.clientInfo = clientInfo;
697
- if (codeVerifier !== undefined) authEntry.codeVerifier = codeVerifier;
698
- if (oauthState !== undefined) authEntry.oauthState = oauthState;
699
- if (serverUrl !== undefined) authEntry.serverUrl = serverUrl;
700
- return authEntry;
701
- }
702
-
703
- function toStoredTokens(value: unknown): StoredTokens | undefined {
704
- const tokens = toRecord(value);
705
- if (!tokens || typeof tokens.accessToken !== 'string') return undefined;
706
-
707
- const refreshToken = optionalString(tokens.refreshToken);
708
- const scope = optionalString(tokens.scope);
709
- const issuer = optionalString(tokens.issuer);
710
- const expiresAt = optionalNumber(tokens.expiresAt);
711
- if (refreshToken === null || scope === null || issuer === null || expiresAt === null) return undefined;
712
-
713
- const storedTokens: StoredTokens = { accessToken: tokens.accessToken };
714
- if (refreshToken !== undefined) storedTokens.refreshToken = refreshToken;
715
- if (expiresAt !== undefined) storedTokens.expiresAt = expiresAt;
716
- if (scope !== undefined) storedTokens.scope = scope;
717
- if (issuer !== undefined) storedTokens.issuer = issuer;
718
- return storedTokens;
719
- }
720
-
721
- function toStoredClientInfo(value: unknown): StoredClientInfo | undefined {
722
- const clientInfo = toRecord(value);
723
- if (!clientInfo || typeof clientInfo.clientId !== 'string') return undefined;
724
-
725
- const clientSecret = optionalString(clientInfo.clientSecret);
726
- const issuer = optionalString(clientInfo.issuer);
727
- const clientIdIssuedAt = optionalNumber(clientInfo.clientIdIssuedAt);
728
- const clientSecretExpiresAt = optionalNumber(clientInfo.clientSecretExpiresAt);
729
- const configPreRegistered = optionalBoolean(clientInfo.configPreRegistered);
730
- if (clientSecret === null || issuer === null || clientIdIssuedAt === null || clientSecretExpiresAt === null || configPreRegistered === null) return undefined;
731
-
732
- const storedClient: StoredClientInfo = { clientId: clientInfo.clientId };
733
- const redirectUris = stringArray(clientInfo.redirectUris);
734
- if (clientSecret !== undefined) storedClient.clientSecret = clientSecret;
735
- if (clientIdIssuedAt !== undefined) storedClient.clientIdIssuedAt = clientIdIssuedAt;
736
- if (clientSecretExpiresAt !== undefined) storedClient.clientSecretExpiresAt = clientSecretExpiresAt;
737
- if (redirectUris !== undefined) storedClient.redirectUris = redirectUris;
738
- if (issuer !== undefined) storedClient.issuer = issuer;
739
- if (configPreRegistered !== undefined) storedClient.configPreRegistered = configPreRegistered;
740
- return storedClient;
741
- }
742
-
743
- function toRecord(value: unknown): Record<string, unknown> | undefined {
744
- return typeof value === 'object' && value !== null && !Array.isArray(value)
745
- ? value as Record<string, unknown>
746
- : undefined;
747
- }
748
-
749
- function optionalString(value: unknown): string | null | undefined {
750
- if (value === undefined) return undefined;
751
- return typeof value === 'string' ? value : null;
752
- }
753
-
754
- function optionalNumber(value: unknown): number | null | undefined {
755
- if (value === undefined) return undefined;
756
- return typeof value === 'number' ? value : null;
757
- }
758
-
759
- function optionalBoolean(value: unknown): boolean | null | undefined {
760
- if (value === undefined) return undefined;
761
- return typeof value === 'boolean' ? value : null;
762
- }
763
-
764
- function stringArray(value: unknown): string[] | undefined {
765
- return Array.isArray(value) && value.every(uri => typeof uri === 'string') ? value : undefined;
766
- }
767
-
768
- function isAuthEntryChunkManifest(value: unknown): value is AuthEntryChunkManifest {
769
- if (typeof value !== 'object' || value === null) return false;
770
- const manifest = value as Partial<AuthEntryChunkManifest>;
771
- return manifest[AUTH_CHUNK_MANIFEST_KEY] === 1
772
- && typeof manifest.chunkCount === 'number'
773
- && Number.isInteger(manifest.chunkCount)
774
- && manifest.chunkCount > 0
775
- && typeof manifest.chunkDigest === 'string'
776
- && /^[a-f0-9]{16}$/.test(manifest.chunkDigest);
777
- }
778
-
779
- function getAuthEntryChunkAccount(account: string, manifest: AuthEntryChunkManifest, index: number): string {
780
- return `${account}.chunk.${manifest.chunkDigest}.${index}`;
781
- }
782
-
783
- function getAuthEntryChunkAccounts(account: string, manifest: AuthEntryChunkManifest): string[] {
784
- return Array.from({ length: manifest.chunkCount }, (_, index) => getAuthEntryChunkAccount(account, manifest, index));
785
- }
786
-
787
- function readChunkManifestFromPayload(serverName: string, payload: string, source: string): AuthEntryChunkManifest | undefined {
788
- const parsed = parseJsonPayload(serverName, payload, source);
789
- return isAuthEntryChunkManifest(parsed) ? parsed : undefined;
790
- }
791
-
792
- function readExistingChunkManifest(store: AuthSecretStore, serverName: string, account: string): AuthEntryChunkManifest | undefined {
793
- try {
794
- const payload = store.read(account);
795
- return payload === undefined ? undefined : readChunkManifestFromPayload(serverName, payload, authSecretStoreLabel(store));
796
- } catch {
797
- return undefined;
798
- }
799
- }
800
-
801
- function removeChunkPayloads(store: AuthSecretStore, account: string, manifest: AuthEntryChunkManifest): void {
802
- for (const chunkAccount of getAuthEntryChunkAccounts(account, manifest)) {
803
- store.remove(chunkAccount);
804
- }
805
- }
806
-
807
- function tryRemoveChunkPayloads(store: AuthSecretStore, account: string, manifest: AuthEntryChunkManifest | undefined): void {
808
- if (!manifest) return;
809
- try {
810
- removeChunkPayloads(store, account, manifest);
811
- } catch {
812
- // Stale chunk cleanup must not hide a successful credential write.
813
- }
814
- }
815
-
816
- function shouldChunkAuthPayload(store: AuthSecretStore, payload: string): boolean {
817
- return store.kind !== 'encrypted-file' && payload.length > AUTH_SECRET_CHUNK_SIZE
818
- && (process.platform === 'win32' || process.env[TEST_AUTH_STORE_ENV] === 'sizelimited');
819
- }
820
-
821
- function getAuthEntryChunkDigest(payload: string): string {
822
- return createHash('sha256').update(payload, 'utf8').digest('hex').slice(0, 16);
823
- }
824
-
825
- function splitAuthPayload(payload: string): string[] {
826
- const chunks: string[] = [];
827
- for (let start = 0; start < payload.length;) {
828
- let end = Math.min(start + AUTH_SECRET_CHUNK_SIZE, payload.length);
829
- const lastCodeUnit = payload.charCodeAt(end - 1);
830
- if (end < payload.length && lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff) end--;
831
- chunks.push(payload.slice(start, end));
832
- start = end;
833
- }
834
- return chunks;
835
- }
836
-
837
- function createChunkManifest(payload: string, chunkCount: number): AuthEntryChunkManifest {
838
- return {
839
- [AUTH_CHUNK_MANIFEST_KEY]: 1,
840
- chunkCount,
841
- chunkDigest: getAuthEntryChunkDigest(payload),
842
- };
843
- }
844
-
845
- function readChunkedAuthEntry(store: AuthSecretStore, serverName: string, account: string, manifest: AuthEntryChunkManifest): AuthEntry {
846
- let payload: string;
847
- try {
848
- payload = getAuthEntryChunkAccounts(account, manifest).map((chunkAccount) => {
849
- const chunk = store.read(chunkAccount);
850
- if (chunk === undefined) {
851
- throw new Error(`Missing OAuth credential chunk ${chunkAccount} for ${serverName}`);
852
- }
853
- return chunk;
854
- }).join('');
855
- if (getAuthEntryChunkDigest(payload) !== manifest.chunkDigest) {
856
- throw new Error('OAuth credential chunk integrity check failed');
857
- }
858
- } catch (error) {
859
- throw new OAuthCredentialStoreError(
860
- `Failed to read OAuth credentials for ${serverName} from the ${authSecretStoreLabel(store)}`,
861
- 'read',
862
- error,
863
- );
864
- }
865
- return parseAuthEntryPayload(serverName, payload, `${authSecretStoreLabel(store)} chunks`);
866
- }
867
-
868
- function readLegacyAuthEntry(serverName: string, options?: AuthStorageOptions): AuthEntry | undefined {
869
- const filePath = getAuthEntryFilePath(serverName, options);
870
- if (!existsSync(filePath)) return undefined;
871
- const data = readFileSync(filePath, 'utf-8');
872
- return parseAuthEntryPayload(serverName, data, filePath);
873
- }
874
-
875
- function removeLegacyAuthEntry(serverName: string, options?: AuthStorageOptions): void {
876
- const filePath = getAuthEntryFilePath(serverName, options);
877
- if (!existsSync(filePath)) return;
878
- try {
879
- rmSync(filePath, { force: true });
880
- } catch (error) {
881
- throw new Error(`Failed to remove legacy plaintext OAuth credentials for ${serverName} at ${filePath}`, { cause: error });
882
- }
883
-
884
- const dir = getServerDir(serverName, options);
885
- try {
886
- rmSync(dir, { recursive: true });
887
- } catch {
888
- // Directory may contain future non-secret metadata; the plaintext file was already removed.
889
- }
890
- }
891
-
892
- function writeSecureAuthEntryToStore(store: AuthSecretStore, serverName: string, entry: AuthEntry): void {
893
- const account = getAuthEntryAccount(serverName);
894
- const payload = JSON.stringify(entry);
895
- const previousManifest = readExistingChunkManifest(store, serverName, account);
896
- const chunks = shouldChunkAuthPayload(store, payload) ? splitAuthPayload(payload) : undefined;
897
- const manifest = chunks ? createChunkManifest(payload, chunks.length) : undefined;
898
-
899
- try {
900
- if (manifest && chunks) {
901
- for (const [index, chunk] of chunks.entries()) {
902
- store.write(getAuthEntryChunkAccount(account, manifest, index), chunk);
903
- }
904
- store.write(account, JSON.stringify(manifest));
905
- } else {
906
- // Compact: multiline secrets corrupt gnome-keyring plaintext (GKeyFile) collections.
907
- store.write(account, payload);
908
- }
909
- if (previousManifest?.chunkDigest !== manifest?.chunkDigest) {
910
- tryRemoveChunkPayloads(store, account, previousManifest);
911
- }
912
- } catch (error) {
913
- tryRemoveChunkPayloads(store, account, manifest);
914
- throw new OAuthCredentialStoreError(
915
- `Failed to write OAuth credentials for ${serverName} to the ${authSecretStoreLabel(store)}`,
916
- 'write',
917
- error,
918
- store.kind,
919
- );
920
- }
921
- }
922
-
923
- function authEntryCacheKey(
924
- serverName: string,
925
- options: AuthStorageOptions = {},
926
- operation: 'read' | 'write' | 'remove' = 'read',
927
- ): string {
928
- if (options.credentialStore !== 'encrypted-file') return `os\0${serverName}`;
929
- try {
930
- const generation = createHash('sha256').update(getEncryptedFileKey()).digest('hex');
931
- return `encrypted-file:${generation}\0${serverName}`;
932
- } catch (error) {
933
- throw new OAuthCredentialStoreError(
934
- `Failed to ${operation} OAuth credentials for ${serverName} with the encrypted OAuth credential file store`,
935
- operation,
936
- error,
937
- 'encrypted-file',
938
- );
939
- }
940
- }
941
-
942
- function publishAuthEntryToCache(serverName: string, payload: string, options?: AuthStorageOptions): void {
943
- if (!isAuthEntryCacheEnabled()) return;
944
- const cacheKey = authEntryCacheKey(serverName, options, 'write');
945
- // Cache the same normalized shape a fresh persistent-store read returns.
946
- const normalized = toAuthEntry(JSON.parse(payload) as unknown);
947
- if (!normalized) {
948
- authEntryCache.delete(cacheKey);
949
- return;
950
- }
951
- authEntryCache.set(cacheKey, cloneAuthEntry(normalized));
952
- }
953
-
954
- function writeSecureAuthEntry(serverName: string, entry: AuthEntry, options?: AuthStorageOptions): void {
955
- try {
956
- writeSecureAuthEntryToStore(getAuthSecretStore(options), serverName, entry);
957
- } catch (error) {
958
- if (options?.credentialStore === 'encrypted-file' || !shouldAttemptLinuxKeyringRecovery(error)) throw error;
959
- writeSecureAuthEntryToStore(linuxKeyringRecoveryAuthSecretStore, serverName, entry);
960
- }
961
- publishAuthEntryToCache(serverName, JSON.stringify(entry), options);
962
- }
963
-
964
- /**
965
- * Read from the selected store. The OS backend imports and deletes a legacy
966
- * plaintext entry when present.
967
- */
968
- function readAuthEntryFromStore(
969
- store: AuthSecretStore,
970
- serverName: string,
971
- options?: AuthStorageOptions,
972
- behavior: { migrateLegacy?: boolean } = {},
973
- ): AuthEntry | undefined {
974
- const account = getAuthEntryAccount(serverName);
975
- let payload: string | undefined;
976
- try {
977
- payload = store.read(account);
978
- } catch (error) {
979
- throw new OAuthCredentialStoreError(
980
- `Failed to read OAuth credentials for ${serverName} from the ${authSecretStoreLabel(store)}`,
981
- 'read',
982
- error,
983
- store.kind,
984
- );
985
- }
986
-
987
- if (payload !== undefined) {
988
- const manifest = store.kind === 'encrypted-file'
989
- ? undefined
990
- : readChunkManifestFromPayload(serverName, payload, authSecretStoreLabel(store));
991
- const entry = manifest
992
- ? readChunkedAuthEntry(store, serverName, account, manifest)
993
- : parseAuthEntryPayload(serverName, payload, authSecretStoreLabel(store));
994
- if (store.kind !== 'encrypted-file') removeLegacyAuthEntry(serverName, options);
995
- if (manifest && behavior.migrateLegacy !== false && !shouldChunkAuthPayload(store, JSON.stringify(entry))) {
996
- writeSecureAuthEntryToStore(store, serverName, entry);
997
- }
998
- return entry;
999
- }
1000
-
1001
- if (store.kind === 'encrypted-file') return undefined;
1002
- const legacyEntry = readLegacyAuthEntry(serverName, options);
1003
- if (!legacyEntry) return undefined;
1004
- if (behavior.migrateLegacy === false) return legacyEntry;
1005
- writeSecureAuthEntryToStore(store, serverName, legacyEntry);
1006
- removeLegacyAuthEntry(serverName, options);
1007
- return legacyEntry;
1008
- }
1009
-
1010
- function readAuthEntry(
1011
- serverName: string,
1012
- options?: AuthStorageOptions,
1013
- behavior: { migrateLegacy?: boolean } = {},
1014
- ): AuthEntry | undefined {
1015
- // Status-only reads deliberately bypass the cache because they do not
1016
- // migrate legacy entries.
1017
- const cacheable = behavior.migrateLegacy !== false && isAuthEntryCacheEnabled();
1018
- const cacheKey = authEntryCacheKey(serverName, options);
1019
- if (cacheable && authEntryCache.has(cacheKey)) {
1020
- return cloneAuthEntry(authEntryCache.get(cacheKey));
1021
- }
1022
-
1023
- let entry: AuthEntry | undefined;
1024
- try {
1025
- entry = readAuthEntryFromStore(getAuthSecretStore(options), serverName, options, behavior);
1026
- } catch (error) {
1027
- if (options?.credentialStore === 'encrypted-file' || !shouldAttemptLinuxKeyringRecovery(error)) throw error;
1028
- entry = readAuthEntryFromStore(linuxKeyringRecoveryAuthSecretStore, serverName, options, behavior);
1029
- }
1030
-
1031
- if (cacheable) authEntryCache.set(cacheKey, cloneAuthEntry(entry));
1032
- return entry;
1033
- }
1034
-
1035
- /**
1036
- * Get auth entry for a server.
1037
- */
1038
- export function getAuthEntry(serverName: string, options?: AuthStorageOptions): AuthEntry | undefined {
1039
- return readAuthEntry(serverName, options);
1040
- }
1041
-
1042
- /**
1043
- * Get auth entry and validate it's for the correct URL.
1044
- * Returns undefined if URL has changed (credentials are invalid).
1045
- */
1046
- export function getAuthForUrl(serverName: string, serverUrl: string, options?: AuthStorageOptions): AuthEntry | undefined {
1047
- const entry = getAuthEntry(serverName, options);
1048
- if (!entry) return undefined;
1049
-
1050
- // If no serverUrl is stored, this is from an old version - consider it invalid
1051
- if (!entry.serverUrl) return undefined;
1052
-
1053
- // If URL has changed, credentials are invalid
1054
- if (entry.serverUrl !== serverUrl) return undefined;
1055
-
1056
- return entry;
1057
- }
1058
-
1059
- /**
1060
- * Inspect credentials for status-only UI paths without treating an unavailable
1061
- * secure store as missing credentials. Authentication operations continue to
1062
- * use getAuthForUrl() directly and therefore remain fail-closed.
1063
- */
1064
- export function inspectAuthForUrl(
1065
- serverName: string,
1066
- serverUrl: string,
1067
- options?: AuthStorageOptions,
1068
- ): OAuthCredentialStatus {
1069
- try {
1070
- const entry = readAuthEntry(serverName, options, { migrateLegacy: false });
1071
- if (!entry?.serverUrl || entry.serverUrl !== serverUrl) return { status: 'absent' };
1072
- return { status: 'present', entry };
1073
- } catch (error) {
1074
- if (!(error instanceof OAuthCredentialStoreError)) throw error;
1075
- return { status: 'unavailable', message: formatOAuthCredentialStoreUnavailable(error) };
1076
- }
1077
- }
1078
-
1079
- /**
1080
- * Save auth entry for a server.
1081
- */
1082
- export function saveAuthEntry(serverName: string, entry: AuthEntry, serverUrl?: string, options?: AuthStorageOptions): void {
1083
- // Always update serverUrl if provided
1084
- if (serverUrl) {
1085
- entry.serverUrl = serverUrl;
1086
- }
1087
- writeSecureAuthEntry(serverName, entry, options);
1088
- if (options?.credentialStore !== 'encrypted-file') removeLegacyAuthEntry(serverName, options);
1089
- }
1090
-
1091
- /**
1092
- * Remove auth entry for a server.
1093
- */
1094
- function removeAuthEntryFromStore(store: AuthSecretStore, serverName: string): void {
1095
- const account = getAuthEntryAccount(serverName);
1096
- try {
1097
- if (store.kind === 'encrypted-file') {
1098
- store.remove(account);
1099
- return;
1100
- }
1101
- const payload = store.read(account);
1102
- const manifest = payload === undefined ? undefined : readChunkManifestFromPayload(serverName, payload, authSecretStoreLabel(store));
1103
- if (manifest) removeChunkPayloads(store, account, manifest);
1104
- store.remove(account);
1105
- } catch (error) {
1106
- throw new OAuthCredentialStoreError(
1107
- `Failed to remove OAuth credentials for ${serverName} from the ${authSecretStoreLabel(store)}`,
1108
- 'remove',
1109
- error,
1110
- store.kind,
1111
- );
1112
- }
1113
- }
1114
-
1115
- export function removeAuthEntry(serverName: string, options?: AuthStorageOptions): void {
1116
- try {
1117
- removeAuthEntryFromStore(getAuthSecretStore(options), serverName);
1118
- } catch (error) {
1119
- if (options?.credentialStore === 'encrypted-file' || !shouldAttemptLinuxKeyringRecovery(error)) throw error;
1120
- removeAuthEntryFromStore(linuxKeyringRecoveryAuthSecretStore, serverName);
1121
- }
1122
- evictAuthEntryCache(serverName, options);
1123
- if (options?.credentialStore !== 'encrypted-file') removeLegacyAuthEntry(serverName, options);
1124
- }
1125
-
1126
- function evictAuthEntryCache(serverName: string, options: AuthStorageOptions = {}): void {
1127
- if (options.credentialStore !== 'encrypted-file') {
1128
- authEntryCache.delete(authEntryCacheKey(serverName, options, 'remove'));
1129
- return;
1130
- }
1131
- for (const key of authEntryCache.keys()) {
1132
- const separator = key.indexOf('\0');
1133
- if (key.startsWith('encrypted-file:') && separator !== -1 && key.slice(separator + 1) === serverName) authEntryCache.delete(key);
1134
- }
1135
- }
1136
-
1137
- /**
1138
- * Forget a cached entry so the next ordinary read reloads secure storage.
1139
- */
1140
- export function invalidateAuthEntryCache(serverName: string): void {
1141
- evictAuthEntryCache(serverName);
1142
- evictAuthEntryCache(serverName, { credentialStore: 'encrypted-file' });
1143
- }
1144
-
1145
- /**
1146
- * Update tokens for a server.
1147
- */
1148
- export function updateTokens(
1149
- serverName: string,
1150
- tokens: StoredTokens,
1151
- serverUrl?: string,
1152
- options?: AuthStorageOptions
1153
- ): void {
1154
- const entry = getAuthEntry(serverName, options) ?? {};
1155
- if (serverUrl && entry.serverUrl !== serverUrl) {
1156
- delete entry.clientInfo;
1157
- delete entry.codeVerifier;
1158
- delete entry.oauthState;
1159
- }
1160
- entry.tokens = tokens;
1161
- saveAuthEntry(serverName, entry, serverUrl, options);
1162
- }
1163
-
1164
- /**
1165
- * Update client info for a server.
1166
- */
1167
- export function updateClientInfo(
1168
- serverName: string,
1169
- clientInfo: StoredClientInfo,
1170
- serverUrl?: string,
1171
- options?: AuthStorageOptions
1172
- ): void {
1173
- const entry = getAuthEntry(serverName, options) ?? {};
1174
- if (serverUrl && entry.serverUrl !== serverUrl) {
1175
- delete entry.tokens;
1176
- delete entry.codeVerifier;
1177
- delete entry.oauthState;
1178
- }
1179
- entry.clientInfo = clientInfo;
1180
- saveAuthEntry(serverName, entry, serverUrl, options);
1181
- }
1182
-
1183
- /**
1184
- * Update code verifier for a server.
1185
- */
1186
- export function updateCodeVerifier(serverName: string, codeVerifier: string, serverUrl?: string, options?: AuthStorageOptions): void {
1187
- const entry = getAuthEntry(serverName, options) ?? {};
1188
- if (serverUrl && entry.serverUrl !== serverUrl) {
1189
- delete entry.tokens;
1190
- delete entry.clientInfo;
1191
- delete entry.oauthState;
1192
- }
1193
- entry.codeVerifier = codeVerifier;
1194
- saveAuthEntry(serverName, entry, serverUrl, options);
1195
- }
1196
-
1197
- /**
1198
- * Clear code verifier for a server.
1199
- */
1200
- export function clearCodeVerifier(serverName: string, options?: AuthStorageOptions): void {
1201
- const entry = getAuthEntry(serverName, options);
1202
- if (entry) {
1203
- delete entry.codeVerifier;
1204
- saveAuthEntry(serverName, entry, undefined, options);
1205
- }
1206
- }
1207
-
1208
- /**
1209
- * Update OAuth state for a server.
1210
- */
1211
- export function updateOAuthState(serverName: string, state: string, serverUrl?: string, options?: AuthStorageOptions): void {
1212
- const entry = getAuthEntry(serverName, options) ?? {};
1213
- if (serverUrl && entry.serverUrl !== serverUrl) {
1214
- delete entry.tokens;
1215
- delete entry.clientInfo;
1216
- delete entry.codeVerifier;
1217
- }
1218
- entry.oauthState = state;
1219
- saveAuthEntry(serverName, entry, serverUrl, options);
1220
- }
1221
-
1222
- /**
1223
- * Get OAuth state for a server.
1224
- */
1225
- export function getOAuthState(serverName: string, options?: AuthStorageOptions): string | undefined {
1226
- const entry = getAuthEntry(serverName, options);
1227
- return entry?.oauthState;
1228
- }
1229
-
1230
- /**
1231
- * Clear OAuth state for a server.
1232
- */
1233
- export function clearOAuthState(serverName: string, options?: AuthStorageOptions): void {
1234
- const entry = getAuthEntry(serverName, options);
1235
- if (entry) {
1236
- delete entry.oauthState;
1237
- saveAuthEntry(serverName, entry, undefined, options);
1238
- }
1239
- }
1240
-
1241
- /**
1242
- * Check if stored tokens are expired.
1243
- * Returns null if no tokens exist, false if no expiry or not expired, true if expired.
1244
- */
1245
- export function isTokenExpired(serverName: string, options?: AuthStorageOptions): boolean | null {
1246
- const entry = getAuthEntry(serverName, options);
1247
- if (!entry?.tokens) return null;
1248
- if (!entry.tokens.expiresAt) return false;
1249
- return entry.tokens.expiresAt < Date.now() / 1000;
1250
- }
1251
-
1252
- /**
1253
- * Check if a server has stored tokens.
1254
- */
1255
- export function hasStoredTokens(serverName: string, options?: AuthStorageOptions): boolean {
1256
- const entry = getAuthEntry(serverName, options);
1257
- return !!entry?.tokens;
1258
- }
1259
-
1260
- /**
1261
- * Clear all credentials for a server.
1262
- */
1263
- export function clearAllCredentials(serverName: string, options?: AuthStorageOptions): void {
1264
- removeAuthEntry(serverName, options);
1265
- }
1266
-
1267
- /**
1268
- * Clear only client info for a server.
1269
- */
1270
- export function clearClientInfo(serverName: string, options?: AuthStorageOptions): void {
1271
- const entry = getAuthEntry(serverName, options);
1272
- if (entry) {
1273
- delete entry.clientInfo;
1274
- saveAuthEntry(serverName, entry, undefined, options);
1275
- }
1276
- }
1277
-
1278
- /**
1279
- * Clear only tokens for a server.
1280
- */
1281
- export function clearTokens(serverName: string, options?: AuthStorageOptions): void {
1282
- const entry = getAuthEntry(serverName, options);
1283
- if (entry) {
1284
- delete entry.tokens;
1285
- saveAuthEntry(serverName, entry, undefined, options);
1286
- }
1287
- }