@velum-labs/cursorkit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -17
- package/dist/src/ckLauncher.d.ts +16 -6
- package/dist/src/ckLauncher.js +334 -478
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.js +25 -253
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +129 -0
- package/dist/src/commands/maintenance.d.ts +2 -0
- package/dist/src/commands/maintenance.js +94 -0
- package/dist/src/commands/render.d.ts +14 -0
- package/dist/src/commands/render.js +17 -0
- package/dist/src/commands/serve.d.ts +2 -0
- package/dist/src/commands/serve.js +52 -0
- package/dist/src/cursorDesktopState.d.ts +10 -0
- package/dist/src/cursorDesktopState.js +204 -0
- package/dist/src/desktopConnectProxy.d.ts +10 -0
- package/dist/src/desktopConnectProxy.js +7 -1
- package/dist/src/server.js +35 -2
- package/dist/src/tools/releaseCheck.d.ts +1 -1
- package/dist/src/tools/releaseCheck.js +1 -6
- package/dist/src/ui/index.d.ts +8 -0
- package/dist/src/ui/index.js +6 -0
- package/dist/src/ui/prompt.d.ts +30 -0
- package/dist/src/ui/prompt.js +182 -0
- package/dist/src/ui/runtime.d.ts +14 -0
- package/dist/src/ui/runtime.js +35 -0
- package/dist/src/ui/spinner.d.ts +31 -0
- package/dist/src/ui/spinner.js +102 -0
- package/dist/src/ui/steps.d.ts +38 -0
- package/dist/src/ui/steps.js +154 -0
- package/dist/src/ui/theme.d.ts +35 -0
- package/dist/src/ui/theme.js +63 -0
- package/package.json +5 -3
- package/dist/src/ck.d.ts +0 -2
- package/dist/src/ck.js +0 -6
package/dist/src/cli.d.ts
CHANGED
package/dist/src/cli.js
CHANGED
|
@@ -1,265 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { assertCursorRunRequestV1, assertCursorRunResultV1, assertHarnessRunRequestV1, assertHarnessRunResultV1, } from "./fixtures/modelFusion.js";
|
|
9
|
-
import { listProtoFiles, loadCursorProto, resolveProtoDirectory, } from "./proto.js";
|
|
10
|
-
import { createBridgeRuntime, startServer } from "./server.js";
|
|
11
|
-
const HELP = `cursorkit
|
|
12
|
-
|
|
13
|
-
Usage:
|
|
14
|
-
cursorkit serve Start the local bridge
|
|
15
|
-
cursorkit doctor Check local configuration and proto availability
|
|
16
|
-
cursorkit desktop-cert Generate local TLS material for Cursor desktop proxying
|
|
17
|
-
cursorkit desktop-proxy Start the bridge with Cursor desktop proxy defaults
|
|
18
|
-
cursorkit desktop-doctor Check Cursor desktop proxy prerequisites
|
|
19
|
-
cursorkit capture Print capture-mode guidance
|
|
20
|
-
cursorkit fixtures Validate committed fixture metadata
|
|
21
|
-
cursorkit --help Show this help
|
|
22
|
-
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerCk } from "./ckLauncher.js";
|
|
4
|
+
import { registerDoctor } from "./commands/doctor.js";
|
|
5
|
+
import { registerMaintenance } from "./commands/maintenance.js";
|
|
6
|
+
import { registerServe } from "./commands/serve.js";
|
|
7
|
+
const ENV_HELP = `
|
|
23
8
|
Environment:
|
|
24
9
|
BRIDGE_HOST=127.0.0.1
|
|
25
10
|
BRIDGE_PORT=9443
|
|
26
11
|
CURSOR_UPSTREAM_BASE_URL=https://example.cursor-backend.local
|
|
27
12
|
MODEL_BASE_URL=http://localhost:8080/v1
|
|
28
|
-
MODEL_NAME=local-model
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
MODEL_NAME=local-model`;
|
|
14
|
+
export function buildCursorkitProgram() {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name("cursorkit")
|
|
18
|
+
.description("local model bridge for Cursor")
|
|
19
|
+
.addHelpText("after", ENV_HELP);
|
|
20
|
+
registerServe(program);
|
|
21
|
+
registerDoctor(program);
|
|
22
|
+
registerMaintenance(program);
|
|
23
|
+
registerCk(program);
|
|
24
|
+
return program;
|
|
25
|
+
}
|
|
26
|
+
async function main() {
|
|
27
|
+
const program = buildCursorkitProgram();
|
|
28
|
+
if (process.argv.slice(2).length === 0) {
|
|
29
|
+
program.outputHelp();
|
|
34
30
|
return;
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
? desktopEnv(process.env)
|
|
38
|
-
: process.env);
|
|
39
|
-
const logger = createLogger(config.logLevel);
|
|
40
|
-
switch (command) {
|
|
41
|
-
case "serve": {
|
|
42
|
-
const runtime = await createBridgeRuntime(config, logger);
|
|
43
|
-
installGracefulShutdown(await startServer(runtime), logger);
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
case "doctor": {
|
|
47
|
-
await doctor(config);
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case "capture": {
|
|
51
|
-
capture(config);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
case "fixtures": {
|
|
55
|
-
validateFixtures(config.captureDir);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
case "desktop-cert": {
|
|
59
|
-
await desktopCert();
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
case "desktop-proxy": {
|
|
63
|
-
const runtime = await createBridgeRuntime(config, logger);
|
|
64
|
-
installGracefulShutdown(await startServer(runtime), logger);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
case "desktop-doctor": {
|
|
68
|
-
await doctor(config);
|
|
69
|
-
await desktopDoctor(config);
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
default: {
|
|
73
|
-
const exhaustive = command;
|
|
74
|
-
throw new Error(`Unhandled command: ${exhaustive}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function parseCommand(argv) {
|
|
79
|
-
const first = argv[2];
|
|
80
|
-
if (first === undefined ||
|
|
81
|
-
first === "--help" ||
|
|
82
|
-
first === "-h" ||
|
|
83
|
-
first === "help") {
|
|
84
|
-
return "help";
|
|
85
|
-
}
|
|
86
|
-
if (first === "serve" ||
|
|
87
|
-
first === "doctor" ||
|
|
88
|
-
first === "capture" ||
|
|
89
|
-
first === "fixtures" ||
|
|
90
|
-
first === "desktop-cert" ||
|
|
91
|
-
first === "desktop-proxy" ||
|
|
92
|
-
first === "desktop-doctor") {
|
|
93
|
-
return first;
|
|
94
|
-
}
|
|
95
|
-
throw new Error(`Unknown command: ${first}\n\n${HELP}`);
|
|
96
|
-
}
|
|
97
|
-
async function doctor(config) {
|
|
98
|
-
const checks = [];
|
|
99
|
-
const protoDir = resolveProtoDirectory();
|
|
100
|
-
checks.push(["proto directory", protoDir]);
|
|
101
|
-
checks.push(["proto files", String(listProtoFiles(protoDir).length)]);
|
|
102
|
-
const proto = await loadCursorProto();
|
|
103
|
-
checks.push([
|
|
104
|
-
"available models type",
|
|
105
|
-
proto.AvailableModelsResponse.fullName,
|
|
106
|
-
]);
|
|
107
|
-
checks.push(["chat type", proto.StreamUnifiedChatRequestWithTools.fullName]);
|
|
108
|
-
checks.push(["bind", `${config.host}:${config.port}`]);
|
|
109
|
-
checks.push(["tls", config.useTls ? "enabled" : "disabled"]);
|
|
110
|
-
checks.push(["upstream", config.upstreamBaseUrl ?? "not configured"]);
|
|
111
|
-
checks.push([
|
|
112
|
-
"local models",
|
|
113
|
-
config.models.map((model) => model.id).join(", "),
|
|
114
|
-
]);
|
|
115
|
-
checks.push([
|
|
116
|
-
"capture",
|
|
117
|
-
config.captureEnabled ? `enabled -> ${config.captureDir}` : "disabled",
|
|
118
|
-
]);
|
|
119
|
-
checks.push(["fail-open", String(config.failOpen)]);
|
|
120
|
-
checks.push([
|
|
121
|
-
"auth",
|
|
122
|
-
config.authToken === undefined ? "disabled" : "enabled",
|
|
123
|
-
]);
|
|
124
|
-
checks.push([
|
|
125
|
-
"non-localhost unsafe mode",
|
|
126
|
-
String(config.unsafeAllowNonLocalhost),
|
|
127
|
-
]);
|
|
128
|
-
if (config.useTls) {
|
|
129
|
-
const tls = await loadTlsMaterial(config);
|
|
130
|
-
checks.push([
|
|
131
|
-
"tls material",
|
|
132
|
-
tls.generated
|
|
133
|
-
? "generated self-signed dev certificate"
|
|
134
|
-
: "custom certificate",
|
|
135
|
-
]);
|
|
136
|
-
}
|
|
137
|
-
for (const [name, value] of checks) {
|
|
138
|
-
console.log(`${name}: ${value}`);
|
|
139
|
-
}
|
|
140
|
-
if (config.upstreamBaseUrl === undefined) {
|
|
141
|
-
console.warn("warning: pass-through traffic needs CURSOR_UPSTREAM_BASE_URL");
|
|
142
|
-
}
|
|
143
|
-
if (config.captureEnabled) {
|
|
144
|
-
console.warn("warning: capture mode treats traffic as sensitive; sanitize before committing fixtures");
|
|
145
|
-
}
|
|
146
|
-
if (config.modelPayloadLogging === "full") {
|
|
147
|
-
console.warn("warning: BRIDGE_LOG_MODEL_PAYLOADS=full may log prompt and tool payloads; use only for local debugging");
|
|
148
|
-
}
|
|
149
|
-
if (config.unsafeAllowNonLocalhost) {
|
|
150
|
-
console.warn("warning: non-localhost unsafe mode exposes the bridge without built-in auth");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
function installGracefulShutdown(server, logger) {
|
|
154
|
-
let shuttingDown = false;
|
|
155
|
-
const shutdown = (signal) => {
|
|
156
|
-
if (shuttingDown) {
|
|
157
|
-
logger.warn("forcing bridge shutdown", { signal });
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
shuttingDown = true;
|
|
161
|
-
logger.info("shutting down bridge", { signal });
|
|
162
|
-
const forceTimer = setTimeout(() => {
|
|
163
|
-
logger.error("bridge shutdown timed out", { signal });
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}, 5_000);
|
|
166
|
-
server.close((error) => {
|
|
167
|
-
clearTimeout(forceTimer);
|
|
168
|
-
if (error !== undefined) {
|
|
169
|
-
logger.error("bridge shutdown failed", { error: error.message });
|
|
170
|
-
process.exitCode = 1;
|
|
171
|
-
}
|
|
172
|
-
process.exit();
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
process.once("SIGINT", shutdown);
|
|
176
|
-
process.once("SIGTERM", shutdown);
|
|
177
|
-
}
|
|
178
|
-
async function desktopCert() {
|
|
179
|
-
const cert = await writeDesktopCertificate();
|
|
180
|
-
console.log(`cert: ${cert.certPath}`);
|
|
181
|
-
console.log(`key: ${cert.keyPath}`);
|
|
182
|
-
console.log("");
|
|
183
|
-
console.log("Manual macOS trust command:");
|
|
184
|
-
console.log(desktopTrustCommand(cert.certPath).join(" "));
|
|
185
|
-
console.log("");
|
|
186
|
-
console.log("Then start with:");
|
|
187
|
-
console.log(`BRIDGE_CERT_PATH=${cert.certPath} BRIDGE_KEY_PATH=${cert.keyPath} cursorkit desktop-proxy`);
|
|
188
|
-
}
|
|
189
|
-
async function desktopDoctor(config) {
|
|
190
|
-
const checks = [];
|
|
191
|
-
checks.push(["desktop mode", String(config.desktopMode)]);
|
|
192
|
-
checks.push(["public origin", config.publicOrigin ?? "not configured"]);
|
|
193
|
-
checks.push(["tls hostnames", config.tlsHostnames.join(", ")]);
|
|
194
|
-
checks.push(["route inventory", String(config.routeInventoryEnabled)]);
|
|
195
|
-
checks.push(["desktop upstream", config.upstreamBaseUrl ?? "not configured"]);
|
|
196
|
-
checks.push([
|
|
197
|
-
"desktop upstream connect",
|
|
198
|
-
config.upstreamConnectHost === undefined
|
|
199
|
-
? "system DNS"
|
|
200
|
-
: `${config.upstreamConnectHost}${config.upstreamConnectPort === undefined ? "" : `:${config.upstreamConnectPort}`}`,
|
|
201
|
-
]);
|
|
202
|
-
checks.push(["desktop cert", desktopCertificateStatus(config)]);
|
|
203
|
-
checks.push(["desktop dns", await desktopDnsStatus(config)]);
|
|
204
|
-
checks.push([
|
|
205
|
-
"upstream reachability",
|
|
206
|
-
await upstreamReachabilityStatus(config),
|
|
207
|
-
]);
|
|
208
|
-
checks.push(["local model backend", await localModelBackendStatus(config)]);
|
|
209
|
-
for (const [name, value] of checks) {
|
|
210
|
-
console.log(`${name}: ${value}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function capture(config) {
|
|
214
|
-
console.log(`captureEnabled: ${config.captureEnabled}`);
|
|
215
|
-
console.log(`captureDir: ${config.captureDir}`);
|
|
216
|
-
console.log("Capture mode is explicit and sanitized fixture writing is required before committing data.");
|
|
217
|
-
}
|
|
218
|
-
function validateFixtures(captureDir) {
|
|
219
|
-
const modelFusionCount = validateModelFusionFixtures();
|
|
220
|
-
if (!fs.existsSync(captureDir)) {
|
|
221
|
-
console.log(`No fixture capture directory found at ${captureDir}`);
|
|
222
|
-
console.log(`Validated ${modelFusionCount} model-fusion fixture file(s)`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
const files = fs
|
|
226
|
-
.readdirSync(captureDir)
|
|
227
|
-
.filter((file) => file.endsWith(".json"));
|
|
228
|
-
for (const file of files) {
|
|
229
|
-
const fullPath = path.join(captureDir, file);
|
|
230
|
-
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
231
|
-
if (parsed.redaction?.status !== "sanitized") {
|
|
232
|
-
throw new Error(`${fullPath} is missing redaction.status=sanitized`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
console.log(`Validated ${files.length} capture fixture file(s) and ${modelFusionCount} model-fusion fixture file(s)`);
|
|
236
|
-
}
|
|
237
|
-
function validateModelFusionFixtures() {
|
|
238
|
-
const root = path.resolve("fixtures", "model-fusion-contract");
|
|
239
|
-
if (!fs.existsSync(root))
|
|
240
|
-
return 0;
|
|
241
|
-
let count = 0;
|
|
242
|
-
const validators = {
|
|
243
|
-
"harness-run-request.v1": assertHarnessRunRequestV1,
|
|
244
|
-
"harness-run-result.v1": assertHarnessRunResultV1,
|
|
245
|
-
"cursor-run-request.v1": assertCursorRunRequestV1,
|
|
246
|
-
"cursor-run-result.v1": assertCursorRunResultV1,
|
|
247
|
-
};
|
|
248
|
-
for (const [schema, validate] of Object.entries(validators)) {
|
|
249
|
-
const schemaDir = path.join(root, schema);
|
|
250
|
-
if (!fs.existsSync(schemaDir))
|
|
251
|
-
continue;
|
|
252
|
-
for (const file of fs
|
|
253
|
-
.readdirSync(schemaDir)
|
|
254
|
-
.filter((item) => item.endsWith(".json"))) {
|
|
255
|
-
const fixturePath = path.join(schemaDir, file);
|
|
256
|
-
validate(JSON.parse(fs.readFileSync(fixturePath, "utf8")));
|
|
257
|
-
count++;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return count;
|
|
32
|
+
await program.parseAsync(process.argv);
|
|
261
33
|
}
|
|
262
|
-
main(
|
|
34
|
+
main().catch((error) => {
|
|
263
35
|
console.error(error instanceof Error ? error.message : String(error));
|
|
264
36
|
process.exitCode = 1;
|
|
265
37
|
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { loadConfig } from "../config.js";
|
|
2
|
+
import { loadTlsMaterial } from "../certs.js";
|
|
3
|
+
import { desktopCertificateStatus, desktopDnsStatus, desktopEnv, localModelBackendStatus, upstreamReachabilityStatus, } from "../desktop.js";
|
|
4
|
+
import { listProtoFiles, loadCursorProto, resolveProtoDirectory, } from "../proto.js";
|
|
5
|
+
import { bold, brandHeader, green, uiStream, withSpinner, } from "../ui/index.js";
|
|
6
|
+
import { renderCheck, warningLine } from "./render.js";
|
|
7
|
+
function write(line) {
|
|
8
|
+
uiStream().write(`${line}\n`);
|
|
9
|
+
}
|
|
10
|
+
async function runDoctor(config) {
|
|
11
|
+
write(`\n${brandHeader("environment check")}\n`);
|
|
12
|
+
const proto = await withSpinner("loading Cursor proto", () => loadCursorProto());
|
|
13
|
+
const protoDir = resolveProtoDirectory();
|
|
14
|
+
const checks = [
|
|
15
|
+
{ label: "proto directory", detail: protoDir },
|
|
16
|
+
{ label: "proto files", detail: String(listProtoFiles(protoDir).length) },
|
|
17
|
+
{
|
|
18
|
+
label: "available models type",
|
|
19
|
+
detail: proto.AvailableModelsResponse.fullName,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: "chat type",
|
|
23
|
+
detail: proto.StreamUnifiedChatRequestWithTools.fullName,
|
|
24
|
+
},
|
|
25
|
+
{ label: "bind", detail: `${config.host}:${config.port}` },
|
|
26
|
+
{ label: "tls", detail: config.useTls ? "enabled" : "disabled" },
|
|
27
|
+
{
|
|
28
|
+
label: "upstream",
|
|
29
|
+
ok: config.upstreamBaseUrl !== undefined,
|
|
30
|
+
detail: config.upstreamBaseUrl ?? "not configured",
|
|
31
|
+
hint: "pass-through traffic needs CURSOR_UPSTREAM_BASE_URL",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "local models",
|
|
35
|
+
detail: config.models.map((model) => model.id).join(", "),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "capture",
|
|
39
|
+
detail: config.captureEnabled
|
|
40
|
+
? `enabled -> ${config.captureDir}`
|
|
41
|
+
: "disabled",
|
|
42
|
+
},
|
|
43
|
+
{ label: "fail-open", detail: String(config.failOpen) },
|
|
44
|
+
{
|
|
45
|
+
label: "auth",
|
|
46
|
+
detail: config.authToken === undefined ? "disabled" : "enabled",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "non-localhost unsafe mode",
|
|
50
|
+
detail: String(config.unsafeAllowNonLocalhost),
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
if (config.useTls) {
|
|
54
|
+
const tls = await loadTlsMaterial(config);
|
|
55
|
+
checks.push({
|
|
56
|
+
label: "tls material",
|
|
57
|
+
detail: tls.generated
|
|
58
|
+
? "generated self-signed dev certificate"
|
|
59
|
+
: "custom certificate",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
write(bold("configuration"));
|
|
63
|
+
for (const check of checks)
|
|
64
|
+
write(` ${renderCheck(check)}`);
|
|
65
|
+
const warnings = [];
|
|
66
|
+
if (config.captureEnabled) {
|
|
67
|
+
warnings.push("capture mode treats traffic as sensitive; sanitize before committing fixtures");
|
|
68
|
+
}
|
|
69
|
+
if (config.modelPayloadLogging === "full") {
|
|
70
|
+
warnings.push("BRIDGE_LOG_MODEL_PAYLOADS=full may log prompt and tool payloads; use only for local debugging");
|
|
71
|
+
}
|
|
72
|
+
if (config.unsafeAllowNonLocalhost) {
|
|
73
|
+
warnings.push("non-localhost unsafe mode exposes the bridge without built-in auth");
|
|
74
|
+
}
|
|
75
|
+
if (warnings.length > 0) {
|
|
76
|
+
write("");
|
|
77
|
+
for (const warning of warnings)
|
|
78
|
+
write(` ${warningLine(warning)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function runDesktopDoctor(config) {
|
|
82
|
+
write(`\n${bold("desktop proxy")}\n`);
|
|
83
|
+
const checks = [
|
|
84
|
+
{ label: "desktop mode", detail: String(config.desktopMode) },
|
|
85
|
+
{ label: "public origin", detail: config.publicOrigin ?? "not configured" },
|
|
86
|
+
{ label: "tls hostnames", detail: config.tlsHostnames.join(", ") },
|
|
87
|
+
{ label: "route inventory", detail: String(config.routeInventoryEnabled) },
|
|
88
|
+
{
|
|
89
|
+
label: "desktop upstream",
|
|
90
|
+
detail: config.upstreamBaseUrl ?? "not configured",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: "desktop upstream connect",
|
|
94
|
+
detail: config.upstreamConnectHost === undefined
|
|
95
|
+
? "system DNS"
|
|
96
|
+
: `${config.upstreamConnectHost}${config.upstreamConnectPort === undefined ? "" : `:${config.upstreamConnectPort}`}`,
|
|
97
|
+
},
|
|
98
|
+
{ label: "desktop cert", detail: desktopCertificateStatus(config) },
|
|
99
|
+
{ label: "desktop dns", detail: await desktopDnsStatus(config) },
|
|
100
|
+
{
|
|
101
|
+
label: "upstream reachability",
|
|
102
|
+
detail: await upstreamReachabilityStatus(config),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "local model backend",
|
|
106
|
+
detail: await localModelBackendStatus(config),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
for (const check of checks)
|
|
110
|
+
write(` ${renderCheck(check)}`);
|
|
111
|
+
write("");
|
|
112
|
+
write(green("desktop checks complete."));
|
|
113
|
+
}
|
|
114
|
+
export function registerDoctor(program) {
|
|
115
|
+
program
|
|
116
|
+
.command("doctor")
|
|
117
|
+
.description("check local configuration and proto availability")
|
|
118
|
+
.action(async () => {
|
|
119
|
+
await runDoctor(loadConfig(process.env));
|
|
120
|
+
});
|
|
121
|
+
program
|
|
122
|
+
.command("desktop-doctor")
|
|
123
|
+
.description("check Cursor desktop proxy prerequisites")
|
|
124
|
+
.action(async () => {
|
|
125
|
+
const config = loadConfig(desktopEnv(process.env));
|
|
126
|
+
await runDoctor(config);
|
|
127
|
+
await runDesktopDoctor(config);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadConfig } from "../config.js";
|
|
4
|
+
import { desktopTrustCommand, writeDesktopCertificate } from "../desktop.js";
|
|
5
|
+
import { assertCursorRunRequestV1, assertCursorRunResultV1, assertHarnessRunRequestV1, assertHarnessRunResultV1, } from "../fixtures/modelFusion.js";
|
|
6
|
+
import { bold, brandHeader, dim, done, note, uiStream, withSpinner, } from "../ui/index.js";
|
|
7
|
+
function write(line) {
|
|
8
|
+
uiStream().write(`${line}\n`);
|
|
9
|
+
}
|
|
10
|
+
function capture(captureDir, captureEnabled) {
|
|
11
|
+
write(`\n${brandHeader("capture")}\n`);
|
|
12
|
+
write(`${dim("captureEnabled:")} ${captureEnabled}`);
|
|
13
|
+
write(`${dim("captureDir:")} ${captureDir}`);
|
|
14
|
+
note("Capture mode is explicit; sanitized fixture writing is required before committing data.");
|
|
15
|
+
}
|
|
16
|
+
function validateModelFusionFixtures() {
|
|
17
|
+
const root = path.resolve("fixtures", "model-fusion-contract");
|
|
18
|
+
if (!fs.existsSync(root))
|
|
19
|
+
return 0;
|
|
20
|
+
let count = 0;
|
|
21
|
+
const validators = {
|
|
22
|
+
"harness-run-request.v1": assertHarnessRunRequestV1,
|
|
23
|
+
"harness-run-result.v1": assertHarnessRunResultV1,
|
|
24
|
+
"cursor-run-request.v1": assertCursorRunRequestV1,
|
|
25
|
+
"cursor-run-result.v1": assertCursorRunResultV1,
|
|
26
|
+
};
|
|
27
|
+
for (const [schema, validate] of Object.entries(validators)) {
|
|
28
|
+
const schemaDir = path.join(root, schema);
|
|
29
|
+
if (!fs.existsSync(schemaDir))
|
|
30
|
+
continue;
|
|
31
|
+
for (const file of fs
|
|
32
|
+
.readdirSync(schemaDir)
|
|
33
|
+
.filter((item) => item.endsWith(".json"))) {
|
|
34
|
+
const fixturePath = path.join(schemaDir, file);
|
|
35
|
+
validate(JSON.parse(fs.readFileSync(fixturePath, "utf8")));
|
|
36
|
+
count++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return count;
|
|
40
|
+
}
|
|
41
|
+
function validateFixtures(captureDir) {
|
|
42
|
+
write(`\n${brandHeader("fixtures")}\n`);
|
|
43
|
+
const modelFusionCount = validateModelFusionFixtures();
|
|
44
|
+
if (!fs.existsSync(captureDir)) {
|
|
45
|
+
note(`No fixture capture directory found at ${captureDir}`);
|
|
46
|
+
done(`Validated ${modelFusionCount} model-fusion fixture file(s)`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const files = fs
|
|
50
|
+
.readdirSync(captureDir)
|
|
51
|
+
.filter((file) => file.endsWith(".json"));
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const fullPath = path.join(captureDir, file);
|
|
54
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
55
|
+
if (parsed.redaction?.status !== "sanitized") {
|
|
56
|
+
throw new Error(`${fullPath} is missing redaction.status=sanitized`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
done(`Validated ${files.length} capture fixture file(s) and ${modelFusionCount} model-fusion fixture file(s)`);
|
|
60
|
+
}
|
|
61
|
+
async function desktopCert() {
|
|
62
|
+
write(`\n${brandHeader("desktop certificate")}\n`);
|
|
63
|
+
const cert = await withSpinner("generating self-signed certificate", () => writeDesktopCertificate());
|
|
64
|
+
write(`${dim("cert:")} ${cert.certPath}`);
|
|
65
|
+
write(`${dim("key:")} ${cert.keyPath}`);
|
|
66
|
+
write("");
|
|
67
|
+
write(bold("Manual macOS trust command:"));
|
|
68
|
+
write(desktopTrustCommand(cert.certPath).join(" "));
|
|
69
|
+
write("");
|
|
70
|
+
write(bold("Then start with:"));
|
|
71
|
+
write(`BRIDGE_CERT_PATH=${cert.certPath} BRIDGE_KEY_PATH=${cert.keyPath} cursorkit desktop-proxy`);
|
|
72
|
+
}
|
|
73
|
+
export function registerMaintenance(program) {
|
|
74
|
+
program
|
|
75
|
+
.command("capture")
|
|
76
|
+
.description("print capture-mode guidance")
|
|
77
|
+
.action(() => {
|
|
78
|
+
const config = loadConfig(process.env);
|
|
79
|
+
capture(config.captureDir, config.captureEnabled);
|
|
80
|
+
});
|
|
81
|
+
program
|
|
82
|
+
.command("fixtures")
|
|
83
|
+
.description("validate committed fixture metadata")
|
|
84
|
+
.action(() => {
|
|
85
|
+
const config = loadConfig(process.env);
|
|
86
|
+
validateFixtures(config.captureDir);
|
|
87
|
+
});
|
|
88
|
+
program
|
|
89
|
+
.command("desktop-cert")
|
|
90
|
+
.description("generate local TLS material for Cursor desktop proxying")
|
|
91
|
+
.action(async () => {
|
|
92
|
+
await desktopCert();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single diagnostic line. `ok === undefined` renders as a neutral bullet
|
|
3
|
+
* (informational), while `true`/`false` render as a tick/cross. A `hint` is
|
|
4
|
+
* shown on the next line only for failed checks.
|
|
5
|
+
*/
|
|
6
|
+
export type Check = {
|
|
7
|
+
label: string;
|
|
8
|
+
ok?: boolean;
|
|
9
|
+
detail?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function renderCheck(check: Check): string;
|
|
13
|
+
/** A yellow advisory line (non-fatal warnings). */
|
|
14
|
+
export declare function warningLine(message: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dim, glyph, gray, green, red, yellow } from "../ui/index.js";
|
|
2
|
+
export function renderCheck(check) {
|
|
3
|
+
const mark = check.ok === undefined
|
|
4
|
+
? gray(glyph.bullet())
|
|
5
|
+
: check.ok
|
|
6
|
+
? green(glyph.tick())
|
|
7
|
+
: red(glyph.cross());
|
|
8
|
+
const detail = check.detail !== undefined ? ` ${dim(check.detail)}` : "";
|
|
9
|
+
const hint = check.ok === false && check.hint !== undefined
|
|
10
|
+
? `\n ${yellow(glyph.arrow())} ${check.hint}`
|
|
11
|
+
: "";
|
|
12
|
+
return `${mark} ${check.label}${detail}${hint}`;
|
|
13
|
+
}
|
|
14
|
+
/** A yellow advisory line (non-fatal warnings). */
|
|
15
|
+
export function warningLine(message) {
|
|
16
|
+
return `${yellow(glyph.warn())} ${message}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { loadConfig } from "../config.js";
|
|
2
|
+
import { desktopEnv } from "../desktop.js";
|
|
3
|
+
import { createLogger } from "../logger.js";
|
|
4
|
+
import { createBridgeRuntime, startServer } from "../server.js";
|
|
5
|
+
import { brandHeader, bold, dim, glyph, green, uiStream } from "../ui/index.js";
|
|
6
|
+
function installGracefulShutdown(server, logger) {
|
|
7
|
+
let shuttingDown = false;
|
|
8
|
+
const shutdown = (signal) => {
|
|
9
|
+
if (shuttingDown) {
|
|
10
|
+
logger.warn("forcing bridge shutdown", { signal });
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
shuttingDown = true;
|
|
14
|
+
logger.info("shutting down bridge", { signal });
|
|
15
|
+
const forceTimer = setTimeout(() => {
|
|
16
|
+
logger.error("bridge shutdown timed out", { signal });
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}, 5_000);
|
|
19
|
+
server.close((error) => {
|
|
20
|
+
clearTimeout(forceTimer);
|
|
21
|
+
if (error !== undefined) {
|
|
22
|
+
logger.error("bridge shutdown failed", { error: error.message });
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
}
|
|
25
|
+
process.exit();
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
process.once("SIGINT", shutdown);
|
|
29
|
+
process.once("SIGTERM", shutdown);
|
|
30
|
+
}
|
|
31
|
+
async function startBridge(env, mode) {
|
|
32
|
+
const config = loadConfig(env);
|
|
33
|
+
const logger = createLogger(config.logLevel);
|
|
34
|
+
uiStream().write(`\n${brandHeader(mode === "desktop" ? "desktop proxy" : "bridge")}\n` +
|
|
35
|
+
`${dim("bind:")} ${bold(`${config.host}:${config.port}`)} ${dim("tls:")} ${config.useTls ? green(glyph.tick()) : "off"}\n\n`);
|
|
36
|
+
const runtime = await createBridgeRuntime(config, logger);
|
|
37
|
+
installGracefulShutdown(await startServer(runtime), logger);
|
|
38
|
+
}
|
|
39
|
+
export function registerServe(program) {
|
|
40
|
+
program
|
|
41
|
+
.command("serve")
|
|
42
|
+
.description("start the local bridge")
|
|
43
|
+
.action(async () => {
|
|
44
|
+
await startBridge(process.env, "default");
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command("desktop-proxy")
|
|
48
|
+
.description("start the bridge with Cursor desktop proxy defaults")
|
|
49
|
+
.action(async () => {
|
|
50
|
+
await startBridge(desktopEnv(process.env), "desktop");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LocalModelConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Builders that shape the Cursor desktop `applicationUser` state and local
|
|
4
|
+
* model catalog entries. Extracted from `ckLauncher` so the (large) launcher
|
|
5
|
+
* module is not also responsible for the desktop state-seed schema. These are
|
|
6
|
+
* pure data transforms over plain JSON-shaped records.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergeLocalAgentBackendUrlsIntoApplicationUser(applicationUser: Record<string, unknown>, agentOrigin: string): void;
|
|
9
|
+
export declare function mergeLocalDesktopModelsIntoApplicationUser(applicationUser: Record<string, unknown>, models: LocalModelConfig[]): void;
|
|
10
|
+
export declare function buildLocalDesktopModelEntry(model: LocalModelConfig): Record<string, unknown>;
|