spiracha 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +6 -1
  2. package/apps/ui/README.md +20 -2
  3. package/apps/ui/package.json +2 -2
  4. package/apps/ui/src/components/antigravity-conversations-table.tsx +50 -11
  5. package/apps/ui/src/components/antigravity-workspaces-table.tsx +1 -1
  6. package/apps/ui/src/components/app-shell.tsx +1 -0
  7. package/apps/ui/src/components/breadcrumbs.tsx +21 -3
  8. package/apps/ui/src/components/claude-code-sessions-table.tsx +42 -6
  9. package/apps/ui/src/components/claude-code-workspaces-table.tsx +3 -1
  10. package/apps/ui/src/components/cursor-threads-table.tsx +10 -47
  11. package/apps/ui/src/components/data-table.tsx +7 -5
  12. package/apps/ui/src/components/export-dialog.tsx +2 -12
  13. package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
  14. package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
  15. package/apps/ui/src/components/kiro-sessions-table.tsx +40 -4
  16. package/apps/ui/src/components/opencode-sessions-table.tsx +40 -4
  17. package/apps/ui/src/components/page-header.tsx +15 -9
  18. package/apps/ui/src/components/qoder-sessions-table.tsx +23 -2
  19. package/apps/ui/src/components/qoder-workspaces-table.tsx +5 -1
  20. package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
  21. package/apps/ui/src/components/threads-table.tsx +9 -46
  22. package/apps/ui/src/components/transcript-view.tsx +148 -52
  23. package/apps/ui/src/components/ui/select.tsx +1 -1
  24. package/apps/ui/src/lib/antigravity-conversation-state.ts +17 -4
  25. package/apps/ui/src/lib/antigravity-server.ts +152 -7
  26. package/apps/ui/src/lib/antigravity-transcript-events.ts +11 -3
  27. package/apps/ui/src/lib/claude-code-queries.ts +8 -0
  28. package/apps/ui/src/lib/claude-code-server.ts +175 -7
  29. package/apps/ui/src/lib/claude-code-transcript-events.ts +8 -1
  30. package/apps/ui/src/lib/codex-queries.ts +19 -3
  31. package/apps/ui/src/lib/codex-server.ts +135 -53
  32. package/apps/ui/src/lib/cursor-server.ts +48 -5
  33. package/apps/ui/src/lib/formatters.ts +3 -5
  34. package/apps/ui/src/lib/grok-queries.ts +22 -0
  35. package/apps/ui/src/lib/grok-server.ts +169 -0
  36. package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
  37. package/apps/ui/src/lib/kiro-server.ts +85 -7
  38. package/apps/ui/src/lib/opencode-server.ts +85 -7
  39. package/apps/ui/src/lib/qoder-server.ts +67 -11
  40. package/apps/ui/src/lib/route-search.ts +114 -0
  41. package/apps/ui/src/lib/source-session-export-server.ts +86 -3
  42. package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
  43. package/apps/ui/src/lib/workspace-delete-navigation.ts +12 -0
  44. package/apps/ui/src/routeTree.gen.ts +107 -0
  45. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +76 -10
  46. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +269 -31
  47. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +4 -0
  48. package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
  49. package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
  50. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +143 -17
  51. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +165 -26
  52. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +2 -2
  53. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +11 -1
  54. package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +388 -0
  55. package/apps/ui/src/routes/grok.$workspaceKey.tsx +301 -0
  56. package/apps/ui/src/routes/grok.index.tsx +48 -0
  57. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +66 -15
  58. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +199 -32
  59. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +105 -19
  60. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +236 -44
  61. package/apps/ui/src/routes/opencode.index.tsx +16 -3
  62. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +7 -4
  63. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +75 -28
  64. package/apps/ui/src/routes/threads.$threadId.tsx +548 -64
  65. package/package.json +19 -18
  66. package/src/client.ts +134 -1
  67. package/src/lib/antigravity-db.ts +208 -32
  68. package/src/lib/antigravity-exporter-types.ts +3 -0
  69. package/src/lib/antigravity-projects.ts +201 -0
  70. package/src/lib/claude-code-db.ts +498 -32
  71. package/src/lib/claude-code-exporter-types.ts +5 -0
  72. package/src/lib/claude-code-transcript-phase.ts +60 -1
  73. package/src/lib/claude-code-transcript.ts +8 -5
  74. package/src/lib/codex-browser-export.ts +23 -27
  75. package/src/lib/codex-thread-cache.ts +41 -13
  76. package/src/lib/codex-thread-parser.ts +86 -35
  77. package/src/lib/codex-transcript-filter.ts +31 -0
  78. package/src/lib/codex-transcript-renderer.ts +39 -19
  79. package/src/lib/concurrency.ts +41 -0
  80. package/src/lib/conversation-api.ts +399 -19
  81. package/src/lib/conversation-data/antigravity-adapter.ts +21 -2
  82. package/src/lib/conversation-data/claude-code-adapter.ts +37 -6
  83. package/src/lib/conversation-data/codex-adapter.ts +28 -4
  84. package/src/lib/conversation-data/cursor-adapter.ts +35 -1
  85. package/src/lib/conversation-data/grok-adapter.ts +210 -0
  86. package/src/lib/conversation-data/index.ts +76 -1
  87. package/src/lib/conversation-data/kiro-adapter.ts +41 -7
  88. package/src/lib/conversation-data/opencode-adapter.ts +24 -2
  89. package/src/lib/conversation-data/qoder-adapter.ts +44 -19
  90. package/src/lib/conversation-data/types.ts +43 -0
  91. package/src/lib/conversation-zip-export.ts +57 -0
  92. package/src/lib/cursor-db.ts +34 -5
  93. package/src/lib/grok-db.ts +1026 -0
  94. package/src/lib/grok-exporter-types.ts +110 -0
  95. package/src/lib/grok-transcript-phase.ts +52 -0
  96. package/src/lib/grok-transcript.ts +154 -0
  97. package/src/lib/kiro-db.ts +52 -1
  98. package/src/lib/model-label.ts +11 -3
  99. package/src/lib/opencode-db.ts +598 -55
  100. package/src/lib/opencode-transcript.ts +8 -5
  101. package/src/lib/shared.ts +12 -0
  102. package/src/lib/transcript-load-limiter.ts +82 -0
  103. package/src/lib/ui-cache.ts +43 -17
  104. package/src/lib/ui-export-archive.ts +60 -0
