spiracha 1.2.0 → 1.3.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.
- package/AGENTS.md +49 -12
- package/README.md +117 -64
- package/apps/ui/AGENTS.md +16 -8
- package/apps/ui/README.md +28 -12
- package/apps/ui/dist/client/assets/{analytics-Cv0JMDN2.js → analytics-B_hYz65v.js} +1 -1
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-qiyygB7e.js +1 -0
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-z1SQC2Kg.js +1 -0
- package/apps/ui/dist/client/assets/antigravity-keychain-panel-dYuRWtCf.js +1 -0
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-CliqUr7o.js +1 -0
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-CnoBzyX6.js +1 -0
- package/apps/ui/dist/client/assets/antigravity.index-CakfZz_E.js +1 -0
- package/apps/ui/dist/client/assets/antigravity.index-DY7M1KhG.js +1 -0
- package/apps/ui/dist/client/assets/badge-aHE9ETVe.js +1 -0
- package/apps/ui/dist/client/assets/checkbox-DN3XnJaA.js +1 -0
- package/apps/ui/dist/client/assets/cursor-threads._composerId-BMQyx8qG.js +1 -0
- package/apps/ui/dist/client/assets/cursor-threads._composerId-BTlaA-tV.js +1 -0
- package/apps/ui/dist/client/assets/cursor._workspaceKey-CrgrfevV.js +1 -0
- package/apps/ui/dist/client/assets/cursor._workspaceKey-bYS2syGL.js +1 -0
- package/apps/ui/dist/client/assets/cursor.index-CTqZMPYU.js +1 -0
- package/apps/ui/dist/client/assets/cursor.index-Clsz4E_e.js +2 -0
- package/apps/ui/dist/client/assets/{data-table-Bgnh7phF.js → data-table-Cj-v-uyB.js} +2 -2
- package/apps/ui/dist/client/assets/delete-confirm-dialog-DTpzBiNK.js +11 -0
- package/apps/ui/dist/client/assets/dist-BNAn99Pu.js +1 -0
- package/apps/ui/dist/client/assets/download-P3Rp23Ad.js +1 -0
- package/apps/ui/dist/client/assets/dropdown-menu-3qB5j9nt.js +1 -0
- package/apps/ui/dist/client/assets/es2015-Dwm_turD.js +41 -0
- package/apps/ui/dist/client/assets/export-dialog-CazdrASq.js +1 -0
- package/apps/ui/dist/client/assets/formatters-BdnWuM1z.js +1 -0
- package/apps/ui/dist/client/assets/index-BVFnfS78.js +22 -0
- package/apps/ui/dist/client/assets/json-panel-DLkS30sQ.js +1 -0
- package/apps/ui/dist/client/assets/metadata-section-jnIkB7dB.js +1 -0
- package/apps/ui/dist/client/assets/{metric-card-BJX5rkHK.js → metric-card-CBZuWLzQ.js} +1 -1
- package/apps/ui/dist/client/assets/page-header-CnD21cPn.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-BLszwvYL.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-DvLxYbvk.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-COn8woBR.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-DYs98skV.js +3 -0
- package/apps/ui/dist/client/assets/refresh-ccw-BDrYXjtD.js +1 -0
- package/apps/ui/dist/client/assets/reload-error-panel-DLAg0AW2.js +1 -0
- package/apps/ui/dist/client/assets/routes-BtF5-coe.js +1 -0
- package/apps/ui/dist/client/assets/scroll-text-CqaFm9by.js +1 -0
- package/apps/ui/dist/client/assets/select-DbnpwqL6.js +1 -0
- package/apps/ui/dist/client/assets/settings-CGX3VleN.js +1 -0
- package/apps/ui/dist/client/assets/styles-Ch0r3kMZ.css +1 -0
- package/apps/ui/dist/client/assets/text-document-panel-DPleOmmq.js +1 -0
- package/apps/ui/dist/client/assets/text-filter-7M6wRo-t.js +2 -0
- package/apps/ui/dist/client/assets/threads._threadId-D5w76IB-.js +7 -0
- package/apps/ui/dist/client/assets/{threads._threadId-CUiCZSwo.js → threads._threadId-Dx85sI9P.js} +1 -1
- package/apps/ui/dist/client/assets/useMutation-MZ3Hr9h9.js +1 -0
- package/apps/ui/dist/client/assets/useQuery-Cb4V0AT0.js +1 -0
- package/apps/ui/dist/client/icon.svg +28 -0
- package/apps/ui/dist/client/manifest.json +6 -16
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CBbkUXw6.js +227 -0
- package/apps/ui/dist/server/assets/{analytics-2QpLKjlG.js → analytics-CBNOYZwJ.js} +2 -2
- package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +16 -0
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-B9Rm4EXh.js +212 -0
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BIdYNy68.js +20 -0
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +11 -0
- package/apps/ui/dist/server/assets/antigravity-db-D9gW1D8G.js +576 -0
- package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +126 -0
- package/apps/ui/dist/server/assets/antigravity-keychain-panel-DcLyBBwd.js +55 -0
- package/apps/ui/dist/server/assets/antigravity-queries-CgQhlQ7J.js +37 -0
- package/apps/ui/dist/server/assets/antigravity-server-DFUx4Khk.js +114 -0
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +11 -0
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-D42ixtzp.js +210 -0
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-DnSlSC-C.js +28 -0
- package/apps/ui/dist/server/assets/antigravity.index-DZVT-cac.js +104 -0
- package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +11 -0
- package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +26 -0
- package/apps/ui/dist/server/assets/{codex-queries-BH4Cb0v3.js → codex-queries-eOJGfHQj.js} +4 -16
- package/apps/ui/dist/server/assets/{codex-server-DqzruLmg.js → codex-server-nrETIF--.js} +149 -140
- package/apps/ui/dist/server/assets/createServerRpc-BtXIw2iP.js +12 -0
- package/apps/ui/dist/server/assets/createSsrRpc-COf5Zuye.js +16 -0
- package/apps/ui/dist/server/assets/cursor-db-B7agkAvM.js +643 -0
- package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +34 -0
- package/apps/ui/dist/server/assets/cursor-queries-BMhuJeUO.js +65 -0
- package/apps/ui/dist/server/assets/cursor-recovery-9bJLs7vG.js +361 -0
- package/apps/ui/dist/server/assets/cursor-server-BgylIFgn.js +184 -0
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +11 -0
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BsxFKzoJ.js +218 -0
- package/apps/ui/dist/server/assets/cursor-threads._composerId-DXffY_CK.js +18 -0
- package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +125 -0
- package/apps/ui/dist/server/assets/cursor._workspaceKey-BP2J1x_x.js +28 -0
- package/apps/ui/dist/server/assets/cursor._workspaceKey-BQd0e-Pd.js +399 -0
- package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +11 -0
- package/apps/ui/dist/server/assets/cursor.index-CQVxtCm8.js +189 -0
- package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +11 -0
- package/apps/ui/dist/server/assets/{delete-confirm-dialog-CWqcTXTF.js → delete-confirm-dialog-PCD7S0_M.js} +5 -4
- package/apps/ui/dist/server/assets/download-DMmiy1xf.js +92 -0
- package/apps/ui/dist/server/assets/{input-B4tEzctc.js → dropdown-menu-Dy_9t6TN.js} +1 -11
- package/apps/ui/dist/server/assets/{download-Drctxary.js → export-dialog-DaPlOGFT.js} +1 -92
- package/apps/ui/dist/server/assets/json-panel-RYsxWFae.js +16 -0
- package/apps/ui/dist/server/assets/{loading-panel-DbLdvjtR.js → loading-panel-BGFnWseS.js} +1 -1
- package/apps/ui/dist/server/assets/metadata-section-D6Lbc7D6.js +54 -0
- package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +29 -0
- package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +12 -0
- package/apps/ui/dist/server/assets/{projects._project-gT01HBqH.js → projects._project-DUN3iWfg.js} +4 -4
- package/apps/ui/dist/server/assets/{projects._project-DreIU5b0.js → projects._project-Dim9Y0kD.js} +54 -26
- package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +12 -0
- package/apps/ui/dist/server/assets/{projects.index-BYmgSGAj.js → projects.index-DjSQK5dm.js} +23 -27
- package/apps/ui/dist/server/assets/{projects.index-CaplpeMy.js → reload-error-panel-BJMxY3U1.js} +5 -6
- package/apps/ui/dist/server/assets/{router-Qj5Kn7bl.js → router-DrDgc-LD.js} +131 -44
- package/apps/ui/dist/server/assets/{routes-_LbCIjtJ.js → routes-B-GlEe2C.js} +54 -39
- package/apps/ui/dist/server/assets/{routes-BtcXuK0x.js → routes-CNHAUMwo.js} +2 -2
- package/apps/ui/dist/server/assets/{settings-MvWDgc1u.js → settings-OayxIYQQ.js} +1 -1
- package/apps/ui/dist/server/assets/shared-CPRNYIql.js +134 -0
- package/apps/ui/dist/server/assets/text-document-panel-D8JbQWAn.js +23 -0
- package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +36 -0
- package/apps/ui/dist/server/assets/{threads._threadId-DcbAJkwf.js → threads._threadId-CJzm4KrZ.js} +3 -3
- package/apps/ui/dist/server/assets/{threads._threadId-D5m6ypGw.js → threads._threadId-DODTYddm.js} +69 -76
- package/apps/ui/dist/server/server.js +77 -13
- package/package.json +19 -9
- package/src/export-cursor.ts +244 -0
- package/src/lib/antigravity-db.ts +936 -0
- package/src/lib/antigravity-exporter-types.ts +70 -0
- package/src/lib/antigravity-keychain.ts +203 -0
- package/src/lib/codex-browser-db.ts +7 -1
- package/src/lib/codex-browser-types.ts +22 -1
- package/src/lib/codex-thread-recovery.ts +202 -0
- package/src/lib/cursor-db.ts +1096 -0
- package/src/lib/cursor-exporter-types.ts +190 -0
- package/src/lib/cursor-exporter.ts +266 -0
- package/src/lib/cursor-recovery.ts +543 -0
- package/src/lib/cursor-transcript.ts +183 -0
- package/src/spiracha.ts +16 -3
- package/src/ui-cli.ts +2 -2
- package/apps/ui/dist/client/assets/checkbox-DjHij7DJ.js +0 -1
- package/apps/ui/dist/client/assets/delete-confirm-dialog-CIZy_LXD.js +0 -11
- package/apps/ui/dist/client/assets/download-DQtfva4z.js +0 -1
- package/apps/ui/dist/client/assets/es2015-DsDKdYCE.js +0 -41
- package/apps/ui/dist/client/assets/formatters-CWFrMKSn.js +0 -1
- package/apps/ui/dist/client/assets/index-C_-e0lDI.js +0 -22
- package/apps/ui/dist/client/assets/input-BbgApiqZ.js +0 -1
- package/apps/ui/dist/client/assets/page-header-ODLuGLAB.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-C2Pys_bB.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-CHvAKvlu.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-BmwtS1x-.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-CuLw73mt.js +0 -1
- package/apps/ui/dist/client/assets/routes-CfnaTOlj.js +0 -1
- package/apps/ui/dist/client/assets/select-B1kH_5lx.js +0 -1
- package/apps/ui/dist/client/assets/settings-mYTB3sso.js +0 -1
- package/apps/ui/dist/client/assets/styles-CMrP9Jb4.css +0 -1
- package/apps/ui/dist/client/assets/threads._threadId-C_47okme.js +0 -7
- package/apps/ui/dist/client/favicon.ico +0 -0
- package/apps/ui/dist/client/logo192.png +0 -0
- package/apps/ui/dist/client/logo512.png +0 -0
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-kj_QB_26.js +0 -99
- package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +0 -25
- package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +0 -26
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { n as asObject, r as asString, t as asNumber } from "./shared-CPRNYIql.js";
|
|
2
|
+
import { getCursorGlobalDbPath, getCursorProjectsDir, getCursorWorkspaceStorageDir, resolveCursorUserDir } from "./cursor-exporter-types-CI3goo-c.js";
|
|
3
|
+
import { Database, constants } from "bun:sqlite";
|
|
4
|
+
import { readdir, stat } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
//#region ../../src/lib/cursor-db.ts
|
|
7
|
+
var CURSOR_READONLY_DB_OPEN_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
|
|
8
|
+
var getCursorReadonlyDbUri = (dbPath) => {
|
|
9
|
+
const normalizedPath = dbPath.replace(/\\/gu, "/");
|
|
10
|
+
return `file://${(normalizedPath.startsWith("/") ? normalizedPath : `/${normalizedPath}`).split("/").map((segment) => /^[A-Za-z]:$/u.test(segment) ? segment : encodeURIComponent(segment)).join("/")}?immutable=1`;
|
|
11
|
+
};
|
|
12
|
+
var openCursorReadonlyDb = (dbPath) => {
|
|
13
|
+
return new Database(getCursorReadonlyDbUri(dbPath), CURSOR_READONLY_DB_OPEN_FLAGS);
|
|
14
|
+
};
|
|
15
|
+
var pathExists = async (target) => {
|
|
16
|
+
try {
|
|
17
|
+
await stat(target);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var isMissingOrUnreadableCursorStoreError = (error) => {
|
|
24
|
+
const code = error.code;
|
|
25
|
+
return code === "ENOENT" || code === "ENOTDIR" || code === "SQLITE_CANTOPEN";
|
|
26
|
+
};
|
|
27
|
+
var readItemValue = (db, key) => {
|
|
28
|
+
const row = db.query("SELECT value FROM ItemTable WHERE key = ?").get(key);
|
|
29
|
+
if (!row?.value) return null;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(row.value);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var readKvValue = (db, key) => {
|
|
37
|
+
const row = db.query("SELECT value FROM cursorDiskKV WHERE key = ?").get(key);
|
|
38
|
+
if (!row?.value) return null;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(row.value);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var decodeCursorUri = (uri) => {
|
|
46
|
+
if (!uri) return "";
|
|
47
|
+
if (uri.startsWith("file://")) return decodeURIComponent(uri.slice(7));
|
|
48
|
+
return uri;
|
|
49
|
+
};
|
|
50
|
+
var normalizeCursorPath = (value) => {
|
|
51
|
+
const decoded = decodeCursorUri(value.trim());
|
|
52
|
+
if (!decoded) return "";
|
|
53
|
+
return decoded.replace(/\/+$/u, "") || decoded;
|
|
54
|
+
};
|
|
55
|
+
var parseCodeWorkspaceFolders = async (workspaceFilePath) => {
|
|
56
|
+
if (!workspaceFilePath.endsWith(".code-workspace")) return [];
|
|
57
|
+
try {
|
|
58
|
+
const data = await Bun.file(workspaceFilePath).json();
|
|
59
|
+
const folders = [];
|
|
60
|
+
for (const entry of data.folders ?? []) {
|
|
61
|
+
const folderPath = entry.path;
|
|
62
|
+
if (!folderPath) continue;
|
|
63
|
+
folders.push(folderPath.startsWith("/") ? normalizeCursorPath(folderPath) : normalizeCursorPath(path.join(path.dirname(workspaceFilePath), folderPath)));
|
|
64
|
+
}
|
|
65
|
+
return folders;
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var loadGlobalComposerHeaders = (globalDbPath) => {
|
|
71
|
+
const db = openCursorReadonlyDb(globalDbPath);
|
|
72
|
+
try {
|
|
73
|
+
return readItemValue(db, "composer.composerHeaders")?.allComposers ?? [];
|
|
74
|
+
} finally {
|
|
75
|
+
db.close();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var readBucketWorkspaceJson = async (workspaceJsonPath) => {
|
|
79
|
+
try {
|
|
80
|
+
return await Bun.file(workspaceJsonPath).json();
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var resolveBucketIdentity = async (wsData, bucketId) => {
|
|
86
|
+
if (wsData.folder) {
|
|
87
|
+
const folder = normalizeCursorPath(wsData.folder);
|
|
88
|
+
return {
|
|
89
|
+
folders: folder ? [folder] : [],
|
|
90
|
+
kind: "folder",
|
|
91
|
+
label: folder ? path.basename(folder) : bucketId,
|
|
92
|
+
uri: wsData.folder
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (wsData.workspace) {
|
|
96
|
+
const workspacePath = normalizeCursorPath(wsData.workspace);
|
|
97
|
+
return {
|
|
98
|
+
folders: workspacePath ? await parseCodeWorkspaceFolders(workspacePath) : [],
|
|
99
|
+
kind: "workspace",
|
|
100
|
+
label: workspacePath ? path.basename(workspacePath) : bucketId,
|
|
101
|
+
uri: wsData.workspace
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
folders: [],
|
|
106
|
+
kind: "unknown",
|
|
107
|
+
label: bucketId,
|
|
108
|
+
uri: ""
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
var readBucketComposerIds = (dbPath) => {
|
|
112
|
+
try {
|
|
113
|
+
const db = openCursorReadonlyDb(dbPath);
|
|
114
|
+
try {
|
|
115
|
+
return (readItemValue(db, "composer.composerData")?.allComposers ?? []).map((entry) => entry.composerId).filter((value) => Boolean(value));
|
|
116
|
+
} finally {
|
|
117
|
+
db.close();
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var loadCursorBuckets = async (userDir = resolveCursorUserDir()) => {
|
|
124
|
+
const workspaceStorageDir = getCursorWorkspaceStorageDir(userDir);
|
|
125
|
+
let bucketIds = [];
|
|
126
|
+
try {
|
|
127
|
+
bucketIds = await readdir(workspaceStorageDir);
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const globalDbPath = getCursorGlobalDbPath(userDir);
|
|
132
|
+
const headerIdsByBucket = /* @__PURE__ */ new Map();
|
|
133
|
+
if (await pathExists(globalDbPath)) for (const header of loadGlobalComposerHeaders(globalDbPath)) {
|
|
134
|
+
const id = header.workspaceIdentifier?.id;
|
|
135
|
+
if (id && header.composerId) {
|
|
136
|
+
const set = headerIdsByBucket.get(id) ?? /* @__PURE__ */ new Set();
|
|
137
|
+
set.add(header.composerId);
|
|
138
|
+
headerIdsByBucket.set(id, set);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const buckets = [];
|
|
142
|
+
for (const bucketId of bucketIds) {
|
|
143
|
+
const bucket = await buildBucket(workspaceStorageDir, bucketId, headerIdsByBucket);
|
|
144
|
+
if (bucket) buckets.push(bucket);
|
|
145
|
+
}
|
|
146
|
+
return buckets;
|
|
147
|
+
};
|
|
148
|
+
var buildBucket = async (workspaceStorageDir, bucketId, headerIdsByBucket) => {
|
|
149
|
+
const root = path.join(workspaceStorageDir, bucketId);
|
|
150
|
+
const workspaceJsonPath = path.join(root, "workspace.json");
|
|
151
|
+
const dbPath = path.join(root, "state.vscdb");
|
|
152
|
+
if (!await pathExists(workspaceJsonPath) || !await pathExists(dbPath)) return null;
|
|
153
|
+
const wsData = await readBucketWorkspaceJson(workspaceJsonPath);
|
|
154
|
+
if (!wsData || !wsData.folder && !wsData.workspace) return null;
|
|
155
|
+
let identity;
|
|
156
|
+
let dbStat;
|
|
157
|
+
let composerIds;
|
|
158
|
+
try {
|
|
159
|
+
identity = await resolveBucketIdentity(wsData, bucketId);
|
|
160
|
+
dbStat = await stat(dbPath);
|
|
161
|
+
composerIds = readBucketComposerIds(dbPath);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (isMissingOrUnreadableCursorStoreError(error)) return null;
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
const headerIds = headerIdsByBucket.get(bucketId) ?? /* @__PURE__ */ new Set();
|
|
167
|
+
const threadComposerIds = [...new Set([...composerIds, ...headerIds])];
|
|
168
|
+
return {
|
|
169
|
+
bucketId,
|
|
170
|
+
composerCount: composerIds.length,
|
|
171
|
+
dbPath,
|
|
172
|
+
dbSizeBytes: dbStat.size,
|
|
173
|
+
folders: identity.folders,
|
|
174
|
+
globalHeaderCount: headerIds.size,
|
|
175
|
+
kind: identity.kind,
|
|
176
|
+
label: identity.label,
|
|
177
|
+
mtimeMs: dbStat.mtimeMs,
|
|
178
|
+
threadComposerIds,
|
|
179
|
+
uri: identity.uri,
|
|
180
|
+
workspaceJsonPath
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
var getCursorWorkspaceGroupKey = (bucket) => {
|
|
184
|
+
if (bucket.kind === "folder" && bucket.folders[0]) return `folder:${bucket.folders[0]}`;
|
|
185
|
+
if (bucket.kind === "workspace") return `workspace:${normalizeCursorPath(bucket.uri)}`;
|
|
186
|
+
return `unknown:${bucket.bucketId}`;
|
|
187
|
+
};
|
|
188
|
+
var groupCursorBuckets = (buckets) => {
|
|
189
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
190
|
+
for (const bucket of buckets) {
|
|
191
|
+
const key = getCursorWorkspaceGroupKey(bucket);
|
|
192
|
+
const list = grouped.get(key) ?? [];
|
|
193
|
+
list.push(bucket);
|
|
194
|
+
grouped.set(key, list);
|
|
195
|
+
}
|
|
196
|
+
const groups = [];
|
|
197
|
+
for (const [key, list] of grouped.entries()) {
|
|
198
|
+
const ranked = [...list].sort((a, b) => b.mtimeMs - a.mtimeMs || b.dbSizeBytes - a.dbSizeBytes);
|
|
199
|
+
const primary = ranked[0];
|
|
200
|
+
const newest = ranked[0];
|
|
201
|
+
const threadCount = new Set(ranked.flatMap((bucket) => bucket.threadComposerIds)).size;
|
|
202
|
+
const olderWithData = ranked.slice(1).some((bucket) => bucket.composerCount > 0 || bucket.globalHeaderCount > 0);
|
|
203
|
+
groups.push({
|
|
204
|
+
buckets: ranked,
|
|
205
|
+
folders: primary.folders,
|
|
206
|
+
key,
|
|
207
|
+
kind: primary.kind,
|
|
208
|
+
label: primary.label,
|
|
209
|
+
lastActiveMs: Math.max(...ranked.map((bucket) => bucket.mtimeMs)),
|
|
210
|
+
needsRecovery: ranked.length > 1 && olderWithData && newest.composerCount === 0,
|
|
211
|
+
threadCount,
|
|
212
|
+
uri: primary.uri
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return groups.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
|
|
216
|
+
};
|
|
217
|
+
var listCursorWorkspaceGroups = async (userDir = resolveCursorUserDir()) => {
|
|
218
|
+
return (await discoverCursorWorkspaces(userDir)).groups;
|
|
219
|
+
};
|
|
220
|
+
var countBubbles = (db, composerId) => {
|
|
221
|
+
const row = db.query("SELECT COUNT(*) AS count, COALESCE(SUM(length(value)), 0) AS bytes FROM cursorDiskKV WHERE key LIKE ?").get(`bubbleId:${composerId}:%`);
|
|
222
|
+
return {
|
|
223
|
+
bytes: row.bytes,
|
|
224
|
+
count: row.count
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
var findCursorTranscriptDirs = async (composerId, userDir = resolveCursorUserDir()) => {
|
|
228
|
+
const projectsDir = getCursorProjectsDir(userDir);
|
|
229
|
+
if (!await pathExists(projectsDir)) return [];
|
|
230
|
+
const matches = [];
|
|
231
|
+
let projectDirs = [];
|
|
232
|
+
try {
|
|
233
|
+
projectDirs = await readdir(projectsDir);
|
|
234
|
+
} catch {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
for (const projectDir of projectDirs) {
|
|
238
|
+
const transcriptDir = path.join(projectsDir, projectDir, "agent-transcripts", composerId);
|
|
239
|
+
if (await pathExists(transcriptDir)) matches.push(transcriptDir);
|
|
240
|
+
}
|
|
241
|
+
return matches;
|
|
242
|
+
};
|
|
243
|
+
var listCursorThreadsForGroup = async (group, userDir = resolveCursorUserDir(), options = {}) => {
|
|
244
|
+
const threads = (await discoverCursorWorkspaces(userDir)).threadsByKey.get(group.key) ?? [];
|
|
245
|
+
if (options.includeTranscriptDirs === false) return threads;
|
|
246
|
+
return Promise.all(threads.map(async (thread) => ({
|
|
247
|
+
...thread,
|
|
248
|
+
transcriptDirs: await findCursorTranscriptDirs(thread.composerId, userDir)
|
|
249
|
+
})));
|
|
250
|
+
};
|
|
251
|
+
var DISCOVERY_TTL_MS = 6e4;
|
|
252
|
+
var UNKNOWN_GROUP_KEY = "unknown";
|
|
253
|
+
var discoveryCache = null;
|
|
254
|
+
var invalidateCursorDiscoveryCache = () => {
|
|
255
|
+
discoveryCache = null;
|
|
256
|
+
};
|
|
257
|
+
var DEV_CONTAINER_DIRS = [
|
|
258
|
+
"workspace",
|
|
259
|
+
"projects",
|
|
260
|
+
"dev",
|
|
261
|
+
"code",
|
|
262
|
+
"repos",
|
|
263
|
+
"src",
|
|
264
|
+
"Documents",
|
|
265
|
+
"Downloads",
|
|
266
|
+
"Desktop"
|
|
267
|
+
];
|
|
268
|
+
var REVERSE_WORKSPACE_ROOT_RE = /^\/Users\/[^/]+\/workspace\/reverse\/[^/]+/u;
|
|
269
|
+
var CONTAINER_ROOT_RE = new RegExp(`^(/Users/[^/]+/(?:${DEV_CONTAINER_DIRS.join("|")})/[^/]+)`);
|
|
270
|
+
var ABS_PATH_RE = /\/Users\/[^"'\s:,)\]]+/g;
|
|
271
|
+
var isNoisePath = (value) => /\/Library(?:\/|$)|\/\.cursor(?:\/|$)|\/node_modules\/|\/\.git\/|^\/tmp|^\/var|^\/private|\/\.Trash\//u.test(value) || /^\/Users\/[^/]+\/(?:Downloads|Desktop)$/u.test(value);
|
|
272
|
+
var stripLikelyFileName = (value) => {
|
|
273
|
+
return path.basename(value).includes(".") ? path.dirname(value) : value;
|
|
274
|
+
};
|
|
275
|
+
var containerRootFromPath = (value) => {
|
|
276
|
+
const candidate = stripLikelyFileName(normalizeCursorPath(value));
|
|
277
|
+
const reverseMatch = candidate.match(REVERSE_WORKSPACE_ROOT_RE);
|
|
278
|
+
if (reverseMatch) return reverseMatch[0] ?? null;
|
|
279
|
+
const match = candidate.match(CONTAINER_ROOT_RE);
|
|
280
|
+
if (match) return match[1] ?? null;
|
|
281
|
+
const parts = candidate.split("/");
|
|
282
|
+
if (parts.length >= 4 && parts[1] === "Users") return `/${parts.slice(1, 4).join("/")}`;
|
|
283
|
+
return null;
|
|
284
|
+
};
|
|
285
|
+
var inferFolderFromPaths = (paths) => {
|
|
286
|
+
const counts = /* @__PURE__ */ new Map();
|
|
287
|
+
for (const value of paths) {
|
|
288
|
+
if (isNoisePath(value)) continue;
|
|
289
|
+
const root = containerRootFromPath(value);
|
|
290
|
+
if (root) counts.set(root, (counts.get(root) ?? 0) + 1);
|
|
291
|
+
}
|
|
292
|
+
let best = null;
|
|
293
|
+
let bestCount = 0;
|
|
294
|
+
for (const [root, count] of counts) if (count > bestCount) {
|
|
295
|
+
best = root;
|
|
296
|
+
bestCount = count;
|
|
297
|
+
}
|
|
298
|
+
return best;
|
|
299
|
+
};
|
|
300
|
+
var inferFolderFromBlob = (blob) => {
|
|
301
|
+
const matches = blob.match(ABS_PATH_RE);
|
|
302
|
+
return matches ? inferFolderFromPaths(matches) : null;
|
|
303
|
+
};
|
|
304
|
+
var readCursorFileHistoryProjectActivity = async (userDir) => {
|
|
305
|
+
const historyDir = path.join(userDir, "History");
|
|
306
|
+
let entries = [];
|
|
307
|
+
try {
|
|
308
|
+
entries = await readdir(historyDir, { withFileTypes: true });
|
|
309
|
+
} catch {
|
|
310
|
+
return /* @__PURE__ */ new Map();
|
|
311
|
+
}
|
|
312
|
+
const activity = /* @__PURE__ */ new Map();
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
if (!entry.isDirectory()) continue;
|
|
315
|
+
const entriesPath = path.join(historyDir, entry.name, "entries.json");
|
|
316
|
+
let data;
|
|
317
|
+
try {
|
|
318
|
+
data = await Bun.file(entriesPath).json();
|
|
319
|
+
} catch {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const folder = containerRootFromPath(typeof data.resource === "string" ? data.resource : "");
|
|
323
|
+
if (!folder || isNoisePath(folder)) continue;
|
|
324
|
+
const lastActiveMs = Math.max(0, ...(data.entries ?? []).map((item) => item.timestamp ?? 0));
|
|
325
|
+
activity.set(folder, Math.max(activity.get(folder) ?? 0, lastActiveMs));
|
|
326
|
+
}
|
|
327
|
+
return activity;
|
|
328
|
+
};
|
|
329
|
+
var inferFolderFromBubbles = (db, composerId) => {
|
|
330
|
+
const rows = db.query("SELECT value FROM cursorDiskKV WHERE key LIKE ? LIMIT 80").all(`bubbleId:${composerId}:%`);
|
|
331
|
+
const paths = [];
|
|
332
|
+
for (const { value } of rows) {
|
|
333
|
+
let bubble;
|
|
334
|
+
try {
|
|
335
|
+
bubble = JSON.parse(value);
|
|
336
|
+
} catch {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const tool = asObject(bubble.toolFormerData ?? null);
|
|
340
|
+
if (!tool) continue;
|
|
341
|
+
const matches = `${asString(tool.rawArgs ?? null) ?? ""} ${asString(tool.params ?? null) ?? ""}`.match(ABS_PATH_RE);
|
|
342
|
+
if (matches) paths.push(...matches);
|
|
343
|
+
if (paths.length > 200) break;
|
|
344
|
+
}
|
|
345
|
+
return inferFolderFromPaths(paths);
|
|
346
|
+
};
|
|
347
|
+
var readAllHeads = (db) => {
|
|
348
|
+
const rows = db.query(`SELECT substr(key, 14) AS id, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'`).all();
|
|
349
|
+
return new Map(rows.map((row) => [row.id, parseGlobalHead(row.value)]));
|
|
350
|
+
};
|
|
351
|
+
var parseGlobalHead = (value) => {
|
|
352
|
+
let parsed = {};
|
|
353
|
+
try {
|
|
354
|
+
parsed = JSON.parse(value);
|
|
355
|
+
} catch {
|
|
356
|
+
return {
|
|
357
|
+
createdAtMs: null,
|
|
358
|
+
lastUpdatedAtMs: null,
|
|
359
|
+
mode: null,
|
|
360
|
+
name: null,
|
|
361
|
+
pathHint: inferFolderFromBlob(value)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
createdAtMs: asNumber(parsed.createdAt ?? null),
|
|
366
|
+
lastUpdatedAtMs: asNumber(parsed.lastUpdatedAt ?? null),
|
|
367
|
+
mode: asString(parsed.unifiedMode ?? null),
|
|
368
|
+
name: asString(parsed.name ?? null),
|
|
369
|
+
pathHint: inferFolderFromBlob(value)
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
var readBubbleStats = (db) => {
|
|
373
|
+
const rows = db.query(`SELECT substr(key, 10, instr(substr(key, 10), ':') - 1) AS id,
|
|
374
|
+
COUNT(*) AS count,
|
|
375
|
+
COALESCE(SUM(length(value)), 0) AS bytes
|
|
376
|
+
FROM cursorDiskKV WHERE key GLOB 'bubbleId:*:*' GROUP BY id`).all();
|
|
377
|
+
return new Map(rows.map((row) => [row.id, {
|
|
378
|
+
bytes: row.bytes,
|
|
379
|
+
count: row.count
|
|
380
|
+
}]));
|
|
381
|
+
};
|
|
382
|
+
var readHeaderInfo = (globalDbPath) => {
|
|
383
|
+
const info = /* @__PURE__ */ new Map();
|
|
384
|
+
for (const header of loadGlobalComposerHeaders(globalDbPath)) {
|
|
385
|
+
if (!header.composerId) continue;
|
|
386
|
+
const identifier = header.workspaceIdentifier;
|
|
387
|
+
const uriPath = identifier?.uri?.path ?? identifier?.uri?.fsPath ?? null;
|
|
388
|
+
info.set(header.composerId, {
|
|
389
|
+
bucketId: identifier?.id ?? null,
|
|
390
|
+
name: typeof header.name === "string" ? header.name : null,
|
|
391
|
+
uriPath: uriPath ? normalizeCursorPath(uriPath) : null
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return info;
|
|
395
|
+
};
|
|
396
|
+
var collectBucketComposerIds = (buckets) => {
|
|
397
|
+
const map = /* @__PURE__ */ new Map();
|
|
398
|
+
for (const bucket of buckets) for (const composerId of readBucketComposerIds(bucket.dbPath)) if (!map.has(composerId)) map.set(composerId, bucket.bucketId);
|
|
399
|
+
return map;
|
|
400
|
+
};
|
|
401
|
+
var findLinkedBucketId = (composerId, headerInfo, bucketIdToGroupKey, bucketComposerIds) => {
|
|
402
|
+
if (headerInfo?.bucketId && bucketIdToGroupKey.has(headerInfo.bucketId)) return headerInfo.bucketId;
|
|
403
|
+
return bucketComposerIds.get(composerId) ?? null;
|
|
404
|
+
};
|
|
405
|
+
var resolveThreadFolderHint = (composerId, head, headerInfo, stat, linkedBucketId, bucketIdToGroupKey, bucketIdToFolder, db) => {
|
|
406
|
+
if (linkedBucketId && bucketIdToGroupKey.has(linkedBucketId)) return {
|
|
407
|
+
folder: bucketIdToFolder.get(linkedBucketId) ?? null,
|
|
408
|
+
groupKey: bucketIdToGroupKey.get(linkedBucketId)
|
|
409
|
+
};
|
|
410
|
+
if (headerInfo?.uriPath) return {
|
|
411
|
+
folder: headerInfo.uriPath,
|
|
412
|
+
groupKey: `folder:${headerInfo.uriPath}`
|
|
413
|
+
};
|
|
414
|
+
if (head?.pathHint) return {
|
|
415
|
+
folder: head.pathHint,
|
|
416
|
+
groupKey: `folder:${head.pathHint}`
|
|
417
|
+
};
|
|
418
|
+
if (stat.count > 0) {
|
|
419
|
+
const folder = inferFolderFromBubbles(db, composerId);
|
|
420
|
+
return {
|
|
421
|
+
folder,
|
|
422
|
+
groupKey: folder ? `folder:${folder}` : UNKNOWN_GROUP_KEY
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
folder: null,
|
|
427
|
+
groupKey: UNKNOWN_GROUP_KEY
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
var resolveThreadFolder = (composerId, head, headerInfo, stat, bucketIdToGroupKey, bucketIdToFolder, bucketComposerIds, db) => {
|
|
431
|
+
const linkedBucketId = findLinkedBucketId(composerId, headerInfo, bucketIdToGroupKey, bucketComposerIds);
|
|
432
|
+
const { folder, groupKey } = resolveThreadFolderHint(composerId, head, headerInfo, stat, linkedBucketId, bucketIdToGroupKey, bucketIdToFolder, db);
|
|
433
|
+
return {
|
|
434
|
+
bucketId: linkedBucketId,
|
|
435
|
+
composerId,
|
|
436
|
+
createdAtMs: head?.createdAtMs ?? null,
|
|
437
|
+
folder,
|
|
438
|
+
groupKey,
|
|
439
|
+
groupLabel: folder ? path.basename(folder) : "Unknown project",
|
|
440
|
+
lastUpdatedAtMs: head?.lastUpdatedAtMs ?? null,
|
|
441
|
+
mode: head?.mode ?? null,
|
|
442
|
+
name: head?.name || headerInfo?.name || "(untitled)",
|
|
443
|
+
stat
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
var toThreadSummary = (resolved) => ({
|
|
447
|
+
bubbleBytes: resolved.stat.bytes,
|
|
448
|
+
bubbleCount: resolved.stat.count,
|
|
449
|
+
bucketId: resolved.bucketId,
|
|
450
|
+
composerId: resolved.composerId,
|
|
451
|
+
createdAtMs: resolved.createdAtMs,
|
|
452
|
+
lastUpdatedAtMs: resolved.lastUpdatedAtMs,
|
|
453
|
+
mode: resolved.mode,
|
|
454
|
+
name: resolved.name,
|
|
455
|
+
transcriptDirs: [],
|
|
456
|
+
workspaceKey: resolved.groupKey,
|
|
457
|
+
workspaceLabel: resolved.groupLabel
|
|
458
|
+
});
|
|
459
|
+
var assembleDiscovery = (resolved, bucketGroups, fileHistoryActivity) => {
|
|
460
|
+
const threadsByKey = /* @__PURE__ */ new Map();
|
|
461
|
+
const lastActiveByKey = /* @__PURE__ */ new Map();
|
|
462
|
+
for (const thread of resolved) {
|
|
463
|
+
if (thread.groupKey === UNKNOWN_GROUP_KEY && thread.stat.count === 0) continue;
|
|
464
|
+
const list = threadsByKey.get(thread.groupKey) ?? [];
|
|
465
|
+
list.push(toThreadSummary(thread));
|
|
466
|
+
threadsByKey.set(thread.groupKey, list);
|
|
467
|
+
lastActiveByKey.set(thread.groupKey, Math.max(lastActiveByKey.get(thread.groupKey) ?? 0, thread.lastUpdatedAtMs ?? 0));
|
|
468
|
+
}
|
|
469
|
+
for (const [folder, lastActiveMs] of fileHistoryActivity) {
|
|
470
|
+
const key = `folder:${folder}`;
|
|
471
|
+
lastActiveByKey.set(key, Math.max(lastActiveByKey.get(key) ?? 0, lastActiveMs));
|
|
472
|
+
}
|
|
473
|
+
for (const list of threadsByKey.values()) list.sort((a, b) => (b.lastUpdatedAtMs ?? 0) - (a.lastUpdatedAtMs ?? 0));
|
|
474
|
+
return {
|
|
475
|
+
groups: buildDiscoveryGroups(threadsByKey, bucketGroups, lastActiveByKey),
|
|
476
|
+
threadsByKey
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
var mergeBucketGroup = (bucketGroup, threadsByKey, lastActiveByKey) => {
|
|
480
|
+
const threads = threadsByKey.get(bucketGroup.key) ?? [];
|
|
481
|
+
return {
|
|
482
|
+
...bucketGroup,
|
|
483
|
+
lastActiveMs: Math.max(bucketGroup.lastActiveMs, lastActiveByKey.get(bucketGroup.key) ?? 0),
|
|
484
|
+
threadCount: threads.length || bucketGroup.threadCount
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
var buildBucketlessGroup = (key, threadCount, lastActiveMs) => {
|
|
488
|
+
const isUnknown = key === UNKNOWN_GROUP_KEY;
|
|
489
|
+
const folder = isUnknown ? "" : key.slice(7);
|
|
490
|
+
return {
|
|
491
|
+
buckets: [],
|
|
492
|
+
folders: folder ? [folder] : [],
|
|
493
|
+
key,
|
|
494
|
+
kind: isUnknown ? "unknown" : "folder",
|
|
495
|
+
label: isUnknown ? "Unknown project" : path.basename(folder),
|
|
496
|
+
lastActiveMs,
|
|
497
|
+
needsRecovery: false,
|
|
498
|
+
threadCount,
|
|
499
|
+
uri: folder ? `file://${folder}` : ""
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
var buildDiscoveryGroups = (threadsByKey, bucketGroups, lastActiveByKey) => {
|
|
503
|
+
const seen = new Set(bucketGroups.map((group) => group.key));
|
|
504
|
+
const groups = bucketGroups.map((group) => mergeBucketGroup(group, threadsByKey, lastActiveByKey));
|
|
505
|
+
const keys = new Set([...threadsByKey.keys(), ...lastActiveByKey.keys()]);
|
|
506
|
+
for (const key of keys) {
|
|
507
|
+
const threads = threadsByKey.get(key) ?? [];
|
|
508
|
+
if (!seen.has(key) && (threads.length > 0 || key !== UNKNOWN_GROUP_KEY)) {
|
|
509
|
+
groups.push(buildBucketlessGroup(key, threads.length, lastActiveByKey.get(key) ?? 0));
|
|
510
|
+
seen.add(key);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return groups.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
|
|
514
|
+
};
|
|
515
|
+
var buildDiscovery = async (userDir) => {
|
|
516
|
+
const buckets = await loadCursorBuckets(userDir);
|
|
517
|
+
const bucketGroups = groupCursorBuckets(buckets);
|
|
518
|
+
const fileHistoryActivity = await readCursorFileHistoryProjectActivity(userDir);
|
|
519
|
+
const globalDbPath = getCursorGlobalDbPath(userDir);
|
|
520
|
+
if (!await pathExists(globalDbPath)) return assembleDiscovery([], bucketGroups, fileHistoryActivity);
|
|
521
|
+
const bucketIdToGroupKey = /* @__PURE__ */ new Map();
|
|
522
|
+
const bucketIdToFolder = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const group of bucketGroups) for (const bucket of group.buckets) {
|
|
524
|
+
bucketIdToGroupKey.set(bucket.bucketId, group.key);
|
|
525
|
+
bucketIdToFolder.set(bucket.bucketId, group.folders[0] ?? null);
|
|
526
|
+
}
|
|
527
|
+
const headerInfo = readHeaderInfo(globalDbPath);
|
|
528
|
+
const bucketComposerIds = collectBucketComposerIds(buckets);
|
|
529
|
+
const db = openCursorReadonlyDb(globalDbPath);
|
|
530
|
+
try {
|
|
531
|
+
const heads = readAllHeads(db);
|
|
532
|
+
const stats = readBubbleStats(db);
|
|
533
|
+
const universe = new Set([
|
|
534
|
+
...heads.keys(),
|
|
535
|
+
...headerInfo.keys(),
|
|
536
|
+
...bucketComposerIds.keys()
|
|
537
|
+
]);
|
|
538
|
+
const resolved = [];
|
|
539
|
+
for (const composerId of universe) resolved.push(resolveThreadFolder(composerId, heads.get(composerId), headerInfo.get(composerId), stats.get(composerId) ?? {
|
|
540
|
+
bytes: 0,
|
|
541
|
+
count: 0
|
|
542
|
+
}, bucketIdToGroupKey, bucketIdToFolder, bucketComposerIds, db));
|
|
543
|
+
return assembleDiscovery(resolved, bucketGroups, fileHistoryActivity);
|
|
544
|
+
} finally {
|
|
545
|
+
db.close();
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
var discoverCursorWorkspaces = async (userDir) => {
|
|
549
|
+
const now = Date.now();
|
|
550
|
+
if (discoveryCache && discoveryCache.userDir === userDir && now - discoveryCache.at < DISCOVERY_TTL_MS) return discoveryCache.value;
|
|
551
|
+
const value = await buildDiscovery(userDir);
|
|
552
|
+
discoveryCache = {
|
|
553
|
+
at: now,
|
|
554
|
+
userDir,
|
|
555
|
+
value
|
|
556
|
+
};
|
|
557
|
+
return value;
|
|
558
|
+
};
|
|
559
|
+
var readCursorThreadHead = (globalDbPath, composerId) => {
|
|
560
|
+
const db = openCursorReadonlyDb(globalDbPath);
|
|
561
|
+
try {
|
|
562
|
+
const head = readKvValue(db, `composerData:${composerId}`);
|
|
563
|
+
if (!head) return null;
|
|
564
|
+
const headerList = Array.isArray(head.fullConversationHeadersOnly) ? head.fullConversationHeadersOnly : [];
|
|
565
|
+
const orderedBubbleIds = headerList.map((item) => asString(asObject(item)?.bubbleId ?? null)).filter((value) => Boolean(value));
|
|
566
|
+
return {
|
|
567
|
+
composerId,
|
|
568
|
+
createdAtMs: asNumber(head.createdAt ?? null),
|
|
569
|
+
lastUpdatedAtMs: asNumber(head.lastUpdatedAt ?? null),
|
|
570
|
+
mode: asString(head.unifiedMode ?? null),
|
|
571
|
+
name: asString(head.name ?? null),
|
|
572
|
+
orderedBubbleIds,
|
|
573
|
+
totalBubbleHeaders: headerList.length
|
|
574
|
+
};
|
|
575
|
+
} finally {
|
|
576
|
+
db.close();
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
var toBubbleKind = (rawType) => {
|
|
580
|
+
if (rawType === 1) return "user";
|
|
581
|
+
if (rawType === 2) return "assistant";
|
|
582
|
+
return "unknown";
|
|
583
|
+
};
|
|
584
|
+
var parseToolCall = (raw) => {
|
|
585
|
+
const data = asObject(raw);
|
|
586
|
+
if (!data) return null;
|
|
587
|
+
const name = asString(data.name ?? null);
|
|
588
|
+
if (!name) return null;
|
|
589
|
+
return {
|
|
590
|
+
argumentsText: asString(data.rawArgs ?? null) ?? asString(data.params ?? null),
|
|
591
|
+
callId: asString(data.toolCallId ?? null),
|
|
592
|
+
name,
|
|
593
|
+
resultText: asString(data.result ?? null),
|
|
594
|
+
status: asString(data.status ?? null)
|
|
595
|
+
};
|
|
596
|
+
};
|
|
597
|
+
var parseCursorBubble = (bubbleId, raw) => {
|
|
598
|
+
const thinking = asObject(raw.thinking ?? null);
|
|
599
|
+
return {
|
|
600
|
+
bubbleId,
|
|
601
|
+
createdAtMs: asNumber(raw.createdAt ?? null),
|
|
602
|
+
kind: toBubbleKind(raw.type ?? null),
|
|
603
|
+
text: asString(raw.text ?? null) ?? "",
|
|
604
|
+
thinking: thinking ? asString(thinking.text ?? null) : null,
|
|
605
|
+
toolCall: parseToolCall(raw.toolFormerData ?? null)
|
|
606
|
+
};
|
|
607
|
+
};
|
|
608
|
+
var readBubble = (db, composerId, bubbleId) => {
|
|
609
|
+
const raw = readKvValue(db, `bubbleId:${composerId}:${bubbleId}`);
|
|
610
|
+
if (!raw) return null;
|
|
611
|
+
return parseCursorBubble(bubbleId, raw);
|
|
612
|
+
};
|
|
613
|
+
var isRenderableBubble = (bubble) => {
|
|
614
|
+
return Boolean(bubble.text.trim() || bubble.thinking?.trim() || bubble.toolCall);
|
|
615
|
+
};
|
|
616
|
+
var readCursorThreadTranscript = (globalDbPath, composerId) => {
|
|
617
|
+
const head = readCursorThreadHead(globalDbPath, composerId);
|
|
618
|
+
if (!head) return null;
|
|
619
|
+
const db = openCursorReadonlyDb(globalDbPath);
|
|
620
|
+
try {
|
|
621
|
+
const orderedIds = head.orderedBubbleIds.length > 0 ? head.orderedBubbleIds : readAllBubbleIds(db, composerId);
|
|
622
|
+
const bubbles = [];
|
|
623
|
+
for (const bubbleId of orderedIds) {
|
|
624
|
+
const bubble = readBubble(db, composerId, bubbleId);
|
|
625
|
+
if (bubble && isRenderableBubble(bubble)) bubbles.push(bubble);
|
|
626
|
+
}
|
|
627
|
+
const totalBubbleRows = countBubbles(db, composerId).count;
|
|
628
|
+
return {
|
|
629
|
+
bubbles,
|
|
630
|
+
head,
|
|
631
|
+
omittedBubbleCount: Math.max(totalBubbleRows - orderedIds.length, 0),
|
|
632
|
+
renderableBubbleCount: bubbles.length
|
|
633
|
+
};
|
|
634
|
+
} finally {
|
|
635
|
+
db.close();
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
var readAllBubbleIds = (db, composerId) => {
|
|
639
|
+
const prefix = `bubbleId:${composerId}:`;
|
|
640
|
+
return db.query("SELECT key FROM cursorDiskKV WHERE key LIKE ? ORDER BY key ASC").all(`${prefix}%`).map((row) => row.key.slice(prefix.length));
|
|
641
|
+
};
|
|
642
|
+
//#endregion
|
|
643
|
+
export { findCursorTranscriptDirs, invalidateCursorDiscoveryCache, listCursorThreadsForGroup, listCursorWorkspaceGroups, loadGlobalComposerHeaders, openCursorReadonlyDb, readCursorThreadTranscript };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region ../../src/lib/cursor-exporter-types.ts
|
|
4
|
+
var getDefaultCursorUserDir = (platform = process.platform, env = process.env, homeDir = os.homedir()) => {
|
|
5
|
+
if (platform === "win32") return path.win32.join(env.APPDATA || path.win32.join(homeDir, "AppData", "Roaming"), "Cursor", "User");
|
|
6
|
+
if (platform === "linux") return path.posix.join(env.XDG_DATA_HOME || path.posix.join(homeDir, ".local", "share"), "Cursor", "User");
|
|
7
|
+
return path.posix.join(homeDir, "Library", "Application Support", "Cursor", "User");
|
|
8
|
+
};
|
|
9
|
+
var DEFAULT_CURSOR_USER_DIR = getDefaultCursorUserDir();
|
|
10
|
+
var resolveCursorUserDir = () => {
|
|
11
|
+
const configured = process.env.SPIRACHA_CURSOR_USER_DIR?.trim();
|
|
12
|
+
return configured ? configured : DEFAULT_CURSOR_USER_DIR;
|
|
13
|
+
};
|
|
14
|
+
var getCursorWorkspaceStorageDir = (userDir = resolveCursorUserDir()) => path.join(userDir, "workspaceStorage");
|
|
15
|
+
var getCursorGlobalDbPath = (userDir = resolveCursorUserDir()) => path.join(userDir, "globalStorage", "state.vscdb");
|
|
16
|
+
var inferHomeDirFromCursorUserDir = (userDir) => {
|
|
17
|
+
const normalized = userDir.replace(/\\/gu, "/");
|
|
18
|
+
for (const suffix of [
|
|
19
|
+
"/Library/Application Support/Cursor/User",
|
|
20
|
+
"/AppData/Roaming/Cursor/User",
|
|
21
|
+
"/.local/share/Cursor/User"
|
|
22
|
+
]) if (normalized.endsWith(suffix)) return userDir.slice(0, userDir.length - suffix.length);
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
var getCursorProjectsDir = (userDir = resolveCursorUserDir()) => {
|
|
26
|
+
const configured = process.env.SPIRACHA_CURSOR_PROJECTS_DIR?.trim();
|
|
27
|
+
if (configured) return configured;
|
|
28
|
+
const inferredHomeDir = inferHomeDirFromCursorUserDir(userDir);
|
|
29
|
+
return inferredHomeDir ? path.join(inferredHomeDir, ".cursor", "projects") : path.join(userDir, "projects");
|
|
30
|
+
};
|
|
31
|
+
var COMPOSER_DATA_KEY = "composer.composerData";
|
|
32
|
+
var COMPOSER_HEADERS_KEY = "composer.composerHeaders";
|
|
33
|
+
//#endregion
|
|
34
|
+
export { COMPOSER_DATA_KEY, COMPOSER_HEADERS_KEY, getCursorGlobalDbPath, getCursorProjectsDir, getCursorWorkspaceStorageDir, resolveCursorUserDir };
|