@velum-labs/cursorkit 0.1.0

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 (142) hide show
  1. package/DISCLAIMER.md +12 -0
  2. package/README.md +157 -0
  3. package/dist/src/agentTools/diff.d.ts +11 -0
  4. package/dist/src/agentTools/diff.js +88 -0
  5. package/dist/src/agentTools/policy.d.ts +3 -0
  6. package/dist/src/agentTools/policy.js +12 -0
  7. package/dist/src/agentTools/registry.d.ts +114 -0
  8. package/dist/src/agentTools/registry.js +663 -0
  9. package/dist/src/agentTools/results.d.ts +14 -0
  10. package/dist/src/agentTools/results.js +117 -0
  11. package/dist/src/agentTools/schemas.d.ts +3 -0
  12. package/dist/src/agentTools/schemas.js +89 -0
  13. package/dist/src/agentTools/surface.d.ts +11 -0
  14. package/dist/src/agentTools/surface.js +251 -0
  15. package/dist/src/certs.d.ts +8 -0
  16. package/dist/src/certs.js +34 -0
  17. package/dist/src/ck.d.ts +2 -0
  18. package/dist/src/ck.js +6 -0
  19. package/dist/src/ckLauncher.d.ts +150 -0
  20. package/dist/src/ckLauncher.js +1496 -0
  21. package/dist/src/cli.d.ts +2 -0
  22. package/dist/src/cli.js +265 -0
  23. package/dist/src/config.d.ts +52 -0
  24. package/dist/src/config.js +210 -0
  25. package/dist/src/connectEnvelope.d.ts +16 -0
  26. package/dist/src/connectEnvelope.js +70 -0
  27. package/dist/src/desktop.d.ts +19 -0
  28. package/dist/src/desktop.js +167 -0
  29. package/dist/src/desktopConnectProxy.d.ts +26 -0
  30. package/dist/src/desktopConnectProxy.js +175 -0
  31. package/dist/src/extensions/index.d.ts +2 -0
  32. package/dist/src/extensions/index.js +1 -0
  33. package/dist/src/extensions/registry.d.ts +8 -0
  34. package/dist/src/extensions/registry.js +52 -0
  35. package/dist/src/extensions/types.d.ts +42 -0
  36. package/dist/src/extensions/types.js +1 -0
  37. package/dist/src/fixtures/modelFusion.d.ts +103 -0
  38. package/dist/src/fixtures/modelFusion.js +404 -0
  39. package/dist/src/fixtures/replay.d.ts +9 -0
  40. package/dist/src/fixtures/replay.js +41 -0
  41. package/dist/src/fixtures/sanitizer.d.ts +9 -0
  42. package/dist/src/fixtures/sanitizer.js +43 -0
  43. package/dist/src/fixtures/schema.d.ts +38 -0
  44. package/dist/src/fixtures/schema.js +33 -0
  45. package/dist/src/gen/agent/v1/agent_pb.d.ts +21577 -0
  46. package/dist/src/gen/agent/v1/agent_pb.js +5325 -0
  47. package/dist/src/gen/aiserver/v1/aiserver_pb.d.ts +135242 -0
  48. package/dist/src/gen/aiserver/v1/aiserver_pb.js +34430 -0
  49. package/dist/src/gen/anyrun/v1/anyrun_pb.d.ts +1163 -0
  50. package/dist/src/gen/anyrun/v1/anyrun_pb.js +374 -0
  51. package/dist/src/gen/google/protobuf/google_pb.d.ts +142 -0
  52. package/dist/src/gen/google/protobuf/google_pb.js +54 -0
  53. package/dist/src/gen/internapi/v1/internapi_pb.d.ts +121 -0
  54. package/dist/src/gen/internapi/v1/internapi_pb.js +79 -0
  55. package/dist/src/logger.d.ts +8 -0
  56. package/dist/src/logger.js +37 -0
  57. package/dist/src/modelFusion/cursorHarness.d.ts +146 -0
  58. package/dist/src/modelFusion/cursorHarness.js +647 -0
  59. package/dist/src/modelFusion/index.d.ts +4 -0
  60. package/dist/src/modelFusion/index.js +2 -0
  61. package/dist/src/models/registry.d.ts +22 -0
  62. package/dist/src/models/registry.js +30 -0
  63. package/dist/src/proto.d.ts +13 -0
  64. package/dist/src/proto.js +61 -0
  65. package/dist/src/providers/openai.d.ts +64 -0
  66. package/dist/src/providers/openai.js +355 -0
  67. package/dist/src/redaction.d.ts +4 -0
  68. package/dist/src/redaction.js +65 -0
  69. package/dist/src/routeInventory.d.ts +16 -0
  70. package/dist/src/routeInventory.js +39 -0
  71. package/dist/src/routes.d.ts +37 -0
  72. package/dist/src/routes.js +227 -0
  73. package/dist/src/server.d.ts +50 -0
  74. package/dist/src/server.js +1353 -0
  75. package/dist/src/services/agent.d.ts +1 -0
  76. package/dist/src/services/agent.js +7 -0
  77. package/dist/src/services/agentRun.d.ts +60 -0
  78. package/dist/src/services/agentRun.js +391 -0
  79. package/dist/src/services/chat.d.ts +11 -0
  80. package/dist/src/services/chat.js +47 -0
  81. package/dist/src/services/models.d.ts +10 -0
  82. package/dist/src/services/models.js +216 -0
  83. package/dist/src/services/serverConfig.d.ts +2 -0
  84. package/dist/src/services/serverConfig.js +19 -0
  85. package/dist/src/testing/artifacts.d.ts +14 -0
  86. package/dist/src/testing/artifacts.js +92 -0
  87. package/dist/src/testing/cli.d.ts +4 -0
  88. package/dist/src/testing/cli.js +192 -0
  89. package/dist/src/testing/localBackend.d.ts +24 -0
  90. package/dist/src/testing/localBackend.js +310 -0
  91. package/dist/src/testing/processRunner.d.ts +7 -0
  92. package/dist/src/testing/processRunner.js +74 -0
  93. package/dist/src/testing/runner.d.ts +9 -0
  94. package/dist/src/testing/runner.js +85 -0
  95. package/dist/src/testing/scenarios.d.ts +3 -0
  96. package/dist/src/testing/scenarios.js +2535 -0
  97. package/dist/src/testing/types.d.ts +66 -0
  98. package/dist/src/testing/types.js +1 -0
  99. package/dist/src/tools/baselineInventory.d.ts +12 -0
  100. package/dist/src/tools/baselineInventory.js +680 -0
  101. package/dist/src/tools/checkModelFusionProtocol.d.ts +1 -0
  102. package/dist/src/tools/checkModelFusionProtocol.js +274 -0
  103. package/dist/src/tools/checkReleasePublishConfig.d.ts +1 -0
  104. package/dist/src/tools/checkReleasePublishConfig.js +99 -0
  105. package/dist/src/tools/generateProtoInventory.d.ts +1 -0
  106. package/dist/src/tools/generateProtoInventory.js +89 -0
  107. package/dist/src/tools/normalizeGeneratedCode.d.ts +1 -0
  108. package/dist/src/tools/normalizeGeneratedCode.js +18 -0
  109. package/dist/src/tools/releaseCheck.d.ts +26 -0
  110. package/dist/src/tools/releaseCheck.js +367 -0
  111. package/dist/src/trace.d.ts +39 -0
  112. package/dist/src/trace.js +106 -0
  113. package/dist/src/translation.d.ts +6 -0
  114. package/dist/src/translation.js +22 -0
  115. package/dist/src/upstream.d.ts +20 -0
  116. package/dist/src/upstream.js +270 -0
  117. package/docs/configuration.md +55 -0
  118. package/docs/cursor-app.md +263 -0
  119. package/docs/implementation-inventory.json +609 -0
  120. package/docs/learnings.md +363 -0
  121. package/docs/model-fusion-protocol-origin.json +126 -0
  122. package/docs/model-fusion-protocol.md +110 -0
  123. package/docs/plugin-authoring.md +24 -0
  124. package/docs/proto-inventory.md +1477 -0
  125. package/docs/protocol-surface-audit.md +92 -0
  126. package/docs/protocol.md +52 -0
  127. package/docs/refreshing-protos.md +78 -0
  128. package/docs/release-gates.md +110 -0
  129. package/docs/release-summary.json +86 -0
  130. package/docs/route-contract-manifest.json +288 -0
  131. package/docs/route-policy.json +133 -0
  132. package/docs/service-manifest.json +9490 -0
  133. package/docs/test-manifest.json +155 -0
  134. package/docs/testing-harness.md +204 -0
  135. package/docs/troubleshooting.md +36 -0
  136. package/docs/type-manifest-summary.json +28927 -0
  137. package/package.json +93 -0
  138. package/proto/agent/v1/agent.proto +5371 -0
  139. package/proto/aiserver/v1/aiserver.proto +32944 -0
  140. package/proto/anyrun/v1/anyrun.proto +294 -0
  141. package/proto/google/protobuf/google.proto +37 -0
  142. package/proto/internapi/v1/internapi.proto +32 -0
