1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum OutputFormat {
6 Json,
7 Human,
8}
9
10impl OutputFormat {
11 pub fn resolve(json: bool, human: bool) -> Self {
13 if human {
14 return OutputFormat::Human;
15 }
16 if json {
17 return OutputFormat::Json;
18 }
19 match std::env::var("LIT_OUTPUT").as_deref() {
21 Ok("human") => OutputFormat::Human,
22 _ => OutputFormat::Json, }
24 }
25}
26
27#[derive(Debug, Serialize, Deserialize)]
29pub struct CommandOutput {
30 pub status: &'static str,
31 pub command: &'static str,
32 #[serde(flatten)]
33 pub data: serde_json::Value,
34}
35
36pub trait CommandResponse: Serialize {
38 fn command_name(&self) -> &'static str;
40
41 fn human_readable(&self) -> String;
43
44 fn to_json_output(&self) -> String {
51 let data = serde_json::to_value(self).unwrap_or(serde_json::Value::Null);
52 let output = CommandOutput {
53 status: "ok",
54 command: self.command_name(),
55 data,
56 };
57 serde_json::to_string(&output).unwrap_or_default()
58 }
59
60 fn to_json_output_pretty(&self) -> String {
62 let data = serde_json::to_value(self).unwrap_or(serde_json::Value::Null);
63 let output = CommandOutput {
64 status: "ok",
65 command: self.command_name(),
66 data,
67 };
68 serde_json::to_string_pretty(&output).unwrap_or_default()
69 }
70}
71
72pub fn render<R: CommandResponse>(response: &R, format: OutputFormat) -> String {
74 match format {
75 OutputFormat::Json => response.to_json_output(),
76 OutputFormat::Human => response.human_readable(),
77 }
78}
79
80pub fn render_error(
82 error: &crate::errors::LitError,
83 command: &str,
84 format: OutputFormat,
85) -> String {
86 match format {
87 OutputFormat::Json => {
88 let err_obj = serde_json::json!({
89 "status": "error",
90 "command": command,
91 "error": {
92 "code": error.error_code(),
93 "message": error.user_message(),
94 "suggestions": error.suggestions(),
95 }
96 });
97 serde_json::to_string(&err_obj).unwrap_or_default()
98 }
99 OutputFormat::Human => {
100 let mut out = format!("error: {}", error.user_message());
101 let suggestions = error.suggestions();
102 if !suggestions.is_empty() {
103 out.push_str("\n\nhint:");
104 for s in suggestions {
105 out.push_str(&format!("\n {}", s));
106 }
107 }
108 out
109 }
110 }
111}
112
113#[derive(Debug, Serialize, Deserialize)]
116pub struct InitResponse {
117 pub path: String,
118 pub bare: bool,
119}
120
121impl CommandResponse for InitResponse {
122 fn command_name(&self) -> &'static str {
123 "init"
124 }
125 fn human_readable(&self) -> String {
126 if self.bare {
127 format!("Initialized empty bare Lit repository in {}", self.path)
128 } else {
129 format!("Initialized empty Lit repository in {}", self.path)
130 }
131 }
132}
133
134#[derive(Debug, Serialize, Deserialize)]
135pub struct AddResponse {
136 pub files_added: usize,
137}
138
139impl CommandResponse for AddResponse {
140 fn command_name(&self) -> &'static str {
141 "add"
142 }
143 fn human_readable(&self) -> String {
144 format!("Added {} file(s) to staging area", self.files_added)
145 }
146}
147
148#[derive(Debug, Serialize, Deserialize)]
149pub struct CommitResponse {
150 pub hash: String,
151 pub short_hash: String,
152 pub tree: String,
153 pub parent: Option<String>,
154 pub author: String,
155 pub message: String,
156 pub timestamp: i64,
157}
158
159impl CommandResponse for CommitResponse {
160 fn command_name(&self) -> &'static str {
161 "commit"
162 }
163 fn human_readable(&self) -> String {
164 format!("[{}] {}", self.short_hash, self.message)
165 }
166}
167
168#[derive(Debug, Serialize, Deserialize)]
169pub struct StatusResponse {
170 pub branch: Option<String>,
171 pub head: Option<String>,
172 pub staged: Vec<String>,
173 pub modified: Vec<String>,
174 pub untracked: Vec<String>,
175 pub clean: bool,
176}
177
178impl CommandResponse for StatusResponse {
179 fn command_name(&self) -> &'static str {
180 "status"
181 }
182 fn human_readable(&self) -> String {
183 const GREEN: &str = "\x1b[32m";
185 const ORANGE: &str = "\x1b[33m";
186 const RED: &str = "\x1b[31m";
187 const BOLD: &str = "\x1b[1m";
188 const RESET: &str = "\x1b[0m";
189
190 let mut out = String::new();
191 if let Some(branch) = &self.branch {
192 out.push_str(&format!("On branch {BOLD}{branch}{RESET}\n"));
193 } else {
194 out.push_str(&format!("{BOLD}HEAD detached{RESET}\n"));
195 }
196
197 if self.clean {
198 out.push_str("nothing to commit, working tree clean\n");
199 return out;
200 }
201
202 if !self.staged.is_empty() {
203 out.push_str(&format!("\n{BOLD}Changes to be committed:{RESET}\n"));
204 for f in &self.staged {
205 out.push_str(&format!("{GREEN} new file: {f}{RESET}\n"));
206 }
207 }
208 if !self.modified.is_empty() {
209 out.push_str(&format!("\n{BOLD}Changes not staged for commit:{RESET}\n"));
210 for f in &self.modified {
211 out.push_str(&format!("{ORANGE} modified: {f}{RESET}\n"));
212 }
213 }
214 if !self.untracked.is_empty() {
215 out.push_str(&format!("\n{BOLD}Untracked files:{RESET}\n"));
216 for f in &self.untracked {
217 out.push_str(&format!("{RED} {f}{RESET}\n"));
218 }
219 }
220 out
221 }
222}
223
224#[derive(Debug, Serialize, Deserialize)]
225pub struct CommitEntry {
226 pub hash: String,
227 pub short_hash: String,
228 pub author: String,
229 pub timestamp: i64,
230 pub message: String,
231 pub is_head: bool,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
235pub struct LogResponse {
236 pub branch: Option<String>,
237 pub commits: Vec<CommitEntry>,
238}
239
240impl CommandResponse for LogResponse {
241 fn command_name(&self) -> &'static str {
242 "log"
243 }
244 fn human_readable(&self) -> String {
245 if self.commits.is_empty() {
246 return "No commits yet\n".to_string();
247 }
248 let mut out = String::new();
249 for entry in &self.commits {
250 out.push_str(&format!("commit {}\n", entry.hash));
251 if entry.is_head {
252 if let Some(branch) = &self.branch {
253 out.push_str(&format!(" (HEAD -> {})\n", branch));
254 }
255 }
256 out.push_str(&format!("Author: {}\n", entry.author));
257 if let Some(dt) = chrono::DateTime::<chrono::Utc>::from_timestamp(entry.timestamp, 0) {
258 out.push_str(&format!(
259 "Date: {}\n",
260 dt.format("%a %b %d %H:%M:%S %Y %z")
261 ));
262 }
263 out.push('\n');
264 for line in entry.message.lines() {
265 out.push_str(&format!(" {}\n", line));
266 }
267 out.push('\n');
268 }
269 out
270 }
271}
272
273#[derive(Debug, Serialize, Deserialize)]
274pub struct BranchEntry {
275 pub name: String,
276 pub is_current: bool,
277}
278
279#[derive(Debug, Serialize, Deserialize)]
280#[serde(tag = "action")]
281pub enum BranchResponse {
282 #[serde(rename = "list")]
283 List { branches: Vec<BranchEntry> },
284 #[serde(rename = "create")]
285 Create { name: String },
286 #[serde(rename = "delete")]
287 Delete { name: String },
288}
289
290impl CommandResponse for BranchResponse {
291 fn command_name(&self) -> &'static str {
292 "branch"
293 }
294 fn human_readable(&self) -> String {
295 match self {
296 BranchResponse::List { branches } => {
297 if branches.is_empty() {
298 return "No branches yet\n".to_string();
299 }
300 let mut out = String::new();
301 for b in branches {
302 let marker = if b.is_current { "* " } else { " " };
303 out.push_str(&format!("{}{}\n", marker, b.name));
304 }
305 out
306 }
307 BranchResponse::Create { name } => format!("Created branch '{}'\n", name),
308 BranchResponse::Delete { name } => format!("Deleted branch '{}'\n", name),
309 }
310 }
311}
312
313#[derive(Debug, Serialize, Deserialize)]
314pub struct CheckoutResponse {
315 pub target: String,
316 pub is_new_branch: bool,
317 pub is_detached: bool,
318}
319
320impl CommandResponse for CheckoutResponse {
321 fn command_name(&self) -> &'static str {
322 "checkout"
323 }
324 fn human_readable(&self) -> String {
325 if self.is_new_branch {
326 format!("Switched to a new branch '{}'\n", self.target)
327 } else if self.is_detached {
328 format!(
329 "HEAD is now at {} (detached)\n",
330 &self.target[..16.min(self.target.len())]
331 )
332 } else {
333 format!("Switched to branch '{}'\n", self.target)
334 }
335 }
336}
337
338#[derive(Debug, Serialize, Deserialize)]
339#[serde(tag = "object_type")]
340pub enum ShowResponse {
341 #[serde(rename = "commit")]
342 Commit {
343 hash: String,
344 author: String,
345 timestamp: i64,
346 message: String,
347 },
348 #[serde(rename = "tree")]
349 Tree {
350 hash: String,
351 entries: Vec<TreeEntryInfo>,
352 },
353 #[serde(rename = "blob")]
354 Blob {
355 hash: String,
356 size: usize,
357 content: Option<String>,
358 is_binary: bool,
359 },
360}
361
362#[derive(Debug, Serialize, Deserialize)]
363pub struct TreeEntryInfo {
364 pub mode: String,
365 pub object_type: String,
366 pub hash: String,
367 pub name: String,
368}
369
370impl CommandResponse for ShowResponse {
371 fn command_name(&self) -> &'static str {
372 "show"
373 }
374 fn human_readable(&self) -> String {
375 match self {
376 ShowResponse::Commit {
377 hash,
378 author,
379 timestamp,
380 message,
381 } => {
382 let mut out = format!("commit {}\nAuthor: {}\n", hash, author);
383 if let Some(dt) = chrono::DateTime::<chrono::Utc>::from_timestamp(*timestamp, 0) {
384 out.push_str(&format!(
385 "Date: {}\n",
386 dt.format("%a %b %d %H:%M:%S %Y %z")
387 ));
388 }
389 out.push_str(&format!("\n{}\n", message));
390 out
391 }
392 ShowResponse::Tree { hash, entries } => {
393 let mut out = format!("tree {}\n\n", hash);
394 for e in entries {
395 out.push_str(&format!(
396 "{} {} {}\t{}\n",
397 e.mode,
398 e.object_type,
399 &e.hash[..16.min(e.hash.len())],
400 e.name
401 ));
402 }
403 out
404 }
405 ShowResponse::Blob {
406 hash,
407 size,
408 content,
409 is_binary,
410 } => {
411 let mut out = format!("blob {}\n\n", hash);
412 if *is_binary {
413 out.push_str(&format!("(binary content, {} bytes)\n", size));
414 } else if let Some(text) = content {
415 out.push_str(text);
416 out.push('\n');
417 }
418 out
419 }
420 }
421 }
422}
423
424#[derive(Debug, Serialize, Deserialize)]
425pub struct RemoteEntry {
426 pub name: String,
427 pub url: String,
428}
429
430#[derive(Debug, Serialize, Deserialize)]
431#[serde(tag = "action")]
432pub enum RemoteResponse {
433 #[serde(rename = "list")]
434 List { remotes: Vec<RemoteEntry> },
435 #[serde(rename = "add")]
436 Add { name: String, url: String },
437 #[serde(rename = "remove")]
438 Remove { name: String },
439}
440
441impl CommandResponse for RemoteResponse {
442 fn command_name(&self) -> &'static str {
443 "remote"
444 }
445 fn human_readable(&self) -> String {
446 match self {
447 RemoteResponse::List { remotes } => {
448 if remotes.is_empty() {
449 return "No remotes configured\n".to_string();
450 }
451 let mut out = String::new();
452 for r in remotes {
453 out.push_str(&format!("{}\t{}\n", r.name, r.url));
454 }
455 out
456 }
457 RemoteResponse::Add { name, .. } => format!("Added remote '{}'\n", name),
458 RemoteResponse::Remove { name } => format!("Removed remote '{}'\n", name),
459 }
460 }
461}
462
463#[derive(Debug, Serialize, Deserialize)]
464pub struct ConfigEntry {
465 pub key: String,
466 pub value: String,
467}
468
469#[derive(Debug, Serialize, Deserialize)]
470#[serde(tag = "action")]
471pub enum ConfigResponse {
472 #[serde(rename = "show")]
473 Show { entries: Vec<ConfigEntry> },
474 #[serde(rename = "get")]
475 Get { key: String, value: String },
476 #[serde(rename = "set")]
477 Set { key: String, value: String },
478}
479
480impl CommandResponse for ConfigResponse {
481 fn command_name(&self) -> &'static str {
482 "config"
483 }
484 fn human_readable(&self) -> String {
485 match self {
486 ConfigResponse::Show { entries } => {
487 let mut out = String::from("Lit Configuration\n==================\n\n");
488 for e in entries {
489 out.push_str(&format!("{} = {}\n", e.key, e.value));
490 }
491 out
492 }
493 ConfigResponse::Get { key, value } => format!("{} = {}\n", key, value),
494 ConfigResponse::Set { key, value } => format!("Set {} = {}\n", key, value),
495 }
496 }
497}
498
499#[derive(Debug, Serialize, Deserialize)]
500pub struct MergeResponse {
501 pub merged: bool,
502 pub fast_forward: bool,
503 pub commit_hash: Option<String>,
504 pub message: String,
505 pub has_conflicts: bool,
506 pub file_results: Vec<FileMergeInfo>,
507 pub strategy: String,
508}
509
510#[derive(Debug, Serialize, Deserialize)]
511pub struct FileMergeInfo {
512 pub path: String,
513 pub status: String,
514 pub conflict_count: usize,
515}
516
517impl CommandResponse for MergeResponse {
518 fn command_name(&self) -> &'static str {
519 "merge"
520 }
521 fn human_readable(&self) -> String {
522 let mut out = String::new();
523 out.push_str(&self.message);
524 out.push('\n');
525
526 if !self.file_results.is_empty() {
527 for f in &self.file_results {
528 let icon = match f.status.as_str() {
529 "conflict" => "C",
530 "added" => "A",
531 "deleted" => "D",
532 "autoresolved" => "M",
533 _ => " ",
534 };
535 out.push_str(&format!(" {} {}\n", icon, f.path));
536 }
537 }
538
539 out
540 }
541}
542
543#[derive(Debug, Serialize, Deserialize)]
544pub struct ResolveResponse {
545 pub resolved_files: Vec<String>,
546 pub remaining_conflicts: usize,
547 pub merge_complete: bool,
548 pub message: String,
549}
550
551impl CommandResponse for ResolveResponse {
552 fn command_name(&self) -> &'static str {
553 "resolve"
554 }
555 fn human_readable(&self) -> String {
556 let mut out = String::new();
557 out.push_str(&self.message);
558 out.push('\n');
559
560 for f in &self.resolved_files {
561 out.push_str(&format!(" Resolved: {}\n", f));
562 }
563
564 if self.remaining_conflicts > 0 {
565 out.push_str(&format!(
566 " {} conflict(s) remaining\n",
567 self.remaining_conflicts
568 ));
569 }
570
571 out
572 }
573}
574
575#[derive(Debug, Serialize, Deserialize)]
576pub struct PushResponse {
577 pub remote: String,
578 pub branch: String,
579 pub objects_transferred: usize,
580 pub updated: bool,
581 pub message: String,
582}
583
584impl CommandResponse for PushResponse {
585 fn command_name(&self) -> &'static str {
586 "push"
587 }
588 fn human_readable(&self) -> String {
589 format!("{}\n", self.message)
590 }
591}
592
593#[derive(Debug, Serialize, Deserialize)]
594pub struct PullResponse {
595 pub remote: String,
596 pub branch: String,
597 pub objects_fetched: usize,
598 pub fast_forward: bool,
599 pub has_conflicts: bool,
600 pub merge_message: String,
601 pub message: String,
602}
603
604impl CommandResponse for PullResponse {
605 fn command_name(&self) -> &'static str {
606 "pull"
607 }
608 fn human_readable(&self) -> String {
609 format!("{}\n", self.message)
610 }
611}
612
613#[derive(Debug, Serialize, Deserialize)]
614pub struct CloneResponse {
615 pub url: String,
616 pub directory: String,
617 pub branches_cloned: Vec<String>,
618 pub objects_transferred: usize,
619 pub message: String,
620}
621
622impl CommandResponse for CloneResponse {
623 fn command_name(&self) -> &'static str {
624 "clone"
625 }
626 fn human_readable(&self) -> String {
627 format!("{}\n", self.message)
628 }
629}
630
631#[derive(Debug, Serialize, Deserialize)]
632pub struct FetchResponse {
633 pub remote: String,
634 pub branches_updated: Vec<String>,
635 pub objects_transferred: usize,
636 pub message: String,
637}
638
639impl CommandResponse for FetchResponse {
640 fn command_name(&self) -> &'static str {
641 "fetch"
642 }
643 fn human_readable(&self) -> String {
644 format!("{}\n", self.message)
645 }
646}
647
648#[derive(Debug, Serialize, Deserialize)]
649pub struct DiffResponse {
650 pub files: Vec<crate::core::diff::FileDiff>,
651 pub stats: Vec<crate::core::diff::DiffStat>,
652 pub stat_only: bool,
653 pub word_diff: bool,
654 pub files_changed: usize,
655 pub total_additions: usize,
656 pub total_deletions: usize,
657}
658
659impl CommandResponse for DiffResponse {
660 fn command_name(&self) -> &'static str {
661 "diff"
662 }
663 fn human_readable(&self) -> String {
664 use crate::core::diff::{annotate_hunk_word_diff, DiffLineKind, FileStatus};
665
666 if self.files.is_empty() {
667 return String::new(); }
669
670 let mut out = String::new();
671
672 if self.stat_only {
673 for stat in &self.stats {
675 let changes = stat.additions + stat.deletions;
676 let bar: String = std::iter::repeat_n('+', stat.additions.min(40))
677 .chain(std::iter::repeat_n('-', stat.deletions.min(40)))
678 .collect();
679 out.push_str(&format!(" {:<40} | {:>4} {}\n", stat.path, changes, bar));
680 }
681 out.push_str(&format!(
682 " {} file(s) changed, {} insertions(+), {} deletions(-)\n",
683 self.files_changed, self.total_additions, self.total_deletions
684 ));
685 return out;
686 }
687
688 for file in &self.files {
689 let header = match file.status {
690 FileStatus::Added => format!("--- /dev/null\n+++ b/{}\n", file.path),
691 FileStatus::Deleted => format!("--- a/{}\n+++ /dev/null\n", file.path),
692 FileStatus::Modified => {
693 format!("--- a/{}\n+++ b/{}\n", file.path, file.path)
694 }
695 };
696 out.push_str(&header);
697
698 if file.is_binary {
699 out.push_str("Binary files differ\n");
700 continue;
701 }
702
703 for hunk in &file.hunks {
704 out.push_str(&format!(
705 "@@ -{},{} +{},{} @@\n",
706 hunk.old_start, hunk.old_count, hunk.new_start, hunk.new_count
707 ));
708 if self.word_diff {
709 let annotated = annotate_hunk_word_diff(hunk);
710 for (line, word_segs) in &annotated {
711 if let Some(segs) = word_segs {
712 let prefix = match line.kind {
713 DiffLineKind::Add => '+',
714 DiffLineKind::Remove => '-',
715 _ => ' ',
716 };
717 out.push(prefix);
718 for seg in segs {
719 match seg.kind {
720 DiffLineKind::Remove => {
721 out.push_str(&format!("[-{}-]", seg.text));
722 }
723 DiffLineKind::Add => {
724 out.push_str(&format!("{{+{}+}}", seg.text));
725 }
726 DiffLineKind::Context => {
727 out.push_str(&seg.text);
728 }
729 }
730 }
731 out.push('\n');
732 } else {
733 let prefix = match line.kind {
734 DiffLineKind::Context => ' ',
735 DiffLineKind::Add => '+',
736 DiffLineKind::Remove => '-',
737 };
738 out.push_str(&format!("{}{}\n", prefix, line.content));
739 }
740 }
741 } else {
742 for line in &hunk.lines {
743 let prefix = match line.kind {
744 DiffLineKind::Context => ' ',
745 DiffLineKind::Add => '+',
746 DiffLineKind::Remove => '-',
747 };
748 out.push_str(&format!("{}{}\n", prefix, line.content));
749 }
750 }
751 }
752 }
753
754 out.push_str(&format!(
756 "\n{} file(s) changed, {} insertions(+), {} deletions(-)\n",
757 self.files_changed, self.total_additions, self.total_deletions
758 ));
759
760 out
761 }
762}
763
764#[derive(Debug, Serialize, Deserialize)]
765#[serde(tag = "action")]
766pub enum TagResponse {
767 #[serde(rename = "create")]
768 Create {
769 name: String,
770 hash: String,
771 annotated: bool,
772 signed: bool,
773 message: String,
774 },
775 #[serde(rename = "list")]
776 List { tags: Vec<String> },
777 #[serde(rename = "delete")]
778 Delete { name: String, message: String },
779 #[serde(rename = "verify")]
780 Verify {
781 name: String,
782 valid: bool,
783 algorithm: String,
784 message: String,
785 },
786}
787
788impl CommandResponse for TagResponse {
789 fn command_name(&self) -> &'static str {
790 "tag"
791 }
792 fn human_readable(&self) -> String {
793 match self {
794 TagResponse::Create { message, .. } => format!("{}\n", message),
795 TagResponse::List { tags } => {
796 if tags.is_empty() {
797 return String::new();
798 }
799 tags.iter().map(|t| format!("{}\n", t)).collect()
800 }
801 TagResponse::Delete { message, .. } => format!("{}\n", message),
802 TagResponse::Verify { message, .. } => format!("{}\n", message),
803 }
804 }
805}
806
807#[derive(Debug, Serialize, Deserialize)]
808pub struct MigrateEncryptionResponse {
809 pub objects_encrypted: usize,
810 pub objects_unpacked: usize,
811 pub refs_encrypted: usize,
812 pub index_encrypted: bool,
813 pub packs_expanded: usize,
814 pub already_encrypted: usize,
815 pub message: String,
816}
817
818impl CommandResponse for MigrateEncryptionResponse {
819 fn command_name(&self) -> &'static str {
820 "migrate-encryption"
821 }
822 fn human_readable(&self) -> String {
823 format!("{}\n", self.message)
824 }
825}
826
827#[derive(Debug, Serialize, Deserialize)]
828pub struct RotateKeyResponse {
829 pub objects_rotated: usize,
830 pub refs_rotated: usize,
831}
832
833impl CommandResponse for RotateKeyResponse {
834 fn command_name(&self) -> &'static str {
835 "rotate-key"
836 }
837 fn human_readable(&self) -> String {
838 format!(
839 "Passphrase rotation complete!\n {} objects re-encrypted\n {} refs re-encrypted\n Old passphrase is no longer valid.\n",
840 self.objects_rotated, self.refs_rotated
841 )
842 }
843}
844
845#[derive(Debug, Serialize, Deserialize)]
850pub struct StashEntryInfo {
851 pub index: usize,
852 pub message: String,
853 pub branch: Option<String>,
854 pub timestamp: i64,
855}
856
857#[derive(Debug, Serialize, Deserialize)]
858#[serde(tag = "action")]
859pub enum StashResponse {
860 #[serde(rename = "push")]
861 Push { index: usize, message: String },
862 #[serde(rename = "pop")]
863 Pop { index: usize, message: String },
864 #[serde(rename = "apply")]
865 Apply { index: usize, message: String },
866 #[serde(rename = "list")]
867 List { entries: Vec<StashEntryInfo> },
868 #[serde(rename = "drop")]
869 Drop { index: usize, message: String },
870}
871
872impl CommandResponse for StashResponse {
873 fn command_name(&self) -> &'static str {
874 "stash"
875 }
876 fn human_readable(&self) -> String {
877 match self {
878 StashResponse::Push { index, message } => {
879 format!(
880 "Saved working directory to stash@{{{}}}: {}",
881 index, message
882 )
883 }
884 StashResponse::Pop { index, message } => {
885 format!("Applied and dropped stash@{{{}}}: {}", index, message)
886 }
887 StashResponse::Apply { index, message } => {
888 format!("Applied stash@{{{}}}: {}", index, message)
889 }
890 StashResponse::List { entries } => {
891 if entries.is_empty() {
892 "No stash entries".to_string()
893 } else {
894 entries
895 .iter()
896 .map(|e| format!("stash@{{{}}}: {}", e.index, e.message))
897 .collect::<Vec<_>>()
898 .join("\n")
899 }
900 }
901 StashResponse::Drop { index, message } => {
902 format!("Dropped stash@{{{}}}: {}", index, message)
903 }
904 }
905 }
906}
907
908#[derive(Debug, Serialize, Deserialize)]
909pub struct ResetResponse {
910 pub target: String,
911 pub mode: String,
912 pub message: String,
913}
914
915impl CommandResponse for ResetResponse {
916 fn command_name(&self) -> &'static str {
917 "reset"
918 }
919 fn human_readable(&self) -> String {
920 format!(
921 "HEAD is now at {} ({})\n{}",
922 self.target, self.mode, self.message
923 )
924 }
925}
926
927#[derive(Debug, Serialize, Deserialize)]
928pub struct RevertResponse {
929 pub reverted_commit: String,
930 pub new_commit: String,
931 pub files_changed: usize,
932 pub message: String,
933}
934
935impl CommandResponse for RevertResponse {
936 fn command_name(&self) -> &'static str {
937 "revert"
938 }
939 fn human_readable(&self) -> String {
940 format!(
941 "Reverted {}\nNew commit: {}\n{} file(s) changed\n{}",
942 self.reverted_commit, self.new_commit, self.files_changed, self.message
943 )
944 }
945}
946
947#[derive(Debug, Serialize, Deserialize)]
948pub struct CherryPickResponse {
949 pub source_commit: String,
950 pub new_commit: String,
951 pub files_changed: usize,
952 pub message: String,
953}
954
955impl CommandResponse for CherryPickResponse {
956 fn command_name(&self) -> &'static str {
957 "cherry-pick"
958 }
959 fn human_readable(&self) -> String {
960 format!(
961 "Cherry-picked {}\nNew commit: {}\n{} file(s) changed\n{}",
962 self.source_commit, self.new_commit, self.files_changed, self.message
963 )
964 }
965}
966
967#[derive(Debug, Serialize, Deserialize)]
968pub struct RebaseResponse {
969 pub rebased_commits: usize,
970 pub onto: String,
971 pub branch: String,
972 pub message: String,
973 #[serde(skip_serializing_if = "Option::is_none")]
974 pub todo: Option<serde_json::Value>,
975}
976
977impl CommandResponse for RebaseResponse {
978 fn command_name(&self) -> &'static str {
979 "rebase"
980 }
981 fn human_readable(&self) -> String {
982 let mut out = format!("{}\n", self.message);
983 if self.rebased_commits > 0 {
984 out.push_str(&format!(
985 "Rebased {} commit(s) onto {}\n",
986 self.rebased_commits, self.onto
987 ));
988 }
989 if let Some(ref todo) = self.todo {
990 out.push_str(&format!(
991 "Todo: {}\n",
992 serde_json::to_string_pretty(todo).unwrap_or_default()
993 ));
994 }
995 out
996 }
997}
998
999#[derive(Debug, Serialize, Deserialize)]
1000pub struct BlameLineInfo {
1001 pub line_number: usize,
1002 pub content: String,
1003 pub commit_hash: String,
1004 pub author: String,
1005 pub timestamp: i64,
1006}
1007
1008#[derive(Debug, Serialize, Deserialize)]
1009pub struct BlameResponse {
1010 pub file: String,
1011 pub lines: Vec<BlameLineInfo>,
1012}
1013
1014impl CommandResponse for BlameResponse {
1015 fn command_name(&self) -> &'static str {
1016 "blame"
1017 }
1018 fn human_readable(&self) -> String {
1019 let mut out = format!("Blame for {}:\n", self.file);
1020 for line in &self.lines {
1021 out.push_str(&format!(
1022 "{} ({} {}) {}\n",
1023 &line.commit_hash[..8.min(line.commit_hash.len())],
1024 line.author,
1025 line.line_number,
1026 line.content
1027 ));
1028 }
1029 out
1030 }
1031}
1032
1033#[derive(Debug, Serialize, Deserialize)]
1034pub struct BisectResponse {
1035 pub action: String,
1036 pub current: Option<String>,
1037 pub remaining: usize,
1038 pub steps: usize,
1039 pub message: String,
1040}
1041
1042impl CommandResponse for BisectResponse {
1043 fn command_name(&self) -> &'static str {
1044 "bisect"
1045 }
1046 fn human_readable(&self) -> String {
1047 let mut out = format!("{}\n", self.message);
1048 if let Some(ref commit) = self.current {
1049 out.push_str(&format!("Current: {}\n", commit));
1050 }
1051 if self.remaining > 0 {
1052 out.push_str(&format!("~{} steps remaining\n", self.steps));
1053 }
1054 out
1055 }
1056}
1057
1058#[derive(Debug, Serialize, Deserialize)]
1059pub struct ReflogEntry {
1060 pub index: usize,
1061 pub old_hash: String,
1062 pub new_hash: String,
1063 pub action: String,
1064 pub message: String,
1065 pub timestamp: i64,
1066}
1067
1068#[derive(Debug, Serialize, Deserialize)]
1069pub struct ReflogResponse {
1070 pub ref_name: String,
1071 pub entries: Vec<ReflogEntry>,
1072}
1073
1074impl CommandResponse for ReflogResponse {
1075 fn command_name(&self) -> &'static str {
1076 "reflog"
1077 }
1078 fn human_readable(&self) -> String {
1079 let mut out = format!("Reflog for {}:\n", self.ref_name);
1080 for entry in &self.entries {
1081 out.push_str(&format!(
1082 "{}@{{{}}} {} -> {} {}: {}\n",
1083 self.ref_name,
1084 entry.index,
1085 &entry.old_hash[..8.min(entry.old_hash.len())],
1086 &entry.new_hash[..8.min(entry.new_hash.len())],
1087 entry.action,
1088 entry.message
1089 ));
1090 }
1091 out
1092 }
1093}
1094
1095#[derive(Debug, Serialize, Deserialize)]
1100pub struct BatchOperationResult {
1101 pub index: usize,
1102 pub command: String,
1103 pub status: String,
1104 pub result: Option<serde_json::Value>,
1105 pub error: Option<String>,
1106}
1107
1108#[derive(Debug, Serialize, Deserialize)]
1109pub struct BatchResponse {
1110 pub total: usize,
1111 pub succeeded: usize,
1112 pub failed: usize,
1113 pub atomic: bool,
1114 pub dry_run: bool,
1115 pub results: Vec<BatchOperationResult>,
1116}
1117
1118impl CommandResponse for BatchResponse {
1119 fn command_name(&self) -> &'static str {
1120 "batch"
1121 }
1122 fn human_readable(&self) -> String {
1123 format!(
1124 "Batch complete: {}/{} succeeded, {} failed{}{}",
1125 self.succeeded,
1126 self.total,
1127 self.failed,
1128 if self.atomic { " (atomic)" } else { "" },
1129 if self.dry_run { " (dry-run)" } else { "" },
1130 )
1131 }
1132}
1133
1134#[derive(Debug, Serialize, Deserialize)]
1135pub struct TransactionResponse {
1136 pub action: String,
1137 pub tx_id: Option<String>,
1138 pub message: String,
1139}
1140
1141impl CommandResponse for TransactionResponse {
1142 fn command_name(&self) -> &'static str {
1143 "transaction"
1144 }
1145 fn human_readable(&self) -> String {
1146 if let Some(ref id) = self.tx_id {
1147 format!(
1148 "Transaction {}: {} [{}]",
1149 self.action,
1150 self.message,
1151 &id[..8.min(id.len())]
1152 )
1153 } else {
1154 format!("Transaction {}: {}", self.action, self.message)
1155 }
1156 }
1157}
1158
1159#[derive(Debug, Serialize, Deserialize)]
1160pub struct SnapshotResponse {
1161 pub hash: String,
1162 pub short_hash: String,
1163 pub tree: String,
1164 pub parent: Option<String>,
1165 pub author: String,
1166 pub message: String,
1167 pub timestamp: i64,
1168 pub files_added: usize,
1169}
1170
1171impl CommandResponse for SnapshotResponse {
1172 fn command_name(&self) -> &'static str {
1173 "snapshot"
1174 }
1175 fn human_readable(&self) -> String {
1176 format!(
1177 "[{}] Snapshot: {}\n {} file(s) captured\n Author: {}",
1178 self.short_hash, self.message, self.files_added, self.author,
1179 )
1180 }
1181}
1182
1183#[derive(Debug, Serialize, Deserialize)]
1184pub struct SearchMatch {
1185 pub file: String,
1186 pub line_number: usize,
1187 pub content: String,
1188 pub commit: Option<String>,
1189 pub match_type: String,
1190}
1191
1192#[derive(Debug, Serialize, Deserialize)]
1193pub struct SearchResponse {
1194 pub query: String,
1195 pub match_type: String,
1196 pub matches: Vec<SearchMatch>,
1197 pub total: usize,
1198}
1199
1200impl CommandResponse for SearchResponse {
1201 fn command_name(&self) -> &'static str {
1202 "search"
1203 }
1204 fn human_readable(&self) -> String {
1205 let mut out = format!("Search '{}': {} result(s)\n", self.query, self.total);
1206 for m in &self.matches {
1207 match m.match_type.as_str() {
1208 "content" => {
1209 out.push_str(&format!(
1210 " {}:{}: {}\n",
1211 m.file,
1212 m.line_number,
1213 m.content.trim()
1214 ));
1215 }
1216 "message" => {
1217 out.push_str(&format!(
1218 " commit {}: {}\n",
1219 m.commit.as_deref().unwrap_or("?"),
1220 m.content.trim()
1221 ));
1222 }
1223 _ => {
1224 out.push_str(&format!(" {}\n", m.content.trim()));
1225 }
1226 }
1227 }
1228 out
1229 }
1230}
1231
1232#[derive(Debug, Serialize, Deserialize)]
1233pub struct WatchEvent {
1234 pub event_type: String,
1235 pub path: String,
1236 pub timestamp: i64,
1237}
1238
1239#[derive(Debug, Serialize, Deserialize)]
1240pub struct WatchResponse {
1241 pub events_emitted: usize,
1242 pub message: String,
1243}
1244
1245impl CommandResponse for WatchResponse {
1246 fn command_name(&self) -> &'static str {
1247 "watch"
1248 }
1249 fn human_readable(&self) -> String {
1250 self.message.clone()
1251 }
1252}
1253
1254#[derive(Debug, Serialize, Deserialize)]
1255pub struct VerifyResult {
1256 pub check: String,
1257 pub status: String,
1258 pub details: Option<String>,
1259}
1260
1261#[derive(Debug, Serialize, Deserialize)]
1262pub struct VerifyResponse {
1263 pub valid: bool,
1264 pub checks: Vec<VerifyResult>,
1265 pub objects_checked: usize,
1266 pub refs_checked: usize,
1267 pub message: String,
1268}
1269
1270impl CommandResponse for VerifyResponse {
1271 fn command_name(&self) -> &'static str {
1272 "verify"
1273 }
1274 fn human_readable(&self) -> String {
1275 let mut out = format!("{}\n", self.message);
1276 for check in &self.checks {
1277 let icon = if check.status == "ok" { "+" } else { "!" };
1278 out.push_str(&format!(" [{}] {}", icon, check.check));
1279 if let Some(ref details) = check.details {
1280 out.push_str(&format!(": {}", details));
1281 }
1282 out.push('\n');
1283 }
1284 out.push_str(&format!(
1285 " {} objects, {} refs checked\n",
1286 self.objects_checked, self.refs_checked
1287 ));
1288 out
1289 }
1290}
1291
1292#[derive(Debug, Serialize, Deserialize)]
1297pub struct ServeResponse {
1298 pub message: String,
1299}
1300
1301impl CommandResponse for ServeResponse {
1302 fn command_name(&self) -> &'static str {
1303 "serve"
1304 }
1305 fn human_readable(&self) -> String {
1306 self.message.clone()
1307 }
1308}
1309
1310#[derive(Debug, Serialize, Deserialize)]
1311pub struct McpServeResponse {
1312 pub transport: String,
1313 pub message: String,
1314}
1315
1316impl CommandResponse for McpServeResponse {
1317 fn command_name(&self) -> &'static str {
1318 "mcp-serve"
1319 }
1320 fn human_readable(&self) -> String {
1321 format!("[{}] {}", self.transport, self.message)
1322 }
1323}
1324
1325#[derive(Debug, Serialize, Deserialize)]
1326pub struct SwarmResponse {
1327 pub action: String,
1328 pub agent_id: Option<String>,
1329 pub message: String,
1330 pub details: Option<serde_json::Value>,
1331}
1332
1333impl CommandResponse for SwarmResponse {
1334 fn command_name(&self) -> &'static str {
1335 "swarm"
1336 }
1337 fn human_readable(&self) -> String {
1338 let mut out = format!("Swarm {}: {}\n", self.action, self.message);
1339 if let Some(ref details) = self.details {
1340 out.push_str(&serde_json::to_string_pretty(details).unwrap_or_default());
1341 }
1342 out
1343 }
1344}
1345
1346#[derive(Debug, Serialize, Deserialize)]
1347pub struct OntologyResponse {
1348 pub ontology: serde_json::Value,
1349}
1350
1351impl CommandResponse for OntologyResponse {
1352 fn command_name(&self) -> &'static str {
1353 "ontology"
1354 }
1355 fn human_readable(&self) -> String {
1356 serde_json::to_string_pretty(&self.ontology).unwrap_or_else(|_| "{}".to_string())
1357 }
1358}
1359
1360#[derive(Debug, Serialize, Deserialize)]
1361pub struct SchemaResponse {
1362 pub schema: serde_json::Value,
1363}
1364
1365impl CommandResponse for SchemaResponse {
1366 fn command_name(&self) -> &'static str {
1367 "schema"
1368 }
1369 fn human_readable(&self) -> String {
1370 serde_json::to_string_pretty(&self.schema).unwrap_or_else(|_| "{}".to_string())
1371 }
1372}
1373
1374#[derive(Debug, Serialize, Deserialize)]
1379pub struct ImportGitResponse {
1380 pub source: String,
1381 pub objects_imported: u64,
1382 pub refs_imported: u64,
1383 pub hash_mapping_count: usize,
1384 pub message: String,
1385}
1386
1387impl CommandResponse for ImportGitResponse {
1388 fn command_name(&self) -> &'static str {
1389 "import-git"
1390 }
1391 fn human_readable(&self) -> String {
1392 format!(
1393 "{}\n Objects imported: {}\n Refs imported: {}\n Hash mappings: {}",
1394 self.message, self.objects_imported, self.refs_imported, self.hash_mapping_count
1395 )
1396 }
1397}
1398
1399#[derive(Debug, Serialize, Deserialize)]
1400pub struct ExportGitResponse {
1401 pub destination: String,
1402 pub objects_exported: u64,
1403 pub refs_exported: u64,
1404 pub message: String,
1405}
1406
1407impl CommandResponse for ExportGitResponse {
1408 fn command_name(&self) -> &'static str {
1409 "export-git"
1410 }
1411 fn human_readable(&self) -> String {
1412 format!(
1413 "{}\n Objects exported: {}\n Refs exported: {}",
1414 self.message, self.objects_exported, self.refs_exported
1415 )
1416 }
1417}
1418
1419#[derive(Debug, Serialize, Deserialize)]
1424pub struct GcResponse {
1425 pub objects_packed: u64,
1426 pub packs_created: u64,
1427 pub loose_removed: u64,
1428 pub bytes_saved: u64,
1429 pub message: String,
1430}
1431
1432impl CommandResponse for GcResponse {
1433 fn command_name(&self) -> &'static str {
1434 "gc"
1435 }
1436 fn human_readable(&self) -> String {
1437 format!(
1438 "{}\n Objects packed: {}\n Packs created: {}\n Loose removed: {}\n Bytes saved: {}",
1439 self.message,
1440 self.objects_packed,
1441 self.packs_created,
1442 self.loose_removed,
1443 self.bytes_saved
1444 )
1445 }
1446}
1447
1448#[derive(Debug, Serialize, Deserialize)]
1449pub struct LfsTrackResponse {
1450 pub patterns: Vec<String>,
1451 pub message: String,
1452}
1453
1454impl CommandResponse for LfsTrackResponse {
1455 fn command_name(&self) -> &'static str {
1456 "lfs-track"
1457 }
1458 fn human_readable(&self) -> String {
1459 let mut out = format!("{}\n Tracked patterns:\n", self.message);
1460 for pat in &self.patterns {
1461 out.push_str(&format!(" {}\n", pat));
1462 }
1463 out
1464 }
1465}
1466
1467#[derive(Debug, Serialize, Deserialize)]
1468pub struct LfsMigrateResponse {
1469 pub files_migrated: u64,
1470 pub bytes_saved: u64,
1471 pub message: String,
1472}
1473
1474impl CommandResponse for LfsMigrateResponse {
1475 fn command_name(&self) -> &'static str {
1476 "lfs-migrate"
1477 }
1478 fn human_readable(&self) -> String {
1479 format!(
1480 "{}\n Files migrated: {}\n Bytes saved: {}",
1481 self.message, self.files_migrated, self.bytes_saved
1482 )
1483 }
1484}
1485
1486#[derive(Debug, Serialize, Deserialize)]
1491pub struct SandboxResponse {
1492 pub action: String,
1493 pub name: String,
1494 pub path: String,
1495 pub message: String,
1496 pub output: Option<String>,
1497 pub exit_code: Option<i32>,
1498}
1499
1500impl CommandResponse for SandboxResponse {
1501 fn command_name(&self) -> &'static str {
1502 "sandbox"
1503 }
1504 fn human_readable(&self) -> String {
1505 let mut out = format!("{}\n", self.message);
1506 if let Some(ref text) = self.output {
1507 if !text.is_empty() {
1508 out.push_str(text);
1509 if !text.ends_with('\n') {
1510 out.push('\n');
1511 }
1512 }
1513 }
1514 out
1515 }
1516}
1517
1518#[derive(Debug, Serialize, Deserialize)]
1523pub struct DidResponse {
1524 pub action: String,
1525 pub did: Option<String>,
1526 pub message: String,
1527 pub details: Option<serde_json::Value>,
1528}
1529
1530impl CommandResponse for DidResponse {
1531 fn command_name(&self) -> &'static str {
1532 "did"
1533 }
1534 fn human_readable(&self) -> String {
1535 let mut out = format!("{}\n", self.message);
1536 if let Some(ref did) = self.did {
1537 out.push_str(&format!(" DID: {}\n", did));
1538 }
1539 if let Some(ref d) = self.details {
1540 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1541 out.push('\n');
1542 }
1543 out
1544 }
1545}
1546
1547#[derive(Debug, Serialize, Deserialize)]
1548pub struct TrustResponse {
1549 pub action: String,
1550 pub did: Option<String>,
1551 pub score: Option<f64>,
1552 pub level: Option<String>,
1553 pub message: String,
1554 pub details: Option<serde_json::Value>,
1555}
1556
1557impl CommandResponse for TrustResponse {
1558 fn command_name(&self) -> &'static str {
1559 "trust"
1560 }
1561 fn human_readable(&self) -> String {
1562 let mut out = format!("{}\n", self.message);
1563 if let Some(ref did) = self.did {
1564 out.push_str(&format!(" Agent: {}\n", did));
1565 }
1566 if let Some(score) = self.score {
1567 out.push_str(&format!(" Score: {:.1}\n", score));
1568 }
1569 if let Some(ref level) = self.level {
1570 out.push_str(&format!(" Level: {}\n", level));
1571 }
1572 if let Some(ref d) = self.details {
1573 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1574 out.push('\n');
1575 }
1576 out
1577 }
1578}
1579
1580#[derive(Debug, Serialize, Deserialize)]
1581pub struct IssueResponse {
1582 pub action: String,
1583 pub id: Option<u64>,
1584 pub message: String,
1585 pub details: Option<serde_json::Value>,
1586}
1587
1588impl CommandResponse for IssueResponse {
1589 fn command_name(&self) -> &'static str {
1590 "issue"
1591 }
1592 fn human_readable(&self) -> String {
1593 let mut out = format!("{}\n", self.message);
1594 if let Some(id) = self.id {
1595 out.push_str(&format!(" Issue #{}\n", id));
1596 }
1597 if let Some(ref d) = self.details {
1598 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1599 out.push('\n');
1600 }
1601 out
1602 }
1603}
1604
1605#[derive(Debug, Serialize, Deserialize)]
1606pub struct PrResponse {
1607 pub action: String,
1608 pub id: Option<u64>,
1609 pub message: String,
1610 pub details: Option<serde_json::Value>,
1611}
1612
1613impl CommandResponse for PrResponse {
1614 fn command_name(&self) -> &'static str {
1615 "pr"
1616 }
1617 fn human_readable(&self) -> String {
1618 let mut out = format!("{}\n", self.message);
1619 if let Some(id) = self.id {
1620 out.push_str(&format!(" PR #{}\n", id));
1621 }
1622 if let Some(ref d) = self.details {
1623 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1624 out.push('\n');
1625 }
1626 out
1627 }
1628}
1629
1630#[derive(Debug, Serialize, Deserialize)]
1631pub struct SubscribeResponse {
1632 pub action: String,
1633 pub subscription_id: Option<String>,
1634 pub message: String,
1635 pub details: Option<serde_json::Value>,
1636}
1637
1638impl CommandResponse for SubscribeResponse {
1639 fn command_name(&self) -> &'static str {
1640 "subscribe"
1641 }
1642 fn human_readable(&self) -> String {
1643 let mut out = format!("{}\n", self.message);
1644 if let Some(ref id) = self.subscription_id {
1645 out.push_str(&format!(" Subscription: {}\n", id));
1646 }
1647 if let Some(ref d) = self.details {
1648 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1649 out.push('\n');
1650 }
1651 out
1652 }
1653}
1654
1655#[derive(Debug, Serialize, Deserialize)]
1656pub struct DelegateResponse {
1657 pub action: String,
1658 pub task_id: Option<String>,
1659 pub message: String,
1660 pub details: Option<serde_json::Value>,
1661}
1662
1663impl CommandResponse for DelegateResponse {
1664 fn command_name(&self) -> &'static str {
1665 "delegate"
1666 }
1667 fn human_readable(&self) -> String {
1668 let mut out = format!("{}\n", self.message);
1669 if let Some(ref id) = self.task_id {
1670 out.push_str(&format!(" Task: {}\n", id));
1671 }
1672 if let Some(ref d) = self.details {
1673 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1674 out.push('\n');
1675 }
1676 out
1677 }
1678}
1679
1680#[derive(Debug, Serialize, Deserialize)]
1681pub struct FederationResponse {
1682 pub action: String,
1683 pub message: String,
1684 pub details: Option<serde_json::Value>,
1685}
1686
1687impl CommandResponse for FederationResponse {
1688 fn command_name(&self) -> &'static str {
1689 "federation"
1690 }
1691 fn human_readable(&self) -> String {
1692 let mut out = format!("{}\n", self.message);
1693 if let Some(ref d) = self.details {
1694 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1695 out.push('\n');
1696 }
1697 out
1698 }
1699}
1700
1701#[derive(Debug, Serialize, Deserialize)]
1702pub struct UcanResponse {
1703 pub action: String,
1704 pub token_cid: Option<String>,
1705 pub message: String,
1706 pub details: Option<serde_json::Value>,
1707}
1708
1709impl CommandResponse for UcanResponse {
1710 fn command_name(&self) -> &'static str {
1711 "ucan"
1712 }
1713 fn human_readable(&self) -> String {
1714 let mut out = format!("{}\n", self.message);
1715 if let Some(ref cid) = self.token_cid {
1716 out.push_str(&format!(" Token CID: {}\n", cid));
1717 }
1718 if let Some(ref d) = self.details {
1719 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1720 out.push('\n');
1721 }
1722 out
1723 }
1724}
1725
1726#[derive(Debug, Serialize, Deserialize)]
1729pub struct IntentResponse {
1730 pub action: String,
1731 pub intent_id: Option<String>,
1732 pub message: String,
1733 pub details: Option<serde_json::Value>,
1734}
1735
1736impl CommandResponse for IntentResponse {
1737 fn command_name(&self) -> &'static str {
1738 "intent"
1739 }
1740 fn human_readable(&self) -> String {
1741 let mut out = format!("{}\n", self.message);
1742 if let Some(ref id) = self.intent_id {
1743 out.push_str(&format!(" Intent: {}\n", id));
1744 }
1745 if let Some(ref d) = self.details {
1746 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1747 out.push('\n');
1748 }
1749 out
1750 }
1751}
1752
1753#[derive(Debug, Serialize, Deserialize)]
1754pub struct ConvergeResponse {
1755 pub converged: bool,
1756 pub strategy: String,
1757 pub intent_id: String,
1758 pub intent_title: String,
1759 pub commit_hash: Option<String>,
1760 pub commits_converged: usize,
1761 pub fast_forward: bool,
1762 pub message: String,
1763 pub details: Option<serde_json::Value>,
1764}
1765
1766impl CommandResponse for ConvergeResponse {
1767 fn command_name(&self) -> &'static str {
1768 "converge"
1769 }
1770 fn human_readable(&self) -> String {
1771 let mut out = format!("{}\n", self.message);
1772 if let Some(ref h) = self.commit_hash {
1773 out.push_str(&format!(" Commit: {}\n", h));
1774 }
1775 out.push_str(&format!(" Strategy: {}\n", self.strategy));
1776 out.push_str(&format!(" Fast-forward: {}\n", self.fast_forward));
1777 if let Some(ref d) = self.details {
1778 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1779 out.push('\n');
1780 }
1781 out
1782 }
1783}
1784
1785#[derive(Debug, Serialize, Deserialize)]
1788pub struct ContentTypeResponse {
1789 pub action: String,
1790 pub content_type_id: Option<String>,
1791 pub message: String,
1792 pub details: Option<serde_json::Value>,
1793}
1794
1795impl CommandResponse for ContentTypeResponse {
1796 fn command_name(&self) -> &'static str {
1797 "content-type"
1798 }
1799 fn human_readable(&self) -> String {
1800 let mut out = format!("{}\n", self.message);
1801 if let Some(ref id) = self.content_type_id {
1802 out.push_str(&format!(" Content-Type: {}\n", id));
1803 }
1804 if let Some(ref d) = self.details {
1805 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1806 out.push('\n');
1807 }
1808 out
1809 }
1810}
1811
1812#[derive(Debug, Serialize, Deserialize)]
1815pub struct DatacenterResponse {
1816 pub action: String,
1817 pub message: String,
1818 pub details: Option<serde_json::Value>,
1819}
1820
1821impl CommandResponse for DatacenterResponse {
1822 fn command_name(&self) -> &'static str {
1823 "datacenter"
1824 }
1825 fn human_readable(&self) -> String {
1826 let mut out = format!("{}\n", self.message);
1827 if let Some(ref d) = self.details {
1828 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1829 out.push('\n');
1830 }
1831 out
1832 }
1833}
1834
1835#[derive(Debug, Serialize, Deserialize)]
1838pub struct AgentProfileResponse {
1839 pub action: String,
1840 pub profile_id: Option<String>,
1841 pub message: String,
1842 pub details: Option<serde_json::Value>,
1843}
1844
1845impl CommandResponse for AgentProfileResponse {
1846 fn command_name(&self) -> &'static str {
1847 "agent-profile"
1848 }
1849 fn human_readable(&self) -> String {
1850 let mut out = format!("{}\n", self.message);
1851 if let Some(ref id) = self.profile_id {
1852 out.push_str(&format!(" Profile: {}\n", id));
1853 }
1854 if let Some(ref d) = self.details {
1855 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1856 out.push('\n');
1857 }
1858 out
1859 }
1860}
1861
1862#[derive(Debug, Serialize, Deserialize)]
1863pub struct AgentResponse {
1864 pub action: String,
1865 pub entries: Option<usize>,
1868 pub idle_timeout_secs: Option<u64>,
1869 pub message: String,
1870}
1871
1872impl CommandResponse for AgentResponse {
1873 fn command_name(&self) -> &'static str {
1874 "agent"
1875 }
1876 fn human_readable(&self) -> String {
1877 format!("{}\n", self.message)
1878 }
1879}