Skip to content

feat(cli): M5 tooling integration — mitm/tunnel/tool/translator/media (30 cmds)#14

Merged
quangdang46 merged 1 commit into
mainfrom
devin/1778566184-cli-m5-tooling-integration
May 12, 2026
Merged

feat(cli): M5 tooling integration — mitm/tunnel/tool/translator/media (30 cmds)#14
quangdang46 merged 1 commit into
mainfrom
devin/1778566184-cli-m5-tooling-integration

Conversation

@quangdang46
Copy link
Copy Markdown
Owner

Summary

Implements the M5 milestone from todo-openproxy.md~30 runtime CLI commands that talk to the running server's HTTP API. Each command supports both human and --robot (openproxy.v1.* envelope) output, and returns exit code 6 when the server is unreachable.

New modules (under src/cli/)

Module Commands Endpoints
mitm.rs (~8) status, start, stop, cert generate, cert path, config get, config set, config apply /api/mitm/*, /api/mitm-config
tunnel_rt.rs (~7, extends M1 stub) enable <p>, disable <p>, tailscale install/login/check/enable/disable /api/tunnel/*, /api/tunnel/tailscale-*
tool.rs (~8) list, show, apply (with --dry-run), revert, execute, run, doc, antigravity-mitm enable/disable /api/cli-tools/*
translator.rs (~6) formats, translate (step2+step3), send, preset list/save/load /api/translator/*
media.rs (~13) providers list/add/edit/delete, combo list/create, tts voices/speak, stt transcribe, embed, image generate, search, web fetch /api/media-providers/*, /api/combos, /v1/{audio,embeddings,images,search,web}

media tts speak is the only command that emits binary to stdout (mp3/wav bytes from /v1/audio/speech); the robot envelope is still written to stdout first, followed by the raw audio.

Plumbing

  • 5 new HTTP helpers on Runtime in src/cli/runtime.rs: put_json, patch_json, delete_json, post_json_bytes (binary), plus a small refactor to share decode_json.
  • New top-level subcommands wired into Cli::Command (Mitm, Tool, Translator, Media) plus three new TunnelCmd variants (Enable, Disable, Tailscale) that route through tunnel_rt::run while keeping the legacy local Start/Stop/Status working against the in-process TunnelManager.
  • Dispatcher updates in both src/cli/mod.rs and src/main.rs so each new command path resolves ResolvedConfig and calls <module>::run(...).

Tests

  • tests/cli_m5_robot_envelopes.rs15 integration tests (assert_cmd + wiremock) covering one happy path per command group; verifies the openproxy.v1.* envelope shape, exit code, and that media tts speak writes raw bytes to stdout.
  • cargo test --lib386 pass.
  • cargo clippy --all-targets --no-deps → no new warnings in any M5 file.

Conventions kept from M3/M4

  • Each handler is async fn run_x(...) -> anyhow::Result<i32>, returns rt_error_to_exit(ctx, e) on runtime failures so the existing server_unreachable → exit 6 contract is preserved.
  • Robot envelopes use the stable openproxy.v1.<area>.<verb> schema (e.g. openproxy.v1.media.tts.speak, openproxy.v1.mitm.config.apply).
  • media web fetch <URL> uses a positional URL arg (named page in the enum) on purpose — using --url would collide with the global --url server-override flag.

Review & Testing Checklist for Human

  • Sanity check media tts speak byte streaming. Confirm the binary stdout payload survives the robot envelope being printed first (envelope ends with a single \n, audio bytes follow). Try piping into a file and playing it back.
  • tool apply body shape. build_apply_body writes a different JSON shape per tool (claude uses env: { ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_MODEL }, others use { baseUrl, apiKey, model }). Verify these still match what the server's *-settings endpoints expect for each integration.
  • Tunnel command UX. Existing users running openproxy tunnel start still hit the local M1 stub; the new tunnel enable/disable and tunnel tailscale * go to the server. Make sure the split feels right and the help text isn't confusing.
  • mitm config apply --from-file - does a single bulk PUT. If you'd rather it do per-key merge semantics, flag it before merging.
  • Run a smoke test end-to-end against a live server: openproxy server start --detach, then walk through one command from each group (mitm status, tool list, translator formats, media providers list, tunnel tailscale check).

Notes

  • No backend changes — every route already exists from earlier milestones.
  • src/cli/schema.rs is intentionally untouched. The new resources (mitm-config, media-provider, translator-preset, ...) can be added there as part of M6 (schema freeze).
  • M6 + cleanup are still pending per todo-openproxy.md.

… (30 cmds)

Implement the M5 milestone from todo-openproxy.md — ~30 runtime CLI commands
that talk to the running server's HTTP API. Each command supports both
human and `--robot` (`openproxy.v1.*` envelope) output, and returns exit
code 6 when the server is unreachable.

New modules:
- src/cli/mitm.rs (~8 cmds): status, start, stop, cert generate|path,
  config get|set|apply
- src/cli/tunnel_rt.rs (~7 cmds, extends M1 stub): enable|disable
  cloudflare|tailscale, tailscale install|login|check|enable|disable
- src/cli/tool.rs (~8 cmds): list, show, apply (with --dry-run),
  revert, execute, run, doc, antigravity-mitm enable|disable
- src/cli/translator.rs (~6 cmds): formats, translate, send,
  preset list|save|load
- src/cli/media.rs (~13 cmds): providers list|add|edit|delete, combo
  list|create, tts voices|speak (binary stdout), stt transcribe,
  embed, image generate, search, web fetch

Runtime helpers added: put_json, patch_json, delete_json, post_json_bytes
in src/cli/runtime.rs.

Tests: tests/cli_m5_robot_envelopes.rs (15 integration tests covering
the happy path for each command group via wiremock).
@quangdang46 quangdang46 merged commit 110ffe3 into main May 12, 2026
2 of 3 checks passed
@quangdang46 quangdang46 deleted the devin/1778566184-cli-m5-tooling-integration branch May 12, 2026 11:58
@quangdang46
Copy link
Copy Markdown
Owner Author

Devin test report — M5 runtime CLI

Ran the merged target/debug/openproxy against a fresh local server (DATA_DIR=/tmp/openproxy-m5, BE on 127.0.0.1:4623). Every step is a real CLI invocation against a real BE, asserted with jq -e.

Result: all 7 tests passed. 29 PASS / 0 FAIL / 2 INCONCLUSIVE (the inconclusives were "BE didn't echo this exact substring in its error message", not feature regressions). Two of my plan's BE-response-shape assumptions were wrong and were corrected mid-execution — the CLI was right in every case, it just faithfully passed the real BE response through.

# Test Result
1 mitm status --robot against live BE returns openproxy.v1.mitm.status with enabled=false + numeric routes; same cmd against stopped BE returns exit 6 + openproxy.v1.error / server_unreachable passed
2 tool list (human + --robot) returns 5 tools incl. provider-list, key-list, pool-list, pool-status, route passed
3 tool apply claude --dry-run emits openproxy.v1.tool.apply.dry_run (not …apply) with env.{ANTHROPIC_BASE_URL,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_MODEL}; BE log shows 0 writes to cli-tools/claude-settings passed
4 translator formats --robot returns the real BE format list (openai, claude, gemini, openai-responses, cursor, kiro) passed
5 After POST /api/media-providers adds smoke-tts, media providers list --robot shows it bucketed under data.tts[] with isActive=true & provider=openai; pre-add list was empty passed
6 media web fetch <URL> accepts the URL positionally with no --url collision; CLI dialed 127.0.0.1, not example.com passed
7 tunnel tailscale check --robot returns openproxy.v1.tunnel.tailscale.check with an object body; legacy tunnel status still returns openproxy.v1.tunnel.status (no dispatcher regression) passed
Selected raw evidence

Test 1b — exit 6 contract when server is down

$ openproxy server stop
Stopped openproxy (pid 8292)
$ openproxy --robot mitm status ; echo "exit=$?"
{"schema":"openproxy.v1.error","ok":false,
 "error":{"code":"server_unreachable",
          "message":"server not running (http://127.0.0.1:4623/api/health: ...). Start it with: openproxy server start --detach"},
 "meta":{}}
exit=6

Test 3 — claude apply dry-run does not write

$ openproxy --robot tool apply claude --endpoint https://example.com \
    --api-key sk-xxx --model claude-3-5-sonnet --dry-run
{"schema":"openproxy.v1.tool.apply.dry_run","ok":true,
 "data":{"body":{"env":{"ANTHROPIC_AUTH_TOKEN":"sk-xxx",
                       "ANTHROPIC_BASE_URL":"https://example.com",
                       "ANTHROPIC_MODEL":"claude-3-5-sonnet"}},
         "path":"/api/cli-tools/claude-settings"},
 "meta":{}}
$ grep -ci "cli-tools/claude-settings" /tmp/openproxy-m5/openproxy.log
0

Test 7 — tunnel dispatcher split (new tailscale.check vs legacy status)

$ openproxy --robot tunnel tailscale check
{"schema":"openproxy.v1.tunnel.tailscale.check","ok":true,
 "data":{"brewAvailable":true,"daemonRunning":false,
         "installed":false,"loggedIn":false,"platform":"linux"},
 "meta":{}}

$ openproxy --robot tunnel status
{"schema":"openproxy.v1.tunnel.status","ok":true,
 "data":{"pid":null,"provider":null,"running":false,"url":null},"meta":{}}
Not in scope, deliberately
  • mitm config apply --from-file - (bulk PUT; PR body flagged it for human review).
  • media tts speak binary streaming (already asserted bit-for-bit by tests/cli_m5_robot_envelopes.rs::media_tts_speak_* — reproducing it live without a real TTS upstream is theatre, not test).
  • mitm cert generate (side-effecting; cert-state read path is covered transitively by Test 1).

Devin session: https://app.devin.ai/sessions/fb8cee23c76b42d29481760433c2183f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant