dedalus-cli 0.0.0-stage → 0.7.0

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 (684) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +296 -2
  3. package/SKILL.md +73 -0
  4. package/api.md +340 -0
  5. package/dist/cjs/bin.d.ts +3 -0
  6. package/dist/cjs/bin.d.ts.map +1 -0
  7. package/dist/cjs/bin.js +7 -0
  8. package/dist/cjs/bin.js.map +1 -0
  9. package/dist/cjs/cli/completions.d.ts +6 -0
  10. package/dist/cjs/cli/completions.d.ts.map +1 -0
  11. package/dist/cjs/cli/completions.js +11 -0
  12. package/dist/cjs/cli/completions.js.map +1 -0
  13. package/dist/cjs/cli/credentials.d.ts +169 -0
  14. package/dist/cjs/cli/credentials.d.ts.map +1 -0
  15. package/dist/cjs/cli/credentials.js +630 -0
  16. package/dist/cjs/cli/credentials.js.map +1 -0
  17. package/dist/cjs/cli/keychain.d.ts +34 -0
  18. package/dist/cjs/cli/keychain.d.ts.map +1 -0
  19. package/dist/cjs/cli/keychain.js +246 -0
  20. package/dist/cjs/cli/keychain.js.map +1 -0
  21. package/dist/cjs/cli/list.d.ts +23 -0
  22. package/dist/cjs/cli/list.d.ts.map +1 -0
  23. package/dist/cjs/cli/list.js +32 -0
  24. package/dist/cjs/cli/list.js.map +1 -0
  25. package/dist/cjs/cli/login.d.ts +137 -0
  26. package/dist/cjs/cli/login.d.ts.map +1 -0
  27. package/dist/cjs/cli/login.js +2060 -0
  28. package/dist/cjs/cli/login.js.map +1 -0
  29. package/dist/cjs/cli/runtime.d.ts +98 -0
  30. package/dist/cjs/cli/runtime.d.ts.map +1 -0
  31. package/dist/cjs/cli/runtime.js +1356 -0
  32. package/dist/cjs/cli/runtime.js.map +1 -0
  33. package/dist/cjs/cli/toon.d.ts +6 -0
  34. package/dist/cjs/cli/toon.d.ts.map +1 -0
  35. package/dist/cjs/cli/toon.js +165 -0
  36. package/dist/cjs/cli/toon.js.map +1 -0
  37. package/dist/cjs/cli/upgrade.d.ts +28 -0
  38. package/dist/cjs/cli/upgrade.d.ts.map +1 -0
  39. package/dist/cjs/cli/upgrade.js +1191 -0
  40. package/dist/cjs/cli/upgrade.js.map +1 -0
  41. package/dist/cjs/commands/index.d.ts +47 -0
  42. package/dist/cjs/commands/index.d.ts.map +1 -0
  43. package/dist/cjs/commands/index.js +1202 -0
  44. package/dist/cjs/commands/index.js.map +1 -0
  45. package/dist/cjs/custom/browser-routing.d.ts +4 -0
  46. package/dist/cjs/custom/browser-routing.d.ts.map +1 -0
  47. package/dist/cjs/custom/browser-routing.js +51 -0
  48. package/dist/cjs/custom/browser-routing.js.map +1 -0
  49. package/dist/cjs/custom/images.d.ts +6 -0
  50. package/dist/cjs/custom/images.d.ts.map +1 -0
  51. package/dist/cjs/custom/images.js +74 -0
  52. package/dist/cjs/custom/images.js.map +1 -0
  53. package/dist/cjs/custom/machines.d.ts +20 -0
  54. package/dist/cjs/custom/machines.d.ts.map +1 -0
  55. package/dist/cjs/custom/machines.js +84 -0
  56. package/dist/cjs/custom/machines.js.map +1 -0
  57. package/dist/cjs/custom/ssh-picker.d.ts +19 -0
  58. package/dist/cjs/custom/ssh-picker.d.ts.map +1 -0
  59. package/dist/cjs/custom/ssh-picker.js +157 -0
  60. package/dist/cjs/custom/ssh-picker.js.map +1 -0
  61. package/dist/cjs/custom/ssh.d.ts +18 -0
  62. package/dist/cjs/custom/ssh.d.ts.map +1 -0
  63. package/dist/cjs/custom/ssh.js +152 -0
  64. package/dist/cjs/custom/ssh.js.map +1 -0
  65. package/dist/cjs/feedback/bundle.d.ts +41 -0
  66. package/dist/cjs/feedback/bundle.d.ts.map +1 -0
  67. package/dist/cjs/feedback/bundle.js +88 -0
  68. package/dist/cjs/feedback/bundle.js.map +1 -0
  69. package/dist/cjs/feedback/command.d.ts +6 -0
  70. package/dist/cjs/feedback/command.d.ts.map +1 -0
  71. package/dist/cjs/feedback/command.js +100 -0
  72. package/dist/cjs/feedback/command.js.map +1 -0
  73. package/dist/cjs/feedback/diagnostics.d.ts +30 -0
  74. package/dist/cjs/feedback/diagnostics.d.ts.map +1 -0
  75. package/dist/cjs/feedback/diagnostics.js +278 -0
  76. package/dist/cjs/feedback/diagnostics.js.map +1 -0
  77. package/dist/cjs/index.d.ts +4 -0
  78. package/dist/cjs/index.d.ts.map +1 -0
  79. package/dist/cjs/index.js +11 -0
  80. package/dist/cjs/index.js.map +1 -0
  81. package/dist/cjs/package.json +3 -0
  82. package/dist/cjs/sdk/api-promise.d.ts +31 -0
  83. package/dist/cjs/sdk/api-promise.d.ts.map +1 -0
  84. package/dist/cjs/sdk/api-promise.js +73 -0
  85. package/dist/cjs/sdk/api-promise.js.map +1 -0
  86. package/dist/cjs/sdk/client.d.ts +267 -0
  87. package/dist/cjs/sdk/client.d.ts.map +1 -0
  88. package/dist/cjs/sdk/client.js +965 -0
  89. package/dist/cjs/sdk/client.js.map +1 -0
  90. package/dist/cjs/sdk/core/api-promise.d.ts +3 -0
  91. package/dist/cjs/sdk/core/api-promise.d.ts.map +1 -0
  92. package/dist/cjs/sdk/core/api-promise.js +8 -0
  93. package/dist/cjs/sdk/core/api-promise.js.map +1 -0
  94. package/dist/cjs/sdk/core/error.d.ts +46 -0
  95. package/dist/cjs/sdk/core/error.d.ts.map +1 -0
  96. package/dist/cjs/sdk/core/error.js +135 -0
  97. package/dist/cjs/sdk/core/error.js.map +1 -0
  98. package/dist/cjs/sdk/core/pagination.d.ts +38 -0
  99. package/dist/cjs/sdk/core/pagination.d.ts.map +1 -0
  100. package/dist/cjs/sdk/core/pagination.js +98 -0
  101. package/dist/cjs/sdk/core/pagination.js.map +1 -0
  102. package/dist/cjs/sdk/core/resource.d.ts +2 -0
  103. package/dist/cjs/sdk/core/resource.d.ts.map +1 -0
  104. package/dist/cjs/sdk/core/resource.js +7 -0
  105. package/dist/cjs/sdk/core/resource.js.map +1 -0
  106. package/dist/cjs/sdk/core/uploads.d.ts +3 -0
  107. package/dist/cjs/sdk/core/uploads.d.ts.map +1 -0
  108. package/dist/cjs/sdk/core/uploads.js +7 -0
  109. package/dist/cjs/sdk/core/uploads.js.map +1 -0
  110. package/dist/cjs/sdk/error.d.ts +3 -0
  111. package/dist/cjs/sdk/error.d.ts.map +1 -0
  112. package/dist/cjs/sdk/error.js +20 -0
  113. package/dist/cjs/sdk/error.js.map +1 -0
  114. package/dist/cjs/sdk/helpers/images.d.ts +31 -0
  115. package/dist/cjs/sdk/helpers/images.d.ts.map +1 -0
  116. package/dist/cjs/sdk/helpers/images.js +195 -0
  117. package/dist/cjs/sdk/helpers/images.js.map +1 -0
  118. package/dist/cjs/sdk/index.d.ts +7 -0
  119. package/dist/cjs/sdk/index.d.ts.map +1 -0
  120. package/dist/cjs/sdk/index.js +29 -0
  121. package/dist/cjs/sdk/index.js.map +1 -0
  122. package/dist/cjs/sdk/internal/builtin-types.d.ts +73 -0
  123. package/dist/cjs/sdk/internal/builtin-types.d.ts.map +1 -0
  124. package/dist/cjs/sdk/internal/builtin-types.js +4 -0
  125. package/dist/cjs/sdk/internal/builtin-types.js.map +1 -0
  126. package/dist/cjs/sdk/internal/detect-platform.d.ts +15 -0
  127. package/dist/cjs/sdk/internal/detect-platform.d.ts.map +1 -0
  128. package/dist/cjs/sdk/internal/detect-platform.js +185 -0
  129. package/dist/cjs/sdk/internal/detect-platform.js.map +1 -0
  130. package/dist/cjs/sdk/internal/errors.d.ts +3 -0
  131. package/dist/cjs/sdk/internal/errors.d.ts.map +1 -0
  132. package/dist/cjs/sdk/internal/errors.js +41 -0
  133. package/dist/cjs/sdk/internal/errors.js.map +1 -0
  134. package/dist/cjs/sdk/internal/headers.d.ts +43 -0
  135. package/dist/cjs/sdk/internal/headers.d.ts.map +1 -0
  136. package/dist/cjs/sdk/internal/headers.js +142 -0
  137. package/dist/cjs/sdk/internal/headers.js.map +1 -0
  138. package/dist/cjs/sdk/internal/parse.d.ts +3 -0
  139. package/dist/cjs/sdk/internal/parse.d.ts.map +1 -0
  140. package/dist/cjs/sdk/internal/parse.js +7 -0
  141. package/dist/cjs/sdk/internal/parse.js.map +1 -0
  142. package/dist/cjs/sdk/internal/qs/formats.d.ts +7 -0
  143. package/dist/cjs/sdk/internal/qs/formats.d.ts.map +1 -0
  144. package/dist/cjs/sdk/internal/qs/formats.js +13 -0
  145. package/dist/cjs/sdk/internal/qs/formats.js.map +1 -0
  146. package/dist/cjs/sdk/internal/qs/index.d.ts +10 -0
  147. package/dist/cjs/sdk/internal/qs/index.d.ts.map +1 -0
  148. package/dist/cjs/sdk/internal/qs/index.js +14 -0
  149. package/dist/cjs/sdk/internal/qs/index.js.map +1 -0
  150. package/dist/cjs/sdk/internal/qs/stringify.d.ts +3 -0
  151. package/dist/cjs/sdk/internal/qs/stringify.d.ts.map +1 -0
  152. package/dist/cjs/sdk/internal/qs/stringify.js +278 -0
  153. package/dist/cjs/sdk/internal/qs/stringify.js.map +1 -0
  154. package/dist/cjs/sdk/internal/qs/types.d.ts +57 -0
  155. package/dist/cjs/sdk/internal/qs/types.d.ts.map +1 -0
  156. package/dist/cjs/sdk/internal/qs/types.js +3 -0
  157. package/dist/cjs/sdk/internal/qs/types.js.map +1 -0
  158. package/dist/cjs/sdk/internal/qs/utils.d.ts +15 -0
  159. package/dist/cjs/sdk/internal/qs/utils.d.ts.map +1 -0
  160. package/dist/cjs/sdk/internal/qs/utils.js +230 -0
  161. package/dist/cjs/sdk/internal/qs/utils.js.map +1 -0
  162. package/dist/cjs/sdk/internal/request-options.d.ts +33 -0
  163. package/dist/cjs/sdk/internal/request-options.d.ts.map +1 -0
  164. package/dist/cjs/sdk/internal/request-options.js +15 -0
  165. package/dist/cjs/sdk/internal/request-options.js.map +1 -0
  166. package/dist/cjs/sdk/internal/shim-types.d.ts +17 -0
  167. package/dist/cjs/sdk/internal/shim-types.d.ts.map +1 -0
  168. package/dist/cjs/sdk/internal/shim-types.js +4 -0
  169. package/dist/cjs/sdk/internal/shim-types.js.map +1 -0
  170. package/dist/cjs/sdk/internal/shims.d.ts +26 -0
  171. package/dist/cjs/sdk/internal/shims.d.ts.map +1 -0
  172. package/dist/cjs/sdk/internal/shims.js +92 -0
  173. package/dist/cjs/sdk/internal/shims.js.map +1 -0
  174. package/dist/cjs/sdk/internal/to-file.d.ts +45 -0
  175. package/dist/cjs/sdk/internal/to-file.d.ts.map +1 -0
  176. package/dist/cjs/sdk/internal/to-file.js +91 -0
  177. package/dist/cjs/sdk/internal/to-file.js.map +1 -0
  178. package/dist/cjs/sdk/internal/types.d.ts +63 -0
  179. package/dist/cjs/sdk/internal/types.d.ts.map +1 -0
  180. package/dist/cjs/sdk/internal/types.js +4 -0
  181. package/dist/cjs/sdk/internal/types.js.map +1 -0
  182. package/dist/cjs/sdk/internal/uploads.d.ts +42 -0
  183. package/dist/cjs/sdk/internal/uploads.d.ts.map +1 -0
  184. package/dist/cjs/sdk/internal/uploads.js +158 -0
  185. package/dist/cjs/sdk/internal/uploads.js.map +1 -0
  186. package/dist/cjs/sdk/internal/utils/base64.d.ts +3 -0
  187. package/dist/cjs/sdk/internal/utils/base64.d.ts.map +1 -0
  188. package/dist/cjs/sdk/internal/utils/base64.js +38 -0
  189. package/dist/cjs/sdk/internal/utils/base64.js.map +1 -0
  190. package/dist/cjs/sdk/internal/utils/body.d.ts +40 -0
  191. package/dist/cjs/sdk/internal/utils/body.d.ts.map +1 -0
  192. package/dist/cjs/sdk/internal/utils/body.js +148 -0
  193. package/dist/cjs/sdk/internal/utils/body.js.map +1 -0
  194. package/dist/cjs/sdk/internal/utils/bytes.d.ts +4 -0
  195. package/dist/cjs/sdk/internal/utils/bytes.d.ts.map +1 -0
  196. package/dist/cjs/sdk/internal/utils/bytes.js +31 -0
  197. package/dist/cjs/sdk/internal/utils/bytes.js.map +1 -0
  198. package/dist/cjs/sdk/internal/utils/env.d.ts +9 -0
  199. package/dist/cjs/sdk/internal/utils/env.d.ts.map +1 -0
  200. package/dist/cjs/sdk/internal/utils/env.js +22 -0
  201. package/dist/cjs/sdk/internal/utils/env.js.map +1 -0
  202. package/dist/cjs/sdk/internal/utils/log.d.ts +52 -0
  203. package/dist/cjs/sdk/internal/utils/log.d.ts.map +1 -0
  204. package/dist/cjs/sdk/internal/utils/log.js +413 -0
  205. package/dist/cjs/sdk/internal/utils/log.js.map +1 -0
  206. package/dist/cjs/sdk/internal/utils/path.d.ts +41 -0
  207. package/dist/cjs/sdk/internal/utils/path.d.ts.map +1 -0
  208. package/dist/cjs/sdk/internal/utils/path.js +115 -0
  209. package/dist/cjs/sdk/internal/utils/path.js.map +1 -0
  210. package/dist/cjs/sdk/internal/utils/query-string.d.ts +21 -0
  211. package/dist/cjs/sdk/internal/utils/query-string.d.ts.map +1 -0
  212. package/dist/cjs/sdk/internal/utils/query-string.js +128 -0
  213. package/dist/cjs/sdk/internal/utils/query-string.js.map +1 -0
  214. package/dist/cjs/sdk/internal/utils/query.d.ts +2 -0
  215. package/dist/cjs/sdk/internal/utils/query.d.ts.map +1 -0
  216. package/dist/cjs/sdk/internal/utils/query.js +12 -0
  217. package/dist/cjs/sdk/internal/utils/query.js.map +1 -0
  218. package/dist/cjs/sdk/internal/utils/sleep.d.ts +2 -0
  219. package/dist/cjs/sdk/internal/utils/sleep.d.ts.map +1 -0
  220. package/dist/cjs/sdk/internal/utils/sleep.js +7 -0
  221. package/dist/cjs/sdk/internal/utils/sleep.js.map +1 -0
  222. package/dist/cjs/sdk/internal/utils/uuid.d.ts +5 -0
  223. package/dist/cjs/sdk/internal/utils/uuid.d.ts.map +1 -0
  224. package/dist/cjs/sdk/internal/utils/uuid.js +19 -0
  225. package/dist/cjs/sdk/internal/utils/uuid.js.map +1 -0
  226. package/dist/cjs/sdk/internal/utils/values.d.ts +18 -0
  227. package/dist/cjs/sdk/internal/utils/values.d.ts.map +1 -0
  228. package/dist/cjs/sdk/internal/utils/values.js +112 -0
  229. package/dist/cjs/sdk/internal/utils/values.js.map +1 -0
  230. package/dist/cjs/sdk/internal/utils.d.ts +8 -0
  231. package/dist/cjs/sdk/internal/utils.d.ts.map +1 -0
  232. package/dist/cjs/sdk/internal/utils.js +25 -0
  233. package/dist/cjs/sdk/internal/utils.js.map +1 -0
  234. package/dist/cjs/sdk/pagination.d.ts +3 -0
  235. package/dist/cjs/sdk/pagination.d.ts.map +1 -0
  236. package/dist/cjs/sdk/pagination.js +20 -0
  237. package/dist/cjs/sdk/pagination.js.map +1 -0
  238. package/dist/cjs/sdk/resource.d.ts +6 -0
  239. package/dist/cjs/sdk/resource.d.ts.map +1 -0
  240. package/dist/cjs/sdk/resource.js +12 -0
  241. package/dist/cjs/sdk/resource.js.map +1 -0
  242. package/dist/cjs/sdk/resources/images/images.d.ts +202 -0
  243. package/dist/cjs/sdk/resources/images/images.d.ts.map +1 -0
  244. package/dist/cjs/sdk/resources/images/images.js +139 -0
  245. package/dist/cjs/sdk/resources/images/images.js.map +1 -0
  246. package/dist/cjs/sdk/resources/images/index.d.ts +5 -0
  247. package/dist/cjs/sdk/resources/images/index.d.ts.map +1 -0
  248. package/dist/cjs/sdk/resources/images/index.js +9 -0
  249. package/dist/cjs/sdk/resources/images/index.js.map +1 -0
  250. package/dist/cjs/sdk/resources/images/uploads.d.ts +50 -0
  251. package/dist/cjs/sdk/resources/images/uploads.d.ts.map +1 -0
  252. package/dist/cjs/sdk/resources/images/uploads.js +28 -0
  253. package/dist/cjs/sdk/resources/images/uploads.js.map +1 -0
  254. package/dist/cjs/sdk/resources/images.d.ts +2 -0
  255. package/dist/cjs/sdk/resources/images.d.ts.map +1 -0
  256. package/dist/cjs/sdk/resources/images.js +19 -0
  257. package/dist/cjs/sdk/resources/images.js.map +1 -0
  258. package/dist/cjs/sdk/resources/index.d.ts +6 -0
  259. package/dist/cjs/sdk/resources/index.d.ts.map +1 -0
  260. package/dist/cjs/sdk/resources/index.js +11 -0
  261. package/dist/cjs/sdk/resources/index.js.map +1 -0
  262. package/dist/cjs/sdk/resources/machines/autoresizing.d.ts +115 -0
  263. package/dist/cjs/sdk/resources/machines/autoresizing.d.ts.map +1 -0
  264. package/dist/cjs/sdk/resources/machines/autoresizing.js +46 -0
  265. package/dist/cjs/sdk/resources/machines/autoresizing.js.map +1 -0
  266. package/dist/cjs/sdk/resources/machines/executions/executions.d.ts +341 -0
  267. package/dist/cjs/sdk/resources/machines/executions/executions.d.ts.map +1 -0
  268. package/dist/cjs/sdk/resources/machines/executions/executions.js +161 -0
  269. package/dist/cjs/sdk/resources/machines/executions/executions.js.map +1 -0
  270. package/dist/cjs/sdk/resources/machines/executions/index.d.ts +5 -0
  271. package/dist/cjs/sdk/resources/machines/executions/index.d.ts.map +1 -0
  272. package/dist/cjs/sdk/resources/machines/executions/index.js +9 -0
  273. package/dist/cjs/sdk/resources/machines/executions/index.js.map +1 -0
  274. package/dist/cjs/sdk/resources/machines/executions/logs.d.ts +103 -0
  275. package/dist/cjs/sdk/resources/machines/executions/logs.d.ts.map +1 -0
  276. package/dist/cjs/sdk/resources/machines/executions/logs.js +48 -0
  277. package/dist/cjs/sdk/resources/machines/executions/logs.js.map +1 -0
  278. package/dist/cjs/sdk/resources/machines/executions.d.ts +2 -0
  279. package/dist/cjs/sdk/resources/machines/executions.d.ts.map +1 -0
  280. package/dist/cjs/sdk/resources/machines/executions.js +19 -0
  281. package/dist/cjs/sdk/resources/machines/executions.js.map +1 -0
  282. package/dist/cjs/sdk/resources/machines/index.d.ts +9 -0
  283. package/dist/cjs/sdk/resources/machines/index.d.ts.map +1 -0
  284. package/dist/cjs/sdk/resources/machines/index.js +13 -0
  285. package/dist/cjs/sdk/resources/machines/index.js.map +1 -0
  286. package/dist/cjs/sdk/resources/machines/machines.d.ts +496 -0
  287. package/dist/cjs/sdk/resources/machines/machines.d.ts.map +1 -0
  288. package/dist/cjs/sdk/resources/machines/machines.js +220 -0
  289. package/dist/cjs/sdk/resources/machines/machines.js.map +1 -0
  290. package/dist/cjs/sdk/resources/machines/ssh.d.ts +175 -0
  291. package/dist/cjs/sdk/resources/machines/ssh.d.ts.map +1 -0
  292. package/dist/cjs/sdk/resources/machines/ssh.js +89 -0
  293. package/dist/cjs/sdk/resources/machines/ssh.js.map +1 -0
  294. package/dist/cjs/sdk/resources/machines.d.ts +2 -0
  295. package/dist/cjs/sdk/resources/machines.d.ts.map +1 -0
  296. package/dist/cjs/sdk/resources/machines.js +19 -0
  297. package/dist/cjs/sdk/resources/machines.js.map +1 -0
  298. package/dist/cjs/sdk/resources/organization/autoresizing.d.ts +86 -0
  299. package/dist/cjs/sdk/resources/organization/autoresizing.d.ts.map +1 -0
  300. package/dist/cjs/sdk/resources/organization/autoresizing.js +38 -0
  301. package/dist/cjs/sdk/resources/organization/autoresizing.js.map +1 -0
  302. package/dist/cjs/sdk/resources/organization/index.d.ts +4 -0
  303. package/dist/cjs/sdk/resources/organization/index.d.ts.map +1 -0
  304. package/dist/cjs/sdk/resources/organization/index.js +9 -0
  305. package/dist/cjs/sdk/resources/organization/index.js.map +1 -0
  306. package/dist/cjs/sdk/resources/organization/organization.d.ts +10 -0
  307. package/dist/cjs/sdk/resources/organization/organization.d.ts.map +1 -0
  308. package/dist/cjs/sdk/resources/organization/organization.js +46 -0
  309. package/dist/cjs/sdk/resources/organization/organization.js.map +1 -0
  310. package/dist/cjs/sdk/resources/organization.d.ts +2 -0
  311. package/dist/cjs/sdk/resources/organization.d.ts.map +1 -0
  312. package/dist/cjs/sdk/resources/organization.js +19 -0
  313. package/dist/cjs/sdk/resources/organization.js.map +1 -0
  314. package/dist/cjs/sdk/resources.d.ts +3 -0
  315. package/dist/cjs/sdk/resources.d.ts.map +1 -0
  316. package/dist/cjs/sdk/resources.js +9 -0
  317. package/dist/cjs/sdk/resources.js.map +1 -0
  318. package/dist/cjs/sdk/uploads.d.ts +2 -0
  319. package/dist/cjs/sdk/uploads.d.ts.map +1 -0
  320. package/dist/cjs/sdk/uploads.js +7 -0
  321. package/dist/cjs/sdk/uploads.js.map +1 -0
  322. package/dist/cjs/sdk/version.d.ts +2 -0
  323. package/dist/cjs/sdk/version.d.ts.map +1 -0
  324. package/dist/cjs/sdk/version.js +6 -0
  325. package/dist/cjs/sdk/version.js.map +1 -0
  326. package/dist/esm/bin.d.ts +3 -0
  327. package/dist/esm/bin.d.ts.map +1 -0
  328. package/dist/esm/bin.js +5 -0
  329. package/dist/esm/bin.js.map +1 -0
  330. package/dist/esm/cli/completions.d.ts +6 -0
  331. package/dist/esm/cli/completions.d.ts.map +1 -0
  332. package/dist/esm/cli/completions.js +8 -0
  333. package/dist/esm/cli/completions.js.map +1 -0
  334. package/dist/esm/cli/credentials.d.ts +169 -0
  335. package/dist/esm/cli/credentials.d.ts.map +1 -0
  336. package/dist/esm/cli/credentials.js +614 -0
  337. package/dist/esm/cli/credentials.js.map +1 -0
  338. package/dist/esm/cli/keychain.d.ts +34 -0
  339. package/dist/esm/cli/keychain.d.ts.map +1 -0
  340. package/dist/esm/cli/keychain.js +237 -0
  341. package/dist/esm/cli/keychain.js.map +1 -0
  342. package/dist/esm/cli/list.d.ts +23 -0
  343. package/dist/esm/cli/list.d.ts.map +1 -0
  344. package/dist/esm/cli/list.js +27 -0
  345. package/dist/esm/cli/list.js.map +1 -0
  346. package/dist/esm/cli/login.d.ts +137 -0
  347. package/dist/esm/cli/login.d.ts.map +1 -0
  348. package/dist/esm/cli/login.js +2053 -0
  349. package/dist/esm/cli/login.js.map +1 -0
  350. package/dist/esm/cli/runtime.d.ts +98 -0
  351. package/dist/esm/cli/runtime.d.ts.map +1 -0
  352. package/dist/esm/cli/runtime.js +1345 -0
  353. package/dist/esm/cli/runtime.js.map +1 -0
  354. package/dist/esm/cli/toon.d.ts +6 -0
  355. package/dist/esm/cli/toon.d.ts.map +1 -0
  356. package/dist/esm/cli/toon.js +161 -0
  357. package/dist/esm/cli/toon.js.map +1 -0
  358. package/dist/esm/cli/upgrade.d.ts +28 -0
  359. package/dist/esm/cli/upgrade.d.ts.map +1 -0
  360. package/dist/esm/cli/upgrade.js +1186 -0
  361. package/dist/esm/cli/upgrade.js.map +1 -0
  362. package/dist/esm/commands/index.d.ts +47 -0
  363. package/dist/esm/commands/index.d.ts.map +1 -0
  364. package/dist/esm/commands/index.js +1195 -0
  365. package/dist/esm/commands/index.js.map +1 -0
  366. package/dist/esm/custom/browser-routing.d.ts +4 -0
  367. package/dist/esm/custom/browser-routing.d.ts.map +1 -0
  368. package/dist/esm/custom/browser-routing.js +47 -0
  369. package/dist/esm/custom/browser-routing.js.map +1 -0
  370. package/dist/esm/custom/images.d.ts +6 -0
  371. package/dist/esm/custom/images.d.ts.map +1 -0
  372. package/dist/esm/custom/images.js +68 -0
  373. package/dist/esm/custom/images.js.map +1 -0
  374. package/dist/esm/custom/machines.d.ts +20 -0
  375. package/dist/esm/custom/machines.d.ts.map +1 -0
  376. package/dist/esm/custom/machines.js +76 -0
  377. package/dist/esm/custom/machines.js.map +1 -0
  378. package/dist/esm/custom/ssh-picker.d.ts +19 -0
  379. package/dist/esm/custom/ssh-picker.d.ts.map +1 -0
  380. package/dist/esm/custom/ssh-picker.js +149 -0
  381. package/dist/esm/custom/ssh-picker.js.map +1 -0
  382. package/dist/esm/custom/ssh.d.ts +18 -0
  383. package/dist/esm/custom/ssh.d.ts.map +1 -0
  384. package/dist/esm/custom/ssh.js +147 -0
  385. package/dist/esm/custom/ssh.js.map +1 -0
  386. package/dist/esm/feedback/bundle.d.ts +41 -0
  387. package/dist/esm/feedback/bundle.d.ts.map +1 -0
  388. package/dist/esm/feedback/bundle.js +83 -0
  389. package/dist/esm/feedback/bundle.js.map +1 -0
  390. package/dist/esm/feedback/command.d.ts +6 -0
  391. package/dist/esm/feedback/command.d.ts.map +1 -0
  392. package/dist/esm/feedback/command.js +92 -0
  393. package/dist/esm/feedback/command.js.map +1 -0
  394. package/dist/esm/feedback/diagnostics.d.ts +30 -0
  395. package/dist/esm/feedback/diagnostics.d.ts.map +1 -0
  396. package/dist/esm/feedback/diagnostics.js +270 -0
  397. package/dist/esm/feedback/diagnostics.js.map +1 -0
  398. package/dist/esm/index.d.ts +4 -0
  399. package/dist/esm/index.d.ts.map +1 -0
  400. package/dist/esm/index.js +7 -0
  401. package/dist/esm/index.js.map +1 -0
  402. package/dist/esm/sdk/api-promise.d.ts +31 -0
  403. package/dist/esm/sdk/api-promise.d.ts.map +1 -0
  404. package/dist/esm/sdk/api-promise.js +68 -0
  405. package/dist/esm/sdk/api-promise.js.map +1 -0
  406. package/dist/esm/sdk/client.d.ts +267 -0
  407. package/dist/esm/sdk/client.d.ts.map +1 -0
  408. package/dist/esm/sdk/client.js +928 -0
  409. package/dist/esm/sdk/client.js.map +1 -0
  410. package/dist/esm/sdk/core/api-promise.d.ts +3 -0
  411. package/dist/esm/sdk/core/api-promise.d.ts.map +1 -0
  412. package/dist/esm/sdk/core/api-promise.js +3 -0
  413. package/dist/esm/sdk/core/api-promise.js.map +1 -0
  414. package/dist/esm/sdk/core/error.d.ts +46 -0
  415. package/dist/esm/sdk/core/error.d.ts.map +1 -0
  416. package/dist/esm/sdk/core/error.js +119 -0
  417. package/dist/esm/sdk/core/error.js.map +1 -0
  418. package/dist/esm/sdk/core/pagination.d.ts +38 -0
  419. package/dist/esm/sdk/core/pagination.d.ts.map +1 -0
  420. package/dist/esm/sdk/core/pagination.js +92 -0
  421. package/dist/esm/sdk/core/pagination.js.map +1 -0
  422. package/dist/esm/sdk/core/resource.d.ts +2 -0
  423. package/dist/esm/sdk/core/resource.d.ts.map +1 -0
  424. package/dist/esm/sdk/core/resource.js +3 -0
  425. package/dist/esm/sdk/core/resource.js.map +1 -0
  426. package/dist/esm/sdk/core/uploads.d.ts +3 -0
  427. package/dist/esm/sdk/core/uploads.d.ts.map +1 -0
  428. package/dist/esm/sdk/core/uploads.js +3 -0
  429. package/dist/esm/sdk/core/uploads.js.map +1 -0
  430. package/dist/esm/sdk/error.d.ts +3 -0
  431. package/dist/esm/sdk/error.d.ts.map +1 -0
  432. package/dist/esm/sdk/error.js +4 -0
  433. package/dist/esm/sdk/error.js.map +1 -0
  434. package/dist/esm/sdk/helpers/images.d.ts +31 -0
  435. package/dist/esm/sdk/helpers/images.d.ts.map +1 -0
  436. package/dist/esm/sdk/helpers/images.js +189 -0
  437. package/dist/esm/sdk/helpers/images.js.map +1 -0
  438. package/dist/esm/sdk/index.d.ts +7 -0
  439. package/dist/esm/sdk/index.d.ts.map +1 -0
  440. package/dist/esm/sdk/index.js +8 -0
  441. package/dist/esm/sdk/index.js.map +1 -0
  442. package/dist/esm/sdk/internal/builtin-types.d.ts +73 -0
  443. package/dist/esm/sdk/internal/builtin-types.d.ts.map +1 -0
  444. package/dist/esm/sdk/internal/builtin-types.js +3 -0
  445. package/dist/esm/sdk/internal/builtin-types.js.map +1 -0
  446. package/dist/esm/sdk/internal/detect-platform.d.ts +15 -0
  447. package/dist/esm/sdk/internal/detect-platform.d.ts.map +1 -0
  448. package/dist/esm/sdk/internal/detect-platform.js +180 -0
  449. package/dist/esm/sdk/internal/detect-platform.js.map +1 -0
  450. package/dist/esm/sdk/internal/errors.d.ts +3 -0
  451. package/dist/esm/sdk/internal/errors.d.ts.map +1 -0
  452. package/dist/esm/sdk/internal/errors.js +36 -0
  453. package/dist/esm/sdk/internal/errors.js.map +1 -0
  454. package/dist/esm/sdk/internal/headers.d.ts +43 -0
  455. package/dist/esm/sdk/internal/headers.d.ts.map +1 -0
  456. package/dist/esm/sdk/internal/headers.js +133 -0
  457. package/dist/esm/sdk/internal/headers.js.map +1 -0
  458. package/dist/esm/sdk/internal/parse.d.ts +3 -0
  459. package/dist/esm/sdk/internal/parse.d.ts.map +1 -0
  460. package/dist/esm/sdk/internal/parse.js +3 -0
  461. package/dist/esm/sdk/internal/parse.js.map +1 -0
  462. package/dist/esm/sdk/internal/qs/formats.d.ts +7 -0
  463. package/dist/esm/sdk/internal/qs/formats.d.ts.map +1 -0
  464. package/dist/esm/sdk/internal/qs/formats.js +9 -0
  465. package/dist/esm/sdk/internal/qs/formats.js.map +1 -0
  466. package/dist/esm/sdk/internal/qs/index.d.ts +10 -0
  467. package/dist/esm/sdk/internal/qs/index.d.ts.map +1 -0
  468. package/dist/esm/sdk/internal/qs/index.js +10 -0
  469. package/dist/esm/sdk/internal/qs/index.js.map +1 -0
  470. package/dist/esm/sdk/internal/qs/stringify.d.ts +3 -0
  471. package/dist/esm/sdk/internal/qs/stringify.d.ts.map +1 -0
  472. package/dist/esm/sdk/internal/qs/stringify.js +275 -0
  473. package/dist/esm/sdk/internal/qs/stringify.js.map +1 -0
  474. package/dist/esm/sdk/internal/qs/types.d.ts +57 -0
  475. package/dist/esm/sdk/internal/qs/types.d.ts.map +1 -0
  476. package/dist/esm/sdk/internal/qs/types.js +2 -0
  477. package/dist/esm/sdk/internal/qs/types.js.map +1 -0
  478. package/dist/esm/sdk/internal/qs/utils.d.ts +15 -0
  479. package/dist/esm/sdk/internal/qs/utils.d.ts.map +1 -0
  480. package/dist/esm/sdk/internal/qs/utils.js +217 -0
  481. package/dist/esm/sdk/internal/qs/utils.js.map +1 -0
  482. package/dist/esm/sdk/internal/request-options.d.ts +33 -0
  483. package/dist/esm/sdk/internal/request-options.d.ts.map +1 -0
  484. package/dist/esm/sdk/internal/request-options.js +11 -0
  485. package/dist/esm/sdk/internal/request-options.js.map +1 -0
  486. package/dist/esm/sdk/internal/shim-types.d.ts +17 -0
  487. package/dist/esm/sdk/internal/shim-types.d.ts.map +1 -0
  488. package/dist/esm/sdk/internal/shim-types.js +3 -0
  489. package/dist/esm/sdk/internal/shim-types.js.map +1 -0
  490. package/dist/esm/sdk/internal/shims.d.ts +26 -0
  491. package/dist/esm/sdk/internal/shims.d.ts.map +1 -0
  492. package/dist/esm/sdk/internal/shims.js +85 -0
  493. package/dist/esm/sdk/internal/shims.js.map +1 -0
  494. package/dist/esm/sdk/internal/to-file.d.ts +45 -0
  495. package/dist/esm/sdk/internal/to-file.d.ts.map +1 -0
  496. package/dist/esm/sdk/internal/to-file.js +88 -0
  497. package/dist/esm/sdk/internal/to-file.js.map +1 -0
  498. package/dist/esm/sdk/internal/types.d.ts +63 -0
  499. package/dist/esm/sdk/internal/types.d.ts.map +1 -0
  500. package/dist/esm/sdk/internal/types.js +3 -0
  501. package/dist/esm/sdk/internal/types.js.map +1 -0
  502. package/dist/esm/sdk/internal/uploads.d.ts +42 -0
  503. package/dist/esm/sdk/internal/uploads.d.ts.map +1 -0
  504. package/dist/esm/sdk/internal/uploads.js +148 -0
  505. package/dist/esm/sdk/internal/uploads.js.map +1 -0
  506. package/dist/esm/sdk/internal/utils/base64.d.ts +3 -0
  507. package/dist/esm/sdk/internal/utils/base64.d.ts.map +1 -0
  508. package/dist/esm/sdk/internal/utils/base64.js +33 -0
  509. package/dist/esm/sdk/internal/utils/base64.js.map +1 -0
  510. package/dist/esm/sdk/internal/utils/body.d.ts +40 -0
  511. package/dist/esm/sdk/internal/utils/body.d.ts.map +1 -0
  512. package/dist/esm/sdk/internal/utils/body.js +140 -0
  513. package/dist/esm/sdk/internal/utils/body.js.map +1 -0
  514. package/dist/esm/sdk/internal/utils/bytes.d.ts +4 -0
  515. package/dist/esm/sdk/internal/utils/bytes.d.ts.map +1 -0
  516. package/dist/esm/sdk/internal/utils/bytes.js +26 -0
  517. package/dist/esm/sdk/internal/utils/bytes.js.map +1 -0
  518. package/dist/esm/sdk/internal/utils/env.d.ts +9 -0
  519. package/dist/esm/sdk/internal/utils/env.d.ts.map +1 -0
  520. package/dist/esm/sdk/internal/utils/env.js +18 -0
  521. package/dist/esm/sdk/internal/utils/env.js.map +1 -0
  522. package/dist/esm/sdk/internal/utils/log.d.ts +52 -0
  523. package/dist/esm/sdk/internal/utils/log.d.ts.map +1 -0
  524. package/dist/esm/sdk/internal/utils/log.js +405 -0
  525. package/dist/esm/sdk/internal/utils/log.js.map +1 -0
  526. package/dist/esm/sdk/internal/utils/path.d.ts +41 -0
  527. package/dist/esm/sdk/internal/utils/path.d.ts.map +1 -0
  528. package/dist/esm/sdk/internal/utils/path.js +108 -0
  529. package/dist/esm/sdk/internal/utils/path.js.map +1 -0
  530. package/dist/esm/sdk/internal/utils/query-string.d.ts +21 -0
  531. package/dist/esm/sdk/internal/utils/query-string.d.ts.map +1 -0
  532. package/dist/esm/sdk/internal/utils/query-string.js +125 -0
  533. package/dist/esm/sdk/internal/utils/query-string.js.map +1 -0
  534. package/dist/esm/sdk/internal/utils/query.d.ts +2 -0
  535. package/dist/esm/sdk/internal/utils/query.d.ts.map +1 -0
  536. package/dist/esm/sdk/internal/utils/query.js +9 -0
  537. package/dist/esm/sdk/internal/utils/query.js.map +1 -0
  538. package/dist/esm/sdk/internal/utils/sleep.d.ts +2 -0
  539. package/dist/esm/sdk/internal/utils/sleep.d.ts.map +1 -0
  540. package/dist/esm/sdk/internal/utils/sleep.js +3 -0
  541. package/dist/esm/sdk/internal/utils/sleep.js.map +1 -0
  542. package/dist/esm/sdk/internal/utils/uuid.d.ts +5 -0
  543. package/dist/esm/sdk/internal/utils/uuid.d.ts.map +1 -0
  544. package/dist/esm/sdk/internal/utils/uuid.js +15 -0
  545. package/dist/esm/sdk/internal/utils/uuid.js.map +1 -0
  546. package/dist/esm/sdk/internal/utils/values.d.ts +18 -0
  547. package/dist/esm/sdk/internal/utils/values.d.ts.map +1 -0
  548. package/dist/esm/sdk/internal/utils/values.js +94 -0
  549. package/dist/esm/sdk/internal/utils/values.js.map +1 -0
  550. package/dist/esm/sdk/internal/utils.d.ts +8 -0
  551. package/dist/esm/sdk/internal/utils.d.ts.map +1 -0
  552. package/dist/esm/sdk/internal/utils.js +9 -0
  553. package/dist/esm/sdk/internal/utils.js.map +1 -0
  554. package/dist/esm/sdk/pagination.d.ts +3 -0
  555. package/dist/esm/sdk/pagination.d.ts.map +1 -0
  556. package/dist/esm/sdk/pagination.js +4 -0
  557. package/dist/esm/sdk/pagination.js.map +1 -0
  558. package/dist/esm/sdk/resource.d.ts +6 -0
  559. package/dist/esm/sdk/resource.d.ts.map +1 -0
  560. package/dist/esm/sdk/resource.js +8 -0
  561. package/dist/esm/sdk/resource.js.map +1 -0
  562. package/dist/esm/sdk/resources/images/images.d.ts +202 -0
  563. package/dist/esm/sdk/resources/images/images.d.ts.map +1 -0
  564. package/dist/esm/sdk/resources/images/images.js +102 -0
  565. package/dist/esm/sdk/resources/images/images.js.map +1 -0
  566. package/dist/esm/sdk/resources/images/index.d.ts +5 -0
  567. package/dist/esm/sdk/resources/images/index.d.ts.map +1 -0
  568. package/dist/esm/sdk/resources/images/index.js +4 -0
  569. package/dist/esm/sdk/resources/images/index.js.map +1 -0
  570. package/dist/esm/sdk/resources/images/uploads.d.ts +50 -0
  571. package/dist/esm/sdk/resources/images/uploads.d.ts.map +1 -0
  572. package/dist/esm/sdk/resources/images/uploads.js +24 -0
  573. package/dist/esm/sdk/resources/images/uploads.js.map +1 -0
  574. package/dist/esm/sdk/resources/images.d.ts +2 -0
  575. package/dist/esm/sdk/resources/images.d.ts.map +1 -0
  576. package/dist/esm/sdk/resources/images.js +3 -0
  577. package/dist/esm/sdk/resources/images.js.map +1 -0
  578. package/dist/esm/sdk/resources/index.d.ts +6 -0
  579. package/dist/esm/sdk/resources/index.d.ts.map +1 -0
  580. package/dist/esm/sdk/resources/index.js +5 -0
  581. package/dist/esm/sdk/resources/index.js.map +1 -0
  582. package/dist/esm/sdk/resources/machines/autoresizing.d.ts +115 -0
  583. package/dist/esm/sdk/resources/machines/autoresizing.d.ts.map +1 -0
  584. package/dist/esm/sdk/resources/machines/autoresizing.js +42 -0
  585. package/dist/esm/sdk/resources/machines/autoresizing.js.map +1 -0
  586. package/dist/esm/sdk/resources/machines/executions/executions.d.ts +341 -0
  587. package/dist/esm/sdk/resources/machines/executions/executions.d.ts.map +1 -0
  588. package/dist/esm/sdk/resources/machines/executions/executions.js +124 -0
  589. package/dist/esm/sdk/resources/machines/executions/executions.js.map +1 -0
  590. package/dist/esm/sdk/resources/machines/executions/index.d.ts +5 -0
  591. package/dist/esm/sdk/resources/machines/executions/index.d.ts.map +1 -0
  592. package/dist/esm/sdk/resources/machines/executions/index.js +4 -0
  593. package/dist/esm/sdk/resources/machines/executions/index.js.map +1 -0
  594. package/dist/esm/sdk/resources/machines/executions/logs.d.ts +103 -0
  595. package/dist/esm/sdk/resources/machines/executions/logs.d.ts.map +1 -0
  596. package/dist/esm/sdk/resources/machines/executions/logs.js +44 -0
  597. package/dist/esm/sdk/resources/machines/executions/logs.js.map +1 -0
  598. package/dist/esm/sdk/resources/machines/executions.d.ts +2 -0
  599. package/dist/esm/sdk/resources/machines/executions.d.ts.map +1 -0
  600. package/dist/esm/sdk/resources/machines/executions.js +3 -0
  601. package/dist/esm/sdk/resources/machines/executions.js.map +1 -0
  602. package/dist/esm/sdk/resources/machines/index.d.ts +9 -0
  603. package/dist/esm/sdk/resources/machines/index.d.ts.map +1 -0
  604. package/dist/esm/sdk/resources/machines/index.js +6 -0
  605. package/dist/esm/sdk/resources/machines/index.js.map +1 -0
  606. package/dist/esm/sdk/resources/machines/machines.d.ts +496 -0
  607. package/dist/esm/sdk/resources/machines/machines.d.ts.map +1 -0
  608. package/dist/esm/sdk/resources/machines/machines.js +183 -0
  609. package/dist/esm/sdk/resources/machines/machines.js.map +1 -0
  610. package/dist/esm/sdk/resources/machines/ssh.d.ts +175 -0
  611. package/dist/esm/sdk/resources/machines/ssh.d.ts.map +1 -0
  612. package/dist/esm/sdk/resources/machines/ssh.js +85 -0
  613. package/dist/esm/sdk/resources/machines/ssh.js.map +1 -0
  614. package/dist/esm/sdk/resources/machines.d.ts +2 -0
  615. package/dist/esm/sdk/resources/machines.d.ts.map +1 -0
  616. package/dist/esm/sdk/resources/machines.js +3 -0
  617. package/dist/esm/sdk/resources/machines.js.map +1 -0
  618. package/dist/esm/sdk/resources/organization/autoresizing.d.ts +86 -0
  619. package/dist/esm/sdk/resources/organization/autoresizing.d.ts.map +1 -0
  620. package/dist/esm/sdk/resources/organization/autoresizing.js +34 -0
  621. package/dist/esm/sdk/resources/organization/autoresizing.js.map +1 -0
  622. package/dist/esm/sdk/resources/organization/index.d.ts +4 -0
  623. package/dist/esm/sdk/resources/organization/index.d.ts.map +1 -0
  624. package/dist/esm/sdk/resources/organization/index.js +4 -0
  625. package/dist/esm/sdk/resources/organization/index.js.map +1 -0
  626. package/dist/esm/sdk/resources/organization/organization.d.ts +10 -0
  627. package/dist/esm/sdk/resources/organization/organization.d.ts.map +1 -0
  628. package/dist/esm/sdk/resources/organization/organization.js +9 -0
  629. package/dist/esm/sdk/resources/organization/organization.js.map +1 -0
  630. package/dist/esm/sdk/resources/organization.d.ts +2 -0
  631. package/dist/esm/sdk/resources/organization.d.ts.map +1 -0
  632. package/dist/esm/sdk/resources/organization.js +3 -0
  633. package/dist/esm/sdk/resources/organization.js.map +1 -0
  634. package/dist/esm/sdk/resources.d.ts +3 -0
  635. package/dist/esm/sdk/resources.d.ts.map +1 -0
  636. package/dist/esm/sdk/resources.js +3 -0
  637. package/dist/esm/sdk/resources.js.map +1 -0
  638. package/dist/esm/sdk/uploads.d.ts +2 -0
  639. package/dist/esm/sdk/uploads.d.ts.map +1 -0
  640. package/dist/esm/sdk/uploads.js +3 -0
  641. package/dist/esm/sdk/uploads.js.map +1 -0
  642. package/dist/esm/sdk/version.d.ts +2 -0
  643. package/dist/esm/sdk/version.d.ts.map +1 -0
  644. package/dist/esm/sdk/version.js +3 -0
  645. package/dist/esm/sdk/version.js.map +1 -0
  646. package/man/dedalus-completion.1 +35 -0
  647. package/man/dedalus-images-create.1 +126 -0
  648. package/man/dedalus-images-delete.1 +102 -0
  649. package/man/dedalus-images-import.1 +34 -0
  650. package/man/dedalus-images-list.1 +105 -0
  651. package/man/dedalus-images-retrieve.1 +102 -0
  652. package/man/dedalus-images-update.1 +108 -0
  653. package/man/dedalus-images-uploads-create.1 +112 -0
  654. package/man/dedalus-login.1 +47 -0
  655. package/man/dedalus-logout.1 +28 -0
  656. package/man/dedalus-machines-autoresizing-retrieve.1 +104 -0
  657. package/man/dedalus-machines-autoresizing-update.1 +116 -0
  658. package/man/dedalus-machines-create.1 +119 -0
  659. package/man/dedalus-machines-delete.1 +107 -0
  660. package/man/dedalus-machines-executions-create.1 +120 -0
  661. package/man/dedalus-machines-executions-delete.1 +108 -0
  662. package/man/dedalus-machines-executions-events.1 +118 -0
  663. package/man/dedalus-machines-executions-list.1 +114 -0
  664. package/man/dedalus-machines-executions-logs-create-token.1 +108 -0
  665. package/man/dedalus-machines-executions-logs-retrieve.1 +108 -0
  666. package/man/dedalus-machines-executions-output.1 +108 -0
  667. package/man/dedalus-machines-executions-retrieve.1 +108 -0
  668. package/man/dedalus-machines-list.1 +108 -0
  669. package/man/dedalus-machines-reboot.1 +107 -0
  670. package/man/dedalus-machines-resize.1 +110 -0
  671. package/man/dedalus-machines-retrieve.1 +104 -0
  672. package/man/dedalus-machines-sleep.1 +104 -0
  673. package/man/dedalus-machines-ssh-create.1 +108 -0
  674. package/man/dedalus-machines-ssh-delete.1 +108 -0
  675. package/man/dedalus-machines-ssh-list.1 +114 -0
  676. package/man/dedalus-machines-ssh-retrieve.1 +108 -0
  677. package/man/dedalus-machines-update.1 +110 -0
  678. package/man/dedalus-machines-wake.1 +104 -0
  679. package/man/dedalus-organization-autoresizing-retrieve.1 +99 -0
  680. package/man/dedalus-organization-autoresizing-update.1 +111 -0
  681. package/man/dedalus-uninstall.1 +23 -0
  682. package/man/dedalus-upgrade.1 +32 -0
  683. package/man/dedalus.1 +222 -0
  684. package/package.json +88 -4
