@cursor/bdk 0.2.3 → 0.2.4
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.
- package/dist/bin/agent-serve.js +9 -1
- package/dist/channels/slack/attachments.d.ts +2 -1
- package/dist/channels/slack/attachments.d.ts.map +1 -1
- package/dist/channels/slack/attachments.js +19 -9
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +26 -24
- package/dist/channels/slack/thread-context.d.ts +21 -11
- package/dist/channels/slack/thread-context.d.ts.map +1 -1
- package/dist/channels/slack/thread-context.js +75 -27
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.BOIHhrKi.js → app.4kQ8Db_q.js} +4 -4
- package/dist/docs/assets/chunks/@localSearchIndexroot.CvuAJ0My.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.D49zVZqM.js → VPLocalSearchBox.DY1RwB8Q.js} +1 -1
- package/dist/docs/assets/chunks/{arc.hmjb-sc4.js → arc.CxE_fSPK.js} +1 -1
- package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.CxEI1KqM.js → architectureDiagram-Q4EWVU46.CPX426ui.js} +1 -1
- package/dist/docs/assets/chunks/{baseUniq.DHHvBWoV.js → baseUniq.CB52JrKG.js} +1 -1
- package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.DnXW-3bu.js → blockDiagram-DXYQGD6D.J0cNaQCL.js} +1 -1
- package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.e-47O2jM.js → c4Diagram-AHTNJAMY.DJn3vEGT.js} +1 -1
- package/dist/docs/assets/chunks/channel.BoNycAoN.js +1 -0
- package/dist/docs/assets/chunks/{chunk-4BX2VUAB.3ry4pYXB.js → chunk-4BX2VUAB.Dao618F6.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-4TB4RGXK.DGIZFRx6.js → chunk-4TB4RGXK.QW8Rhjai.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-55IACEB6.DKBnqg9U.js → chunk-55IACEB6.DfRLLKF_.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BIU8Ue5k.js → chunk-EDXVE4YY.CIV2ApKQ.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-FMBD7UC4.DP-TFhjI.js → chunk-FMBD7UC4.BEheSuxp.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-OYMX7WX6.Ux0OYj5v.js → chunk-OYMX7WX6.CUN88ENB.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BzHMwYox.js → chunk-QZHKN3VN.CgqCs8b_.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-YZCP3GAM.D4OGAsSP.js → chunk-YZCP3GAM.iCH0nnTJ.js} +1 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.C4x07Gkj.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.C4x07Gkj.js +1 -0
- package/dist/docs/assets/chunks/clone.Db22cH_-.js +1 -0
- package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.Jq_7BUNx.js → cose-bilkent-S5V4N54A.BLcUxhWE.js} +1 -1
- package/dist/docs/assets/chunks/{dagre-KV5264BT.Di-skR2x.js → dagre-KV5264BT.Ep7LofOm.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DeREHIdH.js → diagram-5BDNPKRD.Bzf4GSQ4.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.C7Kcqxro.js → diagram-G4DWMVQ6.DD6Jh-eM.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-MMDJMWI5.BK3eohyo.js → diagram-MMDJMWI5.oOIl8bWd.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-TYMM5635.C6TyymXT.js → diagram-TYMM5635.DHs85Ade.js} +1 -1
- package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.DYGG-gGx.js → erDiagram-SMLLAGMA.BIb5ghCk.js} +1 -1
- package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.C9nBcSV4.js → flowDiagram-DWJPFMVM.DV4paY9k.js} +1 -1
- package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.wAg_ymsp.js → ganttDiagram-T4ZO3ILL.AyEKA6_v.js} +1 -1
- package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.B87j9Oeg.js → gitGraphDiagram-UUTBAWPF.BltiCDFo.js} +1 -1
- package/dist/docs/assets/chunks/{graph.CuFMj31w.js → graph.DzuEYGdC.js} +1 -1
- package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.P_mLGumP.js → infoDiagram-42DDH7IO.BV2l981J.js} +1 -1
- package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DyuL_MsT.js → ishikawaDiagram-UXIWVN3A.BA4Izf-m.js} +1 -1
- package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CtP09IoJ.js → journeyDiagram-VCZTEJTY.DjaFbFS3.js} +1 -1
- package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BoeSYPZo.js → kanban-definition-6JOO6SKY.CfTFieDs.js} +1 -1
- package/dist/docs/assets/chunks/{layout.B0lMMtNo.js → layout.DzfWqTBV.js} +1 -1
- package/dist/docs/assets/chunks/{linear.HxRBiAFX.js → linear.BO6mpulV.js} +1 -1
- package/dist/docs/assets/chunks/{min.BGlFFHwS.js → min.BGgTadgT.js} +1 -1
- package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.JyLBD9M8.js → mindmap-definition-QFDTVHPH.DPuIXVWY.js} +1 -1
- package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DnSmNZF8.js → pieDiagram-DEJITSTG.BOnl3bBg.js} +1 -1
- package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.CUCnbfCa.js → quadrantDiagram-34T5L4WZ.DNB6Ckdc.js} +1 -1
- package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.py0IVUn5.js → requirementDiagram-MS252O5E.D5-Qo7vb.js} +1 -1
- package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Bkc-HWYp.js → sankeyDiagram-XADWPNL6.8t65cesf.js} +1 -1
- package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.Dqn8Mlv2.js → sequenceDiagram-FGHM5R23.CaG07BdS.js} +1 -1
- package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.JikkoQUU.js → stateDiagram-FHFEXIEX.TKOweTB6.js} +1 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CuqmyQc4.js +1 -0
- package/dist/docs/assets/chunks/{theme.b6HQ6zG_.js → theme.Dx-JhpTB.js} +2 -2
- package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.BvpSXiBj.js → timeline-definition-GMOUNBTQ.fE53zQ2s.js} +1 -1
- package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.CU7MVnZx.js → vennDiagram-DHZGUBPP.DJsZZaJg.js} +1 -1
- package/dist/docs/assets/chunks/{wardley-RL74JXVD.BKwC4ctG.js → wardley-RL74JXVD.844kyrjl.js} +1 -1
- package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.BfaF6Uhe.js → wardleyDiagram-NUSXRM2D.CMZ-Ru_t.js} +1 -1
- package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.Bg5dcpj4.js → xychartDiagram-5P7HB3ND.XXrhBc7a.js} +1 -1
- package/dist/docs/assets/{deployment.md.BJ8i7Q6p.js → deployment.md.UhnJIMr-.js} +2 -2
- package/dist/docs/assets/{deployment.md.BJ8i7Q6p.lean.js → deployment.md.UhnJIMr-.lean.js} +1 -1
- package/dist/docs/assets/{guides_jev.md.BNDm14Gy.js → guides_jev.md.CRcgmjDb.js} +2 -2
- package/dist/docs/assets/{guides_jev.md.BNDm14Gy.lean.js → guides_jev.md.CRcgmjDb.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.DzGI777n.js → reference_agent-config.md.Cj5uBKp4.js} +1 -1
- package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.js → reference_extensions.md.pa2tN-e4.js} +4 -3
- package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.lean.js → reference_extensions.md.pa2tN-e4.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +35 -35
- package/dist/docs/deployment.html +37 -37
- package/dist/docs/deployment.md +4 -1
- package/dist/docs/evals.html +35 -35
- package/dist/docs/guides/agent-to-agent.html +35 -35
- package/dist/docs/guides/bitbucket.html +35 -35
- package/dist/docs/guides/cloud-agents.html +35 -35
- package/dist/docs/guides/convert-automation.html +35 -35
- package/dist/docs/guides/github.html +35 -35
- package/dist/docs/guides/gitlab.html +35 -35
- package/dist/docs/guides/grokbot-agents.html +35 -35
- package/dist/docs/guides/hooks.html +35 -35
- package/dist/docs/guides/improve.html +35 -35
- package/dist/docs/guides/jev.html +37 -37
- package/dist/docs/guides/jev.md +7 -0
- package/dist/docs/guides/mcp-oauth.html +35 -35
- package/dist/docs/guides/opentelemetry.html +35 -35
- package/dist/docs/guides/slack.html +35 -35
- package/dist/docs/guides/webhooks.html +35 -35
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +35 -35
- package/dist/docs/index.html +35 -35
- package/dist/docs/llms-full.txt +25 -2
- package/dist/docs/quickstart.html +35 -35
- package/dist/docs/reference/agent-config.html +37 -37
- package/dist/docs/reference/agent-config.md +1 -1
- package/dist/docs/reference/artifacts.html +35 -35
- package/dist/docs/reference/channels.html +35 -35
- package/dist/docs/reference/cli.html +35 -35
- package/dist/docs/reference/connections.html +35 -35
- package/dist/docs/reference/evals.html +35 -35
- package/dist/docs/reference/extensions.html +39 -38
- package/dist/docs/reference/extensions.md +13 -0
- package/dist/docs/reference/hooks.html +35 -35
- package/dist/docs/reference/http-api.html +35 -35
- package/dist/docs/reference/instructions.html +35 -35
- package/dist/docs/reference/playground.html +35 -35
- package/dist/docs/reference/project-layout.html +35 -35
- package/dist/docs/reference/prompt.html +35 -35
- package/dist/docs/reference/schedules.html +35 -35
- package/dist/docs/reference/sessions.html +35 -35
- package/dist/docs/reference/skills.html +35 -35
- package/dist/docs/reference/subagents.html +35 -35
- package/dist/docs/reference/tools.html +35 -35
- package/dist/docs/templates/agentic-owners.html +35 -35
- package/dist/docs/templates/security-reviewer.html +35 -35
- package/dist/docs/templates/thermo-quality-review.html +35 -35
- package/dist/docs/templates/thermo-review.html +35 -35
- package/dist/docs/templates/triage.html +35 -35
- package/dist/docs/troubleshooting.html +35 -35
- package/dist/extensions/jev/extension.d.ts +3 -0
- package/dist/extensions/jev/extension.d.ts.map +1 -1
- package/dist/extensions/jev/extension.js +10 -1
- package/dist/extensions/jev/lib/evaluate.d.ts +4 -0
- package/dist/extensions/jev/lib/evaluate.d.ts.map +1 -1
- package/dist/extensions/jev/lib/evaluate.js +6 -2
- package/dist/extensions.d.ts +4 -2
- package/dist/extensions.d.ts.map +1 -1
- package/dist/extensions.js +5 -2
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +19 -5
- package/dist/internal/discovery/agent-config.d.ts +6 -5
- package/dist/internal/discovery/agent-config.d.ts.map +1 -1
- package/dist/internal/discovery/agent-config.js +29 -28
- package/dist/internal/discovery/agent.js +1 -1
- package/dist/internal/discovery/extensions.d.ts.map +1 -1
- package/dist/internal/discovery/extensions.js +12 -0
- package/dist/internal/hosting.d.ts +8 -0
- package/dist/internal/hosting.d.ts.map +1 -1
- package/dist/internal/hosting.js +24 -0
- package/dist/types.d.ts +18 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/deployment.md +4 -1
- package/docs/guides/jev.md +7 -0
- package/docs/reference/agent-config.md +1 -1
- package/docs/reference/extensions.md +13 -0
- package/package.json +1 -9
- package/dist/docs/assets/chunks/@localSearchIndexroot.D5L-b34G.js +0 -1
- package/dist/docs/assets/chunks/channel.BbOGaJQP.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.Db58HCb1.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.Db58HCb1.js +0 -1
- package/dist/docs/assets/chunks/clone.Co4MBflG.js +0 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CMsKhrW0.js +0 -1
- package/src/artifacts.ts +0 -68
- package/src/bin/agent-serve.ts +0 -1854
- package/src/channels/bitbucket/api.ts +0 -891
- package/src/channels/bitbucket/binding.ts +0 -417
- package/src/channels/bitbucket/bitbucket-channel.ts +0 -162
- package/src/channels/bitbucket/bitbucket-webhook.ts +0 -519
- package/src/channels/bitbucket/checks.ts +0 -146
- package/src/channels/bitbucket/cursor-account.ts +0 -106
- package/src/channels/bitbucket/defaults.ts +0 -100
- package/src/channels/bitbucket/events.ts +0 -74
- package/src/channels/bitbucket/index.ts +0 -51
- package/src/channels/bitbucket/replay.ts +0 -421
- package/src/channels/bitbucket/state.ts +0 -59
- package/src/channels/bitbucket/types.ts +0 -261
- package/src/channels/change-monitors/change-monitors-channel.ts +0 -63
- package/src/channels/change-monitors/index.ts +0 -8
- package/src/channels/change-monitors/types.ts +0 -42
- package/src/channels/checks.ts +0 -185
- package/src/channels/deployments/deployments-channel.ts +0 -224
- package/src/channels/deployments/index.ts +0 -25
- package/src/channels/deployments/types.ts +0 -92
- package/src/channels/github/api.ts +0 -526
- package/src/channels/github/auth.ts +0 -293
- package/src/channels/github/binding.ts +0 -233
- package/src/channels/github/checks.ts +0 -145
- package/src/channels/github/cursor-account.ts +0 -303
- package/src/channels/github/defaults.ts +0 -168
- package/src/channels/github/events.ts +0 -67
- package/src/channels/github/forward.ts +0 -359
- package/src/channels/github/github-channel.ts +0 -2334
- package/src/channels/github/index.ts +0 -54
- package/src/channels/github/progress.ts +0 -365
- package/src/channels/github/replay.ts +0 -372
- package/src/channels/github/state.ts +0 -124
- package/src/channels/github/types.ts +0 -590
- package/src/channels/gitlab/api.ts +0 -329
- package/src/channels/gitlab/binding.ts +0 -231
- package/src/channels/gitlab/checks.ts +0 -228
- package/src/channels/gitlab/cursor-account.ts +0 -105
- package/src/channels/gitlab/defaults.ts +0 -80
- package/src/channels/gitlab/events.ts +0 -37
- package/src/channels/gitlab/gitlab-channel.ts +0 -160
- package/src/channels/gitlab/gitlab-webhook.ts +0 -509
- package/src/channels/gitlab/index.ts +0 -44
- package/src/channels/gitlab/replay.ts +0 -328
- package/src/channels/gitlab/state.ts +0 -55
- package/src/channels/gitlab/types.ts +0 -247
- package/src/channels/issues/index.ts +0 -9
- package/src/channels/issues/issues-channel.ts +0 -45
- package/src/channels/issues/types.ts +0 -38
- package/src/channels/origin/api.ts +0 -507
- package/src/channels/origin/binding.ts +0 -114
- package/src/channels/origin/checks.ts +0 -250
- package/src/channels/origin/cursor-account.ts +0 -58
- package/src/channels/origin/defaults.ts +0 -140
- package/src/channels/origin/events.ts +0 -35
- package/src/channels/origin/index.ts +0 -35
- package/src/channels/origin/origin-channel.ts +0 -815
- package/src/channels/origin/origin-webhook.ts +0 -296
- package/src/channels/origin/reactions.ts +0 -72
- package/src/channels/origin/state.ts +0 -54
- package/src/channels/origin/types.ts +0 -295
- package/src/channels/slack/agentic-delivery.ts +0 -634
- package/src/channels/slack/agentic-posted.ts +0 -135
- package/src/channels/slack/agentic-signals.ts +0 -231
- package/src/channels/slack/api.ts +0 -761
- package/src/channels/slack/approvals.ts +0 -279
- package/src/channels/slack/assistant.ts +0 -76
- package/src/channels/slack/attachments.ts +0 -596
- package/src/channels/slack/auth.ts +0 -127
- package/src/channels/slack/blocks.ts +0 -131
- package/src/channels/slack/bot-mentions.ts +0 -318
- package/src/channels/slack/channel-watch.ts +0 -314
- package/src/channels/slack/cli-project.ts +0 -249
- package/src/channels/slack/code-fences.ts +0 -171
- package/src/channels/slack/constants.ts +0 -38
- package/src/channels/slack/credentials.ts +0 -88
- package/src/channels/slack/defaults.ts +0 -714
- package/src/channels/slack/dispatch.ts +0 -1060
- package/src/channels/slack/doctor.ts +0 -338
- package/src/channels/slack/eval-directive.ts +0 -306
- package/src/channels/slack/event-id-dedupe.ts +0 -30
- package/src/channels/slack/external-policy.ts +0 -75
- package/src/channels/slack/footer.ts +0 -106
- package/src/channels/slack/inbound.ts +0 -416
- package/src/channels/slack/index.ts +0 -53
- package/src/channels/slack/init.ts +0 -169
- package/src/channels/slack/interactive.ts +0 -406
- package/src/channels/slack/links.ts +0 -23
- package/src/channels/slack/live-delivery.ts +0 -489
- package/src/channels/slack/log.ts +0 -70
- package/src/channels/slack/manifest.ts +0 -240
- package/src/channels/slack/message-body.ts +0 -82
- package/src/channels/slack/nudge-carrier.ts +0 -224
- package/src/channels/slack/nudge-store.ts +0 -334
- package/src/channels/slack/paint-start.ts +0 -93
- package/src/channels/slack/placeholder.ts +0 -358
- package/src/channels/slack/pr-ref.ts +0 -74
- package/src/channels/slack/progress-delivery.ts +0 -138
- package/src/channels/slack/reasoning-card.ts +0 -125
- package/src/channels/slack/reasoning-chip.ts +0 -41
- package/src/channels/slack/recording-sink.ts +0 -167
- package/src/channels/slack/redact.ts +0 -6
- package/src/channels/slack/reply-options.ts +0 -279
- package/src/channels/slack/setup.ts +0 -125
- package/src/channels/slack/slack-channel.ts +0 -744
- package/src/channels/slack/socket-mode.ts +0 -140
- package/src/channels/slack/stop.ts +0 -25
- package/src/channels/slack/stream.ts +0 -66
- package/src/channels/slack/thread-context.ts +0 -485
- package/src/channels/slack/tool-approval-events.ts +0 -233
- package/src/channels/slack/tool-cards.ts +0 -198
- package/src/channels/slack/tool-chip.ts +0 -34
- package/src/channels/slack/types.ts +0 -787
- package/src/channels/slack/upload.ts +0 -137
- package/src/channels.ts +0 -543
- package/src/connections.ts +0 -153
- package/src/dashboard-session-url.ts +0 -30
- package/src/evals/assertions.ts +0 -688
- package/src/evals/expect.ts +0 -250
- package/src/evals/judge.ts +0 -329
- package/src/evals/loaders.ts +0 -111
- package/src/evals/matchers.ts +0 -223
- package/src/evals/reporters.ts +0 -257
- package/src/evals/results.ts +0 -130
- package/src/evals/run-facts.ts +0 -306
- package/src/evals.ts +0 -709
- package/src/extensions/cursor-cloud-agents/extension.ts +0 -131
- package/src/extensions/cursor-cloud-agents/lib/backend.ts +0 -386
- package/src/extensions/cursor-cloud-agents/lib/channels.ts +0 -11
- package/src/extensions/cursor-cloud-agents/lib/fleet.ts +0 -98
- package/src/extensions/cursor-cloud-agents/lib/inputs.ts +0 -5
- package/src/extensions/cursor-cloud-agents/lib/repos.ts +0 -80
- package/src/extensions/cursor-cloud-agents/lib/transcript.ts +0 -19
- package/src/extensions/cursor-cloud-agents/skills/handoff.md +0 -37
- package/src/extensions/cursor-cloud-agents/tools/cancel.ts +0 -21
- package/src/extensions/cursor-cloud-agents/tools/dump.ts +0 -47
- package/src/extensions/cursor-cloud-agents/tools/get.ts +0 -18
- package/src/extensions/cursor-cloud-agents/tools/launch.ts +0 -62
- package/src/extensions/cursor-cloud-agents/tools/list.ts +0 -31
- package/src/extensions/cursor-cloud-agents/tools/reply.ts +0 -36
- package/src/extensions/cursor-grokbot-agents/extension.ts +0 -132
- package/src/extensions/cursor-grokbot-agents/lib/agents.ts +0 -42
- package/src/extensions/cursor-grokbot-agents/lib/backend.ts +0 -208
- package/src/extensions/cursor-grokbot-agents/lib/contacts.ts +0 -74
- package/src/extensions/cursor-grokbot-agents/lib/conversation.ts +0 -260
- package/src/extensions/cursor-grokbot-agents/lib/inputs.ts +0 -21
- package/src/extensions/cursor-grokbot-agents/lib/lock.ts +0 -26
- package/src/extensions/cursor-grokbot-agents/skills/consult.md +0 -40
- package/src/extensions/cursor-grokbot-agents/tools/ask.ts +0 -32
- package/src/extensions/cursor-grokbot-agents/tools/check.ts +0 -25
- package/src/extensions/cursor-grokbot-agents/tools/interrupt.ts +0 -17
- package/src/extensions/cursor-grokbot-agents/tools/list.ts +0 -26
- package/src/extensions/improve/extension.ts +0 -69
- package/src/extensions/improve/skills/yourself.ts +0 -50
- package/src/extensions/jev/extension.ts +0 -95
- package/src/extensions/jev/lib/evaluate.ts +0 -266
- package/src/extensions/jev/skills/gated-write.md +0 -25
- package/src/extensions/jev/skills/questions.md +0 -33
- package/src/extensions/jev/tools/evaluate.ts +0 -82
- package/src/extensions/slack-communication/evals/harness.ts +0 -705
- package/src/extensions/slack-communication/evals/slack-communication.eval.ts +0 -830
- package/src/extensions/slack-communication/evals/slack-placeholder.eval.ts +0 -172
- package/src/extensions/slack-communication/extension.ts +0 -44
- package/src/extensions/slack-communication/skills/slack-voice.ts +0 -56
- package/src/extensions/slack-communication/tools/delete-message.ts +0 -33
- package/src/extensions/slack-communication/tools/edit-message.ts +0 -42
- package/src/extensions/slack-communication/tools/post-card.ts +0 -28
- package/src/extensions/slack-communication/tools/post-file.ts +0 -82
- package/src/extensions/slack-communication/tools/post-update.ts +0 -40
- package/src/extensions/slack-communication/tools/react.ts +0 -30
- package/src/extensions/slack-communication/tools/set-status.ts +0 -21
- package/src/extensions.ts +0 -179
- package/src/files-backends/agent-store-presigned-url.ts +0 -11
- package/src/files-backends/cursor-hosted.ts +0 -1212
- package/src/files-backends/local-fs.ts +0 -163
- package/src/files.ts +0 -293
- package/src/filesystem.ts +0 -2005
- package/src/hooks.ts +0 -30
- package/src/index.ts +0 -259
- package/src/internal/advertise-tools.ts +0 -370
- package/src/internal/approval-gate.ts +0 -103
- package/src/internal/artifacts-store.ts +0 -836
- package/src/internal/as-of.ts +0 -257
- package/src/internal/auth.ts +0 -46
- package/src/internal/authored-alias-hooks.ts +0 -138
- package/src/internal/authored-loaders.ts +0 -188
- package/src/internal/bounded-int.ts +0 -24
- package/src/internal/bounded.ts +0 -25
- package/src/internal/brand.ts +0 -28
- package/src/internal/browser-origin-guard.ts +0 -67
- package/src/internal/builtin-tools/artifacts.ts +0 -62
- package/src/internal/builtin-tools/index.ts +0 -53
- package/src/internal/builtin-tools/reminders.ts +0 -145
- package/src/internal/channel-dispatch.ts +0 -129
- package/src/internal/channel-route-dispatch.ts +0 -58
- package/src/internal/channel-state.ts +0 -83
- package/src/internal/chat-client.ts +0 -594
- package/src/internal/cli-ax.ts +0 -2318
- package/src/internal/cli-bitbucket.ts +0 -438
- package/src/internal/cli-convert-automation.ts +0 -335
- package/src/internal/cli-cursor.ts +0 -282
- package/src/internal/cli-deploy.ts +0 -2519
- package/src/internal/cli-docs.ts +0 -192
- package/src/internal/cli-github.ts +0 -806
- package/src/internal/cli-gitlab.ts +0 -452
- package/src/internal/cli-images.ts +0 -672
- package/src/internal/cli-manifest.ts +0 -56
- package/src/internal/cli-mcp-oauth.ts +0 -396
- package/src/internal/cli-mcp.ts +0 -808
- package/src/internal/cli-skills.ts +0 -98
- package/src/internal/cli-slack-manual.ts +0 -418
- package/src/internal/cli-slack.ts +0 -874
- package/src/internal/cloud-merge.ts +0 -51
- package/src/internal/cloud-turn-cost.ts +0 -159
- package/src/internal/connection-env.ts +0 -106
- package/src/internal/continuation-channel.ts +0 -233
- package/src/internal/continuation-identity.ts +0 -935
- package/src/internal/conversation-mirror.ts +0 -423
- package/src/internal/convert-automation/convert-workflow.ts +0 -1377
- package/src/internal/convert-automation/slug.ts +0 -29
- package/src/internal/convert-automation/types.ts +0 -212
- package/src/internal/cron.ts +0 -76
- package/src/internal/cursor/account-mcp.ts +0 -692
- package/src/internal/cursor/backend-client.ts +0 -984
- package/src/internal/cursor/bound-secret-file.ts +0 -101
- package/src/internal/cursor/credentials.ts +0 -560
- package/src/internal/cursor/cursor-api-transport.ts +0 -65
- package/src/internal/cursor/github-credentials.ts +0 -228
- package/src/internal/cursor/hosted-bound-secrets.ts +0 -71
- package/src/internal/cursor/hosted-deployment-secrets.ts +0 -100
- package/src/internal/cursor/hosted-store-secrets.ts +0 -152
- package/src/internal/cursor/session-token.ts +0 -111
- package/src/internal/cursor/store-api-client.ts +0 -323
- package/src/internal/cursor-account-mcp-auth.ts +0 -303
- package/src/internal/cursor-agent-template.ts +0 -50
- package/src/internal/cursor-deployment-relay.ts +0 -315
- package/src/internal/cursor-event-relay.ts +0 -675
- package/src/internal/cursor-relay-core.ts +0 -563
- package/src/internal/deferred-channel-session.ts +0 -61
- package/src/internal/deploy-client.ts +0 -1574
- package/src/internal/deploy-manifest.ts +0 -361
- package/src/internal/deploy-source.ts +0 -144
- package/src/internal/describe-error.ts +0 -32
- package/src/internal/dev-mode.ts +0 -20
- package/src/internal/discovery/agent-config.ts +0 -288
- package/src/internal/discovery/agent.ts +0 -381
- package/src/internal/discovery/channels.ts +0 -100
- package/src/internal/discovery/connections.ts +0 -516
- package/src/internal/discovery/context.ts +0 -38
- package/src/internal/discovery/cursor-plugin.ts +0 -628
- package/src/internal/discovery/extension-overlay.ts +0 -247
- package/src/internal/discovery/extensions.ts +0 -490
- package/src/internal/discovery/fs.ts +0 -103
- package/src/internal/discovery/hooks.ts +0 -63
- package/src/internal/discovery/info.ts +0 -174
- package/src/internal/discovery/instructions.ts +0 -98
- package/src/internal/discovery/modules.ts +0 -107
- package/src/internal/discovery/names.ts +0 -45
- package/src/internal/discovery/project.ts +0 -319
- package/src/internal/discovery/reminders.ts +0 -65
- package/src/internal/discovery/runtime.ts +0 -158
- package/src/internal/discovery/schedules.ts +0 -120
- package/src/internal/discovery/seed-files.ts +0 -34
- package/src/internal/discovery/services.ts +0 -151
- package/src/internal/discovery/skills.ts +0 -174
- package/src/internal/discovery/subagent.ts +0 -138
- package/src/internal/discovery/tools.ts +0 -170
- package/src/internal/discovery.ts +0 -27
- package/src/internal/distribution.ts +0 -126
- package/src/internal/docs-site.ts +0 -248
- package/src/internal/duration.ts +0 -37
- package/src/internal/env-file.ts +0 -83
- package/src/internal/eval-judge-model.ts +0 -142
- package/src/internal/eval-run-store.ts +0 -585
- package/src/internal/eval-runner.ts +0 -823
- package/src/internal/evals-client.ts +0 -475
- package/src/internal/event-log.ts +0 -342
- package/src/internal/event-mapper.ts +0 -811
- package/src/internal/expected-delivery-failure.ts +0 -34
- package/src/internal/factory-resource-channel.ts +0 -103
- package/src/internal/filesystem/errors.ts +0 -207
- package/src/internal/filesystem/format.ts +0 -253
- package/src/internal/filesystem/merge.ts +0 -192
- package/src/internal/filesystem/nearest.ts +0 -112
- package/src/internal/filesystem/scratch.ts +0 -239
- package/src/internal/filesystem/sdk-schemas.ts +0 -204
- package/src/internal/filesystem/tools.ts +0 -804
- package/src/internal/filesystem/walk.ts +0 -366
- package/src/internal/fixtures/units-server.ts +0 -52
- package/src/internal/framework-file-storage.ts +0 -357
- package/src/internal/framework-storage-selection.ts +0 -156
- package/src/internal/frontmatter.ts +0 -108
- package/src/internal/github-fanout.ts +0 -144
- package/src/internal/guest-network.ts +0 -391
- package/src/internal/handleAgentServeTrigger.ts +0 -286
- package/src/internal/host-files.ts +0 -341
- package/src/internal/host-kv.ts +0 -81
- package/src/internal/host-otel.ts +0 -231
- package/src/internal/host-platforms.ts +0 -170
- package/src/internal/hosted-admission-adapter.ts +0 -47
- package/src/internal/hosted-admission-context.ts +0 -37
- package/src/internal/hosted-catch-protocol.ts +0 -112
- package/src/internal/hosted-catch.ts +0 -179
- package/src/internal/hosted-delivery-protocol.ts +0 -824
- package/src/internal/hosted-delivery.ts +0 -1410
- package/src/internal/hosted-execution-diag.ts +0 -291
- package/src/internal/hosted-execution-flush.ts +0 -136
- package/src/internal/hosted-execution-latency.ts +0 -301
- package/src/internal/hosted-managed-automation.ts +0 -116
- package/src/internal/hosting.ts +0 -114
- package/src/internal/http-channel-define.ts +0 -25
- package/src/internal/http-channel.ts +0 -669
- package/src/internal/http-control-plane-session.ts +0 -110
- package/src/internal/ids.ts +0 -39
- package/src/internal/incoming-headers-to-fetch.ts +0 -45
- package/src/internal/init-interview.ts +0 -360
- package/src/internal/init-project.ts +0 -711
- package/src/internal/init-scaffold.ts +0 -182
- package/src/internal/inline-stdio-define-stub.ts +0 -4
- package/src/internal/inline-stdio-mcp.ts +0 -230
- package/src/internal/install-cursor-skills.ts +0 -604
- package/src/internal/is-record.ts +0 -3
- package/src/internal/json-schema.ts +0 -95
- package/src/internal/legacy-command-guard.ts +0 -66
- package/src/internal/listen-port.ts +0 -85
- package/src/internal/local-control-plane.ts +0 -442
- package/src/internal/local-env.ts +0 -59
- package/src/internal/local-ingress.ts +0 -90
- package/src/internal/log-ring.ts +0 -59
- package/src/internal/logs-client.ts +0 -477
- package/src/internal/map-pool.ts +0 -39
- package/src/internal/mcp-endpoint.ts +0 -747
- package/src/internal/mcp-host.ts +0 -509
- package/src/internal/mcp-oauth-on-demand.ts +0 -337
- package/src/internal/mcp-oauth-persist.ts +0 -118
- package/src/internal/mcp-oauth.ts +0 -549
- package/src/internal/net.ts +0 -29
- package/src/internal/open-browser.ts +0 -41
- package/src/internal/otel/attrs.ts +0 -16
- package/src/internal/otel/collector.ts +0 -937
- package/src/internal/otel/config.ts +0 -287
- package/src/internal/otel/http.ts +0 -122
- package/src/internal/otel/mcp.ts +0 -56
- package/src/internal/otel/propagation.ts +0 -17
- package/src/internal/otel/runtime.ts +0 -441
- package/src/internal/package-inline-stdio.ts +0 -114
- package/src/internal/peer-connections.ts +0 -96
- package/src/internal/platform-schedule-sync.ts +0 -75
- package/src/internal/platform-timers.ts +0 -365
- package/src/internal/playground/static.ts +0 -278
- package/src/internal/playground/toolchain.ts +0 -270
- package/src/internal/playground-proxy.ts +0 -564
- package/src/internal/process-memory-telemetry.ts +0 -334
- package/src/internal/project-layout.ts +0 -31
- package/src/internal/prompt-context.ts +0 -116
- package/src/internal/reminder-control-plane-protocol.ts +0 -482
- package/src/internal/reminder-runner.ts +0 -1559
- package/src/internal/reminder-schedule.ts +0 -104
- package/src/internal/reminder-store.ts +0 -176
- package/src/internal/request-error-status.ts +0 -95
- package/src/internal/request-headers.ts +0 -21
- package/src/internal/resolve-prod-target.ts +0 -334
- package/src/internal/resolved-connections.ts +0 -190
- package/src/internal/router.ts +0 -143
- package/src/internal/run-client.ts +0 -354
- package/src/internal/runtime-dispatch-runner.ts +0 -50
- package/src/internal/sanitize-mcp-tool-name.ts +0 -31
- package/src/internal/schedule-runner.ts +0 -154
- package/src/internal/scm/pr-url.ts +0 -245
- package/src/internal/sdk-runner.ts +0 -959
- package/src/internal/serve-restart.ts +0 -113
- package/src/internal/server.ts +0 -3147
- package/src/internal/session-cost.ts +0 -208
- package/src/internal/session-engine.ts +0 -5622
- package/src/internal/session-links.ts +0 -84
- package/src/internal/session-run-log.ts +0 -535
- package/src/internal/session-store.ts +0 -307
- package/src/internal/session-urls.ts +0 -32
- package/src/internal/sessions-client.ts +0 -239
- package/src/internal/skill-catalog.ts +0 -66
- package/src/internal/skills-sync.ts +0 -55
- package/src/internal/slack-provision-client.ts +0 -544
- package/src/internal/slack-thread-key.ts +0 -11
- package/src/internal/sleep.ts +0 -3
- package/src/internal/start-log-store.ts +0 -164
- package/src/internal/steering.ts +0 -17
- package/src/internal/storage-coordinator.ts +0 -800
- package/src/internal/storage-roles.ts +0 -86
- package/src/internal/storage.ts +0 -1
- package/src/internal/store-api-protocol.ts +0 -205
- package/src/internal/store-mirror.ts +0 -73
- package/src/internal/stream-progress.ts +0 -294
- package/src/internal/terminal-style.ts +0 -133
- package/src/internal/testing/factory-resource-fixtures.ts +0 -84
- package/src/internal/testing/scm-continuation-fixtures.ts +0 -285
- package/src/internal/tool-catalog.ts +0 -104
- package/src/internal/tool-policy.ts +0 -222
- package/src/internal/tool-result.ts +0 -50
- package/src/internal/trajectory.ts +0 -418
- package/src/internal/turn-cost.ts +0 -136
- package/src/internal/turn-governor.ts +0 -145
- package/src/internal/turn-lease-store.ts +0 -144
- package/src/internal/turn-outbox.ts +0 -77
- package/src/internal/update-check.ts +0 -517
- package/src/internal/v2-hosting.ts +0 -52
- package/src/internal/workspace-sync.ts +0 -114
- package/src/internal/workspace.ts +0 -505
- package/src/kv/memory-kv.ts +0 -94
- package/src/kv.ts +0 -18
- package/src/memory.ts +0 -487
- package/src/otel-public.ts +0 -24
- package/src/otel.ts +0 -320
- package/src/prompt.ts +0 -80
- package/src/reminders.ts +0 -65
- package/src/schedules.ts +0 -31
- package/src/skills.ts +0 -27
- package/src/storage-backends/cursor-hosted-v2.ts +0 -204
- package/src/storage-backends/cursor-hosted.ts +0 -388
- package/src/storage-backends/file-kv.ts +0 -276
- package/src/storage-protocol.ts +0 -261
- package/src/storage.ts +0 -408
- package/src/tools.ts +0 -166
- package/src/types.ts +0 -3776
- /package/dist/docs/assets/{reference_agent-config.md.DzGI777n.lean.js → reference_agent-config.md.Cj5uBKp4.lean.js} +0 -0
|
@@ -1,2519 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `agent-sdk deploy | deployments | deployment | stop | rollback | delete | rotate-token |
|
|
3
|
-
* secrets set|list|unset` — Heroku-style deploys
|
|
4
|
-
* onto Cursor's managed agent-serve hosting.
|
|
5
|
-
*
|
|
6
|
-
* All intelligence is server-side (backend/server/src/agentServe/); these
|
|
7
|
-
* handlers resolve parameters (credentials, team, slug, the project's
|
|
8
|
-
* `hosting` / `architecture` manifest block), call the v1 agent-serve or
|
|
9
|
-
* v2 agentsdk management API through `deploy-client.ts`, poll status, and
|
|
10
|
-
* render.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { basename, resolve } from "node:path";
|
|
14
|
-
import { catchAuthDeclaration, isPublicEndpointPolicy } from "../channels.js";
|
|
15
|
-
import {
|
|
16
|
-
type AgentArchitecture,
|
|
17
|
-
type AgentHostingOptions,
|
|
18
|
-
isCursorAccountTransport,
|
|
19
|
-
} from "../types.js";
|
|
20
|
-
import { CursorBackendClient } from "./cursor/backend-client.js";
|
|
21
|
-
import { cursorExternalApiUrl, resolveApiKey, SIGN_IN_HINT } from "./cursor/credentials.js";
|
|
22
|
-
import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
|
|
23
|
-
import { readDeployTemplate } from "./cursor-agent-template.js";
|
|
24
|
-
import {
|
|
25
|
-
type AgentkitCancelRunsResult,
|
|
26
|
-
type AgentkitDeployAccepted,
|
|
27
|
-
type AgentkitDeployComponent,
|
|
28
|
-
type AgentkitDeploymentListItem,
|
|
29
|
-
type AgentkitDeployStatus,
|
|
30
|
-
type AgentkitStopAccepted,
|
|
31
|
-
AgentServeDeployClient,
|
|
32
|
-
type DeleteAccepted,
|
|
33
|
-
type DeployAccepted,
|
|
34
|
-
DeployApiError,
|
|
35
|
-
type Deployment,
|
|
36
|
-
type DeploymentDetail,
|
|
37
|
-
type DeploymentProgress,
|
|
38
|
-
type DeploySource,
|
|
39
|
-
deploymentServiceAccountId,
|
|
40
|
-
type SecretInfo,
|
|
41
|
-
type StopAccepted,
|
|
42
|
-
slugFromDirectoryName,
|
|
43
|
-
TERMINAL_AGENTKIT_DEPLOY_STATUSES,
|
|
44
|
-
TERMINAL_DEPLOY_STATUSES,
|
|
45
|
-
validateDeploymentSlug,
|
|
46
|
-
} from "./deploy-client.js";
|
|
47
|
-
import { type GitRunner, inferDeployGitSource } from "./deploy-source.js";
|
|
48
|
-
import { describeError } from "./describe-error.js";
|
|
49
|
-
import {
|
|
50
|
-
type ChildAgentDir,
|
|
51
|
-
formatDiagnostics,
|
|
52
|
-
hasErrorDiagnostics,
|
|
53
|
-
isAgentProjectDir,
|
|
54
|
-
listChildAgentDirs,
|
|
55
|
-
loadAgentProject,
|
|
56
|
-
} from "./discovery.js";
|
|
57
|
-
import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
|
|
58
|
-
import {
|
|
59
|
-
isReservedSecretName,
|
|
60
|
-
isValidCursorEventRepo,
|
|
61
|
-
validateSettableSecretName,
|
|
62
|
-
} from "./hosting.js";
|
|
63
|
-
import { deploymentArchitecture, rejectV2ForLegacyCommand } from "./legacy-command-guard.js";
|
|
64
|
-
import { deployProgressLabel, sanitizeCustomerError } from "./logs-client.js";
|
|
65
|
-
import { sleep } from "./sleep.js";
|
|
66
|
-
import { stderrPalette } from "./terminal-style.js";
|
|
67
|
-
|
|
68
|
-
const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
69
|
-
const DEFAULT_DEPLOY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
70
|
-
/** `--multi-tenant` HTTP deploy is Anysphere-only (team id 1). */
|
|
71
|
-
const MULTI_TENANT_HTTP_DEPLOY_TEAM_ID = 1;
|
|
72
|
-
|
|
73
|
-
/** One agent directory selected for deploy (single or multi-agent parent). */
|
|
74
|
-
export type DeployTarget = ChildAgentDir;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Interactive / test seam: pick which agents under a multi-agent parent to
|
|
78
|
-
* deploy. Return `null` to cancel.
|
|
79
|
-
*/
|
|
80
|
-
export type ChooseDeployTargets = (
|
|
81
|
-
agents: readonly DeployTarget[],
|
|
82
|
-
) => Promise<DeployTarget[] | null>;
|
|
83
|
-
|
|
84
|
-
export interface DeployCliOptions {
|
|
85
|
-
dir?: string;
|
|
86
|
-
slug?: string;
|
|
87
|
-
/**
|
|
88
|
-
* deploy `--all`: when `--dir` is a multi-agent parent, deploy every
|
|
89
|
-
* child agent (skips the interactive picker).
|
|
90
|
-
*/
|
|
91
|
-
all?: boolean;
|
|
92
|
-
/** `--team <id>`; resolved from the account when omitted. */
|
|
93
|
-
team?: string;
|
|
94
|
-
json?: boolean;
|
|
95
|
-
noWait?: boolean;
|
|
96
|
-
/** v2 stop: also request cancellation for every active session run. */
|
|
97
|
-
cancelRuns?: boolean;
|
|
98
|
-
apiKey?: string;
|
|
99
|
-
/**
|
|
100
|
-
* deploy `--repo <url>`: https git repo the control plane builds from.
|
|
101
|
-
* Optional — inferred from `git remote origin` when omitted inside an
|
|
102
|
-
* agent project checkout.
|
|
103
|
-
*/
|
|
104
|
-
repo?: string;
|
|
105
|
-
/**
|
|
106
|
-
* deploy `--ref <ref>`: git ref to build. Optional — inferred from the
|
|
107
|
-
* current branch (or HEAD SHA if detached) when omitted; otherwise the
|
|
108
|
-
* server uses the repo's default branch.
|
|
109
|
-
*/
|
|
110
|
-
ref?: string;
|
|
111
|
-
/**
|
|
112
|
-
* deploy `--path <agentPath>`: agent directory relative to the repo root.
|
|
113
|
-
* Optional — inferred from `--dir` vs the git toplevel when omitted.
|
|
114
|
-
*/
|
|
115
|
-
path?: string;
|
|
116
|
-
/** deploy `--cursor-events-repo <owner/name>` (repeatable). */
|
|
117
|
-
cursorEventsRepos?: string[];
|
|
118
|
-
/** deploy `--allow-domain <domain>` (repeatable; unioned with the manifest). */
|
|
119
|
-
allowDomains?: string[];
|
|
120
|
-
/**
|
|
121
|
-
* deploy `--multi-tenant`: add a release to an existing Cursor-managed
|
|
122
|
-
* (multi-tenant) product. Hidden Anysphere-only flag. Cannot create a
|
|
123
|
-
* new multi-tenant application.
|
|
124
|
-
*/
|
|
125
|
-
multiTenant?: boolean;
|
|
126
|
-
/** Test seams. */
|
|
127
|
-
fetchImpl?: typeof fetch;
|
|
128
|
-
/** Inject the connector-auth browser opener (tests; also forces interactive). */
|
|
129
|
-
openBrowserImpl?: (url: string) => void;
|
|
130
|
-
sleepImpl?: (ms: number) => Promise<void>;
|
|
131
|
-
connectorPollIntervalMs?: number;
|
|
132
|
-
connectorPollTimeoutMs?: number;
|
|
133
|
-
/** Inject git probing for deploy source inference (tests). */
|
|
134
|
-
git?: GitRunner;
|
|
135
|
-
/** Inject the multi-agent picker (tests / non-interactive hosts). */
|
|
136
|
-
chooseTargets?: ChooseDeployTargets;
|
|
137
|
-
pollIntervalMs?: number;
|
|
138
|
-
timeoutMs?: number;
|
|
139
|
-
out?: (text: string) => void;
|
|
140
|
-
err?: (text: string) => void;
|
|
141
|
-
/** `secrets set` value input (hidden prompt on a TTY, lines otherwise). */
|
|
142
|
-
stdin?: SecretInputStream;
|
|
143
|
-
/**
|
|
144
|
-
* Opt in to `NAME=VALUE` argv secrets. Default is names only (hidden
|
|
145
|
-
* prompt / stdin) so values never land in shell history or agent-captured
|
|
146
|
-
* terminals.
|
|
147
|
-
*/
|
|
148
|
-
fromArgv?: boolean;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Minimal stdin surface `secrets set` reads values from (test-injectable). */
|
|
152
|
-
export type SecretInputStream = NodeJS.ReadableStream & {
|
|
153
|
-
isTTY?: boolean;
|
|
154
|
-
setRawMode?: (mode: boolean) => void;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export interface DeployCliContext {
|
|
158
|
-
client: AgentServeDeployClient;
|
|
159
|
-
teamId: number;
|
|
160
|
-
apiKey: string;
|
|
161
|
-
backendUrl: string;
|
|
162
|
-
json: boolean;
|
|
163
|
-
noWait: boolean;
|
|
164
|
-
pollIntervalMs: number;
|
|
165
|
-
timeoutMs: number;
|
|
166
|
-
out: (text: string) => void;
|
|
167
|
-
err: (text: string) => void;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export interface ResolveDeployAuthOptions {
|
|
171
|
-
team?: string;
|
|
172
|
-
apiKey?: string;
|
|
173
|
-
fetchImpl?: typeof fetch;
|
|
174
|
-
/** Quiet team-resolution chatter (default: write to stderr). */
|
|
175
|
-
err?: (text: string) => void;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export interface ResolvedDeployAuth {
|
|
179
|
-
client: AgentServeDeployClient;
|
|
180
|
-
teamId: number;
|
|
181
|
-
/** The resolved account credential, for dashboard RPCs beside the deploy API. */
|
|
182
|
-
apiKey: string;
|
|
183
|
-
backendUrl: string;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Credentials → deploy client → team id. Shared by deploy commands and
|
|
188
|
-
* `--prod` AX routing. Returns an exit code (error already rendered) on
|
|
189
|
-
* failure.
|
|
190
|
-
*/
|
|
191
|
-
export async function resolveDeployAuth(
|
|
192
|
-
options: ResolveDeployAuthOptions,
|
|
193
|
-
): Promise<ResolvedDeployAuth | number> {
|
|
194
|
-
const err = options.err ?? ((text: string) => void process.stderr.write(text));
|
|
195
|
-
|
|
196
|
-
const resolved = await resolveApiKey(
|
|
197
|
-
options.apiKey === undefined ? undefined : { apiKey: options.apiKey },
|
|
198
|
-
);
|
|
199
|
-
if (resolved === undefined) {
|
|
200
|
-
err(`Not signed in to Cursor. ${SIGN_IN_HINT}\n`);
|
|
201
|
-
return 1;
|
|
202
|
-
}
|
|
203
|
-
// The deployment management routes live on the external API host
|
|
204
|
-
// (api.cursor.com), not the default backend (api2.cursor.sh) — see
|
|
205
|
-
// cursorExternalApiUrl. GetMe below stays on the resolved backend.
|
|
206
|
-
const client = new AgentServeDeployClient({
|
|
207
|
-
backendUrl: cursorExternalApiUrl(),
|
|
208
|
-
apiKey: resolved.apiKey,
|
|
209
|
-
fetchImpl: options.fetchImpl,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
let teamId: number;
|
|
213
|
-
if (options.team !== undefined) {
|
|
214
|
-
teamId = Number.parseInt(options.team, 10);
|
|
215
|
-
if (Number.isNaN(teamId) || teamId <= 0) {
|
|
216
|
-
err(`Invalid --team: ${options.team} (expected a positive team id)\n`);
|
|
217
|
-
return 1;
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
const account = new CursorBackendClient({
|
|
221
|
-
backendUrl: resolved.backendUrl,
|
|
222
|
-
apiKey: resolved.apiKey,
|
|
223
|
-
fetchImpl: options.fetchImpl,
|
|
224
|
-
});
|
|
225
|
-
let identity: Awaited<ReturnType<CursorBackendClient["getMe"]>>;
|
|
226
|
-
try {
|
|
227
|
-
identity = await account.getMe();
|
|
228
|
-
} catch (error) {
|
|
229
|
-
err(
|
|
230
|
-
`Could not resolve your team from the Cursor account: ${describeError(error)}\nPass --team <id> explicitly.\n`,
|
|
231
|
-
);
|
|
232
|
-
return 1;
|
|
233
|
-
}
|
|
234
|
-
if (identity.teamId === undefined || identity.teamId <= 0) {
|
|
235
|
-
err(
|
|
236
|
-
"This Cursor account has no default team; pass --team <id> (find it in the Cursor dashboard URL).\n",
|
|
237
|
-
);
|
|
238
|
-
return 1;
|
|
239
|
-
}
|
|
240
|
-
teamId = identity.teamId;
|
|
241
|
-
const dim = stderrPalette().dim;
|
|
242
|
-
err(
|
|
243
|
-
`${dim(
|
|
244
|
-
`Using team ${teamId}${identity.teamName === undefined ? "" : ` (${identity.teamName})`}`,
|
|
245
|
-
)}\n${dim(" the account's team — pass --team to override.")}\n`,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
client,
|
|
251
|
-
teamId,
|
|
252
|
-
apiKey: resolved.apiKey,
|
|
253
|
-
backendUrl: resolved.backendUrl,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Shared preamble: credentials → client → team id. Returns an exit code
|
|
259
|
-
* (with the error already rendered) when resolution fails.
|
|
260
|
-
*/
|
|
261
|
-
export async function prepare(options: DeployCliOptions): Promise<DeployCliContext | number> {
|
|
262
|
-
const out = options.out ?? ((text: string) => void process.stdout.write(text));
|
|
263
|
-
const err = options.err ?? ((text: string) => void process.stderr.write(text));
|
|
264
|
-
|
|
265
|
-
const auth = await resolveDeployAuth({
|
|
266
|
-
team: options.team,
|
|
267
|
-
apiKey: options.apiKey,
|
|
268
|
-
fetchImpl: options.fetchImpl,
|
|
269
|
-
err,
|
|
270
|
-
});
|
|
271
|
-
if (typeof auth === "number") {
|
|
272
|
-
return auth;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
client: auth.client,
|
|
277
|
-
teamId: auth.teamId,
|
|
278
|
-
apiKey: auth.apiKey,
|
|
279
|
-
backendUrl: auth.backendUrl,
|
|
280
|
-
json: options.json === true,
|
|
281
|
-
noWait: options.noWait === true,
|
|
282
|
-
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
283
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_DEPLOY_TIMEOUT_MS,
|
|
284
|
-
out,
|
|
285
|
-
err,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ============================================================================
|
|
290
|
-
// deploy
|
|
291
|
-
// ============================================================================
|
|
292
|
-
|
|
293
|
-
export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
|
|
294
|
-
const ctx = await prepare(options);
|
|
295
|
-
if (typeof ctx === "number") {
|
|
296
|
-
return ctx;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (options.multiTenant === true && ctx.teamId !== MULTI_TENANT_HTTP_DEPLOY_TEAM_ID) {
|
|
300
|
-
ctx.err("--multi-tenant is not available for this team.\n");
|
|
301
|
-
return 1;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const targets = await resolveDeployTargets(options, ctx);
|
|
305
|
-
if (typeof targets === "number") {
|
|
306
|
-
return targets;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (targets.length === 1) {
|
|
310
|
-
return deployOne(ctx, options, targets[0]!);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Multi-agent parent: when every child declares `architecture: "v2"`,
|
|
314
|
-
// the v2 control plane models sibling agents as release components of
|
|
315
|
-
// ONE application, so deploy them together (one app, one component per
|
|
316
|
-
// child) instead of minting a separate application per child. Mixed and
|
|
317
|
-
// v1 parents keep the per-child sequential deploys.
|
|
318
|
-
const manifests: DeployManifest[] = [];
|
|
319
|
-
for (const target of targets) {
|
|
320
|
-
manifests.push(await readDeployManifest(target.dir));
|
|
321
|
-
}
|
|
322
|
-
if (manifests.every(manifest => manifest.architecture === "v2")) {
|
|
323
|
-
return deployAgentsdkApplication(ctx, options, targets, {
|
|
324
|
-
// `persistent` is per child agent, like `agentPath`: each release
|
|
325
|
-
// component carries its own.
|
|
326
|
-
componentPersistent: Object.fromEntries(
|
|
327
|
-
targets.map((target, index) => [target.slug, manifests[index]?.persistent === true]),
|
|
328
|
-
),
|
|
329
|
-
componentImage: Object.fromEntries(
|
|
330
|
-
targets.flatMap((target, index) =>
|
|
331
|
-
manifests[index]?.image === undefined ? [] : [[target.slug, manifests[index].image]],
|
|
332
|
-
),
|
|
333
|
-
),
|
|
334
|
-
hasScmChannels: manifests.some(manifest => manifest.hasScmChannels),
|
|
335
|
-
declaredSecretNames: [
|
|
336
|
-
...new Set(manifests.flatMap(manifest => manifest.hosting?.secretNames ?? [])),
|
|
337
|
-
],
|
|
338
|
-
hasDeploymentsChannels: manifests.some(manifest => manifest.hasDeploymentsChannels),
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
if (!ctx.json && manifests.some(manifest => manifest.architecture === "v2")) {
|
|
342
|
-
ctx.err(
|
|
343
|
-
`warning: agents under this parent mix architectures; deploying each separately (v2 children become standalone single-component applications).\n`,
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// --path is per-agent (relative to the repo root). When deploying several
|
|
348
|
-
// agents, ignore a shared --path so each target can infer its own path.
|
|
349
|
-
let deployOptions = options;
|
|
350
|
-
if (options.path !== undefined) {
|
|
351
|
-
if (!ctx.json) {
|
|
352
|
-
ctx.err(
|
|
353
|
-
`warning: ignoring --path ${JSON.stringify(options.path)} for a multi-agent deploy; each agent infers its own path from git.\n`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
deployOptions = { ...options, path: undefined };
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Multi-agent batch: deploy sequentially; --json emits an array.
|
|
360
|
-
const jsonResults: unknown[] = [];
|
|
361
|
-
let failures = 0;
|
|
362
|
-
for (const [index, target] of targets.entries()) {
|
|
363
|
-
if (!ctx.json) {
|
|
364
|
-
ctx.err(`\n=== [${index + 1}/${targets.length}] Deploying ${target.slug} ===\n`);
|
|
365
|
-
}
|
|
366
|
-
const before = jsonResults.length;
|
|
367
|
-
const code = await deployOne(ctx, deployOptions, target, {
|
|
368
|
-
jsonCollect: ctx.json ? jsonResults : undefined,
|
|
369
|
-
});
|
|
370
|
-
if (code !== 0) {
|
|
371
|
-
failures += 1;
|
|
372
|
-
if (ctx.json && jsonResults.length === before) {
|
|
373
|
-
jsonResults.push({ slug: target.slug, error: "deploy failed" });
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (ctx.json) {
|
|
378
|
-
ctx.out(`${JSON.stringify(jsonResults, null, 2)}\n`);
|
|
379
|
-
} else if (failures > 0) {
|
|
380
|
-
ctx.err(`\n${failures} of ${targets.length} deploy(s) failed.\n`);
|
|
381
|
-
} else {
|
|
382
|
-
ctx.err(`\nDeployed ${targets.length} agents.\n`);
|
|
383
|
-
}
|
|
384
|
-
return failures === 0 ? 0 : 1;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Resolve which agent directory(ies) to deploy from `--dir` / `--slug` /
|
|
389
|
-
* `--all`, with an interactive picker when a multi-agent parent is on a TTY.
|
|
390
|
-
*/
|
|
391
|
-
async function resolveDeployTargets(
|
|
392
|
-
options: DeployCliOptions,
|
|
393
|
-
ctx: DeployCliContext,
|
|
394
|
-
): Promise<DeployTarget[] | number> {
|
|
395
|
-
const dir = resolve(options.dir ?? ".");
|
|
396
|
-
|
|
397
|
-
if (options.slug !== undefined) {
|
|
398
|
-
const slugError = validateDeploymentSlug(options.slug);
|
|
399
|
-
if (slugError !== undefined) {
|
|
400
|
-
ctx.err(`${slugError}\n`);
|
|
401
|
-
return 1;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (options.all === true && options.slug !== undefined) {
|
|
405
|
-
ctx.err("Pass either --slug or --all, not both.\n");
|
|
406
|
-
return 1;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (await isAgentProjectDir(dir)) {
|
|
410
|
-
if (options.all === true) {
|
|
411
|
-
ctx.err(
|
|
412
|
-
`--all requires a multi-agent parent directory (several child agents under --dir); ${dir} is a single agent project.\n`,
|
|
413
|
-
);
|
|
414
|
-
return 1;
|
|
415
|
-
}
|
|
416
|
-
let slug: string;
|
|
417
|
-
if (options.slug !== undefined) {
|
|
418
|
-
slug = options.slug;
|
|
419
|
-
} else {
|
|
420
|
-
const derived = slugFromDirectoryName(basename(dir));
|
|
421
|
-
if (derived === undefined) {
|
|
422
|
-
ctx.err(
|
|
423
|
-
`Could not derive a deployment slug from the directory name ${JSON.stringify(basename(dir))}; pass --slug.\n`,
|
|
424
|
-
);
|
|
425
|
-
return 1;
|
|
426
|
-
}
|
|
427
|
-
slug = derived;
|
|
428
|
-
if (!ctx.json) {
|
|
429
|
-
ctx.err(`Deploying as "${slug}" (from the directory name).\n`);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return [{ slug, dir }];
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
let children: ChildAgentDir[];
|
|
436
|
-
try {
|
|
437
|
-
children = await listChildAgentDirs(dir);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
ctx.err(`${describeError(error)}\n`);
|
|
440
|
-
return 1;
|
|
441
|
-
}
|
|
442
|
-
// Deploy slugs are lowercase-only; normalize directory names the same way
|
|
443
|
-
// single-agent deploy does so MixedCase child dirs still deploy.
|
|
444
|
-
children = children.flatMap(child => {
|
|
445
|
-
const slug = slugFromDirectoryName(basename(child.dir));
|
|
446
|
-
if (slug === undefined) {
|
|
447
|
-
return [];
|
|
448
|
-
}
|
|
449
|
-
return [{ slug, dir: child.dir }];
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
if (children.length === 0) {
|
|
453
|
-
if (options.all === true) {
|
|
454
|
-
ctx.err(`--all requires child agent projects under --dir; none found in ${dir}.\n`);
|
|
455
|
-
return 1;
|
|
456
|
-
}
|
|
457
|
-
// Bare-slug deploy against a non-agent directory (still requires --repo).
|
|
458
|
-
let slug: string;
|
|
459
|
-
if (options.slug !== undefined) {
|
|
460
|
-
slug = options.slug;
|
|
461
|
-
} else {
|
|
462
|
-
const derived = slugFromDirectoryName(basename(dir));
|
|
463
|
-
if (derived === undefined) {
|
|
464
|
-
ctx.err(
|
|
465
|
-
`No agent projects under ${dir}, and could not derive a slug from ${JSON.stringify(basename(dir))}; pass --slug or --dir <agent>.\n`,
|
|
466
|
-
);
|
|
467
|
-
return 1;
|
|
468
|
-
}
|
|
469
|
-
slug = derived;
|
|
470
|
-
if (!ctx.json) {
|
|
471
|
-
ctx.err(`Deploying as "${slug}" (no agent project at ${dir}).\n`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return [{ slug, dir }];
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (children.length === 1) {
|
|
478
|
-
const only = children[0]!;
|
|
479
|
-
if (options.all === true) {
|
|
480
|
-
// One child is fine for --all (degenerate multi-agent parent).
|
|
481
|
-
} else if (options.slug !== undefined && options.slug.toLowerCase() !== only.slug) {
|
|
482
|
-
ctx.err(`No agent "${options.slug}" under ${dir}; found "${only.slug}".\n`);
|
|
483
|
-
return 1;
|
|
484
|
-
}
|
|
485
|
-
if (!ctx.json) {
|
|
486
|
-
ctx.err(`Deploying "${only.slug}" (only agent under ${basename(dir)}).\n`);
|
|
487
|
-
}
|
|
488
|
-
return [only];
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Multiple children.
|
|
492
|
-
if (options.all === true) {
|
|
493
|
-
if (!ctx.json) {
|
|
494
|
-
ctx.err(
|
|
495
|
-
`Deploying all ${children.length} agents under ${basename(dir)}: ${children.map(c => c.slug).join(", ")}.\n`,
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
return [...children];
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (options.slug !== undefined) {
|
|
502
|
-
const wanted = options.slug.toLowerCase();
|
|
503
|
-
const match = children.find(child => child.slug === wanted);
|
|
504
|
-
if (match === undefined) {
|
|
505
|
-
ctx.err(
|
|
506
|
-
`No agent "${options.slug}" under ${dir}.\n` +
|
|
507
|
-
formatAgentList(children) +
|
|
508
|
-
`Pass --slug <name> or --all.\n`,
|
|
509
|
-
);
|
|
510
|
-
return 1;
|
|
511
|
-
}
|
|
512
|
-
return [match];
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// An all-v2 parent deploys as ONE application (siblings are components
|
|
516
|
-
// of a single app on the v2 control plane), so "deploy the parent" is
|
|
517
|
-
// unambiguous and needs no picker or --all. A child that is v1 — or
|
|
518
|
-
// fails to load — keeps the interactive picker below.
|
|
519
|
-
if (await allChildrenAreV2(children)) {
|
|
520
|
-
if (!ctx.json) {
|
|
521
|
-
ctx.err(
|
|
522
|
-
`Deploying all ${children.length} v2 agents under ${basename(dir)} as one application (pass --slug <name> to deploy a single agent standalone).\n`,
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
return [...children];
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const chooser =
|
|
529
|
-
options.chooseTargets ??
|
|
530
|
-
(agents =>
|
|
531
|
-
promptChooseDeployTargets(agents, {
|
|
532
|
-
err: ctx.err,
|
|
533
|
-
stdin: options.stdin ?? process.stdin,
|
|
534
|
-
interactive:
|
|
535
|
-
ctx.json !== true &&
|
|
536
|
-
(options.stdin ?? process.stdin).isTTY === true &&
|
|
537
|
-
process.stderr.isTTY === true,
|
|
538
|
-
}));
|
|
539
|
-
const chosen = await chooser(children);
|
|
540
|
-
if (chosen === null) {
|
|
541
|
-
ctx.err("Deploy cancelled.\n");
|
|
542
|
-
return 1;
|
|
543
|
-
}
|
|
544
|
-
if (chosen.length === 0) {
|
|
545
|
-
ctx.err(
|
|
546
|
-
`Multiple agents under ${dir}:\n` +
|
|
547
|
-
formatAgentList(children) +
|
|
548
|
-
`Pass --slug <name> or --all (or re-run on a TTY to pick interactively).\n`,
|
|
549
|
-
);
|
|
550
|
-
return 1;
|
|
551
|
-
}
|
|
552
|
-
return chosen;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function formatAgentList(agents: readonly DeployTarget[]): string {
|
|
556
|
-
return agents.map((agent, i) => ` ${i + 1}) ${agent.slug}\n`).join("");
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* True when every child agent project loads and declares
|
|
561
|
-
* `architecture: "v2"`. Broken projects count as not-v2 so target
|
|
562
|
-
* resolution falls back to the picker instead of failing the whole deploy
|
|
563
|
-
* on a child the user might not even select.
|
|
564
|
-
*/
|
|
565
|
-
async function allChildrenAreV2(children: readonly DeployTarget[]): Promise<boolean> {
|
|
566
|
-
for (const child of children) {
|
|
567
|
-
try {
|
|
568
|
-
if ((await readDeployManifest(child.dir)).architecture !== "v2") {
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
} catch {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* TTY picker for multi-agent parents. Non-interactive callers get `[]`
|
|
580
|
-
* so {@link resolveDeployTargets} can print the --slug/--all hint.
|
|
581
|
-
*/
|
|
582
|
-
export async function promptChooseDeployTargets(
|
|
583
|
-
agents: readonly DeployTarget[],
|
|
584
|
-
options: {
|
|
585
|
-
err: (text: string) => void;
|
|
586
|
-
stdin: SecretInputStream;
|
|
587
|
-
interactive: boolean;
|
|
588
|
-
},
|
|
589
|
-
): Promise<DeployTarget[] | null> {
|
|
590
|
-
options.err(`Multiple agents (${agents.length}):\n` + formatAgentList(agents) + ` a) all\n`);
|
|
591
|
-
if (!options.interactive) {
|
|
592
|
-
return [];
|
|
593
|
-
}
|
|
594
|
-
options.err('Deploy which? Enter a number, slug, "all", or "q" to cancel: ');
|
|
595
|
-
const readLine = createLineReader(options.stdin);
|
|
596
|
-
// createLineReader pauses until data; ensure stdin is flowing on a TTY.
|
|
597
|
-
options.stdin.resume?.();
|
|
598
|
-
const answer = (await readLine())?.trim().toLowerCase();
|
|
599
|
-
if (answer === undefined || answer === "q" || answer === "quit") {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
if (answer === "a" || answer === "all" || answer === "*") {
|
|
603
|
-
return [...agents];
|
|
604
|
-
}
|
|
605
|
-
const asIndex = Number.parseInt(answer, 10);
|
|
606
|
-
if (
|
|
607
|
-
!Number.isNaN(asIndex) &&
|
|
608
|
-
String(asIndex) === answer &&
|
|
609
|
-
asIndex >= 1 &&
|
|
610
|
-
asIndex <= agents.length
|
|
611
|
-
) {
|
|
612
|
-
return [agents[asIndex - 1]!];
|
|
613
|
-
}
|
|
614
|
-
const bySlug = agents.find(agent => agent.slug.toLowerCase() === answer);
|
|
615
|
-
if (bySlug !== undefined) {
|
|
616
|
-
return [bySlug];
|
|
617
|
-
}
|
|
618
|
-
options.err(
|
|
619
|
-
`Unrecognized choice ${JSON.stringify(answer)} — expected 1–${agents.length}, a slug, "all", or "q". Aborting.\n`,
|
|
620
|
-
);
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
async function deployOne(
|
|
625
|
-
ctx: DeployCliContext,
|
|
626
|
-
options: DeployCliOptions,
|
|
627
|
-
target: DeployTarget,
|
|
628
|
-
extras?: { jsonCollect?: unknown[] },
|
|
629
|
-
): Promise<number> {
|
|
630
|
-
const { dir, slug } = target;
|
|
631
|
-
|
|
632
|
-
// Fill --repo/--ref/--path from the local git checkout when omitted so
|
|
633
|
-
// `agent-sdk deploy` inside an agent directory is enough. Only probe git
|
|
634
|
-
// for real agent projects (avoids ambient monorepo remotes when someone
|
|
635
|
-
// deploys a bare --slug). Explicit flags always win.
|
|
636
|
-
const isAgent = await isAgentProjectDir(dir);
|
|
637
|
-
const needsGitInference =
|
|
638
|
-
isAgent &&
|
|
639
|
-
(options.repo === undefined || options.ref === undefined || options.path === undefined);
|
|
640
|
-
const inferred = needsGitInference
|
|
641
|
-
? await inferDeployGitSource(dir, {
|
|
642
|
-
...(options.git === undefined ? {} : { git: options.git }),
|
|
643
|
-
})
|
|
644
|
-
: {};
|
|
645
|
-
const gitRepoUrl = options.repo ?? inferred.gitRepoUrl;
|
|
646
|
-
const gitRef = options.ref ?? inferred.gitRef;
|
|
647
|
-
const agentPath = options.path ?? inferred.agentPath;
|
|
648
|
-
|
|
649
|
-
if (gitRepoUrl !== undefined && !gitRepoUrl.startsWith("https://")) {
|
|
650
|
-
ctx.err(`Invalid --repo: ${gitRepoUrl} (expected an https:// git repository URL)\n`);
|
|
651
|
-
return 1;
|
|
652
|
-
}
|
|
653
|
-
if (gitRepoUrl === undefined) {
|
|
654
|
-
ctx.err(
|
|
655
|
-
`Could not resolve an https:// git repository URL for this deploy.\n` +
|
|
656
|
-
` Pass --repo https://github.com/<owner>/<repo> (inferred from origin when possible).\n`,
|
|
657
|
-
);
|
|
658
|
-
return 1;
|
|
659
|
-
}
|
|
660
|
-
for (const repo of options.cursorEventsRepos ?? []) {
|
|
661
|
-
if (!isValidCursorEventRepo(repo)) {
|
|
662
|
-
ctx.err(`Invalid --cursor-events-repo: ${repo} (expected owner/name)\n`);
|
|
663
|
-
return 1;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (!ctx.json && gitRepoUrl !== undefined) {
|
|
668
|
-
const inferredBits: string[] = [];
|
|
669
|
-
if (options.repo === undefined && inferred.gitRepoUrl !== undefined) {
|
|
670
|
-
inferredBits.push("repo");
|
|
671
|
-
}
|
|
672
|
-
if (options.ref === undefined && inferred.gitRef !== undefined) {
|
|
673
|
-
inferredBits.push("ref");
|
|
674
|
-
}
|
|
675
|
-
if (options.path === undefined && inferred.agentPath !== undefined) {
|
|
676
|
-
inferredBits.push("path");
|
|
677
|
-
}
|
|
678
|
-
ctx.err(
|
|
679
|
-
` source: ${gitRepoUrl}${gitRef === undefined ? "" : ` @ ${gitRef}`}\n` +
|
|
680
|
-
(agentPath === undefined ? "" : ` path: ${agentPath}\n`) +
|
|
681
|
-
(inferredBits.length === 0
|
|
682
|
-
? ""
|
|
683
|
-
: ` (inferred ${inferredBits.join(", ")} from git; pass --repo/--ref/--path to override)\n`),
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// The package manifest (`defineAgent({ hosting })`) is the source of
|
|
688
|
-
// truth for egress domains and expected secret names; flags override or
|
|
689
|
-
// augment it. Directories without an agent project have no manifest. The same load supplies the
|
|
690
|
-
// cursorAccount connection refs for the post-deploy connector auth and
|
|
691
|
-
// the channels declared public via `publicEndpoint()`.
|
|
692
|
-
const manifest = isAgent ? await readDeployManifest(dir) : undefined;
|
|
693
|
-
const hosting = manifest?.hosting;
|
|
694
|
-
const egressAllowedDomains = [
|
|
695
|
-
...new Set([...(hosting?.egressDomains ?? []), ...(options.allowDomains ?? [])]),
|
|
696
|
-
];
|
|
697
|
-
const publicRoutePaths = (manifest?.publicChannelIds ?? []).map(
|
|
698
|
-
channelId => `/v1/channels/${channelId}`,
|
|
699
|
-
);
|
|
700
|
-
if (!ctx.json && manifest?.httpChannelPublicEndpoint === true) {
|
|
701
|
-
ctx.err(
|
|
702
|
-
`warning: publicEndpoint() on the built-in HTTP channel has no effect on hosting — only custom channels (/v1/channels/…) can be public; the session API always requires the alias token.\n`,
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
if (!ctx.json && publicRoutePaths.length > 0) {
|
|
706
|
-
// Security-relevant surface change: say exactly what will be reachable
|
|
707
|
-
// without the alias token before the deploy is accepted.
|
|
708
|
-
ctx.err(
|
|
709
|
-
` public: ${publicRoutePaths.join(", ")} (served WITHOUT the alias token — these channels authenticate requests themselves)\n`,
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
|
-
const deadSecretChannels = manifest?.publicChannelIdsWithDeadSecretPolicies ?? [];
|
|
713
|
-
if (!ctx.json && deadSecretChannels.length > 0) {
|
|
714
|
-
ctx.err(
|
|
715
|
-
`warning: ${deadSecretChannels
|
|
716
|
-
.map(channelId => `/v1/channels/${channelId}`)
|
|
717
|
-
.join(
|
|
718
|
-
", ",
|
|
719
|
-
)}: publicEndpoint() sits beside sharedSecretAuth()/hmacSignatureAuth() — auth chains are any-of, so every request is admitted and those secret policies never run. Drop publicEndpoint() to enforce them, or drop them to make the intent explicit.\n`,
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if (manifest?.architecture === "v2") {
|
|
724
|
-
if (!ctx.json) {
|
|
725
|
-
ctx.err(
|
|
726
|
-
` architecture: v2${manifest.persistent ? " (persistent)" : ""}${manifest.image === undefined ? "" : `\n image: ${manifest.image}`}\n`,
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
return deployOneAgentsdk(ctx, extras, {
|
|
730
|
-
persistent: manifest.persistent,
|
|
731
|
-
image: manifest.image,
|
|
732
|
-
slug,
|
|
733
|
-
gitRepoUrl,
|
|
734
|
-
gitRef,
|
|
735
|
-
agentPath,
|
|
736
|
-
repositories: options.cursorEventsRepos,
|
|
737
|
-
hasScmChannels: manifest.hasScmChannels,
|
|
738
|
-
declaredSecretNames: hosting?.secretNames,
|
|
739
|
-
hasDeploymentsChannels: manifest.hasDeploymentsChannels,
|
|
740
|
-
multiTenant: options.multiTenant === true,
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (options.multiTenant === true) {
|
|
745
|
-
ctx.err(
|
|
746
|
-
"--multi-tenant is only valid for architecture: v2 deploys (Cursor-managed products).\n",
|
|
747
|
-
);
|
|
748
|
-
return 1;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// Today's only source shape: the control plane builds from a git ref.
|
|
752
|
-
// See DeploySource for the artifact seam.
|
|
753
|
-
const source: DeploySource = {
|
|
754
|
-
kind: "git-ref",
|
|
755
|
-
gitRepoUrl,
|
|
756
|
-
...(gitRef === undefined ? {} : { gitRef }),
|
|
757
|
-
...(agentPath === undefined ? {} : { agentPath }),
|
|
758
|
-
...(options.cursorEventsRepos === undefined || options.cursorEventsRepos.length === 0
|
|
759
|
-
? {}
|
|
760
|
-
: { cursorEventRepos: options.cursorEventsRepos }),
|
|
761
|
-
...(egressAllowedDomains.length === 0 ? {} : { egressAllowedDomains }),
|
|
762
|
-
publicRoutePaths: publicRoutePaths.length === 0 ? undefined : publicRoutePaths,
|
|
763
|
-
// Deliberately NO manifest here: the control plane captures it from the
|
|
764
|
-
// provisioned engine (`GET /v1/manifest` after the health probe), which
|
|
765
|
-
// is the single write path for deploy manifests. `july manifest` remains
|
|
766
|
-
// available for local visibility only.
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
let accepted: DeployAccepted;
|
|
770
|
-
try {
|
|
771
|
-
accepted = await ctx.client.deploy({
|
|
772
|
-
teamId: ctx.teamId,
|
|
773
|
-
slug,
|
|
774
|
-
source,
|
|
775
|
-
template: readDeployTemplate(dir),
|
|
776
|
-
});
|
|
777
|
-
} catch (error) {
|
|
778
|
-
return renderApiError(error, ctx.err);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const aliasUrl = ctx.client.aliasUrl(accepted.engineAlias);
|
|
782
|
-
if (!ctx.json && accepted.aliasToken !== undefined) {
|
|
783
|
-
ctx.out(aliasTokenBlock(accepted.aliasToken));
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
if (ctx.noWait) {
|
|
787
|
-
if (extras?.jsonCollect !== undefined) {
|
|
788
|
-
extras.jsonCollect.push(accepted);
|
|
789
|
-
} else if (ctx.json) {
|
|
790
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
791
|
-
} else {
|
|
792
|
-
ctx.out(
|
|
793
|
-
`Deploy accepted: ${slug} (team ${ctx.teamId}, generation ${accepted.generation}, ${accepted.deploymentKind})\n` +
|
|
794
|
-
` alias: ${aliasUrl}\n` +
|
|
795
|
-
`Check progress with \`${CLI} deployment ${slug} --team ${ctx.teamId}\`.\n`,
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
return 0;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
let detail: DeploymentDetail;
|
|
802
|
-
try {
|
|
803
|
-
detail = await pollUntil(ctx, slug, TERMINAL_DEPLOY_STATUSES, () =>
|
|
804
|
-
ctx.client.getDeployment({ teamId: ctx.teamId, slug }),
|
|
805
|
-
);
|
|
806
|
-
} catch (error) {
|
|
807
|
-
return renderApiError(error, ctx.err);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
warnMissingSecrets(ctx, slug, hosting?.secretNames, detail.secretNames);
|
|
811
|
-
|
|
812
|
-
const payload =
|
|
813
|
-
accepted.aliasToken === undefined ? detail : { ...detail, aliasToken: accepted.aliasToken };
|
|
814
|
-
|
|
815
|
-
if (extras?.jsonCollect !== undefined) {
|
|
816
|
-
extras.jsonCollect.push(payload);
|
|
817
|
-
return detail.status === "running" ? 0 : 1;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
if (ctx.json) {
|
|
821
|
-
ctx.out(`${JSON.stringify(payload, null, 2)}\n`);
|
|
822
|
-
return detail.status === "running" ? 0 : 1;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (detail.status !== "running") {
|
|
826
|
-
ctx.err(`Deployment ${slug} ended ${detail.status}${deployFailureDetail(detail.lastError)}\n`);
|
|
827
|
-
return 1;
|
|
828
|
-
}
|
|
829
|
-
const deployedPublicRoutes = detail.publicRoutePaths ?? [];
|
|
830
|
-
ctx.out(
|
|
831
|
-
`Deployed ${slug} (team ${ctx.teamId})\n` +
|
|
832
|
-
` status: running\n` +
|
|
833
|
-
` generation: ${detail.generation}\n` +
|
|
834
|
-
` kind: ${detail.deploymentKind}\n` +
|
|
835
|
-
` alias: ${aliasUrl}\n` +
|
|
836
|
-
` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
|
|
837
|
-
` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n` +
|
|
838
|
-
// Paste-ready webhook URLs: these subtrees skip the alias token, so
|
|
839
|
-
// the channel handler must verify deliveries (webhook signatures).
|
|
840
|
-
deployedPublicRoutes
|
|
841
|
-
.map(path => ` public: ${aliasUrl}${path} (no alias token)\n`)
|
|
842
|
-
.join(""),
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
await runPostDeployConnectorAuth(ctx, options, {
|
|
846
|
-
slug,
|
|
847
|
-
connections: manifest?.cursorAccountConnections ?? [],
|
|
848
|
-
serviceAccountId: deploymentServiceAccountId(detail),
|
|
849
|
-
});
|
|
850
|
-
return 0;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Multi-agent parent whose children all declare `architecture: "v2"`:
|
|
855
|
-
* deploy ONE application whose release carries a component per child. The
|
|
856
|
-
* application slug derives from the parent directory name; each component
|
|
857
|
-
* key is the child's slug and its path is the child directory relative to
|
|
858
|
-
* the repo root (a shared --path names the parent's directory in the repo).
|
|
859
|
-
*/
|
|
860
|
-
async function deployAgentsdkApplication(
|
|
861
|
-
ctx: DeployCliContext,
|
|
862
|
-
options: DeployCliOptions,
|
|
863
|
-
targets: readonly DeployTarget[],
|
|
864
|
-
declarations: {
|
|
865
|
-
/** Per child slug: `persistent: true` on that agent's manifest. */
|
|
866
|
-
componentPersistent: Record<string, boolean>;
|
|
867
|
-
/** Per child slug: `hosting.image` on that agent's manifest. */
|
|
868
|
-
componentImage: Record<string, string>;
|
|
869
|
-
hasScmChannels: boolean;
|
|
870
|
-
declaredSecretNames: string[];
|
|
871
|
-
hasDeploymentsChannels: boolean;
|
|
872
|
-
},
|
|
873
|
-
): Promise<number> {
|
|
874
|
-
const parentDir = resolve(options.dir ?? ".");
|
|
875
|
-
const slug = slugFromDirectoryName(basename(parentDir));
|
|
876
|
-
if (slug === undefined) {
|
|
877
|
-
ctx.err(
|
|
878
|
-
`Could not derive an application slug from the parent directory name ${JSON.stringify(basename(parentDir))} — rename it (deploy slugs use lowercase letters, digits, '-' or '_').\n`,
|
|
879
|
-
);
|
|
880
|
-
return 1;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
const needsGitInference =
|
|
884
|
-
options.repo === undefined || options.ref === undefined || options.path === undefined;
|
|
885
|
-
const inferred = needsGitInference
|
|
886
|
-
? await inferDeployGitSource(parentDir, {
|
|
887
|
-
...(options.git === undefined ? {} : { git: options.git }),
|
|
888
|
-
})
|
|
889
|
-
: {};
|
|
890
|
-
const gitRepoUrl = options.repo ?? inferred.gitRepoUrl;
|
|
891
|
-
const gitRef = options.ref ?? inferred.gitRef;
|
|
892
|
-
const parentPath = options.path ?? inferred.agentPath;
|
|
893
|
-
|
|
894
|
-
if (gitRepoUrl !== undefined && !gitRepoUrl.startsWith("https://")) {
|
|
895
|
-
ctx.err(`Invalid --repo: ${gitRepoUrl} (expected an https:// git repository URL)\n`);
|
|
896
|
-
return 1;
|
|
897
|
-
}
|
|
898
|
-
if (gitRepoUrl === undefined) {
|
|
899
|
-
ctx.err(
|
|
900
|
-
`Could not resolve an https:// git repository URL for this deploy.\n` +
|
|
901
|
-
` Pass --repo https://github.com/<owner>/<repo> (inferred from origin when possible).\n`,
|
|
902
|
-
);
|
|
903
|
-
return 1;
|
|
904
|
-
}
|
|
905
|
-
for (const repo of options.cursorEventsRepos ?? []) {
|
|
906
|
-
if (!isValidCursorEventRepo(repo)) {
|
|
907
|
-
ctx.err(`Invalid --cursor-events-repo: ${repo} (expected owner/name)\n`);
|
|
908
|
-
return 1;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const components: AgentkitDeployComponent[] = targets.map(target => ({
|
|
913
|
-
componentKey: target.slug,
|
|
914
|
-
agentPath: [parentPath, basename(target.dir)]
|
|
915
|
-
.filter((part): part is string => part !== undefined && part !== "")
|
|
916
|
-
.join("/"),
|
|
917
|
-
persistent: declarations.componentPersistent[target.slug] === true,
|
|
918
|
-
image: declarations.componentImage[target.slug],
|
|
919
|
-
}));
|
|
920
|
-
|
|
921
|
-
if (!ctx.json) {
|
|
922
|
-
ctx.err(
|
|
923
|
-
`Deploying "${slug}" as one v2 application with ${targets.length} components (slug from the parent directory name).\n` +
|
|
924
|
-
` architecture: v2\n` +
|
|
925
|
-
` source: ${gitRepoUrl}${gitRef === undefined ? "" : ` @ ${gitRef}`}\n` +
|
|
926
|
-
components
|
|
927
|
-
.map(
|
|
928
|
-
component =>
|
|
929
|
-
` component: ${component.componentKey} (${component.agentPath ?? "."})${component.persistent ? " persistent" : ""}${component.image === undefined ? "" : ` image ${component.image}`}\n`,
|
|
930
|
-
)
|
|
931
|
-
.join(""),
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
return deployOneAgentsdk(ctx, undefined, {
|
|
936
|
-
// Multi-component: the flag travels on each component instead.
|
|
937
|
-
persistent: false,
|
|
938
|
-
slug,
|
|
939
|
-
gitRepoUrl,
|
|
940
|
-
gitRef,
|
|
941
|
-
agentPath: undefined,
|
|
942
|
-
components,
|
|
943
|
-
repositories: options.cursorEventsRepos,
|
|
944
|
-
hasScmChannels: declarations.hasScmChannels,
|
|
945
|
-
declaredSecretNames: declarations.declaredSecretNames,
|
|
946
|
-
hasDeploymentsChannels: declarations.hasDeploymentsChannels,
|
|
947
|
-
multiTenant: options.multiTenant === true,
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* `architecture: "v2"` deploy: POST `/internal/agentsdk/deployments` and
|
|
953
|
-
* poll the matching GET. The v2 accept path starts freeze bake; it does
|
|
954
|
-
* not mint an engine alias or alias token.
|
|
955
|
-
*/
|
|
956
|
-
async function deployOneAgentsdk(
|
|
957
|
-
ctx: DeployCliContext,
|
|
958
|
-
extras: { jsonCollect?: unknown[] } | undefined,
|
|
959
|
-
args: {
|
|
960
|
-
slug: string;
|
|
961
|
-
gitRepoUrl: string;
|
|
962
|
-
gitRef: string | undefined;
|
|
963
|
-
agentPath: string | undefined;
|
|
964
|
-
components?: AgentkitDeployComponent[];
|
|
965
|
-
repositories: string[] | undefined;
|
|
966
|
-
/** Any component declares a GitHub/Origin channel. */
|
|
967
|
-
hasScmChannels: boolean;
|
|
968
|
-
/** Union of application component secret declarations. */
|
|
969
|
-
declaredSecretNames?: string[];
|
|
970
|
-
/** Any component declares a deployments channel (same repo scope). */
|
|
971
|
-
hasDeploymentsChannels?: boolean;
|
|
972
|
-
/** Redeploy an existing Cursor-managed product. */
|
|
973
|
-
multiTenant: boolean;
|
|
974
|
-
/**
|
|
975
|
-
* `persistent: true` in a single agent's manifest. Multi-component
|
|
976
|
-
* deploys pass `false` here and flag each component instead.
|
|
977
|
-
*/
|
|
978
|
-
persistent: boolean;
|
|
979
|
-
/** `hosting.image` of a single agent; components carry their own. */
|
|
980
|
-
image?: string;
|
|
981
|
-
},
|
|
982
|
-
): Promise<number> {
|
|
983
|
-
const { slug } = args;
|
|
984
|
-
const componentsLine =
|
|
985
|
-
args.components === undefined
|
|
986
|
-
? ""
|
|
987
|
-
: ` components: ${args.components.map(component => component.componentKey).join(", ")}\n`;
|
|
988
|
-
if (args.multiTenant && !ctx.json) {
|
|
989
|
-
ctx.err(" multi-tenant: existing product\n");
|
|
990
|
-
}
|
|
991
|
-
let accepted: AgentkitDeployAccepted;
|
|
992
|
-
try {
|
|
993
|
-
accepted = await ctx.client.deployAgentkit({
|
|
994
|
-
teamId: ctx.teamId,
|
|
995
|
-
slug,
|
|
996
|
-
gitRepoUrl: args.gitRepoUrl,
|
|
997
|
-
gitRef: args.gitRef,
|
|
998
|
-
agentPath: args.agentPath,
|
|
999
|
-
components: args.components,
|
|
1000
|
-
repositories: args.repositories,
|
|
1001
|
-
...(args.multiTenant ? { multiTenant: true } : {}),
|
|
1002
|
-
persistent: args.persistent,
|
|
1003
|
-
image: args.image,
|
|
1004
|
-
});
|
|
1005
|
-
} catch (error) {
|
|
1006
|
-
return renderApiError(error, ctx.err, {
|
|
1007
|
-
multiTenant: args.multiTenant,
|
|
1008
|
-
teamId: ctx.teamId,
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (ctx.noWait) {
|
|
1013
|
-
if (extras?.jsonCollect !== undefined) {
|
|
1014
|
-
extras.jsonCollect.push(accepted);
|
|
1015
|
-
} else if (ctx.json) {
|
|
1016
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1017
|
-
} else {
|
|
1018
|
-
ctx.out(
|
|
1019
|
-
`Deploy accepted: ${slug} (team ${ctx.teamId}, v2)\n` +
|
|
1020
|
-
` application: ${accepted.applicationId}\n` +
|
|
1021
|
-
` release: ${accepted.releaseId}\n` +
|
|
1022
|
-
componentsLine,
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
return 0;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
let detail: AgentkitDeployStatus;
|
|
1029
|
-
try {
|
|
1030
|
-
detail = await pollUntil(ctx, slug, TERMINAL_AGENTKIT_DEPLOY_STATUSES, () =>
|
|
1031
|
-
ctx.client.getAgentkitDeploy({ teamId: ctx.teamId, slug }),
|
|
1032
|
-
);
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
return renderApiError(error, ctx.err);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
if (extras?.jsonCollect !== undefined) {
|
|
1038
|
-
extras.jsonCollect.push(detail);
|
|
1039
|
-
return detail.status === "running" ? 0 : 1;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if (ctx.json) {
|
|
1043
|
-
ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
|
|
1044
|
-
return detail.status === "running" ? 0 : 1;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
if (detail.status !== "running") {
|
|
1048
|
-
ctx.err(`Deployment ${slug} ended ${detail.status}${deployFailureDetail(detail.lastError)}\n`);
|
|
1049
|
-
return 1;
|
|
1050
|
-
}
|
|
1051
|
-
warnMissingSecrets(ctx, slug, args.declaredSecretNames, detail.secretNames);
|
|
1052
|
-
const watchesRepositories = args.hasScmChannels || args.hasDeploymentsChannels === true;
|
|
1053
|
-
ctx.out(
|
|
1054
|
-
`Deployed ${slug} (team ${ctx.teamId})\n` +
|
|
1055
|
-
` status: running\n` +
|
|
1056
|
-
` architecture: v2\n` +
|
|
1057
|
-
` application: ${detail.applicationId}\n` +
|
|
1058
|
-
` release: ${detail.releaseId}\n` +
|
|
1059
|
-
(args.components === undefined
|
|
1060
|
-
? ""
|
|
1061
|
-
: ` components: ${args.components
|
|
1062
|
-
.map(component => component.componentKey)
|
|
1063
|
-
.join(", ")}\n`) +
|
|
1064
|
-
(detail.repositories === undefined || !watchesRepositories
|
|
1065
|
-
? ""
|
|
1066
|
-
: ` watching: ${detail.repositories.length === 0 ? "(nothing)" : detail.repositories.join(", ")}\n`),
|
|
1067
|
-
);
|
|
1068
|
-
// An agent with GitHub/Origin/deployments channels bound to no repository
|
|
1069
|
-
// scope gets no deliveries at all (deployments channels receive the
|
|
1070
|
-
// watched repos' deployment events), which is otherwise invisible here.
|
|
1071
|
-
if (watchesRepositories && detail.repositories?.length === 0) {
|
|
1072
|
-
ctx.err(
|
|
1073
|
-
`warning: ${slug} watches no repositories, so its GitHub/Origin/deployments channels receive no events.\n` +
|
|
1074
|
-
` Redeploy with --cursor-events-repo <owner>/<name> to set the scope.\n`,
|
|
1075
|
-
);
|
|
1076
|
-
}
|
|
1077
|
-
return 0;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/**
|
|
1081
|
-
* Post-deploy connector authorization for `cursorAccount` connections: the
|
|
1082
|
-
* agent's connectors execute with tokens owned by the deployment's service
|
|
1083
|
-
* account, which start out unauthorized. On a TTY this runs the dashboard's
|
|
1084
|
-
* Authenticate flow inline (browser OAuth per connector, already-authorized
|
|
1085
|
-
* ones skipped); otherwise it reports status only. Never affects the deploy
|
|
1086
|
-
* outcome — anything left unauthorized just gets a playground-page pointer.
|
|
1087
|
-
*/
|
|
1088
|
-
async function runPostDeployConnectorAuth(
|
|
1089
|
-
ctx: DeployCliContext,
|
|
1090
|
-
options: DeployCliOptions,
|
|
1091
|
-
args: {
|
|
1092
|
-
slug: string;
|
|
1093
|
-
connections: DeployManifest["cursorAccountConnections"];
|
|
1094
|
-
serviceAccountId: string | undefined;
|
|
1095
|
-
},
|
|
1096
|
-
): Promise<void> {
|
|
1097
|
-
if (args.connections.length === 0) {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
const { serviceAccountId } = args;
|
|
1102
|
-
if (serviceAccountId === undefined) {
|
|
1103
|
-
ctx.err(
|
|
1104
|
-
`warning: this agent uses Cursor account connectors, but the deployment has no service account; redeploy to mint one.\n`,
|
|
1105
|
-
);
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
const backendClient = new CursorBackendClient({
|
|
1110
|
-
backendUrl: ctx.backendUrl,
|
|
1111
|
-
apiKey: ctx.apiKey,
|
|
1112
|
-
fetchImpl: options.fetchImpl,
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
const interactive =
|
|
1116
|
-
options.openBrowserImpl !== undefined ||
|
|
1117
|
-
(process.stdout.isTTY === true && process.stdin.isTTY === true);
|
|
1118
|
-
|
|
1119
|
-
let anyUnauthorized = false;
|
|
1120
|
-
for (const ref of args.connections) {
|
|
1121
|
-
ctx.out(
|
|
1122
|
-
`\nThis agent uses Cursor account connectors (connection "${ref.name}"${
|
|
1123
|
-
ref.servers === undefined || ref.servers.length === 0 ? "" : `: ${ref.servers.join(", ")}`
|
|
1124
|
-
}).\n`,
|
|
1125
|
-
);
|
|
1126
|
-
try {
|
|
1127
|
-
const result = await authorizeCursorAccountConnectors({
|
|
1128
|
-
backendClient,
|
|
1129
|
-
teamId: ctx.teamId,
|
|
1130
|
-
slug: args.slug,
|
|
1131
|
-
serviceAccountId,
|
|
1132
|
-
connectionName: ref.name,
|
|
1133
|
-
servers: ref.servers,
|
|
1134
|
-
interactive,
|
|
1135
|
-
out: ctx.out,
|
|
1136
|
-
err: ctx.err,
|
|
1137
|
-
openBrowserImpl: options.openBrowserImpl,
|
|
1138
|
-
sleep: options.sleepImpl,
|
|
1139
|
-
pollIntervalMs: options.connectorPollIntervalMs,
|
|
1140
|
-
pollTimeoutMs: options.connectorPollTimeoutMs,
|
|
1141
|
-
});
|
|
1142
|
-
if (result.pending.length > 0 || result.errored.length > 0) {
|
|
1143
|
-
anyUnauthorized = true;
|
|
1144
|
-
if (result.pending.length > 0) {
|
|
1145
|
-
ctx.out(`Connectors not authorized yet: ${result.pending.join(", ")}.\n`);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
anyUnauthorized = true;
|
|
1150
|
-
ctx.err(
|
|
1151
|
-
`warning: could not check connector authorization for "${ref.name}": ${describeError(error)}\n`,
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
if (anyUnauthorized) {
|
|
1157
|
-
ctx.out(
|
|
1158
|
-
`You can authorize connectors at the playground page (cursor.com dashboard → Deployed Agents → ${args.slug}) or with \`${CLI} mcp oauth <connection>\`.\n`,
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
interface DeployManifest {
|
|
1164
|
-
hosting: AgentHostingOptions | undefined;
|
|
1165
|
-
architecture: AgentArchitecture | undefined;
|
|
1166
|
-
persistent: boolean;
|
|
1167
|
-
/** `hosting.image`, the team container image this agent runs in. */
|
|
1168
|
-
image: string | undefined;
|
|
1169
|
-
/** `cursorAccount` connections declared by the agent. */
|
|
1170
|
-
cursorAccountConnections: Array<{
|
|
1171
|
-
name: string;
|
|
1172
|
-
servers: string[] | undefined;
|
|
1173
|
-
}>;
|
|
1174
|
-
/**
|
|
1175
|
-
* Channels whose authored auth chain contains `publicEndpoint()`: their
|
|
1176
|
-
* `/v1/channels/<id>` subtree is served through the stable alias WITHOUT
|
|
1177
|
-
* `X-Agent-Alias-Token` (the channel authenticates deliveries itself).
|
|
1178
|
-
*/
|
|
1179
|
-
publicChannelIds: string[];
|
|
1180
|
-
/**
|
|
1181
|
-
* Public channels whose auth chain also carries `sharedSecretAuth` /
|
|
1182
|
-
* `hmacSignatureAuth`. Chains are any-of, so `publicEndpoint()` admits
|
|
1183
|
-
* every request and those secret policies never gate anything — locally
|
|
1184
|
-
* or on the control plane; surfaced as a warning.
|
|
1185
|
-
*/
|
|
1186
|
-
publicChannelIdsWithDeadSecretPolicies: string[];
|
|
1187
|
-
/**
|
|
1188
|
-
* `publicEndpoint()` on the built-in HTTP channel — a no-op on hosting
|
|
1189
|
-
* (only `/v1/channels/…` subtrees can be public); surfaced as a warning.
|
|
1190
|
-
*/
|
|
1191
|
-
httpChannelPublicEndpoint: boolean;
|
|
1192
|
-
/**
|
|
1193
|
-
* The agent has GitHub/Origin channels, so it only receives deliveries
|
|
1194
|
-
* while the deployment is bound to at least one `owner/name` scope.
|
|
1195
|
-
*/
|
|
1196
|
-
hasScmChannels: boolean;
|
|
1197
|
-
/**
|
|
1198
|
-
* The agent has deployments channels; deploy-source edges derive from
|
|
1199
|
-
* the same watched-repo set as GitHub/Origin, so it too receives
|
|
1200
|
-
* nothing while the deployment watches no repositories.
|
|
1201
|
-
*/
|
|
1202
|
-
hasDeploymentsChannels: boolean;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Read the deploy-relevant bits of the project manifest at `dir` in one
|
|
1207
|
-
* load: the `hosting` block, the agent's cursorAccount connections, and the
|
|
1208
|
-
* channels declared public via `publicEndpoint()`.
|
|
1209
|
-
*/
|
|
1210
|
-
async function readDeployManifest(dir: string): Promise<DeployManifest> {
|
|
1211
|
-
const project = await loadAgentProject(dir);
|
|
1212
|
-
// Fail here, before anything is dispatched: a project with error
|
|
1213
|
-
// diagnostics (e.g. agent/storage.ts on architecture v2) would only be
|
|
1214
|
-
// refused later by `serve` inside the hosted build, wasting a bake.
|
|
1215
|
-
if (hasErrorDiagnostics(project)) {
|
|
1216
|
-
throw new Error(
|
|
1217
|
-
`Refusing to deploy ${project.name} — it has errors:\n${formatDiagnostics(
|
|
1218
|
-
project.diagnostics.filter(d => d.severity === "error"),
|
|
1219
|
-
)}`,
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
1222
|
-
return {
|
|
1223
|
-
hosting: project.agent.hosting,
|
|
1224
|
-
architecture: project.agent.architecture,
|
|
1225
|
-
persistent: project.agent.persistent === true,
|
|
1226
|
-
image: project.agent.hosting?.image,
|
|
1227
|
-
cursorAccountConnections: project.agent.connections.flatMap(connection =>
|
|
1228
|
-
isCursorAccountTransport(connection.transport)
|
|
1229
|
-
? [{ name: connection.name, servers: connection.transport.servers }]
|
|
1230
|
-
: [],
|
|
1231
|
-
),
|
|
1232
|
-
publicChannelIds: project.channels
|
|
1233
|
-
.filter(channel => (channel.definition.auth ?? []).some(isPublicEndpointPolicy))
|
|
1234
|
-
.map(channel => channel.id),
|
|
1235
|
-
publicChannelIdsWithDeadSecretPolicies: project.channels
|
|
1236
|
-
.filter(channel => {
|
|
1237
|
-
const auth = channel.definition.auth ?? [];
|
|
1238
|
-
return (
|
|
1239
|
-
auth.some(isPublicEndpointPolicy) &&
|
|
1240
|
-
auth.some(policy => {
|
|
1241
|
-
const kind = catchAuthDeclaration(policy)?.kind;
|
|
1242
|
-
return kind !== undefined && kind !== "public";
|
|
1243
|
-
})
|
|
1244
|
-
);
|
|
1245
|
-
})
|
|
1246
|
-
.map(channel => channel.id),
|
|
1247
|
-
httpChannelPublicEndpoint: (project.httpChannel.auth ?? []).some(isPublicEndpointPolicy),
|
|
1248
|
-
hasScmChannels: project.channels.some(channel => {
|
|
1249
|
-
const kind = channel.definition.meta?.kind;
|
|
1250
|
-
return kind === "github" || kind === "origin";
|
|
1251
|
-
}),
|
|
1252
|
-
hasDeploymentsChannels: project.channels.some(
|
|
1253
|
-
channel => channel.definition.meta?.kind === "deployments",
|
|
1254
|
-
),
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Friendly post-deploy nudge: the manifest declares secrets the agent
|
|
1260
|
-
* expects, but the deployment does not have them all set yet.
|
|
1261
|
-
*/
|
|
1262
|
-
function warnMissingSecrets(
|
|
1263
|
-
ctx: DeployCliContext,
|
|
1264
|
-
slug: string,
|
|
1265
|
-
declared: string[] | undefined,
|
|
1266
|
-
set: string[] | undefined,
|
|
1267
|
-
): void {
|
|
1268
|
-
if (declared === undefined || declared.length === 0) {
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
const setNames = new Set(set ?? []);
|
|
1272
|
-
// Platform-managed names (CURSOR_*, hosted storage) are injected by the
|
|
1273
|
-
// control plane and never appear in secrets list — do not warn on them.
|
|
1274
|
-
const missing = declared.filter(name => !setNames.has(name) && !isReservedSecretName(name));
|
|
1275
|
-
if (missing.length === 0) {
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
ctx.err(
|
|
1279
|
-
`warning: secrets declared in the agent manifest but not set on ${slug}: ${missing.join(", ")}\n` +
|
|
1280
|
-
` set them with \`${CLI} secrets set ${slug} ${missing.join(" ")}\`\n`,
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// ============================================================================
|
|
1285
|
-
// deployments (list)
|
|
1286
|
-
// ============================================================================
|
|
1287
|
-
|
|
1288
|
-
export async function cmdDeployments(options: DeployCliOptions): Promise<number> {
|
|
1289
|
-
const ctx = await prepare(options);
|
|
1290
|
-
if (typeof ctx === "number") {
|
|
1291
|
-
return ctx;
|
|
1292
|
-
}
|
|
1293
|
-
let deployments: Deployment[];
|
|
1294
|
-
try {
|
|
1295
|
-
deployments = await ctx.client.listDeployments({ teamId: ctx.teamId });
|
|
1296
|
-
} catch (error) {
|
|
1297
|
-
return renderApiError(error, ctx.err);
|
|
1298
|
-
}
|
|
1299
|
-
let applications: AgentkitDeploymentListItem[] = [];
|
|
1300
|
-
try {
|
|
1301
|
-
applications = await ctx.client.listAgentkitDeployments({
|
|
1302
|
-
teamId: ctx.teamId,
|
|
1303
|
-
});
|
|
1304
|
-
} catch (error) {
|
|
1305
|
-
// Older control planes 404 this route; keep listing v1.
|
|
1306
|
-
if (!(error instanceof DeployApiError && error.status === 404)) {
|
|
1307
|
-
return renderApiError(error, ctx.err);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
if (ctx.json) {
|
|
1311
|
-
// Empty v2 list keeps `{ deployments }` for v1-only parsers.
|
|
1312
|
-
ctx.out(
|
|
1313
|
-
`${JSON.stringify(
|
|
1314
|
-
applications.length === 0 ? { deployments } : { deployments, applications },
|
|
1315
|
-
null,
|
|
1316
|
-
2,
|
|
1317
|
-
)}\n`,
|
|
1318
|
-
);
|
|
1319
|
-
return 0;
|
|
1320
|
-
}
|
|
1321
|
-
if (deployments.length === 0 && applications.length === 0) {
|
|
1322
|
-
ctx.out(`No deployments on team ${ctx.teamId}. Create one with \`${CLI} deploy\`.\n`);
|
|
1323
|
-
return 0;
|
|
1324
|
-
}
|
|
1325
|
-
ctx.out(
|
|
1326
|
-
`${renderTable(
|
|
1327
|
-
["SLUG", "ARCH", "STATUS", "GEN", "KIND", "UPDATED"],
|
|
1328
|
-
[
|
|
1329
|
-
...deployments.map(d => [
|
|
1330
|
-
d.slug,
|
|
1331
|
-
"v1",
|
|
1332
|
-
d.status,
|
|
1333
|
-
String(d.generation),
|
|
1334
|
-
d.deploymentKind,
|
|
1335
|
-
d.desiredUpdatedAt ?? "",
|
|
1336
|
-
]),
|
|
1337
|
-
...applications.map(application => [
|
|
1338
|
-
application.slug,
|
|
1339
|
-
"v2",
|
|
1340
|
-
application.status,
|
|
1341
|
-
"-",
|
|
1342
|
-
"application",
|
|
1343
|
-
application.deployments
|
|
1344
|
-
.map(row => row.desiredUpdatedAt)
|
|
1345
|
-
.sort()
|
|
1346
|
-
.at(-1) ?? "",
|
|
1347
|
-
]),
|
|
1348
|
-
],
|
|
1349
|
-
)}\n`,
|
|
1350
|
-
);
|
|
1351
|
-
return 0;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
// ============================================================================
|
|
1355
|
-
// deployment <slug> (detail)
|
|
1356
|
-
// ============================================================================
|
|
1357
|
-
|
|
1358
|
-
export async function cmdDeploymentStatus(
|
|
1359
|
-
slug: string | undefined,
|
|
1360
|
-
options: DeployCliOptions,
|
|
1361
|
-
): Promise<number> {
|
|
1362
|
-
if (slug === undefined || slug === "") {
|
|
1363
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1364
|
-
`Usage: ${CLI} deployment <slug> [--team <id>] [--json]\n`,
|
|
1365
|
-
);
|
|
1366
|
-
return 1;
|
|
1367
|
-
}
|
|
1368
|
-
const ctx = await prepare(options);
|
|
1369
|
-
if (typeof ctx === "number") {
|
|
1370
|
-
return ctx;
|
|
1371
|
-
}
|
|
1372
|
-
// Same dispatch as `stop`: v1 409s architecture-v2 slugs, so the v2
|
|
1373
|
-
// GET goes first. A v2 404 (missing slug, or an older backend) falls
|
|
1374
|
-
// through to the v1 agent-serve detail.
|
|
1375
|
-
try {
|
|
1376
|
-
const detail = await ctx.client.getAgentkitDeploy({
|
|
1377
|
-
teamId: ctx.teamId,
|
|
1378
|
-
slug,
|
|
1379
|
-
});
|
|
1380
|
-
renderAgentkitDeploymentStatus(ctx, detail);
|
|
1381
|
-
return 0;
|
|
1382
|
-
} catch (error) {
|
|
1383
|
-
if (!(error instanceof DeployApiError) || error.status !== 404) {
|
|
1384
|
-
return renderApiError(error, ctx.err);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
let detail: DeploymentDetail;
|
|
1388
|
-
try {
|
|
1389
|
-
detail = await ctx.client.getDeployment({ teamId: ctx.teamId, slug });
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
return renderApiError(error, ctx.err);
|
|
1392
|
-
}
|
|
1393
|
-
if (ctx.json) {
|
|
1394
|
-
ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
|
|
1395
|
-
return 0;
|
|
1396
|
-
}
|
|
1397
|
-
const aliasUrl = ctx.client.aliasUrl(detail.engineAlias);
|
|
1398
|
-
const lines = [`${detail.slug} (team ${detail.teamId})`, ` status: ${detail.status}`];
|
|
1399
|
-
if (detail.progress !== undefined) {
|
|
1400
|
-
lines.push(` progress: ${detail.progress}`);
|
|
1401
|
-
}
|
|
1402
|
-
if (
|
|
1403
|
-
detail.statusMessage !== null &&
|
|
1404
|
-
detail.statusMessage !== undefined &&
|
|
1405
|
-
detail.statusMessage.trim() !== ""
|
|
1406
|
-
) {
|
|
1407
|
-
lines.push(` step: ${detail.statusMessage}`);
|
|
1408
|
-
}
|
|
1409
|
-
lines.push(
|
|
1410
|
-
` generation: ${detail.generation}`,
|
|
1411
|
-
` kind: ${detail.deploymentKind}`,
|
|
1412
|
-
` alias: ${aliasUrl}`,
|
|
1413
|
-
` playground: ${aliasUrl}/playground`,
|
|
1414
|
-
);
|
|
1415
|
-
if (detail.gitRepoUrl !== null && detail.gitRepoUrl !== undefined) {
|
|
1416
|
-
lines.push(` repo: ${detail.gitRepoUrl} @ ${detail.gitRef ?? ""}`);
|
|
1417
|
-
}
|
|
1418
|
-
if (detail.serviceAccountId !== undefined && detail.serviceAccountId !== null) {
|
|
1419
|
-
lines.push(` service account: ${detail.serviceAccountId}`);
|
|
1420
|
-
}
|
|
1421
|
-
if (detail.agentStoreSourceId !== undefined) {
|
|
1422
|
-
// The durable file store's address: GET /v0/agent-serve-files/<id> lists
|
|
1423
|
-
// it, and the pod sees it as AGENT_SERVE_STORE_SOURCE_ID.
|
|
1424
|
-
lines.push(` file store: ${detail.agentStoreSourceId}`);
|
|
1425
|
-
}
|
|
1426
|
-
if (detail.egressAllowedDomains !== undefined && detail.egressAllowedDomains.length > 0) {
|
|
1427
|
-
lines.push(` egress: ${detail.egressAllowedDomains.join(", ")}`);
|
|
1428
|
-
}
|
|
1429
|
-
if (detail.publicRoutePaths !== undefined && detail.publicRoutePaths.length > 0) {
|
|
1430
|
-
// Served through the alias WITHOUT X-Agent-Alias-Token (the channel
|
|
1431
|
-
// authenticates deliveries itself).
|
|
1432
|
-
for (const path of detail.publicRoutePaths) {
|
|
1433
|
-
lines.push(` public: ${aliasUrl}${path} (no alias token)`);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
if (detail.secretNames !== undefined && detail.secretNames.length > 0) {
|
|
1437
|
-
lines.push(` secrets: ${detail.secretNames.join(", ")}`);
|
|
1438
|
-
}
|
|
1439
|
-
if (detail.podCredentialMaskedKey !== undefined && detail.podCredentialMaskedKey !== null) {
|
|
1440
|
-
lines.push(` pod credential: ${detail.podCredentialMaskedKey}`);
|
|
1441
|
-
}
|
|
1442
|
-
if (detail.engineUrl !== null && detail.engineUrl !== undefined) {
|
|
1443
|
-
lines.push(` engine: ${detail.engineUrl}`);
|
|
1444
|
-
}
|
|
1445
|
-
if (
|
|
1446
|
-
detail.engineAccess !== null &&
|
|
1447
|
-
detail.engineAccess !== undefined &&
|
|
1448
|
-
typeof detail.engineAccess === "object"
|
|
1449
|
-
) {
|
|
1450
|
-
lines.push(
|
|
1451
|
-
` engine access: ${detail.engineAccess.url} (expires ${detail.engineAccess.expiresAt})`,
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
if (detail.lastError !== null && detail.lastError !== undefined) {
|
|
1455
|
-
lines.push(` last error: ${sanitizeCustomerError(detail.lastError)}`);
|
|
1456
|
-
}
|
|
1457
|
-
if (detail.desiredUpdatedAt !== undefined) {
|
|
1458
|
-
lines.push(` updated: ${detail.desiredUpdatedAt}`);
|
|
1459
|
-
}
|
|
1460
|
-
ctx.out(`${lines.join("\n")}\n`);
|
|
1461
|
-
return 0;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
function renderAgentkitDeploymentStatus(ctx: DeployCliContext, detail: AgentkitDeployStatus): void {
|
|
1465
|
-
if (ctx.json) {
|
|
1466
|
-
ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
|
|
1467
|
-
return;
|
|
1468
|
-
}
|
|
1469
|
-
const lines = [
|
|
1470
|
-
`${detail.slug} (team ${detail.teamId})`,
|
|
1471
|
-
` status: ${detail.status}`,
|
|
1472
|
-
` architecture: v2`,
|
|
1473
|
-
` application: ${detail.applicationId}`,
|
|
1474
|
-
` release: ${detail.releaseId}`,
|
|
1475
|
-
` progress: ${detail.progress}`,
|
|
1476
|
-
];
|
|
1477
|
-
if (detail.statusMessage !== null && detail.statusMessage.trim() !== "") {
|
|
1478
|
-
lines.push(` step: ${detail.statusMessage}`);
|
|
1479
|
-
}
|
|
1480
|
-
if (detail.repositories !== undefined) {
|
|
1481
|
-
lines.push(
|
|
1482
|
-
` watching: ${
|
|
1483
|
-
detail.repositories.length === 0 ? "(nothing)" : detail.repositories.join(", ")
|
|
1484
|
-
}`,
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
if (detail.secretNames !== undefined && detail.secretNames.length > 0) {
|
|
1488
|
-
lines.push(` secrets: ${detail.secretNames.join(", ")}`);
|
|
1489
|
-
}
|
|
1490
|
-
if (detail.lastError !== null && detail.lastError !== undefined) {
|
|
1491
|
-
lines.push(` last error: ${sanitizeCustomerError(detail.lastError)}`);
|
|
1492
|
-
}
|
|
1493
|
-
ctx.out(`${lines.join("\n")}\n`);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
// ============================================================================
|
|
1497
|
-
// stop <slug>
|
|
1498
|
-
// ============================================================================
|
|
1499
|
-
|
|
1500
|
-
export async function cmdStop(
|
|
1501
|
-
slug: string | undefined,
|
|
1502
|
-
options: DeployCliOptions,
|
|
1503
|
-
): Promise<number> {
|
|
1504
|
-
if (slug === undefined || slug === "") {
|
|
1505
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1506
|
-
`Usage: ${CLI} stop <slug> [--team <id>] [--json] [--no-wait]\n`,
|
|
1507
|
-
);
|
|
1508
|
-
return 1;
|
|
1509
|
-
}
|
|
1510
|
-
const ctx = await prepare(options);
|
|
1511
|
-
if (typeof ctx === "number") {
|
|
1512
|
-
return ctx;
|
|
1513
|
-
}
|
|
1514
|
-
// Stop takes only a slug, so there is no manifest to dispatch on (unlike
|
|
1515
|
-
// deploy). The v2 agentsdk control plane goes first: v1 endpoints refuse
|
|
1516
|
-
// `architecture: "v2"` slugs with a 409 rather than a 404, so v1-first
|
|
1517
|
-
// could not fall through. A v2 404 (including an older backend without
|
|
1518
|
-
// the `/internal/agentsdk` mount) falls back to the v1 stop.
|
|
1519
|
-
let architecture: "v1" | "v2" = "v2";
|
|
1520
|
-
let accepted: StopAccepted | AgentkitStopAccepted;
|
|
1521
|
-
try {
|
|
1522
|
-
accepted = await ctx.client.stopAgentkit({
|
|
1523
|
-
teamId: ctx.teamId,
|
|
1524
|
-
slug,
|
|
1525
|
-
cancelRuns: options.cancelRuns,
|
|
1526
|
-
});
|
|
1527
|
-
} catch (error) {
|
|
1528
|
-
if (!(error instanceof DeployApiError) || error.status !== 404) {
|
|
1529
|
-
return renderApiError(error, ctx.err);
|
|
1530
|
-
}
|
|
1531
|
-
if (options.cancelRuns === true) {
|
|
1532
|
-
ctx.err(
|
|
1533
|
-
`Canceling active runs while stopping is supported only for Agent SDK v2 deployments.\n`,
|
|
1534
|
-
);
|
|
1535
|
-
return 1;
|
|
1536
|
-
}
|
|
1537
|
-
try {
|
|
1538
|
-
accepted = await ctx.client.stopDeployment({ teamId: ctx.teamId, slug });
|
|
1539
|
-
architecture = "v1";
|
|
1540
|
-
} catch (v1Error) {
|
|
1541
|
-
return renderApiError(v1Error, ctx.err);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
const cancellation = "cancellation" in accepted ? accepted.cancellation : undefined;
|
|
1545
|
-
const stoppedGlobally = "scope" in accepted && accepted.scope === "global";
|
|
1546
|
-
if (ctx.noWait) {
|
|
1547
|
-
if (ctx.json) {
|
|
1548
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1549
|
-
} else {
|
|
1550
|
-
ctx.out(
|
|
1551
|
-
stoppedGlobally
|
|
1552
|
-
? `Stop accepted for ${slug} across all tenants (status: ${accepted.status}).\n`
|
|
1553
|
-
: `Stop accepted for ${slug} (status: ${accepted.status}).\n`,
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
return cancellation?.failed === 0 || cancellation === undefined ? 0 : 1;
|
|
1557
|
-
}
|
|
1558
|
-
let detail: DeploymentDetail | AgentkitDeployStatus;
|
|
1559
|
-
try {
|
|
1560
|
-
detail =
|
|
1561
|
-
architecture === "v2"
|
|
1562
|
-
? await pollUntil(ctx, slug, new Set(["stopped"]), () =>
|
|
1563
|
-
ctx.client.getAgentkitDeploy({ teamId: ctx.teamId, slug }),
|
|
1564
|
-
)
|
|
1565
|
-
: await pollUntil(ctx, slug, new Set(["stopped"]), () =>
|
|
1566
|
-
ctx.client.getDeployment({ teamId: ctx.teamId, slug }),
|
|
1567
|
-
);
|
|
1568
|
-
} catch (error) {
|
|
1569
|
-
return renderApiError(error, ctx.err);
|
|
1570
|
-
}
|
|
1571
|
-
if (ctx.json) {
|
|
1572
|
-
ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
|
|
1573
|
-
} else if (stoppedGlobally) {
|
|
1574
|
-
ctx.out(
|
|
1575
|
-
`Stopped ${slug} across all tenants (team ${ctx.teamId}); resume with \`${CLI} start ${slug}\`.\n`,
|
|
1576
|
-
);
|
|
1577
|
-
} else {
|
|
1578
|
-
ctx.out(`Stopped ${slug} (team ${ctx.teamId}).\n`);
|
|
1579
|
-
}
|
|
1580
|
-
if (cancellation !== undefined) {
|
|
1581
|
-
return writeCancellationStarted(ctx, cancellation, slug);
|
|
1582
|
-
}
|
|
1583
|
-
return 0;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
function writeCancellationStarted(
|
|
1587
|
-
ctx: { out: (text: string) => void; err: (text: string) => void },
|
|
1588
|
-
cancellation: AgentkitCancelRunsResult,
|
|
1589
|
-
slug: string,
|
|
1590
|
-
): number {
|
|
1591
|
-
ctx.out(`Started cancellation across ${cancellation.batchesStarted} deployment batch(es).\n`);
|
|
1592
|
-
if (cancellation.sessionsMarkedFailed !== undefined) {
|
|
1593
|
-
ctx.out(`Marked ${cancellation.sessionsMarkedFailed} factory session(s) failed.\n`);
|
|
1594
|
-
}
|
|
1595
|
-
if (cancellation.failed > 0) {
|
|
1596
|
-
ctx.err(
|
|
1597
|
-
`Failed to start ${cancellation.failed} cancellation batch(es); retry with \`${CLI} cancel-runs ${slug}\`.\n`,
|
|
1598
|
-
);
|
|
1599
|
-
return 1;
|
|
1600
|
-
}
|
|
1601
|
-
return 0;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
export async function cmdStart(
|
|
1605
|
-
slug: string | undefined,
|
|
1606
|
-
options: DeployCliOptions,
|
|
1607
|
-
): Promise<number> {
|
|
1608
|
-
if (slug === undefined || slug === "") {
|
|
1609
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1610
|
-
`Usage: ${CLI} start <slug> [--team <id>] [--json]\n`,
|
|
1611
|
-
);
|
|
1612
|
-
return 1;
|
|
1613
|
-
}
|
|
1614
|
-
const ctx = await prepare(options);
|
|
1615
|
-
if (typeof ctx === "number") {
|
|
1616
|
-
return ctx;
|
|
1617
|
-
}
|
|
1618
|
-
try {
|
|
1619
|
-
const accepted = await ctx.client.startAgentkit({
|
|
1620
|
-
teamId: ctx.teamId,
|
|
1621
|
-
slug,
|
|
1622
|
-
});
|
|
1623
|
-
if (ctx.json) {
|
|
1624
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1625
|
-
} else {
|
|
1626
|
-
ctx.out(`Started ${slug} (team ${ctx.teamId}).\n`);
|
|
1627
|
-
}
|
|
1628
|
-
return 0;
|
|
1629
|
-
} catch (error) {
|
|
1630
|
-
return renderApiError(error, ctx.err);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
export async function cmdKill(
|
|
1635
|
-
slug: string | undefined,
|
|
1636
|
-
options: DeployCliOptions,
|
|
1637
|
-
): Promise<number> {
|
|
1638
|
-
if (slug === undefined || slug === "") {
|
|
1639
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1640
|
-
`Usage: ${CLI} kill <slug> [--team <id>] [--json]\n`,
|
|
1641
|
-
);
|
|
1642
|
-
return 1;
|
|
1643
|
-
}
|
|
1644
|
-
const ctx = await prepare(options);
|
|
1645
|
-
if (typeof ctx === "number") {
|
|
1646
|
-
return ctx;
|
|
1647
|
-
}
|
|
1648
|
-
let accepted: AgentkitStopAccepted;
|
|
1649
|
-
try {
|
|
1650
|
-
accepted = await ctx.client.killAgentkit({ teamId: ctx.teamId, slug });
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
return renderApiError(error, ctx.err);
|
|
1653
|
-
}
|
|
1654
|
-
if (ctx.json) {
|
|
1655
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1656
|
-
} else {
|
|
1657
|
-
ctx.out(
|
|
1658
|
-
accepted.scope === "global"
|
|
1659
|
-
? `Killed ${slug} across all tenants (team ${ctx.teamId}): deploys locked, admission stopped.\n`
|
|
1660
|
-
: `Killed ${slug} (team ${ctx.teamId}): deploys locked, admission stopped.\n`,
|
|
1661
|
-
);
|
|
1662
|
-
ctx.out(
|
|
1663
|
-
`Resume traffic with \`${CLI} unlock-deploys ${slug}\` then \`${CLI} start ${slug}\`.\n`,
|
|
1664
|
-
);
|
|
1665
|
-
ctx.out(`Unlock deploys with \`${CLI} unlock-deploys ${slug}\`.\n`);
|
|
1666
|
-
}
|
|
1667
|
-
if (accepted.cancellation !== undefined) {
|
|
1668
|
-
return writeCancellationStarted(ctx, accepted.cancellation, slug);
|
|
1669
|
-
}
|
|
1670
|
-
return 0;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
export async function cmdUnlockDeploys(
|
|
1674
|
-
slug: string | undefined,
|
|
1675
|
-
options: DeployCliOptions,
|
|
1676
|
-
): Promise<number> {
|
|
1677
|
-
if (slug === undefined || slug === "") {
|
|
1678
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1679
|
-
`Usage: ${CLI} unlock-deploys <slug> [--team <id>] [--json]\n`,
|
|
1680
|
-
);
|
|
1681
|
-
return 1;
|
|
1682
|
-
}
|
|
1683
|
-
const ctx = await prepare(options);
|
|
1684
|
-
if (typeof ctx === "number") {
|
|
1685
|
-
return ctx;
|
|
1686
|
-
}
|
|
1687
|
-
try {
|
|
1688
|
-
const accepted = await ctx.client.unlockAgentkitDeploys({
|
|
1689
|
-
teamId: ctx.teamId,
|
|
1690
|
-
slug,
|
|
1691
|
-
});
|
|
1692
|
-
if (ctx.json) {
|
|
1693
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1694
|
-
} else {
|
|
1695
|
-
ctx.out(`Unlocked deploys for ${slug} (team ${ctx.teamId}).\n`);
|
|
1696
|
-
}
|
|
1697
|
-
return 0;
|
|
1698
|
-
} catch (error) {
|
|
1699
|
-
return renderApiError(error, ctx.err);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
export async function cmdCancelRuns(
|
|
1704
|
-
slug: string | undefined,
|
|
1705
|
-
options: DeployCliOptions,
|
|
1706
|
-
): Promise<number> {
|
|
1707
|
-
if (slug === undefined || slug === "") {
|
|
1708
|
-
(options.err ?? ((text: string) => void process.stderr.write(text)))(
|
|
1709
|
-
`Usage: ${CLI} cancel-runs <slug> [--team <id>] [--json]\n`,
|
|
1710
|
-
);
|
|
1711
|
-
return 1;
|
|
1712
|
-
}
|
|
1713
|
-
const ctx = await prepare(options);
|
|
1714
|
-
if (typeof ctx === "number") {
|
|
1715
|
-
return ctx;
|
|
1716
|
-
}
|
|
1717
|
-
let result: AgentkitCancelRunsResult;
|
|
1718
|
-
try {
|
|
1719
|
-
result = await ctx.client.cancelAgentkitRuns({
|
|
1720
|
-
teamId: ctx.teamId,
|
|
1721
|
-
slug,
|
|
1722
|
-
});
|
|
1723
|
-
} catch (error) {
|
|
1724
|
-
return renderApiError(error, ctx.err);
|
|
1725
|
-
}
|
|
1726
|
-
if (ctx.json) {
|
|
1727
|
-
ctx.out(`${JSON.stringify(result, null, 2)}\n`);
|
|
1728
|
-
} else {
|
|
1729
|
-
ctx.out(`Started cancellation across ${result.batchesStarted} deployment batch(es).\n`);
|
|
1730
|
-
if (result.sessionsMarkedFailed !== undefined) {
|
|
1731
|
-
ctx.out(`Marked ${result.sessionsMarkedFailed} factory session(s) failed.\n`);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
if (result.failed > 0) {
|
|
1735
|
-
ctx.err(`Failed to start ${result.failed} cancellation batch(es).\n`);
|
|
1736
|
-
return 1;
|
|
1737
|
-
}
|
|
1738
|
-
return 0;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
// ============================================================================
|
|
1742
|
-
// event-repos <slug> --repo <owner/name>...
|
|
1743
|
-
// ============================================================================
|
|
1744
|
-
|
|
1745
|
-
export async function cmdEventRepos(
|
|
1746
|
-
slug: string | undefined,
|
|
1747
|
-
repositories: string[],
|
|
1748
|
-
options: DeployCliOptions,
|
|
1749
|
-
): Promise<number> {
|
|
1750
|
-
if (slug === undefined || slug === "") {
|
|
1751
|
-
(options.err ?? ((text: string) => void process.stderr.write(text)))(
|
|
1752
|
-
`Usage: ${CLI} event-repos <slug> --repo <owner/name>... [--team <id>] [--json]\n`,
|
|
1753
|
-
);
|
|
1754
|
-
return 1;
|
|
1755
|
-
}
|
|
1756
|
-
const ctx = await prepare(options);
|
|
1757
|
-
if (typeof ctx === "number") {
|
|
1758
|
-
return ctx;
|
|
1759
|
-
}
|
|
1760
|
-
let configured: string[];
|
|
1761
|
-
try {
|
|
1762
|
-
configured = await ctx.client.setAgentkitEventRepositories({
|
|
1763
|
-
teamId: ctx.teamId,
|
|
1764
|
-
slug,
|
|
1765
|
-
repositories,
|
|
1766
|
-
});
|
|
1767
|
-
} catch (error) {
|
|
1768
|
-
return renderApiError(error, ctx.err);
|
|
1769
|
-
}
|
|
1770
|
-
if (ctx.json) {
|
|
1771
|
-
ctx.out(`${JSON.stringify({ repositories: configured }, null, 2)}\n`);
|
|
1772
|
-
} else if (configured.length === 0) {
|
|
1773
|
-
ctx.out(`${slug} now watches no repositories.\n`);
|
|
1774
|
-
} else {
|
|
1775
|
-
ctx.out(`${slug} now watches ${configured.join(", ")}.\n`);
|
|
1776
|
-
}
|
|
1777
|
-
return 0;
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
// ============================================================================
|
|
1781
|
-
// upgrade <slug>
|
|
1782
|
-
// ============================================================================
|
|
1783
|
-
|
|
1784
|
-
/** Release ids are uuids; anything else the user typed is a version. */
|
|
1785
|
-
const RELEASE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1786
|
-
|
|
1787
|
-
export async function cmdUpgrade(
|
|
1788
|
-
args: { slug: string | undefined; release: string | undefined },
|
|
1789
|
-
options: DeployCliOptions,
|
|
1790
|
-
): Promise<number> {
|
|
1791
|
-
return cmdPinRelease(args, options, {
|
|
1792
|
-
usage: `Usage: ${CLI} upgrade <slug> [--release <id|version>] [--team <id>] [--json]\n`,
|
|
1793
|
-
pin: (client, request) => client.upgradeAgentkitInstall(request),
|
|
1794
|
-
success: (slug, releaseId) => `${slug} now runs release ${releaseId}.\n`,
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
export async function cmdRollback(
|
|
1799
|
-
args: { slug: string | undefined; release: string | undefined },
|
|
1800
|
-
options: DeployCliOptions,
|
|
1801
|
-
): Promise<number> {
|
|
1802
|
-
return cmdPinRelease(args, options, {
|
|
1803
|
-
usage: `Usage: ${CLI} rollback <slug> [--release <id|version>] [--team <id>] [--json]\n`,
|
|
1804
|
-
pin: (client, request) => client.rollbackAgentkitInstall(request),
|
|
1805
|
-
success: (slug, releaseId) => `${slug} rolled back to release ${releaseId}.\n`,
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
async function cmdPinRelease(
|
|
1810
|
-
args: { slug: string | undefined; release: string | undefined },
|
|
1811
|
-
options: DeployCliOptions,
|
|
1812
|
-
pin: {
|
|
1813
|
-
usage: string;
|
|
1814
|
-
pin: (
|
|
1815
|
-
client: AgentServeDeployClient,
|
|
1816
|
-
request: {
|
|
1817
|
-
teamId: number;
|
|
1818
|
-
slug: string;
|
|
1819
|
-
releaseId?: string;
|
|
1820
|
-
version?: string;
|
|
1821
|
-
},
|
|
1822
|
-
) => Promise<string>;
|
|
1823
|
-
success: (slug: string, releaseId: string) => string;
|
|
1824
|
-
},
|
|
1825
|
-
): Promise<number> {
|
|
1826
|
-
const { slug, release } = args;
|
|
1827
|
-
if (slug === undefined || slug === "") {
|
|
1828
|
-
(options.err ?? ((text: string) => void process.stderr.write(text)))(pin.usage);
|
|
1829
|
-
return 1;
|
|
1830
|
-
}
|
|
1831
|
-
const ctx = await prepare(options);
|
|
1832
|
-
if (typeof ctx === "number") {
|
|
1833
|
-
return ctx;
|
|
1834
|
-
}
|
|
1835
|
-
const selector = release?.trim() ?? "";
|
|
1836
|
-
let releaseId: string;
|
|
1837
|
-
try {
|
|
1838
|
-
releaseId = await pin.pin(ctx.client, {
|
|
1839
|
-
teamId: ctx.teamId,
|
|
1840
|
-
slug,
|
|
1841
|
-
...(selector === ""
|
|
1842
|
-
? {}
|
|
1843
|
-
: RELEASE_ID_RE.test(selector)
|
|
1844
|
-
? { releaseId: selector }
|
|
1845
|
-
: { version: selector }),
|
|
1846
|
-
});
|
|
1847
|
-
} catch (error) {
|
|
1848
|
-
return renderApiError(error, ctx.err);
|
|
1849
|
-
}
|
|
1850
|
-
if (ctx.json) {
|
|
1851
|
-
ctx.out(`${JSON.stringify({ releaseId }, null, 2)}\n`);
|
|
1852
|
-
} else {
|
|
1853
|
-
ctx.out(pin.success(slug, releaseId));
|
|
1854
|
-
}
|
|
1855
|
-
return 0;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// ============================================================================
|
|
1859
|
-
// delete <slug>
|
|
1860
|
-
// ============================================================================
|
|
1861
|
-
|
|
1862
|
-
export async function cmdDelete(
|
|
1863
|
-
slug: string | undefined,
|
|
1864
|
-
options: DeployCliOptions,
|
|
1865
|
-
): Promise<number> {
|
|
1866
|
-
if (slug === undefined || slug === "") {
|
|
1867
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1868
|
-
`Usage: ${CLI} delete <slug> [--team <id>] [--json] [--no-wait]\n`,
|
|
1869
|
-
);
|
|
1870
|
-
return 1;
|
|
1871
|
-
}
|
|
1872
|
-
const ctx = await prepare(options);
|
|
1873
|
-
if (typeof ctx === "number") {
|
|
1874
|
-
return ctx;
|
|
1875
|
-
}
|
|
1876
|
-
// Same dispatch as `stop`: v1 409s architecture-v2 slugs, so the v2
|
|
1877
|
-
// DELETE goes first. A v2 404 (missing slug, or an older backend)
|
|
1878
|
-
// falls through to the v1 agent-serve delete.
|
|
1879
|
-
let architecture: "v1" | "v2" = "v2";
|
|
1880
|
-
let accepted: DeleteAccepted | AgentkitStopAccepted;
|
|
1881
|
-
try {
|
|
1882
|
-
accepted = await ctx.client.deleteAgentkit({ teamId: ctx.teamId, slug });
|
|
1883
|
-
} catch (error) {
|
|
1884
|
-
if (!(error instanceof DeployApiError) || error.status !== 404) {
|
|
1885
|
-
return renderApiError(error, ctx.err);
|
|
1886
|
-
}
|
|
1887
|
-
try {
|
|
1888
|
-
accepted = await ctx.client.deleteDeployment({
|
|
1889
|
-
teamId: ctx.teamId,
|
|
1890
|
-
slug,
|
|
1891
|
-
});
|
|
1892
|
-
architecture = "v1";
|
|
1893
|
-
} catch (v1Error) {
|
|
1894
|
-
return renderApiError(v1Error, ctx.err);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
if (ctx.noWait) {
|
|
1898
|
-
if (ctx.json) {
|
|
1899
|
-
ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
|
|
1900
|
-
} else {
|
|
1901
|
-
ctx.out(`Delete accepted for ${slug} (status: ${accepted.status}).\n`);
|
|
1902
|
-
}
|
|
1903
|
-
return 0;
|
|
1904
|
-
}
|
|
1905
|
-
try {
|
|
1906
|
-
await pollUntilGone(ctx, slug, () =>
|
|
1907
|
-
architecture === "v2"
|
|
1908
|
-
? ctx.client.getAgentkitDeploy({ teamId: ctx.teamId, slug })
|
|
1909
|
-
: ctx.client.getDeployment({ teamId: ctx.teamId, slug }),
|
|
1910
|
-
);
|
|
1911
|
-
} catch (error) {
|
|
1912
|
-
return renderApiError(error, ctx.err);
|
|
1913
|
-
}
|
|
1914
|
-
if (ctx.json) {
|
|
1915
|
-
ctx.out(`${JSON.stringify({ slug, status: "deleted" }, null, 2)}\n`);
|
|
1916
|
-
} else {
|
|
1917
|
-
ctx.out(`Deleted ${slug} (team ${ctx.teamId}).\n`);
|
|
1918
|
-
}
|
|
1919
|
-
return 0;
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
// ============================================================================
|
|
1923
|
-
// rotate-token <slug>
|
|
1924
|
-
// ============================================================================
|
|
1925
|
-
|
|
1926
|
-
export async function cmdRotateToken(
|
|
1927
|
-
slug: string | undefined,
|
|
1928
|
-
options: DeployCliOptions,
|
|
1929
|
-
): Promise<number> {
|
|
1930
|
-
if (slug === undefined || slug === "") {
|
|
1931
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
1932
|
-
`Usage: ${CLI} rotate-token <slug> [--team <id>]\n`,
|
|
1933
|
-
);
|
|
1934
|
-
return 1;
|
|
1935
|
-
}
|
|
1936
|
-
const ctx = await prepare(options);
|
|
1937
|
-
if (typeof ctx === "number") {
|
|
1938
|
-
return ctx;
|
|
1939
|
-
}
|
|
1940
|
-
let aliasToken: string;
|
|
1941
|
-
try {
|
|
1942
|
-
const architecture = await deploymentArchitecture({
|
|
1943
|
-
client: ctx.client,
|
|
1944
|
-
teamId: ctx.teamId,
|
|
1945
|
-
slug,
|
|
1946
|
-
});
|
|
1947
|
-
aliasToken =
|
|
1948
|
-
architecture === "v2"
|
|
1949
|
-
? await ctx.client.rotateAgentsdkAliasToken({
|
|
1950
|
-
teamId: ctx.teamId,
|
|
1951
|
-
slug,
|
|
1952
|
-
})
|
|
1953
|
-
: await ctx.client.rotateAliasToken({
|
|
1954
|
-
teamId: ctx.teamId,
|
|
1955
|
-
slug,
|
|
1956
|
-
});
|
|
1957
|
-
} catch (error) {
|
|
1958
|
-
return renderApiError(error, ctx.err);
|
|
1959
|
-
}
|
|
1960
|
-
if (ctx.json) {
|
|
1961
|
-
ctx.out(`${JSON.stringify({ aliasToken }, null, 2)}\n`);
|
|
1962
|
-
return 0;
|
|
1963
|
-
}
|
|
1964
|
-
ctx.out(
|
|
1965
|
-
`Rotated the alias token for ${slug}. The old token stopped working immediately.\n${aliasTokenBlock(aliasToken)}`,
|
|
1966
|
-
);
|
|
1967
|
-
return 0;
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
// ============================================================================
|
|
1971
|
-
// secrets set|list|unset
|
|
1972
|
-
// ============================================================================
|
|
1973
|
-
|
|
1974
|
-
async function secretDeploymentArchitecture(
|
|
1975
|
-
ctx: DeployCliContext,
|
|
1976
|
-
slug: string,
|
|
1977
|
-
): Promise<"v1" | "v2"> {
|
|
1978
|
-
return deploymentArchitecture({
|
|
1979
|
-
client: ctx.client,
|
|
1980
|
-
teamId: ctx.teamId,
|
|
1981
|
-
slug,
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
export async function cmdSecretsSet(
|
|
1986
|
-
slug: string | undefined,
|
|
1987
|
-
specs: string[],
|
|
1988
|
-
options: DeployCliOptions,
|
|
1989
|
-
): Promise<number> {
|
|
1990
|
-
const usageErr = options.err ?? ((t: string) => void process.stderr.write(t));
|
|
1991
|
-
if (slug === undefined || slug === "" || specs.length === 0) {
|
|
1992
|
-
usageErr(
|
|
1993
|
-
`Usage: ${CLI} secrets set <slug> NAME [NAME2 ...] [--team <id>] [--json]\n` +
|
|
1994
|
-
` Enter values at the hidden prompt, or pipe one line per NAME.\n` +
|
|
1995
|
-
` Opt-in (leaks into argv / agent terminals): NAME=VALUE … --from-argv\n`,
|
|
1996
|
-
);
|
|
1997
|
-
return 1;
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// Parse and validate every name before prompting for any value, so a
|
|
2001
|
-
// typo in the last name cannot waste typed-in secrets.
|
|
2002
|
-
const parsed: Array<{ name: string; value?: string }> = [];
|
|
2003
|
-
for (const spec of specs) {
|
|
2004
|
-
const eq = spec.indexOf("=");
|
|
2005
|
-
const name = eq === -1 ? spec : spec.slice(0, eq);
|
|
2006
|
-
const nameError = validateSettableSecretName(name);
|
|
2007
|
-
if (nameError !== undefined) {
|
|
2008
|
-
usageErr(`${nameError}\n`);
|
|
2009
|
-
return 1;
|
|
2010
|
-
}
|
|
2011
|
-
parsed.push(eq === -1 ? { name } : { name, value: spec.slice(eq + 1) });
|
|
2012
|
-
}
|
|
2013
|
-
const hasArgvValues = parsed.some(entry => entry.value !== undefined);
|
|
2014
|
-
if (hasArgvValues && options.fromArgv !== true) {
|
|
2015
|
-
usageErr(
|
|
2016
|
-
`Refusing NAME=VALUE on argv (shows up in shell history and agent-captured terminals).\n` +
|
|
2017
|
-
`Pass names only:\n` +
|
|
2018
|
-
` ${CLI} secrets set ${slug} NAME\n` +
|
|
2019
|
-
`Hidden prompt on a TTY; one stdin line per NAME when piped.\n` +
|
|
2020
|
-
`Or redirect a file: ${CLI} secrets set ${slug} NAME < ./secret.txt\n`,
|
|
2021
|
-
);
|
|
2022
|
-
return 1;
|
|
2023
|
-
}
|
|
2024
|
-
if (hasArgvValues) {
|
|
2025
|
-
usageErr(
|
|
2026
|
-
`warning: --from-argv puts secret values in argv (shell history and agent-captured terminals).\n`,
|
|
2027
|
-
);
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
const ctx = await prepare(options);
|
|
2031
|
-
if (typeof ctx === "number") {
|
|
2032
|
-
return ctx;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
const stdin = options.stdin ?? process.stdin;
|
|
2036
|
-
const readValue = createSecretValueReader(stdin, ctx.err);
|
|
2037
|
-
const secrets: Array<{ name: string; value: string }> = [];
|
|
2038
|
-
for (const entry of parsed) {
|
|
2039
|
-
secrets.push({
|
|
2040
|
-
name: entry.name,
|
|
2041
|
-
value: entry.value ?? (await readValue(entry.name)),
|
|
2042
|
-
});
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
let secretNames: string[];
|
|
2046
|
-
let architecture: "v1" | "v2";
|
|
2047
|
-
try {
|
|
2048
|
-
architecture = await secretDeploymentArchitecture(ctx, slug);
|
|
2049
|
-
secretNames =
|
|
2050
|
-
architecture === "v2"
|
|
2051
|
-
? await ctx.client.setAgentkitSecrets({
|
|
2052
|
-
teamId: ctx.teamId,
|
|
2053
|
-
slug,
|
|
2054
|
-
secrets,
|
|
2055
|
-
})
|
|
2056
|
-
: await ctx.client.setSecrets({
|
|
2057
|
-
teamId: ctx.teamId,
|
|
2058
|
-
slug,
|
|
2059
|
-
secrets,
|
|
2060
|
-
});
|
|
2061
|
-
} catch (error) {
|
|
2062
|
-
return renderApiError(error, ctx.err);
|
|
2063
|
-
}
|
|
2064
|
-
if (ctx.json) {
|
|
2065
|
-
ctx.out(`${JSON.stringify({ secretNames }, null, 2)}\n`);
|
|
2066
|
-
return 0;
|
|
2067
|
-
}
|
|
2068
|
-
ctx.out(
|
|
2069
|
-
`Set ${secrets.length} secret${secrets.length === 1 ? "" : "s"} on ${slug}.\n` +
|
|
2070
|
-
`Secrets now set: ${secretNames.join(", ")}\n` +
|
|
2071
|
-
(architecture === "v2"
|
|
2072
|
-
? `The next task picks them up; no redeploy is required.\n`
|
|
2073
|
-
: `The engine picks them up on its next deploy.\n`),
|
|
2074
|
-
);
|
|
2075
|
-
return 0;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
export async function cmdSecretsList(
|
|
2079
|
-
slug: string | undefined,
|
|
2080
|
-
options: DeployCliOptions,
|
|
2081
|
-
): Promise<number> {
|
|
2082
|
-
if (slug === undefined || slug === "") {
|
|
2083
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
2084
|
-
`Usage: ${CLI} secrets list <slug> [--team <id>] [--json]\n`,
|
|
2085
|
-
);
|
|
2086
|
-
return 1;
|
|
2087
|
-
}
|
|
2088
|
-
const ctx = await prepare(options);
|
|
2089
|
-
if (typeof ctx === "number") {
|
|
2090
|
-
return ctx;
|
|
2091
|
-
}
|
|
2092
|
-
let secrets: SecretInfo[];
|
|
2093
|
-
try {
|
|
2094
|
-
const architecture = await secretDeploymentArchitecture(ctx, slug);
|
|
2095
|
-
secrets =
|
|
2096
|
-
architecture === "v2"
|
|
2097
|
-
? await ctx.client.listAgentkitSecrets({ teamId: ctx.teamId, slug })
|
|
2098
|
-
: await ctx.client.listSecrets({ teamId: ctx.teamId, slug });
|
|
2099
|
-
} catch (error) {
|
|
2100
|
-
return renderApiError(error, ctx.err);
|
|
2101
|
-
}
|
|
2102
|
-
if (ctx.json) {
|
|
2103
|
-
ctx.out(`${JSON.stringify({ secrets }, null, 2)}\n`);
|
|
2104
|
-
return 0;
|
|
2105
|
-
}
|
|
2106
|
-
if (secrets.length === 0) {
|
|
2107
|
-
ctx.out(`No secrets set on ${slug}. Add one with \`${CLI} secrets set ${slug} NAME\`.\n`);
|
|
2108
|
-
return 0;
|
|
2109
|
-
}
|
|
2110
|
-
ctx.out(
|
|
2111
|
-
`${renderTable(
|
|
2112
|
-
["NAME", "CREATED"],
|
|
2113
|
-
secrets.map(secret => [secret.name, secret.createdAt]),
|
|
2114
|
-
)}\n`,
|
|
2115
|
-
);
|
|
2116
|
-
return 0;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
export async function cmdSecretsUnset(
|
|
2120
|
-
slug: string | undefined,
|
|
2121
|
-
name: string | undefined,
|
|
2122
|
-
options: DeployCliOptions,
|
|
2123
|
-
): Promise<number> {
|
|
2124
|
-
if (slug === undefined || slug === "" || name === undefined || name === "") {
|
|
2125
|
-
(options.err ?? ((t: string) => void process.stderr.write(t)))(
|
|
2126
|
-
`Usage: ${CLI} secrets unset <slug> NAME [--team <id>]\n`,
|
|
2127
|
-
);
|
|
2128
|
-
return 1;
|
|
2129
|
-
}
|
|
2130
|
-
const ctx = await prepare(options);
|
|
2131
|
-
if (typeof ctx === "number") {
|
|
2132
|
-
return ctx;
|
|
2133
|
-
}
|
|
2134
|
-
try {
|
|
2135
|
-
const architecture = await secretDeploymentArchitecture(ctx, slug);
|
|
2136
|
-
if (architecture === "v2") {
|
|
2137
|
-
await ctx.client.deleteAgentkitSecret({
|
|
2138
|
-
teamId: ctx.teamId,
|
|
2139
|
-
slug,
|
|
2140
|
-
name,
|
|
2141
|
-
});
|
|
2142
|
-
} else {
|
|
2143
|
-
await ctx.client.deleteSecret({ teamId: ctx.teamId, slug, name });
|
|
2144
|
-
}
|
|
2145
|
-
} catch (error) {
|
|
2146
|
-
if (error instanceof DeployApiError && error.status === 404) {
|
|
2147
|
-
ctx.err(
|
|
2148
|
-
`No secret named ${name} on deployment ${slug} — check \`${CLI} secrets list ${slug}\` (and the slug / --team).\n`,
|
|
2149
|
-
);
|
|
2150
|
-
return 1;
|
|
2151
|
-
}
|
|
2152
|
-
return renderApiError(error, ctx.err);
|
|
2153
|
-
}
|
|
2154
|
-
if (ctx.json) {
|
|
2155
|
-
ctx.out(`${JSON.stringify({ removed: name }, null, 2)}\n`);
|
|
2156
|
-
} else {
|
|
2157
|
-
ctx.out(`Removed secret ${name} from ${slug}.\n`);
|
|
2158
|
-
}
|
|
2159
|
-
return 0;
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
/**
|
|
2163
|
-
* Returns a reader for interactive secret values: hidden (no-echo) prompt
|
|
2164
|
-
* when `stdin` is a TTY, otherwise one line per secret from the piped
|
|
2165
|
-
* stream. One reader per command so piped lines are consumed in order.
|
|
2166
|
-
*/
|
|
2167
|
-
function createSecretValueReader(
|
|
2168
|
-
stdin: SecretInputStream,
|
|
2169
|
-
err: (text: string) => void,
|
|
2170
|
-
): (name: string) => Promise<string> {
|
|
2171
|
-
if (stdin.isTTY === true) {
|
|
2172
|
-
return name => promptHidden(stdin, err, `Value for ${name}: `);
|
|
2173
|
-
}
|
|
2174
|
-
const readLine = createLineReader(stdin);
|
|
2175
|
-
return async name => {
|
|
2176
|
-
const line = await readLine();
|
|
2177
|
-
if (line === undefined) {
|
|
2178
|
-
throw new DeployApiError(
|
|
2179
|
-
`stdin ended before a value for ${name} was provided (pipe one line per secret).`,
|
|
2180
|
-
0,
|
|
2181
|
-
);
|
|
2182
|
-
}
|
|
2183
|
-
return line;
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
/** Longest mask run drawn while typing a secret; the count carries the rest. */
|
|
2188
|
-
const SECRET_MASK_MAX_WIDTH = 24;
|
|
2189
|
-
|
|
2190
|
-
/**
|
|
2191
|
-
* One line of typing feedback for the hidden prompt. Echoing the value would
|
|
2192
|
-
* leak it, so the line carries a bounded mask plus the exact character count —
|
|
2193
|
-
* enough to see that keystrokes, backspaces, and pasted values landed. The
|
|
2194
|
-
* mask is clamped to the terminal width so the redraw stays on one line.
|
|
2195
|
-
*/
|
|
2196
|
-
function renderSecretPromptLine(
|
|
2197
|
-
prompt: string,
|
|
2198
|
-
length: number,
|
|
2199
|
-
dim: (text: string) => string,
|
|
2200
|
-
): string {
|
|
2201
|
-
const count = `${length} char${length === 1 ? "" : "s"}`;
|
|
2202
|
-
const room = (process.stderr.columns ?? 80) - prompt.length - count.length - 2;
|
|
2203
|
-
const maskWidth = Math.max(0, Math.min(length, SECRET_MASK_MAX_WIDTH, room));
|
|
2204
|
-
// `·` over `•`: the rest of the CLI's progress output already uses it, and
|
|
2205
|
-
// fonts that ship no bullet glyph draw a tofu box in its place.
|
|
2206
|
-
const mask = "·".repeat(maskWidth);
|
|
2207
|
-
const body = mask === "" ? count : `${mask} ${count}`;
|
|
2208
|
-
return `\r\u001b[2K${prompt}${dim(body)}`;
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
/**
|
|
2212
|
-
* Raw-mode hidden prompt: never echoes the value, redraws a mask + character
|
|
2213
|
-
* count as it is typed, supports backspace and Ctrl-C.
|
|
2214
|
-
*/
|
|
2215
|
-
function promptHidden(
|
|
2216
|
-
stdin: SecretInputStream,
|
|
2217
|
-
err: (text: string) => void,
|
|
2218
|
-
prompt: string,
|
|
2219
|
-
): Promise<string> {
|
|
2220
|
-
return new Promise((resolvePromise, reject) => {
|
|
2221
|
-
const { dim } = stderrPalette();
|
|
2222
|
-
let value = "";
|
|
2223
|
-
const draw = (): void => {
|
|
2224
|
-
err(renderSecretPromptLine(prompt, value.length, dim));
|
|
2225
|
-
};
|
|
2226
|
-
draw();
|
|
2227
|
-
stdin.setRawMode?.(true);
|
|
2228
|
-
stdin.resume();
|
|
2229
|
-
const cleanup = (): void => {
|
|
2230
|
-
stdin.off("data", onData);
|
|
2231
|
-
stdin.setRawMode?.(false);
|
|
2232
|
-
stdin.pause();
|
|
2233
|
-
draw();
|
|
2234
|
-
err("\n");
|
|
2235
|
-
};
|
|
2236
|
-
const onData = (chunk: Buffer | string): void => {
|
|
2237
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2238
|
-
for (const ch of text) {
|
|
2239
|
-
if (ch === "\r" || ch === "\n") {
|
|
2240
|
-
cleanup();
|
|
2241
|
-
resolvePromise(value);
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
if (ch === "\u0003") {
|
|
2245
|
-
cleanup();
|
|
2246
|
-
reject(new DeployApiError("Aborted.", 0));
|
|
2247
|
-
return;
|
|
2248
|
-
}
|
|
2249
|
-
if (ch === "\u007f" || ch === "\b") {
|
|
2250
|
-
value = value.slice(0, -1);
|
|
2251
|
-
continue;
|
|
2252
|
-
}
|
|
2253
|
-
value += ch;
|
|
2254
|
-
}
|
|
2255
|
-
// One redraw per chunk keeps a pasted value from flooding the terminal.
|
|
2256
|
-
draw();
|
|
2257
|
-
};
|
|
2258
|
-
stdin.on("data", onData);
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
/** Buffered line reader over a piped stream; undefined once it ends. */
|
|
2263
|
-
export function createLineReader(stream: NodeJS.ReadableStream): () => Promise<string | undefined> {
|
|
2264
|
-
let buffer = "";
|
|
2265
|
-
let ended = false;
|
|
2266
|
-
const waiters: Array<(line: string | undefined) => void> = [];
|
|
2267
|
-
|
|
2268
|
-
const drain = (): void => {
|
|
2269
|
-
while (waiters.length > 0) {
|
|
2270
|
-
const newline = buffer.indexOf("\n");
|
|
2271
|
-
if (newline !== -1) {
|
|
2272
|
-
const line = buffer.slice(0, newline).replace(/\r$/, "");
|
|
2273
|
-
buffer = buffer.slice(newline + 1);
|
|
2274
|
-
waiters.shift()?.(line);
|
|
2275
|
-
continue;
|
|
2276
|
-
}
|
|
2277
|
-
if (!ended) {
|
|
2278
|
-
return;
|
|
2279
|
-
}
|
|
2280
|
-
// Stream ended: a trailing unterminated line still counts once.
|
|
2281
|
-
const rest = buffer;
|
|
2282
|
-
buffer = "";
|
|
2283
|
-
waiters.shift()?.(rest === "" ? undefined : rest.replace(/\r$/, ""));
|
|
2284
|
-
}
|
|
2285
|
-
};
|
|
2286
|
-
|
|
2287
|
-
stream.on("data", (chunk: Buffer | string) => {
|
|
2288
|
-
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2289
|
-
drain();
|
|
2290
|
-
});
|
|
2291
|
-
stream.on("end", () => {
|
|
2292
|
-
ended = true;
|
|
2293
|
-
drain();
|
|
2294
|
-
});
|
|
2295
|
-
stream.resume();
|
|
2296
|
-
|
|
2297
|
-
return () =>
|
|
2298
|
-
new Promise(resolvePromise => {
|
|
2299
|
-
waiters.push(resolvePromise);
|
|
2300
|
-
drain();
|
|
2301
|
-
});
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
// ============================================================================
|
|
2305
|
-
// Polling + rendering helpers
|
|
2306
|
-
// ============================================================================
|
|
2307
|
-
|
|
2308
|
-
/**
|
|
2309
|
-
* Poll deployment status every `pollIntervalMs` until it lands in
|
|
2310
|
-
* `terminal` (single-line progress on a TTY, one line per transition
|
|
2311
|
-
* otherwise). Prefers customer `statusMessage` / `progress` over raw
|
|
2312
|
-
* status so create → checkout → start is visible. Throws DeployApiError
|
|
2313
|
-
* on request failures or timeout.
|
|
2314
|
-
*/
|
|
2315
|
-
async function pollUntil<
|
|
2316
|
-
T extends {
|
|
2317
|
-
status: string;
|
|
2318
|
-
progress?: DeploymentProgress;
|
|
2319
|
-
statusMessage?: string | null;
|
|
2320
|
-
},
|
|
2321
|
-
>(
|
|
2322
|
-
ctx: DeployCliContext,
|
|
2323
|
-
slug: string,
|
|
2324
|
-
terminal: ReadonlySet<T["status"]>,
|
|
2325
|
-
fetchDetail: () => Promise<T>,
|
|
2326
|
-
): Promise<T> {
|
|
2327
|
-
const startedAt = Date.now();
|
|
2328
|
-
const tty = process.stderr.isTTY === true;
|
|
2329
|
-
let lastLabel: string | undefined;
|
|
2330
|
-
let sawProgress = false;
|
|
2331
|
-
|
|
2332
|
-
const report = (detail: T): void => {
|
|
2333
|
-
if (ctx.json) {
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2336
|
-
const label = deployProgressLabel(detail);
|
|
2337
|
-
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
2338
|
-
if (tty) {
|
|
2339
|
-
ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
|
|
2340
|
-
sawProgress = true;
|
|
2341
|
-
} else if (label !== lastLabel) {
|
|
2342
|
-
ctx.err(`${slug}: ${label}\n`);
|
|
2343
|
-
}
|
|
2344
|
-
lastLabel = label;
|
|
2345
|
-
};
|
|
2346
|
-
const finishProgressLine = (): void => {
|
|
2347
|
-
if (tty && sawProgress) {
|
|
2348
|
-
ctx.err("\n");
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
|
|
2352
|
-
for (;;) {
|
|
2353
|
-
let detail: T;
|
|
2354
|
-
try {
|
|
2355
|
-
detail = await fetchDetail();
|
|
2356
|
-
} catch (error: unknown) {
|
|
2357
|
-
// Capacity / rate-limit on GET is not a bake failure. The v2 accept
|
|
2358
|
-
// already started freeze; keep polling until the wait budget expires.
|
|
2359
|
-
if (isTransientDeployStatusError(error) && Date.now() - startedAt < ctx.timeoutMs) {
|
|
2360
|
-
if (!ctx.json) {
|
|
2361
|
-
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
2362
|
-
const label = "Waiting for status (server busy)";
|
|
2363
|
-
if (tty) {
|
|
2364
|
-
ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
|
|
2365
|
-
sawProgress = true;
|
|
2366
|
-
} else if (label !== lastLabel) {
|
|
2367
|
-
ctx.err(`${slug}: ${label}\n`);
|
|
2368
|
-
}
|
|
2369
|
-
lastLabel = label;
|
|
2370
|
-
}
|
|
2371
|
-
const waitMs = transientDeployStatusWaitMs(error, ctx.pollIntervalMs);
|
|
2372
|
-
const remaining = ctx.timeoutMs - (Date.now() - startedAt);
|
|
2373
|
-
await sleep(Math.max(0, Math.min(waitMs, remaining)));
|
|
2374
|
-
continue;
|
|
2375
|
-
}
|
|
2376
|
-
finishProgressLine();
|
|
2377
|
-
throw error;
|
|
2378
|
-
}
|
|
2379
|
-
report(detail);
|
|
2380
|
-
if (terminal.has(detail.status)) {
|
|
2381
|
-
finishProgressLine();
|
|
2382
|
-
return detail;
|
|
2383
|
-
}
|
|
2384
|
-
if (Date.now() - startedAt >= ctx.timeoutMs) {
|
|
2385
|
-
finishProgressLine();
|
|
2386
|
-
const last = deployProgressLabel(detail);
|
|
2387
|
-
throw new DeployApiError(
|
|
2388
|
-
`Timed out after ${Math.round(ctx.timeoutMs / 1000)}s waiting for ${slug} (last: ${last}). It may still converge — check \`${CLI} deployment ${slug} --team ${ctx.teamId}\`.`,
|
|
2389
|
-
0,
|
|
2390
|
-
);
|
|
2391
|
-
}
|
|
2392
|
-
await sleep(ctx.pollIntervalMs);
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
|
|
2396
|
-
/** Load-shed / rate-limit on status GET — the deploy itself may still be baking. */
|
|
2397
|
-
function isTransientDeployStatusError(error: unknown): error is DeployApiError {
|
|
2398
|
-
return error instanceof DeployApiError && (error.status === 503 || error.status === 429);
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
function transientDeployStatusWaitMs(error: DeployApiError, pollIntervalMs: number): number {
|
|
2402
|
-
if (
|
|
2403
|
-
error.status === 429 &&
|
|
2404
|
-
error.retryAfterSeconds !== undefined &&
|
|
2405
|
-
error.retryAfterSeconds > 0
|
|
2406
|
-
) {
|
|
2407
|
-
return Math.max(pollIntervalMs, error.retryAfterSeconds * 1000);
|
|
2408
|
-
}
|
|
2409
|
-
return pollIntervalMs;
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
/**
|
|
2413
|
-
* Wait until GET 404s (the delete reconciler purged the record). Treats
|
|
2414
|
-
* other errors as failures. Throws DeployApiError on timeout.
|
|
2415
|
-
*/
|
|
2416
|
-
async function pollUntilGone(
|
|
2417
|
-
ctx: DeployCliContext,
|
|
2418
|
-
slug: string,
|
|
2419
|
-
getDetail: () => Promise<{
|
|
2420
|
-
status: string;
|
|
2421
|
-
progress?: DeploymentProgress;
|
|
2422
|
-
statusMessage?: string | null;
|
|
2423
|
-
}>,
|
|
2424
|
-
): Promise<void> {
|
|
2425
|
-
const startedAt = Date.now();
|
|
2426
|
-
const tty = process.stderr.isTTY === true;
|
|
2427
|
-
let sawProgress = false;
|
|
2428
|
-
const report = (label: string): void => {
|
|
2429
|
-
if (ctx.json) {
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
2433
|
-
if (tty) {
|
|
2434
|
-
ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
|
|
2435
|
-
sawProgress = true;
|
|
2436
|
-
}
|
|
2437
|
-
};
|
|
2438
|
-
const finishProgressLine = (): void => {
|
|
2439
|
-
if (tty && sawProgress) {
|
|
2440
|
-
ctx.err("\n");
|
|
2441
|
-
}
|
|
2442
|
-
};
|
|
2443
|
-
|
|
2444
|
-
for (;;) {
|
|
2445
|
-
try {
|
|
2446
|
-
const detail = await getDetail();
|
|
2447
|
-
report(deployProgressLabel(detail));
|
|
2448
|
-
} catch (error: unknown) {
|
|
2449
|
-
if (error instanceof DeployApiError && error.status === 404) {
|
|
2450
|
-
finishProgressLine();
|
|
2451
|
-
return;
|
|
2452
|
-
}
|
|
2453
|
-
finishProgressLine();
|
|
2454
|
-
throw error;
|
|
2455
|
-
}
|
|
2456
|
-
if (Date.now() - startedAt >= ctx.timeoutMs) {
|
|
2457
|
-
finishProgressLine();
|
|
2458
|
-
throw new DeployApiError(
|
|
2459
|
-
`Timed out after ${Math.round(ctx.timeoutMs / 1000)}s waiting for ${slug} to disappear. It may still be deleting — check \`${CLI} deployment ${slug} --team ${ctx.teamId}\`.`,
|
|
2460
|
-
0,
|
|
2461
|
-
);
|
|
2462
|
-
}
|
|
2463
|
-
await sleep(ctx.pollIntervalMs);
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
function aliasTokenBlock(aliasToken: string): string {
|
|
2468
|
-
return (
|
|
2469
|
-
`\n Alias token (shown ONCE — save it now, it cannot be retrieved again):\n\n` +
|
|
2470
|
-
` ${aliasToken}\n\n` +
|
|
2471
|
-
` Send it as the X-Agent-Alias-Token header on requests to the alias URL,\n` +
|
|
2472
|
-
` or paste it into the hosted playground (<alias>/playground) sign-in gate.\n` +
|
|
2473
|
-
` Lost tokens: \`${CLI} rotate-token <slug>\` mints a replacement.\n\n`
|
|
2474
|
-
);
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
/**
|
|
2478
|
-
* `: <reason>` suffix for a deploy that ended in a non-running state, or
|
|
2479
|
-
* empty when the control plane recorded no reason.
|
|
2480
|
-
*/
|
|
2481
|
-
function deployFailureDetail(lastError: string | null | undefined): string {
|
|
2482
|
-
if (lastError === null || lastError === undefined || lastError.trim() === "") {
|
|
2483
|
-
return "";
|
|
2484
|
-
}
|
|
2485
|
-
return `: ${sanitizeCustomerError(lastError.trim())}`;
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
export function renderApiError(
|
|
2489
|
-
error: unknown,
|
|
2490
|
-
err: (text: string) => void,
|
|
2491
|
-
context?: { multiTenant?: boolean; teamId?: number },
|
|
2492
|
-
): number {
|
|
2493
|
-
if (error instanceof DeployApiError) {
|
|
2494
|
-
err(`${error.message}\n`);
|
|
2495
|
-
if (error.status === 409 && error.message.includes("application multiTenant does not match")) {
|
|
2496
|
-
if (context?.multiTenant === true) {
|
|
2497
|
-
err("This slug is a single-tenant application. Redeploy it without --multi-tenant.\n");
|
|
2498
|
-
} else if (context?.teamId === MULTI_TENANT_HTTP_DEPLOY_TEAM_ID) {
|
|
2499
|
-
err(
|
|
2500
|
-
"This slug is a Cursor-managed (multi-tenant) product. Redeploy it with --multi-tenant.\n",
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
return 1;
|
|
2505
|
-
}
|
|
2506
|
-
throw error;
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
export function renderTable(header: string[], rows: string[][]): string {
|
|
2510
|
-
const widths = header.map((h, i) =>
|
|
2511
|
-
Math.max(h.length, ...rows.map(row => (row[i] ?? "").length)),
|
|
2512
|
-
);
|
|
2513
|
-
const renderRow = (row: string[]): string =>
|
|
2514
|
-
row
|
|
2515
|
-
.map((cell, i) => cell.padEnd(widths[i] ?? 0))
|
|
2516
|
-
.join(" ")
|
|
2517
|
-
.trimEnd();
|
|
2518
|
-
return [renderRow(header), ...rows.map(renderRow)].join("\n");
|
|
2519
|
-
}
|