wx-cli/.gsd.migrating/milestones/M001/M001-CONTEXT.md

7.3 KiB

M001: TCP Transport

Gathered: 2026-01-13 Status: Ready for planning

Project Description

Add TCP socket transport to wx-cli's daemon communication layer, enabling remote clients to query WeChat data over the network. Refactor the existing platform-specific IPC code into a trait-based abstraction to eliminate duplication and make future transport additions easy.

Why This Milestone

Currently wx-cli only supports local IPC (Unix sockets on macOS/Linux, named pipes on Windows). This limits usage to the same machine as the WeChat daemon. Adding TCP transport enables remote access, containerized deployments, and multi-machine setups.

User-Visible Outcome

When this milestone is complete, the user can:

  • Start the daemon with TCP listening: wx daemon start --tcp 127.0.0.1:9876
  • Query WeChat data over TCP: wx sessions --tcp 127.0.0.1:9876
  • Use all existing commands without --tcp and get unchanged local behavior
  • Check daemon status and logs over TCP: wx daemon status --tcp 127.0.0.1:9876

Entry point / environment

  • Entry point: wx CLI command with global --tcp host:port flag
  • Environment: local dev or remote machine (TCP network)
  • Live dependencies involved: wx-daemon process

Completion Class

  • Contract complete means: Transport traits defined, all three implementations compile, protocol handling is shared
  • Integration complete means: Daemon listens on local + TCP simultaneously, client connects via TCP and gets correct response
  • Operational complete means: Daemon starts with --tcp, handles bind errors cleanly, client fails with clear error when TCP unreachable

Final Integrated Acceptance

To call this milestone complete, we must prove:

  • cargo check passes on macOS, Linux, and Windows targets
  • Daemon started with --tcp 127.0.0.1:9876 accepts TCP connections and responds correctly
  • Client with --tcp 127.0.0.1:9876 returns same results as local transport
  • Client with --tcp 127.0.0.1:9999 (unreachable) fails with clear error within 15s
  • Commands without --tcp still work via local transport (no regression)

Architectural Decisions

Transport abstraction via traits

Decision: Use Listener and Connector traits to abstract transport primitives, implement for Unix socket, Windows named pipe, and TCP.

Rationale: Current code has ~50 lines of duplicated JSON-line protocol handling across Unix/Windows. Traits eliminate duplication and provide clear extension point for future transports (TLS, WebSocket).

Alternatives Considered:

  • Continue #[cfg] branching — current approach, hard to extend, duplicative
  • interprocess crate for all transports — doesn't support TCP natively
  • Abstract at protocol level only — would still need per-platform listener/connection code

One request per connection (unchanged)

Decision: Keep existing protocol model — one JSON-line request per connection, no keepalive or pooling.

Rationale: Matches existing behavior, minimal complexity, sufficient for CLI usage patterns.

Alternatives Considered:

  • Persistent connections with multiplexing — adds protocol complexity, not needed for CLI
  • Connection pooling — overkill for single-client CLI tool

Global CLI flag for TCP

Decision: --tcp host:port as global clap flag on root Cli struct, inherited by all subcommands.

Rationale: Discoverable, consistent UX. User specifies once, affects all commands.

Alternatives Considered:

  • Environment variables — hidden, harder to discover
  • Per-subcommand flag — repetitive, inconsistent
  • Config file only — requires edit before use

No built-in TCP security

Decision: No TLS, no auth tokens, no IP whitelist in this milestone. Bind exactly as user specifies.

Rationale: User handles firewall/ACL at OS level. Adding TLS requires cert management, tokio-rustls dependency, and significantly more complexity. Can be added later non-breaking.

Alternatives Considered:

  • Default to localhost-only — too restrictive, user should control bind address
  • Built-in IP whitelist — adds config complexity, OS firewall is better tool

Error Handling Strategy

  • TCP bind failure: "TCP bind failed on {addr}: {reason}" — daemon aborts startup
  • TCP connection failure: "Failed to connect to {addr}: {reason}" — hard error, no fallback
  • Connection timeout: 15s connect, 120s read/write (matches existing)
  • Connection dropped mid-request: "Connection lost: daemon closed or network error"
  • Mixed transport mismatch: "No daemon listening on {addr}" — same as current "daemon not alive" path
  • No --tcp: Existing local transport behavior, no change

Risks and Unknowns

  • Windows named pipe refactoring may require interprocess crate changes — the crate's API differs from std Unix sockets
  • daemon start subcommand needs to handle existing auto-start behavior (currently daemon starts on first query via ensure_daemon())

Existing Codebase / Prior Art

  • src/daemon/server.rs — current IPC server, needs refactoring to use Listener trait
  • src/cli/transport.rs — current IPC client, needs refactoring to use Connector trait
  • src/ipc.rs — protocol types (Request/Response), well-abstracted, no changes needed
  • src/config.rs — needs tcp_addr field extension

Relevant Requirements

  • R001 — TCP transport on server (M001/S01)
  • R002 — TCP transport on client (M001/S02)
  • R003 — Transport abstraction layer (M001/S01)
  • R004 — Global --tcp CLI flag (M001/S02)
  • R005 — Daemon start command (M001/S01)
  • R006 — Cross-platform compilation (M001/S01)
  • R007 — Error handling for TCP failures (M001/S02)
  • R008 — Integration: CLI ↔ daemon over TCP (M001/S04)

Scope

In Scope

  • Trait-based transport abstraction (Listener, Connector)
  • TCP implementation (TcpListener, TcpStream)
  • Global --tcp host:port CLI flag
  • wx daemon start subcommand
  • Error handling for TCP failures
  • Cross-platform compilation

Out of Scope / Non-Goals

  • TLS encryption
  • Authentication tokens
  • IP whitelisting
  • Connection pooling / keepalive
  • Changing the JSON-line protocol

Technical Constraints

  • Must maintain backwards compatibility: no --tcp = existing behavior
  • tokio is already a dependency (TcpListener/TcpStream available)
  • interprocess crate for Windows named pipes — API differs from std

Integration Points

  • src/daemon/server.rssrc/transport/ — server uses Listener trait
  • src/cli/transport.rssrc/transport/ — client uses Connector trait
  • src/config.rs → optional tcp_addr field
  • src/cli/mod.rs → global --tcp flag on Cli struct

Testing Requirements

  • cargo check on x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, and current platform
  • Unit tests for transport::protocol.rs (JSON round-trip)
  • Existing scanner tests continue passing
  • Manual smoke test: daemon on TCP, client queries over TCP

Acceptance Criteria

  • S01: Transport traits defined, all implementations compile on all platforms, existing behavior unchanged
  • S02: wx daemon start --tcp 127.0.0.1:9876 starts daemon listening on TCP
  • S03: wx sessions --tcp 127.0.0.1:9876 connects via TCP and returns correct results
  • S04: End-to-end TCP communication verified manually on localhost

Open Questions

  • None — scope confirmed, architecture agreed, error strategy defined