AgentStack
SKILL verified MIT Self-run

Uploads Cli

skill-buildinternet-uploads-uploads-cli · by buildinternet

>-

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add skill-buildinternet-uploads-uploads-cli

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Uploads Cli? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Uploading files to uploads.sh and embedding in GitHub

What this does and why

GitHub's native image hosting (github.com/user-attachments/…) is only reachable through an authenticated browser session — there is no gh CLI or REST endpoint for it. So any image URL you put in a PR/issue body written with gh … --body-file must already point at something publicly hosted.

This skill solves that with the uploads CLI: it PUTs a local file to the uploads.sh API, which returns a stable public URL you can drop straight into markdown. No browser, no repo bloat, no signing by hand. For PRs and issues it can also create and maintain a single "attachments" comment for you via your local gh auth.

For the common case, use uploads attach . It infers the current branch's PR, uploads every file under stable attachment keys (in parallel), and maintains the comment by default. One bad file does not block the rest — JSON includes uploads and failures (exit 1 when any failed):

uploads attach ./before.png ./after.png
uploads attach ./shot.png --issue 45 --repo buildinternet/uploads

Pass --no-comment when only stable URLs are wanted. Use put for lower-level naming and output control.

The killer feature for GitHub: --pr/--issue produce hash-free, stable keys (gh///pull//), so re-uploading the same filename overwrites in place and the URL never changes. There is no confirmation prompt — hot-swap is intentional for agents and re-runs. Human mode prints >> replaced existing object (same URL) after overwrite; JSON has "replaced": true|false. Use --dry-run to preview: it prints >> would replace existing object (same URL) when the key already exists, without writing. Responses include two public URLs when the shared dual-host setup applies:

| Field | Host (default) | Use for | | ---------- | -------------------- | ---------------------------------------------------- | | url | storage.uploads.sh | Durable link, click-through, non-GitHub embeds | | embedUrl | embed.uploads.sh | GitHub PR/issue markdown (` / `) |

embedUrl is the same object with badge-style no-cache headers so GitHub Camo revalidates after an overwrite. CLI/MCP markdown and the managed attachments comment already prefer embedUrl. Override with UPLOADS_EMBED_PUBLIC_BASE_URL (empty disables; self-host set your no-cache CDN base).

Prerequisites

  • Node.js ≥ 22.
  • The CLI. Install globally for repeated agent use, or run it once with npx:

``bash npm install --global @buildinternet/uploads npx @buildinternet/uploads --help uploads --version ` Every example in this skill uses the **global** uploads … binary (as after install). Inside the uploads monorepo only, pnpm uploads … builds from local source first — do not write product/PR examples that way. Prefer --json or --quiet` for scripted steps (keeps stderr clean and skips optional update-available hints).

  • A configured token (one-time — see below). Check with uploads doctor.
  • gh CLI, authenticated — only for the --comment / comment features that

write to a PR/issue. Plain uploads don't need it.

One-time setup

Config lives in a user-owned file so it survives skill reinstalls:

~/.config/buildinternet/config        # or $XDG_CONFIG_HOME/buildinternet/config

Resolution is per key, first match wins: CLI flags (--api-url, --token, --workspace) → UPLOADS_* environment vars → --env-file $BUILDINTERNET_CONFIG → the shared config file. For a one-off against a different API or workspace, just export the var or pass --env-file.

The fastest path is uploads login. Have a workspace admin invite your email to a workspace first, then run it once, interactively, to sign in:

uploads login          # opens a browser to approve sign-in, saves config, runs doctor
uploads login --workspace acme   # only needed if your account can access more than one

That's a one-time, human-in-the-loop step (device sign-in needs a browser); once the config file is written, every later uploads invocation — including from a non-interactive agent — just reads the saved token. Routine agents never need ADMIN_TOKEN.

Inviting a teammate (workspace admin/owner only): /account/workspaces in the browser, or:

uploads invite create --email teammate@example.com --workspace acme

Device login as you (not ADMIN_TOKEN / not a workspace token). The CLI prints an accept URL to share if email isn’t configured. Invitee accepts, then uploads login.

For headless machines with no browser at all, an operator can mint a token directly (/admin/tokens, ADMIN_TOKEN-gated — see docs/admin-tokens.md) and hand it to the agent as UPLOADS_TOKEN, or an enrollment code (upe_…, an alternative invite-link/code path — useful when you don't have the recipient's email) can be exchanged with uploads login --code. Neither is the normal path for new setups.

The resulting token defaults to 90 days and files:read plus files:write; it cannot delete files unless an administrator explicitly grants files:delete. Verify or inspect setup at any time:

uploads setup                                  # shows effective configuration
uploads doctor                                 # version + health + auth + workspace
uploads doctor --json

Tokens encode their workspace (up__…), so the CLI infers --workspace when you don't set it. Legacy administrator-minted tokens remain valid. See "Config commands" for setting put defaults (default repo, prefix, image width) once instead of per-command.

Core workflow: uploads put

Upload one or more files and get back URL(s) plus ready-to-paste markdown. Multiple paths upload in parallel; multi-file JSON is { uploads, failures } (exit 1 when any failed). Single-file JSON stays a flat object.

uploads put ./shot.png --repo myorg/myapp --ref 1722 --alt "New live feed cards" --width 700
uploads put ./before.png ./after.png

Human output goes to stderr; the URL and markdown to stdout, so you can pipe or capture them. Use - as the file to read from stdin.

Key options (uploads put --help for all):

| Flag | Purpose | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | --alt | Alt text for the markdown (default: filename). Always write meaningful alt text. | | --width | Emit sized ` HTML instead of (markdown can't size images). | | --repo | Repo segment of the auto key (default: git remote, or UPLOADSDEFAULTREPO). | | --ref | PR/issue/branch/date segment (default: today, or UPLOADSDEFAULTREF). | | --destination | Typed root: screenshots \| gh \| f (sets key prefix). | | --prefix | Key prefix (default: screenshots, or UPLOADSDEFAULTPREFIX). | | --key | Set the object key explicitly; skips the auto-naming below. | | --name | Clean filename for the key's leaf + default alt (no /); keeps the --pr/default path. Not with --key. | | --dry-run | Resolve + print the key and final public URL without uploading; reports if the key would replace an existing object. Not with --comment/--gallery. | | --content-type | Override the content type (else inferred from extension; ignored when optimize rewrites the body). | | --frame | Opt-in chrome before optimize: phone, browser, iphone-16-pro. | | --frame-url | Address bar text for --frame browser. | | --frame-fit cover\|contain | How the shot fills the screen (default: cover). | | --no-optimize | Skip client-side image optimization (default: still images → WebP). Or UPLOADSNOOPTIMIZE=1. | | --optimize-max-edge | Max long edge when optimizing (default: 2400). | | --optimize-quality | WebP quality when optimizing (default: 85). | | --keep-exif | Keep EXIF/XMP/ICC when optimizing (default: **strip** for privacy). Or UPLOADSKEEPEXIF=1. | | --no-git | Don't derive --repo from the git remote (or UPLOADSNOGIT=1). | | --format human\|url\|markdown\|json | Control stdout. --json (global) forces json. | | -w, --workspace ` | Override workspace (wins over env and token inference). |

Image optimization (default on): PNG/JPEG and similar still images are re-encoded to WebP (long edge capped at 2400px, quality 85) before upload so PR/issue embeds stay lean. The object key/filename extension follows the output (e.g. shot.png…/shot.webp). EXIF/XMP is stripped by default (public URLs + privacy); pass --keep-exif when the discussion needs the embedded image metadata. Animated GIF, SVG, video, and non-images are left alone; if the optimized payload is not smaller, the original is uploaded. Use --no-optimize when you need lossless originals.

Frames (opt-in): --frame phone (generic bezel), --frame browser, or --frame iphone-16-pro (community device art, cached under ~/.cache/uploads/frames). Default is no frame.

How keys work — three paths, no extra naming modes:

| Intent | Command | | ------------------------------------- | -------------------------------------------------------------- | | Just upload it, give me a URL | uploads put ./file.png | | Explicit typed destination | uploads put ./file.png --destination screenshots | | Stable GitHub embed I might re-upload | uploads put ./file.png --pr | | Stable --pr path but a clean leaf | uploads put ./capture-2026-…Z.png --pr --name hero.png | | I know exactly where it goes | uploads put ./file.png --key screenshots/…/x.png |

Timestamped captures break stable --pr keys — pass --name hero.webp to keep a clean leaf. Use --dry-run to preview the exact public URL before uploading.

Default put is the fast path; you don't need --key, --prefix, or --repo. Without --key, keys look like ///-. — the short hash prevents collisions without random names or a separate "preserve name" flag. Prefer --destination screenshots (or gh with --pr/--issue) over inventing roots — workspaces may allowlist only those destinations. Override with --key only when you have a reason, and keep the key under an allowed root.

Output formats — pick what you'll consume:

uploads put ./shot.png --format url        # just the URL, for scripting
uploads put ./shot.png --format markdown   # just the / snippet
uploads put ./shot.png --json              # {workspace,key,url,size,markdown}

Custom metadata & search

Every object can carry queryable key-value metadata (distinct from optimize/frame provenance) — tag uploads at put time, then find them later. Useful pairs for screenshots:

| Key | Example | Use | | ------ | ------------------------------ | ---------------------------------- | | url | https://app.example/settings | Page URL the screenshot represents | | path | /settings | In-app route or navigation path | | app | web / ios / android | Which surface is shown |

uploads put ./settings.png \
  --meta url=https://app.example/settings \
  --meta path=/settings \
  --meta app=web

uploads attach ./checkout.png \
  --meta url=https://app.example/checkout \
  --meta path=/checkout \
  --meta app=ios

uploads meta get screenshots/myapp/42/settings.webp
uploads meta set screenshots/myapp/42/settings.webp path=/onboarding --delete url
uploads list --meta app=web --meta path=/settings   # ANDed, repeatable
uploads find app=web path=/settings                 # same filter, positional pairs

Rules (validated client-side, fail-fast, before uploading): key ^[a-z][a-z0-9._-]{0,63}$ (lowercase, dot-namespacing allowed, e.g. gh.repo); value 1–512 printable ASCII characters; --meta k=v may repeat up to 24 times per request; a value may itself contain = (only the first = splits key from value). content-sha256 and visibility are reserved (server-computed / the real R2 visibility gate, respectively). uploads attach writes its own gh.* reserved-by-convention keys automatically — see below.

On the default screenshots/… path, put also auto-derives GitHub context and stamps gh.repo/gh.kind/gh.number/gh.ref from the current branch's PR (or a numeric --ref), so the file's /f/ page shows an "Attached to" link. This is on by default and best-effort; disable it with --no-auto, --no-git, or UPLOADS_NO_AUTO_META=1. On this auto path an explicit --meta gh.* overrides the auto-derived value — the opposite of the --pr/--issue precedence below, where the target's own gh.* always wins.

Re-upload semantics: re-uploading to an existing key with --meta replaces that file's entire metadata set (delete-then-set, not a merge); re-uploading without --meta at all preserves the existing metadata untouched. Use uploads meta set to edit individual keys without disturbing the rest.

Non-gh.* metadata values supplied via --meta (CLI) or metadata (MCP) render on the object's public /f/ file page. Treat them like the URL itself: don't put internal notes, secrets, tokens, IDs, or private paths in them.

Public media galleries

Use galleries when several existing public uploads should be shared as one ordered collection. A gallery has an opaque, API-returned public URL; do not derive one in scripts. Anyone who knows the URL can view the gallery and its media. GitHub repository visibility does not make it private, and

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.