Skip to content

Commit 7a31e7c

Browse files
committed
Enforce json/jsonl semantics per command
1 parent 15a4365 commit 7a31e7c

1 file changed

Lines changed: 33 additions & 1 deletion

File tree

jmapc_cli.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ def add_connection_opts(p: argparse.ArgumentParser) -> None:
621621
p.add_argument("--account", default=env_default("JMAP_ACCOUNT", "primary"), help="Account id or 'primary'")
622622
p.add_argument("--timeout", type=float, default=float(env_default("JMAP_TIMEOUT", "30")), help="HTTP timeout seconds")
623623
p.add_argument("--insecure", action="store_true", help="Skip TLS verification")
624-
p.add_argument("--json", choices=["compact", "pretty", "jsonl"], default="compact", help="JSON output style")
624+
p.add_argument("--json", choices=["compact", "pretty", "jsonl"], help="JSON output style")
625625

626626

627627
def build_parser() -> argparse.ArgumentParser:
@@ -637,9 +637,11 @@ def build_parser() -> argparse.ArgumentParser:
637637

638638
s = sub.add_parser("session.get", help="Return JMAP session object")
639639
add_connection_opts(s)
640+
s.set_defaults(json="compact")
640641

641642
eq = sub.add_parser("email.query", help="Email/query")
642643
add_connection_opts(eq)
644+
eq.set_defaults(json="compact")
643645
eq.add_argument("--filter", help="EmailQueryFilter JSON (inline, @file, @-)")
644646
eq.add_argument("--sort", help="JSON array of Comparator objects")
645647
eq.add_argument("--limit", type=int, default=10)
@@ -649,16 +651,19 @@ def build_parser() -> argparse.ArgumentParser:
649651

650652
eg = sub.add_parser("email.get", help="Email/get")
651653
add_connection_opts(eg)
654+
eg.set_defaults(json="compact")
652655
eg.add_argument("--ids", required=True, help="JSON array of ids or @file/@-")
653656
eg.add_argument("--properties", help="JSON array of properties")
654657

655658
ech = sub.add_parser("email.changes", help="Email/changes")
656659
add_connection_opts(ech)
660+
ech.set_defaults(json="compact")
657661
ech.add_argument("--since-state", required=True, help="sinceState string")
658662
ech.add_argument("--max-changes", type=int, help="Maximum changes")
659663

660664
eqc = sub.add_parser("email.query-changes", help="Email/queryChanges")
661665
add_connection_opts(eqc)
666+
eqc.set_defaults(json="compact")
662667
eqc.add_argument("--since-query-state", required=True, help="sinceQueryState string")
663668
eqc.add_argument("--filter", help="EmailQueryFilter JSON (inline/@file/@-)")
664669
eqc.add_argument("--sort", help="JSON array of Comparator objects")
@@ -669,23 +674,27 @@ def build_parser() -> argparse.ArgumentParser:
669674

670675
mq = sub.add_parser("mailbox.query", help="Mailbox/query")
671676
add_connection_opts(mq)
677+
mq.set_defaults(json="compact")
672678
mq.add_argument("--filter", help="MailboxQueryFilter JSON (inline/@file/@-)")
673679
mq.add_argument("--sort", help="JSON array of Comparator objects")
674680
mq.add_argument("--limit", type=int, default=10)
675681
mq.add_argument("--position", type=int, default=0)
676682

677683
tg = sub.add_parser("thread.get", help="Thread/get")
678684
add_connection_opts(tg)
685+
tg.set_defaults(json="compact")
679686
tg.add_argument("--ids", required=True, help="JSON array of thread ids or @file/@-")
680687

681688
ss = sub.add_parser("searchsnippet.get", help="SearchSnippet/get")
682689
add_connection_opts(ss)
690+
ss.set_defaults(json="compact")
683691
ss.add_argument("--email-ids", required=True, help="JSON array of email ids")
684692
ss.add_argument("--filter", help="EmailQueryFilter JSON")
685693
ss.add_argument("--properties", nargs="+", help="Snippet properties")
686694

687695
ev = sub.add_parser("events.listen", help="Listen to JMAP event stream")
688696
add_connection_opts(ev)
697+
ev.set_defaults(json="jsonl")
689698
ev.add_argument("--since", help="lastEventId")
690699
ev.add_argument("--max-events", type=int, help="Max events before exit")
691700
ev.add_argument("--types", help="Comma-separated event types (default *)")
@@ -704,6 +713,29 @@ def main(argv: Optional[List[str]] = None) -> int:
704713
parser = build_parser()
705714
args = parser.parse_args(argv)
706715

716+
# Enforce json mode compatibility before doing any work
717+
streaming_commands = {"events.listen"}
718+
if args.command in streaming_commands and args.json != "jsonl":
719+
err = envelope(
720+
False,
721+
args.command,
722+
vars(args),
723+
meta_block(getattr(args, "host", "n/a"), "unknown", []),
724+
error={"type": "validationError", "message": "--json must be jsonl for streaming commands", "details": {}},
725+
)
726+
json_dump(err, "compact")
727+
return 2
728+
if args.command not in streaming_commands and args.json == "jsonl":
729+
err = envelope(
730+
False,
731+
args.command,
732+
vars(args),
733+
meta_block(getattr(args, "host", "unknown"), "unknown", []),
734+
error={"type": "validationError", "message": "jsonl is only supported for streaming commands", "details": {}},
735+
)
736+
json_dump(err, "compact")
737+
return 2
738+
707739
if args.command in {"help", "describe"}:
708740
# No auth needed for introspection
709741
args_dict = vars(args)

0 commit comments

Comments
 (0)