claudex-export 0.1.0__tar.gz
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.
- claudex_export-0.1.0/LICENSE +21 -0
- claudex_export-0.1.0/MANIFEST.in +9 -0
- claudex_export-0.1.0/PKG-INFO +223 -0
- claudex_export-0.1.0/README.md +191 -0
- claudex_export-0.1.0/examples/claude-demo.jsonl +5 -0
- claudex_export-0.1.0/examples/demo.jsonl +6 -0
- claudex_export-0.1.0/pyproject.toml +56 -0
- claudex_export-0.1.0/scripts/check_release.py +20 -0
- claudex_export-0.1.0/scripts/smoke_install.py +30 -0
- claudex_export-0.1.0/setup.cfg +4 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/PKG-INFO +223 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/SOURCES.txt +27 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/dependency_links.txt +1 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/entry_points.txt +5 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/requires.txt +6 -0
- claudex_export-0.1.0/src/claudex_export.egg-info/top_level.txt +1 -0
- claudex_export-0.1.0/src/codex_export/__init__.py +3 -0
- claudex_export-0.1.0/src/codex_export/__main__.py +3 -0
- claudex_export-0.1.0/src/codex_export/claude.py +125 -0
- claudex_export-0.1.0/src/codex_export/cli.py +118 -0
- claudex_export-0.1.0/src/codex_export/controls.js +15 -0
- claudex_export-0.1.0/src/codex_export/discovery.py +179 -0
- claudex_export-0.1.0/src/codex_export/render.py +231 -0
- claudex_export-0.1.0/src/codex_export/session.py +326 -0
- claudex_export-0.1.0/src/codex_export/style.css +38 -0
- claudex_export-0.1.0/src/codex_export/tui.py +169 -0
- claudex_export-0.1.0/tests/test_claude_tui.py +163 -0
- claudex_export-0.1.0/tests/test_export.py +325 -0
- claudex_export-0.1.0/tests/test_release.py +20 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 matplo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include pyproject.toml
|
|
3
|
+
include LICENSE
|
|
4
|
+
recursive-include src/codex_export *.py *.css *.js
|
|
5
|
+
recursive-include tests *.py
|
|
6
|
+
recursive-include examples *.jsonl
|
|
7
|
+
recursive-include scripts *.py
|
|
8
|
+
prune exports
|
|
9
|
+
global-exclude __pycache__ *.py[cod] .DS_Store
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claudex-export
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Export Codex and Claude Code sessions with a Textual browser and prompt previews
|
|
5
|
+
Author: matplo
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/matplo/claudex-export
|
|
8
|
+
Project-URL: Repository, https://github.com/matplo/claudex-export
|
|
9
|
+
Project-URL: Issues, https://github.com/matplo/claudex-export/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/matplo/claudex-export/releases
|
|
11
|
+
Keywords: codex,claude,sessions,markdown,html,textual
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: markdown-it-py<5,>=3
|
|
27
|
+
Requires-Dist: Pygments<3,>=2.17
|
|
28
|
+
Requires-Dist: textual<7,>=6.12
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest<10,>=8; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Claudex Export
|
|
34
|
+
|
|
35
|
+
Export local Codex and Claude Code conversations to readable **standalone HTML**
|
|
36
|
+
or **Markdown**. Run `claudex-export`
|
|
37
|
+
without a filename to browse both providers in a full-screen Textual picker.
|
|
38
|
+
Preview the first and last 10 user prompts before choosing a session.
|
|
39
|
+
|
|
40
|
+
Requires Python 3.10 or later. No API key or service is needed.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m pip install claudex-export
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or install it as an isolated command-line application with
|
|
49
|
+
`pipx install claudex-export`. The commands `claudex-export`, `claude-export`,
|
|
50
|
+
`codex-export`, and the compatibility alias `session-export` launch the same
|
|
51
|
+
tool and support both providers. Python and its dependencies are sufficient; `henv` is
|
|
52
|
+
only used for development in this repository.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Browse and search local sessions; HTML is the default
|
|
58
|
+
claudex-export
|
|
59
|
+
|
|
60
|
+
# Browse only Claude Code sessions
|
|
61
|
+
claudex-export --source claude
|
|
62
|
+
|
|
63
|
+
# Auto-detect a Claude Code file and export both formats
|
|
64
|
+
claudex-export /path/to/claude-session.jsonl --format both -o exports
|
|
65
|
+
|
|
66
|
+
# Export a specified rollout
|
|
67
|
+
claudex-export /path/to/rollout.jsonl -o conversation.html
|
|
68
|
+
|
|
69
|
+
# Markdown, without tool calls/results
|
|
70
|
+
claudex-export /path/to/rollout.jsonl --format md --no-tools -o conversation.md
|
|
71
|
+
|
|
72
|
+
# Both formats, into a directory
|
|
73
|
+
claudex-export /path/to/rollout.jsonl --format both -o exports
|
|
74
|
+
|
|
75
|
+
# Include archived sessions, or use another Codex home
|
|
76
|
+
claudex-export --include-archived
|
|
77
|
+
claudex-export --codex-home /path/to/.codex
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The Textual picker supports arrow keys, Page Up/Down, and mouse navigation.
|
|
81
|
+
Press `/` to focus live search; search matches titles, directories, IDs, and
|
|
82
|
+
provider names. Enter in the search box returns focus to the results.
|
|
83
|
+
|
|
84
|
+
| Key | Action |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `↑` / `↓` | Move between sessions |
|
|
87
|
+
| `Enter` on a session | Select and export |
|
|
88
|
+
| `v` | Preview the selected session's user prompts |
|
|
89
|
+
| `Esc` / `v` in preview | Return to the list |
|
|
90
|
+
| `e` in preview | Select and export the previewed session |
|
|
91
|
+
| `/` | Focus search |
|
|
92
|
+
| `Esc` in list | Clear search and focus the list |
|
|
93
|
+
| `q` / `Ctrl+C` | Cancel |
|
|
94
|
+
|
|
95
|
+
Previews are scrollable and loaded in a background worker. They show complete
|
|
96
|
+
prompt text with original numbering and timestamps: the first 10 and last 10,
|
|
97
|
+
without duplicates where those ranges overlap. For longer sessions a marker
|
|
98
|
+
shows how many middle prompts were omitted. Tool results, injected context, and
|
|
99
|
+
compaction summaries are excluded from the prompt preview. Images are counted,
|
|
100
|
+
not opened. Reading a preview does not write an export.
|
|
101
|
+
|
|
102
|
+
Use `--plain-picker` for the original numbered terminal picker (`/search`,
|
|
103
|
+
`n`/`p`, number to select, `q` to cancel); prompt previews require the Textual
|
|
104
|
+
picker. Without an interactive terminal, supply an explicit input file.
|
|
105
|
+
|
|
106
|
+
By default, exports are written to the current directory with a sanitized title
|
|
107
|
+
and session ID in the filename. `-o` is a file path for a single format and a
|
|
108
|
+
directory for `--format both`. Parent directories are created as needed.
|
|
109
|
+
Existing files require `--force`; the source session is never overwritten.
|
|
110
|
+
|
|
111
|
+
## Supported content
|
|
112
|
+
|
|
113
|
+
- Native Codex rollout and Claude Code `.jsonl` files, containing one JSON
|
|
114
|
+
record per line. File content determines the format automatically; use
|
|
115
|
+
`--source codex` or `--source claude` to specify it explicitly.
|
|
116
|
+
- `.json` containing a single native session record or an array of records.
|
|
117
|
+
Codex records use `type`/`payload`; Claude Code uses `type`/`message` and
|
|
118
|
+
session metadata. Arbitrary chat JSON formats are not supported.
|
|
119
|
+
- User messages, assistant progress and final replies, tool inputs/results,
|
|
120
|
+
and markers for compaction/interruption, in recorded order.
|
|
121
|
+
- Native transcript records take priority over matching completion events.
|
|
122
|
+
Duplicates are matched by IDs and by content within each turn, one occurrence
|
|
123
|
+
at a time. Separate repeated messages remain present.
|
|
124
|
+
- System/developer messages, reasoning, usage records, and replacement history
|
|
125
|
+
from compaction are omitted. Instructions stored as actual **user** messages
|
|
126
|
+
remain part of the transcript.
|
|
127
|
+
- Tools invoked inside an orchestration tool may appear as separate execution
|
|
128
|
+
events as well as in that tool's output: these describe nested operations.
|
|
129
|
+
- Claude Code tool-use blocks pair with tool results by tool-use ID. Replayed
|
|
130
|
+
UUIDs are deduplicated while separate blocks sharing a message ID survive.
|
|
131
|
+
Thinking blocks, metadata, and compacted summaries are omitted. End-of-turn
|
|
132
|
+
replies use the light blue final-answer styling.
|
|
133
|
+
|
|
134
|
+
HTML includes responsive styling, highlighted code, tables, links, message
|
|
135
|
+
anchors, collapsed tool details, expand/collapse buttons, and a print button.
|
|
136
|
+
It uses embedded CSS/JavaScript and works offline. Printing expands tool details.
|
|
137
|
+
Markdown keeps conversation formatting and uses fenced blocks for tool data.
|
|
138
|
+
|
|
139
|
+
Embedded PNG, JPEG, GIF, and WebP data images are preserved. Other image
|
|
140
|
+
references become placeholders; the exporter does not fetch remote images or
|
|
141
|
+
read image paths from the transcript. Raw HTML is escaped in conversation prose;
|
|
142
|
+
HTML exports restrict scripts and resource loads with a Content Security Policy.
|
|
143
|
+
|
|
144
|
+
Codex discovery respects `--codex-home`, then `CODEX_HOME`, then `~/.codex`.
|
|
145
|
+
It scans `sessions/` (and optionally `archived_sessions/`) and uses
|
|
146
|
+
`session_index.jsonl` titles when available. Sessions are sorted by recent
|
|
147
|
+
activity. Each export covers one file; child/subagent sessions are not recursively
|
|
148
|
+
included. Claude Code discovery respects `--claude-home`, then
|
|
149
|
+
`CLAUDE_CONFIG_DIR`, then `~/.claude`, and reads main session files under
|
|
150
|
+
`projects/`. Custom or automatic session titles take priority over the first
|
|
151
|
+
prompt. Subagent logs are excluded from discovery; pass a subagent file explicitly
|
|
152
|
+
to export it. `--source all` (the default) combines both providers; archived
|
|
153
|
+
session inclusion applies to Codex. Source files and databases are never modified.
|
|
154
|
+
|
|
155
|
+
Unknown record types and malformed JSONL lines produce warnings. An incomplete
|
|
156
|
+
final line in a running session is skipped; rerun the export later to include it.
|
|
157
|
+
Local session formats can evolve, so inspect export notes when a newer record type
|
|
158
|
+
appears. Exports contain the selected conversation and tool data as stored;
|
|
159
|
+
there is no automatic secret redaction.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
henv -n export_codex_session_dev -x python -m pip install -e '.[dev]'
|
|
165
|
+
henv -n export_codex_session_dev -x python -m pytest
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Tests use synthetic sessions, including headless Textual keyboard/preview tests.
|
|
169
|
+
Keep private session files and generated exports
|
|
170
|
+
out of source control; `exports/` is ignored for local previews.
|
|
171
|
+
|
|
172
|
+
## Releases to PyPI
|
|
173
|
+
|
|
174
|
+
Pushing a version tag triggers `.github/workflows/release.yml`. It runs tests
|
|
175
|
+
on Python 3.10–3.14, builds an sdist and wheel, checks package metadata, and
|
|
176
|
+
smoke-tests an installation of the built wheel. The tag must exactly match
|
|
177
|
+
`v` followed by the package version. Only then does it publish to PyPI and
|
|
178
|
+
create a GitHub release with both distribution files attached.
|
|
179
|
+
|
|
180
|
+
### One-time PyPI setup
|
|
181
|
+
|
|
182
|
+
In your PyPI account, open [Publishing](https://pypi.org/manage/account/publishing/)
|
|
183
|
+
and add a **pending GitHub publisher** with these exact values:
|
|
184
|
+
|
|
185
|
+
| Field | Value |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| PyPI project name | `claudex-export` |
|
|
188
|
+
| GitHub owner | `matplo` |
|
|
189
|
+
| Repository | `claudex-export` |
|
|
190
|
+
| Workflow filename | `release.yml` |
|
|
191
|
+
| Environment name | `pypi` |
|
|
192
|
+
|
|
193
|
+
If the PyPI project already exists in your account, add the same publisher
|
|
194
|
+
under that project's Publishing settings. The GitHub workflow uses OIDC
|
|
195
|
+
Trusted Publishing; no `PYPI_API_TOKEN` secret is needed. The GitHub environment
|
|
196
|
+
`pypi` must not require reviewer approval if you want fully automatic releases.
|
|
197
|
+
The PyPI project is created by the first successful publish, not by registering
|
|
198
|
+
the pending publisher.
|
|
199
|
+
|
|
200
|
+
### Publish a version
|
|
201
|
+
|
|
202
|
+
Set `__version__` in `src/codex_export/__init__.py` (the single version source),
|
|
203
|
+
commit the change, and push a matching tag. For the initial `0.1.0` release:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
henv -n export_codex_session_dev -x git push origin main
|
|
207
|
+
henv -n export_codex_session_dev -x git tag -a v0.1.0 -m 'Release 0.1.0'
|
|
208
|
+
henv -n export_codex_session_dev -x git push origin v0.1.0
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For later releases, change the version and tag together. Prereleases such as
|
|
212
|
+
`0.2.0rc1` use `v0.2.0rc1`. PyPI versions are immutable: publish a new version
|
|
213
|
+
for changed artifacts. If publishing fails before upload, fix the configuration
|
|
214
|
+
and rerun the failed workflow jobs. If only GitHub release creation fails after
|
|
215
|
+
PyPI succeeds, rerun that failed job rather than publishing again.
|
|
216
|
+
|
|
217
|
+
See [PyPI's Trusted Publishing documentation](https://docs.pypi.org/trusted-publishers/)
|
|
218
|
+
for account setup details. Ordinary pushes to `main` and pull requests run the
|
|
219
|
+
same tests and packaging checks without publishing.
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT; see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Claudex Export
|
|
2
|
+
|
|
3
|
+
Export local Codex and Claude Code conversations to readable **standalone HTML**
|
|
4
|
+
or **Markdown**. Run `claudex-export`
|
|
5
|
+
without a filename to browse both providers in a full-screen Textual picker.
|
|
6
|
+
Preview the first and last 10 user prompts before choosing a session.
|
|
7
|
+
|
|
8
|
+
Requires Python 3.10 or later. No API key or service is needed.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python -m pip install claudex-export
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or install it as an isolated command-line application with
|
|
17
|
+
`pipx install claudex-export`. The commands `claudex-export`, `claude-export`,
|
|
18
|
+
`codex-export`, and the compatibility alias `session-export` launch the same
|
|
19
|
+
tool and support both providers. Python and its dependencies are sufficient; `henv` is
|
|
20
|
+
only used for development in this repository.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Browse and search local sessions; HTML is the default
|
|
26
|
+
claudex-export
|
|
27
|
+
|
|
28
|
+
# Browse only Claude Code sessions
|
|
29
|
+
claudex-export --source claude
|
|
30
|
+
|
|
31
|
+
# Auto-detect a Claude Code file and export both formats
|
|
32
|
+
claudex-export /path/to/claude-session.jsonl --format both -o exports
|
|
33
|
+
|
|
34
|
+
# Export a specified rollout
|
|
35
|
+
claudex-export /path/to/rollout.jsonl -o conversation.html
|
|
36
|
+
|
|
37
|
+
# Markdown, without tool calls/results
|
|
38
|
+
claudex-export /path/to/rollout.jsonl --format md --no-tools -o conversation.md
|
|
39
|
+
|
|
40
|
+
# Both formats, into a directory
|
|
41
|
+
claudex-export /path/to/rollout.jsonl --format both -o exports
|
|
42
|
+
|
|
43
|
+
# Include archived sessions, or use another Codex home
|
|
44
|
+
claudex-export --include-archived
|
|
45
|
+
claudex-export --codex-home /path/to/.codex
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The Textual picker supports arrow keys, Page Up/Down, and mouse navigation.
|
|
49
|
+
Press `/` to focus live search; search matches titles, directories, IDs, and
|
|
50
|
+
provider names. Enter in the search box returns focus to the results.
|
|
51
|
+
|
|
52
|
+
| Key | Action |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `↑` / `↓` | Move between sessions |
|
|
55
|
+
| `Enter` on a session | Select and export |
|
|
56
|
+
| `v` | Preview the selected session's user prompts |
|
|
57
|
+
| `Esc` / `v` in preview | Return to the list |
|
|
58
|
+
| `e` in preview | Select and export the previewed session |
|
|
59
|
+
| `/` | Focus search |
|
|
60
|
+
| `Esc` in list | Clear search and focus the list |
|
|
61
|
+
| `q` / `Ctrl+C` | Cancel |
|
|
62
|
+
|
|
63
|
+
Previews are scrollable and loaded in a background worker. They show complete
|
|
64
|
+
prompt text with original numbering and timestamps: the first 10 and last 10,
|
|
65
|
+
without duplicates where those ranges overlap. For longer sessions a marker
|
|
66
|
+
shows how many middle prompts were omitted. Tool results, injected context, and
|
|
67
|
+
compaction summaries are excluded from the prompt preview. Images are counted,
|
|
68
|
+
not opened. Reading a preview does not write an export.
|
|
69
|
+
|
|
70
|
+
Use `--plain-picker` for the original numbered terminal picker (`/search`,
|
|
71
|
+
`n`/`p`, number to select, `q` to cancel); prompt previews require the Textual
|
|
72
|
+
picker. Without an interactive terminal, supply an explicit input file.
|
|
73
|
+
|
|
74
|
+
By default, exports are written to the current directory with a sanitized title
|
|
75
|
+
and session ID in the filename. `-o` is a file path for a single format and a
|
|
76
|
+
directory for `--format both`. Parent directories are created as needed.
|
|
77
|
+
Existing files require `--force`; the source session is never overwritten.
|
|
78
|
+
|
|
79
|
+
## Supported content
|
|
80
|
+
|
|
81
|
+
- Native Codex rollout and Claude Code `.jsonl` files, containing one JSON
|
|
82
|
+
record per line. File content determines the format automatically; use
|
|
83
|
+
`--source codex` or `--source claude` to specify it explicitly.
|
|
84
|
+
- `.json` containing a single native session record or an array of records.
|
|
85
|
+
Codex records use `type`/`payload`; Claude Code uses `type`/`message` and
|
|
86
|
+
session metadata. Arbitrary chat JSON formats are not supported.
|
|
87
|
+
- User messages, assistant progress and final replies, tool inputs/results,
|
|
88
|
+
and markers for compaction/interruption, in recorded order.
|
|
89
|
+
- Native transcript records take priority over matching completion events.
|
|
90
|
+
Duplicates are matched by IDs and by content within each turn, one occurrence
|
|
91
|
+
at a time. Separate repeated messages remain present.
|
|
92
|
+
- System/developer messages, reasoning, usage records, and replacement history
|
|
93
|
+
from compaction are omitted. Instructions stored as actual **user** messages
|
|
94
|
+
remain part of the transcript.
|
|
95
|
+
- Tools invoked inside an orchestration tool may appear as separate execution
|
|
96
|
+
events as well as in that tool's output: these describe nested operations.
|
|
97
|
+
- Claude Code tool-use blocks pair with tool results by tool-use ID. Replayed
|
|
98
|
+
UUIDs are deduplicated while separate blocks sharing a message ID survive.
|
|
99
|
+
Thinking blocks, metadata, and compacted summaries are omitted. End-of-turn
|
|
100
|
+
replies use the light blue final-answer styling.
|
|
101
|
+
|
|
102
|
+
HTML includes responsive styling, highlighted code, tables, links, message
|
|
103
|
+
anchors, collapsed tool details, expand/collapse buttons, and a print button.
|
|
104
|
+
It uses embedded CSS/JavaScript and works offline. Printing expands tool details.
|
|
105
|
+
Markdown keeps conversation formatting and uses fenced blocks for tool data.
|
|
106
|
+
|
|
107
|
+
Embedded PNG, JPEG, GIF, and WebP data images are preserved. Other image
|
|
108
|
+
references become placeholders; the exporter does not fetch remote images or
|
|
109
|
+
read image paths from the transcript. Raw HTML is escaped in conversation prose;
|
|
110
|
+
HTML exports restrict scripts and resource loads with a Content Security Policy.
|
|
111
|
+
|
|
112
|
+
Codex discovery respects `--codex-home`, then `CODEX_HOME`, then `~/.codex`.
|
|
113
|
+
It scans `sessions/` (and optionally `archived_sessions/`) and uses
|
|
114
|
+
`session_index.jsonl` titles when available. Sessions are sorted by recent
|
|
115
|
+
activity. Each export covers one file; child/subagent sessions are not recursively
|
|
116
|
+
included. Claude Code discovery respects `--claude-home`, then
|
|
117
|
+
`CLAUDE_CONFIG_DIR`, then `~/.claude`, and reads main session files under
|
|
118
|
+
`projects/`. Custom or automatic session titles take priority over the first
|
|
119
|
+
prompt. Subagent logs are excluded from discovery; pass a subagent file explicitly
|
|
120
|
+
to export it. `--source all` (the default) combines both providers; archived
|
|
121
|
+
session inclusion applies to Codex. Source files and databases are never modified.
|
|
122
|
+
|
|
123
|
+
Unknown record types and malformed JSONL lines produce warnings. An incomplete
|
|
124
|
+
final line in a running session is skipped; rerun the export later to include it.
|
|
125
|
+
Local session formats can evolve, so inspect export notes when a newer record type
|
|
126
|
+
appears. Exports contain the selected conversation and tool data as stored;
|
|
127
|
+
there is no automatic secret redaction.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
henv -n export_codex_session_dev -x python -m pip install -e '.[dev]'
|
|
133
|
+
henv -n export_codex_session_dev -x python -m pytest
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Tests use synthetic sessions, including headless Textual keyboard/preview tests.
|
|
137
|
+
Keep private session files and generated exports
|
|
138
|
+
out of source control; `exports/` is ignored for local previews.
|
|
139
|
+
|
|
140
|
+
## Releases to PyPI
|
|
141
|
+
|
|
142
|
+
Pushing a version tag triggers `.github/workflows/release.yml`. It runs tests
|
|
143
|
+
on Python 3.10–3.14, builds an sdist and wheel, checks package metadata, and
|
|
144
|
+
smoke-tests an installation of the built wheel. The tag must exactly match
|
|
145
|
+
`v` followed by the package version. Only then does it publish to PyPI and
|
|
146
|
+
create a GitHub release with both distribution files attached.
|
|
147
|
+
|
|
148
|
+
### One-time PyPI setup
|
|
149
|
+
|
|
150
|
+
In your PyPI account, open [Publishing](https://pypi.org/manage/account/publishing/)
|
|
151
|
+
and add a **pending GitHub publisher** with these exact values:
|
|
152
|
+
|
|
153
|
+
| Field | Value |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| PyPI project name | `claudex-export` |
|
|
156
|
+
| GitHub owner | `matplo` |
|
|
157
|
+
| Repository | `claudex-export` |
|
|
158
|
+
| Workflow filename | `release.yml` |
|
|
159
|
+
| Environment name | `pypi` |
|
|
160
|
+
|
|
161
|
+
If the PyPI project already exists in your account, add the same publisher
|
|
162
|
+
under that project's Publishing settings. The GitHub workflow uses OIDC
|
|
163
|
+
Trusted Publishing; no `PYPI_API_TOKEN` secret is needed. The GitHub environment
|
|
164
|
+
`pypi` must not require reviewer approval if you want fully automatic releases.
|
|
165
|
+
The PyPI project is created by the first successful publish, not by registering
|
|
166
|
+
the pending publisher.
|
|
167
|
+
|
|
168
|
+
### Publish a version
|
|
169
|
+
|
|
170
|
+
Set `__version__` in `src/codex_export/__init__.py` (the single version source),
|
|
171
|
+
commit the change, and push a matching tag. For the initial `0.1.0` release:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
henv -n export_codex_session_dev -x git push origin main
|
|
175
|
+
henv -n export_codex_session_dev -x git tag -a v0.1.0 -m 'Release 0.1.0'
|
|
176
|
+
henv -n export_codex_session_dev -x git push origin v0.1.0
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For later releases, change the version and tag together. Prereleases such as
|
|
180
|
+
`0.2.0rc1` use `v0.2.0rc1`. PyPI versions are immutable: publish a new version
|
|
181
|
+
for changed artifacts. If publishing fails before upload, fix the configuration
|
|
182
|
+
and rerun the failed workflow jobs. If only GitHub release creation fails after
|
|
183
|
+
PyPI succeeds, rerun that failed job rather than publishing again.
|
|
184
|
+
|
|
185
|
+
See [PyPI's Trusted Publishing documentation](https://docs.pypi.org/trusted-publishers/)
|
|
186
|
+
for account setup details. Ordinary pushes to `main` and pull requests run the
|
|
187
|
+
same tests and packaging checks without publishing.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT; see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"type":"ai-title","sessionId":"claude-demo","aiTitle":"A portable session archive"}
|
|
2
|
+
{"type":"user","uuid":"demo-u1","sessionId":"claude-demo","cwd":"/projects/session-exporter","timestamp":"2026-09-07T17:00:00Z","message":{"role":"user","content":"Can I export Claude Code conversations with the same pastel styling?"}}
|
|
3
|
+
{"type":"assistant","uuid":"demo-a1","sessionId":"claude-demo","timestamp":"2026-09-07T17:00:02Z","message":{"role":"assistant","id":"demo-msg1","stop_reason":"tool_use","content":[{"type":"text","text":"I'll check the session format and reuse the existing renderers."},{"type":"tool_use","id":"demo-tool","name":"Bash","input":{"command":"python -m pytest -q"}}]}}
|
|
4
|
+
{"type":"user","uuid":"demo-result","sessionId":"claude-demo","timestamp":"2026-09-07T17:00:03Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"demo-tool","content":"Tests passed."}]}}
|
|
5
|
+
{"type":"assistant","uuid":"demo-a2","sessionId":"claude-demo","timestamp":"2026-09-07T17:00:06Z","message":{"role":"assistant","id":"demo-msg2","stop_reason":"end_turn","content":[{"type":"text","text":"Claude Code sessions now export to **HTML** and **Markdown**.\n\n```bash\nsession-export --source claude\n```\n\nIn the picker, press **v** to read the first and last 10 user prompts before selecting a session."}]}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"timestamp":"2026-09-07T16:42:00Z","type":"session_meta","payload":{"id":"demo-session","title":"A small tool for keeping good conversations","timestamp":"2026-09-07T16:42:00Z","cwd":"/projects/session-exporter"}}
|
|
2
|
+
{"timestamp":"2026-09-07T16:42:00Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"Can we turn our session logs into something pleasant to read and easy to share? I'd like **HTML** for browsing and **Markdown** for my notes."}]}}
|
|
3
|
+
{"timestamp":"2026-09-07T16:42:08Z","type":"response_item","payload":{"type":"message","role":"assistant","phase":"commentary","content":[{"type":"output_text","text":"I'll build a local exporter with a session picker. The HTML will work offline, and tool details will stay tucked away until you need them."}]}}
|
|
4
|
+
{"timestamp":"2026-09-07T16:42:12Z","type":"response_item","payload":{"type":"function_call","name":"exec_command","call_id":"demo-call","arguments":"{\"cmd\": \"python -m pytest -q\", \"cwd\": \"/projects/session-exporter\"}"}}
|
|
5
|
+
{"timestamp":"2026-09-07T16:42:13Z","type":"response_item","payload":{"type":"function_call_output","call_id":"demo-call","output":"............. [100%]\n13 passed in 0.10s"}}
|
|
6
|
+
{"timestamp":"2026-09-07T16:43:00Z","type":"response_item","payload":{"type":"message","role":"assistant","phase":"final_answer","content":[{"type":"output_text","text":"The exporter is ready. Start with a file, or leave the path out to choose from your local sessions.\n\n```bash\ncodex-export session.jsonl -o conversation.html\ncodex-export session.jsonl --format md -o notes.md\n```\n\n| Format | Good for | Tool details |\n| --- | --- | --- |\n| HTML | Reading, browsing, and printing | Collapsed by default |\n| Markdown | Notes, archives, and version control | Fenced text blocks |\n\n### Designed to travel\n\n- All styling is embedded in the HTML file.\n- Code, tables, and links keep their formatting.\n- Your original session stays untouched.\n\n> A useful export keeps the conversation readable and the technical details close at hand."}]}}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "claudex-export"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Export Codex and Claude Code sessions with a Textual browser and prompt previews"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{name = "matplo"}]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = ["markdown-it-py>=3,<5", "Pygments>=2.17,<3", "textual>=6.12,<7"]
|
|
15
|
+
keywords = ["codex", "claude", "sessions", "markdown", "html", "textual"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/matplo/claudex-export"
|
|
32
|
+
Repository = "https://github.com/matplo/claudex-export"
|
|
33
|
+
Issues = "https://github.com/matplo/claudex-export/issues"
|
|
34
|
+
Changelog = "https://github.com/matplo/claudex-export/releases"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = ["pytest>=8,<10"]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
claudex-export = "codex_export.cli:main"
|
|
41
|
+
claude-export = "codex_export.cli:main"
|
|
42
|
+
codex-export = "codex_export.cli:main"
|
|
43
|
+
session-export = "codex_export.cli:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.dynamic]
|
|
49
|
+
version = {attr = "codex_export.__version__"}
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
codex_export = ["*.css", "*.js"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Reject tags that do not match the installed distribution version."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check_tag(tag: str, package_version: str) -> None:
|
|
8
|
+
expected = f"v{package_version}"
|
|
9
|
+
if tag != expected:
|
|
10
|
+
raise ValueError(f"Tag {tag!r} does not match package version {package_version!r}; expected {expected!r}.")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
if len(sys.argv) != 2:
|
|
15
|
+
raise SystemExit("Usage: python scripts/check_release.py vX.Y.Z")
|
|
16
|
+
try:
|
|
17
|
+
check_tag(sys.argv[1], version("claudex-export"))
|
|
18
|
+
except ValueError as exc:
|
|
19
|
+
raise SystemExit(str(exc)) from exc
|
|
20
|
+
print(f"Verified release tag {sys.argv[1]}")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Run with python -I after installing a wheel, outside source imports."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import distribution
|
|
4
|
+
from importlib.resources import files
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from tempfile import TemporaryDirectory
|
|
7
|
+
|
|
8
|
+
from codex_export import __version__
|
|
9
|
+
from codex_export.cli import main
|
|
10
|
+
from codex_export.render import render_html, render_markdown
|
|
11
|
+
from codex_export.session import Entry, Session
|
|
12
|
+
from codex_export.tui import SessionPicker
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
package = distribution("claudex-export")
|
|
16
|
+
assert package.version == __version__
|
|
17
|
+
assert {"claudex-export", "claude-export", "codex-export", "session-export"} <= {entry.name for entry in package.entry_points}
|
|
18
|
+
assert files("codex_export").joinpath("style.css").is_file()
|
|
19
|
+
assert files("codex_export").joinpath("controls.js").is_file()
|
|
20
|
+
for provider in ("codex", "claude"):
|
|
21
|
+
session = Session(Path("smoke.jsonl"), title="Installed wheel smoke test", provider=provider,
|
|
22
|
+
entries=[Entry("user", "Hello"), Entry("assistant", "Done", phase="final_answer")])
|
|
23
|
+
assert 'class="message assistant final"' in render_html(session)
|
|
24
|
+
assert "Hello" in render_markdown(session)
|
|
25
|
+
assert SessionPicker([]).candidates == []
|
|
26
|
+
with TemporaryDirectory() as directory:
|
|
27
|
+
source = Path(directory) / "session.jsonl"
|
|
28
|
+
source.write_text('{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"Smoke test"}]}}\n', encoding="utf-8")
|
|
29
|
+
assert main([str(source), "--format", "both", "-o", str(Path(directory) / "out")]) == 0
|
|
30
|
+
print(f"Installed wheel {package.version}: entry points, renderers, TUI, and bundled assets OK")
|