No description
Find a file
2026-05-09 10:17:34 -04:00
clean Intermediate commit 2025-11-05 16:09:48 -05:00
src Intermediate commit 2025-11-05 16:09:48 -05:00
tests Intermediate commit 2025-11-05 16:09:48 -05:00
.gitignore Remove weird test files 2025-11-05 16:11:36 -05:00
build.zig Remove weird test files 2025-11-05 16:11:36 -05:00
build.zig.zon Remove weird test files 2025-11-05 16:11:36 -05:00
DSL.md Add docs 2025-11-05 16:10:09 -05:00
HTTPFilter.pm Add docs 2025-11-05 16:10:09 -05:00
MOCK.md Add summary of mock server 2026-05-09 10:17:19 -04:00
README.md Update README.md 2026-05-07 20:11:47 +00:00
routes.json Intermediate commit 2025-11-05 16:09:48 -05:00
SPEC.md Initial checkin 2025-11-02 20:43:35 -05:00
test Initial checkin 2025-11-02 20:43:35 -05:00
TODOS.md Add docs 2025-11-05 16:10:09 -05:00
zig_http_bugs.md Add docs 2025-11-05 16:10:09 -05:00

PostCLI - HTTP Command Processor

A Zig-based command-line tool for executing HTTP commands with support for environment variables, macros, and JSON path extraction.

Why?

I don't really like postman style HTTP testing. This is an alternative.

  • It is hard, using source control, to version postman/insomnia/hoppscotch files. I think this is because their GUI confuses people, but I don't know it if it is also hard to version the files themselves.
  • It is very easy to dangerously commit secrets with aforementioned files, like production tokens, host topology, logging of requests with sensitive information.
  • It is very hard to create flows that depend on prior requests without using very convoluted syntax. For example get the auth token from request #1, use that token in subsequent requests, then use the part of the JSON response from request #3 in the next request, etc.

postcli makes it easy to do the right thing, and hard to do the wrong thing

  • Automatically load .env files, which are usually not included in source repositories. Your HTTP testing files use the same things your code uses.
  • Easily set variables from JSON reponses using jq syntax or from headers.
  • Automatically assign variables, using sensible defaults, to requests and responses and extracted JSON values. Then, you can use those variables in successive requests. And, because they are a stack of variables, you can add flows in between without changing indices to the variable.
  • Use syntax very similar to curl, reduce the learning curve (in fact if you use curl style switches you can run basic scripts using curl itself)
  • Enable an optional perl mode so you can add on control flow only when you need it

Installation

Prerequisites

Install Zig (version 0.11.0 or later):

# macOS (using Homebrew)
brew install zig

# Or download from https://ziglang.org/download/

Building

zig build

The executable will be created in zig-out/bin/postcli.

Usage

./zig-out/bin/postcli <script-file> [options]
./zig-out/bin/postcli --perl <script-file> [--dry-run-print]

Options

  • -e, --env <file>: Load additional environment variables from file (can be used multiple times)
  • --verbose-insecure: Print request headers and body
  • --output-insecure: Print full responses
  • --perl: Execute script as Perl with HTTPFilter module (see Perl Mode section)
  • --dry-run-print: Print transformed Perl code and exit (perl mode only)
  • -h, --help: Show help message

Features

Environment Variables

  • Automatically loads .env file from current directory
  • Variables are interpolated using $VARIABLE_NAME syntax
  • Additional env files can be loaded with --env flag

HTTP Methods

Supports: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS

Command Syntax

METHOD $URL [modifiers] [| json.path]

Headers

Use h() to add headers:

GET $SERVER/api h(Authorization=Bearer token,Accept=application/json)

Or use curl-style -H flags:

GET $SERVER/api -H "Authorization: Bearer token" -H "Accept: application/json"

JSON Body

Use b() for JSON body in POST/PUT requests:

POST $SERVER/api b(name=$NAME,age=25)

Use j() for JSON body with automatic headers (adds Accept: application/json and Content-Type: application/json):

POST $SERVER/api j(name=$NAME,age=25)

JSON Path Extraction

Use pipe to extract values from response:

GET $SERVER/api | .data.id

Variable Storage

Store extracted values in variables:

GET $SERVER/api | .title
$R[0] | .description
POST $SERVER/new b(title=$V[1],desc=$V[0])

Variables are accessed in reverse order (stack-based):

  • $V[0] = most recent value
  • $V[1] = second most recent value

Macros

  • Place macro files in .macros/ directory
  • Or in ~/.config/postcli/macros/
  • Use with pipe syntax: GET|DEBUG $SERVER/api

Example Script

GET $SERVER/posts/1 | .title
$R[0] = .body
POST $SERVER/posts b(title=$V[1],body=$V[0],userId=1)

Variable Validation

