# CLI reference

> vcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API.

- Repository: MaxLeiter/vcup
- GitHub: https://github.com/MaxLeiter/vcup
- Human docs: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb
- Complete Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms-full.txt

## Source Files

- `cli.ts`
- `package.json`
- `README.md`

---

---
title: "CLI reference"
description: "vcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API."
---

The `@maxleiter/vcup` package publishes a single Bun entrypoint (`cli.ts`) as the `vcup` binary. The CLI loads API credentials, uploads files or stdin to `POST /api/upload` with Bearer auth and an `X-Filename` header, prints either the proxy or raw URL from the JSON response, and deletes blobs via `DELETE /api/delete` with `X-Blob-Url`.

## Binary and runtime

| Property | Value |
| --- | --- |
| Package | `@maxleiter/vcup` |
| Binary name | `vcup` |
| Entry file | `cli.ts` (shebang `#!/usr/bin/env bun`) |
| Runtime | Bun (streaming upload uses Bun `duplex: "half"`) |

Install globally with `bun install -g @maxleiter/vcup` or `npm install -g @maxleiter/vcup`. The published `bin` field maps `vcup` directly to `./cli.ts`.

## Command synopsis

```text
vcup [options] [<file>]
vcup rm <url>
vcup delete <url>    # alias for rm
```

| Invocation | Action |
| --- | --- |
| `vcup <path>` | Upload a single file; basename becomes `X-Filename` |
| `vcup` (stdin piped) | Upload stdin body; filename fixed to `paste.txt` |
| `vcup --raw <path>` | Upload; print `raw` blob URL instead of proxy `url` |
| `vcup rm <url>` | Delete by proxy URL (`/f/...`) or raw blob URL |
| `vcup --help`, `vcup -h` | Print built-in help (exit 0) |
| `vcup` (no args, TTY stdin) | Print help (exit 0) |

There is no subcommand name for upload: any first argument that is not `rm`, `delete`, `--help`, or `-h` is treated as an upload path (after `--raw` is stripped).

## Flags

<ParamField body="--raw" type="boolean">
When present on an upload invocation, stdout receives the `raw` field from the upload JSON response (Vercel Blob public URL) instead of the proxy `url` (`{base}/f/{slug}`).
</ParamField>

<ParamField body="--help" type="boolean">
Alias: `-h`. Prints usage and configuration hints, then exits without calling the API.
</ParamField>

<Note>
The CLI does not define short flags for upload paths, verbosity, or custom filenames. Filename always comes from the file basename or `paste.txt` for stdin.
</Note>

## Stdin uploads

Upload-from-stdin activates when **no file path argument** remains after removing `--raw` **and** `process.stdin.isTTY` is false (pipe or redirect).

<Steps>
<Step title="Detect non-TTY stdin">
If `fileArgs` is empty and stdin is not a TTY, the CLI reads the full stdin stream into memory in chunks.
</Step>
<Step title="Assign default filename">
The upload uses the literal filename `paste.txt` for the `X-Filename` header, regardless of pipe content.
</Step>
<Step title="Stream to API">
The buffered body is sent through the same progress stream and `POST /api/upload` path as file uploads.
</Step>
</Steps>

Examples:

```bash
echo "hello world" | vcup
cat logs.txt | vcup
```

<Warning>
If you run `vcup --raw` with no file and a TTY stdin (no pipe), the CLI prints help and exits with code **1**. Piping is required for stdin-only uploads.
</Warning>

## File uploads

When a path argument is present:

- The path is resolved with `resolve()` relative to the current working directory.
- Missing files → stderr message, exit **1**.
- Directories → stderr `Cannot upload a directory`, exit **1**.
- File contents are read fully into memory, then chunked for upload progress on stderr.
- `X-Filename` is `basename(filePath)`.

Progress displays on **stderr** as a 33-character bar, percent, sizes, and speed; stdout stays clean for the final URL only.

## Delete (`rm`)

```bash
vcup rm https://your-host/f/abc123/filename.png
vcup rm https://....public.blob.vercel-storage.com/...
```

| Input URL type | CLI behavior |
| --- | --- |
| Contains `/f/` | Treat as proxy URL; extract slug after `/f/`; requires client `BLOB_STORE_URL` to build `{BLOB_STORE_URL}/{slug}` |
| Otherwise | Passed through as the blob URL in `X-Blob-Url` |

The delete request is `DELETE {config.url}/api/delete` with headers below. On success the CLI prints `Deleted` to stdout.

<Warning>
Deleting via a proxy URL without `BLOB_STORE_URL` in the **client** environment fails before any API call, with instructions to pass the raw blob URL or set `BLOB_STORE_URL`.
</Warning>

## Configuration loading

Before upload or delete, `loadConfig()` resolves API base URL and token:

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set → use them.
2. Else if `~/.vcuprc` exists → parse JSON; env vars override individual keys when only one is set.
3. Else → stderr with example JSON, exit **1**.

