Docs

Everything below works in the shipped CLI today. Nothing here is planned, aspirational, or coming soon.

Setup, start to finish

Four commands. Two minutes.

$ npm i -g shipsure
$ shipsure login
$ shipsure init
$ shipsure link

login prints a short code and opens your browser. Approve it and the machine is connected — no password is ever typed into a terminal.

init detects your language, package manager and test/build commands, then writes .shipsure/config.json. Commit that file; it is project configuration, and it is meant to be reviewed in a pull request like any CI config.

link attaches the folder to a project you created in the dashboard. The CLI cannot create projects on purpose — an agent token is deliberately not allowed to, so a token lifted off a laptop cannot spin up new ones.

Supported stacks

Two different questions, and we keep them apart on purpose. Identification is what ShipSure can recognise in a repository. Verification is what it can actually run and read. The second list is much shorter than the first, and we would rather tell you that than let a language look supported because we can spell its name.

Verified today

  • JavaScript and TypeScript — vitest and jest with per-test detail, npm test for anything emitting TAP or node:test output, npm run build, tsc --noEmit, and your own lint script. pnpm, yarn, npm and bun.
  • PHP and Laravel — php artisan test for Laravel projects, plus Pest and PHPUnit directly, each with per-test names and failure messages. PHPStan and Larastan for static analysis, Laravel Pint for formatting, and a recursive php -l syntax check that skips vendor/.

A project can be both. A Laravel application with a Vite front end is detected as Laravel, PHP, JavaScript, and its PHP suite and its JavaScript suite both run — they are two suites, and collapsing them into one would mean silently dropping whichever scored lower.

Identified, not verified

Detection recognises Python, Ruby, Go, Rust, Java, Kotlin, .NET, Elixir, Dart, Swift, C, C++, Terraform and around eighty more, by manifest or by file extension. None of them have checks behind them yet.

shipsure init lists those under “Detected but not verified”, and a contract that requires a check nothing can run returns UNVERIFIED — never a pass. You can cover them yourself with a shell check in .shipsure/config.json, which runs any command you like and judges it on its exit code.

Blocking a change before it happens

Everything else in ShipSure judges work that is already done. That is the right shape for a verdict and the wrong shape for prevention: by the time verify runs, the key is already in the file. guard is the other half.

In your agent’s tool loop

$ shipsure guard src/config.ts < proposed-content

Exits 0 to allow the write and 2 to refuse it, with the reason on stderr. Claude Code’s PreToolUse hook reads exit 2 as a block and feeds that reason back to the model, so the agent is told what it may not do and why, and corrects itself.

It refuses four things, all of them ones you declared in your policy:

  • A write to a protected path
  • Content that contains a recognisable secret — the value is never echoed back
  • A migration that needs approval first
  • A dependency you did not allow

It deliberately does not decide the rest. Whether tests accompany a change, whether checks pass, whether anything regressed — those are properties of a finished change, and a gate that guessed at them per-write would block correct work halfway through writing it. That is how a gate ends up switched off.

In git

$ shipsure guard install

Installs pre-commit and pre-push hooks that run verification and refuse the commit on a failing or blocked verdict. This is the layer an agent cannot route around: it runs on git commit whoever started it.

An inconclusive verdict does not block — a commit refused because a tool could not run is a commit refused for a reason the author cannot fix. Existing hooks are never overwritten; if something already owns your pre-commit, ShipSure says so and leaves it alone. Bypass a single commit with git commit --no-verify, and remove the hooks with shipsure guard uninstall.

The daily loop

$ shipsure baseline
# … let your agent work …
$ shipsure verify

The order matters. baseline records the state of your tests before the agent touches anything. Run it afterwards and a test the agent just broke looks like it was already failing, so the regression goes undetected. If no usable baseline exists, ShipSure says so in the verdict instead of quietly claiming a clean comparison.

verify runs your project’s own commands, compares against the baseline, checks scope and policy, prints a verdict, and uploads the run so it appears in the dashboard.

Commands

