Skip to main content

hypha/output/
mod.rs

1//! Typed result structs returned by hypha library functions.
2//!
3//! Each command gets its own output type so callers don't need to
4//! parse `serde_json::Value`.  The CLI `handle_*` wrappers serialize
5//! these into Agent-First Data JSON before printing.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10// ───────────────────────── sense ─────────────────────────
11
12/// Result of [`crate::visitor::sense`].
13///
14/// `data` is `{"mycelium": manifest}` or `{"spore": manifest}` depending on
15/// the URI content type.  `trace` carries resolution metadata (DNS, caching,
16/// signature verification).
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct SenseOutput {
19    pub uri: String,
20    /// Resolved content — a mycelium manifest or spore manifest.
21    pub data: Value,
22    /// Resolution metadata: DNS, cmn.json caching, signature verification.
23    pub trace: Value,
24}
25
26// ───────────────────────── search ────────────────────────
27
28/// Result of [`crate::visitor::search`] / [`crate::visitor::search_with_bond`].
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SearchOutput {
31    pub query: String,
32    pub synapse: String,
33    pub count: usize,
34    pub results: Vec<Value>,
35}
36
37// ───────────────────────── taste ─────────────────────────
38
39/// Result of `taste` (download mode — no verdict supplied).
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct TasteDownloadOutput {
42    pub uri: String,
43    pub cache_path: String,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub name: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub synopsis: Option<String>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub parent: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub taste: Option<TasteVerdict>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub remote_tastes: Option<Value>,
54}
55
56/// Embedded taste verdict info.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct TasteVerdict {
59    pub verdict: substrate::TasteVerdict,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub notes: Option<String>,
62    pub tasted_at_epoch_ms: u64,
63}
64
65/// Result of `taste --verdict <verdict>` (record mode).
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct TasteRecordOutput {
68    pub uri: String,
69    pub verdict: substrate::TasteVerdict,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub notes: Option<String>,
72    pub tasted_at_epoch_ms: u64,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub shared: Option<bool>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub synapse: Option<String>,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub share_error: Option<String>,
79}
80
81/// Unified taste output — the library returns one of these.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(untagged)]
84pub enum TasteOutput {
85    Download(TasteDownloadOutput),
86    Record(TasteRecordOutput),
87}
88
89// ───────────────────────── spawn ─────────────────────────
90
91/// Result of a successful `spawn`.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct SpawnOutput {
94    pub uri: String,
95    pub name: String,
96    pub path: String,
97    pub source_type: String,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub vcs: Option<String>,
100}
101
102// ───────────────────────── grow ──────────────────────────
103
104/// Result of a successful `grow`.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(tag = "status")]
107pub enum GrowOutput {
108    #[serde(rename = "updated")]
109    Updated {
110        uri: String,
111        old_hash: String,
112        new_hash: String,
113        method: String,
114        path: String,
115    },
116    #[serde(rename = "up_to_date")]
117    UpToDate { uri: String, hash: String },
118}
119
120// ───────────────────────── bond ──────────────────────────
121
122/// A single bonded entry.
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub struct BondedRef {
125    pub uri: String,
126    pub relation: substrate::BondRelation,
127    pub status: String,
128}
129
130/// Result of `bond` (default mode).
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct BondOutput {
133    pub bonded: Vec<BondedRef>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub message: Option<String>,
136}
137
138/// A single bond status entry.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct BondStatusRef {
141    pub uri: String,
142    pub relation: substrate::BondRelation,
143    /// `true`, `false`, or `"excluded"` for spawned_from/absorbed_from.
144    pub bonded: Value,
145}
146
147/// Result of `bond --status`.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct BondStatusOutput {
150    pub bonds: Vec<BondStatusRef>,
151}
152
153/// Result of `bond --clean`.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct BondCleanOutput {
156    pub cleaned: Vec<String>,
157}
158
159/// A bond ref with its taste status, returned when some refs are not tasted.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct BondTasteRef {
162    pub uri: String,
163    pub relation: substrate::BondRelation,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub id: Option<String>,
166    /// `"safe"`, `"rotten"`, `"toxic"`, or `"not_tasted"`.
167    pub taste: String,
168}
169
170/// Returned when bond cannot proceed because some refs are not tasted.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct BondTasteRequired {
173    pub refs: Vec<BondTasteRef>,
174}
175
176/// Unified bond result.
177#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(untagged)]
179pub enum BondResult {
180    Bond(BondOutput),
181    Status(BondStatusOutput),
182    Clean(BondCleanOutput),
183    TasteRequired(BondTasteRequired),
184}
185
186// ───────────────────────── bonds (ancestors / lineage) ────
187
188/// A node in the bond graph (ancestor or descendant).
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct LineageNode {
191    pub uri: String,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub domain: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub depth: Option<u32>,
196}
197
198/// Result of `bonds`.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct BondsOutput {
201    pub uri: String,
202    pub hash: String,
203    pub synapse: String,
204    pub direction: String,
205    pub max_depth: u32,
206    pub max_depth_reached: bool,
207    pub count: usize,
208    pub bonds: Vec<Value>,
209}
210
211// ───────────────────────── absorb ────────────────────────
212
213/// A single absorb source.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct AbsorbSourceInfo {
216    pub uri: String,
217    pub hash: String,
218    pub name: String,
219    pub path: String,
220}
221
222/// Result of `absorb`.
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct AbsorbOutput {
225    pub sources: Vec<AbsorbSourceInfo>,
226    pub prompt_path: String,
227}