Fast, structured networking from the terminal.

HTTP, WebSocket, TCP, UDP, DNS, Ping, WHOIS, MQTT, and SSE with consistent JSON output. Built for machines, readable by humans.

$ cargo install network-output
HTTP WebSocket TCP UDP DNS MQTT SSE jq

# Quick Start

One command per protocol. All output follows the same structured JSON envelope.

HTTP
no http GET example.com
WebSocket
no ws listen ws://echo.websocket.org
TCP
no tcp connect example.com:80
MQTT
no mqtt sub localhost:1883 -t sensors/temp
SSE
no sse https://stream.example.com/events
jq
echo '{"a":1}' | no jq '.a'

# HTTP

Full-featured HTTP client with support for all standard methods, headers, authentication, request bodies, file downloads, and stdin piping.

Methods

The first argument is the HTTP method. Supported: GET (default), POST, PUT, PATCH, DELETE, HEAD, OPTIONS.

no http GET https://httpbin.org/get
no http POST https://api.example.com/data
no http DELETE https://api.example.com/items/42

Headers

Use -H to add request headers. The flag is repeatable.

no http POST https://api.example.com/data \
  -H "Content-Type:application/json" \
  -H "X-Request-Id:abc123"

Request Body

Use -b to send a request body inline, or --stdin to read it from standard input.

# Inline body
no http POST https://api.example.com/data -b '{"key":"value"}'

# Body from stdin
cat payload.json | no http POST https://api.example.com/data --stdin

Authentication

Bearer tokens and Basic auth are supported as first-class flags.

# Bearer token
no http GET https://api.example.com --bearer $TOKEN

# Basic auth
no http GET https://api.example.com --basic user:pass

Fallback: set NO_AUTH_TOKEN or NO_BASIC_AUTH environment variables for default credentials.

File Download

Use -o to save the response body to a file. A progress bar is shown during the download.

no http GET https://example.com/file.tar.gz -o file.tar.gz

Response Shape

{
  "type": "response",
  "protocol": "http",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "data": {
    "status": 200,
    "headers": { "content-type": "application/json" },
    "body": { "message": "ok" },
    "bytes": 1234
  }
}

With -v (verbose), a metadata field is added containing method, url, and time_ms.

# WebSocket

WebSocket support with two subcommands: listen for receiving messages and send for sending a single message.

Listen

Open a persistent connection and stream incoming messages.

no ws listen ws://localhost:8080
no ws listen api.example.com/ws           # auto-infers wss://
no ws listen localhost:8080/ws -n 5       # stop after 5 messages

Send

Connect, send one message, and print the response.

no ws send ws://localhost:8080 -m "hello"

Response Shapes

// Connection event
{ "type": "connection", "data": { "status": "connected", "url": "..." } }

// Text message
{ "type": "message", "data": { "data": "hello world", "binary": false } }

// Binary message
{ "type": "message", "data": { "binary": true, "length": 4, "hex": "deadbeef" } }

// Close event
{ "type": "connection", "data": { "status": "closed", "code": 1000, "reason": "" } }

# TCP

Raw TCP connections with connect and listen subcommands. Address format is host:port.

Connect

Connect to a remote TCP server. Optionally send a message or pipe data from stdin.

no tcp connect localhost:9090 -m "hello"
no tcp connect [::1]:9090 -m "hello"       # IPv6
no tcp connect localhost:9090 --stdin
no tcp connect example.com:80 -n 1         # stop after 1 message

Listen

Start a TCP server and accept incoming connections.

no tcp listen :9090
no tcp listen 0.0.0.0:9090
no tcp listen [::]:9090                    # IPv6 listen

Response Shapes

// Connection events
{ "type": "connection", "data": { "status": "connected", "address": "..." } }
{ "type": "connection", "data": { "status": "listening", "address": "..." } }
{ "type": "connection", "data": { "status": "accepted", "peer": "..." } }

// Text data
{ "type": "message", "data": { "data": "..." } }

