unkn0wn-root/resterm/main 878k tokens More Tools
```
├── .github/
   ├── workflows/
      ├── ci.yml (500 tokens)
├── .gitignore
├── CHANGELOG.md (10.4k tokens)
├── LICENSE (omitted)
├── README.md (3.6k tokens)
├── _examples/
   ├── auth_command.http (500 tokens)
   ├── auth_scopes.http (200 tokens)
   ├── basic.http (300 tokens)
   ├── bindings/
      ├── bindings.toml (100 tokens)
   ├── compare.http (100 tokens)
   ├── curl-import.http (100 tokens)
   ├── descriptors/
      ├── analytics.protoset
   ├── graphql.http (200 tokens)
   ├── grpc.http (200 tokens)
   ├── k8s.http (200 tokens)
   ├── nested.http (200 tokens)
   ├── oauth2.http (600 tokens)
   ├── openapi-spec.yml (2.4k tokens)
   ├── queries/
      ├── workspace.graphql (100 tokens)
      ├── workspace.variables.json
   ├── resterm.env.json (1100 tokens)
   ├── rts/
      ├── apply_patch.rts (100 tokens)
      ├── apply_profiles.http (100 tokens)
      ├── helpers.rts (100 tokens)
      ├── loops.rts (200 tokens)
      ├── pre_request.rts (200 tokens)
      ├── rts_all_features.http (1000 tokens)
      ├── users.json
      ├── users.rts (100 tokens)
   ├── scopes.http (400 tokens)
   ├── scripts.http (300 tokens)
   ├── service-groups.http (300 tokens)
   ├── sse_manual.http
   ├── sse_scripted.http (100 tokens)
   ├── ssh.http (100 tokens)
   ├── streaming.http (100 tokens)
   ├── themes/
      ├── aurora.toml (500 tokens)
      ├── daybreak.toml (1300 tokens)
   ├── trace.http (100 tokens)
   ├── transport.http (200 tokens)
   ├── websocket_manual.http
   ├── websocket_scripted.http (200 tokens)
   ├── workflows.http (400 tokens)
├── _media/
   ├── oauth.gif
   ├── resterm-lighttheme.png
   ├── resterm_base.png
   ├── resterm_compare.png
   ├── resterm_explain.png
   ├── resterm_full.png
   ├── resterm_hsplit.png
   ├── resterm_logo.png
   ├── resterm_profiler.png
   ├── resterm_script.png
   ├── resterm_trace_timeline.png
   ├── resterm_websocket.png
   ├── resterm_workflow.png
   ├── resterm_zommed.png
   ├── restermscript.png
├── build.sh (200 tokens)
├── cmd/
   ├── resterm/
      ├── collection.go (1600 tokens)
      ├── collection_test.go (2.1k tokens)
      ├── history.go (2000 tokens)
      ├── history_test.go (1900 tokens)
      ├── init.go (500 tokens)
      ├── install_source.go (600 tokens)
      ├── install_source_test.go (200 tokens)
      ├── load_env_test.go (400 tokens)
      ├── main.go (2.7k tokens)
      ├── main_test.go (800 tokens)
      ├── run.go (2.9k tokens)
      ├── run_test.go (5.2k tokens)
      ├── shared_env_test.go (200 tokens)
      ├── update_cli.go (1600 tokens)
      ├── update_cli_test.go (100 tokens)
├── docs/
   ├── cli.md (2.2k tokens)
   ├── resterm.md (17.5k tokens)
   ├── restermscript.md (4.6k tokens)
├── go.mod (1000 tokens)
├── go.sum (6k tokens)
├── headless/
   ├── adapt.go (1200 tokens)
   ├── adapt_test.go (3.1k tokens)
   ├── build.go (1600 tokens)
   ├── doc.go (200 tokens)
   ├── encode.go (100 tokens)
   ├── errors.go (300 tokens)
   ├── example_test.go (400 tokens)
   ├── format.go (100 tokens)
   ├── headless_test.go (200 tokens)
   ├── json.go
   ├── junit.go
   ├── options.go (600 tokens)
   ├── report.go (2.8k tokens)
   ├── run.go (200 tokens)
   ├── run_test.go (3k tokens)
   ├── text.go
   ├── types_test.go (1800 tokens)
   ├── write_test.go (2.6k tokens)
├── install.ps1 (700 tokens)
├── install.sh (700 tokens)
├── internal/
   ├── analysis/
      ├── latency.go (800 tokens)
      ├── latency_test.go (300 tokens)
   ├── authcmd/
      ├── cache.go (200 tokens)
      ├── config.go (1100 tokens)
      ├── config_test.go (700 tokens)
      ├── extract.go (1000 tokens)
      ├── extract_test.go (900 tokens)
      ├── manager.go (1600 tokens)
      ├── manager_test.go (2.5k tokens)
      ├── parse.go (800 tokens)
      ├── parse_test.go (1200 tokens)
      ├── render.go (200 tokens)
      ├── run.go (800 tokens)
      ├── run_test.go (400 tokens)
   ├── binaryview/
      ├── constants.go (100 tokens)
      ├── detect.go (1000 tokens)
      ├── format.go (600 tokens)
      ├── names.go (800 tokens)
      ├── names_test.go (400 tokens)
   ├── bindings/
      ├── bindings.go (2k tokens)
      ├── bindings_test.go (500 tokens)
      ├── defaults.go (1300 tokens)
   ├── bodyfmt/
      ├── bodyfmt.go (2.7k tokens)
      ├── bodyfmt_test.go (600 tokens)
   ├── capture/
      ├── capture.go (1000 tokens)
      ├── capture_test.go (500 tokens)
   ├── cli/
      ├── compare.go (200 tokens)
      ├── conflict.go (100 tokens)
      ├── conflict_test.go (200 tokens)
      ├── env.go (100 tokens)
      ├── exec.go (1100 tokens)
      ├── exec_test.go (500 tokens)
      ├── exit.go (100 tokens)
      ├── flag.go (300 tokens)
      ├── flag_test.go (200 tokens)
      ├── report_text.go (200 tokens)
      ├── report_text_test.go (300 tokens)
      ├── run_pick.go (700 tokens)
      ├── run_pick_test.go (800 tokens)
      ├── run_picker.go (2.8k tokens)
      ├── run_source.go (200 tokens)
   ├── collection/
      ├── archive.go (1600 tokens)
      ├── archive_test.go (1100 tokens)
      ├── checksum.go (200 tokens)
      ├── checksum_test.go (200 tokens)
      ├── env.go (300 tokens)
      ├── export.go (1200 tokens)
      ├── export_test.go (1400 tokens)
      ├── import.go (1500 tokens)
      ├── import_test.go (1600 tokens)
      ├── manifest.go (500 tokens)
      ├── manifest_test.go (600 tokens)
      ├── path.go (1000 tokens)
      ├── path_test.go (400 tokens)
      ├── refs.go (400 tokens)
      ├── types.go (300 tokens)
   ├── config/
      ├── layout.go (700 tokens)
      ├── layout_test.go (600 tokens)
      ├── path.go (100 tokens)
      ├── settings.go (800 tokens)
      ├── settings_test.go (500 tokens)
      ├── theme.go
   ├── connprofile/
      ├── normalize.go (500 tokens)
      ├── resolve.go (200 tokens)
      ├── resolve_test.go (500 tokens)
   ├── curl/
      ├── ast.go
      ├── body.go (1700 tokens)
      ├── constants.go (200 tokens)
      ├── errors.go
      ├── importer/
         ├── render_test.go (500 tokens)
         ├── service.go (500 tokens)
         ├── service_test.go (400 tokens)
         ├── testdata/
            ├── advanced.http (700 tokens)
            ├── basic.http
         ├── writer.go (100 tokens)
      ├── info.go
      ├── norm.go (800 tokens)
      ├── options.go (2.3k tokens)
      ├── parse_cmd.go (600 tokens)
      ├── parser.go (400 tokens)
      ├── parser_test.go (2.1k tokens)
      ├── scan.go (1300 tokens)
      ├── scan_test.go (400 tokens)
      ├── token_state.go (400 tokens)
      ├── tokenizer.go (500 tokens)
      ├── warnings.go (200 tokens)
   ├── duration/
      ├── parse.go (500 tokens)
      ├── parse_test.go (300 tokens)
   ├── engine/
      ├── core/
         ├── cmp.go (800 tokens)
         ├── cmp_evt.go (200 tokens)
         ├── cmp_spec.go (400 tokens)
         ├── cmp_test.go (1000 tokens)
         ├── dep.go (200 tokens)
         ├── evt.go (400 tokens)
         ├── meta.go (200 tokens)
         ├── pro.go (800 tokens)
         ├── pro_evt.go (200 tokens)
         ├── pro_test.go (1300 tokens)
         ├── run_util.go
         ├── sink.go (100 tokens)
         ├── sink_test.go (700 tokens)
         ├── wf.go (5.7k tokens)
         ├── wf_evt.go (400 tokens)
         ├── wf_test.go (1700 tokens)
      ├── headless/
         ├── clone.go (300 tokens)
         ├── clone_test.go (900 tokens)
         ├── cmp.go (1300 tokens)
         ├── cmp_result.go (700 tokens)
         ├── cmp_result_test.go (200 tokens)
         ├── cmp_test.go (400 tokens)
         ├── engine.go (1400 tokens)
         ├── engine_test.go (2.4k tokens)
         ├── errors.go (100 tokens)
         ├── history.go (700 tokens)
         ├── pf.go (1400 tokens)
         ├── pf_result.go (600 tokens)
         ├── pf_test.go (500 tokens)
         ├── request_meta.go (100 tokens)
         ├── wf.go (2.1k tokens)
         ├── wf_result.go (1500 tokens)
         ├── wf_result_test.go (300 tokens)
      ├── request.go (500 tokens)
      ├── request/
         ├── auth.go (2.7k tokens)
         ├── auth_test.go (2.4k tokens)
         ├── capture.go (3.5k tokens)
         ├── clone.go (600 tokens)
         ├── engine.go (5.1k tokens)
         ├── explain_build.go (6.2k tokens)
         ├── explain_redact.go (900 tokens)
         ├── history.go (1500 tokens)
         ├── proto.go (2k tokens)
         ├── registry.go (100 tokens)
         ├── rts.go (5.7k tokens)
         ├── text.go (1400 tokens)
         ├── util.go (200 tokens)
         ├── vars.go (2.3k tokens)
         ├── vars_test.go (600 tokens)
      ├── runtime/
         ├── cookies.go (200 tokens)
         ├── files.go (500 tokens)
         ├── globals.go (400 tokens)
         ├── runtime.go (700 tokens)
         ├── runtime_test.go (1100 tokens)
         ├── store.go (500 tokens)
      ├── types.go (1000 tokens)
   ├── errdef/
      ├── errdef.go (300 tokens)
   ├── exec/
      ├── flow.go (400 tokens)
      ├── flow_test.go (800 tokens)
      ├── http.go (2.1k tokens)
      ├── http_test.go (600 tokens)
   ├── explain/
      ├── defs.go (500 tokens)
      ├── types.go (200 tokens)
   ├── filesvc/
      ├── list.go (900 tokens)
      ├── list_test.go (900 tokens)
   ├── grpcclient/
      ├── client.go (2.5k tokens)
      ├── client_test.go (1000 tokens)
      ├── metadata.go (600 tokens)
      ├── streaming.go (2.1k tokens)
      ├── streaming_test.go (500 tokens)
      ├── test_helpers_test.go (300 tokens)
   ├── history/
      ├── sqlite/
         ├── codec.go (100 tokens)
         ├── corruption_test.go (200 tokens)
         ├── io.go (900 tokens)
         ├── io_test.go (1000 tokens)
         ├── mig.go (700 tokens)
         ├── mig_test.go (900 tokens)
         ├── ops.go (300 tokens)
         ├── ops_test.go (200 tokens)
         ├── recovery_test.go (200 tokens)
         ├── schema.go (1200 tokens)
         ├── schema_test.go (500 tokens)
         ├── store.go (2.5k tokens)
         ├── store_test.go (1100 tokens)
      ├── store.go (200 tokens)
      ├── trace.go (2000 tokens)
      ├── trace_test.go (1100 tokens)
      ├── types.go (500 tokens)
   ├── httpclient/
      ├── body.go (1400 tokens)
      ├── client.go (1500 tokens)
      ├── client_test.go (200 tokens)
      ├── executor_test.go (6.4k tokens)
      ├── fs.go (800 tokens)
      ├── httpver.go (200 tokens)
      ├── options_apply.go (300 tokens)
      ├── request.go (900 tokens)
      ├── request_build.go (600 tokens)
      ├── request_test.go (300 tokens)
      ├── resp_helpers.go (300 tokens)
      ├── stream_headers.go
      ├── stream_helpers.go (300 tokens)
      ├── stream_sse.go (2.4k tokens)
      ├── stream_transcript.go (100 tokens)
      ├── stream_types.go (100 tokens)
      ├── stream_ws.go (5.3k tokens)
      ├── stream_ws_test.go (2.5k tokens)
      ├── trace.go (1100 tokens)
      ├── trace_details.go (1400 tokens)
      ├── trace_details_test.go (200 tokens)
      ├── transport.go (600 tokens)
      ├── ws_linkname.go (100 tokens)
   ├── httpver/
      ├── httpver.go (300 tokens)
      ├── httpver_test.go (300 tokens)
   ├── initcmd/
      ├── command.go (200 tokens)
      ├── constants.go (100 tokens)
      ├── fs.go (300 tokens)
      ├── gitignore.go (500 tokens)
      ├── init_test.go (900 tokens)
      ├── options.go (100 tokens)
      ├── plan.go (300 tokens)
      ├── runner.go (400 tokens)
      ├── templates.go (1100 tokens)
      ├── types.go (100 tokens)
   ├── k8s/
      ├── auth_plugins.go
      ├── clientconfig.go (1000 tokens)
      ├── clientconfig_test.go (900 tokens)
      ├── config.go (1200 tokens)
      ├── config_test.go (1200 tokens)
      ├── manager.go (1800 tokens)
      ├── manager_test.go (4.7k tokens)
      ├── plan.go
      ├── portforward.go (1100 tokens)
      ├── ports.go (900 tokens)
      ├── resolve.go (900 tokens)
      ├── resolve_test.go (1000 tokens)
      ├── runtime_diag.go (800 tokens)
      ├── runtime_diag_test.go (800 tokens)
      ├── target/
         ├── target.go (500 tokens)
         ├── target_test.go (200 tokens)
      ├── targets.go (1600 tokens)
   ├── nettrace/
      ├── budget_test.go (300 tokens)
      ├── collector.go (600 tokens)
      ├── collector_test.go (400 tokens)
      ├── report.go (200 tokens)
      ├── report_test.go (200 tokens)
      ├── timeline.go (700 tokens)
      ├── trace_details.go (300 tokens)
   ├── oauth/
      ├── authcode.go (1700 tokens)
      ├── config.go (400 tokens)
      ├── manager.go (3.7k tokens)
      ├── manager_test.go (3.6k tokens)
   ├── openapi/
      ├── constants.go (200 tokens)
      ├── contract.go (200 tokens)
      ├── generator/
         ├── gen_const.go (100 tokens)
         ├── generator.go (5.3k tokens)
         ├── generator_test.go (2.3k tokens)
         ├── schema_samples.go (1100 tokens)
         ├── schema_samples_test.go (600 tokens)
      ├── model/
         ├── clone.go (100 tokens)
         ├── spec.go (1100 tokens)
         ├── spec_test.go (800 tokens)
      ├── parser/
         ├── compat.go (2.1k tokens)
         ├── parser.go (3.1k tokens)
         ├── parser_test.go (3.6k tokens)
         ├── schema.go (700 tokens)
         ├── schema_test.go (700 tokens)
      ├── service.go (400 tokens)
      ├── service_test.go (700 tokens)
      ├── testdata/
         ├── deviceinventory.yaml (800 tokens)
      ├── writer/
         ├── testdata/
            ├── deviceinventory.http (300 tokens)
         ├── writer.go (100 tokens)
         ├── writer_test.go (300 tokens)
   ├── parser/
      ├── capture_lint.go (200 tokens)
      ├── comment_handlers.go (1800 tokens)
      ├── directive_parsers.go (2.8k tokens)
      ├── document_builder.go (3.6k tokens)
      ├── graphqlbuilder/
         ├── builder.go (700 tokens)
      ├── grpcbuilder/
         ├── builder.go (900 tokens)
      ├── httpbuilder/
         ├── builder.go (500 tokens)
      ├── javascript/
         ├── object.go (3.7k tokens)
         ├── object_test.go (400 tokens)
      ├── k8s_builder.go (1500 tokens)
      ├── parse_helpers.go (400 tokens)
      ├── parser.go (200 tokens)
      ├── parser_test.go (15.3k tokens)
      ├── request_builder.go (1000 tokens)
      ├── scope_helpers.go (100 tokens)
      ├── sse_builder.go (300 tokens)
      ├── ssh_builder.go (1000 tokens)
      ├── stream_utils.go (300 tokens)
      ├── token_utils.go (1900 tokens)
      ├── workflow_builder.go (2.5k tokens)
      ├── ws_builder.go (1000 tokens)
   ├── registry/
      ├── index.go (2k tokens)
      ├── index_test.go (900 tokens)
   ├── restfile/
      ├── auth.go (200 tokens)
      ├── named_lookup.go (100 tokens)
      ├── types.go (1700 tokens)
   ├── restwriter/
      ├── writer.go (2000 tokens)
      ├── writer_test.go (300 tokens)
   ├── rtfmt/
      ├── rtfmt.go (100 tokens)
   ├── rts/
      ├── assert.go (100 tokens)
      ├── ast.go (700 tokens)
      ├── conv.go (600 tokens)
      ├── ctx.go (600 tokens)
      ├── engine.go (800 tokens)
      ├── helpers.go (600 tokens)
      ├── host.go (800 tokens)
      ├── jsonpath.go (600 tokens)
      ├── lexer.go (1200 tokens)
      ├── lexer_test.go (100 tokens)
      ├── map_helpers.go (400 tokens)
      ├── modobj.go (100 tokens)
      ├── module.go (400 tokens)
      ├── module_test.go (500 tokens)
      ├── native_args.go (200 tokens)
      ├── parser.go (3k tokens)
      ├── parser_test.go (1100 tokens)
      ├── pos.go (100 tokens)
      ├── query_helpers.go (200 tokens)
      ├── request.go (1400 tokens)
      ├── request_test.go (1000 tokens)
      ├── require_test.go (200 tokens)
      ├── response_test.go (400 tokens)
      ├── result.go (200 tokens)
      ├── stdlib.go (500 tokens)
      ├── stdlib_base64.go (200 tokens)
      ├── stdlib_call.go (100 tokens)
      ├── stdlib_core.go (700 tokens)
      ├── stdlib_crypto.go (200 tokens)
      ├── stdlib_dict.go (1300 tokens)
      ├── stdlib_encoding.go (500 tokens)
      ├── stdlib_headers.go (1000 tokens)
      ├── stdlib_json.go (1000 tokens)
      ├── stdlib_list.go (1500 tokens)
      ├── stdlib_math.go (500 tokens)
      ├── stdlib_query.go (600 tokens)
      ├── stdlib_test.go (3.2k tokens)
      ├── stdlib_text.go (800 tokens)
      ├── stdlib_time.go (1000 tokens)
      ├── stdlib_types.go (700 tokens)
      ├── stdlib_url.go (200 tokens)
      ├── stream.go (800 tokens)
      ├── stream_test.go (300 tokens)
      ├── token.go (600 tokens)
      ├── trace.go (2.2k tokens)
      ├── trace_test.go (1000 tokens)
      ├── use_resolve.go (500 tokens)
      ├── value.go (600 tokens)
      ├── vars_obj.go (1000 tokens)
      ├── vm.go (3.2k tokens)
      ├── vm_test.go (1400 tokens)
   ├── runcheck/
      ├── conflict.go (200 tokens)
      ├── conflict_test.go (300 tokens)
   ├── runfmt/
      ├── clone.go (100 tokens)
      ├── fmt.go (2000 tokens)
      ├── json.go (2.6k tokens)
      ├── junit.go (700 tokens)
      ├── junit_test.go (200 tokens)
      ├── model.go (600 tokens)
      ├── text.go (900 tokens)
      ├── text_profile.go (1800 tokens)
      ├── text_test.go (1500 tokens)
   ├── runner/
      ├── artifact.go (800 tokens)
      ├── doc.go (600 tokens)
      ├── errors.go (100 tokens)
      ├── normalize_test.go (800 tokens)
      ├── path.go (100 tokens)
      ├── path_test.go (100 tokens)
      ├── plan.go (1300 tokens)
      ├── plan_test.go (1800 tokens)
      ├── report_fmt.go (1600 tokens)
      ├── report_fmt_test.go (1100 tokens)
      ├── report_junit_test.go (400 tokens)
      ├── report_write_test.go (100 tokens)
      ├── run.go (3.2k tokens)
      ├── run_test.go (9.9k tokens)
      ├── select.go (1400 tokens)
      ├── select_test.go (100 tokens)
      ├── state.go (700 tokens)
      ├── view.go (300 tokens)
   ├── runview/
      ├── style.go (700 tokens)
      ├── view.go (2.7k tokens)
      ├── view_test.go (1600 tokens)
   ├── scripts/
      ├── response.go (200 tokens)
      ├── runner.go (4.5k tokens)
      ├── runner_test.go (3.5k tokens)
      ├── stream_info.go (200 tokens)
      ├── trace.go (1800 tokens)
   ├── settings/
      ├── apply.go (900 tokens)
      ├── env.go (200 tokens)
      ├── handlers.go (100 tokens)
      ├── keys.go (100 tokens)
      ├── keys_test.go (100 tokens)
      ├── settings.go (300 tokens)
      ├── settings_test.go (800 tokens)
   ├── ssh/
      ├── config.go (900 tokens)
      ├── config_test.go (1100 tokens)
      ├── manager.go (1600 tokens)
      ├── manager_test.go (1200 tokens)
      ├── plan.go
      ├── resolve.go (600 tokens)
      ├── resolve_test.go (200 tokens)
   ├── stream/
      ├── event.go (200 tokens)
      ├── manager.go (600 tokens)
      ├── manager_test.go (300 tokens)
      ├── ringbuffer.go (100 tokens)
      ├── session.go (1200 tokens)
      ├── session_test.go (300 tokens)
   ├── telemetry/
      ├── config.go (500 tokens)
      ├── config_test.go (300 tokens)
      ├── telemetry.go (2k tokens)
      ├── telemetry_test.go (700 tokens)
   ├── termcolor/
      ├── termcolor.go (500 tokens)
      ├── termcolor_test.go (400 tokens)
   ├── theme/
      ├── definition.go (200 tokens)
      ├── loader.go (1100 tokens)
      ├── loader_test.go (500 tokens)
      ├── spec.go (7.6k tokens)
      ├── spec_test.go (1400 tokens)
      ├── theme.go (3.7k tokens)
   ├── tlsconfig/
      ├── tlsconfig.go (500 tokens)
      ├── tlsconfig_test.go (900 tokens)
   ├── tracebudget/
      ├── budget.go (500 tokens)
      ├── budget_test.go (500 tokens)
   ├── tunnel/
      ├── conn.go (100 tokens)
      ├── conn_test.go (400 tokens)
      ├── transport.go (300 tokens)
   ├── ui/
      ├── capture_eval.go (3.7k tokens)
      ├── ctx.go
      ├── editor_clipboard.go (400 tokens)
      ├── editor_metadata_highlighter.go (2.1k tokens)
      ├── editor_metadata_highlighter_test.go (900 tokens)
      ├── editor_metadata_hints_test.go (900 tokens)
      ├── editor_rts_highlighter.go (1400 tokens)
      ├── editor_rts_highlighter_test.go (400 tokens)
      ├── editor_state.go (10.8k tokens)
      ├── editor_state_test.go (8.7k tokens)
      ├── env_selector.go (100 tokens)
      ├── explain.go (8.3k tokens)
      ├── explain_render.go (2.1k tokens)
      ├── explain_style.go (400 tokens)
      ├── explain_test.go (3.2k tokens)
      ├── explain_view.go (1000 tokens)
      ├── file_browser.go (100 tokens)
      ├── format.go (100 tokens)
      ├── format_js_test.go (200 tokens)
      ├── header_layout.go (600 tokens)
      ├── header_layout_test.go (600 tokens)
      ├── help_bindings.go (500 tokens)
      ├── help_modal.go (2.5k tokens)
      ├── help_modal_test.go (700 tokens)
      ├── hint/
         ├── context.go (500 tokens)
         ├── hint.go (200 tokens)
         ├── location.go (100 tokens)
         ├── manager.go (100 tokens)
         ├── metadata.go (5k tokens)
      ├── histogram_config.go
      ├── histogram_render.go (600 tokens)
      ├── histogram_render_test.go (200 tokens)
      ├── history_filter.go (1200 tokens)
      ├── history_filter_test.go (800 tokens)
      ├── history_list_delegate.go (1100 tokens)
      ├── history_redaction.go (200 tokens)
      ├── history_scope_file.go (500 tokens)
      ├── history_state.go (500 tokens)
      ├── history_view.go (900 tokens)
      ├── history_view_test.go (100 tokens)
      ├── latency_anim.go (1100 tokens)
      ├── latency_anim_test.go (300 tokens)
      ├── latency_sparkline.go (700 tokens)
      ├── latency_sparkline_test.go (600 tokens)
      ├── latency_style.go (300 tokens)
      ├── list_theme.go (600 tokens)
      ├── list_theme_test.go (200 tokens)
      ├── logo_placeholder.go (200 tokens)
      ├── logo_placeholder_test.go (300 tokens)
      ├── messages.go (400 tokens)
      ├── model_compare.go (100 tokens)
      ├── model_compare_run.go (3.6k tokens)
      ├── model_compare_state.go (1300 tokens)
      ├── model_compare_test.go (500 tokens)
      ├── model_compare_ui.go (1100 tokens)
      ├── model_compare_ui_test.go (300 tokens)
      ├── model_console.go (3.5k tokens)
      ├── model_console_test.go (300 tokens)
      ├── model_core.go (4.5k tokens)
      ├── model_engine.go (600 tokens)
      ├── model_engine_consume.go (600 tokens)
      ├── model_engine_consume_test.go (900 tokens)
      ├── model_engine_test.go (100 tokens)
      ├── model_environment.go (300 tokens)
      ├── model_environment_test.go (100 tokens)
      ├── model_error_modal.go (100 tokens)
      ├── model_exec.go (2.7k tokens)
      ├── model_exec_auth.go (600 tokens)
      ├── model_exec_explain.go (900 tokens)
      ├── model_exec_explain_test.go (1500 tokens)
      ├── model_exec_protocol.go (1400 tokens)
      ├── model_exec_request.go (2.6k tokens)
      ├── model_exec_run.go (4.9k tokens)
      ├── model_exec_test.go (14.8k tokens)
      ├── model_exec_vars.go (3.5k tokens)
      ├── model_files.go (1200 tokens)
      ├── model_files_test.go (1100 tokens)
      ├── model_filewatch.go (700 tokens)
      ├── model_focus.go (700 tokens)
      ├── model_focus_test.go (100 tokens)
      ├── model_history.go (11k tokens)
      ├── model_history_delete_test.go (300 tokens)
      ├── model_history_keys_test.go (900 tokens)
      ├── model_history_preview.go (300 tokens)
      ├── model_history_preview_test.go (500 tokens)
      ├── model_history_scope_test.go (900 tokens)
      ├── model_history_test.go (3.5k tokens)
      ├── model_layout.go (3.5k tokens)
      ├── model_layout_save.go (200 tokens)
      ├── model_layout_save_test.go (600 tokens)
      ├── model_layout_settings.go (400 tokens)
      ├── model_layout_settings_test.go (500 tokens)
      ├── model_layout_test.go (2.9k tokens)
      ├── model_navigator.go (2.8k tokens)
      ├── model_navigator_test.go (5.6k tokens)
      ├── model_newfile.go (600 tokens)
      ├── model_newfile_test.go (300 tokens)
      ├── model_open.go (800 tokens)
      ├── model_open_test.go (600 tokens)
      ├── model_orchestration_test.go (8.5k tokens)
      ├── model_panestate.go (900 tokens)
      ├── model_panestate_test.go (900 tokens)
      ├── model_profile.go (5.6k tokens)
      ├── model_render.go (13.5k tokens)
      ├── model_render_commandbar_test.go (400 tokens)
      ├── model_render_test.go (1000 tokens)
      ├── model_request_details.go (1400 tokens)
      ├── model_request_details_test.go (400 tokens)
      ├── model_response_copy.go (300 tokens)
      ├── model_response_copy_test.go (800 tokens)
      ├── model_response_raw.go (1900 tokens)
      ├── model_response_raw_test.go (2.6k tokens)
      ├── model_response_scroll.go (400 tokens)
      ├── model_response_scroll_test.go (1700 tokens)
      ├── model_response_snapshot.go (200 tokens)
      ├── model_run_events.go (200 tokens)
      ├── model_runtime.go (300 tokens)
      ├── model_search.go (1900 tokens)
      ├── model_search_stats_test.go (900 tokens)
      ├── model_search_test.go (600 tokens)
      ├── model_sidebar.go
      ├── model_status.go (100 tokens)
      ├── model_status_test.go (100 tokens)
      ├── model_stream.go (5.4k tokens)
      ├── model_stream_test.go (400 tokens)
      ├── model_tabs.go (900 tokens)
      ├── model_tabs_test.go (500 tokens)
      ├── model_theme_selector.go (400 tokens)
      ├── model_tls_settings_test.go (600 tokens)
      ├── model_update.go (9.8k tokens)
      ├── model_update_checker.go (200 tokens)
      ├── model_update_checker_test.go (200 tokens)
      ├── model_update_navigator.go (1200 tokens)
      ├── model_update_test.go (8.7k tokens)
      ├── model_utils.go (2.7k tokens)
      ├── model_utils_test.go (2.9k tokens)
      ├── model_workflow_display_test.go (100 tokens)
      ├── model_workflow_run.go (11.9k tokens)
      ├── model_workflow_stats.go (600 tokens)
      ├── model_workflow_ui.go (500 tokens)
      ├── navigator/
         ├── model.go (2.1k tokens)
         ├── model_scroll_test.go (200 tokens)
         ├── model_test.go (100 tokens)
         ├── render.go (1000 tokens)
         ├── render_test.go (1000 tokens)
      ├── raw_helpers.go (100 tokens)
      ├── registry.go (100 tokens)
      ├── request_list.go (1600 tokens)
      ├── request_list_test.go (1100 tokens)
      ├── request_meta_test.go (200 tokens)
      ├── response_async.go (600 tokens)
      ├── response_export.go (1200 tokens)
      ├── response_export_test.go (300 tokens)
      ├── response_modes.go (100 tokens)
      ├── response_reflow.go (1500 tokens)
      ├── response_render.go (3.9k tokens)
      ├── response_render_test.go (2.2k tokens)
      ├── response_search.go (900 tokens)
      ├── response_selection.go (5.7k tokens)
      ├── response_selection_test.go (1700 tokens)
      ├── response_split.go (7k tokens)
      ├── response_split_test.go (1200 tokens)
      ├── response_tab_policies.go (100 tokens)
      ├── rts_apply.go (2.4k tokens)
      ├── rts_apply_test.go (1900 tokens)
      ├── rts_assert.go (500 tokens)
      ├── rts_assert_test.go (300 tokens)
      ├── rts_directives.go (400 tokens)
      ├── rts_eval.go (1700 tokens)
      ├── rts_pre_request.go (1300 tokens)
      ├── rts_pre_request_test.go (700 tokens)
      ├── scroll/
         ├── scroll.go (400 tokens)
         ├── scroll_test.go (400 tokens)
      ├── stats_palette.go (500 tokens)
      ├── stats_render.go (4k tokens)
      ├── stats_render_test.go (1000 tokens)
      ├── status_text.go (300 tokens)
      ├── stream_info.go
      ├── stream_state.go (600 tokens)
      ├── textarea/
         ├── textarea.go (10.5k tokens)
         ├── textarea_test.go (7.9k tokens)
      ├── theme_runtime.go (1800 tokens)
      ├── theme_runtime_response.go (1200 tokens)
      ├── theme_runtime_test.go (3k tokens)
      ├── theme_selector.go (400 tokens)
      ├── timeline_details.go (1600 tokens)
      ├── timeline_details_test.go (200 tokens)
      ├── timeline_view.go (2.8k tokens)
      ├── timeline_view_test.go (1300 tokens)
      ├── websocket_response_test.go (300 tokens)
      ├── workflow_list.go (400 tokens)
      ├── workflow_state.go (300 tokens)
      ├── workflow_stats_view.go (2.4k tokens)
      ├── workflow_stats_view_test.go (2.4k tokens)
      ├── workspace_files.go (100 tokens)
      ├── wrap_structured.go (300 tokens)
   ├── update/
      ├── apply.go (1300 tokens)
      ├── apply_test.go (400 tokens)
      ├── client.go (600 tokens)
      ├── client_test.go (300 tokens)
      ├── info.go (300 tokens)
      ├── info_test.go (200 tokens)
      ├── platform.go (200 tokens)
      ├── platform_test.go (200 tokens)
      ├── semver.go (300 tokens)
      ├── semver_test.go (200 tokens)
      ├── testutil_test.go (200 tokens)
   ├── urltpl/
      ├── urltpl.go (1000 tokens)
      ├── urltpl_test.go (700 tokens)
   ├── util/
      ├── maps.go (100 tokens)
      ├── slice.go
      ├── string.go (200 tokens)
      ├── stringset.go (100 tokens)
   ├── vars/
      ├── dotenv.go (1800 tokens)
      ├── dotenv_test.go (800 tokens)
      ├── environment.go (1200 tokens)
      ├── environment_test.go (1000 tokens)
      ├── refs.go (200 tokens)
      ├── resolver.go (1700 tokens)
      ├── resolver_test.go (2000 tokens)
      ├── trace.go (400 tokens)
      ├── trace_test.go (400 tokens)
   ├── watcher/
      ├── watcher.go (1100 tokens)
      ├── watcher_test.go (1000 tokens)
   ├── wrap/
      ├── wrap.go (3.8k tokens)
      ├── wrap_test.go (800 tokens)
```


## /.github/workflows/ci.yml

```yml path="/.github/workflows/ci.yml" 
name: CI

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:
  release:
    types: [published]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - name: Run tests
        run: go test ./...
      - name: Install golangci-lint
        run: |
          go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
          echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
      - name: Run golangci-lint
        run: golangci-lint run

  release-builds:
    if: github.event_name == 'release'
    needs: lint-and-test
    runs-on: ubuntu-latest
    permissions:
      contents: write
    strategy:
      matrix:
        goos: [linux, windows, darwin]
        goarch: [amd64, arm64]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
        run: |
          set -euo pipefail
          mkdir -p dist

          VERSION="${GITHUB_REF_NAME}"
          COMMIT="${GITHUB_SHA::7}"
          DATE="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"

          LDFLAGS="-s -w -X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}'"

          case "$GOOS" in
            darwin) goos_label="Darwin" ;;
            linux) goos_label="Linux" ;;
            windows) goos_label="Windows" ;;
            *) goos_label="$GOOS" ;;
          esac
          case "$GOARCH" in
            amd64) goarch_label="x86_64" ;;
            arm64) goarch_label="arm64" ;;
            *) goarch_label="$GOARCH" ;;
          esac
          output="dist/resterm_${goos_label}_${goarch_label}"
          if [ "$GOOS" = "windows" ]; then
            output="${output}.exe"
          fi

          echo "Building version $VERSION"
          GOOS="$GOOS" GOARCH="$GOARCH" go build -trimpath -ldflags "$LDFLAGS" -o "$output" ./cmd/resterm
          sha256sum "$output" | awk '{print $1}' > "${output}.sha256"
      - name: Upload release artifacts
        uses: softprops/action-gh-release@v1
        with:
          files: dist/*

```

