@fastagent-sh/fastagent 0.21.1 → 0.22.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 (388) hide show
  1. package/README.md +9 -12
  2. package/dist/agent.d.ts +24 -37
  3. package/dist/agent.js +7 -15
  4. package/dist/atomic-write.d.ts +13 -17
  5. package/dist/atomic-write.js +18 -29
  6. package/dist/bind.d.ts +12 -22
  7. package/dist/bind.js +18 -27
  8. package/dist/channel.d.ts +11 -33
  9. package/dist/channels/agentcore-limits.d.ts +2 -13
  10. package/dist/channels/agentcore-limits.js +2 -13
  11. package/dist/channels/agentcore-protocol.d.ts +67 -58
  12. package/dist/channels/agentcore-protocol.js +23 -15
  13. package/dist/channels/agentcore-service.d.ts +19 -33
  14. package/dist/channels/agentcore-service.js +176 -59
  15. package/dist/channels/agentcore.d.ts +39 -33
  16. package/dist/channels/agentcore.js +90 -224
  17. package/dist/channels/body.d.ts +15 -2
  18. package/dist/channels/body.js +24 -2
  19. package/dist/channels/busy.d.ts +1 -21
  20. package/dist/channels/busy.js +2 -39
  21. package/dist/channels/control.d.ts +17 -62
  22. package/dist/channels/control.js +74 -182
  23. package/dist/channels/define-channel.d.ts +39 -0
  24. package/dist/channels/define-channel.js +9 -0
  25. package/dist/channels/discover.d.ts +10 -31
  26. package/dist/channels/discover.js +37 -31
  27. package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
  28. package/dist/channels/feishu/bootstrap-token.js +7 -23
  29. package/dist/channels/feishu/card.d.ts +14 -28
  30. package/dist/channels/feishu/card.js +16 -30
  31. package/dist/channels/feishu/cloud.d.ts +1 -5
  32. package/dist/channels/feishu/context-buffer.d.ts +4 -23
  33. package/dist/channels/feishu/context-buffer.js +8 -31
  34. package/dist/channels/feishu/crypto.d.ts +10 -5
  35. package/dist/channels/feishu/crypto.js +12 -19
  36. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  37. package/dist/channels/feishu/feishu-api.js +5 -4
  38. package/dist/channels/feishu/feishu.d.ts +4 -10
  39. package/dist/channels/feishu/feishu.js +113 -227
  40. package/dist/channels/feishu/invoke-turn.d.ts +5 -4
  41. package/dist/channels/feishu/invoke-turn.js +36 -54
  42. package/dist/channels/feishu/model.d.ts +4 -21
  43. package/dist/channels/feishu/model.js +1 -8
  44. package/dist/channels/feishu/normalize.d.ts +2 -10
  45. package/dist/channels/feishu/normalize.js +7 -42
  46. package/dist/channels/feishu/parse.d.ts +9 -29
  47. package/dist/channels/feishu/parse.js +8 -26
  48. package/dist/channels/feishu/preview.d.ts +12 -14
  49. package/dist/channels/feishu/preview.js +108 -207
  50. package/dist/channels/feishu/register-app.d.ts +8 -23
  51. package/dist/channels/feishu/register-app.js +7 -20
  52. package/dist/channels/feishu/register-webhook.d.ts +2 -9
  53. package/dist/channels/feishu/register-webhook.js +9 -33
  54. package/dist/channels/feishu/scaffold/channel.ts +27 -19
  55. package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
  56. package/dist/channels/feishu/setup-mode.d.ts +11 -36
  57. package/dist/channels/feishu/setup-mode.js +7 -26
  58. package/dist/channels/feishu/ws-ingress.d.ts +1 -2
  59. package/dist/channels/feishu/ws-ingress.js +6 -8
  60. package/dist/channels/http.d.ts +15 -15
  61. package/dist/channels/http.js +19 -9
  62. package/dist/channels/kit/attachment-path.d.ts +4 -5
  63. package/dist/channels/kit/attachment-path.js +10 -34
  64. package/dist/channels/kit/context-buffer.d.ts +14 -21
  65. package/dist/channels/kit/context-buffer.js +25 -38
  66. package/dist/channels/kit/delivery.d.ts +35 -0
  67. package/dist/channels/kit/delivery.js +129 -0
  68. package/dist/channels/kit/event-stream.d.ts +5 -0
  69. package/dist/channels/kit/event-stream.js +29 -0
  70. package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
  71. package/dist/channels/kit/invoke-turn-kit.js +89 -32
  72. package/dist/channels/kit/preview-kit.d.ts +18 -72
  73. package/dist/channels/kit/preview-kit.js +22 -88
  74. package/dist/channels/kit/signature.d.ts +3 -9
  75. package/dist/channels/kit/signature.js +3 -9
  76. package/dist/channels/kit/state.d.ts +1 -2
  77. package/dist/channels/kit/state.js +5 -14
  78. package/dist/channels/kit/stop-command.d.ts +1 -2
  79. package/dist/channels/kit/stop-command.js +6 -9
  80. package/dist/channels/kit/tasks.d.ts +1 -1
  81. package/dist/channels/kit/tasks.js +8 -21
  82. package/dist/channels/kit/text.d.ts +2 -16
  83. package/dist/channels/kit/text.js +23 -31
  84. package/dist/channels/kit/thread-participants.d.ts +4 -17
  85. package/dist/channels/kit/thread-participants.js +8 -55
  86. package/dist/channels/kit/transport.d.ts +26 -0
  87. package/dist/channels/kit/transport.js +20 -0
  88. package/dist/channels/kit/turn-queue.d.ts +7 -11
  89. package/dist/channels/kit/turn-queue.js +21 -39
  90. package/dist/channels/kit/turn-runner.d.ts +32 -18
  91. package/dist/channels/kit/turn-runner.js +98 -53
  92. package/dist/channels/kit/turn-store.d.ts +32 -32
  93. package/dist/channels/kit/turn-store.js +45 -88
  94. package/dist/channels/lark/lark.d.ts +1 -5
  95. package/dist/channels/lark/onboard.d.ts +3 -12
  96. package/dist/channels/lark/onboard.js +3 -13
  97. package/dist/channels/lark/scaffold/channel.ts +27 -19
  98. package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
  99. package/dist/channels/registration.d.ts +9 -35
  100. package/dist/channels/registration.js +6 -37
  101. package/dist/channels/secret.js +2 -5
  102. package/dist/channels/serve.d.ts +47 -44
  103. package/dist/channels/serve.js +165 -101
  104. package/dist/channels/slack/config-api.d.ts +4 -27
  105. package/dist/channels/slack/config-api.js +5 -34
  106. package/dist/channels/slack/context-buffer.d.ts +2 -3
  107. package/dist/channels/slack/context-buffer.js +5 -5
  108. package/dist/channels/slack/invoke-turn.d.ts +3 -1
  109. package/dist/channels/slack/invoke-turn.js +25 -34
  110. package/dist/channels/slack/manifest.js +1 -3
  111. package/dist/channels/slack/model.d.ts +1 -2
  112. package/dist/channels/slack/model.js +1 -1
  113. package/dist/channels/slack/onboard.d.ts +4 -3
  114. package/dist/channels/slack/onboard.js +8 -11
  115. package/dist/channels/slack/onboarding-state.d.ts +3 -5
  116. package/dist/channels/slack/onboarding-state.js +9 -6
  117. package/dist/channels/slack/parse.d.ts +4 -13
  118. package/dist/channels/slack/parse.js +7 -28
  119. package/dist/channels/slack/preview.d.ts +12 -2
  120. package/dist/channels/slack/preview.js +292 -269
  121. package/dist/channels/slack/reaction.d.ts +11 -3
  122. package/dist/channels/slack/reaction.js +19 -8
  123. package/dist/channels/slack/register-webhook.js +4 -7
  124. package/dist/channels/slack/scaffold/channel.ts +20 -13
  125. package/dist/channels/slack/scaffold/slack-send.ts +2 -2
  126. package/dist/channels/slack/setup-server.d.ts +1 -5
  127. package/dist/channels/slack/setup-server.js +1 -5
  128. package/dist/channels/slack/shared-api.d.ts +1 -4
  129. package/dist/channels/slack/shared-api.js +2 -13
  130. package/dist/channels/slack/slack-api.d.ts +2 -1
  131. package/dist/channels/slack/slack-api.js +6 -4
  132. package/dist/channels/slack/slack.d.ts +11 -14
  133. package/dist/channels/slack/slack.js +121 -122
  134. package/dist/channels/sse.d.ts +1 -1
  135. package/dist/channels/sse.js +1 -1
  136. package/dist/channels/telegram/context-buffer.d.ts +7 -22
  137. package/dist/channels/telegram/context-buffer.js +6 -23
  138. package/dist/channels/telegram/invoke-turn.d.ts +7 -7
  139. package/dist/channels/telegram/invoke-turn.js +24 -43
  140. package/dist/channels/telegram/parse.d.ts +24 -51
  141. package/dist/channels/telegram/parse.js +32 -84
  142. package/dist/channels/telegram/preview.d.ts +10 -1
  143. package/dist/channels/telegram/preview.js +79 -100
  144. package/dist/channels/telegram/register-webhook.js +5 -23
  145. package/dist/channels/telegram/scaffold/channel.ts +25 -18
  146. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
  147. package/dist/channels/telegram/telegram-api.d.ts +4 -3
  148. package/dist/channels/telegram/telegram-api.js +9 -7
  149. package/dist/channels/telegram/telegram.d.ts +4 -18
  150. package/dist/channels/telegram/telegram.js +38 -94
  151. package/dist/channels/telegram/turn-store.d.ts +11 -14
  152. package/dist/channels/telegram/turn-store.js +8 -6
  153. package/dist/channels/wait-health.d.ts +4 -6
  154. package/dist/channels/wait-health.js +10 -14
  155. package/dist/cli/add-feishu.d.ts +2 -12
  156. package/dist/cli/add-feishu.js +24 -71
  157. package/dist/cli/add-slack.d.ts +5 -3
  158. package/dist/cli/add-slack.js +53 -76
  159. package/dist/cli/auth-view.d.ts +2 -16
  160. package/dist/cli/auth-view.js +2 -16
  161. package/dist/cli/commands/add.js +25 -40
  162. package/dist/cli/commands/chat.d.ts +0 -1
  163. package/dist/cli/commands/chat.js +5 -6
  164. package/dist/cli/commands/deploy/agentcore.js +60 -65
  165. package/dist/cli/commands/deploy/docker.js +50 -23
  166. package/dist/cli/commands/deploy/fly.js +23 -61
  167. package/dist/cli/commands/deploy/railway.js +23 -18
  168. package/dist/cli/commands/deploy/shared.d.ts +38 -57
  169. package/dist/cli/commands/deploy/shared.js +40 -59
  170. package/dist/cli/commands/deploy.d.ts +10 -27
  171. package/dist/cli/commands/deploy.js +38 -60
  172. package/dist/cli/commands/destroy.d.ts +4 -0
  173. package/dist/cli/commands/destroy.js +56 -0
  174. package/dist/cli/commands/dev.d.ts +2 -1
  175. package/dist/cli/commands/dev.js +20 -23
  176. package/dist/cli/commands/info.d.ts +0 -2
  177. package/dist/cli/commands/info.js +83 -49
  178. package/dist/cli/commands/init.d.ts +1 -2
  179. package/dist/cli/commands/init.js +16 -55
  180. package/dist/cli/commands/invoke.d.ts +0 -1
  181. package/dist/cli/commands/invoke.js +5 -7
  182. package/dist/cli/commands/login.d.ts +2 -1
  183. package/dist/cli/commands/login.js +33 -38
  184. package/dist/cli/commands/logs.js +3 -4
  185. package/dist/cli/commands/models.js +1 -5
  186. package/dist/cli/commands/routine-run.d.ts +6 -0
  187. package/dist/cli/commands/routine-run.js +47 -0
  188. package/dist/cli/commands/routine.d.ts +25 -0
  189. package/dist/cli/commands/routine.js +122 -0
  190. package/dist/cli/commands/start.d.ts +31 -3
  191. package/dist/cli/commands/start.js +131 -81
  192. package/dist/cli/commands/tool.js +26 -12
  193. package/dist/cli/fail.d.ts +13 -23
  194. package/dist/cli/fail.js +20 -23
  195. package/dist/cli/invoke-stream.d.ts +1 -8
  196. package/dist/cli/kernel.d.ts +6 -38
  197. package/dist/cli/kernel.js +14 -77
  198. package/dist/cli/models-view.d.ts +1 -9
  199. package/dist/cli/models-view.js +11 -22
  200. package/dist/cli/program.d.ts +5 -6
  201. package/dist/cli/program.js +120 -157
  202. package/dist/cli/serve.d.ts +57 -41
  203. package/dist/cli/serve.js +94 -121
  204. package/dist/cli/shared.d.ts +15 -61
  205. package/dist/cli/shared.js +55 -181
  206. package/dist/cli.js +1 -8
  207. package/dist/collect.d.ts +6 -0
  208. package/dist/collect.js +6 -0
  209. package/dist/core.d.ts +4 -1
  210. package/dist/core.js +9 -7
  211. package/dist/declared-secrets.d.ts +55 -0
  212. package/dist/declared-secrets.js +88 -0
  213. package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
  214. package/dist/deploy/agentcore/aws-cli.js +64 -0
  215. package/dist/deploy/agentcore/destroy.d.ts +51 -0
  216. package/dist/deploy/agentcore/destroy.js +273 -0
  217. package/dist/deploy/agentcore/forwarder.js +28 -79
  218. package/dist/deploy/agentcore/logs.d.ts +2 -18
  219. package/dist/deploy/agentcore/logs.js +27 -51
  220. package/dist/deploy/agentcore/plan.d.ts +85 -105
  221. package/dist/deploy/agentcore/plan.js +152 -314
  222. package/dist/deploy/agentcore/run.d.ts +28 -28
  223. package/dist/deploy/agentcore/run.js +227 -306
  224. package/dist/deploy/agentcore/zip.d.ts +2 -15
  225. package/dist/deploy/agentcore/zip.js +3 -17
  226. package/dist/deploy/build-context.d.ts +21 -0
  227. package/dist/deploy/build-context.js +137 -0
  228. package/dist/deploy/channel-ingress.d.ts +26 -39
  229. package/dist/deploy/channel-ingress.js +57 -28
  230. package/dist/deploy/container.d.ts +20 -34
  231. package/dist/deploy/container.js +49 -100
  232. package/dist/deploy/docker/plan.d.ts +12 -11
  233. package/dist/deploy/docker/plan.js +43 -24
  234. package/dist/deploy/docker/run.d.ts +22 -25
  235. package/dist/deploy/docker/run.js +39 -37
  236. package/dist/deploy/fly/plan.d.ts +15 -44
  237. package/dist/deploy/fly/plan.js +41 -73
  238. package/dist/deploy/fly/run.d.ts +16 -51
  239. package/dist/deploy/fly/run.js +42 -95
  240. package/dist/deploy/hosts.d.ts +24 -3
  241. package/dist/deploy/hosts.js +24 -3
  242. package/dist/deploy/preflight.d.ts +47 -41
  243. package/dist/deploy/preflight.js +282 -383
  244. package/dist/deploy/railway/plan.d.ts +16 -42
  245. package/dist/deploy/railway/plan.js +42 -60
  246. package/dist/deploy/railway/run.d.ts +30 -68
  247. package/dist/deploy/railway/run.js +86 -100
  248. package/dist/deploy/registration-gate.d.ts +6 -13
  249. package/dist/deploy/residency.d.ts +37 -0
  250. package/dist/deploy/residency.js +24 -0
  251. package/dist/deploy/runner.d.ts +10 -9
  252. package/dist/deploy/runner.js +7 -18
  253. package/dist/deploy/secrets.d.ts +40 -41
  254. package/dist/deploy/secrets.js +117 -103
  255. package/dist/deploy/workspace.d.ts +50 -0
  256. package/dist/deploy/workspace.js +197 -0
  257. package/dist/dev-supervisor.d.ts +1 -9
  258. package/dist/dev-supervisor.js +21 -64
  259. package/dist/effect-port.d.ts +50 -0
  260. package/dist/effect-port.js +77 -0
  261. package/dist/engines/pi/agent-session-factory.d.ts +50 -69
  262. package/dist/engines/pi/agent-session-factory.js +249 -146
  263. package/dist/engines/pi/auth.d.ts +15 -11
  264. package/dist/engines/pi/auth.js +96 -99
  265. package/dist/engines/pi/chat.d.ts +1 -5
  266. package/dist/engines/pi/chat.js +2 -14
  267. package/dist/engines/pi/config.d.ts +53 -51
  268. package/dist/engines/pi/config.js +83 -92
  269. package/dist/engines/pi/create.d.ts +60 -75
  270. package/dist/engines/pi/create.js +108 -141
  271. package/dist/engines/pi/definition.d.ts +14 -58
  272. package/dist/engines/pi/definition.js +21 -85
  273. package/dist/engines/pi/invoke-session.d.ts +5 -25
  274. package/dist/engines/pi/invoke-session.js +233 -244
  275. package/dist/engines/pi/login.d.ts +95 -35
  276. package/dist/engines/pi/login.js +259 -107
  277. package/dist/engines/pi/machine.d.ts +31 -0
  278. package/dist/engines/pi/machine.js +106 -0
  279. package/dist/engines/pi/models.d.ts +70 -89
  280. package/dist/engines/pi/models.js +188 -81
  281. package/dist/engines/pi/open.d.ts +65 -53
  282. package/dist/engines/pi/open.js +129 -69
  283. package/dist/engines/pi/report.d.ts +3 -12
  284. package/dist/engines/pi/report.js +2 -16
  285. package/dist/engines/pi/search-tools.d.ts +1 -9
  286. package/dist/engines/pi/search-tools.js +26 -57
  287. package/dist/engines/pi/service.d.ts +2 -16
  288. package/dist/engines/pi/service.js +2 -16
  289. package/dist/engines/pi/session-builder.d.ts +5 -9
  290. package/dist/engines/pi/session-builder.js +44 -82
  291. package/dist/engines/pi/session-control.js +286 -269
  292. package/dist/engines/pi/session-effects.d.ts +14 -0
  293. package/dist/engines/pi/session-effects.js +30 -0
  294. package/dist/engines/pi/session-inheritance.d.ts +9 -18
  295. package/dist/engines/pi/session-inheritance.js +103 -160
  296. package/dist/engines/pi/session-markers.d.ts +31 -30
  297. package/dist/engines/pi/session-markers.js +26 -25
  298. package/dist/engines/pi/session-settings.d.ts +6 -22
  299. package/dist/engines/pi/session-settings.js +10 -21
  300. package/dist/engines/pi/session-store.d.ts +26 -92
  301. package/dist/engines/pi/session-store.js +96 -310
  302. package/dist/engines/pi/tool-context.d.ts +17 -45
  303. package/dist/engines/pi/tool-context.js +7 -26
  304. package/dist/engines/pi/tool.d.ts +45 -36
  305. package/dist/engines/pi/tool.js +43 -57
  306. package/dist/engines/pi/turn-kit.d.ts +14 -52
  307. package/dist/engines/pi/turn-kit.js +18 -67
  308. package/dist/engines/pi/wake-tool.d.ts +2 -10
  309. package/dist/engines/pi/wake-tool.js +7 -27
  310. package/dist/env.d.ts +27 -28
  311. package/dist/env.js +45 -50
  312. package/dist/index.js +0 -2
  313. package/dist/lark.d.ts +1 -2
  314. package/dist/lark.js +1 -2
  315. package/dist/loader.d.ts +14 -55
  316. package/dist/loader.js +24 -43
  317. package/dist/log.d.ts +2 -12
  318. package/dist/log.js +3 -24
  319. package/dist/node.d.ts +2 -15
  320. package/dist/node.js +2 -18
  321. package/dist/observe.d.ts +2 -8
  322. package/dist/open-url.d.ts +1 -1
  323. package/dist/open-url.js +1 -1
  324. package/dist/paths.d.ts +70 -134
  325. package/dist/paths.js +165 -232
  326. package/dist/pi.d.ts +4 -6
  327. package/dist/pi.js +3 -3
  328. package/dist/proxy.d.ts +16 -6
  329. package/dist/proxy.js +52 -16
  330. package/dist/runtime.d.ts +18 -12
  331. package/dist/runtime.js +18 -11
  332. package/dist/scaffold/add-channel.d.ts +14 -31
  333. package/dist/scaffold/add-channel.js +48 -112
  334. package/dist/scaffold/init.d.ts +9 -31
  335. package/dist/scaffold/init.js +45 -138
  336. package/dist/scaffold/templates/env.example +6 -4
  337. package/dist/scaffold/templates/fastagent.config.ts +36 -0
  338. package/dist/scaffold/templates.d.ts +3 -3
  339. package/dist/scaffold/templates.js +5 -8
  340. package/dist/scaffold/vendor-skill.d.ts +2 -5
  341. package/dist/scaffold/vendor-skill.js +19 -29
  342. package/dist/schedule/cron.d.ts +9 -5
  343. package/dist/schedule/cron.js +9 -11
  344. package/dist/schedule/discover.d.ts +12 -12
  345. package/dist/schedule/discover.js +43 -34
  346. package/dist/schedule/routine.d.ts +43 -0
  347. package/dist/schedule/routine.js +32 -0
  348. package/dist/schedule/run.d.ts +55 -0
  349. package/dist/schedule/run.js +146 -0
  350. package/dist/schedule/scheduler.d.ts +44 -55
  351. package/dist/schedule/scheduler.js +291 -265
  352. package/dist/schedule/state.d.ts +105 -10
  353. package/dist/schedule/state.js +293 -19
  354. package/dist/schedule/wake-alarm.d.ts +10 -23
  355. package/dist/schedule/wake-alarm.js +91 -121
  356. package/dist/schedule/wakeups.d.ts +23 -39
  357. package/dist/schedule/wakeups.js +33 -75
  358. package/dist/secrets-gate.d.ts +32 -0
  359. package/dist/secrets-gate.js +33 -0
  360. package/dist/service.d.ts +137 -98
  361. package/dist/service.js +250 -252
  362. package/dist/session-remote.d.ts +33 -44
  363. package/dist/session-remote.js +203 -192
  364. package/dist/session.d.ts +166 -229
  365. package/dist/session.js +33 -55
  366. package/dist/tunnel.d.ts +8 -36
  367. package/dist/tunnel.js +24 -65
  368. package/dist/version.d.ts +2 -2
  369. package/dist/version.js +2 -2
  370. package/package.json +10 -16
  371. package/dist/channels/agentcore-state.d.ts +0 -77
  372. package/dist/channels/agentcore-state.js +0 -276
  373. package/dist/channels/github/github.d.ts +0 -31
  374. package/dist/channels/github/github.js +0 -89
  375. package/dist/channels/github/scaffold/channel.ts +0 -25
  376. package/dist/cli/commands/attach.d.ts +0 -108
  377. package/dist/cli/commands/attach.js +0 -637
  378. package/dist/cli/commands/fire.d.ts +0 -7
  379. package/dist/cli/commands/fire.js +0 -38
  380. package/dist/cli/commands/schedule.d.ts +0 -12
  381. package/dist/cli/commands/schedule.js +0 -87
  382. package/dist/github.d.ts +0 -3
  383. package/dist/github.js +0 -2
  384. package/dist/scaffold/templates/fastagent.config.mjs +0 -23
  385. package/dist/schedule/audit.d.ts +0 -18
  386. package/dist/schedule/audit.js +0 -57
  387. package/dist/schedule/schedule.d.ts +0 -35
  388. package/dist/schedule/schedule.js +0 -23
