1use clap::{Arg, ArgAction, Command, crate_name, value_parser};
8
9use crate::password_bootstrap::quickstart_password_bootstrap_help;
10
11pub fn build_cli() -> Command {
12 let cmd = Command::new(crate_name!())
13 .version(env!("CARGO_PKG_VERSION"))
14 .about(env!("CARGO_PKG_DESCRIPTION"))
15 .subcommand(
16 Command::new("version")
17 .about("Prints version and build information")
18 )
19 .subcommand(
20 Command::new("config")
21 .about(" work with JACS configuration")
22 .subcommand(
23 Command::new("create")
24 .about(" create a config file")
25 )
26 .subcommand(
27 Command::new("read")
28 .about("read configuration and display to screen. This includes both the config file and the env variables.")
29 ),
30 )
31 .subcommand(
32 Command::new("agent")
33 .about(" work with a JACS agent")
34 .subcommand(
35 Command::new("dns")
36 .about("emit DNS TXT commands for publishing agent fingerprint")
37 .arg(
38 Arg::new("agent-file")
39 .short('a')
40 .long("agent-file")
41 .value_parser(value_parser!(String))
42 .help("Path to agent JSON (optional; defaults via config)"),
43 )
44 .arg(
45 Arg::new("no-dns")
46 .long("no-dns")
47 .help("Disable DNS validation; rely on embedded fingerprint")
48 .action(ArgAction::SetTrue),
49 )
50 .arg(
51 Arg::new("require-dns")
52 .long("require-dns")
53 .help("Require DNS validation; if domain missing, fail. Not strict (no DNSSEC required).")
54 .action(ArgAction::SetTrue),
55 )
56 .arg(
57 Arg::new("require-strict-dns")
58 .long("require-strict-dns")
59 .help("Require strict DNSSEC validation; if domain missing, fail.")
60 .action(ArgAction::SetTrue),
61 )
62 .arg(
63 Arg::new("ignore-dns")
64 .long("ignore-dns")
65 .help("Ignore DNS validation entirely.")
66 .action(ArgAction::SetTrue),
67 )
68 .arg(Arg::new("domain").long("domain").value_parser(value_parser!(String)))
69 .arg(Arg::new("agent-id").long("agent-id").value_parser(value_parser!(String)))
70 .arg(Arg::new("ttl").long("ttl").value_parser(value_parser!(u32)).default_value("3600"))
71 .arg(Arg::new("encoding").long("encoding").value_parser(["base64","hex"]).default_value("base64"))
72 .arg(Arg::new("provider").long("provider").value_parser(["plain","aws","azure","cloudflare"]).default_value("plain"))
73 )
74 .subcommand(
75 Command::new("create")
76 .about(" create an agent")
77 .arg(
78 Arg::new("filename")
79 .short('f')
80 .help("Name of the json file with agent schema and jacsAgentType")
81 .value_parser(value_parser!(String)),
82 )
83 .arg(
84 Arg::new("create-keys")
85 .long("create-keys")
86 .required(true)
87 .help("Create keys or not if they already exist. Configure key type in jacs.config.json")
88 .value_parser(value_parser!(bool)),
89 ),
90 )
91 .subcommand(
92 Command::new("verify")
93 .about(" verify an agent")
94 .arg(
95 Arg::new("agent-file")
96 .short('a')
97 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
98 .value_parser(value_parser!(String)),
99 )
100 .arg(
101 Arg::new("no-dns")
102 .long("no-dns")
103 .help("Disable DNS validation; rely on embedded fingerprint")
104 .action(ArgAction::SetTrue),
105 )
106 .arg(
107 Arg::new("require-dns")
108 .long("require-dns")
109 .help("Require DNS validation; if domain missing, fail. Not strict (no DNSSEC required).")
110 .action(ArgAction::SetTrue),
111 )
112 .arg(
113 Arg::new("require-strict-dns")
114 .long("require-strict-dns")
115 .help("Require strict DNSSEC validation; if domain missing, fail.")
116 .action(ArgAction::SetTrue),
117 )
118 .arg(
119 Arg::new("ignore-dns")
120 .long("ignore-dns")
121 .help("Ignore DNS validation entirely.")
122 .action(ArgAction::SetTrue),
123 ),
124 )
125 .subcommand(
126 Command::new("lookup")
127 .about("Look up another agent's public key and DNS info from their domain")
128 .arg(
129 Arg::new("domain")
130 .required(true)
131 .help("Domain to look up (e.g., agent.example.com)"),
132 )
133 .arg(
134 Arg::new("no-dns")
135 .long("no-dns")
136 .help("Skip DNS TXT record lookup")
137 .action(ArgAction::SetTrue),
138 )
139 .arg(
140 Arg::new("strict")
141 .long("strict")
142 .help("Require DNSSEC validation for DNS lookup")
143 .action(ArgAction::SetTrue),
144 ),
145 )
146 .subcommand(
147 Command::new("rotate-keys")
148 .about("Rotate the agent's cryptographic keys")
149 .arg(
150 Arg::new("algorithm")
151 .long("algorithm")
152 .value_parser(["ring-Ed25519", "pq2025"])
153 .help("Signing algorithm for the new keys (defaults to current)"),
154 )
155 .arg(
156 Arg::new("config")
157 .long("config")
158 .value_parser(value_parser!(String))
159 .help("Path to jacs.config.json (default: ./jacs.config.json)"),
160 ),
161 )
162 .subcommand(
163 Command::new("keys-list")
164 .about("List active and archived key files")
165 .arg(
166 Arg::new("config")
167 .long("config")
168 .value_parser(value_parser!(String))
169 .help("Path to jacs.config.json (default: ./jacs.config.json)"),
170 ),
171 )
172 .subcommand(
173 Command::new("repair")
174 .about("Repair config after an interrupted key rotation")
175 .arg(
176 Arg::new("config")
177 .long("config")
178 .value_parser(value_parser!(String))
179 .help("Path to jacs.config.json (default: ./jacs.config.json)"),
180 ),
181 ),
182 )
183
184 .subcommand(
185 Command::new("document")
186 .about(" work with a general JACS document")
187 .subcommand(
188 Command::new("create")
189 .about(" create a new JACS file, either by embedding or parsing a document")
190 .arg(
191 Arg::new("agent-file")
192 .short('a')
193 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
194 .value_parser(value_parser!(String)),
195 )
196 .arg(
197 Arg::new("filename")
198 .short('f')
199 .help("Path to input file. Must be JSON")
200 .value_parser(value_parser!(String)),
201 )
202 .arg(
203 Arg::new("output")
204 .short('o')
205 .help("Output filename. ")
206 .value_parser(value_parser!(String)),
207 )
208 .arg(
209 Arg::new("directory")
210 .short('d')
211 .help("Path to directory of files. Files should end with .json")
212 .value_parser(value_parser!(String)),
213 )
214 .arg(
215 Arg::new("verbose")
216 .short('v')
217 .long("verbose")
218 .action(ArgAction::SetTrue),
219 )
220 .arg(
221 Arg::new("no-save")
222 .long("no-save")
223 .short('n')
224 .help("Instead of saving files, print to stdout")
225 .action(ArgAction::SetTrue),
226 )
227 .arg(
228 Arg::new("schema")
229 .short('s')
230 .help("Path to JSON schema file to use to create")
231 .long("schema")
232 .value_parser(value_parser!(String)),
233 )
234 .arg(
235 Arg::new("attach")
236 .help("Path to file or directory for file attachments")
237 .long("attach")
238 .value_parser(value_parser!(String)),
239 )
240 .arg(
241 Arg::new("embed")
242 .short('e')
243 .help("Embed documents or keep the documents external")
244 .long("embed")
245 .value_parser(value_parser!(bool)),
246 ),
247 )
248 .subcommand(
249 Command::new("update")
250 .about("create a new version of document. requires both the original JACS file and the modified jacs metadata")
251 .arg(
252 Arg::new("agent-file")
253 .short('a')
254 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
255 .value_parser(value_parser!(String)),
256 )
257 .arg(
258 Arg::new("new")
259 .short('n')
260 .required(true)
261 .help("Path to new version of document.")
262 .value_parser(value_parser!(String)),
263 )
264 .arg(
265 Arg::new("filename")
266 .short('f')
267 .required(true)
268 .help("Path to original document.")
269 .value_parser(value_parser!(String)),
270 )
271 .arg(
272 Arg::new("output")
273 .short('o')
274 .help("Output filename. Filenames will always end with \"json\"")
275 .value_parser(value_parser!(String)),
276 )
277 .arg(
278 Arg::new("verbose")
279 .short('v')
280 .long("verbose")
281 .action(ArgAction::SetTrue),
282 )
283 .arg(
284 Arg::new("no-save")
285 .long("no-save")
286 .short('n')
287 .help("Instead of saving files, print to stdout")
288 .action(ArgAction::SetTrue),
289 )
290 .arg(
291 Arg::new("schema")
292 .short('s')
293 .help("Path to JSON schema file to use to create")
294 .long("schema")
295 .value_parser(value_parser!(String)),
296 )
297 .arg(
298 Arg::new("attach")
299 .help("Path to file or directory for file attachments")
300 .long("attach")
301 .value_parser(value_parser!(String)),
302 )
303 .arg(
304 Arg::new("embed")
305 .short('e')
306 .help("Embed documents or keep the documents external")
307 .long("embed")
308 .value_parser(value_parser!(bool)),
309 )
310 ,
311 )
312 .subcommand(
313 Command::new("check-agreement")
314 .about("List the agents that should sign a document's agreement (legacy v1; prefer `jacs agreement-v2`)")
315 .arg(
316 Arg::new("agent-file")
317 .short('a')
318 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
319 .value_parser(value_parser!(String)),
320 )
321 .arg(
322 Arg::new("filename")
323 .short('f')
324 .required(true)
325 .help("Path to original document.")
326 .value_parser(value_parser!(String)),
327 )
328 .arg(
329 Arg::new("directory")
330 .short('d')
331 .help("Path to directory of files. Files should end with .json")
332 .value_parser(value_parser!(String)),
333 )
334 .arg(
335 Arg::new("schema")
336 .short('s')
337 .help("Path to JSON schema file to use to create")
338 .long("schema")
339 .value_parser(value_parser!(String)),
340 )
341
342 )
343 .subcommand(
344 Command::new("create-agreement")
345 .about("Create a jacsAgreement sidecar on a document (legacy v1; prefer `jacs agreement-v2 create`)")
346 .arg(
347 Arg::new("agent-file")
348 .short('a')
349 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
350 .value_parser(value_parser!(String)),
351 )
352 .arg(
353 Arg::new("filename")
354 .short('f')
355 .required(true)
356 .help("Path to original document.")
357 .value_parser(value_parser!(String)),
358 )
359 .arg(
360 Arg::new("directory")
361 .short('d')
362 .help("Path to directory of files. Files should end with .json")
363 .value_parser(value_parser!(String)),
364 )
365 .arg(
366 Arg::new("agentids")
367 .short('i')
368 .long("agentids")
369 .value_name("VALUES")
370 .help("Comma-separated list of agent ids")
371 .value_delimiter(',')
372 .required(true)
373 .action(clap::ArgAction::Set),
374 )
375 .arg(
376 Arg::new("output")
377 .short('o')
378 .help("Output filename. Filenames will always end with \"json\"")
379 .value_parser(value_parser!(String)),
380 )
381 .arg(
382 Arg::new("verbose")
383 .short('v')
384 .long("verbose")
385 .action(ArgAction::SetTrue),
386 )
387 .arg(
388 Arg::new("no-save")
389 .long("no-save")
390 .short('n')
391 .help("Instead of saving files, print to stdout")
392 .action(ArgAction::SetTrue),
393 )
394 .arg(
395 Arg::new("schema")
396 .short('s')
397 .help("Path to JSON schema file to use to create")
398 .long("schema")
399 .value_parser(value_parser!(String)),
400 )
401
402 ).subcommand(
403 Command::new("sign-agreement")
404 .about("Sign a document's jacsAgreement sidecar (legacy v1; prefer `jacs agreement-v2 sign`)")
405 .arg(
406 Arg::new("agent-file")
407 .short('a')
408 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
409 .value_parser(value_parser!(String)),
410 )
411 .arg(
412 Arg::new("filename")
413 .short('f')
414 .required(true)
415 .help("Path to original document.")
416 .value_parser(value_parser!(String)),
417 )
418 .arg(
419 Arg::new("directory")
420 .short('d')
421 .help("Path to directory of files. Files should end with .json")
422 .value_parser(value_parser!(String)),
423 )
424 .arg(
425 Arg::new("output")
426 .short('o')
427 .help("Output filename. Filenames will always end with \"json\"")
428 .value_parser(value_parser!(String)),
429 )
430 .arg(
431 Arg::new("verbose")
432 .short('v')
433 .long("verbose")
434 .action(ArgAction::SetTrue),
435 )
436 .arg(
437 Arg::new("no-save")
438 .long("no-save")
439 .short('n')
440 .help("Instead of saving files, print to stdout")
441 .action(ArgAction::SetTrue),
442 )
443 .arg(
444 Arg::new("schema")
445 .short('s')
446 .help("Path to JSON schema file to use to create")
447 .long("schema")
448 .value_parser(value_parser!(String)),
449 )
450
451 )
452 .subcommand(
453 Command::new("verify")
454 .about(" verify a documents hash, siginatures, and schema")
455 .arg(
456 Arg::new("agent-file")
457 .short('a')
458 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
459 .value_parser(value_parser!(String)),
460 )
461 .arg(
462 Arg::new("filename")
463 .short('f')
464 .help("Path to input file. Must be JSON")
465 .value_parser(value_parser!(String)),
466 )
467 .arg(
468 Arg::new("directory")
469 .short('d')
470 .help("Path to directory of files. Files should end with .json")
471 .value_parser(value_parser!(String)),
472 )
473 .arg(
474 Arg::new("verbose")
475 .short('v')
476 .long("verbose")
477 .action(ArgAction::SetTrue),
478 )
479 .arg(
480 Arg::new("schema")
481 .short('s')
482 .help("Path to JSON schema file to use to validate")
483 .long("schema")
484 .value_parser(value_parser!(String)),
485 ),
486 )
487 .subcommand(
488 Command::new("extract")
489 .about(" given documents, extract embedded contents if any")
490 .arg(
491 Arg::new("agent-file")
492 .short('a')
493 .help("Path to the agent file. Otherwise use config jacs_agent_id_and_version")
494 .value_parser(value_parser!(String)),
495 )
496 .arg(
497 Arg::new("filename")
498 .short('f')
499 .help("Path to input file. Must be JSON")
500 .value_parser(value_parser!(String)),
501 )
502 .arg(
503 Arg::new("directory")
504 .short('d')
505 .help("Path to directory of files. Files should end with .json")
506 .value_parser(value_parser!(String)),
507 )
508 .arg(
509 Arg::new("verbose")
510 .short('v')
511 .long("verbose")
512 .action(ArgAction::SetTrue),
513 )
514 .arg(
515 Arg::new("schema")
516 .short('s')
517 .help("Path to JSON schema file to use to validate")
518 .long("schema")
519 .value_parser(value_parser!(String)),
520 ),
521 )
522 )
523 .subcommand(
524 Command::new("agreement-v2")
525 .about("Work with standalone JACS agreement v2 documents")
526 .subcommand_required(true)
527 .arg_required_else_help(true)
528 .long_about(
529 "Work with standalone JACS agreement v2 documents.\n\n\
530 An agreement v2 is a self-contained, cryptographically signed `jacsType: \"agreement\"` \
531 artifact: terms, parties, a signature policy (quorum, required roles), an optional \
532 transcript, links to related documents, controllers, and owners. Unlike the legacy \
533 `jacs document *-agreement` sidecar commands, an agreement v2 is its own verifiable \
534 document with a content hash and a version chain.\n\n\
535 Typical workflow:\n \
536 1. create -> build the agreement from a CreateAgreementV2 JSON input\n \
537 2. sign -> each party adds a signer/witness/notary signature\n \
538 3. verify -> recompute hashes, quorum, roles, and status before acting\n\n\
539 Use `apply` to emit a successor version from a typed mutation (append transcript, \
540 update terms, set status, etc.). Use `detect-conflict`, `merge-transcript`, and \
541 `resolve-conflict` when two agents branch from the same prior version.",
542 )
543 .after_help(
544 "Workflow: create -> sign -> verify. Use `apply` for mutations; \
545 `detect-conflict`/`merge-transcript`/`resolve-conflict` for divergent branches.\n\
546 Run `jacs agreement-v2 <SUBCOMMAND> --help` for per-command flags.",
547 )
548 .subcommand(
549 Command::new("create")
550 .about("Create a standalone agreement v2 document")
551 .arg(
552 Arg::new("input")
553 .short('i')
554 .long("input")
555 .required(true)
556 .value_parser(value_parser!(String))
557 .help("CreateAgreementV2 JSON, path to JSON, or '-' for stdin"),
558 ),
559 )
560 .subcommand(
561 Command::new("apply")
562 .about("Apply an agreement v2 mutation and emit a successor version")
563 .arg(
564 Arg::new("agreement")
565 .short('a')
566 .long("agreement")
567 .required(true)
568 .value_parser(value_parser!(String))
569 .help("Agreement JSON, path to JSON, or '-' for stdin"),
570 )
571 .arg(
572 Arg::new("mutation")
573 .short('m')
574 .long("mutation")
575 .required(true)
576 .value_parser(value_parser!(String))
577 .help("Mutation JSON or path to mutation JSON"),
578 ),
579 )
580 .subcommand(
581 Command::new("sign")
582 .about("Sign an agreement v2 document as signer, witness, or notary")
583 .arg(
584 Arg::new("agreement")
585 .short('a')
586 .long("agreement")
587 .required(true)
588 .value_parser(value_parser!(String))
589 .help("Agreement JSON, path to JSON, or '-' for stdin"),
590 )
591 .arg(
592 Arg::new("role")
593 .long("role")
594 .value_parser(["signer", "witness", "notary"])
595 .default_value("signer")
596 .help("Agreement signature role"),
597 ),
598 )
599 .subcommand(
600 Command::new("verify")
601 .about("Verify an agreement v2 document")
602 .arg(
603 Arg::new("agreement")
604 .short('a')
605 .long("agreement")
606 .required(true)
607 .value_parser(value_parser!(String))
608 .help("Agreement JSON, path to JSON, or '-' for stdin"),
609 ),
610 )
611 .subcommand(
612 Command::new("detect-conflict")
613 .about("Detect whether two agreement v2 branches can auto-merge")
614 .arg(
615 Arg::new("base")
616 .long("base")
617 .required(true)
618 .value_parser(value_parser!(String))
619 .help("Common ancestor agreement (JSON, path, or '-' for stdin)"),
620 )
621 .arg(
622 Arg::new("left")
623 .long("left")
624 .required(true)
625 .value_parser(value_parser!(String))
626 .help("One branch successor of base (JSON, path, or '-' for stdin)"),
627 )
628 .arg(
629 Arg::new("right")
630 .long("right")
631 .required(true)
632 .value_parser(value_parser!(String))
633 .help("Other branch successor of base (JSON, path, or '-' for stdin)"),
634 ),
635 )
636 .subcommand(
637 Command::new("merge-transcript")
638 .about("Auto-merge two transcript-only agreement v2 branches")
639 .arg(
640 Arg::new("base")
641 .long("base")
642 .required(true)
643 .value_parser(value_parser!(String))
644 .help("Common ancestor agreement (JSON, path, or '-' for stdin)"),
645 )
646 .arg(
647 Arg::new("left")
648 .long("left")
649 .required(true)
650 .value_parser(value_parser!(String))
651 .help("One transcript-only branch successor (JSON, path, or '-' for stdin)"),
652 )
653 .arg(
654 Arg::new("right")
655 .long("right")
656 .required(true)
657 .value_parser(value_parser!(String))
658 .help("Other transcript-only branch successor (JSON, path, or '-' for stdin)"),
659 ),
660 )
661 .subcommand(
662 Command::new("resolve-conflict")
663 .about("Resolve an agreement v2 branch conflict with an explicit mutation")
664 .arg(
665 Arg::new("base")
666 .long("base")
667 .required(true)
668 .value_parser(value_parser!(String))
669 .help("Common ancestor agreement (JSON, path, or '-' for stdin)"),
670 )
671 .arg(
672 Arg::new("previous")
673 .long("previous")
674 .required(true)
675 .value_parser(value_parser!(String))
676 .help("Branch you are keeping/rebasing onto (JSON, path, or '-' for stdin)"),
677 )
678 .arg(
679 Arg::new("side")
680 .long("side-branch")
681 .alias("side")
682 .required(true)
683 .value_parser(value_parser!(String))
684 .help("Divergent branch whose changes are being reconciled; \
685 recorded as a link on the resolution (JSON, path, or '-' for stdin). \
686 Alias: --side"),
687 )
688 .arg(
689 Arg::new("mutation")
690 .short('m')
691 .long("mutation")
692 .required(true)
693 .value_parser(value_parser!(String))
694 .help("Explicit resolving mutation (JSON or path to JSON)"),
695 ),
696 )
697 )
698 .subcommand(
699 Command::new("key")
700 .about("Work with JACS cryptographic keys")
701 .subcommand(
702 Command::new("reencrypt")
703 .about("Re-encrypt the private key with a new password")
704 )
705 )
706 .subcommand(
707 Command::new("mcp")
708 .about("Start the built-in JACS MCP server (stdio transport)")
709 .arg(
710 Arg::new("profile")
711 .long("profile")
712 .default_value("core")
713 .help(
714 "Tool profile: 'core' (default; document, trust, search, key, w3c tools) or \
715 'full' (adds agreement, a2a, and attestation tools). Agreement v2 tools are \
716 only registered under 'full' (or JACS_MCP_PROFILE=full).",
717 ),
718 )
719 .subcommand(
720 Command::new("install")
721 .about("Deprecated: MCP is now built into the jacs binary")
722 .hide(true)
723 )
724 .subcommand(
725 Command::new("run")
726 .about("Deprecated: use `jacs mcp` directly")
727 .hide(true)
728 ),
729 )
730 .subcommand(
731 Command::new("a2a")
732 .about("A2A (Agent-to-Agent) trust and discovery commands")
733 .subcommand(
734 Command::new("assess")
735 .about("Assess trust level of a remote A2A Agent Card")
736 .arg(
737 Arg::new("source")
738 .required(true)
739 .help("Path to Agent Card JSON file or URL"),
740 )
741 .arg(
742 Arg::new("policy")
743 .long("policy")
744 .short('p')
745 .value_parser(["open", "verified", "strict"])
746 .default_value("verified")
747 .help("Trust policy to apply (default: verified)"),
748 )
749 .arg(
750 Arg::new("json")
751 .long("json")
752 .action(ArgAction::SetTrue)
753 .help("Output result as JSON"),
754 ),
755 )
756 .subcommand(
757 Command::new("trust")
758 .about("Add a remote A2A agent to the local trust store")
759 .arg(
760 Arg::new("source")
761 .required(true)
762 .help("Path to Agent Card JSON file or URL"),
763 ),
764 )
765 .subcommand(
766 Command::new("discover")
767 .about("Discover a remote A2A agent via its well-known Agent Card")
768 .arg(
769 Arg::new("url")
770 .required(true)
771 .help("Base URL of the agent (e.g. https://agent.example.com)"),
772 )
773 .arg(
774 Arg::new("json")
775 .long("json")
776 .action(ArgAction::SetTrue)
777 .help("Output the full Agent Card as JSON"),
778 )
779 .arg(
780 Arg::new("policy")
781 .long("policy")
782 .short('p')
783 .value_parser(["open", "verified", "strict"])
784 .default_value("verified")
785 .help("Trust policy to apply against the discovered card"),
786 ),
787 )
788 .subcommand(
789 Command::new("serve")
790 .about("Serve this agent's .well-known endpoints for A2A discovery")
791 .arg(
792 Arg::new("port")
793 .long("port")
794 .value_parser(value_parser!(u16))
795 .default_value("8080")
796 .help("Port to listen on (default: 8080)"),
797 )
798 .arg(
799 Arg::new("host")
800 .long("host")
801 .default_value("127.0.0.1")
802 .help("Host to bind to (default: 127.0.0.1)"),
803 ),
804 )
805 .subcommand(
806 Command::new("quickstart")
807 .about("Create/load an agent and start serving A2A endpoints (password required)")
808 .after_help(quickstart_password_bootstrap_help())
809 .arg(
810 Arg::new("name")
811 .long("name")
812 .value_parser(value_parser!(String))
813 .required(true)
814 .help("Agent name used for first-time quickstart creation"),
815 )
816 .arg(
817 Arg::new("domain")
818 .long("domain")
819 .value_parser(value_parser!(String))
820 .required(true)
821 .help("Agent domain used for DNS/public-key verification workflows"),
822 )
823 .arg(
824 Arg::new("description")
825 .long("description")
826 .value_parser(value_parser!(String))
827 .help("Optional human-readable agent description"),
828 )
829 .arg(
830 Arg::new("port")
831 .long("port")
832 .value_parser(value_parser!(u16))
833 .default_value("8080")
834 .help("Port to listen on (default: 8080)"),
835 )
836 .arg(
837 Arg::new("host")
838 .long("host")
839 .default_value("127.0.0.1")
840 .help("Host to bind to (default: 127.0.0.1)"),
841 )
842 .arg(
843 Arg::new("algorithm")
844 .long("algorithm")
845 .short('a')
846 .value_parser(["pq2025", "ring-Ed25519"])
847 .help("Signing algorithm (default: pq2025)"),
848 ),
849 ),
850 )
851 .subcommand(
852 Command::new("w3c")
853 .about("W3C AI Agent Protocol interop helpers")
854 .subcommand(
855 Command::new("did")
856 .about("Export this agent's did:wba identifier")
857 .arg(
858 Arg::new("origin")
859 .long("origin")
860 .value_parser(value_parser!(String))
861 .help("Controlling HTTPS origin for did:wba and discovery URLs"),
862 ),
863 )
864 .subcommand(
865 Command::new("did-document")
866 .about("Export this agent's did:wba DID document")
867 .arg(
868 Arg::new("origin")
869 .long("origin")
870 .value_parser(value_parser!(String))
871 .help("Controlling HTTPS origin for did:wba and discovery URLs"),
872 ),
873 )
874 .subcommand(
875 Command::new("agent-description")
876 .about("Export this agent's W3C agent description document")
877 .arg(
878 Arg::new("origin")
879 .long("origin")
880 .value_parser(value_parser!(String))
881 .help("Controlling HTTPS origin for did:wba and discovery URLs"),
882 ),
883 )
884 .subcommand(
885 Command::new("well-known")
886 .about("Generate W3C well-known discovery documents")
887 .arg(
888 Arg::new("origin")
889 .long("origin")
890 .value_parser(value_parser!(String))
891 .help("Controlling HTTPS origin for did:wba and discovery URLs"),
892 )
893 .arg(
894 Arg::new("out")
895 .long("out")
896 .short('o')
897 .value_parser(value_parser!(String))
898 .help("Directory to write generated discovery files into"),
899 ),
900 )
901 .subcommand(
902 Command::new("serve")
903 .about("Serve W3C discovery documents for local demo/testing")
904 .arg(
905 Arg::new("origin")
906 .long("origin")
907 .value_parser(value_parser!(String))
908 .help("Controlling HTTPS origin for did:wba and discovery URLs"),
909 )
910 .arg(
911 Arg::new("port")
912 .long("port")
913 .value_parser(value_parser!(u16))
914 .default_value("8081")
915 .help("Port to listen on (default: 8081)"),
916 )
917 .arg(
918 Arg::new("host")
919 .long("host")
920 .default_value("127.0.0.1")
921 .help("Host to bind to (default: 127.0.0.1)"),
922 ),
923 )
924 .subcommand(
925 Command::new("sign-request")
926 .about("Create a request-bound DID authentication proof")
927 .arg(
928 Arg::new("method")
929 .long("method")
930 .value_parser(value_parser!(String))
931 .required(true)
932 .help("HTTP method to bind into the proof"),
933 )
934 .arg(
935 Arg::new("url")
936 .long("url")
937 .value_parser(value_parser!(String))
938 .required(true)
939 .help("HTTP target URI to bind into the proof"),
940 )
941 .arg(
942 Arg::new("body")
943 .long("body")
944 .value_parser(value_parser!(String))
945 .conflicts_with("body-file")
946 .help("Request body bytes to digest and bind"),
947 )
948 .arg(
949 Arg::new("body-file")
950 .long("body-file")
951 .value_parser(value_parser!(String))
952 .conflicts_with("body")
953 .help("File containing request body bytes to digest and bind"),
954 )
955 .arg(
956 Arg::new("origin")
957 .long("origin")
958 .value_parser(value_parser!(String))
959 .help("Controlling HTTPS origin for the signing agent DID"),
960 )
961 .arg(
962 Arg::new("nonce")
963 .long("nonce")
964 .value_parser(value_parser!(String))
965 .help("Caller-provided nonce; generated when omitted"),
966 )
967 .arg(
968 Arg::new("created")
969 .long("created")
970 .value_parser(value_parser!(String))
971 .help("RFC3339 created time; current time when omitted"),
972 ),
973 )
974 .subcommand(
975 Command::new("verify-request")
976 .about("Verify a request-bound DID authentication proof")
977 .arg(
978 Arg::new("method")
979 .long("method")
980 .value_parser(value_parser!(String))
981 .help("Actual HTTP method to compare against the proof"),
982 )
983 .arg(
984 Arg::new("url")
985 .long("url")
986 .value_parser(value_parser!(String))
987 .help("Actual HTTP target URI to compare against the proof"),
988 )
989 .arg(
990 Arg::new("proof")
991 .long("proof")
992 .value_parser(value_parser!(String))
993 .required(true)
994 .help("Path to request proof JSON"),
995 )
996 .arg(
997 Arg::new("did-document")
998 .long("did-document")
999 .value_parser(value_parser!(String))
1000 .required(true)
1001 .help("Path to resolved DID document JSON"),
1002 )
1003 .arg(
1004 Arg::new("body")
1005 .long("body")
1006 .value_parser(value_parser!(String))
1007 .conflicts_with("body-file")
1008 .help("Request body bytes to check against the proof digest"),
1009 )
1010 .arg(
1011 Arg::new("body-file")
1012 .long("body-file")
1013 .value_parser(value_parser!(String))
1014 .conflicts_with("body")
1015 .help("File containing request body bytes to check"),
1016 )
1017 .arg(
1018 Arg::new("max-age-seconds")
1019 .long("max-age-seconds")
1020 .value_parser(value_parser!(u64))
1021 .default_value("300")
1022 .help("Maximum accepted proof age in seconds"),
1023 ),
1024 ),
1025 )
1026 .subcommand(
1027 Command::new("quickstart")
1028 .about("Create or load a persistent agent for instant sign/verify (password required)")
1029 .after_help(quickstart_password_bootstrap_help())
1030 .arg(
1031 Arg::new("name")
1032 .long("name")
1033 .value_parser(value_parser!(String))
1034 .required(true)
1035 .help("Agent name used for first-time quickstart creation"),
1036 )
1037 .arg(
1038 Arg::new("domain")
1039 .long("domain")
1040 .value_parser(value_parser!(String))
1041 .required(true)
1042 .help("Agent domain used for DNS/public-key verification workflows"),
1043 )
1044 .arg(
1045 Arg::new("description")
1046 .long("description")
1047 .value_parser(value_parser!(String))
1048 .help("Optional human-readable agent description"),
1049 )
1050 .arg(
1051 Arg::new("algorithm")
1052 .long("algorithm")
1053 .short('a')
1054 .value_parser(["ed25519", "pq2025"])
1055 .default_value("pq2025")
1056 .help("Signing algorithm (default: pq2025)"),
1057 )
1058 .arg(
1059 Arg::new("sign")
1060 .long("sign")
1061 .help("Sign JSON from stdin and print signed document to stdout")
1062 .action(ArgAction::SetTrue),
1063 )
1064 .arg(
1065 Arg::new("file")
1066 .short('f')
1067 .long("file")
1068 .value_parser(value_parser!(String))
1069 .help("Sign a JSON file instead of reading from stdin (used with --sign)"),
1070 )
1071 )
1072 .subcommand(
1073 Command::new("init")
1074 .about("Initialize JACS by creating both config and agent (with keys)")
1075 .arg(
1076 Arg::new("yes")
1077 .long("yes")
1078 .short('y')
1079 .action(ArgAction::SetTrue)
1080 .help("Automatically set the new agent ID in jacs.config.json without prompting"),
1081 )
1082 )
1083 .subcommand(
1084 Command::new("attest")
1085 .about("Create and verify attestation documents")
1086 .subcommand(
1087 Command::new("create")
1088 .about("Create a signed attestation")
1089 .arg(
1090 Arg::new("subject-type")
1091 .long("subject-type")
1092 .value_parser(["agent", "artifact", "workflow", "identity"])
1093 .help("Type of subject being attested"),
1094 )
1095 .arg(
1096 Arg::new("subject-id")
1097 .long("subject-id")
1098 .value_parser(value_parser!(String))
1099 .help("Identifier of the subject"),
1100 )
1101 .arg(
1102 Arg::new("subject-digest")
1103 .long("subject-digest")
1104 .value_parser(value_parser!(String))
1105 .help("SHA-256 digest of the subject"),
1106 )
1107 .arg(
1108 Arg::new("claims")
1109 .long("claims")
1110 .value_parser(value_parser!(String))
1111 .required(true)
1112 .help("JSON array of claims, e.g. '[{\"name\":\"reviewed\",\"value\":true}]'"),
1113 )
1114 .arg(
1115 Arg::new("evidence")
1116 .long("evidence")
1117 .value_parser(value_parser!(String))
1118 .help("JSON array of evidence references"),
1119 )
1120 .arg(
1121 Arg::new("from-document")
1122 .long("from-document")
1123 .value_parser(value_parser!(String))
1124 .help("Lift attestation from an existing signed document file"),
1125 )
1126 .arg(
1127 Arg::new("output")
1128 .short('o')
1129 .long("output")
1130 .value_parser(value_parser!(String))
1131 .help("Write attestation to file instead of stdout"),
1132 ),
1133 )
1134 .subcommand(
1135 Command::new("verify")
1136 .about("Verify an attestation document")
1137 .arg(
1138 Arg::new("file")
1139 .help("Path to the attestation JSON file")
1140 .required(true)
1141 .value_parser(value_parser!(String)),
1142 )
1143 .arg(
1144 Arg::new("full")
1145 .long("full")
1146 .action(ArgAction::SetTrue)
1147 .help("Use full verification (evidence + derivation chain)"),
1148 )
1149 .arg(
1150 Arg::new("json")
1151 .long("json")
1152 .action(ArgAction::SetTrue)
1153 .help("Output result as JSON"),
1154 )
1155 .arg(
1156 Arg::new("key-dir")
1157 .long("key-dir")
1158 .value_parser(value_parser!(String))
1159 .help("Directory containing public keys for verification"),
1160 )
1161 .arg(
1162 Arg::new("max-depth")
1163 .long("max-depth")
1164 .value_parser(value_parser!(u32))
1165 .help("Maximum derivation chain depth"),
1166 ),
1167 )
1168 .subcommand(
1169 Command::new("export-dsse")
1170 .about("Export an attestation as a DSSE envelope for in-toto/SLSA")
1171 .arg(
1172 Arg::new("file")
1173 .help("Path to the signed attestation JSON file")
1174 .required(true)
1175 .value_parser(value_parser!(String)),
1176 )
1177 .arg(
1178 Arg::new("output")
1179 .short('o')
1180 .long("output")
1181 .value_parser(value_parser!(String))
1182 .help("Write DSSE envelope to file instead of stdout"),
1183 ),
1184 )
1185 .subcommand_required(true)
1186 .arg_required_else_help(true),
1187 )
1188 .subcommand(
1189 Command::new("verify")
1190 .about("Verify a signed JACS document (no agent required)")
1191 .arg(
1192 Arg::new("file")
1193 .help("Path to the signed JACS JSON file")
1194 .required_unless_present("remote")
1195 .value_parser(value_parser!(String)),
1196 )
1197 .arg(
1198 Arg::new("remote")
1199 .long("remote")
1200 .value_parser(value_parser!(String))
1201 .help("Fetch document from URL before verifying"),
1202 )
1203 .arg(
1204 Arg::new("json")
1205 .long("json")
1206 .action(ArgAction::SetTrue)
1207 .help("Output result as JSON"),
1208 )
1209 .arg(
1210 Arg::new("key-dir")
1211 .long("key-dir")
1212 .value_parser(value_parser!(String))
1213 .help("Directory containing public keys for verification"),
1214 )
1215 );
1216
1217 #[cfg(feature = "keychain")]
1219 let cmd = cmd.subcommand(
1220 Command::new("keychain")
1221 .about("Manage private key passwords in the OS keychain (per-agent)")
1222 .subcommand(
1223 Command::new("set")
1224 .about("Store a password in the OS keychain for an agent")
1225 .arg(
1226 Arg::new("agent-id")
1227 .long("agent-id")
1228 .help("Agent ID to associate the password with")
1229 .value_name("AGENT_ID")
1230 .required(true),
1231 )
1232 .arg(
1233 Arg::new("password")
1234 .long("password")
1235 .help("Password to store (if omitted, prompts interactively)")
1236 .value_name("PASSWORD"),
1237 ),
1238 )
1239 .subcommand(
1240 Command::new("get")
1241 .about("Retrieve the stored password for an agent (prints to stdout)")
1242 .arg(
1243 Arg::new("agent-id")
1244 .long("agent-id")
1245 .help("Agent ID to look up")
1246 .value_name("AGENT_ID")
1247 .required(true),
1248 ),
1249 )
1250 .subcommand(
1251 Command::new("delete")
1252 .about("Remove the stored password for an agent from the OS keychain")
1253 .arg(
1254 Arg::new("agent-id")
1255 .long("agent-id")
1256 .help("Agent ID whose password to delete")
1257 .value_name("AGENT_ID")
1258 .required(true),
1259 ),
1260 )
1261 .subcommand(
1262 Command::new("status")
1263 .about("Check if a password is stored for an agent in the OS keychain")
1264 .arg(
1265 Arg::new("agent-id")
1266 .long("agent-id")
1267 .help("Agent ID to check")
1268 .value_name("AGENT_ID")
1269 .required(true),
1270 ),
1271 )
1272 .arg_required_else_help(true),
1273 );
1274
1275 let cmd = cmd.subcommand(
1276 Command::new("convert")
1277 .about(
1278 "Convert JACS documents between JSON, YAML, and HTML formats (no agent required)",
1279 )
1280 .arg(
1281 Arg::new("to")
1282 .long("to")
1283 .required(true)
1284 .value_parser(["json", "yaml", "html"])
1285 .help("Target format: json, yaml, or html"),
1286 )
1287 .arg(
1288 Arg::new("from")
1289 .long("from")
1290 .value_parser(["json", "yaml", "html"])
1291 .help("Source format (auto-detected from extension if omitted)"),
1292 )
1293 .arg(
1294 Arg::new("file")
1295 .short('f')
1296 .long("file")
1297 .required(true)
1298 .value_parser(value_parser!(String))
1299 .help("Input file path (use '-' for stdin)"),
1300 )
1301 .arg(
1302 Arg::new("output")
1303 .short('o')
1304 .long("output")
1305 .value_parser(value_parser!(String))
1306 .help("Output file path (defaults to stdout)"),
1307 ),
1308 );
1309
1310 cmd.subcommand(
1313 Command::new("sign-text")
1314 .about("Sign a text/markdown file in place with an inline JACS signature")
1315 .arg(
1316 Arg::new("file")
1317 .help("Path to the text file to sign in place")
1318 .required(true)
1319 .value_parser(value_parser!(String)),
1320 )
1321 .arg(
1322 Arg::new("no-backup")
1323 .long("no-backup")
1324 .action(ArgAction::SetTrue)
1325 .help("Skip the automatic <path>.bak backup"),
1326 )
1327 .arg(
1328 Arg::new("json")
1329 .long("json")
1330 .action(ArgAction::SetTrue)
1331 .help("Output result as JSON"),
1332 ),
1333 )
1334 .subcommand(
1335 Command::new("verify-text")
1336 .about("Verify inline JACS signatures in a text/markdown file")
1337 .arg(
1338 Arg::new("file")
1339 .help("Path to the signed text file")
1340 .required(true)
1341 .value_parser(value_parser!(String)),
1342 )
1343 .arg(
1344 Arg::new("key-dir")
1345 .long("key-dir")
1346 .value_parser(value_parser!(String))
1347 .help("Directory containing signer public keys (.public.pem)"),
1348 )
1349 .arg(
1350 Arg::new("json")
1351 .long("json")
1352 .action(ArgAction::SetTrue)
1353 .help("Output result as JSON"),
1354 )
1355 .arg(
1356 Arg::new("strict")
1357 .long("strict")
1358 .action(ArgAction::SetTrue)
1359 .help(
1360 "Treat 'no JACS signature found' as a hard failure (exits 1 instead of 2)",
1361 ),
1362 ),
1363 )
1364 .subcommand(
1365 Command::new("sign-image")
1366 .about("Sign an image (PNG, JPEG, WebP) by embedding a JACS signature")
1367 .arg(
1368 Arg::new("input")
1369 .help("Path to the input image")
1370 .required(true)
1371 .value_parser(value_parser!(String)),
1372 )
1373 .arg(
1374 Arg::new("out")
1375 .long("out")
1376 .required(true)
1377 .value_parser(value_parser!(String))
1378 .help("Output image path"),
1379 )
1380 .arg(
1381 Arg::new("robust")
1382 .long("robust")
1383 .action(ArgAction::SetTrue)
1384 .help("Enable LSB fallback encoding (modifies pixel data; PNG/JPEG only)"),
1385 )
1386 .arg(
1387 Arg::new("format")
1388 .long("format")
1389 .value_parser(["png", "jpeg", "webp"])
1390 .help("Force a specific format (auto-detected by default)"),
1391 )
1392 .arg(
1393 Arg::new("refuse-overwrite")
1394 .long("refuse-overwrite")
1395 .action(ArgAction::SetTrue)
1396 .help("Refuse to overwrite an existing JACS signature on the input"),
1397 )
1398 .arg(
1399 Arg::new("json")
1400 .long("json")
1401 .action(ArgAction::SetTrue)
1402 .help("Output result as JSON"),
1403 ),
1404 )
1405 .subcommand(
1406 Command::new("verify-image")
1407 .about("Verify an embedded JACS signature in an image")
1408 .arg(
1409 Arg::new("file")
1410 .help("Path to the signed image")
1411 .required(true)
1412 .value_parser(value_parser!(String)),
1413 )
1414 .arg(
1415 Arg::new("key-dir")
1416 .long("key-dir")
1417 .value_parser(value_parser!(String))
1418 .help("Directory containing signer public keys (.public.pem)"),
1419 )
1420 .arg(
1421 Arg::new("json")
1422 .long("json")
1423 .action(ArgAction::SetTrue)
1424 .help("Output result as JSON"),
1425 )
1426 .arg(
1427 Arg::new("strict")
1428 .long("strict")
1429 .action(ArgAction::SetTrue)
1430 .help(
1431 "Treat 'no JACS signature found' as a hard failure (exits 1 instead of 2)",
1432 ),
1433 )
1434 .arg(
1435 Arg::new("robust")
1436 .long("robust")
1437 .action(ArgAction::SetTrue)
1438 .help("Scan LSB channel for the robust-mode payload (default off)"),
1439 ),
1440 )
1441 .subcommand(
1442 Command::new("extract-media-signature")
1443 .about("Extract the embedded JACS signature payload from an image")
1444 .arg(
1445 Arg::new("file")
1446 .help("Path to the image to extract from")
1447 .required(true)
1448 .value_parser(value_parser!(String)),
1449 )
1450 .arg(
1451 Arg::new("raw-payload")
1452 .long("raw-payload")
1453 .action(ArgAction::SetTrue)
1454 .help("Print the raw base64url wire form instead of the decoded JSON"),
1455 )
1456 .arg(
1457 Arg::new("robust")
1458 .long("robust")
1459 .action(ArgAction::SetTrue)
1460 .help(
1461 "Scan the LSB channel as a fallback if the metadata channel has \
1462 no payload (R-011; mirrors verify-image --robust)",
1463 ),
1464 ),
1465 )
1466}