@@ -1,17 +1,24 @@
1
1
  import type { ClaudeCodeSessionSummary, ClaudeCodeWorkspaceGroup } from '@spiracha/lib/claude-code-exporter-types';
2
- import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
3
- import { createFileRoute } from '@tanstack/react-router';
4
- import { useDeferredValue, useState } from 'react';
2
+ import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
3
+ import { createFileRoute, useNavigate } from '@tanstack/react-router';
4
+ import { useDeferredValue, useMemo, useState } from 'react';
5
5
  import { ClaudeCodeSessionsTable } from '#/components/claude-code-sessions-table';
6
+ import { DeleteConfirmDialog } from '#/components/delete-confirm-dialog';
6
7
  import { ExportDialog } from '#/components/export-dialog';
7
8
  import { ListSearchInput } from '#/components/list-search-input';
8
9
  import { LoadingPanel } from '#/components/loading-panel';
9
10
  import { PageHeader } from '#/components/page-header';
10
11
  import { ReloadErrorPanel } from '#/components/reload-error-panel';
11
12
  import { claudeCodeSessionsQueryOptions, claudeCodeWorkspacesQueryOptions } from '#/lib/claude-code-queries';
12
- import { exportClaudeCodeSessionFn } from '#/lib/claude-code-server';
13
+ import {
14
+ deleteClaudeCodeSessionFn,
15
+ deleteClaudeCodeSessionsFn,
16
+ exportClaudeCodeSessionFn,
17
+ exportClaudeCodeSessionsFn,
18
+ } from '#/lib/claude-code-server';
13
19
  import { downloadTextFile, downloadUrlFile } from '#/lib/download';
14
20
  import { matchesTextQuery } from '#/lib/text-filter';
21
+ import { isWorkspaceEmptiedByDelete } from '#/lib/workspace-delete-navigation';
15
22
 
