spiracha 1.0.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 +128 -0
- package/LICENSE.md +7 -0
- package/README.md +182 -0
- package/package.json +70 -0
- package/src/export-chats.ts +134 -0
- package/src/export-claude.ts +36 -0
- package/src/lib/claude-exporter.ts +864 -0
- package/src/lib/codex-exporter-cli.ts +272 -0
- package/src/lib/codex-exporter-db.ts +320 -0
- package/src/lib/codex-exporter-transcript.ts +684 -0
- package/src/lib/codex-exporter-types.ts +110 -0
- package/src/lib/codex-exporter.ts +116 -0
- package/src/lib/interactive-cli.ts +448 -0
- package/src/lib/shared.ts +224 -0
- package/src/mcp-server.ts +136 -0
- package/src/spiracha.ts +72 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This repo exports local Codex chats and Claude Code transcripts to Markdown or plain text.
|
|
6
|
+
|
|
7
|
+
Main entrypoints:
|
|
8
|
+
- `bun start ...` for Codex chat export
|
|
9
|
+
- `bun start` for interactive export mode
|
|
10
|
+
- `bun run export:claude -- ...` for Claude transcript export
|
|
11
|
+
- `bun run mcp` for the MCP server used by the local Codex plugin
|
|
12
|
+
- published package goal:
|
|
13
|
+
- `bunx codex-chats`
|
|
14
|
+
- `bunx codex-chats ...`
|
|
15
|
+
- `bunx codex-chats-claude ...`
|
|
16
|
+
|
|
17
|
+
## Conventions and Rules
|
|
18
|
+
|
|
19
|
+
- Always use `bun` and `bunx`, HARD BAN on: `npm` unless absolutely necessary.
|
|
20
|
+
- Prefer `Bun.file()` instead of `fs` whenever possible.
|
|
21
|
+
- Kill any browser instance you start or stray `bun`, `node` processes after you are done. Never leave it running.
|
|
22
|
+
- Prefer arrow functions to classical functions, and `type` over `interface` in TS.
|
|
23
|
+
- Fixes are made using TDD approach.
|
|
24
|
+
- `bun format` and `bun typecheck` should always be ran at the end of your completion, if you introduced any warnings or errors clean them up yourself before you complete.
|
|
25
|
+
- NEVER disable a biome rule or TS rule without explicit permission from the user.
|
|
26
|
+
- If you believe the code you are changing requires some clarification for future AI agents to understand why the change was made, add a brief comment.
|
|
27
|
+
- Do NOT use decorative section headers made of repeated characters (e.g. `// -----`, `// =====`, etc.).
|
|
28
|
+
- Use `it('should...')` style tests.
|
|
29
|
+
- Unit-tests always live in the same folder as their implementation, never in the `test` folder.
|
|
30
|
+
|
|
31
|
+
## Working Rules
|
|
32
|
+
|
|
33
|
+
- Use `rtk` as the default wrapper for shell commands that produce meaningful stdout or stderr.
|
|
34
|
+
- Prefer `bun test` for verification. Run it after non-trivial changes.
|
|
35
|
+
- When changing package/distribution behavior, also run a local packed-tarball smoke test.
|
|
36
|
+
- When changing the interactive flow, validate it in a real TTY session; piped stdin is not a reliable substitute.
|
|
37
|
+
- Use `apply_patch` for manual edits.
|
|
38
|
+
- Keep output compatibility stable. The exported transcript format is user-facing.
|
|
39
|
+
- Preserve the behavior of existing flags unless the task explicitly changes CLI semantics.
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
Codex exporter modules:
|
|
44
|
+
- `src/lib/codex-exporter.ts`
|
|
45
|
+
- top-level orchestration and public barrel exports
|
|
46
|
+
- `src/lib/codex-exporter-cli.ts`
|
|
47
|
+
- CLI parsing, help text, deeplink parsing, default output-dir resolution
|
|
48
|
+
- `src/lib/codex-exporter-db.ts`
|
|
49
|
+
- SQLite queries, fallback session discovery, filter matching, export target construction
|
|
50
|
+
- `src/lib/codex-exporter-transcript.ts`
|
|
51
|
+
- JSONL transcript parsing, metadata extraction, message/tool rendering
|
|
52
|
+
- `src/lib/codex-exporter-types.ts`
|
|
53
|
+
- shared Codex exporter types and default path constants
|
|
54
|
+
|
|
55
|
+
Other important files:
|
|
56
|
+
- `src/lib/claude-exporter.ts`
|
|
57
|
+
- Claude transcript export pipeline
|
|
58
|
+
- `src/mcp-server.ts`
|
|
59
|
+
- MCP server exposing `export_codex_chats` and `export_claude_transcript`
|
|
60
|
+
- `plugins/codex-chats-export/`
|
|
61
|
+
- local Codex plugin manifest, skill, and MCP wiring
|
|
62
|
+
|
|
63
|
+
## Test Strategy
|
|
64
|
+
|
|
65
|
+
Current tests cover:
|
|
66
|
+
- exporter end-to-end behavior for Codex and Claude
|
|
67
|
+
- Codex CLI parsing helpers
|
|
68
|
+
- interactive-mode inference helpers
|
|
69
|
+
- transcript formatting helpers
|
|
70
|
+
- MCP stdio protocol round-trips using the real server process
|
|
71
|
+
- type-checking via `bun run typecheck`
|
|
72
|
+
|
|
73
|
+
When changing risky areas:
|
|
74
|
+
- CLI changes: update/add tests in `src/lib/codex-exporter-cli.test.ts`
|
|
75
|
+
- transcript parsing/rendering: update/add tests in `src/lib/codex-exporter-transcript.test.ts`
|
|
76
|
+
- DB/filter/target logic: prefer focused unit tests against `src/lib/codex-exporter-db.ts`
|
|
77
|
+
- MCP contract changes: update `src/mcp-server.test.ts`
|
|
78
|
+
|
|
79
|
+
## Common Commands
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
rtk bun test
|
|
83
|
+
rtk bun run typecheck
|
|
84
|
+
rtk bun run build
|
|
85
|
+
rtk bun run test:perf
|
|
86
|
+
rtk bun start
|
|
87
|
+
rtk bun start -- --help
|
|
88
|
+
rtk bun start --interactive
|
|
89
|
+
rtk bun run export:claude -- --help
|
|
90
|
+
rtk bun run mcp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Packed tarball smoke test:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
rtk bun pm pack
|
|
97
|
+
package_tgz="$PWD/codex-chats-<version>.tgz"
|
|
98
|
+
tmp_dir=$(mktemp -d)
|
|
99
|
+
cd "$tmp_dir"
|
|
100
|
+
printf '{"name":"codex-chats-smoke","private":true}\n' > package.json
|
|
101
|
+
rtk bun add "$package_tgz"
|
|
102
|
+
rtk bunx codex-chats --help
|
|
103
|
+
rtk bunx codex-chats-claude --help
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Example Codex export:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
rtk bunx codex-chats
|
|
110
|
+
rtk bunx codex-chats codex://threads/<thread-id> --optimized
|
|
111
|
+
rtk bun start --tools --project summer
|
|
112
|
+
rtk bun start codex://threads/<thread-id> --output-format txt
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Example Claude export:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
rtk bunx codex-chats-claude /path/to/transcript --output-format txt
|
|
119
|
+
rtk bun run export:claude -- /path/to/export-dir --output-format txt
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Notes
|
|
123
|
+
|
|
124
|
+
- `--project` matches the final `cwd` path segment for both POSIX and Windows-style paths, not the full path.
|
|
125
|
+
- Running `codex-chats` or `bun start` with no args enters interactive mode.
|
|
126
|
+
- Codex MCP exports must be scoped by at least one of `deeplinks`, `project`, or `cwd`.
|
|
127
|
+
- `txt` output is intentionally real plain text, not Markdown with a `.txt` extension.
|
|
128
|
+
- The published package is Bun-first. `bin` entrypoints target Bun shebang execution.
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Ragaeeb Haq
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# spiracha
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/spiracha)
|
|
4
|
+
[](https://www.npmjs.com/package/spiracha)
|
|
5
|
+
[](LICENSE.md)
|
|
6
|
+
[](https://bun.sh)
|
|
7
|
+
|
|
8
|
+
Export local Codex chats and Claude Code transcripts to Markdown or plain text.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
For repo-local development:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Published package usage, once the package is available on npm:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bunx spiracha
|
|
22
|
+
bunx spiracha claude /path/to/session-export.jsonl --output-format txt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Export Codex session transcripts from local `.codex` history
|
|
28
|
+
- Filter Codex exports by:
|
|
29
|
+
- exact `cwd`
|
|
30
|
+
- project basename via `--project`
|
|
31
|
+
- specific thread deeplinks like `codex://threads/<id>`
|
|
32
|
+
- Include command logs with `--tools`
|
|
33
|
+
- Write Markdown or real plain-text output with `--output-format md|txt`
|
|
34
|
+
- Export Claude Code transcript `.jsonl` files or export directories
|
|
35
|
+
- Run the same export flows through an MCP server and a local Codex plugin
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
bun install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For package use after publish, no local install is required:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bunx spiracha --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Codex exports
|
|
52
|
+
|
|
53
|
+
Package entrypoint:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bunx spiracha [options] [codex://threads/<id> ...]
|
|
57
|
+
bunx spiracha codex [options] [codex://threads/<id> ...]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With no arguments, `spiracha` starts in interactive mode and asks what you want to export.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bunx spiracha
|
|
66
|
+
bunx spiracha --interactive
|
|
67
|
+
bunx spiracha --project summer
|
|
68
|
+
bunx spiracha --tools --project summer
|
|
69
|
+
bunx spiracha codex://threads/019da28f-ee5b-7881-afe0-68b3d3bd2c77
|
|
70
|
+
bunx spiracha codex://threads/019da28f-ee5b-7881-afe0-68b3d3bd2c77 --output-format txt
|
|
71
|
+
bunx spiracha --cwd ~/workspace/reversed/summer --flat
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Important flags:
|
|
75
|
+
- no args: start interactive mode
|
|
76
|
+
- `--interactive`: force the interactive prompt flow
|
|
77
|
+
- `--project <name>`: matches the final `cwd` path segment for both POSIX and Windows-style paths
|
|
78
|
+
- `--cwd <path>`: exact cwd match
|
|
79
|
+
- `--tools`: include `exec_command` call logs and summaries
|
|
80
|
+
- `--optimized`: compact transcript output
|
|
81
|
+
- `--flat`: write files into a single output folder
|
|
82
|
+
- `--output-format md|txt`: output as Markdown or plain text
|
|
83
|
+
|
|
84
|
+
### Claude exports
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bunx spiracha claude <input-path> [options]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bunx spiracha claude /path/to/session.jsonl
|
|
94
|
+
bunx spiracha claude /path/to/export-dir --tools
|
|
95
|
+
bunx spiracha claude /path/to/export-dir --output-format txt
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Repo-local equivalents remain available during development:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
bun start
|
|
102
|
+
bun start --interactive
|
|
103
|
+
bun start ...
|
|
104
|
+
bun run export:claude -- ...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Legacy aliases remain available for compatibility:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bunx codex-chats
|
|
111
|
+
bunx codex-chats-claude
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## MCP server
|
|
115
|
+
|
|
116
|
+
Run the MCP server with:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bun run mcp
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Exposed tools:
|
|
123
|
+
- `export_codex_chats`
|
|
124
|
+
- `export_claude_transcript`
|
|
125
|
+
|
|
126
|
+
The local plugin lives in [plugins/codex-chats-export](~/workspace/codex-chats/plugins/codex-chats-export) and is registered through [plugins/codex-chats-export/.mcp.json](~/workspace/codex-chats/plugins/codex-chats-export/.mcp.json).
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
Useful commands:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
bun test
|
|
134
|
+
bun run typecheck
|
|
135
|
+
bun run build
|
|
136
|
+
bun run test:perf
|
|
137
|
+
bun start
|
|
138
|
+
bun start --interactive
|
|
139
|
+
bun start -- --help
|
|
140
|
+
bun run export:claude -- --help
|
|
141
|
+
bun run mcp
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Packed-tarball smoke test before publishing:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
bun pm pack
|
|
148
|
+
package_tgz="$PWD/codex-chats-<version>.tgz"
|
|
149
|
+
tmp_dir=$(mktemp -d)
|
|
150
|
+
cd "$tmp_dir"
|
|
151
|
+
printf '{"name":"codex-chats-smoke","private":true}\n' > package.json
|
|
152
|
+
bun add "$package_tgz"
|
|
153
|
+
bunx spiracha --help
|
|
154
|
+
bunx spiracha claude --help
|
|
155
|
+
bunx codex-chats --help
|
|
156
|
+
bunx codex-chats-claude --help
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Project Layout
|
|
160
|
+
|
|
161
|
+
- `src/export-chats.ts`: Codex CLI wrapper
|
|
162
|
+
- `src/export-claude.ts`: Claude CLI wrapper
|
|
163
|
+
- `src/mcp-server.ts`: MCP server entrypoint
|
|
164
|
+
- `src/lib/codex-exporter-*.ts`: Codex exporter modules
|
|
165
|
+
- `src/lib/claude-exporter.ts`: Claude exporter implementation
|
|
166
|
+
- `plugins/codex-chats-export/`: local Codex plugin bundle
|
|
167
|
+
|
|
168
|
+
## Testing
|
|
169
|
+
|
|
170
|
+
The test suite includes:
|
|
171
|
+
- Codex exporter end-to-end coverage
|
|
172
|
+
- Claude exporter end-to-end coverage
|
|
173
|
+
- Codex CLI helper tests
|
|
174
|
+
- transcript rendering helper tests
|
|
175
|
+
- MCP stdio protocol round-trip tests
|
|
176
|
+
- local packaging should be smoke-tested with a packed tarball before publishing
|
|
177
|
+
|
|
178
|
+
Run:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
bun test
|
|
182
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": {
|
|
3
|
+
"name": "Ragaeeb Haq",
|
|
4
|
+
"url": "https://github.com/ragaeeb"
|
|
5
|
+
},
|
|
6
|
+
"bin": {
|
|
7
|
+
"codex-chats": "./src/export-chats.ts",
|
|
8
|
+
"codex-chats-claude": "./src/export-claude.ts",
|
|
9
|
+
"spiracha": "./src/spiracha.ts"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ragaeeb/spiracha/issues"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@inquirer/prompts": "^8.4.2",
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
17
|
+
"zod": "^4.4.3"
|
|
18
|
+
},
|
|
19
|
+
"description": "Export local Codex chats and Claude Code transcripts to Markdown or plain text.",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "^1.3.13",
|
|
22
|
+
"@types/node": "^25.6.2",
|
|
23
|
+
"typescript": "^6.0.3"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.3.13"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src/export-chats.ts",
|
|
30
|
+
"src/export-claude.ts",
|
|
31
|
+
"src/mcp-server.ts",
|
|
32
|
+
"src/spiracha.ts",
|
|
33
|
+
"src/lib/claude-exporter.ts",
|
|
34
|
+
"src/lib/codex-exporter-cli.ts",
|
|
35
|
+
"src/lib/codex-exporter-db.ts",
|
|
36
|
+
"src/lib/codex-exporter-transcript.ts",
|
|
37
|
+
"src/lib/codex-exporter-types.ts",
|
|
38
|
+
"src/lib/codex-exporter.ts",
|
|
39
|
+
"src/lib/interactive-cli.ts",
|
|
40
|
+
"src/lib/shared.ts",
|
|
41
|
+
"README.md",
|
|
42
|
+
"AGENTS.md"
|
|
43
|
+
],
|
|
44
|
+
"homepage": "https://github.com/ragaeeb/spiracha#readme",
|
|
45
|
+
"keywords": [
|
|
46
|
+
"codex",
|
|
47
|
+
"claude",
|
|
48
|
+
"transcripts",
|
|
49
|
+
"export",
|
|
50
|
+
"markdown",
|
|
51
|
+
"bun"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"name": "spiracha",
|
|
55
|
+
"packageManager": "bun@1.3.13",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/ragaeeb/spiracha.git"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "bun run typecheck",
|
|
62
|
+
"export:claude": "bun run ./src/export-claude.ts",
|
|
63
|
+
"format": "biome check . --write && biome lint . --write",
|
|
64
|
+
"mcp": "bun run ./src/mcp-server.ts",
|
|
65
|
+
"start": "bun run ./src/export-chats.ts",
|
|
66
|
+
"typecheck": "bunx tsc --noEmit"
|
|
67
|
+
},
|
|
68
|
+
"type": "module",
|
|
69
|
+
"version": "1.0.0"
|
|
70
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { lstat } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
+
import { createInterface } from 'node:readline/promises';
|
|
7
|
+
import { getCodexHelpText, parseCodexCliArgs, runCodexExport } from './lib/codex-exporter';
|
|
8
|
+
import { runInteractiveExport } from './lib/interactive-cli';
|
|
9
|
+
import { CliUsageError } from './lib/shared';
|
|
10
|
+
|
|
11
|
+
export const runExportChatsCli = async (argv = process.argv.slice(2)): Promise<void> => {
|
|
12
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
13
|
+
console.log(getCodexHelpText());
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
if (shouldRunInteractive(argv)) {
|
|
19
|
+
await runInteractiveCliFlow();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await runCodexCliFlow(argv);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof CliUsageError) {
|
|
26
|
+
console.error(error.message);
|
|
27
|
+
console.error('');
|
|
28
|
+
console.error(getCodexHelpText());
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
console.error(error.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const shouldRunInteractive = (argv: string[]): boolean => {
|
|
42
|
+
return argv.length === 0 || argv.includes('--interactive');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const runInteractiveCliFlow = async (): Promise<void> => {
|
|
46
|
+
const result = await runInteractiveExport();
|
|
47
|
+
const targetFolder = await printInteractiveExportResult(result);
|
|
48
|
+
await maybeOpenExportFolder(targetFolder);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const runCodexCliFlow = async (argv: string[]): Promise<void> => {
|
|
52
|
+
const options = parseCodexCliArgs(argv);
|
|
53
|
+
const result = await runCodexExport(options);
|
|
54
|
+
printCodexExportResult(result);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const printInteractiveExportResult = async (
|
|
58
|
+
result: Awaited<ReturnType<typeof runInteractiveExport>>,
|
|
59
|
+
): Promise<string> => {
|
|
60
|
+
if (result.mode === 'claude') {
|
|
61
|
+
console.log(`Exported ${result.sourcePath} -> ${result.outputPath}`);
|
|
62
|
+
return resolveExportFolder(result.outputPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
printCodexExportFiles(result.files);
|
|
66
|
+
printMissingThreadWarnings(result.missingThreadIds);
|
|
67
|
+
console.log(`Done. Exported ${result.exportedCount} chat(s) to ${result.outputDir}`);
|
|
68
|
+
return result.outputDir;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const printCodexExportResult = (result: Awaited<ReturnType<typeof runCodexExport>>): void => {
|
|
72
|
+
printCodexExportFiles(result.files);
|
|
73
|
+
printMissingThreadWarnings(result.missingThreadIds);
|
|
74
|
+
console.log(`Done. Exported ${result.exportedCount} chat(s) to ${result.outputDir}`);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const printCodexExportFiles = (files: Awaited<ReturnType<typeof runCodexExport>>['files']): void => {
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
console.log(`Exported ${file.sourcePath} -> ${file.outputPath}`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const printMissingThreadWarnings = (missingThreadIds: string[]): void => {
|
|
84
|
+
if (missingThreadIds.length > 0) {
|
|
85
|
+
console.warn(
|
|
86
|
+
`Warning: ${missingThreadIds.length} requested thread(s) were not found: ${missingThreadIds.join(', ')}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const maybeOpenExportFolder = async (targetFolder: string): Promise<void> => {
|
|
92
|
+
const rl = createInterface({ input, output });
|
|
93
|
+
try {
|
|
94
|
+
while (true) {
|
|
95
|
+
const answer = (await rl.question('Open the exported folder now? [y/N]: ')).trim().toLowerCase();
|
|
96
|
+
if (!answer || answer === 'n' || answer === 'no') {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (answer === 'y' || answer === 'yes') {
|
|
100
|
+
await openPathNatively(targetFolder);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log('Please answer y or n.');
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
rl.close();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const resolveExportFolder = async (targetPath: string): Promise<string> => {
|
|
111
|
+
const stats = await lstat(targetPath).catch(() => null);
|
|
112
|
+
if (stats?.isDirectory()) {
|
|
113
|
+
return targetPath;
|
|
114
|
+
}
|
|
115
|
+
return path.dirname(targetPath);
|
|
116
|
+
};
|
|
117
|
+
|
|
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
|
+
if (import.meta.main) {
|
|
133
|
+
await runExportChatsCli();
|
|
134
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { getClaudeHelpText, parseClaudeCliArgs, runClaudeExport } from './lib/claude-exporter';
|
|
4
|
+
import { CliUsageError } from './lib/shared';
|
|
5
|
+
|
|
6
|
+
export const runExportClaudeCli = async (argv = process.argv.slice(2)): Promise<void> => {
|
|
7
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
8
|
+
console.log(getClaudeHelpText());
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const options = parseClaudeCliArgs(argv);
|
|
14
|
+
const result = await runClaudeExport(options);
|
|
15
|
+
|
|
16
|
+
console.log(`Exported ${result.sourcePath} -> ${result.outputPath}`);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof CliUsageError) {
|
|
19
|
+
console.error(error.message);
|
|
20
|
+
console.error('');
|
|
21
|
+
console.error(getClaudeHelpText());
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
console.error(error.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (import.meta.main) {
|
|
35
|
+
await runExportClaudeCli();
|
|
36
|
+
}
|