```json
{
  "url": "https://your-vcup.vercel.app",
  "token": "your-token"
}
```

The `url` value is the deployment origin (no trailing path); requests append `/api/upload` or `/api/delete`.

## API requests from the CLI

### Upload

:::endpoint POST /api/upload
CLI-initiated upload to the configured deployment.
:::

<RequestExample>

```http
POST https://your-vcup.vercel.app/api/upload HTTP/1.1
Authorization: Bearer <VCUP_TOKEN>
X-Filename: screenshot.png
Content-Type: application/octet-stream

<streaming body>
```

</RequestExample>

<ResponseExample>

```json
{
  "url": "https://your-vcup.vercel.app/f/abc123/screenshot.png",
  "raw": "https://....public.blob.vercel-storage.com/abc123/screenshot.png"
}
```

</ResponseExample>

| Header | Set by CLI | Required by server |
| --- | --- | --- |
| `Authorization` | `Bearer ${token}` from config | Yes; must match server `VCUP_TOKEN` |
| `X-Filename` | File basename or `paste.txt` | Yes; missing → 400 |

The request body is a `ReadableStream` of 64 KiB chunks with `duplex: "half"` (Bun). The server reads the raw stream into Vercel Blob `put()` with `addRandomSuffix: true`.

On failure the CLI prints `Upload failed: {status} {body}` to stderr and exits **1**. On success it prints one line to stdout: `data.url` by default, or `data.raw` when `--raw` was passed.

### Delete

:::endpoint DELETE /api/delete
CLI-initiated blob deletion.
:::

<RequestExample>

```http
DELETE https://your-vcup.vercel.app/api/delete HTTP/1.1
Authorization: Bearer <VCUP_TOKEN>
X-Blob-Url: https://....public.blob.vercel-storage.com/<slug>
```

</RequestExample>

| Header | Set by CLI | Required by server |
| --- | --- | --- |
| `Authorization` | `Bearer ${token}` | Yes |
| `X-Blob-Url` | Resolved blob store URL | Yes; missing → 400 |

Success: stdout `Deleted`. Failure: stderr `Delete failed: {status} {body}`, exit **1**.

## Built-in help text

Running `vcup --help`, `vcup -h`, or `vcup` with no arguments and a TTY stdin prints:

```text
vcup — simple file sharing via Vercel Blob

Usage:
  vcup <file>              Upload a file
  echo "hi" | vcup         Upload from stdin
  vcup rm <url>            Delete a file by its proxy or raw URL
  vcup --raw <file>        Print raw blob URL instead of proxy URL
  vcup --help              Show this help

Config (~/.vcuprc):
  { "url": "https://your-vcup.vercel.app", "token": "your-token" }

Or set VCUP_API_URL and VCUP_TOKEN environment variables.
```

Help does not load config or contact the API. Exit code **0**.

## Exit codes

| Code | When |
| --- | --- |
| **0** | Help displayed; successful upload (URL on stdout); successful delete (`Deleted`) |
| **1** | Missing config; file not found; directory upload; `vcup rm` without URL; proxy delete without `BLOB_STORE_URL`; non-OK upload/delete HTTP response; upload with `--raw` only and TTY stdin (no file, no pipe) |

The CLI does not define custom exit codes beyond **0** and **1**.

## Client environment variables

| Variable | Role in CLI |
| --- | --- |
| `VCUP_API_URL` | API origin (with `VCUP_TOKEN`, skips rc-only path when both set) |
| `VCUP_TOKEN` | Bearer token for upload and delete |
| `BLOB_STORE_URL` | Required to resolve proxy URLs (`/f/...`) into blob URLs for `vcup rm` |

Server-side variables (`BLOB_READ_WRITE_TOKEN`, `VCUP_BASE_URL`, etc.) affect API responses but are not read by `cli.ts` except indirectly via upload JSON.

## Sequence: upload with progress

```mermaid
sequenceDiagram
  participant User
  participant CLI as cli.ts
  participant API as POST /api/upload
  participant Blob as Vercel Blob

  User->>CLI: vcup file.png
  CLI->>CLI: loadConfig()
  CLI->>CLI: readFile + progressStream
  CLI->>API: Authorization + X-Filename + stream body
  API->>Blob: put(filename, stream)
  Blob-->>API: blob.url
  API-->>CLI: JSON url + raw
  CLI-->>User: stdout url (or raw if --raw)
```

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence used by `loadConfig()`.
</Card>
<Card title="Upload and delete" href="/upload-and-delete">
Workflow-oriented guide for uploads, `--raw`, and `vcup rm`.
</Card>
<Card title="API reference" href="/api-reference">
Server handlers for `/api/upload`, `/api/delete`, and `/f/:slug`.
</Card>
<Card title="Environment variables" href="/environment-variables">
Full client and server variable matrix.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Common CLI failure modes and HTTP status codes.
</Card>
</CardGroup>
