feat: /export with markdown and JSON format#14
Conversation
/export — exports session as readable markdown (default) /export json — exports raw session as JSON Markdown format includes user/assistant/tool messages with proper formatting, tool call details, and token summary.
📝 WalkthroughWalkthroughExport functionality is enhanced to support multiple formats (JSON and Markdown). A format parameter is added to the Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI/TUI
participant Command Handler
participant Session
participant FileSystem
User->>CLI/TUI: Trigger /export command with optional format
CLI/TUI->>Command Handler: Parse format argument ("json" or "md")
Command Handler->>CLI/TUI: Queue PendingCommand::Export(format)
CLI/TUI->>CLI/TUI: Acquire runtime lock (non-blocking)
alt format == "json"
CLI/TUI->>Session: Serialize to JSON
Session-->>CLI/TUI: JSON string
CLI/TUI->>FileSystem: Write to export.json
else format == "md"
CLI/TUI->>Session: Call to_markdown()
Session->>Session: Iterate messages, format per role
Session-->>CLI/TUI: Markdown string
CLI/TUI->>FileSystem: Write to export.md
end
FileSystem-->>User: Export complete
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mc/crates/mc-cli/src/main.rs`:
- Around line 748-750: The code currently calls
serde_json::to_string_pretty(&rt.session).unwrap_or_default(), which can
silently produce an empty JSON string on serialization failure; instead handle
the Result explicitly (e.g., use match or the ? operator) so serialization
errors are returned or logged rather than written as empty content—find the
serde_json::to_string_pretty call around rt.session in main.rs and replace
unwrap_or_default with proper error propagation or a logged error path that
returns Err (or a non-success exit) so callers do not get an empty JSON file on
failure.
- Around line 746-753: The export path ends up with double extensions because
session_path("export.json") always appends ".json"; update the export branch
around the let (path, content) block so you pass a base name to session_path
(e.g., session_path("export")) and then append the desired extension per branch
(".json" for JSON, ".md" for Markdown), or change session_path to detect an
existing extension and not append ".json"; modify the code in the JSON and
Markdown branches accordingly (referencing the let (path, content) assignment
and the session_path function).
In `@mc/crates/mc-tui/src/commands.rs`:
- Around line 41-43: The format selection uses arg directly so inputs with extra
whitespace or different casing (e.g., " JSON") fall through to markdown;
normalize arg by trimming whitespace and comparing case-insensitively before
deciding format. In the "/export" branch update the logic that sets fmt (where
PendingCommand::Export is used and app.pending_command is assigned) to compute a
normalized value (e.g., let normalized = arg.trim().to_ascii_lowercase() or use
eq_ignore_ascii_case on arg.trim()) and then choose "json" when it matches
"json", otherwise "md".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f2b5ef44-8567-4540-91a0-24eaef341b0a
📒 Files selected for processing (4)
mc/crates/mc-cli/src/main.rsmc/crates/mc-core/src/session.rsmc/crates/mc-tui/src/app.rsmc/crates/mc-tui/src/commands.rs
| let (path, content) = if fmt == "json" { | ||
| let p = session_path("export.json"); | ||
| let c = serde_json::to_string_pretty(&rt.session) | ||
| .unwrap_or_default(); | ||
| (p, c) | ||
| } else { | ||
| let p = session_path("export.md"); | ||
| (p, rt.session.to_markdown()) |
There was a problem hiding this comment.
Export path generation is wrong (.json gets appended twice).
Because session_path() always appends .json, Line 747 creates export.json.json and Line 752 creates export.md.json.
Suggested fix
- let (path, content) = if fmt == "json" {
- let p = session_path("export.json");
+ let session_dir = session_path("")
+ .parent()
+ .unwrap_or(std::path::Path::new("."))
+ .to_path_buf();
+ let (path, content) = if fmt == "json" {
+ let p = session_dir.join("export.json");
let c = serde_json::to_string_pretty(&rt.session)
.unwrap_or_default();
(p, c)
} else {
- let p = session_path("export.md");
+ let p = session_dir.join("export.md");
(p, rt.session.to_markdown())
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mc/crates/mc-cli/src/main.rs` around lines 746 - 753, The export path ends up
with double extensions because session_path("export.json") always appends
".json"; update the export branch around the let (path, content) block so you
pass a base name to session_path (e.g., session_path("export")) and then append
the desired extension per branch (".json" for JSON, ".md" for Markdown), or
change session_path to detect an existing extension and not append ".json";
modify the code in the JSON and Markdown branches accordingly (referencing the
let (path, content) assignment and the session_path function).
| let c = serde_json::to_string_pretty(&rt.session) | ||
| .unwrap_or_default(); | ||
| (p, c) |
There was a problem hiding this comment.
Avoid silently exporting empty JSON on serialization failure.
Line 748-749 uses unwrap_or_default(), which can write an empty file and still report success.
Suggested fix
- let c = serde_json::to_string_pretty(&rt.session)
- .unwrap_or_default();
- (p, c)
+ match serde_json::to_string_pretty(&rt.session) {
+ Ok(c) => (p, c),
+ Err(e) => {
+ app.output_lines.push(format!("Export failed: {e}"));
+ continue;
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let c = serde_json::to_string_pretty(&rt.session) | |
| .unwrap_or_default(); | |
| (p, c) | |
| match serde_json::to_string_pretty(&rt.session) { | |
| Ok(c) => (p, c), | |
| Err(e) => { | |
| app.output_lines.push(format!("Export failed: {e}")); | |
| continue; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mc/crates/mc-cli/src/main.rs` around lines 748 - 750, The code currently
calls serde_json::to_string_pretty(&rt.session).unwrap_or_default(), which can
silently produce an empty JSON string on serialization failure; instead handle
the Result explicitly (e.g., use match or the ? operator) so serialization
errors are returned or logged rather than written as empty content—find the
serde_json::to_string_pretty call around rt.session in main.rs and replace
unwrap_or_default with proper error propagation or a logged error path that
returns Err (or a non-success exit) so callers do not get an empty JSON file on
failure.
| "/export" => { | ||
| let fmt = if arg == "json" { "json" } else { "md" }; | ||
| app.pending_command = Some(PendingCommand::Export(fmt.into())); |
There was a problem hiding this comment.
Normalize /export argument before format selection.
Line 42 only matches exact "json", so inputs like /export json or /export JSON unexpectedly export markdown.
Suggested fix
"/export" => {
- let fmt = if arg == "json" { "json" } else { "md" };
+ let fmt = if arg.trim().eq_ignore_ascii_case("json") {
+ "json"
+ } else {
+ "md"
+ };
app.pending_command = Some(PendingCommand::Export(fmt.into()));
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mc/crates/mc-tui/src/commands.rs` around lines 41 - 43, The format selection
uses arg directly so inputs with extra whitespace or different casing (e.g., "
JSON") fall through to markdown; normalize arg by trimming whitespace and
comparing case-insensitively before deciding format. In the "/export" branch
update the logic that sets fmt (where PendingCommand::Export is used and
app.pending_command is assigned) to compute a normalized value (e.g., let
normalized = arg.trim().to_ascii_lowercase() or use eq_ignore_ascii_case on
arg.trim()) and then choose "json" when it matches "json", otherwise "md".
What
Enhanced
/exportto export real session data (not raw TUI lines)./export— readable markdown with user/assistant/tool messages/export json— raw session JSON for programmatic useChanges
mc-core/src/session.rs: AddSession::to_markdown()mc-tui/src/app.rs:Exportnow carries format stringmc-tui/src/commands.rs: Parse/export [json]mc-cli/src/main.rs: Use session data for export152 tests pass.
Summary by CodeRabbit
jsonfor JSON export, defaulting to Markdown). Export now captures the complete session history with improved formatting, including message roles, token counts, and structured content blocks.