Skip to content

Commit a5f32ee

Browse files
committed
Make 'both files' the default; rename suffix convention
Behaviour change: 'claude-backup export <id>' now writes two files by default: <date>--<id>.md — clean dialogue (was previously .minimal.md) <date>--<id>.full.md — audit copy with tool calls (was previously the unsuffixed .md) The default is dual-output because that's what users actually want: an unsuffixed .md they can open and re-read like a chat log, and a .full.md sitting next to it as the safety net for debugging or auditing the agent. Old --minimal boolean is replaced by --mode {both,minimal,full}; default is 'both'. This is a clean break — the prior --minimal flag shipped less than 24 hours ago and had no users beyond the author. Filename convention rationale: the version a user usually wants is the one with no suffix (it's the 90% case). The audit copy carries the .full.md suffix because it's the opt-in detail view. This reverses what the previous release did, where .minimal.md was the suffixed special case. CLI: - export and export-all now accept --mode {both,minimal,full} (default both) - both subcommands list each written path (so 'both' prints two lines) - removed --minimal entirely API: - export_session() returns list[Path] (was Path) - export_session(mode='both'|'minimal'|'full'); raises ValueError otherwise - render_markdown() unchanged Tests: 68/68 pass, 91% coverage. Updated existing tests for new filenames and added new tests for each of the three --mode values, plus invalid mode handling. Docs: README, README.ru, QUICKSTART all updated. QUICKSTART now leads with 'two files by default' and a small table contrasting their use cases.
1 parent e18d54a commit a5f32ee

8 files changed

Lines changed: 256 additions & 110 deletions

File tree

QUICKSTART.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,17 @@ You'll see:
7676

7777
```
7878
Exported: backups/2026-05-07--abc12345-...md
79+
Exported: backups/2026-05-07--abc12345-...full.md
7980
```
8081

81-
Open that `.md` file in any text editor. It looks like this:
82+
**Two files** are written by default:
83+
84+
| File | What's inside | When to use |
85+
|------|---------------|-------------|
86+
| `2026-05-07--abc12345-….md` | The conversation: your prompts + Claude's text replies, nothing else | Re-reading later, archiving, sharing |
87+
| `2026-05-07--abc12345-….full.md` | Everything: every tool call, every shell command, every internal reasoning step | Debugging or auditing what the agent actually did |
88+
89+
Open the `.md` file in any text editor — that's the readable one. It looks like this:
8290

8391
```markdown
8492
---
@@ -88,6 +96,7 @@ branch: main
8896
model: claude-sonnet-4-6
8997
messages: 42
9098
exported_at: 2026-05-07T15:30:00Z
99+
mode: dialogue-only
91100
title: Fix the login bug
92101
---
93102

@@ -102,29 +111,32 @@ fix the login bug
102111
Here's the fix...
103112
```
104113

114+
The `.full.md` file looks similar but includes lines like `## Assistant (10:42:51)\n[tool_use: Edit]` followed by `## User (10:42:52)\nFile updated.` — the agent's tool plumbing.
115+
105116
---
106117

107-
## Step 5 — Export the mini-log version (just the dialogue)
118+
## Step 5 — Want only one of the two files?
108119

109-
The default export captures **everything** — your prompts, Claude's replies, and every tool call (file reads, shell commands, etc.) plus their outputs. Useful for an audit trail, but noisy if you just want to read the conversation later.
120+
```bash
121+
# Only the clean conversation
122+
claude-backup export abc12345 --output ./backups/ --mode minimal
110123

111-
For a clean transcript with only your messages and Claude's text replies, add `--minimal`:
124+
# Only the audit copy
125+
claude-backup export abc12345 --output ./backups/ --mode full
112126

113-
```bash
114-
claude-backup export abc12345 --output ./backups/ --minimal
127+
# Both (default — same as no flag)
128+
claude-backup export abc12345 --output ./backups/ --mode both
115129
```
116130

117-
This writes a separate file with `.minimal.md` suffix. Tool calls, tool results, and Claude's internal reasoning are dropped. Typically the mini-log is **30–50% the size** of the full export and reads like a normal chat log.
118-
119-
You can have both versions of the same session — they don't overwrite each other.
131+
The clean version is typically **half the size** of the audit copy and is what you'll usually want.
120132

121133
---
122134

123135
## Step 6 — Export everything at once
124136

125137
```bash
126-
claude-backup export-all --output ./backups/ # full mode
127-
claude-backup export-all --output ./backups/ --minimal # mini-log of every session
138+
claude-backup export-all --output ./backups/ # both files per session
139+
claude-backup export-all --output ./backups/ --mode minimal # only the clean .md files
128140
```
129141

130142
Each project gets its own subfolder under `./backups/`. You now have a complete Markdown archive of your Claude Code history.

README.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ claude-backup export f7a07eec --output ~/claude-backups/
5050

5151
# Export everything (each project gets its own subfolder)
5252
claude-backup export-all --output ~/claude-backups/
53-
54-
# Mini-log: only your messages and Claude's text replies, no tool calls
55-
claude-backup export f7a07eec --output ~/claude-backups/ --minimal
5653
```
5754