@@ -0,0 +1,216 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { encodeEnvelope, encodeEndStream } from "../connectEnvelope.js";
3
+ import { AgentV1_ApiKeyCredentialsSchema, AgentV1_GetDefaultModelForCliResponseSchema, AgentV1_GetUsableModelsResponseSchema, AgentV1_ModelDetailsSchema, AgentV1_RequestedModel_ModelParameterValueSchema, AvailableModelsResponse_DegradationStatus, AvailableModelsResponse_AvailableModelSchema, AvailableModelsResponse_ModelVariantConfigSchema, AvailableModelsResponse_TooltipDataSchema, AvailableModelsResponse_ModelVendorId, AvailableModelsResponse_ModelVendorSchema, AvailableModelsResponseSchema, GetDefaultModelResponseSchema, ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, ModelParameterDefinition_EnumParameterDefinitionSchema, ModelParameterDefinition_ModelParameterTypeSchema, ModelParameterDefinitionSchema, } from "../gen/aiserver/v1/aiserver_pb.js";
4
+ export function mergeAvailableModels(upstreamPayload, models) {
5
+ const upstream = upstreamPayload === undefined
6
+ ? create(AvailableModelsResponseSchema)
7
+ : fromBinary(AvailableModelsResponseSchema, upstreamPayload);
8
+ const modelNames = new Set(upstream.modelNames);
9
+ const upstreamModels = [...upstream.models];
10
+ for (const model of models.list()) {
11
+ if (modelNames.has(model.id)) {
12
+ continue;
13
+ }
14
+ modelNames.add(model.id);
15
+ upstreamModels.push(create(AvailableModelsResponse_AvailableModelSchema, {
16
+ name: model.id,
17
+ defaultOn: false,
18
+ isChatOnly: false,
19
+ supportsAgent: true,
20
+ degradationStatus: AvailableModelsResponse_DegradationStatus.AVAILABLE_MODELS_RESPONSE_DEGRADATION_STATUS_DEGRADATION_STATUS_UNSPECIFIED,
21
+ supportsThinking: false,
22
+ supportsImages: false,
23
+ supportsAutoContext: false,
24
+ supportsMaxMode: true,
25
+ supportsNonMaxMode: true,
26
+ supportsPlanMode: true,
27
+ supportsSandboxing: false,
28
+ supportsCmdK: false,
29
+ cloudAgentEffortModes: [],
30
+ clientDisplayName: model.displayName,
31
+ serverModelName: model.id,
32
+ inputboxShortModelName: model.displayName,
33
+ contextTokenLimit: model.contextTokenLimit,
34
+ isUserAdded: true,
35
+ parameterDefinitions: localParameterDefinitions(),
36
+ legacySlugs: [model.id],
37
+ idAliases: uniqueStrings([model.id, model.displayName]),
38
+ namedModelSectionIndex: 1,
39
+ visibleInRoutedModelView: true,
40
+ vendorName: "local",
41
+ vendor: create(AvailableModelsResponse_ModelVendorSchema, {
42
+ id: AvailableModelsResponse_ModelVendorId.AVAILABLE_MODELS_RESPONSE_MODEL_VENDOR_ID_MODEL_VENDOR_ID_UNSPECIFIED,
43
+ displayName: "Local",
44
+ }),
45
+ variants: [
46
+ localVariantConfig(model, false),
47
+ localVariantConfig(model, true),
48
+ ],
49
+ }));
50
+ }
51
+ return Buffer.from(toBinary(AvailableModelsResponseSchema, {
52
+ ...upstream,
53
+ modelNames: Array.from(modelNames),
54
+ models: upstreamModels,
55
+ }));
56
+ }
57
+ export function mergeUsableModels(upstreamPayload, models) {
58
+ const upstream = upstreamPayload === undefined
59
+ ? create(AgentV1_GetUsableModelsResponseSchema)
60
+ : fromBinary(AgentV1_GetUsableModelsResponseSchema, upstreamPayload);
61
+ const modelIds = new Set(upstream.models.map((model) => model.modelId));
62
+ const mergedModels = [...upstream.models];
63
+ for (const model of models.list()) {
64
+ if (modelIds.has(model.id)) {
65
+ continue;
66
+ }
67
+ modelIds.add(model.id);
68
+ mergedModels.push(localCliModelDetails(model));
69
+ }
70
+ return Buffer.from(toBinary(AgentV1_GetUsableModelsResponseSchema, {
71
+ ...upstream,
72
+ models: mergedModels,
73
+ }));
74
+ }
75
+ export function mergeDefaultModelForCli(upstreamPayload, models) {
76
+ if (upstreamPayload !== undefined) {
77
+ const upstream = fromBinary(AgentV1_GetDefaultModelForCliResponseSchema, upstreamPayload);
78
+ if (upstream.model !== undefined) {
79
+ return Buffer.from(toBinary(AgentV1_GetDefaultModelForCliResponseSchema, upstream));
80
+ }
81
+ }
82
+ const firstLocalModel = models.list()[0];
83
+ const model = firstLocalModel === undefined
84
+ ? undefined
85
+ : localCliModelDetails(firstLocalModel);
86
+ return Buffer.from(toBinary(AgentV1_GetDefaultModelForCliResponseSchema, create(AgentV1_GetDefaultModelForCliResponseSchema, {
87
+ model,
88
+ })));
89
+ }
90
+ export function mergeDefaultModel(upstreamPayload, models) {
91
+ const upstream = upstreamPayload === undefined
92
+ ? create(GetDefaultModelResponseSchema)
93
+ : fromBinary(GetDefaultModelResponseSchema, upstreamPayload);
94
+ const firstLocalModel = models.list()[0];
95
+ if (firstLocalModel === undefined) {
96
+ return Buffer.from(toBinary(GetDefaultModelResponseSchema, upstream));
97
+ }
98
+ return Buffer.from(toBinary(GetDefaultModelResponseSchema, create(GetDefaultModelResponseSchema, {
99
+ ...upstream,
100
+ model: firstLocalModel.id,
101
+ thinkingModel: firstLocalModel.id,
102
+ maxMode: true,
103
+ })));
104
+ }
105
+ export function writeAvailableModelsResponse(response, payload, logger, format) {
106
+ writeModelResponse(response, payload, logger, "served available models", format);
107
+ }
108
+ export function writeModelResponse(response, payload, logger, message, format) {
109
+ response.statusCode = 200;
110
+ if (format === "connect") {
111
+ response.setHeader("content-type", "application/connect+proto");
112
+ response.write(encodeEnvelope(payload));
113
+ response.end(encodeEndStream());
114
+ }
115
+ else {
116
+ response.setHeader("content-type", "application/proto");
117
+ response.end(payload);
118
+ }
119
+ logger.info(message, { bytes: payload.length });
120
+ }
121
+ function localCliModelDetails(model) {
122
+ return create(AgentV1_ModelDetailsSchema, {
123
+ modelId: model.id,
124
+ displayModelId: model.id,
125
+ displayName: model.displayName,
126
+ displayNameShort: model.displayName,
127
+ aliases: uniqueStrings([model.id, model.displayName]),
128
+ maxMode: false,
129
+ apiKeyCredentials: create(AgentV1_ApiKeyCredentialsSchema, {
130
+ apiKey: model.apiKey,
131
+ baseUrl: model.baseUrl,
132
+ }),
133
+ });
134
+ }
135
+ function localVariantConfig(model, isMaxMode) {
136
+ return create(AvailableModelsResponse_ModelVariantConfigSchema, {
137
+ displayName: model.displayName,
138
+ displayNameOutsidePicker: model.displayName,
139
+ isMaxMode,
140
+ isDefaultMaxConfig: isMaxMode,
141
+ isDefaultNonMaxConfig: !isMaxMode,
142
+ parameterValues: localParameterValues(isMaxMode),
143
+ tooltipData: create(AvailableModelsResponse_TooltipDataSchema, {
144
+ markdownContent: `**${model.displayName}**<br />Local OpenAI-compatible model.`,
145
+ }),
146
+ variantStringRepresentation: localVariantString(model.id, isMaxMode),
147
+ legacySlug: model.id,
148
+ });
149
+ }
150
+ function localVariantString(modelId, isMaxMode) {
151
+ const context = isMaxMode ? "1m" : "272k";
152
+ return `${modelId}[context=${context},reasoning=medium,fast=false]`;
153
+ }
154
+ function localParameterDefinitions() {
155
+ return [
156
+ create(ModelParameterDefinitionSchema, {
157
+ id: "context",
158
+ name: "Context",
159
+ markdownTooltip: "Context size the model has available.",
160
+ parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
161
+ enumParameter: create(ModelParameterDefinition_EnumParameterDefinitionSchema, {
162
+ values: [
163
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "272k", displayName: "272K" }),
164
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "1m", displayName: "1M" }),
165
+ ],
166
+ }),
167
+ }),
168
+ }),
169
+ create(ModelParameterDefinitionSchema, {
170
+ id: "reasoning",
171
+ name: "Reasoning",
172
+ markdownTooltip: "Reasoning effort the model uses to generate its response.",
173
+ parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
174
+ enumParameter: create(ModelParameterDefinition_EnumParameterDefinitionSchema, {
175
+ values: [
176
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "none", displayName: "None" }),
177
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "low", displayName: "Low" }),
178
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "medium", displayName: "Medium" }),
179
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "high", displayName: "High" }),
180
+ create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "extra-high", displayName: "Extra High" }),
181
+ ],
182
+ }),
183
+ }),
184
+ isCycleableByHotkey: true,
185
+ }),
186
+ create(ModelParameterDefinitionSchema, {
187
+ id: "fast",
188
+ name: "Fast",
189
+ markdownTooltip: "Use the provider's fast lane when supported.",
190
+ parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
191
+ booleanParameter: {
192
+ values: [{ value: "false" }, { value: "true", displayName: "Fast" }],
193
+ },
194
+ }),
195
+ }),
196
+ ];
197
+ }
198
+ function localParameterValues(isMaxMode) {
199
+ return [
200
+ create(AgentV1_RequestedModel_ModelParameterValueSchema, {
201
+ id: "context",
202
+ value: isMaxMode ? "1m" : "272k",
203
+ }),
204
+ create(AgentV1_RequestedModel_ModelParameterValueSchema, {
205
+ id: "reasoning",
206
+ value: "medium",
207
+ }),
208
+ create(AgentV1_RequestedModel_ModelParameterValueSchema, {
209
+ id: "fast",
210
+ value: "false",
211
+ }),
212
+ ];
213
+ }
214
+ function uniqueStrings(values) {
215
+ return Array.from(new Set(values.filter((value) => value.length > 0)));
216
+ }
@@ -0,0 +1,2 @@
1
+ export declare function rewriteServerConfigAgentUrls(upstreamPayload: Buffer, bridgeOrigin: string): Buffer;
2
+ export declare function buildLocalServerConfig(bridgeOrigin: string): Buffer;
@@ -0,0 +1,19 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { AgentUrlConfigSchema, GetServerConfigResponseSchema, Http2Config, } from "../gen/aiserver/v1/aiserver_pb.js";
3
+ export function rewriteServerConfigAgentUrls(upstreamPayload, bridgeOrigin) {
4
+ const upstream = fromBinary(GetServerConfigResponseSchema, upstreamPayload);
5
+ return encodeServerConfig(upstream, bridgeOrigin);
6
+ }
7
+ export function buildLocalServerConfig(bridgeOrigin) {
8
+ return encodeServerConfig(create(GetServerConfigResponseSchema), bridgeOrigin);
9
+ }
10
+ function encodeServerConfig(upstream, bridgeOrigin) {
11
+ return Buffer.from(toBinary(GetServerConfigResponseSchema, {
12
+ ...upstream,
13
+ http2Config: Http2Config.HTTP2_CONFIG_FORCE_ALL_DISABLED,
14
+ agentUrlConfig: create(AgentUrlConfigSchema, {
15
+ agentUrl: bridgeOrigin,
16
+ agentnUrl: bridgeOrigin,
17
+ }),
18
+ }));
19
+ }
@@ -0,0 +1,14 @@
1
+ import type { ArtifactWriter, ScenarioResult } from "./types.js";
2
+ export declare class FileArtifactStore implements ArtifactWriter {
3
+ readonly rootDir: string;
4
+ constructor(rootDir: string);
5
+ pathFor(name: string): string;
6
+ writeText(name: string, contents: string): string;
7
+ writeJson(name: string, contents: unknown): string;
8
+ }
9
+ export declare function createRunDirectory(options: {
10
+ cwd: string;
11
+ artifactsDir?: string;
12
+ now?: Date;
13
+ }): string;
14
+ export declare function writeRunSummary(artifacts: ArtifactWriter, results: ScenarioResult[]): Record<string, string>;
@@ -0,0 +1,92 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export class FileArtifactStore {
4
+ rootDir;
5
+ constructor(rootDir) {
6
+ this.rootDir = rootDir;
7
+ fs.mkdirSync(rootDir, { recursive: true });
8
+ }
9
+ pathFor(name) {
10
+ return path.join(this.rootDir, sanitizeArtifactName(name));
11
+ }
12
+ writeText(name, contents) {
13
+ const artifactPath = this.pathFor(name);
14
+ fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
15
+ fs.writeFileSync(artifactPath, contents);
16
+ return artifactPath;
17
+ }
18
+ writeJson(name, contents) {
19
+ return this.writeText(name, `${JSON.stringify(contents, null, 2)}\n`);
20
+ }
21
+ }
22
+ export function createRunDirectory(options) {
23
+ if (options.artifactsDir !== undefined) {
24
+ return path.resolve(options.cwd, options.artifactsDir);
25
+ }
26
+ const timestamp = (options.now ?? new Date())
27
+ .toISOString()
28
+ .replace(/[:.]/g, "-");
29
+ return path.join(options.cwd, ".cursor-rpc", "test-runs", timestamp);
30
+ }
31
+ export function writeRunSummary(artifacts, results) {
32
+ return {
33
+ json: artifacts.writeJson("summary.json", {
34
+ status: results.some((result) => result.status === "failed")
35
+ ? "failed"
36
+ : "passed",
37
+ results,
38
+ }),
39
+ markdown: artifacts.writeText("summary.md", renderMarkdownSummary(results)),
40
+ junit: artifacts.writeText("junit.xml", renderJunit(results)),
41
+ };
42
+ }
43
+ function renderMarkdownSummary(results) {
44
+ const lines = ["# Harness Summary", ""];
45
+ for (const result of results) {
46
+ lines.push(`- ${result.status.toUpperCase()} ${result.id}: ${result.message}`);
47
+ }
48
+ lines.push("");
49
+ return lines.join("\n");
50
+ }
51
+ function renderJunit(results) {
52
+ const failures = results.filter((result) => result.status === "failed");
53
+ const skipped = results.filter((result) => result.status === "skipped");
54
+ const testcases = results
55
+ .map((result) => {
56
+ const seconds = (result.durationMs / 1000).toFixed(3);
57
+ if (result.status === "failed") {
58
+ return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}"><failure message="${escapeXml(result.message)}">${escapeXml(result.failureCode ?? "command_failed")}</failure></testcase>`;
59
+ }
60
+ if (result.status === "skipped") {
61
+ return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}"><skipped message="${escapeXml(result.message)}" /></testcase>`;
62
+ }
63
+ return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}" />`;
64
+ })
65
+ .join("\n");
66
+ return [
67
+ `<?xml version="1.0" encoding="UTF-8"?>`,
68
+ `<testsuite name="cursor-rpc-harness" tests="${results.length}" failures="${failures.length}" skipped="${skipped.length}">`,
69
+ testcases,
70
+ `</testsuite>`,
71
+ "",
72
+ ].join("\n");
73
+ }
74
+ function sanitizeArtifactName(name) {
75
+ return name
76
+ .split("/")
77
+ .map((part) => {
78
+ if (part === "." || part === ".." || part.length === 0) {
79
+ return "_";
80
+ }
81
+ return part.replace(/[^A-Za-z0-9._-]/g, "_");
82
+ })
83
+ .join("/");
84
+ }
85
+ function escapeXml(value) {
86
+ return value
87
+ .replace(/&/g, "&amp;")
88
+ .replace(/</g, "&lt;")
89
+ .replace(/>/g, "&gt;")
90
+ .replace(/"/g, "&quot;")
91
+ .replace(/'/g, "&apos;");
92
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import type { HarnessOptions } from "./types.js";
3
+ export declare function parseHarnessArgs(argv: string[], env?: NodeJS.ProcessEnv, cwd?: string): HarnessOptions;
4
+ export declare function runHarnessCli(argv?: string[]): Promise<number>;
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { FileArtifactStore, createRunDirectory, writeRunSummary, } from "./artifacts.js";
4
+ import { ChildProcessRunner } from "./processRunner.js";
5
+ import { ScenarioRunner } from "./runner.js";
6
+ import { createScenarios } from "./scenarios.js";
7
+ const HELP = `cursorkit test harness
8
+
9
+ Usage:
10
+ pnpm test:harness -- --suite static
11
+ pnpm test:harness -- --suite traffic
12
+ pnpm test:harness -- --suite acp
13
+ pnpm test:harness -- --suite local-backend --base-url http://127.0.0.1:8080/v1 --model mlx-community/Qwen3.5-4B-8bit
14
+ pnpm test:harness -- --suite desktop --use-default-profile --timeout-ms 30000
15
+
16
+ Options:
17
+ --suite <name> Suite to run; repeat or comma-separate. Values: all, static, bridge-protocol, local-backend, cursor-agent, cursor-agent-traffic, traffic, acp, cli, desktop, desktop-route
18
+ --base-url <url> OpenAI-compatible base URL
19
+ --model <id> Model ID for local backend/desktop scenarios
20
+ --provider-model <id> OpenAI-compatible backend model ID when different from --model
21
+ --display-name <name> Cursor-facing display name
22
+ --api-key <key> API key for local backend requests
23
+ --timeout-ms <ms> Per-scenario timeout
24
+ --use-default-profile Desktop: reuse signed-in Cursor profile
25
+ --include-experimental Include ACP/UI experimental suites with all
26
+ --artifacts-dir <path> Artifact output directory
27
+ `;
28
+ export function parseHarnessArgs(argv, env = process.env, cwd = process.cwd()) {
29
+ const suites = [];
30
+ let baseUrl = env.MODEL_BASE_URL ?? "http://127.0.0.1:8080/v1";
31
+ let model = env.MODEL_NAME ?? "mlx-community/Qwen3.5-4B-8bit";
32
+ let providerModel = env.MODEL_PROVIDER_MODEL ?? model;
33
+ let providerModelExplicit = env.MODEL_PROVIDER_MODEL !== undefined;
34
+ let displayName = env.MODEL_DISPLAY_NAME ?? "local-qwen";
35
+ let apiKey = env.MODEL_API_KEY ?? "local";
36
+ let timeoutMs = 30_000;
37
+ let useDefaultProfile = false;
38
+ let includeExperimental = false;
39
+ let artifactsDir;
40
+ const args = argv.slice(2);
41
+ for (let index = 0; index < args.length; index += 1) {
42
+ const arg = args[index];
43
+ switch (arg) {
44
+ case "--":
45
+ break;
46
+ case "--suite": {
47
+ const value = requiredValue(args, index, arg);
48
+ suites.push(...parseSuites(value));
49
+ index += 1;
50
+ break;
51
+ }
52
+ case "--base-url":
53
+ baseUrl = requiredValue(args, index, arg);
54
+ index += 1;
55
+ break;
56
+ case "--model":
57
+ model = requiredValue(args, index, arg);
58
+ if (!providerModelExplicit) {
59
+ providerModel = model;
60
+ }
61
+ index += 1;
62
+ break;
63
+ case "--provider-model":
64
+ providerModel = requiredValue(args, index, arg);
65
+ providerModelExplicit = true;
66
+ index += 1;
67
+ break;
68
+ case "--display-name":
69
+ displayName = requiredValue(args, index, arg);
70
+ index += 1;
71
+ break;
72
+ case "--api-key":
73
+ apiKey = requiredValue(args, index, arg);
74
+ index += 1;
75
+ break;
76
+ case "--timeout-ms":
77
+ timeoutMs = parsePositiveInteger(requiredValue(args, index, arg), arg);
78
+ index += 1;
79
+ break;
80
+ case "--use-default-profile":
81
+ useDefaultProfile = true;
82
+ break;
83
+ case "--include-experimental":
84
+ includeExperimental = true;
85
+ break;
86
+ case "--artifacts-dir":
87
+ artifactsDir = requiredValue(args, index, arg);
88
+ index += 1;
89
+ break;
90
+ case "--help":
91
+ case "-h":
92
+ throw new HelpRequested();
93
+ default:
94
+ throw new Error(`Unknown harness argument: ${arg}\n\n${HELP}`);
95
+ }
96
+ }
97
+ return {
98
+ suites: suites.length > 0 ? suites : ["all"],
99
+ cwd,
100
+ artifactsDir,
101
+ baseUrl,
102
+ model,
103
+ providerModel,
104
+ displayName,
105
+ apiKey,
106
+ timeoutMs,
107
+ useDefaultProfile,
108
+ includeExperimental,
109
+ env,
110
+ };
111
+ }
112
+ export async function runHarnessCli(argv = process.argv) {
113
+ let options;
114
+ try {
115
+ options = parseHarnessArgs(argv);
116
+ }
117
+ catch (error) {
118
+ if (error instanceof HelpRequested) {
119
+ console.log(HELP);
120
+ return 0;
121
+ }
122
+ throw error;
123
+ }
124
+ const runDir = createRunDirectory({
125
+ cwd: options.cwd,
126
+ artifactsDir: options.artifactsDir,
127
+ });
128
+ const artifacts = new FileArtifactStore(runDir);
129
+ const processRunner = new ChildProcessRunner(options.cwd, artifacts);
130
+ const runner = new ScenarioRunner(createScenarios(), artifacts, processRunner);
131
+ const results = await runner.run(options);
132
+ const summaryPaths = writeRunSummary(artifacts, results);
133
+ printSummary(results, summaryPaths.json);
134
+ return results.some((result) => result.status === "failed") ? 1 : 0;
135
+ }
136
+ class HelpRequested extends Error {
137
+ }
138
+ function parseSuites(value) {
139
+ return value
140
+ .split(",")
141
+ .map((suite) => suite.trim())
142
+ .filter((suite) => suite.length > 0)
143
+ .map((suite) => {
144
+ if (isHarnessSuiteInput(suite)) {
145
+ return suite;
146
+ }
147
+ throw new Error(`Unknown suite: ${suite}`);
148
+ });
149
+ }
150
+ function isHarnessSuiteInput(value) {
151
+ return [
152
+ "all",
153
+ "acp",
154
+ "cli",
155
+ "desktop",
156
+ "static",
157
+ "bridge-protocol",
158
+ "local-backend",
159
+ "cursor-agent",
160
+ "cursor-agent-traffic",
161
+ "cursor-agent-acp-experimental",
162
+ "desktop-route",
163
+ "desktop-ui-experimental",
164
+ "traffic",
165
+ ].includes(value);
166
+ }
167
+ function requiredValue(args, index, name) {
168
+ const value = args[index + 1];
169
+ if (value === undefined || value.startsWith("--")) {
170
+ throw new Error(`${name} requires a value`);
171
+ }
172
+ return value;
173
+ }
174
+ function parsePositiveInteger(value, name) {
175
+ const parsed = Number(value);
176
+ if (!Number.isInteger(parsed) || parsed <= 0) {
177
+ throw new Error(`${name} must be a positive integer`);
178
+ }
179
+ return parsed;
180
+ }
181
+ function printSummary(results, summaryPath) {
182
+ console.log("Harness Summary");
183
+ for (const result of results) {
184
+ console.log(`${result.status.toUpperCase()} ${result.id}: ${result.message}`);
185
+ }
186
+ console.log(`Artifacts: ${path.dirname(summaryPath)}`);
187
+ }
188
+ if (import.meta.url === `file://${process.argv[1]}`) {
189
+ runHarnessCli().then((exitCode) => {
190
+ process.exitCode = exitCode;
191
+ });
192
+ }
@@ -0,0 +1,24 @@
1
+ import type { FailureCode } from "./types.js";
2
+ export interface LocalBackendProbeOptions {
3
+ baseUrl: string;
4
+ model: string;
5
+ apiKey: string;
6
+ timeoutMs: number;
7
+ checkToolCalls?: boolean;
8
+ }
9
+ export interface LocalBackendProbeReport {
10
+ ok: boolean;
11
+ failureCode?: FailureCode;
12
+ message: string;
13
+ modelsStatus?: number;
14
+ chatStatus?: number;
15
+ streamingChatStatus?: number;
16
+ toolChatStatus?: number;
17
+ models: string[];
18
+ selectedModelFound?: boolean;
19
+ nonStreamingSupported?: boolean;
20
+ streamingSupported?: boolean;
21
+ toolCallsSupported?: boolean;
22
+ completionPreview?: string;
23
+ }
24
+ export declare function probeLocalBackend(options: LocalBackendProbeOptions): Promise<LocalBackendProbeReport>;