pi-cursor-sdk 0.2.0 → 0.3.7

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 (177) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +55 -51
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +353 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +127 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +6 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +409 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +130 -0
  41. package/dist/cursor-pi-context.js +30 -0
  42. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  43. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  44. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  45. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  46. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  47. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  48. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  49. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  50. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  51. package/dist/cursor-pi-tool-bridge.js +123 -0
  52. package/dist/cursor-provider-errors.js +311 -0
  53. package/dist/cursor-provider-lazy.js +39 -0
  54. package/dist/cursor-provider-live-run-drain.js +420 -0
  55. package/dist/cursor-provider-overflow.js +90 -0
  56. package/dist/cursor-provider-run-finalizer.js +176 -0
  57. package/dist/cursor-provider-run-outcome.js +76 -0
  58. package/dist/cursor-provider-turn-coordinator.js +293 -0
  59. package/dist/cursor-provider-turn-display-router.js +158 -0
  60. package/dist/cursor-provider-turn-emit.js +41 -0
  61. package/dist/cursor-provider-turn-finalize.js +154 -0
  62. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  63. package/dist/cursor-provider-turn-prepare.js +379 -0
  64. package/dist/cursor-provider-turn-runner.js +146 -0
  65. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  66. package/dist/cursor-provider-turn-send.js +138 -0
  67. package/dist/cursor-provider-turn-shell-output.js +117 -0
  68. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  69. package/dist/cursor-provider-turn-types.js +1 -0
  70. package/dist/cursor-provider.js +72 -0
  71. package/dist/cursor-question-tool.js +194 -0
  72. package/dist/cursor-record-utils.js +61 -0
  73. package/dist/cursor-replay-activity-builders.js +114 -0
  74. package/dist/cursor-replay-source-names.js +27 -0
  75. package/dist/cursor-replay-summary-args.js +105 -0
  76. package/dist/cursor-replay-tool-details.js +167 -0
  77. package/dist/cursor-ripgrep-path.js +27 -0
  78. package/dist/cursor-run-final-text.js +63 -0
  79. package/dist/cursor-runtime-state.js +380 -0
  80. package/dist/cursor-sdk-billed-usage.js +86 -0
  81. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  82. package/dist/cursor-sdk-event-debug-session.js +107 -0
  83. package/dist/cursor-sdk-event-debug.js +461 -0
  84. package/dist/cursor-sdk-output-filter.js +1 -0
  85. package/dist/cursor-sdk-process-error-guard.js +221 -0
  86. package/dist/cursor-sdk-runtime.js +3 -0
  87. package/dist/cursor-sensitive-text.js +23 -0
  88. package/dist/cursor-session-agent-cleanup.js +311 -0
  89. package/dist/cursor-session-agent-lifecycle.js +32 -0
  90. package/dist/cursor-session-agent-lineage.js +103 -0
  91. package/dist/cursor-session-agent-resume.js +417 -0
  92. package/dist/cursor-session-agent.js +507 -0
  93. package/dist/cursor-session-compaction-prep.js +21 -0
  94. package/dist/cursor-session-scope.js +113 -0
  95. package/dist/cursor-session-send-policy.js +18 -0
  96. package/dist/cursor-session-store.js +105 -0
  97. package/dist/cursor-session-turn-queue.js +50 -0
  98. package/dist/cursor-setting-sources.js +15 -0
  99. package/dist/cursor-skill-tool.js +230 -0
  100. package/dist/cursor-state.js +519 -0
  101. package/dist/cursor-task-presentation.js +59 -0
  102. package/dist/cursor-tool-lifecycle.js +94 -0
  103. package/dist/cursor-tool-manifest.js +34 -0
  104. package/dist/cursor-tool-presentation-registry.js +305 -0
  105. package/dist/cursor-tool-result-display-readers.js +158 -0
  106. package/dist/cursor-tool-transcript.js +32 -0
  107. package/dist/cursor-tool-visibility.js +28 -0
  108. package/dist/cursor-transcript-tool-formatters.js +618 -0
  109. package/dist/cursor-transcript-tool-specs.js +284 -0
  110. package/dist/cursor-transcript-utils.js +170 -0
  111. package/dist/cursor-usage-accounting.js +166 -0
  112. package/dist/cursor-web-tool-activity.js +23 -0
  113. package/dist/cursor-web-tool-args.js +12 -0
  114. package/dist/index.js +82 -0
  115. package/dist/model-discovery.js +305 -0
  116. package/dist/model-list-cache.js +156 -0
  117. package/docs/cursor-dogfood-checklist.md +2 -1
  118. package/docs/cursor-live-smoke-checklist.md +13 -12
  119. package/docs/cursor-model-ux-spec.md +27 -6
  120. package/docs/cursor-native-tool-replay.md +13 -13
  121. package/docs/cursor-native-tool-visual-audit.md +6 -3
  122. package/docs/cursor-testing-lessons.md +14 -8
  123. package/docs/cursor-tool-surfaces.md +5 -5
  124. package/docs/platform-smoke.md +3 -3
  125. package/package.json +19 -7
  126. package/platform-smoke.config.mjs +1 -1
  127. package/scripts/build.mjs +126 -0
  128. package/scripts/cloud-runtime-smoke.mjs +3 -1
  129. package/scripts/debug-provider-events.mjs +3 -1
  130. package/scripts/debug-sdk-events.mjs +1 -1
  131. package/scripts/isolated-cursor-smoke.sh +5 -4
  132. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  133. package/scripts/lib/cursor-visual-render.mjs +1 -1
  134. package/scripts/lib/ensure-built.mjs +16 -0
  135. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  136. package/scripts/local-resume-smoke.mjs +3 -1
  137. package/scripts/platform-smoke/card-detect.mjs +1 -1
  138. package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
  139. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  140. package/scripts/platform-smoke/targets.mjs +2 -2
  141. package/scripts/prepare.mjs +102 -0
  142. package/scripts/steering-rpc-smoke.mjs +4 -2
  143. package/scripts/tmux-live-smoke.sh +11 -11
  144. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  145. package/scripts/visual-tui-smoke.mjs +51 -7
  146. package/src/bundled-context-windows.ts +4 -1
  147. package/src/context.ts +27 -4
  148. package/src/cursor-agents-context-registration.ts +2 -2
  149. package/src/cursor-agents-context.ts +24 -10
  150. package/src/cursor-api-key.ts +5 -4
  151. package/src/cursor-cloud-reporting.ts +23 -2
  152. package/src/cursor-context-tools.ts +3 -1
  153. package/src/cursor-fallback-models.generated.ts +592 -204
  154. package/src/cursor-live-run-coordinator.ts +11 -6
  155. package/src/cursor-native-tool-display-registration.ts +10 -11
  156. package/src/cursor-partial-content-emitter.ts +13 -0
  157. package/src/cursor-pi-context.ts +44 -0
  158. package/src/cursor-provider-errors.ts +1 -1
  159. package/src/cursor-provider-lazy.ts +5 -4
  160. package/src/cursor-provider-live-run-drain.ts +14 -9
  161. package/src/cursor-provider-run-finalizer.ts +1 -0
  162. package/src/cursor-provider-turn-coordinator.ts +15 -0
  163. package/src/cursor-provider-turn-emit.ts +6 -0
  164. package/src/cursor-provider-turn-finalize.ts +12 -0
  165. package/src/cursor-provider-turn-prepare.ts +10 -5
  166. package/src/cursor-provider-turn-types.ts +2 -0
  167. package/src/cursor-sdk-billed-usage.ts +107 -0
  168. package/src/cursor-sdk-process-error-guard.ts +6 -4
  169. package/src/cursor-session-agent-lifecycle.ts +8 -9
  170. package/src/cursor-session-agent-resume.ts +23 -0
  171. package/src/cursor-session-compaction-prep.ts +4 -0
  172. package/src/cursor-skill-tool.ts +1 -1
  173. package/src/cursor-tool-manifest.ts +1 -1
  174. package/src/cursor-usage-accounting.ts +70 -9
  175. package/src/index.ts +1 -1
  176. package/tsconfig.build.json +8 -0
  177. package/tsconfig.json +16 -0
