@cursor/july 0.1.11 → 0.1.14

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 (174) hide show
  1. package/dist/bin/agent-serve.js +32 -9
  2. package/dist/channels/github/api.d.ts.map +1 -1
  3. package/dist/channels/github/api.js +11 -4
  4. package/dist/channels/github/cursor-account.d.ts +21 -1
  5. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  6. package/dist/channels/github/cursor-account.js +29 -1
  7. package/dist/channels/github/github-channel.d.ts.map +1 -1
  8. package/dist/channels/github/github-channel.js +1 -1
  9. package/dist/channels/github/instrument.d.ts +20 -0
  10. package/dist/channels/github/instrument.d.ts.map +1 -0
  11. package/dist/channels/slack/api.d.ts +9 -0
  12. package/dist/channels/slack/api.d.ts.map +1 -1
  13. package/dist/channels/slack/api.js +40 -7
  14. package/dist/channels/slack/eval-directive.d.ts +3 -0
  15. package/dist/channels/slack/eval-directive.d.ts.map +1 -1
  16. package/dist/channels/slack/eval-directive.js +20 -1
  17. package/dist/docs/404.html +2 -2
  18. package/dist/docs/ab.html +3 -3
  19. package/dist/docs/assets/{app.BcXUGca7.js → app.CKFFLL4Z.js} +1 -1
  20. package/dist/docs/assets/chunks/@localSearchIndexroot.DGWkvXJc.js +1 -0
  21. package/dist/docs/assets/chunks/{VPLocalSearchBox.CWur6XGZ.js → VPLocalSearchBox.BXk19JBN.js} +1 -1
  22. package/dist/docs/assets/chunks/{theme.BnvulOX2.js → theme.BXSPBMyn.js} +2 -2
  23. package/dist/docs/assets/{evals.md.DAgEc_hL.js → evals.md.DYOjkRCX.js} +10 -4
  24. package/dist/docs/assets/{evals.md.DAgEc_hL.lean.js → evals.md.DYOjkRCX.lean.js} +1 -1
  25. package/dist/docs/assets/{reference_cli.md.B74NrVzb.js → reference_cli.md.DnYfr5V2.js} +4 -1
  26. package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.js → reference_http-api.md.BncLd3PZ.js} +2 -2
  27. package/dist/docs/assets/{reference_http-api.md.Dx_nmDG6.lean.js → reference_http-api.md.BncLd3PZ.lean.js} +1 -1
  28. package/dist/docs/building-with-agents.html +3 -3
  29. package/dist/docs/concepts.html +3 -3
  30. package/dist/docs/deployment.html +3 -3
  31. package/dist/docs/evals.html +13 -7
  32. package/dist/docs/example-agents/approval-buddy.html +3 -3
  33. package/dist/docs/example-agents/benny.html +3 -3
  34. package/dist/docs/example-agents/bugbot.html +3 -3
  35. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  36. package/dist/docs/example-agents/codeowners-review.html +3 -3
  37. package/dist/docs/example-agents/concierge.html +3 -3
  38. package/dist/docs/example-agents/fsd.html +3 -3
  39. package/dist/docs/example-agents/index.html +3 -3
  40. package/dist/docs/example-agents/knowledge-base.html +3 -3
  41. package/dist/docs/example-agents/oncall.html +3 -3
  42. package/dist/docs/example-agents/security-reviewer.html +3 -3
  43. package/dist/docs/example-agents/slack-agent.html +3 -3
  44. package/dist/docs/example-agents/weather-agent.html +3 -3
  45. package/dist/docs/guides/agent-to-agent.html +3 -3
  46. package/dist/docs/guides/cloud-runtime.html +3 -3
  47. package/dist/docs/guides/github.html +3 -3
  48. package/dist/docs/guides/human-in-the-loop.html +3 -3
  49. package/dist/docs/guides/mcp-oauth.html +3 -3
  50. package/dist/docs/guides/slack.html +3 -3
  51. package/dist/docs/guides/webhooks.html +3 -3
  52. package/dist/docs/hashmap.json +1 -1
  53. package/dist/docs/hillclimbing.html +3 -3
  54. package/dist/docs/index.html +3 -3
  55. package/dist/docs/quickstart.html +3 -3
  56. package/dist/docs/reference/agent-config.html +3 -3
  57. package/dist/docs/reference/channels.html +3 -3
  58. package/dist/docs/reference/cli.html +8 -5
  59. package/dist/docs/reference/connections.html +3 -3
  60. package/dist/docs/reference/hooks.html +3 -3
  61. package/dist/docs/reference/http-api.html +5 -5
  62. package/dist/docs/reference/instructions.html +3 -3
  63. package/dist/docs/reference/playground.html +3 -3
  64. package/dist/docs/reference/project-layout.html +3 -3
  65. package/dist/docs/reference/prompt.html +3 -3
  66. package/dist/docs/reference/schedules.html +3 -3
  67. package/dist/docs/reference/sessions.html +3 -3
  68. package/dist/docs/reference/skills.html +3 -3
  69. package/dist/docs/reference/subagents.html +3 -3
  70. package/dist/docs/reference/tools.html +3 -3
  71. package/dist/docs/scaffolding-agents.html +3 -3
  72. package/dist/docs/storage.html +3 -3
  73. package/dist/docs/troubleshooting.html +3 -3
  74. package/dist/evals.d.ts +41 -2
  75. package/dist/evals.d.ts.map +1 -1
  76. package/dist/evals.js +53 -16
  77. package/dist/internal/cli-ax.d.ts +4 -0
  78. package/dist/internal/cli-ax.d.ts.map +1 -1
  79. package/dist/internal/cli-ax.js +163 -33
  80. package/dist/internal/cli-deploy.d.ts +8 -0
  81. package/dist/internal/cli-deploy.d.ts.map +1 -1
  82. package/dist/internal/cli-deploy.js +92 -12
  83. package/dist/internal/cli-mcp-oauth.d.ts +14 -2
  84. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
  85. package/dist/internal/cli-mcp-oauth.js +95 -2
  86. package/dist/internal/cursor/backend-client.d.ts +34 -0
  87. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  88. package/dist/internal/cursor/backend-client.js +73 -9
  89. package/dist/internal/cursor/credentials.d.ts +7 -1
  90. package/dist/internal/cursor/credentials.d.ts.map +1 -1
  91. package/dist/internal/cursor/credentials.js +9 -3
  92. package/dist/internal/cursor-account-mcp-auth.d.ts +55 -0
  93. package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -0
  94. package/dist/internal/cursor-account-mcp-auth.js +175 -0
  95. package/dist/internal/cursor-event-relay.d.ts +2 -0
  96. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  97. package/dist/internal/cursor-event-relay.js +3 -0
  98. package/dist/internal/deploy-client.d.ts +6 -0
  99. package/dist/internal/deploy-client.d.ts.map +1 -1
  100. package/dist/internal/deploy-client.js +13 -0
  101. package/dist/internal/eval-run-store.d.ts +15 -0
  102. package/dist/internal/eval-run-store.d.ts.map +1 -1
  103. package/dist/internal/eval-run-store.js +87 -8
  104. package/dist/internal/eval-runner.d.ts +12 -0
  105. package/dist/internal/eval-runner.d.ts.map +1 -1
  106. package/dist/internal/eval-runner.js +97 -22
  107. package/dist/internal/evals-client.d.ts +9 -1
  108. package/dist/internal/evals-client.d.ts.map +1 -1
  109. package/dist/internal/evals-client.js +43 -0
  110. package/dist/internal/http-channel.d.ts.map +1 -1
  111. package/dist/internal/http-channel.js +74 -7
  112. package/dist/internal/map-pool.d.ts +6 -1
  113. package/dist/internal/map-pool.d.ts.map +1 -1
  114. package/dist/internal/map-pool.js +8 -1
  115. package/dist/internal/mcp-host.d.ts +13 -0
  116. package/dist/internal/mcp-host.d.ts.map +1 -1
  117. package/dist/internal/mcp-host.js +68 -13
  118. package/dist/internal/run-client.d.ts +5 -1
  119. package/dist/internal/run-client.d.ts.map +1 -1
  120. package/dist/internal/run-client.js +26 -5
  121. package/dist/internal/server.d.ts.map +1 -1
  122. package/dist/internal/server.js +57 -4
  123. package/dist/internal/session-engine.d.ts +13 -0
  124. package/dist/internal/session-engine.d.ts.map +1 -1
  125. package/dist/internal/session-engine.js +49 -0
  126. package/dist/internal/storage-coordinator.d.ts +7 -0
  127. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  128. package/dist/internal/storage-coordinator.js +29 -4
  129. package/dist/playground/assets/index-CuXb4j4V.css +1 -0
  130. package/dist/playground/assets/index-DeyYpOit.js +86 -0
  131. package/dist/playground/index.html +2 -2
  132. package/dist/types.d.ts +10 -0
  133. package/dist/types.d.ts.map +1 -1
  134. package/docs/evals.md +46 -11
  135. package/docs/reference/cli.md +3 -0
  136. package/docs/reference/http-api.md +2 -1
  137. package/package.json +1 -1
  138. package/src/bin/agent-serve.ts +38 -8
  139. package/src/channels/github/api.ts +12 -1
  140. package/src/channels/github/cursor-account.ts +51 -1
  141. package/src/channels/github/github-channel.ts +4 -1
  142. package/src/channels/slack/api.ts +57 -8
  143. package/src/channels/slack/eval-directive.ts +24 -1
  144. package/src/evals.ts +104 -25
  145. package/src/internal/cli-ax.ts +192 -16
  146. package/src/internal/cli-deploy.ts +140 -13
  147. package/src/internal/cli-mcp-oauth.ts +122 -3
  148. package/src/internal/cursor/backend-client.ts +104 -1
  149. package/src/internal/cursor/credentials.ts +10 -2
  150. package/src/internal/cursor-account-mcp-auth.ts +241 -0
  151. package/src/internal/cursor-event-relay.ts +5 -0
  152. package/src/internal/deploy-client.ts +16 -0
  153. package/src/internal/eval-run-store.ts +89 -6
  154. package/src/internal/eval-runner.ts +113 -18
  155. package/src/internal/evals-client.ts +81 -0
  156. package/src/internal/http-channel.ts +87 -6
  157. package/src/internal/map-pool.ts +8 -1
  158. package/src/internal/mcp-host.ts +74 -16
  159. package/src/internal/run-client.ts +32 -1
  160. package/src/internal/server.ts +69 -2
  161. package/src/internal/session-engine.ts +58 -0
  162. package/src/internal/storage-coordinator.ts +32 -6
  163. package/src/types.ts +10 -0
  164. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  165. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  166. package/dist/channels/slack/post-update-delivery.js +0 -318
  167. package/dist/docs/assets/chunks/@localSearchIndexroot.BIbvK8M1.js +0 -1
  168. package/dist/internal/corp-ca.d.ts +0 -9
  169. package/dist/internal/corp-ca.d.ts.map +0 -1
  170. package/dist/internal/corp-ca.js +0 -44
  171. package/dist/playground/assets/index-BIGLubwK.js +0 -86
  172. package/dist/playground/assets/index-C17ZQKDG.css +0 -1
  173. package/docs/.vitepress/cache/deps/package.json +0 -3
  174. /package/dist/docs/assets/{reference_cli.md.B74NrVzb.lean.js → reference_cli.md.DnYfr5V2.lean.js} +0 -0
