
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
@buildinternet/uploads
Advanced tools
CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds
CLI and client for uploads.sh — upload screenshots, recordings, and other artifacts (reports, logs, JSON, PDFs, zips), get stable public URLs, and produce GitHub-ready markdown.
Binary: uploads. Install globally (or use an npx one-shot):
npm install --global @buildinternet/uploads
npx @buildinternet/uploads --help
uploads setup
uploads --version
uploads attach ./before.png ./after.png
uploads screenshot https://app.example --pr 123 # capture + host in one step
uploads screenshot ./report.html --dark --selector "main"
uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json
uploads annotate ./shot.png --spec ./callouts.json --out ./shot.marked.png
uploads put ./shot.png
uploads put ./shot.png --destination screenshots
uploads put ./shot.png --no-optimize
uploads put ./mobile.png --frame phone
uploads put ./ui.png --frame browser --frame-url "https://app.example"
uploads put ./after.png --pr 123
uploads put ./capture-2026-…Z.png --pr 123 --name hero.png # clean leaf, stable path
uploads put ./shot.png --pr 123 --name hero.png --dry-run --format url # preview URL, no upload
uploads put --url https://cdn.example/shot.png --pr 123
uploads put --url http://localhost:4321/shot.png
uploads gallery create --title "Release screenshots"
uploads put ./after.png --gallery gal_example
uploads feed create owner/repo
uploads feed create --repo owner/repo --pr 123
# custom metadata (queryable): page URL, in-app path, which surface
uploads put ./shot.png --meta url=https://app.example/settings --meta path=/settings --meta app=web
uploads meta get screenshots/myapp/42/shot.webp
uploads meta set screenshots/myapp/42/shot.webp path=/onboarding --delete url
uploads find app=web path=/settings # or: list --meta app=web
uploads doctor
Inside this monorepo only, pnpm uploads … builds the package first so you pick
up local source; product docs and PR “how to try it” examples should use the
global uploads form above.
Commands: attach, put, screenshot, annotate, gallery, feed, comment, list, find, meta, delete, usage,
reconcile, purge-expired, setup, install, login, whoami (alias status),
logout, invite, admin, config, telemetry, report, doctor, health, changelog, docs, mcp,
completion.
Help: bare uploads / uploads help / --help shows essentials; use
uploads help --all (or --help --all) for the full command list. Per-command:
uploads <cmd> --help.
Errors: every failure prints a short error: line on stderr, followed by a
runnable example when an argument is missing, then a hint — never a help dump,
so trimmed output (| tail) still carries the reason.
A mistyped command also suggests the closest real one: uploads set-metadata
answers did you mean: uploads meta set. With --json, an unknown command
returns { "error", "code": "USAGE", "didYouMean" } on stdout.
Shell completion: uploads completion bash|zsh|fish prints a script to
stdout. Save it where your shell looks for completions, then start a new shell.
For zsh, write the script to ~/.zsh/completions/_uploads, then bind it at the
end of ~/.zshrc with fpath=(~/.zsh/completions $fpath) and
autoload -Uz _uploads && compdef _uploads uploads. The compdef line matters:
a cached compinit -C never rescans fpath, so the file alone can go unnoticed.
uploads completion --help covers the rest.
Globals (before the command): --api-url, --token, --workspace / -w,
--env-file, --json, --quiet, --version / -V, -h / --help, --all
(with root help).
Update hints: after a successful run the CLI may print one stderr line when a
newer npm release is available (at most once/day, ~/.cache/uploads/). Silence
with --quiet, --json, UPLOADS_NO_UPDATE=1, or NO_UPDATE_NOTIFIER=1. Not used
for uploads mcp.
Telemetry: the CLI and MCP server send anonymous usage pings (command name,
version, OS/arch, exit code, duration, optional error code) to
POST /v1/telemetry on the configured API. No arguments, paths, tokens, workspace
names, or file content. Opt out with UPLOADS_TELEMETRY_DISABLED=1,
DO_NOT_TRACK=1, or uploads telemetry disable. First interactive run prints a
one-line notice.
Diagnostic reports (opt-in): uploads report "what broke" sends a short
message to the team. Attach a log with --file ./trace.log, or pipe:
uploads doctor --json 2>&1 | uploads report "doctor failed". Never automatic —
also available as the MCP report tool when the user asks to submit feedback.
Attachments are text-only (max 256 KiB) and stored under an unguessable R2 key.
Exit codes: 0 ok, 2 usage/token/file, 3 auth/policy, 4 network, 1 other.
Failures go to stderr; under --format json|url|markdown they also go to stdout so
piped runs stay self-diagnosing. Prefer JSON code over message text. put --dry-run
previews the key + public URL without uploading.
attach is the agent-friendly default for GitHub media. It accepts one or more files,
infers the pull request for the current branch via gh, uploads stable URLs, and creates
or updates one marker-owned GitHub comment. It keeps loose gh/... attachments and linked public galleries in distinct sections, shows up to three available gallery images inline, and updates that same comment in place on every sync. Use --pr, --issue, and --repo to select
the target explicitly, or --no-comment to upload without changing GitHub comments.
Branch staging (pre-PR): attach <files> --branch [name] (also on
screenshot) stages files against a git branch before any PR exists — the
working mode for coding agents capturing as they go. Staged files live under
gh/<owner>/<repo>/branch/<branch>/… and are promoted into the PR's
attachments when one opens: automatically via the
GitHub App webhook, or on the first
attach after the PR exists (--promote forces it with no new files,
--no-promote opts out). uploads github link inspects or claims the
workspace↔repo binding the webhook path uses. Promotion is copy-and-keep —
the staged original is never deleted, so any URL already embedded keeps
serving — and staged objects follow only normal per-workspace retention and
explicit deletes. Promotion (auto or --promote) does skip files staged
more than 30 days before the PR opens, though; they're still there, just no
longer auto-promoted.
Metadata edits re-sync the comment too: uploads meta set on a gh/…-keyed
object refreshes the managed comment automatically when it touches path or
state — best-effort, so backfilled metadata shows up without waiting on the
next attach.
Bare put stages too, by default (issue #403): on a non-default git
branch, a put with none of
--pr/--issue/--key/--ref/--prefix/--destination set
(and not --no-git) stages exactly like attach --branch — same key,
same gh.* metadata. The classic dated layout
(<prefix>/<repo>/<ref-or-date>/<name>) remains the default branch/detached
HEAD/non-repo/--no-git behavior, and the explicit-flags opt-out.
Screenshot capture: uploads screenshot <url|file.html> renders a page to a
hosted image in one step — no separate browser tooling needed. --via auto
(default) drives a Chrome/Chromium already on the machine (playwright-core
ships no browser; --browser <path>, --cdp <endpoint>, or the Playwright/
Puppeteer caches all work), and falls back to server-side rendering when none
is found. localhost/private URLs are local-only; .html files work on both
backends. After capture it joins the same pipeline as put (optimize, frames,
--pr/--issue comments, galleries). See uploads screenshot --help.
Keys / destinations: default put uses the screenshots layout. Typed destinations
(--destination screenshots|gh|f, MCP destination) set the root; --pr/--issue
use gh/…. Workspaces may restrict put/sign to those roots via
allowedKeyPrefixes (see workspaces).
Image optimization: by default, still images are re-encoded to WebP (long edge
capped, high quality) before upload so GitHub embeds stay small, and EXIF is
stripped. Pass --keep-exif / UPLOADS_KEEP_EXIF=1 to preserve image metadata, or
--no-optimize / UPLOADS_NO_OPTIMIZE=1 to upload originals unchanged. Optimize
notes print human sizes (e.g. 411.5 KB → 94.2 KB).
Re-upload / hot-swap: the same key overwrites in place with no prompt (stable
--pr / attach paths). Human mode notes >> replaced existing object (same URL);
JSON includes replaced. Preview with --dry-run (reports would replace when
the key already exists).
Frames (opt-in): --frame phone|browser|iphone-16-pro composites chrome
before optimize. phone/browser are procedural; iphone-16-pro fetches
community art from device-frames-media
into ~/.cache/uploads/frames (not bundled).
Bake hand-drawn boxes, arrows, labels, freeform strokes, and redactions onto a screenshot before it's uploaded:
uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json
uploads annotate ./shot.png --spec ./callouts.json --out ./shot.marked.png
screenshot --annotate resolves CSS selectors against the live page (local
capture backend only); annotate works on an existing image and accepts
pixel coordinates only, no selectors. Spec format and workflow:
skills/annotate-screenshots/SKILL.md.
Create an ordered gallery, then add existing uploads by key. The API returns the canonical public URL; the CLI never constructs it. Anyone who knows that URL can view the gallery and its media—GitHub or repository visibility does not restrict it. Deleting a gallery removes only the gallery record, not its uploaded objects or their retention policy. A workspace can hold up to 100 active galleries, each with up to 100 items and 20 external references.
uploads gallery create --title "Release screenshots"
uploads gallery add gal_example screenshots/myapp/42/after.webp --alt "Updated dashboard"
uploads put ./before.png --gallery gal_example
uploads gallery show gal_example
uploads gallery link gal_example --github buildinternet/uploads#58
uploads gallery list --github https://github.com/buildinternet/uploads/pull/58
When adding several keys, uploads gallery add processes them sequentially and reports any
individual failures in --json output. Gallery item updates use the API's current version to
avoid overwriting concurrent changes.
A feed is a public newest-first page of screenshots already tagged with a GitHub owner/repo,
or with one pull request or issue. Same product — --pr / --issue / --github is one extra
filter. It is a live query, not a curated gallery. Use a gallery when you pick the files; use
a repo feed for the latest shots across a repo; use a PR feed when reviewers should see only
that pull request. The API returns the canonical public URL.
uploads feed create owner/repo
uploads feed create --repo owner/repo
uploads feed create --repo owner/repo --pr 123
uploads feed create --github owner/repo#123
uploads feed create --repo owner/repo --path /settings
A positional owner/repo is the same as --repo. Different repos get different feeds.
Creating the same scope again returns the existing URL. Anyone who knows that URL can
view the feed. Each shot also has /c/<id>/<item> with previous / next. Syncing a
managed PR comment creates the PR feed if needed and points image clicks at that pager.
MCP: feed_create (repo, plus pr / issue / github / path) and feed_get.
Link a gallery to a GitHub issue or pull request with gallery link --github. Run uploads comment --pr <number> to refresh that target’s one managed comment with every linked gallery and loose attachment. Coordinates and strict https://github.com/<owner>/<repo>/issues|pull/<number> URLs are accepted; gallery list --github performs the authenticated reverse lookup. Links never change gallery identity, and GitHub repository visibility does not make the public gallery private.
Config layers (first match wins): CLI flags → env vars → --env-file → ~/.config/buildinternet/config. See config.example for keys.
uploads mcp serves the Model Context Protocol over stdio (newline-delimited JSON-RPC, no extra dependencies). Tools include file operations plus public gallery workflows (gallery_create, gallery_get, gallery_add, gallery_link, gallery_find_by_reference) and change feeds (feed_create, feed_get — pass pr or github to scope a feed to one pull request). Gallery and feed tools return API-provided canonical URLs and never need GitHub credentials. The remaining stdio tools are put, attach, list, delete, get_metadata, set_metadata, find_files, usage, reconcile, purge_expired, comment, whoami, and doctor — with the same config resolution and defaults, plus a per-call workspace argument. put and attach accept a metadata param (same gh.* auto-injection as the CLI's attach); get_metadata, set_metadata, and find_files mirror uploads meta get / meta set / find. Interactive/credential commands (setup, login, admin, config) are not exposed. A token isn't required to start the server; auth errors surface per tool call (whoami needs no auth).
{ "command": "uploads", "args": ["--env-file", "/path/to/.env", "mcp"] }
Or with UPLOADS_TOKEN/UPLOADS_WORKSPACE in the environment or user config. Claude Code: claude mcp add uploads -- uploads --env-file /path/to/.env mcp.
The MCP Registry lists this server as sh.uploads/mcp.
For HTTP clients there's also a hosted variant at https://agents.uploads.sh/mcp — the workspace is inferred from the bearer token, so only the URL and token are needed (https://agents.uploads.sh/<workspace>/mcp and the mcp.uploads.sh hostname also work). Tools: file operations (including get_metadata / set_metadata / find_files) plus gallery_create, gallery_get, gallery_add, gallery_link, gallery_find_by_reference, feed_create, and feed_get; all use the same bearer-token workspace scopes and gallery/feed URLs come from the API — see apps/mcp in the repo. The hosted put also accepts a metadata param. uploads install registers the skills + hosted MCP with whichever of Claude Code, Codex, and Grok are on PATH (a missing CLI is skipped) + Grok/Cursor hooks (short progress; --verbose for underlying output). Claude and Codex use their plugins for the same pre-PR screenshot reminder (uploads hook pre-pr-screenshot). Its put takes no content type: the stored type is sniffed server-side from the bytes and checked against the workspace allowlist, and writes are rate limited per workspace.
import { createUploadsClient } from "@buildinternet/uploads";
Agent/MCP helpers: @buildinternet/uploads/agent (createUploadsWorkerFileTools for Workers); for local stdio MCP, use uploads mcp (above).
src/
cli.ts Entry + help
package-version.ts Shared version for --version / doctor / headers
update-check.ts Optional npm update hint (stderr)
commands.ts put, list, delete, comment, …
commands/mcp.ts `mcp` command entry
mcp/ Stdio MCP server (server.ts, tools.ts)
client.ts HTTP client for the API
github.ts PR/issue key paths + attachment comments
embed.ts Markdown image output
bin/uploads.js Bin shim
pnpm build # tsc → dist/
pnpm typecheck
pnpm test
pnpm pack:check # verify the npm tarball contents
Maintainer release instructions: docs/releasing.md.
Agent-oriented usage: skills/uploads-cli/SKILL.md (full CLI reference), skills/github-screenshots/SKILL.md (visuals into PRs/issues), and skills/annotate-screenshots/SKILL.md (callouts and redaction). REST details: docs/api.md.
FAQs
CLI and client for uploads.sh — workspace-scoped image hosting for GitHub embeds
The npm package @buildinternet/uploads receives a total of 226 weekly downloads. As such, @buildinternet/uploads popularity was classified as not popular.
We found that @buildinternet/uploads demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.