@cursor/july 0.1.23 → 0.1.24

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 +0 -0
  2. package/dist/docs/404.html +1 -1
  3. package/dist/docs/ab.html +2 -2
  4. package/dist/docs/assets/{app.DYcC9FY-.js → app.CvSGaAxk.js} +1 -1
  5. package/dist/docs/assets/chunks/@localSearchIndexroot.CL2Y0Zmh.js +1 -0
  6. package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.J1jJbCvs.js} +1 -1
  7. package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.C7vfzr7h.js} +2 -2
  8. package/dist/docs/building-with-agents.html +2 -2
  9. package/dist/docs/concepts.html +2 -2
  10. package/dist/docs/deployment.html +2 -2
  11. package/dist/docs/evals.html +2 -2
  12. package/dist/docs/example-agents/approval-buddy.html +2 -2
  13. package/dist/docs/example-agents/benny.html +2 -2
  14. package/dist/docs/example-agents/bugbot.html +2 -2
  15. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  16. package/dist/docs/example-agents/codeowners-review.html +2 -2
  17. package/dist/docs/example-agents/concierge.html +2 -2
  18. package/dist/docs/example-agents/fsd.html +2 -2
  19. package/dist/docs/example-agents/index.html +2 -2
  20. package/dist/docs/example-agents/knowledge-base.html +2 -2
  21. package/dist/docs/example-agents/oncall.html +2 -2
  22. package/dist/docs/example-agents/security-reviewer.html +2 -2
  23. package/dist/docs/example-agents/slack-agent.html +2 -2
  24. package/dist/docs/example-agents/weather-agent.html +2 -2
  25. package/dist/docs/guides/agent-to-agent.html +2 -2
  26. package/dist/docs/guides/cloud-runtime.html +2 -2
  27. package/dist/docs/guides/github.html +2 -2
  28. package/dist/docs/guides/human-in-the-loop.html +2 -2
  29. package/dist/docs/guides/mcp-oauth.html +2 -2
  30. package/dist/docs/guides/slack.html +2 -2
  31. package/dist/docs/guides/webhooks.html +2 -2
  32. package/dist/docs/hillclimbing.html +2 -2
  33. package/dist/docs/index.html +2 -2
  34. package/dist/docs/quickstart.html +2 -2
  35. package/dist/docs/reference/agent-config.html +2 -2
  36. package/dist/docs/reference/channels.html +2 -2
  37. package/dist/docs/reference/cli.html +2 -2
  38. package/dist/docs/reference/connections.html +2 -2
  39. package/dist/docs/reference/hooks.html +2 -2
  40. package/dist/docs/reference/http-api.html +2 -2
  41. package/dist/docs/reference/instructions.html +2 -2
  42. package/dist/docs/reference/playground.html +2 -2
  43. package/dist/docs/reference/project-layout.html +2 -2
  44. package/dist/docs/reference/prompt.html +2 -2
  45. package/dist/docs/reference/schedules.html +2 -2
  46. package/dist/docs/reference/sessions.html +2 -2
  47. package/dist/docs/reference/skills.html +2 -2
  48. package/dist/docs/reference/subagents.html +2 -2
  49. package/dist/docs/reference/tools.html +2 -2
  50. package/dist/docs/scaffolding-agents.html +2 -2
  51. package/dist/docs/storage.html +2 -2
  52. package/dist/docs/troubleshooting.html +2 -2
  53. package/dist/playground/assets/index-CidizGZv.css +1 -0
  54. package/dist/playground/assets/{index-DqXdAFGa.js → index-DTG9OsPV.js} +41 -41
  55. package/dist/playground/index.html +2 -2
  56. package/package.json +24 -24
  57. package/dist/channels/github/instrument.d.ts +0 -20
  58. package/dist/channels/github/instrument.d.ts.map +0 -1
  59. package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
  60. package/dist/internal/json-dir-store.d.ts +0 -32
  61. package/dist/internal/json-dir-store.d.ts.map +0 -1
  62. package/dist/internal/json-dir-store.js +0 -100
  63. package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
  64. package/src/bin/agent-serve.version.test.ts +0 -64
  65. package/src/channels/github/api.test.ts +0 -64
  66. package/src/channels/github/auth.test.ts +0 -105
  67. package/src/channels/github/cursor-account.test.ts +0 -204
  68. package/src/channels/github/forward.test.ts +0 -457
  69. package/src/channels/github/github.test.ts +0 -937
  70. package/src/channels/github/replay.test.ts +0 -179
  71. package/src/channels/slack/api.post-message.test.ts +0 -148
  72. package/src/channels/slack/approvals.test.ts +0 -328
  73. package/src/channels/slack/block-actions.test.ts +0 -452
  74. package/src/channels/slack/bot-mentions.test.ts +0 -267
  75. package/src/channels/slack/channel-watch.test.ts +0 -363
  76. package/src/channels/slack/cursor-account.test.ts +0 -253
  77. package/src/channels/slack/defaults.final-post.test.ts +0 -182
  78. package/src/channels/slack/dispatch.test.ts +0 -795
  79. package/src/channels/slack/eval-directive.test.ts +0 -273
  80. package/src/channels/slack/message-body.test.ts +0 -54
  81. package/src/channels/slack/nudge-store.test.ts +0 -143
  82. package/src/channels/slack/slack.test.ts +0 -391
  83. package/src/channels/slack/stop.test.ts +0 -23
  84. package/src/channels/slack/thread-context.test.ts +0 -202
  85. package/src/evals/assertions.test.ts +0 -580
  86. package/src/evals/expect.test.ts +0 -144
  87. package/src/evals/judge.test.ts +0 -181
  88. package/src/evals/loaders.test.ts +0 -132
  89. package/src/evals/matchers.test.ts +0 -95
  90. package/src/evals/reporters.test.ts +0 -303
  91. package/src/evals/run-facts.test.ts +0 -259
  92. package/src/internal/ab-snapshot.test.ts +0 -325
  93. package/src/internal/approval-gate.test.ts +0 -49
  94. package/src/internal/approvals.integration.test.ts +0 -383
  95. package/src/internal/authored-loaders.test.ts +0 -31
  96. package/src/internal/builtin-tools/reminders.test.ts +0 -201
  97. package/src/internal/channel-route-schema.test.ts +0 -294
  98. package/src/internal/chat-attach.test.ts +0 -262
  99. package/src/internal/cli-deploy.test.ts +0 -1991
  100. package/src/internal/cli-docs.test.ts +0 -161
  101. package/src/internal/cli-mcp.test.ts +0 -789
  102. package/src/internal/cli-skills.test.ts +0 -133
  103. package/src/internal/cli-slack.test.ts +0 -1647
  104. package/src/internal/cloud-merge.test.ts +0 -74
  105. package/src/internal/cron.test.ts +0 -22
  106. package/src/internal/cursor/account-mcp.test.ts +0 -807
  107. package/src/internal/cursor/backend-client.test.ts +0 -591
  108. package/src/internal/cursor/credentials.test.ts +0 -351
  109. package/src/internal/cursor/github-credentials.test.ts +0 -136
  110. package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
  111. package/src/internal/cursor-account.integration.test.ts +0 -441
  112. package/src/internal/cursor-event-relay.test.ts +0 -746
  113. package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
  114. package/src/internal/cursor-slack-relay.test.ts +0 -525
  115. package/src/internal/deploy-source.test.ts +0 -111
  116. package/src/internal/discovery.builtin-tools.test.ts +0 -94
  117. package/src/internal/discovery.concurrency.test.ts +0 -60
  118. package/src/internal/discovery.cursor-account.test.ts +0 -133
  119. package/src/internal/discovery.cwd.test.ts +0 -83
  120. package/src/internal/discovery.hosting.test.ts +0 -80
  121. package/src/internal/discovery.identity.test.ts +0 -44
  122. package/src/internal/docs-site.test.ts +0 -66
  123. package/src/internal/duration.test.ts +0 -29
  124. package/src/internal/eval-judge-model.test.ts +0 -187
  125. package/src/internal/eval-run-store.cancel.test.ts +0 -142
  126. package/src/internal/eval-run-store.storage.test.ts +0 -211
  127. package/src/internal/eval-runner.http.test.ts +0 -403
  128. package/src/internal/eval-runner.run.test.ts +0 -928
  129. package/src/internal/evals-client.test.ts +0 -307
  130. package/src/internal/event-mapper.test.ts +0 -243
  131. package/src/internal/github-fanout.test.ts +0 -213
  132. package/src/internal/handleAgentServeTrigger.test.ts +0 -179
  133. package/src/internal/host-kv.test.ts +0 -82
  134. package/src/internal/host-platforms.test.ts +0 -126
  135. package/src/internal/http-channel.test.ts +0 -402
  136. package/src/internal/init-project.test.ts +0 -270
  137. package/src/internal/install-cursor-skills.test.ts +0 -262
  138. package/src/internal/local-env.test.ts +0 -120
  139. package/src/internal/log-ring.test.ts +0 -31
  140. package/src/internal/logs-client.test.ts +0 -350
  141. package/src/internal/mcp-endpoint.test.ts +0 -436
  142. package/src/internal/mcp-host.test.ts +0 -298
  143. package/src/internal/mcp-oauth.test.ts +0 -148
  144. package/src/internal/net.test.ts +0 -17
  145. package/src/internal/peer-connections.test.ts +0 -128
  146. package/src/internal/peer-mcp.integration.test.ts +0 -289
  147. package/src/internal/playground/toolchain.test.ts +0 -53
  148. package/src/internal/playground-cli.test.ts +0 -187
  149. package/src/internal/playground-proxy.test.ts +0 -376
  150. package/src/internal/prompt-context.integration.test.ts +0 -232
  151. package/src/internal/prompt-context.test.ts +0 -127
  152. package/src/internal/reminder-runner.test.ts +0 -390
  153. package/src/internal/reminder-store.test.ts +0 -53
  154. package/src/internal/request-headers.test.ts +0 -27
  155. package/src/internal/resolve-prod-target.test.ts +0 -787
  156. package/src/internal/resolved-connections.test.ts +0 -295
  157. package/src/internal/router.test.ts +0 -57
  158. package/src/internal/sdk-runner.test.ts +0 -290
  159. package/src/internal/session-engine.coalesce.test.ts +0 -169
  160. package/src/internal/session-engine.concurrency.test.ts +0 -250
  161. package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
  162. package/src/internal/session-engine.interrupt.test.ts +0 -577
  163. package/src/internal/session-engine.storage.test.ts +0 -547
  164. package/src/internal/session-urls.test.ts +0 -28
  165. package/src/internal/sessions-client.test.ts +0 -518
  166. package/src/internal/storage-coordinator.test.ts +0 -517
  167. package/src/internal/tool-call.test.ts +0 -458
  168. package/src/internal/tool-result.test.ts +0 -52
  169. package/src/internal/trajectory.approvals.test.ts +0 -83
  170. package/src/internal/trajectory.subagents.test.ts +0 -198
  171. package/src/internal/turn-governor.test.ts +0 -137
  172. package/src/internal/update-check.test.ts +0 -485
  173. package/src/internal/workspace.test.ts +0 -207
  174. package/src/storage-backends/cursor-hosted.test.ts +0 -121