CommandWhat it does
shipsure loginConnect this machine via a device code.
shipsure logoutRevoke this machine server-side, not just locally.
shipsure whoamiAsk the server who this machine is signed in as.
shipsure initDetect the stack and write .shipsure/config.json.
shipsure linkAttach this folder to a cloud project.
shipsure project listList the projects in your organization.
shipsure baselineRecord the "before" state.
shipsure verifyRun verification and print a verdict.
shipsure watchVerify automatically whenever the tree goes quiet.
shipsure cancelStop a run that is still going.
shipsure rollbackRestore the tree to the baseline, stashing first.
shipsure runnerRun this machine as a private runner.
shipsure doctorDiagnose the environment; every failure carries a fix.

Flags

FlagEffect
--jsonMachine-readable output for CI.
--localVerify with no network at all, and do not upload the run.
--no-baselineSkip the comparison. The verdict states regressions were unchecked.
--forceOverwrite config on init, or baseline a dirty tree.
--type <id>On init, load a requirement pack. Repeat or comma-separate for several.
--no-browserOn login, print the URL instead of opening a browser.
--project <id>On link, name the project instead of choosing interactively.
--clear, --showOn baseline, clear or print the stored one.
--quiet-ms <n>On watch, how still the tree must be before verifying.
--yes, --dry-runOn rollback, skip the prompt, or show what would change.
--onceOn runner, take one job and exit.

Exit codes

The CI contract. Separate codes are what let a pipeline treat “needs a human” differently from “the code is broken” — collapsing them into 1 would make ShipSure useless in CI.

CodeMeaning
0Verified
1Failed — a required check failed
2Blocked — a policy or approval gate
3Inconclusive — could not verify reliably
4Cancelled
64Usage error — bad arguments, or not a ShipSure project
77Authentication error
$ - run: npm i -g shipsure
$ - run: shipsure verify --json > shipsure.json

Configuration

.shipsure/config.json holds the project id, the detected adapters, your policy and the default task contract. It is committed. Credentials never go near it — tokens live in ~/.shipsure/credentials.json at mode 0600, per machine.

What runs on your machine

  • Commands run in a child process with an explicit environment allowlist, so stray credentials in your shell are never handed to a test suite.
  • Hard timeouts and output truncation. A runaway process is killed and reported as an error, not a failure — ShipSure does not claim a verdict it cannot support.
  • Secrets are redacted before anything is hashed or uploaded.
  • Source code is never uploaded. File paths and diff statistics are sent as metadata so the dashboard can show what changed; file contents are not.
  • Access tokens are short-lived and rotate on every refresh.

Installing

npm is the primary path and works everywhere Node 20 or newer does.

npm i -g shipsure

On macOS and Linux there is also a Homebrew tap. It depends on node rather than bundling a runtime — anyone verifying a JavaScript project already has one, and embedding it would take a 140 kB download past 100 MB.

brew tap shipsure/shipsure
brew install shipsure

Watching a run happen

A verification opens its run before the checks start, so the dashboard follows along live rather than showing nothing until the end. The run page streams over a WebSocket, falls back to polling where one cannot be opened, and shows each check as it lands.

A run that is still going can be stopped — from the run page, or with shipsure cancel, which with no argument stops whatever is currently in flight. A cancelled run records the verdict cancelled and exit code 4, so a pipeline can tell it apart from a failure. Runs whose machine never reports back are closed out automatically after six hours rather than sitting “running” for ever.

Requirements

A contract that gates nothing returns verified for any change, which is worse than no verification because it looks like verification. Requirement packs give you a real contract in one command.

shipsure init --type saas --type web-app

Each project type carries at least 40 requirements, and selecting several merges them without duplicating what they share. Available types: saas, web-app, website, open-source, api-service, mobile-app, ai-model, game, web-game, fps-game, third-person-game.

Four of them — build, tests, type checking and linting — are bound to real checks and count towards the verdict. The rest are recorded as manual criteria: shown on every run, excluded from the verdict. That distinction is deliberate. ShipSure can prove a test passed; it cannot prove “the onboarding flow makes sense”, and a verifier that claimed otherwise would be lying to you.

