From 649a471846ddeaf9daf00b6e98727fe10d1f56b2 Mon Sep 17 00:00:00 2001 From: Mayne Date: Fri, 27 Mar 2026 23:31:57 +0800 Subject: [PATCH 1/2] feat(weread): add cover URL to shelf command --- adapters/weread/shelf.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adapters/weread/shelf.yaml b/adapters/weread/shelf.yaml index b0c3d60..6ddb6f9 100644 --- a/adapters/weread/shelf.yaml +++ b/adapters/weread/shelf.yaml @@ -30,9 +30,10 @@ pipeline: return books.slice(0, limit).map(item => ({ title: item.title || '', author: item.author || '', + cover: item.cover || '', progress: progressMap[item.bookId] != null ? progressMap[item.bookId] + '%' : '-', bookId: item.bookId || '', })); })() -columns: [title, author, progress, bookId] +columns: [title, author, cover, progress, bookId] From afd49b8a0b4e0c6bd33e95a2ec7b90c3d5deb498 Mon Sep 17 00:00:00 2001 From: Mayne Date: Fri, 27 Mar 2026 23:32:03 +0800 Subject: [PATCH 2/2] feat(output): omit footer for structured formats (JSON/YAML/CSV) --- crates/opencli-rs-output/src/render.rs | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/opencli-rs-output/src/render.rs b/crates/opencli-rs-output/src/render.rs index ba3fcd3..0476f69 100644 --- a/crates/opencli-rs-output/src/render.rs +++ b/crates/opencli-rs-output/src/render.rs @@ -51,11 +51,14 @@ pub fn render(data: &Value, opts: &RenderOptions) -> String { output = format!("{}\n{}", title, output); } - if let Some(footer) = build_footer(opts) { - if !output.ends_with('\n') { - output.push('\n'); + // Only show footer for human-readable formats (Table, Markdown) + if matches!(opts.format, OutputFormat::Table | OutputFormat::Markdown) { + if let Some(footer) = build_footer(opts) { + if !output.ends_with('\n') { + output.push('\n'); + } + output.push_str(&footer); } - output.push_str(&footer); } output @@ -92,9 +95,10 @@ mod tests { #[test] fn test_render_with_footer() { + // Footer should only appear in Table/Markdown format, not in JSON let data = json!({"name": "Alice"}); let opts = RenderOptions { - format: OutputFormat::Json, + format: OutputFormat::Table, elapsed: Some(Duration::from_millis(150)), source: Some("test-api".to_string()), footer_extra: Some("page 1/3".to_string()), @@ -106,6 +110,22 @@ mod tests { assert!(out.contains("page 1/3")); } + #[test] + fn test_render_json_no_footer() { + // JSON format should not have footer + let data = json!({"name": "Alice"}); + let opts = RenderOptions { + format: OutputFormat::Json, + elapsed: Some(Duration::from_millis(150)), + source: Some("test-api".to_string()), + ..Default::default() + }; + let out = render(&data, &opts); + assert!(!out.contains("Elapsed:")); + assert!(!out.contains("Source:")); + assert!(out.contains("Alice")); + } + #[test] fn test_render_with_title() { let data = json!([{"name": "Alice"}]); @@ -122,7 +142,7 @@ mod tests { fn test_render_elapsed_seconds() { let data = json!("ok"); let opts = RenderOptions { - format: OutputFormat::Json, + format: OutputFormat::Table, elapsed: Some(Duration::from_secs_f64(2.5)), ..Default::default() };