@cursor/july 0.1.2 → 0.1.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.
Files changed (232) hide show
  1. package/AGENTS.md +16 -5
  2. package/README.md +8 -9
  3. package/dist/bin/agent-serve.d.ts +3 -1
  4. package/dist/bin/agent-serve.d.ts.map +1 -1
  5. package/dist/bin/agent-serve.js +416 -155
  6. package/dist/channels/slack/log.d.ts +1 -1
  7. package/dist/channels/slack/log.d.ts.map +1 -1
  8. package/dist/channels/slack/log.js +4 -5
  9. package/dist/channels/slack/setup.js +1 -1
  10. package/dist/channels/slack/socket-mode.d.ts.map +1 -1
  11. package/dist/channels/slack/socket-mode.js +10 -9
  12. package/dist/docs/404.html +2 -2
  13. package/dist/docs/ab.html +3 -3
  14. package/dist/docs/assets/{app.Oje4vhlk.js → app.DF-Yx4OP.js} +1 -1
  15. package/dist/docs/assets/chunks/@localSearchIndexroot.0XrheBv2.js +1 -0
  16. package/dist/docs/assets/chunks/{VPLocalSearchBox.H5XZ2zCB.js → VPLocalSearchBox.DSeJSudL.js} +1 -1
  17. package/dist/docs/assets/chunks/{theme.CTR_TuaE.js → theme.DZVLTXOC.js} +2 -2
  18. package/dist/docs/assets/{index.md.m81y7TY7.js → index.md.Cylk70gg.js} +1 -1
  19. package/dist/docs/assets/{quickstart.md.CfU8_uTC.js → quickstart.md.BU6Iwi_9.js} +18 -6
  20. package/dist/docs/assets/{reference_cli.md.DA730zCu.js → reference_cli.md.Bv6pOxcF.js} +11 -6
  21. package/dist/docs/assets/{reference_cli.md.DA730zCu.lean.js → reference_cli.md.Bv6pOxcF.lean.js} +1 -1
  22. package/dist/docs/building-with-agents.html +3 -3
  23. package/dist/docs/concepts.html +3 -3
  24. package/dist/docs/deployment.html +3 -3
  25. package/dist/docs/evals.html +3 -3
  26. package/dist/docs/guides/agent-to-agent.html +3 -3
  27. package/dist/docs/guides/cloud-runtime.html +3 -3
  28. package/dist/docs/guides/github.html +3 -3
  29. package/dist/docs/guides/human-in-the-loop.html +3 -3
  30. package/dist/docs/guides/slack.html +3 -3
  31. package/dist/docs/guides/webhooks.html +3 -3
  32. package/dist/docs/hashmap.json +1 -1
  33. package/dist/docs/hillclimbing.html +3 -3
  34. package/dist/docs/index.html +5 -5
  35. package/dist/docs/quickstart.html +22 -10
  36. package/dist/docs/reference/agent-config.html +3 -3
  37. package/dist/docs/reference/channels.html +3 -3
  38. package/dist/docs/reference/cli.html +14 -9
  39. package/dist/docs/reference/connections.html +3 -3
  40. package/dist/docs/reference/hooks.html +3 -3
  41. package/dist/docs/reference/http-api.html +3 -3
  42. package/dist/docs/reference/instructions.html +3 -3
  43. package/dist/docs/reference/playground.html +3 -3
  44. package/dist/docs/reference/project-layout.html +3 -3
  45. package/dist/docs/reference/schedules.html +3 -3
  46. package/dist/docs/reference/sessions.html +3 -3
  47. package/dist/docs/reference/skills.html +3 -3
  48. package/dist/docs/reference/subagents.html +3 -3
  49. package/dist/docs/reference/tools.html +3 -3
  50. package/dist/docs/scaffolding-agents.html +3 -3
  51. package/dist/docs/storage.html +3 -3
  52. package/dist/docs/troubleshooting.html +3 -3
  53. package/dist/internal/ab-collector.js +3 -3
  54. package/dist/internal/ab-fold.js +1 -1
  55. package/dist/internal/chat-client.d.ts +29 -3
  56. package/dist/internal/chat-client.d.ts.map +1 -1
  57. package/dist/internal/chat-client.js +180 -27
  58. package/dist/internal/cli-ax.d.ts +83 -2
  59. package/dist/internal/cli-ax.d.ts.map +1 -1
  60. package/dist/internal/cli-ax.js +626 -52
  61. package/dist/internal/cli-cursor.d.ts.map +1 -1
  62. package/dist/internal/cli-cursor.js +8 -36
  63. package/dist/internal/cli-deploy.d.ts +59 -3
  64. package/dist/internal/cli-deploy.d.ts.map +1 -1
  65. package/dist/internal/cli-deploy.js +316 -38
  66. package/dist/internal/cli-github.js +13 -13
  67. package/dist/internal/cursor/account-mcp.js +3 -3
  68. package/dist/internal/cursor/backend-client.js +1 -1
  69. package/dist/internal/cursor/credentials.d.ts +14 -3
  70. package/dist/internal/cursor/credentials.d.ts.map +1 -1
  71. package/dist/internal/cursor/credentials.js +21 -6
  72. package/dist/internal/cursor/github-credentials.js +3 -3
  73. package/dist/internal/cursor-event-relay.js +1 -1
  74. package/dist/internal/cursor-relay-core.d.ts +1 -1
  75. package/dist/internal/cursor-relay-core.d.ts.map +1 -1
  76. package/dist/internal/cursor-relay-core.js +2 -1
  77. package/dist/internal/cursor-slack-relay.js +1 -1
  78. package/dist/internal/deploy-client.d.ts +9 -1
  79. package/dist/internal/deploy-client.d.ts.map +1 -1
  80. package/dist/internal/deploy-client.js +23 -3
  81. package/dist/internal/deploy-source.d.ts +35 -0
  82. package/dist/internal/deploy-source.d.ts.map +1 -0
  83. package/dist/internal/deploy-source.js +118 -0
  84. package/dist/internal/discovery.d.ts +11 -0
  85. package/dist/internal/discovery.d.ts.map +1 -1
  86. package/dist/internal/discovery.js +43 -14
  87. package/dist/internal/distribution.d.ts +7 -0
  88. package/dist/internal/distribution.d.ts.map +1 -1
  89. package/dist/internal/distribution.js +7 -0
  90. package/dist/internal/docs-site.js +3 -3
  91. package/dist/internal/eval-run-store.d.ts.map +1 -1
  92. package/dist/internal/eval-run-store.js +10 -9
  93. package/dist/internal/eval-runner.d.ts +2 -0
  94. package/dist/internal/eval-runner.d.ts.map +1 -1
  95. package/dist/internal/eval-runner.js +2 -0
  96. package/dist/internal/event-mapper.d.ts +54 -1
  97. package/dist/internal/event-mapper.d.ts.map +1 -1
  98. package/dist/internal/event-mapper.js +151 -41
  99. package/dist/internal/github-fanout.js +2 -2
  100. package/dist/internal/host-platforms.js +5 -5
  101. package/dist/internal/init-project.d.ts +88 -1
  102. package/dist/internal/init-project.d.ts.map +1 -1
  103. package/dist/internal/init-project.js +221 -31
  104. package/dist/internal/install-cursor-skills.d.ts +64 -0
  105. package/dist/internal/install-cursor-skills.d.ts.map +1 -0
  106. package/dist/internal/install-cursor-skills.js +274 -0
  107. package/dist/internal/logs-client.d.ts +60 -0
  108. package/dist/internal/logs-client.d.ts.map +1 -0
  109. package/dist/internal/logs-client.js +311 -0
  110. package/dist/internal/mcp-endpoint.d.ts.map +1 -1
  111. package/dist/internal/mcp-endpoint.js +1 -1
  112. package/dist/internal/open-browser.d.ts +5 -0
  113. package/dist/internal/open-browser.d.ts.map +1 -0
  114. package/dist/internal/open-browser.js +34 -0
  115. package/dist/internal/playground/toolchain.d.ts.map +1 -1
  116. package/dist/internal/playground/toolchain.js +19 -14
  117. package/dist/internal/playground-proxy.d.ts +69 -0
  118. package/dist/internal/playground-proxy.d.ts.map +1 -0
  119. package/dist/internal/playground-proxy.js +468 -0
  120. package/dist/internal/prompt-context.d.ts +35 -0
  121. package/dist/internal/prompt-context.d.ts.map +1 -0
  122. package/dist/internal/prompt-context.js +71 -0
  123. package/dist/internal/reminder-runner.d.ts.map +1 -1
  124. package/dist/internal/reminder-runner.js +11 -11
  125. package/dist/internal/request-headers.d.ts +11 -0
  126. package/dist/internal/request-headers.d.ts.map +1 -0
  127. package/dist/internal/request-headers.js +14 -0
  128. package/dist/internal/resolve-prod-target.d.ts +44 -0
  129. package/dist/internal/resolve-prod-target.d.ts.map +1 -0
  130. package/dist/internal/resolve-prod-target.js +134 -0
  131. package/dist/internal/resolved-connections.js +5 -5
  132. package/dist/internal/run-client.d.ts +2 -2
  133. package/dist/internal/run-client.d.ts.map +1 -1
  134. package/dist/internal/run-client.js +38 -23
  135. package/dist/internal/schedule-runner.js +1 -1
  136. package/dist/internal/server.d.ts.map +1 -1
  137. package/dist/internal/server.js +23 -17
  138. package/dist/internal/session-engine.d.ts +1 -1
  139. package/dist/internal/session-engine.d.ts.map +1 -1
  140. package/dist/internal/session-engine.js +34 -19
  141. package/dist/internal/sessions-client.d.ts +34 -0
  142. package/dist/internal/sessions-client.d.ts.map +1 -0
  143. package/dist/internal/sessions-client.js +193 -0
  144. package/dist/internal/storage-coordinator.js +6 -6
  145. package/dist/internal/stream-progress.d.ts.map +1 -1
  146. package/dist/internal/stream-progress.js +31 -3
  147. package/dist/internal/terminal-style.d.ts +22 -0
  148. package/dist/internal/terminal-style.d.ts.map +1 -0
  149. package/dist/internal/terminal-style.js +49 -0
  150. package/dist/internal/trajectory.d.ts +10 -5
  151. package/dist/internal/trajectory.d.ts.map +1 -1
  152. package/dist/internal/trajectory.js +82 -10
  153. package/dist/internal/update-check.d.ts +93 -0
  154. package/dist/internal/update-check.d.ts.map +1 -0
  155. package/dist/internal/update-check.js +431 -0
  156. package/dist/internal/workspace.d.ts +11 -0
  157. package/dist/internal/workspace.d.ts.map +1 -1
  158. package/dist/internal/workspace.js +38 -4
  159. package/dist/playground/assets/index-Ct_Usdm8.css +1 -0
  160. package/dist/playground/assets/index-Dsyb-Vaj.js +319 -0
  161. package/dist/playground/index.html +2 -2
  162. package/dist/types.d.ts +29 -2
  163. package/dist/types.d.ts.map +1 -1
  164. package/docs/README.md +0 -1
  165. package/docs/quickstart.md +22 -7
  166. package/docs/reference/cli.md +51 -3
  167. package/package.json +4 -1
  168. package/skills/ab/SKILL.md +8 -8
  169. package/skills/create-agent/SKILL.md +28 -22
  170. package/skills/debug/SKILL.md +11 -11
  171. package/skills/evals/SKILL.md +16 -16
  172. package/skills/framework-map/SKILL.md +6 -6
  173. package/skills/github/SKILL.md +13 -13
  174. package/skills/hillclimb/SKILL.md +10 -10
  175. package/skills/setup-slack/SKILL.md +14 -14
  176. package/src/bin/agent-serve.ts +451 -191
  177. package/src/channels/slack/log.ts +5 -5
  178. package/src/channels/slack/setup.ts +1 -1
  179. package/src/channels/slack/socket-mode.ts +11 -9
  180. package/src/internal/ab-collector.ts +3 -3
  181. package/src/internal/ab-fold.ts +1 -1
  182. package/src/internal/chat-client.ts +252 -37
  183. package/src/internal/cli-ax.ts +761 -72
  184. package/src/internal/cli-cursor.ts +14 -42
  185. package/src/internal/cli-deploy.ts +428 -49
  186. package/src/internal/cli-github.ts +13 -15
  187. package/src/internal/cursor/account-mcp.ts +3 -3
  188. package/src/internal/cursor/backend-client.ts +1 -1
  189. package/src/internal/cursor/credentials.ts +26 -7
  190. package/src/internal/cursor/github-credentials.ts +3 -3
  191. package/src/internal/cursor-event-relay.ts +1 -1
  192. package/src/internal/cursor-relay-core.ts +4 -2
  193. package/src/internal/cursor-slack-relay.ts +1 -1
  194. package/src/internal/deploy-client.ts +45 -4
  195. package/src/internal/deploy-source.ts +133 -0
  196. package/src/internal/discovery.ts +53 -16
  197. package/src/internal/distribution.ts +8 -0
  198. package/src/internal/docs-site.ts +3 -3
  199. package/src/internal/eval-run-store.ts +10 -11
  200. package/src/internal/eval-runner.ts +5 -0
  201. package/src/internal/event-mapper.ts +222 -42
  202. package/src/internal/github-fanout.ts +2 -2
  203. package/src/internal/host-platforms.ts +5 -5
  204. package/src/internal/init-project.ts +333 -51
  205. package/src/internal/install-cursor-skills.ts +327 -0
  206. package/src/internal/logs-client.ts +442 -0
  207. package/src/internal/mcp-endpoint.ts +1 -3
  208. package/src/internal/open-browser.ts +41 -0
  209. package/src/internal/playground/toolchain.ts +18 -16
  210. package/src/internal/playground-proxy.ts +576 -0
  211. package/src/internal/prompt-context.ts +95 -0
  212. package/src/internal/reminder-runner.ts +11 -17
  213. package/src/internal/request-headers.ts +23 -0
  214. package/src/internal/resolve-prod-target.ts +182 -0
  215. package/src/internal/resolved-connections.ts +5 -5
  216. package/src/internal/run-client.ts +39 -36
  217. package/src/internal/schedule-runner.ts +1 -1
  218. package/src/internal/server.ts +25 -16
  219. package/src/internal/session-engine.ts +35 -18
  220. package/src/internal/sessions-client.ts +216 -0
  221. package/src/internal/storage-coordinator.ts +6 -6
  222. package/src/internal/stream-progress.ts +36 -2
  223. package/src/internal/terminal-style.ts +74 -0
  224. package/src/internal/trajectory.ts +91 -13
  225. package/src/internal/update-check.ts +552 -0
  226. package/src/internal/workspace.ts +42 -4
  227. package/src/types.ts +45 -7
  228. package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +0 -1
  229. package/dist/playground/assets/index-B1Qc9h2u.css +0 -1
  230. package/dist/playground/assets/index-CpDYCj8W.js +0 -79
  231. /package/dist/docs/assets/{index.md.m81y7TY7.lean.js → index.md.Cylk70gg.lean.js} +0 -0
  232. /package/dist/docs/assets/{quickstart.md.CfU8_uTC.lean.js → quickstart.md.BU6Iwi_9.lean.js} +0 -0