## /.gitignore

```gitignore path="/.gitignore" 
bin/
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out

*.cover
*.cov
*.profile
*.prof
*.pprof
coverage/

*.log
*.tmp
*.swp
*.swo
*.DS_Store
*.idea/
*.vscode/

.gocache/

/resterm

# test harness services
_test_services/

resterm_*

```

## /CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [0.30.1](https://github.com/unkn0wn-root/resterm/compare/v0.29.2...v0.30.1) (2026-04-21)


### Features

* generic runtime theme picker ([fdb8c6a](https://github.com/unkn0wn-root/resterm/commit/fdb8c6a24030d0a9e431fde16ede635d2fadaab8))
* response snapshot while changing theme ([bdf957d](https://github.com/unkn0wn-root/resterm/commit/bdf957d83f07552a77e8664b435399978e7f567f))


### Bug Fixes

* remove old unused code after refactor ([98eac2e](https://github.com/unkn0wn-root/resterm/commit/98eac2e035651a05ad598decf06be38c86891c42))
* snapshot raw ([3393934](https://github.com/unkn0wn-root/resterm/commit/33939343e09fc2caf328e6917831a4176bc0bad2))
* **ui:** improve contrast for history and help search inputs ([e293b0f](https://github.com/unkn0wn-root/resterm/commit/e293b0f1a3b5e46e5a4bbae0dbcfe795b50e7487))
* **ui:** preserve editor syntax colors on light theme cursor line ([d7f323b](https://github.com/unkn0wn-root/resterm/commit/d7f323b7582f669835829dd2449a09101e59a88b))

### [0.29.2](https://github.com/unkn0wn-root/resterm/compare/v0.29.1...v0.29.2) (2026-04-19)

### [0.29.1](https://github.com/unkn0wn-root/resterm/compare/v0.28.2...v0.29.1) (2026-04-19)


### Features

* **headless,runner:** add reusable run plans for headless execution ([47056a4](https://github.com/unkn0wn-root/resterm/commit/47056a49dae15f92eb3db9956c686242e42d864b))

### [0.28.2](https://github.com/unkn0wn-root/resterm/compare/v0.28.1...v0.28.2) (2026-04-17)


### Features

* make headless api stable ([22a454d](https://github.com/unkn0wn-root/resterm/commit/22a454dce9a90cfbdb7badeaa4ab1446d83875bb))
* use internal string util ([d6aaf6e](https://github.com/unkn0wn-root/resterm/commit/d6aaf6e46b3fc266c8a7e77e3ef59f3c884216a3))

### [0.28.1](https://github.com/unkn0wn-root/resterm/compare/v0.27.3...v0.28.1) (2026-04-17)


### Features

* add cli docs ([8d192e4](https://github.com/unkn0wn-root/resterm/commit/8d192e4b13ebaaf3721154b2d5287db404df9a81))
* add custom cli request picker ([5946175](https://github.com/unkn0wn-root/resterm/commit/5946175f89aacbe321438d6e15621195be39fd27))
* add effective target url and strings util ([fcf8d17](https://github.com/unkn0wn-root/resterm/commit/fcf8d1718d108737403a856a411e5713cf7756b7))
* add histogram ([0c8b8d7](https://github.com/unkn0wn-root/resterm/commit/0c8b8d76a46528f98d22d8b094a172571e5edd3b))
* add PASS badge to the cli ([1e7127d](https://github.com/unkn0wn-root/resterm/commit/1e7127d671705e6c5222a052f279d08ae848a6ee))
* add profile histogram ([c45bb38](https://github.com/unkn0wn-root/resterm/commit/c45bb38815e5e88b579ae733931057429f8143de))
* add some colors to histogram ([3d1f2e8](https://github.com/unkn0wn-root/resterm/commit/3d1f2e8608cf1bca466cfb4953340d42a9f13a43))
* resterm run ([200af87](https://github.com/unkn0wn-root/resterm/commit/200af876cc6fdbcccfc8b517b5ff0eb47e334d2a))


### Bug Fixes

* **auth:** authcmd cache scope and source-dir resolution ([9e5c888](https://github.com/unkn0wn-root/resterm/commit/9e5c8887349bd899131c19214c87f1be2f05b2c2))
* **binaryview:** prefer stable MIME extensions for filename hints ([f0b0eff](https://github.com/unkn0wn-root/resterm/commit/f0b0eff8b421bac2bf9790f7895ca37769768052))
* small code fixes for profiling ([cfcd050](https://github.com/unkn0wn-root/resterm/commit/cfcd050f5dc6440524f9a0719a52c82c440b0266))
* telementry config ([ad482ce](https://github.com/unkn0wn-root/resterm/commit/ad482ce143b20382f80c37ace72f1393e159712d))

### [0.27.3](https://github.com/unkn0wn-root/resterm/compare/v0.27.2...v0.27.3) (2026-04-16)


### ⚠ BREAKING CHANGES

* **headless:** redesign public API and unify run formatting

### Features

* dynamic tab for workflow and profile ([90b280d](https://github.com/unkn0wn-root/resterm/commit/90b280dafd076d402b7b084a315b166b081e6fcf))


### Bug Fixes

* **headless:** restore inferred failure status for public reports ([bf1d3e5](https://github.com/unkn0wn-root/resterm/commit/bf1d3e51012fcd36c5edc283062a4d181109efc4))


* **headless:** redesign public API and unify run formatting ([d611f55](https://github.com/unkn0wn-root/resterm/commit/d611f55eca04a36fa8a99cd78b55da5d33011365))

### [0.27.2](https://github.com/unkn0wn-root/resterm/compare/v0.27.1...v0.27.2) (2026-04-16)


### Features

* add ability to disable cookies per request ([5e7f5b1](https://github.com/unkn0wn-root/resterm/commit/5e7f5b1cf3a020a2b03aa7edbaad9ab055342542))
* add shared store to runtime ([ddac155](https://github.com/unkn0wn-root/resterm/commit/ddac155ba5efef3bb75cdc84fc66a7cf26fe6cd9))
* clean cookie store together with globals ([4404980](https://github.com/unkn0wn-root/resterm/commit/4404980c17252f331c99bb71e0ee8ef3ef7aa6e3))
* cookie store per env ([943a90d](https://github.com/unkn0wn-root/resterm/commit/943a90db7ab6e31206e9a92dedfcb46706d45c32))
* cookies ([0a83cbb](https://github.com/unkn0wn-root/resterm/commit/0a83cbbe1bd9414d1e1863b456c1a22f0c882282))


### Bug Fixes

* add separate hotkey for global cleanup ([81b2f9d](https://github.com/unkn0wn-root/resterm/commit/81b2f9dd6d7a490a11973ded3aa48c4e4d9aae6f))
* better UI names for for-each workflows ([8f92b52](https://github.com/unkn0wn-root/resterm/commit/8f92b52cf5ae120a3805f6638caa3862417f7343))

### [0.27.1](https://github.com/unkn0wn-root/resterm/compare/v0.26.2...v0.27.1) (2026-04-14)


### Features

* add shared registry for globals/locals instead of relying on ui ([347ada6](https://github.com/unkn0wn-root/resterm/commit/347ada6c3d20d30de4a06fb655309326cd880560))
* add websocket and streaming to help modal ([3039487](https://github.com/unkn0wn-root/resterm/commit/30394876ae6f79a810ccb3b7a1d19f11b18ff78c))
* add websocket help test ([e4568de](https://github.com/unkn0wn-root/resterm/commit/e4568de0db57fd345adf9f87f8e6dd8e6c680020))
* **engine,headless,api:** add headless execution engine and workflow runtime ([232f868](https://github.com/unkn0wn-root/resterm/commit/232f86888d5c770f829bf97197ec0d4bd48a77e3))
* move api -> headless and fix some small issues ([cc7565b](https://github.com/unkn0wn-root/resterm/commit/cc7565b51ac5dba43b76eee645c8809fb3f3ac97))
* **oauth:** centralize config normalization ([1eb53a9](https://github.com/unkn0wn-root/resterm/commit/1eb53a9e6c2a02425b2ec82a2235eb5a6759bc1b))


### Bug Fixes

* remove old unused code ([648685e](https://github.com/unkn0wn-root/resterm/commit/648685e33e74a674d81a175683ab84094e7f9da2))
* revert changes made by initial headless runner in resterm ([44d0350](https://github.com/unkn0wn-root/resterm/commit/44d03504c25dda8d6f97306d225597ad1302f2c5))

### [0.26.2](https://github.com/unkn0wn-root/resterm/compare/v0.26.1...v0.26.2) (2026-04-10)


### Features

* add websocket mode bindings ([21c1553](https://github.com/unkn0wn-root/resterm/commit/21c1553cb818222cbc494bcb5f2746134cb485c2))


### Bug Fixes

* focus, keys chord stale and update help ([523d5e3](https://github.com/unkn0wn-root/resterm/commit/523d5e3bf417b9a89b240dce9caf3415d094f4bd))
* websocket persistance conn ([32e58d1](https://github.com/unkn0wn-root/resterm/commit/32e58d169b0c0227c15762295a847ab39e067ec5))

### [0.26.1](https://github.com/unkn0wn-root/resterm/compare/v0.25.2...v0.26.1) (2026-04-08)


### Features

* add global auth ([80d1acf](https://github.com/unkn0wn-root/resterm/commit/80d1acfcbf0d1ebdba80a60156d73ab91a3f1ed4))


### Bug Fixes

* **auth:** same type, same receiver name ([08a9a0f](https://github.com/unkn0wn-root/resterm/commit/08a9a0f203c57d5c0234acbe60070a2f4d487f31))

### [0.25.2](https://github.com/unkn0wn-root/resterm/compare/v0.25.1...v0.25.2) (2026-04-08)


### Features

* make auth command reuse already cached key via cache_key ([19a3335](https://github.com/unkn0wn-root/resterm/commit/19a3335f57dc62af5f750b2bd80f35e3f2eada63))

### [0.25.1](https://github.com/unkn0wn-root/resterm/compare/v0.24.3...v0.25.1) (2026-04-08)


### Features

* add auth hints to the editor ([14a263e](https://github.com/unkn0wn-root/resterm/commit/14a263e032c34ac16ffddfe75bafa64f1bb69574))
* authcmd ([48e4472](https://github.com/unkn0wn-root/resterm/commit/48e4472f975ad1376a85ad7465acd362b2227e6e))


### Bug Fixes

* allow unquoted JSON argv in [@auth](https://github.com/auth) command ([f4bc1a0](https://github.com/unkn0wn-root/resterm/commit/f4bc1a097e32e035d136b70441bfe7394bc688d5))
* **lint:** run_test.go ([fb2366b](https://github.com/unkn0wn-root/resterm/commit/fb2366b71d3cfcc9ba3a84355e0eb980a2e12a46))

### [0.24.3](https://github.com/unkn0wn-root/resterm/compare/v0.24.2...v0.24.3) (2026-04-05)


### Features

* surface environment files in workspace navigator and file listing ([7566c80](https://github.com/unkn0wn-root/resterm/commit/7566c807aabcb99cd04d7a9051be9bc8b504d6e1))
* **ui:** add search/filter to help overlay ([395beb0](https://github.com/unkn0wn-root/resterm/commit/395beb01ae3525366f858ff1708ba7f9ac2818ef))

### [0.24.2](https://github.com/unkn0wn-root/resterm/compare/v0.24.1...v0.24.2) (2026-04-04)

### [0.24.1](https://github.com/unkn0wn-root/resterm/compare/v0.23.6...v0.24.1) (2026-04-02)


### Features

* add Explain preview for request inspection ([8cd0ea5](https://github.com/unkn0wn-root/resterm/commit/8cd0ea5263731bed0630cf2cae987139764c0c7b))

### [0.23.6](https://github.com/unkn0wn-root/resterm/compare/v0.23.5...v0.23.6) (2026-02-26)


### Bug Fixes

* **parser:** ignore unknown commented directives outside req so trailing shorthand vars stay file-scoped ([af3c080](https://github.com/unkn0wn-root/resterm/commit/af3c080d7c4d00a9909fa32deb0c99090cdc9346))

### [0.23.5](https://github.com/unkn0wn-root/resterm/compare/v0.23.4...v0.23.5) (2026-02-23)

### [0.23.4](https://github.com/unkn0wn-root/resterm/compare/v0.23.3...v0.23.4) (2026-02-22)

### [0.23.3](https://github.com/unkn0wn-root/resterm/compare/v0.23.2...v0.23.3) (2026-02-20)

### [0.23.2](https://github.com/unkn0wn-root/resterm/compare/v0.23.1...v0.23.2) (2026-02-16)


### Features

* add $shared env vars ([87bceae](https://github.com/unkn0wn-root/resterm/commit/87bceae396e92f0477c0bdbbf52a8a7958dca65b))
* add resterm import/export/pack/unpack collections ([6c87d67](https://github.com/unkn0wn-root/resterm/commit/6c87d6735217631c12226a1d427a7e0c36523dc3))
* prevent $shared from being used as a concrete env ([0006fcb](https://github.com/unkn0wn-root/resterm/commit/0006fcbf0a267b7557681e78393cb98f7676c32c))

### [0.23.1](https://github.com/unkn0wn-root/resterm/compare/v0.22.0...v0.23.1) (2026-02-15)


### Features

* add Kubernetes port-forward support ([b630a46](https://github.com/unkn0wn-root/resterm/commit/b630a463746f4bcd20cc2bc9c21157a0f08caf79))


### Bug Fixes

* ci ([aea7a09](https://github.com/unkn0wn-root/resterm/commit/aea7a0993e29c9631a550fb089aa7f4b65d584f9))

## [0.22.0](https://github.com/unkn0wn-root/resterm/compare/v0.21.5...v0.22.0) (2026-02-11)


### Features

* **squashed:** sqlite for history ([a8c9d4b](https://github.com/unkn0wn-root/resterm/commit/a8c9d4bd15ec6243e590c63ee49e28313338234f))


### Bug Fixes

* **history:** print subcommand help and skip legacy JSON reads after migration ([db6ba1c](https://github.com/unkn0wn-root/resterm/commit/db6ba1cc7913a825f22a0ea4c0b4cc7cd033dd11))

### [0.21.5](https://github.com/unkn0wn-root/resterm/compare/v0.21.4...v0.21.5) (2026-02-09)


### Features

* **capture:** add strict mode and better warnings ([b58c0da](https://github.com/unkn0wn-root/resterm/commit/b58c0da313570acbb53312b4add4e2e662b9a2bd))
* **capture:** support RST expressions and extract capture evaluator ([cf2cb31](https://github.com/unkn0wn-root/resterm/commit/cf2cb31ad3f1793b36a1402d755e0c2b029568ba))


### Bug Fixes

* **capture:** avoid false legacy template detection for quoted markers ([5fa6938](https://github.com/unkn0wn-root/resterm/commit/5fa69388110394e6b6ee291e2806675a37cd7cc4))
* lint ([aa0cf1b](https://github.com/unkn0wn-root/resterm/commit/aa0cf1b73b00c7175c84a973fe507a8dac700865))

### [0.21.4](https://github.com/unkn0wn-root/resterm/compare/v0.21.3...v0.21.4) (2026-02-07)


### Features

* add [@patch](https://github.com/patch) profiles and extend [@apply](https://github.com/apply) with use=, auth, settings ([4265b56](https://github.com/unkn0wn-root/resterm/commit/4265b566241ef969805180210ea4cd858978a463))

### [0.21.3](https://github.com/unkn0wn-root/resterm/compare/v0.21.2...v0.21.3) (2026-02-05)

### [0.21.2](https://github.com/unkn0wn-root/resterm/compare/v0.21.1...v0.21.2) (2026-02-02)


### Features

* add brew detect to resterm update ([2090caf](https://github.com/unkn0wn-root/resterm/commit/2090cafcb2b652de0107043d7ca53ff58aa99ed6))
* add resterm init cmd ([7388603](https://github.com/unkn0wn-root/resterm/commit/73886035c62aeaa37ee68f4f2898b775c289c51f))


### Bug Fixes

* golci ([10e9440](https://github.com/unkn0wn-root/resterm/commit/10e9440a4a93a74df816289886b8d44b3268381a))
* resterm placeholder on startup ([9970a9d](https://github.com/unkn0wn-root/resterm/commit/9970a9d27e9a3a0ca34c1a28cb2d6b9b25780635))

### [0.21.1](https://github.com/unkn0wn-root/resterm/compare/v0.20.3...v0.21.1) (2026-01-30)


### Features

* rewrite response wrapping logic ([a461e21](https://github.com/unkn0wn-root/resterm/commit/a461e21cab1a6141dba9e831d0f36bcbab2cb851))


### Bug Fixes

* preserve ANSI colors across wraps and avoid indent-only lines ([423df16](https://github.com/unkn0wn-root/resterm/commit/423df16db73c01b646f71ad6f775cead30f436fc))

### [0.20.3](https://github.com/unkn0wn-root/resterm/compare/v0.20.2...v0.20.3) (2026-01-30)


### Features

* async wrap ([d7f5ade](https://github.com/unkn0wn-root/resterm/commit/d7f5ade878f1ee96a5952a1e9ce583e88dd83d5d))
* **ui:** async response formatting with ctx ([c75151b](https://github.com/unkn0wn-root/resterm/commit/c75151b107b42e1bb393111171f087ec7caaa426))


### Bug Fixes

* tests and use new cacheForTab ([747f83b](https://github.com/unkn0wn-root/resterm/commit/747f83b3c75e354251063e3c97919ecbddfc77e1))

### [0.20.2](https://github.com/unkn0wn-root/resterm/compare/v0.20.1...v0.20.2) (2026-01-29)


### Features

* activate cursor with v ([506a162](https://github.com/unkn0wn-root/resterm/commit/506a1622c49c219928518df60facba40fe25714e))


### Bug Fixes

* tests ([79bfc73](https://github.com/unkn0wn-root/resterm/commit/79bfc73363aa7f94caaeaef5c6e8da47c184acaa))
* **ui:** preserve real trailing newlines and skip idle cursor decoration ([4b45f42](https://github.com/unkn0wn-root/resterm/commit/4b45f42dbca9ce33be3809897ce767e992a46117))

### [0.20.1](https://github.com/unkn0wn-root/resterm/compare/v0.20.0...v0.20.1) (2026-01-28)


### Bug Fixes

* repeatable chord to require matching key ([91a8a29](https://github.com/unkn0wn-root/resterm/commit/91a8a2902ac56da59e2818a9a30e9d90639646ab))

## [0.20.0](https://github.com/unkn0wn-root/resterm/compare/v0.19.2...v0.20.0) (2026-01-28)


### Features

* add copy selection to response tab ([5495b0c](https://github.com/unkn0wn-root/resterm/commit/5495b0cd409e3c6768e5ac55e4d4e5b871187cae))
* add cursor ([e568e6c](https://github.com/unkn0wn-root/resterm/commit/e568e6c218615971baac9812bb670c1a4a07c4eb))


### Bug Fixes

* scrolling with cursor enabled ([a756f0b](https://github.com/unkn0wn-root/resterm/commit/a756f0b15264e13d4a718e0e35a99c826eba3fb0))
* scrolling with cursor enabled ([4b6c23f](https://github.com/unkn0wn-root/resterm/commit/4b6c23f51f1496dffc88afa97e4ceec709c4952e))
* tests ([c133745](https://github.com/unkn0wn-root/resterm/commit/c133745e2d64c8d611539e76cf222095fd884621))

### [0.19.2](https://github.com/unkn0wn-root/resterm/compare/v0.19.1...v0.19.2) (2026-01-27)


### Features

* add duration offsets for timestamps and RTS time helpers ([c3679e6](https://github.com/unkn0wn-root/resterm/commit/c3679e6ed865bc9ec131be817ba9195baeda710f))


### Bug Fixes

* **editor:** make visual selection inclusive ([35fe52b](https://github.com/unkn0wn-root/resterm/commit/35fe52bdbbc952e465947e7f29f2d3c6258c7dbf))
* **httpclient:** preserve graphql GET URL mutation and expand override templates ([4307853](https://github.com/unkn0wn-root/resterm/commit/4307853c7944d226c3115ee4d5cd91af3bab1d53))

### [0.19.1](https://github.com/unkn0wn-root/resterm/compare/v0.18.6...v0.19.1) (2026-01-21)


### Features

* http-version setting with strict HTTP/2 enforcement ([5a58adc](https://github.com/unkn0wn-root/resterm/commit/5a58adc42dee2d0b59e4048229f19df6a19b796c))
* **restermscript:** add top level module def ([ce2c670](https://github.com/unkn0wn-root/resterm/commit/ce2c67026c38aa34e6ecedd9bc581f41efee5fb9))
* **rts:** add preflight `[@use](https://github.com/use)` alias resolution ([3e4fd66](https://github.com/unkn0wn-root/resterm/commit/3e4fd66f531cbfe534d5b53d8858b3b9b82afd54))

### [0.18.6](https://github.com/unkn0wn-root/resterm/compare/v0.18.5...v0.18.6) (2026-01-17)


### Bug Fixes

* validate workflow expect values ([ac0570d](https://github.com/unkn0wn-root/resterm/commit/ac0570d78dfe77b78ed2ae85eff05f65fa7d3d70))
* workflow expect status handling ([4e12f64](https://github.com/unkn0wn-root/resterm/commit/4e12f643cc33577e78636fcb91fed940e9fb95fb))

### [0.18.5](https://github.com/unkn0wn-root/resterm/compare/v0.18.4...v0.18.5) (2026-01-14)

### [0.18.4](https://github.com/unkn0wn-root/resterm/compare/v0.18.3...v0.18.4) (2026-01-14)


### Bug Fixes

* template expansion via rts scripts and stdlib ([6799099](https://github.com/unkn0wn-root/resterm/commit/6799099e8f7ea005301980b9c585d0202225edc2))

### [0.18.3](https://github.com/unkn0wn-root/resterm/compare/v0.18.2...v0.18.3) (2026-01-14)

### [0.18.2](https://github.com/unkn0wn-root/resterm/compare/v0.18.1...v0.18.2) (2026-01-13)

### [0.18.1](https://github.com/unkn0wn-root/resterm/compare/v0.17.2...v0.18.1) (2026-01-13)


### Features

* extend std lib and docs ([326c536](https://github.com/unkn0wn-root/resterm/commit/326c5362340edbe730617e70c766d898a5caebd2))

### [0.17.2](https://github.com/unkn0wn-root/resterm/compare/v0.17.1...v0.17.2) (2026-01-13)


### Bug Fixes

* **textarea:** normalize crlf input ([374c72b](https://github.com/unkn0wn-root/resterm/commit/374c72b7bca8699a13254febcbe2a99b5f915e78))

### [0.17.1](https://github.com/unkn0wn-root/resterm/compare/v0.16.3...v0.17.1) (2026-01-11)

### [0.16.3](https://github.com/unkn0wn-root/resterm/compare/v0.16.2...v0.16.3) (2026-01-10)

### [0.16.2](https://github.com/unkn0wn-root/resterm/compare/v0.16.1...v0.16.2) (2026-01-10)

### [0.16.1](https://github.com/unkn0wn-root/resterm/compare/v0.16.0...v0.16.1) (2026-01-09)


### Features

* add latency ascii to headers ([7bb18e1](https://github.com/unkn0wn-root/resterm/commit/7bb18e1d5f4b3df7c9dc74ead41f1ba1c0bfb2cd))
* **history:** add 'gg' and 'G' to jump to first and last and PageUp/Down to jump between pages ([96b2259](https://github.com/unkn0wn-root/resterm/commit/96b225946b0deef833a5bb70d093e1832be0a8ff))
* sending request brings back response tab when minimized ([1872d3a](https://github.com/unkn0wn-root/resterm/commit/1872d3a3b422bb65779eb89ffa94c599d68b118a))
* use status message for unnamed requests in global/file scope ([8eecf3d](https://github.com/unkn0wn-root/resterm/commit/8eecf3d09284473a240a99af35471b46bece0202))


### Bug Fixes

* opening request unminimize editor ([6a261be](https://github.com/unkn0wn-root/resterm/commit/6a261beebb6ede3ab8b9bbaecb4c438ce1c6b54d))

## [0.16.0](https://github.com/unkn0wn-root/resterm/compare/v0.15.2...v0.16.0) (2026-01-09)

### [0.15.2](https://github.com/unkn0wn-root/resterm/compare/v0.15.1...v0.15.2) (2026-01-08)

### [0.15.1](https://github.com/unkn0wn-root/resterm/compare/v0.15.0...v0.15.1) (2026-01-07)


### Bug Fixes

* **editor:** align P paste with Vim semantics ([8b449b2](https://github.com/unkn0wn-root/resterm/commit/8b449b29c55eb04e8f97dbf89e675118cb8ac332))
* **editor:** make visual mode include caret rune for selection ([e20006c](https://github.com/unkn0wn-root/resterm/commit/e20006c6526673b3bc24952350cfb643a104b3d0))
* **editor:** Vim word motions in editor ([3c9bcc4](https://github.com/unkn0wn-root/resterm/commit/3c9bcc4212b3d0802033481cab06f1afdc149e61))
* **textarea:** apply sidescroll margin before overflow ([1b9c7ee](https://github.com/unkn0wn-root/resterm/commit/1b9c7ee25a23674d26662e0ae98f7580b377f208))

## [0.15.0](https://github.com/unkn0wn-root/resterm/compare/v0.14.2...v0.15.0) (2026-01-03)


### Features

* add tls to trace ([103e7cb](https://github.com/unkn0wn-root/resterm/commit/103e7cb25c09294c6ff968bd51b39c36462f45fb))

### [0.14.2](https://github.com/unkn0wn-root/resterm/compare/v0.14.1...v0.14.2) (2026-01-01)

### [0.14.1](https://github.com/unkn0wn-root/resterm/compare/v0.13.6...v0.14.1) (2025-12-30)

### [0.13.6](https://github.com/unkn0wn-root/resterm/compare/v0.13.5...v0.13.6) (2025-12-30)

### [0.13.5](https://github.com/unkn0wn-root/resterm/compare/v0.13.4...v0.13.5) (2025-12-29)


### Features

* add more methods/func coloring to rts editor ([b6ed022](https://github.com/unkn0wn-root/resterm/commit/b6ed022e9b911ea453d6e09240635fe1ff82fcc3))


### Bug Fixes

* don't expand file that has no children ([d865d5b](https://github.com/unkn0wn-root/resterm/commit/d865d5b6dd55972a1f423b0ad50cf15d77d1fd3b))
* when pressing 'l' on .rts file, set focus to the editor ([8b2ad88](https://github.com/unkn0wn-root/resterm/commit/8b2ad88b0326b8f595878a75b0feb7b0a3d9c8ae))

### [0.13.4](https://github.com/unkn0wn-root/resterm/compare/v0.13.3...v0.13.4) (2025-12-29)

### [0.13.3](https://github.com/unkn0wn-root/resterm/compare/v0.13.2...v0.13.3) (2025-12-29)

### [0.13.2](https://github.com/unkn0wn-root/resterm/compare/v0.13.1...v0.13.2) (2025-12-26)

### [0.13.1](https://github.com/unkn0wn-root/resterm/compare/v0.13.0...v0.13.1) (2025-12-26)

## [0.13.0](https://github.com/unkn0wn-root/resterm/compare/v0.12.0...v0.13.0) (2025-12-25)

## [0.12.0](https://github.com/unkn0wn-root/resterm/compare/v0.11.9...v0.12.0) (2025-12-19)


### Features

* rework minimize panes. Hide panes to make more room for other ([6e36871](https://github.com/unkn0wn-root/resterm/commit/6e36871264c031945b9526a00eafb9d0ab2c6b67))

### [0.11.9](https://github.com/unkn0wn-root/resterm/compare/v0.11.8...v0.11.9) (2025-12-19)


### Bug Fixes

* keep cursor based selection safe and sync navigator at EOF ([5dbb7df](https://github.com/unkn0wn-root/resterm/commit/5dbb7df491d4108f9695e1f67f952b8ae2b1785a))

### [0.11.8](https://github.com/unkn0wn-root/resterm/compare/v0.11.7...v0.11.8) (2025-12-19)


### Bug Fixes

* keep navigator state stable and restore cursor request fallback ([c20aac3](https://github.com/unkn0wn-root/resterm/commit/c20aac3a2b3590cc86d0674bc1e7f05060b38d11))

### [0.11.7](https://github.com/unkn0wn-root/resterm/compare/v0.11.6...v0.11.7) (2025-12-19)


### Features

* use reflow when generating hex/base64 and cache the results ([c2c8df9](https://github.com/unkn0wn-root/resterm/commit/c2c8df993fd3d59b2c71640c24701bf0c29506f5))


### Bug Fixes

* flickering on hex/base64 generation ([e00702f](https://github.com/unkn0wn-root/resterm/commit/e00702fd68f3ce0a1656ab21d8a6b82f7041c90c))
* reflow only for hex/base64 ([54f182f](https://github.com/unkn0wn-root/resterm/commit/54f182fcceffa857320af90d098d8e73cf68b8ce))

### [0.11.6](https://github.com/unkn0wn-root/resterm/compare/v0.11.5...v0.11.6) (2025-12-18)

### [0.11.5](https://github.com/unkn0wn-root/resterm/compare/v0.11.4...v0.11.5) (2025-12-18)


### Features

* async loading binary ([cad1dd3](https://github.com/unkn0wn-root/resterm/commit/cad1dd3dde1506b6ea1af2b6d0fe81bcbb4617da))
* cache hex/base64 bin response ([25cf7fa](https://github.com/unkn0wn-root/resterm/commit/25cf7fa4fc627362a51a0a714281a235872ca77b))
* defer large bin responses and add new shortcut to explicitly get whole body ([5c1e7d7](https://github.com/unkn0wn-root/resterm/commit/5c1e7d7ea06444ae99f52bbb76218a653fc8c507))

### [0.11.4](https://github.com/unkn0wn-root/resterm/compare/v0.11.3...v0.11.4) (2025-12-17)


### Bug Fixes

* **websocket:** timeout ([827abd7](https://github.com/unkn0wn-root/resterm/commit/827abd746e57aadc68dd76829d90bc42558f41f5))
* **websocket:** timeout via metadata ([3e1992a](https://github.com/unkn0wn-root/resterm/commit/3e1992afc21351a12455f8218c31dd07c7698ca0))

### [0.11.3](https://github.com/unkn0wn-root/resterm/compare/v0.11.2...v0.11.3) (2025-12-17)


### Bug Fixes

* **raw tab:** remove duplicate headers on empty response ([bcbb2cd](https://github.com/unkn0wn-root/resterm/commit/bcbb2cdd0cb3c4e3ee25304d9fc60e7be669a24f))

### [0.11.2](https://github.com/unkn0wn-root/resterm/compare/v0.11.1...v0.11.2) (2025-12-16)


### Bug Fixes

* add new bin response motions to help modal ([d78fdf9](https://github.com/unkn0wn-root/resterm/commit/d78fdf997756aa8355c9a200341c4a86859441db))

### [0.11.1](https://github.com/unkn0wn-root/resterm/compare/v0.11.0...v0.11.1) (2025-12-16)


### Features

* add gg/G jump up/down for each pane ([eba92c6](https://github.com/unkn0wn-root/resterm/commit/eba92c63784f7887aee0ce22d2954fcb758b4845))
* add quick jump (gg/G) in response tab ([e203244](https://github.com/unkn0wn-root/resterm/commit/e203244a2d894163f82c9086215fab47652bd410))
* input box for saving binary response ([d79d00a](https://github.com/unkn0wn-root/resterm/commit/d79d00af8098cefee750493300ecf91a5cb415ca))


### Bug Fixes

* keep grpc responses json friendly while exposing wire bytes ([f762bbe](https://github.com/unkn0wn-root/resterm/commit/f762bbe1582d7a4f6a781745695120987e5e934c))
* resync panes without clearing the whole terminal ([360f243](https://github.com/unkn0wn-root/resterm/commit/360f2434d4f311ef6dc11171db2f7d1094bb259f))

## [0.11.0](https://github.com/unkn0wn-root/resterm/compare/v0.10.5...v0.11.0) (2025-12-15)


### Features

* add file watcher to watch changes for opened file ([4b96268](https://github.com/unkn0wn-root/resterm/commit/4b962689efed8599a51f9c83376a79bc45343e7d))
* make extended file search opt-in and improve parsing secrets ([8dfe38b](https://github.com/unkn0wn-root/resterm/commit/8dfe38b7ae08c2a54ef5a440615f0c337409c475))


### Bug Fixes

* respect no fallback file lookup ([6cb7016](https://github.com/unkn0wn-root/resterm/commit/6cb701678dfceb96ef48e947777d14f83a9778a6))
* tests ([093074a](https://github.com/unkn0wn-root/resterm/commit/093074a6ad1cd11352c47fe609248ae2d92b5bac))
* **watcher:** show warn modal on local unsaved changes ([dfea2ad](https://github.com/unkn0wn-root/resterm/commit/dfea2ad093b3da8721d6e7927274918a98b71893))

### [0.10.5](https://github.com/unkn0wn-root/resterm/compare/v0.10.4...v0.10.5) (2025-12-12)


### Features

* save custom layout settings to persist after closing resterm ([5c1d41a](https://github.com/unkn0wn-root/resterm/commit/5c1d41a8a8f1a30353956e7c37499f6244470e47))

### [0.10.4](https://github.com/unkn0wn-root/resterm/compare/v0.10.3...v0.10.4) (2025-12-11)


### Features

* add 'type-writer' like scroll to the nav bar, editor and workflows ([510bba8](https://github.com/unkn0wn-root/resterm/commit/510bba88b20c73d204b619050724b40fdf6104e1))
* add jump to request shortcut and focus on request in editor while sending request ([7e0e0d6](https://github.com/unkn0wn-root/resterm/commit/7e0e0d658efd8263cbc02d37233b4ad3b39da7eb))


### Bug Fixes

* space should not bypass view mode ([33ffe45](https://github.com/unkn0wn-root/resterm/commit/33ffe458a0d4a300d9655a5b620de5bd0f02e938))

### [0.10.3](https://github.com/unkn0wn-root/resterm/compare/v0.10.2...v0.10.3) (2025-12-11)


### Bug Fixes

* display 1 script label ([e657615](https://github.com/unkn0wn-root/resterm/commit/e657615f5552f1d67a72451ba3f11cdc9db370e9))

### [0.10.2](https://github.com/unkn0wn-root/resterm/compare/v0.10.1...v0.10.2) (2025-12-10)

### [0.10.1](https://github.com/unkn0wn-root/resterm/compare/v0.10.0...v0.10.1) (2025-12-10)


### Bug Fixes

* don't expand at second right arrow press ([5a87ef0](https://github.com/unkn0wn-root/resterm/commit/5a87ef07c6bc97297b1fcaa378f76b076896b66b))

## [0.10.0](https://github.com/unkn0wn-root/resterm/compare/v0.9.5...v0.10.0) (2025-12-09)

### [0.9.5](https://github.com/unkn0wn-root/resterm/compare/v0.9.4...v0.9.5) (2025-12-07)


### Features

* abstract hints/autocomplete to ui pkg ([3ded126](https://github.com/unkn0wn-root/resterm/commit/3ded1265c621fc7ec39c937b224533a055c4c338))
* add editor hints to setting(s) ([a415db6](https://github.com/unkn0wn-root/resterm/commit/a415db64037e95ef54d73dc52d7d59b073920e62))
* add hints manager ([9fc3070](https://github.com/unkn0wn-root/resterm/commit/9fc3070d0cb5246d8b59a27d7f57f2c01b65318b))

### [0.9.4](https://github.com/unkn0wn-root/resterm/compare/v0.9.3...v0.9.4) (2025-12-06)


### Bug Fixes

* lint ([5b6ea7b](https://github.com/unkn0wn-root/resterm/commit/5b6ea7b76b643d6a8282055e92f547fbd32c800b))

### [0.9.3](https://github.com/unkn0wn-root/resterm/compare/v0.9.2...v0.9.3) (2025-12-06)


### Bug Fixes

* help overlay was swallowing the profile scheduler command ([675512b](https://github.com/unkn0wn-root/resterm/commit/675512bcadda1fc3d3c0aa61ac206327b32e13cc))

### [0.9.2](https://github.com/unkn0wn-root/resterm/compare/v0.9.1...v0.9.2) (2025-12-05)


### Features

* cancel pre-request scripts ([1aa2d19](https://github.com/unkn0wn-root/resterm/commit/1aa2d19e885403205586b15b00afb81979763f1c))


### Bug Fixes

* do not ovveride pinned pane ([c8c1ffb](https://github.com/unkn0wn-root/resterm/commit/c8c1ffb71984435a847bb82c8fba04bfbaba6601))
* pre-requests cancelation ([2d4464f](https://github.com/unkn0wn-root/resterm/commit/2d4464f661c3d3da68fc8877b76978f2e9cfe30e))
* resync response panes after building the summary ([91bb7a3](https://github.com/unkn0wn-root/resterm/commit/91bb7a39243f922b20034e28d54144ab4f7bb4c4))

### [0.9.1](https://github.com/unkn0wn-root/resterm/compare/v0.9.0...v0.9.1) (2025-12-04)

## [0.9.0](https://github.com/unkn0wn-root/resterm/compare/v0.8.5...v0.9.0) (2025-12-04)

### [0.8.5](https://github.com/unkn0wn-root/resterm/compare/v0.8.4...v0.8.5) (2025-12-01)


### Bug Fixes

* clear search in response tab ([1b87b10](https://github.com/unkn0wn-root/resterm/commit/1b87b105cb5d2bd2554559fba93a7087e066ecc7))
* search ([fcd2176](https://github.com/unkn0wn-root/resterm/commit/fcd2176afb55dc323556c71c19d8fc93adcc86b5))
* worflow search and better worflows navigation ([0f8ee5f](https://github.com/unkn0wn-root/resterm/commit/0f8ee5f8f02bc4c2e0ae932ba6845907efc40f0f))

### [0.8.4](https://github.com/unkn0wn-root/resterm/compare/v0.8.3...v0.8.4) (2025-11-29)


### Features

* prettify grpc response in workflows ([cefe457](https://github.com/unkn0wn-root/resterm/commit/cefe45733004751f2b63b1142af8c981350140ba))


### Bug Fixes

* avoid secret leaks in status/list labels and refresh on env change ([c32d638](https://github.com/unkn0wn-root/resterm/commit/c32d638c58e34708a826772b4fd87bed02305bb4))

### [0.8.3](https://github.com/unkn0wn-root/resterm/compare/v0.8.2...v0.8.3) (2025-11-28)


### Features

* improve workflow stats navigation and align gRPC display with HTTP formatting ([3b3dfe7](https://github.com/unkn0wn-root/resterm/commit/3b3dfe798e970d73e90eeebd34e046948e7e1b92))

### [0.8.2](https://github.com/unkn0wn-root/resterm/compare/v0.8.1...v0.8.2) (2025-11-27)


### Features

* add request-headers toggle, capture request metadata, and add scroll to workflows with j/k ([0255878](https://github.com/unkn0wn-root/resterm/commit/02558789597365a33112adcc2b0574c29e36ec15))

### [0.8.1](https://github.com/unkn0wn-root/resterm/compare/v0.8.0...v0.8.1) (2025-11-27)

## [0.8.0](https://github.com/unkn0wn-root/resterm/compare/v0.7.8...v0.8.0) (2025-11-23)


### Features

* wip - ssh ([9ccb01e](https://github.com/unkn0wn-root/resterm/commit/9ccb01e06eedf0e5a899e51129147f68037d679d))


### Bug Fixes

* don't double unlock and panic ([6baa5fd](https://github.com/unkn0wn-root/resterm/commit/6baa5fd55bf267d1237608e0013fb3fc6e288e19))
* grpc via ssh tunnel ([c00b4a4](https://github.com/unkn0wn-root/resterm/commit/c00b4a47f4fc8975b556f8cf48fa62a2f6034038))
* linter ([b73def9](https://github.com/unkn0wn-root/resterm/commit/b73def9d3038d8e14c64d4ed8469eb6128192e49))

### [0.7.8](https://github.com/unkn0wn-root/resterm/compare/v0.7.7...v0.7.8) (2025-11-21)

### [0.7.7](https://github.com/unkn0wn-root/resterm/compare/v0.7.6...v0.7.7) (2025-11-21)

### [0.7.6](https://github.com/unkn0wn-root/resterm/compare/v0.7.5...v0.7.6) (2025-11-20)

### [0.7.5](https://github.com/unkn0wn-root/resterm/compare/v0.7.4...v0.7.5) (2025-11-20)


### Bug Fixes

* profile report window, histogram styling and add profile param hints ([06d875e](https://github.com/unkn0wn-root/resterm/commit/06d875e2c7c8b3a5123706f636417ef93730447c))

### [0.7.4](https://github.com/unkn0wn-root/resterm/compare/v0.7.3...v0.7.4) (2025-11-19)


### Features

* add copy to clipboard shortcut for response ([246a765](https://github.com/unkn0wn-root/resterm/commit/246a7651be03982651135a95c25165cb221eb15b))

### [0.7.3](https://github.com/unkn0wn-root/resterm/compare/v0.7.2...v0.7.3) (2025-11-12)

### [0.7.2](https://github.com/unkn0wn-root/resterm/compare/v0.7.1...v0.7.2) (2025-11-12)


### Features

* read .env file with passing --env-file ([b97846a](https://github.com/unkn0wn-root/resterm/commit/b97846a5e8d775d07620eeb2931466e89eb3d650))

### [0.7.1](https://github.com/unkn0wn-root/resterm/compare/v0.7.0...v0.7.1) (2025-11-11)


### Features

* add configurable keyboard bindings + dynamic help overlay ([1d3c624](https://github.com/unkn0wn-root/resterm/commit/1d3c624e37e8adc1fc982c265cbf2b4df6ca417e))

## [0.7.0](https://github.com/unkn0wn-root/resterm/compare/v0.6.4...v0.7.0) (2025-11-09)


### Features

* **compare:** add multi environment diff workflow ([8928427](https://github.com/unkn0wn-root/resterm/commit/8928427232ed48451b7697bbe2260adb9bbdfb36))


### Bug Fixes

* linter ([0783256](https://github.com/unkn0wn-root/resterm/commit/078325644e88cf477aed96ae74ab6a4e448326a2))

### [0.6.4](https://github.com/unkn0wn-root/resterm/compare/v0.6.3...v0.6.4) (2025-11-06)


### Features

* **ui:** add pane minimization toggles and zoom handling ([81abf38](https://github.com/unkn0wn-root/resterm/commit/81abf3847313463956a6663f5c648a3ae1647f77))


### Bug Fixes

* powershell install script ([f5bd8ba](https://github.com/unkn0wn-root/resterm/commit/f5bd8bac874467ae3f42abbdea99f09f0156d3c3))
* remove old func and var after recent changes ([f829d48](https://github.com/unkn0wn-root/resterm/commit/f829d48e5ed873683a75d650336541f8cbc65baf))

### [0.6.3](https://github.com/unkn0wn-root/resterm/compare/v0.6.2...v0.6.3) (2025-11-01)

### Features

* add pane minimize toggles and zoom mode for sidebar/editor/response panes

### [0.6.2](https://github.com/unkn0wn-root/resterm/compare/v0.6.1...v0.6.2) (2025-11-01)


### Features

* improve response summary content-length rendering ([0ed32ab](https://github.com/unkn0wn-root/resterm/commit/0ed32ab5f5ef7591a811e64b5a2bf55987805518))

### [0.6.1](https://github.com/unkn0wn-root/resterm/compare/v0.6.0...v0.6.1) (2025-10-31)


### Features

* add content-length to the response sum ([596dc5d](https://github.com/unkn0wn-root/resterm/commit/596dc5d99fbe8ecb30cc0c332675b769706340aa))

## [0.6.0](https://github.com/unkn0wn-root/resterm/compare/v0.5.2...v0.6.0) (2025-10-26)


### Features

* add HTTP tracing, timeline UI, and OTEL export support ([af0cee1](https://github.com/unkn0wn-root/resterm/commit/af0cee1708e08b1ecbec577cf2a9365c0bc269d0))

### [0.5.2](https://github.com/unkn0wn-root/resterm/compare/v0.5.1...v0.5.2) (2025-10-24)


### Features

* add subcommands/hints to autocompleter ([9636b05](https://github.com/unkn0wn-root/resterm/commit/9636b05a5c635dccf14b51cec41a3a7c56837d58))

### [0.5.1](https://github.com/unkn0wn-root/resterm/compare/v0.5.0...v0.5.1) (2025-10-24)

## [0.5.0](https://github.com/unkn0wn-root/resterm/compare/v0.4.8...v0.5.0) (2025-10-23)


### Bug Fixes

* remove unused func and fix linter ([baf7c16](https://github.com/unkn0wn-root/resterm/commit/baf7c16f18097b68cbb335e99e6e4fe90d39b7d8))
* response pane width ([e877c17](https://github.com/unkn0wn-root/resterm/commit/e877c17735d9751a6aa79cebe96d531872dcd8b7))
* response pane width ([2673f39](https://github.com/unkn0wn-root/resterm/commit/2673f39ab7cbb12edaafc1d7faf8756af5934c7f))
* response pane width ([3c63b50](https://github.com/unkn0wn-root/resterm/commit/3c63b506012c1f2a3e50ec2698e180978ea39e90))

### [0.4.8](https://github.com/unkn0wn-root/resterm/compare/v0.4.7...v0.4.8) (2025-10-22)


### Features

* add OpenAPI -> resterm import pipeline ([11e76e2](https://github.com/unkn0wn-root/resterm/commit/11e76e2aaeb09f4e285299724154bd7fb2c7c796))

### [0.4.7](https://github.com/unkn0wn-root/resterm/compare/v0.4.6...v0.4.7) (2025-10-20)


### Features

* horizontal response pane ([#62](https://github.com/unkn0wn-root/resterm/issues/62)) ([9ae8f5d](https://github.com/unkn0wn-root/resterm/commit/9ae8f5dbff40fbb71b7977f1e1672f37a1100aef))
* new response wrapper ([3c46110](https://github.com/unkn0wn-root/resterm/commit/3c46110b788a39902c0b8b90d7dba3ff973e3bc0))

### [0.4.6](https://github.com/unkn0wn-root/resterm/compare/v0.4.5...v0.4.6) (2025-10-19)

### [0.4.5](https://github.com/unkn0wn-root/resterm/compare/v0.4.4...v0.4.5) (2025-10-18)


### Bug Fixes

* strips the CommandBar style’s left/right padding before styling and re-inserts it as plain spaces ([#58](https://github.com/unkn0wn-root/resterm/issues/58)) ([73bd114](https://github.com/unkn0wn-root/resterm/commit/73bd114f09d38ca89894f88ab19ebb2ff1f1b54f))

### [0.4.4](https://github.com/unkn0wn-root/resterm/compare/v0.4.3...v0.4.4) (2025-10-18)

### [0.4.3](https://github.com/unkn0wn-root/resterm/compare/v0.4.2...v0.4.3) (2025-10-18)


### Features

* add progress bar to updater ([#56](https://github.com/unkn0wn-root/resterm/issues/56)) ([cbd62df](https://github.com/unkn0wn-root/resterm/commit/cbd62dfb2be20e6e5d167c05684174311261a017))
* **editor:** add 8-column buffer scroll to margin ([#57](https://github.com/unkn0wn-root/resterm/issues/57)) ([560b3d6](https://github.com/unkn0wn-root/resterm/commit/560b3d6e7289b0873cbfdf41961fa45712eaecee))

### [0.4.2](https://github.com/unkn0wn-root/resterm/compare/v0.4.1...v0.4.2) (2025-10-18)


### Features

* stdout update changelog ([#55](https://github.com/unkn0wn-root/resterm/issues/55)) ([3a1f906](https://github.com/unkn0wn-root/resterm/commit/3a1f906d571b938522d2cb84bf38cc257ab49813))

### [0.4.1](https://github.com/unkn0wn-root/resterm/compare/v0.4.0...v0.4.1) (2025-10-18)


### Features

* resterm updater ([#54](https://github.com/unkn0wn-root/resterm/issues/54)) ([369955e](https://github.com/unkn0wn-root/resterm/commit/369955ecff0bca0a5fd310f9067174def95d6de7))

## [0.4.0](https://github.com/unkn0wn-root/resterm/compare/v0.3.1...v0.4.0) (2025-10-18)


### Features

* added custom themes ([1731cf3](https://github.com/unkn0wn-root/resterm/commit/1731cf32a01c64c356493369c03b5ce368a8d1de))
* faint/blur requests when in files ([49819f0](https://github.com/unkn0wn-root/resterm/commit/49819f0a3a14e045fe5648341b283a03218c7225))
* **ui/textarea:** add horizontal scroll and safe ANSI rendering ([3cd7151](https://github.com/unkn0wn-root/resterm/commit/3cd7151c10c94f0730246a4525b5d2f272820635))


### Bug Fixes

* lint and redundant code ([443cbe3](https://github.com/unkn0wn-root/resterm/commit/443cbe3e9a9046c319acb074f0d9a8283d12ba09))

### [0.3.1](https://github.com/unkn0wn-root/resterm/compare/v0.3.0...v0.3.1) (2025-10-16)


### Features

* add files/requests resize (g+h/l) ([501a2b2](https://github.com/unkn0wn-root/resterm/commit/501a2b2938de568b8039b69ce15bdfae0b603438))

## [0.3.0](https://github.com/unkn0wn-root/resterm/compare/v0.2.2...v0.3.0) (2025-10-16)


### Features

* add inline metadata hints (autocomplete) and make some small tweaks to editor ([658a9c2](https://github.com/unkn0wn-root/resterm/commit/658a9c2580f61b78e7693e4da3f8b80983bae449))


### Bug Fixes

* linter ([c9733c2](https://github.com/unkn0wn-root/resterm/commit/c9733c22402badccec47a5d0b9c11bc8b71888a2))

### [0.2.2](https://github.com/unkn0wn-root/resterm/compare/v0.2.1...v0.2.2) (2025-10-16)


### Features

* add version flag and auto install scripts for linux/mac and windows ([fc1ba4a](https://github.com/unkn0wn-root/resterm/commit/fc1ba4aefbc9a4d4cae24acab01b9f95b98199fa))

### [0.2.1](https://github.com/unkn0wn-root/resterm/compare/v0.2.0...v0.2.1) (2025-10-14)

## [0.2.0](https://github.com/unkn0wn-root/resterm/compare/v0.1.25...v0.2.0) (2025-10-13)


### Features

* new lexer for javascript objects to improve prettify ([1c43f8b](https://github.com/unkn0wn-root/resterm/commit/1c43f8b56ed66e591f582d4470471045e3ad28ec))
* **ui:** capture each request for workflow and assign to each workflow task ([5f96868](https://github.com/unkn0wn-root/resterm/commit/5f968687fcf4750a4a709d99075ef495c339969f))


### Bug Fixes

* allow both : / = and direct assigment for gobal/request/var ([4214875](https://github.com/unkn0wn-root/resterm/commit/42148751ba1c1841bf1e6698f965054c01920e63))

### [0.1.25](https://github.com/unkn0wn-root/resterm/compare/v0.1.24...v0.1.25) (2025-10-13)


### Features

* add workflow runner and colorize Stats ([8a6bdfb](https://github.com/unkn0wn-root/resterm/commit/8a6bdfb7b5a8df4a58cf4692a20b708621525789))
* **ui:** refine sidebar resizing and request list layout ([1cfb393](https://github.com/unkn0wn-root/resterm/commit/1cfb393cad7d1747a9253c606815678225e8c240))


### Bug Fixes

* made a mistake. Fix lint ([6e3cfd5](https://github.com/unkn0wn-root/resterm/commit/6e3cfd59c7b02fb659d1bdac331cabab6f194633))

### [0.1.24](https://github.com/unkn0wn-root/resterm/compare/v0.1.23...v0.1.24) (2025-10-11)


### Bug Fixes

* do not log profiler res if no-log is specified ([953be33](https://github.com/unkn0wn-root/resterm/commit/953be3394b0ec792b728152ac7c0dfb8580899a6))

### [0.1.23](https://github.com/unkn0wn-root/resterm/compare/v0.1.22...v0.1.23) (2025-10-11)


### Features

* **history:** record profile runs, add preview modal, enable delete ([bb90292](https://github.com/unkn0wn-root/resterm/commit/bb90292e2cb50fe7a40c7b53ce6e3333fad0dd37))


### Bug Fixes

* lint errors ([e11d388](https://github.com/unkn0wn-root/resterm/commit/e11d388038fada48c72ecb5e7551dda824d5b3c6))

### [0.1.22](https://github.com/unkn0wn-root/resterm/compare/v0.1.21...v0.1.22) (2025-10-10)


### Features

* added new meta  to profile request ([98a0a55](https://github.com/unkn0wn-root/resterm/commit/98a0a55f8055596c4c81b07e3606f7c916cbea46))

### [0.1.21](https://github.com/unkn0wn-root/resterm/compare/v0.1.20...v0.1.21) (2025-10-10)

### [0.1.20](https://github.com/unkn0wn-root/resterm/compare/v0.1.19...v0.1.20) (2025-10-10)


### Features

* add new focus on pane shortcuts ([6cf9cc7](https://github.com/unkn0wn-root/resterm/commit/6cf9cc748eff0248d40c9126635ed3179b3d1ecf))


### Bug Fixes

* suppress tab focus switching in insert mode ([1952a4f](https://github.com/unkn0wn-root/resterm/commit/1952a4f95e728d92508671b6cbfe414d86ef0830))

### [0.1.19](https://github.com/unkn0wn-root/resterm/compare/v0.1.18...v0.1.19) (2025-10-10)


### Features

* mask sensitive history data and decouple history replay from auto-send ([6139108](https://github.com/unkn0wn-root/resterm/commit/6139108d610e29e53beb53180d08765ff5aa2338))

### [0.1.18](https://github.com/unkn0wn-root/resterm/compare/v0.1.17...v0.1.18) (2025-10-10)

### [0.1.17](https://github.com/unkn0wn-root/resterm/compare/v0.1.16...v0.1.17) (2025-10-09)


### Features

* add new temp file ([3bcf158](https://github.com/unkn0wn-root/resterm/commit/3bcf158ec144e6745d410a2da3425ad900861507))
* temporary file and key bindings ([a31d297](https://github.com/unkn0wn-root/resterm/commit/a31d29707c1a4e2a71f4ec776b956290caa8d9e5))

### [0.1.16](https://github.com/unkn0wn-root/resterm/compare/v0.1.15...v0.1.16) (2025-10-09)


### Features

* shorten active header ([d351088](https://github.com/unkn0wn-root/resterm/commit/d35108846fc94afea84880f7fb6cab84e53ca45f))
* use description and tags in requests pane ([7ca1cfb](https://github.com/unkn0wn-root/resterm/commit/7ca1cfb3b0689370a661fd55a57d785d6a0ac784))


### Bug Fixes

* keep current scroll position while changing panes in response panes ([2767633](https://github.com/unkn0wn-root/resterm/commit/276763372340a055200afeb7affda158a954187a))
* sync editor with request selection after opening a req file ([8cfde0e](https://github.com/unkn0wn-root/resterm/commit/8cfde0e969ba330ba4c936b405d882e62baf3f73))

### [0.1.15](https://github.com/unkn0wn-root/resterm/compare/v0.1.14...v0.1.15) (2025-10-09)


### Features

* add oauth2 auth, globals, and capture support ([746d71e](https://github.com/unkn0wn-root/resterm/commit/746d71e8328df035e016c81f2f93680cd51748a0))
* persist [@capture](https://github.com/capture) file/request scopes and document usage ([ff2f9bb](https://github.com/unkn0wn-root/resterm/commit/ff2f9bbb18f659512fee10ab8201c589ad160771))

### [0.1.14](https://github.com/unkn0wn-root/resterm/compare/v0.1.13...v0.1.14) (2025-10-09)


### Features

* add backward search navigation and cover regex behavior ([9b26b64](https://github.com/unkn0wn-root/resterm/commit/9b26b64ecaa4603ef513ff964f626315887691a9))
* added search bar to the response tab ([64a7329](https://github.com/unkn0wn-root/resterm/commit/64a73294c7afcb8b487e27a49ab69dce261889d3))

### [0.1.13](https://github.com/unkn0wn-root/resterm/compare/v0.1.12...v0.1.13) (2025-10-09)


### Features

* **ui:** preserve raw indentation and harden ANSI stripping ([6985454](https://github.com/unkn0wn-root/resterm/commit/6985454f40d0699163d52d815e12dedafce09185))

### [0.1.12](https://github.com/unkn0wn-root/resterm/compare/v0.1.11...v0.1.12) (2025-10-08)


### Features

* add basic model_utils test (wrapper) ([a490629](https://github.com/unkn0wn-root/resterm/commit/a490629ab85e515c720c8dcd9c51876e28ac8ec3))
* add gh ci with lint, tests and build (on release only) ([7bfb06b](https://github.com/unkn0wn-root/resterm/commit/7bfb06b15130bdab2697ea4d2fbe81f5cf131eca))


### Bug Fixes

* graphql query builder from file ([fd12229](https://github.com/unkn0wn-root/resterm/commit/fd12229e0073717a189978fdaa733e1675f920d7))
* wrapToWidth to handle indentation better ([1514674](https://github.com/unkn0wn-root/resterm/commit/1514674240b8c72783c13823356649d6014bc66e))

### [0.1.11](https://github.com/unkn0wn-root/resterm/compare/v0.1.10...v0.1.11) (2025-10-08)

### [0.1.10](https://github.com/unkn0wn-root/resterm/compare/v0.1.9...v0.1.10) (2025-10-08)


### Bug Fixes

* **status:** utf-8 and truncation overflow for narrow width ([a5598cf](https://github.com/unkn0wn-root/resterm/commit/a5598cf045aa43f23585973f26a1620d5e58a1eb))

### [0.1.9](https://github.com/unkn0wn-root/resterm/compare/v0.1.8...v0.1.9) (2025-10-08)

### [0.1.8](https://github.com/unkn0wn-root/resterm/compare/v0.1.7...v0.1.8) (2025-10-08)

### [0.1.7](https://github.com/unkn0wn-root/resterm/compare/v0.1.6...v0.1.7) (2025-10-07)


### Features

* add request separator color ([9ec619d](https://github.com/unkn0wn-root/resterm/commit/9ec619dba65db7facf32a28761fdc0a8cb8af703))
* editor metadata styling ([78f14dd](https://github.com/unkn0wn-root/resterm/commit/78f14dd6108e5e9a855a3ebb91f66b8349ce35e1))

### [0.1.6](https://github.com/unkn0wn-root/resterm/compare/v0.1.5...v0.1.6) (2025-10-07)


### Bug Fixes

* guard history pane so j/k works after switching focus ([0e8b78b](https://github.com/unkn0wn-root/resterm/commit/0e8b78b0cf455647f1b93148324907a5fec4084b))

### [0.1.5](https://github.com/unkn0wn-root/resterm/compare/v0.1.4...v0.1.5) (2025-10-06)

### [0.1.4](https://github.com/unkn0wn-root/resterm/compare/v0.1.3...v0.1.4) (2025-10-04)


### Bug Fixes

* strip ansi seq before applying styles ([4ef6368](https://github.com/unkn0wn-root/resterm/commit/4ef63684913d5822d87e8219852a1832cf162ec7))

### [0.1.3](https://github.com/unkn0wn-root/resterm/compare/v0.1.2...v0.1.3) (2025-10-04)


### Bug Fixes

* **ui:** surface body diffs when viewing headers ([41fbbe6](https://github.com/unkn0wn-root/resterm/commit/41fbbe6516cda2f187cd8891afb3d18383acf26c))

### [0.1.2](https://github.com/unkn0wn-root/resterm/compare/v0.1.1...v0.1.2) (2025-10-04)


### Features

* normalize diff inputs and remove noisy newline warnings ([6f559b5](https://github.com/unkn0wn-root/resterm/commit/6f559b58fa8014c363c193a81372e67438194432))

### [0.1.1](https://github.com/unkn0wn-root/resterm/compare/v0.1.0...v0.1.1) (2025-10-04)


### Features

* add split for response, diff for requests and 'x' now deletes at mark ([786f121](https://github.com/unkn0wn-root/resterm/commit/786f1214a6d1169bed92f2f1020c42612080f16b))

## [0.1.0](https://github.com/unkn0wn-root/resterm/compare/v0.0.9...v0.1.0) (2025-10-04)


### Bug Fixes

* **editor:** normalize clipboard pastes and broaden delete motions ([c6af22c](https://github.com/unkn0wn-root/resterm/commit/c6af22c09f8a32a1e9d96dd6e2919f920e36d1f7))

### [0.0.9](https://github.com/unkn0wn-root/resterm/compare/v0.0.8...v0.0.9) (2025-10-03)


### Features

* add redo/undo, add new editor motions ([bcb1574](https://github.com/unkn0wn-root/resterm/commit/bcb1574bf6236a8e9f03fef05baf57dfed3c11f7))

### [0.0.8](https://github.com/unkn0wn-root/resterm/compare/v0.0.7...v0.0.8) (2025-10-02)


### Bug Fixes

* set the textarea viewport to refresh itself before clamping the scroll offset so non-zero view starts survive even when the viewport hasn’t rendered yet ([adccf37](https://github.com/unkn0wn-root/resterm/commit/adccf37972e202ee1868d7c152392c40360309e2))

### [0.0.7](https://github.com/unkn0wn-root/resterm/compare/v0.0.5...v0.0.7) (2025-10-02)


### Features

* add delete to be able to mark and delete section ([4766ff8](https://github.com/unkn0wn-root/resterm/commit/4766ff8e5fccabcb25d406d4a2a89d0009801c18))
* add undo to deleted buffer ([f600fea](https://github.com/unkn0wn-root/resterm/commit/f600fea8eedfa4c5632cb3b6867c386ad7172682))
* allow loading script blocks from external files ([1d33d6a](https://github.com/unkn0wn-root/resterm/commit/1d33d6ac52d588bdf947f2056416bba3b8a01017))
* respect the current viewport so we don't move editor to deleted line ([b5e11c1](https://github.com/unkn0wn-root/resterm/commit/b5e11c15fae62460c525f02e23cfd78a05fd5073))
* **ui:** add repeatable pane resizing chords and new "g" mode for resizing ([c261653](https://github.com/unkn0wn-root/resterm/commit/c26165306861bbab30b1a18a9889661b36e1c3d8))

### [0.0.6](https://github.com/unkn0wn-root/resterm/compare/v0.0.5...v0.0.6) (2025-10-01)


### Features

* allow loading [@script](https://github.com/script) blocks from external files ([9e9ff60](https://github.com/unkn0wn-root/resterm/commit/9e9ff60390b46bb9c850fd91e4c3bca94fc9d220))

### [0.0.5](https://github.com/unkn0wn-root/resterm/compare/v0.0.4...v0.0.5) (2025-10-01)

### [0.0.4](https://github.com/unkn0wn-root/resterm/compare/v0.0.3...v0.0.4) (2025-10-01)


### Features

* add saveAs for saving directly within editor ([78ef005](https://github.com/unkn0wn-root/resterm/commit/78ef005beac77c5f895d6d95fb4dc07fc008c08a))

### [0.0.3](https://github.com/unkn0wn-root/resterm/compare/v0.0.2...v0.0.3) (2025-10-01)


### Bug Fixes

* disable motions in insert mode ([7fd8985](https://github.com/unkn0wn-root/resterm/commit/7fd8985fd995741dabb0d657b289f0a5cf5208b0))
* inline request sending ([bde3f1f](https://github.com/unkn0wn-root/resterm/commit/bde3f1fc5b27486471cef25e6573cbe8ce1722cf))

### 0.0.2 (2025-10-01)


### Features

* add more vim motions to the editor ([a43a14c](https://github.com/unkn0wn-root/resterm/commit/a43a14c0973133a463e1564a5135f22bd318cf60))
* enable textarea selection highlighting in editor ([8ea748c](https://github.com/unkn0wn-root/resterm/commit/8ea748c185011c876481923c58bddfee360d345a))
* search ([b684ea8](https://github.com/unkn0wn-root/resterm/commit/b684ea839ec7b4efb2b99da221d569b2eece7a6d))


### Bug Fixes

* omit first event on search open ([b0b6f94](https://github.com/unkn0wn-root/resterm/commit/b0b6f94d56a688997024afbf96a05e8d0dcddb85))


## /README.md

<h1 align="center">
  <img src="_media/resterm_logo.png" alt="Resterm" width="200" />
  <br>
  Resterm
</h1>

<p align="center">
  <em>A terminal client for REST, GraphQL, gRPC, WebSocket, and SSE.</em>
</p>

<p align="center">
  <img src="_media/resterm_base.png" alt="Screenshot of Resterm TUI base" width="720" />
</p>

Resterm is a **keyboard-driven** API client that lives in your terminal and keeps everything local. It stores requests as plain files, supports **SSH tunnels**, **Kubernetes port-forwarding**, **OAuth 2.0**, and **command-backed auth**, and gives you a fast feedback loop with `history`, `diffs`, `tracing`, and `profiling`.

Quick links: [Screenshots](#screenshot-tour), [Installation](#installation), [Quick Start](#quick-start), [Features](#overview) and [Documentation](#documentation).

## Screenshot tour

<details>
<summary>See the UI in action</summary>

<p align="center">
  <strong>Light Theme</strong>
</p>

<p align="center">
  <img src="_media/resterm-lighttheme.png" alt="Screenshot of Resterm Light Theme" width="720" />
</p>

<p align="center">
  <strong>Workflows</strong>
</p>

<p align="center">
  <img src="_media/resterm_workflow.png" alt="Screenshot of Resterm with Workflow" width="720" />
</p>

<p align="center">
  <strong>Trace and Timeline</strong>
</p>

<p align="center">
  <img src="_media/resterm_trace_timeline.png" alt="Screenshot of Resterm with timeline" width="720" />
</p>

<p align="center">
  <strong>Profiler</strong>
</p>

<p align="center">
  <img src="_media/resterm_profiler.png" alt="Screenshot of Resterm profiler" width="720" />
</p>

<p align="center">
  <strong>Explain</strong>
</p>

<p align="center">
  <img src="_media/resterm_explain.png" alt="Screenshot of Resterm Explain Tab" width="720" />
</p>

<p align="center">
  <strong>RestermScript</strong>
</p>

<p align="center">
  <img src="_media/resterm_script.png" alt="Screenshot of Resterm with RestermScript" width="720" />
</p>

<p align="center">
  <strong>OAuth browser demo (old UI design)</strong>
</p>

<p align="center">
  <img src="_media/oauth.gif" alt="Resterm OAuth flow" width="720" />
</p>

</details>

## Why Resterm

- Requests live in plain `.http` / `.rest` files.
- **Conditional logic** - `@when`, `@skip-if`, `@if`/`@elif`/`@else`, `@switch`/`@case`, and `@for-each`.
- **Multi-step workflows** with `@workflow` / `@step`.
- **Captures, variables, and assertions** (`@capture`, `@var`, `@assert`).
- **RestermScript** - a small, safe expression language purpose.
- **OAuth 2.0** (client credentials, password, auth code + PKCE), **command-backed auth** via existing CLIs, **SSH tunnels**, and **Kubernetes port-forwards** are built in - no extra tools.
- **CLI** with `resterm run` for requests, workflows, JSON/JUnit output, and reusable run artifacts.
- **Timeline tracing**, **profiling**, and **compare runs** across environments.
- **Streaming transcripts** and an interactive console for WebSocket and SSE sessions.
- No cloud sync, no accounts, no telemetry. Everything stays local.
- There is no AI integration and there will never be.

## Headless

Resterm ships with the engine API, so you can build your own headless runner for Resterm and embed request execution in your own Go tooling/code, CI flows or internal automation.

The public Go API lives in the [`headless`](./headless) package and can run requests, workflows, assertions, compare runs, and profiles without the TUI.

> [!NOTE]
> If you want an "off-the-shelf" runner instead of building your own, check out [resterm-runner](https://github.com/unkn0wn-root/resterm-runner).

## CLI

Resterm also ships with a built-in CLI: `resterm run`.

Use `resterm run` when you want to execute `.http` / `.rest` files directly from the terminal without opening the TUI.

**Example**:

```bash
mkdir my-api && cd my-api
resterm init
resterm run --request Echo requests.http
```

`resterm init` creates `requests.http` with sample requests and `resterm.env.json` with a `dev` environment that points at `httpbin.org`. The command above runs the **Echo** request which POSTs a JSON body and prints the response.

> [!NOTE]
> This is different from the `headless` package. The `headless` package is the embeddable Go API for building your own runner or CI integration, while `resterm run` is the built-in CLI on top of the same execution engine.

See the full [CLI documentation](docs/cli.md) for usage, selectors, output formats, and examples.


## Quick Start

1. Install Resterm using the command that matches your OS.

   ```bash
   # Linux/macOS (Homebrew)
   brew install resterm

   # Linux (install script)
   curl -fsSL https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.sh | bash
   ```

   ```powershell
   # Windows (PowerShell)
   iwr -useb https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.ps1 | iex
   ```

2. Bootstrap a tiny workspace.

   ```bash
   mkdir my-api && cd my-api
   resterm init
   ```

3. Run it and send your first request.

   ```bash
   resterm
   ```

   Press `Ctrl+Enter` in the editor to send the highlighted request.

If you do not want files yet, just run `resterm`, type a URL in the editor, and press `Ctrl+Enter`.
If you already have a curl command, paste it into the editor and press `Ctrl+Enter` to import it.

## Key files

These are the files you will see most often in a Resterm workspace.

- `.http` / `.rest` files store your requests, metadata, and named steps.
- `resterm.env.json` holds environment variables and secrets for one or more environments.
- `resterm.env.example.json` is a safe template you can commit and share.
- `.rts` files contain RestermScript helpers for reuse and small bits of logic.
- `RESTERM.md` is optional project notes created by `resterm init`.

## Overview

Protocols and transports cover the following.

- `HTTP`, `GraphQL`, `gRPC`, `WebSocket`, and `SSE` are supported out of the box.
- `SSH tunnels` include host key verification, keep-alives, retries, and persistent sessions.
- `Kubernetes port-forwards` target pods, services, deployments, and statefulsets with named port resolution, pod readiness checks, retries, and persistent sessions.

Workspace and files focus on the following.

- `.http` / `.rest` files are discovered automatically and the request list stays in sync as you edit.
- The request editor includes inline syntax highlighting, search, and contextual metadata hints.
- Environment files are treated as first-class input. `resterm init` adds _resterm.env.json_ to _.gitignore_ but any custom env files passed via the CLI are **not** ignored automatically.

Automation and reuse include the following.

- Variables, captures, constants, and multi-step workflows.
- RestermScript (RTS) and optional JavaScript hooks handle request-time logic.
- Curl commands and OpenAPI specs can be imported directly into request collections.

Debugging and inspection provide the following.

- `Pretty/raw/header/diff/history` views are available with split panes.
- Streaming transcripts are recorded and an interactive console is available for WebSocket and SSE.
- Timeline tracing and profiling help find slow or flaky endpoints.

Security and auth cover the following.

- OAuth 2.0 client credentials, password grant, and authorization code + PKCE are supported.
- Command-backed auth can reuse existing CLIs without wrapping a shell.
- Tokens can be cached per environment, and OAuth tokens are refreshed automatically.

Customization includes the following.

- Custom themes and keybindings.

## Navigation & layout cheat sheet

A few keys that make Resterm feel “native” quickly:

- **Pane focus & layout**
  - `Tab` / `Shift+Tab` - move focus between sidebar, editor, and response.
  - `g+r` - jump to **Requests** (sidebar).
  - `g+i` - jump to **Editor**.
  - `g+p` - jump to **Response**.
  - `g+h` / `g+l` - adjust layout:
    - When the **left pane** (sidebar) is focused: change sidebar width.
    - Otherwise: change editor/response split.
  - `g+v` / `g+s` - toggle response pane between inline and stacked layout.
  - `g+1`, `g+2`, `g+3` + minimize/restore sidebar, editor, response.
  - `g+z` / `g+Z` - zoom the focused pane / clear zoom.

- **Environments & globals**
  - `Ctrl+E` - switch environments.
  - `Ctrl+G` - inspect captured globals.

- **Working with responses**
  - `Ctrl+V` / `Ctrl+U` - split the response pane for side-by-side comparison.
  - When response pane is focused:
    - `Ctrl+Shift+C` or `g y` - copy the entire Pretty/Raw/Headers tab
      to the clipboard (no mouse selection needed).
  - `g x` - build an Explain preview for the active request without sending it.

---

> [!TIP]
> **If you only remember three shortcuts…**
> - `Ctrl+Enter` - send request
> - `Tab` / `Shift+Tab` - switch panes
> - `g+p` - jump to response

## Installation

**Linux / macOS (Homebrew)**

```bash
brew install resterm
```

> [!NOTE]
> Homebrew installs should be updated with Homebrew (`brew upgrade resterm`). The built-in `resterm --update` command is for binaries installed from GitHub releases or install scripts.

**Linux / macOS (Shell script)**

> [!IMPORTANT]
> Pre-built Linux binaries depend on glibc **2.32 or newer**. If you run an older distro, build from source on a machine with a newer glibc toolchain or upgrade glibc before using the release archives.

```bash
curl -fsSL https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.sh | bash
```

or with `wget`:

```bash
wget -qO- https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.sh | bash
```

**Windows (PowerShell)**

```powershell
iwr -useb https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.ps1 | iex
```

These scripts detect your architecture, download the latest release, and install the binary.

### Manual installation

> [!NOTE]
> The manual install helper uses `curl` and `jq`. Install `jq` with your package manager (`brew install jq`, `sudo apt install jq`, etc.).

**Linux / macOS**

```bash
# Detect latest tag
LATEST_TAG=$(curl -fsSL https://api.github.com/repos/unkn0wn-root/resterm/releases/latest | jq -r .tag_name)

# Download the matching binary (Darwin/Linux + amd64/arm64)
curl -fL -o resterm "https://github.com/unkn0wn-root/resterm/releases/download/${LATEST_TAG}/resterm_$(uname -s)_$(uname -m)"

# Make it executable and move it onto your PATH
chmod +x resterm
sudo install -m 0755 resterm /usr/local/bin/resterm
```

**Windows (PowerShell)**

```powershell
$latest = Invoke-RestMethod https://api.github.com/repos/unkn0wn-root/resterm/releases/latest
$asset  = $latest.assets | Where-Object { $_.name -like 'resterm_Windows_*' } | Select-Object -First 1
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile resterm.exe
# Optionally relocate to a directory on PATH, e.g.:
Move-Item resterm.exe "$env:USERPROFILE\bin\resterm.exe"
```

### From source

```bash
go install github.com/unkn0wn-root/resterm/cmd/resterm@latest
```

## Update

```bash
resterm --check-update
resterm --update
```

The first command reports whether a newer release is available. The second downloads and installs it (Windows users receive a staged binary to swap on restart).

## Quick Configuration Overview

- Environments are JSON files (`resterm.env.json`) discovered in the request directory, workspace root, or CWD. Dotenv files (`.env`, `.env.*`) are opt-in via `--env-file` and are single-workspace. Prefer JSON when you need multiple environments in one file.
- Flags you probably reach for most are `--workspace`, `--file`, `--env`, `--env-file`, `--timeout`, `--insecure`, `--follow`, `--proxy`, `--recursive`, `--from-curl`, `--from-openapi`, and `--http-out`.
- Config is stored at `$HOME/Library/Application Support/resterm`, `%APPDATA%\resterm`, or `$HOME/.config/resterm` and can be overridden with `RESTERM_CONFIG_DIR`.

## Collections

You can export a workspace into a Git-friendly Resterm bundle. The exported bundle always includes a `manifest.json` with checksums, so imports can verify file integrity before writing anything.

When Resterm sees `resterm.env.example.json` in your workspace, it includes that file as-is. If only `resterm.env.json` exists, Resterm generates `resterm.env.example.json` automatically and replaces values with `REPLACE_ME` placeholders so secrets are not exported.

### Export a bundle

```bash
resterm collection export \
  --workspace ./my-api \
  --out ./shared/my-api-bundle \
  --recursive \
  --name "my-api-v1"
```

### Import a bundle

```bash
resterm collection import \
  --in ./shared/my-api-bundle \
  --workspace ./my-local-api
```

If you want to preview actions first, you can run:

```bash
resterm collection import \
  --in ./shared/my-api-bundle \
  --workspace ./my-local-api \
  --dry-run
```

If files already exist and should be replaced intentionally, add `--force`.

## Inline curl import

Paste a curl command into the editor and press `Ctrl+Enter` to convert it into a structured request. Resterm understands common flags, merges repeated data segments, and keeps multipart uploads intact.

```bash
curl \
  --compressed \
  --url "https://httpbin.org/post?source=resterm&case=multipart" \
  --request POST \
  -H "Accept: application/json" \
  -H "X-Client: resterm-dev" \
  --user resterm:test123 \
  -F file=@README.md \
  --form-string memo='Testing resterm inline curl
with multiline value' \
  --form-string meta='{"env":"test","attempt":1}'
```

If you copied the command from a shell, prefixes like `sudo` or `$` are ignored automatically.

## RestermScript

RestermScript (RTS) is a small expression language built specifically for Resterm. Because it targets Resterm features directly, it can evolve alongside Resterm and stay symbiotic with the request format, workflows, and directives. JavaScript hooks are still available when you want them, but RTS is the default because it is readable, predictable, and focused on Resterm’s domain.

Quick example (RTS module + request):

```rts
// rts/helpers.rts
module helpers
export fn authHeader(token) {
  return token ? "Bearer " + token : ""
}
```

```http
# @use ./rts/helpers.rts
# @when env.has("feature")
# @assert response.statusCode == 200
GET https://api.example.com/users/{{= vars.get("user") }}
Authorization: {{= helpers.authHeader(vars.get("auth.token")) }}
```

Full reference: [`docs/restermscript.md`](docs/restermscript.md).

## Deep dive

#### OAuth 2.0

Resterm supports client credentials, password grant, and authorization code + PKCE. For auth code flows, it opens your system browser, spins up a local callback server on `127.0.0.1`, captures the redirect, and exchanges the code automatically. Tokens are cached per environment and refreshed when they expire. Docs: [`docs/resterm.md#oauth-20-directive`](./docs/resterm.md#oauth-20-directive) and `_examples/oauth2.http`.

#### Workflows and scripting

Chain requests with `@workflow` and `@step`, pass data between steps, and add lightweight JS hooks where needed. Docs + sample: [`docs/resterm.md#workflows-multi-step-workflows`](./docs/resterm.md#workflows-multi-step-workflows) and `_examples/workflows.http`.

#### Compare runs

Run the same request across environments with `@compare` or `--compare`, then diff responses side by side with `g+c`. Docs: [`docs/resterm.md#compare-runs`](./docs/resterm.md#compare-runs).

#### Tracing and timeline

Add `@trace` with budgets to capture DNS, connect, TLS, TTFB, and transfer timings. Resterm visualizes overruns and can export spans to OpenTelemetry. Docs: [`docs/resterm.md#timeline--tracing`](./docs/resterm.md#timeline--tracing).

#### Streaming (WebSocket and SSE)

Use `@websocket` with `@ws` steps or `@sse` to script and record streams. The Stream tab keeps transcripts and includes an interactive console. Docs: [`docs/resterm.md#streaming-sse--websocket`](./docs/resterm.md#streaming-sse--websocket).

#### gRPC

Resterm supports unary and streaming calls with transcripts, metadata, and body expansion for gRPC files. Docs: [`docs/resterm.md#grpc`](./docs/resterm.md#grpc).

#### OpenAPI import

Convert OpenAPI 3 specs into Resterm-ready `.http` collections from the CLI with `--from-openapi`. Docs: [`docs/resterm.md#importing-openapi-specs`](./docs/resterm.md#importing-openapi-specs).

#### Collection sharing

Export a portable Resterm-native bundle with `resterm collection export` and import it into another workspace with `resterm collection import`. Docs: [`docs/resterm.md#collection-sharing`](./docs/resterm.md#collection-sharing).

#### Curl import

Convert curl commands into `.http` files from the CLI with `--from-curl`. Docs: [`docs/resterm.md#importing-curl-commands`](./docs/resterm.md#importing-curl-commands).

#### SSH tunnels

Route HTTP, gRPC, WebSocket, and SSE traffic through bastions with `@ssh` profiles. Docs: [`docs/resterm.md#ssh-tunnels`](./docs/resterm.md#ssh-tunnels) and `_examples/ssh.http`.

#### Kubernetes port-forwards

Route HTTP, gRPC, WebSocket, and SSE traffic through Kubernetes with `@k8s` profiles targeting pods/services/deployments/statefulsets. Docs: [`docs/resterm.md#kubernetes-port-forwards`](./docs/resterm.md#kubernetes-port-forwards) and `_examples/k8s.http`.

#### Theming and bindings

Customize colors and keybindings via `themes/*.toml` and `bindings.toml` or `bindings.json` under the config directory. Docs: [`docs/resterm.md#theming`](./docs/resterm.md#theming) and [`docs/resterm.md#custom-bindings`](./docs/resterm.md#custom-bindings).

## Documentation

The full reference, including request syntax, metadata, directive tables, scripting APIs, transport settings, and advanced workflows, lives in [`docs/resterm.md`](./docs/resterm.md).
The command-line guide for `resterm run`, importers, collection tooling, and history maintenance lives in [`docs/cli.md`](./docs/cli.md).


## /_examples/auth_command.http

```http path="/_examples/auth_command.http" 
### Global Command Auth Default
# @auth global command argv=["gh","auth","token"] cache_key=github-cli-global

### Reusable Command Auth Profile
# @patch global githubCliAuth {auth: {type: "command", argv: '["gh","auth","token"]', cache_key: "github-cli"}}

### GitHub CLI Token Via Global Auth
# @name GitHubCLITokenGlobal
# @tag auth command global
# @description Inherits command-backed bearer auth from the global @auth directive above.
GET https://api.github.com/user
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28

### GitHub CLI Token Via @apply
# @name GitHubCLITokenPatched
# @tag auth command
# @description Reuses command-backed bearer auth through a global apply profile.
# @apply use=githubCliAuth
GET https://api.github.com/user
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28

### GitHub CLI Token
# @name GitHubCLIToken
# @tag auth command
# @description Uses the GitHub CLI token printer. Run `gh auth login` outside Resterm first.
# @auth command argv=["gh","auth","token"] cache_key=github-cli
GET https://api.github.com/user
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28

### GitHub CLI Token Reuse
# @name GitHubCLITokenReuse
# @tag auth command
# @description Reuses the seeded github-cli command auth slot without repeating argv.
# @auth command cache_key=github-cli
GET https://api.github.com/user/repos
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28

### Structured JSON Output
# @name CommandJSONToken
# @tag auth command
# @description Parses token, token type, and expiry fields from JSON stdout.
# @auth command argv=["mycli","auth","print","--json"] format=json token_path=access_token type_path=token_type expires_in_path=expires_in cache_key=myapi
GET https://example.com/projects
Accept: application/json

### Custom Header Injection
# @name CommandCustomHeader
# @tag auth command
# @description Sends a raw token to a custom header instead of Authorization.
# @auth command argv=["aws","--profile","{{aws.profile}}","ecr","get-login-password"] header=X-Registry-Token cache_key=ecr ttl=10m
GET https://example.com/registry
Accept: application/json

### Fixed Scheme Override
# @name CommandFixedScheme
# @tag auth command
# @description Overrides token_type with a fixed scheme string.
# @auth command argv=["mycli","auth","print","--json"] format=json token_path=data.token type_path=data.token_type header=Authorization scheme=Token cache_key=custom-scheme
GET https://example.com/audit
Accept: application/json

```

## /_examples/auth_scopes.http

```http path="/_examples/auth_scopes.http" 
# @global auth.globalToken demo-global-token
# @global auth.fileToken demo-file-token

# @auth global bearer {{auth.globalToken}}

### Global Default Auth
# @name AuthGlobalDefault
# @tag auth scopes global
# @description Inherits the global default because no file-scoped auth has been defined yet.
GET https://httpbin.org/anything/auth/global-default
Accept: application/json

# @auth file bearer {{auth.fileToken}}

### File Default Auth
# @name AuthFileDefault
# @tag auth scopes file
# @description Inherits the file default, which overrides the earlier global default for later requests in this document.
GET https://httpbin.org/anything/auth/file-default
Accept: application/json

### Explicit Request Override
# @name AuthRequestOverride
# @tag auth scopes request
# @description Uses explicit request-scoped auth instead of the inherited file default.
# @auth request bearer request-token-{{$uuid}}
GET https://httpbin.org/anything/auth/request-override
Accept: application/json

### Disable Inherited Auth
# @name AuthNone
# @tag auth scopes none
# @description Disables inherited auth for this request with @auth none.
# @auth none
GET https://httpbin.org/anything/auth/no-auth
Accept: application/json

```

## /_examples/basic.http

```http path="/_examples/basic.http" 
### Status Check
# @name getStatus
GET {{base.url}}/status/204
User-Agent: resterm/0.1
Accept: application/json

### Download binary file
# @name downloadBinary
GET https://raw.githubusercontent.com/unkn0wn-root/resterm/main/_media/resterm_base.png

### Create Post
# @name createPost
POST {{base.url}}/anything/posts
Content-Type: application/json
Accept: application/json

{
  "title": "foo",
  "body": "bar",
  "userId": 1
}

### Timestamp Helpers
# @name timestampHelpers
POST {{base.url}}/anything/time
Content-Type: application/json
Accept: application/json

{
  "now_unix": "{{$timestamp}}",
  "now_unix_plus_6d": "{{$timestamp + 6d}}",
  "now_iso_minus_90m": "{{$timestampISO8601 - 90m}}",
  "now_unix_ms": "{{$timestampMs}}"
}

### Bearer Auth Example
# @name bearerEcho
# @auth bearer {{auth.token}}
GET {{base.url}}/bearer
Accept: application/json

### OAuth2 Directive
# @name oauthDirectiveDemo
# @description Demonstrates Resterm's built-in @auth oauth2 support with environment-driven parameters.
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} client_secret={{oauth.clientSecret}} scope="{{oauth.scope}}" audience={{oauth.audience}} cache_key={{oauth.cacheKey}}
GET {{base.url}}/anything/oauth2-demo
Accept: application/json

### OAuth2 Directive (Reuse Cached Token)
# @name oauthDirectiveReuseDemo
# @description After the cache is seeded above, only cache_key is needed to reuse the token.
# @auth oauth2 cache_key={{oauth.cacheKey}}
GET {{base.url}}/anything/oauth2-demo-reuse
Accept: application/json

```

## /_examples/bindings/bindings.toml

```toml path="/_examples/bindings/bindings.toml" 
# Sample bindings override for Resterm.
# Copy this file to ${RESTERM_CONFIG_DIR}/bindings.toml and tweak as needed.

[bindings]
# Remap save.
save_file = ["ctrl+shift+s"]

# Stacking panes.
set_main_split_horizontal = ["g s", "ctrl+alt+s"]

# Sending requests.
send_request = ["ctrl+enter", "cmd+enter"]

# Toggle the response split.
toggle_response_split_vertical = ["alt+v"]

# Map quick focus switches.
focus_requests = ["g r", "ctrl+alt+r"]
focus_editor_normal = ["g i"]
focus_response = ["g p"]

# Resize sidebar.
sidebar_width_decrease = ["g n"]
sidebar_width_increase = ["g m"]

# Open the environment selector.
open_env_selector = ["ctrl+alt+e"]

# Launch the theme selector.
open_theme_selector = ["ctrl+alt+t", "f6"]

```

## /_examples/compare.http

```http path="/_examples/compare.http" 
### Compare health across environments
# @name env-health
# @compare dev stage prod base=prod
GET {{services.api.base}}/status
Accept: application/json

### Compare feature flag
# @name feature-flag
# Toggle `--compare` or press `g+c` to reuse workspace defaults
GET {{services.api.base}}/feature-flags/{{feature.name}}
Accept: application/json

```

## /_examples/curl-import.http

```http path="/_examples/curl-import.http" 
# Source:
# curl -G https://api.example.com/search -d q=hello -H 'X-Flag: yes'
# curl https://api.example.com/login --json '{"user":"demo","password":"pass"}'

### GET https://api.example.com/search?q=hello
GET https://api.example.com/search?q=hello
X-Flag: yes

### POST https://api.example.com/login
POST https://api.example.com/login
Content-Type: application/json

{"user":"demo","password":"pass"}

```

## /_examples/descriptors/analytics.protoset

```protoset path="/_examples/descriptors/analytics.protoset" 
placeholder descriptor set for examples

```

## /_examples/graphql.http

```http path="/_examples/graphql.http" 
### Inline GraphQL Query
# @name GraphQLFetchWorkspace
# @tag graphql workspaces
# @description Inline @graphql request with variables block.
# @graphql
# @operation FetchWorkspace
POST {{graphql.endpoint}}
Accept: application/json

query FetchWorkspace($id: ID!, $includeMembers: Boolean!) {
  workspace(id: $id) {
    id
    name
    region
    plan
    members @include(if: $includeMembers) {
      totalCount
      edges {
        node {
          id
          email
        }
      }
    }
  }
}

# @variables
{
  "id": "{{graphql.workspaceId}}",
  "includeMembers": true
}

### GraphQL With External Files
# @name GraphQLFromFiles
# @tag graphql files
# @description Loads query and variables via file references.
# @graphql
# @operation FetchWorkspace
# @query < ./queries/workspace.graphql
POST {{graphql.endpoint}}
Accept: application/json

# @variables < ./queries/workspace.variables.json

```

## /_examples/grpc.http

```http path="/_examples/grpc.http" 
### Generate Report Over gRPC
# @name GrpcGenerateReport
# @tag grpc reports
# @description Demonstrates @grpc metadata, reflection, and plaintext override.
# @grpc analytics.ReportingService/GenerateReport
# @grpc-reflection true
# @grpc-plaintext true
# @grpc-authority {{grpc.authority}}
# @grpc-metadata x-trace-id: {{$uuid}}
GRPC {{grpc.host}}

{
  "tenantId": "{{tenant.id}}",
  "reportId": "rep-{{$uuid}}",
  "includeHistorical": true
}

### Fetch Report Status With Descriptor
# @name GrpcReportStatus
# @tag grpc reports
# @description Uses a precompiled descriptor set and additional metadata.
# @grpc analytics.ReportingService/GetReportStatus
# @grpc-descriptor descriptors/analytics.protoset
# @grpc-metadata authorization: Bearer {{analytics.sessionToken}}
GRPC {{grpc.host}}

{
  "reportId": "rep-{{$uuid}}"
}

```

## /_examples/k8s.http

```http path="/_examples/k8s.http" 
# @k8s global cluster-api namespace=default service=api port=http context=kind-dev persist retries=2 pod_running_timeout=30s
# @k8s file payments namespace=payments deployment=payments-api port=https container=api

### Health through shared service profile
# @k8s use=cluster-api
GET http://api.default.svc.cluster.local/v1/health
Accept: application/json

### One-off statefulset target with named port
# @k8s namespace=default statefulset=postgres port=pgsql
GET http://postgres.default.svc.cluster.local:5432/healthz

### Override selected profile target and port
# @k8s use=cluster-api target=pod:api-server-0 port=8080
GET http://api.default.svc.cluster.local/v1/version

### Deployment profile over gRPC
# @k8s use=payments
# @grpc testservices.inventory.ProjectService/Seed
# @grpc-descriptor ./descriptors/testservices.protoset
GRPC passthrough:///payments-api.payments.svc.cluster.local:8082

{}

```

## /_examples/nested.http

```http path="/_examples/nested.http" 
### Create Workspace With Nested Settings
# @name Create Workspace
# @tag workspaces nested setup
@workspaceName = Terminal Toolkit
@workspaceId = 4301
@limits.users = 25
@limits.storage = "5GB"
@featureA = realtime-updates
@featureB = script-hooks

POST {{base.url}}/workspaces
Content-Type: application/json
Authorization: Bearer {{auth.token}}
X-Tenant-ID: {{tenant.id}}

{
  "name": "{{workspaceName}}",
  "limits": {
    "users": {{limits.users}},
    "storage": "{{limits.storage}}"
  },
  "features": [
    "{{featureA}}",
    "{{featureB}}",
    "{{features[0]}}"
  ],
  "metadata": {
    "region": "{{meta.region}}",
    "plan": "{{meta.plan}}",
    "flags": {{meta.flags}}
  }
}

### Retrieve Workspace Snapshot
# @name Get Workspace
# @tag workspaces read nested
GET {{base.url}}/workspaces/{{workspaceId}}?include=settings,features&expand={{query.expand}}
Accept: application/json
Authorization: Bearer {{auth.token}}

### List Workspace Members
# @name List Members
# @tag workspaces members nested
GET {{base.url}}/workspaces/{{workspaceId}}/members?limit={{limits.users}}&offset=0
Accept: application/json
Authorization: Bearer {{auth.token}}

```

## /_examples/oauth2.http

```http path="/_examples/oauth2.http" 
### Simulated OAuth2 Token Exchange
# @name OAuthManualExchange
# @tag oauth2 capture
# @description Use httpbin to echo a client-credentials request and capture the token into globals for reuse.
# @var request oauth.requestId {{$uuid}}
# @capture global-secret oauth.manualToken {{response.json.json.access_token}}
# @capture file oauth.manualRefresh {{response.json.json.refresh_token}}
POST https://httpbin.org/anything/oauth/token
Content-Type: application/json
Accept: application/json

{
  "grant_type": "client_credentials",
  "client_id": "{{oauth.clientId}}",
  "client_secret": "{{oauth.clientSecret}}",
  "scope": "{{oauth.scope}}",
  "access_token": "manual-{{$uuid}}",
  "refresh_token": "refresh-{{$uuid}}",
  "token_type": "Bearer",
  "expires_in": 3600,
  "request_id": "{{oauth.requestId}}"
}

### Call API With Captured Token
# @name OAuthManualReuse
# @tag oauth2 reuse
# @description Pulls the token from the global cache populated by the previous request.
# @capture request oauth.manualAuthHeader {{response.json.headers.Authorization}}
# @script test
> client.test("manual token reused", function () {
>   var echoed = vars.get("oauth.manualAuthHeader");
>   var token = vars.get("oauth.manualToken");
>   tests.assert(typeof token === "string" && token.length > 0, "token should be available");
>   tests.assert(typeof echoed === "string" && echoed.includes(token), "Bearer header should include the captured token");
> });
GET {{base.url}}/anything/accounts
Authorization: Bearer {{oauth.manualToken}}
Accept: application/json

### Client Credentials With @auth oauth2
# @name OAuthDirective
# @tag oauth2 directive
# @description Delegates token acquisition to Resterm.
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} client_secret={{oauth.clientSecret}} scope="{{oauth.scope}}" audience={{oauth.audience}} cache_key={{oauth.cacheKey}} client_auth=body
# @capture request oauth.directiveAuthHeader {{response.json.headers.Authorization}}
GET {{base.url}}/anything/projects
Accept: application/json

### Reuse Cached Directive Token
# @name OAuthDirectiveReuse
# @tag oauth2 directive reuse
# @description Using the same cache_key allows the cached access token to be sent without another token exchange when it is still valid.
# @auth oauth2 cache_key={{oauth.cacheKey}}
# @capture request oauth.directiveReuseAuth {{response.json.headers.Authorization}}
GET {{base.url}}/anything/projects/{{tenant.id}}
Accept: application/json

### Authorization Code with PKCE
# @name OAuthAuthCodePKCE
# @tag oauth2 authcode
# @description Opens a browser at auth_url and captures the redirect on a loopback callback (PKCE S256 by default).
# @auth oauth2 auth_url={{oauth.authUrl}} token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} scope="{{oauth.scope}}" grant=authorization_code code_challenge_method=s256
GET {{base.url}}/anything/projects/{{tenant.id}}?flow=auth-code
Accept: application/json

```

## /_examples/openapi-spec.yml

```yml path="/_examples/openapi-spec.yml" 
openapi: 3.0.3
info:
  title: Resterm OpenAPI Feature Harness
  version: 1.0.0
  description: |
    Comprehensive test surface used to validate Resterm's OpenAPI import pipeline.
    Exercises security schemes, parameter serialization styles, callbacks, examples,
    request bodies, and multi-server resolution.
servers:
  - url: https://api.resterm.dev/v1
    description: Primary production server
  - url: https://{stage}.resterm.dev/{basePath}
    description: Stage-specific server
    variables:
      stage:
        default: staging
        enum: [staging, qa]
      basePath:
        default: v1
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    oauthDemo:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://auth.resterm.dev/oauth/token
          scopes:
            metrics.read: Read metrics
            metrics.write: Write metrics
        password:
          tokenUrl: https://auth.resterm.dev/oauth/token
          scopes:
            profile.read: Read user profile
        authorizationCode:
          authorizationUrl: https://auth.resterm.dev/oauth/authorize
          tokenUrl: https://auth.resterm.dev/oauth/token
          scopes:
            dashboards.manage: Manage dashboards
        implicit:
          authorizationUrl: https://auth.resterm.dev/oauth/authorize
          scopes:
            dashboards.view: View dashboards
    oidcProvider:
      type: openIdConnect
      openIdConnectUrl: https://auth.resterm.dev/.well-known/openid-configuration
  schemas:
    User:
      type: object
      required: [id, email]
      properties:
        id:
          type: string
          format: uuid
          example: 9b0e5b1d-4a98-4bb0-9a9e-8a21b713c3da
        email:
          type: string
          format: email
          example: user@example.com
        displayName:
          type: string
          example: Jane Doe
        address:
          $ref: '#/components/schemas/Address'
        preferences:
          $ref: '#/components/schemas/UserPreferences'
    Address:
      type: object
      properties:
        line1:
          type: string
        line2:
          type: string
        city:
          type: string
        country:
          type: string
          default: US
    UserPreferences:
      type: object
      additionalProperties:
        type: string
      properties:
        theme:
          type: string
          enum: [light, dark, system]
        telemetryOptIn:
          type: boolean
          default: false
    NewUser:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        displayName:
          type: string
        roles:
          type: array
          items:
            type: string
          example: [admin, editor]
    Metric:
      type: object
      required: [name, value]
      properties:
        name:
          type: string
        value:
          type: number
          format: double
        tags:
          type: array
          items:
            type: string
    ErrorResponse:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
paths:
  /users:
    get:
      summary: List users
      description: Returns a filtered page of users.
      parameters:
        - name: limit
          in: query
          description: Page size
          schema:
            type: integer
            default: 25
        - name: tags
          in: query
          description: Filter by user tags
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
            example: [beta, premium]
        - name: filter
          in: query
          description: Structured filter criteria
          style: deepObject
          explode: true
          schema:
            type: object
            properties:
              status:
                type: string
              minCreatedAt:
                type: string
                format: date-time
        - name: traceId
          in: header
          schema:
            type: string
            example: trace-123
        - name: locale
          in: cookie
          schema:
            type: string
            default: en-US
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  nextPageToken:
                    type: string
                    nullable: true
              examples:
                sample:
                  value:
                    items:
                      - id: 4d6cccd2-b668-4b8e-8a4f-18ac52ffb1fd
                        email: alice@example.com
                        displayName: Alice Example
                        preferences:
                          theme: dark
                          telemetryOptIn: true
                    nextPageToken: token-2
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
    post:
      summary: Create a user
      tags: [users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewUser'
            examples:
              default:
                value:
                  email: new.user@example.com
                  password: changeme123
                  displayName: New Person
                  roles: [viewer]
          multipart/form-data:
            schema:
              type: object
              required: [payload]
              properties:
                payload:
                  $ref: '#/components/schemas/NewUser'
                avatar:
                  type: string
                  format: binary
      responses:
        '201':
          description: User created
          headers:
            Location:
              schema:
                type: string
                format: uri
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - basicAuth: []
        - oauthDemo:
            - profile.read
  /users/{userId}:
    parameters:
      - name: userId
        in: path
        required: true
        description: Identifier of the user
        schema:
          type: string
    get:
      summary: Get user by ID
      responses:
        '200':
          description: User information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: Not found
      security:
        - apiKeyAuth: []
    patch:
      summary: Update user metadata
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                preferences:
                  $ref: '#/components/schemas/UserPreferences'
                address:
                  $ref: '#/components/schemas/Address'
      responses:
        '200':
          description: Updated user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
      security:
        - oauthDemo:
            - dashboards.manage
  /metrics:
    post:
      summary: Submit metrics
      description: Accepts newline-delimited JSON payloads.
      parameters:
        - name: dataset
          in: query
          required: true
          schema:
            type: string
        - name: region
          in: query
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
            example: [us-east-1, eu-west-1]
        - name: summary
          in: query
          style: form
          explode: false
          schema:
            type: object
            properties:
              window:
                type: string
              includeOutliers:
                type: boolean
            example:
              window: 24h
              includeOutliers: false
      requestBody:
        required: true
        content:
          application/x-ndjson:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Metric'
      responses:
        '202':
          description: Accepted for processing
      security:
        - oauthDemo:
            - metrics.write
        - bearerAuth: []
      callbacks:
        ingestionStatus:
          '{$request.query.dataset}/status':
            get:
              summary: Poll ingestion status
              responses:
                '200':
                  description: Current status
                  content:
                    application/json:
                      schema:
                        type: object
                        properties:
                          dataset:
                            type: string
                          state:
                            type: string
                            enum: [pending, processing, completed, failed]
                          updatedAt:
                            type: string
                            format: date-time
  /dashboards:
    get:
      summary: List dashboards
      servers:
        - url: https://dashboards.resterm.dev/api
      responses:
        '200':
          description: A list of dashboards
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    owner:
                      type: string
      security:
        - oauthDemo:
            - dashboards.view
        - bearerAuth: []
  /reports/{year}:
    parameters:
      - name: year
        in: path
        required: true
        schema:
          type: integer
      - name: quarter
        in: query
        style: form
        explode: false
        schema:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 4
          example: [1, 2]
    get:
      summary: Retrieve quarterly reports
      responses:
        '200':
          description: Reports bundle
          content:
            application/zip:
              schema:
                type: string
                format: binary
        '400':
          description: Invalid selection
      security:
        - apiKeyAuth: []
        - oauthDemo:
            - metrics.read
  /oidc/resources:
    get:
      summary: List OIDC protected resources
      description: Endpoint intentionally protected with OpenID Connect scheme to validate warning handling.
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
      security:
        - oidcProvider: []

```

## /_examples/queries/workspace.graphql

```graphql path="/_examples/queries/workspace.graphql" 
query FetchWorkspace($id: ID!, $includeMembers: Boolean!) {
  workspace(id: $id) {
    id
    name
    region
    plan
    members @include(if: $includeMembers) {
      totalCount
      edges {
        node {
          id
          email
        }
      }
    }
  }
}

```

## /_examples/queries/workspace.variables.json

```json path="/_examples/queries/workspace.variables.json" 
{
  "id": "{{graphql.workspaceId}}",
  "includeMembers": false
}

```

## /_examples/resterm.env.json

```json path="/_examples/resterm.env.json" 
{
  "dev": {
    "base": {
      "url": "https://httpbin.org"
    },
    "services": {
      "api": {
        "base": "https://httpbin.org/anything/api"
      },
      "billing": {
        "base": "https://httpbin.org/anything/billing"
      }
    },
    "feature": {
      "name": "beta-dashboard"
    },
    "auth": {
      "token": "dev-token-123",
      "clientId": "demo-client",
      "clientSecret": "demo-secret"
    },
    "oauth": {
      "tokenUrl": "https://auth.example.com/oauth/token",
      "authUrl": "https://auth.example.com/oauth/authorize",
      "clientId": "demo-client",
      "clientSecret": "demo-secret",
      "scope": "read write analytics",
      "audience": "https://api.dev.example.com",
      "cacheKey": "dev:core"
    },
    "graphql": {
      "endpoint": "https://httpbin.org/anything/graphql",
      "workspaceId": "ws-dev-001"
    },
    "grpc": {
      "host": "localhost:50051",
      "authority": "analytics.dev.local"
    },
    "transport": {
      "proxy": "http://localhost:8080"
    },
    "tenant": {
      "id": "tenant-dev-001",
      "region": "eu-west-1"
    },
    "meta": {
      "region": "eu-west-1",
      "plan": "starter",
      "flags": "{\"betaAccess\":true,\"canaryFeatures\":[\"dashboards\",\"automations\"]}"
    },
    "features": [
      "realtime-updates",
      "script-hooks",
      "audit-export"
    ],
    "query": {
      "expand": "settings,features,limits"
    },
    "limits": {
      "users": 25,
      "projects": 8
    },
    "plans": {
      "primary": "scale",
      "addons": [
        "dashboards",
        "automation"
      ]
    },
    "billing": {
      "currency": "USD",
      "pricing": {
        "workspace": 49.0,
        "seat": 15.0
      },
      "sampleInvoiceId": "inv-0021"
    }
  },
  "stage": {
    "base": {
      "url": "https://httpbin.org"
    },
    "services": {
      "api": {
        "base": "https://httpbin.org/anything/api-stage"
      },
      "billing": {
        "base": "https://httpbin.org/anything/billing-stage"
      }
    },
    "feature": {
      "name": "gamma-switch"
    },
    "auth": {
      "token": "stage-token-456",
      "clientId": "demo-client",
      "clientSecret": "demo-secret"
    },
    "oauth": {
      "tokenUrl": "https://auth.example.com/oauth/token",
      "authUrl": "https://auth.example.com/oauth/authorize",
      "clientId": "demo-client",
      "clientSecret": "demo-secret",
      "scope": "read write analytics",
      "audience": "https://api.stage.example.com",
      "cacheKey": "stage:core"
    },
    "graphql": {
      "endpoint": "https://httpbin.org/anything/graphql-stage",
      "workspaceId": "ws-stage-002"
    },
    "grpc": {
      "host": "localhost:50052",
      "authority": "analytics.stage.local"
    },
    "transport": {
      "proxy": "http://localhost:8081"
    },
    "tenant": {
      "id": "tenant-stage-002",
      "region": "us-east-1"
    },
    "meta": {
      "region": "us-east-1",
      "plan": "growth",
      "flags": "{\"betaAccess\":false,\"canaryFeatures\":[\"dashboards\"]}"
    },
    "features": [
      "realtime-updates",
      "script-hooks"
    ],
    "query": {
      "expand": "settings,features"
    },
    "limits": {
      "users": 15,
      "projects": 5
    },
    "plans": {
      "primary": "growth",
      "addons": [
        "dashboards"
      ]
    },
    "billing": {
      "currency": "USD",
      "pricing": {
        "workspace": 79.0,
        "seat": 19.0
      },
      "sampleInvoiceId": "inv-1102"
    }
  },
  "prod": {
    "base": {
      "url": "https://httpbin.org"
    },
    "services": {
      "api": {
        "base": "https://httpbin.org/anything/api-prod"
      },
      "billing": {
        "base": "https://httpbin.org/anything/billing-prod"
      }
    },
    "feature": {
      "name": "rollout-feature"
    },
    "auth": {
      "token": "prod-token-789",
      "clientId": "demo-client",
      "clientSecret": "demo-secret"
    },
    "oauth": {
      "tokenUrl": "https://auth.example.com/oauth/token",
      "authUrl": "https://auth.example.com/oauth/authorize",
      "clientId": "demo-client",
      "clientSecret": "demo-secret",
      "scope": "read write analytics",
      "audience": "https://api.example.com",
      "cacheKey": "prod:core"
    },
    "graphql": {
      "endpoint": "https://httpbin.org/anything/graphql-prod",
      "workspaceId": "ws-prod-003"
    },
    "grpc": {
      "host": "analytics.example.com:443",
      "authority": "analytics.prod.local"
    },
    "transport": {
      "proxy": ""
    },
    "tenant": {
      "id": "tenant-prod-003",
      "region": "eu-central-1"
    },
    "meta": {
      "region": "eu-central-1",
      "plan": "enterprise",
      "flags": "{\"betaAccess\":false,\"canaryFeatures\":[]}"
    },
    "features": [
      "realtime-updates"
    ],
    "query": {
      "expand": "settings"
    },
    "limits": {
      "users": 100,
      "projects": 20
    },
    "plans": {
      "primary": "enterprise",
      "addons": [
        "dashboards",
        "automation",
        "compliance"
      ]
    },
    "billing": {
      "currency": "USD",
      "pricing": {
        "workspace": 199.0,
        "seat": 29.0
      },
      "sampleInvoiceId": "inv-2099"
    }
  }
}

```

## /_examples/rts/apply_patch.rts

```rts path="/_examples/rts/apply_patch.rts" 
export fn patch(request, vars) {
  let hdrs = headers.merge(
    headers.normalize({"X-Apply": "true", "Content-Type": "application/json", "X-Remove-Me": "gone"}),
    {"X-Remove-Me": null, "X-Multi": ["a", "b"]}
  )

  let mergedURL = query.merge(request.url, {applied: "1"})
  let body = {
    applied: true,
    note: vars.get("pre_request_mode") ?? "none",
    method: request.method
  }

  return {
    method: "PUT",
    url: mergedURL,
    headers: hdrs,
    query: {from: "apply", remove: null},
    body: body,
    vars: {apply_note: "patched"}
  }
}

```

## /_examples/rts/apply_profiles.http

```http path="/_examples/rts/apply_profiles.http" 
# @global base_url https://httpbin.org

# @patch file jsonApi {headers: {"Accept":"application/json","Content-Type":"application/json"}}
# @patch file strict {settings: {timeout:"3s", followredirects:false}}
# @patch global authProd {auth: {type:"oauth2", cache_key:"myapi"}}

### Reusable apply profiles
# @name RTS_ApplyProfiles
# @apply use=jsonApi,use=authProd,use=strict
# @assert request.header("Accept") == "application/json"
POST {{base_url}}/anything

{"hello":"world"}

```

## /_examples/rts/helpers.rts

```rts path="/_examples/rts/helpers.rts" 
export fn mode(env) {
  if env.has("name") and env.get("name") == "prod" {
    return "strict"
  }
  return "debug"
}

export fn envLabel(env) {
  return env.has("name") ? env.get("name") : "default"
}

export fn authHeader(token) {
  return token ? "Bearer " + token : ""
}

export fn coalesce(a, b) {
  return a ?? b
}

export fn traceId() {
  return "trace-" + uuid()
}

export fn safeTag(tag) {
  let cleaned = tag ?? "rts"
  return match("^[a-z0-9-]+{{contextString}}quot;, cleaned) ? cleaned : "rts"
}

```

## /_examples/rts/loops.rts

```rts path="/_examples/rts/loops.rts" 
export const sampleTag = "rts-examples"

export fn sumClassic(n) {
  let total = 0
  for let i = 0; i < n; i = i + 1 {
    if i == 2 {
      continue
    }
    if i == 5 {
      break
    }
    total = total + i
  }
  return total
}

export fn rangeList() {
  let out = ""
  for let i, v range ["a", "b", "c"] {
    out = out + str(i) + v
  }
  return out
}

export fn rangeDict() {
  let out = ""
  for let k, v range {b: 2, a: 1} {
    out = out + k
  }
  return out
}

export fn rangeString() {
  let out = ""
  for let i, ch range "go" {
    out = out + str(i) + ch
  }
  return out
}

export fn countdown(n) {
  let out = ""
  for n > 0 {
    out = out + str(n)
    n = n - 1
  }
  return out
}

export fn constLabel(id) {
  const prefix = "user-"
  return prefix + str(id)
}

export fn dictAccess() {
  let d = {a: 1, "b": 2}
  return d.a + d["b"]
}

export fn tryFail() {
  let res = try fail("boom")
  return res.ok
}

```

## /_examples/rts/pre_request.rts

```rts path="/_examples/rts/pre_request.rts" 
export fn mutate(request, vars, env) {
  let mode = (env.has("name") and env.get("name") == "prod") ? "strict" : "debug"
  let trace = "trace-" + uuid()

  request.setMethod("POST")
  request.setURL(query.merge(request.url, {mode: mode, pre: "1"}))
  request.setHeader("Content-Type", "application/json")
  request.setHeader("X-Mode", mode)
  request.addHeader("X-Trace-Id", trace)
  request.removeHeader("X-Remove-Me")
  request.setQueryParam("mutated", "true")

  let body = {
    mode: mode,
    trace: trace,
    token: vars.get("api.token") ?? "none"
  }
  request.setBody(json.stringify(body, 2))

  vars.set("pre_request_mode", mode)
  vars.global.set("pre_request_token", trace, true)
  if vars.global.has("stale.value") {
    vars.global.delete("stale.value")
  }
}

```

## /_examples/rts/rts_all_features.http

```http path="/_examples/rts/rts_all_features.http" 
# @global base_url https://httpbin.org
# @global api.token demo-token
# @global env_hint demo
# @use ./helpers.rts as helpers
# @use ./loops.rts as loops
# @use ./apply_patch.rts as apply
# @use ./pre_request.rts as pre

### RTS Core Expressions + Standard Library
# @name RTS_Core
# @description Exercises expressions, operators, rts stdlib, and module calls.
# @assert response.statusCode == 200
# @assert response.json("args.mode") == helpers.mode(env)
# @assert response.json("args.tag") == loops.sampleTag
# @assert response.json("headers.X-Encoded") == base64.encode("hello")
# @assert match("^https?://", response.json("url"))
# @assert contains(response.text(), "\"url\"")
# @assert headers.get(headers.normalize({"X-Test":"ok"}), "x-test") == "ok"
# @assert len(headers.merge({"a":"1"}, {"a": null})) == 0
# @assert query.parse(query.merge("https://example.com?p=1&q=2", {q: null})).p == "1"
# @assert json.parse("{\"a\":1}").a == 1
# @assert base64.decode(base64.encode("hi")) == "hi"
# @assert url.decode(url.encode("a b")) == "a b"
# @assert len(time.format("2006-01-02")) == 10
# @assert len(uuid()) == 36
# @assert (try missing_name).ok == false
# @assert (null ?? "fallback") == "fallback"
# @assert default(vars.get("missing"), "fallback") == "fallback"
# @assert (true and not false) == true
# @assert [1,2,3][1] == 2
# @assert ({a: 1, "b": 2}.a + {a: 1, "b": 2}["b"]) == 3
# @assert vars.get("env_hint") == "demo"
GET {{base_url}}/get?mode={{= helpers["mode"](env) }}&tag={{= loops.sampleTag }}&uuid={{= uuid() }}
User-Agent: resterm-rts-examples
X-Encoded: {{= base64.encode("hello") }}
X-Url: {{= url.encode("a b") }}
X-Time: {{= time.nowISO() }}
X-Trace-Id: {{= helpers.traceId() }}

### RTS Time Helpers
# @name RTS_TimeHelpers
# @description Shows duration parsing and timestamp math.
# @assert response.json("json.offset_seconds") == 5400
# @assert match("^\\d{4}-\\d{2}-\\d{2}T", response.json("json.expires_iso"))
POST {{base_url}}/anything/time
Content-Type: application/json

{
  "offset_seconds": {{= time.duration("1h30m") }},
  "expires_iso": "{{= time.formatUnix(time.addUnix(time.nowUnix(), '6d'), '2006-01-02T15:04:05Z07:00') }}"
}

### RTS Pre-request Mutation
# @name RTS_PreRequest
# @description Mutates request, vars, and globals in pre-request.
# @script pre-request lang=rts
> pre.mutate(request, vars, env)
# @assert response.json("method") == "POST"
# @assert request.method == "POST"
# @assert response.json("args.mode") == helpers.mode(env)
# @assert response.json("args.mutated") == "true"
# @assert response.json("headers.X-Mode") == helpers.mode(env)
# @assert vars.get("pre_request_mode") == helpers.mode(env)
POST {{base_url}}/anything
X-Remove-Me: true

### RTS Apply Patch
# @name RTS_Apply
# @description Applies a dict patch to method/url/headers/query/body/vars.
# @apply apply.patch(request, vars)
# @assert response.json("method") == "PUT"
# @assert request.method == "PUT"
# @assert request.query.from == "apply"
# @assert response.json("args.from") == "apply"
# @assert response.json("args.applied") == "1"
# @assert response.json("headers.X-Apply") == "true"
# @assert vars.get("apply_note") == "patched"
GET {{base_url}}/anything?debug=0

### RTS @when
# @name RTS_When
# @description Runs only when api.token exists.
# @when vars.has("api.token")
# @assert response.statusCode == 200
GET {{base_url}}/bearer
Authorization: {{= helpers.authHeader(vars.get("api.token")) }}

### RTS @skip-if
# @name RTS_SkipIf
# @description Skips only when env_hint == "skip".
# @skip-if vars.get("env_hint") == "skip"
# @assert response.statusCode == 204
GET {{base_url}}/status/204

### RTS Loops + Const + Try
# @name RTS_Loops
# @description Exercises for/range/break/continue/const/try in modules.
# @assert loops.sampleTag == "rts-examples"
# @assert loops.sumClassic(6) == 8
# @assert loops.rangeList() == "0a1b2c"
# @assert loops.rangeDict() == "ab"
# @assert loops.rangeString() == "0g1o"
# @assert loops.countdown(3) == "321"
# @assert loops.constLabel(7) == "user-7"
# @assert loops.dictAccess() == 3
# @assert loops.tryFail() == false
GET {{base_url}}/get?tag={{= loops.sampleTag }}

### RTS @for-each with json.file
# @name RTS_ForEach
# @description Iterates a local JSON file and posts each user.
# @use ./users.rts as users
# @for-each json.file("users.json") as user
# @assert response.json("json.id") == user.id
# @assert response.json("headers.X-User") == users.headerTag(user)
POST {{base_url}}/post
Content-Type: application/json
X-User: {{= users.headerTag(user) }}

{{= users.payload(user) }}

### RTS Trace
# @name RTS_Trace
# @description Captures trace timings and budgets.
# @trace dns<=200ms connect<=400ms ttfb<=600ms total<=1000ms tolerance=100ms
# @assert trace.enabled() == true
# @assert trace.budgets().enabled == true
# @assert trace.durationMs() >= 0
GET {{base_url}}/delay/0.1
Accept: application/json

### RTS Stream (SSE)
# @name RTS_SSE
# @description SSE request to populate stream.summary/events.
# @sse duration=5s idle=2s max-events=3
# @assert stream.enabled() == true
# @assert stream.kind() == "sse"
# @assert stream.summary().eventCount >= 0
GET https://stream.wikimedia.org/v2/stream/recentchange
Accept: text/event-stream

```

## /_examples/rts/users.json

```json path="/_examples/rts/users.json" 
[
  {"id": 1, "name": "Ada", "role": "admin"},
  {"id": 2, "name": "Ken"}
]

```

## /_examples/rts/users.rts

```rts path="/_examples/rts/users.rts" 
export fn headerTag(user) {
  return "user-" + str(user.id)
}

export fn role(user) {
  return user["role"] ?? "viewer"
}

export fn payload(user) {
  return {
    id: user.id,
    name: user.name,
    role: role(user),
    label: user.name + "-" + str(user.id)
  }
}

```

## /_examples/scopes.http

```http path="/_examples/scopes.http" 
### Seed Analytics Session
# @name AnalyticsSeedSession
# @description Demonstrates @global, @var file/request, and capture scopes working together.
# @tag analytics session
# @global analytics.apiKey demo-analytics-key
# @var file analytics.baseUrl https://httpbin.org/anything/analytics
# @var file analytics.defaultRange last-30-days
# @var request analytics.jobId job-{{$uuid}}
# @var request analytics.requestToken session-{{$uuid}}
# @capture global-secret analytics.sessionToken {{response.json.json.sessionToken}}
# @capture file analytics.lastJobId {{response.json.json.jobId}}
# @capture request analytics.trace {{response.json.headers.X-Amzn-Trace-Id}}
POST {{analytics.baseUrl}}/sessions
Content-Type: application/json
Authorization: Bearer {{analytics.requestToken}}
X-API-Key: {{analytics.apiKey}}
X-Job-ID: {{analytics.jobId}}

{
  "jobId": "{{analytics.jobId}}",
  "sessionToken": "{{analytics.requestToken}}",
  "range": "{{analytics.defaultRange}}",
  "tenant": "{{tenant.id}}"
}

### Retrieve Last Analytics Job
# @name AnalyticsFetchJob
# @tag analytics read
# @description Reuses captured globals and file variables while showing request-scope captures.
# @var request analytics.traceId {{$uuid}}
# @capture request echoed.authorization {{response.json.headers.Authorization}}
# @script test
> client.test("captures reused session token", function () {
>   var echoed = vars.get("echoed.authorization");
>   var token = vars.get("analytics.sessionToken");
>   tests.assert(typeof token === "string" && token.length > 0, "captured session token is available");
>   tests.assert(typeof echoed === "string" && echoed.includes(token), "Authorization header includes captured token");
> });
> client.test("trace id propagated", function () {
>   tests.assert(vars.get("analytics.traceId"), "request-scope value should be visible to tests");
> });
GET {{analytics.baseUrl}}/jobs/{{analytics.lastJobId}}
Authorization: Bearer {{analytics.sessionToken}}
X-API-Key: {{analytics.apiKey}}
X-Trace-ID: {{analytics.traceId}}
Accept: application/json

```

## /_examples/scripts.http

```http path="/_examples/scripts.http" 
# Top-level globals apply to every request below without needing an immediate request line.
# @global reporting.apiKey demo-reporting-api-key
# @global-secret reporting.sharedSecret shared-secret-value

### Bootstrap Reporting Session
# @name ReportsBootstrap
# @tag scripts pre-request
# @description Pre-request script seeds and reuses a global token while mutating headers and body.
# @script pre-request
> var existing = vars.global.get("reporting.token");
> var token = existing || `script-${Date.now()}`;
> vars.global.set("reporting.token", token, {secret: true});
> request.setHeader("Authorization", `Bearer ${token}`);
> request.setHeader("X-Trace-ID", `trace-${Date.now()}`);
> request.setHeader("X-API-Key", vars.get("reporting.apiKey"));
> var payload = {
>   correlationId: `corr-${Date.now()}`,
>   scope: "reports",
>   requestedAt: new Date().toISOString()
> };
> request.setBody(JSON.stringify(payload, null, 2));
POST {{services.api.base}}/reports/sessions
Content-Type: application/json
Accept: application/json
X-Shared-Secret: {{reporting.sharedSecret}}

### List Reports With Script Token
# @name ReportsList
# @tag scripts reuse
# @description Uses the global token populated by the pre-request script along with the top-level API key.
# @auth bearer {{reporting.token}}
# @capture request reporting.lastTrace {{response.json.headers.X-Trace-Id}}
# @script test
> client.test("script-created token is available", function () {
>   var token = vars.get("reporting.token");
>   tests.assert(typeof token === "string" && token.length > 0, "global token should exist");
> });
GET {{services.api.base}}/reports
Accept: application/json
X-API-Key: {{reporting.apiKey}}
X-Shared-Secret: {{reporting.sharedSecret}}

```

## /_examples/service-groups.http

```http path="/_examples/service-groups.http" 
### Health Checks
# @name API Health
# @tag smoke health
GET {{services.api.base}}/health
Accept: application/json
Authorization: Bearer {{auth.token}}

### Provisioning Flow
# @name Start Provisioning
# @tag provisioning create
@requestId = {{$uuid}}
POST {{services.api.base}}/provisioning
Content-Type: application/json
Authorization: Bearer {{auth.token}}

{
  "requestId": "{{requestId}}",
  "tenant": {
    "id": "{{tenant.id}}",
    "region": "{{tenant.region}}"
  },
  "options": {
    "plan": "{{plans.primary}}",
    "addons": ["{{plans.addons[0]}}", "{{plans.addons[1]}}"],
    "limits": {
      "projects": {{limits.projects}},
      "users": {{limits.users}}
    }
  }
}

### Billing Workflows
# @name Create Invoice
# @tag billing create
POST {{services.billing.base}}/invoices
Content-Type: application/json
Authorization: Bearer {{auth.token}}

{
  "tenantId": "{{tenant.id}}",
  "currency": "{{billing.currency}}",
  "lines": [
    {
      "sku": "workspace",
      "quantity": 1,
      "unitPrice": {{billing.pricing.workspace}}
    },
    {
      "sku": "seat",
      "quantity": {{limits.users}},
      "unitPrice": {{billing.pricing.seat}}
    }
  ]
}

### Billing Workflows
# @name Fetch Invoice
# @tag billing read
GET {{services.billing.base}}/invoices/{{billing.sampleInvoiceId}}
Accept: application/json
Authorization: Bearer {{auth.token}}

```

## /_examples/sse_manual.http

```http path="/_examples/sse_manual.http" 
### Manual SSE Feed
# @name RecentChangesSSE
# @sse idle=5s total=20s
# Tip: switch to the Stream tab to watch events arrive live.
GET https://stream.wikimedia.org/v2/stream/recentchange

```

## /_examples/sse_scripted.http

```http path="/_examples/sse_scripted.http" 
### Scripted SSE Monitor
# @name SSEScriptedMonitor
# @sse idle=5s total=20s
# @script test
> client.test("SSE stream produced events", function () {
>   tests.assert(stream.enabled(), "stream should be enabled");
>   var events = stream.events();
>   tests.assert(events.length > 0, "expected at least one SSE event");
>   var summary = stream.summary();
>   tests.assert(summary.eventCount > 0, "summary tracks event count");
> });
# @capture request firstEvent {{stream.events[0].data}}
# @capture file lastReason {{stream.summary.reason}}
GET https://sse.dev/test

```

## /_examples/ssh.http

```http path="/_examples/ssh.http" 
## SSH jump examples

### Global profile and request override
# @ssh global edge host=env:SSH_JUMP user=ops key=~/.ssh/id_ed25519 persist timeout=20s keepalive=10s
# @global api_host http://10.0.0.10

# @name list over jump
# @ssh use=edge host={{api_host}} strict_hostkey=false
GET http://{{api_host}}/v1/things

### Inline request-scoped jump
# @name create via jump
# @ssh request host=192.168.1.50 user=svc password=env:SSH_PW timeout=12s
POST http://internal.service/api
Content-Type: application/json

{
  "name": "resterm-ssh",
  "ts": "{{$timestampISO8601}}"
}

```

## /_examples/streaming.http

```http path="/_examples/streaming.http" 
### Server Sent Events Demo
# @name sseDemo
# @description Demonstrates streaming responses over SSE.
# @sse duration=30s idle=10s max-events=5
GET https://example.com/events
Accept: text/event-stream

### WebSocket Chat Demo
# @name wsChat
# @description Sends a message over WebSocket and waits for a reply.
# @websocket idle=3s subprotocols=chat.v1
# @ws send Hello from resterm!
# @ws wait 1s
# @ws close 1000 normal closure
GET wss://example.com/chat

```

## /_examples/themes/aurora.toml

```toml path="/_examples/themes/aurora.toml" 
[metadata]
name = "Aurora"
description = "High-contrast midnight palette tuned for dark terminals"
author = "resterm"
version = "1.1.0"
tags = ["dark", "contrast"]

[styles.header_title]
foreground = "#38bdf8"
bold = true

[styles.header_value]
foreground = "#e2e8f0"

[styles.header_separator]
foreground = "#2563eb"

[styles.browser_border]
border_color = "#1f3a5f"

[styles.command_bar]
foreground = "#e2e8f0"
background = "#0b1220"

[styles.command_bar_hint]
foreground = "#34d399"
bold = true

[styles.status_bar]
foreground = "#f8fafc"
background = "#111c33"

[styles.status_bar_key]
foreground = "#f97316"
bold = true

[styles.status_bar_value]
foreground = "#cbd5f5"

[styles.tab_active]
background = "#1d4ed8"
foreground = "#f8fafc"

[styles.tab_inactive]
foreground = "#64748b"

[styles.notification]
background = "#1e293b"
foreground = "#f8fafc"

[styles.error]
background = "#991b1b"
foreground = "#fef2f2"

[styles.success]
background = "#064e3b"
foreground = "#d1fae5"

[styles.list_item_title]
foreground = "#f8fafc"

[styles.list_item_description]
foreground = "#93c5fd"

[styles.list_item_selected_title]
foreground = "#121110"
background = "#38bdf8"
bold = false

[styles.list_item_selected_description]
foreground = "#0f1729"
background = "#22d3ee"

[styles.list_item_dimmed_title]
foreground = "#475569"

[styles.list_item_dimmed_description]
foreground = "#334155"

[styles.list_item_filter_match]
foreground = "#f97316"
underline = true

[styles.response_content]
foreground = "#44b864"

[styles.response_content_raw]
foreground = "#96d4a7"

[styles.response_content_headers]
foreground = "#34d399"

[styles.response_selection]
background = "#3a2b52"

[styles.response_cursor]
foreground = "#94a3b8"
bold = true

[colors]
pane_border_focus_file = "#38bdf8"
pane_border_focus_requests = "#34d399"
pane_active_foreground = "#f8fafc"
modal_backdrop = "#1A1823"
modal_input_background = "#1c1a23"
modal_option = "#4D4663"

[editor_metadata]
comment_marker = "#38bdf8"
value = "#22d3ee"
directive_default = "#c084fc"
request_line = "#fcd34d"
request_separator = "#2563eb"

[editor_metadata.directive_colors]
graphql = "#c084fc"
workflow = "#34d399"
custom = "#94a3b8"

[[header_segments]]
background = "#280CCC"
foreground = "#f8fafc"
border = "#2563eb"
accent = "#E9E9F2"

[[header_segments]]
background = "#E0A643"
foreground = "#302C27"
border = "#2563eb"

[[command_segments]]
background = "#0b1220"
key = "#34d399"
text = "#e2e8f0"

[[command_segments]]
background = "#111c33"
key = "#f97316"
text = "#f8fafc"

```

## /_examples/themes/daybreak.toml

```toml path="/_examples/themes/daybreak.toml" 
[metadata]
name = "Daybreak"
description = "Crisp, low-glare palette tuned for light terminals"
author = "resterm"
version = "1.0.0"
tags = ["light", "contrast"]

[styles.header_title]
foreground = "#1e40af"
bold = true

[styles.header]
foreground = "#0f172a"

[styles.header_value]
foreground = "#0f172a"

[styles.header_separator]
foreground = "#2563eb"

[styles.header_brand]
foreground = "#f8fafc"
background = "#1d4ed8"
border_color = "#93c5fd"
bold = true

[styles.browser_border]
border_color = "#cbd5f5"

[styles.editor_border]
border_color = "#93c5fd"

[styles.response_border]
border_color = "#67e8f9"

[styles.app_frame]
border_color = "#cbd5e1"

[styles.navigator_title]
foreground = "#0f172a"

[styles.navigator_title_selected]
foreground = "#0f172a"
background = "#fde68a"
bold = true

[styles.navigator_subtitle]
foreground = "#475569"

[styles.navigator_subtitle_selected]
foreground = "#0f172a"
background = "#fde68a"

[styles.navigator_badge]
foreground = "#2563eb"
bold = true

[styles.navigator_tag]
foreground = "#64748b"

[styles.command_bar]
foreground = "#1f2933"
background = "#e2e8f0"

[styles.command_bar_hint]
foreground = "#0f172a"
bold = true

[styles.status_bar]
foreground = "#f8fafc"
background = "#1f2937"

[styles.status_bar_key]
foreground = "#facc15"
bold = true

[styles.status_bar_value]
foreground = "#e2e8f0"

[styles.tab_active]
background = "#2563eb"
foreground = "#f8fafc"

[styles.tab_inactive]
foreground = "#475569"

[styles.command_divider]
foreground = "#cbd5e1"
bold = true

[styles.notification]
background = "#c7d2fe"
foreground = "#1f2933"

[styles.error]
background = "#fee2e2"
foreground = "#b91c1c"

[styles.success]
background = "#dcfce7"
foreground = "#166534"

[styles.list_item_title]
foreground = "#0f172a"

[styles.list_item_description]
foreground = "#334155"

[styles.list_item_selected_title]
foreground = "#0f172a"
background = "#bfdbfe"
bold = true

[styles.list_item_selected_description]
foreground = "#0f172a"
background = "#bae6fd"

[styles.list_item_dimmed_title]
foreground = "#94a3b8"

[styles.list_item_dimmed_description]
foreground = "#a1acc2"

[styles.list_item_filter_match]
foreground = "#c026d3"
underline = true

[styles.pane_title]
foreground = "#475569"
bold = true

[styles.pane_title_file]
foreground = "#1d4ed8"
bold = true

[styles.pane_title_requests]
foreground = "#0f766e"
bold = true

[styles.pane_divider]
foreground = "#cbd5e1"

[styles.editor_hint_box]
foreground = "#0f172a"
border_color = "#93c5fd"
background = "#eff6ff"

[styles.editor_hint_item]
foreground = "#0f172a"

[styles.editor_hint_selected]
foreground = "#0f172a"
background = "#fde68a"
bold = true

[styles.editor_hint_annotation]
foreground = "#64748b"

[styles.response_content]
foreground = "#0f172a"

[styles.response_content_raw]
foreground = "#1e3a8a"

[styles.response_content_headers]
foreground = "#047857"

[styles.response_search_highlight]
foreground = "#0f172a"
background = "#bfdbfe"

[styles.response_search_highlight_active]
foreground = "#0f172a"
background = "#fde68a"
bold = true

[styles.response_selection]
background = "#e2e8f0"

[styles.response_cursor]
foreground = "#6b7280"
bold = true

[styles.explain_label]
foreground = "#0369a1"
bold = true

[styles.explain_value]
foreground = "#0f172a"

[styles.explain_muted]
foreground = "#64748b"

[styles.explain_section_title]
foreground = "#1d4ed8"
bold = true

[styles.explain_section_border]
foreground = "#93c5fd"

[styles.explain_badge_ready]
foreground = "#14532d"
background = "#bbf7d0"
bold = true

[styles.explain_badge_skipped]
foreground = "#854d0e"
background = "#fde68a"
bold = true

[styles.explain_badge_error]
foreground = "#991b1b"
background = "#fecaca"
bold = true

[styles.explain_stage_ok]
foreground = "#15803d"
bold = true

[styles.explain_stage_skipped]
foreground = "#b45309"
bold = true

[styles.explain_stage_error]
foreground = "#b91c1c"
bold = true

[styles.explain_change_add]
foreground = "#15803d"
bold = true

[styles.explain_change_remove]
foreground = "#b91c1c"
bold = true

[styles.explain_change_update]
foreground = "#1d4ed8"
bold = true

[styles.explain_warning]
foreground = "#b45309"
bold = true

[styles.stream_content]
foreground = "#0f172a"

[styles.stream_timestamp]
foreground = "#64748b"

[styles.stream_direction_send]
foreground = "#b91c1c"
bold = true

[styles.stream_direction_receive]
foreground = "#15803d"
bold = true

[styles.stream_direction_info]
foreground = "#b45309"
bold = true

[styles.stream_event_name]
foreground = "#1d4ed8"
bold = true

[styles.stream_data]
foreground = "#0f172a"

[styles.stream_binary]
foreground = "#c2410c"

[styles.stream_summary]
foreground = "#64748b"
italic = true

[styles.stream_error]
foreground = "#b91c1c"
bold = true

[styles.stream_console_title]
foreground = "#1d4ed8"
bold = true

[styles.stream_console_mode]
foreground = "#0f766e"
bold = true

[styles.stream_console_status]
foreground = "#b45309"

[styles.stream_console_prompt]
foreground = "#1d4ed8"
bold = true

[styles.stream_console_input]
foreground = "#0f172a"
background = "#f8fafc"
border_color = "#cbd5e1"

[styles.stream_console_input_focused]
foreground = "#0f172a"
background = "#f8fafc"
border_color = "#60a5fa"

[colors]
pane_border_focus_file = "#2563eb"
pane_border_focus_requests = "#0d9488"
pane_active_foreground = "#0f172a"
modal_backdrop = "#e2e8f0"
modal_input_background = "#e2e8f0"
modal_option = "#64748b"

[editor_metadata]
comment_marker = "#94a3b8"
directive_default = "#1e3a8a"
value = "#0f766e"
setting_key = "#9a3412"
setting_value = "#7c2d12"
request_line = "#991b1b"
request_separator = "#1d4ed8"
rts_keyword_default = "#1e3a8a"
rts_keyword_decl = "#7c3aed"
rts_keyword_control = "#9a3412"
rts_keyword_literal = "#15803d"
rts_keyword_logical = "#b91c1c"

[editor_metadata.directive_colors]
graphql = "#7c3aed"
script = "#6d28d9"
setting = "#9a3412"
timeout = "#9a3412"
workflow = "#0f766e"
custom = "#f97316"

[[header_segments]]
background = "#2563eb"
foreground = "#f8fafc"
border = "#1d4ed8"

[[header_segments]]
background = "#0d9488"
foreground = "#f8fafc"
border = "#0f766e"

[[command_segments]]
background = "#e2e8f0"
key = "#1e40af"
text = "#1f2933"

[[command_segments]]
background = "#dbeafe"
key = "#0f172a"
text = "#1f2933"

```

## /_examples/trace.http

```http path="/_examples/trace.http" 
### Happy path
# @name trace-ok
# @description Demonstrates trace budgets that pass comfortably.
# Timeline tip: open the Timeline tab to see Connection/TLS panels (reuse, protocol, certs).
# @trace dns<=40ms connect<=120ms ttfb<=300ms total<=600ms tolerance=25ms
GET https://httpbin.org/delay/0.05
Accept: application/json
User-Agent: resterm-trace-demo

### Intentional breach
# @name trace-breach
# @description Increase the server delay so the `total` budget triggers.
# @trace total<=150ms tolerance=25ms
GET https://httpbin.org/delay/0.25
Accept: application/json
User-Agent: resterm-trace-demo

```

## /_examples/transport.http

```http path="/_examples/transport.http" 
### Fast Timeout
# @name TimeoutDemo
# @tag transport timeout
# @description Demonstrates @timeout to fail fast on slow responses.
# @timeout 2s
GET {{base.url}}/delay/5
Accept: application/json

### Custom Transport Options
# @name CustomTransport
# @tag transport settings
# @description Shows insecure, proxy, and redirect overrides.
# @setting insecure true
# @setting followredirects false
# @setting proxy {{transport.proxy}}
GET https://example.invalid/ping
Accept: */*

### Sensitive Payload With No Logging
# @name NoLogExample
# @tag transport privacy
# @description Prevents body snippets from landing in history.
# @no-log
# @setting timeout 4s
POST {{services.billing.base}}/secrets
Content-Type: application/json
Accept: application/json

{
  "secret": "{{$uuid}}",
  "source": "transport demo"
}

```

## /_examples/websocket_manual.http

```http path="/_examples/websocket_manual.http" 
### Manual WebSocket Echo
# @name WebSocketManualEcho
# @websocket timeout=10s idle=2s
# Tip: Open the Stream tab and use Ctrl+I to toggle the live console.
wss://echo.websocket.events

```

## /_examples/websocket_scripted.http

```http path="/_examples/websocket_scripted.http" 
### Scripted WebSocket Echo
# @name WebSocketScriptedEcho
# @websocket timeout=10s idle=1s
# @script test
> client.test("echo reply received", function () {
>   var events = stream.events();
>   tests.assert(stream.enabled() === true, "stream should be enabled");
>   tests.assert(events.length >= 2, "should see both send and receive events");
>   var send = events[0];
>   var recv = null;
>   for (var i = 0; i < events.length; i++) {
>     if (events[i].direction === "receive") {
>       recv = events[i];
>       break;
>     }
>   }
>   tests.assert(send.text === "Hello from Resterm", "sent payload matches");
>   tests.assert(recv && recv.text === "Hello from Resterm", "echo payload matches");
> });
# @capture request lastEcho {{stream.events[1].text}}
# @ws send Hello from Resterm
# @ws wait 1s
# @ws close 1000 scripted close
wss://echo.websocket.events

```

## /_examples/workflows.http

```http path="/_examples/workflows.http" 
# @global base_url https://httpbin.org
# @global workflow.region demo
# @global-secret auth.token seed-token

# @workflow sample-order on-failure=continue environment=demo vars.workflow.name="Sample Order" vars.workflow.item_id="workflow-42"
# @description Demonstrates multi-step workflows with expectations and variable overrides.
# @description Run inside resterm to see per-step status updates and history aggregation.
# @tag demo workflow
# @step Authenticate using=AcquireToken expect.statuscode=200
# @step FetchHeaders using=FetchHeaders expect.status="200 OK" expect.statuscode=200 vars.request.trace_header="workflow-trace"
# @step CreateResource using=CreateResource vars.request.item_value="workflow created" expect.statuscode=200
# @step Cleanup using=DeleteResource on-failure=continue expect.status="200 OK" expect.statuscode=200

### Acquire OAuth-style token
# @name AcquireToken
# @description Simulated token mint using httpbin echo response.
# @tag workflow auth
# @capture global auth.token {{response.json.json.token}}
POST {{base_url}}/post
Content-Type: application/json

{
  "token": "workflow-token",
  "workflow": "{{vars.workflow.name}}",
  "region": "{{workflow.region}}"
}

### Fetch headers with workflow override
# @name FetchHeaders
# @description Demonstrates vars.request.* overrides and captured globals.
# @tag workflow http
# @auth bearer {{auth.token}}
GET {{base_url}}/headers
X-Trace-ID: {{vars.request.trace_header}}

### Create resource using workflow variables
# @name CreateResource
# @description Uses vars.workflow.* and vars.request.* to populate request body.
# @tag workflow http create
# @auth bearer {{auth.token}}
POST {{base_url}}/anything/resource
Content-Type: application/json

{
  "id": "{{vars.workflow.item_id}}",
  "value": "{{vars.request.item_value}}",
  "workflow": "{{vars.workflow.name}}"
}

### Cleanup resource (simulated)
# @name DeleteResource
# @description Final step shows on-failure handling; httpbin returns 200.
# @tag workflow cleanup
# @auth bearer {{auth.token}}
DELETE {{base_url}}/status/200

```

## /_media/oauth.gif

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/oauth.gif

## /_media/resterm-lighttheme.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm-lighttheme.png

## /_media/resterm_base.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_base.png

## /_media/resterm_compare.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_compare.png

## /_media/resterm_explain.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_explain.png

## /_media/resterm_full.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_full.png

## /_media/resterm_hsplit.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_hsplit.png

## /_media/resterm_logo.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_logo.png

## /_media/resterm_profiler.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_profiler.png

## /_media/resterm_script.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_script.png

## /_media/resterm_trace_timeline.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_trace_timeline.png

## /_media/resterm_websocket.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_websocket.png

## /_media/resterm_workflow.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_workflow.png

## /_media/resterm_zommed.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/resterm_zommed.png

## /_media/restermscript.png

Binary file available at https://raw.githubusercontent.com/unkn0wn-root/resterm/refs/heads/main/_media/restermscript.png

## /build.sh

```sh path="/build.sh" 
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$ROOT_DIR/bin"

mkdir -p "$OUT_DIR"

VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')

LDFLAGS="-s -w -X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}'"

platforms=(
  "darwin amd64"
  "darwin arm64"
  "linux amd64"
  "linux arm64"
  "windows amd64"
  "windows arm64"
)

for platform in "${platforms[@]}"; do
  read -r goos goarch <<<"$platform"

  case "$goos" in
    darwin) goos_label="Darwin" ;;
    linux) goos_label="Linux" ;;
    windows) goos_label="Windows" ;;
    *) goos_label="$goos" ;;
  esac

  case "$goarch" in
    amd64) goarch_label="x86_64" ;;
    arm64) goarch_label="arm64" ;;
    *) goarch_label="$goarch" ;;
  esac

  output="$OUT_DIR/resterm_${goos_label}_${goarch_label}"
  if [[ "$goos" == "windows" ]]; then
    output+=".exe"
  fi

  echo "Building $output (version: $VERSION)"
  GOOS="$goos" GOARCH="$goarch" go build -trimpath -ldflags "$LDFLAGS" -o "$output" ./cmd/resterm
done

echo ""
echo "Build complete! Version: $VERSION"

```

## /cmd/resterm/collection.go

```go path="/cmd/resterm/collection.go" 
package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/collection"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func handleCollectionSubcommand(args []string) (bool, error) {
	if len(args) == 0 || args[0] != "collection" {
		return false, nil
	}
	if len(args) == 1 && cli.HasFileConflict("collection") {
		return true, cli.CommandFileConflict(
			"resterm",
			"collection",
			"pass a subcommand like `resterm collection export --workspace . --out ./bundle`",
		)
	}
	return true, runCollection(args[1:])
}

func runCollection(args []string) error {
	if len(args) == 0 {
		return errors.New(collectionUsageText())
	}
	op := str.Trim(strings.ToLower(args[0]))
	switch op {
	case "-h", "--help", "help":
		if err := writeln(os.Stdout, collectionUsageText()); err != nil {
			return fmt.Errorf("collection: write output: %w", err)
		}
		return nil
	case "export":
		return runCollectionExport(args[1:])
	case "import":
		return runCollectionImport(args[1:])
	case "pack":
		return runCollectionPack(args[1:])
	case "unpack":
		return runCollectionUnpack(args[1:])
	default:
		return fmt.Errorf("collection: unknown subcommand %q\n\n%s", op, collectionUsageText())
	}
}

func runCollectionExport(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "collection export", os.Stderr)
	var workspace string
	var out string
	var name string
	var recursive bool
	var force bool

	fs.StringVar(&workspace, "workspace", "", "Workspace directory to export")
	fs.StringVar(&out, "out", "", "Output bundle directory path")
	fs.StringVar(&name, "name", "", "Optional bundle name")
	fs.BoolVar(&recursive, "recursive", false, "Recursively scan workspace for request files")
	fs.BoolVar(&force, "force", false, "Overwrite existing output directory")

	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("collection export: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf(
			"collection export: unexpected args: %s",
			strings.Join(fs.Args(), " "),
		)
	}

	workspace = str.Trim(workspace)
	if workspace == "" {
		return errors.New("collection export: --workspace is required")
	}
	out = str.Trim(out)
	if out == "" {
		return errors.New("collection export: --out is required")
	}
	name = str.Trim(name)

	res, err := collection.ExportBundle(collection.ExportOptions{
		Workspace: workspace,
		OutDir:    out,
		Name:      name,
		Recursive: recursive,
		Force:     force,
	})
	if err != nil {
		return fmt.Errorf("collection export: %w", err)
	}

	if err := writeCollectionOutput(
		"collection export",
		"Exported %d files to %s\n",
		res.FileCount,
		res.OutDir,
	); err != nil {
		return err
	}
	if err := writeCollectionOutput(
		"collection export",
		"Manifest: %s\n",
		res.ManifestPath,
	); err != nil {
		return err
	}
	return nil
}

func runCollectionImport(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "collection import", os.Stderr)
	var in string
	var workspace string
	var force bool
	var dry bool

	fs.StringVar(&in, "in", "", "Input bundle directory path")
	fs.StringVar(&workspace, "workspace", "", "Destination workspace directory")
	fs.BoolVar(&force, "force", false, "Overwrite existing destination files")
	fs.BoolVar(&dry, "dry-run", false, "Plan import without writing files")

	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("collection import: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf(
			"collection import: unexpected args: %s",
			strings.Join(fs.Args(), " "),
		)
	}

	in = str.Trim(in)
	if in == "" {
		return errors.New("collection import: --in is required")
	}
	workspace = str.Trim(workspace)
	if workspace == "" {
		return errors.New("collection import: --workspace is required")
	}

	res, err := collection.ImportBundle(collection.ImportOptions{
		BundleDir: in,
		Workspace: workspace,
		Force:     force,
		DryRun:    dry,
	})
	if err != nil {
		return fmt.Errorf("collection import: %w", err)
	}

	if dry {
		if err := writeCollectionOutput(
			"collection import",
			"Dry-run: planned %d file operations (%d create, %d overwrite)\n",
			res.FileCount,
			res.Created,
			res.Overwritten,
		); err != nil {
			return err
		}
		return nil
	}
	if err := writeCollectionOutput(
		"collection import",
		"Imported %d files into %s (%d create, %d overwrite)\n",
		res.FileCount,
		res.Workspace,
		res.Created,
		res.Overwritten,
	); err != nil {
		return err
	}
	return nil
}

func runCollectionPack(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "collection pack", os.Stderr)
	var in string
	var out string
	var force bool

	fs.StringVar(&in, "in", "", "Input bundle directory path")
	fs.StringVar(&out, "out", "", "Output archive (.zip) path")
	fs.BoolVar(&force, "force", false, "Overwrite existing archive file")

	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("collection pack: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf(
			"collection pack: unexpected args: %s",
			strings.Join(fs.Args(), " "),
		)
	}

	in = str.Trim(in)
	if in == "" {
		return errors.New("collection pack: --in is required")
	}
	out = str.Trim(out)
	if out == "" {
		return errors.New("collection pack: --out is required")
	}

	res, err := collection.PackBundle(collection.PackOptions{
		BundleDir: in,
		OutFile:   out,
		Force:     force,
	})
	if err != nil {
		return fmt.Errorf("collection pack: %w", err)
	}

	if err := writeCollectionOutput(
		"collection pack",
		"Packed %d files into %s\n",
		res.FileCount,
		res.OutFile,
	); err != nil {
		return err
	}
	return nil
}

func runCollectionUnpack(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "collection unpack", os.Stderr)
	var in string
	var out string
	var force bool

	fs.StringVar(&in, "in", "", "Input archive (.zip) path")
	fs.StringVar(&out, "out", "", "Output bundle directory path")
	fs.BoolVar(&force, "force", false, "Overwrite existing output directory")

	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("collection unpack: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf(
			"collection unpack: unexpected args: %s",
			strings.Join(fs.Args(), " "),
		)
	}

	in = str.Trim(in)
	if in == "" {
		return errors.New("collection unpack: --in is required")
	}
	out = str.Trim(out)
	if out == "" {
		return errors.New("collection unpack: --out is required")
	}

	res, err := collection.UnpackBundle(collection.UnpackOptions{
		InFile: in,
		OutDir: out,
		Force:  force,
	})
	if err != nil {
		return fmt.Errorf("collection unpack: %w", err)
	}

	if err := writeCollectionOutput(
		"collection unpack",
		"Unpacked %d files to %s\n",
		res.FileCount,
		res.OutDir,
	); err != nil {
		return err
	}
	return nil
}

func writeCollectionOutput(op, format string, args ...any) error {
	if err := writef(os.Stdout, format, args...); err != nil {
		return fmt.Errorf("%s: write output: %w", op, err)
	}
	return nil
}

func collectionUsageText() string {
	return str.Trim(`
Usage: resterm collection <export|import|pack|unpack> [flags]

Subcommands:
  export --workspace <dir> --out <dir> [--name <name>] [--recursive] [--force]
      Export a Git-friendly collection bundle directory.
  import --in <dir> --workspace <dir> [--force] [--dry-run]
      Import a collection bundle into a workspace.
  pack --in <dir> --out <file.zip> [--force]
      Pack a bundle directory into a zip archive.
  unpack --in <file.zip> --out <dir> [--force]
      Unpack and validate a bundle archive into a directory.
`)
}

```

## /cmd/resterm/collection_test.go

```go path="/cmd/resterm/collection_test.go" 
package main

import (
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	str "github.com/unkn0wn-root/resterm/internal/util"
)

func TestHandleCollectionSubcommandNotMatched(t *testing.T) {
	handled, err := handleCollectionSubcommand([]string{"history"})
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	if handled {
		t.Fatalf("expected not handled")
	}
}

func TestHandleCollectionSubcommandAmbiguousFile(t *testing.T) {
	dir := t.TempDir()
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(cwd)
	})
	if err := os.Chdir(dir); err != nil {
		t.Fatalf("chdir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(dir, "collection"), []byte("data"), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	handled, err := handleCollectionSubcommand([]string{"collection"})
	if !handled {
		t.Fatalf("expected collection to be handled")
	}
	if err == nil {
		t.Fatalf("expected ambiguity error")
	}
}

func TestRunCollectionRequiresSubcommand(t *testing.T) {
	if err := runCollection(nil); err == nil {
		t.Fatalf("expected error for missing subcommand")
	}
}

func TestRunCollectionUnknownSubcommand(t *testing.T) {
	err := runCollection([]string{"unknown"})
	if err == nil {
		t.Fatalf("expected error for unknown subcommand")
	}
}

func TestRunCollectionHelpFlagShowsUsage(t *testing.T) {
	stdout, stderr, err := captureCollectionIO(t, func() error {
		return runCollection([]string{"-h"})
	})
	if err != nil {
		t.Fatalf("help flag: %v", err)
	}
	if str.Trim(stderr) != "" {
		t.Fatalf("expected empty stderr on help flag, got %q", stderr)
	}
	if !strings.Contains(stdout, "Usage: resterm collection <export|import|pack|unpack> [flags]") {
		t.Fatalf("expected collection usage in stdout, got %q", stdout)
	}
}

func TestRunCollectionFlagErrorsHaveCommandPrefix(t *testing.T) {
	cases := []struct {
		args []string
		want string
	}{
		{args: []string{"export", "--bad"}, want: "collection export:"},
		{args: []string{"import", "--bad"}, want: "collection import:"},
		{args: []string{"pack", "--bad"}, want: "collection pack:"},
		{args: []string{"unpack", "--bad"}, want: "collection unpack:"},
	}
	for _, tc := range cases {
		err := runCollection(tc.args)
		if err == nil {
			t.Fatalf("expected error for %v", tc.args)
		}
		if !strings.Contains(err.Error(), tc.want) {
			t.Fatalf("expected error %q to contain %q", err.Error(), tc.want)
		}
	}
}

func TestRunCollectionLibraryErrorsHaveCommandPrefix(t *testing.T) {
	cases := []struct {
		args []string
		want string
	}{
		{
			args: []string{
				"export",
				"--workspace", filepath.Join(t.TempDir(), "missing-workspace"),
				"--out", filepath.Join(t.TempDir(), "bundle"),
			},
			want: "collection export:",
		},
		{
			args: []string{
				"import",
				"--in", filepath.Join(t.TempDir(), "missing-bundle"),
				"--workspace", filepath.Join(t.TempDir(), "workspace"),
			},
			want: "collection import:",
		},
		{
			args: []string{
				"pack",
				"--in", filepath.Join(t.TempDir(), "missing-bundle"),
				"--out", filepath.Join(t.TempDir(), "bundle.zip"),
			},
			want: "collection pack:",
		},
		{
			args: []string{
				"unpack",
				"--in", filepath.Join(t.TempDir(), "missing.zip"),
				"--out", filepath.Join(t.TempDir(), "bundle"),
			},
			want: "collection unpack:",
		},
	}
	for _, tc := range cases {
		err := runCollection(tc.args)
		if err == nil {
			t.Fatalf("expected error for %v", tc.args)
		}
		if !strings.Contains(err.Error(), tc.want) {
			t.Fatalf("expected error %q to contain %q", err.Error(), tc.want)
		}
	}
}

func TestRunCollectionRequiresFlags(t *testing.T) {
	cases := []struct {
		args []string
		want string
	}{
		{args: []string{"export"}, want: "collection export: --workspace is required"},
		{
			args: []string{"export", "--workspace", "."},
			want: "collection export: --out is required",
		},
		{args: []string{"import"}, want: "collection import: --in is required"},
		{
			args: []string{"import", "--in", "./bundle"},
			want: "collection import: --workspace is required",
		},
		{args: []string{"pack"}, want: "collection pack: --in is required"},
		{
			args: []string{"pack", "--in", "./bundle"},
			want: "collection pack: --out is required",
		},
		{args: []string{"unpack"}, want: "collection unpack: --in is required"},
		{
			args: []string{"unpack", "--in", "./bundle.zip"},
			want: "collection unpack: --out is required",
		},
	}
	for _, tc := range cases {
		err := runCollection(tc.args)
		if err == nil {
			t.Fatalf("expected error for %v", tc.args)
		}
		if !strings.Contains(err.Error(), tc.want) {
			t.Fatalf("expected error %q to contain %q", err.Error(), tc.want)
		}
	}
}

func TestRunCollectionSubcommandHelpShowsUsage(t *testing.T) {
	stdout, stderr, err := captureCollectionIO(t, func() error {
		return runCollection([]string{"export", "-h"})
	})
	if err != nil {
		t.Fatalf("export -h: %v", err)
	}
	if str.Trim(stdout) != "" {
		t.Fatalf("expected empty stdout on help, got %q", stdout)
	}
	if !strings.Contains(stderr, "Usage: resterm collection export [flags]") {
		t.Fatalf("expected usage in stderr, got %q", stderr)
	}
	if !strings.Contains(stderr, "-workspace") {
		t.Fatalf("expected --workspace flag in help output, got %q", stderr)
	}
}

func TestRunCollectionExportImportE2E(t *testing.T) {
	src := t.TempDir()
	writeCollectionFile(t, src, "requests.http", `
# @use ./rts/helpers.rts
GET https://example.com
`)
	writeCollectionFile(t, src, "rts/helpers.rts", "module helpers\n")
	writeCollectionFile(t, src, "resterm.env.example.json", `{"dev":{"token":"SAFE"}}`+"\n")

	bundle := filepath.Join(t.TempDir(), "bundle")
	stdout, stderr, err := captureCollectionIO(t, func() error {
		return runCollection([]string{
			"export",
			"--workspace", src,
			"--out", bundle,
		})
	})
	if err != nil {
		t.Fatalf("collection export: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on export, got %q", stderr)
	}
	if !strings.Contains(stdout, "Exported") {
		t.Fatalf("unexpected export output: %q", stdout)
	}

	dst := t.TempDir()
	stdout, stderr, err = captureCollectionIO(t, func() error {
		return runCollection([]string{
			"import",
			"--in", bundle,
			"--workspace", dst,
		})
	})
	if err != nil {
		t.Fatalf("collection import: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on import, got %q", stderr)
	}
	if !strings.Contains(stdout, "Imported") {
		t.Fatalf("unexpected import output: %q", stdout)
	}

	if _, err := os.Stat(filepath.Join(dst, "requests.http")); err != nil {
		t.Fatalf("expected requests.http in destination: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dst, "rts", "helpers.rts")); err != nil {
		t.Fatalf("expected helpers.rts in destination: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dst, "resterm.env.example.json")); err != nil {
		t.Fatalf("expected env template in destination: %v", err)
	}
}

func TestRunCollectionImportDryRun(t *testing.T) {
	src := t.TempDir()
	writeCollectionFile(t, src, "requests.http", "GET https://example.com\n")

	bundle := filepath.Join(t.TempDir(), "bundle")
	if err := runCollection([]string{
		"export",
		"--workspace", src,
		"--out", bundle,
	}); err != nil {
		t.Fatalf("collection export: %v", err)
	}

	dst := filepath.Join(t.TempDir(), "workspace")
	stdout, stderr, err := captureCollectionIO(t, func() error {
		return runCollection([]string{
			"import",
			"--in", bundle,
			"--workspace", dst,
			"--dry-run",
		})
	})
	if err != nil {
		t.Fatalf("collection import dry-run: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on dry-run import, got %q", stderr)
	}
	if !strings.Contains(stdout, "Dry-run: planned") {
		t.Fatalf("unexpected dry-run output: %q", stdout)
	}
	if _, err := os.Stat(dst); !os.IsNotExist(err) {
		t.Fatalf("workspace should not exist after dry-run")
	}
}

func TestRunCollectionPackUnpackE2E(t *testing.T) {
	src := t.TempDir()
	writeCollectionFile(t, src, "requests.http", "GET https://example.com\n")

	bund := filepath.Join(t.TempDir(), "bundle")
	if err := runCollection([]string{
		"export",
		"--workspace", src,
		"--out", bund,
	}); err != nil {
		t.Fatalf("collection export: %v", err)
	}

	arc := filepath.Join(t.TempDir(), "bundle.zip")
	stdout, stderr, err := captureCollectionIO(t, func() error {
		return runCollection([]string{
			"pack",
			"--in", bund,
			"--out", arc,
		})
	})
	if err != nil {
		t.Fatalf("collection pack: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on pack, got %q", stderr)
	}
	if !strings.Contains(stdout, "Packed") {
		t.Fatalf("unexpected pack output: %q", stdout)
	}

	out := filepath.Join(t.TempDir(), "bundle-unpacked")
	stdout, stderr, err = captureCollectionIO(t, func() error {
		return runCollection([]string{
			"unpack",
			"--in", arc,
			"--out", out,
		})
	})
	if err != nil {
		t.Fatalf("collection unpack: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on unpack, got %q", stderr)
	}
	if !strings.Contains(stdout, "Unpacked") {
		t.Fatalf("unexpected unpack output: %q", stdout)
	}

	if _, err := os.Stat(filepath.Join(out, "manifest.json")); err != nil {
		t.Fatalf("expected manifest in unpacked bundle: %v", err)
	}
	if _, err := os.Stat(filepath.Join(out, "requests.http")); err != nil {
		t.Fatalf("expected requests.http in unpacked bundle: %v", err)
	}
}

func writeCollectionFile(t *testing.T, root, rel, data string) {
	t.Helper()
	path := filepath.Join(root, rel)
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("mkdir for %s: %v", rel, err)
	}
	if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
		t.Fatalf("write %s: %v", rel, err)
	}
}

func captureCollectionIO(t *testing.T, fn func() error) (string, string, error) {
	t.Helper()

	oldOut := os.Stdout
	oldErr := os.Stderr
	outR, outW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stdout: %v", err)
	}
	errR, errW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stderr: %v", err)
	}

	os.Stdout = outW
	os.Stderr = errW
	defer func() {
		os.Stdout = oldOut
		os.Stderr = oldErr
	}()

	runErr := fn()

	_ = outW.Close()
	_ = errW.Close()

	outData, outErr := io.ReadAll(outR)
	if outErr != nil {
		t.Fatalf("read stdout: %v", outErr)
	}
	errData, errErr := io.ReadAll(errR)
	if errErr != nil {
		t.Fatalf("read stderr: %v", errErr)
	}

	_ = outR.Close()
	_ = errR.Close()
	return string(outData), string(errData), runErr
}

```

## /cmd/resterm/history.go

```go path="/cmd/resterm/history.go" 
package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/config"
	"github.com/unkn0wn-root/resterm/internal/history"
	histdb "github.com/unkn0wn-root/resterm/internal/history/sqlite"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func handleHistorySubcommand(args []string) (bool, error) {
	if len(args) == 0 || args[0] != "history" {
		return false, nil
	}
	if len(args) == 1 && cli.HasFileConflict("history") {
		return true, cli.CommandFileConflict(
			"resterm",
			"history",
			"pass a subcommand like `resterm history export --out ./history.json`",
		)
	}
	return true, runHistory(args[1:])
}

func runHistory(args []string) error {
	if len(args) == 0 {
		return errors.New(historyUsageText())
	}
	op := str.Trim(strings.ToLower(args[0]))
	switch op {
	case "-h", "--help", "help":
		if err := writeln(os.Stdout, historyUsageText()); err != nil {
			return fmt.Errorf("history: write output: %w", err)
		}
		return nil
	case "export":
		return runHistoryExport(args[1:])
	case "import":
		return runHistoryImport(args[1:])
	case "backup":
		return runHistoryBackup(args[1:])
	case "stats":
		return runHistoryStats(args[1:])
	case "compact", "vacuum":
		return runHistoryCompact(args[1:])
	case "check":
		return runHistoryCheck(args[1:])
	default:
		return fmt.Errorf("history: unknown subcommand %q\n\n%s", op, historyUsageText())
	}
}

func runHistoryExport(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history export", os.Stderr)
	var out string
	fs.StringVar(&out, "out", "", "Output JSON file path")
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history export: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history export: unexpected args: %s", strings.Join(fs.Args(), " "))
	}
	out = str.Trim(out)
	if out == "" {
		return errors.New("history export: --out is required")
	}

	s, err := openHistoryStore(true)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	n, err := s.ExportJSON(out)
	if err != nil {
		return err
	}
	if err := writef(os.Stdout, "Exported %d history entries to %s\n", n, out); err != nil {
		return fmt.Errorf("history export: write output: %w", err)
	}
	return nil
}

func runHistoryImport(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history import", os.Stderr)
	var in string
	fs.StringVar(&in, "in", "", "Input JSON file path")
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history import: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history import: unexpected args: %s", strings.Join(fs.Args(), " "))
	}
	in = str.Trim(in)
	if in == "" {
		return errors.New("history import: --in is required")
	}

	s, err := openHistoryStore(false)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	n, err := s.ImportJSON(in)
	if err != nil {
		return err
	}
	if err := writef(os.Stdout, "Imported %d history entries from %s\n", n, in); err != nil {
		return fmt.Errorf("history import: write output: %w", err)
	}
	return nil
}

func runHistoryBackup(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history backup", os.Stderr)
	var out string
	fs.StringVar(&out, "out", "", "Output SQLite backup file path")
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history backup: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history backup: unexpected args: %s", strings.Join(fs.Args(), " "))
	}
	out = str.Trim(out)
	if out == "" {
		return errors.New("history backup: --out is required")
	}

	s, err := openHistoryStore(true)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	if err := s.Backup(out); err != nil {
		return err
	}
	if err := writef(os.Stdout, "Created history backup at %s\n", out); err != nil {
		return fmt.Errorf("history backup: write output: %w", err)
	}
	return nil
}

func runHistoryStats(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history stats", os.Stderr)
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history stats: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history stats: unexpected args: %s", strings.Join(fs.Args(), " "))
	}

	s, err := openHistoryStore(true)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	st, err := s.Stats()
	if err != nil {
		return err
	}
	var b strings.Builder
	_, _ = fmt.Fprintf(&b, "History DB: %s\n", st.Path)
	_, _ = fmt.Fprintf(&b, "Schema: %d\n", st.Schema)
	_, _ = fmt.Fprintf(&b, "Rows: %d\n", st.Rows)
	if !st.Oldest.IsZero() {
		_, _ = fmt.Fprintf(&b, "Oldest: %s\n", st.Oldest.UTC().Format(time.RFC3339))
	}
	if !st.Newest.IsZero() {
		_, _ = fmt.Fprintf(&b, "Newest: %s\n", st.Newest.UTC().Format(time.RFC3339))
	}
	_, _ = fmt.Fprintf(&b, "DB Size: %s\n", byteLabel(st.DBBytes))
	_, _ = fmt.Fprintf(&b, "WAL Size: %s\n", byteLabel(st.WALBytes))
	_, _ = fmt.Fprintf(&b, "SHM Size: %s\n", byteLabel(st.SHMBytes))
	if _, err := io.WriteString(os.Stdout, b.String()); err != nil {
		return fmt.Errorf("history stats: write output: %w", err)
	}
	return nil
}

func runHistoryCompact(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history compact", os.Stderr)
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history compact: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history compact: unexpected args: %s", strings.Join(fs.Args(), " "))
	}

	s, err := openHistoryStore(true)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	b, err := s.Stats()
	if err != nil {
		return err
	}

	if err = s.Compact(); err != nil {
		return err
	}

	a, err := s.Stats()
	if err != nil {
		return err
	}

	if err := writef(
		os.Stdout,
		"Compacted history db (%s -> %s)\n",
		byteLabel(b.DBBytes+b.WALBytes+b.SHMBytes),
		byteLabel(a.DBBytes+a.WALBytes+a.SHMBytes),
	); err != nil {
		return fmt.Errorf("history compact: write output: %w", err)
	}
	return nil
}

func runHistoryCheck(args []string) error {
	fs := cli.NewSubcommandFlagSet("resterm", "history check", os.Stderr)
	var full bool
	fs.BoolVar(&full, "full", false, "Use full integrity check")
	if err := fs.Parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return fmt.Errorf("history check: %w", err)
	}
	if len(fs.Args()) > 0 {
		return fmt.Errorf("history check: unexpected args: %s", strings.Join(fs.Args(), " "))
	}

	s, err := openHistoryStore(true)
	if err != nil {
		return err
	}
	defer func() { _ = s.Close() }()

	if err := s.Check(full); err != nil {
		return err
	}
	mode := "quick"
	if full {
		mode = "full"
	}
	if err := writef(os.Stdout, "History integrity check (%s): ok\n", mode); err != nil {
		return fmt.Errorf("history check: write output: %w", err)
	}
	return nil
}

func openHistoryStore(migrate bool) (history.MaintenanceStore, error) {
	// This centralizes all history startup behavior used by CLI maintenance commands.
	// It loads the database, prints recovery warnings, and optionally runs legacy import.
	// On migration failure the store is closed before returning.
	s := histdb.New(config.HistoryPath())
	if err := s.Load(); err != nil {
		return nil, fmt.Errorf("history: load store: %w", err)
	}
	// Recovery is non-fatal, but the warning still goes to stderr so
	// operators know the original file was quarantined.
	if rec := s.RecoveryInfo(); rec != nil {
		if err := printHistoryRecoveryWarning(os.Stderr, rec); err != nil {
			return nil, fmt.Errorf("history: write recovery warning: %w", err)
		}
	}
	// Legacy JSON import is optional because direct maintenance commands
	// may need to inspect or repair SQLite without backfilling first.
	if migrate {
		if _, err := s.MigrateJSON(config.LegacyHistoryPath()); err != nil {
			_ = s.Close()
			return nil, fmt.Errorf("history: migrate legacy json: %w", err)
		}
	}
	return s, nil
}

func historyUsageText() string {
	return str.Trim(`
Usage: resterm history <export|import|backup|stats|check|compact> [flags]

Subcommands:
  export --out <path>   Export history to JSON
  import --in <path>    Import history from JSON
  backup --out <path>   Create a SQLite-consistent backup
  stats                 Show history DB stats
  check [--full]        Run SQLite integrity check
  compact               Run VACUUM and checkpoint
`)
}

func printHistoryRecoveryWarning(w io.Writer, rec *histdb.RecoverInfo) error {
	if rec == nil {
		return nil
	}
	cause := str.Trim(rec.Cause)
	if cause == "" {
		cause = "unknown"
	}
	if err := writef(
		w,
		"history: warning: recovered corrupted db; moved %s to %s (cause: %s)\n",
		rec.Path,
		rec.Backup,
		cause,
	); err != nil {
		return err
	}
	if err := writeln(
		w,
		"history: warning: restore from an export with `resterm history import --in <path>` if needed",
	); err != nil {
		return err
	}
	return nil
}

func writef(w io.Writer, format string, args ...any) error {
	_, err := fmt.Fprintf(w, format, args...)
	return err
}

func writeln(w io.Writer, args ...any) error {
	_, err := fmt.Fprintln(w, args...)
	return err
}

func byteLabel(n int64) string {
	if n <= 0 {
		return "0 B"
	}
	const (
		kb = int64(1024)
		mb = kb * 1024
		gb = mb * 1024
	)
	switch {
	case n >= gb:
		return strconv.FormatFloat(float64(n)/float64(gb), 'f', 2, 64) + " GiB"
	case n >= mb:
		return strconv.FormatFloat(float64(n)/float64(mb), 'f', 2, 64) + " MiB"
	case n >= kb:
		return strconv.FormatFloat(float64(n)/float64(kb), 'f', 2, 64) + " KiB"
	default:
		return strconv.FormatInt(n, 10) + " B"
	}
}

```

## /cmd/resterm/history_test.go

```go path="/cmd/resterm/history_test.go" 
package main

import (
	"encoding/json"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/unkn0wn-root/resterm/internal/history"
	histdb "github.com/unkn0wn-root/resterm/internal/history/sqlite"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func TestHandleHistorySubcommandNotMatched(t *testing.T) {
	handled, err := handleHistorySubcommand([]string{"init"})
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	if handled {
		t.Fatalf("expected not handled")
	}
}

func TestRunHistoryRequiresSubcommand(t *testing.T) {
	err := runHistory(nil)
	if err == nil {
		t.Fatalf("expected error for missing subcommand")
	}
}

func TestRunHistoryUnknownSubcommand(t *testing.T) {
	err := runHistory([]string{"unknown"})
	if err == nil {
		t.Fatalf("expected error for unknown subcommand")
	}
}

func TestRunHistoryHelpFlagShowsUsage(t *testing.T) {
	stdout, stderr, err := captureHistoryIO(t, func() error {
		return runHistory([]string{"-h"})
	})
	if err != nil {
		t.Fatalf("help flag: %v", err)
	}
	if str.Trim(stderr) != "" {
		t.Fatalf("expected empty stderr on help flag, got %q", stderr)
	}
	if !strings.Contains(
		stdout,
		"Usage: resterm history <export|import|backup|stats|check|compact> [flags]",
	) {
		t.Fatalf("expected history usage in stdout, got %q", stdout)
	}
}

func TestRunHistoryMaintenanceCommands(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("RESTERM_CONFIG_DIR", dir)

	if err := runHistory([]string{"stats"}); err != nil {
		t.Fatalf("stats: %v", err)
	}
	if err := runHistory([]string{"check"}); err != nil {
		t.Fatalf("check: %v", err)
	}
	if err := runHistory([]string{"check", "--full"}); err != nil {
		t.Fatalf("check full: %v", err)
	}
	if err := runHistory([]string{"compact"}); err != nil {
		t.Fatalf("compact: %v", err)
	}
}

func TestRunHistoryE2E(t *testing.T) {
	srcDir := t.TempDir()
	t.Setenv("RESTERM_CONFIG_DIR", srcDir)

	src := histdb.New(filepath.Join(srcDir, "history.db"))
	if err := src.Load(); err != nil {
		t.Fatalf("load src: %v", err)
	}
	t1 := time.Date(2024, 1, 1, 9, 0, 0, 0, time.UTC)
	t2 := t1.Add(1 * time.Minute)
	if err := src.Append(
		history.Entry{ID: "1", ExecutedAt: t1, Method: "GET", URL: "https://one.test"},
	); err != nil {
		t.Fatalf("append src 1: %v", err)
	}
	if err := src.Append(
		history.Entry{ID: "2", ExecutedAt: t2, Method: "POST", URL: "https://two.test"},
	); err != nil {
		t.Fatalf("append src 2: %v", err)
	}
	if err := src.Close(); err != nil {
		t.Fatalf("close src: %v", err)
	}

	jsonPath := filepath.Join(t.TempDir(), "history.json")
	stdout, stderr, err := captureHistoryIO(t, func() error {
		return runHistory([]string{"export", "--out", jsonPath})
	})
	if err != nil {
		t.Fatalf("export: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on export, got %q", stderr)
	}
	if !strings.Contains(stdout, "Exported 2 history entries") {
		t.Fatalf("unexpected export output: %q", stdout)
	}

	var exported []history.Entry
	data, err := os.ReadFile(jsonPath)
	if err != nil {
		t.Fatalf("read export file: %v", err)
	}
	if err := json.Unmarshal(data, &exported); err != nil {
		t.Fatalf("decode export file: %v", err)
	}
	if len(exported) != 2 {
		t.Fatalf("expected 2 exported rows, got %d", len(exported))
	}

	backupPath := filepath.Join(t.TempDir(), "history.bak.db")
	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"backup", "--out", backupPath})
	})
	if err != nil {
		t.Fatalf("backup: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on backup, got %q", stderr)
	}
	if !strings.Contains(stdout, "Created history backup") {
		t.Fatalf("unexpected backup output: %q", stdout)
	}

	bak := histdb.New(backupPath)
	if err := bak.Load(); err != nil {
		t.Fatalf("load backup db: %v", err)
	}
	if got, err := bak.Entries(); err != nil {
		t.Fatalf("backup entries: %v", err)
	} else if len(got) != 2 {
		t.Fatalf("expected 2 rows in backup db, got %d", len(got))
	}
	_ = bak.Close()

	dstDir := t.TempDir()
	t.Setenv("RESTERM_CONFIG_DIR", dstDir)

	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"import", "--in", jsonPath})
	})
	if err != nil {
		t.Fatalf("import: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on import, got %q", stderr)
	}
	if !strings.Contains(stdout, "Imported 2 history entries") {
		t.Fatalf("unexpected import output: %q", stdout)
	}

	dst := histdb.New(filepath.Join(dstDir, "history.db"))
	if err := dst.Load(); err != nil {
		t.Fatalf("load dst: %v", err)
	}
	if got, err := dst.Entries(); err != nil {
		t.Fatalf("dst entries: %v", err)
	} else if len(got) != 2 {
		t.Fatalf("expected 2 rows in dst db, got %d", len(got))
	}
	_ = dst.Close()

	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"stats"})
	})
	if err != nil {
		t.Fatalf("stats: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on stats, got %q", stderr)
	}
	if !strings.Contains(stdout, "Rows: 2") || !strings.Contains(stdout, "Schema:") {
		t.Fatalf("unexpected stats output: %q", stdout)
	}

	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"check"})
	})
	if err != nil {
		t.Fatalf("check: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on check, got %q", stderr)
	}
	if !strings.Contains(stdout, "History integrity check (quick): ok") {
		t.Fatalf("unexpected check output: %q", stdout)
	}

	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"check", "--full"})
	})
	if err != nil {
		t.Fatalf("check full: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on full check, got %q", stderr)
	}
	if !strings.Contains(stdout, "History integrity check (full): ok") {
		t.Fatalf("unexpected full check output: %q", stdout)
	}

	stdout, stderr, err = captureHistoryIO(t, func() error {
		return runHistory([]string{"compact"})
	})
	if err != nil {
		t.Fatalf("compact: %v", err)
	}
	if stderr != "" {
		t.Fatalf("expected empty stderr on compact, got %q", stderr)
	}
	if !strings.Contains(stdout, "Compacted history db") {
		t.Fatalf("unexpected compact output: %q", stdout)
	}
}

func TestRunHistoryRecoveryWarning(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("RESTERM_CONFIG_DIR", dir)

	dbPath := filepath.Join(dir, "history.db")
	if err := os.WriteFile(dbPath, []byte("not-a-sqlite-db"), 0o644); err != nil {
		t.Fatalf("write corrupt db: %v", err)
	}

	stdout, stderr, err := captureHistoryIO(t, func() error {
		return runHistory([]string{"stats"})
	})
	if err != nil {
		t.Fatalf("stats: %v", err)
	}
	if !strings.Contains(stdout, "History DB:") {
		t.Fatalf("expected stats output, got %q", stdout)
	}
	if !strings.Contains(stderr, "history: warning: recovered corrupted db") {
		t.Fatalf("expected recovery warning, got %q", stderr)
	}
	if !strings.Contains(stderr, "resterm history import --in <path>") {
		t.Fatalf("expected recovery guidance in stderr, got %q", stderr)
	}

	matches, err := filepath.Glob(filepath.Join(dir, "history.db.corrupt-*"))
	if err != nil {
		t.Fatalf("glob corrupt backups: %v", err)
	}
	if len(matches) == 0 {
		t.Fatalf("expected quarantined corrupt db file")
	}
}

func TestRunHistoryFlagErrorsHaveCommandPrefix(t *testing.T) {
	cases := []struct {
		args []string
		want string
	}{
		{args: []string{"export", "--bad"}, want: "history export:"},
		{args: []string{"import", "--bad"}, want: "history import:"},
		{args: []string{"backup", "--bad"}, want: "history backup:"},
		{args: []string{"stats", "--bad"}, want: "history stats:"},
		{args: []string{"check", "--bad"}, want: "history check:"},
		{args: []string{"compact", "--bad"}, want: "history compact:"},
	}
	for _, tc := range cases {
		err := runHistory(tc.args)
		if err == nil {
			t.Fatalf("expected error for %v", tc.args)
		}
		if !strings.Contains(err.Error(), tc.want) {
			t.Fatalf("expected error %q to contain %q", err.Error(), tc.want)
		}
	}
}

func TestRunHistorySubcommandHelpShowsUsage(t *testing.T) {
	stdout, stderr, err := captureHistoryIO(t, func() error {
		return runHistory([]string{"export", "-h"})
	})
	if err != nil {
		t.Fatalf("export -h: %v", err)
	}
	if str.Trim(stdout) != "" {
		t.Fatalf("expected empty stdout on help, got %q", stdout)
	}
	if !strings.Contains(stderr, "Usage: resterm history export [flags]") {
		t.Fatalf("expected usage in stderr, got %q", stderr)
	}
	if !strings.Contains(stderr, "-out") {
		t.Fatalf("expected --out flag in help output, got %q", stderr)
	}
}

func captureHistoryIO(t *testing.T, fn func() error) (string, string, error) {
	t.Helper()

	oldOut := os.Stdout
	oldErr := os.Stderr
	outR, outW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stdout: %v", err)
	}
	errR, errW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stderr: %v", err)
	}

	os.Stdout = outW
	os.Stderr = errW
	defer func() {
		os.Stdout = oldOut
		os.Stderr = oldErr
	}()

	runErr := fn()

	_ = outW.Close()
	_ = errW.Close()

	outData, outErr := io.ReadAll(outR)
	if outErr != nil {
		t.Fatalf("read stdout: %v", outErr)
	}
	errData, errErr := io.ReadAll(errR)
	if errErr != nil {
		t.Fatalf("read stderr: %v", errErr)
	}

	_ = outR.Close()
	_ = errR.Close()
	return string(outData), string(errData), runErr
}

```

## /cmd/resterm/init.go

```go path="/cmd/resterm/init.go" 
package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/initcmd"
)

func handleInitSubcommand(args []string) (bool, error) {
	if len(args) == 0 || args[0] != "init" {
		return false, nil
	}
	if len(args) == 1 && cli.HasFileConflict("init") {
		return true, cli.CommandFileConflict(
			"resterm",
			"init",
			"pass a flag like `resterm init --dir .` to run init",
		)
	}
	return true, runInit(args[1:])
}

func runInit(args []string) error {
	c := newInitCmd()
	if err := c.parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return err
	}
	return c.run()
}

type initCmd struct {
	fs    *flag.FlagSet
	dir   string
	tpl   string
	force bool
	dry   bool
	list  bool
	noGi  bool
}

func newInitCmd() *initCmd {
	c := &initCmd{}
	fs := cli.NewFlagSet("init")
	c.fs = fs
	c.bind()
	fs.Usage = c.usage
	return c
}

func (c *initCmd) bind() {
	c.fs.StringVar(&c.dir, "dir", initcmd.DefaultDir, "Target directory")
	c.fs.StringVar(&c.tpl, "template", initcmd.DefaultTemplate, "Template to use")
	c.fs.BoolVar(&c.force, "force", false, "Overwrite existing files")
	c.fs.BoolVar(&c.dry, "dry-run", false, "Print actions without writing files")
	c.fs.BoolVar(&c.list, "list", false, "List available templates")
	c.fs.BoolVar(&c.noGi, "no-gitignore", false, "Do not touch .gitignore")
}

func (c *initCmd) parse(args []string) error {
	return c.fs.Parse(args)
}

func (c *initCmd) run() error {
	extra := c.fs.Args()
	if c.list {
		if len(extra) > 0 {
			return fmt.Errorf("init: unexpected args: %s", strings.Join(extra, " "))
		}
		return initcmd.Run(initcmd.Opt{List: true, Out: os.Stdout})
	}
	if len(extra) > 0 {
		if c.dir == initcmd.DefaultDir && len(extra) == 1 {
			c.dir = extra[0]
		} else {
			return fmt.Errorf("init: unexpected args: %s", strings.Join(extra, " "))
		}
	}

	o := initcmd.Opt{
		Dir:         c.dir,
		Template:    c.tpl,
		Force:       c.force,
		DryRun:      c.dry,
		NoGitignore: c.noGi,
		Out:         os.Stdout,
	}
	return initcmd.Run(o)
}

func (c *initCmd) usage() {
	fmt.Fprintln(os.Stderr, "Usage: resterm init [flags] [dir]")
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "Flags:")
	out := c.fs.Output()
	c.fs.SetOutput(os.Stderr)
	c.fs.PrintDefaults()
	c.fs.SetOutput(out)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "Templates:")
	_ = initcmd.Run(initcmd.Opt{List: true, Out: os.Stderr})
}

```

## /cmd/resterm/install_source.go

```go path="/cmd/resterm/install_source.go" 
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	str "github.com/unkn0wn-root/resterm/internal/util"
)

const (
	srcDirect = "direct"
	srcBrew   = "homebrew"
)

const (
	cmdDirect = "resterm --update"
	cmdBrew   = "brew upgrade resterm"
)

// installSource can be set at build time with:
// -ldflags "-X main.installSource=homebrew"
var installSource = ""

type envFn func(string) string

func installSrc() string {
	exe, err := os.Executable()
	if err != nil {
		exe = ""
	}
	return installSrcFor(runtime.GOOS, installSource, exe, os.Getenv)
}

func installSrcFor(goos, src, exe string, env envFn) string {
	s := normSrc(src)
	if s != "" {
		return s
	}
	if goos == "windows" {
		return srcDirect
	}
	exe = resolveExecPath(exe)
	if isBrewExe(goos, exe, env) {
		return srcBrew
	}
	return srcDirect
}

func normSrc(src string) string {
	s := strings.ToLower(str.Trim(src))
	if s == "" {
		return ""
	}
	switch s {
	case "brew", "homebrew":
		return srcBrew
	default:
		return s
	}
}

func updCmd(src string) string {
	if normSrc(src) == srcBrew {
		return cmdBrew
	}
	return cmdDirect
}

func updHint(cmd string) string {
	return fmt.Sprintf("Run `%s` to install.", cmd)
}

func updBlock(cmd string) string {
	return fmt.Sprintf("This resterm was installed via Homebrew. Use `%s`.", cmd)
}

func isBrewExe(goos, exe string, env envFn) bool {
	if exe == "" || goos == "windows" {
		return false
	}
	exe = filepath.Clean(exe)
	for _, dir := range brewDirs(goos, env) {
		if hasPathPrefix(exe, dir) {
			return true
		}
	}
	return false
}

func brewDirs(goos string, env envFn) []string {
	var out []string
	add := func(p string) {
		p = str.Trim(p)
		if p == "" {
			return
		}
		out = append(out, filepath.Clean(p))
	}
	if env != nil {
		add(env("HOMEBREW_CELLAR"))
		pfx := str.Trim(env("HOMEBREW_PREFIX"))
		if pfx != "" {
			add(filepath.Join(pfx, "Cellar"))
			add(filepath.Join(pfx, "opt"))
		}
	}
	for _, p := range defBrewPfx(goos) {
		add(filepath.Join(p, "Cellar"))
		add(filepath.Join(p, "opt"))
	}
	return uniqPaths(out)
}

func defBrewPfx(goos string) []string {
	switch goos {
	case "darwin":
		return []string{"/opt/homebrew", "/usr/local"}
	case "linux":
		return []string{"/home/linuxbrew/.linuxbrew", "/usr/local"}
	default:
		return nil
	}
}

func uniqPaths(in []string) []string {
	seen := make(map[string]struct{}, len(in))
	out := make([]string, 0, len(in))
	for _, p := range in {
		if p == "" {
			continue
		}
		if _, ok := seen[p]; ok {
			continue
		}
		seen[p] = struct{}{}
		out = append(out, p)
	}
	return out
}

func hasPathPrefix(p, pre string) bool {
	if p == "" || pre == "" {
		return false
	}
	if p == pre {
		return true
	}
	sep := string(os.PathSeparator)
	if !strings.HasSuffix(pre, sep) {
		pre += sep
	}
	return strings.HasPrefix(p, pre)
}

```

## /cmd/resterm/install_source_test.go

```go path="/cmd/resterm/install_source_test.go" 
package main

import "testing"

func TestInstallSrcForFlag(t *testing.T) {
	got := installSrcFor("darwin", "homebrew", "/tmp/resterm", envMap(nil))
	if got != srcBrew {
		t.Fatalf("expected %q, got %q", srcBrew, got)
	}
}

func TestInstallSrcForCellar(t *testing.T) {
	env := envMap(map[string]string{
		"HOMEBREW_CELLAR": "/opt/homebrew/Cellar",
	})
	exe := "/opt/homebrew/Cellar/resterm/1.2.3/bin/resterm"
	got := installSrcFor("darwin", "", exe, env)
	if got != srcBrew {
		t.Fatalf("expected %q, got %q", srcBrew, got)
	}
}

func TestInstallSrcForNoFalsePositive(t *testing.T) {
	exe := "/usr/local/bin/resterm"
	got := installSrcFor("darwin", "", exe, envMap(nil))
	if got != srcDirect {
		t.Fatalf("expected %q, got %q", srcDirect, got)
	}
}

func TestUpdCmd(t *testing.T) {
	if got := updCmd("homebrew"); got != cmdBrew {
		t.Fatalf("expected %q, got %q", cmdBrew, got)
	}
	if got := updCmd("direct"); got != cmdDirect {
		t.Fatalf("expected %q, got %q", cmdDirect, got)
	}
}

func envMap(m map[string]string) envFn {
	return func(k string) string {
		return m[k]
	}
}

```

## /cmd/resterm/load_env_test.go

```go path="/cmd/resterm/load_env_test.go" 
package main

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/unkn0wn-root/resterm/internal/cli"
)

func TestLoadEnvironmentExplicitDotEnv(t *testing.T) {
	dir := t.TempDir()
	envPath := filepath.Join(dir, ".env.local")
	content := "workspace=local\nAPI_URL=http://localhost\n"
	if err := os.WriteFile(envPath, []byte(content), 0o644); err != nil {
		t.Fatalf("write env file: %v", err)
	}

	envs, resolved := cli.LoadEnvironment(envPath, "", dir)
	if resolved != envPath {
		t.Fatalf("resolved path = %q, want %q", resolved, envPath)
	}
	env := envs["local"]
	if env == nil {
		t.Fatalf("expected local environment, got %v", envs)
	}
	if env["API_URL"] != "http://localhost" {
		t.Fatalf("API_URL = %q, want %q", env["API_URL"], "http://localhost")
	}
}

func TestLoadEnvironmentIgnoresDotEnvDiscovery(t *testing.T) {
	dir := t.TempDir()
	envPath := filepath.Join(dir, ".env")
	if err := os.WriteFile(
		envPath,
		[]byte("workspace=dev\nAPI_URL=https://api\n"),
		0o644,
	); err != nil {
		t.Fatalf("write env file: %v", err)
	}

	envs, resolved := cli.LoadEnvironment("", "", dir)
	if envs != nil {
		t.Fatalf("expected no auto-discovered envs, got %v", envs)
	}
	if resolved != "" {
		t.Fatalf("resolved path = %q, want empty", resolved)
	}
}

func TestHandleInitSubcommandAmbiguousFile(t *testing.T) {
	dir := t.TempDir()
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(cwd)
	})
	if err := os.Chdir(dir); err != nil {
		t.Fatalf("chdir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(dir, "init"), []byte("data"), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	handled, err := handleInitSubcommand([]string{"init"})
	if !handled {
		t.Fatalf("expected init to be handled")
	}
	if err == nil {
		t.Fatalf("expected ambiguity error")
	}
}

func TestRunInitListArgs(t *testing.T) {
	if err := runInit([]string{"--list", "extra"}); err == nil {
		t.Fatalf("expected error for extra args")
	}
}

```

## /cmd/resterm/main.go

```go path="/cmd/resterm/main.go" 
package main

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"

	"github.com/unkn0wn-root/resterm/internal/bindings"
	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/config"
	curl "github.com/unkn0wn-root/resterm/internal/curl/importer"
	histdb "github.com/unkn0wn-root/resterm/internal/history/sqlite"
	"github.com/unkn0wn-root/resterm/internal/openapi"
	"github.com/unkn0wn-root/resterm/internal/openapi/generator"
	"github.com/unkn0wn-root/resterm/internal/openapi/parser"
	"github.com/unkn0wn-root/resterm/internal/openapi/writer"
	"github.com/unkn0wn-root/resterm/internal/rtfmt"
	"github.com/unkn0wn-root/resterm/internal/theme"
	"github.com/unkn0wn-root/resterm/internal/ui"
	"github.com/unkn0wn-root/resterm/internal/update"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

var (
	version = "dev"
	commit  = "unknown"
	date    = "unknown"
)

func main() {
	if err := run(os.Args[1:]); err != nil {
		if msg := str.Trim(err.Error()); msg != "" {
			_, _ = fmt.Fprintln(os.Stderr, msg)
		}
		os.Exit(cli.ExitCode(err))
	}
}

func run(a []string) error {
	if ok, err := handleCollectionSubcommand(a); ok {
		return err
	}
	if ok, err := handleHistorySubcommand(a); ok {
		return err
	}
	if ok, err := handleInitSubcommand(a); ok {
		return err
	}
	if ok, err := handleRunSubcommand(a); ok {
		return err
	}

	var (
		filePath                 string
		showVersion              bool
		checkUpdate              bool
		doUpdate                 bool
		curlSrc                  string
		openapiSpec              string
		httpOut                  string
		openapiBase              string
		openapiResolveRefs       bool
		openapiIncludeDeprecated bool
		openapiServerIndex       int
	)

	exec := cli.NewExecFlags()
	fs := cli.NewFlagSet("resterm")
	fs.StringVar(&filePath, "file", "", "Path to .http/.rest file to open")
	exec.Bind(fs)
	fs.BoolVar(&showVersion, "version", false, "Show resterm version")
	fs.BoolVar(&checkUpdate, "check-update", false, "Check for newer releases and exit")
	fs.BoolVar(
		&doUpdate,
		"update",
		false,
		"Download and install the latest release, if available",
	)
	fs.StringVar(
		&curlSrc,
		"from-curl",
		"",
		"Curl command or file path to convert",
	)
	fs.StringVar(
		&openapiSpec,
		"from-openapi",
		"",
		"Path to OpenAPI specification file to convert",
	)
	fs.StringVar(&httpOut, "http-out", "", "Destination path for generated .http file")
	fs.StringVar(
		&openapiBase,
		"openapi-base-var",
		openapi.DefaultBaseURLVariable,
		"Variable name for the generated base URL",
	)
	fs.BoolVar(
		&openapiResolveRefs,
		"openapi-resolve-refs",
		false,
		"Resolve external $ref references during OpenAPI import",
	)
	fs.BoolVar(
		&openapiIncludeDeprecated,
		"openapi-include-deprecated",
		false,
		"Include deprecated operations when generating requests",
	)
	fs.IntVar(
		&openapiServerIndex,
		"openapi-server-index",
		0,
		"Preferred server index (0-based) from the spec to use as the base URL",
	)
	if err := fs.Parse(a); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			printMainUsage(os.Stderr, fs)
			return nil
		}
		return cli.ExitErr{Err: err, Code: 2}
	}

	if showVersion {
		fmt.Printf("resterm %s\n", version)
		fmt.Printf("  commit: %s\n", commit)
		fmt.Printf("  built:  %s\n", date)
		if sum, err := executableChecksum(); err == nil {
			fmt.Printf("  sha256: %s\n", sum)
		} else {
			fmt.Printf("  sha256: unavailable (%v)\n", err)
		}
		return nil
	}
	if err := exec.ValidateEnvFlag(); err != nil {
		return err
	}

	hc := &http.Client{Timeout: 60 * time.Second}
	uc, err := update.NewClient(hc, updateRepo)
	if err != nil {
		return fmt.Errorf("update client: %w", err)
	}

	src := installSrc()
	ucmd := updCmd(src)

	if checkUpdate || doUpdate {
		if doUpdate && src == srcBrew {
			return errors.New(updBlock(ucmd))
		}
		u := newCLIUpdater(uc, version)
		ctx := context.Background()
		res, ok, err := u.check(ctx)
		if err != nil {
			if errors.Is(err, errUpdateDisabled) {
				_ = rtfmt.Fprintln(
					os.Stdout,
					rtfmt.LogHandler(log.Printf, "update notice write failed: %v"),
					"Update checks are disabled for dev builds.",
				)
				return nil
			}
			return fmt.Errorf("update check failed: %w", err)
		}
		if !ok {
			u.printNoUpdate()
			return nil
		}
		u.printAvailable(res)
		u.printChangelog(res)
		if !doUpdate {
			_ = rtfmt.Fprintln(
				os.Stdout,
				rtfmt.LogHandler(log.Printf, "update hint write failed: %v"),
				updHint(ucmd),
			)
			return nil
		}
		if _, err := u.apply(ctx, res); err != nil && !errors.Is(err, update.ErrPendingSwap) {
			return fmt.Errorf("update failed: %w", err)
		}
		return nil
	}

	if curlSrc != "" && openapiSpec != "" {
		return errors.New("import error: choose either --from-curl or --from-openapi")
	}

	if curlSrc != "" {
		cmd, err := readCurlCommand(curlSrc)
		if err != nil {
			return fmt.Errorf("curl import error: %w", err)
		}

		targetOut := httpOut
		if targetOut == "" {
			targetOut = defaultCurlOutputPath(curlSrc)
		}

		opts := curl.WriterOptions{
			HeaderComment:     fmt.Sprintf("Generated by resterm %s", version),
			OverwriteExisting: true,
		}

		if err := convertCurlCommand(
			context.Background(),
			cmd,
			targetOut,
			version,
			opts); err != nil {
			return fmt.Errorf("curl import error: %w", err)
		}

		_ = rtfmt.Fprintf(os.Stdout, "Generated %s from curl\n", nil, targetOut)
		return nil
	}

	if openapiSpec != "" {
		targetOut := httpOut
		if targetOut == "" {
			targetOut = defaultHTTPOutputPath(openapiSpec)
		}

		opts := openapi.GenerateOptions{
			Parse: openapi.ParseOptions{ResolveExternalRefs: openapiResolveRefs},
			Generate: openapi.GeneratorOptions{
				BaseURLVariable:      openapiBase,
				IncludeDeprecated:    openapiIncludeDeprecated,
				PreferredServerIndex: openapiServerIndex,
			},
			Write: openapi.WriterOptions{
				HeaderComment:     fmt.Sprintf("Generated by resterm %s", version),
				OverwriteExisting: true,
			},
		}

		if err := convertOpenAPISpec(
			context.Background(),
			openapiSpec,
			targetOut,
			version,
			opts); err != nil {
			return fmt.Errorf("openapi import error: %w", err)
		}

		_ = rtfmt.Fprintf(os.Stdout, "Generated %s from %s\n", nil, targetOut, openapiSpec)
		return nil
	}

	if filePath == "" && fs.NArg() > 0 {
		filePath = fs.Arg(0)
	}
	filePath = cli.CleanExecPath(filePath)

	var initialContent string
	if filePath != "" {
		data, err := os.ReadFile(filePath)
		if err != nil {
			return fmt.Errorf("read file: %w", err)
		}
		initialContent = string(data)
	}

	cfg, err := exec.Resolve(filePath)
	if err != nil {
		return err
	}

	client, shutdown, err := cli.NewExecClient(version, exec)
	if err != nil {
		if exec.TelemetryConfig(version).Enabled() {
			log.Printf("telemetry init error: %v", err)
		}
	} else if shutdown != nil {
		defer func() {
			if shutdownErr := shutdown(); shutdownErr != nil {
				log.Printf("telemetry shutdown: %v", shutdownErr)
			}
		}()
	}

	historyStore := histdb.New(config.HistoryPath())
	// History failures should never block the UI startup path.
	// We log issues and keep running with an empty in-memory view.
	if err := historyStore.Load(); err != nil {
		log.Printf("history load error: %v", err)
	} else if rec := historyStore.RecoveryInfo(); rec != nil {
		log.Printf("history db recovered: %s -> %s", rec.Path, rec.Backup)
	}
	// Migration is also best effort at startup so existing workflows
	// can continue even when legacy files are malformed.
	if n, err := historyStore.MigrateJSON(config.LegacyHistoryPath()); err != nil {
		log.Printf("history migration error: %v", err)
	} else if n > 0 {
		log.Printf(
			"history migration imported %d entries from %s",
			n,
			config.LegacyHistoryPath(),
		)
	}
	defer func() {
		if err := historyStore.Close(); err != nil {
			log.Printf("history close error: %v", err)
		}
	}()

	settings, settingsHandle, err := config.LoadSettings()
	if err != nil {
		log.Printf("settings load error: %v", err)
		settings = config.Settings{}
		settingsHandle = config.SettingsHandle{
			Path:   filepath.Join(config.Dir(), "settings.toml"),
			Format: config.SettingsFormatTOML,
		}
	}

	bindingMap, _, bindingErr := bindings.Load(config.Dir())
	if bindingErr != nil {
		log.Printf("bindings load error: %v", bindingErr)
		bindingMap = bindings.DefaultMap()
	}

	themeCatalog, themeErr := theme.LoadCatalog([]string{config.ThemeDir()})
	if themeErr != nil {
		log.Printf("theme load error: %v", themeErr)
	}

	th := theme.DefaultTheme()
	activeThemeKey := str.Trim(strings.ToLower(settings.DefaultTheme))
	if activeThemeKey == "" {
		activeThemeKey = "default"
	}
	if def, ok := themeCatalog.Get(activeThemeKey); ok {
		th = def.Theme
		activeThemeKey = def.Key
		settings.DefaultTheme = def.Key
	} else {
		if settings.DefaultTheme != "" {
			log.Printf("theme %q not found; using built-in default", settings.DefaultTheme)
		}
		if def, ok := themeCatalog.Get("default"); ok {
			th = def.Theme
			activeThemeKey = def.Key
		} else {
			th = theme.DefaultTheme()
			activeThemeKey = "default"
		}
		settings.DefaultTheme = ""
	}
	updateEnabled := version != "dev"

	model := ui.New(ui.Config{
		FilePath:            filePath,
		InitialContent:      initialContent,
		Client:              client,
		Theme:               &th,
		ThemeCatalog:        themeCatalog,
		ActiveThemeKey:      activeThemeKey,
		Settings:            settings,
		SettingsHandle:      settingsHandle,
		EnvironmentSet:      cfg.EnvSet,
		EnvironmentName:     cfg.EnvName,
		EnvironmentFile:     cfg.EnvFile,
		EnvironmentFallback: cfg.EnvFallback,
		HTTPOptions:         cfg.HTTPOpts,
		GRPCOptions:         cfg.GRPCOpts,
		History:             historyStore,
		WorkspaceRoot:       cfg.Workspace,
		Recursive:           cfg.Recursive,
		Version:             version,
		UpdateClient:        uc,
		EnableUpdate:        updateEnabled,
		UpdateCmd:           ucmd,
		CompareTargets:      cfg.CompareTargets,
		CompareBase:         cfg.CompareBase,
		Bindings:            bindingMap,
	})

	program := tea.NewProgram(model, tea.WithAltScreen())
	if _, err := program.Run(); err != nil {
		return fmt.Errorf("error: %w", err)
	}
	return nil
}

func printMainUsage(w io.Writer, fs *flag.FlagSet) {
	if _, err := fmt.Fprintf(w, "Usage: %s [flags] [file]\n\n", fs.Name()); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "Subcommands:"); err != nil {
		return
	}
	if _, err := fmt.Fprintln(
		w,
		"  run         Execute request files without the TUI",
	); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "  init        Bootstrap a new workspace"); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "  collection  Export or import request bundles"); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "  history     Manage persisted history"); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "Flags:"); err != nil {
		return
	}
	out := fs.Output()
	fs.SetOutput(w)
	defer fs.SetOutput(out)
	fs.PrintDefaults()
}

func executableChecksum() (string, error) {
	exe, err := os.Executable()
	if err != nil {
		return "", err
	}
	exe, err = filepath.EvalSymlinks(exe)
	if err != nil {
		return "", err
	}
	f, err := os.Open(exe)
	if err != nil {
		return "", err
	}
	defer func() {
		_ = f.Close()
	}()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}

func convertCurlCommand(
	ctx context.Context,
	cmd, outputPath, version string,
	opts curl.WriterOptions,
) error {
	if ctx == nil {
		ctx = context.Background()
	}
	if outputPath == "" {
		outputPath = "curl.http"
	}
	if str.Trim(opts.HeaderComment) == "" {
		opts.HeaderComment = fmt.Sprintf("Generated by resterm %s", version)
	}
	svc := curl.Service{
		Writer: curl.NewFileWriter(),
	}
	return svc.GenerateHTTPFile(ctx, cmd, outputPath, opts)
}

func convertOpenAPISpec(
	ctx context.Context,
	specPath, outputPath, version string,
	opts openapi.GenerateOptions,
) error {
	if ctx == nil {
		ctx = context.Background()
	}
	if outputPath == "" {
		outputPath = defaultHTTPOutputPath(specPath)
	}
	if str.Trim(opts.Generate.BaseURLVariable) == "" {
		opts.Generate.BaseURLVariable = openapi.DefaultBaseURLVariable
	}
	if str.Trim(opts.Write.HeaderComment) == "" {
		opts.Write.HeaderComment = fmt.Sprintf("Generated by resterm %s", version)
	}
	svc := openapi.Service{
		Parser:    parser.NewLoader(),
		Generator: generator.NewBuilder(),
		Writer:    writer.NewFileWriter(),
	}
	return svc.GenerateHTTPFile(ctx, specPath, outputPath, opts)
}

func readCurlCommand(src string) (string, error) {
	src = str.Trim(src)
	if src == "" {
		return "", fmt.Errorf("curl source is empty")
	}
	if src == "-" {
		data, err := io.ReadAll(os.Stdin)
		if err != nil {
			return "", err
		}
		return string(data), nil
	}
	if info, err := os.Stat(src); err == nil {
		if info.IsDir() {
			return "", fmt.Errorf("curl source %s is a directory", src)
		}
		data, err := os.ReadFile(src)
		if err != nil {
			return "", err
		}
		return string(data), nil
	}
	return src, nil
}

func defaultCurlOutputPath(src string) string {
	src = str.Trim(src)
	if src == "" || src == "-" {
		return "curl.http"
	}
	if info, err := os.Stat(src); err == nil && !info.IsDir() {
		return defaultHTTPOutputPath(src)
	}
	return "curl.http"
}

func defaultHTTPOutputPath(specPath string) string {
	ext := filepath.Ext(specPath)
	if ext == "" {
		return specPath + ".http"
	}
	return strings.TrimSuffix(specPath, ext) + ".http"
}

```

## /cmd/resterm/main_test.go

```go path="/cmd/resterm/main_test.go" 
package main

import (
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/unkn0wn-root/resterm/internal/cli"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func TestRunVersionFlag(t *testing.T) {
	out, errOut, err := captureRunIO(t, func() error {
		return run([]string{"--version"})
	})
	if err != nil {
		t.Fatalf("run --version: %v", err)
	}
	if errOut != "" {
		t.Fatalf("expected empty stderr, got %q", errOut)
	}
	if !strings.Contains(out, "resterm ") {
		t.Fatalf("expected version header in stdout, got %q", out)
	}
}

func TestRunHelpFlag(t *testing.T) {
	out, errOut, err := captureRunIO(t, func() error {
		return run([]string{"--help"})
	})
	if err != nil {
		t.Fatalf("run --help: %v", err)
	}
	if str.Trim(out) != "" {
		t.Fatalf("expected empty stdout, got %q", out)
	}
	if !strings.Contains(errOut, "Usage: resterm [flags] [file]") {
		t.Fatalf("expected usage in stderr, got %q", errOut)
	}
	if !strings.Contains(errOut, "-file") {
		t.Fatalf("expected top-level flags in help output, got %q", errOut)
	}
}

func TestRunRejectsConflictingImportFlags(t *testing.T) {
	t.Setenv("RESTERM_CONFIG_DIR", t.TempDir())
	err := run([]string{
		"--from-curl", "curl https://example.com",
		"--from-openapi", "spec.yaml",
	})
	if err == nil {
		t.Fatalf("expected conflict error")
	}
	if !strings.Contains(err.Error(), "choose either --from-curl or --from-openapi") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunRejectsInvalidCompareValue(t *testing.T) {
	t.Setenv("RESTERM_CONFIG_DIR", t.TempDir())
	err := run([]string{"--compare", "dev"})
	if err == nil {
		t.Fatalf("expected compare validation error")
	}
	if !strings.Contains(err.Error(), "invalid --compare value") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunRejectsReservedCompareBase(t *testing.T) {
	t.Setenv("RESTERM_CONFIG_DIR", t.TempDir())
	err := run([]string{"--compare", "dev,prod", "--compare-base", "$shared"})
	if err == nil {
		t.Fatalf("expected compare-base validation error")
	}
	if !strings.Contains(err.Error(), "invalid --compare-base value") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunRejectsMissingFile(t *testing.T) {
	t.Setenv("RESTERM_CONFIG_DIR", t.TempDir())
	fp := filepath.Join(t.TempDir(), "missing.http")
	err := run([]string{"--file", fp})
	if err == nil {
		t.Fatalf("expected read error")
	}
	if !strings.Contains(err.Error(), "read file") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunDispatchesHistorySubcommand(t *testing.T) {
	out, errOut, err := captureRunIO(t, func() error {
		return run([]string{"history", "-h"})
	})
	if err != nil {
		t.Fatalf("run history -h: %v", err)
	}
	if str.Trim(errOut) != "" {
		t.Fatalf("expected empty stderr, got %q", errOut)
	}
	if !strings.Contains(
		out,
		"Usage: resterm history <export|import|backup|stats|check|compact> [flags]",
	) {
		t.Fatalf("expected history usage in stdout, got %q", out)
	}
}

func TestRunInvalidFlagReturnsCodeTwo(t *testing.T) {
	err := run([]string{"--definitely-not-a-flag"})
	if err == nil {
		t.Fatalf("expected parse error")
	}
	if c := cli.ExitCode(err); c != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", c, err)
	}
	if !strings.Contains(err.Error(), "flag provided but not defined") {
		t.Fatalf("unexpected parse error: %v", err)
	}
}

func captureRunIO(t *testing.T, fn func() error) (string, string, error) {
	t.Helper()

	oldOut := os.Stdout
	oldErr := os.Stderr
	outR, outW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stdout: %v", err)
	}
	errR, errW, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe stderr: %v", err)
	}

	os.Stdout = outW
	os.Stderr = errW
	defer func() {
		os.Stdout = oldOut
		os.Stderr = oldErr
	}()

	runErr := fn()

	_ = outW.Close()
	_ = errW.Close()

	outData, outErr := io.ReadAll(outR)
	if outErr != nil {
		t.Fatalf("read stdout: %v", outErr)
	}
	errData, errErr := io.ReadAll(errR)
	if errErr != nil {
		t.Fatalf("read stderr: %v", errErr)
	}

	_ = outR.Close()
	_ = errR.Close()
	return string(outData), string(errData), runErr
}

```

## /cmd/resterm/run.go

```go path="/cmd/resterm/run.go" 
package main

import (
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"golang.org/x/term"

	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/httpclient"
	"github.com/unkn0wn-root/resterm/internal/restfile"
	"github.com/unkn0wn-root/resterm/internal/runner"
	"github.com/unkn0wn-root/resterm/internal/runview"
	"github.com/unkn0wn-root/resterm/internal/termcolor"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func handleRunSubcommand(args []string) (bool, error) {
	if len(args) == 0 || args[0] != "run" {
		return false, nil
	}
	if len(args) == 1 && cli.HasFileConflict("run") {
		return true, cli.CommandFileConflict(
			"resterm",
			"run",
			"pass a request file like `resterm run ./requests.http`",
		)
	}
	return true, runRun(args[1:])
}

func runRun(args []string) error {
	if len(args) > 0 {
		op := strings.ToLower(args[0])
		switch op {
		case "-h", "--help", "help":
			cmd := newRunCmd()
			printRunUsage(os.Stdout, cmd.fs)
			return nil
		}
	}

	cmd := newRunCmd()
	if err := cmd.parse(args); err != nil {
		if errors.Is(err, flag.ErrHelp) {
			return nil
		}
		return runExit(err, runExitCodeUsage)
	}
	return cmd.run()
}

type runExecFn func(context.Context, runner.Options) (*runner.Report, error)
type runClientFn func(string, cli.ExecFlags) (*httpclient.Client, func() error, error)

type runFormat string

const (
	runFmtAuto   runFormat = "auto"
	runFmtText   runFormat = "text"
	runFmtJSON   runFormat = "json"
	runFmtJUnit  runFormat = "junit"
	runFmtPretty runFormat = "pretty"
	runFmtRaw    runFormat = "raw"

	runExitCodeUsage    = 2
	runExitCodeCanceled = 130
)

type runUsageError struct {
	err error
}

func (e runUsageError) Error() string {
	if e.err == nil {
		return ""
	}
	return e.err.Error()
}

func (e runUsageError) Unwrap() error {
	return e.err
}

func isRunUsageError(err error) bool {
	var target runUsageError
	return errors.As(err, &target)
}

type runCmd struct {
	fs *flag.FlagSet

	exec cli.ExecFlags

	runFn     runExecFn
	newClient runClientFn

	in        io.Reader
	out       io.Writer
	stdinTTY  bool
	stdoutTTY bool
	lookupEnv termcolor.Lookup

	request        string
	workflow       string
	tag            string
	format         string
	color          string
	line           int
	artifactDir    string
	stateDir       string
	all            bool
	body           bool
	headers        bool
	profile        bool
	persistGlobals bool
	persistAuth    bool
	history        bool
}

func newRunCmd() *runCmd {
	cmd := &runCmd{
		exec:      cli.NewExecFlags(),
		runFn:     runner.RunContext,
		newClient: cli.NewExecClient,
		in:        os.Stdin,
		out:       os.Stdout,
		stdinTTY:  term.IsTerminal(int(os.Stdin.Fd())),
		stdoutTTY: term.IsTerminal(int(os.Stdout.Fd())),
		lookupEnv: os.LookupEnv,
		format:    "auto",
		color:     string(termcolor.ModeAuto),
	}
	fs := cli.NewFlagSet("run")
	cmd.fs = fs
	cmd.bind()
	fs.Usage = cmd.usage
	return cmd
}

func (c *runCmd) bind() {
	c.exec.Bind(c.fs)
	cli.StringVar(c.fs, &c.request, "request", "", "Run a named request")
	cli.StringVar(c.fs, &c.request, "r", "", "Run a named request")
	cli.StringVar(c.fs, &c.workflow, "workflow", "", "Run a named workflow")
	cli.StringVar(c.fs, &c.tag, "tag", "", "Run requests matching a tag")
	c.fs.BoolVar(&c.all, "all", false, "Run all requests in the file")
	c.fs.IntVar(&c.line, "line", 0, "Run the request or workflow at a specific line")
	cli.StringVar(
		c.fs,
		&c.format,
		"format",
		c.format,
		"Output format: auto, text, json, junit, pretty, raw",
	)
	cli.StringVar(c.fs, &c.color, "color", c.color, "Color for pretty output: auto, always, never")
	c.fs.BoolVar(&c.body, "body", false, "Print only the response body for a single request result")
	c.fs.BoolVar(&c.headers, "headers", false, "Include request and response headers in output")
	c.fs.BoolVar(&c.profile, "profile", false, "Force profile mode for the selected request")
	cli.StringVar(c.fs, &c.artifactDir, "artifact-dir", "", "Directory to write run artifacts")
	cli.StringVar(c.fs, &c.stateDir, "state-dir", "", "Directory for persisted run state")
	c.fs.BoolVar(
		&c.persistGlobals,
		"persist-globals",
		false,
		"Persist captured globals between invocations",
	)
	c.fs.BoolVar(
		&c.persistAuth,
		"persist-auth",
		false,
		"Persist cached auth state between invocations",
	)
	c.fs.BoolVar(&c.history, "history", false, "Persist run history to the state directory")
}

func (c *runCmd) parse(args []string) error {
	return c.fs.Parse(args)
}

func (c *runCmd) run() error {
	args := c.fs.Args()
	switch len(args) {
	case 0:
		return runExit(errors.New("request file path is required"), runExitCodeUsage)
	case 1:
	default:
		return runExit(
			fmt.Errorf("unexpected args: %s", strings.Join(args[1:], " ")),
			runExitCodeUsage,
		)
	}

	src, err := c.loadSource(args[0])
	if err != nil {
		return runExit(err, runExitCodeUsage)
	}
	doc, err := cli.ParseRunDoc(src)
	if err != nil {
		return runExit(err, runExitCodeUsage)
	}
	if err := c.resolveDefaultRequest(doc, src); err != nil {
		return err
	}
	if err := c.validateFormat(); err != nil {
		return runExit(err, runExitCodeUsage)
	}

	cfg, err := c.exec.Resolve(src.Path)
	if err != nil {
		return runExit(err, runExitCodeUsage)
	}

	client, shutdown, err := c.client()
	if err != nil && c.exec.TelemetryConfig(version).Enabled() {
		log.Printf("telemetry init error: %v", err)
	}
	if shutdown != nil {
		defer func() {
			if shutErr := shutdown(); shutErr != nil {
				log.Printf("telemetry shutdown: %v", shutErr)
			}
		}()
	}

	rep, err := c.execRun(context.Background(), src, cfg, client)
	if err != nil {
		if runner.IsUsageError(err) {
			return runExit(err, runExitCodeUsage)
		}
		return runExit(err, 1)
	}
	if err := c.writeReport(rep); err != nil {
		if isRunUsageError(err) {
			return runExit(err, runExitCodeUsage)
		}
		return runExit(fmt.Errorf("write report: %w", err), 1)
	}
	if rep.Success() {
		return nil
	}
	return cli.ExitErr{Code: 1}
}

func (c *runCmd) hasRequestSelector() bool {
	if c == nil {
		return false
	}
	switch {
	case c.all:
		return true
	case c.line > 0:
		return true
	case c.request != "":
		return true
	case c.tag != "":
		return true
	default:
		return false
	}
}

func (c *runCmd) loadSource(arg string) (cli.RunSource, error) {
	path := str.Trim(arg)
	switch path {
	case "":
		return cli.RunSource{}, errors.New("run: request file path is required")
	case "-":
		cfg, err := c.exec.Resolve(cli.StdinRunPath(c.exec.Workspace))
		if err != nil {
			return cli.RunSource{}, err
		}
		data, err := io.ReadAll(c.stdin())
		if err != nil {
			return cli.RunSource{}, fmt.Errorf("run: read stdin: %w", err)
		}
		return cli.RunSource{Path: cfg.FilePath, Data: data, Stdin: true}, nil
	default:
		cfg, err := c.exec.Resolve(path)
		if err != nil {
			return cli.RunSource{}, err
		}
		data, err := os.ReadFile(cfg.FilePath)
		if err != nil {
			return cli.RunSource{}, fmt.Errorf("read file: %w", err)
		}
		return cli.RunSource{Path: cfg.FilePath, Data: data}, nil
	}
}

func (c *runCmd) resolveDefaultRequest(doc *restfile.Document, src cli.RunSource) error {
	if c == nil || c.hasRequestSelector() || c.hasWorkflowSelector() {
		return nil
	}
	reqs := cli.BuildRunRequestChoices(doc)
	switch len(reqs) {
	case 0:
		return nil
	case 1:
		c.line = reqs[0].Line
		return nil
	}

	if src.Stdin || !c.stdinTTY || !c.stdoutTTY {
		if err := cli.WriteRunRequestChoices(c.stdout(), src.Path, reqs); err != nil {
			return fmt.Errorf("run: write request list: %w", err)
		}
		return cli.ExitErr{
			Err: errors.New(
				"run: multiple requests found; use --request, --tag, --all, or --line",
			),
			Code: runExitCodeUsage,
		}
	}

	ch, err := cli.PromptRunRequestChoice(
		c.stdin(),
		c.stdout(),
		src.Path,
		reqs,
		cli.RunRequestPromptOptions{
			TTY:   c.stdinTTY && c.stdoutTTY,
			Color: c.prettyColor(),
		},
	)
	if err != nil {
		if errors.Is(err, cli.ErrRunRequestChoiceCanceled) {
			return cli.ExitErr{Code: runExitCodeCanceled}
		}
		return cli.ExitErr{Err: fmt.Errorf("run: %w", err), Code: runExitCodeUsage}
	}
	c.line = ch.Line
	return nil
}

func (c *runCmd) client() (*httpclient.Client, func() error, error) {
	if c != nil && c.newClient != nil {
		return c.newClient(version, c.exec)
	}
	return cli.NewExecClient(version, c.exec)
}

func (c *runCmd) execRun(
	ctx context.Context,
	src cli.RunSource,
	cfg cli.ExecConfig,
	client *httpclient.Client,
) (*runner.Report, error) {
	runFn := runner.RunContext
	if c != nil && c.runFn != nil {
		runFn = c.runFn
	}
	return runFn(ctx, c.runOptions(src, cfg, client))
}

func (c *runCmd) runOptions(
	src cli.RunSource,
	cfg cli.ExecConfig,
	client *httpclient.Client,
) runner.Options {
	return runner.Options{
		Version:         version,
		FilePath:        src.Path,
		FileContent:     src.Data,
		WorkspaceRoot:   cfg.Workspace,
		Recursive:       cfg.Recursive,
		ArtifactDir:     c.artifactDir,
		StateDir:        c.stateDir,
		PersistGlobals:  c.persistGlobals,
		PersistAuth:     c.persistAuth,
		History:         c.history,
		EnvSet:          cfg.EnvSet,
		EnvName:         cfg.EnvName,
		EnvironmentFile: cfg.EnvFile,
		CompareTargets:  cfg.CompareTargets,
		CompareBase:     cfg.CompareBase,
		Profile:         c.profile,
		HTTPOptions:     cfg.HTTPOpts,
		GRPCOptions:     cfg.GRPCOpts,
		Client:          client,
		Select:          c.selector(),
	}
}

func (c *runCmd) selector() runner.Select {
	if c == nil {
		return runner.Select{}
	}
	return runner.Select{
		Request:  c.request,
		Workflow: c.workflow,
		Tag:      c.tag,
		All:      c.all,
		Line:     c.line,
	}
}

func (c *runCmd) writeReport(rep *runner.Report) error {
	if rep == nil {
		return errors.New("empty report")
	}
	if c.body {
		return c.writeBody(rep)
	}
	color := c.prettyColor()
	switch c.reportFormat() {
	case runFmtAuto:
		if runview.CanRenderRequest(rep) {
			return c.writeOutput(func(w io.Writer) error {
				return runview.Write(w, rep, runview.Options{
					Mode:    runview.ModePretty,
					Headers: c.headers,
					Color:   color,
				})
			})
		}
		return c.writeOutput(func(w io.Writer) error {
			if color.Enabled {
				fmtRep := runner.NormalizeReport(rep)
				return cli.WriteTextStyled(w, &fmtRep, color)
			}
			return rep.WriteText(w)
		})
	case runFmtText:
		return c.writeOutput(func(w io.Writer) error {
			return rep.WriteText(w)
		})
	case runFmtJSON:
		return c.writeOutput(func(w io.Writer) error {
			return rep.WriteJSON(w)
		})
	case runFmtJUnit:
		return c.writeOutput(func(w io.Writer) error {
			return rep.WriteJUnit(w)
		})
	case runFmtPretty:
		if !runview.CanRenderRequest(rep) {
			return runUsageError{
				err: errors.New("--format pretty requires exactly one request result"),
			}
		}
		return c.writeOutput(func(w io.Writer) error {
			return runview.Write(w, rep, runview.Options{
				Mode:    runview.ModePretty,
				Headers: c.headers,
				Color:   color,
			})
		})
	case runFmtRaw:
		if !runview.CanRenderRequest(rep) {
			return runUsageError{
				err: errors.New("--format raw requires exactly one request result"),
			}
		}
		return c.writeOutput(func(w io.Writer) error {
			return runview.Write(w, rep, runview.Options{
				Mode:    runview.ModeRaw,
				Headers: c.headers,
			})
		})
	default:
		return runUsageError{err: fmt.Errorf("unsupported --format %q", c.format)}
	}
}

func (c *runCmd) stdin() io.Reader {
	if c != nil && c.in != nil {
		return c.in
	}
	return os.Stdin
}

func (c *runCmd) stdout() io.Writer {
	if c != nil && c.out != nil {
		return c.out
	}
	return os.Stdout
}

func (c *runCmd) hasWorkflowSelector() bool {
	return c != nil && c.workflow != ""
}

func (c *runCmd) validateFormat() error {
	if _, err := termcolor.ParseMode(c.color); err != nil {
		return fmt.Errorf("unsupported --color %q", c.color)
	}
	format := c.reportFormat()
	if format == "" {
		return fmt.Errorf("unsupported --format %q", c.format)
	}
	if c.body {
		switch format {
		case runFmtAuto, runFmtPretty, runFmtRaw:
		default:
			return errors.New("--body can only be combined with --format auto, pretty, or raw")
		}
	}
	return nil
}

func (c *runCmd) reportFormat() runFormat {
	if c == nil {
		return ""
	}
	switch strings.ToLower(c.format) {
	case "", string(runFmtAuto):
		return runFmtAuto
	case string(runFmtText):
		return runFmtText
	case string(runFmtJSON):
		return runFmtJSON
	case string(runFmtJUnit):
		return runFmtJUnit
	case string(runFmtPretty):
		return runFmtPretty
	case string(runFmtRaw):
		return runFmtRaw
	default:
		return ""
	}
}

func (c *runCmd) writeBody(rep *runner.Report) error {
	if !runview.CanRenderRequest(rep) {
		return runUsageError{
			err: errors.New("--body requires exactly one request result"),
		}
	}
	mode := runview.ModeRaw
	if c.reportFormat() == runFmtPretty {
		mode = runview.ModePretty
	}
	color := termcolor.Config{}
	if mode == runview.ModePretty {
		color = c.prettyColor()
	}
	return c.writeOutput(func(w io.Writer) error {
		return runview.WriteBody(w, rep, runview.BodyOptions{Mode: mode, Color: color})
	})
}

func (c *runCmd) writeOutput(fn func(io.Writer) error) error {
	if fn == nil {
		return nil
	}
	var buf bytes.Buffer
	if err := fn(&buf); err != nil {
		return err
	}
	out := buf.Bytes()
	if len(out) == 0 || out[len(out)-1] != '\n' {
		out = append(out, '\n')
	}
	_, err := c.stdout().Write(out)
	return err
}

func (c *runCmd) prettyColor() termcolor.Config {
	if c == nil {
		return termcolor.Config{}
	}
	mode, err := termcolor.ParseMode(c.color)
	if err != nil {
		return termcolor.Config{}
	}
	return termcolor.Resolve(termcolor.Input{
		Mode:   mode,
		TTY:    c.stdoutTTY,
		Lookup: c.lookupEnv,
	})
}

func (c *runCmd) usage() {
	printRunUsage(os.Stderr, c.fs)
}

func runExit(err error, code int) error {
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "run: ") {
		return cli.ExitErr{Err: err, Code: code}
	}
	return cli.ExitErr{Err: fmt.Errorf("run: %w", err), Code: code}
}

func printRunUsage(w io.Writer, fs *flag.FlagSet) {
	if w == nil || fs == nil {
		return
	}
	if _, err := fmt.Fprintf(w, "Usage: resterm run [flags] <file|->\n\n"); err != nil {
		return
	}
	if _, err := fmt.Fprintln(w, "Flags:"); err != nil {
		return
	}
	out := fs.Output()
	fs.SetOutput(w)
	defer fs.SetOutput(out)
	fs.PrintDefaults()
}

```

## /cmd/resterm/run_test.go

```go path="/cmd/resterm/run_test.go" 
package main

import (
	"bytes"
	"context"
	"errors"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/charmbracelet/x/ansi"
	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/httpclient"
	"github.com/unkn0wn-root/resterm/internal/runner"
	"github.com/unkn0wn-root/resterm/internal/termcolor"
	str "github.com/unkn0wn-root/resterm/internal/util"
)

func TestHandleRunSubcommandNotMatched(t *testing.T) {
	handled, err := handleRunSubcommand([]string{"history"})
	if err != nil {
		t.Fatalf("expected nil error, got %v", err)
	}
	if handled {
		t.Fatalf("expected not handled")
	}
}

func TestHandleRunSubcommandAmbiguousFile(t *testing.T) {
	dir := t.TempDir()
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(cwd)
	})
	if err := os.Chdir(dir); err != nil {
		t.Fatalf("chdir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(dir, "run"), []byte("data"), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	handled, err := handleRunSubcommand([]string{"run"})
	if !handled {
		t.Fatalf("expected run to be handled")
	}
	if err == nil {
		t.Fatalf("expected ambiguity error")
	}
}

func TestRunRunRequiresFile(t *testing.T) {
	err := runRun(nil)
	if err == nil {
		t.Fatalf("expected missing file error")
	}
	if !strings.Contains(err.Error(), "request file path is required") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunRunHelpFlagShowsUsage(t *testing.T) {
	stdout, stderr, err := captureRunIO(t, func() error {
		return runRun([]string{"-h"})
	})
	if err != nil {
		t.Fatalf("help flag: %v", err)
	}
	if str.Trim(stderr) != "" {
		t.Fatalf("expected empty stderr on help flag, got %q", stderr)
	}
	if !strings.Contains(stdout, "Usage: resterm run [flags] <file|->") {
		t.Fatalf("expected run usage in stdout, got %q", stdout)
	}
	if !strings.Contains(stdout, "-request") {
		t.Fatalf("expected request flag in stdout, got %q", stdout)
	}
}

func TestRunRunFlagErrorsHaveCommandPrefix(t *testing.T) {
	err := runRun([]string{"--bad"})
	if err == nil {
		t.Fatalf("expected parse error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "run:") {
		t.Fatalf("expected run prefix, got %q", err.Error())
	}
}

func TestRunDispatchesRunSubcommand(t *testing.T) {
	stdout, stderr, err := captureRunIO(t, func() error {
		return run([]string{"run", "-h"})
	})
	if err != nil {
		t.Fatalf("run dispatch: %v", err)
	}
	if str.Trim(stderr) != "" {
		t.Fatalf("expected empty stderr, got %q", stderr)
	}
	if !strings.Contains(stdout, "Usage: resterm run [flags] <file|->") {
		t.Fatalf("expected run usage in stdout, got %q", stdout)
	}
}

func TestRunCmdLoadSourceFromStdinUsesSyntheticPath(t *testing.T) {
	cmd := newRunCmd()
	cmd.exec.Workspace = t.TempDir()
	cmd.in = strings.NewReader("GET https://example.com\n")

	src, err := cmd.loadSource("-")
	if err != nil {
		t.Fatalf("loadSource(-): %v", err)
	}
	if !src.Stdin {
		t.Fatalf("expected stdin source")
	}
	want := cli.StdinRunPath(cmd.exec.Workspace)
	if src.Path != want {
		t.Fatalf("unexpected stdin path: got %q want %q", src.Path, want)
	}
}

func TestParseRunDocReturnsParseError(t *testing.T) {
	_, err := cli.ParseRunDoc(cli.RunSource{
		Path: "broken.http",
		Data: []byte("# @k8s namespace=default\nGET http://example.com\n"),
	})
	if err == nil {
		t.Fatalf("expected parse error")
	}
	if !strings.Contains(err.Error(), "parse error") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdSingleRequestSetsDefaultLine(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	cmd.stdinTTY = false
	cmd.stdoutTTY = false
	cmd.newClient = stubRunClient
	var got runner.Options
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		got = opts
		return stubRunReport(true), nil
	}

	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err != nil {
		t.Fatalf("run: %v", err)
	}
	if cmd.line != 1 {
		t.Fatalf("expected selected line 1, got %d", cmd.line)
	}
	if got.Select.Line != 1 {
		t.Fatalf("expected runner select line 1, got %+v", got.Select)
	}
}

func TestRunCmdNonInteractiveListsRequestsAndReturnsUsageExit(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "many.http")
	src := strings.Join([]string{
		"### One",
		"# @name one",
		"GET https://example.com/one",
		"",
		"### Two",
		"# @name two",
		"GET https://example.com/two",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdinTTY = false
	cmd.stdoutTTY = false

	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected usage exit")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(out.String(), "Multiple requests found in") {
		t.Fatalf("expected list output, got %q", out.String())
	}
	if !strings.Contains(out.String(), "GET one") || !strings.Contains(out.String(), "GET two") {
		t.Fatalf("expected request list, got %q", out.String())
	}
}

func TestRunCmdInteractiveSelectsRequestLineBeforeExecution(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "many.http")
	src := strings.Join([]string{
		"### One",
		"# @name one",
		"GET https://example.com/one",
		"",
		"### Two",
		"# @name two",
		"GET https://example.com/two",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.in = strings.NewReader("2\n")
	cmd.out = &out
	cmd.stdinTTY = true
	cmd.stdoutTTY = true
	cmd.newClient = stubRunClient
	var got runner.Options
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		got = opts
		return stubRunReport(true), nil
	}

	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err != nil {
		t.Fatalf("run: %v", err)
	}
	if cmd.line != 6 {
		t.Fatalf("expected selected line 6, got %d", cmd.line)
	}
	if got.Select.Line != 6 {
		t.Fatalf("expected runner select line 6, got %+v", got.Select)
	}
	gotOut := ansi.Strip(out.String())
	if !strings.Contains(gotOut, "Select request [1-2]:") {
		t.Fatalf("expected text prompt fallback, got %q", gotOut)
	}
}

func TestRunCmdMapsRunnerUsageErrorsToExitCodeTwo(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "many.http")
	src := strings.Join([]string{
		"### One",
		"# @name one",
		"GET https://example.com/one",
		"",
		"### Two",
		"# @name two",
		"GET https://example.com/two",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	cmd.newClient = stubRunClient
	if err := cmd.parse([]string{"--request", "one", "--tag", "two", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected usage error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "cannot be combined") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdMapsFailedReportToExitCodeOne(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(false), nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected failure exit")
	}
	if code := cli.ExitCode(err); code != 1 {
		t.Fatalf("expected exit code 1, got %d (err=%v)", code, err)
	}
	if msg := str.Trim(err.Error()); msg != "" {
		t.Fatalf("expected silent failure message, got %q", msg)
	}
}

func TestRunCmdFailedOutputEndsWithNewline(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(false), nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected failure exit")
	}
	if !strings.HasSuffix(out.String(), "\n") {
		t.Fatalf("expected output to end with newline, got %q", out.String())
	}
}

func TestRunCmdRejectsUnsupportedFormat(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	if err := cmd.parse([]string{"--format", "yaml", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected format error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "unsupported --format") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdRejectsUnsupportedColor(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	if err := cmd.parse([]string{"--color", "blue", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected color error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "unsupported --color") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdAutoUsesHumanViewForSingleRequest(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	got := out.String()
	if !strings.Contains(got, "Request: GET https://example.com/one") {
		t.Fatalf("expected human request summary, got %q", got)
	}
	if !strings.Contains(got, "Body:\n{\n  message: \"ok\"\n}") {
		t.Fatalf("expected pretty body view, got %q", got)
	}
	if strings.Contains(got, "Summary: total=") {
		t.Fatalf("expected auto human view, got report text %q", got)
	}
}

func TestRunCmdAutoColorsPrettyOutputForTTY(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdoutTTY = true
	cmd.lookupEnv = envLookup(map[string]string{"TERM": "xterm-256color"})
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	got := out.String()
	if !strings.Contains(got, "\x1b[") {
		t.Fatalf("expected colored pretty output, got %q", got)
	}
	if plain := ansi.Strip(got); !strings.Contains(plain, "Request: GET https://example.com/one") {
		t.Fatalf("expected stripped output to preserve text, got %q", plain)
	}
}

func TestRunCmdAutoColorsWorkflowOutputForTTY(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "workflow.http")
	src := strings.Join([]string{
		"# @workflow sample-order",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdoutTTY = true
	cmd.lookupEnv = envLookup(map[string]string{"TERM": "xterm-256color"})
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return &runner.Report{
			Results: []runner.Result{{
				Kind:     runner.ResultKindWorkflow,
				Name:     "sample-order",
				Method:   "WORKFLOW",
				Passed:   true,
				Duration: time.Second,
				Steps: []runner.StepResult{{
					Name:     "Login",
					Passed:   true,
					Duration: 250 * time.Millisecond,
					Response: &httpclient.Response{
						Status:     "200 OK",
						StatusCode: 200,
					},
				}},
			}},
			Total:  1,
			Passed: 1,
		}, nil
	}
	if err := cmd.parse([]string{"--workflow", "sample-order", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	got := out.String()
	if !strings.Contains(got, "\x1b[") {
		t.Fatalf("expected colored workflow output, got %q", got)
	}
	plain := ansi.Strip(got)
	if !strings.Contains(plain, "PASS WORKFLOW sample-order") {
		t.Fatalf("expected stripped workflow output to preserve text, got %q", plain)
	}
	if !strings.Contains(plain, "1. PASS Login") {
		t.Fatalf("expected stripped workflow step output to preserve text, got %q", plain)
	}
}

func TestRunCmdAutoKeepsPipedPrettyPlain(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdoutTTY = false
	cmd.lookupEnv = envLookup(map[string]string{"TERM": "xterm-256color"})
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	if strings.Contains(out.String(), "\x1b[") {
		t.Fatalf("expected plain output when stdout is not a tty, got %q", out.String())
	}
}

func TestRunCmdColorNeverDisablesPrettyColor(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdoutTTY = true
	cmd.lookupEnv = envLookup(map[string]string{"TERM": "xterm-256color"})
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{"--color", "never", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	if strings.Contains(out.String(), "\x1b[") {
		t.Fatalf("expected --color never to disable ansi, got %q", out.String())
	}
}

func TestRunCmdAutoFallsBackToTextForMultiResult(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "many.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return &runner.Report{
			Results: []runner.Result{
				stubRunReport(true).Results[0],
				stubRunReport(true).Results[0],
			},
			Total:  2,
			Passed: 2,
		}, nil
	}
	if err := cmd.parse([]string{"--all", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	got := out.String()
	if !strings.Contains(got, "Running 2 request(s)") {
		t.Fatalf("expected text report fallback, got %q", got)
	}
}

func TestRunCmdPrettyRequiresSingleRequestResult(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "wf.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return &runner.Report{
			Results: []runner.Result{{
				Kind:     runner.ResultKindWorkflow,
				Name:     "wf",
				Method:   "WORKFLOW",
				Passed:   true,
				Duration: time.Second,
			}},
			Total:  1,
			Passed: 1,
		}, nil
	}
	if err := cmd.parse([]string{"--format", "pretty", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected format shape error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "requires exactly one request result") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdRawUsesHumanRawView(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{"--format", "raw", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}
	if !strings.Contains(out.String(), "Raw Body:\n{\n  \"message\": \"ok\"\n}") {
		t.Fatalf("expected raw body output, got %q", out.String())
	}
}

func TestRunCmdBodyDefaultsToRawBodyOnly(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse([]string{"--body", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	got := out.String()
	if strings.Contains(got, "Name:") || strings.Contains(got, "Body:") {
		t.Fatalf("expected body-only output, got %q", got)
	}
	if got != "{\n  \"message\": \"ok\"\n}\n" {
		t.Fatalf("unexpected body-only output %q", got)
	}
}

func TestRunCmdBodyKeepsExitCodeWithoutFailureBanner(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(false), nil
	}
	if err := cmd.parse([]string{"--body", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected failure exit")
	}
	if code := cli.ExitCode(err); code != 1 {
		t.Fatalf("expected exit code 1, got %d (err=%v)", code, err)
	}
	if msg := str.Trim(err.Error()); msg != "" {
		t.Fatalf("expected silent failure message, got %q", msg)
	}
	if out.String() != "{\n  \"message\": \"ok\"\n}\n" {
		t.Fatalf("unexpected body output %q", out.String())
	}
}

func TestRunCmdBodyPrettyUsesColorFlag(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.stdoutTTY = false
	cmd.lookupEnv = envLookup(map[string]string{"TERM": "xterm-256color"})
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		return stubRunReport(true), nil
	}
	if err := cmd.parse(
		[]string{"--body", "--format", "pretty", "--color", "always", file},
	); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}

	if !strings.Contains(out.String(), "\x1b[") {
		t.Fatalf("expected colored pretty body output, got %q", out.String())
	}
	if got := ansi.Strip(out.String()); got != "{\n  message: \"ok\"\n}\n" {
		t.Fatalf("unexpected stripped body output %q", got)
	}
}

func TestRunCmdBodyRejectsTextFormat(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	cmd := newRunCmd()
	if err := cmd.parse([]string{"--body", "--format", "text", file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	err := cmd.run()
	if err == nil {
		t.Fatalf("expected format/body error")
	}
	if code := cli.ExitCode(err); code != 2 {
		t.Fatalf("expected exit code 2, got %d (err=%v)", code, err)
	}
	if !strings.Contains(err.Error(), "--body can only be combined") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestRunCmdShowsUnresolvedTemplateWarning(t *testing.T) {
	dir := t.TempDir()
	file := filepath.Join(dir, "one.http")
	src := strings.Join([]string{
		"# @name one",
		"GET https://example.com/one",
		"",
	}, "\n")
	if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
		t.Fatalf("write file: %v", err)
	}

	var out bytes.Buffer
	cmd := newRunCmd()
	cmd.out = &out
	cmd.newClient = stubRunClient
	cmd.runFn = func(_ context.Context, opts runner.Options) (*runner.Report, error) {
		rep := stubRunReport(true)
		rep.Results[0].SetUnresolvedTemplateVars([]string{"reporting.token", "trace.id"})
		return rep, nil
	}
	if err := cmd.parse([]string{file}); err != nil {
		t.Fatalf("parse: %v", err)
	}
	if err := cmd.run(); err != nil {
		t.Fatalf("run: %v", err)
	}
	got := out.String()
	if !strings.Contains(got, "Warnings:") {
		t.Fatalf("expected warnings section, got %q", got)
	}
	if !strings.Contains(got, "reporting.token, trace.id") {
		t.Fatalf("expected unresolved template warning, got %q", got)
	}
}

func TestPromptRunRequestChoiceRetriesInvalidInput(t *testing.T) {
	choices := []cli.RunRequestChoice{
		{Line: 3, Label: "GET one"},
		{Line: 7, Label: "GET two"},
	}
	var out bytes.Buffer
	got, err := cli.PromptRunRequestChoice(
		strings.NewReader("0\nnope\n2\n"),
		&out,
		"many.http",
		choices,
		cli.RunRequestPromptOptions{},
	)
	if err != nil {
		t.Fatalf("promptRunRequestChoice: %v", err)
	}
	if got.Line != 7 {
		t.Fatalf("expected second choice, got %+v", got)
	}
	if n := strings.Count(out.String(), "Enter a number between 1 and 2."); n != 2 {
		t.Fatalf("expected two retry notices, got %d in %q", n, out.String())
	}
}

func TestPromptRunRequestChoiceEOF(t *testing.T) {
	_, err := cli.PromptRunRequestChoice(
		strings.NewReader(""),
		&bytes.Buffer{},
		"many.http",
		[]cli.RunRequestChoice{
			{Line: 3, Label: "GET one"},
			{Line: 7, Label: "GET two"},
		},
		cli.RunRequestPromptOptions{},
	)
	if !errors.Is(err, io.EOF) {
		t.Fatalf("expected EOF, got %v", err)
	}
}

func stubRunClient(string, cli.ExecFlags) (*httpclient.Client, func() error, error) {
	return httpclient.NewClient(nil), nil, nil
}

func envLookup(vals map[string]string) termcolor.Lookup {
	return func(key string) (string, bool) {
		v, ok := vals[key]
		return v, ok
	}
}

func stubRunReport(pass bool) *runner.Report {
	item := runner.Result{
		Kind:   runner.ResultKindRequest,
		Name:   "one",
		Method: "GET",
		Target: "https://example.com/one",
		Response: &httpclient.Response{
			Status:       "200 OK",
			StatusCode:   200,
			Headers:      http.Header{"Content-Type": {"application/json"}},
			Body:         []byte(`{"message":"ok"}`),
			Duration:     10 * time.Millisecond,
			EffectiveURL: "https://example.com/one",
			ReqMethod:    "GET",
		},
		Passed: pass,
	}
	rep := &runner.Report{
		Results: []runner.Result{item},
		Total:   1,
	}
	if pass {
		rep.Passed = 1
		return rep
	}
	rep.Failed = 1
	return rep
}

```

## /cmd/resterm/shared_env_test.go

```go path="/cmd/resterm/shared_env_test.go" 
package main

import (
	"strings"
	"testing"

	"github.com/unkn0wn-root/resterm/internal/cli"
	"github.com/unkn0wn-root/resterm/internal/runcheck"
)

func TestParseCompareTargetsRejectsShared(t *testing.T) {
	_, err := cli.ParseCompareTargets("dev $shared")
	if err == nil {
		t.Fatalf("expected parseCompareTargets to reject $shared")
	}
	if !strings.Contains(err.Error(), "reserved for shared defaults") {
		t.Fatalf("expected reserved-name error, got %v", err)
	}
}

func TestValidateConcreteEnvironmentSelection(t *testing.T) {
	if err := runcheck.ValidateConcreteEnvironment("dev", "--env"); err != nil {
		t.Fatalf("expected dev to be accepted, got %v", err)
	}
	if err := runcheck.ValidateConcreteEnvironment("", "--env"); err != nil {
		t.Fatalf("expected empty value to be accepted, got %v", err)
	}

	err := runcheck.ValidateConcreteEnvironment("$shared", "--env")
	if err == nil {
		t.Fatalf("expected $shared to be rejected")
	}
	if !strings.Contains(err.Error(), "reserved for shared defaults") {
		t.Fatalf("expected reserved-name error, got %v", err)
	}
}

```

## /cmd/resterm/update_cli_test.go

```go path="/cmd/resterm/update_cli_test.go" 
package main

import (
	"context"
	"errors"
	"net/http"
	"testing"

	"github.com/unkn0wn-root/resterm/internal/update"
)

func TestCLIUpdaterCheckDev(t *testing.T) {
	cl, err := update.NewClient(&http.Client{}, updateRepo)
	if err != nil {
		t.Fatalf("new client: %v", err)
	}
	u := newCLIUpdater(cl, "dev")
	if _, _, err := u.check(context.Background()); !errors.Is(err, errUpdateDisabled) {
		t.Fatalf("expected errUpdateDisabled, got %v", err)
	}
}

```

## /headless/doc.go

```go path="/headless/doc.go" 
// Package headless runs Resterm requests and workflows without the TUI.
//
// The package exposes a library API for CI/CD, automation, and other
// non-interactive integrations. Choose the entrypoint based on how you run:
//
//   - Call Run for a one-shot execution.
//   - Call Build and then RunPlan when you want to prepare once and reuse the
//     same validated plan across retries, repeated runs, or concurrent calls.
//
// The primary workflow is:
//
//  1. Construct Options.
//  2. Call Build to prepare and validate a reusable Plan.
//  3. Call RunPlan to execute the prepared plan, or call Run for a one-shot run.
//  4. Inspect the returned Report, Result, and Step values.
//  5. Serialize the report with Report.Encode using JSON, JUnit, or Text.
//
// WriteJSON, WriteJUnit, and WriteText remain available as helpers
// over Encode. Programmatic format selection is available through the Format
// constants JSON, JUnit, and Text, and ParseFormat converts user-provided names
// into a Format value.
//
// Invalid library inputs are reported as UsageError values. Use IsUsageError to
// classify them, and errors.Is to match specific sentinels such as ErrNoSourcePath
// or ErrTooFewTargets.
package headless

```

## /headless/encode.go

```go path="/headless/encode.go" 
package headless

import (
	"fmt"
	"io"

	"github.com/unkn0wn-root/resterm/internal/runfmt"
)

// Encode writes the report in the given format.
func (r *Report) Encode(w io.Writer, f Format) error {
	if r == nil {
		return ErrNilReport
	}
	if w == nil {
		return ErrNilWriter
	}

	rep := toFormatReport(r)
	switch f {
	case JSON:
		return runfmt.WriteJSON(w, &rep)
	case JUnit:
		return runfmt.WriteJUnit(w, &rep)
	case Text:
		return runfmt.WriteText(w, &rep)
	default:
		return fmt.Errorf("headless: unsupported format %d", int(f))
	}
}

```

## /headless/errors.go

```go path="/headless/errors.go" 
package headless

import "errors"

// UsageError reports invalid input or options passed to the headless API.
type UsageError struct {
	err error
}

var (
	// ErrNoSourcePath reports that Options.Source.Path was empty.
	ErrNoSourcePath = errors.New("headless: source path is required")

	// ErrNilContext reports that a nil context was passed to Run or RunPlan.
	ErrNilContext = errors.New("headless: nil context")

	// ErrTooFewTargets reports that compare mode was enabled without enough targets.
	ErrTooFewTargets = errors.New("headless: compare requires at least two target environments")

	// ErrInvalidPlan reports that a zero-value or otherwise invalid Plan was used.
	ErrInvalidPlan = errors.New("headless: invalid plan")

	// ErrNilReport reports an attempt to encode a nil report.
	ErrNilReport = errors.New("headless: nil report")

	// ErrNilWriter reports an attempt to write to a nil writer.
	ErrNilWriter = errors.New("headless: nil writer")
)

func (e UsageError) Error() string {
	if e.err == nil {
		return "usage error"
	}
	return e.err.Error()
}

func (e UsageError) Unwrap() error {
	return e.err
}

// IsUsageError reports whether err contains a UsageError.
func IsUsageError(err error) bool {
	var target UsageError
	return errors.As(err, &target)
}

```

## /headless/format.go

```go path="/headless/format.go" 
package headless

import (
	"fmt"

	str "github.com/unkn0wn-root/resterm/internal/util"
)

type Format int

const (
	JSON Format = iota
	JUnit
	Text
)

func (f Format) String() string {
	switch f {
	case JSON:
		return "json"
	case JUnit:
		return "junit"
	case Text:
		return "text"
	default:
		return fmt.Sprintf("format(%d)", int(f))
	}
}

func ParseFormat(s string) (Format, error) {
	switch str.LowerTrim(s) {
	case JSON.String():
		return JSON, nil
	case JUnit.String():
		return JUnit, nil
	case Text.String():
		return Text, nil
	default:
		return 0, fmt.Errorf("headless: unknown format %q", s)
	}
}

```

## /headless/json.go

```go path="/headless/json.go" 
package headless

import (
	"io"
)

// WriteJSON writes r as indented JSON.
func (r *Report) WriteJSON(w io.Writer) error {
	return r.Encode(w, JSON)
}

```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!