cancho-tools

archive 0.8.0

List, extract and create tar archives, gzip- or zstd-compressed or not (and read bzip2- and xz-compressed ones), and zips, and compress one file with gzip or zstd; refuses links, absolute names, .. and overwriting by rule, checks the whole archive before writing any of it, and writes the same bytes for the same files.

archive [--root DIR] --op list|extract|create [--into DIR] [--as tar|tgz|gz|zip] [--dry-run] [--max-bytes N] [--max-entries N] ARCHIVE [PATH...]
output
NDJSON stream ending in an end record
schema
archive.v1
reversibility
irreversible-bounded
guarantees
deterministic, dry_run
concurrency
read-only: it never writes, so any number may run at once
confinement
beneath: with --root, paths are checked lexically against it and then opened beneath it one component at a time, following no symbolic link (path.symlink), so nothing outside the root is reachable through one; without it, a reader opens the path as given, links and all, and a writer still follows no link in the file's own name
reads the environment
no
reads the clock
no
compiler
de4e52fd8180
for an agent
SKILL.md (archive skill)

Authority

Derived by cancho authority from the source, embedded in the binary, and checked against tools.toml's ceiling in CI. Every label is bounded.

What the row cannot say:

Operands

OperandRoleHow manyMeaning
ARCHIVEpath-write1the archive to list or extract, or the one to create (it must not exist)
PATH...path-read0 or morecreate: the files and directories to add, in order (a directory with everything beneath it, sorted). For list and extract these are the entries to select, as list shows them (a directory selects everything beneath it), and none selects every entry

Flags

FlagKindRoleDefaultMeaning
--rootpathrootresolve every path relative to this directory (default: the working directory) and refuse paths outside it
--opchoice:list/extract/createnonelistlist shows the entries, extract writes them beneath --into, create writes an archive of the PATHs
--intopathpath-writeextract beneath this directory, which must exist (default: the root)
--as -achoice:tar/tgz/gz/zip/tzst/zstnonetgzwhat create writes: a tar, a gzip-compressed tar, one file gzipped, a zip, a zstd-compressed tar, or one file zstd-compressed
--dry-runboolguardcheck everything and report what would be written, exit 9, write nothing
--max-bytesnatnone67108864the largest archive read, expanded size included, and the largest archive written (ceiling 1073741824)
--max-entriesnatnone10000the most entries read or written (ceiling 1000000)

Limits

LimitDefaultCeiling
max-bytes67,108,8641,073,741,824
max-entries10,0001,000,000

Exit codes

CodeNameMeaning
0SUCCESSwhat was asked was done; zero matches is success
1GENERAL_ERRORthe operating system failed mid-operation, or the tool caught a bug in itself
2INVALID_ARGSthe invocation is malformed or names something of the wrong kind; nothing was read
3NOT_FOUNDa named input does not exist
4PERMISSION_DENIEDthe operating system refused, or the tool's own confinement did
5CONFLICTstate is not what the caller said it would be
8PRECONDITION_FAILEDthe answer is no, or a limit was reached
9DRY_RUNa --dry-run completed; planned_actions lists what an apply would do

Rules (44)

Match on rule, not on the message. A retry repair is an argv that can be run as it stands, and never widens what the tool may touch.

RuleExitRepairWhen
args.unknown-flag2sometimesa flag that is not in the tool's table
args.missing-value2nevera flag that takes a value was given none
args.bad-value2nevera flag's value is not of the flag's kind
args.unexpected-value2nevera flag that takes no value was given one
args.duplicate-flag2neverthe same flag was given twice
args.conflict2nevertwo flags that exclude each other were both given
args.missing-operand2neveran operand the tool needs was not given
args.too-many-operands2nevermore operands than the tool takes
args.required-flag2nevera flag the tool needs was not given
path.empty2neveran empty path
path.dotdot2sometimesa path with a `..` component
path.absolute2alwaysan absolute path where one relative to --root is wanted
path.outside-root4neveran absolute path that is not under --root
path.too-long2nevera path longer than 4096 bytes
path.symlink4nevera path that runs through a symbolic link, which the tool does not follow
io.not-found3nevera named input does not exist
io.not-a-directory3nevera path runs through something that is not a directory
io.is-a-directory2nevera directory where a file is wanted
io.permission-denied4neverthe operating system refused access
io.read-failed1neverthe operating system failed a read
io.write-failed1neverthe operating system failed a write, or a write was short
io.rename-unsupported8neverthe filesystem cannot refuse an existing name atomically, so move did not rename
conflict.exists5never--create was given and the path exists
conflict.locked5neveranother writer holds the path's lock
archive.not-an-archive8neverneither gzip nor a tar: no gzip magic and no tar header with a valid checksum
archive.bad-gzip8nevera gzip header that cannot be read, a second member, or bytes after the member
archive.crc-mismatch8neverthe gzip trailer's CRC-32 or length is not the data's
archive.bad-deflate8neverthe DEFLATE stream is invalid
archive.bad-checksum8nevera tar header's checksum is not its bytes'
archive.bad-header8nevera tar header field that cannot be read
archive.truncated8neverthe archive ends inside a header or body, or before its end marker
archive.unsafe-name4neveran entry name that is absolute, has `..` or holds a NUL
archive.entry-kind8neveran entry that is not a regular file or a directory
archive.duplicate-entry8nevertwo entries name the same file
archive.directory-missing8neverthe --into directory, or one above it, does not exist
archive.special-mode8neveran entry has a setuid, setgid or sticky bit
archive.no-match8nevera selected path matches no entry of the archive
archive.unsupported8neverthe archive uses encryption, zip64, several disks, a zip method other than stored and deflate, a randomised bzip2 block, an xz filter other than LZMA2, or a zstd frame that needs a dictionary
archive.bad-bzip28nevera bzip2 stream that is not valid, or bytes after it
archive.bad-xz8neveran xz stream that is not valid, or bytes after it
archive.bad-zstd8nevera zstd frame that is not valid, or bytes after it
archive.name-too-long8nevera name that does not fit a tar header
limit.archive-too-large8neverthe archive, expanded, would pass --max-bytes
limit.too-many-entries8nevermore entries than --max-entries

Evidence

each gate is a test in cancho-tools/tests/conformance, run by CI on the commit this binary was built from:

Agent in the loop: not run: no claim that this tool is more agent-friendly than the incumbents is made (cancho docs/agent-toolbox.md section 7.2).