When the postcli command line runs, it validates all variables to ensure they are properly defined in loaded .env files. Variable validation follows these rules:

  • Variables are detected after HTTP method verbs (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
  • Variables must start with a dollar sign ($)
  • All variables must be defined in loaded environment files (.env)
  • Undefined variables will cause the command to fail with an error

Examples:

Valid (assuming SERVER is defined in .env):

GET $SERVER/api/users
POST $SERVER/api/users b(name=$USERNAME)

Invalid (will fail validation):

GET $UNDEFINED_SERVER/api/users    # $UNDEFINED_SERVER not in .env
POST $SERVER/api b(name=$MISSING)  # $MISSING not in .env

The validation helps catch configuration errors early and ensures all required environment variables are properly set before executing HTTP requests.

Testing

Run the test script:

./zig-out/bin/postcli test.http --output-insecure

Run validation tests:

zig build test

Perl Mode with HTTPFilter

PostCLI includes a powerful Perl mode that transforms postcli syntax into native Perl LWP (Library for WWW in Perl) requests. This mode provides enhanced functionality and better SSL handling.

Usage

# Execute postcli script as Perl
./zig-out/bin/postcli --perl script.postcli

# Show transformed Perl code without executing
./zig-out/bin/postcli --perl --dry-run-print script.postcli

HTTPFilter Module

The HTTPFilter Perl module automatically:

  1. Transforms HTTP syntax - Converts postcli commands to LWP requests
  2. Handles SSL issues - Automatically disables SSL verification to avoid certificate problems
  3. Manages variables - Provides $V[n] and $R[n] variable support
  4. Loads environment - Automatically reads .env files
  5. Extracts JSON paths - Supports jq-style path extraction

SSL Configuration

HTTPFilter automatically configures SSL to avoid common certificate issues:

# Automatically added by HTTPFilter
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
$ua->ssl_opts(verify_hostname => 0);

Code Generation

HTTPFilter generates clean, DRY (Don't Repeat Yourself) Perl code using a centralized http_request() function:

Input (postcli syntax):

GET $SERVER/api h(auth:token) | .data.id
POST $SERVER/users b(name:John,age:30)

Output (generated Perl):

http_request('GET', '$SERVER/api', { 'auth' => 'token' }, undef, '.data.id');
http_request('POST', '$SERVER/users', undef, '{"name":"John","age":"30"}', undef);

Generated Perl Structure

The HTTPFilter generates complete Perl programs with:

#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;
use JSON;

# Global variables for state management
my @variables;  # Stack of extracted values ($V[0], $V[1], etc.)
my @responses;  # Stack of raw responses ($R[0], $R[1], etc.)
my %env_vars;   # Environment variables from .env files

# SSL-configured user agent
my $ua = LWP::UserAgent->new();
$ua->ssl_opts(verify_hostname => 0);
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;

# Centralized HTTP request function
sub http_request {
    my ($method, $url, $headers, $body, $jq_path) = @_;
    # ... handles all HTTP logic with proper error handling
}

# Your transformed requests
http_request('GET', '$SERVER/api', undef, undef, undef);

Benefits of Perl Mode

  1. Better SSL Support - Automatically handles certificate issues
  2. Rich Ecosystem - Access to CPAN modules and Perl libraries
  3. Clean Code Generation - DRY approach with centralized request handling
  4. Enhanced Debugging - Use --dry-run-print to see exact Perl code
  5. Variable Interpolation - Full support for environment and runtime variables

Installing Perl Dependencies

To use Perl mode, you need LWP modules:

# Using CPAN
cpan LWP::UserAgent HTTP::Request JSON

# Using system package manager (Ubuntu/Debian)
sudo apt-get install libwww-perl libjson-perl

# Using system package manager (macOS)
brew install perl
cpan LWP JSON

Advanced Examples

With headers and JSON extraction:

# postcli script
GET $API_BASE/users h(Authorization=Bearer $TOKEN) | .data[0].id
POST $API_BASE/posts b(title=$TITLE,userId=$V[0])

# Run in Perl mode
./zig-out/bin/postcli --perl api-script.postcli

# See generated code
./zig-out/bin/postcli --perl --dry-run-print api-script.postcli

Generated function calls:

http_request('GET', '$API_BASE/users',
    { 'Authorization' => 'Bearer $TOKEN' }, undef, '.data[0].id');
http_request('POST', '$API_BASE/posts', undef,
    '{"title":"$TITLE","userId":"$V[0]"}', undef);

Migration from Native Mode

Both modes use the same postcli syntax, so scripts are fully compatible:

# Native Zig mode
./zig-out/bin/postcli script.postcli

# Perl mode (same script)
./zig-out/bin/postcli --perl script.postcli

The Perl mode provides enhanced SSL handling and debugging capabilities while maintaining full compatibility with existing postcli scripts.

Development History & Recent Improvements

Argument Parsing Modernization

  • Migrated from manual parsing to zig-clap - Replaced custom argument parsing with the industry-standard zig-clap library for better reliability and maintainability
  • Fixed argument order dependencies - Arguments can now be provided in any order
  • Enhanced error handling - Better error messages and validation

Perl Mode Enhancements

  • Added --dry-run-print functionality - Users can now see the exact Perl/LWP code that HTTPFilter generates
  • DRY code generation - Refactored HTTPFilter to generate clean, maintainable Perl code using centralized http_request() function instead of repetitive boilerplate
  • SSL issue resolution - Automatic SSL certificate verification bypass to handle common certificate authority issues in Perl LWP

Error Handling Improvements

  • Graceful HTTP error handling - Fixed crashes on 404/500 errors; now shows proper error messages and exits cleanly
  • Memory leak fixes - Proper cleanup on error conditions
  • Better validation feedback - Clear messages for undefined variables and configuration issues

Code Quality

  • Test structure reorganization - Cleaned up test files and improved zig build test functionality
  • Comprehensive documentation - Added detailed README sections covering all features and modes
  • Variable validation system - Proactive validation of environment variables before script execution

Before vs After Code Generation

Before (repetitive):

{
    my $url = interpolate_vars('$SERVER/api');
    print "GET $url\n";
    my $req = HTTP::Request->new('GET', $url);
    my $response = $ua->request($req);
    my $response_body = $response->content;
    push @responses, $response_body;
    push @variables, $response_body;
}

After (DRY):

http_request('GET', '$SERVER/api', undef, undef, undef);

These improvements make PostCLI more robust, user-friendly, and maintainable while preserving full backward compatibility with existing scripts.