@cursor/july 0.1.8 → 0.1.10

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 (149) hide show
  1. package/dist/bin/agent-serve.js +9 -0
  2. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  3. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  4. package/dist/docs/404.html +2 -2
  5. package/dist/docs/ab.html +3 -3
  6. package/dist/docs/assets/{app.BP8mAVMW.js → app.CY2buaEE.js} +1 -1
  7. package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +1 -0
  8. package/dist/docs/assets/chunks/{VPLocalSearchBox.1FXIZbz8.js → VPLocalSearchBox.Ix954SFJ.js} +1 -1
  9. package/dist/docs/assets/chunks/{theme.Dm1R02v9.js → theme.PDwuJbyi.js} +2 -2
  10. package/dist/docs/assets/{deployment.md.BtfEsc9S.js → deployment.md.DC5-ImcM.js} +1 -1
  11. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.js → example-agents_approval-buddy.md.DFGBYLcc.js} +2 -2
  12. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.lean.js → example-agents_approval-buddy.md.DFGBYLcc.lean.js} +1 -1
  13. package/dist/docs/assets/{example-agents_index.md.CwtWu5ix.js → example-agents_index.md.BPXjUtRB.js} +2 -2
  14. package/dist/docs/assets/{example-agents_index.md.CwtWu5ix.lean.js → example-agents_index.md.BPXjUtRB.lean.js} +1 -1
  15. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.js → example-agents_security-reviewer.md.CE9v-aKm.js} +2 -2
  16. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.lean.js → example-agents_security-reviewer.md.CE9v-aKm.lean.js} +1 -1
  17. package/dist/docs/assets/{reference_cli.md.D189RBCH.js → reference_cli.md.CjfiV2Q-.js} +2 -1
  18. package/dist/docs/assets/{reference_connections.md.zaEYCLHT.js → reference_connections.md.FbWupyzW.js} +1 -1
  19. package/dist/docs/assets/{storage.md.Cu3MwM2a.js → storage.md.DRTdnFvd.js} +7 -1
  20. package/dist/docs/assets/{storage.md.Cu3MwM2a.lean.js → storage.md.DRTdnFvd.lean.js} +1 -1
  21. package/dist/docs/building-with-agents.html +3 -3
  22. package/dist/docs/concepts.html +3 -3
  23. package/dist/docs/deployment.html +5 -5
  24. package/dist/docs/evals.html +3 -3
  25. package/dist/docs/example-agents/approval-buddy.html +5 -5
  26. package/dist/docs/example-agents/benny.html +3 -3
  27. package/dist/docs/example-agents/bugbot.html +3 -3
  28. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  29. package/dist/docs/example-agents/codeowners-review.html +3 -3
  30. package/dist/docs/example-agents/concierge.html +3 -3
  31. package/dist/docs/example-agents/fsd.html +3 -3
  32. package/dist/docs/example-agents/index.html +5 -5
  33. package/dist/docs/example-agents/knowledge-base.html +3 -3
  34. package/dist/docs/example-agents/oncall.html +3 -3
  35. package/dist/docs/example-agents/security-reviewer.html +6 -6
  36. package/dist/docs/example-agents/slack-agent.html +3 -3
  37. package/dist/docs/example-agents/weather-agent.html +3 -3
  38. package/dist/docs/guides/agent-to-agent.html +3 -3
  39. package/dist/docs/guides/cloud-runtime.html +3 -3
  40. package/dist/docs/guides/github.html +3 -3
  41. package/dist/docs/guides/human-in-the-loop.html +3 -3
  42. package/dist/docs/guides/slack.html +3 -3
  43. package/dist/docs/guides/webhooks.html +3 -3
  44. package/dist/docs/hashmap.json +1 -1
  45. package/dist/docs/hillclimbing.html +3 -3
  46. package/dist/docs/index.html +3 -3
  47. package/dist/docs/quickstart.html +3 -3
  48. package/dist/docs/reference/agent-config.html +3 -3
  49. package/dist/docs/reference/channels.html +3 -3
  50. package/dist/docs/reference/cli.html +6 -5
  51. package/dist/docs/reference/connections.html +5 -5
  52. package/dist/docs/reference/hooks.html +3 -3
  53. package/dist/docs/reference/http-api.html +3 -3
  54. package/dist/docs/reference/instructions.html +3 -3
  55. package/dist/docs/reference/playground.html +3 -3
  56. package/dist/docs/reference/project-layout.html +3 -3
  57. package/dist/docs/reference/schedules.html +3 -3
  58. package/dist/docs/reference/sessions.html +3 -3
  59. package/dist/docs/reference/skills.html +3 -3
  60. package/dist/docs/reference/subagents.html +3 -3
  61. package/dist/docs/reference/tools.html +3 -3
  62. package/dist/docs/scaffolding-agents.html +3 -3
  63. package/dist/docs/storage.html +11 -5
  64. package/dist/docs/troubleshooting.html +3 -3
  65. package/dist/internal/cli-ax.d.ts.map +1 -1
  66. package/dist/internal/cli-ax.js +7 -4
  67. package/dist/internal/cli-deploy.d.ts.map +1 -1
  68. package/dist/internal/cli-deploy.js +45 -18
  69. package/dist/internal/deploy-client.d.ts +7 -1
  70. package/dist/internal/deploy-client.d.ts.map +1 -1
  71. package/dist/internal/deploy-client.js +3 -0
  72. package/dist/internal/deploy-source.d.ts +3 -0
  73. package/dist/internal/deploy-source.d.ts.map +1 -1
  74. package/dist/internal/deploy-source.js +25 -7
  75. package/dist/internal/discovery.js +1 -1
  76. package/dist/internal/distribution.d.ts.map +1 -1
  77. package/dist/internal/distribution.js +1 -0
  78. package/dist/internal/hosting.d.ts +12 -2
  79. package/dist/internal/hosting.d.ts.map +1 -1
  80. package/dist/internal/hosting.js +21 -4
  81. package/dist/internal/json-dir-store.d.ts +32 -0
  82. package/dist/internal/json-dir-store.d.ts.map +1 -0
  83. package/dist/internal/logs-client.d.ts +16 -2
  84. package/dist/internal/logs-client.d.ts.map +1 -1
  85. package/dist/internal/logs-client.js +51 -16
  86. package/dist/internal/persistence-coordinator.d.ts +127 -0
  87. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  88. package/dist/internal/resolved-connections.d.ts +6 -0
  89. package/dist/internal/resolved-connections.d.ts.map +1 -1
  90. package/dist/internal/resolved-connections.js +6 -2
  91. package/dist/internal/server.d.ts.map +1 -1
  92. package/dist/internal/server.js +11 -1
  93. package/dist/internal/session-engine.d.ts +23 -1
  94. package/dist/internal/session-engine.d.ts.map +1 -1
  95. package/dist/internal/session-engine.js +100 -26
  96. package/dist/internal/sessions-client.d.ts +16 -2
  97. package/dist/internal/sessions-client.d.ts.map +1 -1
  98. package/dist/internal/sessions-client.js +28 -12
  99. package/dist/internal/storage-coordinator.d.ts +11 -0
  100. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  101. package/dist/internal/storage-coordinator.js +30 -0
  102. package/dist/internal/workspace.d.ts +19 -0
  103. package/dist/internal/workspace.d.ts.map +1 -1
  104. package/dist/internal/workspace.js +47 -13
  105. package/dist/persistence.d.ts +184 -0
  106. package/dist/persistence.d.ts.map +1 -0
  107. package/dist/playground/assets/index-C0-QEG4O.js +86 -0
  108. package/dist/playground/assets/index-z3JY9O2x.css +1 -0
  109. package/dist/playground/index.html +2 -2
  110. package/dist/storage-backends/cursor-hosted.d.ts +75 -0
  111. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -0
  112. package/dist/storage-backends/cursor-hosted.js +217 -0
  113. package/dist/storage-backends/postgres-kv.d.ts +5 -0
  114. package/dist/storage-backends/postgres-kv.d.ts.map +1 -1
  115. package/dist/storage-backends/postgres-kv.js +5 -0
  116. package/dist/types.d.ts +15 -0
  117. package/dist/types.d.ts.map +1 -1
  118. package/docs/deployment.md +7 -3
  119. package/docs/example-agents/approval-buddy.md +1 -1
  120. package/docs/example-agents/index.md +1 -1
  121. package/docs/example-agents/security-reviewer.md +5 -5
  122. package/docs/reference/cli.md +2 -0
  123. package/docs/reference/connections.md +4 -2
  124. package/docs/storage.md +18 -4
  125. package/package.json +8 -1
  126. package/src/bin/agent-serve.ts +10 -0
  127. package/src/internal/cli-ax.ts +10 -4
  128. package/src/internal/cli-deploy.ts +58 -20
  129. package/src/internal/deploy-client.ts +11 -0
  130. package/src/internal/deploy-source.ts +24 -6
  131. package/src/internal/discovery.ts +1 -1
  132. package/src/internal/distribution.ts +1 -0
  133. package/src/internal/hosting.ts +24 -4
  134. package/src/internal/logs-client.ts +67 -21
  135. package/src/internal/resolved-connections.ts +14 -4
  136. package/src/internal/server.ts +15 -1
  137. package/src/internal/session-engine.ts +105 -13
  138. package/src/internal/sessions-client.ts +49 -14
  139. package/src/internal/storage-coordinator.ts +36 -0
  140. package/src/internal/workspace.ts +48 -11
  141. package/src/storage-backends/cursor-hosted.ts +267 -0
  142. package/src/storage-backends/postgres-kv.ts +5 -0
  143. package/src/types.ts +16 -0
  144. package/dist/docs/assets/chunks/@localSearchIndexroot.DevOabKJ.js +0 -1
  145. package/dist/playground/assets/index-7GJIUFQj.css +0 -1
  146. package/dist/playground/assets/index-Bpr4Th4M.js +0 -319
  147. /package/dist/docs/assets/{deployment.md.BtfEsc9S.lean.js → deployment.md.DC5-ImcM.lean.js} +0 -0
  148. /package/dist/docs/assets/{reference_cli.md.D189RBCH.lean.js → reference_cli.md.CjfiV2Q-.lean.js} +0 -0
  149. /package/dist/docs/assets/{reference_connections.md.zaEYCLHT.lean.js → reference_connections.md.FbWupyzW.lean.js} +0 -0
