@cursor/july 0.1.1 → 0.1.2

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 (199) hide show
  1. package/AGENTS.md +24 -2
  2. package/README.md +25 -15
  3. package/dist/bin/agent-serve.js +101 -12
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +31 -14
  6. package/dist/channels/github/cursor-account.d.ts +43 -0
  7. package/dist/channels/github/cursor-account.d.ts.map +1 -0
  8. package/dist/channels/github/cursor-account.js +95 -0
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +46 -9
  11. package/dist/channels/github/index.d.ts +2 -2
  12. package/dist/channels/github/index.js +2 -2
  13. package/dist/channels/github/types.d.ts +17 -0
  14. package/dist/channels/github/types.d.ts.map +1 -1
  15. package/dist/channels/slack/slack-channel.d.ts +8 -2
  16. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  17. package/dist/channels/slack/slack-channel.js +8 -0
  18. package/dist/channels/slack/types.d.ts +24 -3
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/channels/slack/types.js +15 -1
  21. package/dist/docs/404.html +2 -2
  22. package/dist/docs/ab.html +8 -8
  23. package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
  24. package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
  25. package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
  26. package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
  27. package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
  28. package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
  29. package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
  30. package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
  31. package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
  32. package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
  33. package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
  34. package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
  35. package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
  36. package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
  37. package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
  38. package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
  39. package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
  40. package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
  41. package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
  42. package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
  43. package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
  44. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
  45. package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
  46. package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
  47. package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
  48. package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
  49. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
  50. package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
  51. package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
  52. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
  53. package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
  54. package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
  55. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
  56. package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
  57. package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
  58. package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
  59. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
  60. package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
  61. package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
  62. package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
  63. package/dist/docs/building-with-agents.html +4 -4
  64. package/dist/docs/concepts.html +4 -4
  65. package/dist/docs/deployment.html +7 -7
  66. package/dist/docs/evals.html +7 -7
  67. package/dist/docs/guides/agent-to-agent.html +6 -6
  68. package/dist/docs/guides/cloud-runtime.html +5 -5
  69. package/dist/docs/guides/github.html +14 -7
  70. package/dist/docs/guides/human-in-the-loop.html +6 -6
  71. package/dist/docs/guides/slack.html +8 -8
  72. package/dist/docs/guides/webhooks.html +6 -6
  73. package/dist/docs/hashmap.json +1 -1
  74. package/dist/docs/hillclimbing.html +5 -5
  75. package/dist/docs/index.html +7 -7
  76. package/dist/docs/quickstart.html +180 -23
  77. package/dist/docs/reference/agent-config.html +7 -7
  78. package/dist/docs/reference/channels.html +8 -8
  79. package/dist/docs/reference/cli.html +4 -4
  80. package/dist/docs/reference/connections.html +5 -5
  81. package/dist/docs/reference/hooks.html +5 -5
  82. package/dist/docs/reference/http-api.html +6 -6
  83. package/dist/docs/reference/instructions.html +13 -11
  84. package/dist/docs/reference/playground.html +4 -4
  85. package/dist/docs/reference/project-layout.html +4 -4
  86. package/dist/docs/reference/schedules.html +7 -7
  87. package/dist/docs/reference/sessions.html +4 -4
  88. package/dist/docs/reference/skills.html +6 -6
  89. package/dist/docs/reference/subagents.html +6 -6
  90. package/dist/docs/reference/tools.html +7 -7
  91. package/dist/docs/scaffolding-agents.html +5 -5
  92. package/dist/docs/storage.html +41 -0
  93. package/dist/docs/troubleshooting.html +4 -4
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +1 -0
  97. package/dist/internal/cli-deploy.d.ts +52 -0
  98. package/dist/internal/cli-deploy.d.ts.map +1 -0
  99. package/dist/internal/cli-deploy.js +731 -0
  100. package/dist/internal/cursor/backend-client.d.ts +10 -1
  101. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  102. package/dist/internal/cursor/backend-client.js +79 -1
  103. package/dist/internal/cursor/github-credentials.d.ts +44 -0
  104. package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
  105. package/dist/internal/cursor/github-credentials.js +195 -0
  106. package/dist/internal/deploy-client.d.ts +176 -0
  107. package/dist/internal/deploy-client.d.ts.map +1 -0
  108. package/dist/internal/deploy-client.js +375 -0
  109. package/dist/internal/discovery.d.ts.map +1 -1
  110. package/dist/internal/discovery.js +73 -5
  111. package/dist/internal/distribution.d.ts.map +1 -1
  112. package/dist/internal/distribution.js +1 -0
  113. package/dist/internal/eval-run-store.d.ts +22 -3
  114. package/dist/internal/eval-run-store.d.ts.map +1 -1
  115. package/dist/internal/eval-run-store.js +37 -19
  116. package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
  117. package/dist/internal/handleAgentServeTrigger.js +10 -12
  118. package/dist/internal/host-platforms.d.ts +7 -2
  119. package/dist/internal/host-platforms.d.ts.map +1 -1
  120. package/dist/internal/host-platforms.js +15 -10
  121. package/dist/internal/hosting.d.ts +37 -0
  122. package/dist/internal/hosting.d.ts.map +1 -0
  123. package/dist/internal/hosting.js +67 -0
  124. package/dist/internal/reminder-runner.d.ts +7 -0
  125. package/dist/internal/reminder-runner.d.ts.map +1 -1
  126. package/dist/internal/reminder-runner.js +50 -6
  127. package/dist/internal/reminder-store.d.ts +2 -0
  128. package/dist/internal/reminder-store.d.ts.map +1 -1
  129. package/dist/internal/reminder-store.js +18 -0
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +173 -42
  132. package/dist/internal/session-engine.d.ts +49 -0
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +246 -17
  135. package/dist/internal/storage-coordinator.d.ts +139 -0
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -0
  137. package/dist/internal/storage-coordinator.js +499 -0
  138. package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
  139. package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
  140. package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
  141. package/dist/playground/index.html +2 -2
  142. package/dist/storage.d.ts +204 -0
  143. package/dist/storage.d.ts.map +1 -0
  144. package/dist/storage.js +153 -0
  145. package/dist/types.d.ts +44 -3
  146. package/dist/types.d.ts.map +1 -1
  147. package/docs/README.md +3 -2
  148. package/docs/guides/github.md +43 -8
  149. package/docs/guides/slack.md +1 -1
  150. package/docs/quickstart.md +329 -51
  151. package/docs/reference/instructions.md +8 -6
  152. package/docs/scaffolding-agents.md +1 -1
  153. package/docs/storage.md +98 -0
  154. package/package.json +8 -1
  155. package/skills/github/SKILL.md +9 -1
  156. package/src/bin/agent-serve.ts +139 -0
  157. package/src/channels/github/api.ts +42 -23
  158. package/src/channels/github/cursor-account.ts +165 -0
  159. package/src/channels/github/github-channel.ts +66 -6
  160. package/src/channels/github/index.ts +2 -2
  161. package/src/channels/github/types.ts +19 -0
  162. package/src/channels/slack/slack-channel.ts +17 -3
  163. package/src/channels/slack/types.ts +44 -3
  164. package/src/index.ts +13 -0
  165. package/src/internal/cli-deploy.ts +940 -0
  166. package/src/internal/cursor/backend-client.ts +103 -1
  167. package/src/internal/cursor/github-credentials.ts +248 -0
  168. package/src/internal/deploy-client.ts +591 -0
  169. package/src/internal/discovery.ts +88 -1
  170. package/src/internal/distribution.ts +1 -0
  171. package/src/internal/eval-run-store.ts +48 -19
  172. package/src/internal/handleAgentServeTrigger.ts +10 -12
  173. package/src/internal/host-platforms.ts +28 -11
  174. package/src/internal/hosting.ts +77 -0
  175. package/src/internal/reminder-runner.ts +50 -6
  176. package/src/internal/reminder-store.ts +21 -0
  177. package/src/internal/server.ts +213 -27
  178. package/src/internal/session-engine.ts +285 -7
  179. package/src/internal/storage-coordinator.ts +615 -0
  180. package/src/storage.ts +325 -0
  181. package/src/types.ts +40 -2
  182. package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
  183. package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
  184. package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
  185. package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
  186. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
  187. package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
  188. package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
  189. package/dist/playground/assets/index-1K-hG-7p.css +0 -1
  190. /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
  191. /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
  192. /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
  193. /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
  194. /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
  196. /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
  197. /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
  198. /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
  199. /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: "Storage"
