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
$ brew install network-output/tap/network-output
$ npx network-output
# Quick Start
One command per protocol. All output follows the same structured JSON envelope.
no http GET example.com
no ws listen ws://echo.websocket.org
no tcp connect example.com:80
no mqtt sub localhost:1883 -t sensors/temp
no sse https://stream.example.com/events
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:
| Usage | Behavior |
|---|---|
| Omitted | Fire-and-forget. Exit immediately after sending. |
--wait | Wait indefinitely for a response (respects --timeout if set). |
--wait 3s | Wait 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:
- IP addresses use
whois.arin.net .com/.netusewhois.verisign-grs.com.orguseswhois.pir.org.iouseswhois.nic.io.dev/.appusewhois.nic.google- Unknown TLDs fall back to
whois.iana.org
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
| Condition | Mode |
|---|---|
--json flag | JSON (one object per line) |
--pretty flag | Pretty (colored, human-readable) |
| stdout is a TTY | Pretty |
| stdout is piped | JSON |
JSON Envelope
{
"type": "response",
"protocol": "http",
"timestamp": "2024-01-01T00:00:00.000Z",
"data": { ... },
"metadata": { ... }
}
| Field | Type | Description |
|---|---|---|
type | string | One of response, message, connection, error |
protocol | string | One of http, ws, tcp, udp, dns, mqtt, sse |
timestamp | string | RFC 3339 UTC with millisecond precision |
data | object | Protocol-specific payload |
metadata | object | Optional; present when -v is used. Omitted from JSON when absent. |
ResponseType Semantics
| Type | Meaning |
|---|---|
response | Final result from a request (HTTP response, MQTT publish confirmation) |
message | Streamed data from a long-lived connection (WS frame, TCP data, SSE event, MQTT message) |
connection | Lifecycle event (connected, closed, listening, accepted, subscribed) |
error | Structured error with code and message |
# Global Flags
These flags can be placed before or after the subcommand and apply to all protocols.
| Flag | Short | Description |
|---|---|---|
--json | Force JSON output (one object per line, no colors) | |
--pretty | Force pretty-printed, human-readable output | |
--timeout DURATION | Request timeout (e.g. 5s, 300ms, 1m) | |
--no-color | Disable colored output in pretty mode | |
--verbose | -v | Include metadata in responses (method, URL, timing, bytes) |
--count N | -n | Stop after N data messages (streaming protocols only; lifecycle events are not counted) |
--jq EXPR | Apply 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.
| Code | Meaning | Causes |
|---|---|---|
0 | Success | Request completed normally |
1 | Connection / I/O Error | Connection refused, DNS resolution failure, generic I/O error |
2 | Protocol Error | Protocol-level failure, TLS handshake error, unexpected response |
3 | Timeout | Connection or read timed out |
4 | Invalid Input | Bad 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.
| Variable | Description | Format |
|---|---|---|
NO_AUTH_TOKEN | Default bearer token for HTTP and SSE requests | Token string |
NO_BASIC_AUTH | Default basic auth credentials for HTTP and SSE requests | user: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
| Host | HTTP Scheme | WS Scheme |
|---|---|---|
localhost | http:// | ws:// |
127.0.0.1 | http:// | ws:// |
::1 | http:// | ws:// |
0.0.0.0 | http:// | 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 else | https:// | 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.