file-upload publishes intentional public files, PR evidence, videos, and lead-magnet assets to a dedicated Cloudflare R2 bucket. It uses streaming multipart upload, refuses existing keys by default, and supports only exact-object deletion.
Requires Node.js 24, npm, and S3-compatible R2 credentials. A release installation does not require a Vault or workspace checkout:
ctx9 install file-upload
file-upload --versionThe installed launcher uses FILE_UPLOAD_NODE_BIN when set, otherwise Node 24 from PATH, a node24/nodejs24 command, or Homebrew's node@24 installation. This keeps the command on Node 24 even when another Node major is the machine default.
Supply the six runtime variables in the process environment, or put them in ~/.config/ctx9/file-upload.env with owner-only permissions. .env.example documents the names without choosing a bucket, prefix, or public domain for the user.
R2_FILE_UPLOAD_BUCKET_NAME=your-public-bucket
R2_FILE_UPLOAD_ROOT_PREFIX=uploads
R2_FILE_UPLOAD_PUBLIC_BASE_URL=https://files.example.com
The R2 credentials should belong to a token with Object Read & Write access to only the selected bucket. Do not copy credentials into this repository.
The optional SECRET_BINDINGS_BIN and FILE_UPLOAD_SECRET_BINDINGS_DIR variables can delegate configuration loading to Secret Bindings. K3S_INFRA_DIR may additionally name an explicit compatible read-only environment provider. These integrations are optional and never discovered through a private Vault or hardcoded workspace path. The final Node process receives only the uploader's allowlisted environment.
Repository development still uses npm ci, npm run build, and ./install-cli.sh for a source-linked command.
file-upload doctor
file-upload upload ./signup-flow.mp4 --category pr --project impression --group pr-1842
file-upload upload ./guide.pdf --category public --project claudeche --group press
file-upload upload ./resource.pdf --category lead-magnets --project claudeche --group 18-till-i-die --json
file-upload list --category lead-magnets --project claudeche --group 18-till-i-die
file-upload delete uploads/lead-magnets/claudeche/18-till-i-die/resource.pdf --yesCategories map to:
uploads/pr/<repository>/<pr-or-branch>/<filename>
uploads/public/<project>/<group>/<filename>
uploads/lead-magnets/<project>/<offer-slug>/<filename>
Project/group segments use lower-kebab-case. Known env, credential, key, cookie, database, and secret-like filenames are rejected. Existing keys require --replace; versioned filenames are preferred. --content-type, --content-disposition, and --cache-control provide explicit metadata overrides.
scripts/ensure-bucket.sh idempotently gets or creates only the configured bucket through the Cloudflare API and then verifies it. It requires CLOUDFLARE_API_TOKEN, but does not attach a public domain or enable public access.
Run it only during an explicitly approved infrastructure rollout:
scripts/ensure-bucket.shBinding files.ctx9.com to the bucket is a separate Cloudflare action. Ordinary tests mock S3 and Cloudflare and do not touch real storage.
npm run check
npm run check:shell