16
23
  type ExportDialogOptions = {
17
24
  includeCommentary: boolean;
@@ -21,6 +28,15 @@ type ExportDialogOptions = {
21
28
  zipArchive: boolean;
22
29
  };
23
30
 
31
+ type PendingSessionDelete = {
32
+ sessions: ClaudeCodeSessionSummary[];
33
+ };
34
+
35
+ type PendingSessionExport = {
36
+ label: string;
37
+ sessionIds: string[];
38
+ };
39
+
24
40
  const findWorkspaceOrThrow = (workspaces: ClaudeCodeWorkspaceGroup[], workspaceKey: string) => {
25
41
  const workspace = workspaces.find((candidate) => candidate.key === workspaceKey);
26
42
  if (!workspace) {
@@ -30,6 +46,36 @@ const findWorkspaceOrThrow = (workspaces: ClaudeCodeWorkspaceGroup[], workspaceK
30
46
  return workspace;
31
47
  };
32
48
 
49
+ const buildSessionExport = (selectedSessions: ClaudeCodeSessionSummary[]): PendingSessionExport => ({
50
+ label: selectedSessions.length === 1 ? selectedSessions[0]!.title : `${selectedSessions.length} selected sessions`,
51
+ sessionIds: selectedSessions.map((session) => session.sessionId),
52
+ });
53
+
54
+ const getDeleteConfirmLabel = (pendingDelete: PendingSessionDelete | null, isPending: boolean) => {
55
+ if (isPending) {
56
+ return 'Deleting...';
57
+ }
58
+
59
+ return pendingDelete && pendingDelete.sessions.length > 1 ? 'Delete sessions' : 'Delete session';
60
+ };
61
+
62
+ const getDeleteDescription = (pendingDelete: PendingSessionDelete | null) => {
63
+ if (!pendingDelete) {
64
+ return 'Permanently delete the selected Claude Code sessions from disk.';
65
+ }
66
+
67
+ if (pendingDelete.sessions.length === 1) {
68
+ return `Permanently delete "${pendingDelete.sessions[0]!.title}" from Claude Code history. This removes the session JSONL file from disk.`;
69
+ }
70
+
71
+ return `Permanently delete ${pendingDelete.sessions.length} selected Claude Code sessions from disk.`;
72
+ };
73
+
74
+ const getDeleteTitle = (pendingDelete: PendingSessionDelete | null) =>
75
+ pendingDelete && pendingDelete.sessions.length > 1
76
+ ? `Delete ${pendingDelete.sessions.length} Claude Code sessions?`
77
+ : 'Delete this Claude Code session?';
78
+
33
79
  export const Route = createFileRoute('/claude-code/$workspaceKey')({
34
80
  component: ClaudeCodeWorkspacePage,
35
81
  errorComponent: ClaudeCodeWorkspaceErrorComponent,
@@ -48,12 +94,15 @@ function ClaudeCodeWorkspaceErrorComponent({ error }: { error: Error }) {
48
94
  }
49
95
 
50
96
  function ClaudeCodeWorkspacePage() {
97
+ const navigate = useNavigate({ from: Route.fullPath });
51
98
  const params = Route.useParams();
99
+ const queryClient = useQueryClient();
52
100
  const workspaces = useSuspenseQuery(claudeCodeWorkspacesQueryOptions()).data;
53
101
  const workspace = findWorkspaceOrThrow(workspaces, params.workspaceKey);
54
102
  const sessions = useSuspenseQuery(claudeCodeSessionsQueryOptions(workspace.key)).data;
55
103
  const [searchInput, setSearchInput] = useState('');
56
- const [pendingExport, setPendingExport] = useState<ClaudeCodeSessionSummary | null>(null);
104
+ const [pendingDelete, setPendingDelete] = useState<PendingSessionDelete | null>(null);
105
+ const [pendingExport, setPendingExport] = useState<PendingSessionExport | null>(null);
57
106
  const deferredSearch = useDeferredValue(searchInput);
58
107
 
59
108
  const exportMutation = useMutation({
@@ -62,16 +111,28 @@ function ClaudeCodeWorkspacePage() {
62
111
  throw new Error('No Claude Code session selected for export');
63
112
  }
64
113
 
65
- const download = await exportClaudeCodeSessionFn({
66
- data: {
67
- includeCommentary: options.includeCommentary,
68
- includeMetadata: options.includeMetadata,
69
- includeTools: options.includeTools,
70
- outputFormat: options.outputFormat,
71
- sessionId: pendingExport.sessionId,
72
- zipArchive: options.zipArchive,
73
- },
74
- });
114
+ const download =
115
+ pendingExport.sessionIds.length === 1
116
+ ? await exportClaudeCodeSessionFn({
117
+ data: {
118
+ includeCommentary: options.includeCommentary,
119
+ includeMetadata: options.includeMetadata,
120
+ includeTools: options.includeTools,
121
+ outputFormat: options.outputFormat,
122
+ sessionId: pendingExport.sessionIds[0]!,
123
+ zipArchive: options.zipArchive,
124
+ },
125
+ })
126
+ : await exportClaudeCodeSessionsFn({
127
+ data: {
128
+ includeCommentary: options.includeCommentary,
129
+ includeMetadata: options.includeMetadata,
130
+ includeTools: options.includeTools,
131
+ outputFormat: options.outputFormat,
132
+ sessionIds: pendingExport.sessionIds,
133
+ zipArchive: options.zipArchive,
134
+ },
135
+ });
75
136
  if (download.mode === 'download') {
76
137
  downloadTextFile(download.fileName, download.content, download.mimeType);
77
138
  return;
@@ -84,16 +145,62 @@ function ClaudeCodeWorkspacePage() {
84
145
  },
85
146
  });
86
147
 
87
- const visibleSessions = sessions.filter((session) =>
88
- matchesTextQuery(deferredSearch, [
89
- session.title,
90
- session.sessionId,
91
- session.model,
92
- session.version,
93
- session.gitBranch,
94
- session.filePath,
95
- ]),
148
+ const deleteMutation = useMutation({
149
+ mutationFn: async (sessionIds: string[]) =>
150
+ sessionIds.length === 1
151
+ ? deleteClaudeCodeSessionFn({ data: { sessionId: sessionIds[0]! } })
152
+ : deleteClaudeCodeSessionsFn({ data: { sessionIds } }),
153
+ onSuccess: async (_result, sessionIds) => {
154
+ const workspaceEmptied = isWorkspaceEmptiedByDelete(sessions, sessionIds, (session) => session.sessionId);
155
+ setPendingDelete(null);
156
+ if (workspaceEmptied) {
157
+ await navigate({ to: '/claude-code' });
158
+ }
159
+
160
+ await Promise.all([
161
+ queryClient.invalidateQueries({ queryKey: ['claude-code-workspaces'] }),
162
+ queryClient.invalidateQueries({ queryKey: ['claude-code-sessions', workspace.key] }),
163
+ ...sessionIds.map((sessionId) =>
164
+ queryClient.invalidateQueries({ queryKey: ['claude-code-session', sessionId] }),
165
+ ),
166
+ ]);
167
+ },
168
+ });
169
+
170
+ const visibleSessions = useMemo(
171
+ () =>
172
+ sessions.filter((session) =>
173
+ matchesTextQuery(deferredSearch, [
174
+ session.title,
175
+ session.sessionId,
176
+ session.model,
177
+ session.version,
178
+ session.gitBranch,
179
+ session.filePath,
180
+ ]),
181
+ ),
182
+ [deferredSearch, sessions],
96
183
  );
184
+ const visibleSessionsById = useMemo(
185
+ () => new Map(visibleSessions.map((session) => [session.sessionId, session])),
186
+ [visibleSessions],
187
+ );
188
+ const lookupSelectedSessions = (sessionIds: string[]) =>
189
+ sessionIds
190
+ .map((sessionId) => visibleSessionsById.get(sessionId) ?? null)
191
+ .filter((session): session is ClaudeCodeSessionSummary => session !== null);
192
+ const openExportForSessions = (selectedSessions: ClaudeCodeSessionSummary[]) => {
193
+ if (selectedSessions.length === 0) {
194
+ return;
195
+ }
196
+
197
+ setPendingExport(buildSessionExport(selectedSessions));
198
+ };
199
+ const openDeleteForSessions = (selectedSessions: ClaudeCodeSessionSummary[]) => {
200
+ if (selectedSessions.length > 0) {
201
+ setPendingDelete({ sessions: selectedSessions });
202
+ }
203
+ };
97
204
 
98
205
  return (
99
206
  <div className="space-y-6">
@@ -110,12 +217,19 @@ function ClaudeCodeWorkspacePage() {
110
217
  title={workspace.label}
111
218
  />
112
219
 
113
- <ClaudeCodeSessionsTable sessions={visibleSessions} onExportSession={setPendingExport} />
220
+ <ClaudeCodeSessionsTable
221
+ sessions={visibleSessions}
222
+ onDeleteSession={(session) => openDeleteForSessions([session])}
223
+ onDeleteSessions={(sessionIds) => openDeleteForSessions(lookupSelectedSessions(sessionIds))}
224
+ onExportSession={(session) => openExportForSessions([session])}
225
+ onExportSessions={(sessionIds) => openExportForSessions(lookupSelectedSessions(sessionIds))}
226
+ />
114
227
 
115
228
  <ExportDialog
229
+ forceZipArchive={pendingExport ? pendingExport.sessionIds.length > 1 : false}
116
230
  open={pendingExport !== null}
117
231
  pending={exportMutation.isPending}
118
- title={pendingExport ? `Export ${pendingExport.title}` : 'Export session'}
232
+ title={pendingExport ? `Export ${pendingExport.label}` : 'Export session'}
119
233
  onExport={(options) => exportMutation.mutate(options)}
120
234
  onOpenChange={(open) => {
121
235
  if (!open) {
@@ -130,6 +244,31 @@ function ClaudeCodeWorkspacePage() {
130
244
  {exportMutation.error instanceof Error ? exportMutation.error.message : 'Session export failed'}
131
245
  </p>
132
246
  ) : null}
247
+
248
+ <DeleteConfirmDialog
249
+ confirmLabel={getDeleteConfirmLabel(pendingDelete, deleteMutation.isPending)}
250
+ description={getDeleteDescription(pendingDelete)}
251
+ errorMessage={
252
+ deleteMutation.isError
253
+ ? deleteMutation.error instanceof Error
254
+ ? deleteMutation.error.message
255
+ : 'Session delete failed'
256
+ : null
257
+ }
258
+ open={pendingDelete !== null}
259
+ title={getDeleteTitle(pendingDelete)}
260
+ onConfirm={() => {
261
+ if (pendingDelete) {
262
+ deleteMutation.mutate(pendingDelete.sessions.map((session) => session.sessionId));
263
+ }
264
+ }}
265
+ onOpenChange={(open) => {
266
+ if (!open) {
267
+ setPendingDelete(null);
268
+ deleteMutation.reset();
269
+ }
270
+ }}
271
+ />
133
272
  </div>
134
273
  );
135
274
  }
@@ -12,7 +12,7 @@ import { MetadataSection } from '#/components/metadata-section';
12
12
  import { MetricCard } from '#/components/metric-card';
13
13
  import { PageHeader } from '#/components/page-header';
14
14
  import { ReloadErrorPanel } from '#/components/reload-error-panel';
15
- import { TranscriptView } from '#/components/transcript-view';
15
+ import { DEFAULT_SHOW_USER_MESSAGES, TranscriptView } from '#/components/transcript-view';
16
16
  import { Button } from '#/components/ui/button';
17
17
  import { Checkbox } from '#/components/ui/checkbox';
18
18
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '#/components/ui/tabs';
@@ -221,7 +221,7 @@ const CursorThreadDetailPage = () => {
221
221
  const [showCommentary, setShowCommentary] = useState(false);
222
222
  const [showExtraEvents, setShowExtraEvents] = useState(false);
223
223
  const [showRawJson, setShowRawJson] = useState(false);
224
- const [showUserMessages, setShowUserMessages] = useState(true);
224
+ const [showUserMessages, setShowUserMessages] = useState(DEFAULT_SHOW_USER_MESSAGES);
225
225
  const transcriptEvents = useMemo(
226
226
  () => (detail.transcript ? cursorTranscriptToThreadEvents(detail.transcript) : []),
227
227
  [detail.transcript],
@@ -21,6 +21,7 @@ import {
21
21
  } from '#/lib/cursor-server';
22
22
  import { downloadTextFile, downloadUrlFile } from '#/lib/download';
23
23
  import { matchesTextQuery } from '#/lib/text-filter';
24
+ import { isWorkspaceEmptiedByDelete } from '#/lib/workspace-delete-navigation';
24
25
 
25
26
  type PendingCursorDelete =
26
27
  | { kind: 'threads'; threads: CursorThreadSummary[] }
@@ -153,8 +154,17 @@ const CursorWorkspacePage = () => {
153
154
  return;
154
155
  }
155
156
 
156
- await invalidateWorkspaceQueries();
157
+ const workspaceEmptied = isWorkspaceEmptiedByDelete(
158
+ threads,
159
+ target.threads.map((thread) => thread.composerId),
160
+ (thread) => thread.composerId,
161
+ );
157
162
  setPendingDelete(null);
163
+ if (workspaceEmptied) {
164
+ await navigate({ to: '/cursor' });
165
+ }
166
+
167
+ await invalidateWorkspaceQueries();
158
168
  },
159
169
  });
160
170