@@ -0,0 +1,105 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join, toNamespacedPath } from "node:path";
5
+ import { loadCursorSdk } from "./cursor-sdk-runtime.js";
6
+ let sdkOperationsForTests;
7
+ export function hashCursorSessionStoreScope(scopeKey) {
8
+ return createHash("sha256")
9
+ .update("pi-cursor-sdk-session-store\0")
10
+ .update(scopeKey)
11
+ .digest("hex")
12
+ .slice(0, 32);
13
+ }
14
+ export function buildCursorSessionStateRoot(defaultStateRoot, scopeKey, persistent) {
15
+ const baseRoot = persistent ? defaultStateRoot : join(tmpdir(), `pi-cursor-sdk-${randomUUID()}`);
16
+ return join(baseRoot, "pi-sessions", hashCursorSessionStoreScope(scopeKey));
17
+ }
18
+ async function getSdkOperations() {
19
+ if (sdkOperationsForTests)
20
+ return sdkOperationsForTests;
21
+ const [{ getDefaultSdkStateRoot }, { SqliteLocalAgentStore }] = await Promise.all([
22
+ loadCursorSdk(),
23
+ import("@cursor/sdk/sqlite"),
24
+ ]);
25
+ return {
26
+ getDefaultStateRoot: getDefaultSdkStateRoot,
27
+ openSqliteStore: (options) => SqliteLocalAgentStore.open(options),
28
+ };
29
+ }
30
+ export async function getCursorSessionStoreIdentities(cwd, scopeKey, persistent) {
31
+ const defaultStateRoot = await (await getSdkOperations()).getDefaultStateRoot(cwd);
32
+ return {
33
+ defaultStore: { version: 1, stateRoot: defaultStateRoot },
34
+ sessionStore: {
35
+ version: 1,
36
+ stateRoot: buildCursorSessionStateRoot(defaultStateRoot, scopeKey, persistent),
37
+ },
38
+ };
39
+ }
40
+ export function cursorSessionStoreIdentitiesEqual(left, right) {
41
+ return left.version === right.version && left.stateRoot === right.stateRoot;
42
+ }
43
+ async function openOwnedCursorSessionStore(cwd, identity, removalRoot) {
44
+ const openedIdentity = Object.freeze({ ...identity });
45
+ let store;
46
+ try {
47
+ store = await (await getSdkOperations()).openSqliteStore({
48
+ workspaceRef: cwd,
49
+ stateRoot: toNamespacedPath(openedIdentity.stateRoot),
50
+ });
51
+ }
52
+ catch (error) {
53
+ if (removalRoot)
54
+ await rm(removalRoot, { recursive: true, force: true }).catch(() => undefined);
55
+ throw error;
56
+ }
57
+ return {
58
+ identity: openedIdentity,
59
+ store,
60
+ dispose: async () => {
61
+ try {
62
+ await store.dispose();
63
+ }
64
+ finally {
65
+ if (removalRoot)
66
+ await rm(removalRoot, { recursive: true, force: true });
67
+ }
68
+ },
69
+ };
70
+ }
71
+ export function openCursorSessionStore(cwd, identity) {
72
+ return openOwnedCursorSessionStore(cwd, identity);
73
+ }
74
+ export async function openCursorSessionStoreForScope(options) {
75
+ const identities = await getCursorSessionStoreIdentities(options.cwd, options.scopeKey, options.persistent);
76
+ const requestedResumeIdentity = options.hasResumeHandle
77
+ ? options.resumeIdentity ?? (options.persistent ? identities.defaultStore : undefined)
78
+ : undefined;
79
+ const resumableIdentities = options.persistent
80
+ ? [identities.defaultStore, identities.sessionStore]
81
+ : [identities.sessionStore];
82
+ const resumeIdentity = requestedResumeIdentity && resumableIdentities
83
+ .find((identity) => cursorSessionStoreIdentitiesEqual(identity, requestedResumeIdentity));
84
+ let resumeAttemptAllowed = options.hasResumeHandle && resumeIdentity !== undefined;
85
+ let resumeFallback = options.persistent && options.hasResumeHandle && !resumeIdentity;
86
+ const selectedIdentity = resumeIdentity ?? identities.sessionStore;
87
+ const removalRoot = options.persistent ? undefined : dirname(dirname(identities.sessionStore.stateRoot));
88
+ let sessionStore;
89
+ try {
90
+ sessionStore = await openOwnedCursorSessionStore(options.cwd, selectedIdentity, removalRoot);
91
+ }
92
+ catch (error) {
93
+ if (!resumeIdentity || cursorSessionStoreIdentitiesEqual(resumeIdentity, identities.sessionStore))
94
+ throw error;
95
+ resumeAttemptAllowed = false;
96
+ resumeFallback = true;
97
+ sessionStore = await openOwnedCursorSessionStore(options.cwd, identities.sessionStore);
98
+ }
99
+ return { sessionStore, identities, resumeAttemptAllowed, resumeFallback };
100
+ }
101
+ export const __testUtils = {
102
+ setSdkOperations(operations) {
103
+ sdkOperationsForTests = operations;
104
+ },
105
+ };
@@ -0,0 +1,50 @@
1
+ import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
2
+ const turnQueuesByScope = new Map();
3
+ async function waitForPreviousTurn(previous, signal) {
4
+ if (!signal) {
5
+ await previous.catch(() => undefined);
6
+ return;
7
+ }
8
+ if (signal.aborted)
9
+ throw new CursorLiveRunAbortError();
10
+ await new Promise((resolve, reject) => {
11
+ const onAbort = () => {
12
+ reject(new CursorLiveRunAbortError());
13
+ };
14
+ signal.addEventListener("abort", onAbort, { once: true });
15
+ previous.catch(() => undefined).then(() => {
16
+ signal.removeEventListener("abort", onAbort);
17
+ resolve();
18
+ });
19
+ });
20
+ }
21
+ export async function runExclusiveCursorSessionTurn(scopeKey, body, signal) {
22
+ const previous = turnQueuesByScope.get(scopeKey);
23
+ let releaseCurrent;
24
+ const current = new Promise((resolve) => {
25
+ releaseCurrent = resolve;
26
+ });
27
+ const tail = (previous ?? Promise.resolve()).catch(() => undefined).then(() => current);
28
+ turnQueuesByScope.set(scopeKey, tail);
29
+ void tail.finally(() => {
30
+ if (turnQueuesByScope.get(scopeKey) === tail) {
31
+ turnQueuesByScope.delete(scopeKey);
32
+ }
33
+ });
34
+ try {
35
+ if (previous)
36
+ await waitForPreviousTurn(previous, signal);
37
+ return await body();
38
+ }
39
+ finally {
40
+ releaseCurrent();
41
+ }
42
+ }
43
+ export const __testUtils = {
44
+ reset() {
45
+ turnQueuesByScope.clear();
46
+ },
47
+ count() {
48
+ return turnQueuesByScope.size;
49
+ },
50
+ };
@@ -0,0 +1,15 @@
1
+ /** Provider-facing wrapper; canonical parsing lives in shared/cursor-setting-sources.mjs. */
2
+ import { CURSOR_SETTING_SOURCES_ENV as CURSOR_SETTING_SOURCES_ENV_JS, DEFAULT_CURSOR_SETTING_SOURCES as DEFAULT_CURSOR_SETTING_SOURCES_JS, resolveCursorSettingSources as resolveCursorSettingSourcesJs, } from "../shared/cursor-setting-sources.mjs";
3
+ export const CURSOR_SETTING_SOURCES_ENV = CURSOR_SETTING_SOURCES_ENV_JS;
4
+ export const DEFAULT_CURSOR_SETTING_SOURCES = DEFAULT_CURSOR_SETTING_SOURCES_JS;
5
+ export function resolveCursorSettingSources(raw) {
6
+ return resolveCursorSettingSourcesJs(raw);
7
+ }
8
+ export function getEffectiveCursorSettingSources(raw = process.env[CURSOR_SETTING_SOURCES_ENV]) {
9
+ return resolveCursorSettingSources(raw);
10
+ }
11
+ export function cursorSettingSourcesIncludes(settingSources, source) {
12
+ if (!settingSources?.length)
13
+ return false;
14
+ return settingSources.includes("all") || settingSources.includes(source);
15
+ }
@@ -0,0 +1,230 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { Type } from "typebox";
4
+ import { arePiToolsDisabled } from "./cursor-active-tools.js";
5
+ import { isCursorModel } from "./cursor-model.js";
6
+ import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
7
+ import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
8
+ import { resolveEffectiveCursorConfigForContext } from "./cursor-runtime-state.js";
9
+ export const CURSOR_ACTIVATE_SKILL_TOOL_NAME = "cursor_activate_skill";
10
+ export const CURSOR_ACTIVATE_SKILL_MCP_NAME = "pi__cursor_activate_skill";
11
+ const AVAILABLE_SKILLS_SECTION_PATTERN = /<skills>\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>\n<\/skills>|\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>/;
12
+ const MAX_SKILL_RESOURCES = 80;
13
+ const RESOURCE_DIR_NAMES = ["scripts", "references", "assets"];
14
+ let currentSkillsByName = new Map();
15
+ function escapeXml(value) {
16
+ return value
17
+ .replace(/&/g, "&amp;")
18
+ .replace(/</g, "&lt;")
19
+ .replace(/>/g, "&gt;")
20
+ .replace(/\"/g, "&quot;")
21
+ .replace(/'/g, "&apos;");
22
+ }
23
+ function getVisibleSkills(skills) {
24
+ return (skills ?? []).filter((skill) => !skill.disableModelInvocation);
25
+ }
26
+ function setCurrentSkills(skills) {
27
+ currentSkillsByName = new Map(getVisibleSkills(skills).map((skill) => [skill.name, skill]));
28
+ }
29
+ function getAvailableSkillNames() {
30
+ return [...currentSkillsByName.keys()].sort();
31
+ }
32
+ function resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx) {
33
+ return cursorModel ? resolveEffectiveCursorConfigForContext(ctx).runtime.value : "local";
34
+ }
35
+ function shouldExposeSkillTool(model, runtime) {
36
+ return runtime === "local" && isCursorModel(model) && resolveCursorPiToolBridgeEnabled() && currentSkillsByName.size > 0;
37
+ }
38
+ function syncCursorSkillToolForModel(pi, model, runtime) {
39
+ const activeToolNames = new Set(pi.getActiveTools());
40
+ const shouldBeActive = !arePiToolsDisabled(pi) && shouldExposeSkillTool(model, runtime);
41
+ const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
42
+ if (shouldBeActive === alreadyActive)
43
+ return;
44
+ if (shouldBeActive) {
45
+ activeToolNames.add(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
46
+ }
47
+ else {
48
+ activeToolNames.delete(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
49
+ }
50
+ pi.setActiveTools([...activeToolNames]);
51
+ }
52
+ export function formatCursorSkillsForPrompt(skills) {
53
+ const visibleSkills = getVisibleSkills(skills);
54
+ if (visibleSkills.length === 0)
55
+ return "";
56
+ const lines = [
57
+ "\n\nThe following skills provide specialized instructions for specific tasks.",
58
+ `When a task matches a skill's description, call ${CURSOR_ACTIVATE_SKILL_MCP_NAME} with the skill name to load its full SKILL.md instructions before proceeding.`,
59
+ "If the pi bridge is disabled and the activation tool is unavailable, use Cursor's file-read capability on the listed SKILL.md location instead.",
60
+ "When a skill references relative paths, resolve them against the skill directory (the parent of SKILL.md / dirname of the path) and use absolute paths in tool calls.",
61
+ "",
62
+ "<available_skills>",
63
+ ];
64
+ for (const skill of visibleSkills) {
65
+ lines.push(" <skill>");
66
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
67
+ lines.push(` <description>${escapeXml(skill.description)}</description>`);
68
+ lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
69
+ lines.push(" </skill>");
70
+ }
71
+ lines.push("</available_skills>");
72
+ return lines.join("\n");
73
+ }
74
+ export function resolveCursorSkillSystemPrompt(systemPrompt, model, systemPromptOptions, runtime = "local") {
75
+ if (!isCursorModel(model))
76
+ return systemPrompt;
77
+ if (runtime === "cloud")
78
+ return systemPrompt.replace(AVAILABLE_SKILLS_SECTION_PATTERN, "");
79
+ const skills = getVisibleSkills(systemPromptOptions?.skills);
80
+ if (skills.length === 0)
81
+ return systemPrompt;
82
+ const replacement = formatCursorSkillsForPrompt(skills);
83
+ if (AVAILABLE_SKILLS_SECTION_PATTERN.test(systemPrompt)) {
84
+ return systemPrompt.replace(AVAILABLE_SKILLS_SECTION_PATTERN, replacement);
85
+ }
86
+ return `${systemPrompt}${replacement}`;
87
+ }
88
+ async function collectResourcePaths(root, absoluteDir, output) {
89
+ if (output.length >= MAX_SKILL_RESOURCES)
90
+ return;
91
+ let entries;
92
+ try {
93
+ entries = await readdir(absoluteDir, { withFileTypes: true });
94
+ }
95
+ catch {
96
+ return;
97
+ }
98
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
99
+ if (output.length >= MAX_SKILL_RESOURCES)
100
+ return;
101
+ const absolutePath = join(absoluteDir, entry.name);
102
+ if (entry.isSymbolicLink())
103
+ continue;
104
+ if (entry.isDirectory()) {
105
+ await collectResourcePaths(root, absolutePath, output);
106
+ continue;
107
+ }
108
+ if (!entry.isFile())
109
+ continue;
110
+ output.push(relative(root, absolutePath).replace(/\\/g, "/"));
111
+ }
112
+ }
113
+ async function listSkillResourcePaths(baseDir) {
114
+ const resources = [];
115
+ for (const resourceDirName of RESOURCE_DIR_NAMES) {
116
+ await collectResourcePaths(baseDir, join(baseDir, resourceDirName), resources);
117
+ if (resources.length >= MAX_SKILL_RESOURCES)
118
+ break;
119
+ }
120
+ return resources;
121
+ }
122
+ function buildActivationDetails(skill, resources = []) {
123
+ return {
124
+ name: skill?.name,
125
+ filePath: skill?.filePath,
126
+ baseDir: skill ? dirname(skill.filePath) : undefined,
127
+ resources,
128
+ availableSkillNames: getAvailableSkillNames(),
129
+ };
130
+ }
131
+ function formatSkillResources(resources) {
132
+ if (resources.length === 0)
133
+ return "<skill_resources />";
134
+ return [
135
+ "<skill_resources>",
136
+ ...resources.map((resource) => ` <file>${escapeXml(resource)}</file>`),
137
+ "</skill_resources>",
138
+ ].join("\n");
139
+ }
140
+ function wrapSkillContent(skill, content, resources) {
141
+ const baseDir = dirname(skill.filePath);
142
+ return [
143
+ `<skill_content name=\"${escapeXml(skill.name)}\">`,
144
+ content.trim(),
145
+ "",
146
+ `Skill directory: ${baseDir}`,
147
+ "Relative paths in this skill are relative to the skill directory.",
148
+ formatSkillResources(resources),
149
+ "</skill_content>",
150
+ ].join("\n");
151
+ }
152
+ export function registerCursorSkillTool(pi) {
153
+ pi.registerTool({
154
+ name: CURSOR_ACTIVATE_SKILL_TOOL_NAME,
155
+ label: "Cursor skill",
156
+ description: "Load full pi Agent Skill instructions for Cursor. Use with a skill name from the current <available_skills> catalog before applying that skill.",
157
+ promptSnippet: "Load full pi Agent Skill instructions for a listed skill before Cursor applies that skill",
158
+ parameters: Type.Object({
159
+ name: Type.String({ description: "Skill name from the current <available_skills> catalog" }),
160
+ }),
161
+ promptGuidelines: [
162
+ `Use ${CURSOR_ACTIVATE_SKILL_TOOL_NAME} only for skill names listed in the current <available_skills> catalog.`,
163
+ "After loading a skill, follow its instructions and resolve relative skill paths against the returned skill directory.",
164
+ ],
165
+ async execute(_toolCallId, params) {
166
+ const requestedName = params.name?.trim();
167
+ if (!requestedName) {
168
+ throw new Error("No skill name was provided.");
169
+ }
170
+ const skill = currentSkillsByName.get(requestedName);
171
+ if (!skill) {
172
+ throw new Error(`Skill not available: ${requestedName}. Available skills: ${getAvailableSkillNames().join(", ") || "none"}.`);
173
+ }
174
+ try {
175
+ const [content, resources] = await Promise.all([
176
+ readFile(skill.filePath, "utf8"),
177
+ listSkillResourcePaths(dirname(skill.filePath)),
178
+ ]);
179
+ return {
180
+ content: [{ type: "text", text: wrapSkillContent(skill, content, resources) }],
181
+ details: buildActivationDetails(skill, resources),
182
+ };
183
+ }
184
+ catch (error) {
185
+ throw new Error(`Failed to load skill ${requestedName} from ${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);
186
+ }
187
+ },
188
+ });
189
+ const clearSkillsAndSync = (model, runtime = "local") => {
190
+ setCurrentSkills([]);
191
+ syncCursorSkillToolForModel(pi, model, runtime);
192
+ };
193
+ registerCursorModelLifecycle(pi, {
194
+ sessionStart: (_event, ctx) => {
195
+ clearSkillsAndSync(ctx.model);
196
+ },
197
+ modelSelect: (event) => {
198
+ clearSkillsAndSync(event.model);
199
+ },
200
+ turnStart: (_event, ctx) => {
201
+ const cursorModel = isCursorModel(ctx.model);
202
+ const runtime = resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx);
203
+ if (!cursorModel || runtime === "cloud")
204
+ setCurrentSkills([]);
205
+ syncCursorSkillToolForModel(pi, ctx.model, runtime);
206
+ },
207
+ beforeAgentStart: (event, ctx) => {
208
+ const cursorModel = isCursorModel(ctx.model);
209
+ const runtime = resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx);
210
+ if (cursorModel && runtime === "local") {
211
+ setCurrentSkills(event.systemPromptOptions?.skills);
212
+ }
213
+ else {
214
+ setCurrentSkills([]);
215
+ }
216
+ syncCursorSkillToolForModel(pi, ctx.model, runtime);
217
+ const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions, runtime);
218
+ if (resolved === event.systemPrompt)
219
+ return undefined;
220
+ return { systemPrompt: resolved };
221
+ },
222
+ });
223
+ }
224
+ export const __testUtils = {
225
+ AVAILABLE_SKILLS_SECTION_PATTERN,
226
+ buildActivationDetails,
227
+ setCurrentSkills,
228
+ listSkillResourcePaths,
229
+ wrapSkillContent,
230
+ };