@synteq/cli 0.1.6 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (705) hide show
  1. package/bin/run.js +61 -2
  2. package/dist/api/client.d.ts +363 -5
  3. package/dist/api/client.js +559 -44
  4. package/dist/api/client.js.map +1 -1
  5. package/dist/api/factory.d.ts +3 -0
  6. package/dist/api/factory.js +6 -3
  7. package/dist/api/factory.js.map +1 -1
  8. package/dist/api/types.d.ts +896 -4
  9. package/dist/apps-support/deploy.d.ts +197 -0
  10. package/dist/apps-support/deploy.js +639 -0
  11. package/dist/apps-support/deploy.js.map +1 -0
  12. package/dist/apps-support/dotenv.d.ts +67 -0
  13. package/dist/apps-support/dotenv.js +136 -0
  14. package/dist/apps-support/dotenv.js.map +1 -0
  15. package/dist/apps-support/env-rollout.d.ts +18 -0
  16. package/dist/apps-support/env-rollout.js +52 -0
  17. package/dist/apps-support/env-rollout.js.map +1 -0
  18. package/dist/apps-support/git.d.ts +14 -0
  19. package/dist/apps-support/git.js +25 -0
  20. package/dist/apps-support/git.js.map +1 -0
  21. package/dist/apps-support/link.d.ts +25 -0
  22. package/dist/apps-support/link.js +39 -0
  23. package/dist/apps-support/link.js.map +1 -0
  24. package/dist/apps-support/log-stream.d.ts +25 -0
  25. package/dist/apps-support/log-stream.js +85 -0
  26. package/dist/apps-support/log-stream.js.map +1 -0
  27. package/dist/apps-support/paid-app.d.ts +64 -0
  28. package/dist/apps-support/paid-app.js +195 -0
  29. package/dist/apps-support/paid-app.js.map +1 -0
  30. package/dist/apps-support/plans.d.ts +44 -0
  31. package/dist/apps-support/plans.js +43 -0
  32. package/dist/apps-support/plans.js.map +1 -0
  33. package/dist/apps-support/resolve.d.ts +10 -0
  34. package/dist/apps-support/resolve.js +53 -0
  35. package/dist/apps-support/resolve.js.map +1 -0
  36. package/dist/apps-support/time.d.ts +20 -0
  37. package/dist/apps-support/time.js +82 -0
  38. package/dist/apps-support/time.js.map +1 -0
  39. package/dist/auth/scope.d.ts +19 -0
  40. package/dist/auth/scope.js +69 -0
  41. package/dist/auth/scope.js.map +1 -0
  42. package/dist/auth/tokens.d.ts +3 -0
  43. package/dist/auth/tokens.js +6 -4
  44. package/dist/auth/tokens.js.map +1 -1
  45. package/dist/base/command.d.ts +36 -1
  46. package/dist/base/command.js +132 -0
  47. package/dist/base/command.js.map +1 -1
  48. package/dist/commands/account/index.d.ts +1 -0
  49. package/dist/commands/account/index.js +1 -0
  50. package/dist/commands/account/index.js.map +1 -1
  51. package/dist/commands/account/show.d.ts +1 -0
  52. package/dist/commands/account/show.js +1 -0
  53. package/dist/commands/account/show.js.map +1 -1
  54. package/dist/commands/agents.d.ts +10 -0
  55. package/dist/commands/agents.js +53 -0
  56. package/dist/commands/agents.js.map +1 -0
  57. package/dist/commands/api.d.ts +1 -1
  58. package/dist/commands/api.js +6 -4
  59. package/dist/commands/api.js.map +1 -1
  60. package/dist/commands/app/deployment/list.d.ts +14 -0
  61. package/dist/commands/app/deployment/list.js +45 -0
  62. package/dist/commands/app/deployment/list.js.map +1 -0
  63. package/dist/commands/app/deployment/rollback.d.ts +18 -0
  64. package/dist/commands/app/deployment/rollback.js +57 -0
  65. package/dist/commands/app/deployment/rollback.js.map +1 -0
  66. package/dist/commands/app/deployment/show.d.ts +26 -0
  67. package/dist/commands/app/deployment/show.js +98 -0
  68. package/dist/commands/app/deployment/show.js.map +1 -0
  69. package/dist/commands/app/domain/add.d.ts +17 -0
  70. package/dist/commands/app/domain/add.js +59 -0
  71. package/dist/commands/app/domain/add.js.map +1 -0
  72. package/dist/commands/app/domain/list.d.ts +14 -0
  73. package/dist/commands/app/domain/list.js +42 -0
  74. package/dist/commands/app/domain/list.js.map +1 -0
  75. package/dist/commands/app/domain/remove.d.ts +16 -0
  76. package/dist/commands/app/domain/remove.js +51 -0
  77. package/dist/commands/app/domain/remove.js.map +1 -0
  78. package/dist/commands/app/domain/verify.d.ts +15 -0
  79. package/dist/commands/app/domain/verify.js +39 -0
  80. package/dist/commands/app/domain/verify.js.map +1 -0
  81. package/dist/commands/app/env/add.d.ts +22 -0
  82. package/dist/commands/app/env/add.js +103 -0
  83. package/dist/commands/app/env/add.js.map +1 -0
  84. package/dist/commands/app/env/list.d.ts +17 -0
  85. package/dist/commands/app/env/list.js +40 -0
  86. package/dist/commands/app/env/list.js.map +1 -0
  87. package/dist/commands/app/env/reveal.d.ts +17 -0
  88. package/dist/commands/app/env/reveal.js +40 -0
  89. package/dist/commands/app/env/reveal.js.map +1 -0
  90. package/dist/commands/app/env/rm.d.ts +16 -0
  91. package/dist/commands/app/env/rm.js +48 -0
  92. package/dist/commands/app/env/rm.js.map +1 -0
  93. package/dist/commands/app/health.d.ts +14 -0
  94. package/dist/commands/app/health.js +40 -0
  95. package/dist/commands/app/health.js.map +1 -0
  96. package/dist/commands/app/index.d.ts +6 -0
  97. package/dist/commands/app/index.js +7 -0
  98. package/dist/commands/app/index.js.map +1 -0
  99. package/dist/commands/app/link.d.ts +22 -0
  100. package/dist/commands/app/link.js +31 -0
  101. package/dist/commands/app/link.js.map +1 -0
  102. package/dist/commands/app/logs.d.ts +25 -0
  103. package/dist/commands/app/logs.js +121 -0
  104. package/dist/commands/app/logs.js.map +1 -0
  105. package/dist/commands/app/metrics.d.ts +21 -0
  106. package/dist/commands/app/metrics.js +123 -0
  107. package/dist/commands/app/metrics.js.map +1 -0
  108. package/dist/commands/app/plan/change.d.ts +18 -0
  109. package/dist/commands/app/plan/change.js +79 -0
  110. package/dist/commands/app/plan/change.js.map +1 -0
  111. package/dist/commands/app/plan/show.d.ts +14 -0
  112. package/dist/commands/app/plan/show.js +40 -0
  113. package/dist/commands/app/plan/show.js.map +1 -0
  114. package/dist/commands/app/settings/set.d.ts +25 -0
  115. package/dist/commands/app/settings/set.js +96 -0
  116. package/dist/commands/app/settings/set.js.map +1 -0
  117. package/dist/commands/app/settings/show.d.ts +14 -0
  118. package/dist/commands/app/settings/show.js +48 -0
  119. package/dist/commands/app/settings/show.js.map +1 -0
  120. package/dist/commands/app/unlink.d.ts +8 -0
  121. package/dist/commands/app/unlink.js +25 -0
  122. package/dist/commands/app/unlink.js.map +1 -0
  123. package/dist/commands/apps/create.d.ts +26 -0
  124. package/dist/commands/apps/create.js +91 -0
  125. package/dist/commands/apps/create.js.map +1 -0
  126. package/dist/commands/apps/list.d.ts +16 -0
  127. package/dist/commands/apps/list.js +54 -0
  128. package/dist/commands/apps/list.js.map +1 -0
  129. package/dist/commands/baremetal/activity.d.ts +14 -0
  130. package/dist/commands/baremetal/activity.js +53 -0
  131. package/dist/commands/baremetal/activity.js.map +1 -0
  132. package/dist/commands/baremetal/bandwidth.d.ts +10 -0
  133. package/dist/commands/baremetal/bandwidth.js +86 -0
  134. package/dist/commands/baremetal/bandwidth.js.map +1 -0
  135. package/dist/commands/baremetal/console.d.ts +20 -0
  136. package/dist/commands/baremetal/console.js +72 -0
  137. package/dist/commands/baremetal/console.js.map +1 -0
  138. package/dist/commands/baremetal/details.d.ts +11 -0
  139. package/dist/commands/baremetal/details.js +116 -0
  140. package/dist/commands/baremetal/details.js.map +1 -0
  141. package/dist/commands/baremetal/index.d.ts +5 -0
  142. package/dist/commands/baremetal/index.js +6 -0
  143. package/dist/commands/baremetal/index.js.map +1 -0
  144. package/dist/commands/baremetal/install-password.d.ts +14 -0
  145. package/dist/commands/baremetal/install-password.js +80 -0
  146. package/dist/commands/baremetal/install-password.js.map +1 -0
  147. package/dist/commands/baremetal/list.d.ts +11 -0
  148. package/dist/commands/baremetal/list.js +59 -0
  149. package/dist/commands/baremetal/list.js.map +1 -0
  150. package/dist/commands/baremetal/order.d.ts +34 -0
  151. package/dist/commands/baremetal/order.js +394 -0
  152. package/dist/commands/baremetal/order.js.map +1 -0
  153. package/dist/commands/baremetal/power.d.ts +18 -0
  154. package/dist/commands/baremetal/power.js +168 -0
  155. package/dist/commands/baremetal/power.js.map +1 -0
  156. package/dist/commands/baremetal/reinstall.d.ts +21 -0
  157. package/dist/commands/baremetal/reinstall.js +168 -0
  158. package/dist/commands/baremetal/reinstall.js.map +1 -0
  159. package/dist/commands/catalog.d.ts +1 -0
  160. package/dist/commands/catalog.js +5 -0
  161. package/dist/commands/catalog.js.map +1 -1
  162. package/dist/commands/config/index.d.ts +1 -0
  163. package/dist/commands/config/index.js +1 -0
  164. package/dist/commands/config/index.js.map +1 -1
  165. package/dist/commands/config/profile/add.d.ts +11 -0
  166. package/dist/commands/config/profile/add.js +51 -0
  167. package/dist/commands/config/profile/add.js.map +1 -0
  168. package/dist/commands/config/profile/list.d.ts +7 -0
  169. package/dist/commands/config/profile/list.js +29 -0
  170. package/dist/commands/config/profile/list.js.map +1 -0
  171. package/dist/commands/config/profile/remove.d.ts +11 -0
  172. package/dist/commands/config/profile/remove.js +35 -0
  173. package/dist/commands/config/profile/remove.js.map +1 -0
  174. package/dist/commands/config/profile/use.d.ts +10 -0
  175. package/dist/commands/config/profile/use.js +44 -0
  176. package/dist/commands/config/profile/use.js.map +1 -0
  177. package/dist/commands/config/set-api-url.d.ts +1 -0
  178. package/dist/commands/config/set-api-url.js +1 -0
  179. package/dist/commands/config/set-api-url.js.map +1 -1
  180. package/dist/commands/config/show.d.ts +1 -0
  181. package/dist/commands/config/show.js +5 -1
  182. package/dist/commands/config/show.js.map +1 -1
  183. package/dist/commands/credits/add.d.ts +1 -0
  184. package/dist/commands/credits/add.js +3 -2
  185. package/dist/commands/credits/add.js.map +1 -1
  186. package/dist/commands/credits/index.d.ts +1 -0
  187. package/dist/commands/credits/index.js +6 -1
  188. package/dist/commands/credits/index.js.map +1 -1
  189. package/dist/commands/credits/list.d.ts +1 -0
  190. package/dist/commands/credits/list.js +1 -0
  191. package/dist/commands/credits/list.js.map +1 -1
  192. package/dist/commands/deploy.d.ts +51 -0
  193. package/dist/commands/deploy.js +515 -0
  194. package/dist/commands/deploy.js.map +1 -0
  195. package/dist/commands/function/create.d.ts +29 -0
  196. package/dist/commands/function/create.js +113 -0
  197. package/dist/commands/function/create.js.map +1 -0
  198. package/dist/commands/function/credits/add.d.ts +29 -0
  199. package/dist/commands/function/credits/add.js +209 -0
  200. package/dist/commands/function/credits/add.js.map +1 -0
  201. package/dist/commands/function/credits/auto-reload.d.ts +27 -0
  202. package/dist/commands/function/credits/auto-reload.js +107 -0
  203. package/dist/commands/function/credits/auto-reload.js.map +1 -0
  204. package/dist/commands/function/credits/index.d.ts +22 -0
  205. package/dist/commands/function/credits/index.js +80 -0
  206. package/dist/commands/function/credits/index.js.map +1 -0
  207. package/dist/commands/function/cron/add.d.ts +19 -0
  208. package/dist/commands/function/cron/add.js +74 -0
  209. package/dist/commands/function/cron/add.js.map +1 -0
  210. package/dist/commands/function/cron/list.d.ts +12 -0
  211. package/dist/commands/function/cron/list.js +55 -0
  212. package/dist/commands/function/cron/list.js.map +1 -0
  213. package/dist/commands/function/cron/rm.d.ts +17 -0
  214. package/dist/commands/function/cron/rm.js +73 -0
  215. package/dist/commands/function/cron/rm.js.map +1 -0
  216. package/dist/commands/function/cron/run.d.ts +16 -0
  217. package/dist/commands/function/cron/run.js +59 -0
  218. package/dist/commands/function/cron/run.js.map +1 -0
  219. package/dist/commands/function/cron/update.d.ts +21 -0
  220. package/dist/commands/function/cron/update.js +87 -0
  221. package/dist/commands/function/cron/update.js.map +1 -0
  222. package/dist/commands/function/delete.d.ts +23 -0
  223. package/dist/commands/function/delete.js +90 -0
  224. package/dist/commands/function/delete.js.map +1 -0
  225. package/dist/commands/function/deploy.d.ts +64 -0
  226. package/dist/commands/function/deploy.js +608 -0
  227. package/dist/commands/function/deploy.js.map +1 -0
  228. package/dist/commands/function/env/list.d.ts +12 -0
  229. package/dist/commands/function/env/list.js +46 -0
  230. package/dist/commands/function/env/list.js.map +1 -0
  231. package/dist/commands/function/env/reveal.d.ts +21 -0
  232. package/dist/commands/function/env/reveal.js +54 -0
  233. package/dist/commands/function/env/reveal.js.map +1 -0
  234. package/dist/commands/function/env/rm.d.ts +17 -0
  235. package/dist/commands/function/env/rm.js +62 -0
  236. package/dist/commands/function/env/rm.js.map +1 -0
  237. package/dist/commands/function/env/set.d.ts +28 -0
  238. package/dist/commands/function/env/set.js +124 -0
  239. package/dist/commands/function/env/set.js.map +1 -0
  240. package/dist/commands/function/index.d.ts +14 -0
  241. package/dist/commands/function/index.js +11 -0
  242. package/dist/commands/function/index.js.map +1 -0
  243. package/dist/commands/function/init.d.ts +47 -0
  244. package/dist/commands/function/init.js +269 -0
  245. package/dist/commands/function/init.js.map +1 -0
  246. package/dist/commands/function/invoke.d.ts +29 -0
  247. package/dist/commands/function/invoke.js +138 -0
  248. package/dist/commands/function/invoke.js.map +1 -0
  249. package/dist/commands/function/link.d.ts +23 -0
  250. package/dist/commands/function/link.js +80 -0
  251. package/dist/commands/function/link.js.map +1 -0
  252. package/dist/commands/function/list.d.ts +16 -0
  253. package/dist/commands/function/list.js +55 -0
  254. package/dist/commands/function/list.js.map +1 -0
  255. package/dist/commands/function/logs.d.ts +29 -0
  256. package/dist/commands/function/logs.js +171 -0
  257. package/dist/commands/function/logs.js.map +1 -0
  258. package/dist/commands/function/pause.d.ts +19 -0
  259. package/dist/commands/function/pause.js +50 -0
  260. package/dist/commands/function/pause.js.map +1 -0
  261. package/dist/commands/function/releases.d.ts +15 -0
  262. package/dist/commands/function/releases.js +62 -0
  263. package/dist/commands/function/releases.js.map +1 -0
  264. package/dist/commands/function/resume.d.ts +16 -0
  265. package/dist/commands/function/resume.js +47 -0
  266. package/dist/commands/function/resume.js.map +1 -0
  267. package/dist/commands/function/rollback.d.ts +18 -0
  268. package/dist/commands/function/rollback.js +69 -0
  269. package/dist/commands/function/rollback.js.map +1 -0
  270. package/dist/commands/function/settings/set.d.ts +23 -0
  271. package/dist/commands/function/settings/set.js +119 -0
  272. package/dist/commands/function/settings/set.js.map +1 -0
  273. package/dist/commands/function/show.d.ts +15 -0
  274. package/dist/commands/function/show.js +80 -0
  275. package/dist/commands/function/show.js.map +1 -0
  276. package/dist/commands/function/unlink.d.ts +9 -0
  277. package/dist/commands/function/unlink.js +34 -0
  278. package/dist/commands/function/unlink.js.map +1 -0
  279. package/dist/commands/gpu/activity.d.ts +1 -0
  280. package/dist/commands/gpu/activity.js +1 -0
  281. package/dist/commands/gpu/activity.js.map +1 -1
  282. package/dist/commands/gpu/bandwidth.d.ts +1 -0
  283. package/dist/commands/gpu/bandwidth.js +1 -0
  284. package/dist/commands/gpu/bandwidth.js.map +1 -1
  285. package/dist/commands/gpu/boot-order.d.ts +1 -0
  286. package/dist/commands/gpu/boot-order.js +1 -0
  287. package/dist/commands/gpu/boot-order.js.map +1 -1
  288. package/dist/commands/gpu/console.d.ts +1 -0
  289. package/dist/commands/gpu/console.js +1 -0
  290. package/dist/commands/gpu/console.js.map +1 -1
  291. package/dist/commands/gpu/details.d.ts +1 -0
  292. package/dist/commands/gpu/details.js +1 -0
  293. package/dist/commands/gpu/details.js.map +1 -1
  294. package/dist/commands/gpu/index.d.ts +1 -0
  295. package/dist/commands/gpu/index.js +1 -0
  296. package/dist/commands/gpu/index.js.map +1 -1
  297. package/dist/commands/gpu/ip/add.d.ts +1 -0
  298. package/dist/commands/gpu/ip/add.js +1 -0
  299. package/dist/commands/gpu/ip/add.js.map +1 -1
  300. package/dist/commands/gpu/ip/list.d.ts +1 -0
  301. package/dist/commands/gpu/ip/list.js +1 -0
  302. package/dist/commands/gpu/ip/list.js.map +1 -1
  303. package/dist/commands/gpu/list.d.ts +1 -0
  304. package/dist/commands/gpu/list.js +1 -0
  305. package/dist/commands/gpu/list.js.map +1 -1
  306. package/dist/commands/gpu/order.d.ts +1 -0
  307. package/dist/commands/gpu/order.js +1 -0
  308. package/dist/commands/gpu/order.js.map +1 -1
  309. package/dist/commands/gpu/power.d.ts +1 -0
  310. package/dist/commands/gpu/power.js +1 -0
  311. package/dist/commands/gpu/power.js.map +1 -1
  312. package/dist/commands/gpu/reinstall.d.ts +1 -0
  313. package/dist/commands/gpu/reinstall.js +1 -0
  314. package/dist/commands/gpu/reinstall.js.map +1 -1
  315. package/dist/commands/gpu/resize.d.ts +1 -0
  316. package/dist/commands/gpu/resize.js +1 -0
  317. package/dist/commands/gpu/resize.js.map +1 -1
  318. package/dist/commands/gpu/stats.d.ts +1 -0
  319. package/dist/commands/gpu/stats.js +1 -0
  320. package/dist/commands/gpu/stats.js.map +1 -1
  321. package/dist/commands/help.d.ts +2 -0
  322. package/dist/commands/help.js +5 -0
  323. package/dist/commands/help.js.map +1 -1
  324. package/dist/commands/instant-metal/activity.d.ts +1 -0
  325. package/dist/commands/instant-metal/activity.js +1 -0
  326. package/dist/commands/instant-metal/activity.js.map +1 -1
  327. package/dist/commands/instant-metal/bandwidth.d.ts +1 -0
  328. package/dist/commands/instant-metal/bandwidth.js +1 -0
  329. package/dist/commands/instant-metal/bandwidth.js.map +1 -1
  330. package/dist/commands/instant-metal/boot-order.d.ts +1 -0
  331. package/dist/commands/instant-metal/boot-order.js +1 -0
  332. package/dist/commands/instant-metal/boot-order.js.map +1 -1
  333. package/dist/commands/instant-metal/console.d.ts +1 -0
  334. package/dist/commands/instant-metal/console.js +1 -0
  335. package/dist/commands/instant-metal/console.js.map +1 -1
  336. package/dist/commands/instant-metal/details.d.ts +1 -0
  337. package/dist/commands/instant-metal/details.js +1 -0
  338. package/dist/commands/instant-metal/details.js.map +1 -1
  339. package/dist/commands/instant-metal/index.d.ts +1 -0
  340. package/dist/commands/instant-metal/index.js +1 -0
  341. package/dist/commands/instant-metal/index.js.map +1 -1
  342. package/dist/commands/instant-metal/ip/add.d.ts +1 -0
  343. package/dist/commands/instant-metal/ip/add.js +1 -0
  344. package/dist/commands/instant-metal/ip/add.js.map +1 -1
  345. package/dist/commands/instant-metal/ip/list.d.ts +1 -0
  346. package/dist/commands/instant-metal/ip/list.js +1 -0
  347. package/dist/commands/instant-metal/ip/list.js.map +1 -1
  348. package/dist/commands/instant-metal/list.d.ts +1 -0
  349. package/dist/commands/instant-metal/list.js +1 -0
  350. package/dist/commands/instant-metal/list.js.map +1 -1
  351. package/dist/commands/instant-metal/order.d.ts +1 -0
  352. package/dist/commands/instant-metal/order.js +1 -0
  353. package/dist/commands/instant-metal/order.js.map +1 -1
  354. package/dist/commands/instant-metal/power.d.ts +1 -0
  355. package/dist/commands/instant-metal/power.js +1 -0
  356. package/dist/commands/instant-metal/power.js.map +1 -1
  357. package/dist/commands/instant-metal/reinstall.d.ts +1 -0
  358. package/dist/commands/instant-metal/reinstall.js +1 -0
  359. package/dist/commands/instant-metal/reinstall.js.map +1 -1
  360. package/dist/commands/instant-metal/resize.d.ts +1 -0
  361. package/dist/commands/instant-metal/resize.js +1 -0
  362. package/dist/commands/instant-metal/resize.js.map +1 -1
  363. package/dist/commands/instant-metal/stats.d.ts +1 -0
  364. package/dist/commands/instant-metal/stats.js +1 -0
  365. package/dist/commands/instant-metal/stats.js.map +1 -1
  366. package/dist/commands/link.d.ts +77 -0
  367. package/dist/commands/link.js +530 -0
  368. package/dist/commands/link.js.map +1 -0
  369. package/dist/commands/login.d.ts +1 -0
  370. package/dist/commands/login.js +10 -0
  371. package/dist/commands/login.js.map +1 -1
  372. package/dist/commands/logout.d.ts +1 -0
  373. package/dist/commands/logout.js +1 -0
  374. package/dist/commands/logout.js.map +1 -1
  375. package/dist/commands/order/gpu.d.ts +1 -0
  376. package/dist/commands/order/gpu.js +1 -0
  377. package/dist/commands/order/gpu.js.map +1 -1
  378. package/dist/commands/order/instant-metal.d.ts +1 -0
  379. package/dist/commands/order/instant-metal.js +1 -0
  380. package/dist/commands/order/instant-metal.js.map +1 -1
  381. package/dist/commands/order/storage.d.ts +1 -0
  382. package/dist/commands/order/storage.js +1 -0
  383. package/dist/commands/order/storage.js.map +1 -1
  384. package/dist/commands/order/vps.d.ts +1 -0
  385. package/dist/commands/order/vps.js +1 -0
  386. package/dist/commands/order/vps.js.map +1 -1
  387. package/dist/commands/order.d.ts +1 -0
  388. package/dist/commands/order.js +4 -1
  389. package/dist/commands/order.js.map +1 -1
  390. package/dist/commands/os-images.d.ts +1 -0
  391. package/dist/commands/os-images.js +3 -0
  392. package/dist/commands/os-images.js.map +1 -1
  393. package/dist/commands/payments/index.d.ts +1 -0
  394. package/dist/commands/payments/index.js +1 -0
  395. package/dist/commands/payments/index.js.map +1 -1
  396. package/dist/commands/payments/methods/add.d.ts +1 -0
  397. package/dist/commands/payments/methods/add.js +1 -0
  398. package/dist/commands/payments/methods/add.js.map +1 -1
  399. package/dist/commands/payments/methods/delete.d.ts +1 -0
  400. package/dist/commands/payments/methods/delete.js +3 -2
  401. package/dist/commands/payments/methods/delete.js.map +1 -1
  402. package/dist/commands/payments/methods/index.d.ts +1 -0
  403. package/dist/commands/payments/methods/index.js +1 -0
  404. package/dist/commands/payments/methods/index.js.map +1 -1
  405. package/dist/commands/payments/methods/list.d.ts +1 -0
  406. package/dist/commands/payments/methods/list.js +1 -0
  407. package/dist/commands/payments/methods/list.js.map +1 -1
  408. package/dist/commands/payments/methods/set-default.d.ts +1 -0
  409. package/dist/commands/payments/methods/set-default.js +1 -0
  410. package/dist/commands/payments/methods/set-default.js.map +1 -1
  411. package/dist/commands/projects/index.d.ts +1 -0
  412. package/dist/commands/projects/index.js +1 -0
  413. package/dist/commands/projects/index.js.map +1 -1
  414. package/dist/commands/projects/list.d.ts +1 -0
  415. package/dist/commands/projects/list.js +1 -0
  416. package/dist/commands/projects/list.js.map +1 -1
  417. package/dist/commands/resources.d.ts +1 -0
  418. package/dist/commands/resources.js +61 -7
  419. package/dist/commands/resources.js.map +1 -1
  420. package/dist/commands/settings/index.d.ts +1 -0
  421. package/dist/commands/settings/index.js +1 -0
  422. package/dist/commands/settings/index.js.map +1 -1
  423. package/dist/commands/settings/render-mode.d.ts +1 -0
  424. package/dist/commands/settings/render-mode.js +1 -0
  425. package/dist/commands/settings/render-mode.js.map +1 -1
  426. package/dist/commands/ssh-key/add.d.ts +1 -0
  427. package/dist/commands/ssh-key/add.js +1 -0
  428. package/dist/commands/ssh-key/add.js.map +1 -1
  429. package/dist/commands/ssh-key/delete.d.ts +1 -0
  430. package/dist/commands/ssh-key/delete.js +1 -0
  431. package/dist/commands/ssh-key/delete.js.map +1 -1
  432. package/dist/commands/ssh-key/list.d.ts +1 -0
  433. package/dist/commands/ssh-key/list.js +1 -0
  434. package/dist/commands/ssh-key/list.js.map +1 -1
  435. package/dist/commands/status.d.ts +12 -0
  436. package/dist/commands/status.js +71 -0
  437. package/dist/commands/status.js.map +1 -0
  438. package/dist/commands/storage/attach.d.ts +1 -0
  439. package/dist/commands/storage/attach.js +1 -0
  440. package/dist/commands/storage/attach.js.map +1 -1
  441. package/dist/commands/storage/detach.d.ts +1 -0
  442. package/dist/commands/storage/detach.js +2 -1
  443. package/dist/commands/storage/detach.js.map +1 -1
  444. package/dist/commands/storage/list.d.ts +1 -0
  445. package/dist/commands/storage/list.js +7 -2
  446. package/dist/commands/storage/list.js.map +1 -1
  447. package/dist/commands/storage/order.d.ts +3 -0
  448. package/dist/commands/storage/order.js +101 -18
  449. package/dist/commands/storage/order.js.map +1 -1
  450. package/dist/commands/storage/resize.d.ts +1 -0
  451. package/dist/commands/storage/resize.js +20 -6
  452. package/dist/commands/storage/resize.js.map +1 -1
  453. package/dist/commands/team/details.d.ts +1 -0
  454. package/dist/commands/team/details.js +1 -0
  455. package/dist/commands/team/details.js.map +1 -1
  456. package/dist/commands/team/invite.d.ts +1 -0
  457. package/dist/commands/team/invite.js +1 -0
  458. package/dist/commands/team/invite.js.map +1 -1
  459. package/dist/commands/team.d.ts +1 -0
  460. package/dist/commands/team.js +1 -0
  461. package/dist/commands/team.js.map +1 -1
  462. package/dist/commands/tickets/close.d.ts +1 -0
  463. package/dist/commands/tickets/close.js +1 -0
  464. package/dist/commands/tickets/close.js.map +1 -1
  465. package/dist/commands/tickets/create.d.ts +3 -0
  466. package/dist/commands/tickets/create.js +41 -1
  467. package/dist/commands/tickets/create.js.map +1 -1
  468. package/dist/commands/tickets/departments.d.ts +1 -0
  469. package/dist/commands/tickets/departments.js +1 -0
  470. package/dist/commands/tickets/departments.js.map +1 -1
  471. package/dist/commands/tickets/download.d.ts +18 -0
  472. package/dist/commands/tickets/download.js +138 -0
  473. package/dist/commands/tickets/download.js.map +1 -0
  474. package/dist/commands/tickets/list.d.ts +1 -0
  475. package/dist/commands/tickets/list.js +14 -7
  476. package/dist/commands/tickets/list.js.map +1 -1
  477. package/dist/commands/tickets/open.d.ts +1 -0
  478. package/dist/commands/tickets/open.js +1 -0
  479. package/dist/commands/tickets/open.js.map +1 -1
  480. package/dist/commands/tickets/reopen.d.ts +11 -0
  481. package/dist/commands/tickets/reopen.js +34 -0
  482. package/dist/commands/tickets/reopen.js.map +1 -0
  483. package/dist/commands/tickets/reply.d.ts +3 -0
  484. package/dist/commands/tickets/reply.js +34 -4
  485. package/dist/commands/tickets/reply.js.map +1 -1
  486. package/dist/commands/transit.d.ts +1 -0
  487. package/dist/commands/transit.js +1 -0
  488. package/dist/commands/transit.js.map +1 -1
  489. package/dist/commands/tui/index.d.ts +1 -0
  490. package/dist/commands/tui/index.js +21 -0
  491. package/dist/commands/tui/index.js.map +1 -1
  492. package/dist/commands/vds/activity.d.ts +1 -0
  493. package/dist/commands/vds/activity.js +1 -0
  494. package/dist/commands/vds/activity.js.map +1 -1
  495. package/dist/commands/vds/bandwidth.d.ts +1 -0
  496. package/dist/commands/vds/bandwidth.js +1 -0
  497. package/dist/commands/vds/bandwidth.js.map +1 -1
  498. package/dist/commands/vds/boot-order.d.ts +1 -0
  499. package/dist/commands/vds/boot-order.js +1 -0
  500. package/dist/commands/vds/boot-order.js.map +1 -1
  501. package/dist/commands/vds/console.d.ts +1 -0
  502. package/dist/commands/vds/console.js +1 -0
  503. package/dist/commands/vds/console.js.map +1 -1
  504. package/dist/commands/vds/details.d.ts +1 -0
  505. package/dist/commands/vds/details.js +1 -0
  506. package/dist/commands/vds/details.js.map +1 -1
  507. package/dist/commands/vds/index.d.ts +1 -0
  508. package/dist/commands/vds/index.js +1 -0
  509. package/dist/commands/vds/index.js.map +1 -1
  510. package/dist/commands/vds/ip/add.d.ts +1 -0
  511. package/dist/commands/vds/ip/add.js +1 -0
  512. package/dist/commands/vds/ip/add.js.map +1 -1
  513. package/dist/commands/vds/ip/list.d.ts +1 -0
  514. package/dist/commands/vds/ip/list.js +1 -0
  515. package/dist/commands/vds/ip/list.js.map +1 -1
  516. package/dist/commands/vds/list.d.ts +1 -0
  517. package/dist/commands/vds/list.js +1 -0
  518. package/dist/commands/vds/list.js.map +1 -1
  519. package/dist/commands/vds/order.d.ts +1 -0
  520. package/dist/commands/vds/order.js +73 -7
  521. package/dist/commands/vds/order.js.map +1 -1
  522. package/dist/commands/vds/power.d.ts +1 -0
  523. package/dist/commands/vds/power.js +1 -0
  524. package/dist/commands/vds/power.js.map +1 -1
  525. package/dist/commands/vds/reinstall.d.ts +1 -0
  526. package/dist/commands/vds/reinstall.js +1 -0
  527. package/dist/commands/vds/reinstall.js.map +1 -1
  528. package/dist/commands/vds/resize.d.ts +1 -0
  529. package/dist/commands/vds/resize.js +1 -0
  530. package/dist/commands/vds/resize.js.map +1 -1
  531. package/dist/commands/vds/stats.d.ts +1 -0
  532. package/dist/commands/vds/stats.js +1 -0
  533. package/dist/commands/vds/stats.js.map +1 -1
  534. package/dist/commands/vps/activity.d.ts +1 -0
  535. package/dist/commands/vps/activity.js +1 -0
  536. package/dist/commands/vps/activity.js.map +1 -1
  537. package/dist/commands/vps/bandwidth.d.ts +1 -0
  538. package/dist/commands/vps/bandwidth.js +1 -0
  539. package/dist/commands/vps/bandwidth.js.map +1 -1
  540. package/dist/commands/vps/boot-order.d.ts +1 -0
  541. package/dist/commands/vps/boot-order.js +1 -0
  542. package/dist/commands/vps/boot-order.js.map +1 -1
  543. package/dist/commands/vps/console.d.ts +1 -0
  544. package/dist/commands/vps/console.js +15 -6
  545. package/dist/commands/vps/console.js.map +1 -1
  546. package/dist/commands/vps/details.d.ts +1 -0
  547. package/dist/commands/vps/details.js +8 -1
  548. package/dist/commands/vps/details.js.map +1 -1
  549. package/dist/commands/vps/ip/add.d.ts +1 -0
  550. package/dist/commands/vps/ip/add.js +77 -9
  551. package/dist/commands/vps/ip/add.js.map +1 -1
  552. package/dist/commands/vps/ip/list.d.ts +1 -0
  553. package/dist/commands/vps/ip/list.js +1 -0
  554. package/dist/commands/vps/ip/list.js.map +1 -1
  555. package/dist/commands/vps/list.d.ts +1 -0
  556. package/dist/commands/vps/list.js +14 -4
  557. package/dist/commands/vps/list.js.map +1 -1
  558. package/dist/commands/vps/order.d.ts +20 -0
  559. package/dist/commands/vps/order.js +180 -23
  560. package/dist/commands/vps/order.js.map +1 -1
  561. package/dist/commands/vps/power.d.ts +1 -0
  562. package/dist/commands/vps/power.js +3 -2
  563. package/dist/commands/vps/power.js.map +1 -1
  564. package/dist/commands/vps/reinstall.d.ts +1 -0
  565. package/dist/commands/vps/reinstall.js +3 -2
  566. package/dist/commands/vps/reinstall.js.map +1 -1
  567. package/dist/commands/vps/resize.d.ts +1 -0
  568. package/dist/commands/vps/resize.js +37 -13
  569. package/dist/commands/vps/resize.js.map +1 -1
  570. package/dist/commands/vps/stats.d.ts +1 -0
  571. package/dist/commands/vps/stats.js +1 -0
  572. package/dist/commands/vps/stats.js.map +1 -1
  573. package/dist/commands/whoami.d.ts +6 -0
  574. package/dist/commands/whoami.js +8 -2
  575. package/dist/commands/whoami.js.map +1 -1
  576. package/dist/config/paths.d.ts +1 -1
  577. package/dist/config/paths.js +6 -1
  578. package/dist/config/paths.js.map +1 -1
  579. package/dist/config/store.d.ts +58 -0
  580. package/dist/config/store.js +84 -2
  581. package/dist/config/store.js.map +1 -1
  582. package/dist/functions-support/credits.d.ts +64 -0
  583. package/dist/functions-support/credits.js +181 -0
  584. package/dist/functions-support/credits.js.map +1 -0
  585. package/dist/functions-support/cron.d.ts +17 -0
  586. package/dist/functions-support/cron.js +99 -0
  587. package/dist/functions-support/cron.js.map +1 -0
  588. package/dist/functions-support/env.d.ts +23 -0
  589. package/dist/functions-support/env.js +44 -0
  590. package/dist/functions-support/env.js.map +1 -0
  591. package/dist/functions-support/link.d.ts +25 -0
  592. package/dist/functions-support/link.js +51 -0
  593. package/dist/functions-support/link.js.map +1 -0
  594. package/dist/functions-support/poll.d.ts +40 -0
  595. package/dist/functions-support/poll.js +109 -0
  596. package/dist/functions-support/poll.js.map +1 -0
  597. package/dist/functions-support/resolve.d.ts +3 -0
  598. package/dist/functions-support/resolve.js +31 -0
  599. package/dist/functions-support/resolve.js.map +1 -0
  600. package/dist/functions-support/runtime.d.ts +30 -0
  601. package/dist/functions-support/runtime.js +52 -0
  602. package/dist/functions-support/runtime.js.map +1 -0
  603. package/dist/functions-support/source.d.ts +92 -0
  604. package/dist/functions-support/source.js +343 -0
  605. package/dist/functions-support/source.js.map +1 -0
  606. package/dist/help/help-filter.d.ts +10 -0
  607. package/dist/help/help-filter.js +9 -0
  608. package/dist/help/help-filter.js.map +1 -0
  609. package/dist/help/scope-aware-help.d.ts +30 -0
  610. package/dist/help/scope-aware-help.js +80 -0
  611. package/dist/help/scope-aware-help.js.map +1 -0
  612. package/dist/tui/App.js +11 -26
  613. package/dist/tui/App.js.map +1 -1
  614. package/dist/tui/components/Menu.d.ts +3 -1
  615. package/dist/tui/components/Menu.js +7 -3
  616. package/dist/tui/components/Menu.js.map +1 -1
  617. package/dist/tui/components/NumberInput.js +1 -1
  618. package/dist/tui/components/Sparkline.js +1 -1
  619. package/dist/tui/router.d.ts +1 -1
  620. package/dist/tui/router.js.map +1 -1
  621. package/dist/tui/screens/Billing.js +66 -14
  622. package/dist/tui/screens/Billing.js.map +1 -1
  623. package/dist/tui/screens/Dashboard.js +8 -7
  624. package/dist/tui/screens/Dashboard.js.map +1 -1
  625. package/dist/tui/screens/Deploy.d.ts +2 -0
  626. package/dist/tui/screens/Deploy.js +912 -0
  627. package/dist/tui/screens/Deploy.js.map +1 -0
  628. package/dist/tui/screens/Order.js +385 -52
  629. package/dist/tui/screens/Order.js.map +1 -1
  630. package/dist/tui/screens/Resources.js +277 -35
  631. package/dist/tui/screens/Resources.js.map +1 -1
  632. package/dist/tui/screens/Tickets.js +47 -26
  633. package/dist/tui/screens/Tickets.js.map +1 -1
  634. package/dist/tui/screens/deployPreset.d.ts +20 -0
  635. package/dist/tui/screens/deployPreset.js +11 -0
  636. package/dist/tui/screens/deployPreset.js.map +1 -0
  637. package/dist/tui/screens/resources/AppDetail.d.ts +7 -0
  638. package/dist/tui/screens/resources/AppDetail.js +810 -0
  639. package/dist/tui/screens/resources/AppDetail.js.map +1 -0
  640. package/dist/tui/screens/resources/FunctionDetail.d.ts +7 -0
  641. package/dist/tui/screens/resources/FunctionDetail.js +751 -0
  642. package/dist/tui/screens/resources/FunctionDetail.js.map +1 -0
  643. package/dist/ui/attach.d.ts +15 -0
  644. package/dist/ui/attach.js +63 -0
  645. package/dist/ui/attach.js.map +1 -0
  646. package/dist/ui/baremetal-resolve.d.ts +10 -0
  647. package/dist/ui/baremetal-resolve.js +50 -0
  648. package/dist/ui/baremetal-resolve.js.map +1 -0
  649. package/dist/ui/confirm.js +1 -1
  650. package/dist/ui/confirm.js.map +1 -1
  651. package/dist/ui/cycle.d.ts +66 -0
  652. package/dist/ui/cycle.js +96 -0
  653. package/dist/ui/cycle.js.map +1 -0
  654. package/dist/ui/deploy-ui.d.ts +75 -0
  655. package/dist/ui/deploy-ui.js +243 -0
  656. package/dist/ui/deploy-ui.js.map +1 -0
  657. package/dist/ui/order-result.d.ts +4 -0
  658. package/dist/ui/order-result.js +32 -0
  659. package/dist/ui/order-result.js.map +1 -0
  660. package/dist/ui/payment-gate.d.ts +26 -0
  661. package/dist/ui/payment-gate.js +70 -0
  662. package/dist/ui/payment-gate.js.map +1 -0
  663. package/dist/ui/payment.js +10 -3
  664. package/dist/ui/payment.js.map +1 -1
  665. package/dist/ui/portal.d.ts +1 -1
  666. package/dist/ui/portal.js +7 -2
  667. package/dist/ui/portal.js.map +1 -1
  668. package/dist/ui/project-resolve.d.ts +25 -0
  669. package/dist/ui/project-resolve.js +31 -0
  670. package/dist/ui/project-resolve.js.map +1 -0
  671. package/dist/ui/render-mode.d.ts +5 -0
  672. package/dist/ui/render-mode.js +1 -1
  673. package/dist/ui/render-mode.js.map +1 -1
  674. package/dist/ui/resource-detail.js +0 -3
  675. package/dist/ui/resource-detail.js.map +1 -1
  676. package/dist/ui/resource-resolve.d.ts +9 -2
  677. package/dist/ui/resource-resolve.js +11 -4
  678. package/dist/ui/resource-resolve.js.map +1 -1
  679. package/dist/ui/spinner.d.ts +10 -1
  680. package/dist/ui/spinner.js +12 -3
  681. package/dist/ui/spinner.js.map +1 -1
  682. package/dist/ui/table.d.ts +7 -0
  683. package/dist/ui/table.js +11 -0
  684. package/dist/ui/table.js.map +1 -1
  685. package/dist/ui/tax.d.ts +10 -0
  686. package/dist/ui/tax.js +31 -0
  687. package/dist/ui/tax.js.map +1 -0
  688. package/dist/ui/ticket-resolve.d.ts +9 -1
  689. package/dist/ui/ticket-resolve.js +13 -2
  690. package/dist/ui/ticket-resolve.js.map +1 -1
  691. package/dist/ui/volume-resolve.js +4 -1
  692. package/dist/ui/volume-resolve.js.map +1 -1
  693. package/dist/ui/wait.d.ts +4 -0
  694. package/dist/ui/wait.js +7 -2
  695. package/dist/ui/wait.js.map +1 -1
  696. package/dist/ui/wizard.d.ts +2 -1
  697. package/dist/ui/wizard.js +1 -1
  698. package/dist/ui/wizard.js.map +1 -1
  699. package/dist/version-check-bin.d.ts +35 -0
  700. package/dist/version-check-bin.js +123 -0
  701. package/dist/version-check-bin.js.map +1 -0
  702. package/dist/version-check.d.ts +44 -0
  703. package/dist/version-check.js +153 -0
  704. package/dist/version-check.js.map +1 -0
  705. package/package.json +59 -4
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { ApiError, AuthExpiredError, NetworkError } from "./errors.js";
3
- export const CLI_VERSION = "0.1.6";
4
+ export const CLI_VERSION = "1.1.1";
4
5
  let insecureTlsWarned = false;