@@ -13,7 +13,7 @@ import { basename, resolve } from "node:path";
13
13
  import type { AgentHostingOptions } from "../types.js";
14
14
  import { CursorBackendClient } from "./cursor/backend-client.js";
15
15
  import {
16
- cursorBackendUrl,
16
+ cursorExternalApiUrl,
17
17
  resolveApiKey,
18
18
  SIGN_IN_HINT,
19
19
  } from "./cursor/credentials.js";
@@ -31,29 +31,63 @@ import {
31
31
  TERMINAL_DEPLOY_STATUSES,
32
32
  validateDeploymentSlug,
33
33
  } from "./deploy-client.js";
34
- import { isAgentProjectDir, loadAgentProject } from "./discovery.js";
34
+ import { type GitRunner, inferDeployGitSource } from "./deploy-source.js";
35
+ import {
36
+ type ChildAgentDir,
37
+ isAgentProjectDir,
38
+ listChildAgentDirs,
39
+ loadAgentProject,
40
+ } from "./discovery.js";
35
41
  import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
36
42
  import {
37
43
  isValidCursorEventRepo,
38
44
  validateSettableSecretName,
39
45
  } from "./hosting.js";
46
+ import { stderrPalette } from "./terminal-style.js";
40
47
 
41
48
  const DEFAULT_POLL_INTERVAL_MS = 3_000;
