From d03963ce86a722c91afb18d1190ac287071d981f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 21 Sep 2026 16:12:51 -0700 Subject: [PATCH 1/6] refactor(cursor): use shared provider sign-in UI and secret storage --- apps/server/src/provider/CursorAuth.ts | 34 +++- .../src/provider/CursorCredentialStore.ts | 47 +++++ .../src/provider/Drivers/CursorDriver.test.ts | 5 +- .../src/provider/Drivers/CursorDriver.ts | 18 +- apps/server/src/provider/cursorSdk.ts | 10 +- .../settings/CursorSetupSection.tsx | 192 ------------------ .../settings/ProviderSettingsPanel.tsx | 8 +- docs/user/cursor.md | 2 +- 8 files changed, 97 insertions(+), 219 deletions(-) create mode 100644 apps/server/src/provider/CursorCredentialStore.ts delete mode 100644 apps/web/src/components/settings/CursorSetupSection.tsx diff --git a/apps/server/src/provider/CursorAuth.ts b/apps/server/src/provider/CursorAuth.ts index 556233a5726f..cb372c00d5da 100644 --- a/apps/server/src/provider/CursorAuth.ts +++ b/apps/server/src/provider/CursorAuth.ts @@ -43,6 +43,7 @@ export interface CursorAuthOptions { readonly enabled: boolean; readonly apiKey?: string; readonly store: SdkCredentialStore; + readonly credentialBinding?: NonNullable; readonly onChanged: (signedIn: boolean) => Effect.Effect; readonly login?: (options: SdkLoginOptions) => Promise; } @@ -290,6 +291,11 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu }); const controller: ProviderAuthController = { + credentialBinding: options.credentialBinding ?? { + owner: "t3", + key: `cursor:${options.instanceId}`, + }, + isChangingCredentials: Effect.sync(() => operation !== "idle"), start: (owner, stopSessions = Effect.void) => lock.withPermits(1)( Effect.uninterruptible( @@ -432,9 +438,33 @@ export const makeCursorAuth = Effect.fn("makeCursorAuth")(function* (options: Cu subscribe: (owner) => SubscriptionRef.changes(snapshot).pipe( Stream.map((current) => { - if (current.owner === null || current.owner === owner) return current.state; - return { + const state = { ...current.state, + credentialOwner: "t3" as const, + methods: [ + { + id: "browser", + name: "Sign in with Cursor", + description: null, + type: "agent" as const, + }, + ], + interaction: + current.state.phase === "waiting" && + current.state.authorizationUrl && + current.state.flowId + ? { + type: "browser" as const, + id: current.state.flowId, + url: current.state.authorizationUrl, + requiresConsent: false, + } + : null, + }; + if (current.owner === null || current.owner === owner) return state; + return { + ...state, + interaction: null, flowId: null, authorizationUrl: null, expiresAt: null, diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/apps/server/src/provider/CursorCredentialStore.ts new file mode 100644 index 000000000000..eb258e407d33 --- /dev/null +++ b/apps/server/src/provider/CursorCredentialStore.ts @@ -0,0 +1,47 @@ +import type { SdkCredentialStore } from "@cursor/sdk"; +import type { ProviderInstanceId } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; + +import { makeProviderCredentialStore } from "./ProviderCredentialStore.ts"; + +const Credentials = Schema.fromJsonString( + Schema.Struct({ + version: Schema.Literal(1), + backendUrl: Schema.String, + apiKey: Schema.String, + createdAtMs: Schema.Finite, + apiKeyExpiresAtMs: Schema.optionalKey(Schema.Finite), + email: Schema.optionalKey(Schema.String), + }), +); + +/** The SDK owns the credential format; persistence uses the environment's secret store. */ +export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")(function* ( + instanceId: ProviderInstanceId, +) { + const credentials = yield* makeProviderCredentialStore("cursor", instanceId); + const store: SdkCredentialStore = { + load: () => + Effect.runPromise( + credentials.get.pipe( + Effect.flatMap( + Option.match({ + onNone: () => Effect.succeed(undefined), + onSome: (bytes) => + Schema.decodeUnknownEffect(Credentials)(new TextDecoder().decode(bytes)), + }), + ), + ), + ), + save: (value) => + Effect.runPromise( + Schema.encodeEffect(Credentials)(value).pipe( + Effect.flatMap((encoded) => credentials.set(new TextEncoder().encode(encoded))), + ), + ), + clear: () => Effect.runPromise(credentials.remove), + }; + return { store, binding: credentials.binding }; +}); diff --git a/apps/server/src/provider/Drivers/CursorDriver.test.ts b/apps/server/src/provider/Drivers/CursorDriver.test.ts index fb35d4f5b191..0500d653a782 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.test.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.test.ts @@ -11,6 +11,7 @@ import { HttpClient } from "effect/unstable/http"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts"; +import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import { ServerConfig } from "../../config.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; @@ -20,7 +21,7 @@ import { layer as idAllocatorLayer } from "../../orchestration-v2/IdAllocator.ts import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts"; import { Cursor } from "../cursorSdk.ts"; -const testLayer = ServerConfig.layerTest(process.cwd(), { +const dependencies = ServerConfig.layerTest(process.cwd(), { prefix: "t3-cursor-driver-copy-command-", }).pipe( Layer.provideMerge(NodeServices.layer), @@ -45,6 +46,8 @@ const testLayer = ServerConfig.layerTest(process.cwd(), { ), ); +const testLayer = ServerSecretStore.layer.pipe(Layer.provideMerge(dependencies)); + it.layer(testLayer)("CursorDriver", (it) => { it.effect( "persists browser credentials, uses them for chat, and closes the SDK session on logout", diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index 22b59b3ba3cf..fbd45274b70c 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -45,7 +45,8 @@ import { } from "../providerUpdateSettings.ts"; import { probeCursorSkills } from "./CursorSkills.ts"; import { makeCursorAuth } from "../CursorAuth.ts"; -import { FileCredentialStore } from "../cursorSdk.ts"; +import { makeCursorCredentialStore } from "../CursorCredentialStore.ts"; +import { ServerSecretStore } from "../../auth/ServerSecretStore.ts"; import * as CursorAgentSdk from "../../orchestration-v2/Adapters/CursorAgentSdk.ts"; const decodeCursorSettings = Schema.decodeSync(CursorSettings); const isSdkRunnerError = Schema.is(CursorAgentSdk.CursorAgentSdkRunnerError); @@ -64,7 +65,8 @@ export type CursorDriverEnv = | HttpClient.HttpClient | BackgroundPolicy.BackgroundPolicy | ServerConfig - | ServerSettingsService; + | ServerSettingsService + | ServerSecretStore; export const CursorDriver: ProviderDriver = { driverKind: DRIVER_KIND, @@ -80,7 +82,6 @@ export const CursorDriver: ProviderDriver = { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const httpClient = yield* HttpClient.HttpClient; - const serverConfig = yield* ServerConfig; const sdkRunner = yield* CursorAgentSdk.CursorAgentSdkRunner; const processEnv = mergeProviderInstanceEnvironment(environment); const continuationIdentity = defaultProviderContinuationIdentity({ @@ -95,19 +96,14 @@ export const CursorDriver: ProviderDriver = { continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies CursorSettings; + const credentials = yield* makeCursorCredentialStore(instanceId); const auth = yield* makeCursorAuth({ instanceId, displayName: displayName ?? "Cursor", enabled, ...(processEnv.CURSOR_API_KEY ? { apiKey: processEnv.CURSOR_API_KEY } : {}), - store: new FileCredentialStore( - path.join( - serverConfig.stateDir, - "provider-auth", - encodeURIComponent(instanceId), - "cursor.json", - ), - ), + store: credentials.store, + credentialBinding: credentials.binding, onChanged: (signedIn): Effect.Effect => snapshot.refresh.pipe( Effect.flatMap((provider) => diff --git a/apps/server/src/provider/cursorSdk.ts b/apps/server/src/provider/cursorSdk.ts index 7b3f13e5c18a..28593b4eee9d 100644 --- a/apps/server/src/provider/cursorSdk.ts +++ b/apps/server/src/provider/cursorSdk.ts @@ -4,11 +4,5 @@ import * as NodeModule from "node:module"; // Cursor's Webpack chunks and local helpers must stay beside the SDK entry. // createRequire also loads that disk-backed package from a Node SEA executable. const requireCursorSdk = NodeModule.createRequire(import.meta.url); -export const { - Agent, - AuthenticationError, - Cursor, - CursorSdkError, - FileCredentialStore, - InMemoryCredentialStore, -} = requireCursorSdk("@cursor/sdk") as typeof import("@cursor/sdk"); +export const { Agent, AuthenticationError, Cursor, CursorSdkError, InMemoryCredentialStore } = + requireCursorSdk("@cursor/sdk") as typeof import("@cursor/sdk"); diff --git a/apps/web/src/components/settings/CursorSetupSection.tsx b/apps/web/src/components/settings/CursorSetupSection.tsx deleted file mode 100644 index aece92812adf..000000000000 --- a/apps/web/src/components/settings/CursorSetupSection.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { useRef, useState } from "react"; -import { - isAtomCommandInterrupted, - squashAtomCommandFailure, - type AtomCommandResult, -} from "@t3tools/client-runtime/state/runtime"; -import type { EnvironmentId, ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; - -import { writeTextToClipboard } from "../../hooks/useCopyToClipboard"; -import { ensureLocalApi } from "../../localApi"; -import { useEnvironmentQuery } from "../../state/query"; -import { serverEnvironment } from "../../state/server"; -import { useAtomCommand } from "../../state/use-atom-command"; -import { Button } from "../ui/button"; -import { SettingsRow } from "./settingsLayout"; - -interface CursorSetupSectionProps { - readonly environmentId: EnvironmentId; - readonly environmentLabel: string; - readonly instanceId: ProviderInstanceId; - readonly provider: ServerProvider | undefined; - readonly readOnly: boolean; - readonly enabled: boolean; -} - -export function CursorSetupSection(props: CursorSetupSectionProps) { - if (props.readOnly) return null; - if (!props.provider?.setup) { - return ( - - ); - } - if (!props.provider.setup.canAuthenticate) { - return ( - - ); - } - return ( - - ); -} - -function CursorSignIn({ - environmentId, - environmentLabel, - instanceId, - provider, - enabled, -}: CursorSetupSectionProps & { readonly provider: ServerProvider }) { - const target = { environmentId, input: { instanceId } }; - const query = useEnvironmentQuery(serverEnvironment.providerAuthState(target)); - const auth = query.data; - const commandOptions = { reportFailure: false, reportDefect: false }; - const start = useAtomCommand(serverEnvironment.startProviderAuth, commandOptions); - const cancel = useAtomCommand(serverEnvironment.cancelProviderAuth, commandOptions); - const logout = useAtomCommand(serverEnvironment.logoutProviderAuth, commandOptions); - const pendingRef = useRef(false); - const [pending, setPending] = useState(false); - const [error, setError] = useState(null); - const [copiedFlow, setCopiedFlow] = useState(null); - const active = - auth?.phase === "starting" || auth?.phase === "waiting" || auth?.phase === "verifying"; - const authenticated = provider.auth.status === "authenticated"; - const authorizationUrl = auth?.phase === "waiting" ? auth.authorizationUrl : null; - const disabled = pending || auth === null || query.error !== null; - const statusMessage = - active || auth?.phase === "failed" || auth?.phase === "cancelled" - ? auth.message - : authenticated - ? null - : !enabled - ? "Enable Cursor to sign in." - : "Not signed in."; - - async function run(request: () => Promise>) { - if (pendingRef.current) return; - pendingRef.current = true; - setPending(true); - setError(null); - try { - const result = await request(); - if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { - const failure = squashAtomCommandFailure(result); - setError(failure instanceof Error ? failure.message : "Cursor setup failed. Try again."); - } - } catch { - setError("Cursor setup failed. Try again."); - } finally { - pendingRef.current = false; - setPending(false); - } - } - - async function openSignInPage() { - if (!authorizationUrl) return; - try { - await ensureLocalApi().shell.openExternal(authorizationUrl); - setError(null); - } catch { - setError("Could not open the sign-in page. Copy the link and open it in your browser."); - } - } - - async function copySignInLink() { - if (!authorizationUrl) return; - try { - await writeTextToClipboard(authorizationUrl, "Cursor sign-in link"); - setCopiedFlow(auth?.flowId ?? null); - setError(null); - } catch { - setError("Could not copy the sign-in link. Use Open sign-in page."); - } - } - - async function signOut() { - const confirmed = await ensureLocalApi().dialogs.confirm( - `Sign out of Cursor for ${provider.displayName ?? "Cursor"} on ${environmentLabel}? This stops its running threads. Thread history is kept.`, - ); - if (confirmed) await run(() => logout(target)); - } - - return ( - {statusMessage} : null} - control={ -
-
- {authorizationUrl ? ( - <> - - - - ) : null} - {active && auth.flowId ? ( - - ) : !active ? ( - - ) : null} - {!active && authenticated && provider.auth.canLogout ? ( - - ) : null} -
- {error || query.error ? ( -

- {error ?? query.error} -

- ) : null} - {query.error ? ( - - ) : null} -
- } - /> - ); -} diff --git a/apps/web/src/components/settings/ProviderSettingsPanel.tsx b/apps/web/src/components/settings/ProviderSettingsPanel.tsx index 5072ae8261ad..d739ffe366a5 100644 --- a/apps/web/src/components/settings/ProviderSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProviderSettingsPanel.tsx @@ -87,7 +87,7 @@ import { ExpandableText } from "./ExpandableText"; import { ProviderInstanceCard } from "./ProviderInstanceCard"; import { UsageProviderSettings } from "./UsageProviderSettings"; import { ProviderSetupSection, readAntigravityAuthMethod } from "./ProviderSetupSection"; -import { CursorSetupSection } from "./CursorSetupSection"; +import { ProviderAuthenticationSection } from "./ProviderAuthenticationSection"; import { DRIVER_OPTIONS, getDriverOption } from "./providerDriverMeta"; import { searchableSetting } from "./settingsSearch"; import { @@ -1035,13 +1035,13 @@ export function EnvironmentProviderSettings({ readOnly={readOnly} onEnable={() => updateProviderInstance(row, { ...row.instance, enabled: true })} /> - ) : mode === "editor" && row.driver === "cursor" ? ( - ) : null diff --git a/docs/user/cursor.md b/docs/user/cursor.md index 99fea28a38cc..5d7eafa64358 100644 --- a/docs/user/cursor.md +++ b/docs/user/cursor.md @@ -23,7 +23,7 @@ Each provider instance keeps its own login on the environment that runs it. Your CLI login are separate. A configured `CURSOR_API_KEY` overrides browser sign-in; remove that override to use the browser flow. -Use **Switch account** or **Sign out** in the same settings section. Both stop that +Use **Change account** or **Sign out** in the same settings section. Both stop that instance's running threads and keep their history. Sign-out forgets the saved credential; to revoke the generated key before it expires, remove it from your Cursor dashboard's API keys. From c396afd54de16416d4f6d4632fb88db331c4b173 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 21 Sep 2026 16:13:25 -0700 Subject: [PATCH 2/6] fix(cursor): construct credential persistence from driver infrastructure --- .../src/provider/Drivers/CursorDriver.test.ts | 5 +---- .../src/provider/Drivers/CursorDriver.ts | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/server/src/provider/Drivers/CursorDriver.test.ts b/apps/server/src/provider/Drivers/CursorDriver.test.ts index 0500d653a782..fb35d4f5b191 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.test.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.test.ts @@ -11,7 +11,6 @@ import { HttpClient } from "effect/unstable/http"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts"; -import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import { ServerConfig } from "../../config.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; @@ -21,7 +20,7 @@ import { layer as idAllocatorLayer } from "../../orchestration-v2/IdAllocator.ts import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts"; import { Cursor } from "../cursorSdk.ts"; -const dependencies = ServerConfig.layerTest(process.cwd(), { +const testLayer = ServerConfig.layerTest(process.cwd(), { prefix: "t3-cursor-driver-copy-command-", }).pipe( Layer.provideMerge(NodeServices.layer), @@ -46,8 +45,6 @@ const dependencies = ServerConfig.layerTest(process.cwd(), { ), ); -const testLayer = ServerSecretStore.layer.pipe(Layer.provideMerge(dependencies)); - it.layer(testLayer)("CursorDriver", (it) => { it.effect( "persists browser credentials, uses them for chat, and closes the SDK session on logout", diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index fbd45274b70c..3db06d0d1fd9 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -46,7 +46,7 @@ import { import { probeCursorSkills } from "./CursorSkills.ts"; import { makeCursorAuth } from "../CursorAuth.ts"; import { makeCursorCredentialStore } from "../CursorCredentialStore.ts"; -import { ServerSecretStore } from "../../auth/ServerSecretStore.ts"; +import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import * as CursorAgentSdk from "../../orchestration-v2/Adapters/CursorAgentSdk.ts"; const decodeCursorSettings = Schema.decodeSync(CursorSettings); const isSdkRunnerError = Schema.is(CursorAgentSdk.CursorAgentSdkRunnerError); @@ -65,8 +65,7 @@ export type CursorDriverEnv = | HttpClient.HttpClient | BackgroundPolicy.BackgroundPolicy | ServerConfig - | ServerSettingsService - | ServerSecretStore; + | ServerSettingsService; export const CursorDriver: ProviderDriver = { driverKind: DRIVER_KIND, @@ -96,7 +95,18 @@ export const CursorDriver: ProviderDriver = { continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies CursorSettings; - const credentials = yield* makeCursorCredentialStore(instanceId); + const credentials = yield* makeCursorCredentialStore(instanceId).pipe( + Effect.provide(ServerSecretStore.layer), + Effect.mapError( + (cause) => + new ProviderDriverError({ + driver: DRIVER_KIND, + instanceId, + detail: "Could not open the Cursor credential store.", + cause, + }), + ), + ); const auth = yield* makeCursorAuth({ instanceId, displayName: displayName ?? "Cursor", From 1dfd110de31115b7e9f97ecd4387f277b7078ecc Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 21 Sep 2026 16:38:21 -0700 Subject: [PATCH 3/6] test(cursor): verify shared credential persistence and damaged-store recovery --- .../provider/CursorCredentialStore.test.ts | 57 +++++++++++++++++++ .../src/provider/CursorCredentialStore.ts | 19 ++++++- docs/user/cursor.md | 1 + 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 apps/server/src/provider/CursorCredentialStore.test.ts diff --git a/apps/server/src/provider/CursorCredentialStore.test.ts b/apps/server/src/provider/CursorCredentialStore.test.ts new file mode 100644 index 000000000000..ec2b205aaaa3 --- /dev/null +++ b/apps/server/src/provider/CursorCredentialStore.test.ts @@ -0,0 +1,57 @@ +import { assert, it } from "@effect/vitest"; +import { ProviderInstanceId } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; + +import { ServerSecretStore } from "../auth/ServerSecretStore.ts"; +import { makeCursorCredentialStore } from "./CursorCredentialStore.ts"; + +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.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, secrets), + ); + const personal = yield* makeStore("personal"); + const work = yield* makeStore("work"); + const credentials = { + version: 1 as const, + backendUrl: "https://api.cursor.com", + apiKey: "test-only-key", + createdAtMs: 100, + apiKeyExpiresAtMs: 1000, + email: "test@example.com", + }; + yield* Effect.tryPromise(() => personal.store.save(credentials)); + yield* Effect.tryPromise(() => + work.store.save({ ...credentials, apiKey: "test-only-work-key" }), + ); + const restored = yield* makeStore("personal"); + assert.deepEqual(yield* Effect.tryPromise(() => restored.store.load()), credentials); + data.set(personal.binding.key, 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()); + assert.isUndefined(yield* Effect.tryPromise(() => personal.store.load())); + assert.strictEqual( + (yield* Effect.tryPromise(() => work.store.load()))?.apiKey, + "test-only-work-key", + ); + }), +); diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/apps/server/src/provider/CursorCredentialStore.ts index eb258e407d33..1ffa5710d315 100644 --- a/apps/server/src/provider/CursorCredentialStore.ts +++ b/apps/server/src/provider/CursorCredentialStore.ts @@ -1,5 +1,5 @@ import type { SdkCredentialStore } from "@cursor/sdk"; -import type { ProviderInstanceId } from "@t3tools/contracts"; +import { ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -17,6 +17,9 @@ const Credentials = Schema.fromJsonString( }), ); +const decodeCredentials = Schema.decodeUnknownEffect(Credentials); +const encodeCredentials = Schema.encodeEffect(Credentials); + /** The SDK owns the credential format; persistence uses the environment's secret store. */ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")(function* ( instanceId: ProviderInstanceId, @@ -30,14 +33,24 @@ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")( Option.match({ onNone: () => Effect.succeed(undefined), onSome: (bytes) => - Schema.decodeUnknownEffect(Credentials)(new TextDecoder().decode(bytes)), + decodeCredentials(new TextDecoder().decode(bytes)).pipe( + Effect.orElseSucceed(() => undefined), + ), }), ), ), ), save: (value) => Effect.runPromise( - Schema.encodeEffect(Credentials)(value).pipe( + encodeCredentials(value).pipe( + Effect.mapError( + () => + new ProviderSetupError({ + instanceId, + operation: "credentials", + detail: "Cursor returned an unsupported credential format.", + }), + ), Effect.flatMap((encoded) => credentials.set(new TextEncoder().encode(encoded))), ), ), diff --git a/docs/user/cursor.md b/docs/user/cursor.md index 5d7eafa64358..abed86d42c41 100644 --- a/docs/user/cursor.md +++ b/docs/user/cursor.md @@ -18,6 +18,7 @@ not expose yet. Choose **Sign in**, then open the sign-in page and complete it in your browser. T3 Code updates automatically when sign-in finishes. This works when connected to a remote environment too. +On mobile, use **Settings > Provider accounts** for an already configured Cursor instance. Each provider instance keeps its own login on the environment that runs it. Your Cursor editor and CLI login are separate. A configured `CURSOR_API_KEY` overrides browser sign-in; remove that override From 6d6c7918e4aa17543fef4e7ce2c1575e7923f876 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 22 Sep 2026 15:22:43 -0700 Subject: [PATCH 4/6] fix(cursor): migrate file-based sign-ins into the secret store Earlier versions kept Cursor credentials in /provider-auth//cursor.json. Import that sign-in once when the secret store has none, then delete the file so the API key is not left on disk. Co-Authored-By: Claude Opus 5.5 --- .../provider/CursorCredentialStore.test.ts | 80 ++++++++++++++++++- .../src/provider/CursorCredentialStore.ts | 26 +++++- .../src/provider/Drivers/CursorDriver.ts | 10 ++- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/apps/server/src/provider/CursorCredentialStore.test.ts b/apps/server/src/provider/CursorCredentialStore.test.ts index ec2b205aaaa3..47576b5f5886 100644 --- a/apps/server/src/provider/CursorCredentialStore.test.ts +++ b/apps/server/src/provider/CursorCredentialStore.test.ts @@ -1,11 +1,89 @@ +import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { ProviderInstanceId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import * as Option from "effect/Option"; import { ServerSecretStore } from "../auth/ServerSecretStore.ts"; import { makeCursorCredentialStore } from "./CursorCredentialStore.ts"; +const makeSecrets = () => { + const data = new Map(); + return { + data, + secrets: 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 legacyCredentials = { + version: 1 as const, + backendUrl: "https://api.cursor.com", + apiKey: "test-only-legacy-key", + createdAtMs: 100, +}; +// The SDK's FileCredentialStore writes pretty-printed JSON. +const legacyFileText = `{ + "version": 1, + "backendUrl": "https://api.cursor.com", + "apiKey": "test-only-legacy-key", + "createdAtMs": 100 +}`; + +it.effect.each([ + { + name: "imports a legacy sign-in", + legacy: legacyFileText, + stored: undefined, + expected: "test-only-legacy-key", + }, + { + name: "keeps a newer stored sign-in", + legacy: legacyFileText, + stored: "test-only-current-key", + expected: "test-only-current-key", + }, + { + name: "discards a damaged legacy file", + legacy: "damaged", + stored: undefined, + expected: undefined, + }, +])("$name and deletes the legacy file", ({ legacy, stored, expected }) => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const legacyFile = path.join(yield* fileSystem.makeTempDirectoryScoped(), "cursor.json"); + yield* fileSystem.writeFileString(legacyFile, legacy); + const { secrets } = makeSecrets(); + const open = makeCursorCredentialStore(ProviderInstanceId.make("personal"), legacyFile).pipe( + Effect.provideService(ServerSecretStore, secrets), + ); + if (stored !== undefined) { + const current = yield* makeCursorCredentialStore(ProviderInstanceId.make("personal")).pipe( + Effect.provideService(ServerSecretStore, secrets), + ); + yield* Effect.tryPromise(() => current.store.save({ ...legacyCredentials, apiKey: stored })); + } + const migrated = yield* open; + assert.strictEqual((yield* Effect.tryPromise(() => migrated.store.load()))?.apiKey, expected); + assert.isFalse(yield* fileSystem.exists(legacyFile)); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); + it.effect( "restores SDK credentials in a new controller and keeps another account when signing out", () => @@ -53,5 +131,5 @@ it.effect( (yield* Effect.tryPromise(() => work.store.load()))?.apiKey, "test-only-work-key", ); - }), + }).pipe(Effect.provide(NodeServices.layer)), ); diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/apps/server/src/provider/CursorCredentialStore.ts index 1ffa5710d315..5092297500b1 100644 --- a/apps/server/src/provider/CursorCredentialStore.ts +++ b/apps/server/src/provider/CursorCredentialStore.ts @@ -1,6 +1,7 @@ import type { SdkCredentialStore } from "@cursor/sdk"; import { ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -20,11 +21,34 @@ const Credentials = Schema.fromJsonString( const decodeCredentials = Schema.decodeUnknownEffect(Credentials); const encodeCredentials = Schema.encodeEffect(Credentials); -/** The SDK owns the credential format; persistence uses the environment's secret store. */ +/** + * The SDK owns the credential format; persistence uses the environment's secret store. + * `legacyFile` is the SDK file store earlier versions used; its sign-in moves into + * the secret store once and the file is deleted. + */ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")(function* ( instanceId: ProviderInstanceId, + legacyFile?: string, ) { const credentials = yield* makeProviderCredentialStore("cursor", instanceId); + if (legacyFile !== undefined) { + const fileSystem = yield* FileSystem.FileSystem; + yield* Effect.gen(function* () { + const legacy = yield* fileSystem.readFileString(legacyFile).pipe(Effect.option); + if (Option.isNone(legacy)) return; + if ( + Option.isNone(yield* credentials.get) && + Option.isSome(yield* decodeCredentials(legacy.value).pipe(Effect.option)) + ) { + yield* credentials.set(new TextEncoder().encode(legacy.value)); + } + yield* fileSystem.remove(legacyFile); + }).pipe( + Effect.catch((error) => + Effect.logWarning("Could not migrate legacy Cursor credentials", error), + ), + ); + } const store: SdkCredentialStore = { load: () => Effect.runPromise( diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index 3db06d0d1fd9..fd13f8f04e94 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -95,7 +95,15 @@ export const CursorDriver: ProviderDriver = { continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies CursorSettings; - const credentials = yield* makeCursorCredentialStore(instanceId).pipe( + const credentials = yield* makeCursorCredentialStore( + instanceId, + path.join( + (yield* ServerConfig).stateDir, + "provider-auth", + encodeURIComponent(instanceId), + "cursor.json", + ), + ).pipe( Effect.provide(ServerSecretStore.layer), Effect.mapError( (cause) => From daa32c1aa6733b9e249c2d1d462606828e7f3c16 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 22 Sep 2026 16:22:14 -0700 Subject: [PATCH 5/6] fix(cursor): replace damaged stored sign-ins; explain CURSOR_API_KEY sign-in The driver now takes the secret store from its environment. Co-Authored-By: Claude Opus 5.5 --- .../AcpRegistryOrchestratorV2.live.test.ts | 5 +++++ .../CursorOrchestratorV2.live.test.ts | 5 +++++ .../GrokOrchestratorV2.live.test.ts | 5 +++++ .../provider/CursorCredentialStore.test.ts | 20 ++++++++++++------- .../src/provider/CursorCredentialStore.ts | 9 ++++++++- .../src/provider/Drivers/CursorDriver.test.ts | 8 +++++--- .../src/provider/Drivers/CursorDriver.ts | 6 +++--- .../ProviderInstanceRegistryLive.test.ts | 2 ++ .../provider/Layers/ProviderRegistry.test.ts | 5 +++++ .../settings/ProviderSettingsPanel.tsx | 8 ++++++++ 10 files changed, 59 insertions(+), 14 deletions(-) diff --git a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts index 7c984a255906..ba6e339e8ae1 100644 --- a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts @@ -1,3 +1,4 @@ +import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { SourceControlProviderRegistry } from "../sourceControl/SourceControlProviderRegistry.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; @@ -94,6 +95,10 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe Layer.mergeAll( serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), serverSettingsLayer, + ServerSecretStore.layer.pipe( + Layer.provide(serverConfigLayer), + Layer.provide(PlatformTestLayer), + ), NodeServices.layer, FetchHttpClient.layer, OpenCodeRuntimeLive.pipe(Layer.provide(PlatformTestLayer)), diff --git a/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts index 1237fd1f64c6..c3ceef961948 100644 --- a/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts @@ -1,3 +1,4 @@ +import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { SourceControlProviderRegistry } from "../sourceControl/SourceControlProviderRegistry.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; @@ -69,6 +70,10 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe Layer.mergeAll( serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), serverSettingsLayer, + ServerSecretStore.layer.pipe( + Layer.provide(serverConfigLayer), + Layer.provide(PlatformTestLayer), + ), NodeServices.layer, FetchHttpClient.layer, OpenCodeRuntimeLive.pipe(Layer.provide(PlatformTestLayer)), diff --git a/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts index 32cb7e1b23ff..0621fac1ae8a 100644 --- a/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts @@ -1,3 +1,4 @@ +import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { SourceControlProviderRegistry } from "../sourceControl/SourceControlProviderRegistry.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; @@ -68,6 +69,10 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe Layer.mergeAll( serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), serverSettingsLayer, + ServerSecretStore.layer.pipe( + Layer.provide(serverConfigLayer), + Layer.provide(PlatformTestLayer), + ), NodeServices.layer, FetchHttpClient.layer, OpenCodeRuntimeLive.pipe(Layer.provide(PlatformTestLayer)), diff --git a/apps/server/src/provider/CursorCredentialStore.test.ts b/apps/server/src/provider/CursorCredentialStore.test.ts index 47576b5f5886..5705fa01a03c 100644 --- a/apps/server/src/provider/CursorCredentialStore.test.ts +++ b/apps/server/src/provider/CursorCredentialStore.test.ts @@ -53,9 +53,15 @@ it.effect.each([ { name: "keeps a newer stored sign-in", legacy: legacyFileText, - stored: "test-only-current-key", + stored: legacyFileText.replace("test-only-legacy-key", "test-only-current-key"), expected: "test-only-current-key", }, + { + name: "replaces a damaged stored sign-in", + legacy: legacyFileText, + stored: "damaged", + expected: "test-only-legacy-key", + }, { name: "discards a damaged legacy file", legacy: "damaged", @@ -69,16 +75,16 @@ it.effect.each([ const legacyFile = path.join(yield* fileSystem.makeTempDirectoryScoped(), "cursor.json"); yield* fileSystem.writeFileString(legacyFile, legacy); const { secrets } = makeSecrets(); - const open = makeCursorCredentialStore(ProviderInstanceId.make("personal"), legacyFile).pipe( - Effect.provideService(ServerSecretStore, secrets), - ); + const instanceId = ProviderInstanceId.make("personal"); if (stored !== undefined) { - const current = yield* makeCursorCredentialStore(ProviderInstanceId.make("personal")).pipe( + const current = yield* makeCursorCredentialStore(instanceId).pipe( Effect.provideService(ServerSecretStore, secrets), ); - yield* Effect.tryPromise(() => current.store.save({ ...legacyCredentials, apiKey: stored })); + yield* secrets.set(current.binding.key, new TextEncoder().encode(stored)); } - const migrated = yield* open; + const migrated = yield* makeCursorCredentialStore(instanceId, legacyFile).pipe( + Effect.provideService(ServerSecretStore, secrets), + ); assert.strictEqual((yield* Effect.tryPromise(() => migrated.store.load()))?.apiKey, expected); assert.isFalse(yield* fileSystem.exists(legacyFile)); }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/apps/server/src/provider/CursorCredentialStore.ts index 5092297500b1..a1ee9def6a90 100644 --- a/apps/server/src/provider/CursorCredentialStore.ts +++ b/apps/server/src/provider/CursorCredentialStore.ts @@ -36,8 +36,15 @@ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")( yield* Effect.gen(function* () { const legacy = yield* fileSystem.readFileString(legacyFile).pipe(Effect.option); if (Option.isNone(legacy)) return; + // A stored sign-in wins unless it is damaged; the SDK would ignore it anyway. + const stored = yield* credentials.get; + const storedIsValid = + Option.isSome(stored) && + Option.isSome( + yield* decodeCredentials(new TextDecoder().decode(stored.value)).pipe(Effect.option), + ); if ( - Option.isNone(yield* credentials.get) && + !storedIsValid && Option.isSome(yield* decodeCredentials(legacy.value).pipe(Effect.option)) ) { yield* credentials.set(new TextEncoder().encode(legacy.value)); diff --git a/apps/server/src/provider/Drivers/CursorDriver.test.ts b/apps/server/src/provider/Drivers/CursorDriver.test.ts index fb35d4f5b191..ddc71a3a3210 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.test.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.test.ts @@ -1,4 +1,5 @@ // @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"; @@ -20,9 +21,10 @@ import { layer as idAllocatorLayer } from "../../orchestration-v2/IdAllocator.ts import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts"; import { Cursor } from "../cursorSdk.ts"; -const testLayer = ServerConfig.layerTest(process.cwd(), { - prefix: "t3-cursor-driver-copy-command-", -}).pipe( +const testLayer = ServerSecretStore.layer.pipe( + Layer.provideMerge( + ServerConfig.layerTest(process.cwd(), { prefix: "t3-cursor-driver-copy-command-" }), + ), Layer.provideMerge(NodeServices.layer), Layer.provideMerge(idAllocatorLayer), Layer.provideMerge( diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index fd13f8f04e94..0cb70399b9b4 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -45,7 +45,7 @@ import { } from "../providerUpdateSettings.ts"; import { probeCursorSkills } from "./CursorSkills.ts"; import { makeCursorAuth } from "../CursorAuth.ts"; -import { makeCursorCredentialStore } from "../CursorCredentialStore.ts"; +import * as CursorCredentialStore from "../CursorCredentialStore.ts"; import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import * as CursorAgentSdk from "../../orchestration-v2/Adapters/CursorAgentSdk.ts"; const decodeCursorSettings = Schema.decodeSync(CursorSettings); @@ -65,6 +65,7 @@ export type CursorDriverEnv = | HttpClient.HttpClient | BackgroundPolicy.BackgroundPolicy | ServerConfig + | ServerSecretStore.ServerSecretStore | ServerSettingsService; export const CursorDriver: ProviderDriver = { @@ -95,7 +96,7 @@ export const CursorDriver: ProviderDriver = { continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies CursorSettings; - const credentials = yield* makeCursorCredentialStore( + const credentials = yield* CursorCredentialStore.makeCursorCredentialStore( instanceId, path.join( (yield* ServerConfig).stateDir, @@ -104,7 +105,6 @@ export const CursorDriver: ProviderDriver = { "cursor.json", ), ).pipe( - Effect.provide(ServerSecretStore.layer), Effect.mapError( (cause) => new ProviderDriverError({ diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts index e75d7dbfc8cf..82ec671dfd63 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts @@ -1,3 +1,4 @@ +import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import * as CodexResetCredit from "./codexResetCredit.ts"; /** * Multi-instance validation slices for `ProviderInstanceRegistryLive`. @@ -450,6 +451,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { // `FileSystem` dep while keeping everything else surfaced to the test. const infraLayer = OpenCodeRuntimeLive.pipe(Layer.provideMerge(NodeServices.layer)); const baseLayer = AntigravityInstallation.layer.pipe( + Layer.provideMerge(ServerSecretStore.layer), Layer.provideMerge( ServerConfig.layerTest(process.cwd(), { prefix: "provider-instance-registry-all-drivers-test", diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index 24ecd5710f50..b34235ed9122 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -1,3 +1,4 @@ +import * as ServerSecretStore from "../../auth/ServerSecretStore.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, it, assert } from "@effect/vitest"; import * as DateTime from "effect/DateTime"; @@ -2446,6 +2447,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te Layer.provideMerge( Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), ), + Layer.provideMerge(ServerSecretStore.layer), Layer.provideMerge( ServerConfig.layerTest(process.cwd(), { prefix: "t3-provider-registry-", @@ -2545,6 +2547,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te Layer.provideMerge( Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), ), + Layer.provideMerge(ServerSecretStore.layer), Layer.provideMerge( ServerConfig.layerTest(process.cwd(), { prefix: "t3-provider-registry-", @@ -2661,6 +2664,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te Layer.provideMerge( Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), ), + Layer.provideMerge(ServerSecretStore.layer), Layer.provideMerge( ServerConfig.layerTest(process.cwd(), { prefix: "t3-provider-registry-", @@ -2723,6 +2727,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te Layer.provideMerge( Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), ), + Layer.provideMerge(ServerSecretStore.layer), Layer.provideMerge( ServerConfig.layerTest(process.cwd(), { prefix: "t3-provider-registry-", diff --git a/apps/web/src/components/settings/ProviderSettingsPanel.tsx b/apps/web/src/components/settings/ProviderSettingsPanel.tsx index d739ffe366a5..5318a2340503 100644 --- a/apps/web/src/components/settings/ProviderSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProviderSettingsPanel.tsx @@ -1044,6 +1044,14 @@ export function EnvironmentProviderSettings({ provider={liveProvider} readOnly={readOnly} /> + ) : mode === "editor" && + !readOnly && + row.driver === "cursor" && + liveProvider?.setup?.canAuthenticate === false ? ( + ) : null } onUpdate={(next) => { From ccf168b59ef30408531d696b836118b186317f08 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 22 Sep 2026 16:30:21 -0700 Subject: [PATCH 6/6] refactor(cursor): namespace the credential store import Co-Authored-By: Claude Opus 5.5 --- apps/server/src/provider/CursorCredentialStore.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/CursorCredentialStore.ts b/apps/server/src/provider/CursorCredentialStore.ts index a1ee9def6a90..116246055d55 100644 --- a/apps/server/src/provider/CursorCredentialStore.ts +++ b/apps/server/src/provider/CursorCredentialStore.ts @@ -5,7 +5,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; -import { makeProviderCredentialStore } from "./ProviderCredentialStore.ts"; +import * as ProviderCredentialStore from "./ProviderCredentialStore.ts"; const Credentials = Schema.fromJsonString( Schema.Struct({ @@ -30,7 +30,10 @@ export const makeCursorCredentialStore = Effect.fn("makeCursorCredentialStore")( instanceId: ProviderInstanceId, legacyFile?: string, ) { - const credentials = yield* makeProviderCredentialStore("cursor", instanceId); + const credentials = yield* ProviderCredentialStore.makeProviderCredentialStore( + "cursor", + instanceId, + ); if (legacyFile !== undefined) { const fileSystem = yield* FileSystem.FileSystem; yield* Effect.gen(function* () {