Documentation · macOS 15+

Agent & script reference

filecrow is a macOS 15+ command-line sender using the same delivery API, ZIP64 STORED archive, multipart transport, CRC32/SHA-256 entry metadata, and server size/ready checks as the Mac app. It ships inside the Filecrow Mac app. It uploads one archive; it never uploads originals separately. Its credential and recovery directories belong to the CLI.

Quick start

Filecrow sends files as one private download link. To send files for a person, run this one command, replacing PATH with what they asked to send. It finds the CLI inside the Filecrow Mac app and sends:

Shell
F="$(command -v filecrow || true)"
for A in /Applications/filecrow.app "$HOME/Applications/filecrow.app" \
    "$(mdfind "kMDItemCFBundleIdentifier == 'com.clientdelivery.operator'" 2>/dev/null | head -1)"; do
  [ -n "$F" ] && break
  [ -x "$A/Contents/Helpers/filecrow" ] && F="$A/Contents/Helpers/filecrow"
done
[ -n "$F" ] || { echo "filecrow not found" >&2; exit 127; }
"$F" send --json --no-interactive -- PATH

What to send.

  • A folder: pass the folder. Everything inside it is sent, subfolders included, with its structure kept. Hidden files and folders (names starting with .) and empty folders are skipped automatically, and symbolic links are refused, so there is no need to list files to leave those out. The delivery is named after the folder. When you explain the command to the person or to a command reviewer, say that Filecrow skips hidden files.
  • Specific files: list them, and add --title 'NAME'; otherwise the delivery is named after the first file.
  • Large sends take as long as the upload takes; run the command in the background if your tool has a short time limit.

Reading the result. Stdout is one JSON object.

  • Exit 0, "ok": true and "data.status": "ready": give the person data.public_url exactly as printed, as the full URL in plain text rather than behind link text: they usually paste it to someone else.
  • filecrow not found (exit 127): follow If Filecrow is not installed.
  • error.code authentication_required (exit 3): the CLI is not signed in on this Mac yet. Follow Authentication and confirmation, then run the same command again.
  • error.code credential_unavailable (exit 3): your own command sandbox is blocking the macOS Keychain; this is not a missing sign-in, so do not start a login. Run the command outside the sandbox. See Agent sandboxes.
  • Anything else: give the person error.message, do not claim the files were sent, and see Handling failures.

Never run Contents/MacOS/filecrow: that is the app itself, not the CLI. Use --json --no-interactive on every command you run; the rest of this page is reference.

Agent sandboxes

filecrow needs three things a coding agent's sandbox often blocks: the network (to filecrow.com and the upload storage), the macOS Keychain (where its sign-in is saved), and write access to ~/Library/Application Support/FilecrowCLI (its resumable-upload state). Blocked Keychain access shows up as credential_unavailable; blocked network as network_error (exit 5). If your tool runs commands in a sandbox, request to run filecrow outside it from the start. Reading these docs needs no browser; an ordinary HTTP fetch of this page is enough.

Installation

The CLI is included in the Filecrow Mac app, version 0.3.7 and later. There is nothing separate to download, and it updates with the app. It requires macOS 15 or later and lives at:

Command syntax
/Applications/filecrow.app/Contents/Helpers/filecrow

If the app was moved, the path is the app's location followed by Contents/Helpers/filecrow. Contents/MacOS/filecrow is the app itself, not the CLI. There is no separate CLI download or Homebrew formula; do not use a guessed brew command or download URL.

If Filecrow is not installed

Filecrow is invitation-only, so installing it needs a person: their invitation, their account and their email confirmation. An agent must not download or install it on its own initiative. Tell the person:

Filecrow isn't installed on this Mac. Open https://filecrow.com/install, download the app, drag filecrow into Applications, open it and sign in. Tell me when that's done and I'll send the files.

If they want you to start the download for them, ask first, then open https://filecrow.com/install in their browser and let them finish. If they have no Filecrow account, say so and point them to https://filecrow.com/; there is nothing more you can do.

If the app is older than 0.3.7

The app is installed but has no Contents/Helpers/filecrow. Ask the person to open filecrow, choose filecrow › Check for Updates… in the menu bar, and click Install Update when it appears. The CLI is present once the app has relaunched.

Commands

Command syntax
filecrow login [--email ADDRESS]
filecrow logout
filecrow whoami
filecrow send <path...> [--title TEXT] [--from TEXT] [--expires DAYS]
                      [--password | --password-file PATH] [--new]
filecrow send --resume ID
filecrow deliveries [--limit 1..100] [--cursor CURSOR]
filecrow delivery ID
filecrow delete ID [--yes]
Command Purpose
login Sign in through emailed browser confirmation
logout Revoke this CLI session and remove its credential
whoami Show the current account and allowance
send Send one ZIP64 STORED delivery or resume a draft
deliveries List deliveries with cursor pagination
delivery Inspect a delivery and its file manifest
delete Delete a delivery and disable its recipient link

