CLI usage
The file-concat CLI runs the same @fileconcat/core library that
powers the web app. Same filtering semantics, same output format,
no browser required.
Install
npm install -g @fileconcat/cli
# or
pnpm add -g @fileconcat/cli
Node 18 or newer. The published package is @fileconcat/cli; the bin
command it installs is file-concat.
You can also invoke it without a global install:
npx @fileconcat/cli ./src
Usage
file-concat [path] [options]
file-concat concat [path] [options]
The bare command and the explicit concat subcommand share the same
flag set. [path] defaults to the current directory.
Flags
| Flag | Short | Default | Notes |
|---|---|---|---|
[path] | . | Positional argument; root to process | |
--output <file> | -o | output.xml / output.md | Default extension follows --style. Ignored when --stdout set. |
--style <style> | -s | xml | xml, markdown, or md |
--max-size <mb> | -m | 32 | Per-file size cap in megabytes |
--no-hidden | hidden files excluded | Pass to force-exclude after a config opt-in | |
--no-binary | binary files excluded | Pass to force-exclude after a config opt-in | |
--exclude <patterns...> | -e | Repeatable; one or more glob patterns | |
--config <file> | -c | Path to a JSON config file | |
--no-parse | documents are extracted | Pass to skip text extraction entirely — PDF/Office, notebooks and transcripts alike | |
--expand-archives | archives left packed | Unpack zip, tar and gzip archives and include their contents | |
--line-numbers | Prefix each line of file content with its line number | ||
--no-gitignore | .gitignore honored | Pass to ignore the project's .gitignore files | |
--stdout | Write the concatenated output to stdout. Mutually exclusive with --json. | ||
--quiet | -q | Suppress progress logs on stderr. Errors still print. | |
--json | Emit a single-line JSON summary on stdout when finished. |
Defaults for --no-hidden and --no-binary reflect that hidden and
binary files are already excluded out of the box. The flags are there
to flip a per-project override back to the default.
Example
file-concat ./src \
--exclude "**/*.test.ts" \
--exclude "**/__snapshots__/**" \
--style markdown \
--output context.md
This walks ./src, drops every test file and snapshot directory,
emits Markdown with fenced code blocks, and writes to context.md.
Config file
The CLI looks for a config file in the working directory at startup. Names searched in order:
.fileconcatrc.fileconcatrc.jsonfileconcat.config.json
Pass --config <path> to point at a different file. The format is
JSON. A minimal example:
{
"version": 1,
"maxFileSizeMB": 32,
"exclude": ["**/*.test.ts", "**/__snapshots__/**"],
"style": "markdown",
"output": "context.md"
}
Command-line flags override config-file values for the same fields.
Documents (PDF, DOCX, XLSX, PPTX, ODT, ODS, ODP, RTF)
Documents are extracted to text by default and inlined alongside your
code. Pass --no-parse to turn that off and let them fall through as
binaries instead.
file-concat ./repo # documents extracted (default)
file-concat ./repo --no-parse # documents left out as binary
Which files qualify is decided by their leading bytes, not their
name. A .docx someone renamed .zip is extracted, a PDF with no
extension at all is read, and a genuine .zip is never mistaken for
the Office container that shares its signature.
Parse failures (corrupt files, password-protected PDFs, scanned pages
with no text layer) are logged to stderr and counted under
skippedBreakdown.parseFailed in the JSON summary. They never fail
the run.
Notebooks, transcripts and messages (IPYNB, SRT, VTT, EML)
These are already text, so they used to go in whole — which meant base64 PNGs of every plot in a notebook, MIME boundaries and base64 attachments in a saved message, and a cue index plus a timestamp range for every two seconds of a transcript. All three are now rendered instead:
.ipynbbecomes markdown. Prose cells verbatim, code cells fenced in the notebook's own language, text output and tracebacks kept. Images and other binary output are dropped and counted, so the summary says how much was left behind..srt/.vttbecome the transcript. Indices, timestamps and styling go; a line that a rolling caption repeats in the next cue is emitted once. Timestamps are dropped rather than thinned — a transcript is read for what was said..emlbecomes the correspondence: From, To, Cc, Date, Subject, then the body, quoted-printable and MIME-encoded headers decoded, and an HTML-only message flattened to its words. Attachments are named in the text and counted, never inlined and never silently dropped.
Same rule as documents: the leading bytes decide, so a notebook
saved as .json, a transcript saved as .txt, and a message saved with
no extension at all are recognized. .msg — Outlook's own format — is
not read yet.
--no-parse turns this off along with document extraction and leaves
the raw file in the bundle.
Archives
Archives are left packed by default. Walking a directory that happens
to contain assets.zip is not a request to inline it, and doing so
would bury the code you actually asked for. Pass --expand-archives
when you do want the contents:
file-concat ./repo --expand-archives
Zip, tar, and gzip (including .tar.gz) are unpacked; each entry lands
under a folder named after the archive and faces the same size, binary
and extraction handling as a loose file. Nesting is one level deep — an
archive inside an archive stays packed. .rar and .7z are recognized
but cannot be opened yet, and are reported as skipped.
For AI coding agents
The CLI is designed to drop into Claude Code, Cursor agent mode, aider, or any custom orchestrator without surprises. Three contracts hold:
- Stdout is the artifact, stderr is the chatter. Progress lines
and warnings always go to stderr. Stdout only carries the
concatenated output (with
--stdout) or the JSON summary (with--json).file-concat ./repo --stdout 2>/dev/nullis a clean pipe;file-concat ./repo --json 2>>logs.txtpreserves progress. --jsonprovides a single-line machine-readable summary.
The summary appears on stdout, so harness code can pipe it directly into{ "files": 42, "parsed": 3, "skipped": 5, "skippedBreakdown": { "oversize": 1, "binary": 2, "readError": 0, "parseFailed": 2 }, "totalBytes": 184320, "outputPath": "output.xml", "elapsedSeconds": 0.213, "style": "xml" }JSON.parse.- Exit codes are stable.
0on success (including partial-skip outcomes),1on any fatal error or flag conflict. Errors are written to stderr with anError:prefix.
Recipes
Pipe a directory straight into an LLM CLI:
file-concat ./service --stdout --quiet | claude -p "explain this codebase"
Generate context plus a machine-readable summary for a wrapper script:
file-concat ./service -o ctx.xml --json | jq '.parsed'
Pull a shipped archive's contents in alongside the source:
file-concat ./service --expand-archives -o ctx.xml
See also
- File filtering covers the glob syntax, which is shared with the web app's filter textareas.
- Configuration describes the web app's parallel configuration surfaces.