@amodalai/amodal 0.1.2

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 (511) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +42 -0
  4. package/dist/src/auth/index.d.ts +13 -0
  5. package/dist/src/auth/index.d.ts.map +1 -0
  6. package/dist/src/auth/index.js +10 -0
  7. package/dist/src/auth/index.js.map +1 -0
  8. package/dist/src/auth/oauth2.d.ts +51 -0
  9. package/dist/src/auth/oauth2.d.ts.map +1 -0
  10. package/dist/src/auth/oauth2.js +196 -0
  11. package/dist/src/auth/oauth2.js.map +1 -0
  12. package/dist/src/auth/prompt.d.ts +21 -0
  13. package/dist/src/auth/prompt.d.ts.map +1 -0
  14. package/dist/src/auth/prompt.js +81 -0
  15. package/dist/src/auth/prompt.js.map +1 -0
  16. package/dist/src/auth/test-connection.d.ts +27 -0
  17. package/dist/src/auth/test-connection.d.ts.map +1 -0
  18. package/dist/src/auth/test-connection.js +153 -0
  19. package/dist/src/auth/test-connection.js.map +1 -0
  20. package/dist/src/auth/types.d.ts +32 -0
  21. package/dist/src/auth/types.d.ts.map +1 -0
  22. package/dist/src/auth/types.js +7 -0
  23. package/dist/src/auth/types.js.map +1 -0
  24. package/dist/src/commands/audit.d.ts +18 -0
  25. package/dist/src/commands/audit.d.ts.map +1 -0
  26. package/dist/src/commands/audit.js +86 -0
  27. package/dist/src/commands/audit.js.map +1 -0
  28. package/dist/src/commands/automations.d.ts +28 -0
  29. package/dist/src/commands/automations.d.ts.map +1 -0
  30. package/dist/src/commands/automations.js +179 -0
  31. package/dist/src/commands/automations.js.map +1 -0
  32. package/dist/src/commands/build-manifest-types.d.ts +33 -0
  33. package/dist/src/commands/build-manifest-types.d.ts.map +1 -0
  34. package/dist/src/commands/build-manifest-types.js +30 -0
  35. package/dist/src/commands/build-manifest-types.js.map +1 -0
  36. package/dist/src/commands/build-tools.d.ts +33 -0
  37. package/dist/src/commands/build-tools.d.ts.map +1 -0
  38. package/dist/src/commands/build-tools.js +237 -0
  39. package/dist/src/commands/build-tools.js.map +1 -0
  40. package/dist/src/commands/build.d.ts +23 -0
  41. package/dist/src/commands/build.d.ts.map +1 -0
  42. package/dist/src/commands/build.js +120 -0
  43. package/dist/src/commands/build.js.map +1 -0
  44. package/dist/src/commands/chat.d.ts +26 -0
  45. package/dist/src/commands/chat.d.ts.map +1 -0
  46. package/dist/src/commands/chat.js +123 -0
  47. package/dist/src/commands/chat.js.map +1 -0
  48. package/dist/src/commands/connect.d.ts +18 -0
  49. package/dist/src/commands/connect.d.ts.map +1 -0
  50. package/dist/src/commands/connect.js +198 -0
  51. package/dist/src/commands/connect.js.map +1 -0
  52. package/dist/src/commands/deploy.d.ts +20 -0
  53. package/dist/src/commands/deploy.d.ts.map +1 -0
  54. package/dist/src/commands/deploy.js +137 -0
  55. package/dist/src/commands/deploy.js.map +1 -0
  56. package/dist/src/commands/deployments.d.ts +17 -0
  57. package/dist/src/commands/deployments.d.ts.map +1 -0
  58. package/dist/src/commands/deployments.js +77 -0
  59. package/dist/src/commands/deployments.js.map +1 -0
  60. package/dist/src/commands/dev.d.ts +17 -0
  61. package/dist/src/commands/dev.d.ts.map +1 -0
  62. package/dist/src/commands/dev.js +109 -0
  63. package/dist/src/commands/dev.js.map +1 -0
  64. package/dist/src/commands/diff.d.ts +19 -0
  65. package/dist/src/commands/diff.d.ts.map +1 -0
  66. package/dist/src/commands/diff.js +120 -0
  67. package/dist/src/commands/diff.js.map +1 -0
  68. package/dist/src/commands/docker.d.ts +21 -0
  69. package/dist/src/commands/docker.d.ts.map +1 -0
  70. package/dist/src/commands/docker.js +215 -0
  71. package/dist/src/commands/docker.js.map +1 -0
  72. package/dist/src/commands/eval.d.ts +20 -0
  73. package/dist/src/commands/eval.d.ts.map +1 -0
  74. package/dist/src/commands/eval.js +236 -0
  75. package/dist/src/commands/eval.js.map +1 -0
  76. package/dist/src/commands/experiment.d.ts +21 -0
  77. package/dist/src/commands/experiment.d.ts.map +1 -0
  78. package/dist/src/commands/experiment.js +133 -0
  79. package/dist/src/commands/experiment.js.map +1 -0
  80. package/dist/src/commands/index.d.ts +10 -0
  81. package/dist/src/commands/index.d.ts.map +1 -0
  82. package/dist/src/commands/index.js +75 -0
  83. package/dist/src/commands/index.js.map +1 -0
  84. package/dist/src/commands/init.d.ts +17 -0
  85. package/dist/src/commands/init.d.ts.map +1 -0
  86. package/dist/src/commands/init.js +73 -0
  87. package/dist/src/commands/init.js.map +1 -0
  88. package/dist/src/commands/inspect.d.ts +22 -0
  89. package/dist/src/commands/inspect.d.ts.map +1 -0
  90. package/dist/src/commands/inspect.js +131 -0
  91. package/dist/src/commands/inspect.js.map +1 -0
  92. package/dist/src/commands/install-pkg.d.ts +29 -0
  93. package/dist/src/commands/install-pkg.d.ts.map +1 -0
  94. package/dist/src/commands/install-pkg.js +202 -0
  95. package/dist/src/commands/install-pkg.js.map +1 -0
  96. package/dist/src/commands/link.d.ts +32 -0
  97. package/dist/src/commands/link.d.ts.map +1 -0
  98. package/dist/src/commands/link.js +227 -0
  99. package/dist/src/commands/link.js.map +1 -0
  100. package/dist/src/commands/list.d.ts +19 -0
  101. package/dist/src/commands/list.d.ts.map +1 -0
  102. package/dist/src/commands/list.js +78 -0
  103. package/dist/src/commands/list.js.map +1 -0
  104. package/dist/src/commands/login.d.ts +31 -0
  105. package/dist/src/commands/login.d.ts.map +1 -0
  106. package/dist/src/commands/login.js +205 -0
  107. package/dist/src/commands/login.js.map +1 -0
  108. package/dist/src/commands/promote.d.ts +16 -0
  109. package/dist/src/commands/promote.d.ts.map +1 -0
  110. package/dist/src/commands/promote.js +55 -0
  111. package/dist/src/commands/promote.js.map +1 -0
  112. package/dist/src/commands/publish.d.ts +18 -0
  113. package/dist/src/commands/publish.d.ts.map +1 -0
  114. package/dist/src/commands/publish.js +122 -0
  115. package/dist/src/commands/publish.js.map +1 -0
  116. package/dist/src/commands/rollback.d.ts +17 -0
  117. package/dist/src/commands/rollback.d.ts.map +1 -0
  118. package/dist/src/commands/rollback.js +62 -0
  119. package/dist/src/commands/rollback.js.map +1 -0
  120. package/dist/src/commands/search.d.ts +20 -0
  121. package/dist/src/commands/search.d.ts.map +1 -0
  122. package/dist/src/commands/search.js +133 -0
  123. package/dist/src/commands/search.js.map +1 -0
  124. package/dist/src/commands/secrets.d.ts +20 -0
  125. package/dist/src/commands/secrets.d.ts.map +1 -0
  126. package/dist/src/commands/secrets.js +137 -0
  127. package/dist/src/commands/secrets.js.map +1 -0
  128. package/dist/src/commands/serve.d.ts +23 -0
  129. package/dist/src/commands/serve.d.ts.map +1 -0
  130. package/dist/src/commands/serve.js +144 -0
  131. package/dist/src/commands/serve.js.map +1 -0
  132. package/dist/src/commands/status.d.ts +16 -0
  133. package/dist/src/commands/status.d.ts.map +1 -0
  134. package/dist/src/commands/status.js +83 -0
  135. package/dist/src/commands/status.js.map +1 -0
  136. package/dist/src/commands/sync.d.ts +21 -0
  137. package/dist/src/commands/sync.d.ts.map +1 -0
  138. package/dist/src/commands/sync.js +94 -0
  139. package/dist/src/commands/sync.js.map +1 -0
  140. package/dist/src/commands/test-query.d.ts +19 -0
  141. package/dist/src/commands/test-query.d.ts.map +1 -0
  142. package/dist/src/commands/test-query.js +116 -0
  143. package/dist/src/commands/test-query.js.map +1 -0
  144. package/dist/src/commands/uninstall.d.ts +19 -0
  145. package/dist/src/commands/uninstall.d.ts.map +1 -0
  146. package/dist/src/commands/uninstall.js +84 -0
  147. package/dist/src/commands/uninstall.js.map +1 -0
  148. package/dist/src/commands/update.d.ts +21 -0
  149. package/dist/src/commands/update.d.ts.map +1 -0
  150. package/dist/src/commands/update.js +145 -0
  151. package/dist/src/commands/update.js.map +1 -0
  152. package/dist/src/commands/validate.d.ts +19 -0
  153. package/dist/src/commands/validate.d.ts.map +1 -0
  154. package/dist/src/commands/validate.js +114 -0
  155. package/dist/src/commands/validate.js.map +1 -0
  156. package/dist/src/fixtures/incident-response.d.ts +91 -0
  157. package/dist/src/fixtures/incident-response.d.ts.map +1 -0
  158. package/dist/src/fixtures/incident-response.js +208 -0
  159. package/dist/src/fixtures/incident-response.js.map +1 -0
  160. package/dist/src/main.d.ts +8 -0
  161. package/dist/src/main.d.ts.map +1 -0
  162. package/dist/src/main.js +30 -0
  163. package/dist/src/main.js.map +1 -0
  164. package/dist/src/shared/platform-client.d.ts +92 -0
  165. package/dist/src/shared/platform-client.d.ts.map +1 -0
  166. package/dist/src/shared/platform-client.js +155 -0
  167. package/dist/src/shared/platform-client.js.map +1 -0
  168. package/dist/src/shared/repo-discovery.d.ts +11 -0
  169. package/dist/src/shared/repo-discovery.d.ts.map +1 -0
  170. package/dist/src/shared/repo-discovery.js +33 -0
  171. package/dist/src/shared/repo-discovery.js.map +1 -0
  172. package/dist/src/templates/compose-template.d.ts +10 -0
  173. package/dist/src/templates/compose-template.d.ts.map +1 -0
  174. package/dist/src/templates/compose-template.js +30 -0
  175. package/dist/src/templates/compose-template.js.map +1 -0
  176. package/dist/src/templates/config-template.d.ts +14 -0
  177. package/dist/src/templates/config-template.d.ts.map +1 -0
  178. package/dist/src/templates/config-template.js +35 -0
  179. package/dist/src/templates/config-template.js.map +1 -0
  180. package/dist/src/templates/dockerfile-template.d.ts +10 -0
  181. package/dist/src/templates/dockerfile-template.d.ts.map +1 -0
  182. package/dist/src/templates/dockerfile-template.js +30 -0
  183. package/dist/src/templates/dockerfile-template.js.map +1 -0
  184. package/dist/src/templates/env-template.d.ts +12 -0
  185. package/dist/src/templates/env-template.d.ts.map +1 -0
  186. package/dist/src/templates/env-template.js +24 -0
  187. package/dist/src/templates/env-template.js.map +1 -0
  188. package/dist/src/templates/knowledge-template.d.ts +10 -0
  189. package/dist/src/templates/knowledge-template.d.ts.map +1 -0
  190. package/dist/src/templates/knowledge-template.js +24 -0
  191. package/dist/src/templates/knowledge-template.js.map +1 -0
  192. package/dist/src/templates/skill-template.d.ts +10 -0
  193. package/dist/src/templates/skill-template.d.ts.map +1 -0
  194. package/dist/src/templates/skill-template.js +27 -0
  195. package/dist/src/templates/skill-template.js.map +1 -0
  196. package/dist/src/ui/AskUserPrompt.d.ts +14 -0
  197. package/dist/src/ui/AskUserPrompt.d.ts.map +1 -0
  198. package/dist/src/ui/AskUserPrompt.js +17 -0
  199. package/dist/src/ui/AskUserPrompt.js.map +1 -0
  200. package/dist/src/ui/AssistantMessage.d.ts +14 -0
  201. package/dist/src/ui/AssistantMessage.d.ts.map +1 -0
  202. package/dist/src/ui/AssistantMessage.js +8 -0
  203. package/dist/src/ui/AssistantMessage.js.map +1 -0
  204. package/dist/src/ui/ChatApp.d.ts +15 -0
  205. package/dist/src/ui/ChatApp.d.ts.map +1 -0
  206. package/dist/src/ui/ChatApp.js +144 -0
  207. package/dist/src/ui/ChatApp.js.map +1 -0
  208. package/dist/src/ui/ConfirmationPrompt.d.ts +17 -0
  209. package/dist/src/ui/ConfirmationPrompt.d.ts.map +1 -0
  210. package/dist/src/ui/ConfirmationPrompt.js +21 -0
  211. package/dist/src/ui/ConfirmationPrompt.js.map +1 -0
  212. package/dist/src/ui/DiffRenderer.d.ts +32 -0
  213. package/dist/src/ui/DiffRenderer.d.ts.map +1 -0
  214. package/dist/src/ui/DiffRenderer.js +118 -0
  215. package/dist/src/ui/DiffRenderer.js.map +1 -0
  216. package/dist/src/ui/ExpandableContent.d.ts +15 -0
  217. package/dist/src/ui/ExpandableContent.d.ts.map +1 -0
  218. package/dist/src/ui/ExpandableContent.js +22 -0
  219. package/dist/src/ui/ExpandableContent.js.map +1 -0
  220. package/dist/src/ui/ExploreIndicator.d.ts +13 -0
  221. package/dist/src/ui/ExploreIndicator.d.ts.map +1 -0
  222. package/dist/src/ui/ExploreIndicator.js +14 -0
  223. package/dist/src/ui/ExploreIndicator.js.map +1 -0
  224. package/dist/src/ui/Footer.d.ts +19 -0
  225. package/dist/src/ui/Footer.d.ts.map +1 -0
  226. package/dist/src/ui/Footer.js +57 -0
  227. package/dist/src/ui/Footer.js.map +1 -0
  228. package/dist/src/ui/FullScreenLayout.d.ts +18 -0
  229. package/dist/src/ui/FullScreenLayout.d.ts.map +1 -0
  230. package/dist/src/ui/FullScreenLayout.js +14 -0
  231. package/dist/src/ui/FullScreenLayout.js.map +1 -0
  232. package/dist/src/ui/Header.d.ts +14 -0
  233. package/dist/src/ui/Header.d.ts.map +1 -0
  234. package/dist/src/ui/Header.js +11 -0
  235. package/dist/src/ui/Header.js.map +1 -0
  236. package/dist/src/ui/InputBar.d.ts +17 -0
  237. package/dist/src/ui/InputBar.d.ts.map +1 -0
  238. package/dist/src/ui/InputBar.js +49 -0
  239. package/dist/src/ui/InputBar.js.map +1 -0
  240. package/dist/src/ui/MessageList.d.ts +18 -0
  241. package/dist/src/ui/MessageList.d.ts.map +1 -0
  242. package/dist/src/ui/MessageList.js +9 -0
  243. package/dist/src/ui/MessageList.js.map +1 -0
  244. package/dist/src/ui/NotificationBar.d.ts +14 -0
  245. package/dist/src/ui/NotificationBar.d.ts.map +1 -0
  246. package/dist/src/ui/NotificationBar.js +38 -0
  247. package/dist/src/ui/NotificationBar.js.map +1 -0
  248. package/dist/src/ui/ScrollableMessageList.d.ts +27 -0
  249. package/dist/src/ui/ScrollableMessageList.d.ts.map +1 -0
  250. package/dist/src/ui/ScrollableMessageList.js +16 -0
  251. package/dist/src/ui/ScrollableMessageList.js.map +1 -0
  252. package/dist/src/ui/SessionBrowser.d.ts +20 -0
  253. package/dist/src/ui/SessionBrowser.d.ts.map +1 -0
  254. package/dist/src/ui/SessionBrowser.js +93 -0
  255. package/dist/src/ui/SessionBrowser.js.map +1 -0
  256. package/dist/src/ui/StatusMessage.d.ts +13 -0
  257. package/dist/src/ui/StatusMessage.d.ts.map +1 -0
  258. package/dist/src/ui/StatusMessage.js +17 -0
  259. package/dist/src/ui/StatusMessage.js.map +1 -0
  260. package/dist/src/ui/StreamingView.d.ts +19 -0
  261. package/dist/src/ui/StreamingView.d.ts.map +1 -0
  262. package/dist/src/ui/StreamingView.js +18 -0
  263. package/dist/src/ui/StreamingView.js.map +1 -0
  264. package/dist/src/ui/SubagentDisplay.d.ts +13 -0
  265. package/dist/src/ui/SubagentDisplay.d.ts.map +1 -0
  266. package/dist/src/ui/SubagentDisplay.js +22 -0
  267. package/dist/src/ui/SubagentDisplay.js.map +1 -0
  268. package/dist/src/ui/ThinkingDisplay.d.ts +13 -0
  269. package/dist/src/ui/ThinkingDisplay.d.ts.map +1 -0
  270. package/dist/src/ui/ThinkingDisplay.js +15 -0
  271. package/dist/src/ui/ThinkingDisplay.js.map +1 -0
  272. package/dist/src/ui/ToolCallDisplay.d.ts +16 -0
  273. package/dist/src/ui/ToolCallDisplay.d.ts.map +1 -0
  274. package/dist/src/ui/ToolCallDisplay.js +136 -0
  275. package/dist/src/ui/ToolCallDisplay.js.map +1 -0
  276. package/dist/src/ui/UserMessage.d.ts +12 -0
  277. package/dist/src/ui/UserMessage.d.ts.map +1 -0
  278. package/dist/src/ui/UserMessage.js +5 -0
  279. package/dist/src/ui/UserMessage.js.map +1 -0
  280. package/dist/src/ui/commands/clear.d.ts +7 -0
  281. package/dist/src/ui/commands/clear.d.ts.map +1 -0
  282. package/dist/src/ui/commands/clear.js +13 -0
  283. package/dist/src/ui/commands/clear.js.map +1 -0
  284. package/dist/src/ui/commands/help.d.ts +7 -0
  285. package/dist/src/ui/commands/help.d.ts.map +1 -0
  286. package/dist/src/ui/commands/help.js +26 -0
  287. package/dist/src/ui/commands/help.js.map +1 -0
  288. package/dist/src/ui/commands/index.d.ts +14 -0
  289. package/dist/src/ui/commands/index.d.ts.map +1 -0
  290. package/dist/src/ui/commands/index.js +15 -0
  291. package/dist/src/ui/commands/index.js.map +1 -0
  292. package/dist/src/ui/commands/model.d.ts +7 -0
  293. package/dist/src/ui/commands/model.d.ts.map +1 -0
  294. package/dist/src/ui/commands/model.js +16 -0
  295. package/dist/src/ui/commands/model.js.map +1 -0
  296. package/dist/src/ui/commands/registry.d.ts +30 -0
  297. package/dist/src/ui/commands/registry.d.ts.map +1 -0
  298. package/dist/src/ui/commands/registry.js +45 -0
  299. package/dist/src/ui/commands/registry.js.map +1 -0
  300. package/dist/src/ui/commands/sessions.d.ts +7 -0
  301. package/dist/src/ui/commands/sessions.d.ts.map +1 -0
  302. package/dist/src/ui/commands/sessions.js +13 -0
  303. package/dist/src/ui/commands/sessions.js.map +1 -0
  304. package/dist/src/ui/commands/stats.d.ts +7 -0
  305. package/dist/src/ui/commands/stats.d.ts.map +1 -0
  306. package/dist/src/ui/commands/stats.js +33 -0
  307. package/dist/src/ui/commands/stats.js.map +1 -0
  308. package/dist/src/ui/commands/theme.d.ts +7 -0
  309. package/dist/src/ui/commands/theme.d.ts.map +1 -0
  310. package/dist/src/ui/commands/theme.js +35 -0
  311. package/dist/src/ui/commands/theme.js.map +1 -0
  312. package/dist/src/ui/markdown/CodeBlock.d.ts +14 -0
  313. package/dist/src/ui/markdown/CodeBlock.d.ts.map +1 -0
  314. package/dist/src/ui/markdown/CodeBlock.js +55 -0
  315. package/dist/src/ui/markdown/CodeBlock.js.map +1 -0
  316. package/dist/src/ui/markdown/InlineRenderer.d.ts +12 -0
  317. package/dist/src/ui/markdown/InlineRenderer.d.ts.map +1 -0
  318. package/dist/src/ui/markdown/InlineRenderer.js +70 -0
  319. package/dist/src/ui/markdown/InlineRenderer.js.map +1 -0
  320. package/dist/src/ui/markdown/MarkdownDisplay.d.ts +17 -0
  321. package/dist/src/ui/markdown/MarkdownDisplay.d.ts.map +1 -0
  322. package/dist/src/ui/markdown/MarkdownDisplay.js +142 -0
  323. package/dist/src/ui/markdown/MarkdownDisplay.js.map +1 -0
  324. package/dist/src/ui/markdown/Table.d.ts +14 -0
  325. package/dist/src/ui/markdown/Table.d.ts.map +1 -0
  326. package/dist/src/ui/markdown/Table.js +31 -0
  327. package/dist/src/ui/markdown/Table.js.map +1 -0
  328. package/dist/src/ui/theme.d.ts +39 -0
  329. package/dist/src/ui/theme.d.ts.map +1 -0
  330. package/dist/src/ui/theme.js +39 -0
  331. package/dist/src/ui/theme.js.map +1 -0
  332. package/dist/src/ui/themes/index.d.ts +45 -0
  333. package/dist/src/ui/themes/index.d.ts.map +1 -0
  334. package/dist/src/ui/themes/index.js +73 -0
  335. package/dist/src/ui/themes/index.js.map +1 -0
  336. package/dist/src/ui/themes/light.d.ts +8 -0
  337. package/dist/src/ui/themes/light.d.ts.map +1 -0
  338. package/dist/src/ui/themes/light.js +37 -0
  339. package/dist/src/ui/themes/light.js.map +1 -0
  340. package/dist/src/ui/themes/monochrome.d.ts +8 -0
  341. package/dist/src/ui/themes/monochrome.d.ts.map +1 -0
  342. package/dist/src/ui/themes/monochrome.js +37 -0
  343. package/dist/src/ui/themes/monochrome.js.map +1 -0
  344. package/dist/src/ui/types.d.ts +191 -0
  345. package/dist/src/ui/types.d.ts.map +1 -0
  346. package/dist/src/ui/types.js +7 -0
  347. package/dist/src/ui/types.js.map +1 -0
  348. package/dist/src/ui/useAlternateBuffer.d.ts +11 -0
  349. package/dist/src/ui/useAlternateBuffer.d.ts.map +1 -0
  350. package/dist/src/ui/useAlternateBuffer.js +25 -0
  351. package/dist/src/ui/useAlternateBuffer.js.map +1 -0
  352. package/dist/src/ui/useChat.d.ts +18 -0
  353. package/dist/src/ui/useChat.d.ts.map +1 -0
  354. package/dist/src/ui/useChat.js +599 -0
  355. package/dist/src/ui/useChat.js.map +1 -0
  356. package/dist/src/ui/useElapsedTime.d.ts +11 -0
  357. package/dist/src/ui/useElapsedTime.d.ts.map +1 -0
  358. package/dist/src/ui/useElapsedTime.js +39 -0
  359. package/dist/src/ui/useElapsedTime.js.map +1 -0
  360. package/dist/src/ui/useResponsiveLayout.d.ts +16 -0
  361. package/dist/src/ui/useResponsiveLayout.d.ts.map +1 -0
  362. package/dist/src/ui/useResponsiveLayout.js +24 -0
  363. package/dist/src/ui/useResponsiveLayout.js.map +1 -0
  364. package/dist/src/ui/useScroll.d.ts +20 -0
  365. package/dist/src/ui/useScroll.d.ts.map +1 -0
  366. package/dist/src/ui/useScroll.js +64 -0
  367. package/dist/src/ui/useScroll.js.map +1 -0
  368. package/dist/src/ui/useSessionResume.d.ts +20 -0
  369. package/dist/src/ui/useSessionResume.d.ts.map +1 -0
  370. package/dist/src/ui/useSessionResume.js +77 -0
  371. package/dist/src/ui/useSessionResume.js.map +1 -0
  372. package/dist/tsconfig.tsbuildinfo +1 -0
  373. package/package.json +60 -0
  374. package/src/auth/index.ts +13 -0
  375. package/src/auth/oauth2.test.ts +305 -0
  376. package/src/auth/oauth2.ts +269 -0
  377. package/src/auth/prompt.test.ts +205 -0
  378. package/src/auth/prompt.ts +111 -0
  379. package/src/auth/test-connection.test.ts +224 -0
  380. package/src/auth/test-connection.ts +196 -0
  381. package/src/auth/types.ts +34 -0
  382. package/src/commands/audit.test.ts +92 -0
  383. package/src/commands/audit.ts +113 -0
  384. package/src/commands/automations.test.ts +85 -0
  385. package/src/commands/automations.ts +205 -0
  386. package/src/commands/build-manifest-types.ts +35 -0
  387. package/src/commands/build-tools.ts +281 -0
  388. package/src/commands/build.test.ts +63 -0
  389. package/src/commands/build.ts +135 -0
  390. package/src/commands/chat.ts +147 -0
  391. package/src/commands/command-exports.test.ts +88 -0
  392. package/src/commands/connect.test.ts +343 -0
  393. package/src/commands/connect.ts +237 -0
  394. package/src/commands/deploy.test.ts +124 -0
  395. package/src/commands/deploy.ts +153 -0
  396. package/src/commands/deployments.ts +90 -0
  397. package/src/commands/dev.test.ts +63 -0
  398. package/src/commands/dev.ts +124 -0
  399. package/src/commands/diff.test.ts +183 -0
  400. package/src/commands/diff.ts +143 -0
  401. package/src/commands/docker.ts +232 -0
  402. package/src/commands/eval.test.ts +88 -0
  403. package/src/commands/eval.ts +268 -0
  404. package/src/commands/experiment.test.ts +125 -0
  405. package/src/commands/experiment.ts +153 -0
  406. package/src/commands/index.ts +76 -0
  407. package/src/commands/init.test.ts +109 -0
  408. package/src/commands/init.ts +98 -0
  409. package/src/commands/inspect.test.ts +234 -0
  410. package/src/commands/inspect.ts +157 -0
  411. package/src/commands/install-pkg.test.ts +265 -0
  412. package/src/commands/install-pkg.ts +234 -0
  413. package/src/commands/link.ts +270 -0
  414. package/src/commands/list.test.ts +152 -0
  415. package/src/commands/list.ts +95 -0
  416. package/src/commands/login.test.ts +195 -0
  417. package/src/commands/login.ts +258 -0
  418. package/src/commands/promote.ts +64 -0
  419. package/src/commands/publish.test.ts +203 -0
  420. package/src/commands/publish.ts +142 -0
  421. package/src/commands/rollback.ts +72 -0
  422. package/src/commands/search.test.ts +174 -0
  423. package/src/commands/search.ts +154 -0
  424. package/src/commands/secrets.test.ts +168 -0
  425. package/src/commands/secrets.ts +163 -0
  426. package/src/commands/serve.ts +166 -0
  427. package/src/commands/status.ts +94 -0
  428. package/src/commands/sync.test.ts +130 -0
  429. package/src/commands/sync.ts +119 -0
  430. package/src/commands/test-query.test.ts +42 -0
  431. package/src/commands/test-query.ts +129 -0
  432. package/src/commands/uninstall.test.ts +162 -0
  433. package/src/commands/uninstall.ts +107 -0
  434. package/src/commands/update.test.ts +281 -0
  435. package/src/commands/update.ts +180 -0
  436. package/src/commands/validate.test.ts +260 -0
  437. package/src/commands/validate.ts +139 -0
  438. package/src/e2e-automations.test.ts +305 -0
  439. package/src/e2e-commands.test.ts +587 -0
  440. package/src/e2e-incident-response.test.ts +345 -0
  441. package/src/e2e-plugin-connections.test.ts +415 -0
  442. package/src/e2e-plugins.test.ts +492 -0
  443. package/src/e2e.test.ts +602 -0
  444. package/src/fixtures/incident-response.ts +232 -0
  445. package/src/main.ts +35 -0
  446. package/src/shared/platform-client.test.ts +106 -0
  447. package/src/shared/platform-client.ts +193 -0
  448. package/src/shared/repo-discovery.test.ts +56 -0
  449. package/src/shared/repo-discovery.ts +40 -0
  450. package/src/templates/compose-template.ts +30 -0
  451. package/src/templates/config-template.ts +44 -0
  452. package/src/templates/deployment-templates.test.ts +99 -0
  453. package/src/templates/dockerfile-template.ts +30 -0
  454. package/src/templates/env-template.ts +27 -0
  455. package/src/templates/knowledge-template.ts +24 -0
  456. package/src/templates/skill-template.ts +27 -0
  457. package/src/ui/AskUserPrompt.tsx +58 -0
  458. package/src/ui/AssistantMessage.tsx +51 -0
  459. package/src/ui/ChatApp.tsx +283 -0
  460. package/src/ui/ConfirmationPrompt.tsx +75 -0
  461. package/src/ui/DiffRenderer.test.ts +104 -0
  462. package/src/ui/DiffRenderer.tsx +188 -0
  463. package/src/ui/ExpandableContent.tsx +68 -0
  464. package/src/ui/ExploreIndicator.tsx +44 -0
  465. package/src/ui/Footer.tsx +87 -0
  466. package/src/ui/FullScreenLayout.tsx +45 -0
  467. package/src/ui/Header.tsx +50 -0
  468. package/src/ui/InputBar.tsx +105 -0
  469. package/src/ui/MessageList.tsx +31 -0
  470. package/src/ui/NotificationBar.tsx +64 -0
  471. package/src/ui/ScrollableMessageList.tsx +82 -0
  472. package/src/ui/SessionBrowser.test.ts +49 -0
  473. package/src/ui/SessionBrowser.tsx +179 -0
  474. package/src/ui/StatusMessage.tsx +35 -0
  475. package/src/ui/StreamingView.tsx +87 -0
  476. package/src/ui/SubagentDisplay.tsx +57 -0
  477. package/src/ui/ThinkingDisplay.tsx +45 -0
  478. package/src/ui/ToolCallDisplay.tsx +268 -0
  479. package/src/ui/UserMessage.tsx +22 -0
  480. package/src/ui/chat-e2e.test.ts +581 -0
  481. package/src/ui/commands/clear.ts +15 -0
  482. package/src/ui/commands/help.ts +31 -0
  483. package/src/ui/commands/index.ts +22 -0
  484. package/src/ui/commands/model.ts +19 -0
  485. package/src/ui/commands/registry.test.ts +76 -0
  486. package/src/ui/commands/registry.ts +66 -0
  487. package/src/ui/commands/sessions.ts +16 -0
  488. package/src/ui/commands/stats.ts +35 -0
  489. package/src/ui/commands/theme.ts +41 -0
  490. package/src/ui/markdown/CodeBlock.tsx +118 -0
  491. package/src/ui/markdown/InlineRenderer.tsx +115 -0
  492. package/src/ui/markdown/MarkdownDisplay.tsx +223 -0
  493. package/src/ui/markdown/Table.tsx +75 -0
  494. package/src/ui/theme.ts +39 -0
  495. package/src/ui/themes/index.ts +113 -0
  496. package/src/ui/themes/light.ts +39 -0
  497. package/src/ui/themes/monochrome.ts +39 -0
  498. package/src/ui/types.ts +157 -0
  499. package/src/ui/useAlternateBuffer.ts +27 -0
  500. package/src/ui/useChat.test.ts +492 -0
  501. package/src/ui/useChat.ts +693 -0
  502. package/src/ui/useElapsedTime.test.ts +33 -0
  503. package/src/ui/useElapsedTime.ts +43 -0
  504. package/src/ui/useResponsiveLayout.test.ts +54 -0
  505. package/src/ui/useResponsiveLayout.ts +32 -0
  506. package/src/ui/useScroll.test.ts +99 -0
  507. package/src/ui/useScroll.ts +97 -0
  508. package/src/ui/useSessionResume.test.ts +80 -0
  509. package/src/ui/useSessionResume.ts +102 -0
  510. package/tsconfig.json +17 -0
  511. package/vitest.config.ts +20 -0
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@amodalai/amodal",
3
+ "version": "0.1.2",
4
+ "description": "Amodal CLI",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/amodalai/amodal.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "homepage": "https://github.com/amodalai/amodal",
12
+ "bugs": {
13
+ "url": "https://github.com/amodalai/amodal/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "dist/src/main.js",
17
+ "bin": {
18
+ "amodal": "dist/src/main.js"
19
+ },
20
+ "dependencies": {
21
+ "chalk": "^5.6.2",
22
+ "ink": "^6.8.0",
23
+ "ink-spinner": "^5.0.0",
24
+ "ink-text-input": "^6.0.0",
25
+ "marked": "^17.0.4",
26
+ "marked-terminal": "^7.3.0",
27
+ "open": "^10.1.0",
28
+ "prompts": "^2.4.2",
29
+ "react": "^19.2.4",
30
+ "semver": "^7.6.0",
31
+ "yargs": "^17.7.2",
32
+ "zod": "^4.3.6",
33
+ "@amodalai/core": "0.1.2",
34
+ "@amodalai/runtime": "0.1.2"
35
+ },
36
+ "peerDependencies": {
37
+ "@amodalai/runtime-app": "0.1.2"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@amodalai/runtime-app": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.11.24",
46
+ "@types/prompts": "^2.4.9",
47
+ "@types/react": "^19.2.14",
48
+ "@types/semver": "^7.7.0",
49
+ "@types/yargs": "^17.0.32",
50
+ "typescript": "^5.3.3",
51
+ "vitest": "^3.1.1"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc --build",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run",
57
+ "test:ci": "vitest run --exclude 'src/e2e*.test.ts'",
58
+ "lint": "eslint src/"
59
+ }
60
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ export * from './types.js';
8
+ export {getRequiredEnvVars, promptForCredentials} from './prompt.js';
9
+ export type {PromptCredentialsOptions} from './prompt.js';
10
+ export {testConnection} from './test-connection.js';
11
+ export type {TestConnectionOptions} from './test-connection.js';
12
+ export {runOAuth2Flow, OAuth2Error} from './oauth2.js';
13
+ export type {OAuth2Options} from './oauth2.js';
@@ -0,0 +1,305 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
8
+
9
+ const mockOpen = vi.fn();
10
+
11
+ vi.mock('open', () => ({
12
+ default: (...args: unknown[]) => mockOpen(...args),
13
+ }));
14
+
15
+ import {
16
+ startCallbackServer,
17
+ buildAuthorizeUrl,
18
+ exchangeCodeForTokens,
19
+ mapTokensToEnvVars,
20
+ runOAuth2Flow,
21
+ OAuth2Error,
22
+ } from './oauth2.js';
23
+ import type {OAuth2Options} from './oauth2.js';
24
+
25
+ describe('startCallbackServer', () => {
26
+ it('starts a server on a random port', async () => {
27
+ const {server, port} = await startCallbackServer();
28
+ expect(port).toBeGreaterThan(0);
29
+ server.close();
30
+ });
31
+
32
+ it('resolves code from callback request', async () => {
33
+ const {server, port, codePromise} = await startCallbackServer();
34
+
35
+ // Simulate browser callback
36
+ await fetch(`http://127.0.0.1:${port}/callback?code=abc123&state=mystate`);
37
+ const result = await codePromise;
38
+
39
+ expect(result.code).toBe('abc123');
40
+ expect(result.state).toBe('mystate');
41
+ server.close();
42
+ });
43
+
44
+ it('rejects on error callback', async () => {
45
+ const {server, port, codePromise} = await startCallbackServer();
46
+
47
+ // Attach catch handler before triggering to avoid unhandled rejection
48
+ const rejection = codePromise.catch((err: unknown) => err);
49
+ await fetch(`http://127.0.0.1:${port}/callback?error=access_denied&error_description=User+denied`);
50
+ const err = await rejection;
51
+ expect(err).toBeInstanceOf(OAuth2Error);
52
+ expect((err as Error).message).toContain('User denied');
53
+ server.close();
54
+ });
55
+
56
+ it('rejects on missing code parameter', async () => {
57
+ const {server, port, codePromise} = await startCallbackServer();
58
+
59
+ const rejection = codePromise.catch((err: unknown) => err);
60
+ await fetch(`http://127.0.0.1:${port}/callback`);
61
+ const err = await rejection;
62
+ expect(err).toBeInstanceOf(OAuth2Error);
63
+ expect((err as Error).message).toContain('Missing code or state');
64
+ server.close();
65
+ });
66
+
67
+ it('returns 404 for non-callback paths', async () => {
68
+ const {server, port} = await startCallbackServer();
69
+
70
+ const response = await fetch(`http://127.0.0.1:${port}/other`);
71
+ expect(response.status).toBe(404);
72
+ server.close();
73
+ });
74
+ });
75
+
76
+ describe('buildAuthorizeUrl', () => {
77
+ const baseOptions: OAuth2Options = {
78
+ authorizeUrl: 'https://auth.example.com/authorize',
79
+ tokenUrl: 'https://auth.example.com/token',
80
+ clientId: 'client123',
81
+ };
82
+
83
+ it('includes required parameters', () => {
84
+ const url = buildAuthorizeUrl(baseOptions.authorizeUrl, baseOptions, 8080, 'state123');
85
+ const parsed = new URL(url);
86
+
87
+ expect(parsed.searchParams.get('response_type')).toBe('code');
88
+ expect(parsed.searchParams.get('client_id')).toBe('client123');
89
+ expect(parsed.searchParams.get('redirect_uri')).toBe('http://127.0.0.1:8080/callback');
90
+ expect(parsed.searchParams.get('state')).toBe('state123');
91
+ });
92
+
93
+ it('includes scopes when provided', () => {
94
+ const options = {...baseOptions, scopes: ['read', 'write']};
95
+ const url = buildAuthorizeUrl(options.authorizeUrl, options, 8080, 'state');
96
+ const parsed = new URL(url);
97
+
98
+ expect(parsed.searchParams.get('scope')).toBe('read write');
99
+ });
100
+
101
+ it('omits scope when empty', () => {
102
+ const url = buildAuthorizeUrl(baseOptions.authorizeUrl, baseOptions, 8080, 'state');
103
+ const parsed = new URL(url);
104
+
105
+ expect(parsed.searchParams.has('scope')).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe('exchangeCodeForTokens', () => {
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- vitest spy type mismatch with globalThis.fetch overloads
111
+ let fetchSpy: any;
112
+
113
+ beforeEach(() => {
114
+ fetchSpy = vi.spyOn(globalThis, 'fetch');
115
+ });
116
+
117
+ afterEach(() => {
118
+ fetchSpy.mockRestore();
119
+ });
120
+
121
+ it('exchanges code for tokens', async () => {
122
+ const tokenResponse = {access_token: 'at123', refresh_token: 'rt456', token_type: 'Bearer'};
123
+ fetchSpy.mockResolvedValue(new Response(JSON.stringify(tokenResponse), {status: 200}));
124
+
125
+ const tokens = await exchangeCodeForTokens(
126
+ 'https://auth.example.com/token',
127
+ 'authcode',
128
+ 'http://127.0.0.1:9999/callback',
129
+ 'client123',
130
+ 'secret456',
131
+ );
132
+
133
+ expect(tokens['access_token']).toBe('at123');
134
+ expect(tokens['refresh_token']).toBe('rt456');
135
+
136
+ // Verify POST body
137
+ const [, init] = fetchSpy.mock.calls[0] as [string, RequestInit];
138
+ expect(init.method).toBe('POST');
139
+ const body = init.body as string;
140
+ expect(body).toContain('grant_type=authorization_code');
141
+ expect(body).toContain('code=authcode');
142
+ expect(body).toContain('client_secret=secret456');
143
+ });
144
+
145
+ it('throws on HTTP error', async () => {
146
+ fetchSpy.mockResolvedValue(new Response('Bad Request', {status: 400}));
147
+
148
+ await expect(
149
+ exchangeCodeForTokens('https://auth.example.com/token', 'bad', 'http://localhost/cb', 'c'),
150
+ ).rejects.toThrow(OAuth2Error);
151
+ });
152
+
153
+ it('throws on network error', async () => {
154
+ fetchSpy.mockRejectedValue(new Error('ECONNREFUSED'));
155
+
156
+ await expect(
157
+ exchangeCodeForTokens('https://auth.example.com/token', 'code', 'http://localhost/cb', 'c'),
158
+ ).rejects.toThrow('ECONNREFUSED');
159
+ });
160
+ });
161
+
162
+ describe('mapTokensToEnvVars', () => {
163
+ it('uses default mapping when no envVars', () => {
164
+ const tokens = {access_token: 'at', refresh_token: 'rt'};
165
+ const result = mapTokensToEnvVars(tokens);
166
+
167
+ expect(result).toEqual({
168
+ OAUTH_ACCESS_TOKEN: 'at',
169
+ OAUTH_REFRESH_TOKEN: 'rt',
170
+ });
171
+ });
172
+
173
+ it('uses custom mapping based on var name hints', () => {
174
+ const tokens = {access_token: 'at', refresh_token: 'rt'};
175
+ const result = mapTokensToEnvVars(tokens, {
176
+ MY_ACCESS: 'Access token',
177
+ MY_REFRESH: 'Refresh token',
178
+ });
179
+
180
+ expect(result).toEqual({
181
+ MY_ACCESS: 'at',
182
+ MY_REFRESH: 'rt',
183
+ });
184
+ });
185
+
186
+ it('defaults to access_token when var name has no hint', () => {
187
+ const tokens = {access_token: 'at'};
188
+ const result = mapTokensToEnvVars(tokens, {SOME_TOKEN: 'desc'});
189
+
190
+ expect(result).toEqual({SOME_TOKEN: 'at'});
191
+ });
192
+
193
+ it('handles missing tokens gracefully', () => {
194
+ const tokens = {};
195
+ const result = mapTokensToEnvVars(tokens, {ACCESS: 'desc'});
196
+
197
+ expect(result).toEqual({});
198
+ });
199
+ });
200
+
201
+ describe('runOAuth2Flow', () => {
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- vitest spy type mismatch with globalThis.fetch overloads
203
+ let fetchSpy: any;
204
+ const stderrSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
205
+
206
+ beforeEach(() => {
207
+ vi.clearAllMocks();
208
+ fetchSpy = vi.spyOn(globalThis, 'fetch');
209
+ });
210
+
211
+ afterEach(() => {
212
+ fetchSpy.mockRestore();
213
+ });
214
+
215
+ it('completes the full flow', async () => {
216
+ const tokenResponse = {access_token: 'tok123', refresh_token: 'ref456'};
217
+
218
+ // Mock open to simulate browser callback
219
+ mockOpen.mockImplementation(async (url: string) => {
220
+ const parsed = new URL(url);
221
+ const state = parsed.searchParams.get('state');
222
+ const redirectUri = parsed.searchParams.get('redirect_uri')!;
223
+ // Hit the callback
224
+ await fetch(`${redirectUri}?code=authcode&state=${state}`);
225
+ });
226
+
227
+ // Mock token exchange
228
+ fetchSpy.mockImplementation(async (input: string | URL | Request) => {
229
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
230
+ if (url.includes('/callback')) {
231
+ // This is the real callback to our server, let it through
232
+ return fetch(input);
233
+ }
234
+ // Token exchange
235
+ return new Response(JSON.stringify(tokenResponse), {status: 200});
236
+ });
237
+
238
+ // Can't easily test the full flow with mocked fetch intercepting all calls.
239
+ // Instead test the helper functions above. This test verifies error paths.
240
+ });
241
+
242
+ it('falls back to printing URL when browser fails', async () => {
243
+ mockOpen.mockRejectedValue(new Error('No browser'));
244
+
245
+ // Start the flow but let it timeout quickly
246
+ const options: OAuth2Options = {
247
+ authorizeUrl: 'https://auth.example.com/authorize',
248
+ tokenUrl: 'https://auth.example.com/token',
249
+ clientId: 'client123',
250
+ timeoutMs: 100,
251
+ };
252
+
253
+ await expect(runOAuth2Flow(options)).rejects.toThrow('timed out');
254
+ expect(stderrSpy).toHaveBeenCalledWith(
255
+ expect.stringContaining('Open this URL'),
256
+ );
257
+ });
258
+
259
+ it('throws on timeout', async () => {
260
+ mockOpen.mockResolvedValue(undefined);
261
+
262
+ const options: OAuth2Options = {
263
+ authorizeUrl: 'https://auth.example.com/authorize',
264
+ tokenUrl: 'https://auth.example.com/token',
265
+ clientId: 'client123',
266
+ timeoutMs: 100,
267
+ };
268
+
269
+ await expect(runOAuth2Flow(options)).rejects.toThrow(OAuth2Error);
270
+ });
271
+
272
+ it('cleans up server on error', async () => {
273
+ mockOpen.mockResolvedValue(undefined);
274
+
275
+ const options: OAuth2Options = {
276
+ authorizeUrl: 'https://auth.example.com/authorize',
277
+ tokenUrl: 'https://auth.example.com/token',
278
+ clientId: 'client123',
279
+ timeoutMs: 50,
280
+ };
281
+
282
+ try {
283
+ await runOAuth2Flow(options);
284
+ } catch {
285
+ // Expected
286
+ }
287
+
288
+ // Server should be closed — no dangling listeners
289
+ });
290
+ });
291
+
292
+ describe('OAuth2Error', () => {
293
+ it('has correct name and code', () => {
294
+ const err = new OAuth2Error('TIMEOUT', 'timed out');
295
+ expect(err.name).toBe('OAuth2Error');
296
+ expect(err.code).toBe('TIMEOUT');
297
+ expect(err.message).toBe('timed out');
298
+ });
299
+
300
+ it('preserves cause', () => {
301
+ const cause = new Error('original');
302
+ const err = new OAuth2Error('SERVER_FAILED', 'failed', cause);
303
+ expect(err.cause).toBe(cause);
304
+ });
305
+ });
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ import {createServer} from 'node:http';
8
+ import type {Server} from 'node:http';
9
+ import {randomUUID} from 'node:crypto';
10
+ import {URL, URLSearchParams} from 'node:url';
11
+ import open from 'open';
12
+
13
+ import type {AuthResult} from './types.js';
14
+
15
+ export type OAuth2ErrorCode =
16
+ | 'SERVER_FAILED'
17
+ | 'BROWSER_FAILED'
18
+ | 'TIMEOUT'
19
+ | 'TOKEN_EXCHANGE_FAILED'
20
+ | 'CALLBACK_ERROR'
21
+ | 'STATE_MISMATCH';
22
+
23
+ export class OAuth2Error extends Error {
24
+ readonly code: OAuth2ErrorCode;
25
+
26
+ constructor(code: OAuth2ErrorCode, message: string, cause?: unknown) {
27
+ super(message, {cause});
28
+ this.name = 'OAuth2Error';
29
+ this.code = code;
30
+ }
31
+ }
32
+
33
+ export interface OAuth2Options {
34
+ authorizeUrl: string;
35
+ tokenUrl: string;
36
+ scopes?: string[];
37
+ envVars?: Record<string, string>;
38
+ clientId: string;
39
+ clientSecret?: string;
40
+ timeoutMs?: number;
41
+ }
42
+
43
+ const DEFAULT_TIMEOUT_MS = 120_000;
44
+
45
+ interface CallbackServerResult {
46
+ server: Server;
47
+ port: number;
48
+ codePromise: Promise<{code: string; state: string}>;
49
+ }
50
+
51
+ /**
52
+ * Start a local HTTP server that listens for the OAuth2 callback.
53
+ */
54
+ export function startCallbackServer(): Promise<CallbackServerResult> {
55
+ return new Promise((resolve, reject) => {
56
+ let resolveCode: (value: {code: string; state: string}) => void;
57
+ let rejectCode: (reason: Error) => void;
58
+ const codePromise = new Promise<{code: string; state: string}>((res, rej) => {
59
+ resolveCode = res;
60
+ rejectCode = rej;
61
+ });
62
+
63
+ const server = createServer((req, res) => {
64
+ try {
65
+ const url = new URL(req.url ?? '/', `http://localhost`);
66
+ if (url.pathname !== '/callback') {
67
+ res.writeHead(404);
68
+ res.end('Not found');
69
+ return;
70
+ }
71
+
72
+ const code = url.searchParams.get('code');
73
+ const state = url.searchParams.get('state');
74
+ const error = url.searchParams.get('error');
75
+
76
+ if (error) {
77
+ const description = url.searchParams.get('error_description') ?? error;
78
+ res.writeHead(200, {'Content-Type': 'text/html'});
79
+ res.end('<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>');
80
+ rejectCode(new OAuth2Error('CALLBACK_ERROR', `OAuth2 error: ${description}`));
81
+ return;
82
+ }
83
+
84
+ if (!code || !state) {
85
+ res.writeHead(400, {'Content-Type': 'text/html'});
86
+ res.end('<html><body><h1>Missing Parameters</h1></body></html>');
87
+ rejectCode(new OAuth2Error('CALLBACK_ERROR', 'Missing code or state parameter'));
88
+ return;
89
+ }
90
+
91
+ res.writeHead(200, {'Content-Type': 'text/html'});
92
+ res.end('<html><body><h1>Authorization Complete</h1><p>You can close this window.</p></body></html>');
93
+ resolveCode({code, state});
94
+ } catch (err) {
95
+ res.writeHead(500);
96
+ res.end('Internal error');
97
+ rejectCode(err instanceof Error ? err : new Error(String(err)));
98
+ }
99
+ });
100
+
101
+ server.listen(0, '127.0.0.1', () => {
102
+ const addr = server.address();
103
+ if (!addr || typeof addr === 'string') {
104
+ server.close();
105
+ reject(new OAuth2Error('SERVER_FAILED', 'Failed to get server port'));
106
+ return;
107
+ }
108
+ resolve({server, port: addr.port, codePromise});
109
+ });
110
+
111
+ server.on('error', (err) => {
112
+ reject(new OAuth2Error('SERVER_FAILED', 'Failed to start callback server', err));
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Build the OAuth2 authorize URL.
119
+ */
120
+ export function buildAuthorizeUrl(
121
+ baseUrl: string,
122
+ options: OAuth2Options,
123
+ port: number,
124
+ state: string,
125
+ ): string {
126
+ const url = new URL(baseUrl);
127
+ url.searchParams.set('response_type', 'code');
128
+ url.searchParams.set('client_id', options.clientId);
129
+ url.searchParams.set('redirect_uri', `http://127.0.0.1:${port}/callback`);
130
+ url.searchParams.set('state', state);
131
+ if (options.scopes && options.scopes.length > 0) {
132
+ url.searchParams.set('scope', options.scopes.join(' '));
133
+ }
134
+ return url.toString();
135
+ }
136
+
137
+ /**
138
+ * Exchange authorization code for tokens.
139
+ */
140
+ export async function exchangeCodeForTokens(
141
+ tokenUrl: string,
142
+ code: string,
143
+ redirectUri: string,
144
+ clientId: string,
145
+ clientSecret?: string,
146
+ ): Promise<Record<string, unknown>> {
147
+ const body = new URLSearchParams({
148
+ grant_type: 'authorization_code',
149
+ code,
150
+ redirect_uri: redirectUri,
151
+ client_id: clientId,
152
+ });
153
+ if (clientSecret) {
154
+ body.set('client_secret', clientSecret);
155
+ }
156
+
157
+ const response = await fetch(tokenUrl, {
158
+ method: 'POST',
159
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
160
+ body: body.toString(),
161
+ });
162
+
163
+ if (!response.ok) {
164
+ const text = await response.text().catch(() => '');
165
+ throw new OAuth2Error(
166
+ 'TOKEN_EXCHANGE_FAILED',
167
+ `Token exchange failed: HTTP ${response.status}${text ? ` — ${text}` : ''}`,
168
+ );
169
+ }
170
+
171
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
172
+ return (await response.json()) as Record<string, unknown>;
173
+ }
174
+
175
+ /**
176
+ * Map token response fields to env var names.
177
+ */
178
+ export function mapTokensToEnvVars(
179
+ tokens: Record<string, unknown>,
180
+ envVars?: Record<string, string>,
181
+ ): Record<string, string> {
182
+ const result: Record<string, string> = {};
183
+
184
+ if (envVars && Object.keys(envVars).length > 0) {
185
+ // envVars maps envVarName → description (for prompting)
186
+ // Token fields: access_token, refresh_token, etc.
187
+ // Convention: env var name hints at which token field
188
+ for (const envVarName of Object.keys(envVars)) {
189
+ const lower = envVarName.toLowerCase();
190
+ if (lower.includes('refresh') && typeof tokens['refresh_token'] === 'string') {
191
+ result[envVarName] = tokens['refresh_token'];
192
+ } else if (lower.includes('access') && typeof tokens['access_token'] === 'string') {
193
+ result[envVarName] = tokens['access_token'];
194
+ } else if (typeof tokens['access_token'] === 'string') {
195
+ // Default: map to access_token
196
+ result[envVarName] = tokens['access_token'];
197
+ }
198
+ }
199
+ } else {
200
+ // Default mapping
201
+ if (typeof tokens['access_token'] === 'string') {
202
+ result['OAUTH_ACCESS_TOKEN'] = tokens['access_token'];
203
+ }
204
+ if (typeof tokens['refresh_token'] === 'string') {
205
+ result['OAUTH_REFRESH_TOKEN'] = tokens['refresh_token'];
206
+ }
207
+ }
208
+
209
+ return result;
210
+ }
211
+
212
+ /**
213
+ * Run the full OAuth2 authorization code flow.
214
+ */
215
+ export async function runOAuth2Flow(options: OAuth2Options): Promise<AuthResult> {
216
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
217
+ let server: Server | undefined;
218
+
219
+ try {
220
+ const serverResult = await startCallbackServer();
221
+ server = serverResult.server;
222
+ const {port, codePromise} = serverResult;
223
+
224
+ const state = randomUUID();
225
+ const authorizeUrl = buildAuthorizeUrl(options.authorizeUrl, options, port, state);
226
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
227
+
228
+ // Try to open browser, fall back to printing URL
229
+ try {
230
+ await open(authorizeUrl);
231
+ } catch {
232
+ process.stderr.write(`\nOpen this URL in your browser to authorize:\n${authorizeUrl}\n\n`);
233
+ }
234
+
235
+ // Wait for callback or timeout
236
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
237
+ setTimeout(() => {
238
+ reject(new OAuth2Error('TIMEOUT', `OAuth2 flow timed out after ${timeoutMs}ms`));
239
+ }, timeoutMs);
240
+ });
241
+
242
+ const callbackResult = await Promise.race([codePromise, timeoutPromise]);
243
+
244
+ // Verify state (CSRF protection)
245
+ if (callbackResult.state !== state) {
246
+ throw new OAuth2Error('STATE_MISMATCH', 'OAuth2 state parameter mismatch');
247
+ }
248
+
249
+ // Exchange code for tokens
250
+ const tokens = await exchangeCodeForTokens(
251
+ options.tokenUrl,
252
+ callbackResult.code,
253
+ redirectUri,
254
+ options.clientId,
255
+ options.clientSecret,
256
+ );
257
+
258
+ const credentials = mapTokensToEnvVars(tokens, options.envVars);
259
+
260
+ return {
261
+ credentials,
262
+ summary: `OAuth2 authorization complete. ${Object.keys(credentials).length} token${Object.keys(credentials).length === 1 ? '' : 's'} obtained`,
263
+ };
264
+ } finally {
265
+ if (server) {
266
+ server.close();
267
+ }
268
+ }
269
+ }