kaizenai 0.1.0

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 (74) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +246 -0
  3. package/bin/kaizen +15 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/index-D-ORCGrq.js +603 -0
  6. package/dist/client/assets/index-r28mcHqz.css +32 -0
  7. package/dist/client/favicon.png +0 -0
  8. package/dist/client/fonts/body-medium.woff2 +0 -0
  9. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  10. package/dist/client/fonts/body-regular.woff2 +0 -0
  11. package/dist/client/fonts/body-semibold.woff2 +0 -0
  12. package/dist/client/index.html +22 -0
  13. package/dist/client/manifest-dark.webmanifest +24 -0
  14. package/dist/client/manifest.webmanifest +24 -0
  15. package/dist/client/pwa-192.png +0 -0
  16. package/dist/client/pwa-512.png +0 -0
  17. package/dist/client/pwa-icon.svg +15 -0
  18. package/dist/client/pwa-splash.png +0 -0
  19. package/dist/client/pwa-splash.svg +15 -0
  20. package/package.json +103 -0
  21. package/src/server/acp-shared.ts +315 -0
  22. package/src/server/agent.ts +1120 -0
  23. package/src/server/attachments.ts +133 -0
  24. package/src/server/backgrounds.ts +74 -0
  25. package/src/server/cli-runtime.ts +333 -0
  26. package/src/server/cli-supervisor.ts +81 -0
  27. package/src/server/cli.ts +68 -0
  28. package/src/server/codex-app-server-protocol.ts +453 -0
  29. package/src/server/codex-app-server.ts +1350 -0
  30. package/src/server/cursor-acp.ts +819 -0
  31. package/src/server/discovery.ts +322 -0
  32. package/src/server/event-store.ts +1369 -0
  33. package/src/server/events.ts +244 -0
  34. package/src/server/external-open.ts +272 -0
  35. package/src/server/gemini-acp.ts +844 -0
  36. package/src/server/gemini-cli.ts +525 -0
  37. package/src/server/generate-title.ts +36 -0
  38. package/src/server/git-manager.ts +79 -0
  39. package/src/server/git-repository.ts +101 -0
  40. package/src/server/harness-types.ts +20 -0
  41. package/src/server/keybindings.ts +177 -0
  42. package/src/server/machine-name.ts +22 -0
  43. package/src/server/paths.ts +112 -0
  44. package/src/server/process-utils.ts +22 -0
  45. package/src/server/project-icon.ts +344 -0
  46. package/src/server/project-metadata.ts +10 -0
  47. package/src/server/provider-catalog.ts +85 -0
  48. package/src/server/provider-settings.ts +155 -0
  49. package/src/server/quick-response.ts +153 -0
  50. package/src/server/read-models.ts +275 -0
  51. package/src/server/recovery.ts +507 -0
  52. package/src/server/restart.ts +30 -0
  53. package/src/server/server.ts +244 -0
  54. package/src/server/terminal-manager.ts +350 -0
  55. package/src/server/theme-settings.ts +179 -0
  56. package/src/server/update-manager.ts +230 -0
  57. package/src/server/usage/base-provider-usage.ts +57 -0
  58. package/src/server/usage/claude-usage.ts +558 -0
  59. package/src/server/usage/codex-usage.ts +144 -0
  60. package/src/server/usage/cursor-browser.ts +120 -0
  61. package/src/server/usage/cursor-cookies.ts +390 -0
  62. package/src/server/usage/cursor-usage.ts +490 -0
  63. package/src/server/usage/gemini-usage.ts +24 -0
  64. package/src/server/usage/provider-usage.ts +61 -0
  65. package/src/server/usage/test-helpers.ts +9 -0
  66. package/src/server/usage/types.ts +54 -0
  67. package/src/server/usage/utils.ts +325 -0
  68. package/src/server/ws-router.ts +717 -0
  69. package/src/shared/branding.ts +83 -0
  70. package/src/shared/dev-ports.ts +43 -0
  71. package/src/shared/ports.ts +2 -0
  72. package/src/shared/protocol.ts +152 -0
  73. package/src/shared/tools.ts +251 -0
  74. package/src/shared/types.ts +1028 -0
