Skip to main content

fremforge CLI

The fremforge CLI is a single-binary wrapper around the public REST API. Same verbs, same authorization model, same scopes. Distributed from EU PoPs only; no npm registry, no Docker Hub, no Sigstore Fulcio dependency.

Install

One-liner (recommended)

curl -sSfL https://cli.frem.sh/cli/install.sh | sh

The installer auto-detects your OS (Linux or macOS) and architecture (arm64 or x86_64), downloads the matching binary from https://cli.frem.sh/cli/, downloads the canonical SHA256SUMS from https://trust.frem.sh/ (a separate host with a separate writer credential, so a compromise of the binary host alone cannot forge a match), verifies the SHA-256, and installs to ~/.local/bin/fremforge or /usr/local/bin/fremforge.

Override the install location with FREMFORGE_INSTALL_DIR:

curl -sSfL https://cli.frem.sh/cli/install.sh | FREMFORGE_INSTALL_DIR=/opt/fremforge/bin sh

Pin a specific version:

curl -sSfL https://cli.frem.sh/cli/install.sh | FREMFORGE_VERSION=0.1.0 sh

Manual download

If you’d rather not pipe a remote script to sh, the binaries are reachable directly. Pick the matching pair:

OSArchURL
macOSarm64https://cli.frem.sh/cli/fremforge-darwin-arm64
macOSx86_64https://cli.frem.sh/cli/fremforge-darwin-x64
Linuxarm64https://cli.frem.sh/cli/fremforge-linux-arm64
Linuxx86_64https://cli.frem.sh/cli/fremforge-linux-x64
Windowsx86_64https://cli.frem.sh/cli/fremforge-windows-x64.exe

The architecture suffix is x64 (not x86_64) and arm64 — these match Bun’s --target tokens. uname -m reports x86_64 on Intel/AMD, so map it to x64 when building the URL by hand (the one-liner installer does this for you).

Verify against the canonical hash on trust.frem.sh before running the binary:

# 0. Map uname -m's x86_64 to the published x64 token
arch=$(uname -m); [ "$arch" = x86_64 ] && arch=x64

# 1. Download the binary
curl -fsSL https://cli.frem.sh/cli/fremforge-$(uname -s | tr A-Z a-z)-$arch \
  -o fremforge

# 2. Download the canonical SHA256SUMS (separate host, separate credential)
curl -fsSL https://trust.frem.sh/SHA256SUMS -o SHA256SUMS

# 3. Verify
sha256sum --check --ignore-missing SHA256SUMS

# 4. Install
chmod +x fremforge
sudo install -m 0755 fremforge /usr/local/bin/fremforge

Verify the Ed25519 signature (optional, recommended)

Beyond the SHA-256 match, each release is signed as a DSSE-wrapped in-toto Statement under the same trust root as fremforge SLSA build provenance. The verifier downloads the public trust root from www.frem.sh/.well-known/slsa-trust-root.json and checks the Ed25519 signature on the SHA256SUMS bytes.

# Pull the trust root and the signed envelope
curl -fsSL https://www.frem.sh/.well-known/slsa-trust-root.json -o slsa-trust-root.json
curl -fsSL https://trust.frem.sh/SHA256SUMS.intoto.jsonl -o SHA256SUMS.intoto.jsonl

# Verify using slsa-verifier
slsa-verifier verify-artifact fremforge \
  --provenance-path SHA256SUMS.intoto.jsonl \
  --source-uri https://frem.sh/internal-fremforge/monolith \
  --builder-id https://frem.sh/runner-controller/v1 \
  --trusted-root slsa-trust-root.json

The trust root is the same one used for SLSA-L2 build provenance on artifacts your runner produces, same Ed25519 key, same builderId, same verification recipe.

Authentication

The CLI supports three credential modes, resolved in this order: --token=… flag, FREMFORGE_TOKEN env var, then the stored login at ~/.config/fremforge/auth.json.

Recommended — browser-based login (no paste)

fremforge auth login