// Binary data (non-UTF-8)
{ "type": "message", "data": { "binary": true, "length": 4, "hex": "deadbeef" } }

# UDP

Connectionless UDP datagrams with send and listen subcommands. Address format is host:port for IPv4 or [host]:port for IPv6.

Send

Send a UDP datagram. By default it is fire-and-forget -- the process exits immediately after sending. Use --wait to wait for a response.

# Send a datagram
no udp send 127.0.0.1:9090 -m "hello"
no udp send [::1]:9090 -m "hello"          # IPv6

# Send and wait for echo
no udp send 127.0.0.1:9090 -m "ping" --wait 3s

--wait Modes

The --wait flag controls whether send waits for a response after sending:

UsageBehavior
OmittedFire-and-forget. Exit immediately after sending.
--waitWait indefinitely for a response (respects --timeout if set).
--wait 3sWait up to 3 seconds for a response, then exit cleanly.

Listen

Listen for incoming UDP datagrams on a local address.

# Listen for incoming datagrams
no udp listen :9090
no udp listen [::]:9090                    # IPv6 listen

# Listen with count limit
no udp listen :9090 --count 5

Response Shapes

// Send confirmation
{ "type": "response", "data": { "status": "sent", "address": "127.0.0.1:9090", "bytes": 5 } }

// Received response (when using --wait)
{ "type": "message", "data": { "data": "pong", "peer": "127.0.0.1:9090" } }

// Listen events
{ "type": "connection", "data": { "status": "listening", "address": "0.0.0.0:9090" } }
{ "type": "message", "data": { "data": "hello", "peer": "192.168.1.10:54321" } }

// Binary data (non-UTF-8)
{ "type": "message", "data": { "binary": true, "length": 4, "hex": "deadbeef", "peer": "..." } }

# DNS

DNS lookups with support for all common record types. If the argument is an IP address, it automatically performs a PTR (reverse) lookup.

Basic Usage

no dns example.com
no dns example.com AAAA
no dns google.com MX
no dns 8.8.8.8                    # auto-detects reverse (PTR) lookup
no dns example.com --server 1.1.1.1
no dns example.com --server 2001:4860:4860::8888   # IPv6 DNS server

Record Types

Supported record types: A, AAAA, MX, TXT, CNAME, NS, SOA, SRV, PTR.

When the record type is omitted, it defaults to A. When the argument is an IP address, it defaults to PTR.

Custom Server

Use --server to specify a DNS resolver instead of the system default.

no dns example.com --server 1.1.1.1
no dns example.com AAAA --server 8.8.8.8

Response Shape

{
  "type": "response",
  "protocol": "dns",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "data": {
    "name": "example.com",
    "type": "A",
    "records": [
      { "value": "93.184.216.34", "ttl": 3600 }
    ]
  }
}

# Ping

ICMP ping with per-ping timing and aggregate statistics. Supports IPv4 and IPv6. Uses non-privileged ICMP sockets on macOS; Linux may require CAP_NET_RAW.

Basic Usage

no ping 127.0.0.1
no ping ::1                               # IPv6
no ping example.com
no ping example.com -n 2              # send 2 pings (default: 4)
no ping 127.0.0.1 --interval 500ms    # 500ms between pings

Options

Use the global -n flag to control the number of pings (default 4) and --timeout for per-ping timeout (default 1s). The --interval flag sets the delay between pings.

Response Shapes

// Per-ping reply (type: message)
{
  "type": "message",
  "protocol": "ping",
  "data": {
    "seq": 0, "host": "example.com", "ip": "93.184.216.34",
    "ttl": 56, "size": 64, "time_ms": 12.3
  }
}

// Summary (type: response)
{
  "type": "response",
  "protocol": "ping",
  "data": {
    "host": "example.com", "ip": "93.184.216.34",
    "transmitted": 4, "received": 4, "loss_pct": 0.0,
    "min_ms": 11.2, "avg_ms": 12.5, "max_ms": 14.1
  }
}

# WHOIS

WHOIS domain and IP lookups via raw TCP to port 43. The WHOIS server is auto-detected based on the TLD or can be specified manually.