All commands accept --json, --quiet, and --no-interactive. --json implies noninteractive operation. Use --help on any command for its options.

Authentication and confirmation

filecrow login --email ADDRESS requests a magic-link email and waits for approval until the server challenge deadline. Open the email's link and confirm sign-in in a browser; approval can happen on another computer. --no-interactive disables terminal prompts, but it does not bypass email approval. A login with --email --json --no-interactive can still wait for that human action.

Credentials use the CLI's own macOS Keychain item, separate from the Mac app's, so signing in to the app does not sign in the CLI. Each Mac needs one CLI sign-in; after that it stays signed in until logout or the session expires.

An agent does not need to check the sign-in before sending: send itself reports authentication_required (exit 3) when the CLI is not signed in. whoami --json --no-interactive shows the account when that is what you want to know. When a command reports authentication_required:

  1. Ask the person which email address their Filecrow account uses, and tell them: "A Filecrow email is on its way. Open it and click Confirm."
  2. Run filecrow login --email ADDRESS --json --no-interactive. It waits until they confirm, for up to 15 minutes; run it in the background if your tool has a shorter time limit.
  3. Run the original command again. If macOS showed a Keychain dialog, the person should click Always Allow.

If you cannot run a waiting command at all, ask the person to run filecrow login --email ADDRESS in Terminal instead. Never request an account token, reuse the Mac app's credential, or repeatedly retry an unauthenticated send. A refused sign-in may also require account access; report the returned error to the person.

delete prompts for y in an interactive terminal. In JSON or noninteractive mode it requires --yes; without confirmation it returns confirmation_required and exit 9. Inspect the intended delivery and obtain the human's authorization before an agent runs delete ID --yes. No active upload is deleted on interruption. logout revokes the CLI session server-side before removing it locally. If the network cannot acknowledge revocation, the credential is retained and the command fails so logout can be retried.

File selection

Directories contribute their contents without their top directory. Individual files contribute their basename; multiple roots merge into the same archive. Paths are NFC normalized and sorted deterministically. Duplicate archive paths, symlinks, special files, unreadable files, and invalid paths are refused. Dotfiles and dot directories inside a traversed directory are omitted; an explicitly selected hidden regular file is included. Empty directories are omitted. Use -- before filenames beginning with a dash.

Output for scripts and agents

Stdout carries one final result. Progress and human diagnostics use stderr. Animation appears only on a terminal, never in JSON or quiet mode. Progress counts bytes acknowledged by the server and uses the shared network speed tracker; source reads do not become confirmed upload progress.

With --json --no-interactive, stdout contains exactly one newline-terminated JSON object on success or failure. Parse stdout and check the process exit status and ok; do not parse human text or progress. send --quiet prints only the URL on success. Other human commands print a readable result. Help and version are plain text even with --json.

If a process is killed or stdout breaks, a complete envelope may be absent. Treat missing/truncated JSON as a failed command and inspect or resume the transfer; never infer successful delivery from partial output.

Success envelope (formatted here for readability):

JSON
{
  "schema_version": 1,
  "command": "send",
  "ok": true,
  "data": {
    "delivery_id": "11111111-1111-4111-8111-111111111111",
    "status": "ready",
    "public_url": "https://files.filecrow.com/d/<recipient-token>",
    "file_count": 147,
    "source_bytes": 18400000000,
    "archive_bytes": 18400025000,
    "expires_at": "2026-10-09T00:00:00Z",
    "resumed": false
  }
}

Error envelope (missing authentication example):

JSON
{
  "schema_version": 1,
  "command": "send",
  "ok": false,
  "error": {
    "code": "authentication_required",
    "message": "Run filecrow login first.",
    "retryable": false,
    "resumable": false
  }
}

Failures use the same envelope with ok:false and error instead of data. An error contains code, message, retryable, and resumable. Where available, it adds delivery_id, resume_deadline, backend_code, request_id, and details. No raw session token or password is returned. IDs are UUIDs. Byte counts are integers; send expiry/deadline strings are UTC ISO 8601. Account and history/detail timestamps preserve the API's Unix seconds. Unknown additive fields must be ignored by consumers. schema_version changes for breaking output changes. The example recipient token above is a placeholder, not a live link.

Stable exit codes

Exit Meaning
0 Success
1 Internal/unclassified API failure
2 Arguments or selection
3 Authentication or credential storage
4 Entitlement, quota, expiry ceiling, or sending disabled
5 Network failure or uncertain creation
6 Changed source, ambiguous/conflicting resume, expired draft
7 Recovery state or competing process
8 Archive verification failure
9 Destructive confirmation missing
130 / 143 SIGINT / SIGTERM

