1use crate::error::AppError;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::io::{self, Write};
5
6pub const CONTRACT: u8 = 6;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Meta {
10 pub contract: u8,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub file: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub agent_source: Option<String>,
15 #[serde(default, skip_serializing_if = "Vec::is_empty")]
16 pub warnings: Vec<String>,
17}
18
19impl Meta {
20 pub fn new() -> Self {
21 Self::default()
22 }
23}
24
25impl Default for Meta {
26 fn default() -> Self {
27 Self {
28 contract: CONTRACT,
29 file: None,
30 agent_source: None,
31 warnings: Vec::new(),
32 }
33 }
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct SuccessEnvelope<T> {
38 pub ok: bool,
39 pub data: T,
40 pub meta: Meta,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
44pub struct ErrorEnvelope {
45 pub ok: bool,
46 pub error: ErrorBody,
47 pub meta: Meta,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51pub struct ErrorBody {
52 pub code: String,
53 pub message: String,
54 pub details: Value,
55 pub retryable: bool,
56 pub suggested_fix: String,
57}
58
59#[cfg(unix)]
60pub fn stdout_writer() -> io::Result<Box<dyn Write>> {
61 use std::os::fd::AsFd;
62
63 let stdout = io::stdout();
64 let stdout = std::fs::File::from(stdout.as_fd().try_clone_to_owned()?);
65 Ok(Box::new(io::BufWriter::new(stdout)))
66}
67
68#[cfg(windows)]
69pub fn stdout_writer() -> io::Result<Box<dyn Write>> {
70 use std::io::IsTerminal;
71 use std::os::windows::io::AsHandle;
72
73 let stdout = io::stdout();
74 if stdout.is_terminal() {
79 return Ok(Box::new(io::BufWriter::new(stdout)));
80 }
81 let stdout = std::fs::File::from(stdout.as_handle().try_clone_to_owned()?);
82 Ok(Box::new(io::BufWriter::new(stdout)))
83}
84
85#[cfg(not(any(unix, windows)))]
86pub fn stdout_writer() -> io::Result<Box<dyn Write>> {
87 Ok(Box::new(io::BufWriter::new(io::stdout())))
88}
89
90pub fn write_success<T: Serialize>(data: T, pretty: bool, meta: Meta) -> io::Result<()> {
91 let envelope = SuccessEnvelope {
92 ok: true,
93 data,
94 meta,
95 };
96 let mut output = stdout_writer()?;
97 if pretty {
98 serde_json::to_writer_pretty(&mut output, &envelope)?;
99 } else {
100 serde_json::to_writer(&mut output, &envelope)?;
101 }
102 writeln!(output)?;
103 output.flush()
104}
105
106pub fn collapse_markdown_text(text: &str) -> String {
107 text.split_whitespace().collect::<Vec<_>>().join(" ")
108}
109
110pub fn write_error(error: &AppError) -> i32 {
111 let envelope = ErrorEnvelope {
112 ok: false,
113 error: ErrorBody {
114 code: error.code.into(),
115 message: error.message.clone(),
116 details: error.details.clone(),
117 retryable: error.retryable,
118 suggested_fix: error.suggested_fix.clone(),
119 },
120 meta: Meta::new(),
121 };
122 let mut output = io::BufWriter::new(io::stderr().lock());
123 let _ = serde_json::to_writer(&mut output, &envelope);
125 let _ = writeln!(output);
126 error.exit_code
127}