@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
@@ -18,16 +18,51 @@ The companion skill for coding agents is
18
18
  Connect GitHub in Cursor for the repositories you care about (Settings or
19
19
  [cursor.com/dashboard](https://cursor.com/dashboard)). That gives your
20
20
  account access and lets Cursor receive the repo's webhooks. Sign the host
21
- in (`agentkit login` or `CURSOR_API_KEY`), define a GitHub
22
- channel, then serve with `--cursor-events` and at least one `--repo`:
21
+ in (`agentkit login` or `CURSOR_API_KEY`), then opt the channel into the
22
+ Cursor account connection:
23
+
24
+ ```ts
25
+ export default githubChannel({
26
+ cursorAccount: {
27
+ repos: ["owner/repo"],
28
+ // permissions?: "read" | "pr-write" | "contents-write"
29
+ // default "pr-write" (comments / PR writes, no contents:write)
30
+ },
31
+ // hooks...
32
+ });
33
+ ```
34
+
35
+ `cursorAccount` starts the event relay and mints one short-lived GitHub
36
+ credential scoped to those repositories. `ctx.github`, `ctx.host.github`,
37
+ and child `gh` commands share it. Agentkit refreshes the credential before
38
+ expiry. No GitHub App key, PAT, or separate `gh auth login` is needed on
39
+ the host.
40
+
41
+ Choose `permissions` by what the agent needs:
42
+
43
+ | `permissions` | Use when |
44
+ | --- | --- |
45
+ | `"read"` | Inspect PRs / issues / statuses only |
46
+ | `"pr-write"` (default) | Comment, review, update PR/issue metadata |
47
+ | `"contents-write"` | Push code (`contents:write`) |
48
+
49
+ `contents-write` is an explicit opt-up. The host holds a push-capable
50
+ token shared with model-driven tools and untrusted webhook content —
51
+ prefer `"pr-write"` unless the agent must push.
52
+
53
+ Selected repositories must share one GitHub owner (one App installation).
54
+ Configuration that spans owners fails at startup / mint time.
55
+
56
+ To keep repository scope in deployment config instead, use
57
+ `cursorAccount: true` and pass it at serve time:
23
58
 
24
59
  ```bash
25
60
  agentkit serve --dir . --cursor-events --repo owner/repo
26
61
  ```
27
62
 
28
- Repeat `--repo` for each repository. The stream is read as the host's
29
- Cursor user. `serve` refuses to start signed out rather than run a relay
30
- that can never receive events.
63
+ Repeat `--repo` for each repository. The stream and credential are
64
+ resolved as the signed-in Cursor principal. `serve` refuses to start
65
+ signed out.
31
66
 
32
67
  Offset and consumer id live under `<state-root>/cursor-events/`.
33
68
  `CURSOR_API_BASE_URL` overrides the backend. The stream carries event
@@ -51,7 +86,7 @@ import { defaultGitHubAuth, githubChannel } from "@cursor/july/channels/github";
51
86
 
52
87
  export default githubChannel({
53
88
  botName: "my-agent", // or GITHUB_APP_SLUG; used to ignore self-comments
54
- credentials: { webhookSecret: () => process.env.GITHUB_WEBHOOK_SECRET },
89
+ cursorAccount: { repos: ["owner/repo"] },
55
90
  onPullRequest: (ctx, pr) =>
56
91
  pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null,
57
92
  onCheckSuite: (ctx, suite) =>
@@ -72,10 +107,10 @@ things:
72
107
 
73
108
  Return `{ task }` when the wake drives deterministic code. A security
74
109
  reviewer can run its whole review loop this way and report
75
- through commit statuses. Return `{ auth }` when the model needs to reason
110
+ through PR comments. Return `{ auth }` when the model needs to reason
76
111
  about the event.
77
112
 
78
- Outbound GitHub API calls prefer App installation tokens when
113
+ Without `cursorAccount`, outbound GitHub API calls prefer App installation tokens when
79
114
  `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY` are set (with an
80
115
  installation id from the event or `GITHUB_APP_INSTALLATION_ID`). On
81
116
  serve warmup, App-backed hosts also export a short-lived installation
@@ -53,7 +53,7 @@ import { slackChannel } from "@cursor/july/channels/slack";
53
53
 
54
54
  export default slackChannel({
55
55
  cursorAccount: true,
56
- agentName: "Weatherbot", // defaults to a name derived from the mount slug
56
+ agentName: "Weatherbot", // single token no spaces; defaults from mount slug (PascalCase)
57
57
  agentIcon: { emoji: ":robot_face:" },
58
58
  });
59
59
  ```
@@ -1,12 +1,18 @@
1
1
  ---
2
- title: "Build your first weather agent"
3
- description: "Create a weather agent, add a typed tool, run it from the terminal, and open it in the playground."
2
+ title: "Build your first PR approver"
3
+ description: "Create an agent that reviews pull requests by complexity, approves the safe ones, and wakes from GitHub webhooks."
4
4
  ---
5
5
 
6
- # Build your first weather agent
6
+ # Build your first PR approver
7
7
 
8
- Create an agent, give it a weather tool, run a complete turn, and chat
9
- with it in the browser.
8
+ Build an agent that reviews GitHub pull requests. It fetches the diff,
9
+ rates the change's complexity in plain TypeScript, approves the safe
10
+ ones, and flags the rest for a human. Then wire it to GitHub webhooks
11
+ and watch a pull request wake it.
12
+
13
+ The split is the point of the exercise: deterministic policy lives in
14
+ typed tools, judgment lives in the model, and every decision is
15
+ inspectable in the playground.
10
16
 
11
17
  ## Prerequisites
12
18
 
@@ -23,13 +29,18 @@ agentkit login
23
29
 
24
30
  You can also set `CURSOR_API_KEY` instead of signing in.
25
31
 
32
+ - A GitHub credential. `gh auth login` is enough, or set
33
+ `GITHUB_TOKEN`. The tools you write resolve either one
34
+ automatically. Reading pull requests works on any public repo;
35
+ posting reviews needs write access to the repo you review.
36
+
26
37
  ## Scaffolding Agents
27
38
 
28
39
  Have Cursor read [`skills/create-agent/SKILL.md`](../skills/create-agent/SKILL.md)
29
40
  and describe what you want:
30
41
 
31
- > Build me a weather agent for the playground. Start with one weather
32
- > tool and guide me through the remaining decisions.
42
+ > Build me a PR approver for the playground. Start with one tool that
43
+ > inspects a pull request and guide me through the remaining decisions.
33
44
 
34
45
  Cursor asks for missing choices, shows you the plan, then builds and
35
46
  verifies the agent. Continue below to do the same by hand.
@@ -42,14 +53,14 @@ full guided workflow.
42
53
  Start with the built-in scaffold:
43
54
 
44
55
  ```bash
45
- agentkit init ./weather-agent
46
- cd weather-agent
56
+ agentkit init ./pr-approver
57
+ cd pr-approver
47
58
  ```
48
59
 
49
60
  The scaffold creates the files agentkit discovers:
50
61
 
51
62
  ```text
52
- weather-agent/
63
+ pr-approver/
53
64
  ├── agent/
54
65
  │ ├── agent.ts
55
66
  │ ├── instructions.md
@@ -85,18 +96,32 @@ reply. It prints a JSON trajectory with the response, tool calls, and
85
96
  token usage. It also writes an NDJSON trace under
86
97
  `.agentkit/traces/`.
87
98
 
88
- ## Add weather instructions
99
+ ## Teach it to review
89
100
 
90
101
  Replace `agent/instructions.md`:
91
102
 
92
103
  ```md
93
- # Weather agent
94
-
95
- You are a concise weather assistant.
96
-
97
- - Use `get_weather` before answering questions about current weather.
98
- - Tell the user the weather data is simulated.
99
- - Keep replies to two sentences or fewer.
104
+ # PR approver
105
+
106
+ You review GitHub pull requests. Be specific and brief.
107
+
108
+ For every pull request:
109
+
110
+ 1. Call `inspect_pr` first. Never judge a change you haven't fetched.
111
+ 2. Match your review to the complexity it reports:
112
+ - `trivial`: read the patches. If the diff does what the title says
113
+ and nothing looks risky, call `submit_review` with verdict
114
+ `approve`.
115
+ - `moderate`: read every patch. Approve only when you understand the
116
+ whole change and see no risk. Otherwise ask for a human review and
117
+ say which files worry you.
118
+ - `large`: call `submit_review` with verdict `request_human_review`
119
+ right away. Use the stats you already have to point the reviewer at
120
+ the biggest files; don't dig further.
121
+ 3. Never approve a draft. Point out anything surprising, even when you
122
+ approve.
123
+
124
+ End with one sentence: the verdict and why.
100
125
  ```
101
126
 
102
127
  Remove the demo echo tool:
@@ -105,79 +130,332 @@ Remove the demo echo tool:
105
130
  rm agent/tools/echo.ts
106
131
  ```
107
132
 
108
- ## Add a weather tool
133
+ ## Add a shared helper
134
+
135
+ Both tools need to split a PR URL into its parts. Shared code lives in
136
+ `agent/lib/`, which the framework never loads as tools.
137
+
138
+ Create `agent/lib/github.ts`:
139
+
140
+ ```ts
141
+ export interface PullRef {
142
+ owner: string;
143
+ repo: string;
144
+ number: number;
145
+ }
146
+
147
+ /** Split https://github.com/owner/repo/pull/123 into its parts. */
148
+ export function parsePullUrl(prUrl: string): PullRef {
149
+ const url = new URL(prUrl);
150
+ const [owner, repo, pulls, number] = url.pathname.split("/").filter(Boolean);
151
+ const parsed = Number.parseInt(number ?? "", 10);
152
+ if (pulls !== "pull" || owner === undefined || Number.isNaN(parsed)) {
153
+ throw new Error(`Not a pull request URL: ${prUrl}`);
154
+ }
155
+ return { owner, repo, number: parsed };
156
+ }
157
+ ```
158
+
159
+ ## Add the inspect tool
109
160
 
110
- Create `agent/tools/get_weather.ts`:
161
+ Create `agent/tools/inspect_pr.ts`:
111
162
 
112
163
  ```ts
113
164
  import { defineTool } from "@cursor/july/tools";
114
165
  import { z } from "zod";
166
+ import { parsePullUrl } from "../lib/github.js";
167
+
168
+ export type Complexity = "trivial" | "moderate" | "large";
169
+
170
+ /** Deterministic policy: the tool rates the change, not the model. */
171
+ function rateComplexity(linesChanged: number, changedFiles: number): Complexity {
172
+ if (linesChanged <= 25 && changedFiles <= 2) {
173
+ return "trivial";
174
+ }
175
+ if (linesChanged <= 400 && changedFiles <= 15) {
176
+ return "moderate";
177
+ }
178
+ return "large";
179
+ }
180
+
181
+ /** Keep one oversized file from flooding the model's context. */
182
+ function trimPatch(patch: string | undefined): string | undefined {
183
+ if (patch === undefined || patch.length <= 3000) {
184
+ return patch;
185
+ }
186
+ return `${patch.slice(0, 3000)}\n[... patch trimmed ...]`;
187
+ }
115
188
 
116
189
  export default defineTool({
117
- description: "Get simulated current weather for a city.",
190
+ description:
191
+ "Fetch a pull request's title, stats, and per-file patches, plus a deterministic complexity rating (trivial, moderate, or large). Call this before any review decision.",
118
192
  inputSchema: z.object({
119
- city: z.string().describe("City name, such as San Francisco"),
193
+ prUrl: z
194
+ .string()
195
+ .describe("Pull request URL: https://github.com/owner/repo/pull/123"),
120
196
  }),
121
- async execute({ city }) {
122
- const temperatureF = Math.round(Math.random() * (90 - 32) + 32);
123
-
197
+ async execute({ prUrl }, ctx) {
198
+ const { owner, repo, number } = parsePullUrl(prUrl);
199
+ const octokit = await ctx.host.github.getOctokit();
200
+ const { data: pr } = await octokit.rest.pulls.get({
201
+ owner,
202
+ repo,
203
+ pull_number: number,
204
+ });
205
+ const { data: files } = await octokit.rest.pulls.listFiles({
206
+ owner,
207
+ repo,
208
+ pull_number: number,
209
+ per_page: 100,
210
+ });
211
+
212
+ const complexity = rateComplexity(
213
+ pr.additions + pr.deletions,
214
+ pr.changed_files
215
+ );
124
216
  return {
125
- city,
126
- temperatureF,
127
- conditions: "sunny",
217
+ title: pr.title,
218
+ author: pr.user?.login,
219
+ state: pr.state,
220
+ draft: pr.draft ?? false,
221
+ additions: pr.additions,
222
+ deletions: pr.deletions,
223
+ changedFiles: pr.changed_files,
224
+ complexity,
225
+ files: files.map((file) => ({
226
+ path: file.filename,
227
+ additions: file.additions,
228
+ deletions: file.deletions,
229
+ // Large changes get a stats-only skim; a human reads the code.
230
+ ...(complexity === "large" ? {} : { patch: trimPatch(file.patch) }),
231
+ })),
128
232
  };
129
233
  },
130
234
  });
131
235
  ```
132
236
 
133
- The file adds one tool named `get_weather`:
237
+ The file adds one tool named `inspect_pr`:
134
238
 
135
239
  - `description` tells the model when to call it.
136
240
  - `inputSchema` defines and validates the arguments.
137
241
  - `execute` runs on the server and returns data to the model.
138
242
 
139
- This version uses simulated data so you can run it without another API
140
- key. Replace `execute` with a weather API when you're ready.
243
+ Two details carry the design. `rateComplexity` is the review policy,
244
+ and it lives in code: the model never decides what counts as a big
245
+ change. And `ctx.host.github` is the shared host GitHub client, so the
246
+ tool inherits whatever credential the host has (a token, `gh auth`, or
247
+ a GitHub App) without parsing any of it.
141
248
 
142
- ## Try the weather tool
249
+ ## Try the inspect tool
143
250
 
144
- Call the tool directly first:
251
+ Call the tool directly first, on a real merged pull request:
145
252
 
146
253
  ```bash
147
- agentkit call get_weather --dir . \
148
- --input '{"city":"San Francisco"}'
254
+ agentkit call inspect_pr --dir . \
255
+ --input '{"prUrl":"https://github.com/react/react/pull/35623"}'
256
+ ```
257
+
258
+ `call` validates the input and runs `execute` without a model turn.
259
+ This PR is a one-character typo fix, so the result comes back rated
260
+ `trivial` with the whole patch inline:
261
+
262
+ ```json
263
+ {
264
+ "title": "Fix typo: accomodate -> accommodate",
265
+ "additions": 1,
266
+ "deletions": 1,
267
+ "changedFiles": 1,
268
+ "complexity": "trivial",
269
+ "files": [{ "path": "compiler/packages/...", "patch": "@@ -1315,7 ..." }]
270
+ }
271
+ ```
272
+
273
+ Now call it on the PR that added `experimental_useEvent` to React:
274
+ 1,027 additions across 26 files.
275
+
276
+ ```bash
277
+ agentkit call inspect_pr --dir . \
278
+ --input '{"prUrl":"https://github.com/react/react/pull/25229"}'
279
+ ```
280
+
281
+ The rating flips to `large` and the patches disappear from the result.
282
+ The policy in the tool decides how much the model gets to see, before
283
+ any model turn spends a token on it.
284
+
285
+ ## Add the review tool
286
+
287
+ The approver needs a way to act on its verdict. Create
288
+ `agent/tools/submit_review.ts`:
289
+
290
+ ```ts
291
+ import { defineTool } from "@cursor/july/tools";
292
+ import { z } from "zod";
293
+ import { parsePullUrl } from "../lib/github.js";
294
+
295
+ export default defineTool({
296
+ description:
297
+ "Post the review decision to GitHub: approve the pull request, or comment asking for a human review.",
298
+ inputSchema: z.object({
299
+ prUrl: z
300
+ .string()
301
+ .describe("Pull request URL: https://github.com/owner/repo/pull/123"),
302
+ verdict: z.enum(["approve", "request_human_review"]),
303
+ summary: z
304
+ .string()
305
+ .describe("One or two sentences explaining the verdict."),
306
+ }),
307
+ async execute({ prUrl, verdict, summary }, ctx) {
308
+ const { owner, repo, number } = parsePullUrl(prUrl);
309
+ const review =
310
+ verdict === "approve"
311
+ ? { event: "APPROVE" as const, body: `PR approver: ${summary}` }
312
+ : {
313
+ event: "COMMENT" as const,
314
+ body: `PR approver: this change needs a human review. ${summary}`,
315
+ };
316
+
317
+ const octokit = await ctx.host.github.getOctokit();
318
+ await octokit.rest.pulls.createReview({
319
+ owner,
320
+ repo,
321
+ pull_number: number,
322
+ event: review.event,
323
+ body: review.body,
324
+ });
325
+ return { posted: true, ...review };
326
+ },
327
+ });
149
328
  ```
150
329
 
151
- `call` validates the input and runs `execute` without a model turn. If
152
- the result looks right, ask the agent:
330
+ The tool posts a real review: an APPROVE when the agent approves, a
331
+ comment asking for a human otherwise. Two GitHub rules shape how you
332
+ test it. Your credential needs write access to the repo it reviews,
333
+ and GitHub rejects approving your own pull request, so hand the agent
334
+ a teammate's PR rather than one you authored. When a post fails, the
335
+ tool call reports the GitHub error to the model and the turn keeps
336
+ going.
337
+
338
+ Want a person to sign off before the review lands? Set
339
+ `needsApproval: true` on the tool and the call parks until someone
340
+ approves it from the playground or Slack.
341
+ [Human-in-the-loop approvals](./guides/human-in-the-loop.md) shows the
342
+ flow.
343
+
344
+ ## Review a pull request
345
+
346
+ Run the whole loop on a pull request your credential can review. A
347
+ teammate's open PR is the right pick: write access to the repo, and
348
+ not authored by you.
153
349
 
154
350
  ```bash
155
351
  agentkit run --dir . \
156
- --message "What's the weather in San Francisco?"
352
+ --message "Review https://github.com/acme/checkout/pull/42"
157
353
  ```
158
354
 
159
- The agent calls `get_weather`, receives the result, and uses it in the
160
- final reply. agentkit runs the tool loop for you.
355
+ The trajectory shows two tool calls. The agent inspects the PR, reads
356
+ the patches, and submits its verdict. A small, clean change gets an
357
+ APPROVE review on the spot, with a one-line summary of what it checked.
358
+ A large one gets a comment asking for a human review, pointing at the
359
+ files a reviewer should start with. Same instructions, different
360
+ behavior, because the policy in the tool decided how much the model got
361
+ to see.
161
362
 
162
- ## Open the playground
363
+ Open the PR on GitHub: the review is on the timeline, posted by
364
+ whatever identity your credential belongs to.
365
+
366
+ ## Wake it from GitHub
163
367
 
164
- Start the development server:
368
+ A reviewer you have to prompt is only half useful. Give the agent a
369
+ GitHub channel so pull requests wake it. Create
370
+ `agent/channels/github.ts`:
371
+
372
+ ```ts
373
+ import {
374
+ defaultGitHubAuth,
375
+ githubChannel,
376
+ } from "@cursor/july/channels/github";
377
+
378
+ const REVIEW_ACTIONS = new Set(["opened", "reopened", "ready_for_review"]);
379
+
380
+ export default githubChannel({
381
+ botName: "pr-approver",
382
+ webhookEvents: ["pull_request"],
383
+ // submit_review owns every GitHub write. Without these flags the channel
384
+ // also posts chat replies and reactions to the PR when the token allows it.
385
+ deliverReplies: false,
386
+ progress: { reactions: false },
387
+ onPullRequest: (ctx, pr) => {
388
+ if (!REVIEW_ACTIONS.has(pr.action) || pr.draft) {
389
+ return null;
390
+ }
391
+ return {
392
+ auth: defaultGitHubAuth(ctx),
393
+ title: `Review ${ctx.repository.fullName}#${pr.number}`,
394
+ context: [
395
+ "",
396
+ `Review ${pr.url}. Inspect it first, then submit your verdict with submit_review.`,
397
+ ],
398
+ };
399
+ },
400
+ });
401
+ ```
402
+
403
+ The channel mounts `POST /v1/channels/github` and dispatches on the
404
+ `pull_request` events you declared. Opened, reopened, and undrafted PRs
405
+ start a model turn; everything else returns `null` and is skipped.
406
+
407
+ Serve the agent, then replay a real PR at it from a second terminal.
408
+ `replay` reads the PR through `gh api`, synthesizes a GitHub-shaped
409
+ webhook delivery, and POSTs it to the channel. No repo admin, no
410
+ tunnel:
165
411
 
166
412
  ```bash
167
413
  agentkit serve --dir . --dev
414
+ # second terminal:
415
+ agentkit github replay https://github.com/acme/checkout/pull/42 \
416
+ --dir . --action opened
168
417
  ```
169
418
 
170
- Open the playground URL printed in the terminal. Ask the same weather
171
- question. The playground streams the reply and shows the tool arguments
172
- and result inline.
419
+ The replay prints the delivery, and the serve terminal shows the wake:
420
+
421
+ ```text
422
+ [agentkit] replaying acme/checkout#42 (pull_request) → 1 channel
423
+ [agentkit] pull_request.opened → pr-approver/github 200
424
+ ```
425
+
426
+ The agent runs the same inspect-then-submit loop, unprompted this time.
427
+ Replay the same PR again and the channel resumes that PR's session
428
+ instead of starting a new one: each pull request keeps one running
429
+ conversation.
430
+
431
+ ## Open the playground
432
+
433
+ Keep `serve --dev` running and open the playground URL it printed. The
434
+ webhook session is in the session list, titled
435
+ `Review acme/checkout#42`, with the trigger message, both tool calls,
436
+ and the verdict laid out. Start a new chat there and ask for another
437
+ review to watch a turn stream live.
438
+
439
+ ## Go live
440
+
441
+ Replay is for development. For real deliveries, serve with
442
+ `--cursor-events --repo owner/repo` to pull events for repositories
443
+ connected to Cursor with no public URL, or run
444
+ `agentkit github forward` to relay webhooks to your dev server. The
445
+ [GitHub guide](./guides/github.md) compares the options. In production,
446
+ give the host GitHub App credentials so reviews post as your app's bot
447
+ identity instead of a personal account.
173
448
 
174
449
  ## Where to go next
175
450
 
176
- - [Tools](./reference/tools.md): add more typed capabilities, like
177
- replacing the simulated weather tool with live Open-Meteo data
178
- - [Evals](./evals.md): turn this weather question into a regression
179
- check
180
- - [Channels](./reference/channels.md): expose the agent through HTTP,
181
- Slack, or another webhook
451
+ - [`examples/approval-buddy`](../examples/approval-buddy/): the
452
+ production-shaped sibling, with commit statuses, review subagents,
453
+ and a deterministic stamp policy
454
+ - [Evals](./evals.md): freeze these two PRs as regression checks so
455
+ prompt changes can't flip a verdict
456
+ - [Tools](./reference/tools.md): more on typed tools, approvals, and
457
+ direct calls
458
+ - [GitHub](./guides/github.md): fixtures, forwarding, and pulling
459
+ events from Cursor
182
460
  - [Building agents with agents](./building-with-agents.md): have a
183
461
  coding agent extend the project for you
@@ -43,16 +43,18 @@ covers controlling that.
43
43
  ## Best Practices
44
44
 
45
45
  Keep them a few lines: identity, when to use which tool, output shape.
46
- The [quickstart weather agent](../quickstart.md) is the pattern:
46
+ The [quickstart PR approver](../quickstart.md) is the pattern:
47
47
 
48
48
  ```md
49
- # Weather agent
49
+ # PR approver
50
50
 
51
- You are a concise weather assistant.
51
+ You review GitHub pull requests. Be specific and brief.
52
52
 
53
- - Use `get_weather` before answering questions about current weather.
54
- - Tell the user the weather data is simulated.
55
- - Keep replies to two sentences or fewer.
53
+ 1. Call `inspect_pr` first. Never judge a change you haven't fetched.
54
+ 2. Match your review to the complexity it reports.
55
+ 3. Never approve a draft.
56
+
57
+ End with one sentence: the verdict and why.
56
58
  ```
57
59
 
58
60
  - Name the tools and the decision rule ("use X before answering about
@@ -106,7 +106,7 @@ inputs again, and adds an eval for each improvement you keep.
106
106
 
107
107
  ## Related
108
108
 
109
- - [Build your first weather agent](./quickstart.md)
109
+ - [Build your first PR approver](./quickstart.md)
110
110
  - [Building agents with agents](./building-with-agents.md)
111
111
  - [Evals](./evals.md)
112
112
  - [Hillclimbing](./hillclimbing.md)