Opens your browser to frem.sh, you approve the device, the token lands at ~/.config/fremforge/auth.json (mode 600) and every subsequent invocation auto-loads it. Same OAuth2 + PKCE flow your git push already uses, so revoking the token at frem.sh/-/user/settings/applications revokes git access and CLI access in one step.

fremforge auth status      # show current login + expiry
fremforge auth logout      # revoke + delete the local file

The token works on both the fremforge REST API (/api/v1/orgs/…) AND Forgejo’s native API (/api/v1/repos/…) — Forgejo accepts its own OAuth bearers natively, and the fremforge api validates them via introspection against Forgejo’s /api/v1/user. Same identity, both surfaces.

CI / scripted use — PAT

For non-interactive contexts (CI jobs, scripts, agents), mint a scoped PAT. Easiest is the CLI itself once you’ve logged in interactively once (fremforge auth tokens create --name=… --scopes=… — see Mint a scoped PAT for CI); or mint one at https://frem.sh/-/user/settings/applications. Then:

export FREMFORGE_TOKEN='<your PAT>'
fremforge whoami

Override the base URL with FREMFORGE_URL= (must be HTTPS); default is https://frem.sh.

Commands

fremforge auth login                              Browser-based OAuth2+PKCE login.
fremforge auth logout                             Revoke + delete the stored token.
fremforge auth status                             Show current login + expiry.
fremforge whoami                                  Print the user the token authenticates as.
fremforge data-export start <slug>                Queue a full data export for the org.
fremforge data-export list <slug>                 List recent export jobs.
fremforge dependency-updates enable <slug>        Enable hosted Renovate for the org.
fremforge dependency-updates disable <slug>       Disable hosted Renovate for the org.
fremforge dependency-updates run-now <slug>       Trigger an out-of-cycle Renovate run (1/h cap).
fremforge audit-log <slug>                        Fetch the org audit-log slice.
                  [--since=DATE] [--actor=…] [--action=…] [--limit=N]
fremforge audit-verify <slug> [--human]           Walk the per-tenant audit hash chain + WORM
                                                  anchor; report integrity.
                                                  Exit 0=ok, 2=partial, 3=broken, 4=anchor_mismatch.
fremforge sbom <owner>/<repo> <tag>               Download a release SBOM.
                  [--format=spdx|cyclonedx]
fremforge runs <owner>/<repo>                     List recent CI runs (JSON; --human for a table).
                  [--limit=N] [--status=…] [--event=…]
fremforge runs logs <owner>/<repo> <run-id>       Fetch a run's logs (text/plain, all jobs).
fremforge auth tokens create                      Mint a scoped PAT (printed once).
                  --name=N --scopes=a,b [--ttl-days=N]
fremforge auth tokens list                        List your PATs (--human for a table).
fremforge auth tokens revoke <token-id>           Revoke a PAT.
fremforge findings <type> <slug>                  Security findings. type ∈ secrets |
                  [--status=…] [--repo=…]         dependencies | signed-commits | attestations.
fremforge secrets list <slug>                     List org Actions/CI secret names.
fremforge secrets set <slug> <NAME>               Set a secret (value via FREMFORGE_SECRET_VALUE).
fremforge secrets delete <slug> <NAME>            Delete a secret.
fremforge seats list <slug>                       List org members + seat usage.
fremforge seats add <slug> --email=E              Add a member (--username=, --full-name= optional).
fremforge seats remove <slug> <username>          Remove a member.
fremforge version                                 Print the CLI version.

Run fremforge --help for the canonical reference, or pipe to --human on any command that supports it for table-formatted output.

Worked examples

Audit-chain integrity (use in CI)

export FREMFORGE_TOKEN='<your PAT, audit:read scope>'

# Human-readable, for terminals
fremforge audit-verify acme --human

# Script-friendly: exit code reflects integrity_status
#   0 = ok, 2 = partial, 3 = broken, 4 = anchor_mismatch
fremforge audit-verify acme || {
  echo "integrity check failed with exit code $?"
  exit 1
}

