spiracha 1.1.2 → 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.
Files changed (159) hide show
  1. package/AGENTS.md +57 -14
  2. package/README.md +122 -65
  3. package/apps/ui/AGENTS.md +18 -8
  4. package/apps/ui/README.md +30 -12
  5. package/apps/ui/dist/client/assets/{analytics-CqWZmyV6.js → analytics-B_hYz65v.js} +1 -1
  6. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-qiyygB7e.js +1 -0
  7. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-z1SQC2Kg.js +1 -0
  8. package/apps/ui/dist/client/assets/antigravity-keychain-panel-dYuRWtCf.js +1 -0
  9. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CliqUr7o.js +1 -0
  10. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CnoBzyX6.js +1 -0
  11. package/apps/ui/dist/client/assets/antigravity.index-CakfZz_E.js +1 -0
  12. package/apps/ui/dist/client/assets/antigravity.index-DY7M1KhG.js +1 -0
  13. package/apps/ui/dist/client/assets/badge-aHE9ETVe.js +1 -0
  14. package/apps/ui/dist/client/assets/checkbox-DN3XnJaA.js +1 -0
  15. package/apps/ui/dist/client/assets/cursor-threads._composerId-BMQyx8qG.js +1 -0
  16. package/apps/ui/dist/client/assets/cursor-threads._composerId-BTlaA-tV.js +1 -0
  17. package/apps/ui/dist/client/assets/cursor._workspaceKey-CrgrfevV.js +1 -0
  18. package/apps/ui/dist/client/assets/cursor._workspaceKey-bYS2syGL.js +1 -0
  19. package/apps/ui/dist/client/assets/cursor.index-CTqZMPYU.js +1 -0
  20. package/apps/ui/dist/client/assets/cursor.index-Clsz4E_e.js +2 -0
  21. package/apps/ui/dist/client/assets/{data-table-DnPYMPCD.js → data-table-Cj-v-uyB.js} +2 -2
  22. package/apps/ui/dist/client/assets/delete-confirm-dialog-DTpzBiNK.js +11 -0
  23. package/apps/ui/dist/client/assets/dist-BNAn99Pu.js +1 -0
  24. package/apps/ui/dist/client/assets/download-P3Rp23Ad.js +1 -0
  25. package/apps/ui/dist/client/assets/dropdown-menu-3qB5j9nt.js +1 -0
  26. package/apps/ui/dist/client/assets/es2015-Dwm_turD.js +41 -0
  27. package/apps/ui/dist/client/assets/export-dialog-CazdrASq.js +1 -0
  28. package/apps/ui/dist/client/assets/formatters-BdnWuM1z.js +1 -0
  29. package/apps/ui/dist/client/assets/index-BVFnfS78.js +22 -0
  30. package/apps/ui/dist/client/assets/json-panel-DLkS30sQ.js +1 -0
  31. package/apps/ui/dist/client/assets/metadata-section-jnIkB7dB.js +1 -0
  32. package/apps/ui/dist/client/assets/{metric-card-9jwBF7rG.js → metric-card-CBZuWLzQ.js} +1 -1
  33. package/apps/ui/dist/client/assets/page-header-CnD21cPn.js +1 -0
  34. package/apps/ui/dist/client/assets/projects._project-BLszwvYL.js +1 -0
  35. package/apps/ui/dist/client/assets/projects._project-DvLxYbvk.js +1 -0
  36. package/apps/ui/dist/client/assets/projects.index-COn8woBR.js +1 -0
  37. package/apps/ui/dist/client/assets/projects.index-DYs98skV.js +3 -0
  38. package/apps/ui/dist/client/assets/refresh-ccw-BDrYXjtD.js +1 -0
  39. package/apps/ui/dist/client/assets/reload-error-panel-DLAg0AW2.js +1 -0
  40. package/apps/ui/dist/client/assets/routes-BtF5-coe.js +1 -0
  41. package/apps/ui/dist/client/assets/scroll-text-CqaFm9by.js +1 -0
  42. package/apps/ui/dist/client/assets/select-DbnpwqL6.js +1 -0
  43. package/apps/ui/dist/client/assets/settings-CGX3VleN.js +1 -0
  44. package/apps/ui/dist/client/assets/styles-Ch0r3kMZ.css +1 -0
  45. package/apps/ui/dist/client/assets/text-document-panel-DPleOmmq.js +1 -0
  46. package/apps/ui/dist/client/assets/text-filter-7M6wRo-t.js +2 -0
  47. package/apps/ui/dist/client/assets/threads._threadId-D5w76IB-.js +7 -0
  48. package/apps/ui/dist/client/assets/{threads._threadId-DT75NiBa.js → threads._threadId-Dx85sI9P.js} +1 -1
  49. package/apps/ui/dist/client/assets/useMutation-MZ3Hr9h9.js +1 -0
  50. package/apps/ui/dist/client/assets/useQuery-Cb4V0AT0.js +1 -0
  51. package/apps/ui/dist/client/icon.svg +28 -0
  52. package/apps/ui/dist/client/manifest.json +6 -16
  53. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CBbkUXw6.js +227 -0
  54. package/apps/ui/dist/server/assets/{analytics-BMxW_bZL.js → analytics-CBNOYZwJ.js} +2 -2
  55. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +16 -0
  56. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-B9Rm4EXh.js +212 -0
  57. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BIdYNy68.js +20 -0
  58. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +11 -0
  59. package/apps/ui/dist/server/assets/antigravity-db-D9gW1D8G.js +576 -0
  60. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +126 -0
  61. package/apps/ui/dist/server/assets/antigravity-keychain-panel-DcLyBBwd.js +55 -0
  62. package/apps/ui/dist/server/assets/antigravity-queries-CgQhlQ7J.js +37 -0
  63. package/apps/ui/dist/server/assets/antigravity-server-DFUx4Khk.js +114 -0
  64. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +11 -0
  65. package/apps/ui/dist/server/assets/antigravity._workspaceKey-D42ixtzp.js +210 -0
  66. package/apps/ui/dist/server/assets/antigravity._workspaceKey-DnSlSC-C.js +28 -0
  67. package/apps/ui/dist/server/assets/antigravity.index-DZVT-cac.js +104 -0
  68. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +11 -0
  69. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +26 -0
  70. package/apps/ui/dist/server/assets/{codex-queries-CAF6HYiG.js → codex-queries-eOJGfHQj.js} +6 -18
  71. package/apps/ui/dist/server/assets/{codex-server-C01sv0JJ.js → codex-server-nrETIF--.js} +166 -226
  72. package/apps/ui/dist/server/assets/createServerRpc-BtXIw2iP.js +12 -0
  73. package/apps/ui/dist/server/assets/createSsrRpc-COf5Zuye.js +16 -0
  74. package/apps/ui/dist/server/assets/cursor-db-B7agkAvM.js +643 -0
  75. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +34 -0
  76. package/apps/ui/dist/server/assets/cursor-queries-BMhuJeUO.js +65 -0
  77. package/apps/ui/dist/server/assets/cursor-recovery-9bJLs7vG.js +361 -0
  78. package/apps/ui/dist/server/assets/cursor-server-BgylIFgn.js +184 -0
  79. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +11 -0
  80. package/apps/ui/dist/server/assets/cursor-threads._composerId-BsxFKzoJ.js +218 -0
  81. package/apps/ui/dist/server/assets/cursor-threads._composerId-DXffY_CK.js +18 -0
  82. package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +125 -0
  83. package/apps/ui/dist/server/assets/cursor._workspaceKey-BP2J1x_x.js +28 -0
  84. package/apps/ui/dist/server/assets/cursor._workspaceKey-BQd0e-Pd.js +399 -0
  85. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +11 -0
  86. package/apps/ui/dist/server/assets/cursor.index-CQVxtCm8.js +189 -0
  87. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +11 -0
  88. package/apps/ui/dist/server/assets/{delete-confirm-dialog-CWqcTXTF.js → delete-confirm-dialog-PCD7S0_M.js} +5 -4
  89. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +92 -0
  90. package/apps/ui/dist/server/assets/{input-B4tEzctc.js → dropdown-menu-Dy_9t6TN.js} +1 -11
  91. package/apps/ui/dist/server/assets/{download-C5rkk_Bo.js → export-dialog-DaPlOGFT.js} +8 -99
  92. package/apps/ui/dist/server/assets/json-panel-RYsxWFae.js +16 -0
  93. package/apps/ui/dist/server/assets/{loading-panel-DbLdvjtR.js → loading-panel-BGFnWseS.js} +1 -1
  94. package/apps/ui/dist/server/assets/metadata-section-D6Lbc7D6.js +54 -0
  95. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +29 -0
  96. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +12 -0
  97. package/apps/ui/dist/server/assets/{projects._project-CJ7l0ynC.js → projects._project-DUN3iWfg.js} +4 -4
  98. package/apps/ui/dist/server/assets/{projects._project-CcJLp_A8.js → projects._project-Dim9Y0kD.js} +54 -26
  99. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +12 -0
  100. package/apps/ui/dist/server/assets/{projects.index-srtogpuF.js → projects.index-DjSQK5dm.js} +23 -27
  101. package/apps/ui/dist/server/assets/{projects.index-CaplpeMy.js → reload-error-panel-BJMxY3U1.js} +5 -6
  102. package/apps/ui/dist/server/assets/{router-C_w-haH6.js → router-DrDgc-LD.js} +131 -44
  103. package/apps/ui/dist/server/assets/{routes-CPe-ppmC.js → routes-B-GlEe2C.js} +54 -39
  104. package/apps/ui/dist/server/assets/{routes-BhbxvJE7.js → routes-CNHAUMwo.js} +2 -2
  105. package/apps/ui/dist/server/assets/{settings-MvWDgc1u.js → settings-OayxIYQQ.js} +1 -1
  106. package/apps/ui/dist/server/assets/shared-CPRNYIql.js +134 -0
  107. package/apps/ui/dist/server/assets/text-document-panel-D8JbQWAn.js +23 -0
  108. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +36 -0
  109. package/apps/ui/dist/server/assets/{threads._threadId-Ba7vv6-K.js → threads._threadId-CJzm4KrZ.js} +3 -3
  110. package/apps/ui/dist/server/assets/{threads._threadId-euyNckhj.js → threads._threadId-DODTYddm.js} +69 -76
  111. package/apps/ui/dist/server/server.js +83 -36
  112. package/bin/codex-chats-claude.js +5 -0
  113. package/bin/codex-chats.js +5 -0
  114. package/bin/spiracha.js +5 -0
  115. package/package.json +26 -13
  116. package/src/export-cursor.ts +244 -0
  117. package/src/lib/antigravity-db.ts +936 -0
  118. package/src/lib/antigravity-exporter-types.ts +70 -0
  119. package/src/lib/antigravity-keychain.ts +203 -0
  120. package/src/lib/codex-browser-db.ts +7 -1
  121. package/src/lib/codex-browser-export.ts +2 -2
  122. package/src/lib/codex-browser-types.ts +22 -1
  123. package/src/lib/codex-exporter-cli.ts +9 -9
  124. package/src/lib/codex-exporter-transcript.ts +16 -190
  125. package/src/lib/codex-exporter-types.ts +1 -1
  126. package/src/lib/codex-exporter.ts +0 -1
  127. package/src/lib/codex-thread-recovery.ts +202 -0
  128. package/src/lib/cursor-db.ts +1096 -0
  129. package/src/lib/cursor-exporter-types.ts +190 -0
  130. package/src/lib/cursor-exporter.ts +266 -0
  131. package/src/lib/cursor-recovery.ts +543 -0
  132. package/src/lib/cursor-transcript.ts +183 -0
  133. package/src/lib/interactive-cli.ts +2 -2
  134. package/src/mcp-server.ts +2 -2
  135. package/src/spiracha.ts +16 -3
  136. package/src/ui-cli.ts +2 -2
  137. package/apps/ui/dist/client/assets/checkbox-DXM4lkJq.js +0 -1
  138. package/apps/ui/dist/client/assets/delete-confirm-dialog-CcZaRX33.js +0 -11
  139. package/apps/ui/dist/client/assets/download-DOwxk-cG.js +0 -1
  140. package/apps/ui/dist/client/assets/es2015-Bm0kEzx2.js +0 -41
  141. package/apps/ui/dist/client/assets/formatters-C12LmYaa.js +0 -1
  142. package/apps/ui/dist/client/assets/index-DdJ7ahIt.js +0 -22
  143. package/apps/ui/dist/client/assets/input-CEsI7EpI.js +0 -1
  144. package/apps/ui/dist/client/assets/page-header-Dr_h1CVv.js +0 -1
  145. package/apps/ui/dist/client/assets/projects._project-uyNGnpjH.js +0 -1
  146. package/apps/ui/dist/client/assets/projects._project-zoM8d2nH.js +0 -1
  147. package/apps/ui/dist/client/assets/projects.index-D1CWVN-O.js +0 -1
  148. package/apps/ui/dist/client/assets/projects.index-DukMuny6.js +0 -1
  149. package/apps/ui/dist/client/assets/routes-Gr2Wwh83.js +0 -1
  150. package/apps/ui/dist/client/assets/select-CFim44gT.js +0 -1
  151. package/apps/ui/dist/client/assets/settings-DqhyDxo2.js +0 -1
  152. package/apps/ui/dist/client/assets/styles-CMrP9Jb4.css +0 -1
  153. package/apps/ui/dist/client/assets/threads._threadId-Df5VXIuZ.js +0 -7
  154. package/apps/ui/dist/client/favicon.ico +0 -0
  155. package/apps/ui/dist/client/logo192.png +0 -0
  156. package/apps/ui/dist/client/logo512.png +0 -0
  157. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-C0V305Nt.js +0 -99
  158. package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +0 -25
  159. package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +0 -26