@@ -0,0 +1,2060 @@
1
+ "use strict";
2
+ // File generated from our OpenAPI spec by Scalar. See README.md for details.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.UsageError = exports.storedCredentials = exports.runLogout = exports.runLogin = void 0;
5
+ const node_child_process_1 = require("node:child_process");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const node_http_1 = require("node:http");
8
+ const node_readline_1 = require("node:readline");
9
+ const node_process_1 = require("node:process");
10
+ const credentials_1 = require("./credentials");
11
+ const keychain_1 = require("./keychain");
12
+ const list_1 = require("./list");
13
+ /** How long the browser flow waits for the redirect before giving the terminal back. */
14
+ const BROWSER_FLOW_TIMEOUT_MS = 300000;
15
+ /**
16
+ * How long the browser flow waits for its answered pages to finish sending before closing the
17
+ * listener. Loopback delivers a page in milliseconds; this only bounds a browser that stops reading.
18
+ */
19
+ const RESPONSE_FLUSH_TIMEOUT_MS = 2000;
20
+ /** The one path the loopback listener answers as a redirect; it is what `redirect_uri` registers. */
21
+ const REDIRECT_PATH = '/callback';
22
+ /**
23
+ * Ceiling on how long the device grant polls, whatever `expires_in` claims.
24
+ *
25
+ * RFC 8628 leaves the lifetime to the server, and the server is named by the OpenAPI document. This
26
+ * is the same bound the browser grant has, so neither interactive sign-in can be made to wait
27
+ * indefinitely by an endpoint that answers `authorization_pending` and a very large expiry.
28
+ */
29
+ const DEVICE_FLOW_TIMEOUT_MS = 300000;
30
+ /**
31
+ * How long a token request may take, and how much of its answer is read.
32
+ *
33
+ * Both bounds exist because the endpoint comes from the OpenAPI document. `fetch` has no default
34
+ * timeout, and a refresh runs before every command that has no flag or environment credential, so an
35
+ * endpoint that accepts the connection and never answers would hang the whole CLI with nothing on
36
+ * screen. The size cap keeps an endless body from being buffered into memory for a response whose
37
+ * useful part is a few hundred bytes.
38
+ */
39
+ const TOKEN_REQUEST_TIMEOUT_MS = 30000;
40
+ const TOKEN_RESPONSE_LIMIT = 65536;
41
+ /**
42
+ * Deadline for each token revocation `logout` sends, and for the metadata read `login` makes only to
43
+ * learn where those go.
44
+ *
45
+ * Short because the sign-out has already happened by then — the local copy is gone — so the user is
46
+ * waiting only on a courtesy to the provider, and an endpoint that never answers must not hold the
47
+ * terminal for the full token-request deadline, once per token. The sign-in read is optional in the
48
+ * same way: its grant has succeeded, and the token is not saved until the read returns.
49
+ */
50
+ const REVOCATION_TIMEOUT_MS = 5000;
51
+ /** Refresh this far ahead of expiry so a token cannot lapse between the check and the request. */
52
+ const REFRESH_LEEWAY_MS = 60000;
53
+ /**
54
+ * Deadline for the refresh that runs ahead of an ordinary command, rather than the full one above.
55
+ *
56
+ * That refresh is not what the user asked for — it is preparation for a request they are waiting on
57
+ * — and its failure is swallowed, so the whole of its cost is delay they cannot see a reason for. An
58
+ * unreachable refresh endpoint would otherwise add `TOKEN_REQUEST_TIMEOUT_MS` to *every* invocation
59
+ * for the life of the stored entry. Giving up sooner costs a refresh that a slow provider might
60
+ * still have answered; the command then goes out with the credential it already had, and a 401 says
61
+ * to sign in again. An interactive `login` keeps the full deadline, because there the exchange is
62
+ * the thing the user is waiting for.
63
+ */
64
+ const SILENT_REFRESH_TIMEOUT_MS = 5000;
65
+ /**
66
+ * How far past its `exp` an ID token is still accepted, to absorb clock drift between this machine
67
+ * and the provider.
68
+ *
69
+ * OpenID Connect Core leaves the allowance to the client. Five minutes is the customary figure: a
70
+ * token the provider minted seconds ago is not refused because a laptop's clock runs slow, and a
71
+ * token that genuinely expired is still refused long before it could be useful to a replay.
72
+ */
73
+ const ID_TOKEN_CLOCK_SKEW_MS = 300000;
74
+ /** Lifetime assumed for a token whose endpoint did not state one. See {@link oauthMetadata}. */
75
+ const ASSUMED_TOKEN_LIFETIME_S = 3600;
76
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
77
+ /**
78
+ * Shortest device-code poll interval a server can ask for.
79
+ *
80
+ * Low enough that a local authorization server still feels immediate, high enough that one asking
81
+ * for zero cannot turn the wait for authorization into a request flood.
82
+ */
83
+ const DEVICE_POLL_FLOOR_MS = 200;
84
+ /** The grant type RFC 8628 registers, sent on each poll and looked for in server metadata. */
85
+ const DEVICE_CODE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';
86
+ const storeLocation = (auth) => ({
87
+ storeName: auth.storeName,
88
+ storeEnv: auth.storeEnv,
89
+ backend: auth.backend,
90
+ });
91
+ /** The client option keys a method fills, which for HTTP Basic is the username and password pair. */
92
+ const methodKeys = (method) => method.kind === 'basic' ? [method.usernameKey, method.passwordKey] : [method.clientKey];
93
+ /**
94
+ * Names what a request still needs when this sign-in cannot satisfy any alternative by itself.
95
+ *
96
+ * An OpenAPI `security` list is OR-of-ANDs, so an alternative can require two schemes at once — an
97
+ * API key *and* a bearer token. `login` captures one credential per run, which leaves that document
98
+ * signed in only halfway, and saying nothing would send the user off to collect a 401 whose hint
99
+ * tells them to run the command they have just run.
100
+ *
101
+ * Read back from the store rather than from what was captured, so a credential an earlier `login`
102
+ * already left behind counts towards the requirement instead of being asked for a second time.
103
+ *
104
+ * The alternative closest to complete is the one named: it is the cheapest way out for the user,
105
+ * and listing every unmet alternative would describe choices the API treats as interchangeable.
106
+ *
107
+ * `loginCommand` names the environment this `login` selected, when it selected one. The credential
108
+ * just saved is filed under that environment's URL, so a hint naming the bare command would file
109
+ * the next one under the default environment instead, and the pair would never meet. A sign-in made
110
+ * with `--base-url` selects no environment and gets the bare command, as the request hints do.
111
+ */
112
+ const outstandingRequirement = (auth, location, key, loginCommand) => {
113
+ if (auth.requirements.length === 0)
114
+ return undefined;
115
+ // The store *and* the environment, because that is what the request path counts. `applyStoredCredentials`
116
+ // treats a non-blank environment variable as an explicit value and never consults the store for
117
+ // that key, so a user who had exported one half of an AND alternative would otherwise be sent to
118
+ // obtain a credential their very next command was going to send anyway.
119
+ const held = new Set(Object.keys((0, credentials_1.readProfile)(location, key).credentials ?? {}));
120
+ for (const [clientKey, name] of Object.entries(auth.envByKey)) {
121
+ if ((process.env[name] ?? '').trim())
122
+ held.add(clientKey);
123
+ }
124
+ if (auth.requirements.some((keys) => keys.every((candidate) => held.has(candidate))))
125
+ return undefined;
126
+ const missing = auth.requirements
127
+ .map((keys) => keys.filter((candidate) => !held.has(candidate)))
128
+ .filter((keys) => keys.length > 0)
129
+ .sort((left, right) => left.length - right.length)[0];
130
+ if (!missing || missing.length === 0)
131
+ return undefined;
132
+ // A key with no method behind it cannot be asked for, so it is described rather than commanded.
133
+ // Deduplicated by method, because HTTP Basic fills two keys in one sitting and would otherwise be
134
+ // offered twice for the one sign-in that satisfies both.
135
+ const names = new Set();
136
+ const runnable = [];
137
+ for (const candidate of missing) {
138
+ const method = auth.methods.find((entry) => methodKeys(entry).includes(candidate));
139
+ if (!method || names.has(method.name))
140
+ continue;
141
+ names.add(method.name);
142
+ runnable.push(method);
143
+ }
144
+ if (runnable.length === 0)
145
+ return 'Requests here also need another credential this CLI cannot obtain.';
146
+ const commands = runnable.map((method) => loginCommand + ' --flow ' + method.name);
147
+ return ('Requests here also need ' +
148
+ (runnable.length === 1 ? 'one more credential' : String(runnable.length) + ' more credentials') +
149
+ '. Run ' +
150
+ commands.join(', then ') +
151
+ '.');
152
+ };
153
+ /**
154
+ * Runs an interactive sign-in and saves the result, returning the line to print.
155
+ *
156
+ * Every prompt is written to standard error, so the confirmation this returns is the only thing on
157
+ * standard output and `login` stays usable in a pipeline.
158
+ *
159
+ * `loginCommand` is the sign-in command for the environment this one selected (the bare command
160
+ * when none was); it names the next step when the API needs a further credential.
161
+ */
162
+ const runLogin = async (auth, baseUrl, flow, loginCommand) => {
163
+ const method = await chooseMethod(auth, flow);
164
+ const location = storeLocation(auth);
165
+ const key = (0, credentials_1.profileKey)(baseUrl);
166
+ const captured = await captureCredentials(method, baseUrl, auth.logo);
167
+ // Where it landed is reported by the write itself rather than predicted, so under `auto` the
168
+ // line a user reads names the store the credential actually went into. The write merges this
169
+ // captured result inside its lock, because a browser grant can sit for minutes before it arrives.
170
+ const backend = (0, credentials_1.writeProfile)(location, key, captured);
171
+ const line = 'Signed in to ' +
172
+ // The base URL can be the document's own `servers[0].url` when no flag and no environment
173
+ // variable named one, which makes it spec-derived text on its way to a terminal.
174
+ safeText(baseUrl || 'the API') +
175
+ '. Credentials saved to ' +
176
+ (0, credentials_1.storeDescription)(location, backend) +
177
+ '.';
178
+ const outstanding = outstandingRequirement(auth, location, key, loginCommand);
179
+ return outstanding ? line + '\n' + outstanding : line;
180
+ };
181
+ exports.runLogin = runLogin;
182
+ /**
183
+ * Forgets stored credentials and revokes their OAuth tokens, returning the line to print.
184
+ *
185
+ * The local copy goes first, under the store lock, and only then is the provider asked to revoke.
186
+ * That order is what makes an interrupted or offline `logout` safe: whatever happens on the
187
+ * network, nothing is left on disk, and the refresh path's conditional write will not bring a
188
+ * deleted profile back. Revocation is best effort — a failure is a warning, never a failed logout,
189
+ * because the user asked to be signed out here and that part has already succeeded.
190
+ */
191
+ const runLogout = async (auth, baseUrl, all) => {
192
+ const location = storeLocation(auth);
193
+ // Only an OAuth sign-in stores anything revocable, so a CLI without one skips reading what a
194
+ // profile held and signs out exactly as it did before revocation existed.
195
+ const revocable = auth.methods.some((method) => method.kind === 'oauth');
196
+ if (all) {
197
+ const { removed, failure } = (0, credentials_1.clearAll)(location, revocable);
198
+ // Every profile's revocation runs at once, so a host that never answers costs its own timeout
199
+ // rather than one per host. Warnings are raised afterwards, in store order, so the output does
200
+ // not depend on which server answered first.
201
+ const outcomes = await Promise.all(removed.map(({ key, held }) => {
202
+ const base = profileBase(auth, key);
203
+ return revokeHeld(auth, base, base, held);
204
+ }));
205
+ for (const message of outcomes.flat())
206
+ (0, credentials_1.warn)(message);
207
+ if (failure)
208
+ throw new Error(failure);
209
+ return 'Signed out everywhere.';
210
+ }
211
+ const key = (0, credentials_1.profileKey)(baseUrl);
212
+ const { outcome, held } = (0, credentials_1.deleteProfile)(location, key, revocable);
213
+ // Named without any `user:password@` it carries, in the warnings and the result line alike, so
214
+ // the two still name the host the same way.
215
+ const shown = withoutCredentials(baseUrl);
216
+ for (const message of await revokeHeld(auth, baseUrl, shown, held))
217
+ (0, credentials_1.warn)(message);
218
+ if (outcome === 'removed')
219
+ return 'Signed out of ' + safeText(shown || 'the API') + '.';
220
+ return 'No stored credentials for ' + safeText(shown || 'the API') + '.';
221
+ };
222
+ exports.runLogout = runLogout;
223
+ /**
224
+ * The base URL a profile key stands for, for `logout --all`, which knows a profile only by its key.
225
+ *
226
+ * A profile with no stored revocation endpoint falls back to the configured one, resolved against
227
+ * the base URL as sign-in resolves its endpoints. The key drops a trailing slash, which moves where
228
+ * a path-relative endpoint lands (`oauth/revoke` beside `https://api.example.com/v1/` is under
229
+ * `/v1/`, beside the key it is not), so a base URL this run knows that files under this key is
230
+ * used when there is one: the base-URL environment variable if it is still set, the configured
231
+ * default, or an environment URL. The variable comes first because sign-in prefers it to the
232
+ * default, which matters when the two differ only in a trailing slash and so share a key. Only a
233
+ * sign-in under a `--base-url` or a variable since unset falls back to the key.
234
+ *
235
+ * Credentials in the base URL are dropped, as `profileKey` drops them: the result is printed in
236
+ * warnings, and an endpoint resolved against it would carry them into a request `fetch` refuses.
237
+ */
238
+ const profileBase = (auth, key) => {
239
+ if (key === 'default')
240
+ return '';
241
+ const known = [
242
+ (auth.baseUrlEnv ? process.env[auth.baseUrlEnv] : undefined)?.trim(),
243
+ auth.defaultBaseUrl,
244
+ ...(auth.environments ?? []).map((environment) => environment.url),
245
+ ];
246
+ const base = known.find((url) => url && (0, credentials_1.profileKey)(url) === key);
247
+ return base ? withoutCredentials(base) : key;
248
+ };
249
+ /** A URL with any `user:password@` removed, or the value unchanged when it does not parse. */
250
+ const withoutCredentials = (raw) => {
251
+ try {
252
+ const url = new URL(raw);
253
+ // Rewritten only when there is something to drop: serializing adds a trailing slash to a bare
254
+ // origin, which would change how every warning names the host.
255
+ if (!url.username && !url.password)
256
+ return raw;
257
+ url.username = '';
258
+ url.password = '';
259
+ return url.toString();
260
+ }
261
+ catch {
262
+ return raw;
263
+ }
264
+ };
265
+ /**
266
+ * The revocation endpoint to keep for a token, or `undefined` for a local-only sign-out.
267
+ *
268
+ * Each candidate is resolved against `base` exactly as the token endpoint is, and the first one on
269
+ * the same origin as the endpoint that issued the token wins. `revocationUrl` is one value for
270
+ * every OAuth scheme in the CLI, so without that check a token from one provider would be posted
271
+ * to another's revocation endpoint. The discovered endpoint is held to the same rule: every
272
+ * provider that advertises one serves it beside its token endpoint, and a sign-out that stays
273
+ * local is a smaller loss than a token sent somewhere that did not issue it.
274
+ */
275
+ const revocationEndpoint = (candidates, tokenUrl, base) => {
276
+ const issuer = tokenUrl ? urlOrigin(tokenUrl, base) : undefined;
277
+ if (!issuer)
278
+ return undefined;
279
+ for (const candidate of candidates) {
280
+ const resolved = storedEndpoint(candidate, base);
281
+ if (resolved && urlOrigin(resolved, '') === issuer)
282
+ return resolved;
283
+ }
284
+ return undefined;
285
+ };
286
+ /** A URL's origin, or `undefined` when it cannot be parsed or has none (an opaque `null` origin). */
287
+ const urlOrigin = (raw, base) => {
288
+ try {
289
+ const origin = new URL(raw, base || undefined).origin;
290
+ return origin === 'null' ? undefined : origin;
291
+ }
292
+ catch {
293
+ return undefined;
294
+ }
295
+ };
296
+ /**
297
+ * Revokes what one removed profile held, returning the warnings to raise.
298
+ *
299
+ * Each warning names the base URL, because under `logout --all` that is the only way to tell which
300
+ * sign-in may still be live at its provider. `label` is the one the success line prints for a
301
+ * single `logout`, so the two lines name the host the same way. `base` is what a fallback
302
+ * endpoint resolves against: the base URL a single `logout` was given, or {@link profileBase}.
303
+ */
304
+ const revokeHeld = async (auth, base, label, held) => {
305
+ const where = 'Signed out of ' + safeText(label || 'the API') + ' locally, but ';
306
+ if (held.unreadable) {
307
+ // Said only where this build could have revoked something: a configured endpoint, or a
308
+ // provider whose published metadata (OpenID Connect discovery or RFC 8414) may name one.
309
+ // Anywhere else revocation never happens, and the keyring warning the removal already raised
310
+ // covers what is left to say.
311
+ const couldRevoke = auth.methods.some((method) => method.kind === 'oauth' &&
312
+ (method.revocationUrl !== undefined ||
313
+ method.grant === 'openIdConnect' ||
314
+ 'metadataUrl' in method ||
315
+ ('revocationMetadataUrl' in method && method.revocationMetadataUrl !== undefined)));
316
+ if (!couldRevoke)
317
+ return [];
318
+ return [
319
+ where +
320
+ 'the saved credentials could not be read back, so no OAuth token in them was revoked at the provider.',
321
+ ];
322
+ }
323
+ return revokeProfile(auth, base, held.profile, where);
324
+ };
325
+ /**
326
+ * Revokes every OAuth token one removed profile held (RFC 7009), returning the warnings to raise.
327
+ *
328
+ * API keys and Basic credentials have nothing to revoke, so only entries with OAuth metadata are
329
+ * considered. The endpoint is the one stored at sign-in; a profile that stored none falls back to
330
+ * the one this build is configured with (see {@link fallbackRevocation}). With neither, the
331
+ * sign-out stays local, as it always was. `where` opens every warning.
332
+ */
333
+ const revokeProfile = async (auth, base, profile, where) => {
334
+ const messages = [];
335
+ for (const [clientKey, meta] of Object.entries(profile.oauth ?? {})) {
336
+ // Same guard as `storedCredentials`: the store is parsed without checking the shape beneath.
337
+ if (!meta || typeof meta !== 'object')
338
+ continue;
339
+ const endpoint = meta.revocationUrl ?? fallbackRevocation(auth, clientKey, meta, base);
340
+ if (!endpoint)
341
+ continue;
342
+ const accessToken = profile.credentials?.[clientKey];
343
+ // Refresh token first: on most servers revoking it ends the whole grant, access tokens included,
344
+ // so it is the one that matters if only one call gets through.
345
+ const tokens = [
346
+ ...(meta.refreshToken ? [{ token: meta.refreshToken, hint: 'refresh_token' }] : []),
347
+ ...(accessToken ? [{ token: accessToken, hint: 'access_token' }] : []),
348
+ ];
349
+ if (tokens.length === 0)
350
+ continue;
351
+ let url;
352
+ try {
353
+ // Checked here rather than at sign-in: this is the request that carries a token, and failing
354
+ // a login over an endpoint only `logout` uses would cost the user the sign-in itself.
355
+ url = requireSecureUrl(endpoint, base, 'revocation endpoint');
356
+ }
357
+ catch (error) {
358
+ messages.push(where + 'the token was not revoked. ' + errorMessage(error));
359
+ continue;
360
+ }
361
+ try {
362
+ let refreshRevoked = false;
363
+ for (const { token, hint } of tokens) {
364
+ await revokeToken(url, token, hint, meta.clientId, refreshRevoked);
365
+ if (hint === 'refresh_token')
366
+ refreshRevoked = true;
367
+ }
368
+ }
369
+ catch (error) {
370
+ // One failure ends this profile's revocation rather than trying the next token: an endpoint
371
+ // that has just timed out or refused is unlikely to do otherwise a second later, and each try
372
+ // costs the user up to `REVOCATION_TIMEOUT_MS`.
373
+ messages.push(where + safeText(url.host) + ' could not revoke the token. ' + errorMessage(error));
374
+ }
375
+ }
376
+ return messages;
377
+ };
378
+ /**
379
+ * The configured revocation endpoint for a profile that stored none, or `undefined` to stay local.
380
+ *
381
+ * Reached by every profile that stored none: one saved before revocation existed, one whose
382
+ * sign-in had nothing configured — so a `revocationUrl` a later build adds still reaches it — and
383
+ * one whose sign-in rejected the configured endpoint. That last needs no mark of its own: the
384
+ * checks below are sign-in's rule, so the endpoint it rejected is rejected here too, and only a
385
+ * build whose configuration was since corrected revokes it.
386
+ *
387
+ * The endpoint, resolved against `base`, has to pass two origin checks. One is against the
388
+ * profile's saved refresh endpoint, the only endpoint an older profile recorded. The other is
389
+ * sign-in's own rule against the token endpoint, so a build never revokes an old sign-in somewhere
390
+ * it would refuse for a new one — a provider can serve refresh from another host than the one
391
+ * that issues tokens. A profile does not record which grant produced it, and every grant of one
392
+ * scheme shares its key, so the token endpoint of each of those grants has to pass. An OpenID
393
+ * Connect grant, or a device grant reading RFC 8414 metadata, learns its token endpoint at sign-in
394
+ * and saved that same URL as the refresh endpoint, so the first check covers it.
395
+ */
396
+ const fallbackRevocation = (auth, clientKey, meta, base) => {
397
+ const grants = auth.methods.filter((method) => method.kind === 'oauth' && method.clientKey === clientKey);
398
+ // One configured value covers every OAuth method, so any grant that carries it names it.
399
+ const configured = grants.find((method) => method.revocationUrl)?.revocationUrl;
400
+ const endpoint = revocationEndpoint([configured], meta.refreshUrl ?? meta.tokenUrl, base);
401
+ if (!endpoint)
402
+ return undefined;
403
+ // OpenID Connect and device sign-in from metadata learn their token endpoint at sign-in and saved
404
+ // it as the refresh endpoint, which the check above already covers.
405
+ const issuedHere = grants.every((method) => !('tokenUrl' in method) || revocationEndpoint([configured], method.tokenUrl, base) !== undefined);
406
+ return issuedHere ? endpoint : undefined;
407
+ };
408
+ /**
409
+ * Sends one RFC 7009 revocation request, throwing a printable reason when it did not succeed.
410
+ *
411
+ * Only the client id goes with the token: the CLI keeps no client secret, so a provider that
412
+ * requires confidential-client authentication here — typical for tokens from the client-credentials
413
+ * grant — refuses, and that surfaces as the warning rather than as anything the CLI could fix.
414
+ */
415
+ const revokeToken = async (url, token, hint, clientId, refreshRevoked) => {
416
+ const { response, text } = await postForToken(url, { token, token_type_hint: hint, ...(clientId ? { client_id: clientId } : {}) }, REVOCATION_TIMEOUT_MS, 'revocation endpoint');
417
+ // Section 2.2: a 200 is the answer for an invalid or already-revoked token too, so success says
418
+ // nothing about the token's prior state and needs nothing checked beyond the status.
419
+ if (response.ok)
420
+ return;
421
+ const payload = parseJson(text);
422
+ // Section 2.2.1 lets a server that only revokes refresh tokens answer this for an access token.
423
+ // When this call already revoked the refresh token, that ended the grant and there is nothing to
424
+ // report. With no refresh token (a client-credentials grant never gets one) the access token is
425
+ // the whole grant and is still live, so the refusal is a failure like any other.
426
+ if (hint === 'access_token' && refreshRevoked && payload?.['error'] === 'unsupported_token_type')
427
+ return;
428
+ throw new Error('The revocation endpoint rejected the request' + tokenErrorDetail(payload, response.status));
429
+ };
430
+ /** The message of a caught value, which every throw in this module makes an `Error` with safe text. */
431
+ const errorMessage = (error) => (error instanceof Error ? error.message : String(error));
432
+ /**
433
+ * Stored credentials for one base URL, keyed by client-option name.
434
+ *
435
+ * An OAuth access token at or near its expiry is refreshed in place first, so a CLI left alone
436
+ * overnight keeps working without a second `login`. A refresh that fails is swallowed: the stale
437
+ * token still goes out, and the 401 that follows carries the runtime's own sign-in hint, which
438
+ * says more than a refresh error would.
439
+ *
440
+ * Refresh exchanges may overlap, but their results merge into the current profile under the same
441
+ * lock as interactive sign-in. A server that rotates refresh tokens can still reject one of two
442
+ * simultaneous exchanges; that is recoverable with one `login` and never loses another flow's key.
443
+ */
444
+ const storedCredentials = async (auth, baseUrl) => {
445
+ const location = storeLocation(auth);
446
+ const key = (0, credentials_1.profileKey)(baseUrl);
447
+ const profile = (0, credentials_1.readProfile)(location, key);
448
+ const credentials = { ...(profile.credentials ?? {}) };
449
+ for (const [clientKey, meta] of Object.entries(profile.oauth ?? {})) {
450
+ // `readProfile` checks that the stored value parses and is an object, but not the shape beneath
451
+ // it, so an entry this CLI did not write can still be `null` or a scalar. Reading `expiresAt` off
452
+ // one would throw on the request path of *every* command — the exact failure the corrupt-store
453
+ // handling exists to avoid, and worse than it, since a bad OAuth entry would also take out the
454
+ // commands that need no credential at all. Skipping it falls back to `login`, as a missing
455
+ // entry does.
456
+ if (!meta || typeof meta !== 'object')
457
+ continue;
458
+ // Older generated CLIs stored this as `tokenUrl`; retaining that fallback makes their saved
459
+ // credentials refreshable after an upgrade while new entries preserve a distinct refresh URL.
460
+ const refreshUrl = meta.refreshUrl ?? meta.tokenUrl;
461
+ if (!needsRefresh(meta) || !meta.refreshToken || !refreshUrl)
462
+ continue;
463
+ // The target API is read from this CLI's own definition for the key, not from the stored entry,
464
+ // which never held it: it is configuration rather than something a sign-in produced, so the
465
+ // refresh follows the CLI the user is running now. A key belongs to exactly one security scheme
466
+ // (the emitter gives each scheme its own option name), and every OAuth method of that scheme
467
+ // carries the scheme's one value, so the first is as good as any.
468
+ const target = auth.methods.find((method) => method.kind === 'oauth' && method.clientKey === clientKey);
469
+ try {
470
+ const token = await exchangeToken(refreshUrl, baseUrl, {
471
+ grant_type: 'refresh_token',
472
+ refresh_token: meta.refreshToken,
473
+ ...(meta.clientId ? { client_id: meta.clientId } : {}),
474
+ ...(target?.kind === 'oauth' ? targetParams(target) : {}),
475
+ }, SILENT_REFRESH_TIMEOUT_MS);
476
+ credentials[clientKey] = token.accessToken;
477
+ // Conditional, not `writeProfile`: a `logout` that landed while the exchange was in flight
478
+ // must not be undone by the token it produced. See `refreshProfile`.
479
+ (0, credentials_1.refreshProfile)(location, key, {
480
+ credentials: { [clientKey]: token.accessToken },
481
+ oauth: {
482
+ [clientKey]: oauthMetadata(token, refreshUrl, meta.clientId, meta.refreshToken, meta.revocationUrl),
483
+ },
484
+ });
485
+ }
486
+ catch {
487
+ // Deliberately silent; see this function's doc comment.
488
+ }
489
+ }
490
+ return credentials;
491
+ };
492
+ exports.storedCredentials = storedCredentials;
493
+ const needsRefresh = (meta) => meta.expiresAt !== undefined && meta.expiresAt - Date.now() < REFRESH_LEEWAY_MS;
494
+ /**
495
+ * Picks the sign-in method to run.
496
+ *
497
+ * A named flow is matched exactly; otherwise a single method runs unprompted and several are put to
498
+ * the user. The picker needs a terminal, so a non-interactive shell is told to name a flow instead
499
+ * of being left waiting on stdin that will never arrive.
500
+ */
501
+ const chooseMethod = async (auth, flow) => {
502
+ const names = auth.methods.map((method) => method.name);
503
+ if (flow !== undefined) {
504
+ const chosen = auth.methods.find((method) => method.name === flow);
505
+ if (!chosen)
506
+ throw new UsageError("Unknown sign-in flow '" + flow + "'. Available: " + names.join(', ') + '.');
507
+ return chosen;
508
+ }
509
+ const first = auth.methods[0];
510
+ if (!first)
511
+ throw new Error('This CLI has no sign-in flow.');
512
+ if (auth.methods.length === 1)
513
+ return first;
514
+ const index = await promptChoice('How would you like to sign in?', auth.methods.map((method) => ({ label: method.label, hint: method.name })));
515
+ // The picker only ever resolves an index it drew, so `first` stands in for a state that cannot
516
+ // occur rather than an error message describing one.
517
+ return auth.methods[index] ?? first;
518
+ };
519
+ const captureCredentials = async (method, baseUrl, logo) => {
520
+ if (method.kind === 'token') {
521
+ const value = await promptSecret(method.prompt);
522
+ if (!value)
523
+ throw new UsageError('No value entered; nothing was saved.');
524
+ return { credentials: { [method.clientKey]: value } };
525
+ }
526
+ if (method.kind === 'basic') {
527
+ const username = await promptLine('Username: ');
528
+ if (!username)
529
+ throw new UsageError('No username entered; nothing was saved.');
530
+ const password = await promptSecret('Password: ');
531
+ if (!password)
532
+ throw new UsageError('No password entered; nothing was saved.');
533
+ return { credentials: { [method.usernameKey]: username, [method.passwordKey]: password } };
534
+ }
535
+ const { token, clientId, refreshUrl, revocationUrl } = await runOauthFlow(method, baseUrl, logo);
536
+ return {
537
+ credentials: { [method.clientKey]: token.accessToken },
538
+ oauth: { [method.clientKey]: oauthMetadata(token, refreshUrl, clientId, undefined, revocationUrl) },
539
+ };
540
+ };
541
+ const oauthMetadata = (token, refreshUrl, clientId, previousRefreshToken, revocationUrl) => {
542
+ // A refresh response may legitimately omit `refresh_token`, which means "keep using the one you
543
+ // have" rather than "you no longer have one"; dropping it would turn every refresh into the last.
544
+ const refreshToken = token.refreshToken ?? previousRefreshToken;
545
+ // `expires_in` is OPTIONAL in RFC 6749 §5.1, and an absent one used to mean no `expiresAt`, which
546
+ // `needsRefresh` reads as "never expires" — so a provider that omits it left the refresh token
547
+ // stored and never used, and the user re-ran the whole flow on every expiry. When there is
548
+ // something to refresh with, an unstated lifetime is assumed to be the hour nearly every provider
549
+ // issues; refreshing earlier than necessary costs one request, never refreshing costs the feature.
550
+ const lifetime = token.expiresIn ?? (refreshToken ? ASSUMED_TOKEN_LIFETIME_S : undefined);
551
+ return {
552
+ ...(refreshToken ? { refreshToken } : {}),
553
+ ...(lifetime === undefined ? {} : { expiresAt: Date.now() + lifetime * 1000 }),
554
+ refreshUrl,
555
+ ...(clientId ? { clientId } : {}),
556
+ // Carried through every refresh too, so a token renewed in place is still revoked where it was issued.
557
+ ...(revocationUrl ? { revocationUrl } : {}),
558
+ };
559
+ };
560
+ /**
561
+ * Runs one OAuth grant and picks the revocation endpoint to store beside its token.
562
+ *
563
+ * OpenID Connect picks its own, since only discovery names its token endpoint; every other grant is
564
+ * decided here, after the grant succeeded. The configured endpoint comes first, then whatever the
565
+ * scheme's RFC 8414 metadata advertises — read during a device sign-in that took its endpoints from
566
+ * there, otherwise read now by {@link metadataRevocation} — and both are held to the origin of the
567
+ * token endpoint that issued the token.
568
+ */
569
+ const runOauthFlow = async (method, baseUrl, logo) => {
570
+ if (method.grant === 'openIdConnect')
571
+ return openIdConnectFlow(method, baseUrl, logo);
572
+ const result = await runTokenGrant(method, baseUrl, logo);
573
+ // A device sign-in from metadata names no token endpoint up front; the one it discovered is the
574
+ // refresh endpoint it returned.
575
+ const tokenUrl = 'tokenUrl' in method ? method.tokenUrl : result.refreshUrl;
576
+ const discovered = result.revocationUrl
577
+ ? { revocationUrl: result.revocationUrl }
578
+ : await metadataRevocation(method, tokenUrl, baseUrl);
579
+ return {
580
+ ...result,
581
+ revocationUrl: signInRevocation(method.revocationUrl, discovered, tokenUrl, baseUrl),
582
+ };
583
+ };
584
+ /**
585
+ * The revocation endpoint a scheme's RFC 8414 metadata advertises, read once a grant has succeeded.
586
+ *
587
+ * Read only when the configured endpoint does not already qualify, so a sign-in the config covers
588
+ * costs no round trip. It passes the same issuer checks as a device sign-in from that metadata.
589
+ * Unlike that sign-in, nothing but revocation depends on it, so a failed read never fails the
590
+ * sign-in: it warns, since `logout` will then only forget the token locally, and the user should
591
+ * hear so at the one point they are watching — {@link signInRevocation} says so, with the reason
592
+ * returned here, in the one warning it raises. For the same reason it gets the revocation request's
593
+ * short deadline rather than a token request's: the grant has already succeeded, its token is not
594
+ * saved until this returns, and a provider that never answers must not hold that up.
595
+ */
596
+ const metadataRevocation = async (method, tokenUrl, baseUrl) => {
597
+ if (!('revocationMetadataUrl' in method) || !method.revocationMetadataUrl)
598
+ return {};
599
+ if (revocationEndpoint([method.revocationUrl], tokenUrl, baseUrl) !== undefined)
600
+ return {};
601
+ try {
602
+ const { payload, issuerUrl } = await readAuthorizationServerMetadata({ metadataUrl: method.revocationMetadataUrl, issuer: method.issuer }, baseUrl, REVOCATION_TIMEOUT_MS);
603
+ return { revocationUrl: advertisedRevocation(payload, issuerUrl) };
604
+ }
605
+ catch (error) {
606
+ return { failure: errorMessage(error) };
607
+ }
608
+ };
609
+ /**
610
+ * A metadata document's `revocation_endpoint` (RFC 8414 section 2), resolved against its verified
611
+ * issuer, or `undefined` when it names none.
612
+ *
613
+ * Optional, and only `logout` uses it, so a missing or odd one is left out rather than failing the
614
+ * sign-in, and its HTTPS check waits for the revocation itself.
615
+ */
616
+ const advertisedRevocation = (payload, issuerUrl) => {
617
+ const advertised = payload['revocation_endpoint'];
618
+ return typeof advertised === 'string' && advertised
619
+ ? storedEndpoint(advertised, issuerUrl.toString())
620
+ : undefined;
621
+ };
622
+ /** Runs a grant whose token endpoint the document names, returning the token and how to refresh it. */
623
+ const runTokenGrant = async (method, baseUrl, logo) => {
624
+ if (method.grant === 'authorizationCode') {
625
+ const result = await authorizationCodeFlow(method, baseUrl, logo);
626
+ return { ...result, refreshUrl: method.refreshUrl };
627
+ }
628
+ if (method.grant === 'deviceAuthorization')
629
+ return deviceAuthorizationFlow(method, baseUrl);
630
+ if (method.grant === 'clientCredentials') {
631
+ const clientId = method.clientId ?? (await promptLine('Client id: '));
632
+ if (!clientId)
633
+ throw new UsageError('No client id entered; nothing was saved.');
634
+ const clientSecret = await promptSecret('Client secret: ');
635
+ if (!clientSecret)
636
+ throw new UsageError('No client secret entered; nothing was saved.');
637
+ const token = await exchangeToken(method.tokenUrl, baseUrl, {
638
+ grant_type: 'client_credentials',
639
+ client_id: clientId,
640
+ client_secret: clientSecret,
641
+ ...scopeParam(method.scopes),
642
+ ...targetParams(method),
643
+ });
644
+ return { token, clientId, refreshUrl: method.refreshUrl };
645
+ }
646
+ // Asked for the same way the client-credentials grant above asks, so a provider that requires a
647
+ // client id on this grant — which a public client's does — can be signed into without the value
648
+ // having to be baked into the generated source. It stays optional, unlike there: RFC 6749 lets a
649
+ // client that authenticates some other way leave it out, so a blank answer omits the parameter
650
+ // rather than sending an empty one, and the prompt says which it is.
651
+ const clientId = method.clientId ?? (await promptLine('Client id (optional): '));
652
+ const username = await promptLine('Username: ');
653
+ if (!username)
654
+ throw new UsageError('No username entered; nothing was saved.');
655
+ const password = await promptSecret('Password: ');
656
+ if (!password)
657
+ throw new UsageError('No password entered; nothing was saved.');
658
+ const token = await exchangeToken(method.tokenUrl, baseUrl, {
659
+ grant_type: 'password',
660
+ username,
661
+ password,
662
+ ...(clientId ? { client_id: clientId } : {}),
663
+ ...scopeParam(method.scopes),
664
+ ...targetParams(method),
665
+ });
666
+ // Persisted so the refresh exchange can send the same client id the grant was made with; a blank
667
+ // answer stores nothing, which is what an absent one already did.
668
+ return { token, clientId: clientId || undefined, refreshUrl: method.refreshUrl };
669
+ };
670
+ /**
671
+ * Resolves an endpoint against the base URL it applies to, for storing beside the token.
672
+ *
673
+ * Deliberately not `requireSecureUrl`: nothing is sent to a revocation endpoint at sign-in, so a
674
+ * bad one must not cost the user the sign-in. `logout` checks it before a token goes out. An
675
+ * unparseable value yields `undefined`, so it is never stored; a configured one is reported at
676
+ * sign-in by {@link signInRevocation}, and a discovered one is optional and simply left out.
677
+ */
678
+ const storedEndpoint = (raw, base) => {
679
+ if (!raw)
680
+ return undefined;
681
+ try {
682
+ const url = new URL(raw, base || undefined);
683
+ // A relative endpoint inherits credentials from a `https://user:pass@host/` base. Kept, they
684
+ // would sit in the store beside the token and make `fetch` refuse every revocation request.
685
+ url.username = '';
686
+ url.password = '';
687
+ return url.toString();
688
+ }
689
+ catch {
690
+ return undefined;
691
+ }
692
+ };
693
+ /**
694
+ * The revocation endpoint to store with a token that sign-in has just obtained.
695
+ *
696
+ * Runs only once the grant succeeded, because it can warn, and a failed `login` drains warnings
697
+ * too. It warns when the configured endpoint cannot be used — unparseable, or off the token
698
+ * endpoint's origin — and nothing else qualified either, since `logout` will then only forget
699
+ * this sign-in locally, and the user should hear so while the configuration can still be fixed.
700
+ *
701
+ * It also warns when the provider's metadata could not be read for one, with nothing configured
702
+ * or alongside the configured endpoint's own reason, so both causes reach the user in one warning:
703
+ * each alone would point at only half of why `logout` will not revoke.
704
+ *
705
+ * Nothing stored means `logout` falls back to the configured endpoint under this same rule (see
706
+ * {@link fallbackRevocation}), so a rejected endpoint stays rejected there.
707
+ */
708
+ const signInRevocation = (configured, discovered, tokenUrl, baseUrl) => {
709
+ const chosen = revocationEndpoint([configured, discovered.revocationUrl], tokenUrl, baseUrl);
710
+ if (chosen !== undefined)
711
+ return chosen;
712
+ const metadataReason = discovered.failure ? ' ' + discovered.failure : '';
713
+ if (!configured) {
714
+ if (discovered.failure)
715
+ (0, credentials_1.warn)('Logout will not revoke this sign-in at the provider.' + metadataReason);
716
+ return undefined;
717
+ }
718
+ const revocationOrigin = urlOrigin(configured, baseUrl);
719
+ const issuerOrigin = urlOrigin(tokenUrl, baseUrl);
720
+ (0, credentials_1.warn)((revocationOrigin === undefined || issuerOrigin === undefined
721
+ ? 'The configured revocation endpoint is not a usable URL, so logout will not revoke this sign-in at the provider.'
722
+ : 'The configured revocation endpoint ' +
723
+ safeText(revocationOrigin) +
724
+ " is not on the token endpoint's origin " +
725
+ safeText(issuerOrigin) +
726
+ ', so logout will not revoke this sign-in at the provider.') + metadataReason);
727
+ return undefined;
728
+ };
729
+ /**
730
+ * Runs the Device Authorization Grant without needing a loopback listener or a local browser.
731
+ *
732
+ * The device code is never printed: it is the credential that the token endpoint accepts. The
733
+ * user code, by contrast, is deliberately meant to be entered in a browser and is shown beside the
734
+ * verification URL. Polling follows RFC 8628's ‘authorization_pending’ and ‘slow_down’ signals
735
+ * instead of treating them as failed sign-ins.
736
+ */
737
+ const deviceAuthorizationFlow = async (method, baseUrl) => {
738
+ // Discovered before the client id is asked for, so a provider that cannot run this grant fails
739
+ // the sign-in before the user has been asked to type anything.
740
+ const endpoints = 'metadataUrl' in method ? await discoverDeviceEndpoints(method, baseUrl) : method;
741
+ const clientId = method.clientId ?? (await promptLine('Client id: '));
742
+ if (!clientId)
743
+ throw new UsageError('No client id entered; nothing was saved.');
744
+ const device = await requestDeviceAuthorization(endpoints.deviceAuthorizationUrl, baseUrl, {
745
+ client_id: clientId,
746
+ ...scopeParam(method.scopes),
747
+ ...targetParams(method),
748
+ });
749
+ node_process_1.stderr.write('To sign in, visit:\n\n ' +
750
+ device.verificationUrl +
751
+ (device.userCode ? '\n\nThen enter code: ' + safeText(device.userCode) : '') +
752
+ '\n\nWaiting for authorization...\n');
753
+ return {
754
+ token: await pollDeviceToken(endpoints.tokenUrl, baseUrl, { client_id: clientId, ...targetParams(method) }, device),
755
+ clientId,
756
+ refreshUrl: endpoints.refreshUrl,
757
+ // From the metadata the endpoints came from, so revocation costs no second read of it.
758
+ ...('revocationUrl' in endpoints && endpoints.revocationUrl
759
+ ? { revocationUrl: endpoints.revocationUrl }
760
+ : {}),
761
+ };
762
+ };
763
+ /**
764
+ * Reads the device grant's endpoints from the authorization server's RFC 8414 metadata.
765
+ *
766
+ * Both come back absolute, resolved against the verified issuer, so the polls and every later
767
+ * refresh go to the provider that published them rather than to the API host.
768
+ */
769
+ const discoverDeviceEndpoints = async (method, baseUrl) => {
770
+ const label = AUTHORIZATION_SERVER_METADATA;
771
+ const { payload, issuerUrl } = await readAuthorizationServerMetadata(method, baseUrl);
772
+ // RFC 8414 defaults an omitted `grant_types_supported` to the code and implicit grants, but
773
+ // providers that do run this grant routinely leave the list out, so only a list that is present
774
+ // and leaves the grant out is taken as a refusal. The endpoint below is the signal that counts.
775
+ const grants = payload['grant_types_supported'];
776
+ if (Array.isArray(grants) && !grants.includes(DEVICE_CODE_GRANT)) {
777
+ throw new Error('The ' + label + ' does not list the device authorization grant as supported.');
778
+ }
779
+ const deviceAuthorizationUrl = payload['device_authorization_endpoint'];
780
+ const tokenUrl = payload['token_endpoint'];
781
+ if (typeof deviceAuthorizationUrl !== 'string' || !deviceAuthorizationUrl) {
782
+ throw new Error('The ' + label + ' names no device authorization endpoint.');
783
+ }
784
+ if (typeof tokenUrl !== 'string' || !tokenUrl)
785
+ throw new Error('The ' + label + ' names no token endpoint.');
786
+ const token = requireSecureUrl(tokenUrl, issuerUrl.toString(), 'token endpoint').toString();
787
+ const revocationUrl = advertisedRevocation(payload, issuerUrl);
788
+ return {
789
+ deviceAuthorizationUrl: requireSecureUrl(deviceAuthorizationUrl, issuerUrl.toString(), 'device authorization endpoint').toString(),
790
+ tokenUrl: token,
791
+ refreshUrl: token,
792
+ ...(revocationUrl ? { revocationUrl } : {}),
793
+ };
794
+ };
795
+ const AUTHORIZATION_SERVER_METADATA = 'authorization server metadata';
796
+ /**
797
+ * Reads a scheme's RFC 8414 metadata and holds it to its issuer: the one its URL was built from
798
+ * (section 3.3), and the configured issuer when there is one.
799
+ *
800
+ * Shared by device sign-in, which takes its endpoints from the document, and by revocation, which
801
+ * takes only `revocation_endpoint`, so the two cannot disagree about which documents to trust.
802
+ */
803
+ const readAuthorizationServerMetadata = async (method, baseUrl, timeoutMs) => {
804
+ const label = AUTHORIZATION_SERVER_METADATA;
805
+ const { payload, issuer, issuerUrl } = await readServerMetadata(method.metadataUrl, baseUrl, { document: label, issuer: 'authorization server issuer' }, metadataIssuer, timeoutMs);
806
+ if (method.issuer !== undefined && !sameIssuer(issuer, method.issuer)) {
807
+ throw new Error('The ' +
808
+ label +
809
+ ' is issued by ' +
810
+ safeText(issuer) +
811
+ ', not the configured issuer ' +
812
+ safeText(method.issuer) +
813
+ '.');
814
+ }
815
+ return { payload, issuerUrl };
816
+ };
817
+ /**
818
+ * Discovers OpenID Connect endpoints, then uses the same PKCE flow as a declared authorization code
819
+ * grant — holding the redirect's `iss` and the ID token to the issuer discovery verified.
820
+ */
821
+ const openIdConnectFlow = async (method, baseUrl, logo) => {
822
+ const endpoints = await discoverOpenIdConnect(method.discoveryUrl, baseUrl);
823
+ // A configured issuer is the publisher saying which provider this CLI signs in with. Discovery
824
+ // only proves the document agrees with the URL it came from, and that URL is spec-derived, so a
825
+ // document that points somewhere else is refused here rather than trusted for the rest of the flow.
826
+ if (method.issuer !== undefined && !sameIssuer(endpoints.issuer, method.issuer)) {
827
+ throw new Error('The OpenID Connect discovery document is issued by ' +
828
+ safeText(endpoints.issuer) +
829
+ ', not the configured issuer ' +
830
+ safeText(method.issuer) +
831
+ '.');
832
+ }
833
+ // Same randomness as `state`. `state` ties the redirect to this process; the nonce ties the ID
834
+ // token to it, so a token minted for some other sign-in cannot be replayed into this one.
835
+ const nonce = base64Url((0, node_crypto_1.randomBytes)(16));
836
+ const result = await authorizationCodeFlow({
837
+ ...method,
838
+ grant: 'authorizationCode',
839
+ authorizationUrl: endpoints.authorizationUrl,
840
+ tokenUrl: endpoints.tokenUrl,
841
+ refreshUrl: endpoints.tokenUrl,
842
+ issuer: endpoints.issuer,
843
+ }, baseUrl, logo, {
844
+ // Discovery always yields an issuer to compare a received `iss` with, but only a configured
845
+ // issuer or the provider's own advertisement makes a *missing* one a failure: RFC 9207 lets a
846
+ // server that never promised the parameter leave it out.
847
+ issRequired: method.issuer !== undefined || endpoints.issParameterSupported,
848
+ nonce,
849
+ // Run inside the code flow, while the browser is still waiting on its page, so a refused ID
850
+ // token is what the browser is told about too. It also runs before `login` writes anything:
851
+ // a token that fails is not stored, and neither is the access token that arrived beside it.
852
+ verify: (token) => {
853
+ if (token.idToken !== undefined) {
854
+ verifyIdToken(token.idToken, { issuer: endpoints.issuer, clientId: method.clientId, nonce });
855
+ }
856
+ },
857
+ });
858
+ // A configured endpoint wins over the discovered one when it sits beside the token endpoint
859
+ // discovery named; the discovered one was already resolved against the issuer.
860
+ const revocationUrl = signInRevocation(method.revocationUrl, { revocationUrl: endpoints.revocationUrl }, endpoints.tokenUrl, baseUrl);
861
+ return { ...result, refreshUrl: endpoints.tokenUrl, revocationUrl };
862
+ };
863
+ /**
864
+ * Checks the claims of an ID token the token endpoint returned, per OpenID Connect Core 3.1.3.7.
865
+ *
866
+ * The signature is deliberately not verified. Section 3.1.3.7 lets TLS server validation stand in
867
+ * for it when the token is received directly from the token endpoint — which is the only way this
868
+ * flow receives one: `exchangeToken` refuses a plain-HTTP endpoint off loopback and follows no
869
+ * redirects. Verifying it anyway would mean fetching and caching the provider's JWKS and
870
+ * implementing each signing algorithm, in a runtime that ships with no dependencies. The claims are
871
+ * what bind the token to this sign-in, and those are checked in full.
872
+ */
873
+ const verifyIdToken = (idToken, expected) => {
874
+ const claims = idTokenClaims(idToken);
875
+ const issuer = claims['iss'];
876
+ if (typeof issuer !== 'string' || !sameIssuer(issuer, expected.issuer)) {
877
+ throw new Error('The ID token was issued by ' +
878
+ (typeof issuer === 'string' ? safeText(issuer) || 'an empty issuer' : 'no issuer') +
879
+ ', but this sign-in expected ' +
880
+ safeText(expected.issuer) +
881
+ '.');
882
+ }
883
+ const audience = claims['aud'];
884
+ const audiences = typeof audience === 'string' ? [audience] : Array.isArray(audience) ? audience : [];
885
+ if (!audiences.includes(expected.clientId)) {
886
+ throw new Error('The ID token is not addressed to this client (' + safeText(expected.clientId) + ').');
887
+ }
888
+ // With more than one audience, `azp` names the party the token was actually issued to; without
889
+ // it, a token minted for another client that merely lists this one could be passed off as ours.
890
+ // A present `azp` is held to the client id even for a single audience, as the spec recommends.
891
+ const party = claims['azp'];
892
+ if ((audiences.length > 1 || party !== undefined) && party !== expected.clientId) {
893
+ throw new Error(party === undefined
894
+ ? 'The ID token lists several audiences but names no authorized party (azp).'
895
+ : 'The ID token was issued to another authorized party (azp) than this client.');
896
+ }
897
+ const expiry = claims['exp'];
898
+ if (typeof expiry !== 'number' || !Number.isFinite(expiry)) {
899
+ throw new Error('The ID token has no usable expiry.');
900
+ }
901
+ if (expiry * 1000 + ID_TOKEN_CLOCK_SKEW_MS <= Date.now()) {
902
+ throw new Error('The ID token has expired.');
903
+ }
904
+ const nonce = claims['nonce'];
905
+ if (typeof nonce !== 'string' || !sameToken(nonce, expected.nonce)) {
906
+ throw new Error('The ID token does not carry the nonce this sign-in sent, so it may belong to another sign-in.');
907
+ }
908
+ };
909
+ /**
910
+ * Decodes the payload of a compact-serialized JWT into its claims, refusing anything malformed.
911
+ *
912
+ * Strict about the alphabet because Node's base64url decoder is not: it skips characters it does
913
+ * not recognise, so a mangled segment would otherwise decode into *something* rather than fail.
914
+ */
915
+ const idTokenClaims = (idToken) => {
916
+ const malformed = new Error('The token endpoint returned a malformed ID token.');
917
+ const segments = idToken.split('.');
918
+ const payload = segments[1];
919
+ if (segments.length !== 3 || !payload || !/^[A-Za-z0-9_-]+$/u.test(payload))
920
+ throw malformed;
921
+ let claims;
922
+ try {
923
+ claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
924
+ }
925
+ catch {
926
+ throw malformed;
927
+ }
928
+ if (!claims || typeof claims !== 'object' || Array.isArray(claims))
929
+ throw malformed;
930
+ return claims;
931
+ };
932
+ /** The `scope` parameter, left off entirely when there is nothing to request. */
933
+ const scopeParam = (scopes) => scopes.length > 0 ? { scope: scopes.join(' ') } : {};
934
+ /**
935
+ * The parameters naming which API a token is for, spread into every request a grant makes.
936
+ *
937
+ * Every one — the authorization URL, the device authorization request, each token-endpoint call and
938
+ * the refresh — because providers differ in where they read it: some bind it to the authorization
939
+ * and check the token request against it, others look only at the token endpoint, and a request
940
+ * without it can come back with a token for a different API. Kept in one place so no grant can be
941
+ * written without it. Both are absent unless configured, which leaves an unconfigured CLI's
942
+ * requests exactly as they were.
943
+ */
944
+ const targetParams = (target) => ({
945
+ ...(target.resource && target.resource.length > 0 ? { resource: target.resource } : {}),
946
+ ...(target.audience ? { audience: target.audience } : {}),
947
+ });
948
+ /**
949
+ * Writes grant fields into a query or form, one entry per value.
950
+ *
951
+ * Each key is cleared first, so a field replaces whatever the authorization URL already carried
952
+ * under that name — the same outcome `searchParams.set` gives a single value — while a list still
953
+ * repeats its key.
954
+ */
955
+ const writeParams = (params, fields) => {
956
+ for (const [key, value] of Object.entries(fields)) {
957
+ params.delete(key);
958
+ for (const entry of typeof value === 'string' ? [value] : value)
959
+ params.append(key, entry);
960
+ }
961
+ return params;
962
+ };
963
+ /**
964
+ * Starts a device grant and validates the response before anything is printed to the terminal.
965
+ *
966
+ * `fields` is the whole request body — the client id, the scope and the target API — so the caller
967
+ * that knows the method decides what the grant asks for.
968
+ */
969
+ const requestDeviceAuthorization = async (endpoint, baseUrl, fields) => {
970
+ const url = requireSecureUrl(endpoint, baseUrl, 'device authorization endpoint');
971
+ const { response, text } = await postForToken(url, fields);
972
+ const payload = parseJson(text);
973
+ if (!response.ok) {
974
+ throw new Error('The device authorization endpoint rejected the request' + tokenErrorDetail(payload, response.status));
975
+ }
976
+ const deviceCode = payload?.['device_code'];
977
+ const verificationUri = payload?.['verification_uri_complete'] ?? payload?.['verification_uri'];
978
+ const userCode = payload?.['user_code'];
979
+ const expiresIn = payload?.['expires_in'];
980
+ const interval = payload?.['interval'];
981
+ if (typeof deviceCode !== 'string' ||
982
+ !deviceCode ||
983
+ typeof verificationUri !== 'string' ||
984
+ !verificationUri) {
985
+ throw new Error('The device authorization endpoint returned an incomplete response.');
986
+ }
987
+ if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {
988
+ throw new Error('The device authorization endpoint returned no usable expiry.');
989
+ }
990
+ const verificationUrl = requireSecureUrl(verificationUri, baseUrl, 'device verification URL').toString();
991
+ return {
992
+ deviceCode,
993
+ verificationUrl,
994
+ ...(typeof userCode === 'string' && userCode ? { userCode } : {}),
995
+ // Capped at this CLI's own ceiling rather than taken as given. `expires_in` comes from a
996
+ // spec-derived endpoint, so a hostile or simply wrong one answering `1e12` would make the only
997
+ // loop guard in `pollDeviceToken` unreachable and leave `login` waiting with no way out but
998
+ // Ctrl-C — the browser grant is bounded by `BROWSER_FLOW_TIMEOUT_MS` and this is its equal.
999
+ expiresAt: Date.now() + Math.min(expiresIn * 1000, DEVICE_FLOW_TIMEOUT_MS),
1000
+ // RFC 8628 defaults to five seconds. A server may shorten it — a local authorization server
1001
+ // answering instantly is the case that matters — but never below `DEVICE_POLL_FLOOR_MS`, and a
1002
+ // negative or non-finite value never shortens it at all. Honouring a literal zero polled with no
1003
+ // delay for the whole of `expires_in`: a request flood against someone else's token endpoint and
1004
+ // a pinned core here, for a grant that answers `authorization_pending` until the user acts.
1005
+ intervalMs: typeof interval === 'number' && Number.isFinite(interval) && interval >= 0
1006
+ ? Math.max(interval * 1000, DEVICE_POLL_FLOOR_MS)
1007
+ : 5000,
1008
+ };
1009
+ };
1010
+ /**
1011
+ * Polls the token endpoint until the user authorizes, declines, or the device code expires.
1012
+ *
1013
+ * `fields` carries the client id and the target API, sent on every poll beside the device code.
1014
+ */
1015
+ const pollDeviceToken = async (endpoint, baseUrl, fields, device) => {
1016
+ const url = requireSecureUrl(endpoint, baseUrl, 'token endpoint');
1017
+ let intervalMs = device.intervalMs;
1018
+ while (Date.now() < device.expiresAt) {
1019
+ await waitFor(Math.min(intervalMs, Math.max(0, device.expiresAt - Date.now())));
1020
+ if (Date.now() >= device.expiresAt)
1021
+ break;
1022
+ const { response, text } = await postForToken(url, {
1023
+ grant_type: DEVICE_CODE_GRANT,
1024
+ device_code: device.deviceCode,
1025
+ ...fields,
1026
+ });
1027
+ const payload = parseJson(text);
1028
+ if (response.ok)
1029
+ return tokenResponse(payload);
1030
+ const error = payload?.['error'];
1031
+ if (error === 'authorization_pending')
1032
+ continue;
1033
+ if (error === 'slow_down') {
1034
+ intervalMs += 5000;
1035
+ continue;
1036
+ }
1037
+ if (error === 'expired_token')
1038
+ throw new Error('The device code expired before authorization completed.');
1039
+ if (error === 'access_denied')
1040
+ throw new Error('The device authorization request was denied.');
1041
+ throw new Error('The token endpoint rejected the device code' + tokenErrorDetail(payload, response.status));
1042
+ }
1043
+ throw new Error('The device code expired before authorization completed.');
1044
+ };
1045
+ /** Waits between device-code polls without blocking the event loop that owns terminal I/O. */
1046
+ const waitFor = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
1047
+ /** The suffix OpenID Connect Discovery appends to an issuer to reach its configuration document. */
1048
+ const OPENID_CONFIGURATION_PATH = '/.well-known/openid-configuration';
1049
+ /**
1050
+ * The issuer a discovery URL was built from, per OpenID Connect Discovery section 4.
1051
+ *
1052
+ * The spec builds the URL by appending the well-known path to the issuer, so removing it again is
1053
+ * what the returned `issuer` has to match. A URL spelled any other way — a document naming a
1054
+ * configuration endpoint that is not derived from its issuer at all — yields nothing, and the
1055
+ * caller falls back to comparing origins, which still pins the answer to the host that was asked.
1056
+ */
1057
+ const discoveryIssuer = (url) => url.pathname.endsWith(OPENID_CONFIGURATION_PATH)
1058
+ ? url.origin + url.pathname.slice(0, -OPENID_CONFIGURATION_PATH.length)
1059
+ : undefined;
1060
+ /** Compares two issuer spellings, where a trailing slash is the one difference that is not one. */
1061
+ const sameIssuer = (left, right) => left.replace(/\/+$/u, '') === right.replace(/\/+$/u, '');
1062
+ /** The well-known name RFC 8414 registers for an OAuth authorization server's metadata. */
1063
+ const OAUTH_METADATA_PATH = '/.well-known/oauth-authorization-server';
1064
+ /**
1065
+ * The issuer an RFC 8414 metadata URL was built from, or `undefined` when its spelling does not say.
1066
+ *
1067
+ * RFC 8414 section 3.1 *inserts* the well-known path between the host and the issuer's own path
1068
+ * (`https://as.example/.well-known/oauth-authorization-server/tenant` for the issuer
1069
+ * `https://as.example/tenant`), and section 5 uses the same insertion for an OpenID Connect
1070
+ * configuration. OpenID Connect Discovery appends instead, so that spelling is accepted as well.
1071
+ */
1072
+ const metadataIssuer = (url) => {
1073
+ for (const wellKnown of [OAUTH_METADATA_PATH, OPENID_CONFIGURATION_PATH]) {
1074
+ if (url.pathname === wellKnown || url.pathname.startsWith(wellKnown + '/')) {
1075
+ return url.origin + url.pathname.slice(wellKnown.length);
1076
+ }
1077
+ }
1078
+ return discoveryIssuer(url);
1079
+ };
1080
+ /**
1081
+ * Fetches an authorization server's published metadata and verifies who published it.
1082
+ *
1083
+ * Both OpenID Connect Discovery (section 4.3) and RFC 8414 (section 3.3) require the document's
1084
+ * `issuer` to be the one its URL was built from. It is the check that keeps a provider from naming
1085
+ * somebody else's endpoints: without it the sign-in sends the user, and then the code or device
1086
+ * grant it obtains, wherever an untrusted document says. A URL whose spelling names no issuer is
1087
+ * held to its origin instead, which still pins the answer to the host that was asked.
1088
+ */
1089
+ const readServerMetadata = async (metadataUrl, baseUrl, labels, issuerFor, timeoutMs) => {
1090
+ const url = requireSecureUrl(metadataUrl, baseUrl, labels.document);
1091
+ const { response, text } = await getJson(url, labels.document, timeoutMs);
1092
+ const payload = parseJson(text);
1093
+ if (!response.ok) {
1094
+ throw new Error('The ' + labels.document + ' could not be read' + tokenErrorDetail(payload, response.status));
1095
+ }
1096
+ const issuer = payload?.['issuer'];
1097
+ if (!payload || typeof issuer !== 'string' || !issuer) {
1098
+ throw new Error('The ' + labels.document + ' named no issuer.');
1099
+ }
1100
+ const expected = issuerFor(url);
1101
+ const issuerUrl = requireSecureUrl(issuer, '', labels.issuer);
1102
+ const matches = expected === undefined ? issuerUrl.origin === url.origin : sameIssuer(issuer, expected);
1103
+ if (!matches) {
1104
+ throw new Error('The ' +
1105
+ labels.document +
1106
+ ' is issued by ' +
1107
+ safeText(issuer) +
1108
+ ', which is not the provider it was fetched from.');
1109
+ }
1110
+ return { payload, issuer, issuerUrl };
1111
+ };
1112
+ /**
1113
+ * Fetches the OpenID Provider configuration and keeps what the browser flow needs: the two
1114
+ * endpoints PKCE uses, the issuer the document was verified to come from, and whether the provider
1115
+ * promises RFC 9207's `iss` on its redirects — plus the revocation endpoint `logout` uses when
1116
+ * the provider advertises one.
1117
+ */
1118
+ const discoverOpenIdConnect = async (discoveryUrl, baseUrl) => {
1119
+ // Held to the appended spelling alone, which is the derivation the compiler's
1120
+ // `Config/OidcIssuerMismatch` check mirrors at generation time.
1121
+ const { payload, issuer, issuerUrl } = await readServerMetadata(discoveryUrl, baseUrl, { document: 'OpenID Connect discovery document', issuer: 'OpenID Connect issuer' }, discoveryIssuer);
1122
+ const authorizationUrl = payload['authorization_endpoint'];
1123
+ const tokenUrl = payload['token_endpoint'];
1124
+ if (typeof authorizationUrl !== 'string' || typeof tokenUrl !== 'string') {
1125
+ throw new Error('The OpenID Connect discovery document returned no usable authorization and token endpoints.');
1126
+ }
1127
+ // Resolved against the issuer, never the API base URL: a relative `/authorize` belongs to the
1128
+ // provider that answered, and resolving it against the API would point the browser — and the code
1129
+ // exchange after it — at the API host instead.
1130
+ const revocationUrl = advertisedRevocation(payload, issuerUrl);
1131
+ return {
1132
+ authorizationUrl: requireSecureUrl(authorizationUrl, issuerUrl.toString(), 'OpenID Connect authorization endpoint').toString(),
1133
+ tokenUrl: requireSecureUrl(tokenUrl, issuerUrl.toString(), 'OpenID Connect token endpoint').toString(),
1134
+ ...(revocationUrl ? { revocationUrl } : {}),
1135
+ issuer,
1136
+ issParameterSupported: payload?.['authorization_response_iss_parameter_supported'] === true,
1137
+ };
1138
+ };
1139
+ /**
1140
+ * The browser sign-in: PKCE authorization code against a loopback redirect (RFC 8252).
1141
+ *
1142
+ * The CLI is a public client, so it holds no secret; PKCE is what stops an intercepted code from
1143
+ * being redeemed by anything but this process. The redirect listens on 127.0.0.1 rather than a
1144
+ * public interface, and the authorization URL is printed before the browser is opened so a headless
1145
+ * or remote shell can still complete the flow by hand.
1146
+ *
1147
+ * `options.issRequired` makes an `iss`-less redirect a failure; it defaults to whether an issuer
1148
+ * is known at all, which for this grant means one was configured. `options.nonce` is sent on the
1149
+ * authorize request, and only OpenID Connect sends one, since only it returns an ID token to hold
1150
+ * the nonce to. `options.verify` checks the exchanged token and throws to refuse it.
1151
+ *
1152
+ * The browser's request for the redirect is answered only after the code exchange and `verify`,
1153
+ * so it never shows "Signed in" for a token this flow goes on to refuse. (Saving the credential
1154
+ * comes later, in `runLogin`; a store that then refuses the write is reported in the terminal.)
1155
+ * The wait is bounded by the token request's own timeout. Afterwards no redirect is held any more,
1156
+ * and the listener is closed only once every answered response has finished sending — or its
1157
+ * browser has gone — or `RESPONSE_FLUSH_TIMEOUT_MS` has passed.
1158
+ */
1159
+ const authorizationCodeFlow = async (method, baseUrl, logo, options = {}) => {
1160
+ const clientId = method.clientId;
1161
+ if (!clientId || !method.authorizationUrl)
1162
+ throw new Error('This flow needs a configured OAuth client id.');
1163
+ const authorizeUrl = requireSecureUrl(method.authorizationUrl, baseUrl, 'authorization endpoint');
1164
+ const verifier = base64Url((0, node_crypto_1.randomBytes)(32));
1165
+ const challenge = base64Url((0, node_crypto_1.createHash)('sha256').update(verifier).digest());
1166
+ const state = base64Url((0, node_crypto_1.randomBytes)(16));
1167
+ const server = (0, node_http_1.createServer)();
1168
+ // Bound first, and only then given its request handler. Registering the handler earlier meant a
1169
+ // bind failure (a configured `redirectPort` already in use) rejected two promises while only
1170
+ // `listen`'s was ever awaited, and the orphan took the process down with an unhandled rejection —
1171
+ // losing the friendly message and the auth exit status. Nothing can reach the port in between:
1172
+ // the browser has not been opened yet.
1173
+ // A configured port that is already taken is the one bind failure a user can act on, and Node's
1174
+ // raw `EADDRINUSE` names neither the setting that chose it nor the alternative. Port 0 lets the
1175
+ // OS pick, so it cannot reach this.
1176
+ const port = await listen(server, method.redirectPort ?? 0).catch((error) => {
1177
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
1178
+ if (code === 'EADDRINUSE' && method.redirectPort) {
1179
+ throw new Error('Port ' +
1180
+ String(method.redirectPort) +
1181
+ ' is already in use, so the sign-in redirect could not be received. Free it, or remove the configured redirect port to let the operating system choose one.');
1182
+ }
1183
+ throw error;
1184
+ });
1185
+ const listener = { answered: [], closing: false };
1186
+ const redirect = awaitRedirect(server, state, REDIRECT_PATH, logo, { expected: method.issuer, required: options.issRequired ?? method.issuer !== undefined }, listener);
1187
+ const redirectUri = 'http://127.0.0.1:' + String(port) + REDIRECT_PATH;
1188
+ try {
1189
+ authorizeUrl.searchParams.set('response_type', 'code');
1190
+ authorizeUrl.searchParams.set('client_id', clientId);
1191
+ authorizeUrl.searchParams.set('redirect_uri', redirectUri);
1192
+ authorizeUrl.searchParams.set('state', state);
1193
+ authorizeUrl.searchParams.set('code_challenge', challenge);
1194
+ authorizeUrl.searchParams.set('code_challenge_method', 'S256');
1195
+ if (options.nonce !== undefined)
1196
+ authorizeUrl.searchParams.set('nonce', options.nonce);
1197
+ if (method.scopes.length > 0)
1198
+ authorizeUrl.searchParams.set('scope', method.scopes.join(' '));
1199
+ writeParams(authorizeUrl.searchParams, targetParams(method));
1200
+ const target = authorizeUrl.toString();
1201
+ node_process_1.stderr.write('Opening your browser to sign in. If it does not open, visit:\n\n ' + target + '\n\n');
1202
+ openBrowser(target);
1203
+ const { code, respond } = await withTimeout(redirect, BROWSER_FLOW_TIMEOUT_MS, 'Timed out waiting for the browser redirect.');
1204
+ // `respond` answers the held browser request once and ignores later calls, so the `finally`
1205
+ // below can answer it with a failure on any path that got here without answering it.
1206
+ try {
1207
+ let token;
1208
+ try {
1209
+ token = await exchangeToken(method.tokenUrl, baseUrl, {
1210
+ grant_type: 'authorization_code',
1211
+ code,
1212
+ redirect_uri: redirectUri,
1213
+ client_id: clientId,
1214
+ code_verifier: verifier,
1215
+ ...targetParams(method),
1216
+ });
1217
+ }
1218
+ catch (error) {
1219
+ respond('Sign-in failed', 'Your provider did not issue a credential. Return to your terminal for details.', false);
1220
+ throw error;
1221
+ }
1222
+ options.verify?.(token);
1223
+ respond('Signed in', 'You can close this window and return to your terminal.', true);
1224
+ return { token, clientId };
1225
+ }
1226
+ finally {
1227
+ respond('Sign-in could not be verified', 'The credential your provider issued did not match this sign-in. Return to your terminal for details.', false);
1228
+ }
1229
+ }
1230
+ finally {
1231
+ // From here on a redirect is answered at once with the neutral page and never held, so nothing
1232
+ // that arrives during the wait below is left for nobody to answer — a redirect landing after the
1233
+ // browser wait timed out included.
1234
+ listener.closing = true;
1235
+ // Answered pages are waited for before any socket is destroyed: `closeAllConnections` cancels
1236
+ // writes still queued on a socket, and a page carrying a large publisher logo takes more than one
1237
+ // write to send, so closing straight after `end()` could leave the browser a blank or truncated
1238
+ // page. Bounded, so a browser that stops reading cannot hold the terminal.
1239
+ //
1240
+ // `server.close()` comes after the wait, not before it: since Node 19 it also closes every
1241
+ // connection with no request in progress, and a connection whose response has been ended but not
1242
+ // yet flushed counts as one — so closing first cut off exactly the pages this wait protects.
1243
+ await drainAnswered(listener.answered, RESPONSE_FLUSH_TIMEOUT_MS);
1244
+ server.close();
1245
+ // Belt and braces with the `connection: close` above: a connection opened but never used (a
1246
+ // browser preconnecting to the redirect host) is not covered by that header and would keep the
1247
+ // listener, and so the process, alive.
1248
+ server.closeAllConnections();
1249
+ }
1250
+ };
1251
+ /**
1252
+ * Waits, up to `ms` in all, for every answered response — including any answered while waiting.
1253
+ *
1254
+ * Re-reads the list after each batch because the listener keeps accepting until it is closed, and a
1255
+ * response written during the wait is just as liable to be cut off as one written before it.
1256
+ */
1257
+ const drainAnswered = async (answered, ms) => {
1258
+ const deadline = Date.now() + ms;
1259
+ let waited = 0;
1260
+ while (waited < answered.length && Date.now() < deadline) {
1261
+ const batch = answered.slice(waited);
1262
+ waited = answered.length;
1263
+ await settledWithin(Promise.all(batch), deadline - Date.now());
1264
+ }
1265
+ };
1266
+ /**
1267
+ * Resolves once `promise` settles or `ms` have passed, whichever is first, and never rejects.
1268
+ *
1269
+ * The timer is cleared when the promise wins, so a finished wait leaves nothing pending that would
1270
+ * keep the process alive.
1271
+ */
1272
+ const settledWithin = (promise, ms) => new Promise((resolve) => {
1273
+ const timer = setTimeout(resolve, ms);
1274
+ const done = () => {
1275
+ clearTimeout(timer);
1276
+ resolve();
1277
+ };
1278
+ promise.then(done, done);
1279
+ });
1280
+ /**
1281
+ * The style for the one page a generated CLI ever puts in front of a browser.
1282
+ *
1283
+ * Laid out after the Scalar dashboard's own sign-in confirmation: the publisher's logo centred a
1284
+ * little above the fold, then a one-line heading with its icon, then a muted line of detail — no
1285
+ * card, no accent colour. Every colour is a token on `:root` so the palette can be restyled in one
1286
+ * block.
1287
+ *
1288
+ * The font stack names Inter first and then falls back to the system UI face. Naming it is free
1289
+ * where it is already installed and costs nothing where it is not — what it must never do is
1290
+ * *fetch* it, for the same reason the rest of this page is inline.
1291
+ */
1292
+ const PAGE_STYLE =
1293
+ // Values taken from the Scalar dashboard's own theme: `--scalar-background-1` and
1294
+ // `--scalar-color-1/2`, per mode.
1295
+ //
1296
+ // `light-dark()` rather than a `prefers-color-scheme` block, so each token states its light and
1297
+ // dark value together and the palette can be read as pairs. It resolves against the
1298
+ // `color-scheme` declared on the same rule, which is why that comes first.
1299
+ //
1300
+ // Where the function is not understood (before Firefox 120, Chrome 123, Safari 17.5) every token
1301
+ // is invalid at computed-value time and the properties using them fall back to their initial
1302
+ // values — black on white. Plain, still legible, and still saying the sign-in worked, which is the
1303
+ // page's whole job.
1304
+ ':root{color-scheme:light dark;' +
1305
+ '--bg:light-dark(#fff,#0f0f0f);' +
1306
+ '--fg:light-dark(#1b1b1b,#e7e7e7);' +
1307
+ '--muted:light-dark(#757575,#a4a4a4)}' +
1308
+ '*{box-sizing:border-box}' +
1309
+ // The dashboard's auth layout: content starts 15% of the way down the viewport in a 380px column.
1310
+ 'body{margin:0;padding:15vh 48px 48px;background:var(--bg);color:var(--fg);' +
1311
+ // Mirrors `--scalar-font`, which also leads with Inter and falls back to the system UI face.
1312
+ 'font:14px/1.5 Inter,ui-sans-serif,system-ui,sans-serif;-webkit-font-smoothing:antialiased}' +
1313
+ 'main{max-width:380px;margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center}' +
1314
+ // 32px tall like the dashboard's mark, but width left to the image: a publisher's logo is as
1315
+ // often a wordmark as a square icon. The 16px margin plus the 8px gap is the dashboard's 24px.
1316
+ '.logo{display:block;height:32px;max-width:100%;margin-bottom:16px;object-fit:contain}' +
1317
+ // `text-lg` at regular weight, as the dashboard sets it, with a 20px icon beside it.
1318
+ 'h1{margin:0;display:flex;align-items:center;justify-content:center;gap:8px;font-size:16px;font-weight:400;line-height:24px}' +
1319
+ 'h1 svg{flex:none;width:20px;height:20px}' +
1320
+ // `text-sm` in `--scalar-color-2`.
1321
+ 'p{margin:0;color:var(--muted);font-size:13px;line-height:20px}';
1322
+ /**
1323
+ * The page's two marks, as Phosphor's light-weight `sign-in` and `warning` glyphs on a 256 grid —
1324
+ * the icons the dashboard's own confirmation and failure screens use.
1325
+ *
1326
+ * Phosphor Icons, Copyright (c) 2020 Phosphor Icons, MIT License (https://github.com/phosphor-icons/core).
1327
+ */
1328
+ const SIGNED_IN_ICON = 'M140.24,132.24l-40,40a6,6,0,0,1-8.48-8.48L121.51,134H24a6,6,0,0,1,0-12h97.51L91.76,92.24a6,6,0,0,1,8.48-8.48l40,40A6,6,0,0,1,140.24,132.24ZM200,34H136a6,6,0,0,0,0,12h58V210H136a6,6,0,0,0,0,12h64a6,6,0,0,0,6-6V40A6,6,0,0,0,200,34Z';
1329
+ const WARNING_ICON = 'M235.07,189.09,147.61,37.22h0a22.75,22.75,0,0,0-39.22,0L20.93,189.09a21.53,21.53,0,0,0,0,21.72A22.35,22.35,0,0,0,40.55,222h174.9a22.35,22.35,0,0,0,19.6-11.19A21.53,21.53,0,0,0,235.07,189.09ZM224.66,204.8a10.46,10.46,0,0,1-9.21,5.2H40.55a10.46,10.46,0,0,1-9.21-5.2,9.51,9.51,0,0,1,0-9.72L118.79,43.21a10.75,10.75,0,0,1,18.42,0l87.46,151.87A9.51,9.51,0,0,1,224.66,204.8ZM122,144V104a6,6,0,0,1,12,0v40a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,180Z';
1330
+ /**
1331
+ * Escapes a value for a double-quoted HTML attribute.
1332
+ *
1333
+ * The logo arrives through the generator's own filter, which admits only an `https` URL or an image
1334
+ * `data:` URI, but an SVG `data:` URI can legally carry quotes and angle brackets. Escaping here is
1335
+ * lossless — the parser decodes the entities back before the URL is read — so the attribute cannot
1336
+ * be closed early whatever the filter let through.
1337
+ */
1338
+ const escapeAttribute = (value) => value.replace(/&/gu, '&amp;').replace(/"/gu, '&quot;').replace(/</gu, '&lt;').replace(/>/gu, '&gt;');
1339
+ /**
1340
+ * The tab icon: the publisher's logo when one is configured, otherwise the page's own mark.
1341
+ *
1342
+ * Always declared, so the browser never requests `/favicon.ico` from the loopback listener — which
1343
+ * would otherwise answer a 404 to a request it only receives because the page declared no icon.
1344
+ *
1345
+ * The fallback is deliberately a status mark and not a vendor logo: this page is served by every
1346
+ * generated CLI, so a default icon identifying the SDK's *generator* would put one company's
1347
+ * branding on another's sign-in.
1348
+ *
1349
+ * The fallback's colours are a green and a red that read against a light and a dark tab strip
1350
+ * alike; `light-dark()` is no use here because the SVG is a separate document with no
1351
+ * `color-scheme` of its own. Only `#`, `<` and `>` are escaped, which is all a data URI in an
1352
+ * attribute needs, and the SVG quotes its attributes with apostrophes so the `href` can keep the
1353
+ * double quotes.
1354
+ */
1355
+ const favicon = (icon, ok, logo) => {
1356
+ if (logo)
1357
+ return '<link rel="icon" href="' + escapeAttribute(logo) + '">';
1358
+ const svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='" +
1359
+ (ok ? '#00b648' : '#dc1b19') +
1360
+ "'><path d='" +
1361
+ icon +
1362
+ "'/></svg>";
1363
+ return ('<link rel="icon" href="data:image/svg+xml,' +
1364
+ svg.replace(/#/gu, '%23').replace(/</gu, '%3C').replace(/>/gu, '%3E') +
1365
+ '">');
1366
+ };
1367
+ /**
1368
+ * Renders the redirect page.
1369
+ *
1370
+ * Inlined down to the icon, with nothing fetched but a configured logo: the listener answers on
1371
+ * loopback, where the machine may well have no route out at all, and a callback page that reached
1372
+ * for a stylesheet or a font would also tell whoever served it that a sign-in had just happened.
1373
+ * The logo is the one exception, because it is the publisher's own and the publisher's provider has
1374
+ * just seen the sign-in anyway. It is sent with no referrer, so the host serving it never sees the
1375
+ * loopback URL, and a `data:` URI keeps even that request from happening. A logo that fails to
1376
+ * load has an empty `alt`, so it leaves nothing on the page rather than a broken-image box.
1377
+ *
1378
+ * Nothing the authorization server sent is interpolated here. The provider's own `error` goes to
1379
+ * the terminal through `safeText` instead, which keeps untrusted text out of markup entirely rather
1380
+ * than relying on this function to escape it.
1381
+ */
1382
+ const redirectPage = (heading, detail, ok, logo) => {
1383
+ const icon = ok ? SIGNED_IN_ICON : WARNING_ICON;
1384
+ return ('<!doctype html><html lang="en"><meta charset="utf-8">' +
1385
+ '<meta name="viewport" content="width=device-width,initial-scale=1">' +
1386
+ '<meta name="referrer" content="no-referrer">' +
1387
+ favicon(icon, ok, logo) +
1388
+ '<title>' +
1389
+ heading +
1390
+ '</title><style>' +
1391
+ PAGE_STYLE +
1392
+ '</style><main>' +
1393
+ (logo ? '<img class="logo" src="' + escapeAttribute(logo) + '" alt="">' : '') +
1394
+ '<h1><svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="' +
1395
+ icon +
1396
+ '"/></svg>' +
1397
+ heading +
1398
+ '</h1><p>' +
1399
+ detail +
1400
+ '</p></main>');
1401
+ };
1402
+ /**
1403
+ * Resolves with the authorization code the redirect carries, holding the browser's request open.
1404
+ *
1405
+ * A redirect that fails a check here is answered at once. One that passes is left waiting, and the
1406
+ * caller answers it through `respond` once the rest of the sign-in has succeeded or failed.
1407
+ *
1408
+ * Anything that is not the redirect — a browser probing `/favicon.ico`, a stray request to the
1409
+ * port — is answered 404 and ignored, so it cannot resolve or reject the wait. The `state` is
1410
+ * compared in constant time and rejected on mismatch, which is what ties the redirect back to the
1411
+ * request this process started; the `iss` is then held to `issuer` (see {@link redirectIssuerProblem}).
1412
+ */
1413
+ const awaitRedirect = (server, state, path, logo, issuer, listener) => new Promise((resolve, reject) => {
1414
+ // Set once a redirect has resolved or rejected this wait. A later redirect — the user reloading
1415
+ // the tab while the first is still held — can change nothing, but it still deserves an answer:
1416
+ // left unanswered it would hang until the listener closed and then show an empty-response error.
1417
+ let settled = false;
1418
+ server.on('request', (request, response) => {
1419
+ const url = new URL(request.url ?? '/', 'http://127.0.0.1');
1420
+ const params = url.searchParams;
1421
+ // Listened for as soon as the request arrives, not when it is answered: a held redirect whose
1422
+ // browser gives up while the code is exchanged (a reload, a closed tab) has already fired its
1423
+ // `'close'` by the time it is answered, and a listener added then would never hear it —
1424
+ // leaving the flow to wait out `RESPONSE_FLUSH_TIMEOUT_MS` for a page nobody will read.
1425
+ const closed = new Promise((done) => response.once('close', () => done()));
1426
+ const send = (status, type, body) => {
1427
+ // `connection: close` rather than the default keep-alive: `server.close()` waits for open
1428
+ // connections, and a browser holding one would leave the CLI running after it had signed in.
1429
+ response.writeHead(status, { 'content-type': type + '; charset=utf-8', connection: 'close' });
1430
+ listener.answered.push(closed);
1431
+ response.end(body);
1432
+ };
1433
+ const finish = (status, heading, detail, ok) => {
1434
+ send(status, 'text/html', redirectPage(heading, detail, ok, logo));
1435
+ };
1436
+ // The path is part of what was registered as `redirect_uri`, so anything else is not the
1437
+ // redirect however it is decorated. Checked alongside the parameters rather than instead of
1438
+ // them: both are cheap, and together they keep this handler from reading a request that was
1439
+ // never the provider's as though it were.
1440
+ if (url.pathname !== path || (!params.has('code') && !params.has('error'))) {
1441
+ // Plain text, and deliberately not the page above: this answers a probe rather than a
1442
+ // person — a browser asking for `/favicon.ico`, or anything else that finds the port.
1443
+ send(404, 'text/plain', 'Not found.\n');
1444
+ return;
1445
+ }
1446
+ if (settled || listener.closing) {
1447
+ finish(409, 'Return to your terminal', 'This sign-in has already been received. Your terminal shows how it ended.', false);
1448
+ return;
1449
+ }
1450
+ settled = true;
1451
+ // scalar-sdk-generator:custom-code oauth-callback-singletons:start
1452
+ // Reject ambiguous callbacks before selecting any security-sensitive value.
1453
+ if (['state', 'iss', 'code', 'error'].some((name) => params.getAll(name).length > 1)) {
1454
+ finish(400, 'Sign-in could not be verified', 'This response contains repeated parameters. Return to your terminal and try again.', false);
1455
+ reject(new Error('The browser redirect contains repeated OAuth parameters.'));
1456
+ return;
1457
+ }
1458
+ // scalar-sdk-generator:custom-code oauth-callback-singletons:end
1459
+ if (!sameToken(params.get('state') ?? '', state)) {
1460
+ // Ends the sign-in rather than waiting for a better redirect. A request that reaches here is
1461
+ // on the registered path and carries a `code` or an `error`, which makes it the provider's
1462
+ // redirect rather than a stray probe — the path check above is what turns those away — so a
1463
+ // `state` that does not match is tampering or a stale tab, and neither is worth leaving the
1464
+ // user watching a terminal for `BROWSER_FLOW_TIMEOUT_MS` on the chance that a second,
1465
+ // better redirect arrives.
1466
+ finish(400, 'Sign-in could not be verified', 'This page did not match the sign-in your terminal started. Return to your terminal and try again.', false);
1467
+ reject(new Error('The browser redirect did not match this sign-in attempt.'));
1468
+ return;
1469
+ }
1470
+ // Checked before the `error` branch as well as the code: RFC 9207 puts `iss` on error
1471
+ // responses too, and an error page from a server that is not the expected one is no more its
1472
+ // provider's word than a code is.
1473
+ const issuerProblem = redirectIssuerProblem(params.get('iss'), issuer);
1474
+ if (issuerProblem) {
1475
+ finish(400, 'Sign-in could not be verified', 'This response did not come from the provider your terminal expected. Return to your terminal and try again.', false);
1476
+ reject(new Error(issuerProblem));
1477
+ return;
1478
+ }
1479
+ const failure = params.get('error');
1480
+ if (failure) {
1481
+ finish(400, 'Sign-in failed', 'Your provider declined the request. Return to your terminal for details.', false);
1482
+ reject(new Error('Authorization failed: ' + safeText(failure) + '.'));
1483
+ return;
1484
+ }
1485
+ const code = params.get('code');
1486
+ if (!code) {
1487
+ finish(400, 'Sign-in failed', 'The redirect carried no authorization code. Return to your terminal and try again.', false);
1488
+ reject(new Error('The browser redirect carried no authorization code.'));
1489
+ return;
1490
+ }
1491
+ let responded = false;
1492
+ resolve({
1493
+ code,
1494
+ respond: (heading, detail, ok) => {
1495
+ if (responded)
1496
+ return;
1497
+ responded = true;
1498
+ finish(ok ? 200 : 400, heading, detail, ok);
1499
+ },
1500
+ });
1501
+ });
1502
+ server.on('error', reject);
1503
+ });
1504
+ /**
1505
+ * Why a redirect's `iss` parameter (RFC 9207) is unacceptable, or `undefined` when it is fine.
1506
+ *
1507
+ * This is the mix-up-attack defence. `state` proves the redirect answers this process's request,
1508
+ * but not which authorization server sent it: a CLI that trusts more than one provider can have its
1509
+ * browser steered through an attacker's, and would then redeem the code that server handed back at
1510
+ * the honest provider's token endpoint — or hand an honest code to the attacker's. Naming the issuer
1511
+ * on the redirect, and refusing one that is not the server this sign-in began with, closes that
1512
+ * before the code is exchanged anywhere.
1513
+ *
1514
+ * With no expected issuer — a plain OAuth scheme and none configured — a received `iss` is
1515
+ * ignored: there is nothing to compare it with, so it can neither confirm nor refute the sender,
1516
+ * and refusing it would break every provider that sends one to clients that never asked.
1517
+ */
1518
+ const redirectIssuerProblem = (received, issuer) => {
1519
+ if (issuer.expected === undefined)
1520
+ return undefined;
1521
+ if (received === null) {
1522
+ return issuer.required
1523
+ ? 'The browser redirect did not say which provider sent it (no iss parameter), but this sign-in expected ' +
1524
+ safeText(issuer.expected) +
1525
+ '.'
1526
+ : undefined;
1527
+ }
1528
+ if (sameIssuer(received, issuer.expected))
1529
+ return undefined;
1530
+ return ('The browser redirect was sent by ' +
1531
+ (safeText(received) || 'an empty issuer') +
1532
+ ', but this sign-in expected ' +
1533
+ safeText(issuer.expected) +
1534
+ '.');
1535
+ };
1536
+ /** Starts the loopback listener, resolving with the port the OS actually bound. */
1537
+ const listen = (server, port) => new Promise((resolve, reject) => {
1538
+ server.once('error', reject);
1539
+ // 127.0.0.1 rather than every interface: the redirect is for this machine's browser alone.
1540
+ server.listen(port, '127.0.0.1', () => {
1541
+ const address = server.address();
1542
+ if (address && typeof address === 'object')
1543
+ resolve(address.port);
1544
+ else
1545
+ reject(new Error('Could not open a local port for the sign-in redirect.'));
1546
+ });
1547
+ });
1548
+ /**
1549
+ * Opens a URL in the user's browser, best effort.
1550
+ *
1551
+ * A missing opener is not an error: the URL was printed first precisely so the flow still completes
1552
+ * when this does nothing.
1553
+ *
1554
+ * The URL never reaches a shell. On Unix that is automatic — `open` and `xdg-open` are spawned
1555
+ * directly. Windows is the trap: `cmd /c start` would make cmd itself the shell, and Node quotes an
1556
+ * argument only when it contains a space, tab or quote, so every `&` in an authorization URL would
1557
+ * arrive at cmd as a command separator. That breaks the flow even on a benign document (the browser
1558
+ * opens on the first query parameter alone, with no `state` or `code_challenge`) and runs arbitrary
1559
+ * programs on a hostile one, since `&` survives URL normalization inside the path. So Windows gets
1560
+ * PowerShell with the URL in the environment, where nothing parses it as a command line at all.
1561
+ */
1562
+ const openBrowser = (url) => {
1563
+ const windows = process.platform === 'win32';
1564
+ const [file, ...args] = windows
1565
+ ? [(0, keychain_1.windowsPowerShell)(), '-NoProfile', '-NonInteractive', '-Command', '-']
1566
+ : process.platform === 'darwin'
1567
+ ? ['open', url]
1568
+ : ['xdg-open', url];
1569
+ try {
1570
+ const child = (0, node_child_process_1.spawn)(file, args, {
1571
+ stdio: windows ? ['pipe', 'ignore', 'ignore'] : 'ignore',
1572
+ detached: !windows,
1573
+ ...(windows ? { env: { ...process.env, SCALAR_BROWSER_URL: url } } : {}),
1574
+ });
1575
+ child.on('error', () => { });
1576
+ if (windows) {
1577
+ // `-FilePath` is explicit so a URL is never read as a PowerShell parameter, and the value comes
1578
+ // from the environment so it is not part of any command line.
1579
+ child.stdin?.end('Start-Process -FilePath $env:SCALAR_BROWSER_URL\n');
1580
+ }
1581
+ child.unref();
1582
+ }
1583
+ catch {
1584
+ // Ignored; see this function's doc comment.
1585
+ }
1586
+ };
1587
+ /**
1588
+ * Exchanges a grant for an access token.
1589
+ *
1590
+ * The endpoint comes from the OpenAPI document, which is untrusted input, so it is resolved against
1591
+ * the base URL and required to be HTTPS before a secret is put in the request body. Only the
1592
+ * standard `error`/`error_description` fields of a failure response are reported: a server that
1593
+ * echoed the submitted secret back would otherwise have it printed to stderr and into any CI log.
1594
+ */
1595
+ const exchangeToken = async (tokenUrl, baseUrl, body, timeoutMs = TOKEN_REQUEST_TIMEOUT_MS) => {
1596
+ const url = requireSecureUrl(tokenUrl, baseUrl, 'token endpoint');
1597
+ const { response, text } = await postForToken(url, body, timeoutMs);
1598
+ const payload = parseJson(text);
1599
+ if (!response.ok)
1600
+ throw new Error('The token endpoint rejected the request' + tokenErrorDetail(payload, response.status));
1601
+ return tokenResponse(payload);
1602
+ };
1603
+ /** Validates the standard OAuth token response fields before a token reaches the credential store. */
1604
+ const tokenResponse = (payload) => {
1605
+ const accessToken = payload?.['access_token'];
1606
+ if (typeof accessToken !== 'string' || !accessToken) {
1607
+ throw new Error('The token endpoint returned no access token.');
1608
+ }
1609
+ const refreshToken = payload?.['refresh_token'];
1610
+ const expiresIn = payload?.['expires_in'];
1611
+ const idToken = payload?.['id_token'];
1612
+ return {
1613
+ accessToken,
1614
+ ...(typeof refreshToken === 'string' && refreshToken ? { refreshToken } : {}),
1615
+ // Kept as sent, even when empty or not a string, so the OIDC flow refuses a malformed one rather
1616
+ // than treating it as absent.
1617
+ ...(idToken === undefined || idToken === null ? {} : { idToken: String(idToken) }),
1618
+ // A lifetime at or below zero is not a lifetime. Stored as one it makes `needsRefresh` true
1619
+ // forever, so every command pays a full refresh round trip ahead of the request it wanted —
1620
+ // on a token that is in fact perfectly good. The device grant already applies this floor to
1621
+ // its own `expires_in`; treating an unusable value as unstated puts the two in step.
1622
+ ...(typeof expiresIn === 'number' && Number.isFinite(expiresIn) && expiresIn > 0 ? { expiresIn } : {}),
1623
+ };
1624
+ };
1625
+ /** Fetches a bounded JSON document under the same timeout policy used for token endpoints. */
1626
+ const getJson = async (url, label, timeoutMs = TOKEN_REQUEST_TIMEOUT_MS) => {
1627
+ try {
1628
+ const response = await fetch(url, {
1629
+ headers: { accept: 'application/json' },
1630
+ // Same reasoning as `postForToken`, for the document that *names* the endpoints rather than
1631
+ // the exchange itself: only the URL handed to `fetch` was checked, so a spec-derived
1632
+ // discovery endpoint that redirects would have its answer — the authorization and token URLs
1633
+ // this whole grant is then pointed at — supplied by whichever origin it forwarded to.
1634
+ redirect: 'error',
1635
+ signal: AbortSignal.timeout(timeoutMs),
1636
+ });
1637
+ return { response, text: await readBounded(response) };
1638
+ }
1639
+ catch (error) {
1640
+ const name = error instanceof Error ? error.name : '';
1641
+ throw new Error(name === 'TimeoutError' || name === 'AbortError'
1642
+ ? 'The ' + label + ' did not answer within ' + String(timeoutMs / 1000) + ' seconds.'
1643
+ : 'Could not reach the ' + label + '.');
1644
+ }
1645
+ };
1646
+ /**
1647
+ * Posts the grant and reads the answer, both under one deadline.
1648
+ *
1649
+ * The read is inside the same `try` as the request because the deadline covers it too: a server
1650
+ * that answers its headers and then stalls aborts here, and without this the user would see a raw
1651
+ * `The operation was aborted due to timeout` instead of a sentence naming the endpoint.
1652
+ *
1653
+ * The endpoint is spec-derived, so its own error text is never repeated beyond whether the wait ran
1654
+ * out: a `fetch` failure can carry the request, and the request carries the secret.
1655
+ *
1656
+ * `label` names the endpoint in those two messages, so a revocation that times out does not
1657
+ * claim to have been a token request.
1658
+ */
1659
+ const postForToken = async (url, body, timeoutMs = TOKEN_REQUEST_TIMEOUT_MS, label = 'token endpoint') => {
1660
+ try {
1661
+ const response = await fetch(url, {
1662
+ method: 'POST',
1663
+ headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },
1664
+ body: writeParams(new URLSearchParams(), body).toString(),
1665
+ // A 307/308 preserves the POST body. Do not let a spec-derived endpoint forward a refresh
1666
+ // token, authorization code, or device code to a different (possibly insecure) origin.
1667
+ redirect: 'error',
1668
+ signal: AbortSignal.timeout(timeoutMs),
1669
+ });
1670
+ return { response, text: await readBounded(response) };
1671
+ }
1672
+ catch (error) {
1673
+ // A deadline reached during the request is a `TimeoutError`; one reached while the body is
1674
+ // still arriving surfaces as the abort itself.
1675
+ const name = error instanceof Error ? error.name : '';
1676
+ throw new Error(name === 'TimeoutError' || name === 'AbortError'
1677
+ ? 'The ' + label + ' did not answer within ' + String(timeoutMs / 1000) + ' seconds.'
1678
+ : 'Could not reach the ' + label + '.');
1679
+ }
1680
+ };
1681
+ /**
1682
+ * Reads at most {@link TOKEN_RESPONSE_LIMIT} characters of a response body.
1683
+ *
1684
+ * `response.text()` would buffer whatever the endpoint chooses to send before anything could cap
1685
+ * it, so the stream is read in pieces and cancelled once there is more than enough to parse.
1686
+ */
1687
+ const readBounded = async (response) => {
1688
+ const body = response.body;
1689
+ if (!body)
1690
+ return '';
1691
+ const reader = body.getReader();
1692
+ const decoder = new TextDecoder();
1693
+ let text = '';
1694
+ try {
1695
+ while (text.length < TOKEN_RESPONSE_LIMIT) {
1696
+ const { done, value } = await reader.read();
1697
+ if (done)
1698
+ break;
1699
+ text += decoder.decode(value, { stream: true });
1700
+ }
1701
+ }
1702
+ finally {
1703
+ // Nothing is read after this point, and an abandoned body keeps the socket open.
1704
+ void reader.cancel().catch(() => { });
1705
+ }
1706
+ return text.slice(0, TOKEN_RESPONSE_LIMIT);
1707
+ };
1708
+ const parseJson = (text) => {
1709
+ try {
1710
+ const parsed = JSON.parse(text);
1711
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
1712
+ }
1713
+ catch {
1714
+ return undefined;
1715
+ }
1716
+ };
1717
+ const tokenErrorDetail = (payload, status) => {
1718
+ const code = payload?.['error'];
1719
+ const description = payload?.['error_description'];
1720
+ const parts = [
1721
+ typeof code === 'string' ? code : undefined,
1722
+ typeof description === 'string' ? description : undefined,
1723
+ ]
1724
+ .filter((part) => !!part)
1725
+ .map(safeText);
1726
+ return parts.length > 0 ? ' (' + parts.join(': ') + ').' : ' with status ' + String(status) + '.';
1727
+ };
1728
+ /**
1729
+ * Resolves a spec-derived endpoint and refuses to send credentials to it in the clear.
1730
+ *
1731
+ * HTTPS is required, with the usual carve-out for loopback so a local development authorization
1732
+ * server still works. Without this, a document naming an `http://` token endpoint would have the
1733
+ * CLI post a client secret unencrypted to whatever host it named.
1734
+ */
1735
+ const requireSecureUrl = (raw, baseUrl, label) => {
1736
+ let url;
1737
+ try {
1738
+ url = new URL(raw, baseUrl || undefined);
1739
+ }
1740
+ catch {
1741
+ throw new Error('The ' + label + ' is not a usable URL.');
1742
+ }
1743
+ if (url.protocol === 'https:')
1744
+ return url;
1745
+ if (url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname))
1746
+ return url;
1747
+ throw new Error('Refusing to send credentials to a non-HTTPS ' +
1748
+ label +
1749
+ ' (' +
1750
+ safeText(url.protocol + '//' + url.host) +
1751
+ ').');
1752
+ };
1753
+ /**
1754
+ * Compares two opaque tokens without leaking their contents through timing.
1755
+ *
1756
+ * Lengths are compared first because `timingSafeEqual` throws on a mismatch; both values here are
1757
+ * fixed-length base64url, so an unequal length is already a mismatch.
1758
+ */
1759
+ const sameToken = (received, expected) => {
1760
+ const a = Buffer.from(received);
1761
+ const b = Buffer.from(expected);
1762
+ return a.length === b.length && (0, node_crypto_1.timingSafeEqual)(a, b);
1763
+ };
1764
+ const base64Url = (bytes) => bytes.toString('base64url');
1765
+ /**
1766
+ * Strips anything from a network-supplied string that a terminal would act on.
1767
+ *
1768
+ * An OAuth error message reaches stderr verbatim, and both the redirect query and the token
1769
+ * endpoint's response body are written by whoever the document points at, so control characters
1770
+ * (escape sequences that reposition the cursor or recolour the line) are removed and the result is
1771
+ * capped rather than printed as received.
1772
+ */
1773
+ const safeText = (value) => {
1774
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
1775
+ const stripped = value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, ' ').trim();
1776
+ return stripped.length > 200 ? stripped.slice(0, 200) + '...' : stripped;
1777
+ };
1778
+ const withTimeout = (promise, ms, message) => new Promise((resolve, reject) => {
1779
+ const timer = setTimeout(() => reject(new Error(message)), ms);
1780
+ promise.then((value) => {
1781
+ clearTimeout(timer);
1782
+ resolve(value);
1783
+ }, (error) => {
1784
+ clearTimeout(timer);
1785
+ reject(error instanceof Error ? error : new Error(String(error)));
1786
+ });
1787
+ });
1788
+ /**
1789
+ * A failure the user can fix by invoking the command differently, reported as a usage error.
1790
+ *
1791
+ * Distinguished from an authentication failure so a script can tell "you typed the wrong flow name"
1792
+ * (retrying will not help) from "the credential was rejected" (a fresh sign-in might).
1793
+ */
1794
+ class UsageError extends Error {
1795
+ }
1796
+ exports.UsageError = UsageError;
1797
+ const NO_TERMINAL = 'Sign-in needs an interactive terminal. Run it from a terminal, pick a flow that needs no prompt with --flow <name> (see --help), or set the credential through its flag or environment variable instead.';
1798
+ /** Puts the terminal cursor back, for the picker's own exit paths and for a signal that skips them. */
1799
+ const restoreCursor = () => {
1800
+ node_process_1.stderr.write('\u001b[?25h');
1801
+ };
1802
+ /**
1803
+ * Puts a list of choices to the user and resolves the index of the one chosen.
1804
+ *
1805
+ * A highlight moved with the arrow keys rather than a number typed at a prompt, so the row being
1806
+ * chosen is the row being read. Only the highlighted row carries its `--flow` value, which is what
1807
+ * keeps a nine-line list from spelling out nine of them; the value is still on screen for whichever
1808
+ * flow the user is about to pick, which is the one they would want to write down.
1809
+ *
1810
+ * Raw mode is what delivers a keystroke without Enter behind it, and is required rather than fallen
1811
+ * back from, exactly as `promptSecret` requires it: a terminal Node reports as a TTY always offers
1812
+ * it, so the alternative was a branch that could not be reached to be tested. A digit still jumps to
1813
+ * its row, so the keys that answered the numbered prompt this replaces still answer this one.
1814
+ *
1815
+ * Drawn on stderr, like every other prompt, so stdout stays machine-readable.
1816
+ */
1817
+ const promptChoice = async (heading, choices) => {
1818
+ if (!node_process_1.stdin.isTTY || typeof node_process_1.stdin.setRawMode !== 'function') {
1819
+ throw new UsageError(NO_TERMINAL);
1820
+ }
1821
+ return new Promise((resolve, reject) => {
1822
+ let active = 0;
1823
+ // How many rows the last draw wrote, which is how far back up the cursor has to go to redraw
1824
+ // over them. Without it every keystroke would leave another copy of the list in the scrollback.
1825
+ let drawn = 0;
1826
+ const width = () => Number(node_process_1.stderr.columns) || 80;
1827
+ /**
1828
+ * The slice of choices to draw, keeping the highlight inside it.
1829
+ *
1830
+ * A list taller than the terminal would scroll the region the redraw is about to move back over,
1831
+ * putting every subsequent frame in the wrong place, so a long list is windowed instead.
1832
+ */
1833
+ const window = () => {
1834
+ const room = Math.max(1, (Number(node_process_1.stderr.rows) || 24) - 3);
1835
+ if (choices.length <= room)
1836
+ return choices;
1837
+ const start = Math.min(Math.max(0, active - (room >> 1)), choices.length - room);
1838
+ return choices.slice(start, start + room);
1839
+ };
1840
+ const render = () => {
1841
+ if (drawn > 0)
1842
+ node_process_1.stderr.write('\u001b[' + String(drawn) + 'A');
1843
+ const columns = width();
1844
+ const shown = window();
1845
+ const lines = [(0, list_1.clip)(heading, columns)];
1846
+ for (const choice of shown) {
1847
+ const selected = choice === choices[active];
1848
+ const head = (selected ? '> ' : ' ') + choice.label;
1849
+ const tail = ' (--flow ' + choice.hint + ')';
1850
+ const plain = (0, list_1.clip)(head + tail, columns);
1851
+ // The flow name is kept on every row rather than only the highlighted one, because labels
1852
+ // are not unique: an API key in a header, in a query parameter and in a cookie all read as
1853
+ // `Enter your API key`, and the flow name is the only thing telling those rows apart. It is
1854
+ // dimmed so the labels still read as the list, and dropped entirely from a row too narrow
1855
+ // to hold it, where clipping has already eaten the closing bracket.
1856
+ const row = plain === head + tail ? head + '\u001b[2m' + tail + '\u001b[22m' : plain;
1857
+ lines.push(selected ? '\u001b[36m' + row + '\u001b[39m' : row);
1858
+ }
1859
+ const position = shown.length < choices.length ? String(active + 1) + '/' + String(choices.length) + ', ' : '';
1860
+ lines.push((0, list_1.clip)(' ' + position + 'arrows to move, enter to choose', columns));
1861
+ // Each row is cleared to its end before being rewritten: a shorter line would otherwise leave
1862
+ // the tail of whatever longer one occupied that row in the frame before.
1863
+ node_process_1.stderr.write(lines.map((line) => '\u001b[2K' + line).join('\n') + '\n');
1864
+ drawn = lines.length;
1865
+ };
1866
+ const move = (delta) => {
1867
+ active = (active + delta + choices.length) % choices.length;
1868
+ render();
1869
+ };
1870
+ const jump = (index) => {
1871
+ if (index < 0 || index >= choices.length)
1872
+ return;
1873
+ active = index;
1874
+ render();
1875
+ };
1876
+ const onData = (chunk) => {
1877
+ let index = 0;
1878
+ while (index < chunk.length) {
1879
+ const character = chunk[index] ?? '';
1880
+ if (character === '\u001b') {
1881
+ // An arrow is ESC [ A or ESC O A. The sequence is scanned to its final byte so that no
1882
+ // part of it is read as a keystroke in its own right — the `[` of an Up arrow would
1883
+ // otherwise be an ordinary printable character arriving between two frames.
1884
+ let cursor = index + 1;
1885
+ if (chunk[cursor] === '[' || chunk[cursor] === 'O') {
1886
+ cursor += 1;
1887
+ // A CSI sequence ends at its final byte, anything in the range `@` to `~`.
1888
+ while (cursor < chunk.length) {
1889
+ const byte = chunk[cursor] ?? '';
1890
+ if (byte >= '@' && byte <= '~')
1891
+ break;
1892
+ cursor += 1;
1893
+ }
1894
+ const final = chunk[cursor] ?? '';
1895
+ if (final === 'A')
1896
+ move(-1);
1897
+ else if (final === 'B')
1898
+ move(1);
1899
+ else if (final === 'H')
1900
+ jump(0);
1901
+ else if (final === 'F')
1902
+ jump(choices.length - 1);
1903
+ index = cursor + 1;
1904
+ continue;
1905
+ }
1906
+ index = cursor;
1907
+ continue;
1908
+ }
1909
+ index += 1;
1910
+ if (character === '\r' || character === '\n') {
1911
+ finish();
1912
+ return;
1913
+ }
1914
+ // Ctrl-C and Ctrl-D: raw mode has taken the terminal's own handling away, so the cancel has
1915
+ // to be honoured here or the picker could never be escaped.
1916
+ if (character === '\u0003' || character === '\u0004') {
1917
+ finish(new UsageError('Cancelled.'));
1918
+ return;
1919
+ }
1920
+ // Ctrl-P and Ctrl-N beside the vim keys, so the three habits a terminal user might arrive
1921
+ // with all work without anything on screen having to offer them.
1922
+ if (character === 'k' || character === '\u0010')
1923
+ move(-1);
1924
+ else if (character === 'j' || character === '\u000e')
1925
+ move(1);
1926
+ else if (character >= '1' && character <= '9')
1927
+ jump(Number(character) - 1);
1928
+ }
1929
+ };
1930
+ const finish = (error) => {
1931
+ node_process_1.stdin.off('data', onData);
1932
+ node_process_1.stdin.setRawMode(false);
1933
+ node_process_1.stdin.pause();
1934
+ // The list is wound back over and replaced by the single line saying what was chosen: a menu
1935
+ // is worth reading while it is being answered and is clutter in the scrollback afterwards.
1936
+ if (drawn > 0)
1937
+ node_process_1.stderr.write('\u001b[' + String(drawn) + 'A\u001b[0J');
1938
+ process.off('exit', restoreCursor);
1939
+ restoreCursor();
1940
+ if (error) {
1941
+ reject(error);
1942
+ return;
1943
+ }
1944
+ const chosen = choices[active];
1945
+ node_process_1.stderr.write(heading + ' ' + (chosen?.label ?? '') + ' (--flow ' + (chosen?.hint ?? '') + ')\n');
1946
+ resolve(active);
1947
+ };
1948
+ // Hidden for the duration: the cursor would otherwise sit at the end of the last row drawn,
1949
+ // blinking somewhere that has nothing to do with the highlight. Restoring it is registered with
1950
+ // the hide rather than left to `finish`, because a signal that ends the process while the menu
1951
+ // is up reaches neither the key handler nor the rejection paths, and an invisible cursor
1952
+ // outlives the CLI it belonged to — the user's next shell prompt is where they find out.
1953
+ process.on('exit', restoreCursor);
1954
+ node_process_1.stderr.write('\u001b[?25l');
1955
+ node_process_1.stdin.setRawMode(true);
1956
+ node_process_1.stdin.resume();
1957
+ node_process_1.stdin.setEncoding('utf8');
1958
+ node_process_1.stdin.on('data', onData);
1959
+ render();
1960
+ });
1961
+ };
1962
+ /** Reads one visible line from the terminal. Prompts go to stderr so stdout stays machine-readable. */
1963
+ const promptLine = (label) => new Promise((resolve, reject) => {
1964
+ if (!node_process_1.stdin.isTTY) {
1965
+ reject(new UsageError(NO_TERMINAL));
1966
+ return;
1967
+ }
1968
+ const rl = (0, node_readline_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stderr });
1969
+ let answered = false;
1970
+ // Closing the interface emits 'close' synchronously, so the guard is what keeps the end-of-input
1971
+ // fallback from resolving ahead of the answer that was just typed.
1972
+ rl.on('close', () => {
1973
+ if (!answered)
1974
+ resolve('');
1975
+ });
1976
+ rl.question(label, (answer) => {
1977
+ answered = true;
1978
+ resolve(answer.trim());
1979
+ rl.close();
1980
+ });
1981
+ });
1982
+ /**
1983
+ * Reads one secret from the terminal without echoing it.
1984
+ *
1985
+ * Raw mode is entered directly rather than muting a readline interface: readline exposes no
1986
+ * supported way to suppress its echo, and the credential would otherwise be left on screen and in
1987
+ * any recorded terminal session. Nothing typed here is written back, so there is no cursor to
1988
+ * manage — only the terminating newline is emitted.
1989
+ */
1990
+ const promptSecret = (label) => new Promise((resolve, reject) => {
1991
+ if (!node_process_1.stdin.isTTY || typeof node_process_1.stdin.setRawMode !== 'function') {
1992
+ reject(new UsageError(NO_TERMINAL));
1993
+ return;
1994
+ }
1995
+ node_process_1.stderr.write(label);
1996
+ let value = '';
1997
+ // Where an escape sequence has got to: 0 outside one, 1 just after ESC, 2 inside CSI/SS3
1998
+ // parameters. Dropping the ESC alone is not enough — an Up arrow is ESC [ A, and the `[` and
1999
+ // `A` are ordinary printable bytes that would otherwise land in the middle of a credential the
2000
+ // user cannot see to correct.
2001
+ let escape = 0;
2002
+ const onData = (chunk) => {
2003
+ for (const character of chunk) {
2004
+ if (escape === 1) {
2005
+ escape = character === '[' || character === 'O' ? 2 : 0;
2006
+ continue;
2007
+ }
2008
+ if (escape === 2) {
2009
+ // A CSI sequence ends at its final byte, anything in the range `@` to `~`.
2010
+ if (character >= '@' && character <= '~')
2011
+ escape = 0;
2012
+ continue;
2013
+ }
2014
+ if (character === '\u001b') {
2015
+ escape = 1;
2016
+ continue;
2017
+ }
2018
+ if (character === '\r' || character === '\n') {
2019
+ finish();
2020
+ return;
2021
+ }
2022
+ // Ctrl-C and Ctrl-D: raw mode has taken the terminal's own handling away, so the cancel has
2023
+ // to be honoured here or the prompt could never be escaped.
2024
+ if (character === '\u0003') {
2025
+ finish(new UsageError('Cancelled.'));
2026
+ return;
2027
+ }
2028
+ if (character === '\u0004') {
2029
+ if (value)
2030
+ finish();
2031
+ else
2032
+ finish(new UsageError('Cancelled.'));
2033
+ return;
2034
+ }
2035
+ if (character === '\u007f' || character === '\b') {
2036
+ value = value.slice(0, -1);
2037
+ continue;
2038
+ }
2039
+ // Any other control byte is dropped for the same reason.
2040
+ if (character < ' ')
2041
+ continue;
2042
+ value += character;
2043
+ }
2044
+ };
2045
+ const finish = (error) => {
2046
+ node_process_1.stdin.off('data', onData);
2047
+ node_process_1.stdin.setRawMode(false);
2048
+ node_process_1.stdin.pause();
2049
+ node_process_1.stderr.write('\n');
2050
+ if (error)
2051
+ reject(error);
2052
+ else
2053
+ resolve(value);
2054
+ };
2055
+ node_process_1.stdin.setRawMode(true);
2056
+ node_process_1.stdin.resume();
2057
+ node_process_1.stdin.setEncoding('utf8');
2058
+ node_process_1.stdin.on('data', onData);
2059
+ });
2060
+ //# sourceMappingURL=login.js.map