3
+ description: "Point agentkit's durable storage at a backend you own with defineStorage."
4
+ ---
5
+
6
+ # Storage
7
+
8
+ agentkit owns durable storage for sessions, continuation tokens,
9
+ reminders, playground eval history, and live A/B samples. It chooses the
10
+ keys (under `agentkit/v1/`), when to read and write, and how to restore
11
+ after restart.
12
+
13
+ Keys have bounded length: caller-controlled segments (channel ids,
14
+ continuation tokens) are URI-encoded, and any segment past 256 encoded
15
+ bytes is replaced by its `sha256:…` digest — deterministically, so writes
16
+ and lookups always agree. Backends can rely on this instead of imposing
17
+ their own key-length caps (which would silently drop writes, since a
18
+ throwing `put` is at-most-once).
19
+
20
+ By default that storage lives under `--state-root` on local disk. Fine
21
+ for one machine; it does not survive replacing the host.
22
+
23
+ To keep the same framework storage across hosts, plug in a key-value
24
+ backend with `agent/storage.ts`. You provide `put` / `get` / `delete` /
25
+ `list`. agentkit does the rest.
26
+
27
+ ```ts
28
+ // agent/storage.ts
29
+ import { defineStorage } from "@cursor/july/storage";
30
+
31
+ export default defineStorage({
32
+ put: (key, value) => db.upsert(key, value),
33
+ get: (key) => db.get(key),
34
+ delete: (key) => db.delete(key),
35
+ list: (prefix) => db.listByPrefix(prefix), // [{ key, value }], key order
36
+ });
37
+ ```
38
+
39
+ > [!NOTE]
40
+ > Import paths here use `@cursor/july/storage`. On projects still
41
+ > using `@anysphere/agent-serve`, swap the import. See
42
+ > [Run the CLI](./README.md#run-the-cli) for the full rename table.
43
+
44
+ ## Which methods to provide
45
+
46
+ | Method | Required | Role |
47
+ | --- | --- | --- |
48
+ | `put` | Yes | Write or update a value |
49
+ | `get` | For restore | Look up one key |
50
+ | `list` | For restore | Return entries under a prefix, in key order |
51
+ | `delete` | For cleanup | Remove a key |
52
+
53
+ A throwing `put` is logged and dropped. It never fails a turn. When
54
+ resolving a missing continuation token, a throwing `get` fails the
55
+ follow-up so a store outage does not open a new session. Return
56
+ `undefined` only for a real miss.
57
+
58
+ ## Policy
59
+
60
+ Two knobs change behavior:
61
+
62
+ ```ts
63
+ export default defineStorage({
64
+ policy: {
65
+ // Batch event writes while the session is busy (default: once per turn)
66
+ debounceMs: 30_000,
67
+ // Cap how much loads at serve start, or "off" to restore on demand
68
+ restore: { maxSessions: 500 },
69
+ },
70
+ // put / get / delete / list …
71
+ });
72
+ ```
73
+
74
+ | Knob | Default | Meaning |
75
+ | --- | --- | --- |
76
+ | `debounceMs` | unset (once per turn) | Wait this long after activity before writing event batches |
77
+ | `restore` | caps below | How much to load at serve start |
78
+ | `restore.maxSessions` | `1000` | Max sessions loaded at serve start |
79
+ | `restore.maxAgeMs` | 30 days | Skip older sessions at serve start |
80
+ | `restore.maxTotalBytes` | 1 GiB | Stop loading once this budget is reached |
81
+
82
+ Set `restore: "off"` on high-traffic hosts. Sessions then load when a
83
+ follow-up arrives instead of at startup.
84
+
85
+ ## Restore after restart
86
+
87
+ With `get` and `list`, serve can rebuild local state from your store:
88
+
89
+ - At startup, agentkit loads recent sessions up to the restore caps.
90
+ Local disk wins when both sides have the same session. Reminders
91
+ hydrate the same way into `--state-root/reminders`.
92
+ - On demand, a missing continuation token resolves through the store
93
+ and resumes that session.
94
+ - Playground eval history and A/B aggregates can load from the same
95
+ sink.
96
+
97
+ A turn in flight at crash time is not replayed. The next follow-up
98
+ resumes from the last flushed state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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": {
@@ -117,6 +117,13 @@
117
117
  "import": "./dist/ab.js",
118
118
  "default": "./dist/ab.js"
119
119
  },
