@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.
@@ -4,10 +4,16 @@ import fs from "node:fs";
4
4
  import net from "node:net";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
+ import { Command, InvalidArgumentError } from "commander";
7
8
  import { loadConfig } from "./config.js";
9
+ import { buildLocalDesktopModelEntry, mergeLocalAgentBackendUrlsIntoApplicationUser, mergeLocalDesktopModelsIntoApplicationUser, } from "./cursorDesktopState.js";
10
+ // Re-exported for backwards compatibility (callers/tests import these from
11
+ // ckLauncher); the implementations now live in cursorDesktopState.
12
+ export { buildLocalDesktopModelEntry, mergeLocalAgentBackendUrlsIntoApplicationUser, mergeLocalDesktopModelsIntoApplicationUser, };
8
13
  import { startDesktopConnectProxy, } from "./desktopConnectProxy.js";
9
14
  import { DESKTOP_CERT_PATH, DESKTOP_HOSTNAME, DESKTOP_HOSTNAMES, DESKTOP_KEY_PATH, desktopCertificateStatus, desktopDnsStatus, desktopEnv, desktopTrustCommand, localModelBackendStatus, upstreamReachabilityStatus, writeDesktopCertificate, } from "./desktop.js";
10
15
  import { AGENT_RUN_PATH, AGENT_RUN_SSE_PATH, AVAILABLE_MODELS_PATH, BIDI_APPEND_PATH, GET_DEFAULT_MODEL_FOR_CLI_PATH, GET_USABLE_MODELS_PATH, STREAM_CHAT_WITH_TOOLS_PATH, } from "./routes.js";
16
+ import { StepList, bold, brandHeader, cyan, dim, glyph, gray, green, note, red, uiStream, withSpinner, yellow, } from "./ui/index.js";
11
17
  export const CK_STATE_DIR = path.join(".cursor-rpc", "ck");
12
18
  export const CK_STATE_PATH = path.join(CK_STATE_DIR, "state.json");
13
19
  const DEFAULT_CURSOR_USER_DATA_DIR = path.join(process.env.HOME ?? "", "Library", "Application Support", "Cursor");
@@ -24,158 +30,143 @@ const LOCAL_AGENT_CANDIDATE_PATHS = [
24
30
  BIDI_APPEND_PATH,
25
31
  STREAM_CHAT_WITH_TOOLS_PATH,
26
32
  ];