55+
By default each export produces **two files** side-by-side:
56+
- `<date>--<id>.md` — clean dialogue (your prompts + Claude's text replies)
57+
- `<date>--<id>.full.md` — full audit copy with tool calls, tool results, and reasoning
58+
59+
If you only want one of them, pass `--mode minimal` or `--mode full`.
60+
5861
New users: see [QUICKSTART.md](./QUICKSTART.md) for the full step-by-step guide (macOS, Linux, Windows/WSL).
5962

6063
---
@@ -96,23 +99,31 @@ Documents/Claude f7a07eec Build claude-backup CLI tool with export 296
9699
claude-backup export f7a07eec --output ./backups/
97100
```
98101

99-
Writes `./backups/2026-05-07--f7a07eec-<full-uuid>.md`. **You only need the first 8 characters of the session ID** — the tool resolves the prefix.
102+
By default writes two files into `./backups/`:
103+
104+
```
105+
2026-05-07--f7a07eec-<full-uuid>.md ← clean dialogue, the one you'd open to re-read
106+
2026-05-07--f7a07eec-<full-uuid>.full.md ← audit copy with every tool call and result
107+
```
100108

101-
### Mini-log mode (`--minimal`)
109+
**You only need the first 8 characters of the session ID** — the tool resolves the prefix.
102110

103-
When you just want a clean transcript of the conversation — your prompts and Claude's text replies — without tool calls, tool results, or extended-thinking blocks:
111+
### Choosing one or both files (`--mode`)
104112

105-
```bash
106-
claude-backup export f7a07eec --output ./backups/ --minimal
107-
```
113+
| Flag | Output |
114+
|------|--------|
115+
| _(default)_ | both `<id>.md` and `<id>.full.md` |
116+
| `--mode minimal` | only `<id>.md` (clean dialogue) |
117+
| `--mode full` | only `<id>.full.md` (audit copy) |
108118

109-
Writes a separate file with a `.minimal.md` suffix, so the full and minimal exports can coexist for the same session. Frontmatter records `mode: dialogue-only` and an adjusted `messages` count. Typically the minimal file is 30–50% the size of the full one.
119+
The `.md` file is the version you'll usually open — typically half the size of the audit copy and reads like a normal chat log. The `.full.md` is the safety net: every tool call, every shell output, every internal reasoning step — useful when you actually need to debug what the agent did.
110120

111121
### Export everything
112122

113123
```bash
114-
claude-backup export-all --output ./backups/
115-
claude-backup export-all --output ./backups/ --minimal # mini-log version of all sessions
124+
claude-backup export-all --output ./backups/ # both files per session
125+
claude-backup export-all --output ./backups/ --mode minimal # only the clean .md files
126+
claude-backup export-all --output ./backups/ --mode full # only the .full.md audit copies
116127
```
117128

118129
Each project gets its own subdirectory under `--output`.
@@ -163,7 +174,7 @@ I'll create the utility according to the spec...
163174
...
164175
```
165176

166-
`--minimal` mode produces the same frontmatter (with `mode: dialogue-only` added) but the body contains only `## User` and `## Assistant` text turns — no `[tool_use: ...]` markers, no tool-result echoes, no thinking blocks.
177+
The `<id>.md` file (default and `--mode minimal`) shares the same frontmatter — plus `mode: dialogue-only` but the body contains only `## User` and `## Assistant` text turns. No `[tool_use: ...]` markers, no tool-result echoes, no extended-thinking blocks.
167178

168179
---
169180

@@ -190,7 +201,7 @@ pytest -v --cov=claude_backup
190201

191202
CI runs on Python 3.10, 3.11, and 3.12 — see [.github/workflows/test.yml](.github/workflows/test.yml).
192203

193-
**Test coverage: 91%** (64/64 tests passing) — covers the real Claude Code format, the legacy spec format, edge cases (empty/corrupt JSONL, unicode, missing index), the `--minimal` mode, and CLI integration.
204+
**Test coverage: 91%** (68/68 tests passing) — covers the real Claude Code format, the legacy spec format, edge cases (empty/corrupt JSONL, unicode, missing index), `--mode` selection (both / minimal / full), and CLI integration.
194205

195206
### Project layout
196207

README.ru.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ claude-backup export f7a07eec --output ~/claude-backups/
5050

5151
# Экспортировать всё (каждый проект — в отдельную папку)
5252
claude-backup export-all --output ~/claude-backups/
53-
54-
# Mini-лог: только ваши сообщения и текстовые ответы Claude, без tool-вызовов
55-
claude-backup export f7a07eec --output ~/claude-backups/ --minimal
5653
```
5754

55+
По умолчанию каждый экспорт создаёт **два файла** рядом:
56+
- `<date>--<id>.md` — чистый диалог (ваши промпты + текстовые ответы Claude)
57+
- `<date>--<id>.full.md` — полная audit-копия с tool-вызовами, tool-результатами и блоками рассуждений
58+
59+
Если нужен только один из них — `--mode minimal` или `--mode full`.
60+
5861
Новым пользователям: см. [QUICKSTART.md](./QUICKSTART.md) — полная пошаговая инструкция (macOS, Linux, Windows/WSL).
5962

6063
---
@@ -96,23 +99,31 @@ Documents/Claude f7a07eec Build claude-backup CLI tool with export 296
9699
claude-backup export f7a07eec --output ./backups/
97100
```
98101

99-
Создаёт `./backups/2026-05-07--f7a07eec-<полный-uuid>.md`. **Достаточно первых 8 символов session ID** — утилита сама находит сессию по префиксу.
102+
По умолчанию создаёт два файла в `./backups/`:
103+
104+
```
105+
2026-05-07--f7a07eec-<полный-uuid>.md ← чистый диалог, тот что хочется открыть
106+
2026-05-07--f7a07eec-<полный-uuid>.full.md ← полная audit-копия со всеми tool-вызовами
107+
```
100108

101-
### Mini-лог (`--minimal`)
109+
**Достаточно первых 8 символов session ID** — утилита сама находит сессию по префиксу.
102110

103-
Когда нужен чистый транскрипт диалога — только ваши промпты и текстовые ответы Claude, без tool-вызовов, tool-результатов и блоков extended thinking:
111+
### Выбор файлов (`--mode`)
104112

105-
```bash
106-
claude-backup export f7a07eec --output ./backups/ --minimal
107-
```
113+
| Флаг | Что записывает |
114+
|------|----------------|
115+
| _(по умолчанию)_ | оба файла: `<id>.md` и `<id>.full.md` |
116+
| `--mode minimal` | только `<id>.md` (чистый диалог) |
117+
| `--mode full` | только `<id>.full.md` (audit-копия) |
108118

109-
Создаётся отдельный файл с суффиксом `.minimal.md`, чтобы полный и mini-экспорты могли существовать одновременно для одной сессии. В frontmatter — `mode: dialogue-only` и пересчитанный `messages`. Обычно mini-лог занимает 30–50% от объёма полного экспорта.
119+
`.md` файл — тот, что обычно хочется открыть: как правило в 2 раза меньше audit-копии и читается как обычный чат-лог. `.full.md` — это страховка: каждый tool-вызов, каждый shell-вывод, каждый блок reasoning. Полезно когда реально нужно отдебажить что делал агент.
110120

111121
### Экспорт всего
112122

113123
```bash
114-
claude-backup export-all --output ./backups/
115-
claude-backup export-all --output ./backups/ --minimal # mini-версия всех сессий
124+
claude-backup export-all --output ./backups/ # оба файла на каждую сессию
125+
claude-backup export-all --output ./backups/ --mode minimal # только чистые .md
126+
claude-backup export-all --output ./backups/ --mode full # только .full.md audit-копии
116127
```
117128

118129
Каждый проект получает свою поддиректорию в `--output`.
@@ -160,7 +171,7 @@ I'll create the utility according to the spec...
160171
...
161172
```
162173

163-
В режиме `--minimal` frontmatter тот же (плюс поле `mode: dialogue-only`), но в теле остаются только турны `## User` и `## Assistant` с реальным текстом — без `[tool_use: ...]`, без эхо tool-результатов, без блоков thinking.
174+
В файле `<id>.md` (по умолчанию и при `--mode minimal`) frontmatter тот же плюс поле `mode: dialogue-only`, — но в теле остаются только турны `## User` и `## Assistant` с реальным текстом. Без `[tool_use: ...]`, без эхо tool-результатов, без блоков extended thinking.
164175

165176
---
166177

@@ -187,7 +198,7 @@ pytest -v --cov=claude_backup
187198

188199
CI запускается на Python 3.10, 3.11 и 3.12 — см. [.github/workflows/test.yml](.github/workflows/test.yml).
189200

190-
**Покрытие тестами: 91%** (64/64 тестов проходят) — реальный формат Claude Code, legacy-формат из спеки, edge-кейсы (пустой/битый JSONL, unicode, отсутствующий индекс), режим `--minimal` и интеграционные тесты CLI.
201+
**Покрытие тестами: 91%** (68/68 тестов проходят) — реальный формат Claude Code, legacy-формат из спеки, edge-кейсы (пустой/битый JSONL, unicode, отсутствующий индекс), выбор `--mode` (both / minimal / full), и интеграционные тесты CLI.
191202

192203
### Структура проекта
193204

claude_backup/cli.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,20 @@ def list_cmd(ctx: click.Context) -> None:
6060
help="Output directory (default: ./backups/).",
6161
)
6262
@click.option(
63-
"--minimal",
64-
is_flag=True,
65-
default=False,
66-
help="Mini-log: only user/assistant dialogue, no tool calls or reasoning. "
67-
"Filename gets a .minimal.md suffix so it doesn't overwrite the full export.",
63+
"--mode",
64+
type=click.Choice(["both", "minimal", "full"], case_sensitive=False),
65+
default="both",
66+
show_default=True,
67+
help=(
68+
"Which file(s) to write. "
69+
"'both' writes the clean dialogue (<id>.md) AND the audit copy with tool "
70+
"calls (<id>.full.md). 'minimal' writes only the clean dialogue. "
71+
"'full' writes only the audit copy."
72+
),
6873
)
6974
@click.pass_context
7075
def export_cmd(
71-
ctx: click.Context, session_id: str, output: Path, minimal: bool
76+
ctx: click.Context, session_id: str, output: Path, mode: str
7277
) -> None:
7378
projects = _safe_scan(ctx.obj.get("claude_home"))
7479
matches = []
@@ -96,8 +101,9 @@ def export_cmd(
96101
)
97102
sys.exit(2)
98103

