spiracha 1.0.0 → 1.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.
- package/AGENTS.md +28 -1
- package/README.md +47 -7
- package/apps/ui/AGENTS.md +70 -0
- package/apps/ui/README.md +72 -0
- package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +1 -0
- package/apps/ui/dist/client/assets/analytics-BjYaHqXk.js +1 -0
- package/apps/ui/dist/client/assets/checkbox-wPoGG3of.js +1 -0
- package/apps/ui/dist/client/assets/data-table-6yDgAdtf.js +4 -0
- package/apps/ui/dist/client/assets/delete-confirm-dialog-DJUAk7ha.js +11 -0
- package/apps/ui/dist/client/assets/download-BhWd-Pm5.js +1 -0
- package/apps/ui/dist/client/assets/es2015-BlyMI4CF.js +41 -0
- package/apps/ui/dist/client/assets/formatters-BxjZwWSE.js +1 -0
- package/apps/ui/dist/client/assets/index-T01rPkb4.js +22 -0
- package/apps/ui/dist/client/assets/input-B3YN8gzg.js +1 -0
- package/apps/ui/dist/client/assets/metric-card-BWW7TWER.js +1 -0
- package/apps/ui/dist/client/assets/page-header-BZ8Gnxgs.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-B7XcpoLt.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-EfBhCHPY.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-4vfIwLjw.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-DzEZ4pAJ.js +1 -0
- package/apps/ui/dist/client/assets/routes-CWCCZykE.js +1 -0
- package/apps/ui/dist/client/assets/select-DLXGsyZ4.js +1 -0
- package/apps/ui/dist/client/assets/settings-b0Xthfae.js +1 -0
- package/apps/ui/dist/client/assets/styles-8Wtc8YJw.css +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-CgtoCqTb.js +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-DBiDb38K.js +7 -0
- 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/client/manifest.json +25 -0
- package/apps/ui/dist/client/robots.txt +3 -0
- package/apps/ui/dist/server/assets/__23tanstack-start-plugin-adapters-BzCA6dXo.js +5 -0
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-BjsXNYgm.js +99 -0
- package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +6 -0
- package/apps/ui/dist/server/assets/analytics-Br_fZB6a.js +139 -0
- package/apps/ui/dist/server/assets/button-CmTDnzOn.js +46 -0
- package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +19 -0
- package/apps/ui/dist/server/assets/codex-queries-CAF6HYiG.js +109 -0
- package/apps/ui/dist/server/assets/codex-server-Cqh0hb93.js +1995 -0
- package/apps/ui/dist/server/assets/data-table-Cdct823O.js +189 -0
- package/apps/ui/dist/server/assets/delete-confirm-dialog-CWqcTXTF.js +139 -0
- package/apps/ui/dist/server/assets/download-CzHmFWGk.js +286 -0
- package/apps/ui/dist/server/assets/formatters-B6o5pTY9.js +72 -0
- package/apps/ui/dist/server/assets/input-B4tEzctc.js +46 -0
- package/apps/ui/dist/server/assets/loading-panel-DbLdvjtR.js +27 -0
- package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +23 -0
- package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +25 -0
- package/apps/ui/dist/server/assets/path-transforms-DD1e7rhY.js +31 -0
- package/apps/ui/dist/server/assets/projects._project-Bwf6iJC-.js +335 -0
- package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +26 -0
- package/apps/ui/dist/server/assets/projects._project-DdVSdfPe.js +18 -0
- package/apps/ui/dist/server/assets/projects.index-CaplpeMy.js +26 -0
- package/apps/ui/dist/server/assets/projects.index-DKeVeqUZ.js +171 -0
- package/apps/ui/dist/server/assets/router-ve2Hrl2Y.js +307 -0
- package/apps/ui/dist/server/assets/routes-BJyx5OmO.js +34 -0
- package/apps/ui/dist/server/assets/routes-pkOwjjYc.js +168 -0
- package/apps/ui/dist/server/assets/select-GW76p-ld.js +76 -0
- package/apps/ui/dist/server/assets/settings-MvWDgc1u.js +100 -0
- package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +52 -0
- package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +13 -0
- package/apps/ui/dist/server/assets/start-BAvbjjfs.js +4 -0
- package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +26 -0
- package/apps/ui/dist/server/assets/threads._threadId-D3PYZIwl.js +18 -0
- package/apps/ui/dist/server/assets/threads._threadId-D3xaWM86.js +1037 -0
- package/apps/ui/dist/server/assets/utils-C_uf36nf.js +8 -0
- package/apps/ui/dist/server/server.js +5678 -0
- package/package.json +47 -7
- package/src/export-chats.ts +1 -14
- package/src/lib/codex-analytics.ts +100 -0
- package/src/lib/codex-browser-db.ts +518 -0
- package/src/lib/codex-browser-export.ts +418 -0
- package/src/lib/codex-browser-types.ts +224 -0
- package/src/lib/codex-exporter-cli.ts +5 -0
- package/src/lib/codex-exporter-transcript.ts +143 -32
- package/src/lib/codex-exporter-types.ts +8 -0
- package/src/lib/codex-thread-cache.ts +58 -0
- package/src/lib/codex-thread-parser.ts +604 -0
- package/src/lib/interactive-cli.ts +5 -13
- package/src/lib/native-open.ts +54 -0
- package/src/lib/path-transforms.ts +45 -0
- package/src/lib/shared.ts +37 -1
- package/src/lib/sqlite-error.ts +14 -0
- package/src/lib/sqlite-retry.ts +39 -0
- package/src/lib/ui-cache.ts +96 -0
- package/src/lib/ui-export-files.ts +77 -0
- package/src/mcp-server.ts +1 -0
- package/src/spiracha.ts +14 -1
- package/src/ui-cli.ts +310 -0
package/package.json
CHANGED
|
@@ -12,14 +12,26 @@
|
|
|
12
12
|
"url": "https://github.com/ragaeeb/spiracha/issues"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@inquirer/prompts": "^8.4.
|
|
15
|
+
"@inquirer/prompts": "^8.4.3",
|
|
16
16
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
17
|
+
"@tanstack/react-query": "5.100.14",
|
|
18
|
+
"@tanstack/react-router": "1.170.8",
|
|
19
|
+
"@tanstack/react-router-ssr-query": "1.167.0",
|
|
20
|
+
"@tanstack/react-table": "8.21.3",
|
|
21
|
+
"@tanstack/react-virtual": "3.13.25",
|
|
22
|
+
"class-variance-authority": "0.7.1",
|
|
23
|
+
"clsx": "2.1.1",
|
|
24
|
+
"lucide-react": "1.16.0",
|
|
25
|
+
"radix-ui": "1.4.3",
|
|
26
|
+
"react": "19.2.6",
|
|
27
|
+
"react-dom": "19.2.6",
|
|
28
|
+
"tailwind-merge": "3.6.0",
|
|
17
29
|
"zod": "^4.4.3"
|
|
18
30
|
},
|
|
19
31
|
"description": "Export local Codex chats and Claude Code transcripts to Markdown or plain text.",
|
|
20
32
|
"devDependencies": {
|
|
21
|
-
"@types/bun": "^1.3.
|
|
22
|
-
"@types/node": "^25.
|
|
33
|
+
"@types/bun": "^1.3.14",
|
|
34
|
+
"@types/node": "^25.9.1",
|
|
23
35
|
"typescript": "^6.0.3"
|
|
24
36
|
},
|
|
25
37
|
"engines": {
|
|
@@ -30,14 +42,34 @@
|
|
|
30
42
|
"src/export-claude.ts",
|
|
31
43
|
"src/mcp-server.ts",
|
|
32
44
|
"src/spiracha.ts",
|
|
45
|
+
"src/ui-cli.ts",
|
|
33
46
|
"src/lib/claude-exporter.ts",
|
|
34
47
|
"src/lib/codex-exporter-cli.ts",
|
|
35
48
|
"src/lib/codex-exporter-db.ts",
|
|
36
49
|
"src/lib/codex-exporter-transcript.ts",
|
|
37
50
|
"src/lib/codex-exporter-types.ts",
|
|
38
51
|
"src/lib/codex-exporter.ts",
|
|
52
|
+
"src/lib/codex-browser-db.ts",
|
|
53
|
+
"src/lib/codex-browser-export.ts",
|
|
54
|
+
"src/lib/codex-browser-types.ts",
|
|
55
|
+
"src/lib/codex-thread-cache.ts",
|
|
56
|
+
"src/lib/codex-thread-parser.ts",
|
|
57
|
+
"src/lib/codex-analytics.ts",
|
|
58
|
+
"src/lib/path-transforms.ts",
|
|
59
|
+
"src/lib/sqlite-error.ts",
|
|
60
|
+
"src/lib/sqlite-retry.ts",
|
|
61
|
+
"src/lib/ui-export-files.ts",
|
|
62
|
+
"src/lib/ui-cache.ts",
|
|
39
63
|
"src/lib/interactive-cli.ts",
|
|
64
|
+
"src/lib/native-open.ts",
|
|
40
65
|
"src/lib/shared.ts",
|
|
66
|
+
"apps/ui/dist/client/assets/**/*",
|
|
67
|
+
"apps/ui/dist/client/favicon.ico",
|
|
68
|
+
"apps/ui/dist/client/logo192.png",
|
|
69
|
+
"apps/ui/dist/client/logo512.png",
|
|
70
|
+
"apps/ui/dist/client/manifest.json",
|
|
71
|
+
"apps/ui/dist/client/robots.txt",
|
|
72
|
+
"apps/ui/dist/server/**/*",
|
|
41
73
|
"README.md",
|
|
42
74
|
"AGENTS.md"
|
|
43
75
|
],
|
|
@@ -52,19 +84,27 @@
|
|
|
52
84
|
],
|
|
53
85
|
"license": "MIT",
|
|
54
86
|
"name": "spiracha",
|
|
55
|
-
"packageManager": "bun@1.3.
|
|
87
|
+
"packageManager": "bun@1.3.14",
|
|
56
88
|
"repository": {
|
|
57
89
|
"type": "git",
|
|
58
90
|
"url": "git+https://github.com/ragaeeb/spiracha.git"
|
|
59
91
|
},
|
|
60
92
|
"scripts": {
|
|
61
|
-
"build": "bun run typecheck",
|
|
93
|
+
"build": "bun run typecheck && bun run --cwd apps/ui build",
|
|
62
94
|
"export:claude": "bun run ./src/export-claude.ts",
|
|
63
95
|
"format": "biome check . --write && biome lint . --write",
|
|
96
|
+
"lint": "biome check .",
|
|
64
97
|
"mcp": "bun run ./src/mcp-server.ts",
|
|
65
98
|
"start": "bun run ./src/export-chats.ts",
|
|
66
|
-
"typecheck": "
|
|
99
|
+
"typecheck": "bun run typecheck:root && bun run typecheck:ui",
|
|
100
|
+
"typecheck:root": "bunx tsc --noEmit",
|
|
101
|
+
"typecheck:ui": "bun run --cwd apps/ui typecheck",
|
|
102
|
+
"ui:dev": "bun run --cwd apps/ui dev",
|
|
103
|
+
"ui:preview": "bun run --cwd apps/ui preview"
|
|
67
104
|
},
|
|
68
105
|
"type": "module",
|
|
69
|
-
"version": "1.
|
|
106
|
+
"version": "1.1.0",
|
|
107
|
+
"workspaces": [
|
|
108
|
+
"apps/*"
|
|
109
|
+
]
|
|
70
110
|
}
|
package/src/export-chats.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
7
7
|
import { getCodexHelpText, parseCodexCliArgs, runCodexExport } from './lib/codex-exporter';
|
|
8
8
|
import { runInteractiveExport } from './lib/interactive-cli';
|
|
9
|
+
import { openPathNatively } from './lib/native-open';
|
|
9
10
|
import { CliUsageError } from './lib/shared';
|
|
10
11
|
|
|
11
12
|
export const runExportChatsCli = async (argv = process.argv.slice(2)): Promise<void> => {
|
|
@@ -115,20 +116,6 @@ const resolveExportFolder = async (targetPath: string): Promise<string> => {
|
|
|
115
116
|
return path.dirname(targetPath);
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
const openPathNatively = async (targetPath: string): Promise<void> => {
|
|
119
|
-
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open';
|
|
120
|
-
|
|
121
|
-
const proc = Bun.spawn([command, targetPath], {
|
|
122
|
-
stderr: 'pipe',
|
|
123
|
-
stdout: 'ignore',
|
|
124
|
-
});
|
|
125
|
-
const exitCode = await proc.exited;
|
|
126
|
-
if (exitCode !== 0) {
|
|
127
|
-
const errorText = await new Response(proc.stderr).text();
|
|
128
|
-
throw new Error(`Failed to open ${targetPath} with ${command}: ${errorText.trim() || `exit code ${exitCode}`}`);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
119
|
if (import.meta.main) {
|
|
133
120
|
await runExportChatsCli();
|
|
134
121
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { listScopedThreads } from './codex-browser-db';
|
|
2
|
+
import type { CodexAnalytics, DistributionItem, ModelTokenSummary } from './codex-browser-types';
|
|
3
|
+
import type { ThreadRow } from './codex-exporter-types';
|
|
4
|
+
import { getCachedParsedCodexTranscript } from './codex-thread-cache';
|
|
5
|
+
import { getPortablePathBasename } from './shared';
|
|
6
|
+
import { getFileFingerprint, hashCacheKeyParts, withCachedJson } from './ui-cache';
|
|
7
|
+
|
|
8
|
+
export type CodexAnalyticsInput = {
|
|
9
|
+
dbPath: string;
|
|
10
|
+
project: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const roundToTwoDecimals = (value: number) => {
|
|
14
|
+
return Number(value.toFixed(2));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const incrementCount = (counts: Map<string, number>, key: string) => {
|
|
18
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const toDistribution = (counts: Map<string, number>): DistributionItem[] => {
|
|
22
|
+
return [...counts.entries()]
|
|
23
|
+
.map(([label, count]) => ({ count, label }))
|
|
24
|
+
.sort((left, right) => {
|
|
25
|
+
if (left.count !== right.count) {
|
|
26
|
+
return right.count - left.count;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return left.label.localeCompare(right.label);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const buildModelsByTokens = (threads: ThreadRow[]): ModelTokenSummary[] => {
|
|
34
|
+
const models = new Map<string, { threadCount: number; totalTokens: number }>();
|
|
35
|
+
|
|
36
|
+
for (const thread of threads) {
|
|
37
|
+
const model = thread.model ?? 'unknown';
|
|
38
|
+
const current = models.get(model) ?? { threadCount: 0, totalTokens: 0 };
|
|
39
|
+
current.threadCount += 1;
|
|
40
|
+
current.totalTokens += thread.tokens_used;
|
|
41
|
+
models.set(model, current);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return [...models.entries()]
|
|
45
|
+
.map(([model, value]) => ({ model, ...value }))
|
|
46
|
+
.sort((left, right) => {
|
|
47
|
+
if (left.totalTokens !== right.totalTokens) {
|
|
48
|
+
return right.totalTokens - left.totalTokens;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return left.model.localeCompare(right.model);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const buildAnalyticsCacheKey = async (dbPath: string, threads: ThreadRow[], project: string | null) => {
|
|
56
|
+
const dbFingerprint = await getFileFingerprint(dbPath);
|
|
57
|
+
const rolloutFingerprints = await Promise.all(threads.map((thread) => getFileFingerprint(thread.rollout_path)));
|
|
58
|
+
return `analytics-${hashCacheKeyParts(dbFingerprint, project ?? 'all', ...rolloutFingerprints)}`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const computeCodexAnalytics = async (threads: ThreadRow[]): Promise<CodexAnalytics> => {
|
|
62
|
+
const totalTokens = threads.reduce((sum, thread) => sum + thread.tokens_used, 0);
|
|
63
|
+
const projectNames = new Set(threads.map((thread) => getPortablePathBasename(thread.cwd)).filter(Boolean));
|
|
64
|
+
const toolUsage = new Map<string, number>();
|
|
65
|
+
const transcripts = await Promise.all(threads.map((thread) => getCachedParsedCodexTranscript(thread.rollout_path)));
|
|
66
|
+
let threadsWithWebSearch = 0;
|
|
67
|
+
|
|
68
|
+
for (const transcript of transcripts) {
|
|
69
|
+
if (transcript.stats.webSearchEventCount > 0) {
|
|
70
|
+
threadsWithWebSearch += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const event of transcript.events) {
|
|
74
|
+
if (event.kind === 'tool_call') {
|
|
75
|
+
incrementCount(toolUsage, event.name);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
modelsByTokens: buildModelsByTokens(threads),
|
|
82
|
+
summary: {
|
|
83
|
+
archivedThreads: threads.filter((thread) => Boolean(thread.archived)).length,
|
|
84
|
+
averageTokensPerThread: threads.length === 0 ? 0 : roundToTwoDecimals(totalTokens / threads.length),
|
|
85
|
+
distinctToolNames: toolUsage.size,
|
|
86
|
+
threadsWithWebSearch,
|
|
87
|
+
totalProjects: projectNames.size,
|
|
88
|
+
totalThreads: threads.length,
|
|
89
|
+
totalTokens,
|
|
90
|
+
},
|
|
91
|
+
toolUsage: toDistribution(toolUsage).map((item) => ({ count: item.count, name: item.label })),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const getCodexAnalytics = async (input: CodexAnalyticsInput): Promise<CodexAnalytics> => {
|
|
96
|
+
const threads = listScopedThreads(input.dbPath, input.project);
|
|
97
|
+
const key = await buildAnalyticsCacheKey(input.dbPath, threads, input.project);
|
|
98
|
+
|
|
99
|
+
return withCachedJson(key, async () => computeCodexAnalytics(threads));
|
|
100
|
+
};
|