@@ -1,28 +1,101 @@
1
1
  /**
2
- * `fastagent login`: authenticate a MODEL PROVIDER into the resolved auth file (project-level
3
- * `<root>/.secrets/auth.json` by default, or `--auth-path`/`FASTAGENT_AUTH_PATH`) via the same
4
- * {@link fastagentCredentialStore} the runtime uses (one writer, one lock/corruption semantics).
2
+ * Signing in to a MODEL PROVIDER, for any front end: {@link login} runs one provider's flow over pi-ai's own
3
+ * `AuthInteraction` and persists the credential through the same {@link fastagentCredentialStore} the runtime reads
4
+ * (one writer, one lock/corruption semantics); {@link loginOptions} is the list a picker offers. `fastagent login` is
5
+ * one front end ({@link loginFlow}: terminal menus, then {@link login}); a GUI client is another.
6
+ */
7
+ import { type AuthInteraction, type Provider } from "@earendil-works/pi-ai";
8
+ export type LoginMethod = "oauth" | "api_key";
9
+ /** The user backed out of a prompt/menu, or the flow was aborted — a decision, not a failure. */
10
+ export declare class LoginCancelled extends Error {
11
+ }
12
+ /** One provider sign-in a picker can offer. */
13
+ export interface LoginOption {
14
+ provider: string;
15
+ method: LoginMethod;
16
+ /** The method's own name, e.g. "Anthropic (Claude Pro/Max)" or "Anthropic API key". */
17
+ label: string;
18
+ /** An OAuth login backed by a provider subscription. */
19
+ subscription: boolean;
20
+ /**
21
+ * What the file holds for this provider now. It keeps ONE credential per provider, so signing in with the other
22
+ * method replaces it — worth a warning in a client.
23
+ */
24
+ stored?: LoginMethod;
25
+ }
26
+ /**
27
+ * Every interactive sign-in of pi's built-in providers, with what `authPath` already holds for each. A provider whose
28
+ * key can only come from its env var offers none and is left out.
29
+ */
30
+ export declare function loginOptions(authPath: string): Promise<LoginOption[]>;
31
+ /** {@link loginOptions} over pi's built-ins plus `providers` (a same id replaces one). Not public: a test seam. */
32
+ export declare function loginOptionsOver(authPath: string, providers?: readonly Provider[]): Promise<LoginOption[]>;
33
+ export interface LoginRequest {
34
+ provider: string;
35
+ method: LoginMethod;
36
+ /** The credentials file the sign-in is written to. */
37
+ authPath: string;
38
+ /** pi-ai's own interaction: prompts (`text`, `secret`, `select`, `manual_code`) and events (`auth_url`, …). */
39
+ interaction: AuthInteraction;
40
+ }
41
+ export interface LoginResult {
42
+ provider: string;
43
+ method: LoginMethod;
44
+ /** An API key checked with one minimal request: accepted, or not decidable (kept). OAuth needs no check. */
45
+ verified: "ok" | "unknown" | "n/a";
46
+ }
47
+ /**
48
+ * Sign in to one of pi's built-in providers and persist the credential to `authPath`.
5
49
  *
6
- * Flow (pi-ai's unified `ProviderAuth` API): pick a method (OAuth or API key), then a provider that
7
- * offers it, then run `provider.auth.{oauth|apiKey}.login(callbacks)` and persist with `store.modify`
8
- * (which refuses to clobber a corrupt file). The terminal IO and providers are injected, so the
9
- * routing is testable without real stdin or a real auth round-trip.
50
+ * The file is checked FIRST (a no-op write runs the refuse-corrupt and writability checks), so a flow never runs
51
+ * toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
52
+ * default model for the provider (its first model when pi names none), at pi's built-in endpoint, BEFORE it is
53
+ * written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
54
+ * the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
55
+ * again (the provider and method were not the mistake). Progress and outcome go out through `interaction.notify`.
56
+ * A provider an agent's models.json points elsewhere is not this call's: store that key with `fastagentCredentialStore`.
57
+ *
58
+ * Aborting `interaction.signal` at any point, the verification included, rejects with {@link LoginCancelled} and
59
+ * writes nothing. Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
60
+ * beating a `manual_code` prompt), and when the flow settles with a prompt still pending.
61
+ */
62
+ export declare function login(request: LoginRequest): Promise<LoginResult>;
63
+ /** Not public: what the CLI and the tests add to {@link login}. */
64
+ export interface LoginInternals {
65
+ /** Providers added to pi's built-ins (a same id replaces one): a test seam. */
66
+ providers?: readonly Provider[];
67
+ /**
68
+ * The agent the key is for. Verification then runs on THAT agent's registry (its models.json and the machine's over
69
+ * the built-ins), so the check reaches the endpoint its turns will: a models.json can point a built-in provider at a
70
+ * gateway, where the provider's own endpoint would refuse the gateway's key and loop the person through re-entry.
71
+ */
72
+ agentDir?: string;
73
+ /**
74
+ * A model spec to verify an entered key with, when the registry has it as one of this provider's models: the
75
+ * first-run picker's choice, the request the agent is about to make. Otherwise pi's default model for the provider
76
+ * is used, then its first. On one registry, any of a provider's models answers the 401 question.
77
+ */
78
+ verifyWith?: string;
79
+ }
80
+ /** {@link login}, plus {@link LoginInternals}. */
81
+ export declare function loginOver(request: LoginRequest, internals?: LoginInternals): Promise<LoginResult>;
82
+ /**
83
+ * pi's default model per provider (`defaultModelPerProvider` in pi-coding-agent's model-resolver): the general-purpose
84
+ * model pi itself starts on, so the likeliest to answer a minimal request. COPIED, because pi does not export it;
85
+ * `test/login.test.ts` compares this against pi's own table, so a pi release that changes a default fails there.
10
86
  */