99-
out = export_session(target, output, minimal=minimal)
100-
click.echo(f"Exported: {out}")
104+
paths = export_session(target, output, mode=mode)
105+
for p in paths:
106+
click.echo(f"Exported: {p}")
101107

102108

103109
@main.command("export-all", help="Export every discovered session.")
@@ -109,13 +115,14 @@ def export_cmd(
109115
help="Output directory (default: ./backups/).",
110116
)
111117
@click.option(
112-
"--minimal",
113-
is_flag=True,
114-
default=False,
115-
help="Mini-log mode: only user/assistant dialogue, no tools or reasoning.",
118+
"--mode",
119+
type=click.Choice(["both", "minimal", "full"], case_sensitive=False),
120+
default="both",
121+
show_default=True,
122+
help="Which file(s) to write per session — see `claude-backup export --help`.",
116123
)
117124
@click.pass_context
118-
def export_all_cmd(ctx: click.Context, output: Path, minimal: bool) -> None:
125+
def export_all_cmd(ctx: click.Context, output: Path, mode: str) -> None:
119126
projects = _safe_scan(ctx.obj.get("claude_home"))
120127
exported = 0
121128
skipped = 0
@@ -131,8 +138,9 @@ def export_all_cmd(ctx: click.Context, output: Path, minimal: bool) -> None:
131138
skipped += 1
132139
continue
133140
try:
134-
path = export_session(session, project_out, minimal=minimal)
135-
click.echo(f"Exported: {path}")
141+
paths = export_session(session, project_out, mode=mode)
142+
for p in paths:
143+
click.echo(f"Exported: {p}")
136144
exported += 1
137145
except Exception as e: # pragma: no cover - defensive
138146
click.echo(

claude_backup/exporter.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,58 @@
1717
}
1818