Everything lands in .shipsure/config.json under defaultContract.acceptanceCriteria. Edit it, delete what does not apply, and add your own — a team’s own requirements are as valid as any pack.

Setting up through your agent

You do not have to run the setup yourself. Open a project in the dashboard and copy the block under Set up with your agent — it is generated for that project, with the real slug and the packs you picked already filled in. Paste it at the end of your next prompt to Claude Code, Cursor, Codex, or anything else with a shell.

The agent installs the CLI, links the project and records a baseline before it starts editing. One step stays yours: signing in prints a short code and opens a browser, and approving it is something only you can do. The prompt tells the agent to stop and show you the code if it cannot open a browser itself.

It also carries the rules that stop the most common wasted run — do not edit a test to make a check pass, do not treat inconclusive as a pass, fix the cause rather than the check. Those are worth saying up front to an agent that has been asked to make verification go green.

The requirement library

Requirements in the sidebar holds every starting requirement we ship — a universal set that applies to all software, plus eleven packs for the kind of thing you are building: SaaS, API service, website, mobile app, game, model, and so on. Around 300 sentences in total, all readable in full.

Four of them are bound to real checks and decide the verdict: the build succeeds, the tests pass, types report no new errors, lint reports no new errors. The rest are recorded intent — they appear on every run and are excluded from the verdict, because a verifier cannot decide whether “the onboarding makes sense”. The library marks which is which on every line rather than leaving you to assume.

You can write your own packs there too. One requirement per line, saved once, and available to every project in your organization — the rules your team keeps repeating in review, written down where an agent can be held to them.

Apply them from a project’s Requirements tab. Pick the packs that fit, add anything specific to that project, and the page shows the exact list that will be written — in order, de-duplicated.

One thing worth being clear about: ticking a box in the dashboard does not change what runs on your machine. The contract lives in .shipsure/config.json in your repository, where it can be reviewed like any other config and where verification can read it without needing us to be reachable. Selecting packs updates the setup prompt; running shipsure init is what writes them to disk.

Flaky checks

ShipSure watches each check across a project’s recent history and flags one whose result keeps changing direction. A check that has simply failed twenty times running is broken, not flaky, and is deliberately not flagged — letting a real breakage hide behind the word is the failure this exists to prevent.

Quarantining a flagged check, under the project’s Flaky tab, records it as flaky rather than failed so it stops blocking every run while staying visible. If the only failing check in a run is a quarantined one, the run passes and says so in its reasons. Any other failure still fails the run.

Watch and rollback

shipsure watch verifies whenever the tree has been still for a few seconds. An agent edits, goes quiet, and the verdict is on screen before you think to ask. It never runs two verifications at once, and edits arriving mid-run queue exactly one more — not one per file.

shipsure watch
shipsure watch --quiet-ms 5000

shipsure rollback puts the tree back to the baseline after a bad run. It stashes your current state first with --include-untracked and prints how to get it back, so nothing is destroyed. It refuses if a commit has landed since the baseline — discarding a commit is a different and worse operation, and git revert is the right tool for that.

shipsure rollback --dry-run
shipsure rollback

Private runners

For code that cannot leave hardware you control. Enrol a machine under Settings → Runners, then run it with the token you are given:

SHIPSURE_RUNNER_TOKEN=ssr_... shipsure runner

The runner polls outward over HTTPS. Nothing connects to it, so there is no firewall exception and no route into your network to arrange. Each job gets a fresh clone that is deleted when the job ends, two runners can never claim the same job, and a runner killed mid-job releases its lease after fifteen minutes so the work requeues rather than vanishing.

It clones two commits deep and captures the baseline from the parent, so a run answers “did this commit break something that worked before it?” rather than just “do the checks pass right now”. On the first commit in a repository there is nothing to compare against and the verdict is inconclusive, which is the honest answer. The result uploads through the same endpoint every other client uses, so the server’s verdict cross-check applies to runner output exactly as it does to a laptop’s.

GitHub and Slack

Connect them under Settings → Integrations. GitHub posts a commit status — a green tick driven by the check results rather than by what the agent claimed — and optionally a pull-request comment. It needs a fine-grained token with Commit statuses: read and write, and nothing else.