@@ -25,11 +25,13 @@ import {
25
25
  runDiscoveredEvals,
26
26
  } from "./eval-runner.js";
27
27
  import {
28
+ cancelRemoteEvalRun,
28
29
  getRemoteEvalRun,
29
30
  listRemoteEvalRuns,
30
31
  listRemoteEvals,
32
+ pollRemoteEvalRun,
31
33
  RemoteEvalError,
32
- runRemoteEvals,
34
+ snapshotToEvalRunResults,
33
35
  startRemoteEvalRun,
34
36
  } from "./evals-client.js";
35
37
  import {
@@ -874,23 +876,38 @@ export async function cmdEval(options: AxCliOptions): Promise<number> {
874
876
  },
875
877
  fetchImpl: options.fetchImpl,
876
878
  });
877
- return printEvalAccepted(started, options, target.agentUrl);
879
+ return await printEvalAccepted(started, options, target.agentUrl);
878
880
  }
879
881
 
880
882
  const progress = createRemoteEvalProgress(
881
883
  options.verbose === true || stream
882
884
  );
883
- const { snapshot, results } = await runRemoteEvals({
885
+ // Start first so we can print the Eval ID before the (often long) wait.
886
+ const started = await startRemoteEvalRun({
884
887
  baseUrl: target.agentUrl,
885
888
  filterIds,
886
889
  tags,
887
890
  timeoutMs: options.timeoutMs,
888
891
  verbose: Boolean(options.verbose) || stream,
889
- bearerToken: options.bearerToken,
890
- headers: target.headers,
892
+ auth: {
893
+ bearerToken: options.bearerToken,
894
+ headers: target.headers,
895
+ },
896
+ fetchImpl: options.fetchImpl,
897
+ });
898
+ await printEvalIdBanner(started, options, target.agentUrl);
899
+ progress?.onSnapshot?.(started);
900
+ const snapshot = await pollRemoteEvalRun({
901
+ baseUrl: target.agentUrl,
902
+ runId: started.runId,
903
+ auth: {
904
+ bearerToken: options.bearerToken,
905
+ headers: target.headers,
906
+ },
891
907
  fetchImpl: options.fetchImpl,
892
908
  onSnapshot: progress === undefined ? undefined : progress.onSnapshot,
893
909
  });
910
+ const results = snapshotToEvalRunResults(snapshot);
894
911
  if (results.length === 0 && snapshot.status !== "failed") {
895
912
  process.stderr.write("No matching evals found.\n");
896
913
  return 2;
@@ -944,25 +961,74 @@ export function formatEvalStatusFlags(
944
961
  return parts.length === 0 ? "" : ` ${parts.join(" ")}`;
945
962
  }
946
963
 
964
+ /** Print Eval ID + cancel/status hints after a remote batch is accepted. */
965
+ async function printEvalIdBanner(
966
+ started: EvalRunSnapshot,
967
+ options: AxCliOptions,
968
+ agentUrl: string
969
+ ): Promise<void> {
970
+ const pollPath = `${agentUrl.replace(/\/$/, "")}/v1/dev/evals/runs/${started.runId}`;
971
+ const statusFlags = formatEvalStatusFlags(options);
972
+ const playgroundUrl = await resolveEvalPlaygroundDeepLink(
973
+ options,
974
+ started.runId,
975
+ agentUrl
976
+ );
977
+ if (options.json) {
978
+ // Waiters still want the id on stderr so progress text stays readable.
979
+ process.stderr.write(
980
+ `${JSON.stringify({
981
+ evalId: started.runId,
982
+ runId: started.runId,
983
+ status: started.status,
984
+ summary: started.summary,
985
+ pollUrl: pollPath,
986
+ ...(playgroundUrl === undefined ? {} : { playgroundUrl }),
987
+ })}\n`
988
+ );
989
+ return;
990
+ }
991
+ const durable = started.config.durableRuns === true ? " (persisted)" : "";
992
+ process.stderr.write(
993
+ `Eval ID: ${started.runId}${durable} — ${started.summary.total} case(s)\n`
994
+ );
995
+ process.stderr.write(
996
+ `Cancel: ${CLI_COMMAND_NAME} eval cancel ${started.runId}${statusFlags}\n`
997
+ );
998
+ process.stderr.write(
999
+ `Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
1000
+ );
1001
+ if (playgroundUrl !== undefined) {
1002
+ process.stderr.write(`Playground: ${playgroundUrl}\n`);
1003
+ }
1004
+ }
1005
+
947
1006
  /** `--no-wait`: print the accepted batch id (and how to poll) then exit. */
948
- function printEvalAccepted(
1007
+ async function printEvalAccepted(
949
1008
  started: EvalRunSnapshot,
950
1009
  options: AxCliOptions,
951
1010
  agentUrl: string
952
- ): number {
1011
+ ): Promise<number> {
953
1012
  const pollPath = `${agentUrl.replace(/\/$/, "")}/v1/dev/evals/runs/${started.runId}`;
954
1013
  const statusFlags = formatEvalStatusFlags(options);
1014
+ const playgroundUrl = await resolveEvalPlaygroundDeepLink(
1015
+ options,
1016
+ started.runId,
1017
+ agentUrl
1018
+ );
955
1019
  if (options.json) {
956
1020
  console.log(
957
1021
  JSON.stringify(
958
1022
  {
959
1023
  ok: true,
960
1024
  accepted: true,
1025
+ evalId: started.runId,
961
1026
  runId: started.runId,
962
1027
  status: started.status,
963
1028
  summary: started.summary,
964
1029
  pollUrl: pollPath,
965
1030
  durableRuns: started.config.durableRuns === true,
1031
+ ...(playgroundUrl === undefined ? {} : { playgroundUrl }),
966
1032
  },
967
1033
  null,
968
1034
  2
@@ -971,16 +1037,126 @@ function printEvalAccepted(
971
1037
  } else {
972
1038
  const durable = started.config.durableRuns === true ? " (persisted)" : "";
973
1039
  process.stdout.write(
974
- `Accepted eval run ${started.runId}${durable} — ${started.summary.total} case(s)\n`
1040
+ `Eval ID: ${started.runId}${durable} — ${started.summary.total} case(s)\n`
1041
+ );
1042
+ process.stdout.write(
1043
+ `Cancel: ${CLI_COMMAND_NAME} eval cancel ${started.runId}${statusFlags}\n`
975
1044
  );
976
1045
  process.stdout.write(
977
- `Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
1046
+ `Status: ${CLI_COMMAND_NAME} eval status ${started.runId}${statusFlags}\n`
978
1047
  );
1048
+ if (playgroundUrl !== undefined) {
1049
+ process.stdout.write(`Playground: ${playgroundUrl}\n`);
1050
+ }
979
1051
  process.stdout.write(`Poll: ${pollPath}\n`);
980
1052
  }
981
1053
  return 0;
982
1054
  }
983
1055
 
1056
+ /**
1057
+ * Hosted alias playground URL with `view=evals` + `evalRunId` so the SPA
1058
+ * opens the batch that CLI / Slack started (not a stale sessionStorage id).
1059
+ */
1060
+ async function resolveEvalPlaygroundDeepLink(
1061
+ options: AxCliOptions,
1062
+ runId: string,
1063
+ agentUrl: string
1064
+ ): Promise<string | undefined> {
1065
+ if (options.prod !== true) {
1066
+ const base = `${agentUrl.replace(/\/$/, "")}/playground`;
1067
+ try {
1068
+ const url = new URL(base);
1069
+ url.searchParams.set("view", "evals");
1070
+ url.searchParams.set("evalRunId", runId);
1071
+ return url.toString();
1072
+ } catch {
1073
+ return undefined;
1074
+ }
1075
+ }
1076
+ try {
1077
+ const resolved = await resolveProdAliasPlaygroundUrl({
1078
+ dir: options.dir,
1079
+ cwd: options.cwd,
1080
+ slug: options.slug,
1081
+ team: options.team,
1082
+ apiKey: options.apiKey,
1083
+ fetchImpl: options.fetchImpl,
1084
+ // Avoid a second "Using hosted deployment" line; banner already has context.
1085
+ err: () => {},
1086
+ });
1087
+ const url = new URL(resolved.playgroundUrl);
1088
+ url.searchParams.set("view", "evals");
1089
+ url.searchParams.set("evalRunId", runId);
1090
+ return url.toString();
1091
+ } catch {
1092
+ return undefined;
1093
+ }
1094
+ }
1095
+
1096
+ /**
1097
+ * `eval cancel <runId>` — cancel a running remote batch by Eval ID.
1098
+ */
1099
+ export async function cmdEvalCancel(
1100
+ runId: string | undefined,
1101
+ options: AxCliOptions
1102
+ ): Promise<number> {
1103
+ const mode = resolveEvalTargetMode(options);
1104
+ if (typeof mode === "object") {
1105
+ process.stderr.write(`${mode.error}\n`);
1106
+ return 2;
1107
+ }
1108
+ if (mode === "local") {
1109
+ process.stderr.write(
1110
+ "eval cancel requires --prod or --url (not the default local target)\n"
1111
+ );
1112
+ return 2;
1113
+ }
1114
+ if (runId === undefined || runId === "") {
1115
+ process.stderr.write("usage: eval cancel <evalId> --prod|--url …\n");
1116
+ return 2;
1117
+ }
1118
+ if (options.list === true || options.noWait === true) {
1119
+ process.stderr.write("eval cancel does not accept --list or --no-wait\n");
1120
+ return 2;
1121
+ }
1122
+
1123
+ const target = await resolveEvalTarget(options);
1124
+ if (typeof target === "number") {
1125
+ return target;
1126
+ }
1127
+
1128
+ try {
1129
+ const snap = await cancelRemoteEvalRun({
1130
+ baseUrl: target.agentUrl,
1131
+ runId,
1132
+ auth: {
1133
+ bearerToken: options.bearerToken,
1134
+ headers: target.headers,
1135
+ },
1136
+ fetchImpl: options.fetchImpl,
1137
+ });
1138
+ if (options.json) {
1139
+ console.log(JSON.stringify(snap, null, 2));
1140
+ } else {
1141
+ process.stdout.write(
1142
+ `Cancelled Eval ID ${snap.runId} (${snap.summary.done}/${snap.summary.total} cases finalized)\n`
1143
+ );
1144
+ }
1145
+ return 0;
1146
+ } catch (error) {
1147
+ process.stderr.write(
1148
+ `${sanitizeCustomerError(
1149
+ error instanceof Error ? error.message : String(error)
1150
+ )}\n`
1151
+ );
1152
+ return 1;
1153
+ } finally {
1154
+ if (target.close !== undefined) {
1155
+ await target.close();
1156
+ }
1157
+ }
1158
+ }
1159
+
984
1160
  /**
985
1161
  * `eval status [runId]` — fetch one batch (or list recent) on the deployment /
986
1162
  * `--url` target. Exit `0` when the batch is completed with all cases passing,
@@ -1068,7 +1244,9 @@ export async function cmdEvalStatus(
1068
1244
  return 3;
1069
1245
  }
1070
1246
  const failed =
1071
- snap.status === "failed" || snap.cases.some((c) => c.ok !== true);
1247
+ snap.status === "failed" ||
1248
+ snap.status === "cancelled" ||
1249
+ snap.cases.some((c) => c.ok !== true);
1072
1250
  return failed ? 1 : 0;
1073
1251
  } catch (error) {
1074
1252
  process.stderr.write(
@@ -1146,16 +1324,11 @@ function createRemoteEvalProgress(
1146
1324
  if (!enabled) {
1147
1325
  return undefined;
1148
1326
  }
1149
- let announced = false;
1150
1327
  let lastSummaryKey = "";
1151
1328
  const seenCaseKeys = new Set<string>();
1152
1329
  return {
1153
1330
  onSnapshot(snap) {
1154
- if (!announced) {
1155
- announced = true;
1156
- const durable = snap.config.durableRuns === true ? " (persisted)" : "";
1157
- process.stderr.write(`eval run: ${snap.runId}${durable}\n`);
1158
- }
1331
+ // Eval ID is printed by printEvalIdBanner / printEvalAccepted.
1159
1332
  const summaryKey = `${snap.status}:${snap.summary.done}:${snap.summary.passed}:${snap.summary.failed}`;
1160
1333
  if (summaryKey !== lastSummaryKey) {
1161
1334
  lastSummaryKey = summaryKey;
@@ -1239,6 +1412,9 @@ function printEvalResults(
1239
1412
  }\n`
1240
1413
  );
1241
1414
  }
1415
+ for (const line of r.logs ?? []) {
1416
+ process.stdout.write(` ${line}\n`);
1417
+ }
1242
1418
  if (r.error !== undefined) {
1243
1419
  process.stdout.write(` error: ${r.error}\n`);
1244
1420
  }
@@ -10,13 +10,17 @@
10
10
  */
11
11
 
12
12
  import { basename, resolve } from "node:path";
13
- import type { AgentHostingOptions } from "../types.js";
13
+ import {
14
+ type AgentHostingOptions,
15
+ isCursorAccountTransport,
16
+ } from "../types.js";
14
17
  import { CursorBackendClient } from "./cursor/backend-client.js";
15
18
  import {
16
19
  cursorExternalApiUrl,
17
20
  resolveApiKey,
18
21
  SIGN_IN_HINT,
19
22
  } from "./cursor/credentials.js";
23
+ import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
20
24
  import {
21
25
  AgentServeDeployClient,
22
26
  type DeployAccepted,
@@ -25,6 +29,7 @@ import {
25
29
  type DeploymentDetail,
26
30
  type DeploymentStatus,
27
31
  type DeploySource,
32
+ deploymentServiceAccountId,
28
33
  type SecretInfo,
29
34
  type StopAccepted,
30
35
  slugFromDirectoryName,
@@ -97,6 +102,11 @@ export interface DeployCliOptions {
97
102
  allowDomains?: string[];
98
103
  /** Test seams. */
99
104
  fetchImpl?: typeof fetch;
105
+ /** Inject the connector-auth browser opener (tests; also forces interactive). */
106
+ openBrowserImpl?: (url: string) => void;
107
+ sleepImpl?: (ms: number) => Promise<void>;
108
+ connectorPollIntervalMs?: number;
109
+ connectorPollTimeoutMs?: number;
100
110
  /** Inject git probing for deploy source inference (tests). */
101
111
  git?: GitRunner;
102
112
  /** Inject the multi-agent picker (tests / non-interactive hosts). */
@@ -118,6 +128,8 @@ export type SecretInputStream = NodeJS.ReadableStream & {
118
128
  interface DeployCliContext {
119
129
  client: AgentServeDeployClient;
120
130
  teamId: number;
131
+ apiKey: string;
132
+ backendUrl: string;
121
133
  json: boolean;
122
134
  noWait: boolean;
123
135
  pollIntervalMs: number;
@@ -137,6 +149,9 @@ export interface ResolveDeployAuthOptions {
137
149
  export interface ResolvedDeployAuth {
138
150
  client: AgentServeDeployClient;
139
151
  teamId: number;
152
+ /** The resolved account credential, for dashboard RPCs beside the deploy API. */
153
+ apiKey: string;
154
+ backendUrl: string;
140
155
  }
141
156
 
142
157
  /**
@@ -205,7 +220,12 @@ export async function resolveDeployAuth(
205
220
  );
206
221
  }
207
222
 
208
- return { client, teamId };
223
+ return {
224
+ client,
225
+ teamId,
226
+ apiKey: resolved.apiKey,
227
+ backendUrl: resolved.backendUrl,
228
+ };
209
229
  }
210
230
 
211
231
  /**
@@ -233,6 +253,8 @@ async function prepare(
233
253
  return {
234
254
  client: auth.client,
235
255
  teamId: auth.teamId,
256
+ apiKey: auth.apiKey,
257
+ backendUrl: auth.backendUrl,
236
258
  json: options.json === true,
237
259
  noWait: options.noWait === true,
238
260
  pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
@@ -604,8 +626,10 @@ async function deployOne(
604
626
  // The package manifest (`defineAgent({ hosting })`) is the source of
605
627
  // truth for egress domains and expected secret names; flags override or
606
628
  // augment it. Directories without an agent project (static placeholder
607
- // deploys) simply have no manifest.
608
- const hosting = isAgent ? await readHostingManifest(dir) : undefined;
629
+ // deploys) simply have no manifest. The same load supplies the
630
+ // cursorAccount connection refs for the post-deploy connector auth.
631
+ const manifest = isAgent ? await readDeployManifest(dir) : undefined;
632
+ const hosting = manifest?.hosting;
609
633
  const egressAllowedDomains = [
610
634
  ...new Set([
611
635
  ...(hosting?.egressDomains ?? []),
@@ -695,22 +719,125 @@ async function deployOne(
695
719
  ` playground: ${aliasUrl}/playground (sign in with the alias token)\n` +
696
720
  ` open: ${CLI} playground --prod --slug ${slug} --team ${ctx.teamId}\n`
697
721
  );
722
+
723
+ await runPostDeployConnectorAuth(ctx, options, {
724
+ slug,
725
+ connections: manifest?.cursorAccountConnections ?? [],
726
+ serviceAccountId: deploymentServiceAccountId(detail),
727
+ });
698
728
  return 0;
699
729
  }
700
730
 
701
731
  /**
702
- * Read the `hosting` block from the project manifest at `dir`, or
703
- * undefined when the directory is not an agent project (static deploys
704
- * have nothing to discover).
732
+ * Post-deploy connector authorization for `cursorAccount` connections: the
733
+ * agent's connectors execute with tokens owned by the deployment's service
734
+ * account, which start out unauthorized. On a TTY this runs the dashboard's
735
+ * Authenticate flow inline (browser OAuth per connector, already-authorized
736
+ * ones skipped); otherwise it reports status only. Never affects the deploy
737
+ * outcome — anything left unauthorized just gets a playground-page pointer.
705
738
  */
706
- async function readHostingManifest(
707
- dir: string
708
- ): Promise<AgentHostingOptions | undefined> {
709
- if (!(await isAgentProjectDir(dir))) {
710
- return undefined;
739
+ async function runPostDeployConnectorAuth(
740
+ ctx: DeployCliContext,
741
+ options: DeployCliOptions,
742
+ args: {
743
+ slug: string;
744
+ connections: DeployManifest["cursorAccountConnections"];
745
+ serviceAccountId: string | undefined;
746
+ }
747
+ ): Promise<void> {
748
+ if (args.connections.length === 0) {
749
+ return;
711
750
  }
751
+
752
+ const { serviceAccountId } = args;
753
+ if (serviceAccountId === undefined) {
754
+ ctx.err(
755
+ `warning: this agent uses Cursor account connectors, but the deployment has no service account; redeploy to mint one.\n`
756
+ );
757
+ return;
758
+ }
759
+
760
+ const backendClient = new CursorBackendClient({
761
+ backendUrl: ctx.backendUrl,
762
+ apiKey: ctx.apiKey,
763
+ fetchImpl: options.fetchImpl,
764
+ });
765
+
766
+ const interactive =
767
+ options.openBrowserImpl !== undefined ||
768
+ (process.stdout.isTTY === true && process.stdin.isTTY === true);
769
+
770
+ let anyUnauthorized = false;
771
+ for (const ref of args.connections) {
772
+ ctx.out(
773
+ `\nThis agent uses Cursor account connectors (connection "${ref.name}"${
774
+ ref.servers === undefined || ref.servers.length === 0
775
+ ? ""
776
+ : `: ${ref.servers.join(", ")}`
777
+ }).\n`
778
+ );
779
+ try {
780
+ const result = await authorizeCursorAccountConnectors({
781
+ backendClient,
782
+ teamId: ctx.teamId,
783
+ slug: args.slug,
784
+ serviceAccountId,
785
+ connectionName: ref.name,
786
+ servers: ref.servers,
787
+ interactive,
788
+ out: ctx.out,
789
+ err: ctx.err,
790
+ openBrowserImpl: options.openBrowserImpl,
791
+ sleep: options.sleepImpl,
792
+ pollIntervalMs: options.connectorPollIntervalMs,
793
+ pollTimeoutMs: options.connectorPollTimeoutMs,
794
+ });
795
+ if (result.pending.length > 0 || result.errored.length > 0) {
796
+ anyUnauthorized = true;
797
+ if (result.pending.length > 0) {
798
+ ctx.out(
799
+ `Connectors not authorized yet: ${result.pending.join(", ")}.\n`
800
+ );
801
+ }
802
+ }
803
+ } catch (error) {
804
+ anyUnauthorized = true;
805
+ ctx.err(
806
+ `warning: could not check connector authorization for "${ref.name}": ${describeError(error)}\n`
807
+ );
808
+ }
809
+ }
810
+
811
+ if (anyUnauthorized) {
812
+ ctx.out(
813
+ `You can authorize connectors at the playground page (cursor.com dashboard → Deployed Agents → ${args.slug}) or with \`${CLI} mcp oauth <connection>\`.\n`
814
+ );
815
+ }
816
+ }
817
+
818
+ interface DeployManifest {
819
+ hosting: AgentHostingOptions | undefined;
820
+ /** `cursorAccount` connections declared by the agent. */
821
+ cursorAccountConnections: Array<{
822
+ name: string;
823
+ servers: string[] | undefined;
824
+ }>;
825
+ }
826
+
827
+ /**
828
+ * Read the deploy-relevant bits of the project manifest at `dir` in one
829
+ * load: the `hosting` block plus the agent's cursorAccount connections.
830
+ */
831
+ async function readDeployManifest(dir: string): Promise<DeployManifest> {
712
832
  const project = await loadAgentProject(dir);
713
- return project.agent.hosting;
833
+ return {
834
+ hosting: project.agent.hosting,
835
+ cursorAccountConnections: project.agent.connections.flatMap((connection) =>
836
+ isCursorAccountTransport(connection.transport)
837
+ ? [{ name: connection.name, servers: connection.transport.servers }]
838
+ : []
839
+ ),
840
+ };
714
841
  }
715
842
 
716
843
  /**
@@ -1,5 +1,11 @@
1
1
  /**
2
- * `agentkit mcp oauth <connection> [--store]` for `defineConnection({ url, oauth: true })`.
2
+ * `agentkit mcp oauth <connection> [--store]`.
3
+ *
4
+ * Host MCP OAuth for `defineConnection({ url, oauth: true })` (local tokens,
5
+ * optionally stored as deployment secrets), and service-account connector
6
+ * authorization for `defineConnection({ cursorAccount: true })` (tokens
7
+ * stored backend-side against the hosted deployment's service account — see
8
+ * `cursor-account-mcp-auth.ts`).
3
9
  */
4
10
 
5
11
  import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
@@ -7,7 +13,11 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
13
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
8
14
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
9
15
  import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
16
+ import { isCursorAccountTransport } from "../types.js";
10
17
  import { resolveDeployAuth } from "./cli-deploy.js";
18
+ import { CursorBackendClient } from "./cursor/backend-client.js";
19
+ import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
20
+ import { deploymentServiceAccountId } from "./deploy-client.js";
11
21
  import { loadAgentProject } from "./discovery.js";
12
22
  import { CLI_COMMAND_NAME as CLI, LOG_PREFIX } from "./distribution.js";
13
23
  import {
@@ -22,13 +32,19 @@ import { resolveProdSlug } from "./resolve-prod-target.js";
22
32
  export type McpOauthCliOptions = {
23
33
  dir: string;
24
34
  connection: string;
25
- /** Upsert MCP_OAUTH_* on the hosted deployment. */
35
+ /** Upsert MCP_OAUTH_* on the hosted deployment (host-OAuth connections only). */
26
36
  store?: boolean;
27
37
  slug?: string;
28
38
  team?: string;
29
39
  apiKey?: string;
30
40
  err?: (text: string) => void;
31
41
  out?: (text: string) => void;
42
+ /** Test seams. */
43
+ fetchImpl?: typeof fetch;
44
+ openBrowserImpl?: (url: string) => void;
45
+ sleep?: (ms: number) => Promise<void>;
46
+ pollIntervalMs?: number;
47
+ pollTimeoutMs?: number;
32
48
  };
33
49
 
34
50
  function describe(error: unknown): string {
@@ -66,9 +82,16 @@ export async function cmdMcpOauth(
66
82
  }
67
83
 
68
84
  const transport = connection.transport;
85
+ if (isCursorAccountTransport(transport)) {
86
+ return cmdMcpOauthCursorAccount(options, {
87
+ servers: transport.servers,
88
+ out,
89
+ err,
90
+ });
91
+ }
69
92
  if (!("url" in transport) || transport.oauth !== true) {
70
93
  err(
71
- `${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }).\n`
94
+ `${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }) or defineConnection({ cursorAccount: true }).\n`
72
95
  );
73
96
  return 1;
74
97
  }
@@ -184,6 +207,102 @@ export async function cmdMcpOauth(
184
207
  });
185
208
  }
186
209
 
210
+ /**
211
+ * `mcp oauth` for a `cursorAccount` connection: authorize the hosted
212
+ * deployment's service account for the connection's connectors. Tokens are
213
+ * stored backend-side by the cursor.com OAuth callback, so `--store` (a
214
+ * deployment-secrets concept) is a no-op here.
215
+ */
216
+ async function cmdMcpOauthCursorAccount(
217
+ options: McpOauthCliOptions,
218
+ args: {
219
+ servers: string[] | undefined;
220
+ out: (text: string) => void;
221
+ err: (text: string) => void;
222
+ }
223
+ ): Promise<number> {
224
+ const { out, err } = args;
225
+
226
+ let slug: string;
227
+ try {
228
+ slug = resolveProdSlug({ dir: options.dir, slug: options.slug });
229
+ } catch (error) {
230
+ err(`${LOG_PREFIX} ${describe(error)}\n`);
231
+ return 1;
232
+ }
233
+
234
+ const auth = await resolveDeployAuth({
235
+ team: options.team,
236
+ apiKey: options.apiKey,
237
+ fetchImpl: options.fetchImpl,
238
+ err,
239
+ });
240
+ if (typeof auth === "number") {
241
+ return auth;
242
+ }
243
+ const backendClient = new CursorBackendClient({
244
+ backendUrl: auth.backendUrl,
245
+ apiKey: auth.apiKey,
246
+ fetchImpl: options.fetchImpl,
247
+ });
248
+
249
+ let serviceAccountId: string | undefined;
250
+ try {
251
+ const detail = await auth.client.getDeployment({
252
+ teamId: auth.teamId,
253
+ slug,
254
+ });
255
+ serviceAccountId = deploymentServiceAccountId(detail);
256
+ } catch (error) {
257
+ err(
258
+ `${LOG_PREFIX} Could not load deployment "${slug}" (team ${auth.teamId}): ${describe(error)}\n` +
259
+ `${LOG_PREFIX} Deploy first with \`${CLI} deploy\`.\n`
260
+ );
261
+ return 1;
262
+ }
263
+ if (serviceAccountId === undefined) {
264
+ err(
265
+ `${LOG_PREFIX} Deployment "${slug}" has no service account yet (it predates service-account-backed deploys). Redeploy with \`${CLI} deploy\` and rerun.\n`
266
+ );
267
+ return 1;
268
+ }
269
+
270
+ if (options.store === true) {
271
+ out(
272
+ `${LOG_PREFIX} Note: --store is not needed for cursorAccount connections — tokens are stored on the Cursor backend against the deployment's service account.\n`
273
+ );
274
+ }
275
+
276
+ out(
277
+ `${LOG_PREFIX} Authorizing connection "${options.connection}" for ${slug}'s service account…\n`
278
+ );
279
+ const result = await authorizeCursorAccountConnectors({
280
+ backendClient,
281
+ teamId: auth.teamId,
282
+ slug,
283
+ serviceAccountId,
284
+ connectionName: options.connection,
285
+ servers: args.servers,
286
+ interactive: true,
287
+ out,
288
+ err,
289
+ openBrowserImpl: options.openBrowserImpl,
290
+ sleep: options.sleep,
291
+ pollIntervalMs: options.pollIntervalMs,
292
+ pollTimeoutMs: options.pollTimeoutMs,
293
+ });
294
+
295
+ if (result.pending.length > 0 || result.errored.length > 0) {
296
+ err(
297
+ `${LOG_PREFIX} You can authorize connectors any time from the playground page ` +
298
+ `(cursor.com dashboard → Deployed Agents → ${slug}) or by rerunning ` +
299
+ `\`${CLI} mcp oauth ${options.connection}\`.\n`
300
+ );
301
+ return 1;
302
+ }
303
+ return 0;
304
+ }
305
+
187
306
  async function storeOauthSecrets(args: {
188
307
  connection: string;
189
308
  dir: string;