HomeGuides › CLI guide

zipnative-cli — Command-line reference

zipnative-cli 1.0.0 is the official command-line interface for the zipnative engine — fifteen commands over stdin/stdout pipelines, an agent-native automation contract (a --json envelope carrying the engine's err.code verbatim, --dry-run, --summary / --fields projection, JSON Schemas), and the one job the engine refuses by design: writing to the filesystem. Offline, always. Pins zipnative ^1.0.0.

zipnative-cli is a pure dispatch layer over zipnative: no ZIP logic lives in the CLI, every command forwards to a public engine API, and the engine's guarantees — safe-by-default extraction, deterministic bytes, the frozen 39-code error vocabulary — arrive unchanged. What the CLI adds is the process boundary the engine deliberately lacks: files, directories, pipes, exit codes, shell completion, and a JSON contract that agents and CI can drive without parsing prose.

Command Engine API behind it
create createZip() / createParallelZip() (zipnative/worker) — add, addDirectory, addStream, toBytes / stream
modify createZipModifier(openZip(bytes))save() (append-only) / saveCompact()
list openZip()entries()
inspect openZip({ validate: 'eager' }) → entries, flags, diagnostics; the --check assertions are CLI logic over engine facts
cat openZip()readEntryStream() / readEntryRaw()
extract extractZipStream() (default) / extractZip() (--buffered) + sanitizeEntryPath() re-checked at the sink
stream iterateZipEntries() — local headers only
verify verifyZip() verbatim; with --entry, openZip({ validate: 'eager' })verifyEntry() per name
crc32 crc32()
inflate createInflator() (or a registered codec)
batch the commands above, orchestrated
doctor VERSION, activeDeflateTier(), getCodec(), DEFAULT_ZIP_LIMITS + runtime probes
schema — (the CLI's own JSON Schemas, Draft 2020-12)
completion — (generated from the same command table as --help)
govern — (the AI-governance contract, .github/ai-governance.json)

Installation#

# Run directly with npx — no global install required
npx zipnative-cli list archive.zip

# Or install globally: the binary is called `zipnative`
npm install --global zipnative-cli
zipnative list archive.zip

Requirements: Node.js ≥ 22. The package has exactly one runtime dependency, zipnative ^1.0.0, ships as a single CommonJS binary, and is published with npm provenance — verify the artefact with npm audit signatures. Every command runs offline: no command can open a socket.

When to use the CLI#

Use the CLI when… Use the library when… Use the MCP server when…
You write shell scripts, Makefiles or CI jobs You build a Node, Bun, Deno, browser or Worker service A conversational assistant needs archive tools
Archives live on disk or arrive through a pipe Archives live in memory or in a stream you control Archives arrive as base64 or in a sandbox directory
You want exit codes, --dry-run and a JSON envelope You want the full API — random access, modifiers, codecs You want typed tools with schemas and prompts

The three surfaces share one engine, so the artefacts are interchangeable; the choosing guide holds the full capability × surface matrix.

Quick start#

# A reproducible archive: identical SHA-256 on every runtime
zipnative create src/ docs/ --output release.zip --deterministic

# Inventory without decompressing a byte
zipnative list release.zip --format json | jq '.entries[].name'

# A CI gate: fail (exit 1, E_CHECK_FAILED) unless every assertion holds
zipnative inspect release.zip --check deterministic,no-encryption,safe-names,max-entries=1000

# Extract behind the default guards — zip-slip, symlinks, bombs, duplicates refused
zipnative extract --input release.zip --output-dir ./out

# Integrity in one call (never fails for an archive problem — it reports)
zipnative verify release.zip --json

The agent contract#

The CLI is designed to be driven by programs — CI runners, coding agents, other languages — without parsing prose.

A status envelope and an error envelope, as an agent sees them. The error message is the CLI's short context, a colon, then the engine's own message verbatim; remedy is the exact string from the remedy table below (message wording is not a contract — the codes and remedy are):

{"ok":true,"command":"create","dryRun":false,"output":"release.zip","entries":42,"files":40,"directories":2,"bytes":183044,"bytesIn":611203,"method":"deflate","level":6,"deterministic":true,"tier":"pure-pinned","order":"canonical","stream":false,"layout":"buffered","parallel":false,"skipped":[],"diagnostics":[]}
{"ok":false,"command":"extract","error":{"code":"E_SECURITY","message":"Failed to extract: zipnative: entry name '../../etc/passwd' escapes the extraction root (zip-slip, CWE-22) — this archive is hostile or corrupt; pass rejectTraversal: false to skip such entries instead","zipCode":"ZIP_PATH_TRAVERSAL","entryName":"../../etc/passwd","remedy":"--skip-unsafe (extract, stream)"}}

Without --json the same failure prints the message line and then a remedy: … line on stderr, with the same text.

Refused combinations (exit 2, E_USAGE)#

Usage errors are detected before any I/O, so a refused combination never leaves a partial file behind. Beyond the obvious (an unknown flag, a malformed value, a missing required flag), the CLI refuses:

Command Refused
create --from-manifest together with input paths or --stdin-name; no input at all; --stdin-name together with a - input path (stdin is consumed once); --workers / --min-job-size / --job-timeout without --parallel; --chunk-size without --stream or --stdin-name; --parallel under --pure-codecs unless --deterministic is also given (the worker bundle resolves node:zlib itself); --parallel with a --codec module that registers method 0 / 8, or exports deflateImpl without --deterministic; --entry-comment without =, or naming an entry that is not in the archive; --comment with --comment-file
modify no --input; no edit at all (at least one of --add, --replace, --remove, --rename, --add-dir, --comment, --comment-file, --from-manifest); --from-manifest together with any edit flag or comment flag; --in-place with --output; --in-place when the input is stdin; stdin named twice (--input - plus a - payload, or two - payloads)
extract no --output-dir; --allow-symlinks with --skip-symlinks
stream --output-dir with --cat; --preserve-mode, --allow-symlinks or --skip-symlinks at all (no central directory to read attributes from)
cat no --input; no entry name
inflate --max-output 0; a --method that is not deflate, store or a number
batch --manifest with --input-dir / --output-dir; neither --manifest nor --input-dir; --task create without --output-dir; --concurrency outside 1–64
govern verify-issue without a draft path
completion a shell other than bash, zsh, fish, powershell (pwsh is accepted as an alias)
global any --max-* bound set to 0 (use none to disable); a .zipnativerc.json that sets codec; --config naming a file that does not exist; combined short flags (-lq)

--config together with --no-config is not refused: --no-config wins and the named file is never read.

Error classes, codes and remedies#

The engine's 39 frozen ZIP_* codes map to the CLI's E_* classes by a fixed table (zipnative schema errors prints it; a core release that added a code would fail the CLI's own type-check rather than leak as E_RUNTIME). Grouped by class:

Class (exit) ZIP_* codes
E_USAGE (2) ZIP_INVALID_OPTION, ZIP_LIMIT_INVALID
E_PARSE (1) ZIP_EOCD_NOT_FOUND, ZIP_EOCD_INCONSISTENT, ZIP_ZIP64_LOCATOR_MISSING, ZIP_ZIP64_EOCD_MISPLACED, ZIP_CD_INCONSISTENT, ZIP_RECORD_TRUNCATED, ZIP_SIGNATURE_MISMATCH, ZIP_STREAM_TRUNCATED, ZIP_VALUE_UNREPRESENTABLE, ZIP_DEFLATE_TRUNCATED, ZIP_DEFLATE_CORRUPT
E_INPUT (1) ZIP_ENTRY_EXISTS, ZIP_INVALID_ENTRY_NAME, ZIP_DUPLICATE_ENTRY_NAME
E_SECURITY (1) ZIP_ENTRY_OVERLAP, ZIP_CD_LFH_MISMATCH, ZIP_ZIP64_CONTRADICTION, ZIP_PATH_TRAVERSAL, ZIP_SYMLINK_REJECTED, ZIP_EXTRACT_DUPLICATE_PATH
E_DATA (1) ZIP_CRC_MISMATCH, ZIP_SIZE_MISMATCH, ZIP_INFLATE_OUTPUT_OVERFLOW, ZIP_DESCRIPTOR_MISMATCH, ZIP_DECOMPRESSION_FAILED
E_LIMIT (1) ZIP_LIMIT_EXCEEDED, ZIP_INPUT_TOO_LARGE
E_UNSUPPORTED (1) ZIP_UNSUPPORTED_ENCRYPTION, ZIP_UNSUPPORTED_METHOD, ZIP_UNSUPPORTED_MULTI_DISK, ZIP_UNSUPPORTED_ZIP64_STREAMING, ZIP_UNSUPPORTED_CD_LESS_DESCRIPTOR, ZIP_UNSUPPORTED_CODEC_MODE
E_NOT_FOUND (1) ZIP_ENTRY_NOT_FOUND
E_CHECK_FAILED (1) ZIP_STRICT_DIAGNOSTIC
E_RUNTIME (1) ZIP_API_MISUSE, ZIP_INTERNAL

E_IO, E_VERIFY_FAILED and E_POLICY carry no ZIP_* code: they are the CLI's own (a filesystem error, a verify verdict, a governance violation). A node:zlib failure on the sync path is mapped to the same codes the pure tier would raise (Z_DATA_ERRORZIP_DEFLATE_CORRUPT, Z_BUF_ERRORZIP_DEFLATE_TRUNCATED), so the class never depends on the codec tier.

Remedies. The engine's messages name library options that do not exist on a command line, so the envelope adds remedy — the flag or command that lifts the refusal. The table is fixed; a code absent from it has no remedy (structural refusals, corrupt data, usage errors). One exception: a ZIP_LIMIT_EXCEEDED names its bound, and the remedy is then the exact flag, --max-<bound> <value> (raise the bound for trusted input only; "none" disables it).

zipCode remedy
ZIP_PATH_TRAVERSAL --skip-unsafe (extract, stream)
ZIP_SYMLINK_REJECTED --allow-symlinks (target text as data) | --skip-symlinks (extract)
ZIP_EXTRACT_DUPLICATE_PATH --on-duplicate first|last (extract, stream)
ZIP_LIMIT_EXCEEDED --max-<bound> <size> (the bound is named in detail.limit; trusted input only)
ZIP_UNSUPPORTED_ENCRYPTION --skip-unsupported (extract, stream); no password support in 1.x
ZIP_UNSUPPORTED_METHOD --codec <module> | --skip-unsupported (extract, stream)
ZIP_UNSUPPORTED_CODEC_MODE cat / extract --codec <module> on the complete file
ZIP_UNSUPPORTED_CD_LESS_DESCRIPTOR cat / extract on the complete file (random access)
ZIP_UNSUPPORTED_ZIP64_STREAMING create without --stream (buffered entries are fully Zip64)
ZIP_ENTRY_NOT_FOUND zipnative list <archive> (names are case-sensitive)
ZIP_ENTRY_EXISTS modify --replace <name>=<path>
ZIP_STRICT_DIAGNOSTIC drop --strict, or fix the producer named by the diagnostic
ZIP_INVALID_ENTRY_NAME a plain relative name (no .., no drive, no device name)
ZIP_DUPLICATE_ENTRY_NAME unique entry names

Diagnostics. The engine's eleven diagnostic codes are informational — they ride in the envelope's diagnostics[] and are never thrown unless --strict (when the first one becomes E_CHECK_FAILED, or E_VERIFY_FAILED under verify): ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO. The errors guide explains each code and each diagnostic.

Command reference#

Fifteen commands in four groups. Every command accepts the global options; the tables below list the command-specific flags.

Create & modify#

zipnative create#

Build a deterministic ZIP from files, directories, stdin or a manifest.

zipnative create [<path>...] --output <out.zip> [options]
zipnative create --from-manifest <entries.json> -o <out.zip>
cat file | zipnative create --stdin-name <name> -o <out.zip>
Flag Default Description
<path>... / --input, -i Files and directories (directories walked recursively); --input is repeatable
--stdin-name <n> Read stdin as one entry named <n>
--from-manifest <f> JSON manifest (zipnative schema create-manifest)
--output, -o stdout Output path
--overwrite refuse (E_IO) Replace an existing output file
--base <dir> each input's parent Entry names relative to <dir>
--prefix <dir/> Prepend to every entry name
--dir-entries off Emit explicit directory entries (keeps empty directories)
--include <glob> / --exclude <glob> Keep / drop matching names (repeatable; *, **, ?)
--follow-symlinks skipped with a warning Dereference symlinks — symlink entries are never written
--method store|deflate deflate Compression method
--level 0-9 6 Deflate level
--deterministic off Pin the pure-TS encoder: identical SHA-256 on every runtime (tier: "pure-pinned")
--order canonical|insertion canonical Entry order; insertion keeps argv order (an EPUB mimetype first)
--date epoch|now|<ISO> epoch Entry timestamp; an ISO date is UTC wall-clock (range 1980–2107, 2-second resolution)
--mtime off Use each file's modification time (local time, non-reproducible)
--comment <text> / --comment-file <path> Archive comment (text, or raw bytes from a file, - = stdin; exclusive; ≤ 65 535 bytes)
--entry-comment <name>=<text> Per-entry comment (repeatable)
--preserve-mode off Store POSIX mode bits (never setuid / setgid / sticky)
--store-ext png,jpg,zip Store (no deflate) entries with these extensions — matched by file extension, so an extensionless EPUB mimetype is not caught; give it "method": "store" in a --from-manifest entry
--stream off Constant-memory writer, data-descriptor layout; entries > 4 GiB refused (ZIP_UNSUPPORTED_ZIP64_STREAMING)
--chunk-size <size> 65536 Output chunk size for the chunked writer (--stream or --stdin-name)
--parallel off Deflate across a worker pool (zipnative/worker), byte-identical per tier. Refused (exit 2) under --pure-codecs unless --deterministic is also given, and with a --codec module that registers method 0 / 8 or exports deflateImpl without --deterministic — the workers never see the module
--workers <n> cores − 1, max 8 Worker count, a non-negative integer (0 = main thread); requires --parallel
--min-job-size <size> 32k Minimum entry size sent to a worker; requires --parallel
--job-timeout <ms> 60000 Per-job cap (a positive integer) before inline fallback; requires --parallel

The status envelope reports entries, files, directories, bytes, bytesIn, method, level, deterministic, tier, order, stream, layout, parallel, skipped and diagnostics. A --stream archive is reproducible but not canonical (data descriptors) — inspect --check canonical-layout tells the two apart.

zipnative modify#

Incremental edits without recompressing untouched entries.

zipnative modify --input <a.zip> --output <b.zip> [edits] [--compact]
zipnative modify --input <a.zip> --in-place [edits]
zipnative modify --input <a.zip> -o <b.zip> --from-manifest <edits.json>
Flag Default Description
--input, -i Source archive
--output, -o stdout Output path
--overwrite refuse (E_IO) Replace an existing --output file
--remove <name> Remove an entry (repeatable)
--rename <from>=<to> Rename an entry (repeatable; never overwrites implicitly)
--replace <name>=<path> Replace an entry's content (repeatable; - = stdin)
--add <name>=<path> Add an entry (repeatable; a bare <path> uses its basename)
--add-dir <name> Add an explicit directory entry (repeatable)
--comment <text> / --comment-file <path> Set the archive comment ("" clears; raw bytes from a file, - = stdin; exclusive)
--from-manifest <f> JSON edits (zipnative schema modify-manifest)
--method / --level / --deterministic engine defaults Compression for new payloads
--date epoch|now|<ISO> epoch Timestamp for new payloads
--compact off Canonical rewrite (saveCompact()): removed data truly gone, still no recompression
--in-place off Write back to the input path (exclusive temp file + atomic rename)

Edits apply in a fixed order regardless of argv order: remove → rename → replace → add / add-dir → comment. The default save is append-only: original bytes verbatim, edits appended, a new central directory — removed or replaced content remains recoverable (data remanence), and 7-Zip's CLI is known to mis-read this layout. Pass --compact when either matters. Every untouched entry is verified (CRC-32, sizes, local header against central directory) before it is re-emitted; a lying record is refused with E_DATA / E_SECURITY and the entry name. Encrypted entries are copied as-is and counted in verifySkipped.

Read & extract#

zipnative list#

List archive entries without decompressing anything.

zipnative list --input <a.zip> [options]
zipnative list <a.zip> --format ndjson --include '**/*.json'
Flag Default Description
--input, -i stdin Archive path (positional accepted)
--format, -f text|json|ndjson text (json under --json) Output format
--long off Add mode, flags, versions, offsets, extra fields, rawNameHex, commentHex
--validate lazy|eager lazy eager cross-checks every local header up front
--include <glob> / --exclude <glob> Name filters (repeatable)
--summary { entries, files, directories, compressedSize, uncompressedSize, zip64, encrypted }
--fields a,b.c Dot-path projection of the JSON report

JSON shape: zipnative schema entries.

zipnative inspect#

A forensic report with determinism and security assertions — the CI gate. The archive is opened eagerly: every local header cross-checked, the overlap table built up front.

zipnative inspect --input <a.zip> [--format json|text] [--check <assert>]...
zipnative inspect <a.zip> --check deterministic,no-encryption,safe-names --json
Flag Default Description
--input, -i stdin Archive path (positional accepted)
--format, -f text|json text (json under --json) Output format
--entries off Include every entry (long form) in the report; ignored when --entry is given
--entry <name> Include only the named entries (repeatable; wins over --entries; an unknown name is E_NOT_FOUND)
--extra off Include extra-field payloads as hex
--check <assert> Assertion (repeatable, comma-separable); any failure prints the report then exits 1 with E_CHECK_FAILED
--summary { entries, bytes, uncompressedSize, zip64, encrypted, deterministic, canonicalLayout, diagnostics, checksPassed? }
--fields a,b.c Dot-path projection

The --check vocabulary — fifteen simple assertions and six that take a value (canonical-layout and no-data-descriptor are aliases, so twenty distinct checks):

Check Passes when
deterministic epoch timestamps + canonical order + UTF-8 flags (reproducible on every runtime)
epoch-timestamps · canonical-order · utf8-names the individual determinism facts
canonical-layout / no-data-descriptor buffered layout — a --stream archive is reproducible but not canonical
no-zip64 · zip64 Zip64 records absent / present
no-encryption no entry carries the encryption flag
no-symlinks no symlink entries
safe-names every name passes sanitizeEntryPath()
no-duplicates no duplicate names
no-diagnostics the engine emitted no diagnostic
store-only · deflate-only a single method throughout
max-entries=N · min-entries=N entry-count bounds
max-uncompressed=<size> · max-ratio=N size and worst per-entry ratio bounds
has=<name> the named entry exists
method=store|deflate|<id> every entry uses that method

JSON shape: zipnative schema inspect.

zipnative cat#

Stream one or more entries to stdout.

zipnative cat --input <a.zip> --entry <name> [--entry <name>]... [-o <file>]
zipnative cat <a.zip> <name> [<name>...]
Flag Default Description
--input, -i Archive path
--entry, -e Entry name (repeatable); entries are concatenated in order
--output, -o stdout Write to a file instead
--overwrite refuse (E_IO) Replace an existing --output file
--raw off Output the compressed payload (zero-copy), no decoding
--no-verify-crc off Skip the CRC-32 check at the end of the stream

The CRC is verified at the end of the stream (like unzip -p), so stdout may already carry bytes when E_DATA fires; with --output the partial file is removed.

zipnative extract#

Extract to a directory, secure by default — the CLI is the filesystem trust boundary the engine refuses to be.

zipnative extract --input <a.zip> --output-dir <dir> [options]
zipnative extract release.zip -d ./out --dry-run --json
Flag Default Description
--input, -i stdin Archive path (positional accepted)
--output-dir, -d required Destination (created if missing); every path re-checked with sanitizeEntryPath() and contained under this root
--include <glob> / --exclude <glob> Name filters (repeatable)
--entry, -e <name> Extract only the named entries (repeatable)
--overwrite refuse (E_IO) Replace existing files
--on-duplicate error|first|last error Two entries mapping to one sanitized path
--skip-unsafe off Skip entries whose names cannot be made safe (zip-slip, absolute, drive / UNC, NUL, ADS, device names) instead of failing — nothing unsafe is ever written
--skip-unsupported off Skip encrypted entries and methods with no registered codec (reason unsupported)
--allow-symlinks refuse Write a symlink entry's target text as a regular file (a symlink is never materialised)
--skip-symlinks off Drop symlink entries silently
--flat off Drop directories, write basenames only
--buffered off Use the in-memory extractor (many tiny entries)
--preserve-mode off Apply POSIX mode bits (never setuid / setgid / sticky)
--preserve-mtime off Apply the entry timestamp to each file

Refusals are E_SECURITY with the engine code: ZIP_PATH_TRAVERSAL, ZIP_SYMLINK_REJECTED, ZIP_EXTRACT_DUPLICATE_PATH, ZIP_ENTRY_OVERLAP, ZIP_CD_LFH_MISMATCH. Bounds are E_LIMIT (--max-entry-size, --max-total-size, --max-ratio, …).

zipnative stream#

The forward-only reader for unseekable input — stdin, pipes, a download in flight. No central directory is consulted.

curl ... | zipnative stream [--list] [--format ndjson]
curl ... | zipnative stream --output-dir <dir>
cat a.zip | zipnative stream --cat <name>
Flag Default Description
--input, -i stdin File to read sequentially
--list default mode List entries as they arrive
--output-dir, -d Extract under <dir> (sanitizeEntryPath + containment)
--cat <name> Write the named entry's data to stdout (repeatable)
--format, -f text|json|ndjson text (ndjson under --json; json with --summary / --fields) Output format
--long off Add flags, versions and extra fields
--include / --exclude, --overwrite, --on-duplicate, --flat, --preserve-mtime as extract
--skip-unsafe / --skip-unsupported off Skip instead of failing
--summary / --fields Projection of the --format json report

Trust caveat: local headers alone — there is no central directory to cross-check names, sizes, methods or attributes, so --preserve-mode, --allow-symlinks and --skip-symlinks are unavailable here, a warning: line is printed at start, and every JSON output carries trust: "local-headers-only". Prefer list / extract whenever the whole file is available.

Integrity & codecs#

zipnative verify#

Deep integrity verification in one call — the engine's ZipVerificationReport verbatim.

zipnative verify --input <a.zip> [--format json|text] [--strict]
zipnative verify <a.zip> --entry manifest.json --json
Flag Default Description
--input, -i stdin Archive path (positional accepted)
--entry, -e all Verify only the named entries (repeatable); an unknown name is E_NOT_FOUND before any output
--format, -f text|json text (json under --json) Output format
--summary { ok, entries, failed, skipped, diagnostics, selected?, error? }
--fields a,b.c Dot-path projection

The report is { ok, error, entryCount, entries[{ name, ok, crcMatch, sizeMatch, localHeaderMatch, skipped? }], diagnostics } plus failed, skipped and strict; selected (the requested names) is present only under --entry. Without --entry the report is verifyZip() verbatim; with it, the CLI opens the archive eagerly (the same structural pass) and calls verifyEntry() on each name, so a structural refusal lands in error exactly as it would from verifyZip(). Encrypted entries are honestly skipped, never faked as verified. Exit 1 / E_VERIFY_FAILED when ok is false; --strict also fails on any diagnostic. verify proves integrity and structure, not path safety: a zip-slip archive with valid CRCs is ok — gate names with inspect --check safe-names,no-symlinks or extract --dry-run before extracting.

zipnative crc32#

CRC-32 (IEEE, the ZIP checksum) of files or stdin, in 64 KiB chunks.

zipnative crc32 [<file>...] [--seed <hex>] [--expect <hex>] [--format text|json]
Flag Default Description
<file>... / --input, -i stdin Files (repeatable)
--seed <hex> 0 Continue a running checksum from this value
--expect <hex> Single input: exit 1 / E_CHECK_FAILED on mismatch (detail { expectedCrc, actualCrc })
--format, -f text|json text (<crc> <bytes> <file>) Output format

zipnative inflate#

Decompress a raw DEFLATE (RFC 1951) or registered-codec stream through the engine's resumable inflater — constant memory, exact bytesConsumed, trailing bytes reported as leftover.

zipnative inflate [--input <file>] [--output <file>] [--max-output <size>]
Flag Default Description
--input, -i stdin Compressed input
--output, -o stdout Decompressed output
--overwrite refuse (E_IO) Replace an existing --output file
--max-output <size> the effective --max-entry-size (1 GiB) Hard output bound; none only for trusted input
--method deflate|store|<id> deflate Codec (ids via --codec)
--sync off Buffer the input and use the codec's decompressSync
--allow-trailing off Silence the warning about bytes after the stream end

Errors: ZIP_DEFLATE_CORRUPT / ZIP_DEFLATE_TRUNCATEDE_PARSE, ZIP_INFLATE_OUTPUT_OVERFLOWE_DATA.

Automation & meta#

zipnative batch#

Batch orchestration in two modes.

zipnative batch --input-dir <dir> --output-dir <dir> [--task create] [create flags]
zipnative batch --input-dir <dir> --task verify
zipnative batch --manifest <tasks.json> [--continue-on-error] [--allow-codec-load]
Flag Default Description
--input-dir <dir> Directory mode: --task create archives each immediate subdirectory to <output-dir>/<name>.zip; --task verify verifies every *.zip
--output-dir <dir> Destination for --task create
--task create|verify create Directory-mode task
--overwrite refuse (E_IO) Replace existing <name>.zip files
--concurrency <n> 4 (max 64) Parallel workers
--fail-fast off Stop scheduling after the first failure
--method / --level / --deterministic / --order / --date / --comment Forwarded to every create task (any other create flag is forwarded too)
--manifest <file> Manifest mode: an ordered pipeline of whitelisted commands (create, list, inspect, extract, cat, verify, stream, modify, crc32, inflate) with @<id> output references — zipnative schema batch-manifest
--continue-on-error off Keep running independent tasks after a failure
--allow-codec-load off Permit a codec flag inside tasks (executes user code)
--format, -f text|json text (json under --json) Output format
--summary { ok, command, mode, total, succeeded, failed, skipped }
--fields a,b.c Dot-path projection

Under --json stdout is one batch document — each task's stdout is captured into tasks[i].report (parsed JSON / NDJSON) or .stdout, so create / modify / cat / inflate tasks must declare an output and stream --cat is refused at validation. Path values inside a manifest are data and are refused on ... Exit 1 carries the first failing task's E_* code.

Manifest rules (validated in full before any task runs):

zipnative doctor#

Environment and capability preflight — always offline.

zipnative doctor [--format json|text]
Flag Default Description
--format, -f text|json text (json under --json) Output format

Ten checks: cli, node (≥ 22), zipnative (package vs VERSION export), deflate-tier (node-zlib expected; pure under --pure-codecs), deflate-pinned (the tier --deterministic uses), web-streams, workers (for create --parallel), codecs, limits (the effective bounds, --max-* overrides included) and commands. Statuses ok, warn, error; exit 0 when every check passes, 1 otherwise.

zipnative schema#

Print a JSON Schema (Draft 2020-12) or the capability manifest.

zipnative schema [<subject>]     # default subject: create-manifest
zipnative schema list            # { subjects: [...] }
zipnative schema manifest        # commands, flags, codes, limits, schemas

Twenty-two subjects: inputs create-manifest, modify-manifest, batch-manifest; outputs entries, entries-summary, inspect, inspect-summary, verify, verify-summary, stream, stream-summary, batch, batch-summary, doctor, govern-verify, crc32; the envelopes status, error; errors (the E_* codes, the 39 ZIP_*E_* mapping and the diagnostics); limits, diagnostics; and manifest. Every $id is versioned: https://zipnative.dev/schema/cli/1.0.0/<subject>.schema.json.

zipnative completion#

Emit a shell completion script, generated from the same command table as --help and schema manifest.

zipnative completion bash > /etc/bash_completion.d/zipnative
zipnative completion zsh  > "${fpath[1]}/_zipnative"
zipnative completion fish > ~/.config/fish/completions/zipnative.fish
zipnative completion powershell >> $PROFILE     # `pwsh` is accepted as an alias

Path flags (--input, --output, --output-dir, --input-dir, --base, --from-manifest, --manifest, --config, --codec, --comment-file) complete files; other value flags require an argument; boolean flags take none. The shells are bash, zsh, fish and powershell (pwsh emits the same PowerShell script); anything else is exit 2.

zipnative govern#

The AI-governance / human-in-the-loop contract.

zipnative govern rules                    # the human/agent protocol
zipnative govern policy [--pretty]        # the machine-readable policy (JSON)
zipnative govern verify-issue <draft.md>  # validate a draft; exit 1 / E_POLICY on violation
Flag Default Description
--input, -i positional Draft path for verify-issue (- = stdin)
--format, -f json|text text (json under --json) Report format for verify-issue (zipnative schema govern-verify)

verify-issue fails a draft that proposes a runtime dependency or omits a reproduction; missing recommended fields and apparent anti-goal proposals (encryption, other formats, multi-disk, repair) are warnings. A passing check is necessary, never sufficient — agents are draftsmen; a human reviews and submits.

Global options#

Flag Default Description
--help, -h Global or per-command usage
--version, -V Version; with --json: { name, version, zipnative }
--json Agent mode: JSON envelope on stderr, data on stdout (batch: the report is the stdout document)
--pretty Indent JSON output under --json
--dry-run Validate and plan without writing (create, extract, modify, stream, cat, inflate, batch)
--quiet, -q Suppress progress and text diagnostics on stderr (never envelopes or errors)
--no-color Disable ANSI colour on stderr (also NO_COLOR; FORCE_COLOR turns it on; TERM=dumb off)
--strict Escalate the first engine diagnostic into E_CHECK_FAILED before any output byte (verify: E_VERIFY_FAILED)
--config <file> nearest .zipnativerc.json upward Use a specific config file
--no-config Ignore any .zipnativerc.json
--max-entries <n> 100000 Central-directory entry bound (CWE-400)
--max-entry-size <size> 1 GiB Decompressed size of one entry (CWE-400)
--max-total-size <size> 8 GiB Total decompressed size (CWE-400)
--max-ratio <n> 1024 Uncompressed / compressed ratio (CWE-409)
--max-name-bytes <size> 4096 Entry-name length (CWE-400)
--max-extra-bytes <size> 65535 Extra-field block length (CWE-400)
--max-comment-bytes <size> 65535 Comment length (CWE-400)
--max-cd-bytes <size> 256 MiB Central-directory size (CWE-400)
--max-input-size <size> 4 GiB CLI-owned bound on every buffered read (list, inspect, verify, extract, cat, modify, create --stdin-name, inflate --sync, govern verify-issue); E_LIMIT beyond it. The streaming commands are not bounded by it
--pure-codecs Skip node:zlib, run the pure-TS codec tier
--codec <module> Load an ESM module exporting { codecs: ZipCodec[] } (+ optional inflateImpl / deflateImpl). Executes user code: command line only, never from a config file. create --parallel refuses a module that registers method 0 / 8, or one with a deflateImpl unless --deterministic pins the encoder — the workers never see the module; sequentially the override is honoured and announced with a warning

Value grammars. A <size> is a decimal integer with an optional binary suffix k, m, g or t (case-insensitive), optionally followed by i, b or ib65536, 512k, 1m, 8g, 1t, 1GiB and 4gb are all binary multiples; none, inf and infinity disable the bound (a visible warning for every --max-* flag). A count (--max-entries, --max-ratio, --check max-entries=N, min-entries=N, max-ratio=N) is a plain non-negative integer or the same three words — a k / m / g suffix is refused there. Every --max-* bound, --max-input-size and inflate --max-output refuse 0 (use none). The eight --max-* flags are the engine's ZipLimits; --max-input-size is the CLI's own.

Configuration file#

.zipnativerc.json is looked up by walking upward from the working directory (the nearest file wins) unless --config <file> names one — in which case discovery is skipped and a file that does not exist is exit 2 — or --no-config disables the mechanism (then --config is ignored). The file must be a JSON object of at most 1 MB; invalid JSON, a non-object, or a larger file is exit 2. Top-level keys are bare flag names with global scope; a key that names a command and holds an object is that command's section, and a command-scoped value wins over a global one. Numbers are coerced to strings ("level": 9--level 9), arrays of strings or numbers become repeatable flags, booleans are flags; null or nested objects are ignored. Config only fills flags you did not pass — an explicit flag, even a boolean, is never overridden. codec is refused from any file, in any section, whether or not the section is for the running command.

{
  "no-color": true,
  "max-total-size": "32g",
  "create": { "deterministic": true, "level": 9 },
  "extract": { "overwrite": true }
}

Environment#

Variable Effect
ZIPNATIVE_JSON=1 same as --json
ZIPNATIVE_DRY_RUN=1 same as --dry-run
ZIPNATIVE_QUIET=1 same as --quiet
ZIPNATIVE_STRICT=1 same as --strict
ZIPNATIVE_PURE_CODECS=1 same as --pure-codecs
ZIPNATIVE_DEBUG=1 add the stack trace to an error
NO_COLOR / FORCE_COLOR / TERM=dumb colour of the stderr lines

Output is English and locale-independent: no locale is read, dates are ISO-8601 UTC, sizes use binary units, and messages are not part of the contract — branch on error.code, error.zipCode and error.remedy.

Security posture#

Conformance#

Every archive the CLI writes is ISO/IEC 21320-1:2015 conformant: the CLI repository runs its own veraZIP gate — the engine's clause-by-clause validator over a corpus of archives produced by the CLI, plus foreign integrity passes — blocking in CI and before every publish. The conformance guide explains the gate; conformant is not the same as safe, which is why the extraction guards exist on top.

Not supported#

Encryption, read or write Engine policy in 1.x (ZipCrypto is broken). Encrypted entries are detected and listed; verify skips them, extract / stream refuse them unless --skip-unsupported; inspect --check no-encryption asserts their absence. Confidentiality belongs to the document layer — see Case 5.
Other archive formats No 7z, RAR, tar, gzip; --codec is the extension point for methods other than 0 / 8
Multi-disk archives ZIP_UNSUPPORTED_MULTI_DISK
Archive repair Reported by verify, never guessed
Streamed entries > 4 GiB create --stream refuses (ZIP_UNSUPPORTED_ZIP64_STREAMING); buffered entries are fully Zip64
Network access None, in any mode

Versioning#

SemVer over the fifteen commands and their flags, the exit codes, the thirteen E_* classes and the ZIP_*E_* mapping, the envelope and report keys, the schema subjects, the .zipnativerc.json keys, the ZIPNATIVE_* variables — and the bytes written under --deterministic (a byte change is semver-major, as for the engine). Message wording, layout and key order are not a contract.

Further reading#