See audit chain integrity for the full guarantee.

Self-service data export

# Kick off
fremforge data-export start acme | jq .

# Poll
fremforge data-export list acme | jq '.[] | {id, status, created_at}'

See data export for the artifact format, retention, and signed-URL download flow.

Dependency updates (hosted Renovate)

fremforge dependency-updates enable acme
fremforge dependency-updates run-now acme   # 1/h rate-limit applies

See dependency updates for what gets enrolled and how to scope it per-repo.

SBOM download

fremforge sbom acme/api v1.4.0 --format=spdx > sbom.spdx.json
fremforge sbom acme/api v1.4.0 --format=cyclonedx > sbom.cyclonedx.json

Audit log slice

fremforge audit-log acme --since=2026-05-01 --action=push-protection-override | jq .

CI runs + logs

List recent runs, then pull a run’s full logs (every job concatenated) by its global run id:

# List the 20 most recent runs as a table
fremforge runs acme/api --human

# Script-friendly: grab the newest run id, then fetch its logs
run_id=$(fremforge runs acme/api --limit=1 | jq -r '.workflow_runs[0].id')
fremforge runs logs acme/api "$run_id"

# Tail just the failing step
fremforge runs logs acme/api "$run_id" | grep -iE "error|failed|exit code" | tail

<run-id> is the global run id shown by fremforge runs (the .workflow_runs[].id field) — not the per-workflow run number in the web UI URL. The logs endpoint returns text/plain with all jobs in one response; no per-job artifact download needed.

Mint a scoped PAT for CI

After your first fremforge auth login, mint narrow-scoped child tokens for CI without leaving the terminal. The token is printed once — capture it immediately:

fremforge auth tokens create --name=ci-readonly --scopes=findings:read,orgs:read --ttl-days=30 \
  | jq -r .token
fremforge auth tokens list --human
fremforge auth tokens revoke <token-id>

You can only grant scopes your own token already holds, and wildcard (*) cannot be minted here.

Manage CI secrets

fremforge secrets list acme

# Pass the value via env (preferred — not in shell history or `ps`)
FREMFORGE_SECRET_VALUE="$(cat ./deploy-key)" fremforge secrets set acme DEPLOY_KEY

fremforge secrets delete acme OLD_TOKEN

Seats / members

fremforge seats list acme --human                       # cap + usage + roster
fremforge seats add acme --email=jane@acme.io --full-name="Jane Doe"
fremforge seats remove acme jane

Adding a member over the contracted seat cap returns a 409 with a billing link — buy more seats first.

Output format

By default every command emits JSON to stdout, script-friendly and pipe-friendly. Pass --human on the commands that support it (currently audit-verify) for a formatted summary.

Updating the CLI

The one-liner installer is idempotent, running it again pulls the latest binary, re-verifies the checksum, and overwrites the old binary in place:

curl -sSfL https://cli.frem.sh/cli/install.sh | sh

Sovereignty notes

The CLI distribution path is fully EU-sovereign:

  • Binaries: T Cloud Public OBS (eu-de, Magdeburg / Biere, Germany), behind a Bunny pull-zone restricted to EU PoPs only.
  • SHA256SUMS + DSSE envelope: A separate T Cloud Public OBS bucket (trust.frem.sh) with a separate writer credential, splitting the binary host from the integrity host means compromising one cannot forge a match against the other.
  • Trust root: Static JSON file served from www.frem.sh (same Bunny EU-only routing).
  • No npm registry dependency: registry.npmjs.com runs in US AWS; the CLI never touches it on install or on use.
  • No Docker Hub dependency: same reason.
  • No Sigstore Fulcio / Rekor: Sigstore’s transparency log is US-hosted. fremforge runs its own Ed25519 signer + a static trust root rather than depending on Fulcio.

Sub-processors involved on the install path are listed in the public sub-processor register and are the same ones already covering customer Personal Data per the DPA Annex B.