@@ -1,64 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { promisify } from "node:util";
5
- import { describe, expect, it } from "vitest";
6
- import { packageVersion } from "../internal/distribution.js";
7
-
8
- const execFileAsync = promisify(execFile);
9
- const packageRoot = join(import.meta.dirname, "..", "..");
10
- const tsxBin = join(packageRoot, "node_modules", ".bin", "tsx");
11
- const cliEntry = join(packageRoot, "src", "bin", "agent-serve.ts");
12
-
13
- async function runCli(
14
- args: string[]
15
- ): Promise<{ stdout: string; stderr: string; code: number }> {
16
- try {
17
- const { stdout, stderr } = await execFileAsync(
18
- tsxBin,
19
- [cliEntry, ...args],
20
- {
21
- cwd: packageRoot,
22
- timeout: 25_000,
23
- env: process.env,
24
- }
25
- );
26
- return { stdout, stderr, code: 0 };
27
- } catch (error) {
28
- const err = error as {
29
- stdout?: string;
30
- stderr?: string;
31
- code?: number;
32
- };
33
- return {
34
- stdout: err.stdout ?? "",
35
- stderr: err.stderr ?? "",
36
- code: typeof err.code === "number" ? err.code : 1,
37
- };
38
- }
39
- }
40
-
41
- describe("agentkit --version", () => {
42
- it("prints `<cli> <package.json version>` for --version, -V, and version", async () => {
43
- const pkg = JSON.parse(
44
- await readFile(join(packageRoot, "package.json"), "utf8")
45
- ) as { version: string };
46
- expect(packageVersion()).toBe(pkg.version);
47
-
48
- for (const arg of ["--version", "-V", "version"] as const) {
49
- const { stdout, stderr, code } = await runCli([arg]);
50
- expect(code, arg).toBe(0);
51
- expect(stderr, arg).toBe("");
52
- expect(stdout.trim(), arg).toBe(`agentkit ${pkg.version}`);
53
- }
54
- }, 30_000);
55
-
56
- it("mentions version in --help", async () => {
57
- const { stdout, code } = await runCli(["--help"]);
58
- expect(code).toBe(0);
59
- expect(stdout).toContain("--version, -V");
60
- expect(stdout).toContain("agentkit version");
61
- expect(stdout).toContain("agentkit docs");
62
- expect(stdout).toContain("npx @cursor/july docs");
63
- }, 30_000);
64
- });
@@ -1,64 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { createGitHubOctokit } from "./api.js";
3
- import {
4
- createCursorAccountGithubTokenResolver,
5
- createPendingCursorGithubCredentials,
6
- } from "./cursor-account.js";
7
-
8
- describe("createGitHubOctokit", () => {
9
- afterEach(() => {
10
- delete process.env.GITHUB_TOKEN;
11
- });
12
-
13
- it("resolves lazy credentials for every request on a long-lived client", async () => {
14
- const authorizationHeaders: string[] = [];
15
- const fetchImpl: typeof fetch = async (input, init) => {
16
- const headers =
17
- input instanceof Request ? input.headers : new Headers(init?.headers);
18
- authorizationHeaders.push(headers.get("authorization") ?? "");
19
- return Response.json({ login: "octocat" });
20
- };
21
- const getToken = vi
22
- .fn<() => Promise<string>>()
23
- .mockResolvedValueOnce("ghs_first")
24
- .mockResolvedValueOnce("ghs_second");
25
- const pending = createPendingCursorGithubCredentials();
26
- pending.bind(getToken);
27
- const octokit = await createGitHubOctokit({
28
- api: { fetch: fetchImpl },
29
- credentials: {
30
- token: createCursorAccountGithubTokenResolver(() => pending.getToken()),
31
- },
32
- });
33
-
34
- await octokit.request("GET /user");
35
- await octokit.request("GET /user");
36
-
37
- expect(getToken).toHaveBeenCalledTimes(2);
38
- expect(authorizationHeaders).toEqual([
39
- "Bearer ghs_first",
40
- "Bearer ghs_second",
41
- ]);
42
- });
43
-
44
- it("preserves ambient fallback for ordinary lazy credentials", async () => {
45
- process.env.GITHUB_TOKEN = "ghp_fallback";
46
- const authorizationHeaders: string[] = [];
47
- const octokit = await createGitHubOctokit({
48
- api: {
49
- fetch: async (input, init) => {
50
- const headers =
51
- input instanceof Request
52
- ? input.headers
53
- : new Headers(init?.headers);
54
- authorizationHeaders.push(headers.get("authorization") ?? "");
55
- return Response.json({ login: "octocat" });
56
- },
57
- },
58
- credentials: { token: async () => undefined },
59
- });
60
-
61
- await octokit.request("GET /user");
62
- expect(authorizationHeaders).toEqual(["token ghp_fallback"]);
63
- });
64
- });
@@ -1,105 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const execFileMock = vi.hoisted(() => vi.fn());
4
- const createAppAuthMock = vi.hoisted(() => vi.fn());
5
-
6
- vi.mock("node:child_process", () => ({
7
- execFile: execFileMock,
8
- }));
9
-
10
- vi.mock("@octokit/auth-app", () => ({
11
- createAppAuth: createAppAuthMock,
12
- }));
13
-
14
- import * as auth from "./auth.js";
15
-
16
- function clearAuthEnv(): void {
17
- delete process.env.GITHUB_TOKEN;
18
- delete process.env.GH_TOKEN;
19
- delete process.env.GITHUB_APP_ID;
20
- delete process.env.GITHUB_APP_PRIVATE_KEY;
21
- delete process.env.GITHUB_APP_INSTALLATION_ID;
22
- }
23
-
24
- describe("resolveGitHubAccessToken", () => {
25
- beforeEach(() => {
26
- auth.clearGitHubAuthCaches();
27
- execFileMock.mockReset();
28
- createAppAuthMock.mockReset();
29
- clearAuthEnv();
30
- });
31
-
32
- afterEach(() => {
33
- auth.clearGitHubAuthCaches();
34
- execFileMock.mockReset();
35
- createAppAuthMock.mockReset();
36
- clearAuthEnv();
37
- });
38
-
39
- it("prefers App installation tokens over ambient PAT when App creds are set", async () => {
40
- process.env.GITHUB_TOKEN = "pat-should-not-win";
41
- process.env.GITHUB_APP_ID = "123";
42
- process.env.GITHUB_APP_PRIVATE_KEY =
43
- "-----BEGIN PRIVATE KEY-----\nabc\n-----END PRIVATE KEY-----";
44
- createAppAuthMock.mockReturnValue(
45
- async () => ({ token: "ghs_installation" }) as never
46
- );
47
-
48
- await expect(
49
- auth.resolveGitHubAccessToken({ installationId: 42 })
50
- ).resolves.toBe("ghs_installation");
51
- expect(createAppAuthMock).toHaveBeenCalledOnce();
52
- expect(execFileMock).not.toHaveBeenCalled();
53
- });
54
-
55
- it("falls back to PAT when App credentials are unset (local testing)", async () => {
56
- process.env.GITHUB_TOKEN = "local-pat";
57
-
58
- await expect(
59
- auth.resolveGitHubAccessToken({ installationId: 42 })
60
- ).resolves.toBe("local-pat");
61
- expect(createAppAuthMock).not.toHaveBeenCalled();
62
- });
63
-
64
- it("uses GITHUB_APP_INSTALLATION_ID for host-scoped App auth", async () => {
65
- process.env.GITHUB_APP_ID = "123";
66
- process.env.GITHUB_APP_PRIVATE_KEY =
67
- "-----BEGIN PRIVATE KEY-----\nabc\n-----END PRIVATE KEY-----";
68
- process.env.GITHUB_APP_INSTALLATION_ID = "99";
69
- createAppAuthMock.mockReturnValue(
70
- async () => ({ token: "ghs_host" }) as never
71
- );
72
-
73
- await expect(auth.resolveGitHubAccessToken({})).resolves.toBe("ghs_host");
74
- expect(createAppAuthMock).toHaveBeenCalledOnce();
75
- });
76
- });
77
-
78
- describe("ensureGhCliTokenEnv", () => {
79
- beforeEach(() => {
80
- auth.clearGitHubAuthCaches();
81
- execFileMock.mockReset();
82
- createAppAuthMock.mockReset();
83
- clearAuthEnv();
84
- });
85
-
86
- afterEach(() => {
87
- auth.clearGitHubAuthCaches();
88
- execFileMock.mockReset();
89
- createAppAuthMock.mockReset();
90
- clearAuthEnv();
91
- });
92
-
93
- it("exports an App installation token as GH_TOKEN for gh CLI", async () => {
94
- process.env.GITHUB_APP_ID = "123";
95
- process.env.GITHUB_APP_PRIVATE_KEY =
96
- "-----BEGIN PRIVATE KEY-----\nabc\n-----END PRIVATE KEY-----";
97
- process.env.GITHUB_APP_INSTALLATION_ID = "99";
98
- createAppAuthMock.mockReturnValue(
99
- async () => ({ token: "ghs_for_gh" }) as never
100
- );
101
-
102
- await expect(auth.ensureGhCliTokenEnv()).resolves.toBe(true);
103
- expect(process.env.GH_TOKEN).toBe("ghs_for_gh");
104
- });
105
- });
@@ -1,204 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import { createGitHubOctokit } from "./api.js";
3
- import { resolveCredentialValue } from "./auth.js";
4
- import {
5
- getGithubCursorAccountRuntime,
6
- resolveCursorGithubPermissions,
7
- resolveCursorGithubProxyUrl,
8
- } from "./cursor-account.js";
9
- import { getGitHubChannelOptions, githubChannel } from "./github-channel.js";
10
-
11
- describe("githubChannel({ cursorAccount })", () => {
12
- it("binds outbound API auth without rewriting authored cursorAccount options", async () => {
13
- const definition = githubChannel({
14
- cursorAccount: { repos: ["Acme/Widgets"], permissions: "read" },
15
- });
16
- const runtime = getGithubCursorAccountRuntime(definition);
17
-
18
- expect(runtime?.options.cursorAccount).toEqual({
19
- repos: ["Acme/Widgets"],
20
- permissions: "read",
21
- });
22
- expect(runtime?.scope).toEqual({
23
- owner: "acme",
24
- repos: ["acme/widgets"],
25
- permissions: "read",
26
- });
27
- expect(() => runtime?.credentials.getToken()).toThrow(/has not been bound/);
28
- runtime?.credentials.bind(async () => "ghs_cursor_account");
29
- await expect(
30
- resolveCredentialValue(
31
- getGitHubChannelOptions(definition)?.credentials?.token
32
- )
33
- ).resolves.toBe("ghs_cursor_account");
34
- });
35
-
36
- it("defaults permissions to pr-write on the private scope", () => {
37
- const definition = githubChannel({
38
- cursorAccount: { repos: ["acme/widgets"] },
39
- });
40
- expect(getGithubCursorAccountRuntime(definition)?.scope).toEqual({
41
- owner: "acme",
42
- repos: ["acme/widgets"],
43
- permissions: "pr-write",
44
- });
45
- });
46
-
47
- it("allows a webhook secret but rejects separate outbound credentials", () => {
48
- expect(() =>
49
- githubChannel({
50
- cursorAccount: true,
51
- credentials: { webhookSecret: "signing-secret" },
52
- })
53
- ).not.toThrow();
54
- expect(() =>
55
- githubChannel({
56
- cursorAccount: true,
57
- credentials: { token: "ghp_dedicated" },
58
- })
59
- ).toThrow(/cannot be combined/);
60
- expect(() =>
61
- githubChannel({
62
- cursorAccount: true,
63
- credentials: { appId: 1, privateKey: "pem" },
64
- })
65
- ).toThrow(/cannot be combined/);
66
- });
67
-
68
- it("rejects an empty inline repository scope", () => {
69
- expect(() => githubChannel({ cursorAccount: { repos: [] } })).toThrow(
70
- /require at least one repository/
71
- );
72
- });
73
-
74
- it.each([
75
- [["acme/widgets", "other/tools"], /must share one owner/],
76
- [["https://github.com/acme/widgets"], /expected owner\/repo/],
77
- [
78
- Array.from({ length: 21 }, (_, index) => `acme/repo-${index}`),
79
- /at most 20 repositories/,
80
- ],
81
- ])("rejects an unsupported repository scope", (repos, error) => {
82
- expect(() => githubChannel({ cursorAccount: { repos } })).toThrow(error);
83
- });
84
-
85
- it("treats cursorAccount false as disabled", () => {
86
- expect(
87
- getGithubCursorAccountRuntime(githubChannel({ cursorAccount: false }))
88
- ).toBeUndefined();
89
- });
90
-
91
- it("accepts contents-write permissions on the private scope", () => {
92
- const definition = githubChannel({
93
- cursorAccount: {
94
- repos: ["acme/widgets"],
95
- permissions: "contents-write",
96
- },
97
- });
98
- expect(getGithubCursorAccountRuntime(definition)?.scope?.permissions).toBe(
99
- "contents-write"
100
- );
101
- });
102
- });
103
-
104
- describe("resolveCursorGithubPermissions", () => {
105
- it("escalates to the most privileged requested tier", () => {
106
- expect(resolveCursorGithubPermissions(["read", "pr-write"])).toBe(
107
- "pr-write"
108
- );
109
- expect(
110
- resolveCursorGithubPermissions(["read", "contents-write", "pr-write"])
111
- ).toBe("contents-write");
112
- });
113
- });
114
-
115
- describe("cursor-account GitHub proxy mode", () => {
116
- afterEach(() => {
117
- delete process.env.AGENT_SERVE_GITHUB_PROXY_URL;
118
- });
119
-
120
- function captureFetch(): {
121
- fetchImpl: typeof fetch;
122
- requests: { url: string; headers: Headers }[];
123
- } {
124
- const requests: { url: string; headers: Headers }[] = [];
125
- const fetchImpl: typeof fetch = async (input, init) => {
126
- requests.push({
127
- url: input instanceof Request ? input.url : String(input),
128
- headers:
129
- input instanceof Request ? input.headers : new Headers(init?.headers),
130
- });
131
- return Response.json({ id: 1 });
132
- };
133
- return { fetchImpl, requests };
134
- }
135
-
136
- it("resolves the proxy URL from the backend base URL or env override", () => {
137
- expect(resolveCursorGithubProxyUrl("https://api.cursor.com")).toBe(
138
- "https://api.cursor.com/v0/github-proxy"
139
- );
140
- process.env.AGENT_SERVE_GITHUB_PROXY_URL =
141
- "https://backend.local:8000/v0/github-proxy/";
142
- expect(resolveCursorGithubProxyUrl("https://api.cursor.com")).toBe(
143
- "https://backend.local:8000/v0/github-proxy"
144
- );
145
- });
146
-
147
- it("routes channel Octokit calls to the bound proxy with scope headers", async () => {
148
- const definition = githubChannel({
149
- cursorAccount: { repos: ["acme/widgets"], permissions: "pr-write" },
150
- });
151
- const runtime = getGithubCursorAccountRuntime(definition);
152
- runtime?.credentials.bind(async () => "crsr_api_key");
153
- runtime?.credentials.bindProxyTransport({
154
- apiBaseUrl: "https://api.cursor.com/v0/github-proxy",
155
- headers: {
156
- "x-cursor-github-repos": "acme/widgets",
157
- "x-cursor-github-permissions": "pr-write",
158
- },
159
- });
160
-
161
- const { fetchImpl, requests } = captureFetch();
162
- const octokit = await createGitHubOctokit({
163
- api: { fetch: fetchImpl },
164
- credentials: getGitHubChannelOptions(definition)?.credentials,
165
- });
166
- await octokit.request("GET /repos/{owner}/{repo}", {
167
- owner: "acme",
168
- repo: "widgets",
169
- });
170
-
171
- expect(requests).toHaveLength(1);
172
- const { url, headers } = requests[0]!;
173
- expect(url).toBe(
174
- "https://api.cursor.com/v0/github-proxy/repos/acme/widgets"
175
- );
176
- expect(headers.get("authorization")).toBe("Bearer crsr_api_key");
177
- expect(headers.get("x-cursor-github-repos")).toBe("acme/widgets");
178
- expect(headers.get("x-cursor-github-permissions")).toBe("pr-write");
179
- });
180
-
181
- it("keeps raw-token mode when no proxy transport is bound", async () => {
182
- const definition = githubChannel({
183
- cursorAccount: { repos: ["acme/widgets"] },
184
- });
185
- getGithubCursorAccountRuntime(definition)?.credentials.bind(
186
- async () => "ghs_raw"
187
- );
188
-
189
- const { fetchImpl, requests } = captureFetch();
190
- const octokit = await createGitHubOctokit({
191
- api: { fetch: fetchImpl },
192
- credentials: getGitHubChannelOptions(definition)?.credentials,
193
- });
194
- await octokit.request("GET /repos/{owner}/{repo}", {
195
- owner: "acme",
196
- repo: "widgets",
197
- });
198
-
199
- const { url, headers } = requests[0]!;
200
- expect(url).toBe("https://api.github.com/repos/acme/widgets");
201
- expect(headers.get("authorization")).toBe("Bearer ghs_raw");
202
- expect(headers.get("x-cursor-github-repos")).toBeNull();
203
- });
204
- });