@cursor/july 0.1.95 → 0.1.96
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.
- package/AGENTS.md +3 -4
- package/dist/channels/deployments/types.d.ts +1 -1
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/deployments/types.js +1 -0
- package/dist/channels/origin/api.d.ts +59 -13
- package/dist/channels/origin/api.d.ts.map +1 -1
- package/dist/channels/origin/api.js +168 -27
- package/dist/channels/origin/binding.d.ts +2 -2
- package/dist/channels/origin/checks.d.ts +6 -6
- package/dist/channels/origin/checks.js +6 -6
- package/dist/channels/origin/cursor-account.d.ts +5 -3
- package/dist/channels/origin/cursor-account.d.ts.map +1 -1
- package/dist/channels/origin/cursor-account.js +5 -3
- package/dist/channels/slack/api.d.ts +9 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +36 -5
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +17 -46
- package/dist/channels/slack/dispatch.d.ts +7 -1
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +47 -23
- package/dist/channels/slack/inbound.d.ts +8 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +32 -0
- package/dist/channels/slack/live-delivery.d.ts +26 -29
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +172 -73
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +5 -1
- package/dist/channels/slack/nudge-carrier.d.ts +2 -0
- package/dist/channels/slack/nudge-carrier.d.ts.map +1 -1
- package/dist/channels/slack/nudge-carrier.js +48 -1
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +2 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.BBj0klBO.js → app.jXleCmOc.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CUEXpL78.js → VPLocalSearchBox.8LhyglIT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DabmQnia.js → theme.9ged-pr2.js} +2 -2
- package/dist/docs/assets/index.md.CI93Ltlb.js +5 -0
- package/dist/docs/assets/{index.md.BoHaMdeZ.lean.js → index.md.CI93Ltlb.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.BsMOxDvh.js → reference_cli.md.CCnG-nJx.js} +2 -1
- package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.js +2 -0
- package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/index.md +3 -1
- package/dist/docs/llms-full.txt +102 -3
- package/dist/docs/llms.txt +1 -0
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +7 -6
- package/dist/docs/reference/cli.md +3 -2
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/code-wiki.html +28 -0
- package/dist/docs/templates/code-wiki.md +91 -0
- package/dist/docs/templates/demo.html +5 -5
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +5 -5
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/files-backends/cursor-hosted.d.ts +26 -4
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +32 -4
- package/dist/internal/advertise-tools.d.ts +2 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +15 -1
- package/dist/internal/bounded.d.ts +7 -0
- package/dist/internal/bounded.d.ts.map +1 -0
- package/dist/internal/bounded.js +32 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +7 -1
- package/dist/internal/conversation-mirror.d.ts +11 -8
- package/dist/internal/conversation-mirror.d.ts.map +1 -1
- package/dist/internal/conversation-mirror.js +116 -55
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +22 -48
- package/dist/internal/cursor/session-token.d.ts +45 -0
- package/dist/internal/cursor/session-token.d.ts.map +1 -0
- package/dist/internal/cursor/session-token.js +99 -0
- package/dist/internal/cursor-agent-template.d.ts +1 -1
- package/dist/internal/cursor-agent-template.d.ts.map +1 -1
- package/dist/internal/cursor-agent-template.js +1 -0
- package/dist/internal/framework-file-storage.d.ts +61 -0
- package/dist/internal/framework-file-storage.d.ts.map +1 -0
- package/dist/internal/framework-file-storage.js +204 -0
- package/dist/internal/framework-storage-selection.d.ts +11 -5
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +38 -11
- package/dist/internal/hosted-delivery.d.ts +7 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +4 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +2 -0
- package/dist/internal/install-cursor-skills.d.ts.map +1 -1
- package/dist/internal/install-cursor-skills.js +1 -3
- package/dist/internal/managed-delivery.d.ts +80 -0
- package/dist/internal/managed-delivery.d.ts.map +1 -0
- package/dist/internal/managed-delivery.js +86 -0
- package/dist/internal/runtime-dispatch-runner.d.ts +5 -1
- package/dist/internal/runtime-dispatch-runner.d.ts.map +1 -1
- package/dist/internal/runtime-dispatch-runner.js +7 -0
- package/dist/internal/sdk-runner.d.ts +11 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +39 -8
- package/dist/internal/server.js +1 -0
- package/dist/internal/session-engine.d.ts +13 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +62 -3
- package/dist/internal/steering.d.ts +15 -0
- package/dist/internal/steering.d.ts.map +1 -0
- package/dist/internal/steering.js +18 -0
- package/dist/playground/assets/index-B1DDvBeC.css +1 -0
- package/dist/playground/assets/{index-CZA1uEWA.js → index-Cx9CclK_.js} +51 -51
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +8 -5
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +4 -3
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -1
- package/docs/reference/cli.md +3 -2
- package/docs/templates/code-wiki.md +96 -0
- package/package.json +2 -2
- package/skills/ab/SKILL.md +13 -97
- package/skills/create-agent/SKILL.md +123 -223
- package/skills/debug/SKILL.md +34 -68
- package/skills/evals/SKILL.md +55 -180
- package/skills/framework-map/SKILL.md +83 -145
- package/skills/github/SKILL.md +53 -111
- package/skills/hillclimb/SKILL.md +40 -163
- package/skills/mcp-auth/SKILL.md +48 -107
- package/skills/otel/SKILL.md +22 -68
- package/skills/setup-slack/SKILL.md +77 -208
- package/src/channels/deployments/types.ts +1 -0
- package/src/channels/origin/api.ts +242 -30
- package/src/channels/origin/binding.ts +2 -2
- package/src/channels/origin/checks.ts +6 -6
- package/src/channels/origin/cursor-account.ts +5 -3
- package/src/channels/slack/api.ts +41 -5
- package/src/channels/slack/defaults.ts +15 -49
- package/src/channels/slack/dispatch.ts +56 -22
- package/src/channels/slack/inbound.ts +43 -0
- package/src/channels/slack/live-delivery.ts +235 -114
- package/src/channels/slack/manifest.ts +5 -1
- package/src/channels/slack/nudge-carrier.ts +48 -1
- package/src/channels/slack/slack-channel.ts +2 -0
- package/src/files-backends/cursor-hosted.ts +40 -7
- package/src/internal/advertise-tools.ts +18 -2
- package/src/internal/bounded.ts +28 -0
- package/src/internal/cli-deploy.ts +12 -0
- package/src/internal/conversation-mirror.ts +145 -69
- package/src/internal/cursor/backend-client.ts +34 -58
- package/src/internal/cursor/session-token.ts +115 -0
- package/src/internal/cursor-agent-template.ts +1 -0
- package/src/internal/framework-file-storage.ts +222 -0
- package/src/internal/framework-storage-selection.ts +51 -11
- package/src/internal/hosted-delivery.ts +10 -0
- package/src/internal/init-project.ts +2 -0
- package/src/internal/install-cursor-skills.ts +1 -4
- package/src/internal/managed-delivery.ts +125 -0
- package/src/internal/runtime-dispatch-runner.ts +10 -0
- package/src/internal/sdk-runner.ts +56 -8
- package/src/internal/server.ts +2 -0
- package/src/internal/session-engine.ts +64 -4
- package/src/internal/steering.ts +17 -0
- package/src/storage-backends/cursor-hosted.ts +9 -7
- package/src/types.ts +2 -1
- package/templates/code-wiki/README.md +34 -0
- package/templates/code-wiki/agent/agent.ts +8 -0
- package/templates/code-wiki/agent/channels/github.ts +144 -0
- package/templates/code-wiki/agent/instructions.md +39 -0
- package/templates/code-wiki/agent/lib/repos.ts +5 -0
- package/templates/code-wiki/agent/lib/wiki.ts +22 -0
- package/templates/code-wiki/agent/tools/apply_wiki.ts +219 -0
- package/templates/code-wiki/evals/evals.config.ts +5 -0
- package/templates/code-wiki/evals/wiki.eval.ts +132 -0
- package/templates/code-wiki/init.json +25 -0
- package/templates/code-wiki/package.json +18 -0
- package/templates/code-wiki/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.oqPawjiD.js +0 -1
- package/dist/docs/assets/index.md.BoHaMdeZ.js +0 -5
- package/dist/internal/deployment-scm-event-stream.d.ts +0 -116
- package/dist/internal/deployment-scm-event-stream.d.ts.map +0 -1
- package/dist/internal/deployment-scm-event-stream.js +0 -474
- package/dist/playground/assets/index-CF7hUDnQ.css +0 -1
- package/src/internal/deployment-scm-event-stream.ts +0 -593
- /package/dist/docs/assets/{reference_cli.md.BsMOxDvh.lean.js → reference_cli.md.CCnG-nJx.lean.js} +0 -0
|
@@ -17,14 +17,15 @@ import { randomUUID } from "node:crypto";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import type { McpToolAnnotations } from "../../types.js";
|
|
19
19
|
import { boundedToolAnnotations } from "../mcp-host.js";
|
|
20
|
+
import {
|
|
21
|
+
CURSOR_SESSION_EXCHANGE_PATH,
|
|
22
|
+
CursorSessionExchangeError,
|
|
23
|
+
exchangeCursorApiKeyForSession,
|
|
24
|
+
SESSION_TOKEN_EXPIRY_LEEWAY_MS,
|
|
25
|
+
} from "./session-token.js";
|
|
20
26
|
|
|
21
27
|
const CLIENT_VERSION = "agent-serve-0.1.0";
|
|
22
|
-
const EXCHANGE_PATH = "/auth/exchange_user_api_key";
|
|
23
28
|
const SERVICE_PREFIX = "/aiserver.v1.DashboardService";
|
|
24
|
-
/** Re-exchange when the cached token is within this window of expiry. */
|
|
25
|
-
const TOKEN_EXPIRY_LEEWAY_MS = 5 * 60 * 1000;
|
|
26
|
-
/** Fallback token lifetime when the JWT `exp` claim cannot be parsed. */
|
|
27
|
-
const FALLBACK_TOKEN_LIFETIME_MS = 30 * 60 * 1000;
|
|
28
29
|
const LIST_TIMEOUT_MS = 60_000;
|
|
29
30
|
/** Backend-executed tools call out to third-party servers; be generous. */
|
|
30
31
|
const EXECUTE_TIMEOUT_MS = 120_000;
|
|
@@ -550,7 +551,7 @@ export class CursorBackendClient {
|
|
|
550
551
|
}
|
|
551
552
|
if (
|
|
552
553
|
this.token !== undefined &&
|
|
553
|
-
Date.now() < this.token.expiresAtMs -
|
|
554
|
+
Date.now() < this.token.expiresAtMs - SESSION_TOKEN_EXPIRY_LEEWAY_MS
|
|
554
555
|
) {
|
|
555
556
|
return Promise.resolve(this.token.value);
|
|
556
557
|
}
|
|
@@ -565,44 +566,39 @@ export class CursorBackendClient {
|
|
|
565
566
|
|
|
566
567
|
private async exchangeApiKey(): Promise<string> {
|
|
567
568
|
const apiKey = this.currentApiKey();
|
|
568
|
-
let
|
|
569
|
+
let session: { token: string; expiresAtMs: number };
|
|
569
570
|
try {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
authorization: `Bearer ${apiKey}`,
|
|
575
|
-
},
|
|
576
|
-
body: "{}",
|
|
577
|
-
signal: AbortSignal.timeout(30_000),
|
|
571
|
+
session = await exchangeCursorApiKeyForSession({
|
|
572
|
+
exchangeUrl: `${this.backendUrl}${CURSOR_SESSION_EXCHANGE_PATH}`,
|
|
573
|
+
apiKey,
|
|
574
|
+
fetchImpl: this.fetchImpl,
|
|
578
575
|
});
|
|
579
576
|
} catch (error) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
577
|
+
if (error instanceof CursorSessionExchangeError) {
|
|
578
|
+
if (error.status === undefined) {
|
|
579
|
+
throw new CursorBackendError(
|
|
580
|
+
`Could not reach the Cursor backend at ${this.backendUrl}: ${error.message}`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
if (error.status >= 200 && error.status < 300) {
|
|
584
|
+
// 2xx without a token in the body.
|
|
585
|
+
throw new CursorBackendError(
|
|
586
|
+
"Cursor API key exchange returned no access token."
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
throw new CursorBackendError(
|
|
590
|
+
error.status === 401
|
|
591
|
+
? "The Cursor API key was rejected. Run `agent-serve login` again or update CURSOR_API_KEY."
|
|
592
|
+
: `Cursor API key exchange failed (${error.status}).`,
|
|
593
|
+
error.status
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
throw error;
|
|
598
597
|
}
|
|
599
|
-
this.token = {
|
|
600
|
-
value: body.data.accessToken,
|
|
601
|
-
expiresAtMs: jwtExpiryMs(body.data.accessToken),
|
|
602
|
-
};
|
|
598
|
+
this.token = { value: session.token, expiresAtMs: session.expiresAtMs };
|
|
603
599
|
this.lastExchangedApiKey = apiKey;
|
|
604
600
|
this.logger("[agent-sdk] cursor account: minted a fresh access token");
|
|
605
|
-
return
|
|
601
|
+
return session.token;
|
|
606
602
|
}
|
|
607
603
|
}
|
|
608
604
|
|
|
@@ -705,8 +701,6 @@ export async function revokeMintedUserApiKey(args: {
|
|
|
705
701
|
// Helpers
|
|
706
702
|
// ============================================================================
|
|
707
703
|
|
|
708
|
-
const accessTokenResponseSchema = z.object({ accessToken: z.string() });
|
|
709
|
-
|
|
710
704
|
function safeJsonParse(text: string): unknown {
|
|
711
705
|
try {
|
|
712
706
|
return JSON.parse(text) as unknown;
|
|
@@ -883,24 +877,6 @@ function retryAfterMsFromResponse(response: Response): number | undefined {
|
|
|
883
877
|
return Math.min(retryAfter * 1000, MAX_GITHUB_TOKEN_RETRY_AFTER_MS);
|
|
884
878
|
}
|
|
885
879
|
|
|
886
|
-
function jwtExpiryMs(token: string): number {
|
|
887
|
-
try {
|
|
888
|
-
const payload = token.split(".")[1];
|
|
889
|
-
if (payload === undefined) {
|
|
890
|
-
throw new Error("not a JWT");
|
|
891
|
-
}
|
|
892
|
-
const claims = JSON.parse(
|
|
893
|
-
Buffer.from(payload, "base64url").toString("utf8")
|
|
894
|
-
) as { exp?: unknown };
|
|
895
|
-
if (typeof claims.exp === "number") {
|
|
896
|
-
return claims.exp * 1000;
|
|
897
|
-
}
|
|
898
|
-
} catch {
|
|
899
|
-
// Opaque token — assume a conservative lifetime.
|
|
900
|
-
}
|
|
901
|
-
return Date.now() + FALLBACK_TOKEN_LIFETIME_MS;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
880
|
function describeError(error: unknown): string {
|
|
905
881
|
return error instanceof Error ? error.message : String(error);
|
|
906
882
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `CURSOR_API_KEY` → Cursor session-token exchange primitive
|
|
3
|
+
* (`POST /auth/exchange_user_api_key`, ~1h session).
|
|
4
|
+
*
|
|
5
|
+
* Both `CursorBackendClient` (dashboard RPCs) and the Origin channel's
|
|
6
|
+
* session provider (`cursorSessionTokenProvider` in
|
|
7
|
+
* `../../channels/origin/api.ts`) mint through this module, so the URL,
|
|
8
|
+
* request shape, response parsing, JWT-expiry handling, and renewal
|
|
9
|
+
* constants cannot drift between the two. Callers own their caching and
|
|
10
|
+
* retry policies; this module owns exactly one exchange round trip.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const CURSOR_SESSION_EXCHANGE_PATH = "/auth/exchange_user_api_key";
|
|
14
|
+
/** Re-exchange when the cached session is within this window of expiry. */
|
|
15
|
+
export const SESSION_TOKEN_EXPIRY_LEEWAY_MS: number = 5 * 60 * 1000;
|
|
16
|
+
/** Fallback session lifetime when the JWT `exp` claim cannot be parsed. */
|
|
17
|
+
export const SESSION_TOKEN_FALLBACK_LIFETIME_MS: number = 30 * 60 * 1000;
|
|
18
|
+
const EXCHANGE_TIMEOUT_MS = 30_000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Exchange failure. `status` is the HTTP status when the backend answered,
|
|
22
|
+
* and undefined for network/transport errors. `detail` carries the server's
|
|
23
|
+
* `error` field when present so callers can surface the reason verbatim.
|
|
24
|
+
*/
|
|
25
|
+
export class CursorSessionExchangeError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
message: string,
|
|
28
|
+
readonly status?: number,
|
|
29
|
+
readonly detail?: string
|
|
30
|
+
) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "CursorSessionExchangeError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Epoch-ms expiry from a JWT `exp` claim, or undefined when unparsable. */
|
|
37
|
+
export function jwtExpiryMs(token: string): number | undefined {
|
|
38
|
+
try {
|
|
39
|
+
const payload = token.split(".")[1];
|
|
40
|
+
if (payload === undefined) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const claims = JSON.parse(
|
|
44
|
+
Buffer.from(payload, "base64url").toString("utf8")
|
|
45
|
+
) as { exp?: unknown };
|
|
46
|
+
return typeof claims.exp === "number" ? claims.exp * 1000 : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One exchange round trip. Throws {@link CursorSessionExchangeError} on
|
|
54
|
+
* transport failure, a non-2xx response, or a token-free body. `expiresAtMs`
|
|
55
|
+
* comes from the session JWT's `exp` claim, falling back to
|
|
56
|
+
* {@link SESSION_TOKEN_FALLBACK_LIFETIME_MS} for opaque tokens.
|
|
57
|
+
*/
|
|
58
|
+
export async function exchangeCursorApiKeyForSession(args: {
|
|
59
|
+
/** Full exchange URL (base + {@link CURSOR_SESSION_EXCHANGE_PATH}). */
|
|
60
|
+
exchangeUrl: string;
|
|
61
|
+
apiKey: string;
|
|
62
|
+
fetchImpl?: typeof fetch;
|
|
63
|
+
nowMs?: number;
|
|
64
|
+
}): Promise<{ token: string; expiresAtMs: number }> {
|
|
65
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
66
|
+
let response: Response;
|
|
67
|
+
try {
|
|
68
|
+
response = await fetchImpl(args.exchangeUrl, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
authorization: `Bearer ${args.apiKey}`,
|
|
73
|
+
},
|
|
74
|
+
body: "{}",
|
|
75
|
+
signal: AbortSignal.timeout(EXCHANGE_TIMEOUT_MS),
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw new CursorSessionExchangeError(
|
|
79
|
+
`Could not reach the Cursor session exchange at ${args.exchangeUrl}: ${
|
|
80
|
+
error instanceof Error ? error.message : String(error)
|
|
81
|
+
}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const body: unknown = await response.json().catch(() => undefined);
|
|
85
|
+
const record =
|
|
86
|
+
typeof body === "object" && body !== null && !Array.isArray(body)
|
|
87
|
+
? (body as Record<string, unknown>)
|
|
88
|
+
: {};
|
|
89
|
+
const detail =
|
|
90
|
+
typeof record.error === "string" && record.error !== ""
|
|
91
|
+
? record.error
|
|
92
|
+
: undefined;
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new CursorSessionExchangeError(
|
|
95
|
+
`Cursor session exchange failed with HTTP ${response.status}${
|
|
96
|
+
detail === undefined ? "" : ` (${detail})`
|
|
97
|
+
}.`,
|
|
98
|
+
response.status,
|
|
99
|
+
detail
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const token = record.accessToken;
|
|
103
|
+
if (typeof token !== "string" || token === "") {
|
|
104
|
+
throw new CursorSessionExchangeError(
|
|
105
|
+
"Cursor session exchange returned no access token.",
|
|
106
|
+
response.status
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
110
|
+
return {
|
|
111
|
+
token,
|
|
112
|
+
expiresAtMs:
|
|
113
|
+
jwtExpiryMs(token) ?? nowMs + SESSION_TOKEN_FALLBACK_LIFETIME_MS,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-level file storage for v2 hosted engines.
|
|
3
|
+
*
|
|
4
|
+
* Hosted deployments carry two file stores with different audiences:
|
|
5
|
+
*
|
|
6
|
+
* - the **user-level** store (`AGENT_SERVE_STORE_SOURCE_ID`) holds
|
|
7
|
+
* author-facing bytes — `ctx.host.files`, skills, memory, artifacts — and
|
|
8
|
+
* is browsable from the dashboard and mountable on cloud-runtime VMs;
|
|
9
|
+
* - the **framework-level** store (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`)
|
|
10
|
+
* holds engine-owned session state — session records, event transcripts,
|
|
11
|
+
* the continuation index, and the SDK conversation mirror — and is never
|
|
12
|
+
* exposed to authors or models.
|
|
13
|
+
*
|
|
14
|
+
* This module puts framework session state on the framework store. The
|
|
15
|
+
* {@link storageKeys} scheme was designed for exactly this: keys are a
|
|
16
|
+
* stable versioned contract that sinks may route on, so
|
|
17
|
+
* {@link composeFrameworkStorage} splits one {@link StorageConfig} by key
|
|
18
|
+
* kind — `session` / `session-events` / `continuation` go to the framework
|
|
19
|
+
* file store (file-shaped; the store accepts values up to 64 MiB per
|
|
20
|
+
* object without chunking, versus the KV proxy's 1 MiB row cap), while
|
|
21
|
+
* author `kv`, eval runs, A/B samples, and conditional writes stay on the
|
|
22
|
+
* control-plane KV proxy (the dashboard reads them there, and CAS needs a
|
|
23
|
+
* database).
|
|
24
|
+
*
|
|
25
|
+
* v2 only, by construction: the composite is selected in
|
|
26
|
+
* framework-storage-selection.ts, which v1 never reaches with a
|
|
27
|
+
* platform-selected definition.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { FileSink } from "../files.js";
|
|
31
|
+
import type { StorageConfig, StorageEntry } from "../storage.js";
|
|
32
|
+
import { STORAGE_KEY_ROOT } from "../storage-protocol.js";
|
|
33
|
+
import type { JsonValue } from "../types.js";
|
|
34
|
+
import { mapBounded } from "./bounded.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Key kinds (`agentkit/v1/{agent}/{kind}/…`) that live on the framework
|
|
38
|
+
* file store. Everything else stays on the KV sink.
|
|
39
|
+
*/
|
|
40
|
+
const FRAMEWORK_FILE_KINDS: ReadonlySet<string> = new Set([
|
|
41
|
+
"session",
|
|
42
|
+
"session-events",
|
|
43
|
+
"continuation",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a framework-minted key (or list prefix) belongs on the framework
|
|
48
|
+
* file store. Prefixes used by the coordinator are always at least
|
|
49
|
+
* kind-scoped (`…/{agent}/session/`), so a prefix routes wholly to one side.
|
|
50
|
+
*/
|
|
51
|
+
export function isFrameworkFileKey(key: string): boolean {
|
|
52
|
+
if (!key.startsWith(`${STORAGE_KEY_ROOT}/`)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const rest = key.slice(STORAGE_KEY_ROOT.length + 1);
|
|
56
|
+
const agentEnd = rest.indexOf("/");
|
|
57
|
+
if (agentEnd === -1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const afterAgent = rest.slice(agentEnd + 1);
|
|
61
|
+
const kindEnd = afterAgent.indexOf("/");
|
|
62
|
+
const kind = kindEnd === -1 ? afterAgent : afterAgent.slice(0, kindEnd);
|
|
63
|
+
return FRAMEWORK_FILE_KINDS.has(kind);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The KV core over a {@link FileSink}: one JSON document per key. */
|
|
67
|
+
export type FileSinkStorageCore = Required<
|
|
68
|
+
Pick<StorageConfig, "put" | "get" | "delete" | "list">
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Adapt a {@link FileSink} to the `defineStorage` KV core. Storage keys are
|
|
73
|
+
* framework-minted (URI-encoded, length-bounded segments — see
|
|
74
|
+
* {@link storageKeys}), so they are used as file keys verbatim; values are
|
|
75
|
+
* stored as JSON bytes. Parse failures throw: on the lazy-restore path a
|
|
76
|
+
* corrupt document must fail the follow-up rather than read as a miss, and
|
|
77
|
+
* the startup bulk restore already logs-and-skips a throwing `list`.
|
|
78
|
+
*/
|
|
79
|
+
/** Parallel per-key reads backing `list` (each is presign + object GET). */
|
|
80
|
+
const LIST_READ_CONCURRENCY = 8;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A storage key whose path the file store cannot represent (an empty, `.`,
|
|
84
|
+
* or `..` segment — possible only through caller-controlled segments such
|
|
85
|
+
* as continuation tokens or a caller-supplied session id). Such an object
|
|
86
|
+
* can never exist in the store, so reads are a definitive miss rather than
|
|
87
|
+
* a per-request 400 that the lazy-restore path would propagate forever.
|
|
88
|
+
*/
|
|
89
|
+
function isUnrepresentableFileKey(key: string): boolean {
|
|
90
|
+
return key
|
|
91
|
+
.split("/")
|
|
92
|
+
.some((segment) => segment === "" || segment === "." || segment === "..");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function fileSinkStorageCore(sink: FileSink): FileSinkStorageCore {
|
|
96
|
+
return {
|
|
97
|
+
async put(key, value): Promise<void> {
|
|
98
|
+
if (isUnrepresentableFileKey(key)) {
|
|
99
|
+
// Loud rather than silent: the coordinator logs and drops failed
|
|
100
|
+
// mirror writes (at-most-once), and awaited callers must not
|
|
101
|
+
// believe an impossible write landed.
|
|
102
|
+
throw new Error(
|
|
103
|
+
`framework file storage: key "${key}" has a path segment the store cannot represent`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
await sink.put(key, new TextEncoder().encode(JSON.stringify(value)));
|
|
107
|
+
},
|
|
108
|
+
async get(key): Promise<JsonValue | undefined> {
|
|
109
|
+
if (isUnrepresentableFileKey(key)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const body = await sink.get(key);
|
|
113
|
+
if (body === undefined) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return decodeDocument(key, body instanceof Uint8Array ? body : body.body);
|
|
117
|
+
},
|
|
118
|
+
async delete(key): Promise<void> {
|
|
119
|
+
if (isUnrepresentableFileKey(key)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await sink.delete(key);
|
|
123
|
+
},
|
|
124
|
+
async list(prefix): Promise<StorageEntry[]> {
|
|
125
|
+
const entries = await sink.list(prefix);
|
|
126
|
+
// FileSink.list returns metadata only, so each value is its own
|
|
127
|
+
// read; bound the fan-out — a bulk read here sits on restore paths.
|
|
128
|
+
const bodies = await mapBounded(entries, LIST_READ_CONCURRENCY, (entry) =>
|
|
129
|
+
sink.get(entry.key)
|
|
130
|
+
);
|
|
131
|
+
const out: StorageEntry[] = [];
|
|
132
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
133
|
+
const entry = entries[index];
|
|
134
|
+
const body = bodies[index];
|
|
135
|
+
if (entry === undefined || body === undefined) {
|
|
136
|
+
// Deleted between list and read; a miss is not corruption.
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
out.push({
|
|
140
|
+
key: entry.key,
|
|
141
|
+
value: decodeDocument(
|
|
142
|
+
entry.key,
|
|
143
|
+
body instanceof Uint8Array ? body : body.body
|
|
144
|
+
),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decodeDocument(key: string, bytes: Uint8Array): JsonValue {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(new TextDecoder().decode(bytes)) as JsonValue;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`framework file storage: stored document at "${key}" is not JSON (${
|
|
158
|
+
error instanceof Error ? error.message : String(error)
|
|
159
|
+
})`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Split one storage core by key kind: framework session state to `files`,
|
|
166
|
+
* everything else to `kv`. Spread over the KV backend when building the
|
|
167
|
+
* full definition so its `evals` / `abs` groups (and the cursor-hosted
|
|
168
|
+
* marker) ride along untouched; wrap the KV `cas` group with
|
|
169
|
+
* {@link frameworkGuardedCas} rather than spreading it as-is.
|
|
170
|
+
*/
|
|
171
|
+
export function composeFrameworkStorage(input: {
|
|
172
|
+
kv: FileSinkStorageCore;
|
|
173
|
+
files: FileSinkStorageCore;
|
|
174
|
+
}): FileSinkStorageCore {
|
|
175
|
+
const route = (key: string): FileSinkStorageCore =>
|
|
176
|
+
isFrameworkFileKey(key) ? input.files : input.kv;
|
|
177
|
+
return {
|
|
178
|
+
put: (key, value, ctx) => route(key).put(key, value, ctx),
|
|
179
|
+
get: (key, ctx) => route(key).get(key, ctx),
|
|
180
|
+
delete: (key, ctx) => route(key).delete(key, ctx),
|
|
181
|
+
list: (prefix, ctx) => route(prefix).list(prefix, ctx),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The KV backend's conditional writes, refusing framework-file keys.
|
|
187
|
+
*
|
|
188
|
+
* CAS runs on the KV proxy (a database row version); the plain core routes
|
|
189
|
+
* framework session state to the file store. A future coordination path
|
|
190
|
+
* that CASes a `session/` or `continuation/` key would otherwise read and
|
|
191
|
+
* write a different store than the plain path — silent split-brain. Fail
|
|
192
|
+
* loudly instead; nothing in the package CASes those kinds today.
|
|
193
|
+
*/
|
|
194
|
+
export function frameworkGuardedCas(
|
|
195
|
+
cas: NonNullable<StorageConfig["cas"]>
|
|
196
|
+
): NonNullable<StorageConfig["cas"]> {
|
|
197
|
+
const refuse = (key: string): void => {
|
|
198
|
+
if (isFrameworkFileKey(key)) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`framework file storage: conditional writes are KV-only, but "${key}" routes to the framework file store`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
getWithVersion: (key, ctx) => {
|
|
206
|
+
refuse(key);
|
|
207
|
+
return cas.getWithVersion(key, ctx);
|
|
208
|
+
},
|
|
209
|
+
putIfAbsent: (key, value, ctx) => {
|
|
210
|
+
refuse(key);
|
|
211
|
+
return cas.putIfAbsent(key, value, ctx);
|
|
212
|
+
},
|
|
213
|
+
putIfVersion: (key, value, expectedVersion, ctx) => {
|
|
214
|
+
refuse(key);
|
|
215
|
+
return cas.putIfVersion(key, value, expectedVersion, ctx);
|
|
216
|
+
},
|
|
217
|
+
listKeys: (prefix, ctx) => {
|
|
218
|
+
refuse(prefix);
|
|
219
|
+
return cas.listKeys(prefix, ctx);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -17,11 +17,17 @@
|
|
|
17
17
|
*
|
|
18
18
|
* - **Cursor-managed hosting** (the deployment-generation environment
|
|
19
19
|
* variable is set, same signal that registers `GET /v1/manifest`):
|
|
20
|
-
* the Cursor-hosted backend
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* the Cursor-hosted backend. When the deployment carries a
|
|
21
|
+
* **framework-level file store**
|
|
22
|
+
* (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`), session records, event
|
|
23
|
+
* transcripts, and the continuation index live there — file-shaped state
|
|
24
|
+
* on file storage, apart from the author-facing user store — while
|
|
25
|
+
* `host.kv`, eval runs, A/B samples, and conditional writes stay on the
|
|
26
|
+
* control-plane KV proxy (see framework-file-storage.ts). Without one
|
|
27
|
+
* (a deploy predating framework-store provisioning), everything goes to
|
|
28
|
+
* the KV proxy as before. The API key resolves through the full
|
|
29
|
+
* credential chain on every call because hosted freeze snapshots listen
|
|
30
|
+
* unsigned and the binder drops the key file after restore.
|
|
25
31
|
* - **Self-hosted**: no mirror. `--state-root` is the durable copy, which
|
|
26
32
|
* is correct when the operator owns the disk.
|
|
27
33
|
*
|
|
@@ -34,11 +40,20 @@
|
|
|
34
40
|
* pod's filesystem.
|
|
35
41
|
*/
|
|
36
42
|
|
|
43
|
+
import {
|
|
44
|
+
cursorFrameworkStoreSourceId,
|
|
45
|
+
cursorHostedFiles,
|
|
46
|
+
} from "../files-backends/cursor-hosted.js";
|
|
37
47
|
import { defineStorage, type StorageDefinition } from "../storage.js";
|
|
38
48
|
import { cursorHostedStorage } from "../storage-backends/cursor-hosted.js";
|
|
39
49
|
import type { AgentArchitecture } from "../types.js";
|
|
40
50
|
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
41
51
|
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
52
|
+
import {
|
|
53
|
+
composeFrameworkStorage,
|
|
54
|
+
fileSinkStorageCore,
|
|
55
|
+
frameworkGuardedCas,
|
|
56
|
+
} from "./framework-file-storage.js";
|
|
42
57
|
|
|
43
58
|
export interface FrameworkStorageSelectionInput {
|
|
44
59
|
/** `agent/storage.ts`, when the author wrote one (v1 only: discovery rejects it on v2). */
|
|
@@ -75,13 +90,38 @@ export function selectFrameworkStorageDefinition(
|
|
|
75
90
|
// Self-hosted v2: the state root is the durable copy.
|
|
76
91
|
return undefined;
|
|
77
92
|
}
|
|
93
|
+
// Hosted v2 pods wake per delivery: bulk-restoring up to the default
|
|
94
|
+
// 1000 sessions on every boot is waste (and against the file-store
|
|
95
|
+
// backend it is per-key HTTP round trips before the listener even
|
|
96
|
+
// comes up). Follow-ups restore lazily by continuation or session id;
|
|
97
|
+
// GET /v1/sessions lists from the sink either way.
|
|
98
|
+
const policy = { restore: "off" } as const;
|
|
99
|
+
// Full chain (env → key file → stored login): frozen engines listen
|
|
100
|
+
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this
|
|
101
|
+
// definition is constructed.
|
|
102
|
+
const resolveApiKey = (): string | undefined => resolveApiKeySync()?.apiKey;
|
|
103
|
+
const kv = cursorHostedStorage({ resolveApiKey });
|
|
104
|
+
const frameworkStoreId = cursorFrameworkStoreSourceId(
|
|
105
|
+
env as NodeJS.ProcessEnv
|
|
106
|
+
);
|
|
107
|
+
if (frameworkStoreId === undefined) {
|
|
108
|
+
return defineStorage({ name: "cursor-hosted", policy, ...kv });
|
|
109
|
+
}
|
|
110
|
+
// Framework session state on the framework-level file store; author kv,
|
|
111
|
+
// evals, and A/B tables stay on the KV proxy (spread from `kv`, which
|
|
112
|
+
// also carries the cursor-hosted marker for GET /v1/sessions).
|
|
78
113
|
return defineStorage({
|
|
79
|
-
name: "cursor-hosted",
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
name: "cursor-hosted (framework files)",
|
|
115
|
+
policy,
|
|
116
|
+
...kv,
|
|
117
|
+
...composeFrameworkStorage({
|
|
118
|
+
kv,
|
|
119
|
+
files: fileSinkStorageCore(
|
|
120
|
+
cursorHostedFiles({ storeSourceId: frameworkStoreId, resolveApiKey })
|
|
121
|
+
),
|
|
85
122
|
}),
|
|
123
|
+
// CAS stays on the KV proxy but must refuse framework-file keys, or a
|
|
124
|
+
// future coordination path would split-brain against the plain core.
|
|
125
|
+
cas: frameworkGuardedCas(kv.cas),
|
|
86
126
|
});
|
|
87
127
|
}
|
|
@@ -245,6 +245,13 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
245
245
|
channelId: string,
|
|
246
246
|
sessionPublicId?: string
|
|
247
247
|
): ChannelHandlerArgs;
|
|
248
|
+
/**
|
|
249
|
+
* Await the session's durable mirror (dispatch chain + storage queue)
|
|
250
|
+
* before the execution reply: the control plane may park the pod the
|
|
251
|
+
* moment the reply lands, and a shed session-record flush would strand
|
|
252
|
+
* the conversation on the next wake.
|
|
253
|
+
*/
|
|
254
|
+
drainSessionDurability?(sessionId: string): Promise<void>;
|
|
248
255
|
}): void {
|
|
249
256
|
const pending = new Map<
|
|
250
257
|
string,
|
|
@@ -387,6 +394,9 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
387
394
|
},
|
|
388
395
|
});
|
|
389
396
|
const outcome = await session.waitForCompletion();
|
|
397
|
+
// Failed turns drain too: their record/continuation updates must
|
|
398
|
+
// survive the park just like a finished turn's.
|
|
399
|
+
await input.drainSessionDurability?.(session.id);
|
|
390
400
|
if (outcome.status !== "finished") {
|
|
391
401
|
return {
|
|
392
402
|
status: 503,
|
|
@@ -74,6 +74,7 @@ type TemplateEntry = {
|
|
|
74
74
|
|
|
75
75
|
const TEMPLATES: Readonly<Record<TemplateName, TemplateEntry>> = {
|
|
76
76
|
"agentic-owners": { dir: "agentic-owners" },
|
|
77
|
+
"code-wiki": { dir: "code-wiki" },
|
|
77
78
|
demo: { dir: "demo" },
|
|
78
79
|
"pr-autofixer": { dir: "pr-autofixer" },
|
|
79
80
|
"security-help": { dir: "security-help" },
|
|
@@ -709,6 +710,7 @@ export function printInitGuide(): void {
|
|
|
709
710
|
Scaffold a project, then open the playground:
|
|
710
711
|
npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
|
|
711
712
|
npx ${PACKAGE_NAME} init ./my-demo --template demo # record a PR walkthrough
|
|
713
|
+
npx ${PACKAGE_NAME} init ./code-wiki --template code-wiki # keep wiki pages current after merge
|
|
712
714
|
npx ${PACKAGE_NAME} init ./my-reviewer --template security-reviewer # review PRs for security bugs
|
|
713
715
|
npx ${PACKAGE_NAME} init ./security-help --template security-help # answer security questions in Slack
|
|
714
716
|
npx ${PACKAGE_NAME} init ./my-triage --template triage-linear # comment on Linear issues
|
|
@@ -31,9 +31,6 @@ import { CLI_COMMAND_NAME, packageRootDir } from "./distribution.js";
|
|
|
31
31
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
32
32
|
import { stdoutPalette } from "./terminal-style.js";
|
|
33
33
|
|
|
34
|
-
/** Monorepo-only skill; excluded from the published package. */
|
|
35
|
-
const SKIP_SKILL_DIRS = new Set(["operate"]);
|
|
36
|
-
|
|
37
34
|
/** Category folder under ~/.cursor/skills (Cursor discovers nested skills). */
|
|
38
35
|
export const CURSOR_SKILLS_NAMESPACE = "agentsdk";
|
|
39
36
|
|
|
@@ -122,7 +119,7 @@ export async function listShippedPackageSkills(
|
|
|
122
119
|
|
|
123
120
|
const skills: PackageSkill[] = [];
|
|
124
121
|
for (const entry of entries) {
|
|
125
|
-
if (!entry.isDirectory()
|
|
122
|
+
if (!entry.isDirectory()) {
|
|
126
123
|
continue;
|
|
127
124
|
}
|
|
128
125
|
const sourceDir = join(skillsRoot, entry.name);
|