120
+ "./storage": {
121
+ "anysphere-source": "./src/storage.ts",
122
+ "bun": "./src/storage.ts",
123
+ "types": "./dist/storage.d.ts",
124
+ "import": "./dist/storage.js",
125
+ "default": "./dist/storage.js"
126
+ },
120
127
  "./package.json": {
121
128
  "anysphere-source": "./package.json",
122
129
  "bun": "./package.json",
@@ -20,7 +20,8 @@ import { defaultGitHubAuth, githubChannel } from "@anysphere/agent-serve/channel
20
20
 
21
21
  export default githubChannel({
22
22
  botName: "my-agent", // or GITHUB_APP_SLUG; used to ignore self-comments
23
- credentials: { webhookSecret: () => process.env.GITHUB_WEBHOOK_SECRET },
23
+ cursorAccount: { repos: ["owner/repo"] }, // permissions?: "read" | "pr-write" | "contents-write"
24
+
24
25
  onPullRequest: (ctx, pr) =>
25
26
  pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null,
26
27
  onCheckSuite: (ctx, suite) =>
@@ -44,6 +45,13 @@ the model should reason about the event.
44
45
 
45
46
  ## Auth modes
46
47
 
48
+ - **`cursorAccount`** → the signed-in Cursor principal supplies both the SCM
49
+ event stream and a short-lived, repo-scoped GitHub credential. It works in
50
+ local dev (`agentkit login`) and hosted deployments (`CURSOR_API_KEY`).
51
+ `ctx.github`, `ctx.host.github`, and child `gh` commands share the lease.
52
+ Use `{ repos: ["owner/repo"] }`, or `true` with `serve --cursor-events
53
+ --repo owner/repo`. One host credential covers up to 20 repositories under
54
+ one GitHub owner; it cannot span owners.
47
55
  - **Secret set** → route auth is `allowAll()` + `X-Hub-Signature-256`
48
56
  verification before parsing; the HMAC becomes the request principal.
49
57
  - **No secret** → `localDevStrict()` (loopback only) — except under
@@ -31,6 +31,18 @@ import {
31
31
  cmdRun,
32
32
  cmdTrajectory,
33
33
  } from "../internal/cli-ax.js";
34
+ import {
35
+ cmdDeploy,
36
+ cmdDeploymentStatus,
37
+ cmdDeployments,
38
+ cmdRotatePodCredential,
39
+ cmdRotateToken,
40
+ cmdSecretsList,
41
+ cmdSecretsSet,
42
+ cmdSecretsUnset,
43
+ cmdStop,
44
+ type DeployCliOptions,
45
+ } from "../internal/cli-deploy.js";
34
46
  import { CLI_COMMAND_NAME as CLI } from "../internal/distribution.js";
35
47
  import { runSession } from "../internal/run-client.js";
36
48
  import { formatTrajectoryText } from "../internal/trajectory.js";
@@ -60,6 +72,17 @@ Usage:
60
72
  ${CLI} login [--api-key <key>] [--key-name <name>]
61
73
  ${CLI} logout
62
74
  ${CLI} whoami [--json]
75
+ ${CLI} deploy [--dir <path>] [--slug <slug>] [--team <id>] [--json]
76
+ [--no-wait] [--repo <url>] [--ref <ref>]
77
+ [--path <agentPath>] [--cursor-events-repo <owner/name>]...
78
+ [--allow-domain <domain>]...
79
+ ${CLI} deployments [--team <id>] [--json]
80
+ ${CLI} deployment <slug> [--team <id>] [--json]
81
+ ${CLI} stop <slug> [--team <id>] [--json] [--no-wait]
82
+ ${CLI} rotate-token <slug> [--team <id>]
83
+ ${CLI} rotate-pod-credential <slug> [--team <id>]
84
+ ${CLI} secrets set <slug> NAME[=VALUE]... | list <slug> | unset <slug> NAME
85
+ [--team <id>] [--json]
63
86
  ${CLI} slack <subcommand> …
64
87
  ${CLI} github <subcommand> …
65
88
 
@@ -86,6 +109,20 @@ Commands:
86
109
  inference, cloud runtime, and cursorAccount MCP connections.
87
110
  logout Remove the stored Cursor credentials.
88
111
  whoami Show which Cursor account powers this host (and why).
112
+ deploy Deploy onto Cursor's managed agent-serve hosting, then wait for
113
+ the engine to come up. First deploy of a slug prints the alias
114
+ token exactly once — save it.
115
+ deployments List the team's deployments (slug, status, generation, kind).
116
+ deployment Show one deployment in full (status, lastError, engine info).
117
+ stop Stop a deployment and wait until it reports stopped.
118
+ rotate-token Mint a new alias token for a deployment (shown once); the old
119
+ token stops working immediately.
120
+ rotate-pod-credential
121
+ Rotate a deployment's pod credential (prints the masked key);
122
+ the running pod keeps the old one until the next deploy.
123
+ secrets Manage deployment secrets: set (upsert by name; NAMEs without
124
+ =VALUE are prompted for with hidden input, or read from piped
125
+ stdin), list (names + createdAt; never values), unset.
89
126
  slack Slack channel pack: init, manifests, doctor (see slack help).
90
127
  github GitHub webhook pack: forward live deliveries, events, doctor
91
128
  (wraps \`gh webhook forward\`; see github help).
@@ -120,6 +157,10 @@ Options:
120
157
  connections (defineConnection({ agent })) on cloud-runtime
121
158
  turns; local turns always use loopback.
122
159
  --no-schedules Disable the cron runner outside dev mode.
160
+ --team deploy/deployments/deployment/stop/rotate-token: Cursor
161
+ team id. Defaults to the account's team when it has one.
162
+ --no-wait deploy/stop: return right after the request is accepted
163
+ instead of polling until a terminal status.
123
164
  --no-playground Disable the bundled web playground at /playground (also skips
124
165
  auto-build / HMR).
125
166
  --no-docs Disable the documentation site at /docs (also skips its
@@ -150,6 +191,15 @@ Options:
150
191
  --repo serve --cursor-events: repo to subscribe to (repeatable).
151
192
  github forward: owner/repo to forward from (inferred from
152
193
  the git remote when omitted; last wins).
194
+ deploy: https git repo URL the control plane builds from.
195
+ --ref deploy: git ref to build (defaults to the repo's default
196
+ branch server-side).
197
+ --path deploy: agent directory relative to the repo root.
198
+ --cursor-events-repo deploy: repo (owner/name) whose SCM events the
199
+ deployed engine pulls (repeatable).
200
+ --allow-domain deploy: egress domain the engine pod may reach
201
+ (repeatable; unioned with hosting.egressDomains from the
202
+ agent manifest). Repo-backed deploys only.
153
203
  --org github forward: organization login (org-level hooks).
154
204
  --channel github: select one channel id when several are declared.
155
205
  --secret github forward: webhook signing secret (defaults to
@@ -233,6 +283,12 @@ async function main(argv: string[]): Promise<number> {
233
283
  comment: { type: "string" },
234
284
  "dry-run": { type: "boolean", default: false },
235
285
  out: { type: "string" },
286
+ team: { type: "string" },
287
+ "no-wait": { type: "boolean", default: false },
288
+ ref: { type: "string" },
289
+ path: { type: "string" },
290
+ "cursor-events-repo": { type: "string", multiple: true },
291
+ "allow-domain": { type: "string", multiple: true },
236
292
  },
237
293
  });
238
294
  const dir = values.dir ?? ".";
@@ -317,6 +373,69 @@ async function main(argv: string[]): Promise<number> {
317
373
  return cmdWhoami({ json: values.json });
318
374
  }
319
375
 
376
+ case "deploy": {
377
+ // `--repo` is repeatable for `serve --cursor-events`; deploy targets a
378
+ // single source repo, so the last one wins.
379
+ const repo = values.repo?.at(-1);
380
+ return cmdDeploy({
381
+ ...deployOptionsFromValues(values),
382
+ dir,
383
+ ...(values.slug === undefined ? {} : { slug: values.slug }),
384
+ ...(repo === undefined ? {} : { repo }),
385
+ ...(values.ref === undefined ? {} : { ref: values.ref }),
386
+ ...(values.path === undefined ? {} : { path: values.path }),
387
+ ...(values["cursor-events-repo"] === undefined
388
+ ? {}
389
+ : { cursorEventsRepos: values["cursor-events-repo"] }),
390
+ ...(values["allow-domain"] === undefined
391
+ ? {}
392
+ : { allowDomains: values["allow-domain"] }),
393
+ });
394
+ }
395
+
396
+ case "deployments": {
397
+ return cmdDeployments(deployOptionsFromValues(values));
398
+ }
399
+
400
+ case "deployment": {
401
+ return cmdDeploymentStatus(
402
+ positionals[0],
403
+ deployOptionsFromValues(values)
404
+ );
405
+ }
406
+
407
+ case "stop": {
408
+ return cmdStop(positionals[0], deployOptionsFromValues(values));
409
+ }
410
+
411
+ case "rotate-token": {
412
+ return cmdRotateToken(positionals[0], deployOptionsFromValues(values));
413
+ }
414
+
415
+ case "rotate-pod-credential": {
416
+ return cmdRotatePodCredential(
417
+ positionals[0],
418
+ deployOptionsFromValues(values)
419
+ );
420
+ }
421
+
422
+ case "secrets": {
423
+ const base = deployOptionsFromValues(values);
424
+ switch (positionals[0]) {
425
+ case "set":
426
+ return cmdSecretsSet(positionals[1], positionals.slice(2), base);
427
+ case "list":
428
+ return cmdSecretsList(positionals[1], base);
429
+ case "unset":
430
+ return cmdSecretsUnset(positionals[1], positionals[2], base);
431
+ default:
432
+ process.stderr.write(
433
+ `Usage: ${CLI} secrets <set|list|unset> <slug> ... [--team <id>] [--json]\n`
434
+ );
435
+ return 1;
436
+ }
437
+ }
438
+
320
439
  case "run": {
321
440
  const messages = await loadMessages(
322
441
  values.message,
@@ -511,6 +630,12 @@ async function main(argv: string[]): Promise<number> {
511
630
  )
512
631
  );
513
632
  lines.push(section("hooks", info.hooks));
633
+ lines.push(
634
+ section("hosting egress domains", info.hosting?.egressDomains ?? [])
635
+ );
636
+ lines.push(
637
+ section("hosting secret names", info.hosting?.secretNames ?? [])
638
+ );
514
639
  if (info.diagnostics.length > 0) {
515
640
  lines.push(`\ndiagnostics:\n${formatDiagnostics(info.diagnostics)}`);
516
641
  }
@@ -646,6 +771,20 @@ async function main(argv: string[]): Promise<number> {
646
771
  }
647
772
  }
648
773
 
774
+ function deployOptionsFromValues(values: {
775
+ team?: string;
776
+ json?: boolean;
777
+ "no-wait"?: boolean;
778
+ "api-key"?: string;
779
+ }): DeployCliOptions {
780
+ return {
781
+ ...(values.team === undefined ? {} : { team: values.team }),
782
+ json: values.json === true,
783
+ noWait: values["no-wait"] === true,
784
+ ...(values["api-key"] === undefined ? {} : { apiKey: values["api-key"] }),
785
+ };
786
+ }
787
+
649
788
  function axFromOptions(
650
789
  partial: Partial<AxCliOptions> & { dir: string }
651
790
  ): AxCliOptions {
@@ -17,6 +17,10 @@ import {
17
17
  resolveGitHubInstallationId,
18
18
  resolveGitHubPrivateKey,
19
19
  } from "./auth.js";
20
+ import {
21
+ type CursorAccountGithubTokenResolver,
22
+ isCursorAccountGithubTokenResolver,
23
+ } from "./cursor-account.js";
20
24
  import type { GitHubChannelCredentials } from "./types.js";
21
25
 
22
26
  export type GitHubJsonObject = Record<string, unknown>;
@@ -91,19 +95,31 @@ export async function createGitHubOctokit(input: {
91
95
  credentials?: GitHubChannelCredentials;
92
96
  installationId?: number;
93
97
  }): Promise<Octokit> {
94
- const requestOptions =
95
- input.api?.fetch === undefined ? undefined : { fetch: input.api.fetch };
96
- const baseUrl = input.api?.apiBaseUrl;
98
+ const transport = githubOctokitTransport(input.api);
99
+
100
+ const dynamicToken: CursorAccountGithubTokenResolver | undefined =
101
+ isCursorAccountGithubTokenResolver(input.credentials?.token)
102
+ ? input.credentials.token
103
+ : isCursorAccountGithubTokenResolver(input.credentials?.installationToken)
104
+ ? input.credentials.installationToken
105
+ : undefined;
106
+ if (dynamicToken !== undefined) {
107
+ const octokit = new Octokit(transport);
108
+ octokit.hook.before("request", async (request) => {
109
+ const token = await resolveCredentialValue(dynamicToken);
110
+ if (token === undefined) {
111
+ throw new Error("githubChannel: dynamic GitHub token is unavailable.");
112
+ }
113
+ request.headers.authorization = `Bearer ${token}`;
114
+ });
115
+ return octokit;
116
+ }
97
117
 
98
118
  const explicit =
99
119
  (await resolveCredentialValue(input.credentials?.token)) ??
100
120
  (await resolveCredentialValue(input.credentials?.installationToken));
101
121
  if (explicit !== undefined) {
102
- return new Octokit({
103
- auth: explicit,
104
- ...(baseUrl === undefined ? {} : { baseUrl }),
105
- ...(requestOptions === undefined ? {} : { request: requestOptions }),
106
- });
122
+ return new Octokit({ ...transport, auth: explicit });
107
123
  }
108
124
 
109
125
  const installationId = await resolveGitHubInstallationId(
@@ -118,26 +134,34 @@ export async function createGitHubOctokit(input: {
118
134
  input.credentials?.privateKey
119
135
  );
120
136
  return new Octokit({
137
+ ...transport,
121
138
  authStrategy: createAppAuth,
122
139
  auth: {
123
140
  appId,
124
141
  privateKey,
125
142
  installationId,
126
143
  },
127
- ...(baseUrl === undefined ? {} : { baseUrl }),
128
- ...(requestOptions === undefined ? {} : { request: requestOptions }),
129
144
  });
130
145
  }
131
146
 
132
147
  // Local testing: ambient PAT / `gh auth token` when App creds are unset.
133
- const token = await resolveGitHubAccessToken(input);
134
148
  return new Octokit({
135
- auth: token,
136
- ...(baseUrl === undefined ? {} : { baseUrl }),
137
- ...(requestOptions === undefined ? {} : { request: requestOptions }),
149
+ ...transport,
150
+ auth: await resolveGitHubAccessToken(input),
138
151
  });
139
152
  }
140
153
 
154
+ /** Shared `baseUrl` / custom `fetch` for every Octokit constructed here. */
155
+ function githubOctokitTransport(api: GitHubApiOptions | undefined): {
156
+ baseUrl?: string;
157
+ request?: { fetch: typeof fetch };
158
+ } {
159
+ return {
160
+ baseUrl: api?.apiBaseUrl,
161
+ request: api?.fetch === undefined ? undefined : { fetch: api.fetch },
162
+ };
163
+ }
164
+
141
165
  /**
142
166
  * Call a GitHub REST path with channel auth. Prefer typed Octokit methods via
143
167
  * {@link createGitHubOctokit} / `ctx.github.getOctokit()` when possible.
@@ -152,15 +176,10 @@ export async function callGitHubApi<T = unknown>(input: {
152
176
  options?: { auth?: boolean; headers?: Readonly<Record<string, string>> };
153
177
  }): Promise<GitHubApiResponse<T>> {
154
178
  if (input.options?.auth === false) {
155
- const octokit = new Octokit({
156
- ...(input.api?.apiBaseUrl === undefined
157
- ? {}
158
- : { baseUrl: input.api.apiBaseUrl }),
159
- ...(input.api?.fetch === undefined
160
- ? {}
161
- : { request: { fetch: input.api.fetch } }),
162
- });
163
- return requestWithOctokit(octokit, input);
179
+ return requestWithOctokit(
180
+ new Octokit(githubOctokitTransport(input.api)),
181
+ input
182
+ );
164
183
  }
165
184
 
166
185
  const octokit = await createGitHubOctokit({
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Cursor-account mode for GitHub channels.
3
+ *
4
+ * The channel declares the intent at definition time. The serve host binds
5
+ * this placeholder to its signed-in Cursor account after it resolves the
6
+ * repository scope into private runtime metadata (never by rewriting the
7
+ * authored `cursorAccount` options object).
8
+ */
9
+
10
+ import type { GitHubChannelOptions } from "./types.js";
11
+
12
+ export const CURSOR_GITHUB_MAX_REPOS = 20;
13
+ const CURSOR_GITHUB_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
14
+ const cursorAccountTokenResolvers = new WeakSet<object>();
15
+
16
+ /** Least → most privilege. Host mints one lease at the max requested tier. */
17
+ export const CURSOR_GITHUB_PERMISSIONS = [
18
+ "read",
19
+ "pr-write",
20
+ "contents-write",
21
+ ] as const;
22
+
23
+ export type CursorGithubPermissions =
24
+ (typeof CURSOR_GITHUB_PERMISSIONS)[number];
25
+
26
+ export interface CursorGithubRepoScope {
27
+ owner: string;
28
+ repos: readonly string[];
29
+ permissions: CursorGithubPermissions;
30
+ }
31
+
32
+ export type CursorAccountGithubTokenResolver = () => Promise<string>;
33
+
34
+ export interface PendingCursorGithubCredentials {
35
+ bind(getToken: () => Promise<string>): void;
36
+ getToken(): Promise<string>;
37
+ }
38
+
39
+ export interface GithubCursorAccountRuntime {
40
+ /** Authored channel options (not rewritten). */
41
+ options: GitHubChannelOptions;
42
+ credentials: PendingCursorGithubCredentials;
43
+ /**
44
+ * Normalized repo + permissions scope when `cursorAccount` is an object.
45
+ * Absent for `cursorAccount: true` (scope comes from serve `--repo`).
46
+ */
47
+ scope: CursorGithubRepoScope | undefined;
48
+ }
49
+
50
+ const runtimeByDefinition = new WeakMap<object, GithubCursorAccountRuntime>();
51
+
52
+ export function parseCursorGithubPermissions(
53
+ value: unknown,
54
+ fallback: CursorGithubPermissions = "pr-write"
55
+ ): CursorGithubPermissions {
56
+ if (value === undefined) {
57
+ return fallback;
58
+ }
59
+ if (value === "read" || value === "pr-write" || value === "contents-write") {
60
+ return value;
61
+ }
62
+ throw new Error(
63
+ 'Cursor account GitHub permissions must be "read", "pr-write", or "contents-write".'
64
+ );
65
+ }
66
+
67
+ export function parseCursorGithubRepoScope(
68
+ values: readonly string[],
69
+ permissions: CursorGithubPermissions = "pr-write"
70
+ ): CursorGithubRepoScope {
71
+ const repos = [
72
+ ...new Set(values.map((repo) => repo.trim().toLowerCase()).filter(Boolean)),
73
+ ];
74
+ if (repos.length === 0) {
75
+ throw new Error(
76
+ "Cursor account GitHub credentials require at least one repository."
77
+ );
78
+ }
79
+ if (repos.length > CURSOR_GITHUB_MAX_REPOS) {
80
+ throw new Error(
81
+ `Cursor account GitHub credentials support at most ${CURSOR_GITHUB_MAX_REPOS} repositories.`
82
+ );
83
+ }
84
+ for (const repo of repos) {
85
+ if (!CURSOR_GITHUB_REPO_PATTERN.test(repo) || repo.includes("..")) {
86
+ throw new Error(
87
+ `Invalid Cursor account GitHub repository "${repo}"; expected owner/repo.`
88
+ );
89
+ }
90
+ }
91
+ const owner = repos[0]?.split("/", 1)[0];
92
+ if (owner === undefined) {
93
+ throw new Error("Cursor account GitHub repository owner is missing.");
94
+ }
95
+ if (repos.some((repo) => repo.split("/", 1)[0] !== owner)) {
96
+ throw new Error(
97
+ "Cursor account GitHub repositories must share one owner for a single installation token."
98
+ );
99
+ }
100
+ return {
101
+ owner,
102
+ repos,
103
+ permissions: parseCursorGithubPermissions(permissions),
104
+ };
105
+ }
106
+
107
+ /** One host credential: the most privileged requested tier wins. */
108
+ export function resolveCursorGithubPermissions(
109
+ values: readonly CursorGithubPermissions[]
110
+ ): CursorGithubPermissions {
111
+ let max: CursorGithubPermissions = "read";
112
+ for (const value of values) {
113
+ if (
114
+ CURSOR_GITHUB_PERMISSIONS.indexOf(value) >
115
+ CURSOR_GITHUB_PERMISSIONS.indexOf(max)
116
+ ) {
117
+ max = value;
118
+ }
119
+ }
120
+ return values.length === 0 ? "pr-write" : max;
121
+ }
122
+
123
+ export function createCursorAccountGithubTokenResolver(
124
+ getToken: () => Promise<string>
125
+ ): CursorAccountGithubTokenResolver {
126
+ const resolver = () => getToken();
127
+ cursorAccountTokenResolvers.add(resolver);
128
+ return resolver;
129
+ }
130
+
131
+ export function isCursorAccountGithubTokenResolver(
132
+ value: unknown
133
+ ): value is CursorAccountGithubTokenResolver {
134
+ return typeof value === "function" && cursorAccountTokenResolvers.has(value);
135
+ }
136
+
137
+ export function createPendingCursorGithubCredentials(): PendingCursorGithubCredentials {
138
+ let getBoundToken: (() => Promise<string>) | undefined;
139
+ return {
140
+ bind(getToken) {
141
+ getBoundToken = getToken;
142
+ },
143
+ getToken() {
144
+ if (getBoundToken === undefined) {
145
+ throw new Error(
146
+ "githubChannel({ cursorAccount: true }) has not been bound to a signed-in Cursor host."
147
+ );
148
+ }
149
+ return getBoundToken();
150
+ },
151
+ };
152
+ }
153
+
154
+ export function registerGithubCursorAccountRuntime(
155
+ definition: object,
156
+ runtime: GithubCursorAccountRuntime
157
+ ): void {
158
+ runtimeByDefinition.set(definition, runtime);
159
+ }
160
+
161
+ export function getGithubCursorAccountRuntime(
162
+ definition: object
163
+ ): GithubCursorAccountRuntime | undefined {
164
+ return runtimeByDefinition.get(definition);
165
+ }