diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 02ba1b20ae85..2b73304bebd1 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -70,6 +70,7 @@
"@t3tools/mobile-review-diff-native": "file:./modules/t3-review-diff",
"@t3tools/mobile-terminal-native": "file:./modules/t3-terminal",
"@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-muse": "workspace:*",
"@t3tools/provider-opencode": "workspace:*",
"@t3tools/provider-pi": "workspace:*",
diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx
index dda6e6f3b544..f2eb4e3c7f70 100644
--- a/apps/mobile/src/components/ProviderIcon.tsx
+++ b/apps/mobile/src/components/ProviderIcon.tsx
@@ -109,17 +109,6 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}
- if (props.provider === "cursor") {
- return (
-
- );
- }
-
const packageIcon = getProviderClient(props.provider)?.icon;
if (packageIcon) {
const themeFill = (fill: { readonly light: string; readonly dark: string }) =>
diff --git a/apps/mobile/src/lib/providerClients.ts b/apps/mobile/src/lib/providerClients.ts
index 11f7e13a3a78..cb469a28706c 100644
--- a/apps/mobile/src/lib/providerClients.ts
+++ b/apps/mobile/src/lib/providerClients.ts
@@ -1,11 +1,17 @@
import { isProviderDriverKind } from "@t3tools/contracts";
import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
+import { cursorClient } from "@t3tools/provider-cursor/client";
import { museClient } from "@t3tools/provider-muse/client";
import { openCodeClient } from "@t3tools/provider-opencode/client";
import { piClient } from "@t3tools/provider-pi/client";
/** The provider client definitions this mobile build ships. */
-const providerClients = makeProviderClientRegistry([museClient, openCodeClient, piClient]);
+const providerClients = makeProviderClientRegistry([
+ cursorClient,
+ museClient,
+ openCodeClient,
+ piClient,
+]);
/** The client definition for a driver kind, or `undefined` for drivers drawn by hand. */
export function getProviderClient(driver: string | null | undefined) {
diff --git a/apps/server/package.json b/apps/server/package.json
index 11f202120f85..de20a01de3b9 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -39,6 +39,7 @@
"@noble/hashes": "catalog:",
"@opencode-ai/sdk": "^1.3.15",
"@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-muse": "workspace:*",
"@t3tools/provider-opencode": "workspace:*",
"@t3tools/provider-pi": "workspace:*",
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
index a3a72f741138..3f2f6581f90d 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
+++ b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
@@ -1,6 +1,4 @@
import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
-import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
-import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
import type { InteractionUpdate, RunResult } from "@cursor/sdk";
import * as NodeServices from "@effect/platform-node/NodeServices";
import {
@@ -15,23 +13,21 @@ import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
-import * as Path from "effect/Path";
-import * as PlatformError from "effect/PlatformError";
import * as Schema from "effect/Schema";
-import * as ServerConfig from "../../config.ts";
+import { layerTestProviderHost } from "@t3tools/provider-testing/host";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
-import * as CursorAgentSdk from "./CursorAgentSdk.ts";
+import { CursorAdapterV2Driver } from "@t3tools/provider-cursor/server";
+import * as CursorAgentSdk from "@t3tools/provider-cursor/server/CursorAgentSdk";
import {
CURSOR_DEFAULT_INSTANCE_ID,
CURSOR_DRIVER_KIND,
- CursorAdapterV2Driver,
cursorSdkModelSelection,
makeCursorAgentOptions,
-} from "./CursorAdapterV2.ts";
+} from "@t3tools/provider-cursor/testing";
import type { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter";
import type { RuntimePolicyV2Override } from "../RuntimePolicy.ts";
@@ -513,91 +509,6 @@ function layerCursorAgentSdkReplay(
);
}
-function makeReplayServerConfig(
- scenario: string,
-): Effect.Effect<
- ServerConfig.ServerConfig["Service"],
- PlatformError.PlatformError,
- FileSystem.FileSystem | Path.Path
-> {
- return Effect.gen(function* () {
- const fs = yield* FileSystem.FileSystem;
- const path = yield* Path.Path;
- const baseDir = yield* fs.makeTempDirectory({
- prefix: `t3-orchestration-v2-cursor-${scenario}-`,
- });
- const stateDir = path.join(baseDir, "userdata");
- const logsDir = path.join(stateDir, "logs");
- const providerLogsDir = path.join(logsDir, "provider");
- const terminalLogsDir = path.join(logsDir, "terminals");
- const attachmentsDir = path.join(stateDir, "attachments");
- const environmentThemesDir = path.join(stateDir, "themes");
- const worktreesDir = path.join(baseDir, "worktrees");
- const providerStatusCacheDir = path.join(baseDir, "caches");
- for (const directory of [
- stateDir,
- logsDir,
- providerLogsDir,
- terminalLogsDir,
- attachmentsDir,
- environmentThemesDir,
- worktreesDir,
- providerStatusCacheDir,
- ]) {
- yield* fs.makeDirectory(directory, { recursive: true });
- }
- return {
- logLevel: "Error",
- traceMinLevel: "Info",
- traceTimingEnabled: true,
- traceBatchWindowMs: 200,
- traceMaxBytes: 10 * 1024 * 1024,
- traceMaxFiles: 10,
- otelEnvironment: OtelEnvironment.none,
- otlpTracesUrl: undefined,
- otlpMetricsUrl: undefined,
- otlpLogsUrl: undefined,
- otlpTracesExport: DEFAULT_SIGNAL_EXPORT,
- otlpMetricsExport: DEFAULT_SIGNAL_EXPORT,
- otlpLogsExport: DEFAULT_SIGNAL_EXPORT,
- mode: "web",
- port: 0,
- host: undefined,
- cwd: process.cwd(),
- baseDir,
- staticDir: undefined,
- devUrl: undefined,
- devAllowedOrigins: [],
- noBrowser: false,
- startupPresentation: "browser",
- tailscaleServeEnabled: false,
- tailscaleServePort: 443,
- desktopBootstrapToken: undefined,
- autoBootstrapProjectFromCwd: false,
- logWebSocketEvents: false,
- stateDir,
- dbPath: path.join(stateDir, "state.sqlite"),
- keybindingsConfigPath: path.join(stateDir, "keybindings.json"),
- settingsPath: path.join(stateDir, "settings.json"),
- providerStatusCacheDir,
- worktreesDir,
- attachmentsDir,
- browserArtifactsDir: path.join(stateDir, "browser-artifacts"),
- environmentThemesDir,
- logsDir,
- serverLogPath: path.join(logsDir, "server.log"),
- serverTracePath: path.join(logsDir, "server.trace.ndjson"),
- providerLogsDir,
- providerEventLogPath: path.join(providerLogsDir, "events.log"),
- terminalLogsDir,
- anonymousIdPath: path.join(stateDir, "anonymous-id"),
- environmentIdPath: path.join(stateDir, "environment-id"),
- serverRuntimeStatePath: path.join(stateDir, "server-runtime.json"),
- secretsDir: path.join(stateDir, "secrets"),
- };
- });
-}
-
export function layer(
transcript: CursorAgentSdkReplayTranscript,
options?: {
@@ -605,10 +516,6 @@ export function layer(
readonly assertCompleteOnFinalize?: boolean;
},
) {
- const layerServerConfig = Layer.effect(
- ServerConfig.ServerConfig,
- makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),
- ).pipe(Layer.provide(NodeServices.layer));
// Skill discovery also scans user roots under HOME; an empty HOME keeps
// replays from picking up the host's own skills.
const layerHostEnvironment = Layer.effect(
@@ -630,7 +537,7 @@ export function layer(
Layer.provide(
Layer.mergeAll(
layerCursorAgentSdkReplay(transcript, options),
- layerServerConfig,
+ layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
layerHostEnvironment,
NodeServices.layer,
IdAllocator.layer,
diff --git a/apps/server/src/orchestration-v2/CommandPolicy.test.ts b/apps/server/src/orchestration-v2/CommandPolicy.test.ts
index 256ecd45e75a..517229c222ea 100644
--- a/apps/server/src/orchestration-v2/CommandPolicy.test.ts
+++ b/apps/server/src/orchestration-v2/CommandPolicy.test.ts
@@ -12,7 +12,7 @@ import {
import * as Effect from "effect/Effect";
import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
-import { CursorProviderCapabilitiesV2 } from "./Adapters/CursorAdapterV2.ts";
+import { CursorProviderCapabilitiesV2 } from "@t3tools/provider-cursor/testing";
import { GrokProviderCapabilitiesV2 } from "./Adapters/GrokAdapterV2.ts";
import * as CommandPolicy from "./CommandPolicy.ts";
diff --git a/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts b/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
index 3e33d5b2de41..e48ceb60177c 100644
--- a/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
+++ b/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
@@ -12,7 +12,7 @@ import { CodexAdapterV2Driver, type CodexAdapterV2DriverEnv } from "./Adapters/C
import {
CursorAdapterV2Driver,
type CursorAdapterV2DriverEnv,
-} from "./Adapters/CursorAdapterV2.ts";
+} from "@t3tools/provider-cursor/server";
import { GrokAdapterV2Driver, type GrokAdapterV2DriverEnv } from "./Adapters/GrokAdapterV2.ts";
import {
OpenCodeAdapterV2Driver,
diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
index a0de3392631c..b71043aabc4e 100644
--- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
+++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
@@ -43,7 +43,7 @@ import {
canReuseCodexContextUsage,
} from "../Adapters/CodexAdapterV2.ts";
import { AcpProviderCapabilitiesV2 } from "../Adapters/AcpAdapterV2.ts";
-import { CursorProviderCapabilitiesV2 } from "../Adapters/CursorAdapterV2.ts";
+import { CursorProviderCapabilitiesV2 } from "@t3tools/provider-cursor/testing";
import * as EventSink from "../EventSink.ts";
import * as EventStore from "../EventStore.ts";
import * as LegacyV1ThreadImporter from "../legacy/LegacyV1ThreadImporter.ts";
diff --git a/apps/server/src/provider/AntigravityProvider.test.ts b/apps/server/src/provider/AntigravityProvider.test.ts
index 77a436aaf7be..69502047f73d 100644
--- a/apps/server/src/provider/AntigravityProvider.test.ts
+++ b/apps/server/src/provider/AntigravityProvider.test.ts
@@ -22,6 +22,7 @@ import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
import * as ServerConfig from "../config.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as ProviderHostLive from "./ProviderHostLive.ts";
+import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import type { AcpSessionRuntimeStartResult } from "./acp/AcpSessionRuntime.ts";
import {
buildAntigravityModelsFromSession,
@@ -116,9 +117,10 @@ const layerTest = ProviderHostLive.layer.pipe(
}),
),
Layer.provideMerge(ServerSettings.layerTest()),
+ Layer.provideMerge(ServerSecretStore.layer),
Layer.provide(
ServerConfig.layerTest(process.cwd(), { prefix: "t3-antigravity-provider-test-" }).pipe(
- Layer.provide(NodeServices.layer),
+ Layer.provideMerge(NodeServices.layer),
),
),
);
diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts
index d08cacb60dcd..33aa39f5598b 100644
--- a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts
+++ b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts
@@ -34,6 +34,7 @@ import * as ModelManifest from "../ModelManifest.ts";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import { AntigravityDriver } from "./AntigravityDriver.ts";
import * as ProviderHostLive from "../ProviderHostLive.ts";
+import * as ServerSecretStore from "../../auth/ServerSecretStore.ts";
const hostPlatform = HostProcessPlatform.defaultValue();
const windowsHost = hostPlatform === "win32";
@@ -264,7 +265,10 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
Layer.provideMerge(ModelManifest.layerTest),
Layer.provideMerge(IdAllocator.layer),
);
-const layerTest = ProviderHostLive.layer.pipe(Layer.provideMerge(layerDeps));
+const layerTest = ProviderHostLive.layer.pipe(
+ Layer.provideMerge(ServerSecretStore.layer),
+ Layer.provideMerge(layerDeps),
+);
it.layer(layerTest)("AntigravityDriver", (it) => {
it.effect.skipIf(windowsHost)(
diff --git a/apps/server/src/provider/Drivers/GrokDriver.test.ts b/apps/server/src/provider/Drivers/GrokDriver.test.ts
index f1737f71f391..e0da3203902c 100644
--- a/apps/server/src/provider/Drivers/GrokDriver.test.ts
+++ b/apps/server/src/provider/Drivers/GrokDriver.test.ts
@@ -17,6 +17,7 @@ import { GrokDriver } from "./GrokDriver.ts";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import * as ProviderHostLive from "../ProviderHostLive.ts";
+import * as ServerSecretStore from "../../auth/ServerSecretStore.ts";
const layerDeps = ServerConfig.layerTest(process.cwd(), {
prefix: "t3-grok-driver-update-",
@@ -42,7 +43,10 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
),
),
);
-const layerTest = ProviderHostLive.layer.pipe(Layer.provideMerge(layerDeps));
+const layerTest = ProviderHostLive.layer.pipe(
+ Layer.provideMerge(ServerSecretStore.layer),
+ Layer.provideMerge(layerDeps),
+);
const noSpawner = ChildProcessSpawner.make(() =>
Effect.die("Disabled Grok must not spawn a process"),
diff --git a/apps/server/src/provider/ProviderHostLive.ts b/apps/server/src/provider/ProviderHostLive.ts
index 965cb47e365f..372ac75765c6 100644
--- a/apps/server/src/provider/ProviderHostLive.ts
+++ b/apps/server/src/provider/ProviderHostLive.ts
@@ -5,13 +5,17 @@
* @module provider/ProviderHostLive
*/
import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost";
+import { ProviderCredentialError } from "@t3tools/provider-core/server/errors";
+import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { resolveAttachmentPath } from "../attachmentStore.ts";
+import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
import * as ServerConfig from "../config.ts";
import * as ServerSettings from "../serverSettings.ts";
+import * as ProviderCredentialStore from "./ProviderCredentialStore.ts";
export const layer = Layer.effect(
ProviderHost,
@@ -19,6 +23,8 @@ export const layer = Layer.effect(
const config = yield* ServerConfig.ServerConfig;
const serverSettings = yield* ServerSettings.ServerSettingsService;
const backgroundPolicy = yield* BackgroundPolicy.BackgroundPolicy;
+ const secrets = yield* ServerSecretStore.ServerSecretStore;
+ const crypto = yield* Crypto.Crypto;
return ProviderHost.of({
paths: {
cwd: config.cwd,
@@ -34,6 +40,30 @@ export const layer = Layer.effect(
shouldRunBackgroundWork: backgroundPolicy.shouldRunScopeWork,
resolveAttachmentPath: (attachment) =>
resolveAttachmentPath({ attachmentsDir: config.attachmentsDir, attachment }),
+ credentials: (namespace, bindingId) =>
+ ProviderCredentialStore.make(namespace, bindingId).pipe(
+ Effect.map((store) => ({
+ binding: store.binding,
+ get: store.get.pipe(
+ Effect.mapError((cause) => new ProviderCredentialError({ operation: "get", cause })),
+ ),
+ set: (credentials: Uint8Array) =>
+ store
+ .set(credentials)
+ .pipe(
+ Effect.mapError(
+ (cause) => new ProviderCredentialError({ operation: "set", cause }),
+ ),
+ ),
+ remove: store.remove.pipe(
+ Effect.mapError(
+ (cause) => new ProviderCredentialError({ operation: "remove", cause }),
+ ),
+ ),
+ })),
+ Effect.provideService(ServerSecretStore.ServerSecretStore, secrets),
+ Effect.provideService(Crypto.Crypto, crypto),
+ ),
});
}),
);
diff --git a/apps/server/src/provider/ProviderInstanceRegistry.test.ts b/apps/server/src/provider/ProviderInstanceRegistry.test.ts
index 5c1bbb165822..353c29fb90da 100644
--- a/apps/server/src/provider/ProviderInstanceRegistry.test.ts
+++ b/apps/server/src/provider/ProviderInstanceRegistry.test.ts
@@ -31,12 +31,12 @@ import {
EnvironmentId,
type ClaudeSettings,
type CodexSettings,
- type CursorSettings,
type GrokSettings,
ProviderDriverKind,
type ProviderInstanceConfigMap,
ProviderInstanceId,
} from "@t3tools/contracts";
+import type { CursorSettings } from "@t3tools/provider-cursor/settings";
import { HostProcessPlatform, isHostWindows } from "@t3tools/shared/hostProcess";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
@@ -53,7 +53,7 @@ import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
import * as ServerSettings from "../serverSettings.ts";
import { ClaudeDriver, type ClaudeDriverEnv } from "./Drivers/ClaudeDriver.ts";
import { CodexDriver, type CodexDriverEnv } from "./Drivers/CodexDriver.ts";
-import { CursorDriver, type CursorDriverEnv } from "./Drivers/CursorDriver.ts";
+import { CursorDriver, type CursorDriverEnv } from "@t3tools/provider-cursor/server";
import { GrokDriver, type GrokDriverEnv } from "./Drivers/GrokDriver.ts";
import { OpenCodeDriver, type OpenCodeDriverEnv } from "@t3tools/provider-opencode/server";
import type { OpenCodeSettings } from "@t3tools/provider-opencode/settings";
diff --git a/apps/server/src/provider/ProviderOrchestrationAdapterInfrastructure.ts b/apps/server/src/provider/ProviderOrchestrationAdapterInfrastructure.ts
index 6640288c7ff4..2cbb70fbbdec 100644
--- a/apps/server/src/provider/ProviderOrchestrationAdapterInfrastructure.ts
+++ b/apps/server/src/provider/ProviderOrchestrationAdapterInfrastructure.ts
@@ -2,7 +2,7 @@ import * as Layer from "effect/Layer";
import * as ClaudeAdapterV2 from "../orchestration-v2/Adapters/ClaudeAdapterV2.ts";
import * as CodexAdapterV2 from "../orchestration-v2/Adapters/CodexAdapterV2.ts";
-import * as CursorAgentSdk from "../orchestration-v2/Adapters/CursorAgentSdk.ts";
+import * as CursorAgentSdk from "@t3tools/provider-cursor/server/CursorAgentSdk";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import * as ProviderContinuationRequests from "@t3tools/provider-core/server/continuationRequests";
diff --git a/apps/server/src/provider/builtInDrivers.ts b/apps/server/src/provider/builtInDrivers.ts
index 8cfcc007e9c1..516e52d0e49b 100644
--- a/apps/server/src/provider/builtInDrivers.ts
+++ b/apps/server/src/provider/builtInDrivers.ts
@@ -24,7 +24,7 @@ import { AcpRegistryDriver, type AcpRegistryDriverEnv } from "./Drivers/AcpRegis
import { AntigravityDriver, type AntigravityDriverEnv } from "./Drivers/AntigravityDriver.ts";
import { ClaudeDriver, type ClaudeDriverEnv } from "./Drivers/ClaudeDriver.ts";
import { CodexDriver, type CodexDriverEnv } from "./Drivers/CodexDriver.ts";
-import { CursorDriver, type CursorDriverEnv } from "./Drivers/CursorDriver.ts";
+import { CursorDriver, type CursorDriverEnv } from "@t3tools/provider-cursor/server";
import { GrokDriver, type GrokDriverEnv } from "./Drivers/GrokDriver.ts";
import { OpenCodeDriver, type OpenCodeDriverEnv } from "@t3tools/provider-opencode/server";
import { MuseDriver, type MuseDriverEnv } from "@t3tools/provider-muse/server";
diff --git a/apps/server/src/usage/cursorUsageReader.test.ts b/apps/server/src/usage/cursorUsageReader.test.ts
index 0c6783757748..4897f4604a9e 100644
--- a/apps/server/src/usage/cursorUsageReader.test.ts
+++ b/apps/server/src/usage/cursorUsageReader.test.ts
@@ -1,6 +1,6 @@
import { assert, describe, it } from "@effect/vitest";
-import { CursorKeychainTimeoutError } from "../provider/cursorKeychainToken.ts";
+import { CursorKeychainTimeoutError } from "@t3tools/provider-cursor/server";
import { readCursorAccountUsage } from "./cursorUsageReader.ts";
describe("readCursorAccountUsage", () => {
diff --git a/apps/server/src/usage/cursorUsageReader.ts b/apps/server/src/usage/cursorUsageReader.ts
index 86ba226a0529..95b87f1984bb 100644
--- a/apps/server/src/usage/cursorUsageReader.ts
+++ b/apps/server/src/usage/cursorUsageReader.ts
@@ -12,7 +12,7 @@ import type { UsageRecord } from "./usageTranscripts.ts";
import {
CursorKeychainTimeoutError,
readMacCursorAccessToken,
-} from "../provider/cursorKeychainToken.ts";
+} from "@t3tools/provider-cursor/server";
function object(value: unknown): Record {
return typeof value === "object" && value !== null && !Array.isArray(value)
diff --git a/apps/web/package.json b/apps/web/package.json
index 372c956ec04e..64012d9bdab3 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -29,6 +29,7 @@
"@t3tools/client-runtime": "workspace:*",
"@t3tools/contracts": "workspace:*",
"@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-muse": "workspace:*",
"@t3tools/provider-opencode": "workspace:*",
"@t3tools/provider-pi": "workspace:*",
diff --git a/apps/web/src/components/chat/ProviderInstanceIcon.tsx b/apps/web/src/components/chat/ProviderInstanceIcon.tsx
index 5337fbc9cf3f..927f78ceb4f2 100644
--- a/apps/web/src/components/chat/ProviderInstanceIcon.tsx
+++ b/apps/web/src/components/chat/ProviderInstanceIcon.tsx
@@ -3,7 +3,7 @@ import { type CSSProperties, memo } from "react";
import { providerInstanceInitials } from "@t3tools/client-runtime/state/provider-instance-display";
import { ProviderDriverKind } from "@t3tools/contracts";
-import { AntigravityIcon, ClaudeAI, CursorIcon, GrokIcon, Icon, OpenAI } from "../Icons";
+import { AntigravityIcon, ClaudeAI, GrokIcon, Icon, OpenAI } from "../Icons";
import { cn } from "~/lib/utils";
import { providerClients } from "../settings/providerDriverMeta";
@@ -17,7 +17,6 @@ import {
const PROVIDER_ICON_BY_PROVIDER: Partial> = {
[ProviderDriverKind.make("codex")]: OpenAI,
[ProviderDriverKind.make("claudeAgent")]: ClaudeAI,
- [ProviderDriverKind.make("cursor")]: CursorIcon,
[ProviderDriverKind.make("grok")]: GrokIcon,
[ProviderDriverKind.make("antigravity")]: AntigravityIcon,
};
@@ -25,7 +24,6 @@ const PROVIDER_ICON_BY_PROVIDER: Partial> = {
const PROVIDER_TEXT_COLOR_BY_PROVIDER: Partial> = {
[ProviderDriverKind.make("codex")]: "text-black dark:text-white",
[ProviderDriverKind.make("claudeAgent")]: "text-[#d97757]",
- [ProviderDriverKind.make("cursor")]: "text-[#26251E] dark:text-[#EDECEC]",
[ProviderDriverKind.make("grok")]: "text-[#0F0F0F] dark:text-[#F5F5F5]",
[ProviderDriverKind.make("antigravity")]: "text-[#5b87bf]",
};
diff --git a/apps/web/src/components/settings/providerDriverMeta.ts b/apps/web/src/components/settings/providerDriverMeta.ts
index ae70d22ec9d7..aca6ffbc724f 100644
--- a/apps/web/src/components/settings/providerDriverMeta.ts
+++ b/apps/web/src/components/settings/providerDriverMeta.ts
@@ -3,11 +3,11 @@ import {
AntigravitySettings,
ClaudeSettings,
CodexSettings,
- CursorSettings,
GrokSettings,
ProviderDriverKind,
} from "@t3tools/contracts";
import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
+import { cursorClient } from "@t3tools/provider-cursor/client";
import { museClient } from "@t3tools/provider-muse/client";
import { openCodeClient } from "@t3tools/provider-opencode/client";
import { piClient } from "@t3tools/provider-pi/client";
@@ -24,20 +24,7 @@ export const providerClients = makeProviderClientRegistry([
label: "Claude",
settingsSchema: ClaudeSettings,
},
- {
- driverKind: ProviderDriverKind.make("cursor"),
- label: "Cursor",
- settingsSchema: CursorSettings,
- environmentFields: [
- {
- name: "CURSOR_API_KEY",
- label: "Cursor API key",
- description: "Optional. Overrides browser sign-in for this provider.",
- placeholder: "Paste API key",
- sensitive: true,
- },
- ],
- },
+ cursorClient,
{
driverKind: ProviderDriverKind.make("grok"),
label: "Grok",
diff --git a/docs/internals/adding-a-provider.md b/docs/internals/adding-a-provider.md
index 3c6fa7f00649..b442053c8001 100644
--- a/docs/internals/adding-a-provider.md
+++ b/docs/internals/adding-a-provider.md
@@ -115,7 +115,7 @@ A provider lives in its own `packages/provider-` package, with
Settings live in `providerInstances`; new providers are off by default.
- **Package boundaries.** Server code imports only `@t3tools/provider-core/server/*`, contracts,
and shared. It reaches the server through `ProviderHost` (paths, settings, background demand,
- attachments), never through `apps/server`. Tests use `@t3tools/provider-testing`.
+ attachments, stored credentials), never through `apps/server`. Tests use `@t3tools/provider-testing`.
- **Server registration:** the driver's entry in
[`builtInDrivers.ts`](../../apps/server/src/provider/builtInDrivers.ts) and the adapter driver's in
[`builtInProviderAdapterDrivers.ts`](../../apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts).
diff --git a/knip.jsonc b/knip.jsonc
index 24d0f85db881..c3fed7f19200 100644
--- a/knip.jsonc
+++ b/knip.jsonc
@@ -36,7 +36,9 @@
],
// Keep the transitive Effect runtime pinned for standalone npm installs.
// The Vite+ web build prerequisite.
- "ignoreDependencies": ["@effect/platform-node-shared", "@t3tools/web"],
+ // Native and disk-loaded provider packages stay external to the bundle; the
+ // CLI archive and desktop sidecar stage them from this manifest.
+ "ignoreDependencies": ["@effect/platform-node-shared", "@t3tools/web", "@napi-rs/keyring"],
},
"apps/desktop": {
// Electron loads these bundles by filename rather than importing them.
diff --git a/package.json b/package.json
index e261ffedfb35..545a4247c098 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"tc": "vp run -r --concurrency-limit 2 typecheck",
"lint": "vp lint --report-unused-disable-directives",
"knip": "knip --preprocessor ./scripts/knip-schemas.ts",
- "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints",
+ "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --workspace packages/provider-cursor --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints",
"knip:production": "knip --production --preprocessor ./scripts/knip-schemas.ts",
"lint:mobile": "node scripts/mobile-native-static-check.ts",
"test": "vp run -r test",
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
index a302412f179e..c43df190af3f 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
@@ -707,32 +707,6 @@ export const ClaudeSettings = makeProviderSettingsSchema(
);
export type ClaudeSettings = typeof ClaudeSettings.Type;
-export const CursorSettings = makeProviderSettingsSchema(
- {
- // Off by default like Grok and OpenCode. Users opt in from Settings.
- enabled: Schema.Boolean.pipe(
- Schema.withDecodingDefault(Effect.succeed(false)),
- Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
- ),
- // Keep V1's CLI configuration when V2 rewrites the shared settings file.
- // V2's Cursor SDK does not use these fields.
- binaryPath: Schema.optionalKey(TrimmedString).pipe(
- Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
- ),
- apiEndpoint: Schema.optionalKey(TrimmedString).pipe(
- Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
- ),
- customModels: Schema.Array(CustomModelSetting).pipe(
- Schema.withDecodingDefault(Effect.succeed([])),
- Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
- ),
- },
- {
- order: [],
- },
-);
-export type CursorSettings = typeof CursorSettings.Type;
-
export const GrokSettings = makeProviderSettingsSchema(
{
// Off by default (like Cursor and OpenCode): the binding is not yet
diff --git a/packages/provider-core/src/server/ProviderHost.ts b/packages/provider-core/src/server/ProviderHost.ts
index f0a695da43d4..318037cdcb3b 100644
--- a/packages/provider-core/src/server/ProviderHost.ts
+++ b/packages/provider-core/src/server/ProviderHost.ts
@@ -17,9 +17,12 @@ import type {
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import type * as Effect from "effect/Effect";
+import type * as Option from "effect/Option";
import type * as Scope from "effect/Scope";
import type * as Stream from "effect/Stream";
+import type { ProviderCredentialError } from "./errors.ts";
+
export interface ProviderHostPaths {
/** Directory the server was started in; the fallback cwd for sessions without a project. */
readonly cwd: string;
@@ -31,6 +34,15 @@ export interface ProviderHostPaths {
readonly providerStatusCacheDir: string;
}
+/** One opaque credential blob in the server's secret store. Only the provider decodes it. */
+export interface ProviderCredentials {
+ /** Instances whose bindings are equal share these credentials. */
+ readonly binding: { readonly owner: "t3"; readonly key: string };
+ readonly get: Effect.Effect, ProviderCredentialError>;
+ readonly set: (credentials: Uint8Array) => Effect.Effect;
+ readonly remove: Effect.Effect;
+}
+
export interface ProviderHostShape {
readonly paths: ProviderHostPaths;
readonly settings: {
@@ -47,6 +59,11 @@ export interface ProviderHostShape {
* resolve inside the attachments directory.
*/
readonly resolveAttachmentPath: (attachment: ChatAttachment) => string | null;
+ /** The credentials stored for `bindingId`, usually an instance id, under a provider-chosen namespace. */
+ readonly credentials: (
+ namespace: string,
+ bindingId: string,
+ ) => Effect.Effect;
}
export class ProviderHost extends Context.Service()(
diff --git a/packages/provider-core/src/server/errors.ts b/packages/provider-core/src/server/errors.ts
index eda2eaa686ec..46ca6e6541fe 100644
--- a/packages/provider-core/src/server/errors.ts
+++ b/packages/provider-core/src/server/errors.ts
@@ -19,3 +19,16 @@ export class ProviderDriverError extends Schema.TaggedError
return `Provider driver '${this.driver}' failed to create instance '${this.instanceId}': ${this.detail}`;
}
}
+
+/** Reading or writing a provider's stored credentials failed. */
+export class ProviderCredentialError extends Schema.TaggedError()(
+ "ProviderCredentialError",
+ {
+ operation: Schema.Literals(["get", "set", "remove"]),
+ cause: Schema.Defect(),
+ },
+) {
+ override get message(): string {
+ return `Could not ${this.operation} stored provider credentials.`;
+ }
+}
diff --git a/packages/provider-core/src/server/managedProvider.test.ts b/packages/provider-core/src/server/managedProvider.test.ts
index a4d7dc752f1c..6d9e3827531b 100644
--- a/packages/provider-core/src/server/managedProvider.test.ts
+++ b/packages/provider-core/src/server/managedProvider.test.ts
@@ -117,6 +117,7 @@ function layerProviderHost(input: {
},
shouldRunBackgroundWork: () => Effect.succeed(input.runBackgroundWork),
resolveAttachmentPath: () => null,
+ credentials: () => Effect.die("unused"),
}),
);
}
diff --git a/packages/provider-cursor/package.json b/packages/provider-cursor/package.json
new file mode 100644
index 000000000000..4a50b9647332
--- /dev/null
+++ b/packages/provider-cursor/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@t3tools/provider-cursor",
+ "private": true,
+ "type": "module",
+ "exports": {
+ "./client": {
+ "types": "./src/client.ts",
+ "import": "./src/client.ts"
+ },
+ "./settings": {
+ "types": "./src/settings.ts",
+ "import": "./src/settings.ts"
+ },
+ "./server": {
+ "types": "./src/server.ts",
+ "import": "./src/server.ts"
+ },
+ "./testing": {
+ "types": "./src/testing.ts",
+ "import": "./src/testing.ts"
+ },
+ "./server/CursorAgentSdk": {
+ "types": "./src/server/CursorAgentSdk.ts",
+ "import": "./src/server/CursorAgentSdk.ts"
+ }
+ },
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "test": "vp test run --config ../../vite.config.ts --dir ."
+ },
+ "dependencies": {
+ "@cursor/sdk": "1.0.35",
+ "@napi-rs/keyring": "^1.3.0",
+ "@t3tools/contracts": "workspace:*",
+ "@t3tools/provider-core": "workspace:*",
+ "@t3tools/shared": "workspace:*",
+ "effect": "catalog:",
+ "yaml": "catalog:"
+ },
+ "devDependencies": {
+ "@effect/platform-node": "catalog:",
+ "@effect/vitest": "catalog:",
+ "@t3tools/provider-testing": "workspace:*",
+ "@types/node": "catalog:",
+ "vite-plus": "catalog:"
+ }
+}
diff --git a/packages/provider-cursor/src/client.ts b/packages/provider-cursor/src/client.ts
new file mode 100644
index 000000000000..c76378ab1739
--- /dev/null
+++ b/packages/provider-cursor/src/client.ts
@@ -0,0 +1,34 @@
+/**
+ * Cursor's client definition: settings form, label, sign-in environment
+ * field, and glyph. Browser- and React Native-safe.
+ *
+ * @module provider-cursor/client
+ */
+import { ProviderDriverKind } from "@t3tools/contracts";
+import { defineProviderClient } from "@t3tools/provider-core/client";
+
+import { CursorSettings } from "./settings.ts";
+
+export const cursorClient = defineProviderClient({
+ driverKind: ProviderDriverKind.make("cursor"),
+ label: "Cursor",
+ settingsSchema: CursorSettings,
+ environmentFields: [
+ {
+ name: "CURSOR_API_KEY",
+ label: "Cursor API key",
+ description: "Optional. Overrides browser sign-in for this provider.",
+ placeholder: "Paste API key",
+ sensitive: true,
+ },
+ ],
+ icon: {
+ viewBox: "0 0 466.73 532.09",
+ fill: { light: "#26251E", dark: "#EDECEC" },
+ paths: [
+ {
+ d: "M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z",
+ },
+ ],
+ },
+});
diff --git a/packages/provider-cursor/src/server.ts b/packages/provider-cursor/src/server.ts
new file mode 100644
index 000000000000..471737aa58fd
--- /dev/null
+++ b/packages/provider-cursor/src/server.ts
@@ -0,0 +1,10 @@
+/**
+ * Cursor's server entry: the driver the server registers, its adapter
+ * driver, the SDK runner layer the server provides once, and the keychain
+ * token reader the usage scanner shares.
+ *
+ * @module provider-cursor/server
+ */
+export { CursorDriver, type CursorDriverEnv } from "./server/driver.ts";
+export { CursorAdapterV2Driver, type CursorAdapterV2DriverEnv } from "./server/adapter.ts";
+export { CursorKeychainTimeoutError, readMacCursorAccessToken } from "./server/keychainToken.ts";
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts b/packages/provider-cursor/src/server/CursorAgentSdk.test.ts
similarity index 98%
rename from apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
rename to packages/provider-cursor/src/server/CursorAgentSdk.test.ts
index 5754ec28706c..6a4e209bf192 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
+++ b/packages/provider-cursor/src/server/CursorAgentSdk.test.ts
@@ -5,7 +5,7 @@ import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { vi } from "vite-plus/test";
-import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts";
+import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
import * as CursorAgentSdk from "./CursorAgentSdk.ts";
const cursorSdkMock = vi.hoisted(() => {
@@ -92,7 +92,7 @@ const cursorSdkMock = vi.hoisted(() => {
};
});
-vi.mock("../../provider/cursorSdk.ts", () => ({
+vi.mock("./sdk.ts", () => ({
Agent: {
create: cursorSdkMock.create,
resume: cursorSdkMock.resume,
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts b/packages/provider-cursor/src/server/CursorAgentSdk.ts
similarity index 98%
rename from apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
rename to packages/provider-cursor/src/server/CursorAgentSdk.ts
index 036a93a0e557..30d6090fab4e 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
+++ b/packages/provider-cursor/src/server/CursorAgentSdk.ts
@@ -16,9 +16,8 @@ import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
-import { Agent, createAgentPlatform } from "../../provider/cursorSdk.ts";
-import type { EventNdjsonLogger } from "../../provider/EventNdjsonLogger.ts";
-import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts";
+import { Agent, createAgentPlatform } from "./sdk.ts";
+import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
export const CURSOR_AGENT_SDK_PROTOCOL = "cursor-agent-sdk.local" as const;
export const CURSOR_PROVIDER = ProviderDriverKind.make("cursor");
@@ -77,7 +76,7 @@ export interface CursorAgentSdkRunnerShape {
export class CursorAgentSdkRunner extends Context.Service<
CursorAgentSdkRunner,
CursorAgentSdkRunnerShape
->()("t3/orchestration-v2/Adapters/CursorAgentSdk/CursorAgentSdkRunner") {}
+>()("@t3tools/provider-cursor/server/CursorAgentSdk/CursorAgentSdkRunner") {}
export interface CursorAgentSdkLoggedAgentOptions {
readonly model?: AgentOptions["model"];
@@ -276,7 +275,7 @@ export function loggedCursorSendOptions(
}
function makeCursorAgentSdkProtocolLogger(input: {
- readonly nativeEventLogger: EventNdjsonLogger | undefined;
+ readonly nativeEventLogger: ProviderEventLoggers.EventNdjsonLogger | undefined;
readonly threadId: ThreadId;
readonly providerSessionId: OrchestrationV2ProviderSession["id"];
}): CursorAgentSdkProtocolLogger | undefined {
diff --git a/apps/server/src/provider/CursorSdkCatalog.test.ts b/packages/provider-cursor/src/server/CursorSdkCatalog.test.ts
similarity index 100%
rename from apps/server/src/provider/CursorSdkCatalog.test.ts
rename to packages/provider-cursor/src/server/CursorSdkCatalog.test.ts
diff --git a/apps/server/src/provider/CursorSdkCatalog.ts b/packages/provider-cursor/src/server/CursorSdkCatalog.ts
similarity index 95%
rename from apps/server/src/provider/CursorSdkCatalog.ts
rename to packages/provider-cursor/src/server/CursorSdkCatalog.ts
index e866955dd55f..1a0f6b649496 100644
--- a/apps/server/src/provider/CursorSdkCatalog.ts
+++ b/packages/provider-cursor/src/server/CursorSdkCatalog.ts
@@ -7,7 +7,7 @@ import * as Exit from "effect/Exit";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
-import { AuthenticationError, Cursor, CursorSdkError } from "./cursorSdk.ts";
+import { AuthenticationError, Cursor, CursorSdkError } from "./sdk.ts";
export interface CursorSdkCatalogSnapshot {
readonly user: SDKUser;
@@ -33,7 +33,7 @@ export interface CursorSdkCatalogShape {
}
export class CursorSdkCatalog extends Context.Service()(
- "t3/provider/CursorSdkCatalog",
+ "@t3tools/provider-cursor/server/CursorSdkCatalog",
) {}
function isAuthenticationFailure(cause: unknown): boolean {
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts b/packages/provider-cursor/src/server/adapter.test.ts
similarity index 95%
rename from apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
rename to packages/provider-cursor/src/server/adapter.test.ts
index cf37340d0e18..730cbb7649f8 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
+++ b/packages/provider-cursor/src/server/adapter.test.ts
@@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import type { InteractionUpdate } from "@cursor/sdk";
import {
- CursorSettings,
EnvironmentId,
MessageId,
NodeId,
@@ -13,6 +12,7 @@ import {
RunId,
ThreadId,
} from "@t3tools/contracts";
+import { CursorSettings } from "../settings.ts";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
@@ -22,10 +22,11 @@ import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
-import * as ServerConfig from "../../config.ts";
+import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
+import { layerTestProviderHost } from "@t3tools/provider-testing/host";
import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
-import { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter";
+import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
import {
cursorMcpServers,
cursorRuntimeAgentPolicy,
@@ -33,8 +34,8 @@ import {
makeCursorAgentOptions,
makeCursorAdapterV2,
nestedToolCallFromEnvelope,
-} from "./CursorAdapterV2.ts";
-import { isCursorCancellationError, loggedCursorAgentOptions } from "./CursorAgentSdk.ts";
+} from "./adapter.ts";
+import * as CursorAgentSdk from "./CursorAgentSdk.ts";
const decodeCursorSettings = Schema.decodeEffect(CursorSettings);
@@ -57,7 +58,7 @@ describe("CursorAdapterV2", () => {
const instanceId = ProviderInstanceId.make("cursor");
const threadId = ThreadId.make("cursor-lifecycle-thread");
const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
+ const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
runtimeMode: "full-access",
interactionMode: "default",
cwd: workspace,
@@ -69,10 +70,8 @@ describe("CursorAdapterV2", () => {
fileSystem,
path,
idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(
- ServerConfig.layerTest(workspace, { prefix: "cursor-v2-lifecycle-config-" }),
- ),
+ host: yield* ProviderHost.ProviderHost.pipe(
+ Effect.provide(layerTestProviderHost({ cwd: workspace })),
),
runner: {
assertComplete: Effect.void,
@@ -219,7 +218,7 @@ describe("CursorAdapterV2", () => {
const instanceId = ProviderInstanceId.make("cursor");
const threadId = ThreadId.make("cursor-transport-error-thread");
const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
+ const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
runtimeMode: "full-access",
interactionMode: "default",
cwd: workspace,
@@ -231,8 +230,8 @@ describe("CursorAdapterV2", () => {
fileSystem,
path,
idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(ServerConfig.layerTest(workspace, { prefix: "cursor-v2-error-config-" })),
+ host: yield* ProviderHost.ProviderHost.pipe(
+ Effect.provide(layerTestProviderHost({ cwd: workspace })),
),
runner: {
assertComplete: Effect.void,
@@ -367,7 +366,7 @@ describe("CursorAdapterV2", () => {
const instanceId = ProviderInstanceId.make("cursor");
const threadId = ThreadId.make("cursor-search-thread");
const modelSelection = { instanceId, model: "composer-2.5" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
+ const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
runtimeMode: "full-access",
interactionMode: "default",
cwd: workspace,
@@ -572,8 +571,8 @@ describe("CursorAdapterV2", () => {
fileSystem,
path,
idAllocator: yield* IdAllocator.IdAllocatorV2,
- serverConfig: yield* ServerConfig.ServerConfig.pipe(
- Effect.provide(ServerConfig.layerTest(workspace, { prefix: "cursor-v2-search-config-" })),
+ host: yield* ProviderHost.ProviderHost.pipe(
+ Effect.provide(layerTestProviderHost({ cwd: workspace })),
),
runner: {
assertComplete: Effect.void,
@@ -908,7 +907,7 @@ describe("CursorAdapterV2", () => {
});
assert.deepEqual(options.mcpServers, cursorMcpServers(threadId));
- const logged = JSON.stringify(loggedCursorAgentOptions(options));
+ const logged = JSON.stringify(CursorAgentSdk.loggedCursorAgentOptions(options));
assert.notInclude(logged, "secret-cursor-api-key");
assert.notInclude(logged, "secret-cursor-mcp-token");
} finally {
@@ -917,9 +916,9 @@ describe("CursorAdapterV2", () => {
});
it("recognizes direct and SDK-wrapped abort failures as cancellation", () => {
- assert.isTrue(isCursorCancellationError({ name: "AbortError" }));
+ assert.isTrue(CursorAgentSdk.isCursorCancellationError({ name: "AbortError" }));
assert.isTrue(
- isCursorCancellationError({
+ CursorAgentSdk.isCursorCancellationError({
name: "ConnectError",
cause: {
name: "ConnectError",
@@ -927,8 +926,8 @@ describe("CursorAdapterV2", () => {
},
}),
);
- assert.isFalse(isCursorCancellationError(new Error("request failed")));
- assert.isFalse(isCursorCancellationError(null));
+ assert.isFalse(CursorAgentSdk.isCursorCancellationError(new Error("request failed")));
+ assert.isFalse(CursorAgentSdk.isCursorCancellationError(null));
});
it("preserves failed nested read calls when Cursor omits their path", () => {
diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts b/packages/provider-cursor/src/server/adapter.ts
similarity index 98%
rename from apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
rename to packages/provider-cursor/src/server/adapter.ts
index 273564e786c4..33796270205a 100644
--- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
+++ b/packages/provider-cursor/src/server/adapter.ts
@@ -11,7 +11,6 @@ import type {
import { formatReadToolLabel, formatSearchToolLabel } from "@t3tools/shared/toolActivity";
import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
import {
- CursorSettings,
isOrchestrationV2WorkActive,
defaultInstanceIdForDriver,
type ChatAttachment,
@@ -30,6 +29,7 @@ import {
type ProviderInstanceId,
type ThreadId,
} from "@t3tools/contracts";
+import { CursorSettings } from "../settings.ts";
import * as DateTime from "effect/DateTime";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
@@ -42,17 +42,16 @@ import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
-import { resolveAttachmentPath } from "../../attachmentStore.ts";
-import * as ServerConfig from "../../config.ts";
+import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
-import { CursorTransportFailure } from "../../provider/acp/CursorTransportFailure.ts";
-import { cursorSdkModelSelection } from "../../provider/cursorSdkModel.ts";
+import { CursorTransportFailure } from "./transportFailure.ts";
+import { cursorSdkModelSelection } from "./sdkModel.ts";
import {
discoverCursorSkills,
hasCursorSkillMention,
rewriteCursorSkillMentions,
-} from "../../provider/Drivers/CursorSkills.ts";
+} from "./skills.ts";
import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
import { t3OrchestrationPromptForFirstRun } from "@t3tools/provider-core/server/orchestrationInstructions";
import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
@@ -75,7 +74,7 @@ import {
subagentThreadTitle,
} from "@t3tools/provider-core/server/subagentProjection";
import * as CursorAgentSdk from "./CursorAgentSdk.ts";
-export { cursorSdkModelSelection } from "../../provider/cursorSdkModel.ts";
+export { cursorSdkModelSelection } from "./sdkModel.ts";
export const CURSOR_DRIVER_KIND = CursorAgentSdk.CURSOR_PROVIDER;
export const CURSOR_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(CURSOR_DRIVER_KIND);
@@ -849,13 +848,13 @@ export interface CursorAdapterV2Options {
readonly path: Path.Path;
readonly idAllocator: IdAllocator.IdAllocatorV2Shape;
readonly runner: CursorAgentSdk.CursorAgentSdkRunnerShape;
- readonly serverConfig: ServerConfig.ServerConfig["Service"];
+ readonly host: ProviderHost.ProviderHostShape;
}
export function makeCursorAdapterV2(
adapterOptions: CursorAdapterV2Options,
): ProviderAdapter.ProviderAdapterV2Shape {
- const { fileSystem, path, idAllocator, runner, serverConfig } = adapterOptions;
+ const { fileSystem, path, idAllocator, runner, host } = adapterOptions;
const apiKey = adapterOptions.environment.CURSOR_API_KEY?.trim() || undefined;
return ProviderAdapter.ProviderAdapterV2.of({
@@ -2133,8 +2132,7 @@ export function makeCursorAdapterV2(
? rawText
: rewriteCursorSkillMentions(rawText, cursorSkillNames),
attachments: turnInput.message.attachments,
- resolveAttachmentPath: (attachment) =>
- resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }),
+ resolveAttachmentPath: host.resolveAttachmentPath,
}),
runOrdinal: turnInput.runOrdinal,
hasT3Mcp: cursorMcpServers(turnInput.threadId) !== undefined,
@@ -2143,10 +2141,7 @@ export function makeCursorAdapterV2(
turnInput.message.attachments.filter(isProviderNativeImageAttachment),
(attachment: ChatAttachment) =>
Effect.gen(function* () {
- const path = resolveAttachmentPath({
- attachmentsDir: serverConfig.attachmentsDir,
- attachment,
- });
+ const path = host.resolveAttachmentPath(attachment);
if (path === null) {
return yield* new ProviderAdapter.ProviderAdapterProtocolError({
driver: CursorAgentSdk.CURSOR_PROVIDER,
@@ -2608,7 +2603,7 @@ export type CursorAdapterV2DriverEnv =
| FileSystem.FileSystem
| Path.Path
| IdAllocator.IdAllocatorV2
- | ServerConfig.ServerConfig;
+ | ProviderHost.ProviderHost;
export const CursorAdapterV2Driver: ProviderAdapterDriver<
CursorSettings,
@@ -2624,7 +2619,7 @@ export const CursorAdapterV2Driver: ProviderAdapterDriver<
const path = yield* Path.Path;
const idAllocator = yield* IdAllocator.IdAllocatorV2;
const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const serverConfig = yield* ServerConfig.ServerConfig;
+ const host = yield* ProviderHost.ProviderHost;
return makeCursorAdapterV2({
instanceId: input.instanceId,
settings: {
@@ -2636,7 +2631,7 @@ export const CursorAdapterV2Driver: ProviderAdapterDriver<
path,
idAllocator,
runner,
- serverConfig,
+ host,
});
},
(effect, input) =>
@@ -2661,7 +2656,7 @@ const layer: Layer.Layer<
| FileSystem.FileSystem
| Path.Path
| IdAllocator.IdAllocatorV2
- | ServerConfig.ServerConfig
+ | ProviderHost.ProviderHost
> = Layer.effect(
ProviderAdapter.ProviderAdapterV2,
Effect.gen(function* () {
@@ -2670,7 +2665,7 @@ const layer: Layer.Layer<
const path = yield* Path.Path;
const idAllocator = yield* IdAllocator.IdAllocatorV2;
const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
- const serverConfig = yield* ServerConfig.ServerConfig;
+ const host = yield* ProviderHost.ProviderHost;
return makeCursorAdapterV2({
instanceId: CURSOR_DEFAULT_INSTANCE_ID,
settings: DEFAULT_CURSOR_SETTINGS,
@@ -2679,7 +2674,7 @@ const layer: Layer.Layer<
path,
idAllocator,
runner,
- serverConfig,
+ host,
});
}),
);
diff --git a/apps/server/src/provider/CursorAuth.test.ts b/packages/provider-cursor/src/server/auth.test.ts
similarity index 99%
rename from apps/server/src/provider/CursorAuth.test.ts
rename to packages/provider-cursor/src/server/auth.test.ts
index 6fa7133093c7..3d31211ac5ae 100644
--- a/apps/server/src/provider/CursorAuth.test.ts
+++ b/packages/provider-cursor/src/server/auth.test.ts
@@ -12,8 +12,8 @@ import * as Scope from "effect/Scope";
import * as Stream from "effect/Stream";
import * as TestClock from "effect/testing/TestClock";
-import { makeCursorAuth, type CursorAuthOptions } from "./CursorAuth.ts";
-import { InMemoryCredentialStore } from "./cursorSdk.ts";
+import { makeCursorAuth, type CursorAuthOptions } from "./auth.ts";
+import { InMemoryCredentialStore } from "./sdk.ts";
const instanceId = ProviderInstanceId.make("cursor-personal");
const owner = "owner-client";
diff --git a/apps/server/src/provider/CursorAuth.ts b/packages/provider-cursor/src/server/auth.ts
similarity index 99%
rename from apps/server/src/provider/CursorAuth.ts
rename to packages/provider-cursor/src/server/auth.ts
index 14988170b1eb..ca1c64d9b2c0 100644
--- a/apps/server/src/provider/CursorAuth.ts
+++ b/packages/provider-cursor/src/server/auth.ts
@@ -16,8 +16,8 @@ import * as Semaphore from "effect/Semaphore";
import * as Stream from "effect/Stream";
import * as SubscriptionRef from "effect/SubscriptionRef";
-import { Cursor, InMemoryCredentialStore } from "./cursorSdk.ts";
-import type { ProviderAuthController } from "./ProviderAuthService.ts";
+import { Cursor, InMemoryCredentialStore } from "./sdk.ts";
+import type { ProviderAuthController } from "@t3tools/provider-core/server/auth";
const AUTH_TIMEOUT_MS = 300_000;
diff --git a/apps/server/src/provider/CursorCredentialStore.test.ts b/packages/provider-cursor/src/server/credentialStore.test.ts
similarity index 62%
rename from apps/server/src/provider/CursorCredentialStore.test.ts
rename to packages/provider-cursor/src/server/credentialStore.test.ts
index e3c0995ba072..5e176b661c46 100644
--- a/apps/server/src/provider/CursorCredentialStore.test.ts
+++ b/packages/provider-cursor/src/server/credentialStore.test.ts
@@ -6,28 +6,12 @@ import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Option from "effect/Option";
-import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
-import { makeCursorCredentialStore } from "./CursorCredentialStore.ts";
+import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
+import { layerTestProviderHost } from "@t3tools/provider-testing/host";
+import * as Layer from "effect/Layer";
+import { makeCursorCredentialStore } from "./credentialStore.ts";
-const makeSecrets = () => {
- const data = new Map();
- return {
- data,
- secrets: ServerSecretStore.ServerSecretStore.of({
- get: (key) => Effect.sync(() => Option.fromUndefinedOr(data.get(key))),
- set: (key, bytes) =>
- Effect.sync(() => {
- data.set(key, bytes);
- }),
- remove: (key) =>
- Effect.sync(() => {
- data.delete(key);
- }),
- create: () => Effect.die("unused"),
- getOrCreateRandom: () => Effect.die("unused"),
- }),
- };
-};
+const layerHost = Layer.provideMerge(layerTestProviderHost(), NodeServices.layer);
const legacyCredentials = {
version: 1 as const,
@@ -74,44 +58,23 @@ it.effect.each([
const path = yield* Path.Path;
const legacyFile = path.join(yield* fileSystem.makeTempDirectoryScoped(), "cursor.json");
yield* fileSystem.writeFileString(legacyFile, legacy);
- const { secrets } = makeSecrets();
+ const host = yield* ProviderHost.ProviderHost;
const instanceId = ProviderInstanceId.make("personal");
if (stored !== undefined) {
- const current = yield* makeCursorCredentialStore(instanceId).pipe(
- Effect.provideService(ServerSecretStore.ServerSecretStore, secrets),
- );
- yield* secrets.set(current.binding.key, new TextEncoder().encode(stored));
+ yield* (yield* host.credentials("cursor", instanceId)).set(new TextEncoder().encode(stored));
}
- const migrated = yield* makeCursorCredentialStore(instanceId, legacyFile).pipe(
- Effect.provideService(ServerSecretStore.ServerSecretStore, secrets),
- );
+ const migrated = yield* makeCursorCredentialStore(instanceId, legacyFile);
assert.strictEqual((yield* Effect.tryPromise(() => migrated.store.load()))?.apiKey, expected);
assert.isFalse(yield* fileSystem.exists(legacyFile));
- }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
+ }).pipe(Effect.scoped, Effect.provide(layerHost)),
);
it.effect(
"restores SDK credentials in a new controller and keeps another account when signing out",
() =>
Effect.gen(function* () {
- const data = new Map();
- const secrets = ServerSecretStore.ServerSecretStore.of({
- get: (key) => Effect.sync(() => Option.fromUndefinedOr(data.get(key))),
- set: (key, bytes) =>
- Effect.sync(() => {
- data.set(key, bytes);
- }),
- remove: (key) =>
- Effect.sync(() => {
- data.delete(key);
- }),
- create: () => Effect.die("unused"),
- getOrCreateRandom: () => Effect.die("unused"),
- });
- const makeStore = (id: string) =>
- makeCursorCredentialStore(ProviderInstanceId.make(id)).pipe(
- Effect.provideService(ServerSecretStore.ServerSecretStore, secrets),
- );
+ const host = yield* ProviderHost.ProviderHost;
+ const makeStore = (id: string) => makeCursorCredentialStore(ProviderInstanceId.make(id));
const personal = yield* makeStore("personal");
const work = yield* makeStore("work");
const credentials = {
@@ -128,7 +91,9 @@ it.effect(
);
const restored = yield* makeStore("personal");
assert.deepEqual(yield* Effect.tryPromise(() => restored.store.load()), credentials);
- data.set(personal.binding.key, new TextEncoder().encode("damaged credential"));
+ yield* (yield* host.credentials("cursor", "personal")).set(
+ new TextEncoder().encode("damaged credential"),
+ );
assert.isUndefined(yield* Effect.tryPromise(() => restored.store.load()));
yield* Effect.tryPromise(() => restored.store.save(credentials));
yield* Effect.tryPromise(() => restored.store.clear());
@@ -137,5 +102,5 @@ it.effect(
(yield* Effect.tryPromise(() => work.store.load()))?.apiKey,
"test-only-work-key",
);
- }).pipe(Effect.provide(NodeServices.layer)),
+ }).pipe(Effect.provide(layerHost)),
);
diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/packages/provider-cursor/src/server/credentialStore.ts
similarity index 94%
rename from apps/server/src/provider/CursorCredentialStore.ts
rename to packages/provider-cursor/src/server/credentialStore.ts
index 56c3014deedb..70277a54e216 100644
--- a/apps/server/src/provider/CursorCredentialStore.ts
+++ b/packages/provider-cursor/src/server/credentialStore.ts
@@ -5,7 +5,7 @@ import * as FileSystem from "effect/FileSystem";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
-import * as ProviderCredentialStore from "./ProviderCredentialStore.ts";
+import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
const Credentials = Schema.fromJsonString(
Schema.Struct({
@@ -30,7 +30,7 @@ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")(
instanceId: ProviderInstanceId,
legacyFile?: string,
) {
- const credentials = yield* ProviderCredentialStore.make("cursor", instanceId);
+ const credentials = yield* (yield* ProviderHost.ProviderHost).credentials("cursor", instanceId);
if (legacyFile !== undefined) {
const fileSystem = yield* FileSystem.FileSystem;
yield* Effect.gen(function* () {
diff --git a/apps/server/src/provider/Drivers/CursorDriver.test.ts b/packages/provider-cursor/src/server/driver.test.ts
similarity index 84%
rename from apps/server/src/provider/Drivers/CursorDriver.test.ts
rename to packages/provider-cursor/src/server/driver.test.ts
index 8aae15d1c575..b7320ce8fa98 100644
--- a/apps/server/src/provider/Drivers/CursorDriver.test.ts
+++ b/packages/provider-cursor/src/server/driver.test.ts
@@ -1,5 +1,4 @@
// @effect-diagnostics nodeBuiltinImport:off
-import * as ServerSecretStore from "../../auth/ServerSecretStore.ts";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { expect, it } from "@effect/vitest";
import { ProviderInstanceId, ProviderSessionId, ThreadId } from "@t3tools/contracts";
@@ -11,23 +10,15 @@ import { vi } from "vite-plus/test";
import { HttpClient } from "effect/http";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
-import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
-import * as ServerConfig from "../../config.ts";
-import * as ServerSettings from "../../serverSettings.ts";
-import * as ProviderEventLoggers from "../ProviderEventLoggers.ts";
-import { CursorDriver } from "./CursorDriver.ts";
-import * as CursorAgentSdk from "../../orchestration-v2/Adapters/CursorAgentSdk.ts";
+import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
+import { layerTestProviderHost } from "@t3tools/provider-testing/host";
+import { CursorDriver } from "./driver.ts";
+import * as CursorAgentSdk from "./CursorAgentSdk.ts";
import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
-import { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter";
-import { Cursor } from "../cursorSdk.ts";
-import * as ProviderHostLive from "../ProviderHostLive.ts";
+import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
+import { Cursor } from "./sdk.ts";
-const layerDeps = ServerSecretStore.layer.pipe(
- Layer.provideMerge(
- ServerConfig.layerTest(process.cwd(), {
- prefix: "t3-cursor-driver-copy-command-",
- }),
- ),
+const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
Layer.provideMerge(NodeServices.layer),
Layer.provideMerge(IdAllocator.layer),
Layer.provideMerge(
@@ -35,12 +26,6 @@ const layerDeps = ServerSecretStore.layer.pipe(
open: () => Effect.die("Maintenance resolution must not open a Cursor session"),
}),
),
- Layer.provideMerge(ServerSettings.layerTest()),
- Layer.provideMerge(
- Layer.mock(BackgroundPolicy.BackgroundPolicy)({
- shouldRunScopeWork: () => Effect.succeed(false),
- }),
- ),
Layer.provideMerge(
Layer.succeed(
ProviderEventLoggers.ProviderEventLoggers,
@@ -54,7 +39,6 @@ const layerDeps = ServerSecretStore.layer.pipe(
),
),
);
-const layerTest = ProviderHostLive.layer.pipe(Layer.provideMerge(layerDeps));
it.layer(layerTest)("CursorDriver", (it) => {
it.effect(
@@ -134,7 +118,7 @@ it.layer(layerTest)("CursorDriver", (it) => {
});
const threadId = ThreadId.make("cursor-browser-thread");
const modelSelection = { instanceId: input.instanceId, model: "auto" };
- const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
+ const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
runtimeMode: "full-access",
interactionMode: "default",
cwd: process.cwd(),
diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/packages/provider-cursor/src/server/driver.ts
similarity index 86%
rename from apps/server/src/provider/Drivers/CursorDriver.ts
rename to packages/provider-cursor/src/server/driver.ts
index cf4d82bcfdb1..157b7dca5c1e 100644
--- a/apps/server/src/provider/Drivers/CursorDriver.ts
+++ b/packages/provider-cursor/src/server/driver.ts
@@ -6,29 +6,22 @@
*
* @module provider/Drivers/CursorDriver
*/
-import { CursorSettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";
+import { ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";
+import { CursorSettings } from "../settings.ts";
import * as Effect from "effect/Effect";
import * as Crypto from "effect/Crypto";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { HttpClient } from "effect/http";
-import { readCursorUsageLimits } from "../cursorUsageLimits.ts";
+import { readCursorUsageLimits } from "./usageLimits.ts";
-import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost";
-import * as ServerConfig from "../../config.ts";
-import * as ServerSettings from "../../serverSettings.ts";
-import { makeCursorTextGeneration } from "../../textGeneration/CursorTextGeneration.ts";
-import {
- CursorAdapterV2Driver,
- type CursorAdapterV2DriverEnv,
-} from "../../orchestration-v2/Adapters/CursorAdapterV2.ts";
-import { ProviderDriverError } from "../Errors.ts";
-import {
- buildInitialCursorProviderSnapshot,
- checkCursorProviderStatus,
-} from "../CursorProvider.ts";
-import * as CursorSdkCatalog from "../CursorSdkCatalog.ts";
+import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
+import { makeCursorTextGeneration } from "./textGeneration.ts";
+import { CursorAdapterV2Driver, type CursorAdapterV2DriverEnv } from "./adapter.ts";
+import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
+import { buildInitialCursorProviderSnapshot, checkCursorProviderStatus } from "./status.ts";
+import * as CursorSdkCatalog from "./CursorSdkCatalog.ts";
import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider";
import {
defaultProviderContinuationIdentity,
@@ -43,11 +36,10 @@ import {
makeProviderSnapshotSettingsSource,
type ProviderSnapshotSettings,
} from "@t3tools/provider-core/server/snapshotSettings";
-import { probeCursorSkills } from "./CursorSkills.ts";
-import { makeCursorAuth } from "../CursorAuth.ts";
-import * as CursorCredentialStore from "../CursorCredentialStore.ts";
-import * as ServerSecretStore from "../../auth/ServerSecretStore.ts";
-import * as CursorAgentSdk from "../../orchestration-v2/Adapters/CursorAgentSdk.ts";
+import { probeCursorSkills } from "./skills.ts";
+import { makeCursorAuth } from "./auth.ts";
+import * as CursorCredentialStore from "./credentialStore.ts";
+import * as CursorAgentSdk from "./CursorAgentSdk.ts";
const decodeCursorSettings = Schema.decodeSync(CursorSettings);
const isSdkRunnerError = Schema.is(CursorAgentSdk.CursorAgentSdkRunnerError);
@@ -63,10 +55,7 @@ export type CursorDriverEnv =
| FileSystem.FileSystem
| Path.Path
| HttpClient.HttpClient
- | ProviderHost
- | ServerConfig.ServerConfig
- | ServerSecretStore.ServerSecretStore
- | ServerSettings.ServerSettingsService;
+ | ProviderHost.ProviderHost;
export const CursorDriver: ProviderDriver = {
driverKind: DRIVER_KIND,
@@ -78,8 +67,7 @@ export const CursorDriver: ProviderDriver = {
defaultConfig: (): CursorSettings => decodeCursorSettings({}),
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
Effect.gen(function* () {
- const serverSettings = yield* ServerSettings.ServerSettingsService;
- const host = yield* ProviderHost;
+ const host = yield* ProviderHost.ProviderHost;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const httpClient = yield* HttpClient.HttpClient;
@@ -100,7 +88,7 @@ export const CursorDriver: ProviderDriver = {
const credentials = yield* CursorCredentialStore.makeCursorCredentialStore(
instanceId,
path.join(
- (yield* ServerConfig.ServerConfig).stateDir,
+ host.paths.stateDir,
"provider-auth",
encodeURIComponent(instanceId),
"cursor.json",
@@ -214,7 +202,7 @@ export const CursorDriver: ProviderDriver = {
effectiveConfig.enabled &&
snapshot.installed &&
snapshot.auth.status === "authenticated"
- ? serverSettings.getSettings.pipe(
+ ? host.settings.get.pipe(
Effect.flatMap((settings) =>
readCursorUsageLimits(
effectiveConfig,
diff --git a/apps/server/src/provider/cursorKeychainToken.test.ts b/packages/provider-cursor/src/server/keychainToken.test.ts
similarity index 91%
rename from apps/server/src/provider/cursorKeychainToken.test.ts
rename to packages/provider-cursor/src/server/keychainToken.test.ts
index 6c2e68d7b427..7f279bb60892 100644
--- a/apps/server/src/provider/cursorKeychainToken.test.ts
+++ b/packages/provider-cursor/src/server/keychainToken.test.ts
@@ -1,9 +1,6 @@
import { assert, describe, it } from "@effect/vitest";
-import {
- CursorKeychainTimeoutError,
- makeCachedCursorAccessTokenReader,
-} from "./cursorKeychainToken.ts";
+import { CursorKeychainTimeoutError, makeCachedCursorAccessTokenReader } from "./keychainToken.ts";
describe("Cursor Keychain reader", () => {
it("shares concurrent reads and rechecks after the cache expires", async () => {
diff --git a/apps/server/src/provider/cursorKeychainToken.ts b/packages/provider-cursor/src/server/keychainToken.ts
similarity index 100%
rename from apps/server/src/provider/cursorKeychainToken.ts
rename to packages/provider-cursor/src/server/keychainToken.ts
diff --git a/apps/server/src/provider/cursorSdk.test.ts b/packages/provider-cursor/src/server/sdk.test.ts
similarity index 97%
rename from apps/server/src/provider/cursorSdk.test.ts
rename to packages/provider-cursor/src/server/sdk.test.ts
index 6a34ee9fc40e..06a9b6229fc5 100644
--- a/apps/server/src/provider/cursorSdk.test.ts
+++ b/packages/provider-cursor/src/server/sdk.test.ts
@@ -2,9 +2,9 @@
import * as NodeChildProcess from "node:child_process";
import { describe, expect, it } from "vite-plus/test";
-const cursorSdkUrl = new URL("./cursorSdk.ts", import.meta.url).href;
+const cursorSdkUrl = new URL("./sdk.ts", import.meta.url).href;
-// Loads cursorSdk.ts in a fresh process. @cursor/sdk is stubbed so the guard
+// Loads sdk.ts in a fresh process. @cursor/sdk is stubbed so the guard
// can be tested without the real package. The vitest worker already has its
// own unhandledRejection listener, which would hide the process-exit behavior.
const probeProgram = `
diff --git a/apps/server/src/provider/cursorSdk.ts b/packages/provider-cursor/src/server/sdk.ts
similarity index 100%
rename from apps/server/src/provider/cursorSdk.ts
rename to packages/provider-cursor/src/server/sdk.ts
diff --git a/apps/server/src/provider/cursorSdkModel.ts b/packages/provider-cursor/src/server/sdkModel.ts
similarity index 100%
rename from apps/server/src/provider/cursorSdkModel.ts
rename to packages/provider-cursor/src/server/sdkModel.ts
diff --git a/apps/server/src/provider/Drivers/CursorSkills.test.ts b/packages/provider-cursor/src/server/skills.test.ts
similarity index 99%
rename from apps/server/src/provider/Drivers/CursorSkills.test.ts
rename to packages/provider-cursor/src/server/skills.test.ts
index 382f9c6dc54e..7831e5909e0f 100644
--- a/apps/server/src/provider/Drivers/CursorSkills.test.ts
+++ b/packages/provider-cursor/src/server/skills.test.ts
@@ -12,7 +12,7 @@ import {
hasCursorSkillMention,
probeCursorSkills,
rewriteCursorSkillMentions,
-} from "./CursorSkills.ts";
+} from "./skills.ts";
const runNode = (
effect: Effect.Effect,
diff --git a/apps/server/src/provider/Drivers/CursorSkills.ts b/packages/provider-cursor/src/server/skills.ts
similarity index 100%
rename from apps/server/src/provider/Drivers/CursorSkills.ts
rename to packages/provider-cursor/src/server/skills.ts
diff --git a/apps/server/src/provider/CursorProvider.test.ts b/packages/provider-cursor/src/server/status.test.ts
similarity index 97%
rename from apps/server/src/provider/CursorProvider.test.ts
rename to packages/provider-cursor/src/server/status.test.ts
index 7fe35db9ea32..32eedf9e3a9c 100644
--- a/apps/server/src/provider/CursorProvider.test.ts
+++ b/packages/provider-cursor/src/server/status.test.ts
@@ -2,8 +2,8 @@ import type { SDKModel } from "@cursor/sdk";
import { describe, expect, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
-import type { CursorSettings } from "@t3tools/contracts";
-import { CursorSettings as CursorSettingsSchema } from "@t3tools/contracts";
+import type { CursorSettings } from "../settings.ts";
+import { CursorSettings as CursorSettingsSchema } from "../settings.ts";
import { createModelCapabilities } from "@t3tools/shared/model";
import {
@@ -12,7 +12,7 @@ import {
buildCursorProviderSnapshot,
buildInitialCursorProviderSnapshot,
checkCursorProviderStatus,
-} from "./CursorProvider.ts";
+} from "./status.ts";
import * as CursorSdkCatalog from "./CursorSdkCatalog.ts";
const decodeCursorSettings = Schema.decodeSync(CursorSettingsSchema);
diff --git a/apps/server/src/provider/CursorProvider.ts b/packages/provider-cursor/src/server/status.ts
similarity index 99%
rename from apps/server/src/provider/CursorProvider.ts
rename to packages/provider-cursor/src/server/status.ts
index 473a7bb8c5aa..3b00118688df 100644
--- a/apps/server/src/provider/CursorProvider.ts
+++ b/packages/provider-cursor/src/server/status.ts
@@ -1,19 +1,19 @@
import type { SDKModel, SDKUser } from "@cursor/sdk";
import type {
- CursorSettings,
ModelCapabilities,
ProviderOptionDescriptor,
ServerProviderAuth,
ServerProviderModel,
ServerProviderState,
} from "@t3tools/contracts";
+import type { CursorSettings } from "../settings.ts";
import { createModelCapabilities } from "@t3tools/shared/model";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Result from "effect/Result";
-import { cursorSdkParameterPriority, cursorSdkProviderOptionId } from "./cursorSdkModel.ts";
+import { cursorSdkParameterPriority, cursorSdkProviderOptionId } from "./sdkModel.ts";
import {
buildBooleanOptionDescriptor,
buildSelectOptionDescriptor,
diff --git a/apps/server/src/textGeneration/CursorTextGeneration.test.ts b/packages/provider-cursor/src/server/textGeneration.test.ts
similarity index 98%
rename from apps/server/src/textGeneration/CursorTextGeneration.test.ts
rename to packages/provider-cursor/src/server/textGeneration.test.ts
index 4b3ce0eebbe5..8b007c9dbaf7 100644
--- a/apps/server/src/textGeneration/CursorTextGeneration.test.ts
+++ b/packages/provider-cursor/src/server/textGeneration.test.ts
@@ -1,5 +1,6 @@
import type { RunResult } from "@cursor/sdk";
-import { CursorSettings, ProviderInstanceId, TextGenerationError } from "@t3tools/contracts";
+import { ProviderInstanceId, TextGenerationError } from "@t3tools/contracts";
+import { CursorSettings } from "../settings.ts";
import { describe, expect, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
@@ -9,7 +10,7 @@ import * as Schema from "effect/Schema";
import { createModelSelection } from "@t3tools/shared/model";
import { beforeEach, vi } from "vite-plus/test";
-import { makeCursorTextGeneration } from "./CursorTextGeneration.ts";
+import { makeCursorTextGeneration } from "./textGeneration.ts";
const cursorSdkMock = vi.hoisted(() => ({
create: vi.fn<(options: unknown) => Promise>(),
@@ -24,7 +25,7 @@ const cursorSdkMock = vi.hoisted(() => ({
})),
}));
-vi.mock("../provider/cursorSdk.ts", () => ({ Agent: { create: cursorSdkMock.create } }));
+vi.mock("./sdk.ts", () => ({ Agent: { create: cursorSdkMock.create } }));
let hasCustomPolicy = false;
const layerFs = FileSystem.layerNoop({
diff --git a/apps/server/src/textGeneration/CursorTextGeneration.ts b/packages/provider-cursor/src/server/textGeneration.ts
similarity index 95%
rename from apps/server/src/textGeneration/CursorTextGeneration.ts
rename to packages/provider-cursor/src/server/textGeneration.ts
index fb796438158b..924d620bbc76 100644
--- a/apps/server/src/textGeneration/CursorTextGeneration.ts
+++ b/packages/provider-cursor/src/server/textGeneration.ts
@@ -2,20 +2,17 @@ import * as NodeOS from "node:os";
import * as FileSystem from "effect/FileSystem";
import type { AgentOptions, RunResult } from "@cursor/sdk";
-import { Agent } from "../provider/cursorSdk.ts";
+import { Agent } from "./sdk.ts";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
-import {
- type CursorSettings,
- type ProviderSetupError,
- TextGenerationError,
-} from "@t3tools/contracts";
+import { type ProviderSetupError, TextGenerationError } from "@t3tools/contracts";
+import type { CursorSettings } from "../settings.ts";
import * as TextGenerationOperations from "@t3tools/provider-core/server/textGenerationOperations";
-import { cursorSdkModelSelection } from "../provider/cursorSdkModel.ts";
-import type { CursorAuth } from "../provider/CursorAuth.ts";
+import { cursorSdkModelSelection } from "./sdkModel.ts";
+import type { CursorAuth } from "./auth.ts";
const CURSOR_TIMEOUT_MS = 180_000;
diff --git a/apps/server/src/provider/acp/CursorTransportFailure.test.ts b/packages/provider-cursor/src/server/transportFailure.test.ts
similarity index 97%
rename from apps/server/src/provider/acp/CursorTransportFailure.test.ts
rename to packages/provider-cursor/src/server/transportFailure.test.ts
index fdde19b934d2..91d4fb9f17e1 100644
--- a/apps/server/src/provider/acp/CursorTransportFailure.test.ts
+++ b/packages/provider-cursor/src/server/transportFailure.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vite-plus/test";
-import { CursorTransportFailure } from "./CursorTransportFailure.ts";
+import { CursorTransportFailure } from "./transportFailure.ts";
const diagnostic = "Error: RetriableError: WritableIterable is closed";
diff --git a/apps/server/src/provider/acp/CursorTransportFailure.ts b/packages/provider-cursor/src/server/transportFailure.ts
similarity index 100%
rename from apps/server/src/provider/acp/CursorTransportFailure.ts
rename to packages/provider-cursor/src/server/transportFailure.ts
diff --git a/apps/server/src/provider/cursorUsageLimits.test.ts b/packages/provider-cursor/src/server/usageLimits.test.ts
similarity index 99%
rename from apps/server/src/provider/cursorUsageLimits.test.ts
rename to packages/provider-cursor/src/server/usageLimits.test.ts
index 5ce2cee7fe8c..634168447d9c 100644
--- a/apps/server/src/provider/cursorUsageLimits.test.ts
+++ b/packages/provider-cursor/src/server/usageLimits.test.ts
@@ -5,7 +5,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import { HttpClient, HttpClientResponse } from "effect/http";
-import { cursorUsageResponseToLimits, readCursorUsageLimits } from "./cursorUsageLimits.ts";
+import { cursorUsageResponseToLimits, readCursorUsageLimits } from "./usageLimits.ts";
const withNodeServices = (effect: Effect.Effect) =>
effect.pipe(Effect.provide(NodeServices.layer));
diff --git a/apps/server/src/provider/cursorUsageLimits.ts b/packages/provider-cursor/src/server/usageLimits.ts
similarity index 97%
rename from apps/server/src/provider/cursorUsageLimits.ts
rename to packages/provider-cursor/src/server/usageLimits.ts
index 987eeda74850..24c314fe7d7d 100644
--- a/apps/server/src/provider/cursorUsageLimits.ts
+++ b/packages/provider-cursor/src/server/usageLimits.ts
@@ -1,5 +1,6 @@
import * as NodeOS from "node:os";
-import type { CursorSettings, ServerProviderUsageWindow } from "@t3tools/contracts";
+import type { ServerProviderUsageWindow } from "@t3tools/contracts";
+import type { CursorSettings } from "../settings.ts";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import { CURSOR_USAGE_WINDOWS } from "@t3tools/shared/usageLimits";
import * as DateTime from "effect/DateTime";
@@ -14,7 +15,7 @@ import {
makeUnavailableUsageLimits,
makeUsageLimits,
} from "@t3tools/provider-core/server/usageLimits";
-import { readMacCursorAccessToken } from "./cursorKeychainToken.ts";
+import { readMacCursorAccessToken } from "./keychainToken.ts";
const CursorCredentials = Schema.Struct({ accessToken: Schema.optional(Schema.String) });
const DEFAULT_CURSOR_API_ENDPOINT = "https://api2.cursor.sh";
diff --git a/packages/provider-cursor/src/settings.ts b/packages/provider-cursor/src/settings.ts
new file mode 100644
index 000000000000..d4d5017d4f89
--- /dev/null
+++ b/packages/provider-cursor/src/settings.ts
@@ -0,0 +1,35 @@
+/**
+ * Cursor instance settings. Shared by the server driver and the client
+ * settings form, so it holds only browser-safe schema code.
+ *
+ * @module provider-cursor/settings
+ */
+import { CustomModelSetting, makeProviderSettingsSchema, TrimmedString } from "@t3tools/contracts";
+import * as Effect from "effect/Effect";
+import * as Schema from "effect/Schema";
+
+export const CursorSettings = makeProviderSettingsSchema(
+ {
+ // Off by default like Grok and OpenCode. Users opt in from Settings.
+ enabled: Schema.Boolean.pipe(
+ Schema.withDecodingDefault(Effect.succeed(false)),
+ Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
+ ),
+ // Keep V1's CLI configuration when V2 rewrites the shared settings file.
+ // V2's Cursor SDK does not use these fields.
+ binaryPath: Schema.optionalKey(TrimmedString).pipe(
+ Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
+ ),
+ apiEndpoint: Schema.optionalKey(TrimmedString).pipe(
+ Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
+ ),
+ customModels: Schema.Array(CustomModelSetting).pipe(
+ Schema.withDecodingDefault(Effect.succeed([])),
+ Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
+ ),
+ },
+ {
+ order: [],
+ },
+);
+export type CursorSettings = typeof CursorSettings.Type;
diff --git a/packages/provider-cursor/src/testing.ts b/packages/provider-cursor/src/testing.ts
new file mode 100644
index 000000000000..1357074782f5
--- /dev/null
+++ b/packages/provider-cursor/src/testing.ts
@@ -0,0 +1,13 @@
+/**
+ * Cursor internals the server's replay testkit, fixture recorder, and
+ * orchestration tests use directly.
+ *
+ * @module provider-cursor/testing
+ */
+export {
+ CURSOR_DEFAULT_INSTANCE_ID,
+ CURSOR_DRIVER_KIND,
+ CursorProviderCapabilitiesV2,
+ cursorSdkModelSelection,
+ makeCursorAgentOptions,
+} from "./server/adapter.ts";
diff --git a/packages/provider-cursor/tsconfig.json b/packages/provider-cursor/tsconfig.json
new file mode 100644
index 000000000000..374bac55202d
--- /dev/null
+++ b/packages/provider-cursor/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "types": ["node"]
+ },
+ "include": ["src"]
+}
diff --git a/packages/provider-testing/src/host.ts b/packages/provider-testing/src/host.ts
index 6c9a9e113472..eb1d07af6c86 100644
--- a/packages/provider-testing/src/host.ts
+++ b/packages/provider-testing/src/host.ts
@@ -10,6 +10,7 @@ import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
+import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Stream from "effect/Stream";
@@ -39,6 +40,7 @@ export const layerTestProviderHost = (
yield* fileSystem.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
}
const settings = options.settings ?? DEFAULT_SERVER_SETTINGS;
+ const credentials = new Map();
return ProviderHost.ProviderHost.of({
paths: { cwd: options.cwd ?? process.cwd(), baseDir, stateDir, providerStatusCacheDir },
settings: {
@@ -49,6 +51,17 @@ export const layerTestProviderHost = (
shouldRunBackgroundWork: () => Effect.succeed(options.runBackgroundWork ?? true),
// Tests store attachments flat under the attachments directory by id.
resolveAttachmentPath: (attachment) => path.join(attachmentsDir, attachment.id),
+ // Credentials live in memory for the layer's lifetime.
+ credentials: (namespace, bindingId) =>
+ Effect.sync(() => {
+ const key = `${namespace}:${bindingId}`;
+ return {
+ binding: { owner: "t3" as const, key },
+ get: Effect.sync(() => Option.fromUndefinedOr(credentials.get(key))),
+ set: (value: Uint8Array) => Effect.sync(() => void credentials.set(key, value)),
+ remove: Effect.sync(() => void credentials.delete(key)),
+ };
+ }),
});
}),
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 39a350f843f4..85e33ad5cabe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -356,6 +356,9 @@ importers:
'@t3tools/provider-core':
specifier: workspace:*
version: link:../../packages/provider-core
+ '@t3tools/provider-cursor':
+ specifier: workspace:*
+ version: link:../../packages/provider-cursor
'@t3tools/provider-muse':
specifier: workspace:*
version: link:../../packages/provider-muse
@@ -600,6 +603,9 @@ importers:
'@t3tools/provider-core':
specifier: workspace:*
version: link:../../packages/provider-core
+ '@t3tools/provider-cursor':
+ specifier: workspace:*
+ version: link:../../packages/provider-cursor
'@t3tools/provider-muse':
specifier: workspace:*
version: link:../../packages/provider-muse
@@ -736,6 +742,9 @@ importers:
'@t3tools/provider-core':
specifier: workspace:*
version: link:../../packages/provider-core
+ '@t3tools/provider-cursor':
+ specifier: workspace:*
+ version: link:../../packages/provider-cursor
'@t3tools/provider-muse':
specifier: workspace:*
version: link:../../packages/provider-muse
@@ -1108,6 +1117,46 @@ importers:
specifier: 1.0.0
version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)
+ packages/provider-cursor:
+ dependencies:
+ '@cursor/sdk':
+ specifier: 1.0.35
+ version: 1.0.35
+ '@napi-rs/keyring':
+ specifier: ^1.3.0
+ version: 1.3.0
+ '@t3tools/contracts':
+ specifier: workspace:*
+ version: link:../contracts
+ '@t3tools/provider-core':
+ specifier: workspace:*
+ version: link:../provider-core
+ '@t3tools/shared':
+ specifier: workspace:*
+ version: link:../shared
+ effect:
+ specifier: 4.0.1
+ version: 4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f)
+ yaml:
+ specifier: ^2.9.0
+ version: 2.9.0
+ devDependencies:
+ '@effect/platform-node':
+ specifier: 4.0.1
+ version: 4.0.1(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(redis@6.2.1)(utf-8-validate@6.0.6)
+ '@effect/vitest':
+ specifier: 4.0.1
+ version: 4.0.1(patch_hash=359f6fb2f7b3ec145bb72208edb9034f02489791aa2491a55cdbd69bd56ee0d2)(@types/node@24.12.4)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)
+ '@t3tools/provider-testing':
+ specifier: workspace:*
+ version: link:../provider-testing
+ '@types/node':
+ specifier: 24.12.4
+ version: 24.12.4
+ vite-plus:
+ specifier: 1.0.0
+ version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)
+
packages/provider-muse:
dependencies:
'@muse-code/sdk':
diff --git a/scripts/lib/cursor-sdk-packaging.test.ts b/scripts/lib/cursor-sdk-packaging.test.ts
index 1eabc6664e1a..efaaa96b4954 100644
--- a/scripts/lib/cursor-sdk-packaging.test.ts
+++ b/scripts/lib/cursor-sdk-packaging.test.ts
@@ -79,7 +79,7 @@ it("loads packaged Cursor catalog chunks without credentials or checkout depende
entry,
`
import assert from 'node:assert/strict';
- import { Cursor } from ${JSON.stringify(NodePath.join(repoRoot, "apps/server/src/provider/cursorSdk.ts"))};
+ import { Cursor } from ${JSON.stringify(NodePath.join(repoRoot, "packages/provider-cursor/src/server/sdk.ts"))};
for (const [operation, request] of [
['Cursor.models.list', () => Cursor.models.list({ apiKey: '' })],
['Cursor.me', () => Cursor.me({ apiKey: '' })],
diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts
index 400fc17efab4..6c5a1707854f 100644
--- a/scripts/release-smoke.ts
+++ b/scripts/release-smoke.ts
@@ -31,6 +31,7 @@ const workspaceFiles = [
"packages/provider-core/package.json",
"packages/provider-testing/package.json",
"packages/provider-pi/package.json",
+ "packages/provider-cursor/package.json",
"packages/provider-muse/package.json",
"packages/provider-opencode/package.json",
"packages/effect-acp/package.json",