agent-dealer 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/bundle/server/dist/adapters/linear-inbox.js +9 -5
  2. package/bundle/server/dist/capacity/adapter.js +2 -0
  3. package/bundle/server/dist/capacity/claude-events.js +45 -10
  4. package/bundle/server/dist/capacity/claude-events.test.js +7 -0
  5. package/bundle/server/dist/capacity/claude-local-cache.js +788 -0
  6. package/bundle/server/dist/capacity/claude-local-cache.test.js +520 -0
  7. package/bundle/server/dist/capacity/codex-app-server.js +100 -10
  8. package/bundle/server/dist/capacity/codex-app-server.test.js +195 -2
  9. package/bundle/server/dist/capacity/cursor-individual-credentials.js +425 -0
  10. package/bundle/server/dist/capacity/cursor-individual-credentials.test.js +369 -0
  11. package/bundle/server/dist/capacity/cursor-individual.js +1037 -0
  12. package/bundle/server/dist/capacity/cursor-individual.test.js +1081 -0
  13. package/bundle/server/dist/capacity/muse-host.js +748 -0
  14. package/bundle/server/dist/capacity/muse-host.test.js +480 -0
  15. package/bundle/server/dist/capacity/muse-lifecycle.test.js +97 -0
  16. package/bundle/server/dist/capacity/muse.js +389 -91
  17. package/bundle/server/dist/capacity/muse.test.js +100 -61
  18. package/bundle/server/dist/capacity/service.js +1 -0
  19. package/bundle/server/dist/coordinator/muse-spawn.js +143 -1
  20. package/bundle/server/dist/db/index.js +30 -0
  21. package/bundle/server/dist/db/schema.sql +27 -0
  22. package/bundle/server/dist/index.js +17 -2
  23. package/bundle/server/dist/repository/cursor-individual-billing.js +84 -0
  24. package/bundle/server/dist/repository/repository-mappings.js +46 -0
  25. package/bundle/server/dist/repository/repository-mappings.test.js +83 -0
  26. package/bundle/server/dist/repository/runtime-capacity.js +8 -3
  27. package/bundle/server/dist/repository/runtime-capacity.test.js +4 -0
  28. package/bundle/server/dist/routes/cursor-individual-billing.test.js +80 -0
  29. package/bundle/server/dist/routes/index.js +58 -9
  30. package/bundle/server/dist/routes/intake-linear.test.js +55 -1
  31. package/bundle/server/dist/routes/repository-mappings.js +22 -0
  32. package/bundle/server/dist/routes/repository-mappings.test.js +110 -0
  33. package/bundle/server/dist/routes/runtime-capacity.test.js +84 -8
  34. package/bundle/server/dist/runners/muse-code-jsonl.js +7 -1
  35. package/bundle/server/dist/runners/muse-serve-session.js +313 -0
  36. package/bundle/server/dist/runners/muse-serve-session.test.js +325 -0
  37. package/bundle/server/package.json +2 -2
  38. package/bundle/server/static-ui/assets/codex-YZSpL-SB.png +0 -0
  39. package/bundle/server/static-ui/assets/index-CYqRh_-S.css +1 -0
  40. package/bundle/server/static-ui/assets/index-UO4lHZw4.js +60 -0
  41. package/bundle/server/static-ui/assets/muse-CtdcC6ve.svg +33 -0
  42. package/bundle/server/static-ui/index.html +2 -2
  43. package/bundle/shared/dist/index.d.ts +6 -6
  44. package/bundle/shared/dist/linear-intake.d.ts +71 -0
  45. package/bundle/shared/dist/linear-intake.js +90 -0
  46. package/bundle/shared/dist/linear-intake.test.js +80 -1
  47. package/bundle/shared/dist/runtime-capacity.d.ts +89 -0
  48. package/bundle/shared/dist/runtime-capacity.js +60 -0
  49. package/bundle/shared/dist/runtime-capacity.test.js +1 -0
  50. package/bundle/shared/package.json +1 -1
  51. package/dist/doctor.d.ts +57 -0
  52. package/dist/doctor.js +211 -0
  53. package/dist/doctor.test.d.ts +1 -0
  54. package/dist/doctor.test.js +246 -0
  55. package/package.json +1 -1
  56. package/bundle/server/static-ui/assets/index-ChnI0F02.css +0 -1
  57. package/bundle/server/static-ui/assets/index-CyB1573Q.js +0 -60
