claudexer 0.4.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.
@@ -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,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: claudexer
3
+ Version: 0.4.0
4
+ Summary: Browse, resume, and export Codex and Claude Code sessions with a Textual picker
5
+ Author: matplo
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/matplo/claudexer
8
+ Project-URL: Repository, https://github.com/matplo/claudexer
9
+ Project-URL: Issues, https://github.com/matplo/claudexer/issues
10
+ Project-URL: Changelog, https://github.com/matplo/claudexer/releases
11
+ Keywords: codex,claude,sessions,markdown,html,textual,resume
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<9,>=8.2
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest<10,>=8; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # Claudexer
34
+
35
+ Browse local Codex and Claude Code sessions, resume one where you left off, or
36
+ export it to readable **standalone HTML** or **Markdown**. Run `claudexer`
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
+ This project was previously published as `claudex-export`; see its
41
+ [final release notes](https://pypi.org/project/claudex-export/) if you're
42
+ migrating from that name.
43
+
44
+ In the Textual picker, `Enter` resumes the selected session by exec'ing
45
+ `claude --resume ID` or `codex resume ID` (whichever binary the session
46
+ belongs to must be on `PATH`, and it runs from the session's original working
47
+ directory when known). Press `e` to export instead; a modal reports the
48
+ written file(s) or any error without leaving the picker.
49
+
50
+ **Exports are sanitized by default.** They contain the session date, title,
51
+ recorded elapsed time when available, direct user prompts, and assistant
52
+ progress/final replies. Tools, execution plans, injected context, images,
53
+ session IDs, working directories, and individual message timestamps are omitted.
54
+ Use `--full` to restore the previous detailed transcript, or press `f` in the
55
+ Textual picker or preview to toggle between sanitized and full export.
56
+
57
+ Requires Python 3.10 or later. No API key or service is needed.
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ python -m pip install claudexer
63
+ ```
64
+
65
+ Or install it as an isolated command-line application with
66
+ `pipx install claudexer`. Python and its dependencies are sufficient; `henv` is
67
+ only used for development in this repository.
68
+
69
+ ## Usage
70
+
71
+ ```bash
72
+ # Browse and search local sessions; HTML is the default
73
+ claudexer
74
+
75
+ # Browse only Claude Code sessions
76
+ claudexer --source claude
77
+
78
+ # Auto-detect a Claude Code file and export both formats
79
+ claudexer /path/to/claude-session.jsonl --format both -o exports
80
+
81
+ # Opt in to metadata, original messages, tool details, and images
82
+ claudexer /path/to/session.jsonl --full -o detailed.html
83
+
84
+ # Remove additional names or literal strings from sanitized output
85
+ claudexer /path/to/session.jsonl --redact 'Jane Doe' --redact 'Acme Private Project'
86
+
87
+ # Export a specified rollout
88
+ claudexer /path/to/rollout.jsonl -o conversation.html
89
+
90
+ # Markdown, without tool calls/results
91
+ claudexer /path/to/rollout.jsonl --format md --no-tools -o conversation.md
92
+
93
+ # Both formats, into a directory
94
+ claudexer /path/to/rollout.jsonl --format both -o exports
95
+
96
+ # Include archived sessions, or use another Codex home
97
+ claudexer --include-archived
98
+ claudexer --codex-home /path/to/.codex
99
+ ```
100
+
101
+ The Textual picker supports arrow keys, Page Up/Down, and mouse navigation.
102
+ Press `/` to focus live search; search matches titles, directories, IDs, and
103
+ provider names. Enter in the search box returns focus to the results.
104
+
105
+ | Key | Action |
106
+ | --- | --- |
107
+ | `↑` / `↓` | Move between sessions |
108
+ | `Enter` on a session | Resume it: exec into `claude --resume ID` or `codex resume ID` |
109
+ | `v` | Preview the selected session's user prompts |
110
+ | `e` in list or preview | Export the selected/previewed session; a modal reports the result |
111
+ | `f` in list or preview | Toggle sanitized/full export; current mode is displayed |
112
+ | `Esc` / `v` in preview | Return to the list |
113
+ | `/` | Focus search |
114
+ | `Esc` in list | Clear search and focus the list |
115
+ | `q` / `Ctrl+C` | Cancel |
116
+
117
+ Previews are scrollable and loaded in a background worker. They follow the selected
118
+ export mode and show prompt text with original numbering: the first 10 and last 10,
119
+ without duplicates where those ranges overlap. For longer sessions a marker
120
+ shows how many middle prompts were omitted. Tool results, injected context, and
121
+ compaction summaries are excluded from the prompt preview. Images are counted,
122
+ not opened. Reading a preview does not write an export.
123
+
124
+ Use `--plain-picker` for the original numbered terminal picker (`/search`,
125
+ `n`/`p`, number to select, `q` to cancel); prompt previews require the Textual
126
+ picker. Without an interactive terminal, supply an explicit input file.
127
+
128
+ By default, exports are written to the current directory with the date and a
129
+ redacted title in the filename. Full exports use the original title and session
130
+ ID. `-o` is a file path for a single format and a
131
+ directory for `--format both`. Parent directories are created as needed.
132
+ Existing files require `--force`; the source session is never overwritten.
133
+
134
+ ## Sanitization and full exports
135
+
136
+ The same defaults apply to HTML and Markdown, including `--format both`.
137
+ Sanitization removes recognizable email addresses, IP/MAC addresses, common
138
+ phone-number formats, UUIDs, local paths, local hostnames, and known identity
139
+ values from metadata and local account information. It also redacts those values
140
+ in titles, prose, and code blocks. Links lose their destinations and image
141
+ payloads are omitted. Generated filenames exclude the session ID and redact the
142
+ title; an explicitly supplied `-o` name is used as given.
143
+
144
+ This is deterministic, local, **best-effort** redaction. It cannot identify every
145
+ person's name, street address, organization, or other identifying fact in arbitrary
146
+ prose. Review a file before sharing it and supply repeatable `--redact TEXT`
147
+ arguments for additional names or strings. No external service is used.
148
+
149
+ Elapsed time is the span between recorded timestamps and includes idle time;
150
+ it is omitted if the log does not provide distinct usable timestamps.
151
+
152
+ `--full` disables sanitization and restores metadata, original conversation text,
153
+ tools, images, and session event markers. `--no-tools` can still suppress tools
154
+ in full mode. `--redact` applies only to sanitized mode. Full mode does not add
155
+ raw system/developer instructions, reasoning, usage records, or unknown records
156
+ that the parser has always excluded. The local picker still displays directories
157
+ and IDs to help identify a session; they are not included in sanitized exports.
158
+
159
+ ## Supported content
160
+
161
+ - Native Codex rollout and Claude Code `.jsonl` files, containing one JSON
162
+ record per line. File content determines the format automatically; use
163
+ `--source codex` or `--source claude` to specify it explicitly.
164
+ - `.json` containing a single native session record or an array of records.
165
+ Codex records use `type`/`payload`; Claude Code uses `type`/`message` and
166
+ session metadata. Arbitrary chat JSON formats are not supported.
167
+ - User messages and assistant progress/final replies in recorded order. Tool
168
+ inputs/results and compaction/interruption markers are included with `--full`.
169
+ - Native transcript records take priority over matching completion events.
170
+ Duplicates are matched by IDs and by content within each turn, one occurrence
171
+ at a time. Separate repeated messages remain present.
172
+ - System/developer messages, reasoning, usage records, and replacement history
173
+ from compaction are omitted. Instructions stored as actual **user** messages
174
+ remain part of the full transcript. Known injected context is omitted in
175
+ sanitized mode.
176
+ - Tools invoked inside an orchestration tool may appear as separate execution
177
+ events as well as in that tool's output: these describe nested operations.
178
+ - Claude Code tool-use blocks pair with tool results by tool-use ID. Replayed
179
+ UUIDs are deduplicated while separate blocks sharing a message ID survive.
180
+ Thinking blocks, metadata, and compacted summaries are omitted. End-of-turn
181
+ replies use the light blue final-answer styling.
182
+
183
+ HTML includes responsive styling, highlighted code, tables, links, message
184
+ anchors, collapsed tool details, expand/collapse buttons, and a print button.
185
+ It uses embedded CSS/JavaScript and works offline. Printing expands tool details.
186
+ Markdown keeps conversation formatting and uses fenced blocks for tool data.
187
+
188
+ With `--full`, embedded PNG, JPEG, GIF, and WebP data images are preserved. Other image
189
+ references become placeholders; the exporter does not fetch remote images or
190
+ read image paths from the transcript. Raw HTML is escaped in conversation prose;
191
+ HTML exports restrict scripts and resource loads with a Content Security Policy.
192
+
193
+ Codex discovery respects `--codex-home`, then `CODEX_HOME`, then `~/.codex`.
194
+ It scans `sessions/` (and optionally `archived_sessions/`) and uses
195
+ `session_index.jsonl` titles when available. Sessions are sorted by recent
196
+ activity. Each export covers one file; child/subagent sessions are not recursively
197
+ included. Claude Code discovery respects `--claude-home`, then
198
+ `CLAUDE_CONFIG_DIR`, then `~/.claude`, and reads main session files under
199
+ `projects/`. Custom or automatic session titles take priority over the first
200
+ prompt. Subagent logs are excluded from discovery; pass a subagent file explicitly
201
+ to export it. `--source all` (the default) combines both providers; archived
202
+ session inclusion applies to Codex. Source files and databases are never modified.
203
+
204
+ Unknown record types and malformed JSONL lines produce warnings. An incomplete
205
+ final line in a running session is skipped; rerun the export later to include it.
206
+ Local session formats can evolve, so inspect export notes when a newer record type
207
+ appears. Full exports contain the selected conversation and tool data as stored;
208
+ sanitized exports apply the redaction described above.
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ henv -n export_codex_session_dev -x python -m pip install -e '.[dev]'
214
+ henv -n export_codex_session_dev -x python -m pytest
215
+ ```
216
+
217
+ Tests use synthetic sessions, including headless Textual keyboard/preview tests.
218
+ Keep private session files and generated exports
219
+ out of source control; `exports/` is ignored for local previews.
220
+
221
+ ## Releases to PyPI
222
+
223
+ Pushing a version tag triggers `.github/workflows/release.yml`. It runs tests
224
+ on Python 3.10–3.14, builds an sdist and wheel, checks package metadata, and
225
+ smoke-tests an installation of the built wheel. The tag must exactly match
226
+ `v` followed by the package version. Only then does it publish to PyPI and
227
+ create a GitHub release with both distribution files attached.
228
+
229
+ ### One-time PyPI setup
230
+
231
+ In your PyPI account, open [Publishing](https://pypi.org/manage/account/publishing/)
232
+ and add a **pending GitHub publisher** with these exact values:
233
+
234
+ | Field | Value |
235
+ | --- | --- |
236
+ | PyPI project name | `claudexer` |
237
+ | GitHub owner | `matplo` |
238
+ | Repository | `claudexer` |
239
+ | Workflow filename | `release.yml` |
240
+ | Environment name | `pypi` |
241
+
242
+ If the PyPI project already exists in your account, add the same publisher
243
+ under that project's Publishing settings. The GitHub workflow uses OIDC
244
+ Trusted Publishing; no `PYPI_API_TOKEN` secret is needed. The GitHub environment
245
+ `pypi` must not require reviewer approval if you want fully automatic releases.
246
+ The PyPI project is created by the first successful publish, not by registering
247
+ the pending publisher.
248
+
249
+ ### Publish a version
250
+
251
+ Set `__version__` in `src/claudexer/__init__.py` (the single version source),
252
+ commit the change, and push a matching tag. For the initial `0.1.0` release:
253
+
254
+ ```bash
255
+ henv -n export_codex_session_dev -x git push origin main
256
+ henv -n export_codex_session_dev -x git tag -a v0.1.0 -m 'Release 0.1.0'
257
+ henv -n export_codex_session_dev -x git push origin v0.1.0
258
+ ```
259
+
260
+ For later releases, change the version and tag together. Prereleases such as
261
+ `0.2.0rc1` use `v0.2.0rc1`. PyPI versions are immutable: publish a new version
262
+ for changed artifacts. If publishing fails before upload, fix the configuration
263
+ and rerun the failed workflow jobs. If only GitHub release creation fails after
264
+ PyPI succeeds, rerun that failed job rather than publishing again.
265
+
266
+ See [PyPI's Trusted Publishing documentation](https://docs.pypi.org/trusted-publishers/)
267
+ for account setup details. Ordinary pushes to `main` and pull requests run the
268
+ same tests and packaging checks without publishing.
269
+
270
+ ## License
271
+
272
+ MIT; see [LICENSE](LICENSE).
@@ -0,0 +1,240 @@
1
+ # Claudexer
2
+
3
+ Browse local Codex and Claude Code sessions, resume one where you left off, or
4
+ export it to readable **standalone HTML** or **Markdown**. Run `claudexer`
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
+ This project was previously published as `claudex-export`; see its
9
+ [final release notes](https://pypi.org/project/claudex-export/) if you're
10
+ migrating from that name.
11
+
12
+ In the Textual picker, `Enter` resumes the selected session by exec'ing
13
+ `claude --resume ID` or `codex resume ID` (whichever binary the session
14
+ belongs to must be on `PATH`, and it runs from the session's original working
15
+ directory when known). Press `e` to export instead; a modal reports the
16
+ written file(s) or any error without leaving the picker.
17
+
18
+ **Exports are sanitized by default.** They contain the session date, title,
19
+ recorded elapsed time when available, direct user prompts, and assistant
20
+ progress/final replies. Tools, execution plans, injected context, images,
21
+ session IDs, working directories, and individual message timestamps are omitted.
22
+ Use `--full` to restore the previous detailed transcript, or press `f` in the
23
+ Textual picker or preview to toggle between sanitized and full export.
24
+
25
+ Requires Python 3.10 or later. No API key or service is needed.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ python -m pip install claudexer
31
+ ```
32
+
33
+ Or install it as an isolated command-line application with
34
+ `pipx install claudexer`. Python and its dependencies are sufficient; `henv` is
35
+ only used for development in this repository.
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Browse and search local sessions; HTML is the default
41
+ claudexer
42
+
43
+ # Browse only Claude Code sessions
44
+ claudexer --source claude
45
+
46
+ # Auto-detect a Claude Code file and export both formats
47
+ claudexer /path/to/claude-session.jsonl --format both -o exports
48
+
49
+ # Opt in to metadata, original messages, tool details, and images
50
+ claudexer /path/to/session.jsonl --full -o detailed.html
51
+
52
+ # Remove additional names or literal strings from sanitized output
53
+ claudexer /path/to/session.jsonl --redact 'Jane Doe' --redact 'Acme Private Project'
54
+
55
+ # Export a specified rollout
56
+ claudexer /path/to/rollout.jsonl -o conversation.html
57
+
58
+ # Markdown, without tool calls/results
59
+ claudexer /path/to/rollout.jsonl --format md --no-tools -o conversation.md
60
+
61
+ # Both formats, into a directory
62
+ claudexer /path/to/rollout.jsonl --format both -o exports
63
+
64
+ # Include archived sessions, or use another Codex home
65
+ claudexer --include-archived
66
+ claudexer --codex-home /path/to/.codex
67
+ ```
68
+
69
+ The Textual picker supports arrow keys, Page Up/Down, and mouse navigation.
70
+ Press `/` to focus live search; search matches titles, directories, IDs, and
71
+ provider names. Enter in the search box returns focus to the results.
72
+
73
+ | Key | Action |
74
+ | --- | --- |
75
+ | `↑` / `↓` | Move between sessions |
76
+ | `Enter` on a session | Resume it: exec into `claude --resume ID` or `codex resume ID` |
77
+ | `v` | Preview the selected session's user prompts |
78
+ | `e` in list or preview | Export the selected/previewed session; a modal reports the result |
79
+ | `f` in list or preview | Toggle sanitized/full export; current mode is displayed |
80
+ | `Esc` / `v` in preview | Return to the list |
81
+ | `/` | Focus search |
82
+ | `Esc` in list | Clear search and focus the list |
83
+ | `q` / `Ctrl+C` | Cancel |
84
+
85
+ Previews are scrollable and loaded in a background worker. They follow the selected
86
+ export mode and show prompt text with original numbering: the first 10 and last 10,
87
+ without duplicates where those ranges overlap. For longer sessions a marker
88
+ shows how many middle prompts were omitted. Tool results, injected context, and
89
+ compaction summaries are excluded from the prompt preview. Images are counted,
90
+ not opened. Reading a preview does not write an export.
91
+
92
+ Use `--plain-picker` for the original numbered terminal picker (`/search`,
93
+ `n`/`p`, number to select, `q` to cancel); prompt previews require the Textual
94
+ picker. Without an interactive terminal, supply an explicit input file.
95
+
96
+ By default, exports are written to the current directory with the date and a
97
+ redacted title in the filename. Full exports use the original title and session
98
+ ID. `-o` is a file path for a single format and a
99
+ directory for `--format both`. Parent directories are created as needed.
100
+ Existing files require `--force`; the source session is never overwritten.
101
+
102
+ ## Sanitization and full exports
103
+
104
+ The same defaults apply to HTML and Markdown, including `--format both`.
105
+ Sanitization removes recognizable email addresses, IP/MAC addresses, common
106
+ phone-number formats, UUIDs, local paths, local hostnames, and known identity
107
+ values from metadata and local account information. It also redacts those values
108
+ in titles, prose, and code blocks. Links lose their destinations and image
109
+ payloads are omitted. Generated filenames exclude the session ID and redact the
110
+ title; an explicitly supplied `-o` name is used as given.
111
+
112
+ This is deterministic, local, **best-effort** redaction. It cannot identify every
113
+ person's name, street address, organization, or other identifying fact in arbitrary
114
+ prose. Review a file before sharing it and supply repeatable `--redact TEXT`
115
+ arguments for additional names or strings. No external service is used.
116
+
117
+ Elapsed time is the span between recorded timestamps and includes idle time;
118
+ it is omitted if the log does not provide distinct usable timestamps.
119
+
120
+ `--full` disables sanitization and restores metadata, original conversation text,
121
+ tools, images, and session event markers. `--no-tools` can still suppress tools
122
+ in full mode. `--redact` applies only to sanitized mode. Full mode does not add
123
+ raw system/developer instructions, reasoning, usage records, or unknown records
124
+ that the parser has always excluded. The local picker still displays directories
125
+ and IDs to help identify a session; they are not included in sanitized exports.
126
+
127
+ ## Supported content
128
+
129
+ - Native Codex rollout and Claude Code `.jsonl` files, containing one JSON
130
+ record per line. File content determines the format automatically; use
131
+ `--source codex` or `--source claude` to specify it explicitly.
132
+ - `.json` containing a single native session record or an array of records.
133
+ Codex records use `type`/`payload`; Claude Code uses `type`/`message` and
134
+ session metadata. Arbitrary chat JSON formats are not supported.
135
+ - User messages and assistant progress/final replies in recorded order. Tool
136
+ inputs/results and compaction/interruption markers are included with `--full`.
137
+ - Native transcript records take priority over matching completion events.
138
+ Duplicates are matched by IDs and by content within each turn, one occurrence
139
+ at a time. Separate repeated messages remain present.
140
+ - System/developer messages, reasoning, usage records, and replacement history
141
+ from compaction are omitted. Instructions stored as actual **user** messages
142
+ remain part of the full transcript. Known injected context is omitted in
143
+ sanitized mode.
144
+ - Tools invoked inside an orchestration tool may appear as separate execution
145
+ events as well as in that tool's output: these describe nested operations.
146
+ - Claude Code tool-use blocks pair with tool results by tool-use ID. Replayed
147
+ UUIDs are deduplicated while separate blocks sharing a message ID survive.
148
+ Thinking blocks, metadata, and compacted summaries are omitted. End-of-turn
149
+ replies use the light blue final-answer styling.
150
+
151
+ HTML includes responsive styling, highlighted code, tables, links, message
152
+ anchors, collapsed tool details, expand/collapse buttons, and a print button.
153
+ It uses embedded CSS/JavaScript and works offline. Printing expands tool details.
154
+ Markdown keeps conversation formatting and uses fenced blocks for tool data.
155
+
156
+ With `--full`, embedded PNG, JPEG, GIF, and WebP data images are preserved. Other image
157
+ references become placeholders; the exporter does not fetch remote images or
158
+ read image paths from the transcript. Raw HTML is escaped in conversation prose;
159
+ HTML exports restrict scripts and resource loads with a Content Security Policy.
160
+
161
+ Codex discovery respects `--codex-home`, then `CODEX_HOME`, then `~/.codex`.
162
+ It scans `sessions/` (and optionally `archived_sessions/`) and uses
163
+ `session_index.jsonl` titles when available. Sessions are sorted by recent
164
+ activity. Each export covers one file; child/subagent sessions are not recursively
165
+ included. Claude Code discovery respects `--claude-home`, then
166
+ `CLAUDE_CONFIG_DIR`, then `~/.claude`, and reads main session files under
167
+ `projects/`. Custom or automatic session titles take priority over the first
168
+ prompt. Subagent logs are excluded from discovery; pass a subagent file explicitly
169
+ to export it. `--source all` (the default) combines both providers; archived
170
+ session inclusion applies to Codex. Source files and databases are never modified.
171
+
172
+ Unknown record types and malformed JSONL lines produce warnings. An incomplete
173
+ final line in a running session is skipped; rerun the export later to include it.
174
+ Local session formats can evolve, so inspect export notes when a newer record type
175
+ appears. Full exports contain the selected conversation and tool data as stored;
176
+ sanitized exports apply the redaction described above.
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ henv -n export_codex_session_dev -x python -m pip install -e '.[dev]'
182
+ henv -n export_codex_session_dev -x python -m pytest
183
+ ```
184
+
185
+ Tests use synthetic sessions, including headless Textual keyboard/preview tests.
186
+ Keep private session files and generated exports
187
+ out of source control; `exports/` is ignored for local previews.
188
+
189
+ ## Releases to PyPI
190
+
191
+ Pushing a version tag triggers `.github/workflows/release.yml`. It runs tests
192
+ on Python 3.10–3.14, builds an sdist and wheel, checks package metadata, and
193
+ smoke-tests an installation of the built wheel. The tag must exactly match
194
+ `v` followed by the package version. Only then does it publish to PyPI and
195
+ create a GitHub release with both distribution files attached.
196
+
197
+ ### One-time PyPI setup
198
+
199
+ In your PyPI account, open [Publishing](https://pypi.org/manage/account/publishing/)
200
+ and add a **pending GitHub publisher** with these exact values:
201
+
202
+ | Field | Value |
203
+ | --- | --- |
204
+ | PyPI project name | `claudexer` |
205
+ | GitHub owner | `matplo` |
206
+ | Repository | `claudexer` |
207
+ | Workflow filename | `release.yml` |
208
+ | Environment name | `pypi` |
209
+
210
+ If the PyPI project already exists in your account, add the same publisher
211
+ under that project's Publishing settings. The GitHub workflow uses OIDC
212
+ Trusted Publishing; no `PYPI_API_TOKEN` secret is needed. The GitHub environment
213
+ `pypi` must not require reviewer approval if you want fully automatic releases.
214
+ The PyPI project is created by the first successful publish, not by registering
215
+ the pending publisher.
216
+
217
+ ### Publish a version
218
+
219
+ Set `__version__` in `src/claudexer/__init__.py` (the single version source),
220
+ commit the change, and push a matching tag. For the initial `0.1.0` release:
221
+
222
+ ```bash
223
+ henv -n export_codex_session_dev -x git push origin main
224
+ henv -n export_codex_session_dev -x git tag -a v0.1.0 -m 'Release 0.1.0'
225
+ henv -n export_codex_session_dev -x git push origin v0.1.0
226
+ ```
227
+
228
+ For later releases, change the version and tag together. Prereleases such as
229
+ `0.2.0rc1` use `v0.2.0rc1`. PyPI versions are immutable: publish a new version
230
+ for changed artifacts. If publishing fails before upload, fix the configuration
231
+ and rerun the failed workflow jobs. If only GitHub release creation fails after
232
+ PyPI succeeds, rerun that failed job rather than publishing again.
233
+
234
+ See [PyPI's Trusted Publishing documentation](https://docs.pypi.org/trusted-publishers/)
235
+ for account setup details. Ordinary pushes to `main` and pull requests run the
236
+ same tests and packaging checks without publishing.
237
+
238
+ ## License
239
+
240
+ 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,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "claudexer"
7
+ dynamic = ["version"]
8
+ description = "Browse, resume, and export Codex and Claude Code sessions with a Textual picker"
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>=8.2,<9"]
15
+ keywords = ["codex", "claude", "sessions", "markdown", "html", "textual", "resume"]
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/claudexer"
32
+ Repository = "https://github.com/matplo/claudexer"
33
+ Issues = "https://github.com/matplo/claudexer/issues"
34
+ Changelog = "https://github.com/matplo/claudexer/releases"
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["pytest>=8,<10"]
38
+
39
+ [project.scripts]
40
+ claudexer = "claudexer.cli:main"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = {attr = "claudexer.__version__"}
47
+
48
+ [tool.setuptools.package-data]
49
+ claudexer = ["*.css", "*.js"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ 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("claudexer"))
18
+ except ValueError as exc:
19
+ raise SystemExit(str(exc)) from exc
20
+ print(f"Verified release tag {sys.argv[1]}")