1919

20+
EXPORT_MODES = ("both", "minimal", "full")
21+
22+
2023
def export_session(
2124
session: SessionInfo,
2225
output_dir: Path,
2326
now: datetime | None = None,
24-
minimal: bool = False,
25-
) -> Path:
26-
"""Export a single session to Markdown. Returns path to the written file.
27-
28-
When `minimal=True`, drops tool calls, tool results, and reasoning blocks —
29-
keeps only the user/assistant dialogue. Filename gets a `.minimal.md` suffix.
27+
mode: str = "both",
28+
) -> list[Path]:
29+
"""Export a session to Markdown. Returns the list of written file paths.
30+
31+
Two output flavours, controlled by `mode`:
32+
- `<date>--<session_id>.md` — clean dialogue: only user prompts and
33+
Claude's text replies.
34+
- `<date>--<session_id>.full.md` — everything: tool calls, tool results,
35+
and any visible thinking text.
36+
37+
`mode` values:
38+
- `"both"` (default) — write both files
39+
- `"minimal"` — write only the clean `.md` file
40+
- `"full"` — write only the `.full.md` file
3041
"""
42+
if mode not in EXPORT_MODES:
43+
raise ValueError(
44+
f"mode must be one of {EXPORT_MODES}; got {mode!r}"
45+
)
3146
if session.jsonl_path is None or not session.jsonl_path.exists():
3247
raise FileNotFoundError(
3348
f"Session JSONL not found for {session.session_id}"
3449
)
3550

3651
output_dir.mkdir(parents=True, exist_ok=True)
3752
messages = parse_session(session.jsonl_path)
38-
3953
date_prefix = _date_prefix(session, messages)
40-
suffix = ".minimal.md" if minimal else ".md"
41-
filename = f"{date_prefix}--{session.session_id}{suffix}"
42-
out_path = output_dir / filename
43-
44-
md = render_markdown(session, messages, now=now, minimal=minimal)
45-
out_path.write_text(md, encoding="utf-8")
46-
return out_path
54+
base = f"{date_prefix}--{session.session_id}"
55+
56+
written: list[Path] = []
57+
if mode in ("both", "minimal"):
58+
path = output_dir / f"{base}.md"
59+
path.write_text(
60+
render_markdown(session, messages, now=now, minimal=True),
61+
encoding="utf-8",
62+
)
63+
written.append(path)
64+
if mode in ("both", "full"):
65+
path = output_dir / f"{base}.full.md"
66+
path.write_text(
67+
render_markdown(session, messages, now=now, minimal=False),
68+
encoding="utf-8",
69+
)
70+
written.append(path)
71+
return written
4772

4873

4974
def render_markdown(

tests/test_cli.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,21 @@ def test_list_handles_missing_claude_home(tmp_path: Path) -> None:
2727
assert "not found" in result.output.lower()
2828

2929

30-
def test_export_command_writes_file(claude_home: Path, tmp_path: Path) -> None:
30+
def test_export_command_writes_both_files_by_default(
31+
claude_home: Path, tmp_path: Path
32+
) -> None:
3133
runner = CliRunner()
3234
out = tmp_path / "backups"
3335
result = runner.invoke(
3436
main,
3537
["--claude-home", str(claude_home), "export", "abc-123", "--output", str(out)],
3638
)
3739
assert result.exit_code == 0, result.output
38-
written = list(out.glob("*.md"))
39-
assert len(written) == 1
40-
assert "abc-123" in written[0].name
40+
written = sorted(p.name for p in out.glob("*.md"))
41+
assert written == [
42+
"2026-05-07--abc-123.full.md",
43+
"2026-05-07--abc-123.md",
44+
]
4145

4246

4347
def test_export_unknown_session_returns_error(claude_home: Path, tmp_path: Path) -> None:

0 commit comments

Comments
 (0)