@@ -0,0 +1,120 @@
1
+ import { canOpenMacApp, resolveCommandPath } from "../process-utils"
2
+ import { buildCursorCookieHeader, mergeCursorCookies, responseSetCookies } from "./cursor-cookies"
3
+ import { parseCursorUsagePayload } from "./cursor-usage"
4
+ import type { CursorSessionCache } from "./types"
5
+
6
+ export const CURSOR_DASHBOARD_URL = "https://cursor.com/dashboard/spending"
7
+ export const CURSOR_USAGE_URL = "https://cursor.com/api/dashboard/get-current-period-usage"
8
+ export const CURSOR_BROWSER_LOGIN_TIMEOUT_MS = 5 * 60 * 1000
9
+
10
+ export async function fetchCursorEndpoint(args: {
11
+ url: string
12
+ method?: "GET" | "POST"
13
+ session: CursorSessionCache
14
+ body?: unknown
15
+ }) {
16
+ const response = await fetch(args.url, {
17
+ method: args.method ?? "GET",
18
+ headers: {
19
+ accept: "*/*",
20
+ "content-type": "application/json",
21
+ origin: "https://cursor.com",
22
+ referer: CURSOR_DASHBOARD_URL,
23
+ cookie: buildCursorCookieHeader(args.session.cookies),
24
+ "user-agent": "Kaizen/1.0",
25
+ },
26
+ body: args.body === undefined ? undefined : JSON.stringify(args.body),
27
+ })
28
+
29
+ const mergedSession: CursorSessionCache = {
30
+ cookies: mergeCursorCookies(args.session.cookies, responseSetCookies(response)),
31
+ updatedAt: Date.now(),
32
+ lastSuccessAt: args.session.lastSuccessAt,
33
+ }
34
+
35
+ return { response, session: mergedSession }
36
+ }
37
+
38
+ export function isCursorAuthFailure(response: Response) {
39
+ return response.status === 401 || response.status === 403
40
+ }
41
+
42
+ export async function attemptCursorUsageFetch(session: CursorSessionCache) {
43
+ const { response, session: nextSession } = await fetchCursorEndpoint({
44
+ url: CURSOR_USAGE_URL,
45
+ method: "POST",
46
+ session,
47
+ body: {},
48
+ })
49
+
50
+ if (isCursorAuthFailure(response)) {
51
+ return { ok: false as const, authFailed: true, session: nextSession, payload: null }
52
+ }
53
+
54
+ if (!response.ok) {
55
+ return { ok: false as const, authFailed: false, session: nextSession, payload: null }
56
+ }
57
+
58
+ const payload = parseCursorUsagePayload(await response.json().catch(() => null))
59
+ if (!payload) {
60
+ return { ok: false as const, authFailed: false, session: nextSession, payload: null }
61
+ }
62
+
63
+ return { ok: true as const, authFailed: false, session: nextSession, payload }
64
+ }
65
+
66
+ export async function refreshCursorSessionFromDashboard(session: CursorSessionCache) {
67
+ const { response, session: nextSession } = await fetchCursorEndpoint({
68
+ url: CURSOR_DASHBOARD_URL,
69
+ method: "GET",
70
+ session,
71
+ })
72
+
73
+ if (!response.ok && !isCursorAuthFailure(response)) {
74
+ return { ok: false as const, session: nextSession }
75
+ }
76
+
77
+ return { ok: true as const, session: nextSession }
78
+ }
79
+
80
+ export function resolveBrowserExecutable(platform = process.platform) {
81
+ const resolvedCommand = resolveCommandPath("google-chrome")
82
+ ?? resolveCommandPath("chromium")
83
+ ?? resolveCommandPath("chromium-browser")
84
+ ?? resolveCommandPath("brave-browser")
85
+ if (resolvedCommand) return resolvedCommand
86
+
87
+ if (platform === "darwin") {
88
+ if (canOpenMacApp("Google Chrome")) return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
89
+ if (canOpenMacApp("Chromium")) return "/Applications/Chromium.app/Contents/MacOS/Chromium"
90
+ if (canOpenMacApp("Brave Browser")) return "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ export function sessionFromBrowserCookies(cookies: Array<{
97
+ name: string
98
+ value: string
99
+ domain?: string
100
+ path?: string
101
+ expires?: number
102
+ secure?: boolean
103
+ httpOnly?: boolean
104
+ }>): CursorSessionCache {
105
+ return {
106
+ cookies: cookies
107
+ .filter((cookie) => (cookie.domain ?? "cursor.com").replace(/^\./, "").endsWith("cursor.com"))
108
+ .map((cookie) => ({
109
+ name: cookie.name,
110
+ value: cookie.value,
111
+ domain: (cookie.domain ?? "cursor.com").replace(/^\./, ""),
112
+ path: cookie.path ?? "/",
113
+ expiresAt: typeof cookie.expires === "number" && Number.isFinite(cookie.expires) ? cookie.expires * 1000 : null,
114
+ secure: cookie.secure !== false,
115
+ httpOnly: cookie.httpOnly === true,
116
+ })),
117
+ updatedAt: Date.now(),
118
+ lastSuccessAt: null,
119
+ }
120
+ }
@@ -0,0 +1,390 @@
1
+ import { Database } from "bun:sqlite"
2
+ import { copyFileSync, existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs"
3
+ import { createDecipheriv, pbkdf2Sync } from "node:crypto"
4
+ import { homedir, tmpdir } from "node:os"
5
+ import path from "node:path"
6
+ import process from "node:process"
7
+ import type { CursorCurlImportResult, CursorSessionCache, CursorSessionCookie } from "./types"
8
+
9
+ const CHROME_EPOCH_OFFSET_MS = Date.UTC(1601, 0, 1)
10
+ export const CURSOR_SESSION_COOKIE_NAME = "WorkosCursorSessionToken"
11
+
12
+ function normalizeCookieDomain(domain: string) {
13
+ return domain.startsWith(".") ? domain.slice(1) : domain
14
+ }
15
+
16
+ function isExpiredCookie(cookie: CursorSessionCookie, now = Date.now()) {
17
+ return cookie.expiresAt !== null && cookie.expiresAt <= now
18
+ }
19
+
20
+ export function mergeCursorCookies(
21
+ existing: CursorSessionCookie[],
22
+ incoming: CursorSessionCookie[],
23
+ now = Date.now()
24
+ ) {
25
+ const merged = new Map<string, CursorSessionCookie>()
26
+ for (const cookie of existing) {
27
+ if (isExpiredCookie(cookie, now)) continue
28
+ merged.set(`${normalizeCookieDomain(cookie.domain)}|${cookie.path}|${cookie.name}`, cookie)
29
+ }
30
+
31
+ for (const cookie of incoming) {
32
+ const key = `${normalizeCookieDomain(cookie.domain)}|${cookie.path}|${cookie.name}`
33
+ if (!cookie.value || isExpiredCookie(cookie, now)) {
34
+ merged.delete(key)
35
+ continue
36
+ }
37
+ merged.set(key, {
38
+ ...cookie,
39
+ domain: normalizeCookieDomain(cookie.domain),
40
+ })
41
+ }
42
+
43
+ return [...merged.values()]
44
+ .filter((cookie) => cookie.value)
45
+ .filter((cookie) => !isExpiredCookie(cookie, now))
46
+ }
47
+
48
+ export function buildCursorCookieHeader(cookies: CursorSessionCookie[]) {
49
+ return cookies
50
+ .filter((cookie) => !isExpiredCookie(cookie))
51
+ .filter((cookie) => cookie.value)
52
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
53
+ .join("; ")
54
+ }
55
+
56
+ function parseSetCookie(header: string): CursorSessionCookie | null {
57
+ const segments = header.split(";").map((part) => part.trim()).filter(Boolean)
58
+ const [cookiePair, ...attributes] = segments
59
+ if (!cookiePair) return null
60
+ const equalsIndex = cookiePair.indexOf("=")
61
+ if (equalsIndex <= 0) return null
62
+ const name = cookiePair.slice(0, equalsIndex).trim()
63
+ const value = cookiePair.slice(equalsIndex + 1).trim()
64
+ if (!name) return null
65
+
66
+ let domain = "cursor.com"
67
+ let cookiePath = "/"
68
+ let expiresAt: number | null = null
69
+ let secure = false
70
+ let httpOnly = false
71
+
72
+ for (const attribute of attributes) {
73
+ const [rawKey, ...rawRest] = attribute.split("=")
74
+ const key = rawKey.trim().toLowerCase()
75
+ const rawValue = rawRest.join("=").trim()
76
+ if (key === "domain" && rawValue) {
77
+ domain = normalizeCookieDomain(rawValue)
78
+ } else if (key === "path" && rawValue) {
79
+ cookiePath = rawValue
80
+ } else if (key === "expires" && rawValue) {
81
+ const parsed = Date.parse(rawValue)
82
+ expiresAt = Number.isFinite(parsed) ? parsed : null
83
+ } else if (key === "max-age" && rawValue) {
84
+ const seconds = Number(rawValue)
85
+ if (Number.isFinite(seconds)) {
86
+ expiresAt = Date.now() + seconds * 1000
87
+ }
88
+ } else if (key === "secure") {
89
+ secure = true
90
+ } else if (key === "httponly") {
91
+ httpOnly = true
92
+ }
93
+ }
94
+
95
+ return {
96
+ name,
97
+ value,
98
+ domain,
99
+ path: cookiePath,
100
+ expiresAt,
101
+ secure,
102
+ httpOnly,
103
+ }
104
+ }
105
+
106
+ function parseCookieHeaderValue(cookieHeader: string): CursorSessionCookie[] {
107
+ return cookieHeader
108
+ .split(";")
109
+ .map((segment) => segment.trim())
110
+ .filter(Boolean)
111
+ .map((segment): CursorSessionCookie | null => {
112
+ const equalsIndex = segment.indexOf("=")
113
+ if (equalsIndex <= 0) return null
114
+ return {
115
+ name: segment.slice(0, equalsIndex).trim(),
116
+ value: segment.slice(equalsIndex + 1).trim(),
117
+ domain: "cursor.com",
118
+ path: "/",
119
+ expiresAt: null,
120
+ secure: true,
121
+ httpOnly: true,
122
+ }
123
+ })
124
+ .filter((cookie): cookie is CursorSessionCookie => cookie !== null)
125
+ }
126
+
127
+ function extractCurlArguments(source: string, patterns: string[]) {
128
+ const values: string[] = []
129
+ for (const pattern of patterns) {
130
+ const regex = new RegExp(`${pattern}\\s+(?:'([^']*)'|"([^"]*)"|(\\S+))`, "ig")
131
+ for (const match of source.matchAll(regex)) {
132
+ values.push(match[1] ?? match[2] ?? match[3] ?? "")
133
+ }
134
+ }
135
+ return values.filter(Boolean)
136
+ }
137
+
138
+ export function importCursorSessionFromCurl(curlCommand: string): CursorCurlImportResult | null {
139
+ const cookieHeader = extractCurlArguments(curlCommand, [
140
+ "-b",
141
+ "--cookie",
142
+ ])[0] ?? extractCurlArguments(curlCommand, [
143
+ "-H",
144
+ "--header",
145
+ ])
146
+ .map((header) => header.match(/^cookie:\s*(.+)$/i)?.[1] ?? null)
147
+ .find((header): header is string => Boolean(header))
148
+ ?? null
149
+
150
+ if (!cookieHeader) return null
151
+ const cookies = parseCookieHeaderValue(cookieHeader)
152
+ if (!cookies.some((cookie) => cookie.name === CURSOR_SESSION_COOKIE_NAME)) {
153
+ return null
154
+ }
155
+
156
+ return { cookies }
157
+ }
158
+
159
+ export function responseSetCookies(response: Response): CursorSessionCookie[] {
160
+ const rawGetSetCookie = (response.headers as Headers & { getSetCookie?: () => string[] }).getSetCookie
161
+ const cookieHeaders = typeof rawGetSetCookie === "function"
162
+ ? rawGetSetCookie.call(response.headers)
163
+ : (() => {
164
+ const combined = response.headers.get("set-cookie")
165
+ return combined ? [combined] : []
166
+ })()
167
+
168
+ return cookieHeaders
169
+ .map((value) => parseSetCookie(value))
170
+ .filter((cookie): cookie is CursorSessionCookie => Boolean(cookie))
171
+ .filter((cookie) => normalizeCookieDomain(cookie.domain).endsWith("cursor.com"))
172
+ }
173
+
174
+ function parseCursorProfileCookiesDb(args: {
175
+ cookiesPath: string
176
+ browserName: string
177
+ platform: NodeJS.Platform
178
+ }): CursorSessionCookie[] {
179
+ const tempDir = mkdtempSync(path.join(tmpdir(), "kaizen-cursor-cookies-"))
180
+ const tempDbPath = path.join(tempDir, "Cookies")
181
+ try {
182
+ copyFileSync(args.cookiesPath, tempDbPath)
183
+ const database = new Database(tempDbPath, { readonly: true })
184
+ try {
185
+ const rows = database
186
+ .query(`
187
+ SELECT host_key, name, value, encrypted_value, path, expires_utc, is_secure, is_httponly
188
+ FROM cookies
189
+ WHERE host_key LIKE '%cursor.com'
190
+ `)
191
+ .all() as Array<{
192
+ host_key: string
193
+ name: string
194
+ value: string
195
+ encrypted_value: Uint8Array | null
196
+ path: string
197
+ expires_utc: number
198
+ is_secure: number
199
+ is_httponly: number
200
+ }>
201
+
202
+ const key = getChromiumCookieKey({
203
+ cookiesPath: args.cookiesPath,
204
+ browserName: args.browserName,
205
+ platform: args.platform,
206
+ })
207
+
208
+ return rows
209
+ .map((row) => {
210
+ const value = row.value || decryptChromiumCookieValue(row.encrypted_value, key, args.platform)
211
+ if (!value) return null
212
+ return {
213
+ name: row.name,
214
+ value,
215
+ domain: normalizeCookieDomain(row.host_key),
216
+ path: row.path || "/",
217
+ expiresAt: chromeTimestampToUnixMs(row.expires_utc),
218
+ secure: row.is_secure === 1,
219
+ httpOnly: row.is_httponly === 1,
220
+ } satisfies CursorSessionCookie
221
+ })
222
+ .filter((cookie): cookie is CursorSessionCookie => Boolean(cookie))
223
+ } finally {
224
+ database.close()
225
+ }
226
+ } catch {
227
+ return []
228
+ } finally {
229
+ rmSync(tempDir, { recursive: true, force: true })
230
+ }
231
+ }
232
+
233
+ function chromeTimestampToUnixMs(value: number | null | undefined) {
234
+ if (!value || value <= 0) return null
235
+ return CHROME_EPOCH_OFFSET_MS + Math.floor(value / 1000)
236
+ }
237
+
238
+ function browserRootCandidates(platform: NodeJS.Platform) {
239
+ const home = homedir()
240
+ if (platform === "darwin") {
241
+ return [
242
+ {
243
+ name: "chrome",
244
+ rootPath: path.join(home, "Library", "Application Support", "Google", "Chrome"),
245
+ safeStorageName: "Chrome Safe Storage",
246
+ },
247
+ {
248
+ name: "chromium",
249
+ rootPath: path.join(home, "Library", "Application Support", "Chromium"),
250
+ safeStorageName: "Chromium Safe Storage",
251
+ },
252
+ {
253
+ name: "brave",
254
+ rootPath: path.join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser"),
255
+ safeStorageName: "Brave Safe Storage",
256
+ },
257
+ ]
258
+ }
259
+
260
+ if (platform === "linux") {
261
+ return [
262
+ {
263
+ name: "chrome",
264
+ rootPath: path.join(home, ".config", "google-chrome"),
265
+ safeStorageName: "Chrome Safe Storage",
266
+ },
267
+ {
268
+ name: "chromium",
269
+ rootPath: path.join(home, ".config", "chromium"),
270
+ safeStorageName: "Chromium Safe Storage",
271
+ },
272
+ {
273
+ name: "brave",
274
+ rootPath: path.join(home, ".config", "BraveSoftware", "Brave-Browser"),
275
+ safeStorageName: "Brave Safe Storage",
276
+ },
277
+ ]
278
+ }
279
+
280
+ return []
281
+ }
282
+
283
+ function discoverChromiumCookieSources(platform: NodeJS.Platform) {
284
+ const profiles: Array<{ browserName: string; cookiesPath: string }> = []
285
+ for (const browser of browserRootCandidates(platform)) {
286
+ if (!existsSync(browser.rootPath)) continue
287
+ for (const entry of readdirSync(browser.rootPath, { withFileTypes: true })) {
288
+ if (!entry.isDirectory()) continue
289
+ if (entry.name !== "Default" && !entry.name.startsWith("Profile ")) continue
290
+ const cookiesPath = path.join(browser.rootPath, entry.name, "Cookies")
291
+ if (!existsSync(cookiesPath)) continue
292
+ profiles.push({ browserName: browser.name, cookiesPath })
293
+ }
294
+ }
295
+ return profiles
296
+ }
297
+
298
+ function safeStoragePasswordForMac(safeStorageName: string) {
299
+ const result = Bun.spawnSync(["security", "find-generic-password", "-w", "-s", safeStorageName], {
300
+ stdin: "ignore",
301
+ stdout: "pipe",
302
+ stderr: "ignore",
303
+ })
304
+ if (result.exitCode !== 0) return null
305
+ const output = new TextDecoder().decode(result.stdout).trim()
306
+ return output || null
307
+ }
308
+
309
+ function safeStoragePasswordForLinux(browserName: string, safeStorageName: string) {
310
+ const candidateCommands = [
311
+ ["secret-tool", "lookup", "application", browserName],
312
+ ["secret-tool", "lookup", "service", safeStorageName],
313
+ ["secret-tool", "lookup", "application", `${browserName} Safe Storage`],
314
+ ]
315
+
316
+ for (const command of candidateCommands) {
317
+ const result = Bun.spawnSync(command, {
318
+ stdin: "ignore",
319
+ stdout: "pipe",
320
+ stderr: "ignore",
321
+ })
322
+ if (result.exitCode !== 0) continue
323
+ const output = new TextDecoder().decode(result.stdout).trim()
324
+ if (output) return output
325
+ }
326
+
327
+ return "peanuts"
328
+ }
329
+
330
+ function getChromiumCookieKey(args: {
331
+ cookiesPath: string
332
+ browserName: string
333
+ platform: NodeJS.Platform
334
+ }) {
335
+ const browser = browserRootCandidates(args.platform).find((candidate) => candidate.name === args.browserName)
336
+ if (!browser) return null
337
+ const password = args.platform === "darwin"
338
+ ? safeStoragePasswordForMac(browser.safeStorageName)
339
+ : args.platform === "linux"
340
+ ? safeStoragePasswordForLinux(args.browserName, browser.safeStorageName)
341
+ : null
342
+
343
+ if (!password) return null
344
+
345
+ const iterations = args.platform === "darwin" ? 1003 : 1
346
+ return pbkdf2Sync(password, "saltysalt", iterations, 16, "sha1")
347
+ }
348
+
349
+ function decryptChromiumCookieValue(
350
+ encryptedValue: Uint8Array | null,
351
+ key: Buffer | null,
352
+ platform: NodeJS.Platform
353
+ ) {
354
+ if (!encryptedValue || encryptedValue.length === 0 || !key) return null
355
+ const encrypted = Buffer.from(encryptedValue)
356
+ const versionPrefix = encrypted.subarray(0, 3).toString("utf8")
357
+ if (platform !== "darwin" && platform !== "linux") return null
358
+ if (versionPrefix !== "v10" && versionPrefix !== "v11") return null
359
+
360
+ try {
361
+ const decipher = createDecipheriv("aes-128-cbc", key, Buffer.alloc(16, 0x20))
362
+ const decrypted = Buffer.concat([
363
+ decipher.update(encrypted.subarray(3)),
364
+ decipher.final(),
365
+ ])
366
+ return decrypted.toString("utf8")
367
+ } catch {
368
+ return null
369
+ }
370
+ }
371
+
372
+ export function bootstrapCursorSessionFromBrowser(platform = process.platform): CursorSessionCache | null {
373
+ if (platform !== "linux" && platform !== "darwin") return null
374
+
375
+ for (const source of discoverChromiumCookieSources(platform)) {
376
+ const cookies = parseCursorProfileCookiesDb({
377
+ cookiesPath: source.cookiesPath,
378
+ browserName: source.browserName,
379
+ platform,
380
+ })
381
+ if (!cookies.some((cookie) => cookie.name === CURSOR_SESSION_COOKIE_NAME)) continue
382
+ return {
383
+ cookies,
384
+ updatedAt: Date.now(),
385
+ lastSuccessAt: null,
386
+ }
387
+ }
388
+
389
+ return null
390
+ }