From dc74998718e740fa1a3e94025addaf0884820aaa Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:24:32 -0700 Subject: [PATCH] fix(sessions): support stdout (-) in session and snapshot export (salvage #3617) (#3641) * fix(sessions): support stdout when output path is '-' in session export * fix: style cleanup + extend stdout support to snapshot export Follow-up for salvaged PR #3617: - Fix import sys; on one line (style consistency) - Update help text to mention - for stdout - Apply same stdout support to hermes skills snapshot export --------- Co-authored-by: ygd58 --- hermes_cli/main.py | 26 ++++++++++++++++++-------- hermes_cli/skills_hub.py | 13 +++++++++---- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index cdb27c024..1f2750b3c 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3713,7 +3713,7 @@ For more help on a command: skills_snapshot = skills_subparsers.add_parser("snapshot", help="Export/import skill configurations") snapshot_subparsers = skills_snapshot.add_subparsers(dest="snapshot_action") snap_export = snapshot_subparsers.add_parser("export", help="Export installed skills to a file") - snap_export.add_argument("output", help="Output JSON file path") + snap_export.add_argument("output", help="Output JSON file path (use - for stdout)") snap_import = snapshot_subparsers.add_parser("import", help="Import and install skills from a file") snap_import.add_argument("input", help="Input JSON file path") snap_import.add_argument("--force", action="store_true", help="Force install despite caution verdict") @@ -3990,7 +3990,7 @@ For more help on a command: sessions_list.add_argument("--limit", type=int, default=20, help="Max sessions to show") sessions_export = sessions_subparsers.add_parser("export", help="Export sessions to a JSONL file") - sessions_export.add_argument("output", help="Output JSONL file path") + sessions_export.add_argument("output", help="Output JSONL file path (use - for stdout)") sessions_export.add_argument("--source", help="Filter by source") sessions_export.add_argument("--session-id", help="Export a specific session") @@ -4071,15 +4071,25 @@ For more help on a command: if not data: print(f"Session '{args.session_id}' not found.") return - with open(args.output, "w", encoding="utf-8") as f: - f.write(_json.dumps(data, ensure_ascii=False) + "\n") - print(f"Exported 1 session to {args.output}") + line = _json.dumps(data, ensure_ascii=False) + "\n" + if args.output == "-": + import sys + sys.stdout.write(line) + else: + with open(args.output, "w", encoding="utf-8") as f: + f.write(line) + print(f"Exported 1 session to {args.output}") else: sessions = db.export_all(source=args.source) - with open(args.output, "w", encoding="utf-8") as f: + if args.output == "-": + import sys for s in sessions: - f.write(_json.dumps(s, ensure_ascii=False) + "\n") - print(f"Exported {len(sessions)} sessions to {args.output}") + sys.stdout.write(_json.dumps(s, ensure_ascii=False) + "\n") + else: + with open(args.output, "w", encoding="utf-8") as f: + for s in sessions: + f.write(_json.dumps(s, ensure_ascii=False) + "\n") + print(f"Exported {len(sessions)} sessions to {args.output}") elif action == "delete": resolved_session_id = db.resolve_session_id(args.session_id) diff --git a/hermes_cli/skills_hub.py b/hermes_cli/skills_hub.py index 02082f179..a1f15f863 100644 --- a/hermes_cli/skills_hub.py +++ b/hermes_cli/skills_hub.py @@ -887,10 +887,15 @@ def do_snapshot_export(output_path: str, console: Optional[Console] = None) -> N "taps": tap_list, } - out = Path(output_path) - out.write_text(json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n") - c.print(f"[bold green]Snapshot exported:[/] {out}") - c.print(f"[dim]{len(installed)} skill(s), {len(tap_list)} tap(s)[/]\n") + payload = json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n" + if output_path == "-": + import sys + sys.stdout.write(payload) + else: + out = Path(output_path) + out.write_text(payload) + c.print(f"[bold green]Snapshot exported:[/] {out}") + c.print(f"[dim]{len(installed)} skill(s), {len(tap_list)} tap(s)[/]\n") def do_snapshot_import(input_path: str, force: bool = False,