Basic Usage

no whois example.com
no whois 8.8.8.8
no whois ::1                                         # IPv6 address
no whois example.com --server whois.verisign-grs.com

Server Detection

The WHOIS server is automatically selected based on the query:

Response Shape

{
  "type": "response",
  "protocol": "whois",
  "data": {
    "query": "example.com",
    "server": "whois.verisign-grs.com",
    "response": "Domain Name: EXAMPLE.COM\r\n..."
  }
}

# MQTT

MQTT pub/sub with sub and pub subcommands. Broker address is a positional argument in host:port format.

Subscribe

Subscribe to a topic and stream incoming messages.

no mqtt sub localhost:1883 -t "sensor/temp"
no mqtt sub [::1]:1883 -t "sensor/temp"             # IPv6
no mqtt sub localhost:1883 -t "sensor/temp" -n 10   # stop after 10 messages

Publish

Publish a single message to a topic.

no mqtt pub localhost:1883 -t "sensor/temp" -m '{"value":22.5}'

Response Shapes

// Subscribe confirmation
{ "type": "connection", "data": { "status": "subscribed", "topic": "sensor/temp" } }

// Incoming message
{ "type": "message", "data": { "topic": "sensor/temp", "payload": "...", "qos": 0 } }

// Publish confirmation
{ "type": "response", "data": { "status": "published", "topic": "sensor/temp", "payload": "..." } }

# SSE

Connect to a Server-Sent Events endpoint and stream events. Supports authentication and custom headers.

no sse https://example.com/events
no sse https://example.com/events --bearer $TOKEN
no sse https://example.com/events -H "X-Custom:value"
no sse https://example.com/events -n 5     # stop after 5 events

Authentication

SSE supports --bearer and --basic flags, plus the NO_AUTH_TOKEN and NO_BASIC_AUTH environment variable fallbacks.

Response Shapes

// Connection event
{ "type": "connection", "data": { "status": "connected", "url": "..." } }

// SSE event
{
  "type": "message",
  "data": {
    "data": "event payload",
    "event": "update",
    "id": "1"
  }
}

# jq Filtering

Built-in jq filtering powered by jaq-core (pure Rust). No external jq binary required.

Global Flag: --jq

Apply a jq expression to the output of any command. The filter receives the full NetResponse JSON envelope as input.

# Extract just the HTTP status code
no http GET example.com --jq '.data.status'

# Stream WebSocket message payloads
no ws listen localhost:8080 --jq '.data' -n 5

# Extract SSE event data
no sse example.com/events --jq '.data.data' -n 1

# Drill into nested JSON body
no http GET example.com/api --jq '.data.body.items[]'

Standalone Subcommand: no jq

Filter arbitrary JSON from stdin. Works as a lightweight, portable jq replacement.

echo '{"a":1,"b":2}' | no jq '.a'
# Output: 1

echo '{"s":"hello"}' | no jq '.s'
# Output: hello

echo '[1,2,3]' | no jq '.[]'
# Output: 1
# Output: 2
# Output: 3

# Pipe protocol output through standalone jq
no http GET https://httpbin.org/get | no jq '.data.body'

String results print raw (without quotes), matching jq -r behavior. Error responses (type: "error") bypass the filter and print normally.

# Output Format

All protocol handlers emit structured JSON through a consistent envelope. Output mode is automatically detected based on whether stdout is a TTY.

Mode Detection

ConditionMode
--json flagJSON (one object per line)
--pretty flagPretty (colored, human-readable)
stdout is a TTYPretty
stdout is pipedJSON

JSON Envelope

{
  "type": "response",
  "protocol": "http",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "data": { ... },
  "metadata": { ... }
}
FieldTypeDescription
typestringOne of response, message, connection, error
protocolstringOne of http, ws, tcp, udp, dns, mqtt, sse
timestampstringRFC 3339 UTC with millisecond precision
dataobjectProtocol-specific payload
metadataobjectOptional; present when -v is used. Omitted from JSON when absent.

