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 RotateKeyResponse {
809 pub objects_rotated: usize,
810 pub refs_rotated: usize,
811}
812
813impl CommandResponse for RotateKeyResponse {
814 fn command_name(&self) -> &'static str {
815 "rotate-key"
816 }
817 fn human_readable(&self) -> String {
818 format!(
819 "Passphrase rotation complete!\n {} objects re-encrypted\n {} refs re-encrypted\n Old passphrase is no longer valid.\n",
820 self.objects_rotated, self.refs_rotated
821 )
822 }
823}
824
825#[derive(Debug, Serialize, Deserialize)]
830pub struct StashEntryInfo {
831 pub index: usize,
832 pub message: String,
833 pub branch: Option<String>,
834 pub timestamp: i64,
835}
836
837#[derive(Debug, Serialize, Deserialize)]
838#[serde(tag = "action")]
839pub enum StashResponse {
840 #[serde(rename = "push")]
841 Push { index: usize, message: String },
842 #[serde(rename = "pop")]
843 Pop { index: usize, message: String },
844 #[serde(rename = "apply")]
845 Apply { index: usize, message: String },
846 #[serde(rename = "list")]
847 List { entries: Vec<StashEntryInfo> },
848 #[serde(rename = "drop")]
849 Drop { index: usize, message: String },
850}
851
852impl CommandResponse for StashResponse {
853 fn command_name(&self) -> &'static str {
854 "stash"
855 }
856 fn human_readable(&self) -> String {
857 match self {
858 StashResponse::Push { index, message } => {
859 format!(
860 "Saved working directory to stash@{{{}}}: {}",
861 index, message
862 )
863 }
864 StashResponse::Pop { index, message } => {
865 format!("Applied and dropped stash@{{{}}}: {}", index, message)
866 }
867 StashResponse::Apply { index, message } => {
868 format!("Applied stash@{{{}}}: {}", index, message)
869 }
870 StashResponse::List { entries } => {
871 if entries.is_empty() {
872 "No stash entries".to_string()
873 } else {
874 entries
875 .iter()
876 .map(|e| format!("stash@{{{}}}: {}", e.index, e.message))
877 .collect::<Vec<_>>()
878 .join("\n")
879 }
880 }
881 StashResponse::Drop { index, message } => {
882 format!("Dropped stash@{{{}}}: {}", index, message)
883 }
884 }
885 }
886}
887
888#[derive(Debug, Serialize, Deserialize)]
889pub struct ResetResponse {
890 pub target: String,
891 pub mode: String,
892 pub message: String,
893}
894
895impl CommandResponse for ResetResponse {
896 fn command_name(&self) -> &'static str {
897 "reset"
898 }
899 fn human_readable(&self) -> String {
900 format!(
901 "HEAD is now at {} ({})\n{}",
902 self.target, self.mode, self.message
903 )
904 }
905}
906
907#[derive(Debug, Serialize, Deserialize)]
908pub struct RevertResponse {
909 pub reverted_commit: String,
910 pub new_commit: String,
911 pub files_changed: usize,
912 pub message: String,
913}
914
915impl CommandResponse for RevertResponse {
916 fn command_name(&self) -> &'static str {
917 "revert"
918 }
919 fn human_readable(&self) -> String {
920 format!(
921 "Reverted {}\nNew commit: {}\n{} file(s) changed\n{}",
922 self.reverted_commit, self.new_commit, self.files_changed, self.message
923 )
924 }
925}
926
927#[derive(Debug, Serialize, Deserialize)]
928pub struct CherryPickResponse {
929 pub source_commit: String,
930 pub new_commit: String,
931 pub files_changed: usize,
932 pub message: String,
933}
934
935impl CommandResponse for CherryPickResponse {
936 fn command_name(&self) -> &'static str {
937 "cherry-pick"
938 }
939 fn human_readable(&self) -> String {
940 format!(
941 "Cherry-picked {}\nNew commit: {}\n{} file(s) changed\n{}",
942 self.source_commit, self.new_commit, self.files_changed, self.message
943 )
944 }
945}
946
947#[derive(Debug, Serialize, Deserialize)]
948pub struct RebaseResponse {
949 pub rebased_commits: usize,
950 pub onto: String,
951 pub branch: String,
952 pub message: String,
953 #[serde(skip_serializing_if = "Option::is_none")]
954 pub todo: Option<serde_json::Value>,
955}
956
957impl CommandResponse for RebaseResponse {
958 fn command_name(&self) -> &'static str {
959 "rebase"
960 }
961 fn human_readable(&self) -> String {
962 let mut out = format!("{}\n", self.message);
963 if self.rebased_commits > 0 {
964 out.push_str(&format!(
965 "Rebased {} commit(s) onto {}\n",
966 self.rebased_commits, self.onto
967 ));
968 }
969 if let Some(ref todo) = self.todo {
970 out.push_str(&format!(
971 "Todo: {}\n",
972 serde_json::to_string_pretty(todo).unwrap_or_default()
973 ));
974 }
975 out
976 }
977}
978
979#[derive(Debug, Serialize, Deserialize)]
980pub struct BlameLineInfo {
981 pub line_number: usize,
982 pub content: String,
983 pub commit_hash: String,
984 pub author: String,
985 pub timestamp: i64,
986}
987
988#[derive(Debug, Serialize, Deserialize)]
989pub struct BlameResponse {
990 pub file: String,
991 pub lines: Vec<BlameLineInfo>,
992}
993
994impl CommandResponse for BlameResponse {
995 fn command_name(&self) -> &'static str {
996 "blame"
997 }
998 fn human_readable(&self) -> String {
999 let mut out = format!("Blame for {}:\n", self.file);
1000 for line in &self.lines {
1001 out.push_str(&format!(
1002 "{} ({} {}) {}\n",
1003 &line.commit_hash[..8.min(line.commit_hash.len())],
1004 line.author,
1005 line.line_number,
1006 line.content
1007 ));
1008 }
1009 out
1010 }
1011}
1012
1013#[derive(Debug, Serialize, Deserialize)]
1014pub struct BisectResponse {
1015 pub action: String,
1016 pub current: Option<String>,
1017 pub remaining: usize,
1018 pub steps: usize,
1019 pub message: String,
1020}
1021
1022impl CommandResponse for BisectResponse {
1023 fn command_name(&self) -> &'static str {
1024 "bisect"
1025 }
1026 fn human_readable(&self) -> String {
1027 let mut out = format!("{}\n", self.message);
1028 if let Some(ref commit) = self.current {
1029 out.push_str(&format!("Current: {}\n", commit));
1030 }
1031 if self.remaining > 0 {
1032 out.push_str(&format!("~{} steps remaining\n", self.steps));
1033 }
1034 out
1035 }
1036}
1037
1038#[derive(Debug, Serialize, Deserialize)]
1039pub struct ReflogEntry {
1040 pub index: usize,
1041 pub old_hash: String,
1042 pub new_hash: String,
1043 pub action: String,
1044 pub message: String,
1045 pub timestamp: i64,
1046}
1047
1048#[derive(Debug, Serialize, Deserialize)]
1049pub struct ReflogResponse {
1050 pub ref_name: String,
1051 pub entries: Vec<ReflogEntry>,
1052}
1053
1054impl CommandResponse for ReflogResponse {
1055 fn command_name(&self) -> &'static str {
1056 "reflog"
1057 }
1058 fn human_readable(&self) -> String {
1059 let mut out = format!("Reflog for {}:\n", self.ref_name);
1060 for entry in &self.entries {
1061 out.push_str(&format!(
1062 "{}@{{{}}} {} -> {} {}: {}\n",
1063 self.ref_name,
1064 entry.index,
1065 &entry.old_hash[..8.min(entry.old_hash.len())],
1066 &entry.new_hash[..8.min(entry.new_hash.len())],
1067 entry.action,
1068 entry.message
1069 ));
1070 }
1071 out
1072 }
1073}
1074
1075#[derive(Debug, Serialize, Deserialize)]
1080pub struct BatchOperationResult {
1081 pub index: usize,
1082 pub command: String,
1083 pub status: String,
1084 pub result: Option<serde_json::Value>,
1085 pub error: Option<String>,
1086}
1087
1088#[derive(Debug, Serialize, Deserialize)]
1089pub struct BatchResponse {
1090 pub total: usize,
1091 pub succeeded: usize,
1092 pub failed: usize,
1093 pub atomic: bool,
1094 pub dry_run: bool,
1095 pub results: Vec<BatchOperationResult>,
1096}
1097
1098impl CommandResponse for BatchResponse {
1099 fn command_name(&self) -> &'static str {
1100 "batch"
1101 }
1102 fn human_readable(&self) -> String {
1103 format!(
1104 "Batch complete: {}/{} succeeded, {} failed{}{}",
1105 self.succeeded,
1106 self.total,
1107 self.failed,
1108 if self.atomic { " (atomic)" } else { "" },
1109 if self.dry_run { " (dry-run)" } else { "" },
1110 )
1111 }
1112}
1113
1114#[derive(Debug, Serialize, Deserialize)]
1115pub struct TransactionResponse {
1116 pub action: String,
1117 pub tx_id: Option<String>,
1118 pub message: String,
1119}
1120
1121impl CommandResponse for TransactionResponse {
1122 fn command_name(&self) -> &'static str {
1123 "transaction"
1124 }
1125 fn human_readable(&self) -> String {
1126 if let Some(ref id) = self.tx_id {
1127 format!(
1128 "Transaction {}: {} [{}]",
1129 self.action,
1130 self.message,
1131 &id[..8.min(id.len())]
1132 )
1133 } else {
1134 format!("Transaction {}: {}", self.action, self.message)
1135 }
1136 }
1137}
1138
1139#[derive(Debug, Serialize, Deserialize)]
1140pub struct SnapshotResponse {
1141 pub hash: String,
1142 pub short_hash: String,
1143 pub tree: String,
1144 pub parent: Option<String>,
1145 pub author: String,
1146 pub message: String,
1147 pub timestamp: i64,
1148 pub files_added: usize,
1149}
1150
1151impl CommandResponse for SnapshotResponse {
1152 fn command_name(&self) -> &'static str {
1153 "snapshot"
1154 }
1155 fn human_readable(&self) -> String {
1156 format!(
1157 "[{}] Snapshot: {}\n {} file(s) captured\n Author: {}",
1158 self.short_hash, self.message, self.files_added, self.author,
1159 )
1160 }
1161}
1162
1163#[derive(Debug, Serialize, Deserialize)]
1164pub struct SearchMatch {
1165 pub file: String,
1166 pub line_number: usize,
1167 pub content: String,
1168 pub commit: Option<String>,
1169 pub match_type: String,
1170}
1171
1172#[derive(Debug, Serialize, Deserialize)]
1173pub struct SearchResponse {
1174 pub query: String,
1175 pub match_type: String,
1176 pub matches: Vec<SearchMatch>,
1177 pub total: usize,
1178}
1179
1180impl CommandResponse for SearchResponse {
1181 fn command_name(&self) -> &'static str {
1182 "search"
1183 }
1184 fn human_readable(&self) -> String {
1185 let mut out = format!("Search '{}': {} result(s)\n", self.query, self.total);
1186 for m in &self.matches {
1187 match m.match_type.as_str() {
1188 "content" => {
1189 out.push_str(&format!(
1190 " {}:{}: {}\n",
1191 m.file,
1192 m.line_number,
1193 m.content.trim()
1194 ));
1195 }
1196 "message" => {
1197 out.push_str(&format!(
1198 " commit {}: {}\n",
1199 m.commit.as_deref().unwrap_or("?"),
1200 m.content.trim()
1201 ));
1202 }
1203 _ => {
1204 out.push_str(&format!(" {}\n", m.content.trim()));
1205 }
1206 }
1207 }
1208 out
1209 }
1210}
1211
1212#[derive(Debug, Serialize, Deserialize)]
1213pub struct WatchEvent {
1214 pub event_type: String,
1215 pub path: String,
1216 pub timestamp: i64,
1217}
1218
1219#[derive(Debug, Serialize, Deserialize)]
1220pub struct WatchResponse {
1221 pub events_emitted: usize,
1222 pub message: String,
1223}
1224
1225impl CommandResponse for WatchResponse {
1226 fn command_name(&self) -> &'static str {
1227 "watch"
1228 }
1229 fn human_readable(&self) -> String {
1230 self.message.clone()
1231 }
1232}
1233
1234#[derive(Debug, Serialize, Deserialize)]
1235pub struct VerifyResult {
1236 pub check: String,
1237 pub status: String,
1238 pub details: Option<String>,
1239}
1240
1241#[derive(Debug, Serialize, Deserialize)]
1242pub struct VerifyResponse {
1243 pub valid: bool,
1244 pub checks: Vec<VerifyResult>,
1245 pub objects_checked: usize,
1246 pub refs_checked: usize,
1247 pub message: String,
1248}
1249
1250impl CommandResponse for VerifyResponse {
1251 fn command_name(&self) -> &'static str {
1252 "verify"
1253 }
1254 fn human_readable(&self) -> String {
1255 let mut out = format!("{}\n", self.message);
1256 for check in &self.checks {
1257 let icon = if check.status == "ok" { "+" } else { "!" };
1258 out.push_str(&format!(" [{}] {}", icon, check.check));
1259 if let Some(ref details) = check.details {
1260 out.push_str(&format!(": {}", details));
1261 }
1262 out.push('\n');
1263 }
1264 out.push_str(&format!(
1265 " {} objects, {} refs checked\n",
1266 self.objects_checked, self.refs_checked
1267 ));
1268 out
1269 }
1270}
1271
1272#[derive(Debug, Serialize, Deserialize)]
1277pub struct ServeResponse {
1278 pub message: String,
1279}
1280
1281impl CommandResponse for ServeResponse {
1282 fn command_name(&self) -> &'static str {
1283 "serve"
1284 }
1285 fn human_readable(&self) -> String {
1286 self.message.clone()
1287 }
1288}
1289
1290#[derive(Debug, Serialize, Deserialize)]
1291pub struct McpServeResponse {
1292 pub transport: String,
1293 pub message: String,
1294}
1295
1296impl CommandResponse for McpServeResponse {
1297 fn command_name(&self) -> &'static str {
1298 "mcp-serve"
1299 }
1300 fn human_readable(&self) -> String {
1301 format!("[{}] {}", self.transport, self.message)
1302 }
1303}
1304
1305#[derive(Debug, Serialize, Deserialize)]
1306pub struct SwarmResponse {
1307 pub action: String,
1308 pub agent_id: Option<String>,
1309 pub message: String,
1310 pub details: Option<serde_json::Value>,
1311}
1312
1313impl CommandResponse for SwarmResponse {
1314 fn command_name(&self) -> &'static str {
1315 "swarm"
1316 }
1317 fn human_readable(&self) -> String {
1318 let mut out = format!("Swarm {}: {}\n", self.action, self.message);
1319 if let Some(ref details) = self.details {
1320 out.push_str(&serde_json::to_string_pretty(details).unwrap_or_default());
1321 }
1322 out
1323 }
1324}
1325
1326#[derive(Debug, Serialize, Deserialize)]
1327pub struct OntologyResponse {
1328 pub ontology: serde_json::Value,
1329}
1330
1331impl CommandResponse for OntologyResponse {
1332 fn command_name(&self) -> &'static str {
1333 "ontology"
1334 }
1335 fn human_readable(&self) -> String {
1336 serde_json::to_string_pretty(&self.ontology).unwrap_or_else(|_| "{}".to_string())
1337 }
1338}
1339
1340#[derive(Debug, Serialize, Deserialize)]
1341pub struct SchemaResponse {
1342 pub schema: serde_json::Value,
1343}
1344
1345impl CommandResponse for SchemaResponse {
1346 fn command_name(&self) -> &'static str {
1347 "schema"
1348 }
1349 fn human_readable(&self) -> String {
1350 serde_json::to_string_pretty(&self.schema).unwrap_or_else(|_| "{}".to_string())
1351 }
1352}
1353
1354#[derive(Debug, Serialize, Deserialize)]
1359pub struct ImportGitResponse {
1360 pub source: String,
1361 pub objects_imported: u64,
1362 pub refs_imported: u64,
1363 pub hash_mapping_count: usize,
1364 pub message: String,
1365}
1366
1367impl CommandResponse for ImportGitResponse {
1368 fn command_name(&self) -> &'static str {
1369 "import-git"
1370 }
1371 fn human_readable(&self) -> String {
1372 format!(
1373 "{}\n Objects imported: {}\n Refs imported: {}\n Hash mappings: {}",
1374 self.message, self.objects_imported, self.refs_imported, self.hash_mapping_count
1375 )
1376 }
1377}
1378
1379#[derive(Debug, Serialize, Deserialize)]
1380pub struct ExportGitResponse {
1381 pub destination: String,
1382 pub objects_exported: u64,
1383 pub refs_exported: u64,
1384 pub message: String,
1385}
1386
1387impl CommandResponse for ExportGitResponse {
1388 fn command_name(&self) -> &'static str {
1389 "export-git"
1390 }
1391 fn human_readable(&self) -> String {
1392 format!(
1393 "{}\n Objects exported: {}\n Refs exported: {}",
1394 self.message, self.objects_exported, self.refs_exported
1395 )
1396 }
1397}
1398
1399#[derive(Debug, Serialize, Deserialize)]
1404pub struct GcResponse {
1405 pub objects_packed: u64,
1406 pub packs_created: u64,
1407 pub loose_removed: u64,
1408 pub bytes_saved: u64,
1409 pub message: String,
1410}
1411
1412impl CommandResponse for GcResponse {
1413 fn command_name(&self) -> &'static str {
1414 "gc"
1415 }
1416 fn human_readable(&self) -> String {
1417 format!(
1418 "{}\n Objects packed: {}\n Packs created: {}\n Loose removed: {}\n Bytes saved: {}",
1419 self.message,
1420 self.objects_packed,
1421 self.packs_created,
1422 self.loose_removed,
1423 self.bytes_saved
1424 )
1425 }
1426}
1427
1428#[derive(Debug, Serialize, Deserialize)]
1429pub struct LfsTrackResponse {
1430 pub patterns: Vec<String>,
1431 pub message: String,
1432}
1433
1434impl CommandResponse for LfsTrackResponse {
1435 fn command_name(&self) -> &'static str {
1436 "lfs-track"
1437 }
1438 fn human_readable(&self) -> String {
1439 let mut out = format!("{}\n Tracked patterns:\n", self.message);
1440 for pat in &self.patterns {
1441 out.push_str(&format!(" {}\n", pat));
1442 }
1443 out
1444 }
1445}
1446
1447#[derive(Debug, Serialize, Deserialize)]
1448pub struct LfsMigrateResponse {
1449 pub files_migrated: u64,
1450 pub bytes_saved: u64,
1451 pub message: String,
1452}
1453
1454impl CommandResponse for LfsMigrateResponse {
1455 fn command_name(&self) -> &'static str {
1456 "lfs-migrate"
1457 }
1458 fn human_readable(&self) -> String {
1459 format!(
1460 "{}\n Files migrated: {}\n Bytes saved: {}",
1461 self.message, self.files_migrated, self.bytes_saved
1462 )
1463 }
1464}
1465
1466#[derive(Debug, Serialize, Deserialize)]
1471pub struct SandboxResponse {
1472 pub action: String,
1473 pub name: String,
1474 pub path: String,
1475 pub message: String,
1476 pub output: Option<String>,
1477 pub exit_code: Option<i32>,
1478}
1479
1480impl CommandResponse for SandboxResponse {
1481 fn command_name(&self) -> &'static str {
1482 "sandbox"
1483 }
1484 fn human_readable(&self) -> String {
1485 let mut out = format!("{}\n", self.message);
1486 if let Some(ref text) = self.output {
1487 if !text.is_empty() {
1488 out.push_str(text);
1489 if !text.ends_with('\n') {
1490 out.push('\n');
1491 }
1492 }
1493 }
1494 out
1495 }
1496}
1497
1498#[derive(Debug, Serialize, Deserialize)]
1503pub struct DidResponse {
1504 pub action: String,
1505 pub did: Option<String>,
1506 pub message: String,
1507 pub details: Option<serde_json::Value>,
1508}
1509
1510impl CommandResponse for DidResponse {
1511 fn command_name(&self) -> &'static str {
1512 "did"
1513 }
1514 fn human_readable(&self) -> String {
1515 let mut out = format!("{}\n", self.message);
1516 if let Some(ref did) = self.did {
1517 out.push_str(&format!(" DID: {}\n", did));
1518 }
1519 if let Some(ref d) = self.details {
1520 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1521 out.push('\n');
1522 }
1523 out
1524 }
1525}
1526
1527#[derive(Debug, Serialize, Deserialize)]
1528pub struct TrustResponse {
1529 pub action: String,
1530 pub did: Option<String>,
1531 pub score: Option<f64>,
1532 pub level: Option<String>,
1533 pub message: String,
1534 pub details: Option<serde_json::Value>,
1535}
1536
1537impl CommandResponse for TrustResponse {
1538 fn command_name(&self) -> &'static str {
1539 "trust"
1540 }
1541 fn human_readable(&self) -> String {
1542 let mut out = format!("{}\n", self.message);
1543 if let Some(ref did) = self.did {
1544 out.push_str(&format!(" Agent: {}\n", did));
1545 }
1546 if let Some(score) = self.score {
1547 out.push_str(&format!(" Score: {:.1}\n", score));
1548 }
1549 if let Some(ref level) = self.level {
1550 out.push_str(&format!(" Level: {}\n", level));
1551 }
1552 if let Some(ref d) = self.details {
1553 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1554 out.push('\n');
1555 }
1556 out
1557 }
1558}
1559
1560#[derive(Debug, Serialize, Deserialize)]
1561pub struct IssueResponse {
1562 pub action: String,
1563 pub id: Option<u64>,
1564 pub message: String,
1565 pub details: Option<serde_json::Value>,
1566}
1567
1568impl CommandResponse for IssueResponse {
1569 fn command_name(&self) -> &'static str {
1570 "issue"
1571 }
1572 fn human_readable(&self) -> String {
1573 let mut out = format!("{}\n", self.message);
1574 if let Some(id) = self.id {
1575 out.push_str(&format!(" Issue #{}\n", id));
1576 }
1577 if let Some(ref d) = self.details {
1578 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1579 out.push('\n');
1580 }
1581 out
1582 }
1583}
1584
1585#[derive(Debug, Serialize, Deserialize)]
1586pub struct PrResponse {
1587 pub action: String,
1588 pub id: Option<u64>,
1589 pub message: String,
1590 pub details: Option<serde_json::Value>,
1591}
1592
1593impl CommandResponse for PrResponse {
1594 fn command_name(&self) -> &'static str {
1595 "pr"
1596 }
1597 fn human_readable(&self) -> String {
1598 let mut out = format!("{}\n", self.message);
1599 if let Some(id) = self.id {
1600 out.push_str(&format!(" PR #{}\n", id));
1601 }
1602 if let Some(ref d) = self.details {
1603 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1604 out.push('\n');
1605 }
1606 out
1607 }
1608}
1609
1610#[derive(Debug, Serialize, Deserialize)]
1611pub struct SubscribeResponse {
1612 pub action: String,
1613 pub subscription_id: Option<String>,
1614 pub message: String,
1615 pub details: Option<serde_json::Value>,
1616}
1617
1618impl CommandResponse for SubscribeResponse {
1619 fn command_name(&self) -> &'static str {
1620 "subscribe"
1621 }
1622 fn human_readable(&self) -> String {
1623 let mut out = format!("{}\n", self.message);
1624 if let Some(ref id) = self.subscription_id {
1625 out.push_str(&format!(" Subscription: {}\n", id));
1626 }
1627 if let Some(ref d) = self.details {
1628 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1629 out.push('\n');
1630 }
1631 out
1632 }
1633}
1634
1635#[derive(Debug, Serialize, Deserialize)]
1636pub struct DelegateResponse {
1637 pub action: String,
1638 pub task_id: Option<String>,
1639 pub message: String,
1640 pub details: Option<serde_json::Value>,
1641}
1642
1643impl CommandResponse for DelegateResponse {
1644 fn command_name(&self) -> &'static str {
1645 "delegate"
1646 }
1647 fn human_readable(&self) -> String {
1648 let mut out = format!("{}\n", self.message);
1649 if let Some(ref id) = self.task_id {
1650 out.push_str(&format!(" Task: {}\n", id));
1651 }
1652 if let Some(ref d) = self.details {
1653 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1654 out.push('\n');
1655 }
1656 out
1657 }
1658}
1659
1660#[derive(Debug, Serialize, Deserialize)]
1661pub struct FederationResponse {
1662 pub action: String,
1663 pub message: String,
1664 pub details: Option<serde_json::Value>,
1665}
1666
1667impl CommandResponse for FederationResponse {
1668 fn command_name(&self) -> &'static str {
1669 "federation"
1670 }
1671 fn human_readable(&self) -> String {
1672 let mut out = format!("{}\n", self.message);
1673 if let Some(ref d) = self.details {
1674 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1675 out.push('\n');
1676 }
1677 out
1678 }
1679}
1680
1681#[derive(Debug, Serialize, Deserialize)]
1682pub struct UcanResponse {
1683 pub action: String,
1684 pub token_cid: Option<String>,
1685 pub message: String,
1686 pub details: Option<serde_json::Value>,
1687}
1688
1689impl CommandResponse for UcanResponse {
1690 fn command_name(&self) -> &'static str {
1691 "ucan"
1692 }
1693 fn human_readable(&self) -> String {
1694 let mut out = format!("{}\n", self.message);
1695 if let Some(ref cid) = self.token_cid {
1696 out.push_str(&format!(" Token CID: {}\n", cid));
1697 }
1698 if let Some(ref d) = self.details {
1699 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1700 out.push('\n');
1701 }
1702 out
1703 }
1704}
1705
1706#[derive(Debug, Serialize, Deserialize)]
1709pub struct IntentResponse {
1710 pub action: String,
1711 pub intent_id: Option<String>,
1712 pub message: String,
1713 pub details: Option<serde_json::Value>,
1714}
1715
1716impl CommandResponse for IntentResponse {
1717 fn command_name(&self) -> &'static str {
1718 "intent"
1719 }
1720 fn human_readable(&self) -> String {
1721 let mut out = format!("{}\n", self.message);
1722 if let Some(ref id) = self.intent_id {
1723 out.push_str(&format!(" Intent: {}\n", id));
1724 }
1725 if let Some(ref d) = self.details {
1726 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1727 out.push('\n');
1728 }
1729 out
1730 }
1731}
1732
1733#[derive(Debug, Serialize, Deserialize)]
1734pub struct ConvergeResponse {
1735 pub converged: bool,
1736 pub strategy: String,
1737 pub intent_id: String,
1738 pub intent_title: String,
1739 pub commit_hash: Option<String>,
1740 pub commits_converged: usize,
1741 pub fast_forward: bool,
1742 pub message: String,
1743 pub details: Option<serde_json::Value>,
1744}
1745
1746impl CommandResponse for ConvergeResponse {
1747 fn command_name(&self) -> &'static str {
1748 "converge"
1749 }
1750 fn human_readable(&self) -> String {
1751 let mut out = format!("{}\n", self.message);
1752 if let Some(ref h) = self.commit_hash {
1753 out.push_str(&format!(" Commit: {}\n", h));
1754 }
1755 out.push_str(&format!(" Strategy: {}\n", self.strategy));
1756 out.push_str(&format!(" Fast-forward: {}\n", self.fast_forward));
1757 if let Some(ref d) = self.details {
1758 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1759 out.push('\n');
1760 }
1761 out
1762 }
1763}
1764
1765#[derive(Debug, Serialize, Deserialize)]
1768pub struct ContentTypeResponse {
1769 pub action: String,
1770 pub content_type_id: Option<String>,
1771 pub message: String,
1772 pub details: Option<serde_json::Value>,
1773}
1774
1775impl CommandResponse for ContentTypeResponse {
1776 fn command_name(&self) -> &'static str {
1777 "content-type"
1778 }
1779 fn human_readable(&self) -> String {
1780 let mut out = format!("{}\n", self.message);
1781 if let Some(ref id) = self.content_type_id {
1782 out.push_str(&format!(" Content-Type: {}\n", id));
1783 }
1784 if let Some(ref d) = self.details {
1785 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1786 out.push('\n');
1787 }
1788 out
1789 }
1790}
1791
1792#[derive(Debug, Serialize, Deserialize)]
1795pub struct DatacenterResponse {
1796 pub action: String,
1797 pub message: String,
1798 pub details: Option<serde_json::Value>,
1799}
1800
1801impl CommandResponse for DatacenterResponse {
1802 fn command_name(&self) -> &'static str {
1803 "datacenter"
1804 }
1805 fn human_readable(&self) -> String {
1806 let mut out = format!("{}\n", self.message);
1807 if let Some(ref d) = self.details {
1808 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1809 out.push('\n');
1810 }
1811 out
1812 }
1813}
1814
1815#[derive(Debug, Serialize, Deserialize)]
1818pub struct AgentProfileResponse {
1819 pub action: String,
1820 pub profile_id: Option<String>,
1821 pub message: String,
1822 pub details: Option<serde_json::Value>,
1823}
1824
1825impl CommandResponse for AgentProfileResponse {
1826 fn command_name(&self) -> &'static str {
1827 "agent-profile"
1828 }
1829 fn human_readable(&self) -> String {
1830 let mut out = format!("{}\n", self.message);
1831 if let Some(ref id) = self.profile_id {
1832 out.push_str(&format!(" Profile: {}\n", id));
1833 }
1834 if let Some(ref d) = self.details {
1835 out.push_str(&serde_json::to_string_pretty(d).unwrap_or_default());
1836 out.push('\n');
1837 }
1838 out
1839 }
1840}