42
49
  const DEFAULT_DEPLOY_TIMEOUT_MS = 10 * 60 * 1000;
43
50
 
51
+ /** One agent directory selected for deploy (single or multi-agent parent). */
52
+ export type DeployTarget = ChildAgentDir;
53
+
54
+ /**
55
+ * Interactive / test seam: pick which agents under a multi-agent parent to
56
+ * deploy. Return `null` to cancel.
57
+ */
58
+ export type ChooseDeployTargets = (
59
+ agents: readonly DeployTarget[]
60
+ ) => Promise<DeployTarget[] | null>;
61
+
44
62
  export interface DeployCliOptions {
45
63
  dir?: string;
46
64
  slug?: string;
65
+ /**
66
+ * deploy `--all`: when `--dir` is a multi-agent parent, deploy every
67
+ * child agent (skips the interactive picker).
68
+ */
69
+ all?: boolean;
47
70
  /** `--team <id>`; resolved from the account when omitted. */
48
71
  team?: string;
49
72
  json?: boolean;
50
73
  noWait?: boolean;
51
74
  apiKey?: string;
52
- /** deploy `--repo <url>`: https git repo the control plane builds from. */
75
+ /**
76
+ * deploy `--repo <url>`: https git repo the control plane builds from.
77
+ * Optional — inferred from `git remote origin` when omitted inside an
78
+ * agent project checkout.
79
+ */
53
80
  repo?: string;
54
- /** deploy `--ref <ref>`: git ref (defaults to the default branch server-side). */
81
+ /**
82
+ * deploy `--ref <ref>`: git ref to build. Optional — inferred from the
83
+ * current branch (or HEAD SHA if detached) when omitted; otherwise the
84
+ * server uses the repo's default branch.
85
+ */
55
86
  ref?: string;
56
- /** deploy `--path <agentPath>`: agent directory relative to the repo root. */
87
+ /**
88
+ * deploy `--path <agentPath>`: agent directory relative to the repo root.
89
+ * Optional — inferred from `--dir` vs the git toplevel when omitted.
90
+ */
57
91
  path?: string;
58
92
  /** deploy `--cursor-events-repo <owner/name>` (repeatable). */
59
93
  cursorEventsRepos?: string[];
@@ -61,6 +95,10 @@ export interface DeployCliOptions {
61
95
  allowDomains?: string[];
62
96
  /** Test seams. */
63
97
  fetchImpl?: typeof fetch;
98
+ /** Inject git probing for deploy source inference (tests). */
99
+ git?: GitRunner;
100
+ /** Inject the multi-agent picker (tests / non-interactive hosts). */
101
+ chooseTargets?: ChooseDeployTargets;
64
102
  pollIntervalMs?: number;
65
103
  timeoutMs?: number;
66
104
  out?: (text: string) => void;
@@ -86,15 +124,27 @@ interface DeployCliContext {
86
124
  err: (text: string) => void;
87
125
  }
88
126
 
127
+ export interface ResolveDeployAuthOptions {
128
+ team?: string;
129
+ apiKey?: string;
130
+ fetchImpl?: typeof fetch;
131
+ /** Quiet team-resolution chatter (default: write to stderr). */
132
+ err?: (text: string) => void;
133
+ }
134
+
135
+ export interface ResolvedDeployAuth {
136
+ client: AgentServeDeployClient;
137
+ teamId: number;
138
+ }
139
+
89
140
  /**
90
- * Shared preamble: credentials client → team id. Returns an exit code
91
- * (with the error already rendered) when resolution fails.
141
+ * Credentials deploy client → team id. Shared by deploy commands and
142
+ * `--prod` AX routing. Returns an exit code (error already rendered) on
143
+ * failure.
92
144
  */
93
- async function prepare(
94
- options: DeployCliOptions
95
- ): Promise<DeployCliContext | number> {
96
- const out =
97
- options.out ?? ((text: string) => void process.stdout.write(text));
145
+ export async function resolveDeployAuth(
146
+ options: ResolveDeployAuthOptions
147
+ ): Promise<ResolvedDeployAuth | number> {
98
148
  const err =
99
149
  options.err ?? ((text: string) => void process.stderr.write(text));
100
150
 
@@ -105,12 +155,13 @@ async function prepare(
105
155
  err(`Not signed in to Cursor. ${SIGN_IN_HINT}\n`);
106
156
  return 1;
107
157
  }
158
+ // The deployment management routes live on the external API host
159
+ // (api.cursor.com), not the default backend (api2.cursor.sh) — see
160
+ // cursorExternalApiUrl. GetMe below stays on the resolved backend.
108
161
  const client = new AgentServeDeployClient({
109
- backendUrl: resolved.backendUrl,
162
+ backendUrl: cursorExternalApiUrl(),
110
163
  apiKey: resolved.apiKey,
111
- ...(options.fetchImpl === undefined
112
- ? {}
113
- : { fetchImpl: options.fetchImpl }),
164
+ fetchImpl: options.fetchImpl,
114
165
  });
115
166
 
116
167
  let teamId: number;
@@ -124,9 +175,7 @@ async function prepare(
124
175
  const account = new CursorBackendClient({
125
176
  backendUrl: resolved.backendUrl,
126
177
  apiKey: resolved.apiKey,
127
- ...(options.fetchImpl === undefined
128
- ? {}
129
- : { fetchImpl: options.fetchImpl }),
178
+ fetchImpl: options.fetchImpl,
130
179
  });
131
180
  let identity: Awaited<ReturnType<CursorBackendClient["getMe"]>>;
132
181
  try {
@@ -144,16 +193,44 @@ async function prepare(
144
193
  return 1;
145
194
  }
146
195
  teamId = identity.teamId;
196
+ const dim = stderrPalette().dim;
147
197
  err(
148
- `Using team ${teamId}${
149
- identity.teamName === undefined ? "" : ` (${identity.teamName})`
150
- } the account's team; pass --team to override.\n`
198
+ `${dim(
199
+ `Using team ${teamId}${
200
+ identity.teamName === undefined ? "" : ` (${identity.teamName})`
201
+ } — the account's team; pass --team to override.`
202
+ )}\n`
151
203
  );
152
204
  }
153
205
 
206
+ return { client, teamId };
207
+ }
208
+
209
+ /**
210
+ * Shared preamble: credentials → client → team id. Returns an exit code
211
+ * (with the error already rendered) when resolution fails.
212
+ */
213
+ async function prepare(
214
+ options: DeployCliOptions
215
+ ): Promise<DeployCliContext | number> {
216
+ const out =
217
+ options.out ?? ((text: string) => void process.stdout.write(text));
218
+ const err =
219
+ options.err ?? ((text: string) => void process.stderr.write(text));
220
+
221
+ const auth = await resolveDeployAuth({
222
+ team: options.team,
223
+ apiKey: options.apiKey,
224
+ fetchImpl: options.fetchImpl,
225
+ err,
226
+ });
227
+ if (typeof auth === "number") {
228
+ return auth;
229
+ }
230
+
154
231
  return {
155
- client,
156
- teamId,
232
+ client: auth.client,
233
+ teamId: auth.teamId,
157
234
  json: options.json === true,
158
235
  noWait: options.noWait === true,
159
236
  pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
@@ -172,30 +249,308 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
172
249
  if (typeof ctx === "number") {
173
250
  return ctx;
174
251
  }
252
+
253
+ const targets = await resolveDeployTargets(options, ctx);
254
+ if (typeof targets === "number") {
255
+ return targets;
256
+ }
257
+
258
+ // --path is per-agent (relative to the repo root). When deploying several
259
+ // agents, ignore a shared --path so each target can infer its own path.
260
+ let deployOptions = options;
261
+ if (targets.length > 1 && options.path !== undefined) {
262
+ if (!ctx.json) {
263
+ ctx.err(
264
+ `warning: ignoring --path ${JSON.stringify(options.path)} for a multi-agent deploy; each agent infers its own path from git.\n`
265
+ );
266
+ }
267
+ deployOptions = { ...options, path: undefined };
268
+ }
269
+
270
+ if (targets.length === 1) {
271
+ return deployOne(ctx, deployOptions, targets[0]!);
272
+ }
273
+
274
+ // Multi-agent batch: deploy sequentially; --json emits an array.
275
+ const jsonResults: unknown[] = [];
276
+ let failures = 0;
277
+ for (const [index, target] of targets.entries()) {
278
+ if (!ctx.json) {
279
+ ctx.err(
280
+ `\n=== [${index + 1}/${targets.length}] Deploying ${target.slug} ===\n`
281
+ );
282
+ }
283
+ const before = jsonResults.length;
284
+ const code = await deployOne(ctx, deployOptions, target, {
285
+ jsonCollect: ctx.json ? jsonResults : undefined,
286
+ });
287
+ if (code !== 0) {
288
+ failures += 1;
289
+ if (ctx.json && jsonResults.length === before) {
290
+ jsonResults.push({ slug: target.slug, error: "deploy failed" });
291
+ }
292
+ }
293
+ }
294
+ if (ctx.json) {
295
+ ctx.out(`${JSON.stringify(jsonResults, null, 2)}\n`);
296
+ } else if (failures > 0) {
297
+ ctx.err(`\n${failures} of ${targets.length} deploy(s) failed.\n`);
298
+ } else {
299
+ ctx.err(`\nDeployed ${targets.length} agents.\n`);
300
+ }
301
+ return failures === 0 ? 0 : 1;
302
+ }
303
+
304
+ /**
305
+ * Resolve which agent directory(ies) to deploy from `--dir` / `--slug` /
306
+ * `--all`, with an interactive picker when a multi-agent parent is on a TTY.
307
+ */
308
+ async function resolveDeployTargets(
309
+ options: DeployCliOptions,
310
+ ctx: DeployCliContext
311
+ ): Promise<DeployTarget[] | number> {
175
312
  const dir = resolve(options.dir ?? ".");
176
- let slug: string;
313
+
177
314
  if (options.slug !== undefined) {
178
315
  const slugError = validateDeploymentSlug(options.slug);
179
316
  if (slugError !== undefined) {
180
317
  ctx.err(`${slugError}\n`);
181
318
  return 1;
182
319
  }
183
- slug = options.slug;
184
- } else {
185
- const derived = slugFromDirectoryName(basename(dir));
186
- if (derived === undefined) {
320
+ }
321
+ if (options.all === true && options.slug !== undefined) {
322
+ ctx.err("Pass either --slug or --all, not both.\n");
323
+ return 1;
324
+ }
325
+
326
+ if (await isAgentProjectDir(dir)) {
327
+ if (options.all === true) {
187
328
  ctx.err(
188
- `Could not derive a deployment slug from the directory name ${JSON.stringify(basename(dir))}; pass --slug.\n`
329
+ `--all requires a multi-agent parent directory (several child agents under --dir); ${dir} is a single agent project.\n`
189
330
  );
190
331
  return 1;
191
332
  }
192
- slug = derived;
193
- ctx.err(`Deploying as "${slug}" (from the directory name).\n`);
333
+ let slug: string;
334
+ if (options.slug !== undefined) {
335
+ slug = options.slug;
336
+ } else {
337
+ const derived = slugFromDirectoryName(basename(dir));
338
+ if (derived === undefined) {
339
+ ctx.err(
340
+ `Could not derive a deployment slug from the directory name ${JSON.stringify(basename(dir))}; pass --slug.\n`
341
+ );
342
+ return 1;
343
+ }
344
+ slug = derived;
345
+ if (!ctx.json) {
346
+ ctx.err(`Deploying as "${slug}" (from the directory name).\n`);
347
+ }
348
+ }
349
+ return [{ slug, dir }];
194
350
  }
195
351
 
196
- if (options.repo !== undefined && !options.repo.startsWith("https://")) {
352
+ let children: ChildAgentDir[];
353
+ try {
354
+ children = await listChildAgentDirs(dir);
355
+ } catch (error) {
356
+ ctx.err(`${describeError(error)}\n`);
357
+ return 1;
358
+ }
359
+ // Deploy slugs are lowercase-only; normalize directory names the same way
360
+ // single-agent deploy does so MixedCase child dirs still deploy.
361
+ children = children.flatMap((child) => {
362
+ const slug = slugFromDirectoryName(basename(child.dir));
363
+ if (slug === undefined) {
364
+ return [];
365
+ }
366
+ return [{ slug, dir: child.dir }];
367
+ });
368
+
369
+ if (children.length === 0) {
370
+ if (options.all === true) {
371
+ ctx.err(
372
+ `--all requires child agent projects under --dir; none found in ${dir}.\n`
373
+ );
374
+ return 1;
375
+ }
376
+ // Static / bare-slug deploy against a non-agent directory.
377
+ let slug: string;
378
+ if (options.slug !== undefined) {
379
+ slug = options.slug;
380
+ } else {
381
+ const derived = slugFromDirectoryName(basename(dir));
382
+ if (derived === undefined) {
383
+ ctx.err(
384
+ `No agent projects under ${dir}, and could not derive a slug from ${JSON.stringify(basename(dir))}; pass --slug or --dir <agent>.\n`
385
+ );
386
+ return 1;
387
+ }
388
+ slug = derived;
389
+ if (!ctx.json) {
390
+ ctx.err(
391
+ `Deploying as "${slug}" (static; no agent project at ${dir}).\n`
392
+ );
393
+ }
394
+ }
395
+ return [{ slug, dir }];
396
+ }
397
+
398
+ if (children.length === 1) {
399
+ const only = children[0]!;
400
+ if (options.all === true) {
401
+ // One child is fine for --all (degenerate multi-agent parent).
402
+ } else if (
403
+ options.slug !== undefined &&
404
+ options.slug.toLowerCase() !== only.slug
405
+ ) {
406
+ ctx.err(
407
+ `No agent "${options.slug}" under ${dir}; found "${only.slug}".\n`
408
+ );
409
+ return 1;
410
+ }
411
+ if (!ctx.json) {
412
+ ctx.err(
413
+ `Deploying "${only.slug}" (only agent under ${basename(dir)}).\n`
414
+ );
415
+ }
416
+ return [only];
417
+ }
418
+
419
+ // Multiple children.
420
+ if (options.all === true) {
421
+ if (!ctx.json) {
422
+ ctx.err(
423
+ `Deploying all ${children.length} agents under ${basename(dir)}: ${children.map((c) => c.slug).join(", ")}.\n`
424
+ );
425
+ }
426
+ return [...children];
427
+ }
428
+
429
+ if (options.slug !== undefined) {
430
+ const wanted = options.slug.toLowerCase();
431
+ const match = children.find((child) => child.slug === wanted);
432
+ if (match === undefined) {
433
+ ctx.err(
434
+ `No agent "${options.slug}" under ${dir}.\n` +
435
+ formatAgentList(children) +
436
+ `Pass --slug <name> or --all.\n`
437
+ );
438
+ return 1;
439
+ }
440
+ return [match];
441
+ }
442
+
443
+ const chooser =
444
+ options.chooseTargets ??
445
+ ((agents) =>
446
+ promptChooseDeployTargets(agents, {
447
+ err: ctx.err,
448
+ stdin: options.stdin ?? process.stdin,
449
+ interactive:
450
+ ctx.json !== true &&
451
+ (options.stdin ?? process.stdin).isTTY === true &&
452
+ process.stderr.isTTY === true,
453
+ }));
454
+ const chosen = await chooser(children);
455
+ if (chosen === null) {
456
+ ctx.err("Deploy cancelled.\n");
457
+ return 1;
458
+ }
459
+ if (chosen.length === 0) {
460
+ ctx.err(
461
+ `Multiple agents under ${dir}:\n` +
462
+ formatAgentList(children) +
463
+ `Pass --slug <name> or --all (or re-run on a TTY to pick interactively).\n`
464
+ );
465
+ return 1;
466
+ }
467
+ return chosen;
468
+ }
469
+
470
+ function formatAgentList(agents: readonly DeployTarget[]): string {
471
+ return agents.map((agent, i) => ` ${i + 1}) ${agent.slug}\n`).join("");
472
+ }
473
+
474
+ /**
475
+ * TTY picker for multi-agent parents. Non-interactive callers get `[]`
476
+ * so {@link resolveDeployTargets} can print the --slug/--all hint.
477
+ */
478
+ export async function promptChooseDeployTargets(
479
+ agents: readonly DeployTarget[],
480
+ options: {
481
+ err: (text: string) => void;
482
+ stdin: SecretInputStream;
483
+ interactive: boolean;
484
+ }
485
+ ): Promise<DeployTarget[] | null> {
486
+ options.err(
487
+ `Multiple agents (${agents.length}):\n` +
488
+ formatAgentList(agents) +
489
+ ` a) all\n`
490
+ );
491
+ if (!options.interactive) {
492
+ return [];
493
+ }
494
+ options.err('Deploy which? Enter a number, slug, "all", or "q" to cancel: ');
495
+ const readLine = createLineReader(options.stdin);
496
+ // createLineReader pauses until data; ensure stdin is flowing on a TTY.
497
+ options.stdin.resume?.();
498
+ const answer = (await readLine())?.trim().toLowerCase();
499
+ if (answer === undefined || answer === "q" || answer === "quit") {
500
+ return null;
501
+ }
502
+ if (answer === "a" || answer === "all" || answer === "*") {
503
+ return [...agents];
504
+ }
505
+ const asIndex = Number.parseInt(answer, 10);
506
+ if (
507
+ !Number.isNaN(asIndex) &&
508
+ String(asIndex) === answer &&
509
+ asIndex >= 1 &&
510
+ asIndex <= agents.length
511
+ ) {
512
+ return [agents[asIndex - 1]!];
513
+ }
514
+ const bySlug = agents.find((agent) => agent.slug.toLowerCase() === answer);
515
+ if (bySlug !== undefined) {
516
+ return [bySlug];
517
+ }
518
+ options.err(
519
+ `Unrecognized choice ${JSON.stringify(answer)} — expected 1–${agents.length}, a slug, "all", or "q". Aborting.\n`
520
+ );
521
+ return null;
522
+ }
523
+
524
+ async function deployOne(
525
+ ctx: DeployCliContext,
526
+ options: DeployCliOptions,
527
+ target: DeployTarget,
528
+ extras?: { jsonCollect?: unknown[] }
529
+ ): Promise<number> {
530
+ const { dir, slug } = target;
531
+
532
+ // Fill --repo/--ref/--path from the local git checkout when omitted so
533
+ // `agentkit deploy` inside an agent directory is enough. Only probe git
534
+ // for real agent projects (avoids ambient monorepo remotes when someone
535
+ // deploys a bare --slug). Explicit flags always win.
536
+ const isAgent = await isAgentProjectDir(dir);
537
+ const needsGitInference =
538
+ isAgent &&
539
+ (options.repo === undefined ||
540
+ options.ref === undefined ||
541
+ options.path === undefined);
542
+ const inferred = needsGitInference
543
+ ? await inferDeployGitSource(dir, {
544
+ ...(options.git === undefined ? {} : { git: options.git }),
545
+ })
546
+ : {};
547
+ const gitRepoUrl = options.repo ?? inferred.gitRepoUrl;
548
+ const gitRef = options.ref ?? inferred.gitRef;
549
+ const agentPath = options.path ?? inferred.agentPath;
550
+
551
+ if (gitRepoUrl !== undefined && !gitRepoUrl.startsWith("https://")) {
197
552
  ctx.err(
198
- `Invalid --repo: ${options.repo} (expected an https:// git repository URL)\n`
553
+ `Invalid --repo: ${gitRepoUrl} (expected an https:// git repository URL)\n`
199
554
  );
200
555
  return 1;
201
556
  }
@@ -206,11 +561,31 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
206
561
  }
207
562
  }