@@ -0,0 +1,369 @@
1
+ // packages/server/src/capacity/cursor-individual-credentials.test.ts
2
+ //
3
+ // NOT-250: credential lookup is isolated behind an adapter, returns no
4
+ // secret on diagnostics surfaces, and degrades on absent/changed formats.
5
+ // Every case uses temporary fixture files (or an injected reader) — CI
6
+ // never touches the real local Cursor login.
7
+ import { test, beforeEach, afterEach } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import fs from "node:fs";
10
+ import os from "node:os";
11
+ import path from "node:path";
12
+ import Database from "better-sqlite3";
13
+ import { CURSOR_DESKTOP_ACCESS_TOKEN_KEY, CURSOR_DESKTOP_FORMAT, CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV, CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV, CURSOR_INDIVIDUAL_HOME_ENV, cursorIndividualAuthHeader, cursorIndividualCredentialCandidates, cursorIndividualCredentialStatus, cursorIndividualDesktopStateCandidates, decodeJwtSubject, defaultReadDesktopAccessToken, desktopTokenToAuthHeader, loadCursorIndividualCredential, normalizeDesktopTokenValue, normalizeWorkosUserId, } from "./cursor-individual-credentials.js";
14
+ const SECRET = "fixture-secret-token-abc123";
15
+ const USER_ID = "user_fixture_abc123";
16
+ /** header.payload.signature with { sub: USER_ID } — no signature verification, so any value works. */
17
+ function fixtureJwt(sub) {
18
+ const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
19
+ return `${b64url({ alg: "none" })}.${b64url({ sub })}.sig`;
20
+ }
21
+ let dir;
22
+ let savedFileEnv;
23
+ let savedHomeEnv;
24
+ let savedDesktopEnv;
25
+ let savedAppData;
26
+ beforeEach(() => {
27
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "dealer-cursor-indiv-creds-"));
28
+ savedFileEnv = process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
29
+ savedHomeEnv = process.env[CURSOR_INDIVIDUAL_HOME_ENV];
30
+ savedDesktopEnv = process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
31
+ savedAppData = process.env.APPDATA;
32
+ });
33
+ afterEach(() => {
34
+ if (savedFileEnv === undefined)
35
+ delete process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
36
+ else
37
+ process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV] = savedFileEnv;
38
+ if (savedHomeEnv === undefined)
39
+ delete process.env[CURSOR_INDIVIDUAL_HOME_ENV];
40
+ else
41
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = savedHomeEnv;
42
+ if (savedDesktopEnv === undefined)
43
+ delete process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
44
+ else
45
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = savedDesktopEnv;
46
+ if (savedAppData === undefined)
47
+ delete process.env.APPDATA;
48
+ else
49
+ process.env.APPDATA = savedAppData;
50
+ fs.rmSync(dir, { recursive: true, force: true });
51
+ });
52
+ function fixture(name, content) {
53
+ const file = path.join(dir, name);
54
+ fs.writeFileSync(file, content);
55
+ process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV] = file;
56
+ return file;
57
+ }
58
+ test("absent credential file reads absent with no path", () => {
59
+ process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV] = path.join(dir, "does-not-exist.json");
60
+ const credential = loadCursorIndividualCredential();
61
+ assert.equal(credential.status, "absent");
62
+ assert.equal(credential.path, null);
63
+ assert.equal(credential.format, null);
64
+ assert.equal(credential.authHeader, undefined);
65
+ const status = cursorIndividualCredentialStatus();
66
+ assert.deepEqual(status, { present: false, source: null, path: null, format: null });
67
+ });
68
+ test("empty home directory reads absent (no real login touched)", () => {
69
+ delete process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
70
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = dir;
71
+ assert.deepEqual(cursorIndividualCredentialCandidates(), [
72
+ path.join(dir, ".cursor", "auth.json"),
73
+ ]);
74
+ assert.equal(loadCursorIndividualCredential().status, "absent");
75
+ assert.equal(cursorIndividualCredentialStatus().present, false);
76
+ });
77
+ test("accepted token shapes load with an in-memory auth header", () => {
78
+ const cases = [
79
+ { body: { token: SECRET, userId: USER_ID }, format: "token" },
80
+ { body: { accessToken: SECRET, userId: USER_ID }, format: "accessToken" },
81
+ { body: { access_token: SECRET, user_id: USER_ID }, format: "access_token" },
82
+ { body: { sessionToken: SECRET, userId: USER_ID }, format: "sessionToken" },
83
+ { body: { apiKey: SECRET, userId: USER_ID }, format: "apiKey" },
84
+ { body: { auth: { accessToken: SECRET }, userId: USER_ID }, format: "auth.accessToken" },
85
+ ];
86
+ for (const { body, format } of cases) {
87
+ const file = fixture("auth.json", JSON.stringify(body));
88
+ const credential = loadCursorIndividualCredential();
89
+ assert.equal(credential.status, "found");
90
+ assert.equal(credential.source, "agent");
91
+ assert.equal(credential.path, file);
92
+ assert.equal(credential.format, format);
93
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader(USER_ID, SECRET));
94
+ assert.ok(credential.authHeader.includes(SECRET), "header carries the token in memory only");
95
+ }
96
+ });
97
+ test("a user id is derived from the token's own JWT `sub` claim when no explicit field is present", () => {
98
+ const jwt = fixtureJwt(USER_ID);
99
+ assert.equal(decodeJwtSubject(jwt), USER_ID);
100
+ fixture("auth.json", JSON.stringify({ token: jwt }));
101
+ const credential = loadCursorIndividualCredential();
102
+ assert.equal(credential.status, "found");
103
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader(USER_ID, jwt));
104
+ });
105
+ test("an explicit user-id field wins over the token's own JWT `sub` claim", () => {
106
+ const jwt = fixtureJwt("jwt-subject-should-lose");
107
+ fixture("auth.json", JSON.stringify({ token: jwt, userId: USER_ID }));
108
+ const credential = loadCursorIndividualCredential();
109
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader(USER_ID, jwt));
110
+ });
111
+ test("a token with no derivable user id reads unparsable — never a Bearer-only guess", () => {
112
+ fixture("auth.json", JSON.stringify({ token: SECRET }));
113
+ const credential = loadCursorIndividualCredential();
114
+ assert.equal(credential.status, "unparsable");
115
+ assert.equal(credential.authHeader, undefined);
116
+ });
117
+ test("the auth header is the WorkosCursorSessionToken cookie with the :: delimiter percent-encoded", () => {
118
+ const header = cursorIndividualAuthHeader(USER_ID, SECRET);
119
+ assert.equal(header, `WorkosCursorSessionToken=${USER_ID}%3A%3A${SECRET}`);
120
+ // Never the raw, unencoded delimiter the dashboard rejects.
121
+ assert.ok(!header.includes(`${USER_ID}::${SECRET}`));
122
+ });
123
+ test("normalizeWorkosUserId strips a provider connection-type prefix", () => {
124
+ assert.equal(normalizeWorkosUserId("google-oauth2|user_abc"), "user_abc");
125
+ assert.equal(normalizeWorkosUserId("github|12345"), "12345");
126
+ // A bare id (no prefix) passes through unchanged.
127
+ assert.equal(normalizeWorkosUserId(USER_ID), USER_ID);
128
+ // Only the LAST `|` matters, in case a value itself contains one.
129
+ assert.equal(normalizeWorkosUserId("a|b|c"), "c");
130
+ });
131
+ test("a provider-prefixed JWT subject is normalized before it reaches the cookie", () => {
132
+ const jwt = fixtureJwt("google-oauth2|user_fixture");
133
+ fixture("auth.json", JSON.stringify({ token: jwt }));
134
+ const credential = loadCursorIndividualCredential();
135
+ assert.equal(credential.status, "found");
136
+ // The cookie carries the bare id, never the provider-qualified subject.
137
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader("user_fixture", jwt));
138
+ assert.ok(!credential.authHeader.includes("google-oauth2"));
139
+ });
140
+ test("a provider-prefixed explicit userId field is normalized the same way", () => {
141
+ fixture("auth.json", JSON.stringify({ token: SECRET, userId: "github|user_fixture" }));
142
+ const credential = loadCursorIndividualCredential();
143
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader("user_fixture", SECRET));
144
+ });
145
+ test("an id that normalizes to empty (nothing after the final |) reads unparsable, never an empty-id cookie", () => {
146
+ // Non-empty before normalization (passes the raw presence check), but
147
+ // normalizeWorkosUserId("github|") === "" — must still degrade, not build
148
+ // WorkosCursorSessionToken=%3A%3A<token>.
149
+ assert.equal(normalizeWorkosUserId("github|"), "");
150
+ // Explicit-field path.
151
+ fixture("auth.json", JSON.stringify({ token: SECRET, userId: "github|" }));
152
+ const fromField = loadCursorIndividualCredential();
153
+ assert.equal(fromField.status, "unparsable");
154
+ assert.equal(fromField.authHeader, undefined);
155
+ // JWT-`sub`-derived path.
156
+ const jwt = fixtureJwt("github|");
157
+ fixture("auth.json", JSON.stringify({ token: jwt }));
158
+ const fromJwt = loadCursorIndividualCredential();
159
+ assert.equal(fromJwt.status, "unparsable");
160
+ assert.equal(fromJwt.authHeader, undefined);
161
+ });
162
+ test("changed credential formats read unparsable, never a guess", () => {
163
+ const changed = [
164
+ { totallyNewShape: true, session: { id: "abc" } },
165
+ { token: "" },
166
+ { token: " " },
167
+ { token: 12345 },
168
+ { accessToken: null },
169
+ ["token-array"],
170
+ "just-a-string",
171
+ 42,
172
+ null,
173
+ ];
174
+ for (const body of changed) {
175
+ fixture("auth.json", JSON.stringify(body));
176
+ const credential = loadCursorIndividualCredential();
177
+ assert.equal(credential.status, "unparsable", `body ${JSON.stringify(body)} must not parse`);
178
+ assert.equal(credential.authHeader, undefined);
179
+ }
180
+ fixture("auth.json", "{ not valid json{{{");
181
+ assert.equal(loadCursorIndividualCredential().status, "unparsable");
182
+ });
183
+ test("diagnostics status never carries secret material", () => {
184
+ fixture("auth.json", JSON.stringify({ token: SECRET, userId: USER_ID }));
185
+ const status = cursorIndividualCredentialStatus();
186
+ assert.equal(status.present, true);
187
+ assert.equal(status.format, "token");
188
+ assert.ok(!JSON.stringify(status).includes(SECRET), "raw token must not appear in status output");
189
+ });
190
+ test("unreadable files degrade to absent without throwing", () => {
191
+ // The default reader swallows read errors (missing file, directory at the
192
+ // candidate path, permissions): none of them throw out of the loader.
193
+ process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV] = dir;
194
+ assert.equal(loadCursorIndividualCredential().status, "absent");
195
+ process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV] = path.join(dir, "missing.json");
196
+ assert.equal(loadCursorIndividualCredential().status, "absent");
197
+ const dirAsFile = loadCursorIndividualCredential((_p) => null);
198
+ assert.equal(dirAsFile.status, "absent");
199
+ });
200
+ // ---------------------------------------------------------------------------
201
+ // NOT-267: desktop state.vscdb as the primary credential source.
202
+ // ---------------------------------------------------------------------------
203
+ /** Build a fixture desktop-state database with exactly the given keys. */
204
+ function fixtureStateDb(entries, name = "state.vscdb") {
205
+ const file = path.join(dir, name);
206
+ const db = new Database(file);
207
+ try {
208
+ db.exec("CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)");
209
+ const stmt = db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)");
210
+ for (const [key, value] of Object.entries(entries))
211
+ stmt.run(key, value);
212
+ }
213
+ finally {
214
+ db.close();
215
+ }
216
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = file;
217
+ return file;
218
+ }
219
+ /** Desktop tests use the real resolution order: no pinned credential file. */
220
+ function useDefaultSources() {
221
+ delete process.env[CURSOR_INDIVIDUAL_CREDENTIAL_FILE_ENV];
222
+ process.env[CURSOR_INDIVIDUAL_HOME_ENV] = dir;
223
+ }
224
+ test("desktop state paths resolve per OS without touching the real login", () => {
225
+ const home = path.join(dir, "fake-home");
226
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "darwin", home }), [
227
+ path.join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb"),
228
+ ]);
229
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "linux", home }), [
230
+ path.join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb"),
231
+ ]);
232
+ const appData = path.join(dir, "fake-appdata");
233
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "win32", home, appData }), [path.join(appData, "Cursor", "User", "globalStorage", "state.vscdb")]);
234
+ // A present-but-blank APPDATA is the same as unset: the roaming default
235
+ // applies instead of an empty candidate list.
236
+ process.env.APPDATA = " ";
237
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "win32", home }), [
238
+ path.join(home, "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb"),
239
+ ]);
240
+ delete process.env.APPDATA;
241
+ // Unknown platforms fall back to the Linux-style layout, never to nothing.
242
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "freebsd", home }), [
243
+ path.join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb"),
244
+ ]);
245
+ // The explicit override wins on every platform.
246
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(dir, "custom.vscdb");
247
+ assert.deepEqual(cursorIndividualDesktopStateCandidates({ platform: "darwin", home }), [
248
+ path.join(dir, "custom.vscdb"),
249
+ ]);
250
+ });
251
+ test("a fixture state.vscdb access token resolves to a desktop credential", () => {
252
+ useDefaultSources();
253
+ const jwt = fixtureJwt(USER_ID);
254
+ // Unrelated state travels alongside the token — including secret-looking
255
+ // values the loader must never select, return, or report.
256
+ const dbFile = fixtureStateDb({
257
+ [CURSOR_DESKTOP_ACCESS_TOKEN_KEY]: jwt,
258
+ "cursorAuth/refreshToken": "fixture-refresh-must-never-leak",
259
+ "cursorAuth/cachedEmail": "someone@example.com",
260
+ "someOtherFeature/state": JSON.stringify({ token: "not-a-cursor-token" }),
261
+ });
262
+ const credential = loadCursorIndividualCredential();
263
+ assert.equal(credential.status, "found");
264
+ assert.equal(credential.source, "desktop");
265
+ assert.equal(credential.path, dbFile);
266
+ assert.equal(credential.format, CURSOR_DESKTOP_FORMAT);
267
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader(USER_ID, jwt));
268
+ const status = cursorIndividualCredentialStatus();
269
+ assert.deepEqual(status, {
270
+ present: true,
271
+ source: "desktop",
272
+ path: dbFile,
273
+ format: CURSOR_DESKTOP_FORMAT,
274
+ });
275
+ const serialized = JSON.stringify({ credential: { ...credential, authHeader: undefined }, status });
276
+ assert.ok(!serialized.includes(jwt), "raw token must not appear outside the in-memory header");
277
+ assert.ok(!serialized.includes("fixture-refresh-must-never-leak"), "unrelated desktop keys must never be read");
278
+ });
279
+ test("desktop SQLite wins over Agent auth.json; Agent auth remains the fallback", () => {
280
+ useDefaultSources();
281
+ const desktopJwt = fixtureJwt("desktop-user-wins");
282
+ const agentJwt = fixtureJwt("agent-user-fallback");
283
+ fixtureStateDb({ [CURSOR_DESKTOP_ACCESS_TOKEN_KEY]: desktopJwt });
284
+ const agentFile = path.join(dir, ".cursor", "auth.json");
285
+ fs.mkdirSync(path.dirname(agentFile), { recursive: true });
286
+ fs.writeFileSync(agentFile, JSON.stringify({ token: agentJwt }));
287
+ // Both sources usable: the desktop login wins.
288
+ let credential = loadCursorIndividualCredential();
289
+ assert.equal(credential.status, "found");
290
+ assert.equal(credential.source, "desktop");
291
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader("desktop-user-wins", desktopJwt));
292
+ // Desktop database gone: the same Agent file now answers as the fallback.
293
+ fs.rmSync(path.join(dir, "state.vscdb"));
294
+ delete process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV];
295
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(dir, "missing.vscdb");
296
+ credential = loadCursorIndividualCredential();
297
+ assert.equal(credential.status, "found");
298
+ assert.equal(credential.source, "agent");
299
+ assert.equal(credential.authHeader, cursorIndividualAuthHeader("agent-user-fallback", agentJwt));
300
+ });
301
+ test("a usable Agent credential still wins over a present-but-unusable desktop token", () => {
302
+ useDefaultSources();
303
+ // Desktop token with no derivable user id (not a JWT): unusable on its
304
+ // own, but it must not sink a working Agent fallback.
305
+ fixtureStateDb({ [CURSOR_DESKTOP_ACCESS_TOKEN_KEY]: "not-a-jwt" });
306
+ const agentFile = path.join(dir, ".cursor", "auth.json");
307
+ fs.mkdirSync(path.dirname(agentFile), { recursive: true });
308
+ fs.writeFileSync(agentFile, JSON.stringify({ token: SECRET, userId: USER_ID }));
309
+ const credential = loadCursorIndividualCredential();
310
+ assert.equal(credential.status, "found");
311
+ assert.equal(credential.source, "agent");
312
+ });
313
+ test("no usable source in either login reads absent (desktop missing, Agent missing)", () => {
314
+ useDefaultSources();
315
+ process.env[CURSOR_INDIVIDUAL_DESKTOP_STATE_FILE_ENV] = path.join(dir, "missing.vscdb");
316
+ const credential = loadCursorIndividualCredential();
317
+ assert.equal(credential.status, "absent");
318
+ assert.equal(credential.source, null);
319
+ assert.equal(credential.authHeader, undefined);
320
+ });
321
+ test("a present-but-unusable desktop login reads unparsable when Agent auth cannot help", () => {
322
+ useDefaultSources();
323
+ const dbFile = fixtureStateDb({ [CURSOR_DESKTOP_ACCESS_TOKEN_KEY]: "not-a-jwt" });
324
+ const credential = loadCursorIndividualCredential();
325
+ assert.equal(credential.status, "unparsable");
326
+ assert.equal(credential.path, dbFile);
327
+ assert.equal(credential.format, CURSOR_DESKTOP_FORMAT);
328
+ assert.equal(credential.authHeader, undefined);
329
+ });
330
+ test("the desktop reader opens read-only, reads one key, and never throws", () => {
331
+ const jwt = fixtureJwt(USER_ID);
332
+ const dbFile = fixtureStateDb({ [CURSOR_DESKTOP_ACCESS_TOKEN_KEY]: jwt });
333
+ assert.equal(defaultReadDesktopAccessToken(dbFile), jwt);
334
+ // Missing database, directory path, and missing key all read null.
335
+ assert.equal(defaultReadDesktopAccessToken(path.join(dir, "missing.vscdb")), null);
336
+ assert.equal(defaultReadDesktopAccessToken(dir), null);
337
+ const noKey = fixtureStateDb({ "something/else": "x" }, "nokey.vscdb");
338
+ assert.equal(defaultReadDesktopAccessToken(noKey), null);
339
+ // The fixture database is untouched and unlocked after the read.
340
+ const db = new Database(noKey);
341
+ try {
342
+ assert.equal(db.prepare("SELECT COUNT(*) AS n FROM ItemTable").get().n, 1);
343
+ }
344
+ finally {
345
+ db.close();
346
+ }
347
+ });
348
+ test("normalizeDesktopTokenValue unwraps a JSON-quoted token; empties and non-strings read null", () => {
349
+ const jwt = fixtureJwt(USER_ID);
350
+ assert.equal(normalizeDesktopTokenValue(jwt), jwt);
351
+ assert.equal(normalizeDesktopTokenValue(` ${jwt} `), jwt);
352
+ assert.equal(normalizeDesktopTokenValue(JSON.stringify(jwt)), jwt);
353
+ assert.equal(normalizeDesktopTokenValue(""), null);
354
+ assert.equal(normalizeDesktopTokenValue(" "), null);
355
+ assert.equal(normalizeDesktopTokenValue(42), null);
356
+ assert.equal(normalizeDesktopTokenValue(null), null);
357
+ assert.equal(normalizeDesktopTokenValue('"not-closed'), null);
358
+ // A non-empty unquoted value passes through here — usability (JWT `sub`)
359
+ // is decided downstream by desktopTokenToAuthHeader, never by guessing.
360
+ assert.equal(normalizeDesktopTokenValue("null"), "null");
361
+ assert.equal(desktopTokenToAuthHeader("null"), null);
362
+ });
363
+ test("desktopTokenToAuthHeader derives the WorkOS id from the JWT sub", () => {
364
+ const jwt = fixtureJwt(USER_ID);
365
+ assert.equal(desktopTokenToAuthHeader(jwt), cursorIndividualAuthHeader(USER_ID, jwt));
366
+ assert.equal(desktopTokenToAuthHeader(fixtureJwt("google-oauth2|user_fixture")), cursorIndividualAuthHeader("user_fixture", fixtureJwt("google-oauth2|user_fixture")));
367
+ assert.equal(desktopTokenToAuthHeader("not-a-jwt"), null);
368
+ assert.equal(desktopTokenToAuthHeader(""), null);
369
+ });