From 35ee80179f5a08b379db2c36c871f8d6a24547d2 Mon Sep 17 00:00:00 2001 From: Ahmed Bedair Date: Mon, 9 Nov 2026 19:22:54 +0000 Subject: [PATCH 1/2] fix(source-control): Cursor Origin recognises a signed-in CLI `origin auth status --json` exits 1 unless the fields are named, so the connection check read no sign-in and reported "Could not verify Cursor Origin" for a CLI that was installed and signed in. The viewer lookup ran the same command and failed the same way. Both now ask for the three fields the parser reads. Co-Authored-By: Claude --- .../source-control-cursor-origin/package.json | 1 + .../src/server/OriginCli.ts | 10 +++- .../OriginSourceControlProvider.test.ts | 48 +++++++++++++++++++ .../src/server/OriginSourceControlProvider.ts | 2 +- pnpm-lock.yaml | 3 ++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts diff --git a/packages/source-control-cursor-origin/package.json b/packages/source-control-cursor-origin/package.json index b15d11bfdb77f..31611103ccf17 100644 --- a/packages/source-control-cursor-origin/package.json +++ b/packages/source-control-cursor-origin/package.json @@ -26,6 +26,7 @@ }, "devDependencies": { "@effect/vitest": "catalog:", + "@t3tools/source-control-testing": "workspace:*", "@types/node": "catalog:", "vite-plus": "catalog:" } diff --git a/packages/source-control-cursor-origin/src/server/OriginCli.ts b/packages/source-control-cursor-origin/src/server/OriginCli.ts index 844fb223fb6f3..7204eb88fd331 100644 --- a/packages/source-control-cursor-origin/src/server/OriginCli.ts +++ b/packages/source-control-cursor-origin/src/server/OriginCli.ts @@ -64,6 +64,14 @@ const OriginAuthStatusSchema = Schema.Struct({ }); type OriginAuthStatus = typeof OriginAuthStatusSchema.Type; +/** `origin` exits 1 on a bare `--json`, so this names the fields `OriginAuthStatusSchema` reads. */ +export const ORIGIN_AUTH_STATUS_ARGS = [ + "auth", + "status", + "--json", + "authenticated,tokenValid,account", +] as const; + export function parseOriginAuthStatus(raw: string): OriginAuthStatus | null { const decoded = decodeJsonResult(OriginAuthStatusSchema)(raw); return Result.isSuccess(decoded) ? decoded.success : null; @@ -382,7 +390,7 @@ const make = Effect.gen(function* () { }); const authStatus = (cwd: string) => - run({ cwd, operation: "authStatus", args: ["auth", "status", "--json"] }).pipe( + run({ cwd, operation: "authStatus", args: ORIGIN_AUTH_STATUS_ARGS }).pipe( Effect.flatMap((result) => { const status = parseOriginAuthStatus(result.stdout); if (status !== null) return Effect.succeed(status); diff --git a/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts new file mode 100644 index 0000000000000..3f33a9d4098b0 --- /dev/null +++ b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts @@ -0,0 +1,48 @@ +import { assert, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; +import * as TestSourceControlHost from "@t3tools/source-control-testing/TestSourceControlHost"; + +import * as OriginCli from "./OriginCli.ts"; +import * as OriginSourceControlProvider from "./OriginSourceControlProvider.ts"; + +const signedIn: Record = { + authenticated: true, + authMethod: "oauth", + tokenValid: true, + account: { email: "dev@example.test" }, +}; + +// As the real CLI does, this exits 1 on a bare `--json` and prints only the fields it is asked for. +const origin = (args: ReadonlyArray) => { + if (args[0] === "--version") return TestSourceControlHost.processOutput("2026.11.03"); + const fields = args[args.indexOf("--json") + 1]?.split(",") ?? []; + return fields.length === 0 + ? TestSourceControlHost.processOutput("", { + stderr: "Error: Specify one or more comma-separated fields for `--json`:\n authenticated", + exitCode: ChildProcessSpawner.ExitCode(1), + }) + : TestSourceControlHost.processOutput( + JSON.stringify(Object.fromEntries(fields.map((field) => [field, signedIn[field]]))), + ); +}; + +it.effect("reads the sign-in from an Origin CLI that wants its --json fields named", () => + Effect.gen(function* () { + const discovery = yield* OriginSourceControlProvider.makeDiscovery.pipe( + Effect.provide( + Layer.mergeAll( + Layer.mock(OriginCli.OriginCli)({ personalNamespace: () => Effect.succeed("acme") }), + TestSourceControlHost.layer({ + process: { run: (input) => Effect.succeed(origin(input.args)) }, + }), + ), + ), + ); + const item = yield* discovery.probe("/repo"); + assert.strictEqual(item.auth.status, "authenticated"); + assert.deepStrictEqual(item.auth.account, Option.some("acme")); + }), +); diff --git a/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.ts b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.ts index 6128de0baff4e..5e1a970041731 100644 --- a/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.ts +++ b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.ts @@ -31,7 +31,7 @@ const discovery = { label: "Cursor Origin", executable: OriginCli.ORIGIN_COMMAND, versionArgs: ["--version"], - authArgs: ["auth", "status", "--json"], + authArgs: OriginCli.ORIGIN_AUTH_STATUS_ARGS, parseAuth: (input) => { const status = OriginCli.parseOriginAuthStatus(input.stdout); const account = OriginCli.originAccount(status); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a31789a5863e1..65f66de41a114 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2229,6 +2229,9 @@ importers: '@effect/vitest': specifier: 4.1.2 version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@6.0.3))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@t3tools/source-control-testing': + specifier: workspace:* + version: link:../source-control-testing '@types/node': specifier: 24.12.4 version: 24.12.4 From 2c2f8796e8014353f319f6827b8e3571744da9b7 Mon Sep 17 00:00:00 2001 From: Ahmed Bedair Date: Mon, 9 Nov 2026 19:40:15 +0000 Subject: [PATCH 2/2] test(source-control): cover Origin account auth field --- .../src/server/OriginSourceControlProvider.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts index 3f33a9d4098b0..0419f4c858b20 100644 --- a/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts +++ b/packages/source-control-cursor-origin/src/server/OriginSourceControlProvider.test.ts @@ -44,5 +44,12 @@ it.effect("reads the sign-in from an Origin CLI that wants its --json fields nam const item = yield* discovery.probe("/repo"); assert.strictEqual(item.auth.status, "authenticated"); assert.deepStrictEqual(item.auth.account, Option.some("acme")); + // Discovery shows the namespace instead, so only the viewer lookup needs the email. + assert.strictEqual( + OriginCli.originAccount( + OriginCli.parseOriginAuthStatus(origin(OriginCli.ORIGIN_AUTH_STATUS_ARGS).stdout), + ), + "dev@example.test", + ); }), );