Recovery

Rerun the same send ./folder after a crash or a network failure. A single matching unfinished CLI transfer resumes automatically. The complete file plan must still match: path, size, and modification time. A partially sent entry is re-appended whole through the existing archive checkpoint machinery. No partial-file hash state or alternate journal format is introduced.

Use send --resume ID when several transfers match. Resume uses saved paths and delivery options; it accepts no new selection/options. Omit password options when resuming; the original password is already set on the delivery. --new deliberately starts a new delivery and preserves prior drafts. Use delete ID --yes to delete a draft and its local recovery state after server acknowledgement. Keep the resume deadline in error output: the multipart upload cannot be continued after it lapses. A missing/truncated spool or changed source fails closed.

Creation is not automatically retried. A lost creation response leaves a marker and returns creation_outcome_unknown; inspect deliveries before using --new. This avoids silently creating more charged deliveries. It does not add server creation idempotency.

After sealing, CLI metadata preserves the manifest, expected size, and recipient URL until commit/complete/ready and output succeed. Recovery reuses those existing idempotent endpoints. If the server is already ready it returns the original URL. An output failure retains the result for a later retry. The existing Mac app's checkpoint cleanup remains unchanged.

Configuration and security

The default API/account/auth origin is https://filecrow.com. Recipient links come from the server, normally https://files.filecrow.com; the CLI never creates one from the API origin. FILECROW_API_ORIGIN can select another HTTPS origin (loopback HTTP is allowed for development). Credentials and state are namespaced by that origin.

FILECROW_STATE_DIR overrides the default ~/Library/Application Support/FilecrowCLI. Recovery directories are private (0700); CLI metadata and locks are 0600. Do not share or commit them: they contain source paths and recipient capabilities. Passwords and sessions are not stored there. The Keychain service is com.filecrow.cli; it never borrows the app's session. Only an intentional interactive login allows credential UI. Unattended credential access fails promptly if permission/unlocking is required.

From and expiry default to the account's settings. No configuration file or production environment-token fallback is provided. An agent uses an already signed-in CLI without receiving an account secret.

Task recipes

Use --json --no-interactive for every automated operation. These recipes assume a signed-in CLI and write it as filecrow; substitute the full path from Quick start if it is not on PATH. JSON extraction recipes also require jq. Use the recipient URL returned by send, never construct one from the API origin. A success requires exit 0, ok: true, and data.status: "ready".

Send this file or folder and return data.public_url

Replace the path with the requested file or folder. Quote it, and use -- for paths beginning with a dash. This recipe preserves a failing CLI exit code and prints only the verified recipient URL on success:

Shell
if result=$(filecrow send --json --no-interactive -- ./folder); then
  printf '%s\n' "$result" | jq -er '
  select(.schema_version == 1 and .ok == true and .data.status == "ready")
  | .data.public_url'
else
  status=$?
  printf '%s\n' "$result" >&2
  exit "$status"
fi

For one file, replace ./folder with ./final.pdf. Preserve the error JSON when reporting a failure to the human; do not claim the transfer completed.

Send with delivery options

Shell
filecrow send ./folder --title 'Client finals' --from 'Studio' --expires 7 \
  --json --no-interactive

Use --password-file /path/to/private-password.txt if a password is requested. The file must be private (chmod 600); do not put the password in command arguments.

Check account and inspect deliveries

Shell
filecrow whoami --json --no-interactive
filecrow deliveries --limit 20 --json --no-interactive
filecrow delivery "$DELIVERY_ID" --json --no-interactive

For the next history page, pass the returned data.next_cursor to deliveries --cursor "$CURSOR". A null cursor means there is no next page.

Resume an interrupted send

Shell
filecrow send --resume "$DELIVERY_ID" --json --no-interactive

Use the error.delivery_id from the failed send. Alternatively, rerun the same original selection for automatic resume. Do not use --new as a retry strategy; it deliberately creates another delivery. Do not supply new password/options with --resume.

Delete an authorized delivery

Shell
filecrow delivery "$DELIVERY_ID" --json --no-interactive
# After explicit authorization for this delivery:
filecrow delete "$DELIVERY_ID" --yes --json --no-interactive

Handling failures

On authentication errors (exit 3), request human sign-in as described above. On quota or entitlement errors (exit 4), report the limitation; retrying does not grant capacity. For a resumable network failure, retry the same selection or saved ID with bounded attempts. Preserve error.resume_deadline when reporting unfinished work. Source changes, missing recovery state, or an expired deadline require human review; do not silently discard the draft or create a replacement. For creation_outcome_unknown, inspect deliveries and ask the human to resolve the uncertain creation before using --new. Never report data.public_url from a failed envelope, and never treat a draft's URL as a completed send.