diff --git a/apps/server/src/provider/Layers/CursorProvider.test.ts b/apps/server/src/provider/Layers/CursorProvider.test.ts
index 60a7312eea3..78f62ac2123 100644
--- a/apps/server/src/provider/Layers/CursorProvider.test.ts
+++ b/apps/server/src/provider/Layers/CursorProvider.test.ts
@@ -4,6 +4,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
+import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
import { describe, expect, it } from "vite-plus/test";
import type * as EffectAcpSchema from "effect-acp/schema";
import type { CursorSettings } from "@t3tools/contracts";
@@ -24,7 +25,11 @@ import {
} from "./CursorProvider.ts";
const runNode = (
- effect: Effect.Effect,
+ effect: Effect.Effect<
+ A,
+ E,
+ ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path
+ >,
): Promise => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer)));
const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () {
@@ -293,6 +298,18 @@ const baseCursorSettings: CursorSettings = {
apiEndpoint: "",
customModels: [],
};
+const cursorAcpDiscoveryFailedMessage = [
+ "Cursor ACP model discovery failed.",
+ "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.",
+ "See https://cursor.com/docs/cli/installation.",
+ "Check server logs for ACP details.",
+].join(" ");
+const missingCursorBinaryPath = "/definitely/not/installed/t3-cursor-agent";
+const cursorCliCommandMissingMessage = [
+ `Cursor CLI command \`${missingCursorBinaryPath}\` was not found.`,
+ `Install or enable the Cursor CLI, make sure \`${missingCursorBinaryPath}\` is on PATH, then restart T3 Code.`,
+ "See https://cursor.com/docs/cli/installation.",
+].join(" ");
describe("getCursorFallbackModels", () => {
it("does not publish any built-in cursor models before ACP discovery", () => {
@@ -338,12 +355,11 @@ describe("buildCursorProviderSnapshot", () => {
auth: { status: "unauthenticated" },
message: "Cursor Agent is not authenticated. Run `agent login` and try again.",
},
- discoveryWarning: "Cursor ACP model discovery failed. Check server logs for details.",
+ discoveryWarning: cursorAcpDiscoveryFailedMessage,
}),
).toMatchObject({
status: "error",
- message:
- "Cursor Agent is not authenticated. Run `agent login` and try again. Cursor ACP model discovery failed. Check server logs for details.",
+ message: `Cursor Agent is not authenticated. Run \`agent login\` and try again. ${cursorAcpDiscoveryFailedMessage}`,
models: [
{
slug: "claude-sonnet-4-6",
@@ -411,10 +427,28 @@ describe("buildCursorCapabilitiesFromConfigOptions", () => {
});
describe("checkCursorProviderStatus", () => {
+ it("reports the install docs when the Cursor CLI command is missing", async () => {
+ const provider = await runNode(
+ checkCursorProviderStatus({
+ enabled: true,
+ binaryPath: missingCursorBinaryPath,
+ apiEndpoint: "",
+ customModels: [],
+ }),
+ );
+
+ expect(provider).toMatchObject({
+ installed: false,
+ status: "error",
+ auth: { status: "unknown" },
+ message: cursorCliCommandMissingMessage,
+ });
+ });
+
it("passes the injected environment to ACP model discovery", async () => {
const { requestLogPath, wrapperPath } = await runNode(makeProviderStatusEnvFixture());
- const provider = await Effect.runPromise(
+ const provider = await runNode(
checkCursorProviderStatus(
{
enabled: true,
@@ -426,7 +460,7 @@ describe("checkCursorProviderStatus", () => {
...process.env,
T3_ACP_REQUEST_LOG_PATH: requestLogPath,
},
- ).pipe(Effect.provide(NodeServices.layer)),
+ ),
);
expect(provider.models.map((model) => model.slug)).toEqual([
@@ -443,13 +477,13 @@ describe("discoverCursorModelsViaAcp", () => {
it("keeps the ACP probe runtime alive long enough to discover models", async () => {
const wrapperPath = await runNode(makeMockAgentWrapper());
- const models = await Effect.runPromise(
+ const models = await runNode(
discoverCursorModelsViaAcp({
enabled: true,
binaryPath: wrapperPath,
apiEndpoint: "",
customModels: [],
- }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+ }).pipe(Effect.scoped),
);
expect(models.map((model) => model.slug)).toEqual([
@@ -465,13 +499,13 @@ describe("discoverCursorModelsViaAcp", () => {
makeExitLogFixture("cursor-provider-exit-log-"),
);
- await Effect.runPromise(
+ await runNode(
discoverCursorModelsViaAcp({
enabled: true,
binaryPath: wrapperPath,
apiEndpoint: "",
customModels: [],
- }).pipe(Effect.provide(NodeServices.layer)),
+ }),
);
const exitLog = await runNode(waitForFileContent(exitLogPath));
diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts
index ff96ece9349..cd9b93a4734 100644
--- a/apps/server/src/provider/Layers/CursorProvider.ts
+++ b/apps/server/src/provider/Layers/CursorProvider.ts
@@ -62,6 +62,13 @@ const EMPTY_CAPABILITIES: ModelCapabilities = createModelCapabilities({
const CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS = 15_000;
const CURSOR_PARAMETERIZED_MODEL_PICKER_MIN_VERSION_DATE = 2026_04_08;
+const CURSOR_CLI_INSTALLATION_DOCS_URL = "https://cursor.com/docs/cli/installation";
+const CURSOR_ACP_MODEL_DISCOVERY_FAILED_MESSAGE = [
+ "Cursor ACP model discovery failed.",
+ "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.",
+ `See ${CURSOR_CLI_INSTALLATION_DOCS_URL}.`,
+ "Check server logs for ACP details.",
+].join(" ");
export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = {
_meta: {
parameterizedModelPicker: true,
@@ -608,6 +615,14 @@ function joinProviderMessages(...messages: ReadonlyArray): s
return parts.length > 0 ? parts.join(" ") : undefined;
}
+function buildCursorCliCommandMissingMessage(binaryPath: string): string {
+ return [
+ `Cursor CLI command \`${binaryPath}\` was not found.`,
+ `Install or enable the Cursor CLI, make sure \`${binaryPath}\` is on PATH, then restart T3 Code.`,
+ `See ${CURSOR_CLI_INSTALLATION_DOCS_URL}.`,
+ ].join(" ");
+}
+
export function buildCursorProviderSnapshot(input: {
readonly checkedAt: string;
readonly cursorSettings: CursorSettings;
@@ -1020,7 +1035,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
status: "error",
auth: { status: "unknown" },
message: isCommandMissingCause(error)
- ? "Cursor Agent CLI (`agent`) is not installed or not on PATH."
+ ? buildCursorCliCommandMissingMessage(cursorSettings.binaryPath)
: "Failed to execute Cursor Agent CLI health check.",
},
});
@@ -1079,7 +1094,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
yield* Effect.logWarning("Cursor ACP model discovery failed", {
errorTag: causeErrorTag(discoveryExit.cause),
});
- discoveryWarning = "Cursor ACP model discovery failed. Check server logs for details.";
+ discoveryWarning = CURSOR_ACP_MODEL_DISCOVERY_FAILED_MESSAGE;
} else if (Option.isNone(discoveryExit.value)) {
discoveryWarning = `Cursor ACP model discovery timed out after ${CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS}ms.`;
} else if (discoveryExit.value.value.length === 0) {