208
563
 
564
+ if (!ctx.json && gitRepoUrl !== undefined) {
565
+ const inferredBits: string[] = [];
566
+ if (options.repo === undefined && inferred.gitRepoUrl !== undefined) {
567
+ inferredBits.push("repo");
568
+ }
569
+ if (options.ref === undefined && inferred.gitRef !== undefined) {
570
+ inferredBits.push("ref");
571
+ }
572
+ if (options.path === undefined && inferred.agentPath !== undefined) {
573
+ inferredBits.push("path");
574
+ }
575
+ ctx.err(
576
+ ` source: ${gitRepoUrl}${gitRef === undefined ? "" : ` @ ${gitRef}`}\n` +
577
+ (agentPath === undefined ? "" : ` path: ${agentPath}\n`) +
578
+ (inferredBits.length === 0
579
+ ? ""
580
+ : ` (inferred ${inferredBits.join(", ")} from git; pass --repo/--ref/--path to override)\n`)
581
+ );
582
+ }
583
+
209
584
  // The package manifest (`defineAgent({ hosting })`) is the source of
210
585
  // truth for egress domains and expected secret names; flags override or
211
586
  // augment it. Directories without an agent project (static placeholder
212
587
  // deploys) simply have no manifest.
213
- const hosting = await readHostingManifest(dir);
588
+ const hosting = isAgent ? await readHostingManifest(dir) : undefined;
214
589
  const egressAllowedDomains = [
215
590
  ...new Set([
216
591
  ...(hosting?.egressDomains ?? []),
@@ -222,9 +597,9 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
222
597
  // serves the static placeholder). See DeploySource for the artifact seam.
223
598
  const source: DeploySource = {
224
599
  kind: "git-ref",
225
- ...(options.repo === undefined ? {} : { gitRepoUrl: options.repo }),
226
- ...(options.ref === undefined ? {} : { gitRef: options.ref }),
227
- ...(options.path === undefined ? {} : { agentPath: options.path }),
600
+ ...(gitRepoUrl === undefined ? {} : { gitRepoUrl }),
601
+ ...(gitRef === undefined ? {} : { gitRef }),
602
+ ...(agentPath === undefined ? {} : { agentPath }),
228
603
  ...(options.cursorEventsRepos === undefined ||
229
604
  options.cursorEventsRepos.length === 0
230
605
  ? {}
@@ -245,7 +620,9 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
245
620
  }
246
621
 
247
622
  if (ctx.noWait) {
248
- if (ctx.json) {
623
+ if (extras?.jsonCollect !== undefined) {
624
+ extras.jsonCollect.push(accepted);
625
+ } else if (ctx.json) {
249
626
  ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
250
627
  } else {
251
628
  ctx.out(
@@ -266,16 +643,18 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
266
643
 
267
644
  warnMissingSecrets(ctx, slug, hosting?.secretNames, detail.secretNames);
268
645
 
646
+ const payload =
647
+ accepted.aliasToken === undefined
648
+ ? detail
649
+ : { ...detail, aliasToken: accepted.aliasToken };
650
+
651
+ if (extras?.jsonCollect !== undefined) {
652
+ extras.jsonCollect.push(payload);
653
+ return detail.status === "running" ? 0 : 1;
654
+ }
655
+
269
656
  if (ctx.json) {
270
- ctx.out(
271
- `${JSON.stringify(
272
- accepted.aliasToken === undefined
273
- ? detail
274
- : { ...detail, aliasToken: accepted.aliasToken },
275
- null,
276
- 2
277
- )}\n`
278
- );
657
+ ctx.out(`${JSON.stringify(payload, null, 2)}\n`);
279
658
  return detail.status === "running" ? 0 : 1;
280
659
  }
281
660
 
@@ -295,7 +674,7 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
295
674
  ` generation: ${detail.generation}\n` +
296
675
  ` kind: ${detail.deploymentKind}\n` +
297
676
  ` alias: ${aliasUrl}\n` +
298
- ` playground: ${aliasUrl}/playground (send X-Agent-Alias-Token)\n`
677
+ ` playground: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
299
678
  );
300
679
  return 0;
301
680
  }