1use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
7use serde::Serialize;
8
9mod run;
10
11pub use run::execute;
12
13#[derive(Parser)]
14#[command(name = "hypha")]
15#[command(version)]
16#[command(about = "CMN Client - A bio-digital extension for Visitors to release and absorb Spores")]
17#[command(disable_help_subcommand = true)]
18#[command(after_long_help = concat!(
19 "All output follows Agent-First Data format:\n",
20 " {\"code\": \"ok\", \"result\": {...}, \"trace\": {...}}\n",
21 "\n",
22 "Quick start (try with cmn.dev):\n",
23 " hypha sense cmn://cmn.dev\n",
24 " hypha sense cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
25 " hypha spawn cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
26 " hypha cache list\n",
27 "\n",
28 "More help:\n",
29 " hypha <command> --help Show one command layer\n",
30 " hypha --help --recursive Expand every command and flag\n",
31 " hypha --help --recursive --output markdown\n",
32 " Generate recursive Markdown reference",
33))]
34pub struct Cli {
35 #[arg(short, long, default_value = "json", global = true)]
37 pub output: String,
38
39 #[arg(long, value_delimiter = ',', global = true)]
41 pub log: Vec<String>,
42
43 #[command(subcommand)]
44 pub command: Commands,
45}
46
47#[derive(Subcommand, Serialize)]
48#[serde(tag = "command", rename_all = "snake_case")]
49pub enum Commands {
50 #[command(after_long_help = concat!(
55 "URI types:\n",
56 " cmn://DOMAIN List all spores on a site\n",
57 " cmn://DOMAIN/HASH View a specific spore\n",
58 " cmn://DOMAIN --id SPORE_ID View latest spore with id from a site\n",
59 "\n",
60 "Examples:\n",
61 " hypha sense cmn://cmn.dev\n",
62 " hypha sense cmn://cmn.dev --id cmn-spec\n",
63 " hypha sense cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
64 " hypha sense cmn://cmn.dev -o yaml",
65 ))]
66 Sense {
67 uri: String,
69 #[arg(long)]
71 id: Option<String>,
72 },
73
74 #[command(after_long_help = concat!(
76 "Without --verdict: downloads the spore for local review.\n",
77 "With --verdict: records a verdict (sweet, fresh, safe, rotten, toxic).\n",
78 "\n",
79 "Examples:\n",
80 " hypha taste cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
81 " hypha taste cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
82 " --verdict safe --notes \"Reviewed: clean code\"\n",
83 " hypha taste cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
84 " --verdict safe --domain cmn.dev --synapse https://synapse.cmn.dev",
85 ))]
86 Taste {
87 uri: String,
89 #[arg(long, value_name = "VERDICT")]
91 verdict: Option<substrate::TasteVerdict>,
92 #[arg(long)]
94 notes: Option<String>,
95 #[arg(long)]
97 synapse: Option<String>,
98 #[arg(long)]
100 synapse_token_secret: Option<String>,
101 #[arg(long)]
103 domain: Option<String>,
104 },
105
106 #[command(after_long_help = concat!(
108 "Distribution sources (auto-detected):\n",
109 " archive Download .tar.zst archive (default, fastest)\n",
110 " git Clone from dist.git URL if available\n",
111 "\n",
112 "Examples:\n",
113 " hypha spawn cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
114 " hypha spawn cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
115 " my-project --vcs git\n",
116 " hypha spawn cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
117 " --dist git",
118 ))]
119 Spawn {
120 uri: String,
122 directory: Option<String>,
124 #[arg(long, value_enum, value_name = "TYPE")]
126 vcs: Option<VcsArg>,
127 #[arg(long, value_enum, value_name = "SOURCE")]
129 dist: Option<DistArg>,
130 #[arg(long)]
132 bond: bool,
133 },
134
135 #[command(after_long_help = "\
137Run inside a previously spawned directory to update it.
138Uses Synapse lineage to discover newer versions from the same publisher.
139
140If local files have been modified (git dirty or tree hash mismatch),
141grow refuses to overwrite them and shows the cache path for manual merge.
142
143Examples:
144 hypha grow
145 hypha grow --synapse synapse.cmn.dev
146 hypha grow --dist git
147 hypha grow --dist archive
148 hypha grow --bond --synapse synapse.cmn.dev")]
149 Grow {
150 #[arg(long, value_enum, value_name = "SOURCE")]
152 dist: Option<DistArg>,
153 #[arg(long)]
155 synapse: Option<String>,
156 #[arg(long)]
158 synapse_token_secret: Option<String>,
159 #[arg(long)]
161 bond: bool,
162 },
163
164 #[command(after_long_help = concat!(
166 "Absorb downloads spores into .cmn/absorb/ for AI-assisted merge.\n",
167 "Use --discover to auto-discover descendants via Synapse.\n",
168 "\n",
169 "Examples:\n",
170 " hypha absorb cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "\n",
171 " hypha absorb --discover --synapse https://synapse.cmn.dev\n",
172 " hypha absorb --discover --synapse https://synapse.cmn.dev --max-depth 5",
173 ))]
174 Absorb {
175 #[arg(required_unless_present = "discover")]
177 uris: Vec<String>,
178 #[arg(long)]
180 discover: bool,
181 #[arg(long)]
183 synapse: Option<String>,
184 #[arg(long)]
186 synapse_token_secret: Option<String>,
187 #[arg(long, default_value = "10")]
189 max_depth: u32,
190 },
191
192 #[command(after_long_help = "\
194Examples:
195 hypha bond
196 hypha bond --status
197 hypha bond --clean")]
198 Bond {
199 #[arg(long)]
201 clean: bool,
202 #[arg(long)]
204 status: bool,
205 },
206
207 #[command(after_long_help = "\
209Replicates spores from another domain to yours. The hash stays the same
210because core + core_signature are preserved. Only capsule_signature changes.
211
212Examples:
213 hypha replicate cmn://other.dev/HASH --domain my.dev
214 hypha replicate --refs --domain my.dev")]
215 Replicate {
216 #[arg(required_unless_present = "refs")]
218 uris: Vec<String>,
219 #[arg(long)]
221 refs: bool,
222 #[arg(long)]
224 domain: String,
225 #[arg(long)]
227 site_path: Option<String>,
228 },
229
230 #[command(after_long_help = "\
232Examples:
233 hypha hatch --id my-tool --name \"My Tool\" --synopsis \"A useful tool\"
234 hypha hatch --intent \"Provide a reusable HTTP client for CMN agents\" --mutations \"Initial release\"
235 hypha hatch --license MIT --domain cmn.dev
236
237Subcommands:
238 hypha hatch bond set/remove/clear Manage bonds in spore.core.json
239 hypha hatch tree set/show Manage tree configuration")]
240 #[command(args_conflicts_with_subcommands = true)]
241 Hatch {
242 #[arg(long)]
244 id: Option<String>,
245 #[arg(long)]
247 version: Option<String>,
248 #[arg(long)]
250 name: Option<String>,
251 #[arg(long)]
253 domain: Option<String>,
254 #[arg(long)]
256 synopsis: Option<String>,
257 #[arg(long)]
259 intent: Vec<String>,
260 #[arg(long)]
262 mutations: Vec<String>,
263 #[arg(long)]
265 license: Option<String>,
266
267 #[command(subcommand)]
268 #[serde(skip)]
269 command: Option<HatchCommands>,
270 },
271
272 #[command(after_long_help = "\
274Requires `hypha mycelium root` first to set up the site.
275
276Examples:
277 hypha release --domain cmn.dev
278 hypha release --domain cmn.dev --source ./my-spore
279 hypha release --domain cmn.dev --dry-run # pre-compute URI without releasing
280 hypha release --domain cmn.dev --archive zstd
281 hypha release --domain cmn.dev --dist-git https://github.com/user/repo --dist-ref v1.0")]
282 Release {
283 #[arg(long)]
285 domain: String,
286 #[arg(long)]
288 source: Option<String>,
289 #[arg(long)]
291 site_path: Option<String>,
292 #[arg(long)]
294 dist_git: Option<String>,
295 #[arg(long)]
297 dist_ref: Option<String>,
298 #[arg(long, value_name = "FORMAT", default_value = "zstd")]
300 archive: String,
301 #[arg(long)]
303 dry_run: bool,
304 },
305
306 #[command(after_long_help = concat!(
311 "Direction:\n",
312 " --direction in Find descendants / forks (default)\n",
313 " --direction out Trace ancestors / spawn chain\n",
314 "\n",
315 "Examples:\n",
316 " hypha lineage cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
317 " --synapse https://synapse.cmn.dev\n",
318 " hypha lineage cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
319 " --direction out --synapse https://synapse.cmn.dev\n",
320 " hypha lineage cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
321 " --synapse https://synapse.cmn.dev --max-depth 5",
322 ))]
323 Lineage {
324 uri: String,
326 #[arg(long, value_enum)]
328 direction: Option<DirectionArg>,
329 #[arg(long)]
331 synapse: Option<String>,
332 #[arg(long)]
334 synapse_token_secret: Option<String>,
335 #[arg(long, default_value = "10")]
337 max_depth: u32,
338 },
339
340 #[command(after_long_help = "\
342Examples:
343 hypha search \"protocol spec\" --synapse https://synapse.cmn.dev
344 hypha search \"data format\" --synapse https://synapse.cmn.dev --domain cmn.dev
345 hypha search \"agent tools\" --synapse https://synapse.cmn.dev --license MIT --limit 5
346 hypha search \"http client\" --bonds spawned_from:cmn://cmn.dev/b3.abc123")]
347 Search {
348 query: String,
350 #[arg(long)]
352 synapse: Option<String>,
353 #[arg(long)]
355 synapse_token_secret: Option<String>,
356 #[arg(long)]
358 domain: Option<String>,
359 #[arg(long)]
361 license: Option<String>,
362 #[arg(long)]
364 bonds: Option<String>,
365 #[arg(long, default_value = "20")]
367 limit: u32,
368 },
369
370 Mycelium {
375 #[command(subcommand)]
376 #[serde(flatten)]
377 action: MyceliumAction,
378 },
379
380 Synapse {
382 #[command(subcommand)]
383 #[serde(flatten)]
384 action: SynapseAction,
385 },
386
387 Cache {
389 #[command(subcommand)]
390 #[serde(flatten)]
391 action: CacheAction,
392 },
393
394 Config {
396 #[command(subcommand)]
397 #[serde(flatten)]
398 action: ConfigAction,
399 },
400
401 #[command(after_long_help = "\
403Examples:
404 hypha skill status
405 hypha skill install --agent codex
406 hypha skill install --agent claude-code --scope project
407 hypha skill uninstall --agent opencode --skills-dir /tmp/skills --force")]
408 Skill {
409 #[command(subcommand)]
410 #[serde(flatten)]
411 action: SkillCommand,
412 },
413}
414
415#[derive(Subcommand, Serialize)]
416#[serde(tag = "action", rename_all = "snake_case")]
417pub enum SkillCommand {
418 Status(SkillOptionsArg),
420 Install(SkillOptionsArg),
422 Uninstall(SkillOptionsArg),
424}
425
426#[derive(Args, Serialize)]
427pub struct SkillOptionsArg {
428 #[arg(long, value_enum, default_value = "all")]
430 pub agent: SkillAgentArg,
431 #[arg(long, value_enum, default_value = "personal")]
433 pub scope: SkillScopeArg,
434 #[arg(long)]
436 pub skills_dir: Option<String>,
437 #[arg(long)]
439 pub force: bool,
440}
441
442#[derive(Clone, Copy, Debug, Serialize, ValueEnum)]
443#[serde(rename_all = "kebab-case")]
444pub enum SkillAgentArg {
445 All,
446 Codex,
447 ClaudeCode,
448 Opencode,
449}
450
451#[derive(Clone, Copy, Debug, Serialize, ValueEnum)]
452#[serde(rename_all = "lowercase")]
453pub enum SkillScopeArg {
454 Personal,
455 Project,
456}
457
458#[derive(Clone, Copy, Debug, Serialize, ValueEnum)]
460#[serde(rename_all = "lowercase")]
461pub enum DistArg {
462 Archive,
463 Git,
464}
465
466impl DistArg {
467 pub fn as_str(self) -> &'static str {
469 match self {
470 Self::Archive => "archive",
471 Self::Git => "git",
472 }
473 }
474}
475
476#[derive(Clone, Copy, Debug, Serialize, ValueEnum)]
478#[serde(rename_all = "lowercase")]
479pub enum VcsArg {
480 Git,
481 None,
482}
483
484impl VcsArg {
485 pub fn as_str(self) -> &'static str {
487 match self {
488 Self::Git => "git",
489 Self::None => "none",
490 }
491 }
492}
493
494#[derive(Clone, Copy, Debug, Serialize, ValueEnum)]
496#[serde(rename_all = "lowercase")]
497pub enum DirectionArg {
498 In,
499 Out,
500}
501
502impl DirectionArg {
503 pub fn as_str(self) -> &'static str {
505 match self {
506 Self::In => "in",
507 Self::Out => "out",
508 }
509 }
510}
511
512#[derive(Subcommand, Serialize)]
513#[serde(tag = "action", rename_all = "snake_case")]
514pub enum HatchCommands {
515 #[command(after_long_help = "\
517Examples:
518 hypha hatch bond set --uri cmn://cmn.dev/b3.abc --relation follows --id my-lib --reason \"Core library\"
519 hypha hatch bond set --uri cmn://cmn.dev/b3.abc --with 'mints=[\"https://mint.example.com\"]'
520 hypha hatch bond remove --relation follows
521 hypha hatch bond clear")]
522 Bond {
523 #[command(subcommand)]
524 #[serde(flatten)]
525 command: HatchBondCommands,
526 },
527 Tree {
529 #[command(subcommand)]
530 #[serde(flatten)]
531 command: HatchTreeCommands,
532 },
533}
534
535#[derive(Subcommand, Serialize)]
536#[serde(tag = "action", rename_all = "snake_case")]
537pub enum HatchBondCommands {
538 Set {
540 #[arg(long)]
542 uri: String,
543 #[arg(long)]
545 relation: Option<substrate::BondRelation>,
546 #[arg(long)]
548 id: Option<String>,
549 #[arg(long)]
551 reason: Option<String>,
552 #[arg(long = "with", value_name = "KEY=VALUE")]
554 with_entries: Vec<String>,
555 },
556 Remove {
558 #[arg(long)]
560 uri: Option<String>,
561 #[arg(long)]
563 relation: Option<substrate::BondRelation>,
564 },
565 Clear,
567}
568
569#[derive(Subcommand, Serialize)]
570#[serde(tag = "action", rename_all = "snake_case")]
571pub enum HatchTreeCommands {
572 Set {
574 #[arg(long)]
576 algorithm: Option<String>,
577 #[arg(long, num_args = 1..)]
579 exclude_names: Option<Vec<String>>,
580 #[arg(long, num_args = 1..)]
582 follow_rules: Option<Vec<String>>,
583 },
584 Show,
586}
587
588#[derive(Subcommand, Serialize)]
589#[serde(tag = "action", rename_all = "snake_case")]
590pub enum MyceliumAction {
591 #[command(after_long_help = "\
593Creates ~/.cmn/mycelium/<domain>/ with key pair and site structure.
594Run this once before `hypha release`.
595
596With --hub, creates a taste-only account on a hosted hub (e.g. cmnhub.com):
597 1. Generates ed25519 key pair
598 2. Computes subdomain from pubkey (ed-<base32>.hub)
599 3. Creates taste-only cmn.json with taste endpoint
600 4. Registers hub as a synapse node
601 5. Sets [defaults.taste] so `hypha taste` auto-submits
602
603After --hub, register with the hub then taste without extra flags:
604 curl -X POST https://cmnhub.com/synapse/pulse -H 'Content-Type: application/json' \\
605 -d @~/.cmn/mycelium/ed-xxx.cmnhub.com/public/.well-known/cmn.json
606 hypha taste cmn://example.com/b3.HASH --verdict safe
607
608Examples:
609 hypha mycelium root cmn.dev --name \"CMN\" --synopsis \"Code Mycelial Network\"
610 hypha mycelium root cmn.dev --endpoints-base https://cmn.dev
611 hypha mycelium root example.com --site-path /custom/path
612 hypha mycelium root --hub cmnhub.com")]
613 Root {
614 domain: Option<String>,
616 #[arg(long, conflicts_with = "endpoints_base")]
619 hub: Option<String>,
620 #[arg(long)]
622 site_path: Option<String>,
623 #[arg(long)]
625 name: Option<String>,
626 #[arg(long)]
628 synopsis: Option<String>,
629 #[arg(long)]
631 bio: Option<String>,
632 #[arg(long)]
634 endpoints_base: Option<String>,
635 },
636 #[command(after_long_help = "\
638Examples:
639 hypha mycelium status
640 hypha mycelium status cmn.dev
641 hypha mycelium status cmn.dev --id cmn-spec --site-path deploy/cmn.dev")]
642 Status {
643 domain: Option<String>,
645 #[arg(long)]
647 site_path: Option<String>,
648 #[arg(long)]
650 id: Option<String>,
651 },
652 #[command(after_long_help = "\
654Examples:
655 hypha mycelium serve
656 hypha mycelium serve cmn.dev --port 3000")]
657 Serve {
658 domain: Option<String>,
660 #[arg(long)]
662 site_path: Option<String>,
663 #[arg(long, default_value = "8080")]
665 port: u16,
666 },
667 #[command(after_long_help = "\
669Examples:
670 hypha mycelium nutrient add cmn.dev --type lightning_address --with address=user@example.com
671 hypha mycelium nutrient add cmn.dev --type url --with url=https://example.com --with label=Donate
672 hypha mycelium nutrient remove cmn.dev --type url
673 hypha mycelium nutrient clear cmn.dev")]
674 Nutrient {
675 #[command(subcommand)]
676 #[serde(flatten)]
677 command: NutrientCommands,
678 },
679 #[command(after_long_help = "\
681Examples:
682 hypha mycelium pulse --synapse synapse.cmn.dev --file ~/.cmn/mycelium/cmn.dev/public/cmn/mycelium/<hash>.json
683 hypha mycelium pulse --synapse https://synapse.cmn.dev --file ~/.cmn/mycelium/cmn.dev/public/cmn/mycelium/<hash>.json")]
684 Pulse {
685 #[arg(long)]
687 synapse: Option<String>,
688 #[arg(long)]
690 synapse_token_secret: Option<String>,
691 #[arg(long)]
693 file: String,
694 },
695}
696
697#[derive(Subcommand, Serialize)]
698#[serde(tag = "action", rename_all = "snake_case")]
699pub enum NutrientCommands {
700 Add {
702 domain: String,
704 #[arg(long = "type", value_name = "TYPE")]
706 method_type: String,
707 #[arg(long = "with", value_name = "KEY=VALUE")]
709 with_entries: Vec<String>,
710 #[arg(long)]
712 site_path: Option<String>,
713 },
714 Remove {
716 domain: String,
718 #[arg(long = "type", value_name = "TYPE")]
720 method_type: String,
721 #[arg(long)]
723 site_path: Option<String>,
724 },
725 Clear {
727 domain: String,
729 #[arg(long)]
731 site_path: Option<String>,
732 },
733}
734
735#[derive(Subcommand, Serialize)]
736#[serde(tag = "action", rename_all = "snake_case")]
737pub enum SynapseAction {
738 #[command(after_long_help = "\
740Examples:
741 hypha synapse discover
742 hypha synapse discover --synapse https://synapse.cmn.dev")]
743 Discover {
744 #[arg(long)]
746 synapse: Option<String>,
747 #[arg(long)]
749 synapse_token_secret: Option<String>,
750 },
751 #[command(after_long_help = "\
753Examples:
754 hypha synapse list")]
755 List,
756 #[command(after_long_help = "\
758Examples:
759 hypha synapse health
760 hypha synapse health synapse.cmn.dev
761 hypha synapse health https://synapse.cmn.dev")]
762 Health {
763 synapse: Option<String>,
765 #[arg(long)]
767 synapse_token_secret: Option<String>,
768 },
769 #[command(after_long_help = "\
771Examples:
772 hypha synapse add https://synapse.cmn.dev")]
773 Add {
774 url: String,
776 },
777 #[command(after_long_help = "\
779Examples:
780 hypha synapse remove synapse.cmn.dev")]
781 Remove {
782 domain: String,
784 },
785 #[command(after_long_help = "\
787Examples:
788 hypha synapse use synapse.cmn.dev")]
789 Use {
790 domain: String,
792 },
793 #[command(after_long_help = "\
795Examples:
796 hypha synapse config synapse.cmn.dev --token-secret sk-abc123
797 hypha synapse config synapse.cmn.dev --token-secret \"\" # clear token")]
798 Config {
799 domain: String,
801 #[arg(long)]
803 token_secret: Option<String>,
804 },
805}
806
807#[derive(Subcommand, Serialize)]
808#[serde(tag = "action", rename_all = "snake_case")]
809pub enum CacheAction {
810 #[command(after_long_help = "\
812Examples:
813 hypha cache list
814 hypha cache list -o yaml")]
815 List,
816 #[command(after_long_help = "\
818Examples:
819 hypha cache clean --all")]
820 Clean {
821 #[arg(long)]
823 all: bool,
824 },
825 #[command(after_long_help = concat!(
827 "Examples:\n",
828 " hypha cache path cmn://cmn.dev/", "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
829 ))]
830 Path {
831 uri: String,
833 },
834}
835
836#[derive(Subcommand, Serialize)]
837#[serde(tag = "action", rename_all = "snake_case")]
838pub enum ConfigAction {
839 #[command(after_long_help = "\
841Examples:
842 hypha config list
843 hypha config list -o yaml")]
844 List,
845 #[command(after_long_help = "\
847Dotted keys map to TOML sections:
848 cache.path Custom cache directory
849 cache.cmn_ttl_s cmn.json cache TTL in seconds
850 cache.key_trust_ttl_s Key trust cache TTL in seconds
851 cache.key_trust_refresh_mode
852 Key trust refresh mode: expired | always | offline
853 cache.key_trust_synapse_witness_mode
854 Key trust fallback when domain is offline: allow | require_domain
855 cache.spore_max_download_bytes
856 Max spore archive download bytes
857 cache.spore_max_extract_bytes
858 Max total bytes extracted from a spore archive
859 cache.spore_max_extract_files
860 Max files extracted from a spore archive
861 cache.spore_max_extract_file_bytes
862 Max bytes extracted for one spore archive file
863 cache.spore_reject_path_components
864 TOML string array of protected received path components
865 cache.clock_skew_tolerance_s
866 Clock skew tolerance in seconds for key trust TTL (default: 300)
867 defaults.synapse Default synapse domain
868 defaults.domain Default domain for publishing (release)
869 defaults.taste.synapse
870 Synapse to submit taste reports to (overrides defaults.synapse for taste)
871 defaults.taste.domain Domain to sign taste reports with (overrides defaults.domain for taste)
872
873Examples:
874 hypha config set cache.cmn_ttl_s 600
875 hypha config set cache.key_trust_ttl_s 604800
876 hypha config set cache.key_trust_refresh_mode offline
877 hypha config set cache.key_trust_synapse_witness_mode require_domain
878 hypha config set cache.spore_max_download_bytes 1073741824
879 hypha config set cache.spore_reject_path_components '[\".git\", \".cmn\"]'
880 hypha config set cache.path /tmp/hypha-cache
881 hypha config set defaults.synapse synapse.cmn.dev
882 hypha config set defaults.taste.synapse cmnhub.com
883 hypha config set defaults.taste.domain ed-xxx.cmnhub.com")]
884 Set {
885 key: String,
887 value: String,
889 },
890}
891
892pub(crate) fn cli_error_value(message: &str, hint: &str) -> serde_json::Value {
898 let mut value = agent_first_data::build_json_error(
899 message,
900 Some(hint),
901 Some(serde_json::json!({ "duration_ms": 0 })),
902 );
903 if let serde_json::Value::Object(map) = &mut value {
904 map.insert(
905 "error_code".to_string(),
906 serde_json::Value::String("invalid_request".to_string()),
907 );
908 map.insert("retryable".to_string(), serde_json::Value::Bool(false));
909 }
910 value
911}
912
913pub fn parse_or_exit() -> Cli {
914 let raw: Vec<String> = std::env::args().collect();
915
916 match agent_first_data::cli_handle_help_or_continue(
917 &raw,
918 &Cli::command(),
919 &agent_first_data::HelpConfig::human_cli_default(),
920 ) {
921 Ok(Some(help)) => {
922 let mut stdout = std::io::stdout();
923 let _ = std::io::Write::write_all(&mut stdout, help.as_bytes());
924 std::process::exit(0);
925 }
926 Ok(None) => {}
927 Err(err) => {
928 let mut stdout = std::io::stdout();
929 let message = agent_first_data::output_json(&err);
930 let _ = std::io::Write::write_all(&mut stdout, message.as_bytes());
931 let _ = std::io::Write::write_all(&mut stdout, b"\n");
932 std::process::exit(2);
933 }
934 }
935
936 Cli::try_parse().unwrap_or_else(|e| {
937 if matches!(e.kind(), clap::error::ErrorKind::DisplayVersion) {
938 let mut stdout = std::io::stdout();
939 let message = agent_first_data::output_json(&agent_first_data::build_json_ok(
940 serde_json::json!({ "version": env!("CARGO_PKG_VERSION") }),
941 None,
942 ));
943 let _ = std::io::Write::write_all(&mut stdout, message.as_bytes());
944 let _ = std::io::Write::write_all(&mut stdout, b"\n");
945 std::process::exit(0);
946 }
947 if matches!(e.kind(), clap::error::ErrorKind::DisplayHelp) {
948 let mut stdout = std::io::stdout();
949 let _ = std::io::Write::write_all(&mut stdout, e.to_string().as_bytes());
950 std::process::exit(0);
951 }
952
953 let mut stdout = std::io::stdout();
954 let message = agent_first_data::output_json(&cli_error_value(
955 &e.to_string(),
956 "run hypha --help to inspect all commands and flags",
957 ));
958 let _ = std::io::Write::write_all(&mut stdout, message.as_bytes());
959 let _ = std::io::Write::write_all(&mut stdout, b"\n");
960 std::process::exit(2);
961 })
962}