27
- const CK_HELP = `ck
28
-
29
- Usage:
30
- ck Start desktop proxy and isolated Cursor
31
- ck test Launch, monitor route inventory, print diagnosis, then stop bridge
32
- ck --use-default-profile Reuse your logged-in Cursor profile for auth-sensitive testing
33
- ck --timeout-ms 30000 Set launch/test route-inventory wait time
34
- ck --debug-port 9333 Launch Cursor with a Chromium remote debugging port
35
- ck --instance-id name Use a fresh isolated ck state/profile subdirectory
36
- ck --seed-auth-from-default Copy Cursor auth rows from your default profile
37
- ck --no-seed-auth-from-default Start isolated Cursor without copying auth rows
38
- ck --print Print commands without launching
39
- ck doctor Check desktop launch readiness
40
- ck cert Generate desktop proxy certificate
41
- ck route Print manual desktop routing setup and rollback commands
42
- ck route status Check desktop routing prerequisites and current DNS state
43
- ck route rollback Print rollback commands only
44
- ck route --method direct Print direct :443 routing commands instead of pf redirect
45
- ck stop Stop ck-owned bridge process
46
- ck --help Show this help
47
- `;
48
- export function parseCkArgs(argv) {
49
- const args = argv.slice(2);
50
- let dryRun = false;
51
- let profileMode = "isolated";
52
- let routeMethod = "pf";
53
- let routeAction = "plan";
54
- let debugPort;
55
- let instanceId;
56
- let seedAuthFromDefault;
57
- let timeoutMs = DEFAULT_ROUTE_INVENTORY_TIMEOUT_MS;
58
- const commandArgs = [];
59
- for (let index = 0; index < args.length; index += 1) {
60
- const arg = args[index];
61
- if (arg === "--print") {
62
- dryRun = true;
63
- }
64
- else if (arg === "--use-default-profile") {
65
- profileMode = "default";
66
- }
67
- else if (arg === "--profile") {
68
- const next = args[index + 1];
69
- if (next !== "isolated" && next !== "default") {
70
- throw new Error("--profile must be isolated or default");
71
- }
72
- profileMode = next;
73
- index += 1;
74
- }
75
- else if (arg === "--timeout-ms") {
76
- const next = args[index + 1];
77
- const parsed = Number(next);
78
- if (!Number.isInteger(parsed) || parsed <= 0) {
79
- throw new Error("--timeout-ms must be a positive integer");
80
- }
81
- timeoutMs = parsed;
82
- index += 1;
83
- }
84
- else if (arg === "--method") {
85
- const next = args[index + 1];
86
- if (next !== "pf" && next !== "direct") {
87
- throw new Error("--method must be pf or direct");
88
- }
89
- routeMethod = next;
90
- index += 1;
91
- }
92
- else if (arg === "--debug-port") {
93
- const next = args[index + 1];
94
- const parsed = Number(next);
95
- if (!Number.isInteger(parsed) || parsed <= 0) {
96
- throw new Error("--debug-port must be a positive integer");
97
- }
98
- debugPort = parsed;
99
- index += 1;
100
- }
101
- else if (arg === "--instance-id") {
102
- const next = args[index + 1];
103
- if (next === undefined || !/^[A-Za-z0-9._-]+$/.test(next)) {
104
- throw new Error("--instance-id must use only letters, numbers, dot, underscore, or dash");
105
- }
106
- instanceId = next;
107
- index += 1;
108
- }
109
- else if (arg === "--seed-auth-from-default") {
110
- seedAuthFromDefault = true;
111
- }
112
- else if (arg === "--no-seed-auth-from-default") {
113
- seedAuthFromDefault = false;
114
- }
115
- else if (arg !== undefined) {
116
- commandArgs.push(arg);
33
+ const CK_ENV_HELP = `
34
+ cursorkit ck launches an isolated Cursor against a local desktop-proxy bridge.
35
+ Bridge logs stream live during \`ck\` and are written to .cursor-rpc/ck/bridge.log.`;
36
+ function parsePositiveInt(flag) {
37
+ return (value) => {
38
+ const parsed = Number(value);
39
+ if (!Number.isInteger(parsed) || parsed <= 0) {
40
+ throw new InvalidArgumentError(`${flag} must be a positive integer`);
117
41
  }
42
+ return parsed;
43
+ };
44
+ }
45
+ function parseProfile(value) {
46
+ if (value !== "isolated" && value !== "default") {
47
+ throw new InvalidArgumentError("--profile must be isolated or default");
118
48
  }
119
- const first = commandArgs[0];
120
- const second = commandArgs[1];
121
- if (first === "route") {
122
- if (second === undefined) {
123
- routeAction = "plan";
124
- }
125
- else if (second === "status" || second === "rollback") {
126
- routeAction = second;
127
- }
128
- else {
129
- throw new Error("ck route subcommand must be status or rollback");
130
- }
49
+ return value;
50
+ }
51
+ function parseMethod(value) {
52
+ if (value !== "pf" && value !== "direct") {
53
+ throw new InvalidArgumentError("--method must be pf or direct");
131
54
  }
132
- if (first === undefined) {
133
- return compactCkArgs({
134
- command: "launch",
135
- dryRun,
136
- profileMode,
137
- timeoutMs,
138
- routeMethod,
139
- routeAction,
140
- debugPort,
141
- instanceId,
142
- seedAuthFromDefault,
143
- });
55
+ return value;
56
+ }
57
+ function parseInstanceId(value) {
58
+ if (!/^[A-Za-z0-9._-]+$/.test(value)) {
59
+ throw new InvalidArgumentError("--instance-id must use only letters, numbers, dot, underscore, or dash");
144
60
  }
145
- switch (first) {
146
- case "test":
147
- case "doctor":
148
- case "cert":
149
- case "route":
150
- case "stop":
151
- return compactCkArgs({
152
- command: first,
153
- dryRun,
154
- profileMode,
155
- timeoutMs,
156
- routeMethod,
157
- routeAction,
158
- debugPort,
159
- instanceId,
160
- seedAuthFromDefault,
161
- });
162
- case "help":
163
- case "--help":
164
- case "-h":
165
- return compactCkArgs({
166
- command: "help",
167
- dryRun,
168
- profileMode,
169
- timeoutMs,
170
- routeMethod,
171
- routeAction,
172
- debugPort,
173
- instanceId,
174
- seedAuthFromDefault,
175
- });
176
- default:
177
- throw new Error(`Unknown ck command: ${first}\n\n${CK_HELP}`);
61
+ return value;
62
+ }
63
+ function normalizeRouteAction(action) {
64
+ if (action === undefined)
65
+ return "plan";
66
+ if (action === "status" || action === "rollback")
67
+ return action;
68
+ throw new Error("ck route subcommand must be status or rollback");
69
+ }
70
+ /**
71
+ * Shared launch/test options. Defaults are applied later in {@link ckArgsFromOpts}
72
+ * (not via commander) so the same flag works before or after a subcommand name:
73
+ * an unset option stays absent and the parent value wins via `optsWithGlobals`.
74
+ */
75
+ function applyLaunchOptions(cmd) {
76
+ return cmd
77
+ .option("--print", "print commands without launching")
78
+ .option("--use-default-profile", "reuse your logged-in Cursor profile for auth-sensitive testing")
79
+ .option("--profile <mode>", "isolated | default", parseProfile)
80
+ .option("--timeout-ms <ms>", "launch/test route-inventory wait time", parsePositiveInt("--timeout-ms"))
81
+ .option("--debug-port <port>", "launch Cursor with a Chromium remote debugging port", parsePositiveInt("--debug-port"))
82
+ .option("--instance-id <name>", "use a fresh isolated ck state/profile subdirectory", parseInstanceId)
83
+ .option("--seed-auth-from-default", "copy Cursor auth rows from your default profile")
84
+ .option("--no-seed-auth-from-default", "start isolated Cursor without copying auth rows");
85
+ }
86
+ function ckArgsFromOpts(command, opts, routeAction) {
87
+ const profileMode = opts.profile ??
88
+ (opts.useDefaultProfile === true ? "default" : "isolated");
89
+ return compactCkArgs({
90
+ command,
91
+ dryRun: opts.print === true,
92
+ profileMode,
93
+ timeoutMs: opts.timeoutMs ??
94
+ DEFAULT_ROUTE_INVENTORY_TIMEOUT_MS,
95
+ routeMethod: opts.method ?? "pf",
96
+ routeAction,
97
+ debugPort: opts.debugPort,
98
+ instanceId: opts.instanceId,
99
+ seedAuthFromDefault: opts.seedAuthFromDefault,
100
+ });
101
+ }
102
+ /**
103
+ * Build the commander program for `ck`. `dispatch` receives the resolved
104
+ * {@link CkArgs}; the real binary runs the command while {@link parseCkArgs}
105
+ * captures the args for tests.
106
+ */
107
+ export function buildCkProgram(dispatch) {
108
+ const program = new Command();
109
+ program
110
+ .name("ck")
111
+ .description("desktop proxy launcher for Cursor")
112
+ .addHelpText("after", CK_ENV_HELP);
113
+ applyLaunchOptions(program).action(function () {
114
+ return dispatch(ckArgsFromOpts("launch", this.optsWithGlobals(), "plan"));
115
+ });
116
+ applyLaunchOptions(program
117
+ .command("test")
118
+ .description("launch, monitor route inventory, print diagnosis, then stop bridge")).action(function () {
119
+ return dispatch(ckArgsFromOpts("test", this.optsWithGlobals(), "plan"));
120
+ });
121
+ program
122
+ .command("doctor")
123
+ .description("check desktop launch readiness")
124
+ .action(function () {
125
+ return dispatch(ckArgsFromOpts("doctor", this.optsWithGlobals(), "plan"));
126
+ });
127
+ program
128
+ .command("cert")
129
+ .description("generate desktop proxy certificate")
130
+ .action(function () {
131
+ return dispatch(ckArgsFromOpts("cert", this.optsWithGlobals(), "plan"));
132
+ });
133
+ program
134
+ .command("route [action]")
135
+ .description("print manual desktop routing setup and rollback commands (action: status | rollback)")
136
+ .option("--method <method>", "pf | direct", parseMethod)
137
+ .action(function (action) {
138
+ return dispatch(ckArgsFromOpts("route", this.optsWithGlobals(), normalizeRouteAction(action)));
139
+ });
140
+ program
141
+ .command("stop")
142
+ .description("stop ck-owned bridge process")
143
+ .action(function () {
144
+ return dispatch(ckArgsFromOpts("stop", this.optsWithGlobals(), "plan"));
145
+ });
146
+ return program;
147
+ }
148
+ /**
149
+ * Pure argument parser built on the commander program: returns the resolved
150
+ * {@link CkArgs} (or throws on invalid input) without running the command.
151
+ */
152
+ export function parseCkArgs(argv) {
153
+ let captured;
154
+ const program = buildCkProgram((args) => {
155
+ captured = args;
156
+ });
157
+ // Throw (rather than exit) on invalid input, and stay silent; applied to
158
+ // every command since subcommands like `route` validate their own options.
159
+ const makeParseOnly = (cmd) => {
160
+ cmd.exitOverride();
161
+ cmd.configureOutput({ writeOut: () => { }, writeErr: () => { } });
162
+ cmd.commands.forEach(makeParseOnly);
163
+ };
164
+ makeParseOnly(program);
165
+ program.parse(argv);
166
+ if (captured === undefined) {
167
+ throw new Error("ck: no command parsed");
178
168
  }
169
+ return captured;
179
170
  }
180
171
  function compactCkArgs(args) {
181
172
  const { debugPort, instanceId, seedAuthFromDefault, ...rest } = args;
@@ -498,11 +489,14 @@ function desktopTestDiagnosis(report) {
498
489
  return diagnosis;
499
490
  }
500
491
  export async function runCk(argv = process.argv) {
501
- const parsed = parseCkArgs(argv);
502
- if (parsed.command === "help") {
503
- console.log(CK_HELP);
504
- return;
505
- }
492
+ const program = buildCkProgram(runCkCommand);
493
+ await program.parseAsync(argv);
494
+ }
495
+ /** Attach the `ck` desktop launcher as a subcommand group of another program. */
496
+ export function registerCk(program) {
497
+ program.addCommand(buildCkProgram(runCkCommand));
498
+ }
499
+ async function runCkCommand(parsed) {
506
500
  if (parsed.command === "cert") {
507
501
  await printCertInstructions();
508
502
  return;
@@ -536,9 +530,12 @@ export async function runCk(argv = process.argv) {
536
530
  printPlan(plan);
537
531
  return;
538
532
  }
539
- const cert = await writeDesktopCertificate();
533
+ const cert = await withSpinner("preparing desktop certificate", () => writeDesktopCertificate(), {
534
+ success: (result) => result.created
535
+ ? "generated desktop proxy certificate"
536
+ : "desktop certificate ready",
537
+ });
540
538
  if (cert.created) {
541
- console.warn("Generated desktop proxy certificate.");
542
539
  printTrustInstructions(cert.certPath);
543
540
  }
544
541
  if (parsed.command === "test") {
@@ -570,17 +567,25 @@ async function chooseFreePort() {
570
567
  server.on("error", reject);
571
568
  });
572
569
  }
570
+ /** Write a single human-facing line to the UI stream (stderr). */
571
+ function out(line = "") {
572
+ uiStream().write(`${line}\n`);
573
+ }
573
574
  async function launch(plan, timeoutMs) {
574
- const { bridge, log, connectProxy } = await startBridge(plan);
575
+ out(`\n${brandHeader("desktop launch")}\n`);
576
+ const { bridge, log, connectProxy } = await startBridge(plan, {
577
+ mirror: true,
578
+ });
575
579
  const routeSeen = waitForRouteInventory(bridge, timeoutMs);
576
- launchCursor(plan);
580
+ launchCursor(plan, {});
581
+ out(`${green(glyph.tick())} ${bold("ck ready")} ${dim(`https://127.0.0.1:${plan.bridgePort}`)} ${dim(`(log: ${plan.logPath})`)}`);
577
582
  const observed = await routeSeen;
578
583
  if (observed) {
579
- console.log("Desktop route inventory observed.");
584
+ out(`${green(glyph.tick())} desktop route inventory observed`);
580
585
  }
581
586
  else {
582
587
  for (const line of routeInventoryTimeoutDiagnosis()) {
583
- console.warn(line);
588
+ out(`${yellow(glyph.warn())} ${line}`);
584
589
  }
585
590
  }
586
591
  await new Promise((resolve) => {
@@ -592,18 +597,38 @@ async function launch(plan, timeoutMs) {
592
597
  });
593
598
  }
594
599
  async function testDesktopLaunch(plan, timeoutMs) {
595
- const { bridge, log, connectProxy } = await startBridge(plan);
600
+ out(`\n${brandHeader("desktop test")}\n`);
601
+ const steps = new StepList([
602
+ { id: "bridge", label: "start bridge" },
603
+ { id: "cursor", label: "launch Cursor" },
604
+ { id: "inventory", label: "monitor route inventory" },
605
+ ], { title: dim(`bridge log: ${plan.logPath}`) }).start();
606
+ steps.setActive("bridge");
607
+ const { bridge, log, connectProxy } = await startBridge(plan, {
608
+ mirror: false,
609
+ quiet: true,
610
+ });
611
+ steps.setDone("bridge", `127.0.0.1:${plan.bridgePort}`);
596
612
  try {
597
- launchCursor(plan);
598
- console.log(`Monitoring desktop route inventory for ${timeoutMs}ms`);
613
+ steps.setActive("cursor");
614
+ launchCursor(plan, { quiet: true });
615
+ steps.setDone("cursor", plan.profileMode === "isolated" ? "isolated profile" : "default profile");
616
+ steps.setActive("inventory", `up to ${timeoutMs}ms`);
599
617
  await delay(timeoutMs);
600
618
  const logText = fs.existsSync(plan.logPath)
601
619
  ? fs.readFileSync(plan.logPath, "utf8")
602
620
  : "";
603
621
  const report = analyzeRouteInventoryLog(logText);
622
+ steps.setDone("inventory", report.routeInventorySeen ? "observed" : "none seen");
623
+ steps.stop();
604
624
  writeLatestStatus(plan, report);
605
625
  printDesktopTestReport(plan, report);
606
626
  }
627
+ catch (error) {
628
+ steps.setFailed("inventory");
629
+ steps.stop();
630
+ throw error;
631
+ }
607
632
  finally {
608
633
  bridge.kill("SIGTERM");
609
634
  await connectProxy?.close();
@@ -613,55 +638,62 @@ async function testDesktopLaunch(plan, timeoutMs) {
613
638
  }
614
639
  }
615
640
  }
616
- async function startBridge(plan) {
641
+ async function startBridge(plan, options = {}) {
642
+ const { mirror = true, quiet = false } = options;
617
643
  fs.mkdirSync(plan.stateDir, { recursive: true });
618
644
  if (plan.userDataDir !== undefined) {
619
645
  fs.mkdirSync(plan.userDataDir, { recursive: true });
620
646
  }
621
647
  fs.mkdirSync(plan.extensionsDir, { recursive: true });
622
648
  plan.authSeedStatus = seedCursorAuthFromDefault(plan);
623
- if (plan.authSeedStatus === "seeded") {
624
- console.log("Seeded isolated Cursor profile with default auth rows");
625
- }
626
- else if (plan.seedAuthFromDefault &&
627
- plan.authSeedStatus !== "not-isolated") {
628
- console.warn(`Cursor auth seeding status: ${plan.authSeedStatus}`);
649
+ if (!quiet) {
650
+ if (plan.authSeedStatus === "seeded") {
651
+ out(`${green(glyph.tick())} seeded isolated Cursor profile with default auth rows`);
652
+ }
653
+ else if (plan.seedAuthFromDefault &&
654
+ plan.authSeedStatus !== "not-isolated") {
655
+ out(`${yellow(glyph.warn())} Cursor auth seeding status: ${plan.authSeedStatus}`);
656
+ }
629
657
  }
630
658
  plan.localModelSeedStatus = seedLocalModelsIntoCursorState(plan);
631
- if (plan.localModelSeedStatus === "seeded") {
632
- console.log("Seeded isolated Cursor profile with local model entries");
633
- }
634
- else if (plan.localModelSeedStatus !== "not-isolated") {
635
- console.warn(`Cursor local model seeding status: ${plan.localModelSeedStatus}`);
659
+ if (!quiet) {
660
+ if (plan.localModelSeedStatus === "seeded") {
661
+ out(`${green(glyph.tick())} seeded isolated Cursor profile with local model entries`);
662
+ }
663
+ else if (plan.localModelSeedStatus !== "not-isolated") {
664
+ out(`${yellow(glyph.warn())} Cursor local model seeding status: ${plan.localModelSeedStatus}`);
665
+ }
636
666
  }
637
667
  configureCursorNodeTlsEnv(plan);
638
668
  assertSafe(plan.bridge);
639
669
  assertSafe(plan.cursor);
640
- console.log(`Starting bridge on 127.0.0.1:${plan.bridgePort}`);
641
- console.log(`Writing bridge log to ${plan.logPath}`);
670
+ if (!quiet) {
671
+ out(`${cyan(glyph.arrow())} starting bridge on 127.0.0.1:${plan.bridgePort} ${dim(`(log: ${plan.logPath})`)}`);
672
+ }
642
673
  const log = fs.createWriteStream(plan.logPath, { flags: "w" });
643
674
  const bridge = spawn(plan.bridge.executable, plan.bridge.args, {
644
675
  env: { ...process.env, ...plan.bridge.env },
645
676
  stdio: ["ignore", "pipe", "pipe"],
646
677
  });
647
- attachBridgeOutput(bridge, log);
678
+ attachBridgeOutput(bridge, log, mirror);
648
679
  writeState(plan, bridge);
649
680
  await waitForBridgeListening(bridge);
650
- const connectProxy = await startConnectProxy(plan);
681
+ const connectProxy = await startConnectProxy(plan, quiet);
651
682
  return {
652
683
  bridge,
653
684
  log,
654
685
  ...(connectProxy === undefined ? {} : { connectProxy }),
655
686
  };
656
687
  }
657
- async function startConnectProxy(plan) {
688
+ async function startConnectProxy(plan, quiet = false) {
658
689
  if (plan.connectProxyPort === undefined ||
659
690
  plan.connectProxyLogPath === undefined) {
660
691
  return undefined;
661
692
  }
662
693
  fs.writeFileSync(plan.connectProxyLogPath, "");
663
- console.log(`Starting CONNECT proxy on 127.0.0.1:${plan.connectProxyPort}`);
664
- console.log(`Writing CONNECT proxy log to ${plan.connectProxyLogPath}`);
694
+ if (!quiet) {
695
+ out(`${cyan(glyph.arrow())} starting CONNECT proxy on 127.0.0.1:${plan.connectProxyPort} ${dim(`(log: ${plan.connectProxyLogPath})`)}`);
696
+ }
665
697
  return startDesktopConnectProxy({
666
698
  host: "127.0.0.1",
667
699
  port: plan.connectProxyPort,
@@ -739,101 +771,24 @@ export function seedLocalModelsIntoCursorState(plan) {
739
771
  if (typeof agentPublicOrigin === "string") {
740
772
  mergeLocalAgentBackendUrlsIntoApplicationUser(applicationUser, agentPublicOrigin);
741
773
  }
774
+ // The seed file mirrors the user's Cursor credentials (cursorCreds), so it is
775
+ // written with owner-only permissions and removed once sqlite3 has imported
776
+ // it rather than being left on disk.
742
777
  const seedPath = path.join(globalStorageDir, "applicationUser.seed.json");
743
- fs.writeFileSync(seedPath, JSON.stringify(applicationUser));
744
- const escapedSeedPath = seedPath.replaceAll("'", "''");
745
- const seed = spawnSync("sqlite3", [
746
- targetDb,
747
- `insert or replace into ItemTable(key, value) values('${key}', cast(readfile('${escapedSeedPath}') as text));`,
748
- ], { encoding: "utf8" });
749
- if (seed.status !== 0) {
750
- return "sqlite-unavailable";
751
- }
752
- return "seeded";
753
- }
754
- export function mergeLocalAgentBackendUrlsIntoApplicationUser(applicationUser, agentOrigin) {
755
- const cursorCreds = ensureRecord(applicationUser, "cursorCreds");
756
- const urls = { default: agentOrigin };
757
- cursorCreds.agentBackendUrlPrivacy = urls;
758
- cursorCreds.agentBackendUrlNonPrivacy = urls;
759
- }
760
- export function mergeLocalDesktopModelsIntoApplicationUser(applicationUser, models) {
761
- const localModelIds = new Set(models.map((model) => model.id));
762
- const current = Array.isArray(applicationUser.availableDefaultModels2)
763
- ? applicationUser.availableDefaultModels2.filter((item) => {
764
- if (!isPlainRecord(item) || typeof item.name !== "string") {
765
- return true;
766
- }
767
- return !localModelIds.has(item.name);
768
- })
769
- : [];
770
- applicationUser.availableDefaultModels2 = current;
771
- const aiSettings = ensureRecord(applicationUser, "aiSettings");
772
- for (const model of models) {
773
- appendUnique(ensureStringArray(aiSettings, "userAddedModels"), model.id);
774
- appendUnique(ensureStringArray(aiSettings, "modelOverrideEnabled"), model.id);
775
- removeValue(ensureStringArray(aiSettings, "modelOverrideDisabled"), model.id);
776
- }
777
- const preferences = ensureRecord(aiSettings, "modelParameterPreferences");
778
- const updatedAt = new Date().toISOString();
779
- for (const model of models) {
780
- preferences[model.id] = {
781
- modelId: model.id,
782
- parameters: localDesktopParameterValues(true),
783
- updatedAt,
784
- };
785
- }
786
- const firstModel = models[0];
787
- if (firstModel !== undefined) {
788
- applicationUser.useOpenAIKey = true;
789
- applicationUser.openAIBaseUrl = firstModel.baseUrl;
790
- applicationUser.openAIKey = firstModel.apiKey;
791
- const modelConfig = ensureRecord(aiSettings, "modelConfig");
792
- const selectedModel = {
793
- modelId: firstModel.id,
794
- parameters: localDesktopParameterValues(true),
795
- };
796
- for (const key of ["composer", "background-composer"]) {
797
- modelConfig[key] = {
798
- modelName: firstModel.id,
799
- maxMode: true,
800
- selectedModels: [selectedModel],
801
- };
802
- }
803
- }
804
- const featureModelConfigs = ensureRecord(applicationUser, "featureModelConfigs");
805
- for (const value of Object.values(featureModelConfigs)) {
806
- if (!isPlainRecord(value)) {
807
- continue;
808
- }
809
- const fallbackModels = ensureStringArray(value, "fallbackModels");
810
- for (const model of models) {
811
- appendUnique(fallbackModels, model.id);
778
+ fs.writeFileSync(seedPath, JSON.stringify(applicationUser), { mode: 0o600 });
779
+ try {
780
+ const escapedSeedPath = seedPath.replaceAll("'", "''");
781
+ const seed = spawnSync("sqlite3", [
782
+ targetDb,
783
+ `insert or replace into ItemTable(key, value) values('${key}', cast(readfile('${escapedSeedPath}') as text));`,
784
+ ], { encoding: "utf8" });
785
+ if (seed.status !== 0) {
786
+ return "sqlite-unavailable";
812
787
  }
788
+ return "seeded";
813
789
  }
814
- }
815
- function ensureRecord(target, key) {
816
- if (!isPlainRecord(target[key])) {
817
- target[key] = {};
818
- }
819
- return target[key];
820
- }
821
- function ensureStringArray(target, key) {
822
- const values = Array.isArray(target[key])
823
- ? target[key].filter((value) => typeof value === "string")
824
- : [];
825
- target[key] = values;
826
- return values;
827
- }
828
- function appendUnique(values, value) {
829
- if (!values.includes(value)) {
830
- values.push(value);
831
- }
832
- }
833
- function removeValue(values, value) {
834
- const index = values.indexOf(value);
835
- if (index !== -1) {
836
- values.splice(index, 1);
790
+ finally {
791
+ fs.rmSync(seedPath, { force: true });
837
792
  }
838
793
  }
839
794
  function defaultCursorStateDbPath() {
@@ -853,119 +808,6 @@ function readCursorStateValue(dbPath, key) {
853
808
  }
854
809
  return result.stdout;
855
810
  }
856
- export function buildLocalDesktopModelEntry(model) {
857
- const tooltipData = {
858
- primaryText: "",
859
- secondaryText: "",
860
- secondaryWarningText: false,
861
- icon: "",
862
- tertiaryText: "",
863
- tertiaryTextUrl: "",
864
- markdownContent: `**${model.displayName}**<br />Local OpenAI-compatible model served by cursorkit.<br /><br />${model.contextTokenLimit.toLocaleString()} token context window`,
865
- };
866
- return {
867
- name: model.id,
868
- serverModelName: model.id,
869
- clientDisplayName: model.displayName,
870
- inputboxShortModelName: model.displayName,
871
- vendorName: "local",
872
- vendor: { displayName: "Local" },
873
- supportsAgent: true,
874
- supportsCmdK: false,
875
- supportsImages: false,
876
- supportsMaxMode: true,
877
- supportsNonMaxMode: true,
878
- supportsPlanMode: true,
879
- supportsSandboxing: false,
880
- supportsThinking: false,
881
- cloudAgentEffortModes: [],
882
- defaultOn: true,
883
- degradationStatus: 0,
884
- isRecommendedForBackgroundComposer: false,
885
- legacySlugs: [model.id],
886
- idAliases: [model.id, model.displayName],
887
- parameterDefinitions: localDesktopParameterDefinitions(),
888
- namedModelSectionIndex: 10_000,
889
- visibleInRoutedModelView: true,
890
- tooltipData,
891
- tooltipDataForMaxMode: tooltipData,
892
- variants: [
893
- localDesktopVariantConfig(model, tooltipData, false),
894
- localDesktopVariantConfig(model, tooltipData, true),
895
- ],
896
- };
897
- }
898
- function localDesktopVariantConfig(model, tooltipData, isMaxMode) {
899
- return {
900
- parameterValues: localDesktopParameterValues(isMaxMode),
901
- displayName: model.displayName,
902
- isMaxMode,
903
- isDefaultMaxConfig: isMaxMode,
904
- isDefaultNonMaxConfig: !isMaxMode,
905
- tooltipData,
906
- displayNameOutsidePicker: model.displayName,
907
- variantStringRepresentation: localDesktopVariantString(model.id, isMaxMode),
908
- legacySlug: model.id,
909
- };
910
- }
911
- function localDesktopVariantString(modelId, isMaxMode) {
912
- const context = isMaxMode ? "1m" : "272k";
913
- return `${modelId}[context=${context},reasoning=medium,fast=false]`;
914
- }
915
- function localDesktopParameterValues(isMaxMode) {
916
- return [
917
- { id: "context", value: isMaxMode ? "1m" : "272k" },
918
- { id: "reasoning", value: "medium" },
919
- { id: "fast", value: "false" },
920
- ];
921
- }
922
- function localDesktopParameterDefinitions() {
923
- return [
924
- {
925
- id: "context",
926
- name: "Context",
927
- markdownTooltip: "Context size the model has available.",
928
- parameterType: {
929
- enumParameter: {
930
- values: [
931
- { value: "272k", displayName: "272K" },
932
- { value: "1m", displayName: "1M" },
933
- ],
934
- },
935
- },
936
- },
937
- {
938
- id: "reasoning",
939
- name: "Reasoning",
940
- markdownTooltip: "Reasoning effort the model uses to generate its response.",
941
- parameterType: {
942
- enumParameter: {
943
- values: [
944
- { value: "none", displayName: "None" },
945
- { value: "low", displayName: "Low" },
946
- { value: "medium", displayName: "Medium" },
947
- { value: "high", displayName: "High" },
948
- { value: "extra-high", displayName: "Extra High" },
949
- ],
950
- },
951
- },
952
- isCycleableByHotkey: true,
953
- },
954
- {
955
- id: "fast",
956
- name: "Fast",
957
- markdownTooltip: "Use the provider's fast lane when supported.",
958
- parameterType: {
959
- booleanParameter: {
960
- values: [{ value: "false" }, { value: "true", displayName: "Fast" }],
961
- },
962
- },
963
- },
964
- ];
965
- }
966
- function isPlainRecord(value) {
967
- return typeof value === "object" && value !== null && !Array.isArray(value);
968
- }
969
811
  export function cleanupIsolatedCursorProcesses(userDataDir) {
970
812
  const killed = terminateIsolatedCursorProcesses(userDataDir, "SIGTERM");
971
813
  spawnSync("sleep", ["1"]);
@@ -1014,30 +856,35 @@ function cursorCommandUsesUserDataDir(command, userDataDir) {
1014
856
  command.includes(`--user-data-dir "${userDataDir}"`) ||
1015
857
  command.includes(`--user-data-dir '${userDataDir}'`));
1016
858
  }
1017
- function launchCursor(plan) {
1018
- console.log(plan.profileMode === "isolated"
1019
- ? "Launching isolated Cursor instance"
1020
- : "Launching Cursor with the default signed-in profile");
1021
- if (plan.profileMode === "default") {
1022
- console.warn("Default profile mode reuses your existing Cursor auth state; it is less isolated but avoids browser login callback loss.");
859
+ function launchCursor(plan, options = {}) {
860
+ const quiet = options.quiet ?? false;
861
+ if (!quiet) {
862
+ out(plan.profileMode === "isolated"
863
+ ? `${cyan(glyph.arrow())} launching isolated Cursor instance`
864
+ : `${cyan(glyph.arrow())} launching Cursor with the default signed-in profile`);
865
+ if (plan.profileMode === "default") {
866
+ out(`${yellow(glyph.warn())} default profile mode reuses your existing Cursor auth state; it is less isolated but avoids browser login callback loss.`);
867
+ }
1023
868
  }
1024
869
  const cursor = spawn(plan.cursor.executable, plan.cursor.args, {
1025
870
  env: { ...process.env, ...plan.cursor.env },
1026
871
  stdio: "ignore",
1027
872
  });
1028
873
  cursor.on("error", (error) => {
1029
- console.error(`Cursor launch failed: ${error.message}`);
874
+ out(`${red(glyph.cross())} Cursor launch failed: ${error.message}`);
1030
875
  });
1031
876
  return cursor;
1032
877
  }
1033
- function attachBridgeOutput(bridge, log) {
878
+ function attachBridgeOutput(bridge, log, mirror) {
1034
879
  bridge.stdout?.on("data", (chunk) => {
1035
880
  log.write(chunk);
1036
- process.stdout.write(chunk);
881
+ if (mirror)
882
+ process.stdout.write(chunk);
1037
883
  });
1038
884
  bridge.stderr?.on("data", (chunk) => {
1039
885
  log.write(chunk);
1040
- process.stderr.write(chunk);
886
+ if (mirror)
887
+ process.stderr.write(chunk);
1041
888
  });
1042
889
  }
1043
890
  function writeLatestStatus(plan, report) {
@@ -1061,29 +908,32 @@ function writeLatestStatus(plan, report) {
1061
908
  }, null, 2));
1062
909
  }
1063
910
  function printDesktopTestReport(plan, report) {
1064
- console.log("");
1065
- console.log("Desktop Test Report");
1066
- console.log(`route inventory: ${report.routeInventorySeen ? "yes" : "no"}`);
1067
- console.log(`model routes seen: ${report.modelRoutesSeen.length > 0
911
+ const field = (label, value) => out(`${dim(`${label}:`)} ${value}`);
912
+ out("");
913
+ out(bold("Desktop Test Report"));
914
+ field("route inventory", report.routeInventorySeen ? green("yes") : yellow("no"));
915
+ field("model routes seen", report.modelRoutesSeen.length > 0
1068
916
  ? report.modelRoutesSeen.join(", ")
1069
- : "none"}`);
1070
- console.log(`model routes missing: ${report.missingModelRoutes.length > 0
917
+ : "none");
918
+ field("model routes missing", report.missingModelRoutes.length > 0
1071
919
  ? report.missingModelRoutes.join(", ")
1072
- : "none"}`);
1073
- console.log(`observed paths: ${report.observedPaths.length > 0 ? report.observedPaths.join(", ") : "none"}`);
1074
- console.log(`failed routes: ${String(report.failedRoutes.length)}`);
1075
- console.log(`pass-through routes: ${String(report.passThroughRoutes.length)}`);
1076
- console.log(`auth seed: ${plan.authSeedStatus ?? "not-run"}; local model seed: ${plan.localModelSeedStatus ?? "not-run"}`);
1077
- console.log(`route categories: ${report.routeCategories.length > 0
920
+ : "none");
921
+ field("observed paths", report.observedPaths.length > 0 ? report.observedPaths.join(", ") : "none");
922
+ field("failed routes", report.failedRoutes.length > 0
923
+ ? red(String(report.failedRoutes.length))
924
+ : String(report.failedRoutes.length));
925
+ field("pass-through routes", String(report.passThroughRoutes.length));
926
+ field("auth seed", `${plan.authSeedStatus ?? "not-run"}; local model seed: ${plan.localModelSeedStatus ?? "not-run"}`);
927
+ field("route categories", report.routeCategories.length > 0
1078
928
  ? report.routeCategories
1079
929
  .map((entry) => `${entry.category}:${entry.path}`)
1080
930
  .join(", ")
1081
- : "none"}`);
1082
- console.log(`log: ${plan.logPath}`);
1083
- console.log(`state: ${plan.statePath}`);
1084
- console.log("diagnosis:");
931
+ : "none");
932
+ field("log", plan.logPath);
933
+ field("state", plan.statePath);
934
+ out(bold("diagnosis:"));
1085
935
  for (const line of report.diagnosis) {
1086
- console.log(`- ${line}`);
936
+ out(` ${cyan(glyph.bullet())} ${line}`);
1087
937
  }
1088
938
  }
1089
939
  function bridgeCommandSpec(cwd) {
@@ -1160,52 +1010,58 @@ function configureCursorNodeTlsEnv(plan) {
1160
1010
  if (plan.profileMode !== "isolated") {
1161
1011
  return;
1162
1012
  }
1013
+ // Trust only the bridge's self-signed certificate via NODE_EXTRA_CA_CERTS
1014
+ // instead of globally disabling TLS verification. This keeps certificate
1015
+ // validation intact for real upstream traffic (e.g. api2.cursor.sh) while
1016
+ // allowing the spawned Cursor process to connect to the local bridge.
1017
+ const bridgeCertPath = plan.bridge.env?.BRIDGE_CERT_PATH ?? DESKTOP_CERT_PATH;
1163
1018
  plan.cursor.env = {
1164
1019
  ...plan.cursor.env,
1165
- NODE_TLS_REJECT_UNAUTHORIZED: "0",
1020
+ NODE_EXTRA_CA_CERTS: bridgeCertPath,
1166
1021
  };
1167
1022
  }
1168
1023
  function printPlan(plan) {
1169
- console.log("Bridge:");
1170
- console.log(commandForDisplay(plan.bridge));
1171
- console.log("");
1172
- console.log("Cursor:");
1173
- console.log(commandForDisplay(plan.cursor));
1174
- console.log("");
1175
- console.log(`State: ${plan.statePath}`);
1176
- console.log(`Log: ${plan.logPath}`);
1024
+ out(bold("Bridge:"));
1025
+ out(commandForDisplay(plan.bridge));
1026
+ out("");
1027
+ out(bold("Cursor:"));
1028
+ out(commandForDisplay(plan.cursor));
1029
+ out("");
1030
+ out(`${dim("State:")} ${plan.statePath}`);
1031
+ out(`${dim("Log:")} ${plan.logPath}`);
1177
1032
  if (plan.connectProxyPort !== undefined) {
1178
- console.log(`CONNECT proxy: 127.0.0.1:${plan.connectProxyPort}`);
1033
+ out(`${dim("CONNECT proxy:")} 127.0.0.1:${plan.connectProxyPort}`);
1179
1034
  }
1180
1035
  if (plan.agentHttpPort !== undefined) {
1181
- console.log(`Agent HTTP bridge: 127.0.0.1:${plan.agentHttpPort}`);
1036
+ out(`${dim("Agent HTTP bridge:")} 127.0.0.1:${plan.agentHttpPort}`);
1182
1037
  }
1183
1038
  if (plan.connectProxyLogPath !== undefined) {
1184
- console.log(`CONNECT proxy log: ${plan.connectProxyLogPath}`);
1039
+ out(`${dim("CONNECT proxy log:")} ${plan.connectProxyLogPath}`);
1185
1040
  }
1186
1041
  }
1187
1042
  async function printCertInstructions() {
1188
- const cert = await writeDesktopCertificate();
1189
- console.log(`cert: ${cert.certPath}`);
1190
- console.log(`key: ${cert.keyPath}`);
1043
+ const cert = await withSpinner("generating desktop proxy certificate", () => writeDesktopCertificate());
1044
+ out(`${dim("cert:")} ${cert.certPath}`);
1045
+ out(`${dim("key:")} ${cert.keyPath}`);
1191
1046
  printTrustInstructions(cert.certPath);
1192
1047
  }
1193
1048
  function printTrustInstructions(certPath) {
1194
- console.log("Manual macOS trust command:");
1195
- console.log(desktopTrustCommand(certPath).map(shellQuote).join(" "));
1049
+ out(bold("Manual macOS trust command:"));
1050
+ out(desktopTrustCommand(certPath).map(shellQuote).join(" "));
1196
1051
  }
1197
1052
  async function printDoctor() {
1198
1053
  const env = desktopEnv(process.env);
1199
1054
  const config = loadConfig(env);
1200
- console.log(`desktop cert: ${desktopCertificateStatus(config)}`);
1201
- console.log(`desktop dns: ${await desktopDnsStatus(config)}`);
1202
- console.log(`upstream reachability: ${await upstreamReachabilityStatus(config)}`);
1203
- console.log(`local model backend: ${await localModelBackendStatus(config)}`);
1055
+ out(`\n${brandHeader("desktop launch readiness")}\n`);
1056
+ out(`${dim("desktop cert:")} ${desktopCertificateStatus(config)}`);
1057
+ out(`${dim("desktop dns:")} ${await desktopDnsStatus(config)}`);
1058
+ out(`${dim("upstream reachability:")} ${await upstreamReachabilityStatus(config)}`);
1059
+ out(`${dim("local model backend:")} ${await localModelBackendStatus(config)}`);
1204
1060
  if (!fs.existsSync(DESKTOP_CERT_PATH) || !fs.existsSync(DESKTOP_KEY_PATH)) {
1205
- console.warn("Run ck cert before launching.");
1061
+ out(`${yellow(glyph.warn())} run ${bold("ck cert")} before launching.`);
1206
1062
  }
1207
- console.log("");
1208
- console.log("manual route plan: pnpm ck route");
1063
+ out("");
1064
+ note("manual route plan: pnpm ck route");
1209
1065
  }
1210
1066
  async function printRoute(action, method) {
1211
1067
  if (action === "status") {
@@ -1220,90 +1076,90 @@ async function printRoute(action, method) {
1220
1076
  printRoutePlan(plan);
1221
1077
  }
1222
1078
  function printRoutePlan(plan) {
1223
- console.log("Desktop Manual Routing Plan");
1224
- console.log(`method: ${plan.method}`);
1225
- console.log(`primary hostname: ${plan.hostname}`);
1226
- console.log(`hostnames: ${plan.hostnames.join(", ")}`);
1227
- console.log(`bridge port: ${String(plan.bridgePort)}`);
1228
- console.log(`upstream connect host: ${plan.upstreamConnectHost ?? "<set manually>"}`);
1229
- console.log("");
1079
+ out(bold("Desktop Manual Routing Plan"));
1080
+ out(`${dim("method:")} ${plan.method}`);
1081
+ out(`${dim("primary hostname:")} ${plan.hostname}`);
1082
+ out(`${dim("hostnames:")} ${plan.hostnames.join(", ")}`);
1083
+ out(`${dim("bridge port:")} ${String(plan.bridgePort)}`);
1084
+ out(`${dim("upstream connect host:")} ${plan.upstreamConnectHost ?? "<set manually>"}`);
1085
+ out("");
1230
1086
  if (plan.warnings.length > 0) {
1231
- console.log("Warnings:");
1087
+ out(bold("Warnings:"));
1232
1088
  for (const warning of plan.warnings) {
1233
- console.log(`- ${warning}`);
1089
+ out(`${yellow(glyph.warn())} ${warning}`);
1234
1090
  }
1235
- console.log("");
1091
+ out("");
1236
1092
  }
1237
- console.log("Setup commands to run manually:");
1093
+ out(bold("Setup commands to run manually:"));
1238
1094
  for (const command of plan.setupCommands) {
1239
- console.log(commandForDisplay(command));
1095
+ out(commandForDisplay(command));
1240
1096
  }
1241
- console.log("");
1242
- console.log("Verification:");
1097
+ out("");
1098
+ out(bold("Verification:"));
1243
1099
  for (const command of plan.verificationCommands) {
1244
- console.log(commandForDisplay(command));
1100
+ out(commandForDisplay(command));
1245
1101
  }
1246
- console.log("");
1247
- console.log("Rollback:");
1102
+ out("");
1103
+ out(bold("Rollback:"));
1248
1104
  for (const command of plan.rollbackCommands) {
1249
- console.log(commandForDisplay(command));
1105
+ out(commandForDisplay(command));
1250
1106
  }
1251
- console.log("");
1252
- console.log("ck prints these commands only. It does not install trust, edit hosts, configure pf, or kill Cursor for you.");
1107
+ out("");
1108
+ note("ck prints these commands only. It does not install trust, edit hosts, configure pf, or kill Cursor for you.");
1253
1109
  }
1254
1110
  async function printRouteStatus() {
1255
1111
  const env = desktopEnv(process.env);
1256
1112
  const config = loadConfig(env);
1257
- console.log("Desktop Routing Status");
1258
- console.log(`desktop cert: ${desktopCertificateStatus(config)}`);
1259
- console.log(`desktop dns: ${await desktopDnsStatus(config)}`);
1113
+ out(bold("Desktop Routing Status"));
1114
+ out(`${dim("desktop cert:")} ${desktopCertificateStatus(config)}`);
1115
+ out(`${dim("desktop dns:")} ${await desktopDnsStatus(config)}`);
1260
1116
  for (const hostname of DESKTOP_HOSTNAMES.filter((hostname) => hostname !== DESKTOP_HOSTNAME)) {
1261
- console.log(`desktop dns: ${await desktopDnsStatusForHostname(hostname)}`);
1117
+ out(`${dim("desktop dns:")} ${await desktopDnsStatusForHostname(hostname)}`);
1262
1118
  }
1263
- console.log(`detected upstream connect host: ${(await detectUpstreamConnectHost()) ?? "<none; set CURSOR_UPSTREAM_CONNECT_HOST manually>"}`);
1264
- console.log(`configured upstream connect: ${config.upstreamConnectHost === undefined
1119
+ out(`${dim("detected upstream connect host:")} ${(await detectUpstreamConnectHost()) ?? "<none; set CURSOR_UPSTREAM_CONNECT_HOST manually>"}`);
1120
+ out(`${dim("configured upstream connect:")} ${config.upstreamConnectHost === undefined
1265
1121
  ? "system DNS"
1266
1122
  : `${config.upstreamConnectHost}${config.upstreamConnectPort === undefined ? "" : `:${config.upstreamConnectPort}`}`}`);
1267
- console.log(`upstream reachability: ${await upstreamReachabilityStatus(config)}`);
1268
- console.log(`local model backend: ${await localModelBackendStatus(config)}`);
1269
- console.log("");
1270
- console.log("Next steps:");
1271
- console.log("- Run `pnpm ck route` before system cutover to capture a real upstream IP.");
1272
- console.log("- Run `pnpm ck route rollback` to print the rollback commands.");
1123
+ out(`${dim("upstream reachability:")} ${await upstreamReachabilityStatus(config)}`);
1124
+ out(`${dim("local model backend:")} ${await localModelBackendStatus(config)}`);
1125
+ out("");
1126
+ out(bold("Next steps:"));
1127
+ note("Run `pnpm ck route` before system cutover to capture a real upstream IP.");
1128
+ note("Run `pnpm ck route rollback` to print the rollback commands.");
1273
1129
  }
1274
1130
  function printRouteRollback(plan) {
1275
- console.log("Desktop Routing Rollback");
1131
+ out(bold("Desktop Routing Rollback"));
1276
1132
  for (const command of plan.rollbackCommands) {
1277
- console.log(commandForDisplay(command));
1133
+ out(commandForDisplay(command));
1278
1134
  }
1279
- console.log("");
1280
- console.log("ck prints rollback commands only. Review them before running; especially `pkill -x Cursor`.");
1135
+ out("");
1136
+ note("ck prints rollback commands only. Review them before running; especially `pkill -x Cursor`.");
1281
1137
  }
1282
1138
  async function stopBridgeFromState() {
1283
1139
  if (!fs.existsSync(CK_STATE_PATH)) {
1284
- console.log("No ck state file found.");
1140
+ out(`${gray(glyph.bullet())} No ck state file found.`);
1285
1141
  return;
1286
1142
  }
1287
1143
  const state = JSON.parse(fs.readFileSync(CK_STATE_PATH, "utf8"));
1288
1144
  if (state.bridgePid === undefined) {
1289
- console.log("No bridge PID recorded.");
1145
+ out(`${gray(glyph.bullet())} No bridge PID recorded.`);
1290
1146
  return;
1291
1147
  }
1292
1148
  const command = processCommandForPid(state.bridgePid);
1293
1149
  if (command === undefined) {
1294
- console.warn(`No running process found for ck bridge PID ${state.bridgePid}.`);
1150
+ out(`${yellow(glyph.warn())} No running process found for ck bridge PID ${state.bridgePid}.`);
1295
1151
  return;
1296
1152
  }
1297
1153
  if (!bridgeProcessMatchesState(command, state)) {
1298
- console.warn(`Refusing to stop PID ${state.bridgePid}; it does not look like the ck-owned desktop bridge recorded in state.`);
1154
+ out(`${yellow(glyph.warn())} Refusing to stop PID ${state.bridgePid}; it does not look like the ck-owned desktop bridge recorded in state.`);
1299
1155
  return;
1300
1156
  }
1301
1157
  try {
1302
1158
  process.kill(state.bridgePid, "SIGTERM");
1303
- console.log(`Stopped ck bridge process ${state.bridgePid}.`);
1159
+ out(`${green(glyph.tick())} Stopped ck bridge process ${state.bridgePid}.`);
1304
1160
  }
1305
1161
  catch (error) {
1306
- console.warn(`Could not stop bridge process ${state.bridgePid}: ${error instanceof Error ? error.message : String(error)}`);
1162
+ out(`${yellow(glyph.warn())} Could not stop bridge process ${state.bridgePid}: ${error instanceof Error ? error.message : String(error)}`);
1307
1163
  }
1308
1164
  }
1309
1165
  export function bridgeProcessMatchesState(command, state = {}) {