ResponseType Semantics

TypeMeaning
responseFinal result from a request (HTTP response, MQTT publish confirmation)
messageStreamed data from a long-lived connection (WS frame, TCP data, SSE event, MQTT message)
connectionLifecycle event (connected, closed, listening, accepted, subscribed)
errorStructured error with code and message

# Global Flags

These flags can be placed before or after the subcommand and apply to all protocols.

FlagShortDescription
--jsonForce JSON output (one object per line, no colors)
--prettyForce pretty-printed, human-readable output
--timeout DURATIONRequest timeout (e.g. 5s, 300ms, 1m)
--no-colorDisable colored output in pretty mode
--verbose-vInclude metadata in responses (method, URL, timing, bytes)
--count N-nStop after N data messages (streaming protocols only; lifecycle events are not counted)
--jq EXPRApply a jq filter expression to each response before printing

Flag Placement

# Before subcommand
no --jq '.data.status' http GET example.com

# After subcommand arguments
no http GET example.com --jq '.data.status'

# Mixed
no --json http GET example.com -v

# Exit Codes

Every error maps to a deterministic exit code for scripting and automation.

CodeMeaningCauses
0SuccessRequest completed normally
1Connection / I/O ErrorConnection refused, DNS resolution failure, generic I/O error
2Protocol ErrorProtocol-level failure, TLS handshake error, unexpected response
3TimeoutConnection or read timed out
4Invalid InputBad URL, invalid header format, unknown method, invalid port, bad jq expression

Error Output

Errors follow the same JSON envelope with "type": "error":

{
  "type": "error",
  "protocol": "http",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "data": {
    "code": "CONNECTION_REFUSED",
    "message": "connection refused"
  }
}

Error codes are serialized in SCREAMING_SNAKE_CASE: CONNECTION_REFUSED, DNS_RESOLUTION, IO_ERROR, PROTOCOL_ERROR, TLS_ERROR, CONNECTION_TIMEOUT, INVALID_INPUT.

# Environment Variables

Default authentication credentials for HTTP and SSE requests. These are used when no explicit --bearer or --basic flag is provided.

VariableDescriptionFormat
NO_AUTH_TOKENDefault bearer token for HTTP and SSE requestsToken string
NO_BASIC_AUTHDefault basic auth credentials for HTTP and SSE requestsuser:pass

Precedence

# 1. Explicit flag (highest priority)
no http GET https://api.example.com --bearer my-token

# 2. NO_AUTH_TOKEN environment variable
export NO_AUTH_TOKEN="my-token"
no http GET https://api.example.com

# 3. NO_BASIC_AUTH (used when --basic not set and NO_AUTH_TOKEN not set)
export NO_BASIC_AUTH="admin:secret"
no http GET https://api.example.com

# URL Normalization

HTTP, WebSocket, and SSE protocols auto-infer the URL scheme when you omit it. The scheme is chosen based on whether the host is a local/private address or a public one.

Rules

HostHTTP SchemeWS Scheme
localhosthttp://ws://
127.0.0.1http://ws://
::1http://ws://
0.0.0.0http://ws://
10.*http://ws://
172.16-31.*http://ws://
192.168.*http://ws://
fc00::/7 (ULA)http://ws://
fe80::/10 (link-local)http://ws://
Everything elsehttps://wss://

Examples

# Local addresses get http:// / ws://
no http GET localhost:3000/api         # -> http://localhost:3000/api
no ws listen localhost:8080/ws         # -> ws://localhost:8080/ws
no http GET 192.168.1.10:3000/api      # -> http://192.168.1.10:3000/api

# Public addresses get https:// / wss://
no http GET example.com/api            # -> https://example.com/api
no ws listen api.example.com/ws        # -> wss://api.example.com/ws

# Explicit schemes are always preserved
no http GET http://example.com/api     # -> http://example.com/api

TCP, UDP, and MQTT are not affected by URL normalization. They use raw host:port addresses (bracket IPv6 with [host]:port). MQTT also accepts mqtt:// scheme.