limit_cli/tui/commands/
share.rs1use super::{Command, CommandContext, CommandResult};
6use crate::error::CliError;
7use crate::session_share::{ExportFormat, SessionShare};
8
9pub struct ShareCommand;
11
12impl ShareCommand {
13 pub fn new() -> Self {
14 Self
15 }
16
17 fn get_format(&self, args: &str) -> Option<ExportFormat> {
18 match args.trim().to_lowercase().as_str() {
19 "" | "clipboard" | "cb" => Some(ExportFormat::Markdown),
20 "md" | "markdown" => Some(ExportFormat::Markdown),
21 "json" => Some(ExportFormat::Json),
22 _ => None,
23 }
24 }
25
26 fn handle_clipboard(
27 &self,
28 ctx: &mut CommandContext,
29 format: ExportFormat,
30 session_id: &str,
31 ) -> Result<CommandResult, CliError> {
32 let messages = ctx.messages.lock().unwrap().clone();
33 let total_input_tokens = *ctx.total_input_tokens.lock().unwrap();
34 let total_output_tokens = *ctx.total_output_tokens.lock().unwrap();
35
36 let user_assistant_count = messages
38 .iter()
39 .filter(|m| matches!(m.role, limit_llm::Role::User | limit_llm::Role::Assistant))
40 .count();
41
42 if user_assistant_count == 0 {
43 ctx.add_system_message(
44 "⚠ No messages to share. Start a conversation first.".to_string(),
45 );
46 return Ok(CommandResult::Continue);
47 }
48
49 match SessionShare::generate_share_content(
50 session_id,
51 &messages,
52 total_input_tokens,
53 total_output_tokens,
54 None, format,
56 ) {
57 Ok(content) => {
58 if let Some(ref clipboard) = ctx.clipboard {
59 match clipboard.lock().unwrap().set_text(&content) {
60 Ok(()) => {
61 let short_id = &session_id[..session_id.len().min(8)];
62 ctx.add_system_message(format!(
63 "✓ Session {} copied to clipboard ({} messages, {} tokens)",
64 short_id,
65 user_assistant_count,
66 total_input_tokens + total_output_tokens
67 ));
68 }
69 Err(e) => {
70 ctx.add_system_message(format!(
71 "❌ Failed to copy to clipboard: {}",
72 e
73 ));
74 }
75 }
76 } else {
77 ctx.add_system_message(
78 "❌ Clipboard not available. Try '/share md' to save as file.".to_string(),
79 );
80 }
81 }
82 Err(e) => {
83 ctx.add_system_message(format!("❌ Failed to generate share content: {}", e));
84 }
85 }
86
87 Ok(CommandResult::Continue)
88 }
89
90 fn handle_file_export(
91 &self,
92 ctx: &mut CommandContext,
93 format: ExportFormat,
94 session_id: &str,
95 ) -> Result<CommandResult, CliError> {
96 let messages = ctx.messages.lock().unwrap().clone();
97 let total_input_tokens = *ctx.total_input_tokens.lock().unwrap();
98 let total_output_tokens = *ctx.total_output_tokens.lock().unwrap();
99
100 let user_assistant_count = messages
102 .iter()
103 .filter(|m| matches!(m.role, limit_llm::Role::User | limit_llm::Role::Assistant))
104 .count();
105
106 if user_assistant_count == 0 {
107 ctx.add_system_message(
108 "⚠ No messages to share. Start a conversation first.".to_string(),
109 );
110 return Ok(CommandResult::Continue);
111 }
112
113 match SessionShare::export_session(
114 session_id,
115 &messages,
116 total_input_tokens,
117 total_output_tokens,
118 None, format,
120 ) {
121 Ok((filepath, export)) => {
122 let short_id = &session_id[..session_id.len().min(8)];
123 let extension = match format {
124 ExportFormat::Markdown => "md",
125 ExportFormat::Json => "json",
126 };
127 ctx.add_system_message(format!(
128 "✓ Session {} exported to {}\n ({} messages, {} tokens)\n Location: ~/.limit/exports/",
129 short_id,
130 extension,
131 user_assistant_count,
132 total_input_tokens + total_output_tokens
133 ));
134
135 tracing::info!(
136 "Session exported to {:?} ({} messages)",
137 filepath,
138 export.messages.len()
139 );
140 }
141 Err(e) => {
142 ctx.add_system_message(format!("❌ Failed to export session: {}", e));
143 }
144 }
145
146 Ok(CommandResult::Continue)
147 }
148}
149
150impl Command for ShareCommand {
151 fn name(&self) -> &str {
152 "share"
153 }
154
155 fn description(&self) -> &str {
156 "Export session to clipboard or file"
157 }
158
159 fn usage(&self) -> Vec<&str> {
160 vec!["/share", "/share md", "/share json"]
161 }
162
163 fn execute(&self, args: &str, ctx: &mut CommandContext) -> Result<CommandResult, CliError> {
164 let format = match self.get_format(args) {
165 Some(f) => f,
166 None => {
167 ctx.add_system_message(
168 "Invalid format. Use: /share, /share md, /share json".to_string(),
169 );
170 return Ok(CommandResult::Continue);
171 }
172 };
173
174 let session_id = ctx.session_id.clone();
175 let args_lower = args.trim().to_lowercase();
176
177 if args_lower.is_empty() || args_lower == "clipboard" || args_lower == "cb" {
179 self.handle_clipboard(ctx, format, &session_id)
180 } else {
181 self.handle_file_export(ctx, format, &session_id)
183 }
184 }
185}
186
187impl Default for ShareCommand {
188 fn default() -> Self {
189 Self::new()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_share_command() {
199 let cmd = ShareCommand::new();
200 assert_eq!(cmd.name(), "share");
201 }
202
203 #[test]
204 fn test_share_default() {
205 let cmd = ShareCommand;
206 assert_eq!(cmd.name(), "share");
207 }
208
209 #[test]
210 fn test_get_format() {
211 let cmd = ShareCommand::new();
212
213 assert!(matches!(cmd.get_format(""), Some(ExportFormat::Markdown)));
214 assert!(matches!(cmd.get_format("md"), Some(ExportFormat::Markdown)));
215 assert!(matches!(cmd.get_format("json"), Some(ExportFormat::Json)));
216 assert!(cmd.get_format("invalid").is_none());
217 }
218}