Slack takes an incoming webhook URL and posts to that one channel. It defaults to failed and blocked runs only; posting on every green build is how a channel learns to ignore the bot.

Both credentials are checked against the provider before they are stored, and encrypted at rest. Neither is ever returned by the API after you save it.

API keys and webhooks

Create a key under Settings → API keys. Read keys can list projects and runs; write keys can also create them. Neither can touch members, other keys, or the audit trail.

curl https://api.shipsure.space/v1/runs -H "Authorization: Bearer ssk_..."

Webhooks live under Settings → Webhooks. Every delivery carries X-ShipSure-Signature as v1=<hex>, an HMAC-SHA256 over timestamp.rawBody. Compare it in constant time and reject anything older than five minutes — the timestamp is inside the signature precisely so a captured request cannot be replayed later. Failed deliveries retry six times over about two hours, and twenty consecutive failures disables the endpoint rather than hammering a server that is clearly gone.

Your team

Invite people from Settings → Team. They receive a link that works once and expires in seven days, and joining puts them on a seat your plan already covers — an invited teammate does not need their own subscription or card.

A pending invitation holds a seat until it is accepted, revoked or expires, so the count on that page is what you are actually using. Revoking frees the seat immediately and the link stops working.

RoleCan do
OwnerEverything, including billing and deleting the organization.
AdminProjects, integrations, policies and members. Not billing.
DeveloperRun verifications and read everything in the organization.
ViewerRead-only access to runs, evidence and reports.

An organization always has at least one owner: the last one cannot be demoted or removed. Ownership is transferred by promoting somebody else first — it is never handed out by invitation.

Directory provisioning (SCIM)

On the Scale plan you can hand your directory the job of deciding who is in the organization. ShipSure implements SCIM 2.0, which Okta, Microsoft Entra and JumpCloud all speak. Assign somebody to the ShipSure app and they get a membership; deactivate them and the membership is removed and every session they have ends immediately.

This is provisioning, not single sign-on. SAML is not built. A person your directory creates here still signs in with a ShipSure password — they set one through the reset link the first time. Your directory controls membership; it does not authenticate. The two are sold together often enough that it is worth saying plainly rather than leaving you to find out during a security review.

Set it up in Settings → Team: generate a token, then paste it and the base URL into your provider.

Field in your IdPValue
Base URLhttps://api.shipsure.space/scim/v2
AuthHTTP header token — the ss_scim_… value you generated.
Provisioning actionsCreate users, update user attributes, deactivate users.
Group pushNot supported. Roles are set in ShipSure, not by your directory.

The token is shown once and stored only as a hash, so there is no way to read it back later. Generating a new one revokes the old one — there is exactly one live token per organization, because a forgotten second credential that can still add members is not worth the convenience of overlapping rotation.

Two behaviours worth knowing before you wire it up. New people arrive as developers; promote them in Settings → Team, and a role you set there is never overwritten by a later sync. And a deactivated person keeps their user record — only the membership goes — so reactivating them in your directory restores access rather than creating a stranger with their name.

Settings → Team shows when your provider last called. If that says never used, the connection is not live yet, whatever the configuration screen in your IdP claims.

Signing in with GitHub

Where the deployment has it configured, the sign-in page offers GitHub. Two rules apply, and both are deliberate:

  • It signs you in, it does not sign you up. There is no free tier, so an account cannot exist without a plan — an unrecognised identity goes to pricing rather than becoming a dead account.
  • It never auto-links on a matching email. If the address already has a password account, you are asked to sign in and link it from Settings. Trusting a provider’s claim about an address is the classic account-takeover route.

Passwords and account recovery

Forgot yours? Use the reset link on the sign-in page. The email arrives in a moment, works once, and expires after an hour. Requesting a new link retires the previous one, so if somebody else triggered a reset on your account, asking for your own immediately kills theirs.

Completing a reset signs the account out on every device, including any CLI session — run shipsure login again on each machine afterwards. To change a password you still know, use Settings instead; that keeps the device you are on signed in and drops the rest.