@@ -0,0 +1,70 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ export const DEFAULT_ANTIGRAVITY_IDE_DIR = path.join(os.homedir(), '.gemini', 'antigravity-ide');
5
+ export const DEFAULT_ANTIGRAVITY_DIR = path.join(os.homedir(), '.gemini', 'antigravity');
6
+
7
+ export const resolveAntigravityRoots = (): string[] => {
8
+ const configured = process.env.SPIRACHA_ANTIGRAVITY_DIRS?.trim() || process.env.SPIRACHA_ANTIGRAVITY_DIR?.trim();
9
+ if (configured) {
10
+ return configured
11
+ .split(path.delimiter)
12
+ .map((entry) => entry.trim())
13
+ .filter(Boolean);
14
+ }
15
+
16
+ return [DEFAULT_ANTIGRAVITY_IDE_DIR, DEFAULT_ANTIGRAVITY_DIR];
17
+ };
18
+
19
+ export const getAntigravityConversationDir = (root: string): string => path.join(root, 'conversations');
20
+
21
+ export const getAntigravityBrainDir = (root: string): string => path.join(root, 'brain');
22
+
23
+ export const getAntigravitySummaryIndexPath = (root: string): string => path.join(root, 'agyhub_summaries_proto.pb');
24
+
25
+ export type AntigravityArtifact = {
26
+ artifactType: string | null;
27
+ bytes: number;
28
+ name: string;
29
+ path: string;
30
+ sourceRoot: string;
31
+ summary: string | null;
32
+ updatedAtMs: number | null;
33
+ };
34
+
35
+ export type AntigravityTranscriptSource = 'overview' | 'safe-storage' | 'transcript';
36
+
37
+ export type AntigravityConversation = {
38
+ artifactBytes: number;
39
+ artifactCount: number;
40
+ artifacts: AntigravityArtifact[];
41
+ conversationBytes: number;
42
+ conversationId: string;
43
+ conversationMtimeMs: number | null;
44
+ conversationPath: string | null;
45
+ createdAtMs: number | null;
46
+ indexedItemCount: number | null;
47
+ lastUpdatedAtMs: number | null;
48
+ sourceRoot: string | null;
49
+ summaryPath: string | null;
50
+ title: string;
51
+ transcriptBytes: number;
52
+ transcriptEntryCount: number;
53
+ transcriptPath: string | null;
54
+ transcriptSource: AntigravityTranscriptSource | null;
55
+ workspaceFolder: string | null;
56
+ workspaceKey: string;
57
+ workspaceLabel: string;
58
+ workspaceUri: string | null;
59
+ };
60
+
61
+ export type AntigravityWorkspaceGroup = {
62
+ artifactCount: number;
63
+ conversationBytes: number;
64
+ conversationCount: number;
65
+ key: string;
66
+ label: string;
67
+ lastActiveMs: number;
68
+ transcriptCount: number;
69
+ uri: string | null;
70
+ };
@@ -0,0 +1,203 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createDecipheriv, pbkdf2Sync } from 'node:crypto';
3
+ import { promisify } from 'node:util';
4
+
5
+ export const ANTIGRAVITY_KEYCHAIN_SERVICE = 'Antigravity Safe Storage';
6
+ export const ANTIGRAVITY_KEYCHAIN_ACCOUNT = 'Antigravity Key';
7
+
8
+ export type AntigravityDecryptionState = {
9
+ canRequestAccess: boolean;
10
+ error: string | null;
11
+ isUnlocked: boolean;
12
+ keychainAccount: string;
13
+ keychainService: string;
14
+ platform: NodeJS.Platform;
15
+ provider: 'keychain' | 'unsupported';
16
+ status: 'error' | 'locked' | 'unlocked' | 'unsupported';
17
+ };
18
+
19
+ const execFileAsync = promisify(execFile);
20
+ const SAFE_STORAGE_SALT = 'saltysalt';
21
+ const SAFE_STORAGE_ITERATIONS = 1003;
22
+ const SAFE_STORAGE_KEY_LENGTH = 16;
23
+ const SAFE_STORAGE_IV = Buffer.alloc(16, 0x20);
24
+
25
+ let cachedKeychainSecret: string | null = null;
26
+ let lastKeychainError: string | null = null;
27
+
28
+ export const deriveAntigravitySafeStorageKey = (keychainSecret: string | Buffer): Buffer => {
29
+ return pbkdf2Sync(keychainSecret, SAFE_STORAGE_SALT, SAFE_STORAGE_ITERATIONS, SAFE_STORAGE_KEY_LENGTH, 'sha1');
30
+ };
31
+
32
+ const parseBufferJson = (value: unknown): Buffer | null => {
33
+ if (!value || typeof value !== 'object') {
34
+ return null;
35
+ }
36
+
37
+ const data = (value as { data?: unknown }).data;
38
+ if (!Array.isArray(data) || data.some((entry) => typeof entry !== 'number')) {
39
+ return null;
40
+ }
41
+
42
+ return Buffer.from(data);
43
+ };
44
+
45
+ const normalizeEncryptedPayload = (payload: Buffer | Uint8Array | string): Buffer | null => {
46
+ if (payload instanceof Buffer) {
47
+ return payload;
48
+ }
49
+
50
+ if (payload instanceof Uint8Array) {
51
+ return Buffer.from(payload);
52
+ }
53
+
54
+ const trimmed = payload.trim();
55
+ if (trimmed.startsWith('{')) {
56
+ try {
57
+ return parseBufferJson(JSON.parse(trimmed));
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ return Buffer.from(payload, 'binary');
64
+ };
65
+
66
+ const hasSafeStoragePrefix = (payload: Buffer): boolean => {
67
+ const prefix = payload.subarray(0, 3).toString('ascii');
68
+ return prefix === 'v10' || prefix === 'v11';
69
+ };
70
+
71
+ const isReadableUtf8 = (value: string): boolean => {
72
+ if (value.includes('\uFFFD')) {
73
+ return false;
74
+ }
75
+
76
+ const printable = [...value].filter((char) => {
77
+ const code = char.charCodeAt(0);
78
+ return code === 9 || code === 10 || code === 13 || code >= 32;
79
+ }).length;
80
+
81
+ return value.length === 0 || printable / value.length > 0.95;
82
+ };
83
+
84
+ const decryptWithKey = (encrypted: Buffer, key: Buffer): string | null => {
85
+ const ciphertext = hasSafeStoragePrefix(encrypted) ? encrypted.subarray(3) : encrypted;
86
+ if (ciphertext.length === 0 || ciphertext.length % 16 !== 0) {
87
+ return null;
88
+ }
89
+
90
+ try {
91
+ const decipher = createDecipheriv('aes-128-cbc', key, SAFE_STORAGE_IV);
92
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
93
+ return isReadableUtf8(decrypted) ? decrypted : null;
94
+ } catch {
95
+ return null;
96
+ }
97
+ };
98
+
99
+ export const decryptAntigravitySafeStoragePayload = (
100
+ payload: Buffer | Uint8Array | string,
101
+ keychainSecret: string,
102
+ ): string | null => {
103
+ const encrypted = normalizeEncryptedPayload(payload);
104
+ if (!encrypted) {
105
+ return null;
106
+ }
107
+
108
+ const keyAttempts = [deriveAntigravitySafeStorageKey(keychainSecret)];
109
+ if (/^[A-Za-z0-9+/]+={0,2}$/u.test(keychainSecret)) {
110
+ keyAttempts.push(deriveAntigravitySafeStorageKey(Buffer.from(keychainSecret, 'base64')));
111
+ }
112
+
113
+ for (const key of keyAttempts) {
114
+ const decrypted = decryptWithKey(encrypted, key);
115
+ if (decrypted !== null) {
116
+ return decrypted;
117
+ }
118
+ }
119
+
120
+ return null;
121
+ };
122
+
123
+ export const getAntigravityDecryptionState = ({
124
+ cachedSecret = cachedKeychainSecret,
125
+ lastError = lastKeychainError,
126
+ platform = process.platform,
127
+ }: {
128
+ cachedSecret?: string | null;
129
+ lastError?: string | null;
130
+ platform?: NodeJS.Platform;
131
+ } = {}): AntigravityDecryptionState => {
132
+ if (platform !== 'darwin') {
133
+ return {
134
+ canRequestAccess: false,
135
+ error: null,
136
+ isUnlocked: false,
137
+ keychainAccount: ANTIGRAVITY_KEYCHAIN_ACCOUNT,
138
+ keychainService: ANTIGRAVITY_KEYCHAIN_SERVICE,
139
+ platform,
140
+ provider: 'unsupported',
141
+ status: 'unsupported',
142
+ };
143
+ }
144
+
145
+ if (cachedSecret) {
146
+ return {
147
+ canRequestAccess: true,
148
+ error: null,
149
+ isUnlocked: true,
150
+ keychainAccount: ANTIGRAVITY_KEYCHAIN_ACCOUNT,
151
+ keychainService: ANTIGRAVITY_KEYCHAIN_SERVICE,
152
+ platform,
153
+ provider: 'keychain',
154
+ status: 'unlocked',
155
+ };
156
+ }
157
+
158
+ return {
159
+ canRequestAccess: true,
160
+ error: lastError,
161
+ isUnlocked: false,
162
+ keychainAccount: ANTIGRAVITY_KEYCHAIN_ACCOUNT,
163
+ keychainService: ANTIGRAVITY_KEYCHAIN_SERVICE,
164
+ platform,
165
+ provider: 'keychain',
166
+ status: lastError ? 'error' : 'locked',
167
+ };
168
+ };
169
+
170
+ export const getCachedAntigravityKeychainSecret = (): string | null => cachedKeychainSecret;
171
+
172
+ export const readAntigravityKeychainSecret = async (): Promise<string> => {
173
+ if (process.platform !== 'darwin') {
174
+ throw new Error('Antigravity Keychain access is only available on macOS.');
175
+ }
176
+
177
+ const { stdout } = await execFileAsync('security', [
178
+ 'find-generic-password',
179
+ '-s',
180
+ ANTIGRAVITY_KEYCHAIN_SERVICE,
181
+ '-a',
182
+ ANTIGRAVITY_KEYCHAIN_ACCOUNT,
183
+ '-w',
184
+ ]);
185
+ const secret = stdout.trim();
186
+ if (!secret) {
187
+ throw new Error(`No secret was returned for ${ANTIGRAVITY_KEYCHAIN_SERVICE}.`);
188
+ }
189
+
190
+ return secret;
191
+ };
192
+
193
+ export const unlockAntigravityDecryption = async (): Promise<AntigravityDecryptionState> => {
194
+ try {
195
+ cachedKeychainSecret = await readAntigravityKeychainSecret();
196
+ lastKeychainError = null;
197
+ } catch (error) {
198
+ cachedKeychainSecret = null;
199
+ lastKeychainError = error instanceof Error ? error.message : String(error);
200
+ }
201
+
202
+ return getAntigravityDecryptionState();
203
+ };
@@ -491,7 +491,13 @@ export const getCodexDashboardSummary = (dbPath: string): DashboardSummary => {
491
491
  return {
492
492
  activeThreads: threads.filter((thread) => !thread.archived).length,
493
493
  archivedThreads: threads.filter((thread) => Boolean(thread.archived)).length,
494
- recentThreads: threads.slice(0, 5),
494
+ recentThreads: threads
495
+ .slice(0, 5)
496
+ .filter((thread) => Boolean(getPortablePathBasename(thread.cwd)))
497
+ .map((thread) => ({
498
+ project: getPortablePathBasename(thread.cwd),
499
+ thread: compactThreadListRow(thread),
500
+ })),
495
501
  threadsWithRelations,
496
502
  topProjectsByThreadCount: [...projects]
497
503
  .sort((left, right) => {
@@ -11,9 +11,9 @@ import { buildUiExportDownloadUrl, ensureUiExportDir } from './ui-export-files';
11
11
  type RenderCodexThreadDownloadInput = {
12
12
  dbPath: string;
13
13
  includeCommentary: boolean;
14
+ includeMetadata: boolean;
14
15
  includeTools: boolean;
15
16
  largeExportThresholdBytes?: number;
16
- optimized: boolean;
17
17
  outputFormat: ExportFormat;
18
18
  pathDisplaySettings?: Pick<PathDisplaySettings, 'convertToProjectRoot' | 'redactUsername'>;
19
19
  publicExportDir?: string;
@@ -101,9 +101,9 @@ const toDownloadOptions = (input: RenderCodexThreadDownloadInput): CodexCliOptio
101
101
  dbPath: input.dbPath,
102
102
  flat: false,
103
103
  includeCommentary: input.includeCommentary,
104
+ includeMetadata: input.includeMetadata,
104
105
  includeTools: input.includeTools,
105
106
  inputDir: '',
106
- optimized: input.optimized,
107
107
  outputDir: '',
108
108
  outputFormat: input.outputFormat,
109
109
  projectFilter: null,
@@ -170,10 +170,15 @@ export type ThreadBrowseData = {
170
170
  thread: ThreadRow;
171
171
  };
172
172
 
173
+ export type DashboardRecentThread = {
174
+ project: string;
175
+ thread: ThreadRow;
176
+ };
177
+
173
178
  export type DashboardSummary = {
174
179
  activeThreads: number;
175
180
  archivedThreads: number;
176
- recentThreads: ThreadRow[];
181
+ recentThreads: DashboardRecentThread[];
177
182
  threadsWithRelations: number;
178
183
  topProjectsByThreadCount: ProjectSummary[];
179
184
  topProjectsByTokens: ProjectSummary[];
@@ -191,6 +196,22 @@ export type DeleteProjectResult = DeleteThreadsResult & {
191
196
  projectName: string;
192
197
  };
193
198
 
199
+ export type RecoverProjectThreadsResult = {
200
+ backups: {
201
+ globalState: string;
202
+ sessionIndex: string;
203
+ stateDb: string;
204
+ };
205
+ projectName: string;
206
+ projectRootsAdded: number;
207
+ resolvedCwds: string[];
208
+ rolloutFilesTouched: number;
209
+ savedRootsAdded: number;
210
+ sessionIndexRowsUpdated: number;
211
+ threadDbRowsUpdated: number;
212
+ topLevelThreadsFound: number;
213
+ };
214
+
194
215
  export type ToolUsageSummary = {
195
216
  count: number;
196
217
  name: string;
@@ -10,7 +10,7 @@ export const parseCodexCliArgs = (argv: string[]): CodexCliOptions => {
10
10
  let projectFilter: string | null = null;
11
11
  let threadIds: string[] = [];
12
12
  let outputProvided = false;
13
- let optimized = false;
13
+ let includeMetadata = true;
14
14
  let includeCommentary = true;
15
15
  let includeTools = false;
16
16
  let outputFormat: ExportFormat = 'md';
@@ -22,9 +22,9 @@ export const parseCodexCliArgs = (argv: string[]): CodexCliOptions => {
22
22
  dbPath,
23
23
  flat,
24
24
  includeCommentary,
25
+ includeMetadata,
25
26
  includeTools,
26
27
  inputDir,
27
- optimized,
28
28
  outputDir,
29
29
  outputFormat,
30
30
  outputProvided,
@@ -37,9 +37,9 @@ export const parseCodexCliArgs = (argv: string[]): CodexCliOptions => {
37
37
  dbPath,
38
38
  flat,
39
39
  includeCommentary,
40
+ includeMetadata,
40
41
  includeTools,
41
42
  inputDir,
42
- optimized,
43
43
  outputDir,
44
44
  outputFormat,
45
45
  outputProvided,
@@ -58,9 +58,9 @@ export const parseCodexCliArgs = (argv: string[]): CodexCliOptions => {
58
58
  dbPath,
59
59
  flat,
60
60
  includeCommentary,
61
+ includeMetadata,
61
62
  includeTools,
62
63
  inputDir,
63
- optimized,
64
64
  outputDir: outputDir ?? DEFAULT_OUTPUT_DIR,
65
65
  outputFormat,
66
66
  projectFilter,
@@ -73,9 +73,9 @@ type CodexCliState = {
73
73
  dbPath: string;
74
74
  flat: boolean;
75
75
  includeCommentary: boolean;
76
+ includeMetadata: boolean;
76
77
  includeTools: boolean;
77
78
  inputDir: string;
78
- optimized: boolean;
79
79
  outputDir: string | null;
80
80
  outputFormat: ExportFormat;
81
81
  outputProvided: boolean;
@@ -142,12 +142,12 @@ const applyCodexCliArg = (argv: string[], index: number, state: CodexCliState):
142
142
  };
143
143
  }
144
144
 
145
- if (arg === '--optimized') {
145
+ if (arg === '--no-metadata') {
146
146
  return {
147
147
  index,
148
148
  state: {
149
149
  ...state,
150
- optimized: true,
150
+ includeMetadata: false,
151
151
  },
152
152
  };
153
153
  }
@@ -220,7 +220,7 @@ export const getCodexHelpText = (): string => {
220
220
  'Usage:',
221
221
  ' codex-chats',
222
222
  ' codex-chats --interactive',
223
- ' codex-chats [--db FILE] [--input DIR] [--output DIR] [--cwd DIR] [--project NAME] [--optimized] [--tools] [--flat] [--output-format md|txt] [codex://threads/<thread-id> ...]',
223
+ ' codex-chats [--db FILE] [--input DIR] [--output DIR] [--cwd DIR] [--project NAME] [--no-metadata] [--tools] [--flat] [--output-format md|txt] [codex://threads/<thread-id> ...]',
224
224
  '',
225
225
  'Options:',
226
226
  ` --db Thread database path (default: ${DEFAULT_DB_PATH})`,
@@ -230,7 +230,7 @@ export const getCodexHelpText = (): string => {
230
230
  ' --project Only export chats whose cwd basename matches this project name',
231
231
  ' codex://threads/<id>',
232
232
  ' Only export the exact threads referenced by these Codex deeplinks',
233
- ' --optimized Suppress metadata and apply token-saving text cleanup',
233
+ ' --no-metadata Omit the chat metadata section from the export header',
234
234
  ' --tools Include tool-call logs such as exec_command invocations',
235
235
  ' --flat Write all exports into one folder instead of nested subfolders',
236
236
  ' --output-format Output file format: md or txt (default: md)',