11
- import type { CredentialStore, Provider } from "@earendil-works/pi-ai";
87
+ export declare const PI_DEFAULT_MODELS: Readonly<Record<string, string>>;
12
88
  /** One picker option: a stable `value`, a human `label`, and an optional `hint` (e.g. configured status). */
13
89
  export interface IoOption {
14
90
  value: string;
15
91
  label: string;
16
92
  hint?: string;
17
93
  }
18
- /**
19
- * Terminal interaction, injectable for tests (no real stdin/stdout or browser). The CLI implements it
20
- * with `@clack/prompts` (a searchable list for many options, a hidden prompt for keys).
21
- */
94
+ /** Terminal interaction, injectable for tests (no real stdin/stdout or browser). */
22
95
  export interface LoginIO {
23
- /** Single-choice picker. Returns the chosen `value`, or undefined on cancel. */
96
+ /** Single-choice picker. */
24
97
  select(message: string, options: IoOption[]): Promise<string | undefined>;
25
- /** Free-text or hidden input. Returns the entered string, or undefined on cancel/abort. */
98
+ /** Free-text or hidden input. */
26
99
  prompt(message: string, opts?: {
27
100
  hidden?: boolean;
28
101
  signal?: AbortSignal;
@@ -32,31 +105,18 @@ export interface LoginIO {
32
105
  /** Best-effort open a URL in the browser (printed regardless). */
33
106
  openUrl(url: string): void;
34
107
  }
35
- export type LoginMethod = "oauth" | "api_key";
36
- /** The user backed out of a prompt/menu — a decision, not a failure. Callers (the first-run picker,
37
- * the login command) match on this to report neutrally instead of as a login "failure". */
38
- export declare class LoginCancelled extends Error {
39
- }
40
- /** What `loginFlow` can offer a provider interactively: an OAuth flow, an API-key ENTRY prompt, or
41
- * nothing ("none" — the key must come from the provider's env var). */
42
- export type InteractiveLoginKind = LoginMethod | "none";
43
- /** The provider's {@link InteractiveLoginKind}. OAuth wins when both exist (methodForProvider still
44
- * asks at login time); the first-run picker annotates with this so the hint predicts what picking
45
- * actually does — a browser login ("oauth"), a key prompt ("api_key"), or neither. */
46
- export declare function interactiveLoginKind(p: Provider): InteractiveLoginKind;
47
- export interface LoginResult {
48
- provider: string;
49
- method: LoginMethod;
50
- }
51
108
  /**
52
- * Resolve method + provider (asking only what is not given), run the login flow, and persist. A no-op
53
- * `modify` up front runs the refuse-corrupt check BEFORE the flow, so a known-bad file fails fast.
109
+ * `fastagent login`: ask only what was not given (method, then provider, from {@link loginOptions} with each one's
110
+ * stored credential), then {@link login}.
54
111
  */
55
- export declare function loginFlow(io: LoginIO, options?: {
112
+ export declare function loginFlow(io: LoginIO, options: {
113
+ authPath: string;
56
114
  provider?: string;
57
115
  method?: LoginMethod;
58
- authPath?: string;
59
- store?: CredentialStore;
60
116
  providers?: Provider[];
61
117
  signal?: AbortSignal;
118
+ /** {@link LoginInternals.verifyWith}. */
119
+ verifyWith?: string;
120
+ /** {@link LoginInternals.agentDir}. */
121
+ agentDir?: string;
62
122
  }): Promise<LoginResult>;
@@ -1,16 +1,44 @@
1
- import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
2
- import { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./auth.js";
3
- /** The user backed out of a prompt/menu — a decision, not a failure. Callers (the first-run picker,
4
- * the login command) match on this to report neutrally instead of as a login "failure". */
1
+ /**
2
+ * Signing in to a MODEL PROVIDER, for any front end: {@link login} runs one provider's flow over pi-ai's own
3
+ * `AuthInteraction` and persists the credential through the same {@link fastagentCredentialStore} the runtime reads
4
+ * (one writer, one lock/corruption semantics); {@link loginOptions} is the list a picker offers. `fastagent login` is
5
+ * one front end ({@link loginFlow}: terminal menus, then {@link login}); a GUI client is another.
6
+ */
7
+ import { InMemoryCredentialStore, } from "@earendil-works/pi-ai";
8
+ import { fastagentCredentialStore } from "./auth.js";
9
+ import { createPiModelRuntime, interactiveAuth, loginProviders, piModelsOver, probeApiKey } from "./models.js";
10
+ /** The user backed out of a prompt/menu, or the flow was aborted — a decision, not a failure. */
5
11
  export class LoginCancelled extends Error {
6
12
  }
7
- /** The provider's {@link InteractiveLoginKind}. OAuth wins when both exist (methodForProvider still
8
- * asks at login time); the first-run picker annotates with this so the hint predicts what picking
9
- * actually does — a browser login ("oauth"), a key prompt ("api_key"), or neither. */
10
- export function interactiveLoginKind(p) {
11
- if (p.auth.oauth)
12
- return "oauth";
13
- return p.auth.apiKey?.login ? "api_key" : "none";
13
+ /**
14
+ * Every interactive sign-in of pi's built-in providers, with what `authPath` already holds for each. A provider whose
15
+ * key can only come from its env var offers none and is left out.
16
+ */
17
+ export function loginOptions(authPath) {
18
+ return loginOptionsOver(authPath);
19
+ }
20
+ /** {@link loginOptions} over pi's built-ins plus `providers` (a same id replaces one). Not public: a test seam. */
21
+ export async function loginOptionsOver(authPath, providers) {
22
+ // ONE read of the file: `list` is metadata only, and reading per provider would repeat a corrupt file's warning for
23
+ // every provider.
24
+ const held = new Map((await fastagentCredentialStore(authPath).list()).map((info) => [info.providerId, info.type]));
25
+ const offered = [];
26
+ for (const provider of loginProviders(providers)) {
27
+ const methods = ["oauth", "api_key"].filter((method) => interactiveAuth(provider, method));
28
+ if (methods.length === 0)
29
+ continue;
30
+ const stored = held.get(provider.id);
31
+ for (const method of methods) {
32
+ offered.push({
33
+ provider: provider.id,
34
+ method,
35
+ label: interactiveAuth(provider, method)?.name ?? provider.name,
36
+ subscription: method === "oauth" && provider.auth.oauth?.isSubscription === true,
37
+ ...(stored ? { stored } : {}),
38
+ });
39
+ }
40
+ }
41
+ return offered;
14
42
  }
15
43
  /** Combine present abort signals into one (no-op when none/one). */
16
44
  function anySignal(...signals) {
@@ -18,37 +46,193 @@ function anySignal(...signals) {
18
46
  return present.length === 0 ? undefined : present.length === 1 ? present[0] : AbortSignal.any(present);
19
47
  }
20
48
  /**
21
- * Map pi-ai's `ProviderAuthInteraction` onto the injected {@link LoginIO}. `doneSignal` fires when
22
- * the flow resolves, cancelling a prompt the provider left pending (a manual-code paste racing a
23
- * callback server it just won) so the one-shot CLI exits instead of hanging on stdin.
49
+ * Sign in to one of pi's built-in providers and persist the credential to `authPath`.
24
50
  *
25
- * The signal is REQUIRED by that type (pi normalizes it before calling a provider), so a caller that
26
- * passes none gets one that never fires — the same "no cancellation" the optional field meant.
51
+ * The file is checked FIRST (a no-op write runs the refuse-corrupt and writability checks), so a flow never runs
52
+ * toward a credential that could not be saved. An entered API key is verified with one minimal request to pi's
53
+ * default model for the provider (its first model when pi names none), at pi's built-in endpoint, BEFORE it is
54
+ * written. Credentials are provider-scoped, so any of the provider's models answers the question the check asks (does
55
+ * the provider refuse the key, HTTP 401); a key the provider rejects is never stored, and the provider's key flow runs
56
+ * again (the provider and method were not the mistake). Progress and outcome go out through `interaction.notify`.
57
+ * A provider an agent's models.json points elsewhere is not this call's: store that key with `fastagentCredentialStore`.
58
+ *
59
+ * Aborting `interaction.signal` at any point, the verification included, rejects with {@link LoginCancelled} and
60
+ * writes nothing. Every prompt carries a signal that aborts on it, on the prompt's own signal (a callback server
61
+ * beating a `manual_code` prompt), and when the flow settles with a prompt still pending.
27
62
  */
28
- function authCallbacks(io, userSignal, doneSignal) {
63
+ export function login(request) {
64
+ return loginOver(request);
65
+ }
66
+ /** {@link login}, plus {@link LoginInternals}. */
67
+ export async function loginOver(request, internals = {}) {
68
+ const { providers, verifyWith, agentDir } = internals;
69
+ const { method, authPath, interaction } = request;
70
+ const provider = loginProviders(providers).find((p) => p.id === request.provider);
71
+ if (!provider)
72
+ throw new Error(`unknown provider "${request.provider}"`);
73
+ const auth = interactiveAuth(provider, method);
74
+ if (!auth?.login)
75
+ throw new Error(`provider "${provider.id}" has no interactive ${method} login`);
76
+ // The key under test lives here, not in the file, until it passes.
77
+ const trial = new InMemoryCredentialStore();
78
+ // Only a key needs the registry, and building an agent's fails on a broken models.json: an OAuth sign-in must not.
79
+ // For a key it is built BEFORE the flow, so that failure comes before the person types anything.
80
+ const models = method !== "api_key"
81
+ ? undefined
82
+ : agentDir
83
+ ? await createPiModelRuntime({
84
+ agentDir,
85
+ credentials: trial,
86
+ ...(providers ? { providers: [...providers] } : {}),
87
+ })
88
+ : piModelsOver(trial, providers);
89
+ const store = fastagentCredentialStore(authPath);
90
+ await store.modify(provider.id, async () => undefined);
91
+ const signal = interaction.signal ?? new AbortController().signal;
92
+ const notify = (event) => interaction.notify(event);
93
+ for (;;) {
94
+ const credential = await runFlow(auth.login.bind(auth), interaction, signal);
95
+ let verified = "n/a";
96
+ if (models) {
97
+ await trial.modify(provider.id, async () => credential);
98
+ const verdict = await verifyApiKey(models, provider.id, verifyWith, notify, signal);
99
+ if (signal.aborted)
100
+ throw new LoginCancelled("cancelled");
101
+ if (verdict === "rejected")
102
+ continue;
103
+ verified = verdict;
104
+ }
105
+ await store.modify(provider.id, async () => credential);
106
+ return { provider: provider.id, method, verified };
107
+ }
108
+ }
109
+ async function runFlow(flow, interaction, signal) {
110
+ if (signal.aborted)
111
+ throw new LoginCancelled("cancelled");
112
+ const done = new AbortController();
113
+ let credential;
114
+ try {
115
+ credential = await flow({
116
+ signal,
117
+ prompt: (prompt) => interaction.prompt({ ...prompt, signal: anySignal(prompt.signal, signal, done.signal) }),
118
+ notify: (event) => interaction.notify(event),
119
+ });
120
+ }
121
+ catch (error) {
122
+ // Whatever the provider made of the abort (its own error, a rejected prompt), it was the caller's decision.
123
+ if (signal.aborted)
124
+ throw new LoginCancelled("cancelled");
125
+ throw error;
126
+ }
127
+ finally {
128
+ done.abort();
129
+ }
130
+ // A provider that ignored the abort and finished anyway: the caller still cancelled.
131
+ if (signal.aborted)
132
+ throw new LoginCancelled("cancelled");
133
+ return credential;
134
+ }
135
+ /**
136
+ * pi's default model per provider (`defaultModelPerProvider` in pi-coding-agent's model-resolver): the general-purpose
137
+ * model pi itself starts on, so the likeliest to answer a minimal request. COPIED, because pi does not export it;
138
+ * `test/login.test.ts` compares this against pi's own table, so a pi release that changes a default fails there.
139
+ */
140
+ export const PI_DEFAULT_MODELS = {
141
+ "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
142
+ "ant-ling": "Ring-2.6-1T",
143
+ anthropic: "claude-opus-4-8",
144
+ openai: "gpt-5.5",
145
+ "azure-openai-responses": "gpt-5.4",
146
+ "openai-codex": "gpt-5.5",
147
+ radius: "balanced",
148
+ nvidia: "nvidia/nemotron-3-super-120b-a12b",
149
+ deepseek: "deepseek-v4-pro",
150
+ google: "gemini-3.1-pro-preview",
151
+ "google-vertex": "gemini-3.1-pro-preview",
152
+ "github-copilot": "gpt-5.4",
153
+ openrouter: "moonshotai/kimi-k2.6",
154
+ "vercel-ai-gateway": "zai/glm-5.1",
155
+ xai: "grok-4.7",
156
+ groq: "openai/gpt-oss-120b",
157
+ cerebras: "gpt-oss-120b",
158
+ zai: "glm-5.3",
159
+ "zai-coding-cn": "glm-5.3",
160
+ mistral: "devstral-medium-latest",
161
+ minimax: "MiniMax-M2.7",
162
+ "minimax-cn": "MiniMax-M2.7",
163
+ moonshotai: "kimi-k2.6",
164
+ "moonshotai-cn": "kimi-k2.6",
165
+ huggingface: "moonshotai/Kimi-K2.6",
166
+ fireworks: "accounts/fireworks/models/kimi-k2p6",
167
+ together: "moonshotai/Kimi-K2.6",
168
+ baseten: "zai-org/GLM-5.2",
169
+ opencode: "kimi-k2.6",
170
+ "opencode-go": "kimi-k2.6",
171
+ "kimi-coding": "kimi-for-coding",
172
+ meta: "muse-spark-1.3",
173
+ "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
174
+ "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
175
+ "qwen-token-plan": "qwen3.7-max",
176
+ "qwen-token-plan-cn": "qwen3.7-max",
177
+ "qwen-token-plan-individual": "qwen3.8-max",
178
+ xiaomi: "mimo-v2.5-pro",
179
+ "xiaomi-token-plan-cn": "mimo-v2.5-pro",
180
+ "xiaomi-token-plan-ams": "mimo-v2.5-pro",
181
+ "xiaomi-token-plan-sgp": "mimo-v2.5-pro",
182
+ };
183
+ /** One minimal request with the key under test. */
184
+ async function verifyApiKey(models, providerId, verifyWith, notify, signal) {
185
+ const chosen = verifyWith?.startsWith(`${providerId}/`)
186
+ ? models.getModel(providerId, verifyWith.slice(providerId.length + 1))
187
+ : undefined;
188
+ const piDefault = PI_DEFAULT_MODELS[providerId];
189
+ // The provider's first model is the last resort only: catalogs are alphabetical, and a first model can be a
190
+ // special-purpose one that rejects a minimal request (which still answers the 401 question).
191
+ const model = chosen ??
192
+ (piDefault ? models.getModel(providerId, piDefault) : undefined) ??
193
+ models.getProvider(providerId)?.getModels()[0];
194
+ if (!model) {
195
+ notify({
196
+ type: "info",
197
+ message: `cannot verify the key: provider "${providerId}" lists no models — kept as entered`,
198
+ });
199
+ return "unknown";
200
+ }
201
+ const label = `${model.provider}/${model.id}`;
202
+ notify({ type: "progress", message: `verifying the key with ${label}…` });
203
+ const probe = await probeApiKey(models, model, signal);
204
+ if (signal.aborted)
205
+ return "unknown"; // the caller reports the cancel, not a verdict on the key
206
+ if (probe.state === "ok") {
207
+ notify({ type: "info", message: `key verified — ${label} responded` });
208
+ }
209
+ else if (probe.state === "rejected") {
210
+ notify({
211
+ type: "info",
212
+ message: `${providerId} rejected the API key (HTTP 401): ${probe.message} — enter it again (or cancel)`,
213
+ });
214
+ }
215
+ else {
216
+ notify({
217
+ type: "info",
218
+ message: `could not verify the key with ${label}: ${probe.message} — kept; invokes surface the provider's error`,
219
+ });
220
+ }
221
+ return probe.state;
222
+ }
223
+ /** {@link LoginIO} as pi-ai's `AuthInteraction`: what {@link login} drives for the terminal. */
224
+ function terminalInteraction(io, signal) {
29
225
  return {
30
- signal: userSignal ?? new AbortController().signal,
226
+ ...(signal ? { signal } : {}),
31
227
  prompt: async (p) => {
32
- if (p.type === "select") {
33
- // This branch is UNCANCELLABLE: `LoginIO.select` takes no signal, so all three the line
34
- // below composes — the provider's own, the caller's `loginFlow({ signal })`, and the
35
- // pending-prompt backstop — are dropped here.
36
- //
37
- // It costs nothing against pi 0.84's providers, where every `select` is the FIRST call of
38
- // `login()` (bedrock, vertex, openai-codex, radius — all of them asking which login method
39
- // to use). Nothing is racing it: no callback server is up yet, and `doneSignal` fires when
40
- // `auth.login()` RETURNS, which it cannot do while blocked on this await.
41
- //
42
- // That is a property of the providers, not a guarantee, and nothing here holds it. A
43
- // provider that ever issues a `select` after starting its callback server shows up as the
44
- // CLI parked on stdin — widen `LoginIO.select` with a signal then.
45
- const v = await io.select(p.message, p.options.map((o) => ({ value: o.id, label: o.label, hint: o.description })));
46
- if (v === undefined)
47
- throw new LoginCancelled("cancelled");
48
- return v;
49
- }
50
- const signal = anySignal(p.signal, userSignal, doneSignal);
51
- const v = await io.prompt(p.message, { hidden: p.type === "secret", signal });
228
+ // A select is UNCANCELLABLE here: `LoginIO.select` takes no signal.
229
+ const v = p.type === "select"
230
+ ? await io.select(p.message, p.options.map((o) => ({
231
+ value: o.id,
232
+ label: o.label,
233
+ ...(o.description ? { hint: o.description } : {}),
234
+ })))
235
+ : await io.prompt(p.message, { hidden: p.type === "secret", ...(p.signal ? { signal: p.signal } : {}) });
52
236
  if (v === undefined)
53
237
  throw new LoginCancelled("cancelled");
54
238
  return v;
@@ -70,10 +254,6 @@ function authCallbacks(io, userSignal, doneSignal) {
70
254
  },
71
255
  };
72
256
  }
73
- /** Providers offering the given method as an INTERACTIVE login (so `login` can actually run it). */
74
- function candidatesFor(providers, method) {
75
- return providers.filter((p) => (method === "oauth" ? p.auth.oauth : p.auth.apiKey?.login));
76
- }
77
257
  async function selectMethod(io) {
78
258
  const v = await io.select("Authentication method", [
79
259
  { value: "oauth", label: "Use a subscription (OAuth)" },
@@ -83,74 +263,46 @@ async function selectMethod(io) {
83
263
  throw new LoginCancelled("no authentication method selected");
84
264
  return v;
85
265
  }
86
- /** Given a provider arg, pick the method it supports (asking only when it offers both). */
87
- async function methodForProvider(io, p) {
88
- const hasOauth = !!p.auth.oauth;
89
- const hasKeyLogin = !!p.auth.apiKey?.login;
90
- if (hasOauth && hasKeyLogin)
91
- return selectMethod(io);
92
- if (hasOauth)
93
- return "oauth";
94
- if (hasKeyLogin)
95
- return "api_key";
96
- throw new Error(`provider "${p.id}" has no interactive login — set its API key via the provider's env var`);
97
- }
98
- /** List providers for the method with their configured status, and let the user pick one. */
99
- async function selectProvider(io, providers, method, store) {
100
- const candidates = candidatesFor(providers, method);
101
- if (candidates.length === 0)
102
- throw new Error(`no provider supports ${method} login`);
103
- const options = await Promise.all(candidates.map(async (p) => {
104
- const cred = await store.read(p.id);
105
- const auth = method === "oauth" ? p.auth.oauth : p.auth.apiKey;
106
- return { value: p.id, label: auth?.name ?? p.name, hint: cred ? `configured (${cred.type})` : undefined };
107
- }));
108
- const id = await io.select("Select a provider", options);
109
- // Answers the PROVIDER, not its id: the caller needs the object, and resolving it here means the
110
- // one place that can fail to is the one that just offered the list. Cancel and an id that was
111
- // never offered are the same answer — nothing was chosen — and `find` gives both, since a
112
- // provider id is always a string and `undefined` matches none of them.
113
- const chosen = candidates.find((p) => p.id === id);
114
- if (!chosen)
115
- throw new LoginCancelled("no provider selected");
116
- return chosen;
117
- }
118
266
  /**
119
- * Resolve method + provider (asking only what is not given), run the login flow, and persist. A no-op
120
- * `modify` up front runs the refuse-corrupt check BEFORE the flow, so a known-bad file fails fast.
267
+ * `fastagent login`: ask only what was not given (method, then provider, from {@link loginOptions} with each one's
268
+ * stored credential), then {@link login}.
121
269
  */
122
- export async function loginFlow(io, options = {}) {
123
- const store = options.store ?? fastagentCredentialStore(options.authPath ?? GLOBAL_AUTH_PATH);
124
- const providers = options.providers ?? builtinProviders();
125
- let method;
126
- let provider;
127
- if (options.provider) {
128
- const named = providers.find((x) => x.id === options.provider);
129
- if (!named)
130
- throw new Error(`unknown provider "${options.provider}"`);
131
- provider = named;
132
- method = options.method ?? (await methodForProvider(io, provider));
270
+ export async function loginFlow(io, options) {
271
+ const offered = await loginOptionsOver(options.authPath, options.providers);
272
+ let provider = options.provider;
273
+ let method = options.method;
274
+ if (provider) {
275
+ const methods = offered.filter((o) => o.provider === provider).map((o) => o.method);
276
+ if (methods.length === 0) {
277
+ const known = loginProviders(options.providers).some((p) => p.id === provider);
278
+ throw new Error(known
279
+ ? `provider "${provider}" has no interactive login — set its API key via the provider's env var`
280
+ : `unknown provider "${provider}"`);
281
+ }
282
+ method ??= methods.length > 1 ? await selectMethod(io) : methods[0];
133
283
  }
134
284
  else {
135
- method = options.method ?? (await selectMethod(io));
136
- provider = await selectProvider(io, providers, method, store);
137
- }
138
- // Preflight: a no-op modify runs the refuse-corrupt / writability check BEFORE the flow.
139
- await store.modify(provider.id, async () => undefined);
140
- // Reachable even though both branches above resolved a provider: an explicit `method` bypasses
141
- // methodForProvider, so `{ provider: "openai-codex", method: "api_key" }` arrives here.
142
- const auth = method === "oauth" ? provider.auth.oauth : provider.auth.apiKey;
143
- if (!auth?.login)
144
- throw new Error(`provider "${provider.id}" has no ${method} login`);
145
- // `done` cancels any prompt left pending when login resolves (manual-code race backstop).
146
- const done = new AbortController();
147
- let credential;
148
- try {
149
- credential = await auth.login(authCallbacks(io, options.signal, done.signal));
150
- }
151
- finally {
152
- done.abort();
285
+ const chosenMethod = method ?? (await selectMethod(io));
286
+ method = chosenMethod;
287
+ const candidates = offered.filter((o) => o.method === chosenMethod);
288
+ if (candidates.length === 0)
289
+ throw new Error(`no provider supports ${chosenMethod} login`);
290
+ provider = await io.select("Select a provider", candidates.map((o) => ({
291
+ value: o.provider,
292
+ label: o.label,
293
+ ...(o.stored ? { hint: `configured (${o.stored})` } : {}),
294
+ })));
295
+ if (!candidates.some((o) => o.provider === provider))
296
+ throw new LoginCancelled("no provider selected");
153
297
  }
154
- await store.modify(provider.id, async () => credential);
155
- return { provider: provider.id, method };
298
+ return loginOver({
299
+ provider: provider,
300
+ method: method,
301
+ authPath: options.authPath,
302
+ interaction: terminalInteraction(io, options.signal),
303
+ }, {
304
+ ...(options.providers ? { providers: options.providers } : {}),
305
+ ...(options.verifyWith ? { verifyWith: options.verifyWith } : {}),
306
+ ...(options.agentDir ? { agentDir: options.agentDir } : {}),
307
+ });
156
308
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * THE MACHINE an agent runs on, as fastagent inherits it: the skills and prompt templates this box provides — found
3
+ * by pi's own Agent Skills discovery, installed pi packages included — and pi's engine settings.
4
+ *
5
+ * An agent inherits its machine the way it already inherits the `PATH`: `bash` runs whatever is installed, and a
6
+ * skill in `~/.pi/agent/skills` is available the same way. Deploying ships the project scope — the workspace — and
7
+ * nothing here is compared against a deployment, the same way nobody is told their local `ffmpeg` is not in the image.
8
+ *
9
+ * READ ONCE per process, like the environment it is. The definition is what stays live (`dev` re-reads it per turn,
10
+ * because it is what an author edits); a skill installed after boot arrives on the next start.
11
+ */
12
+ import { DefaultResourceLoader, SettingsManager } from "@earendil-works/pi-coding-agent";
13
+ /** pi's own shapes, taken from its loader rather than re-declared. */
14
+ export type MachineSkill = ReturnType<DefaultResourceLoader["getSkills"]>["skills"][number];
15
+ type MachinePrompt = ReturnType<DefaultResourceLoader["getPrompts"]>["prompts"][number];
16
+ /** What this box lends an agent. */
17
+ export interface Machine {
18
+ skills: MachineSkill[];
19
+ prompts: MachinePrompt[];
20
+ /** pi's engine settings (retry, compaction, cache warming, …) as read at boot — a fresh manager per caller. */
21
+ settingsManager(): SettingsManager;
22
+ }
23
+ /** This process's one read of the machine, shared by the run plane and `commands()` so the two cannot disagree. */
24
+ export declare function readMachine(workspace: string): Promise<Machine>;
25
+ /** The definition's names win: vendoring a skill into `skills/` is how an author overrides the machine's. */
26
+ export declare function withMachine<Own extends {
27
+ name: string;
28
+ }, Lent extends {
29
+ name: string;
30
+ }>(own: readonly Own[], lent: readonly Lent[]): (Own | Lent)[];
31
+ export {};