5
6
  function isLocalOrPrivateHost(hostname) {
6
7
  const h = hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
@@ -38,6 +39,86 @@ function warnInsecureTls(baseUrl) {
38
39
  insecureTlsWarned = true;
39
40
  process.stderr.write(`WARNING: TLS certificate verification disabled for ${baseUrl} — credentials may be exposed.\n`);
40
41
  }
42
+ /** A list route's rows whether the portal answers a bare array (the shape
43
+ * these routes had through 2026-10-08) or the paginated
44
+ * `{items, total, page, per_page, pages}` envelope the apps / functions
45
+ * lists moved to (portal 1a57feba0). The CLI never names a page, so the
46
+ * first page is the list it asked for; a shape that is neither is an empty
47
+ * list rather than a `.find is not a function` crash mid-deploy. */
48
+ /** The widest page the paginated list routes allow (`page_params(max_per_page=200)`),
49
+ * asked for on every list the CLI reads whole — a 50-row default page would
50
+ * silently drop the 51st app or release that the pre-page routes returned.
51
+ * An older portal that still answers a bare array ignores the query. */
52
+ const PAGE_MAX = "per_page=200";
53
+ /** The numeric twin of PAGE_MAX for the routes that take `query:` params. */
54
+ export const LIST_PAGE_MAX = 200;
55
+ /** /tickets caps per_page at 100 and 422s on a larger value (never clamps). */
56
+ export const TICKET_PAGE_MAX = 100;
57
+ export function listItems(r) {
58
+ if (Array.isArray(r))
59
+ return r;
60
+ if (r && Array.isArray(r.items))
61
+ return r.items;
62
+ return [];
63
+ }
64
+ /** Normalise a list response to a `Page` whether the portal answered the
65
+ * `{items,total,page,per_page,pages}` envelope or (an older portal) a bare
66
+ * array — a bare array is the whole list, so it is one page of itself. */
67
+ export function asPage(r) {
68
+ if (Array.isArray(r))
69
+ return { items: r, total: r.length, page: 1, per_page: r.length, pages: 1 };
70
+ const items = listItems(r);
71
+ const total = typeof r?.total === "number" ? r.total : items.length;
72
+ const page = typeof r?.page === "number" && r.page > 0 ? r.page : 1;
73
+ const per_page = typeof r?.per_page === "number" && r.per_page > 0 ? r.per_page : items.length;
74
+ const pages = typeof r?.pages === "number" && r.pages > 0 ? r.pages : 1;
75
+ return { items, total, page, per_page, pages };
76
+ }
77
+ /**
78
+ * Walk a paginated route from page 1 until the last page, an empty page, or
79
+ * `stop(page)` says the caller has what it came for. THE one paging loop in
80
+ * the CLI: the name resolvers (an id prefix `q=` cannot search, a ticket #
81
+ * whose `q=` page is still full, a release number past the first 200) all
82
+ * go through here rather than each carrying its own `while` loop.
83
+ */
84
+ export async function pageThrough(fetchPage, stop) {
85
+ const out = [];
86
+ let pageNo = 1;
87
+ for (;;) {
88
+ const page = await fetchPage(pageNo);
89
+ out.push(...page.items);
90
+ if (page.items.length === 0 || stop?.(page))
91
+ break;
92
+ // `pages` is the server's count; a bare-array portal reports 1. A page
93
+ // that came back short of `per_page` is also the last one.
94
+ if (page.page >= page.pages || page.items.length < page.per_page)
95
+ break;
96
+ pageNo = page.page + 1;
97
+ }
98
+ return out;
99
+ }
100
+ /** A /resources/block_volume row in the CLI's StorageVolume shape (the
101
+ * GET /storage list this replaced). */
102
+ function storageVolumeFromRow(r) {
103
+ return {
104
+ id: r.id,
105
+ name: r.name,
106
+ size_gb: r.size_gb,
107
+ status: r.status,
108
+ is_attached: r.status === "attached",
109
+ disk_slot: r.disk_slot,
110
+ monthly_price: r.monthly_price != null ? Number(r.monthly_price) : null,
111
+ attached_resource_id: r.resource_id,
112
+ attached_resource_hostname: r.attached_resource_hostname,
113
+ project_id: r.project_id,
114
+ project_name: r.project_name,
115
+ // BlockVolumeOut carries location_name only; nothing in the CLI
116
+ // consumes StorageVolume.location_id.
117
+ location_id: null,
118
+ location_name: r.location_name,
119
+ created_at: r.created_at,
120
+ };
121
+ }
41
122
  export class ApiClient {
42
123
  opts;
43
124
  constructor(opts) {
@@ -46,11 +127,26 @@ export class ApiClient {
46
127
  get fetchImpl() {
47
128
  return (this.opts.fetchImpl ?? globalThis.fetch);
48
129
  }
49
- async makeInit(method, headers, body) {
130
+ /** The token refresh in flight, if any. Requests that 401 together (a
131
+ * `Promise.all` of list calls on an expired access token) coalesce onto
132
+ * it: a refresh token is single-use, so a second POST /auth/refresh with
133
+ * the same one is refused and would turn a recoverable expiry into a
134
+ * spurious "session expired". */
135
+ refreshing = null;
136
+ /** Whether this client was configured to skip TLS verification
137
+ * (--insecure / SYNTEQ_INSECURE_TLS). Read by the direct-upload PUT
138
+ * (apps-support/deploy.ts's uploadSource), which goes over node:http(s)
139
+ * rather than through dispatch()/makeInit() and needs to honour the same
140
+ * setting for local-dev self-signed certs. */
141
+ get insecureTls() {
142
+ return Boolean(this.opts.insecure);
143
+ }
144
+ async makeInit(method, headers, body, rawBody) {
50
145
  const init = {
51
146
  method,
52
147
  headers,
53
- body: body ? JSON.stringify(body) : undefined,
148
+ body: rawBody ??
149
+ (body instanceof FormData ? body : body ? JSON.stringify(body) : undefined),
54
150
  };
55
151
  if (this.opts.insecure) {
56
152
  warnInsecureTls(this.opts.baseUrl);
@@ -70,18 +166,71 @@ export class ApiClient {
70
166
  headers[k] = v;
71
167
  });
72
168
  opts.onResponse?.(res.status, headers);
73
- const text = res.status === 204 ? "" : await res.text();
169
+ let text;
170
+ try {
171
+ text = res.status === 204 ? "" : await res.text();
172
+ }
173
+ catch (e) {
174
+ // A body that dies mid-stream is a retryable network fault (exit 3), not
175
+ // the raw TypeError it surfaces as — the same wrap `parseResponse` has.
176
+ throw new NetworkError(e, res.url || undefined);
177
+ }
74
178
  const parsed = text ? safeJson(text) : undefined;
75
179
  const body = parsed === null && text ? undefined : parsed;
76
180
  if (!res.ok)
77
181
  throw buildApiError(res, body ?? {});
78
182
  return { status: res.status, headers, body, text };
79
183
  }
184
+ /**
185
+ * POST a single file as multipart/form-data. Reuses request()/dispatch() for
186
+ * auth and error handling; the FormData body makes fetch pick the boundary
187
+ * and Content-Type, so it bypasses the JSON envelope applied by default.
188
+ */
189
+ async uploadMultipart(path, file, field = "file") {
190
+ const bytes = await readFile(file.path);
191
+ const form = new FormData();
192
+ form.append(field, new Blob([bytes], { type: file.contentType }), file.filename);
193
+ return this.request("POST", path, { formData: form });
194
+ }
195
+ /**
196
+ * GET a binary resource (e.g. a ticket attachment). Unlike request(), this
197
+ * reads the raw bytes instead of JSON-decoding, and returns the server's
198
+ * Content-Type and suggested filename from the response headers.
199
+ */
200
+ async download(path) {
201
+ const res = await this.dispatch("GET", path, {});
202
+ if (!res.ok) {
203
+ let text;
204
+ try {
205
+ text = res.status === 204 ? "" : await res.text();
206
+ }
207
+ catch (e) {
208
+ throw new NetworkError(e, res.url || undefined);
209
+ }
210
+ throw buildApiError(res, text ? (safeJson(text) ?? {}) : {});
211
+ }
212
+ let bytes;
213
+ try {
214
+ // Reading the attachment bytes can drop mid-stream too — a network
215
+ // fault (exit 3), not the raw TypeError it would otherwise surface as.
216
+ bytes = Buffer.from(await res.arrayBuffer());
217
+ }
218
+ catch (e) {
219
+ throw new NetworkError(e, res.url || undefined);
220
+ }
221
+ return {
222
+ bytes,
223
+ contentType: res.headers.get("content-type"),
224
+ filename: filenameFromContentDisposition(res.headers.get("content-disposition")),
225
+ };
226
+ }
80
227
  async dispatch(method, path, opts) {
81
228
  const auth = opts.auth ?? true;
229
+ const payload = opts.formData ?? opts.body;
82
230
  const headers = {
83
- "Content-Type": "application/json",
84
231
  "User-Agent": `synteq-cli/${CLI_VERSION} node/${process.version}`,
232
+ // Let fetch set the multipart Content-Type (with boundary) for uploads.
233
+ ...(opts.formData ? {} : { "Content-Type": "application/json" }),
85
234
  ...(opts.headers ?? {}),
86
235
  };
87
236
  let refreshable = null;
@@ -112,7 +261,7 @@ export class ApiClient {
112
261
  const url = this.buildUrl(path, opts.query);
113
262
  let res;
114
263
  try {
115
- res = await this.fetchImpl(url, await this.makeInit(method, headers, opts.body));
264
+ res = await this.fetchImpl(url, await this.makeInit(method, headers, payload, opts.rawBody));
116
265
  }
117
266
  catch (e) {
118
267
  throw new NetworkError(e, url);
@@ -123,12 +272,12 @@ export class ApiClient {
123
272
  : "Stored API token rejected. Re-run `synteq login --with-token` with a valid key, or `synteq logout` to clear it.");
124
273
  }
125
274
  if (res.status === 401 && refreshable) {
126
- const refreshed = await this.tryRefresh(refreshable);
275
+ const refreshed = await this.refreshTokens(refreshable);
127
276
  if (!refreshed)
128
277
  throw new AuthExpiredError();
129
278
  headers["Authorization"] = `Bearer ${refreshed.access_token}`;
130
279
  try {
131
- res = await this.fetchImpl(url, await this.makeInit(method, headers, opts.body));
280
+ res = await this.fetchImpl(url, await this.makeInit(method, headers, payload, opts.rawBody));
132
281
  }
133
282
  catch (e) {
134
283
  throw new NetworkError(e, url);
@@ -138,6 +287,28 @@ export class ApiClient {
138
287
  }
139
288
  return res;
140
289
  }
290
+ /**
291
+ * Single-flight wrapper around `tryRefresh`. `used` is the pair the 401'd
292
+ * request was sent with. One refresh runs at a time; everyone who 401s
293
+ * while it is in flight gets its result. A request that 401s AFTER a
294
+ * refresh already finished (it was sent with the old access token, the
295
+ * store now holds the new pair) takes the stored pair as-is rather than
296
+ * spending a refresh token that no longer exists.
297
+ */
298
+ refreshTokens(used) {
299
+ if (!this.refreshing) {
300
+ this.refreshing = (async () => {
301
+ const stored = await this.opts.tokens.read();
302
+ if (stored && stored.kind !== "api_key" && stored.access_token !== used.access_token) {
303
+ return stored;
304
+ }
305
+ return this.tryRefresh(used);
306
+ })().finally(() => {
307
+ this.refreshing = null;
308
+ });
309
+ }
310
+ return this.refreshing;
311
+ }
141
312
  async tryRefresh(current) {
142
313
  let res;
143
314
  try {
@@ -188,11 +359,32 @@ export class ApiClient {
188
359
  async parseResponse(res) {
189
360
  if (res.status === 204)
190
361
  return undefined;
191
- const text = await res.text();
362
+ // `fetch()` resolving only means the headers arrived — the body can still
363
+ // die mid-stream (a socket reset, a load balancer cycling). `dispatch`
364
+ // wraps the fetch call as a NetworkError but not this read, so an
365
+ // interrupted body surfaced as a raw `TypeError: terminated`: fatal to a
366
+ // long `deploy`/`function deploy` poll instead of the retryable network
367
+ // fault it is (`pollBuild`/`waitFor` keep polling through NetworkError).
368
+ let text;
369
+ try {
370
+ text = await res.text();
371
+ }
372
+ catch (e) {
373
+ throw new NetworkError(e, res.url || undefined);
374
+ }
192
375
  const body = text ? safeJson(text) : {};
193
- if (res.ok)
194
- return body;
195
- throw buildApiError(res, body);
376
+ if (!res.ok)
377
+ throw buildApiError(res, body);
378
+ // A 2xx whose body is not JSON never came from the API (a proxy's HTML
379
+ // error page, a captive portal). Surfacing it as a network fault — exit
380
+ // 3, safe to retry — beats handing the caller `null` to read `.items`
381
+ // off. A literal `null` body is what a None-returning route answers and
382
+ // stays as-is.
383
+ if (body === null && text.trim() !== "null") {
384
+ const contentType = res.headers.get("content-type") ?? "no content-type";
385
+ throw new NetworkError(new Error(`non-JSON response (HTTP ${res.status}, ${contentType})`), res.url || undefined);
386
+ }
387
+ return body;
196
388
  }
197
389
  auth = {
198
390
  deviceCode: (client_id = "synteq-cli", scope = "cli") => this.request("POST", "/auth/device/code", {
@@ -211,6 +403,14 @@ export class ApiClient {
211
403
  whoami: () => this.request("GET", "/auth/me"),
212
404
  apiKeyWhoami: () => this.request("GET", "/organization/api-keys/whoami"),
213
405
  teams: () => this.request("GET", "/account/teams"),
406
+ /**
407
+ * Mint a single-use, 60s WebSocket ticket. Browsers can't attach the
408
+ * session cookie to a `ws://` handshake; the CLI carries a bearer token
409
+ * but the log WS reuses the same ticket contract for parity, so we fetch
410
+ * a throwaway ticket and pass it as `?token=` on the ws URL. The ticket
411
+ * is consumed exactly once server-side.
412
+ */
413
+ wsTicket: () => this.request("POST", "/auth/ws-ticket", { body: {} }),
214
414
  };
215
415
  projects = {
216
416
  list: () => this.request("GET", "/account/projects"),
@@ -221,51 +421,46 @@ export class ApiClient {
221
421
  remove: (keyId) => this.request("DELETE", `/account/ssh-keys/${keyId}`),
222
422
  };
223
423
  virtualized = {
224
- list: (projectId) => this.request("GET", "/resources", {
424
+ /** GET /resources (50/200): `q` matches hostname / id / IP / location,
425
+ * `kind` narrows to one virtualized kind server-side. */
426
+ listPage: (projectId, query = {}) => this.request("GET", "/resources", {
427
+ query: { per_page: LIST_PAGE_MAX, ...query },
225
428
  headers: projectId ? { "X-Project-Id": projectId } : undefined,
226
- }),
429
+ }).then(asPage),
430
+ list: (projectId, query) => this.virtualized.listPage(projectId, query).then((p) => p.items),
227
431
  get: (resourceId) => this.request("GET", `/resources/${resourceId}`),
228
432
  stats: (resourceId, timeframe = "hour") => this.request("GET", `/virtualized/${resourceId}/stats`, { query: { timeframe } }),
229
433
  rrd: (resourceId, timeframe = "hour") => this.request("GET", `/virtualized/${resourceId}/rrd`, { query: { timeframe } }),
230
434
  bandwidth: (resourceId) => this.request("GET", `/virtualized/${resourceId}/bandwidth`),
231
435
  };
232
436
  transit = {
233
- list: (projectId) => this.request("GET", "/transit/services", {
437
+ list: (projectId) => this.request("GET", `/transit/services?${PAGE_MAX}`, {
234
438
  headers: projectId ? { "X-Project-Id": projectId } : undefined,
235
- }),
439
+ }).then(listItems),
236
440
  };
237
441
  storage = {
238
442
  // GET /storage was deleted (/resources unification) — volumes now come
239
443
  // from the generic kind-scoped list. Adapt rows back to the CLI's
240
444
  // StorageVolume shape at this single call site. No projectId → list the
241
445
  // whole team (?team=true), matching the old endpoint's breadth.
242
- list: async (projectId) => {
243
- const rows = await this.request("GET", "/resources/block_volume", projectId
244
- ? { headers: { "X-Project-Id": projectId } }
245
- : { query: { team: "true" } });
246
- return rows
247
- // Parity with the old /storage list: cancelled volumes are dead
248
- // rows kept for audit, not customer-visible storage.
249
- .filter((r) => r.status !== "cancelled")
250
- .map((r) => ({
251
- id: r.id,
252
- name: r.name,
253
- size_gb: r.size_gb,
254
- status: r.status,
255
- is_attached: r.status === "attached",
256
- disk_slot: r.disk_slot,
257
- monthly_price: r.monthly_price != null ? Number(r.monthly_price) : null,
258
- attached_resource_id: r.resource_id,
259
- attached_resource_hostname: r.attached_resource_hostname,
260
- project_id: r.project_id,
261
- project_name: r.project_name,
262
- // BlockVolumeOut carries location_name only; nothing in the CLI
263
- // consumes StorageVolume.location_id.
264
- location_id: null,
265
- location_name: r.location_name,
266
- created_at: r.created_at,
446
+ /** GET /resources/block_volume (50/200): `q` matches name / id /
447
+ * attached VM hostname. */
448
+ listPage: async (projectId, query = {}) => {
449
+ const page = asPage(await this.request("GET", "/resources/block_volume", {
450
+ query: { per_page: LIST_PAGE_MAX, ...query, ...(projectId ? {} : { team: "true" }) },
451
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
267
452
  }));
453
+ // Parity with the old /storage list: cancelled volumes are dead
454
+ // rows kept for audit, not customer-visible storage. They still
455
+ // count in the server's `total`, so take them off it too.
456
+ const live = page.items.filter((r) => r.status !== "cancelled");
457
+ return {
458
+ ...page,
459
+ total: Math.max(live.length, page.total - (page.items.length - live.length)),
460
+ items: live.map(storageVolumeFromRow),
461
+ };
268
462
  },
463
+ list: (projectId, query) => this.storage.listPage(projectId, query).then((p) => p.items),
269
464
  catalog: () => this.request("GET", "/public/storage/catalog", { auth: false }),
270
465
  // POST /block-volumes was deleted (/resources unification) — standalone
271
466
  // volume creation now rides the generic /orders surface
@@ -327,6 +522,10 @@ export class ApiClient {
327
522
  };
328
523
  payments = {
329
524
  methods: () => this.request("GET", "/payments/methods"),
525
+ /** Every active team method annotated with whether THIS touchpoint may
526
+ * charge it (the backend owns the allowlist; the web's pickers read the
527
+ * same route). */
528
+ methodsForTouchpoint: (touchpoint) => this.request("GET", `/payments/touchpoints/${encodeURIComponent(touchpoint)}/payment-methods`),
330
529
  topup: (body) => this.request("POST", "/payments/credits/topup", { body }),
331
530
  setDefaultMethod: (methodId) => this.request("POST", `/payments/methods/${methodId}/set-default`),
332
531
  deleteMethod: (methodId) => this.request("DELETE", `/payments/methods/${methodId}`),
@@ -350,6 +549,7 @@ export class ApiClient {
350
549
  list: (resourceId) => this.request("GET", `/virtualized/${resourceId}/addons`),
351
550
  pending: (resourceId) => this.request("GET", `/virtualized/${resourceId}/pending-addons`),
352
551
  attach: (resourceId, body) => this.request("POST", `/virtualized/${resourceId}/addons`, { body }),
552
+ quote: (resourceId, body) => this.request("POST", `/virtualized/${resourceId}/addons/quote`, { body }),
353
553
  };
354
554
  projectActivity = {
355
555
  list: (projectId, limit = 25, offset = 0) => this.request("GET", `/projects/${projectId}/activity`, {
@@ -361,6 +561,9 @@ export class ApiClient {
361
561
  // Single-spec fit at one location — returns {available, reason?, max_fittable?}.
362
562
  // The backend defaults `kind` to "vps"; pass explicitly when probing a VDS /
363
563
  // GPU Cloud / Instant Metal custom spec so the right node pool is used.
564
+ // `max_fittable` is entitlement-clamped server-side: null whenever the
565
+ // caller's entitled quantity fits (always, for anon calls) — treat it as
566
+ // advisory, never as an inventory count.
364
567
  validate: (body) => this.request("POST", "/public/order/validate", { body, auth: false }),
365
568
  // Customer order surface is /orders + /orders/batch + /orders/{id}
366
569
  // (kind-dispatched, spec/payment wrapper, N-independent-specs batch,
@@ -386,6 +589,69 @@ export class ApiClient {
386
589
  const out = await this.request("GET", `/orders/${orderId}`);
387
590
  return genericOrderOutToStatusOut(out);
388
591
  },
592
+ // Price a {kind, spec} without creating an order — used by the
593
+ // vps/order, storage/order and baremetal/order wizards to show a
594
+ // tax-inclusive total before the "Submit order?" confirm. Read-only,
595
+ // rate-limited server-side. Callers MUST wrap this in try/catch and
596
+ // fall back to the net estimate — a preview failure (Numeral outage,
597
+ // rate limit) must never block ordering.
598
+ preview: (body) => this.request("POST", "/orders/preview", { body }),
599
+ /** A paid app is an ORDER: the first month is invoiced and charged, and
600
+ * provisioning runs off the settled order (the web's NewAppPayModal
601
+ * does the same). POST /apps only creates free-tier apps. */
602
+ createApp: (spec, payment) => this.request("POST", "/orders", { body: { kind: "app", spec, payment } }),
603
+ };
604
+ // ── Bare metal (ILA-managed physical servers) ─────────────────────────
605
+ // Separate resource family from /resources + /virtualized (Instant Metal
606
+ // is a whole-node VM; this is a physical box). List/detail/invoices live
607
+ // on /baremetal/*; lifecycle (power/jobs/install-password/reinstall) on
608
+ // the same prefix via baremetal_ila.py — ILA-backed, no Proxmox involved.
609
+ baremetal = {
610
+ /** GET /baremetal (50/200): `q` matches hostname / reference / primary
611
+ * IP / id. */
612
+ listPage: (query = {}) => this.request("GET", "/baremetal", {
613
+ query: { per_page: LIST_PAGE_MAX, ...query },
614
+ }).then(asPage),
615
+ list: (query) => this.baremetal.listPage(query).then((p) => p.items),
616
+ get: (serviceId) => this.request("GET", `/baremetal/${serviceId}`),
617
+ invoices: (serviceId) => this.request("GET", `/baremetal/${serviceId}/invoices`),
618
+ // Dedicated routes, NOT the generic /resources/{id}/activity or
619
+ // /virtualized/{id}/bandwidth — those resolve via get_owned_resource_any
620
+ // (virtualized/block_volume only) and would 404 a baremetal service id.
621
+ // See backend/app/routers/customer/resource_activity.py's
622
+ // list_baremetal_activity and bandwidth.py's baremetal_bandwidth.
623
+ activity: (serviceId, limit = 25, offset = 0) => this.request("GET", `/baremetal/${serviceId}/activity`, {
624
+ query: { limit, offset },
625
+ }),
626
+ bandwidth: (serviceId) => this.request("GET", `/baremetal/${serviceId}/bandwidth`),
627
+ powerState: (serviceId) => this.request("GET", `/baremetal/${serviceId}/power`),
628
+ power: (serviceId, action) => this.request("POST", `/baremetal/${serviceId}/power`, {
629
+ body: { action },
630
+ }),
631
+ installProfiles: (serviceId) => this.request("GET", `/baremetal/${serviceId}/install-profiles`),
632
+ jobs: (serviceId) => this.request("GET", `/baremetal/${serviceId}/jobs`),
633
+ jobEvents: (serviceId, jobId) => this.request("GET", `/baremetal/${serviceId}/jobs/${jobId}/events`),
634
+ cancelJob: (serviceId, jobId) => this.request("POST", `/baremetal/${serviceId}/jobs/${jobId}/cancel`),
635
+ reinstall: (serviceId, body) => this.request("POST", `/baremetal/${serviceId}/reinstall`, { body }),
636
+ installPasswordStatus: (serviceId) => this.request("GET", `/baremetal/${serviceId}/install-password`),
637
+ revealInstallPassword: (serviceId) => this.request("POST", `/baremetal/${serviceId}/install-password/reveal`),
638
+ // Public catalog — unauthenticated, groups sellable offerings by
639
+ // location. The common case is an empty list (build-to-order product);
640
+ // `baremetal order` uses this to decide whether there's a self-serve
641
+ // path at all before touching /orders.
642
+ catalog: () => this.request("GET", "/public/baremetal/catalog", { auth: false }),
643
+ installProfilesForOffering: (offeringId) => this.request("GET", `/public/baremetal/offerings/${offeringId}/install-profiles`, { auth: false }),
644
+ // POST /orders {kind:"baremetal", spec:{...}} — adapt to the same
645
+ // generic /orders wrapper the virtualized/storage adapters use above,
646
+ // so order.ts keeps a flat BaremetalOrderSpec + payment pair.
647
+ order: async (spec, payment, projectId) => {
648
+ const specBody = { ...spec };
649
+ const out = await this.request("POST", "/orders", {
650
+ body: { kind: "baremetal", spec: specBody, payment },
651
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
652
+ });
653
+ return genericCreateOutToOrderResult(out);
654
+ },
389
655
  };
390
656
  teams = {
391
657
  details: (teamId) => this.request("GET", `/account/teams/${teamId}/details`),
@@ -393,15 +659,238 @@ export class ApiClient {
393
659
  };
394
660
  tickets = {
395
661
  departments: () => this.request("GET", "/tickets/departments"),
396
- list: () => this.request("GET", "/tickets"),
662
+ /** GET /tickets (25/100): `q` matches `display_id == int(q)` OR a
663
+ * subject / body substring (so a `#` lookup still exact-matches
664
+ * client-side); `status` filters server-side. Its cap is 100, not
665
+ * 200 — a larger per_page is a 422, never clamped. */
666
+ listPage: (query = {}) => this.request("GET", "/tickets", {
667
+ query: { per_page: TICKET_PAGE_MAX, ...query },
668
+ }).then(asPage),
669
+ list: (query) => this.tickets.listPage(query).then((p) => p.items),
397
670
  get: (ticketId) => this.request("GET", `/tickets/${ticketId}`),
398
671
  messages: (ticketId) => this.request("GET", `/tickets/${ticketId}/messages`),
399
672
  create: (body) => this.request("POST", "/tickets", { body }),
400
673
  reply: (ticketId, body) => this.request("POST", `/tickets/${ticketId}/messages`, { body }),
674
+ uploadAttachment: (ticketId, messageId, file) => this.uploadMultipart(`/tickets/${ticketId}/messages/${messageId}/attachments`, file),
675
+ downloadAttachment: (ticketId, messageId, attachmentId) => this.download(`/tickets/${ticketId}/messages/${messageId}/attachments/${attachmentId}`),
401
676
  setStatus: (ticketId, status) => this.request("PATCH", `/tickets/${ticketId}/status`, {
402
677
  body: { status },
403
678
  }),
404
679
  };
680
+ /**
681
+ * Managed containers (apps). Like `vps`, list/create are scoped to the
682
+ * active project by the `X-Project-Id` header, not a query param — the
683
+ * backend resolves the active project from it (falling back to the team's
684
+ * first project when unset). Responses are camelCase (`_CamelOut`).
685
+ */
686
+ apps = {
687
+ plans: () => this.request("GET", "/apps-plans"),
688
+ /** Placeable clusters for this environment — what the web onboarding's
689
+ * location picker reads, so the CLI never offers a location that has
690
+ * no cluster behind it. */
691
+ locations: () => this.request("GET", "/apps/locations"),
692
+ /** GET /apps (50/200): `q` matches name / slug / hostname. */
693
+ listPage: (projectId, query = {}) => this.request("GET", "/apps", {
694
+ query: { per_page: LIST_PAGE_MAX, ...query },
695
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
696
+ }).then(asPage),
697
+ list: (projectId, query) => this.apps.listPage(projectId, query).then((p) => p.items),
698
+ get: (appId) => this.request("GET", `/apps/${appId}`),
699
+ /** Apps deploying from a GitHub repo, across every team the user is in. */
700
+ findByRepo: (repo) => this.request("GET", `/apps/by-repo?repo=${encodeURIComponent(repo)}`),
701
+ checkName: (slug) => this.request("POST", "/apps/check-name", { body: { slug } }),
702
+ create: (body, projectId) => this.request("POST", "/apps", {
703
+ body,
704
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
705
+ }),
706
+ deploy: (appId, body) => this.request("POST", `/apps/${appId}/deploys`, { body }),
707
+ /** Deploy from a local source tarball (gzipped tar as the raw body). The
708
+ * portal stores it + the cluster builds it — no external URL needed.
709
+ * Fallback path (loads the whole tarball into memory) — kept for
710
+ * clusters that don't support direct upload; use `uploadSource()`
711
+ * (apps-support/deploy.ts) for the streamed path, which tries this
712
+ * automatically when direct upload isn't available and the pack fits
713
+ * the proxy's body-size limit. */
714
+ deploySource: (appId, tarball) => this.request("POST", `/apps/${appId}/deploys/source`, {
715
+ rawBody: tarball,
716
+ headers: { "Content-Type": "application/gzip" },
717
+ }),
718
+ /** Mint a presigned PUT for the object store, so the CLI can upload the
719
+ * (possibly ~1 GB) source tarball directly instead of through the API —
720
+ * the proxy in front of the API rejects bodies over 100 MB. A used or
721
+ * expired URL must not be reused: call this again for every retry, with
722
+ * `replaces` set to the previous attempt's `objectKey` so the API frees
723
+ * that attempt's pending-upload slot (an older API 422s the field). */
724
+ sourceUploadUrl: (appId, body) => this.request("POST", `/apps/${appId}/deploys/source/upload-url`, { body }),
725
+ /** Trigger the build from an object already PUT to the URL
726
+ * `sourceUploadUrl` minted — the counterpart to `deploySource` for the
727
+ * direct-upload path. */
728
+ deploySourceObject: (appId, objectKey) => this.request("POST", `/apps/${appId}/deploys/source`, {
729
+ body: { sourceObjectKey: objectKey },
730
+ }),
731
+ builds: (appId) => this.request("GET", `/apps/${appId}/builds?${PAGE_MAX}`).then(listItems),
732
+ /** GET /apps/{id}/releases (50/200), ordered by number desc. */
733
+ releasesPage: (appId, query = {}) => this.request("GET", `/apps/${appId}/releases`, {
734
+ query: { per_page: LIST_PAGE_MAX, ...query },
735
+ }).then(asPage),
736
+ releases: (appId) => this.apps.releasesPage(appId).then((p) => p.items),
737
+ env: (appId) => this.request("GET", `/apps/${appId}/env`),
738
+ setEnv: (appId, body) => this.request("PUT", `/apps/${appId}/env`, { body }),
739
+ /** "Roll out now": push the saved env and redeploy the current release. */
740
+ rolloutEnv: (appId) => this.request("POST", `/apps/${appId}/env/rollout`, { body: {} }),
741
+ /** GitHub push-to-deploy link. GET returns null when nothing is linked. */
742
+ githubLink: (appId) => this.request("GET", `/apps/${appId}/github/link`),
743
+ linkGithub: (appId, body) => this.request("POST", `/apps/${appId}/github/link`, { body }),
744
+ unlinkGithub: (appId) => this.request("DELETE", `/apps/${appId}/github/link`),
745
+ // --- env: reveal + delete (set/list are above) ---
746
+ revealEnv: (appId, key) => this.request("POST", `/apps/${appId}/env/${encodeURIComponent(key)}/reveal`),
747
+ deleteEnv: (appId, key) => this.request("DELETE", `/apps/${appId}/env/${encodeURIComponent(key)}`),
748
+ // --- deploy history detail ---
749
+ buildDetail: (appId, buildId) => this.request("GET", `/apps/${appId}/builds/${buildId}`),
750
+ // --- logs: bounded history/search (live tail is the WS in logs.ts) ---
751
+ /** `since` is a relative window (15m … 4320h). `from` / `until` (ISO
752
+ * instants) name an absolute timeframe instead — `from` replaces
753
+ * `since`, `until` bounds the top (2026-09-12). */
754
+ logHistory: (appId, opts = {}) => {
755
+ const q = new URLSearchParams();
756
+ if (opts.from)
757
+ q.set("from", opts.from);
758
+ else if (opts.since)
759
+ q.set("since", opts.since);
760
+ if (opts.until)
761
+ q.set("until", opts.until);
762
+ if (opts.container)
763
+ q.set("container", opts.container);
764
+ if (opts.search)
765
+ q.set("search", opts.search);
766
+ if (opts.limit != null)
767
+ q.set("limit", String(opts.limit));
768
+ const qs = q.toString();
769
+ return this.request("GET", `/apps/${appId}/logs/history${qs ? `?${qs}` : ""}`);
770
+ },
771
+ // --- metrics: usage series + live current + plan envelope ---
772
+ /** `range` (15m/1h/6h/24h/72h/168h) shapes the series; `since`+`until`
773
+ * (ISO, together) pick an explicit window instead. */
774
+ usage: (appId, opts = {}) => {
775
+ const q = new URLSearchParams();
776
+ if (opts.range)
777
+ q.set("range", opts.range);
778
+ if (opts.since)
779
+ q.set("since", opts.since);
780
+ if (opts.until)
781
+ q.set("until", opts.until);
782
+ const qs = q.toString();
783
+ return this.request("GET", `/apps/${appId}/usage${qs ? `?${qs}` : ""}`);
784
+ },
785
+ // --- rollback: re-deploy an earlier release's image (mints a new release) ---
786
+ rollback: (appId, toRelease) => this.request("POST", `/apps/${appId}/rollback`, { body: { toRelease } }),
787
+ // --- health ---
788
+ health: (appId) => this.request("GET", `/apps/${appId}/health-check`),
789
+ // --- domains (add/list/verify are all that exist server-side; delete added) ---
790
+ domains: (appId) => this.request("GET", `/apps/${appId}/domains`),
791
+ // The body key is `domain` (DomainAddIn) — it was `hostname`, which the
792
+ // API rejected with a 422 on every `app domain add`. `acceptFee` answers
793
+ // the 402 a priced tier returns for the first custom domain.
794
+ addDomain: (appId, domain, opts = {}) => this.request("POST", `/apps/${appId}/domains`, {
795
+ body: { domain, acceptFee: opts.acceptFee ?? false },
796
+ }),
797
+ verifyDomain: (appId, domainId) => this.request("POST", `/apps/${appId}/domains/${domainId}/verify`),
798
+ removeDomain: (appId, domainId) => this.request("DELETE", `/apps/${appId}/domains/${domainId}`),
799
+ // --- plan: preview + apply change ---
800
+ planPreview: (appId, planTier, schedule = "now") => this.request("GET", `/apps/${appId}/plan/preview?planTier=${encodeURIComponent(planTier)}&schedule=${schedule}`),
801
+ changePlan: (appId, body) => this.request("PATCH", `/apps/${appId}/plan`, { body }),
802
+ // --- settings: read + patch ---
803
+ settings: (appId) => this.request("GET", `/apps/${appId}/settings`),
804
+ patchSettings: (appId, body) => this.request("PATCH", `/apps/${appId}/settings`, { body }),
805
+ };
806
+ /**
807
+ * Functions (serverless). Same shape discipline as `apps` above, against
808
+ * portal/backend/app/routers/functions*.py. Project scoping rides the
809
+ * X-Project-Id header exactly as apps does. Nothing here names a build
810
+ * frontend: how a function builds is decided server-side from the bundle
811
+ * and the function's runtime, never by the caller.
812
+ */
813
+ functions = {
814
+ plans: () => this.request("GET", "/functions-plans").then((r) => r.plans),
815
+ locations: () => this.request("GET", "/functions-locations"),
816
+ /** GET /functions (100/200): `q` matches name / slug / hostname. Until
817
+ * #419 this read the 100-row default page and unwrapped it by hand,
818
+ * so the 101st function was invisible to every resolver. */
819
+ listPage: (projectId, query = {}) => this.request("GET", "/functions", {
820
+ query: { per_page: LIST_PAGE_MAX, ...query },
821
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
822
+ }).then(asPage),
823
+ list: (projectId, query) => this.functions.listPage(projectId, query).then((p) => p.items),
824
+ get: (fnId) => this.request("GET", `/functions/${fnId}`),
825
+ checkName: (name) => this.request("POST", "/functions/check-name", { body: { name } }),
826
+ create: (body, projectId) => this.request("POST", "/functions", {
827
+ body,
828
+ headers: projectId ? { "X-Project-Id": projectId } : undefined,
829
+ }),
830
+ update: (fnId, body) => this.request("PATCH", `/functions/${fnId}`, { body }),
831
+ // Step-up gated: the customer's current password rides in the body.
832
+ delete: (fnId, passwordChallenge) => this.request("DELETE", `/functions/${fnId}`, { body: { password_challenge: passwordChallenge } }),
833
+ /** Deploy pasted files. The backend tars them, keeps them on the build row
834
+ * (so `source` can reopen them) and picks the build path itself. */
835
+ deployFiles: (fnId, files) => this.request("POST", `/functions/${fnId}/deploys/source`, { body: { files } }),
836
+ /** The same endpoint's tarball shape: no 32-file editor limit, only the
837
+ * function's source caps. */
838
+ deployTarball: (fnId, tarball) => this.request("POST", `/functions/${fnId}/deploys/source`, {
839
+ body: { sourceB64: Buffer.from(tarball).toString("base64") },
840
+ }),
841
+ /** Deploy a pre-built image: the apps `deploy --image` route, function-owned
842
+ * (POST /functions/{id}/deploys — a pull Build; the status cron mints the
843
+ * release off the build's own digest). */
844
+ deployImage: (fnId, imageRef) => this.request("POST", `/functions/${fnId}/deploys`, { body: { imageRef } }),
845
+ source: (fnId) => this.request("GET", `/functions/${fnId}/source`),
846
+ builds: (fnId) => this.request("GET", `/functions/${fnId}/builds?${PAGE_MAX}`).then(listItems),
847
+ build: (fnId, buildId) => this.request("GET", `/functions/${fnId}/builds/${buildId}`),
848
+ /** GET /functions/{id}/releases (50/200), ordered by number desc. */
849
+ releasesPage: (fnId, query = {}) => this.request("GET", `/functions/${fnId}/releases`, {
850
+ query: { per_page: LIST_PAGE_MAX, ...query },
851
+ }).then(asPage),
852
+ releases: (fnId) => this.functions.releasesPage(fnId).then((p) => p.items),
853
+ rollback: (fnId, toRelease) => this.request("POST", `/functions/${fnId}/rollback`, { body: { toRelease } }),
854
+ /** Per-invocation records, newest first. `before` is the opaque cursor
855
+ * from the previous page's `nextBefore`. */
856
+ runs: (fnId, opts = {}) => {
857
+ const qs = new URLSearchParams();
858
+ if (opts.limit != null)
859
+ qs.set("limit", String(opts.limit));
860
+ if (opts.before)
861
+ qs.set("before", opts.before);
862
+ if (opts.status)
863
+ qs.set("status", opts.status);
864
+ if (opts.release != null)
865
+ qs.set("release", String(opts.release));
866
+ if (opts.q)
867
+ qs.set("q", opts.q);
868
+ const q = qs.toString();
869
+ return this.request("GET", `/functions/${fnId}/runs${q ? `?${q}` : ""}`);
870
+ },
871
+ env: (fnId) => this.request("GET", `/functions/${fnId}/env`),
872
+ setEnv: (fnId, body) => this.request("PUT", `/functions/${fnId}/env`, { body }),
873
+ /** "Roll out now": push the saved env and redeploy the current release. */
874
+ rolloutEnv: (fnId) => this.request("POST", `/functions/${fnId}/env/rollout`, { body: {} }),
875
+ deleteEnv: (fnId, key) => this.request("DELETE", `/functions/${fnId}/env/${encodeURIComponent(key)}`),
876
+ revealEnv: (fnId, key) => this.request("POST", `/functions/${fnId}/env/${encodeURIComponent(key)}/reveal`),
877
+ // --- cron triggers ---
878
+ cronTriggers: (fnId) => this.request("GET", `/functions/${fnId}/triggers/cron`),
879
+ createCronTrigger: (fnId, body) => this.request("POST", `/functions/${fnId}/triggers/cron`, { body }),
880
+ updateCronTrigger: (fnId, triggerId, body) => this.request("PATCH", `/functions/${fnId}/triggers/cron/${triggerId}`, { body }),
881
+ deleteCronTrigger: (fnId, triggerId) => this.request("DELETE", `/functions/${fnId}/triggers/cron/${triggerId}`),
882
+ runCronTrigger: (fnId, triggerId) => this.request("POST", `/functions/${fnId}/triggers/cron/${triggerId}/run`),
883
+ // --- run credits (the Billing tab's buy panel + auto-reload) ---
884
+ billing: (fnId) => this.request("GET", `/functions/${fnId}/billing`),
885
+ setCreditAutoReload: (fnId, body) => this.request("PUT", `/functions/${fnId}/credits/auto-reload`, { body }),
886
+ /** No per-function quote route exists: price from the billing view's
887
+ * `creditRateUsd` (an estimate; tax lands on the invoice). */
888
+ purchaseCredits: (fnId, body) => this.request("POST", `/functions/${fnId}/credits/purchase`, { body }),
889
+ teamBilling: () => this.request("GET", "/functions-team-billing"),
890
+ setTeamCreditAutoReload: (body) => this.request("PUT", "/functions-team-credits/auto-reload", { body }),
891
+ quoteTeamCredits: (body) => this.request("POST", "/functions-team-credits/quote", { body }),
892
+ purchaseTeamCredits: (body) => this.request("POST", "/functions-team-credits/purchase", { body }),
893
+ };
405
894
  }
406
895
  /** Old `VirtualizedOrderIn.payment_kind` ("method"/"bitpay") + a `payment_method_id`
407
896
  * that may or may not be set collapses two customer intents ("charge this
@@ -409,12 +898,19 @@ export class ApiClient {
409
898
  * The new `GenericPaymentIn.method` enum keeps them distinct — split here
410
899
  * so callers (order.ts, pickPayment) don't need to know about the new
411
900
  * vocabulary at all. */
412
- function virtualizedOrderInToGenericSpecs(body) {
901
+ // Exported so vps/order.ts's wizard can build the same {kind, spec} shape
902
+ // for a POST /orders/preview call before the confirm prompt, without
903
+ // duplicating the plan/tier/custom-spec branching here.
904
+ export function virtualizedOrderInToGenericSpecs(body) {
413
905
  const sharedSpec = {
414
906
  project_id: body.project_id,
415
907
  location_id: body.location_id,
416
908
  image_id: body.image_id,
417
909
  };
910
+ // billing_cycle must ride inside each spec — the generic /orders body has no
911
+ // top-level cycle field. Dropping it here silently bills monthly (CLI-C1).
912
+ if (body.billing_cycle)
913
+ sharedSpec.billing_cycle = body.billing_cycle;
418
914
  if (body.plan_id) {
419
915
  sharedSpec.plan_id = body.plan_id;
420
916
  }
@@ -488,6 +984,23 @@ function safeJson(s) {
488
984
  return null;
489
985
  }
490
986
  }
987
+ // Pull a suggested filename out of a Content-Disposition header. The RFC 5987
988
+ // `filename*=UTF-8''…` form (percent-encoded) wins over a plain `filename=`.
989
+ function filenameFromContentDisposition(value) {
990
+ if (!value)
991
+ return null;
992
+ const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(value);
993
+ if (star) {
994
+ try {
995
+ return decodeURIComponent(star[1].trim().replace(/^"|"$/g, ""));
996
+ }
997
+ catch {
998
+ // Malformed encoding — fall back to the plain filename= form below.
999
+ }
1000
+ }
1001
+ const plain = /filename="?([^";]+)"?/i.exec(value);
1002
+ return plain ? plain[1].trim() : null;
1003
+ }
491
1004
  function buildApiError(res, body) {
492
1005
  const requestId = res.headers.get("x-request-id") ?? undefined;
493
1006
  const d = body?.detail;
@@ -525,7 +1038,9 @@ function buildApiError(res, body) {
525
1038
  bmsg ??
526
1039
  (body && typeof body === "object" && Object.keys(body).length
527
1040
  ? JSON.stringify(body)
528
- : `HTTP ${res.status}`);
1041
+ : res.status === 413
1042
+ ? "Upload rejected as too large before it reached the API (HTTP 413). Exclude build output (.next, dist, node_modules) or check your plan's source cap with `synteq apps plans`."
1043
+ : `HTTP ${res.status}`);
529
1044
  }
530
1045
  const code = typeof d === "object" && d ? d.error ?? "error" : "error";
531
1046
  return new ApiError(res.status, code, detail, requestId);