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 cmn_url: 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_url: String,
33    pub result_count: usize,
34    pub results: Vec<SearchResult>,
35}
36
37/// One Hypha-owned search result adapted from the Synapse wire response.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SearchResult {
40    pub cmn_url: String,
41    pub domain: String,
42    pub name: String,
43    pub synopsis: String,
44    pub license: String,
45    pub intent: Vec<String>,
46    pub relevance: f32,
47}
48
49// ───────────────────────── taste ─────────────────────────
50
51/// Result of `taste` (download mode — no verdict supplied).
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TasteDownloadOutput {
54    pub cmn_url: String,
55    pub cache_path: String,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub name: Option<String>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub synopsis: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub parent_cmn_url: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub taste: Option<TasteVerdict>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub remote_tastes: Option<Value>,
66}
67
68/// Embedded taste verdict info.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct TasteVerdict {
71    pub verdict: substrate::TasteVerdict,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub notes: Option<String>,
74    pub tasted_at_epoch_ms: u64,
75}
76
77/// Result of `taste --verdict <verdict>` (record mode).
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct TasteRecordOutput {
80    pub cmn_url: String,
81    pub verdict: substrate::TasteVerdict,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub notes: Option<String>,
84    pub tasted_at_epoch_ms: u64,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub shared: Option<bool>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub synapse_url: Option<String>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub share_error: Option<String>,
91}
92
93/// Unified taste output — the library returns one of these.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(untagged)]
96pub enum TasteOutput {
97    Download(TasteDownloadOutput),
98    Record(TasteRecordOutput),
99}
100
101// ───────────────────────── spawn ─────────────────────────
102
103/// Result of a successful `spawn`.
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct SpawnOutput {
106    pub cmn_url: String,
107    pub name: String,
108    pub path: String,
109    pub source_type: String,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub vcs: Option<String>,
112}
113
114// ───────────────────────── grow ──────────────────────────
115
116/// Result of a successful `grow`.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(tag = "status")]
119pub enum GrowOutput {
120    #[serde(rename = "updated")]
121    Updated {
122        cmn_url: String,
123        old_hash: String,
124        new_hash: String,
125        method: String,
126        path: String,
127    },
128    #[serde(rename = "up_to_date")]
129    UpToDate { cmn_url: String, hash: String },
130}
131
132// ───────────────────────── bond ──────────────────────────
133
134/// A single bonded entry.
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub struct BondedRef {
137    pub cmn_url: String,
138    pub relation: substrate::BondRelation,
139    pub status: String,
140}
141
142/// Result of `bond` (default mode).
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct BondOutput {
145    pub bonded: Vec<BondedRef>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub message: Option<String>,
148}
149
150/// A single bond status entry.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct BondStatusRef {
153    pub cmn_url: String,
154    pub relation: substrate::BondRelation,
155    /// `true`, `false`, or `"excluded"` for spawned_from/absorbed_from.
156    pub bonded: Value,
157}
158
159/// Result of `bond --status`.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct BondStatusOutput {
162    pub bonds: Vec<BondStatusRef>,
163}
164
165/// Result of `bond --clean`.
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct BondCleanOutput {
168    pub cleaned: Vec<String>,
169}
170
171/// A bond ref with its taste status, returned when some refs are not tasted.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct BondTasteRef {
174    pub cmn_url: String,
175    pub relation: substrate::BondRelation,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub id: Option<String>,
178    /// `"safe"`, `"rotten"`, `"toxic"`, or `"not_tasted"`.
179    pub taste: String,
180}
181
182/// Returned when bond cannot proceed because some refs are not tasted.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct BondTasteRequired {
185    pub refs: Vec<BondTasteRef>,
186}
187
188/// Unified bond result.
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(untagged)]
191pub enum BondResult {
192    Bond(BondOutput),
193    Status(BondStatusOutput),
194    Clean(BondCleanOutput),
195    TasteRequired(BondTasteRequired),
196}
197
198// ───────────────────────── bonds (ancestors / lineage) ────
199
200/// A node in the bond graph (ancestor or descendant).
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct LineageNode {
203    pub cmn_url: String,
204    pub domain: String,
205    pub name: String,
206    pub synopsis: String,
207    pub license: String,
208    pub intent: Vec<String>,
209    pub relation: substrate::BondRelation,
210}
211
212/// Result of `bonds`.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct BondsOutput {
215    pub cmn_url: String,
216    pub hash: String,
217    pub synapse_url: String,
218    pub direction: String,
219    pub max_depth: u32,
220    pub max_depth_reached: bool,
221    pub bond_count: usize,
222    pub bonds: Vec<LineageNode>,
223}
224
225// ───────────────────────── absorb ────────────────────────
226
227/// A single absorb source.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct AbsorbSourceInfo {
230    pub cmn_url: String,
231    pub hash: String,
232    pub name: String,
233    pub path: String,
234}
235
236/// Result of `absorb`.
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct AbsorbOutput {
239    pub sources: Vec<AbsorbSourceInfo>,
240    pub prompt_path: String,
241}