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
@@ -40,16 +40,16 @@ export const convertSessionFile = async (target: ExportTarget, options: CodexCli
40
40
  return null;
41
41
  }
42
42
 
43
- if (options.optimized) {
44
- return transcriptState.sections.join('\n\n').trimEnd() + '\n';
45
- }
46
-
47
43
  const title = getTitle(target, transcriptState.sessionMeta);
48
- const metadata = buildMetadataEntries(target, transcriptState.sessionMeta, options);
49
44
  const parts = [
50
45
  renderDocumentTitle(title, options.outputFormat),
51
46
  '',
52
- renderMetadataBlock(metadata, options.outputFormat),
47
+ options.includeMetadata
48
+ ? renderMetadataBlock(
49
+ buildMetadataEntries(target, transcriptState.sessionMeta, options),
50
+ options.outputFormat,
51
+ )
52
+ : '',
53
53
  ...transcriptState.sections,
54
54
  ].filter(Boolean);
55
55
  return parts.join('\n').trimEnd() + '\n';
@@ -69,7 +69,6 @@ export const writeSessionFileExport = async (
69
69
  assistantModel: target.thread?.model ?? null,
70
70
  sections: [],
71
71
  sessionMeta: {},
72
- startedTranscript: false,
73
72
  };
74
73
  let wroteSection = false;
75
74
 
@@ -82,7 +81,7 @@ export const writeSessionFileExport = async (
82
81
  continue;
83
82
  }
84
83
 
85
- transcriptStream.write(transform(wroteSection ? `${getSectionSeparator(options)}${block}` : block));
84
+ transcriptStream.write(transform(wroteSection ? `${getSectionSeparator()}${block}` : block));
86
85
  wroteSection = true;
87
86
  }
88
87
  await finalizeExportWriteStream(transcriptStream);
@@ -124,7 +123,6 @@ type CodexTranscriptState = {
124
123
  assistantModel: string | null;
125
124
  sessionMeta: SessionMeta;
126
125
  sections: string[];
127
- startedTranscript: boolean;
128
126
  };
129
127
 