package/docs/storage.md CHANGED
@@ -113,13 +113,27 @@ propagate errors — unlike session mirrors, which are at-most-once.
113
113
 
114
114
  Without `agent/storage.ts`, `host.kv` falls back to files under
115
115
  `--state-root/kv`. That is fine for local dogfood; it does **not**
116
- survive replacing the host. For prod, author `defineStorage` (for example
117
- `@cursor/july/storage/postgres-kv`) so both sessions and author KV share
118
- the durable backend.
116
+ survive replacing the host. For Cursor-managed hosting, prefer
117
+ `@cursor/july/storage/cursor-hosted` so sessions and author KV share the
118
+ platform Bugbot tables through a control-plane HTTP proxy (authenticated
119
+ as the deployment pod credential — engines never receive a database URL).
120
+ For self-hosted Postgres you own, use `@cursor/july/storage/postgres-kv`
121
+ and supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` yourself.
122
+
123
+ ```ts
124
+ // agent/storage.ts — Cursor-managed hosting
125
+ import { defineStorage } from "@cursor/july/storage";
126
+ import { cursorHostedStorage } from "@cursor/july/storage/cursor-hosted";
127
+
128
+ export default defineStorage({
129
+ ...cursorHostedStorage(),
130
+ });
131
+ ```
119
132
 
120
133
  Built-in helpers:
121
134
 
122
135
  | Import | Backend |
123
136
  | --- | --- |
124
137
  | `@cursor/july/storage/file-kv` | File-per-key under `.agent-serve/kv` |
125
- | `@cursor/july/storage/postgres-kv` | `agent_serve_*` tables (`BUGBOTDB_URL`) |
138
+ | `@cursor/july/storage/cursor-hosted` | Platform Bugbot `agent_serve_*` via control-plane proxy |
139
+ | `@cursor/july/storage/postgres-kv` | Same tables; you supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -138,6 +138,13 @@
138
138
  "import": "./dist/storage-backends/postgres-kv.js",
139
139
  "default": "./dist/storage-backends/postgres-kv.js"
140
140
  },
141
+ "./storage/cursor-hosted": {
142
+ "anysphere-source": "./src/storage-backends/cursor-hosted.ts",
143
+ "bun": "./src/storage-backends/cursor-hosted.ts",
144
+ "types": "./dist/storage-backends/cursor-hosted.d.ts",
145
+ "import": "./dist/storage-backends/cursor-hosted.js",
146
+ "default": "./dist/storage-backends/cursor-hosted.js"
147
+ },
141
148
  "./package.json": {
142
149
  "anysphere-source": "./package.json",
143
150
  "bun": "./package.json",
@@ -247,6 +247,11 @@ Options:
247
247
  githubChannel({ cursorAccount }). Admitted callers can drive
248
248
  sessions holding the account's repo-scoped GitHub credential,
249
249
  so use only behind a trusted boundary (e.g. an SSO proxy).
250
+ --allow-anonymous-cursor-account-mcp
251
+ serve: allow --allow-anonymous together with
252
+ defineConnection({ cursorAccount: true }). Admitted callers
253
+ can drive the account's MCP connectors, so use only behind
254
+ a trusted boundary (e.g. an SSO proxy or hosted alias token).
250
255
  --public-url serve: base URL where this host is reachable from outside
251
256
  (e.g. https://agent-serve.example.com). Required for peer
252
257
  connections (defineConnection({ agent })) on cloud-runtime
@@ -372,6 +377,7 @@ async function main(argv: string[]): Promise<number> {
372
377
  "bearer-token": { type: "string" },
373
378
  "allow-anonymous": { type: "boolean", default: false },
374
379
  "allow-anonymous-cursor-github": { type: "boolean", default: false },
380
+ "allow-anonymous-cursor-account-mcp": { type: "boolean", default: false },
375
381
  "no-schedules": { type: "boolean", default: false },
376
382
  "no-playground": { type: "boolean", default: false },
377
383
  "no-docs": { type: "boolean", default: false },
@@ -976,6 +982,10 @@ async function main(argv: string[]): Promise<number> {
976
982
  allowAnonymous: values["allow-anonymous"] === true ? true : undefined,
977
983
  allowAnonymousCursorGithub:
978
984
  values["allow-anonymous-cursor-github"] === true ? true : undefined,
985
+ allowAnonymousCursorAccountMcp:
986
+ values["allow-anonymous-cursor-account-mcp"] === true
987
+ ? true
988
+ : undefined,
979
989
  schedules: values["no-schedules"] === true ? false : undefined,
980
990
  playground: values["no-playground"] === true ? false : undefined,
981
991
  docs: values["no-docs"] === true ? false : undefined,
@@ -54,7 +54,7 @@ import { runSession } from "./run-client.js";
54
54
  import {
55
55
  fetchSessionEvents,
56
56
  formatSessionsTable,
57
- listSessions,
57
+ listSessionsWithSource,
58
58
  resolvePreviousSessionId,
59
59
  } from "./sessions-client.js";
60
60
  import { createStreamProgress } from "./stream-progress.js";
@@ -1383,7 +1383,7 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
1383
1383
  return target;
1384
1384
  }
1385
1385
  try {
1386
- const sessions = await listSessions({
1386
+ const listed = await listSessionsWithSource({
1387
1387
  baseUrl: target.agentUrl,
1388
1388
  auth: {
1389
1389
  bearerToken: options.bearerToken,
@@ -1392,10 +1392,16 @@ export async function cmdSessions(options: AxCliOptions): Promise<number> {
1392
1392
  fetchImpl: options.fetchImpl,
1393
1393
  });
1394
1394
  if (options.json === true) {
1395
- process.stdout.write(`${JSON.stringify({ sessions }, null, 2)}\n`);
1395
+ const payload =
1396
+ listed.source === undefined
1397
+ ? { sessions: listed.sessions }
1398
+ : { sessions: listed.sessions, source: listed.source };
1399
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1396
1400
  return 0;
1397
1401
  }
1398
- process.stdout.write(formatSessionsTable(sessions));
1402
+ process.stdout.write(
1403
+ formatSessionsTable(listed.sessions, { source: listed.source })
1404
+ );
1399
1405
  return 0;
1400
1406
  } catch (error) {
1401
1407
  process.stderr.write(
@@ -40,9 +40,11 @@ import {
40
40
  } from "./discovery.js";
41
41
  import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
42
42
  import {
43
+ isReservedSecretName,
43
44
  isValidCursorEventRepo,
44
45
  validateSettableSecretName,
45
46
  } from "./hosting.js";
47
+ import { deployProgressLabel, sanitizeCustomerError } from "./logs-client.js";
46
48
  import { stderrPalette } from "./terminal-style.js";
47
49
 
48
50
  const DEFAULT_POLL_INTERVAL_MS = 3_000;
@@ -554,6 +556,24 @@ async function deployOne(
554
556
  );
555
557
  return 1;
556
558
  }
559
+ // Repo-backed fields without a resolvable https origin become a static
560
+ // deploy body the control plane rejects. Fail here with a fix hint —
561
+ // common when origin is an unrecognized SSH shape (pass --repo).
562
+ if (
563
+ gitRepoUrl === undefined &&
564
+ (gitRef !== undefined ||
565
+ agentPath !== undefined ||
566
+ (options.cursorEventsRepos !== undefined &&
567
+ options.cursorEventsRepos.length > 0) ||
568
+ (options.allowDomains !== undefined && options.allowDomains.length > 0))
569
+ ) {
570
+ ctx.err(
571
+ `Could not resolve an https:// git repository URL for this deploy.\n` +
572
+ ` Pass --repo https://github.com/<owner>/<repo> (inferred from origin when possible).\n` +
573
+ ` Or omit --ref/--path/--cursor-events-repo/--allow-domain for a static engine.\n`
574
+ );
575
+ return 1;
576
+ }
557
577
  for (const repo of options.cursorEventsRepos ?? []) {
558
578
  if (!isValidCursorEventRepo(repo)) {
559
579
  ctx.err(`Invalid --cursor-events-repo: ${repo} (expected owner/name)\n`);
@@ -659,13 +679,11 @@ async function deployOne(
659
679
  }
660
680
 
661
681
  if (detail.status !== "running") {
662
- ctx.err(
663
- `Deployment ${slug} ended ${detail.status}${
664
- detail.lastError === null || detail.lastError === undefined
665
- ? ""
666
- : `: ${detail.lastError}`
667
- }\n`
668
- );
682
+ const errorDetail =
683
+ detail.lastError === null || detail.lastError === undefined
684
+ ? ""
685
+ : `: ${sanitizeCustomerError(detail.lastError)}`;
686
+ ctx.err(`Deployment ${slug} ended ${detail.status}${errorDetail}\n`);
669
687
  return 1;
670
688
  }
671
689
  ctx.out(
@@ -709,7 +727,11 @@ function warnMissingSecrets(
709
727
  return;
710
728
  }
711
729
  const setNames = new Set(set ?? []);
712
- const missing = declared.filter((name) => !setNames.has(name));
730
+ // Platform-managed names (CURSOR_*, hosted storage) are injected by the
731
+ // control plane and never appear in secrets list — do not warn on them.
732
+ const missing = declared.filter(
733
+ (name) => !setNames.has(name) && !isReservedSecretName(name)
734
+ );
713
735
  if (missing.length === 0) {
714
736
  return;
715
737
  }
@@ -793,11 +815,23 @@ export async function cmdDeploymentStatus(
793
815
  const lines = [
794
816
  `${detail.slug} (team ${detail.teamId})`,
795
817
  ` status: ${detail.status}`,
818
+ ];
819
+ if (detail.progress !== undefined) {
820
+ lines.push(` progress: ${detail.progress}`);
821
+ }
822
+ if (
823
+ detail.statusMessage !== null &&
824
+ detail.statusMessage !== undefined &&
825
+ detail.statusMessage.trim() !== ""
826
+ ) {
827
+ lines.push(` step: ${detail.statusMessage}`);
828
+ }
829
+ lines.push(
796
830
  ` generation: ${detail.generation}`,
797
831
  ` kind: ${detail.deploymentKind}`,
798
832
  ` alias: ${aliasUrl}`,
799
- ` playground: ${aliasUrl}/playground`,
800
- ];
833
+ ` playground: ${aliasUrl}/playground`
834
+ );
801
835
  if (detail.gitRepoUrl !== null && detail.gitRepoUrl !== undefined) {
802
836
  lines.push(` repo: ${detail.gitRepoUrl} @ ${detail.gitRef ?? ""}`);
803
837
  }
@@ -835,7 +869,7 @@ export async function cmdDeploymentStatus(
835
869
  );
836
870
  }
837
871
  if (detail.lastError !== null && detail.lastError !== undefined) {
838
- lines.push(` last error: ${detail.lastError}`);
872
+ lines.push(` last error: ${sanitizeCustomerError(detail.lastError)}`);
839
873
  }
840
874
  if (detail.desiredUpdatedAt !== undefined) {
841
875
  lines.push(` updated: ${detail.desiredUpdatedAt}`);
@@ -1229,7 +1263,9 @@ function createLineReader(
1229
1263
  /**
1230
1264
  * Poll deployment status every `pollIntervalMs` until it lands in
1231
1265
  * `terminal` (single-line progress on a TTY, one line per transition
1232
- * otherwise). Throws DeployApiError on request failures or timeout.
1266
+ * otherwise). Prefers customer `statusMessage` / `progress` over raw
1267
+ * status so create → checkout → start is visible. Throws DeployApiError
1268
+ * on request failures or timeout.
1233
1269
  */
1234
1270
  async function pollUntil(
1235
1271
  ctx: DeployCliContext,
@@ -1238,21 +1274,22 @@ async function pollUntil(
1238
1274
  ): Promise<DeploymentDetail> {
1239
1275
  const startedAt = Date.now();
1240
1276
  const tty = process.stderr.isTTY === true;
1241
- let lastStatus: DeploymentStatus | undefined;
1277
+ let lastLabel: string | undefined;
1242
1278
  let sawProgress = false;
1243
1279
 
1244
- const report = (status: DeploymentStatus): void => {
1280
+ const report = (detail: DeploymentDetail): void => {
1245
1281
  if (ctx.json) {
1246
1282
  return;
1247
1283
  }
1284
+ const label = deployProgressLabel(detail);
1248
1285
  const elapsed = Math.round((Date.now() - startedAt) / 1000);
1249
1286
  if (tty) {
1250
- ctx.err(`\r\x1b[2K${slug}: ${status} (${elapsed}s)`);
1287
+ ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
1251
1288
  sawProgress = true;
1252
- } else if (status !== lastStatus) {
1253
- ctx.err(`${slug}: ${status}\n`);
1289
+ } else if (label !== lastLabel) {
1290
+ ctx.err(`${slug}: ${label}\n`);
1254
1291
  }
1255
- lastStatus = status;
1292
+ lastLabel = label;
1256
1293
  };
1257
1294
  const finishProgressLine = (): void => {
1258
1295
  if (tty && sawProgress) {
@@ -1267,15 +1304,16 @@ async function pollUntil(
1267
1304
  finishProgressLine();
1268
1305
  throw error;
1269
1306
  });
1270
- report(detail.status);
1307
+ report(detail);
1271
1308
  if (terminal.has(detail.status)) {
1272
1309
  finishProgressLine();
1273
1310
  return detail;
1274
1311
  }
1275
1312
  if (Date.now() - startedAt >= ctx.timeoutMs) {
1276
1313
  finishProgressLine();
1314
+ const last = deployProgressLabel(detail);
1277
1315
  throw new DeployApiError(
1278
- `Timed out after ${Math.round(ctx.timeoutMs / 1000)}s waiting for ${slug} (last status: ${detail.status}). It may still converge — check \`${CLI} deployment ${slug} --team ${ctx.teamId}\`.`,
1316
+ `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}\`.`,
1279
1317
  0
1280
1318
  );
1281
1319
  }
@@ -31,7 +31,9 @@ export type DeploymentStatus =
31
31
  export type DeploymentProgress =
32
32
  | "queued"
33
33
  | "preparing"
34
+ | "creating"
34
35
  | "building"
36
+ | "starting"
35
37
  | "finalizing"
36
38
  | "running"
37
39
  | "failed"
@@ -135,6 +137,12 @@ export interface Deployment {
135
137
  * backends; older responses omit it.
136
138
  */
137
139
  progress?: DeploymentProgress;
140
+ /**
141
+ * Live step while the deploy converges (e.g. "Checking out source…").
142
+ * Null when running / stopped or when only a failure is on the row.
143
+ * Present on newer backends; older responses omit it.
144
+ */
145
+ statusMessage?: string | null;
138
146
  generation: number;
139
147
  observedGeneration?: number | null;
140
148
  engineGeneration?: number | null;
@@ -218,7 +226,9 @@ const deploymentSchema = z.object({
218
226
  .enum([
219
227
  "queued",
220
228
  "preparing",
229
+ "creating",
221
230
  "building",
231
+ "starting",
222
232
  "finalizing",
223
233
  "running",
224
234
  "failed",
@@ -227,6 +237,7 @@ const deploymentSchema = z.object({
227
237
  "degraded",
228
238
  ])
229
239
  .optional(),
240
+ statusMessage: z.string().nullable().optional(),
230
241
  generation: z.number(),
231
242
  observedGeneration: z.number().nullable().optional(),
232
243
  engineGeneration: z.number().nullable().optional(),
@@ -39,17 +39,35 @@ const defaultGitRunner: GitRunner = async (args, cwd) => {
39
39
  * Normalize a git remote URL to the https form the control plane accepts
40
40
  * (`https://host/owner/repo`, no `.git` suffix). Returns undefined for
41
41
  * unrecognized shapes.
42
+ *
43
+ * Accepts HTTPS, `git@host:path`, GitHub org-scoped SSH
44
+ * (`org-123@github.com:owner/repo.git`), and `ssh://user@host[:port]/path`.
42
45
  */
43
46
  export function httpsGitRepoUrlFromRemote(remote: string): string | undefined {
44
47
  let url = remote.trim();
45
48
  if (url === "") {
46
49
  return undefined;
47
50
  }
48
- if (url.startsWith("git@")) {
49
- // git@host:owner/repo.git https://host/owner/repo.git
50
- url = `https://${url.slice("git@".length).replace(":", "/")}`;
51
- } else if (url.startsWith("ssh://git@")) {
52
- url = `https://${url.slice("ssh://git@".length)}`;
51
+
52
+ // SCP-like SSH: user@host:owner/repo(.git) includes git@ and GitHub
53
+ // org deploy keys (org-NNNN@github.com:…).
54
+ const scpMatch = /^([^@/\s]+)@([^:/\s]+):(.+)$/.exec(url);
55
+ if (scpMatch !== null) {
56
+ const host = scpMatch[2]!;
57
+ const path = scpMatch[3]!;
58
+ url = `https://${host}/${path}`;
59
+ } else if (/^ssh:\/\//i.test(url)) {
60
+ // ssh://user@host[:port]/owner/repo → https://host/owner/repo
61
+ try {
62
+ const ssh = new URL(url);
63
+ const path = ssh.pathname.replace(/^\/+/, "");
64
+ if (ssh.hostname === "" || path === "") {
65
+ return undefined;
66
+ }
67
+ url = `https://${ssh.hostname}/${path}`;
68
+ } catch {
69
+ return undefined;
70
+ }
53
71
  } else if (!/^https?:\/\//i.test(url)) {
54
72
  return undefined;
55
73
  }
@@ -67,7 +85,7 @@ export function httpsGitRepoUrlFromRemote(remote: string): string | undefined {
67
85
  if (path === "" || !path.includes("/")) {
68
86
  return undefined;
69
87
  }
70
- // Use hostname (not host) so ssh://git@github.com:22/… does not keep :22.
88
+ // Use hostname (not host) so ssh://…@github.com:22/… does not keep :22.
71
89
  return `https://${parsed.hostname}/${path}`;
72
90
  }
73
91
 
@@ -454,7 +454,7 @@ function resolveHosting(
454
454
  warn(`hosting.secretNames: ${nameError}`);
455
455
  } else if (isReservedSecretName(name)) {
456
456
  warn(
457
- `hosting.secretNames: ${JSON.stringify(name)} is platform-reserved (CURSOR_*) and injected automatically; it cannot be set with \`secrets set\`.`
457
+ `hosting.secretNames: ${JSON.stringify(name)} is platform-managed (CURSOR_* / Cursor-hosted storage) and injected automatically; omit it from secretNames.`
458
458
  );
459
459
  }
460
460
  }
@@ -105,4 +105,5 @@ export const AUTHORING_ENTRY_FILES: ReadonlyArray<
105
105
  ["storage", "storage.js"],
106
106
  ["storage/file-kv", "storage-backends/file-kv.js"],
107
107
  ["storage/postgres-kv", "storage-backends/postgres-kv.js"],
108
+ ["storage/cursor-hosted", "storage-backends/cursor-hosted.js"],
108
109
  ];
@@ -18,6 +18,17 @@ export const MAX_SECRET_NAME_LENGTH = 64;
18
18
  /** Secret names the platform injects; user writes are rejected. */
19
19
  export const RESERVED_SECRET_NAME_PREFIX = "CURSOR_";
20
20
 
21
+ /**
22
+ * Additional platform-managed env names for Cursor-hosted durable storage
23
+ * (Bugbot `agent_serve_*`). Mirrored from
24
+ * `@cursor/july/storage/cursor-hosted` so validate/secrets CLI stay in sync
25
+ * without importing the storage backend into the CLI graph.
26
+ */
27
+ export const PLATFORM_HOSTED_STORAGE_SECRET_NAMES = [
28
+ "BUGBOTDB_URL",
29
+ "AGENT_SERVE_DEPLOYMENT_ID",
30
+ ] as const;
31
+
21
32
  const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
22
33
 
23
34
  // Lowercase hostname with >=2 labels, an alphabetic TLD, and at most one
@@ -49,14 +60,23 @@ export function validateSecretName(name: string): string | undefined {
49
60
  return undefined;
50
61
  }
51
62
 
52
- /** True when the platform reserves this name (`CURSOR_*`); writes are rejected. */
63
+ /**
64
+ * True when the platform manages this name (`CURSOR_*` or hosted-storage
65
+ * env vars); user writes / deletes are rejected.
66
+ */
53
67
  export function isReservedSecretName(name: string): boolean {
54
- return name.startsWith(RESERVED_SECRET_NAME_PREFIX);
68
+ if (name.startsWith(RESERVED_SECRET_NAME_PREFIX)) {
69
+ return true;
70
+ }
71
+ const upper = name.toUpperCase();
72
+ return (PLATFORM_HOSTED_STORAGE_SECRET_NAMES as readonly string[]).some(
73
+ (managed) => managed === upper
74
+ );
55
75
  }
56
76
 
57
77
  /**
58
78
  * Human error for a secret name a user is trying to write or delete:
59
- * grammar plus the reserved `CURSOR_*` prefix.
79
+ * grammar plus platform-managed names (`CURSOR_*`, hosted storage).
60
80
  */
61
81
  export function validateSettableSecretName(name: string): string | undefined {
62
82
  const nameError = validateSecretName(name);
@@ -64,7 +84,7 @@ export function validateSettableSecretName(name: string): string | undefined {
64
84
  return nameError;
65
85
  }
66
86
  if (isReservedSecretName(name)) {
67
- return `Secret name ${JSON.stringify(name)} is platform-reserved (CURSOR_*); the platform injects those values automatically.`;
87
+ return `Secret name ${JSON.stringify(name)} is platform-managed; the platform injects CURSOR_* and Cursor-hosted storage values automatically.`;
68
88
  }
69
89
  return undefined;
70
90
  }
@@ -35,7 +35,9 @@ export type LogsProgress =
35
35
  | DeploymentProgress
36
36
  | "queued"
37
37
  | "preparing"
38
+ | "creating"
38
39
  | "building"
40
+ | "starting"
39
41
  | "finalizing"
40
42
  | "running"
41
43
  | "failed"
@@ -46,7 +48,9 @@ export type LogsProgress =
46
48
  const PROGRESS_MESSAGES: Readonly<Record<LogsProgress, string>> = {
47
49
  queued: "Waiting to start…",
48
50
  preparing: "Preparing runtime…",
49
- building: "Building and starting agent…",
51
+ creating: "Creating runtime…",
52
+ building: "Checking out source and installing dependencies…",
53
+ starting: "Starting agent and waiting for health…",
50
54
  finalizing: "Finishing deployment…",
51
55
  running: "Deployment running",
52
56
  failed: "Deploy failed",
@@ -55,9 +59,15 @@ const PROGRESS_MESSAGES: Readonly<Record<LogsProgress, string>> = {
55
59
  degraded: "Deployment degraded",
56
60
  };
57
61
 
62
+ /**
63
+ * Customer-facing line for the current deploy step. Prefer the live
64
+ * `statusMessage` from the API when present; otherwise the progress
65
+ * vocabulary. Failed/degraded always append sanitized `lastError`.
66
+ */
58
67
  export function progressMessage(
59
68
  progress: LogsProgress,
60
- lastError?: string | null
69
+ lastError?: string | null,
70
+ statusMessage?: string | null
61
71
  ): string {
62
72
  if (progress === "failed") {
63
73
  const detail =
@@ -73,6 +83,13 @@ export function progressMessage(
73
83
  : `: ${sanitizeCustomerError(lastError)}`;
74
84
  return `Deployment degraded${detail}`;
75
85
  }
86
+ if (
87
+ statusMessage !== null &&
88
+ statusMessage !== undefined &&
89
+ statusMessage.trim() !== ""
90
+ ) {
91
+ return statusMessage.trim();
92
+ }
76
93
  return PROGRESS_MESSAGES[progress];
77
94
  }
78
95
 
@@ -85,7 +102,38 @@ export function sanitizeCustomerError(message: string): string {
85
102
  .replace(/\bcursorvm\.com\b/gi, "host")
86
103
  .replace(/\bx-anyrun-[a-z0-9-]+\b/gi, "header")
87
104
  .replace(/\bPROVISIONING\b/g, "building")
88
- .replace(/\bPLANNED\b/g, "preparing");
105
+ .replace(/\bPLANNED\b/g, "preparing")
106
+ .replace(/\bgitCloneCheckout\b/gi, "git clone")
107
+ .replace(/\bgitFetchCheckout\b/gi, "git fetch")
108
+ .replace(/\bgitCheckout:clone\b/gi, "checkout")
109
+ .replace(/\bgitCheckout:fetch\b/gi, "checkout");
110
+ }
111
+
112
+ /**
113
+ * One-line label for `agentkit deploy` polling (TTY spinner / non-TTY
114
+ * transitions). Uses statusMessage → progress → raw status.
115
+ */
116
+ export function deployProgressLabel(detail: {
117
+ status: string;
118
+ progress?: DeploymentProgress;
119
+ statusMessage?: string | null;
120
+ }): string {
121
+ if (
122
+ detail.statusMessage !== null &&
123
+ detail.statusMessage !== undefined &&
124
+ detail.statusMessage.trim() !== ""
125
+ ) {
126
+ return detail.statusMessage
127
+ .trim()
128
+ .replace(/…$/, "")
129
+ .replace(/\.\.\.$/, "");
130
+ }
131
+ if (detail.progress !== undefined) {
132
+ return PROGRESS_MESSAGES[detail.progress]
133
+ .replace(/…$/, "")
134
+ .replace(/\.\.\.$/, "");
135
+ }
136
+ return detail.status;
89
137
  }
90
138
 
91
139
  export async function fetchLogs(args: {
@@ -241,7 +289,7 @@ async function followProdLogs(args: {
241
289
  );
242
290
  });
243
291
 
244
- let lastProgress: string | undefined;
292
+ let lastEmittedKey: string | undefined;
245
293
  const slugHint = args.slug !== undefined && args.slug !== "" ? args.slug : "";
246
294
  if (args.once !== true) {
247
295
  const label = slugHint !== "" ? slugHint : "deployment";
@@ -260,33 +308,32 @@ async function followProdLogs(args: {
260
308
  }
261
309
 
262
310
  const progress = resolveProgress(detail);
311
+ const message = progressMessage(
312
+ progress,
313
+ detail.lastError,
314
+ detail.statusMessage
315
+ );
316
+ const emitKey = `${progress}\0${message}`;
263
317
 
264
318
  if (progress === "failed") {
265
- const message = progressMessage(progress, detail.lastError);
266
- if (progress !== lastProgress) {
267
- lastProgress = progress;
319
+ if (emitKey !== lastEmittedKey) {
320
+ lastEmittedKey = emitKey;
268
321
  emitProgress(args.write, args.json === true, progress, message);
269
322
  }
270
323
  throw new Error(`Deployment "${detail.slug}" failed.`);
271
324
  }
272
325
  if (progress === "stopped") {
273
- const message = progressMessage(progress, detail.lastError);
274
- if (progress !== lastProgress) {
275
- lastProgress = progress;
326
+ if (emitKey !== lastEmittedKey) {
327
+ lastEmittedKey = emitKey;
276
328
  emitProgress(args.write, args.json === true, progress, message);
277
329
  }
278
330
  throw new Error(`Deployment "${detail.slug}" is stopped.`);
279
331
  }
280
332
  if (progress === "running" || progress === "degraded") {
281
333
  // Hand off to runtime logs. Print degraded once; skip a "running" line.
282
- if (progress === "degraded" && progress !== lastProgress) {
283
- lastProgress = progress;
284
- emitProgress(
285
- args.write,
286
- args.json === true,
287
- progress,
288
- progressMessage(progress, detail.lastError)
289
- );
334
+ if (progress === "degraded" && emitKey !== lastEmittedKey) {
335
+ lastEmittedKey = emitKey;
336
+ emitProgress(args.write, args.json === true, progress, message);
290
337
  }
291
338
  const runtime = await resolveRuntime();
292
339
  await followRuntimeLogs({
@@ -306,9 +353,8 @@ async function followProdLogs(args: {
306
353
  return;
307
354
  }
308
355
 
309
- const message = progressMessage(progress, detail.lastError);
310
- if (progress !== lastProgress) {
311
- lastProgress = progress;
356
+ if (emitKey !== lastEmittedKey) {
357
+ lastEmittedKey = emitKey;
312
358
  emitProgress(args.write, args.json === true, progress, message);
313
359
  }
314
360
 
@@ -78,13 +78,23 @@ export function assertCloudCanHonorAccountServersFilters(args: {
78
78
  /** Fail closed when anonymous HTTP admission would expose account connectors. */
79
79
  export function assertAccountConnectionsNotAnonymous(args: {
80
80
  allowAnonymous?: boolean;
81
+ /**
82
+ * Opt in when every admitted caller is already trusted (e.g. behind an
83
+ * authenticating proxy / hosted alias token). Same trust model as
84
+ * `--allow-anonymous-cursor-github`.
85
+ */
86
+ allowAnonymousCursorAccountMcp?: boolean;
81
87
  hasAccountConnections: boolean;
82
88
  }): void {
83
- if (args.allowAnonymous === true && args.hasAccountConnections) {
84
- throw new Error(
85
- "[agentkit] --allow-anonymous cannot be used with Cursor account MCP connections (defineConnection({ cursorAccount: true })); use --bearer-token or remove the account MCP connections."
86
- );
89
+ if (args.allowAnonymous !== true || !args.hasAccountConnections) {
90
+ return;
91
+ }
92
+ if (args.allowAnonymousCursorAccountMcp === true) {
93
+ return;
87
94
  }
95
+ throw new Error(
96
+ "[agentkit] --allow-anonymous cannot be used with Cursor account MCP connections (defineConnection({ cursorAccount: true })); use --bearer-token, or opt in with --allow-anonymous-cursor-account-mcp behind a trusted boundary."
97
+ );
88
98
  }
89
99
 
90
100
  export function resolveMountConnections(args: {
@@ -307,8 +307,18 @@ export async function startServer(
307
307
  const accountRefsBySlug = collectCursorAccountConnections(mounts);
308
308
  assertAccountConnectionsNotAnonymous({
309
309
  allowAnonymous: options.allowAnonymous,
310
+ allowAnonymousCursorAccountMcp: options.allowAnonymousCursorAccountMcp,
310
311
  hasAccountConnections: accountRefsBySlug.size > 0,
311
312
  });
313
+ if (
314
+ options.allowAnonymous === true &&
315
+ options.allowAnonymousCursorAccountMcp === true &&
316
+ accountRefsBySlug.size > 0
317
+ ) {
318
+ logger(
319
+ "[agentkit] warning: --allow-anonymous-cursor-account-mcp — every admitted HTTP caller can drive Cursor account MCP connectors; serve this only behind a trusted boundary (e.g. an SSO proxy or hosted alias token)"
320
+ );
321
+ }
312
322
  let accountBridge: CursorAccountMcpBridge | undefined;
313
323
  if (accountRefsBySlug.size > 0) {
314
324
  if (accountKey === undefined) {
@@ -914,8 +924,12 @@ function mountAgentRoutes(
914
924
  (relaxSessionReadAuthBeyondLoopback ||
915
925
  (typeof ctx.requestIp === "string" &&
916
926
  isLoopbackAddress(ctx.requestIp)));
927
+ const listed = await engine.listSessionsWithSource(ctx.auth, {
928
+ includeAll,
929
+ });
917
930
  return jsonResponse({
918
- sessions: await engine.listSessions(ctx.auth, { includeAll }),
931
+ sessions: listed.sessions,
932
+ source: listed.source,
919
933
  });
920
934
  },
921
935
  });