130
128
  const collectCodexTranscript = async (
@@ -136,7 +134,6 @@ const collectCodexTranscript = async (
136
134
  assistantModel,
137
135
  sections: [],
138
136
  sessionMeta: {},
139
- startedTranscript: false,
140
137
  };
141
138
 
142
139
  for await (const parsed of readJsonlObjects(sessionFile)) {
@@ -146,9 +143,7 @@ const collectCodexTranscript = async (
146
143
  return state;
147
144
  };
148
145
 
149
- const getSectionSeparator = (options: CodexCliOptions) => {
150
- return options.optimized ? '\n\n' : '\n';
151
- };
146
+ const getSectionSeparator = () => '\n';
152
147
 
153
148
  const processCodexTranscriptRecord = (
154
149
  parsed: Record<string, JsonValue>,
@@ -181,58 +176,23 @@ const renderCodexTranscriptRecord = (
181
176
  return '';
182
177
  }
183
178
 
184
- return options.optimized
185
- ? renderCompactToolBlock(tool, options.outputFormat)
186
- : renderToolBlock(tool, options.outputFormat);
179
+ return renderToolBlock(tool, options.outputFormat);
187
180
  };
188
181
 
189
182
  const processCodexMessageRecord = (message: MessageRecord, options: CodexCliOptions, state: CodexTranscriptState) => {
190
- if (options.optimized) {
191
- return processOptimizedCodexMessageRecord(message, options, state);
192
- }
193
-
194
183
  return renderMessageBlock(message, options.outputFormat, state.assistantModel, options.includeCommentary);
195
184
  };
196
185
 
197
- const processOptimizedCodexMessageRecord = (
198
- message: MessageRecord,
199
- options: CodexCliOptions,
200
- state: CodexTranscriptState,
201
- ) => {
202
- if (message.role !== 'user' && message.role !== 'assistant') {
203
- return '';
204
- }
205
-
206
- if (message.role === 'assistant' && message.phase === 'commentary' && !options.includeCommentary) {
207
- return '';
208
- }
209
-
210
- const compact = compactMessageText(message, true);
211
- if (!compact) {
212
- return '';
213
- }
214
-
215
- if (!state.startedTranscript) {
216
- if (shouldSkipOptimizedPrelude(message.role, compact)) {
217
- return '';
218
- }
219
- state.startedTranscript = true;
220
- }
221
-
222
- return renderCompactBlock(message, compact, options.outputFormat, state.assistantModel);
223
- };
224
-
225
186
  const buildStreamExportPrefix = (target: ExportTarget, sessionMeta: SessionMeta, options: CodexCliOptions) => {
226
- if (options.optimized) {
187
+ if (!options.includeMetadata) {
227
188
  return '';
228
189
  }
229
190
 
230
191
  const title = getTitle(target, sessionMeta);
231
- const metadata = buildMetadataEntries(target, sessionMeta, options);
232
192
  const parts = [
233
193
  renderDocumentTitle(title, options.outputFormat),
234
194
  '',
235
- renderMetadataBlock(metadata, options.outputFormat),
195
+ renderMetadataBlock(buildMetadataEntries(target, sessionMeta, options), options.outputFormat),
236
196
  ]
237
197
  .filter(Boolean)
238
198
  .join('\n');
@@ -240,13 +200,6 @@ const buildStreamExportPrefix = (target: ExportTarget, sessionMeta: SessionMeta,
240
200
  return `${parts}\n`;
241
201
  };
242
202
 
243
- export const compactMessageText = (message: MessageRecord, optimized: boolean): string => {
244
- const rawText = extractText(message.content);
245
- const cleaned = stripPreviewBlock(rawText);
246
-
247
- return optimized ? optimizePlainText(optimizeRenderedText(cleaned)) : cleaned.trim();
248
- };
249
-
250
203
  export const formatToolOutputSummary = (outputText: string, outputFormat: ExportFormat): string => {
251
204
  if (!outputText) {
252
205
  return '';
@@ -308,27 +261,6 @@ const getTitle = (target: ExportTarget, sessionMeta: SessionMeta): string => {
308
261
  return sessionMeta.id ?? path.basename(target.sessionFile, '.jsonl');
309
262
  };
310
263
 
311
- const shouldSkipOptimizedPrelude = (role: string, text: string): boolean => {
312
- if (role !== 'user') {
313
- return true;
314
- }
315
-
316
- return (
317
- text.startsWith('AGENTS.md instructions for ') ||
318
- text.startsWith('# AGENTS.md instructions for ') ||
319
- text.startsWith('<permissions instructions>') ||
320
- text.startsWith('<environment_context>') ||
321
- text.startsWith('<app-context>') ||
322
- text.startsWith('<collaboration_mode>') ||
323
- text.startsWith('<skills_instructions>') ||
324
- text.startsWith('You are Codex, a coding agent based on GPT-5.') ||
325
- text.startsWith('Read this before making changes.') ||
326
- text.includes('Filesystem sandboxing defines which files can be read or written.') ||
327
- text.includes('approval_policy') ||
328
- text.includes('base_instructions')
329
- );
330
- };
331
-
332
264
  const buildMetadataEntries = (
333
265
  target: ExportTarget,
334
266
  sessionMeta: SessionMeta,
@@ -585,7 +517,7 @@ const renderMessageBlock = (
585
517
  return '';
586
518
  }
587
519
 
588
- const text = cleanExtractedText(extractText(message.content)).trim();
520
+ const text = cleanExtractedText(stripPreviewBlock(extractText(message.content))).trim();
589
521
  if (!text || shouldSkipMessage(message.role, text)) {
590
522
  return '';
591
523
  }
@@ -606,36 +538,6 @@ const renderToolBlock = (tool: ToolRecord, outputFormat: ExportFormat): string =
606
538
  return summary ? renderSection('Tool Output', summary, outputFormat) : '';
607
539
  };
608
540
 
609
- const renderCompactBlock = (
610
- message: MessageRecord,
611
- text: string,
612
- outputFormat: ExportFormat,
613
- assistantModel: string | null,
614
- ): string => {
615
- const prefix = message.role === 'user' ? 'U:' : `${formatModelLabel(message.model ?? assistantModel)}:`;
616
- const lines = text.split('\n');
617
- const [firstLine, ...rest] = lines;
618
-
619
- if (rest.length === 0) {
620
- return `${prefix} ${normalizeCompactLiteral(firstLine, outputFormat)}`;
621
- }
622
-
623
- return [
624
- `${prefix} ${normalizeCompactLiteral(firstLine, outputFormat)}`,
625
- ...rest.map((line) => normalizeCompactLiteral(line, outputFormat)),
626
- ].join('\n');
627
- };
628
-
629
- const renderCompactToolBlock = (tool: ToolRecord, outputFormat: ExportFormat): string => {
630
- if (tool.kind === 'call') {
631
- const details = formatCompactToolCall(tool, outputFormat);
632
- return details ? `T: ${details}` : '';
633
- }
634
-
635
- const summary = formatCompactToolOutput(tool.outputText ?? '');
636
- return summary ? `R: ${summary}` : '';
637
- };
638
-
639
541
  const stripPreviewBlock = (text: string): string => {
640
542
  const parts = text
641
543
  .split(/\n{2,}/)
@@ -662,49 +564,15 @@ const stripPreviewBlock = (text: string): string => {
662
564
  return parts.slice(1).join('\n\n');
663
565
  };
664
566
 
665
- const optimizeRenderedText = (text: string): string => {
666
- return text
667
- .replace(/^\*Phase:\s+`[^`]+`\*\s*\n*/gm, '')
668
- .replace(/^\s*<image\b[^>]*>\s*$/gim, '')
669
- .replace(/^\s*<\/image>\s*$/gim, '')
670
- .replace(/^\s*\[Image attached\]\s*$/gim, '')
671
- .replace(/^#{1,6}\s+/gm, '')
672
- .replace(/^```[^\n]*\n?/gm, '')
673
- .replace(/\n```$/gm, '')
674
- .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
675
- .replace(/^##\s+User\s*$/gm, 'User:')
676
- .replace(/^##\s+Assistant\s*$/gm, 'Assistant:')
677
- .replace(/`([^`]+)`/g, '$1')
678
- .replace(/\*\*([^*]+)\*\*/g, '$1')
679
- .replace(/\*([^*\n]+)\*/g, '$1')
680
- .replace(/\n{3,}/g, '\n\n')
681
- .trim();
682
- };
683
-
684
- const optimizePlainText = (text: string): string => {
685
- const normalized = text
686
- .replace(/\r/g, '')
687
- .replace(/^\s*<image\b[^>]*>\s*$/gim, '')
688
- .replace(/^\s*<\/image>\s*$/gim, '')
689
- .replace(/^\s*\[Image attached\]\s*$/gim, '')
690
- .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
691
- .replace(/`([^`]+)`/g, '$1')
692
- .replace(/\*\*([^*]+)\*\*/g, '$1')
693
- .replace(/\*([^*\n]+)\*/g, '$1');
694
-
695
- return normalized
696
- .split('\n')
697
- .map((line) => line.replace(/[ \t]+$/g, ''))
698
- .join('\n')
699
- .replace(/\n{3,}/g, '\n\n')
700
- .trim();
701
- };
702
-
703
567
  const shouldSkipMessage = (role: string, text: string): boolean => {
704
568
  if (text.startsWith('<environment_context>')) {
705
569
  return true;
706
570
  }
707
571
 
572
+ if (text.startsWith('AGENTS.md instructions for ')) {
573
+ return true;
574
+ }
575
+
708
576
  if (text.startsWith('# AGENTS.md instructions for ')) {
709
577
  return true;
710
578
  }
@@ -725,44 +593,6 @@ const formatToolCallDetails = (tool: ToolRecord, outputFormat: ExportFormat): st
725
593
  return details.cmd ? `Command: ${formatInlineLiteral(details.cmd, outputFormat)}` : '';
726
594
  };
727
595
 
728
- const formatCompactToolCall = (tool: ToolRecord, outputFormat: ExportFormat): string => {
729
- if (tool.name === 'exec_command') {
730
- const details = parseExecCommandArguments(tool.argumentsText);
731
- if (!details.cmd) {
732
- return 'exec_command';
733
- }
734
-
735
- const command = formatInlineLiteral(details.cmd, outputFormat);
736
- return details.workdir ? `exec_command ${command} @ ${details.workdir}` : `exec_command ${command}`;
737
- }
738
-
739
- return tool.callId ? `${tool.name} (${tool.callId})` : tool.name;
740
- };
741
-
742
- const formatCompactToolOutput = (outputText: string): string => {
743
- if (!outputText) {
744
- return '';
745
- }
746
-
747
- const lines = outputText
748
- .split('\n')
749
- .map((line) => line.trim())
750
- .filter(Boolean);
751
-
752
- const exit = lines.find((line) => line.startsWith('Process exited with code '));
753
- const wall = lines.find((line) => line.startsWith('Wall time: '));
754
-
755
- if (exit && wall) {
756
- return `${exit.replace('Process ', '')}; ${wall.toLowerCase()}`;
757
- }
758
-
759
- if (exit) {
760
- return exit.replace('Process ', '');
761
- }
762
-
763
- return '';
764
- };
765
-
766
596
  const extractText = (content: JsonValue): string => {
767
597
  if (typeof content === 'string') {
768
598
  return content;
@@ -802,7 +632,3 @@ const extractContentPart = (value: JsonValue): string => {
802
632
 
803
633
  return text ?? '';
804
634
  };
805
-
806
- const normalizeCompactLiteral = (value: string, outputFormat: ExportFormat): string => {
807
- return outputFormat === 'md' ? value : value.replace(/`([^`]+)`/g, '$1');
808
- };
@@ -9,7 +9,7 @@ export type CodexCliOptions = {
9
9
  cwdFilter: string | null;
10
10
  projectFilter: string | null;
11
11
  threadIds: string[];
12
- optimized: boolean;
12
+ includeMetadata: boolean;
13
13
  includeCommentary: boolean;
14
14
  includeTools: boolean;
15
15
  outputFormat: ExportFormat;
@@ -27,7 +27,6 @@ export {
27
27
  toOutputRelativePath,
28
28
  } from './codex-exporter-db';
29
29
  export {
30
- compactMessageText,
31
30
  convertSessionFile,
32
31
  formatToolOutputSummary,
33
32
  parseExecCommandArguments,
@@ -0,0 +1,202 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { copyFile, utimes } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import type { RecoverProjectThreadsResult } from './codex-browser-types';
5
+ import { getPortablePathBasename } from './shared';
6
+ import { runWithSqliteRetry } from './sqlite-retry';
7
+
8
+ type RecoveryThreadRow = {
9
+ cwd: string;
10
+ id: string;
11
+ rollout_path: string;
12
+ thread_source: string | null;
13
+ };
14
+
15
+ type GlobalState = {
16
+ 'active-workspace-roots'?: string[];
17
+ 'electron-saved-workspace-roots'?: string[];
18
+ 'project-order'?: string[];
19
+ };
20
+
21
+ const backupFile = async (filePath: string, label: string) => {
22
+ const stamp = new Date()
23
+ .toISOString()
24
+ .replaceAll(':', '')
25
+ .replace(/\.\d{3}Z$/, 'Z')
26
+ .replace('T', '-');
27
+ const backupPath = `${filePath}.bak-${label}-${stamp}`;
28
+ await copyFile(filePath, backupPath);
29
+ return backupPath;
30
+ };
31
+
32
+ const resolveCodexDirFromDbPath = (dbPath: string) => {
33
+ const dbDir = path.dirname(dbPath);
34
+ return path.basename(dbDir) === 'sqlite' ? path.dirname(dbDir) : dbDir;
35
+ };
36
+
37
+ const assertRequiredStatePath = async (filePath: string) => {
38
+ if (!(await Bun.file(filePath).exists())) {
39
+ throw new Error(`Required Codex state file not found: ${filePath}`);
40
+ }
41
+ };
42
+
43
+ const readGlobalState = async (globalStatePath: string) => {
44
+ return (await Bun.file(globalStatePath).json()) as GlobalState;
45
+ };
46
+
47
+ const writeGlobalState = async (globalStatePath: string, state: GlobalState) => {
48
+ await Bun.write(globalStatePath, JSON.stringify(state));
49
+ };
50
+
51
+ const updateGlobalRoots = (state: GlobalState, projectCwds: string[]) => {
52
+ const savedRoots = state['electron-saved-workspace-roots'] ?? [];
53
+ const projectOrder = state['project-order'] ?? [];
54
+ const missingSaved = projectCwds.filter((cwd) => !savedRoots.includes(cwd));
55
+ const missingProjectOrder = projectCwds.filter((cwd) => !projectOrder.includes(cwd));
56
+
57
+ if (missingSaved.length === 0 && missingProjectOrder.length === 0) {
58
+ return {
59
+ projectRootsAdded: 0,
60
+ savedRootsAdded: 0,
61
+ state,
62
+ };
63
+ }
64
+
65
+ state['electron-saved-workspace-roots'] = [...savedRoots, ...missingSaved];
66
+ state['project-order'] = [...projectOrder, ...missingProjectOrder];
67
+
68
+ return {
69
+ projectRootsAdded: missingProjectOrder.length,
70
+ savedRootsAdded: missingSaved.length,
71
+ state,
72
+ };
73
+ };
74
+
75
+ const getProjectTopLevelThreads = (db: Database, projectName: string): RecoveryThreadRow[] => {
76
+ const threads = db
77
+ .query('SELECT id, cwd, rollout_path, thread_source FROM threads WHERE archived = 0')
78
+ .all() as RecoveryThreadRow[];
79
+ return threads.filter((thread) => {
80
+ return getPortablePathBasename(thread.cwd) === projectName && thread.thread_source !== 'subagent';
81
+ });
82
+ };
83
+
84
+ const refreshThreadRows = (db: Database, threadIds: string[]) => {
85
+ if (threadIds.length === 0) {
86
+ return 0;
87
+ }
88
+
89
+ const nowSeconds = Math.floor(Date.now() / 1000);
90
+ const nowMs = Date.now();
91
+ const placeholders = threadIds.map(() => '?').join(', ');
92
+ const statement = db.prepare(`
93
+ UPDATE threads
94
+ SET updated_at = ?1,
95
+ updated_at_ms = ?2,
96
+ has_user_event = 1
97
+ WHERE id IN (${placeholders})
98
+ `);
99
+ const result = statement.run(nowSeconds, nowMs, ...threadIds);
100
+ return Number(result.changes);
101
+ };
102
+
103
+ const refreshSessionIndex = async (sessionIndexPath: string, threadIds: string[]) => {
104
+ if (threadIds.length === 0) {
105
+ return 0;
106
+ }
107
+
108
+ const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
109
+ const threadIdSet = new Set(threadIds);
110
+ const lines = (await Bun.file(sessionIndexPath).text()).split('\n');
111
+ let updated = 0;
112
+ const rewrittenLines: string[] = [];
113
+
114
+ for (const line of lines) {
115
+ if (!line.trim()) {
116
+ continue;
117
+ }
118
+
119
+ const parsed = JSON.parse(line) as { id?: string; updated_at?: string };
120
+ if (parsed.id && threadIdSet.has(parsed.id)) {
121
+ parsed.updated_at = now;
122
+ updated += 1;
123
+ }
124
+
125
+ rewrittenLines.push(JSON.stringify(parsed));
126
+ }
127
+
128
+ await Bun.write(sessionIndexPath, `${rewrittenLines.join('\n')}\n`);
129
+ return updated;
130
+ };
131
+
132
+ const touchRolloutFiles = async (codexDir: string, rolloutPaths: string[]) => {
133
+ const now = new Date();
134
+ let touched = 0;
135
+
136
+ for (const rolloutPath of rolloutPaths) {
137
+ const absolutePath = path.isAbsolute(rolloutPath) ? rolloutPath : path.join(codexDir, rolloutPath);
138
+ if (!(await Bun.file(absolutePath).exists())) {
139
+ continue;
140
+ }
141
+
142
+ await utimes(absolutePath, now, now);
143
+ touched += 1;
144
+ }
145
+
146
+ return touched;
147
+ };
148
+
149
+ export const recoverCodexProjectThreads = async (
150
+ dbPath: string,
151
+ projectName: string,
152
+ ): Promise<RecoverProjectThreadsResult> => {
153
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
154
+ const globalStatePath = path.join(codexDir, '.codex-global-state.json');
155
+ const sessionIndexPath = path.join(codexDir, 'session_index.jsonl');
156
+
157
+ await assertRequiredStatePath(dbPath);
158
+ await assertRequiredStatePath(globalStatePath);
159
+ await assertRequiredStatePath(sessionIndexPath);
160
+
161
+ const backups = {
162
+ globalState: await backupFile(globalStatePath, 'recover-project-roots'),
163
+ sessionIndex: await backupFile(sessionIndexPath, 'recover-project-session-index'),
164
+ stateDb: await backupFile(dbPath, 'recover-project-threads'),
165
+ };
166
+
167
+ const globalState = await readGlobalState(globalStatePath);
168
+ const db = runWithSqliteRetry({
169
+ action: () => {
170
+ const opened = new Database(dbPath);
171
+ opened.exec('PRAGMA busy_timeout = 5000');
172
+ return opened;
173
+ },
174
+ });
175
+
176
+ try {
177
+ const topLevelThreads = getProjectTopLevelThreads(db, projectName);
178
+ const projectCwds = [...new Set(topLevelThreads.map((thread) => thread.cwd))];
179
+ const rootUpdateResult = updateGlobalRoots(globalState, projectCwds);
180
+ await writeGlobalState(globalStatePath, rootUpdateResult.state);
181
+
182
+ const threadIds = topLevelThreads.map((thread) => thread.id);
183
+ const rolloutPaths = topLevelThreads.map((thread) => thread.rollout_path);
184
+ const threadDbRowsUpdated = refreshThreadRows(db, threadIds);
185
+ const sessionIndexRowsUpdated = await refreshSessionIndex(sessionIndexPath, threadIds);
186
+ const rolloutFilesTouched = await touchRolloutFiles(codexDir, rolloutPaths);
187
+
188
+ return {
189
+ backups,
190
+ projectName,
191
+ projectRootsAdded: rootUpdateResult.projectRootsAdded,
192
+ resolvedCwds: projectCwds,
193
+ rolloutFilesTouched,
194
+ savedRootsAdded: rootUpdateResult.savedRootsAdded,
195
+ sessionIndexRowsUpdated,
196
+ threadDbRowsUpdated,
197
+ topLevelThreadsFound: threadIds.length,
198
+ };
199
+ } finally {
200
+ db.close();
201
+ }
202
+ };