spaps
v0.9.1
Published
Sweet Potato Authentication & Payment Service CLI - Docker Compose orchestrator for local Python/FastAPI SPAPS server with built-in admin middleware
Downloads
728
Maintainers
Readme
spaps
Test SPAPS auth in a new app before you build auth backend infrastructure.
spaps is the shortest path from "I have a new frontend" to "this app can hit authenticated SPAPS routes locally." It wraps the local SPAPS server, tells you what auth mode the server is actually in, scaffolds starter code, and provisions a real local application when the server requires one.
Run spaps with no arguments to get the current operator session, app context, runtime mode, and the next recommended command.
TL;DR
The problem
You want to wire auth into a new app, but you do not want to design and ship your own auth backend first.
The shortcut
Run SPAPS locally, inspect the current auth mode, then either use local mode directly or provision a real local app with one command. The CLI tells you which mode the server is actually in, so you do not have to guess.
Why Use spaps?
| Need | spaps gives you |
| --- | --- |
| "Can I test auth in this app today?" | spaps local, status, and quickstart |
| "Do I need a real app key or not?" | Runtime truth from /health/local-mode |
| "Can you scaffold the wiring for me?" | spaps create <name> --template ... |
| "I want a working local app, not a fake contract" | Self-service provisioning when SELF_SERVICE_PASSWORD is available |
| "If provisioning fails, tell me exactly why" | Explicit scaffold_only fallback and warnings |
| "I want persistent local personas, roles, and entitlements in this repo" | spaps fixtures ... and a repo-local .spaps/ kernel |
| "Can an agent tell me why this action is blocked?" | spaps access check, journey run, graph, explain, and contract |
No Backend Yet? Start Here
npx spaps
npx spaps local
npx spaps quickstart --json | jq '.auth'
SELF_SERVICE_PASSWORD=your-password npx spaps create demo-app --template react
cd demo-app
npm installWhat happens next depends on the server mode:
| Server state | What spaps does | What you do |
| --- | --- | --- |
| local_mode_active: true | Exposes local-mode hints and test personas | Use the starter against localhost; no app key provisioning required |
| local_mode_active: false and SELF_SERVICE_PASSWORD is set | Provisions a real local SPAPS app and writes the working key into .env.local | Start wiring auth flows in your app immediately |
| Server unreachable or no self-service password | Scaffolds files only and tells you why provisioning was skipped | Bring the server up or rerun with SELF_SERVICE_PASSWORD later |
This package targets Node.js >=22.
Local Runtime Modes
spaps local now has two runtime paths:
| Runtime path | When it is used | What it does |
| --- | --- | --- |
| repo | You are running from the sweet-potato checkout | Uses the repo's Docker Compose stack and source-mounted assets |
| bundle | You installed spaps from npm elsewhere | Uses bundled Docker assets plus the published spaps-server-quickstart package |
The default is auto: use repo assets when they exist, otherwise fall back to the bundled runtime. The bundled runtime defaults to SPAPS_LOCAL_MODE=true, so RBAC fixtures and test personas work out of the box.
SPAPS_LOCAL_MODE is the SPAPS app's canonical local auth/persona switch.
DEVELOPMENT_ENVIRONMENT=local is only for base quickstart services that wire
LocalAuthMiddleware. The CLI reads runtime truth from /health/local-mode;
servers also expose the resolved value in /health/ready and the
X-SPAPS-Mode response header.
Local Data Sources
spaps local separates runtime selection from base data selection:
| Data source | What it does |
| --- | --- |
| empty | Boot an empty local DB and let migrations create schema |
| prod-cache | Restore the cached SPAPS production dump before the API starts, then reuse it until the cached dump changes |
| prod-fresh | Force a fresh production dump fetch, restore it, then boot the API |
| --from-backup <path> | Restore a specific .sql.gz dump file before boot |
Restore order is always: restore base dump first, then let the API container run migrations to head on top of it.
Install
Run it without installing:
npx --yes spapsFor CI, SSH automation, or the first run on a fresh box, prefer npx --yes spaps ... so npm does not block on its install confirmation prompt.
Install globally:
npm install -g spapsAdd it to a project:
npm install spapsQuick Start
| Step | Command | Why |
| --- | --- |
| 1 | npx spaps | Inspect operator session, detected app context, runtime mode, and the next command |
| 2 | npx spaps local | Start the local SPAPS stack when the runtime is not up yet |
| 2b | npx spaps local --data-source prod-cache | Start the local stack with a cached prod-backed base DB |
| 3 | npx spaps connect | Authenticate the CLI with the active SPAPS server |
| 4 | npx spaps verify --json | Confirm the runtime and auth path end to end |
| 5 | npx spaps create my-app --template react | Scaffold a starter and, when possible, provision a real local app |
| 6 | npx spaps tools --json | Export the current AI tool contract for agents or tests |
| 7 | npx spaps fixtures apply | Materialize repo-local personas into Playwright/browser artifacts |
CLI Surface
| Command | Purpose | Common flags |
| --- | --- | --- |
| spaps / spaps home | Show operator session, app context, runtime mode, and the next recommended action | --port, --server-url, --json |
| spaps local [stop] | Start or stop the local server workflow | --port, --runtime-dir, --runtime-source, --data-source, --detach, --fresh, --from-backup, --open, --json |
| spaps status | Check whether the local server is running | --port, --json |
| spaps verify | Run a compact runtime and auth verification pass | --port, --server-url, --json |
| spaps quickstart | Print quick-start instructions | --port, --json |
| spaps init | Create a starter .env.local | --json |
| spaps create <name> | Scaffold a SPAPS starter project directory and try local provisioning | --template, --dir, --port, --force, --json |
| spaps fixtures <subcommand> | Manage repo-local .spaps auth fixtures | --dir, --port, --base-url, --persona, --seed, --sync-server, --format, --force, --json |
| spaps docs | Browse or search bundled docs | --interactive, --search, --json |
| spaps tools | Emit the AI tool spec (covers auth-method discovery, auth, stripe, dayrate, email, webhooks, policies, billing, issue-reporting) | --port, --format, --json |
| spaps doctor | Diagnose local environment, domain mounts, and auth-provider configuration | --port, --server-url, --origin, --stripe, --json |
| spaps auth methods | Print the active app's auth-method matrix from /api/auth/methods | --server-url, --port, --origin, --json |
| spaps auth mfa-test | Exercise local TOTP MFA enroll, activate, login challenge, verify, and cleanup | --email, --password, --server-url, --origin, --allow-remote, --json |
| spaps auth sms-test | Request or verify a local console SMS OTP challenge | --phone-number, --challenge-id, --code, --server-url, --origin, --allow-remote, --json |
| spaps dayrate config | Fetch the active dayrate admin config (requires admin JWT) | --server-url, --port, --json |
| spaps billing status\|attach\|verify | Inspect, bind, and verify the current app's billing-account resolution (admin/operator) | --billing-account-id, --server-url, --port, --json |
| spaps email <verb> | Thin email control-plane commands over the existing SPAPS email API | --server-url, --port, --json; run spaps email --help for verb-specific flags |
| spaps policy list\|create\|delete | List, create, or delete authorization policies (admin) | --name, --effect, --conditions, --id, --is-active, --limit, --json |
| spaps webhook list\|register | List registered webhooks or register a new outbound webhook | --url, --events, --json |
| spaps issue-reports list-mine | List issue reports created by the authenticated caller | --status, --limit, --offset, --json |
| spaps access check | Compose an access decision and return reasons, facts, next actions, and sources | --actor-ref, --action, --resource-type, --resource-ref, --entitlement-key, --policy-name, --usage-feature-key, --json |
| spaps journey run | Prepare the next safe action for an agent and request command templates when the server recognizes trusted operator context | access flags plus --include-command-templates, --operator-gated, --operator-labels, --environment, --json |
| spaps graph nodes\|paths\|impact\|refresh | Inspect or refresh the materialized capability graph for the current app | --application-id, --node-type, --query, --from, --to, --node-key, --max-depth, --include-stale, --correlation-id, --json |
| spaps explain <decision-id> | Fetch a persisted decision trace with graph references | --server-url, --port, --json |
| spaps contract | Fetch the capability graph client contract | --server-url, --port, --json |
spaps contract --json includes graph vocabulary fields for agents:
graph_node_types, graph_edge_types, graph_source_domains, and
source_domain_notes.
Agent Decision Commands
The capability commands use a stable JSON envelope when --json is present:
{
"schema_version": "spaps.cli.capability.v1",
"command": "access.check",
"success": true,
"status": 200,
"data": {},
"diagnostics": [],
"remediations": [],
"sources": []
}access check exits 0 for a valid denied decision. Agents should inspect
data.allowed, data.outcome, and remediations instead of treating denial
as a shell failure.
Failed API responses preserve server diagnostics: diagnostics[].code,
diagnostics[].status, diagnostics[].details, top-level request_id, and
top-level remediations are carried through when the server returns them.
--operator-gated and --operator-labels are compatibility/descriptive inputs.
They do not grant operator authority. Mutation command templates require a
server-recognized non-publishable key plus an authenticated admin/operator user
context; publishable callers cannot self-attest the gate.
Exit codes used by the capability commands:
| Code | Meaning |
| --- | --- |
| 0 | Command completed; the decision may still be allowed: false |
| 2 | Local input or setup error |
| 10 | Authenticated API request failed |
| 20 | spaps contract failed |
| 30 | spaps journey run failed |
Email Commands
Use nested help for the exact flag surface:
spaps email --help
spaps email send --help
spaps email preview --help| Verb | Auth | Purpose |
| --- | --- | --- |
| send | API key | Send a transactional email by template key |
| get-template | API key + JWT | Fetch one template definition |
| preview | API key + JWT | Render a preview with sample or custom context |
| logs | API key + JWT | List email logs for the caller's scope |
| list-templates | API key + JWT + admin | List all templates for the application |
| create-template | API key + JWT + admin | Create a template |
| update-template | API key + JWT + admin | Update a template |
| get-override | API key + JWT | Read the current template override |
| set-override | API key + JWT + admin | Create or update an override |
| clear-override | API key + JWT + admin | Delete an override |
Example command usage:
spaps --json
spaps local --port 3400 --detach
spaps local --runtime-source bundle --runtime-dir ./.skillbox/spaps-local
spaps local --runtime-source repo --data-source prod-cache
spaps local --runtime-source repo --fresh --data-source prod-fresh
spaps local --from-backup ~/.cache/spaps/db/prod.sql.gz
spaps status --json
spaps verify --json
spaps create my-app --template react
spaps connect --json
spaps fixtures apply --base-url http://localhost:5173
spaps fixtures apply --sync-server --base-url http://localhost:5173
spaps fixtures apply --seed --persona dayrate-entitled --base-url http://localhost:5173
spaps fixtures storage-state --persona admin
spaps docs --search secure-messages
spaps doctor --stripe mock
spaps local stop
spaps dayrate config --json
spaps billing status --json
spaps billing attach --billing-account-id 11111111-1111-4111-8111-111111111111 --json
spaps billing verify --json
spaps email --help
spaps email send --help
spaps email send --template-key welcome --to [email protected] --context '{"user_name":"Jane"}' --json
spaps email preview --template-key welcome
spaps email logs --owner-id owner-123 --limit 25 --json
spaps email set-override --template-key welcome --subject-override "New subject" --json
spaps policy list --json
spaps policy create --name allow_admin --effect allow --conditions '{"role":"admin"}' --json
spaps webhook register --url https://example.com/hook --events user.created,user.deleted --json
spaps issue-reports list-mine --status open --json
spaps access check --actor-ref user_123 --action checkout.create --resource-type product --resource-ref dayrate --entitlement-key bookme_paid --json
spaps journey run --action admin.delete_user --resource-type user --resource-ref user_123 --include-command-templates --json
spaps graph refresh --correlation-id local-refresh --json
spaps graph nodes --node-type x402_resource --query dayrate --json
spaps graph paths --from actor:user_123 --to x402_resource:dayrate --json
spaps explain 11111111-1111-4111-8111-111111111111 --json
spaps contract --jsonCLI Auth
spaps connect is an alias for spaps login, and both use the active FastAPI device-flow contract:
- device authorization at
/api/cli/device/* - authenticated follow-up calls at
/api/auth/* - standard SPAPS response envelopes
For users with activated TOTP MFA, spaps connect keeps polling the device
token endpoint while the browser approval page completes the second factor. The
approval page handles mfa_required from /api/cli/device/verify by retrying
that endpoint with challenge_id, challenge, and either totp_code or
recovery_code; the CLI does not need an MFA flag.
The CLI no longer assumes a built-in spaps-cli application slug. Resolve the client id in this order:
--client-id <app-slug>SPAPS_CLI_CLIENT_ID- repo-local
spaps.app.json - repo-local
.spaps/app.json /health/local-modetest application metadata
Authenticated follow-up calls such as whoami, logout, and token refresh still need a normal SPAPS app key when the server is not in local mode. The CLI resolves that key from:
SPAPS_API_KEYNEXT_PUBLIC_SPAPS_API_KEYVITE_SPAPS_API_KEY- repo-local
.env.local - repo-local
.env
Examples:
npx spaps connect --client-id my-app
npx spaps login --client-id my-app
SPAPS_CLI_CLIENT_ID=my-app npx spaps login
VITE_SPAPS_API_KEY=spaps_pub_demo npx spaps whoami --jsonAuth diagnostics:
npx spaps doctor --server-url http://localhost:3301 --origin http://localhost:3000 --json
npx spaps auth methods --origin http://localhost:3000 --json
[email protected] SPAPS_TEST_PASSWORD=correct-horse npx spaps auth mfa-test --json
npx spaps auth sms-test --phone-number +15555550100 --json
npx spaps auth sms-test --phone-number +15555550100 --challenge-id <id> --code <code> --jsonmfa-test and sms-test refuse non-local server URLs unless --allow-remote
is set. The SMS command cannot read OTP digits from the server because SPAPS
does not return them in API responses; with the console provider, read the code
from local server logs and rerun with --challenge-id and --code.
Still reserved and not finished:
spaps types
Create A Starter Project
spaps create ships a local-first starter kit rather than a full framework generator. It creates a new directory with:
spaps.app.jsonfor the machine-readable SPAPS app contract.env.localpointing at local SPAPS and, when available, a working template-appropriate keypackage.jsonwithspaps-sdk- a small template-specific integration starter
When the local server is reachable and SELF_SERVICE_PASSWORD is set, create also provisions a real local SPAPS application. When that is not possible, it falls back to scaffold-only mode and tells you why.
Supported templates:
nextjsreactnodevanilla
Examples:
npx spaps create my-app --template react
npx spaps create my-api --template node --dir ./services/my-api
SELF_SERVICE_PASSWORD=your-password npx spaps create my-app --template reactThe generated starter is template-aware:
- browser templates write a publishable key when provisioning succeeds
- server templates write a server key when provisioning succeeds
spaps.app.jsonrecords provisioning status and application id, but never stores raw keys
Repo-Local Fixtures
Use .spaps/ when you want persistent local personas for clicking around, Playwright, or app-level auth tests without inventing ad hoc storage keys in each repo.
spaps fixtures init creates:
.spaps/app.jsonfor runtime and browser target settings.spaps/users.jsonfor personas and profile data.spaps/roles.jsonfor RBAC grants.spaps/entitlements.jsonfor entitlement grants.spaps/browser/for generated storage-state and header artifacts
Then run:
npx spaps fixtures apply --base-url http://localhost:5173
npx spaps fixtures apply --sync-server --base-url http://localhost:5173
npx spaps fixtures apply --seed --persona issue-reporter-blocked --base-url http://localhost:5173
npx spaps fixtures storage-state --persona adminUse --sync-server when a local app needs real SPAPS DB state. It calls the
local-only /api/dev/fixtures/apply endpoint to upsert fixture users,
memberships, and entitlements, then writes ownership metadata to
.spaps/fixtures.lock.json. It refuses non-local targets and only runs when
/health/local-mode reports local mode active.
The default fixture pack includes shared personas (user, admin, premium),
CFO-style cfo-billing-demo company entitlements, and HTMA-style
htma-local-auth invite/template seed state. Browser artifacts keep
entitlements as plain keys, while --sync-server can send structured grants
with resource scope metadata to SPAPS.
Use --seed when a persona declares backend seed steps in .spaps/users.json. Seeding is opt-in, only runs against a reachable local-mode SPAPS server, and reuses the persona selectors already defined in the fixture kernel instead of adding fixture-only backend routes.
What apply emits:
browser/<persona>.storage-state.jsonfor PlaywrightstorageStatebrowser/<persona>.headers.jsonforextraHTTPHeadersbrowser/<persona>.context.jsonwith merged persona/runtime metadatapublic/spaps-dev-auth.jsfor frontend-only auth/RBAC dev mode
This does not try to import browser password-manager state. It writes deterministic, repo-local test artifacts instead.
If you want a human to launch the frontend and click around without a mock backend, include the generated bridge before your app boots:
<script src="/spaps-dev-auth.js"></script>That bridge does three things:
- seeds SDK-compatible localStorage keys like
sweet_potato_userandsweet_potato_access_token - intercepts
/api/auth/user,/api/auth/login,/api/auth/logout,/api/entitlements, and/api/entitlements/check - renders a tiny persona switcher so you can flip between
user,admin, andpremium
Personas can also carry app-owned demo state in .spaps/users.json:
{
"code": "pds-initiator",
"display_name": "PDS Initiator",
"profile": { "user_id": "00000000-0000-0000-0000-000000000001" },
"scenario": {
"pds": {
"dashboard": "work",
"role": "initiator"
}
}
}scenario is opaque to SPAPS. It is copied into browser/<persona>.context.json, persisted as spaps.fixture.scenario, and exposed by the bridge:
const pdsScenario = window.__SPAPS_DEV_AUTH__.getScenario("pds");
const unsubscribe = window.__SPAPS_DEV_AUTH__.onPersonaChange((event) => {
console.log(event.persona.code, event.scenario.pds);
});
window.addEventListener("spaps:persona-change", (event) => {
console.log(event.detail.persona.code);
});Use this for persona-specific dashboard stories, queues, and UI fixture hints. Keep product-specific data in the consuming app; SPAPS only owns the persona/auth/role/entitlement envelope.
Middleware Example
The main module exports admin and permission helpers for Express-style apps.
const express = require("express");
const { requireAdmin, requirePermission } = require("spaps");
const app = express();
const customAdmins = ["[email protected]"];
app.get(
"/admin",
requireAdmin({ customAdmins }),
(_req, res) => {
res.json({ ok: true });
},
);
app.post(
"/admin/products",
requirePermission("manage_products", { customAdmins }),
(_req, res) => {
res.json({ created: true });
},
);What spaps init Writes
spaps init creates a .env.local file with a local API URL starter and leaves an existing file alone if one is already present.
Troubleshooting
Port already in use
Start on another port:
npx spaps local --port 3400I need deterministic portable runtime files
Pin the bundled runtime directory explicitly:
npx spaps local --runtime-source bundle --runtime-dir ./.skillbox/spaps-localThis is the recommended path for managed environments such as skillbox.
I need machine-readable output
Use --json on commands that support it, including local, status, quickstart, init, fixtures, docs, tools, and doctor.
The local server is not responding
Check current status first:
npx spaps statusIf needed, restart with a clean boot:
npx spaps local --freshIf you want the old "db=fresh from prod" behavior through the portable CLI:
npx spaps local --fresh --data-source prod-freshIf you want to keep a cached prod-backed base DB around for day-to-day work:
npx spaps local --data-source prod-cachespaps create only scaffolded files
That means one of two things:
- the local server was unreachable
- the server is not in local mode and
SELF_SERVICE_PASSWORDwas missing or invalid
Check the current mode first:
npx spaps quickstart --jsonIf the server requires provisioning, rerun with:
SELF_SERVICE_PASSWORD=your-password npx spaps create my-app --template react --forceLimitations
- The CLI is aimed at local workflows. It is not a full deployment or hosting tool.
createdoes not runcreate-next-app, Vite, or Express generators for you; it scaffolds the SPAPS integration layer.typesis still a placeholder today.- The middleware ships a legacy default admin configuration in code; public-facing apps should pass explicit
customAdminsinstead of relying on that default.
FAQ
What is this package actually for?
Use it when you want to prove auth wiring in a new app before you build backend auth infrastructure yourself.
Does this package include the TypeScript SDK?
No. The SDK is published separately as spaps-sdk.
Can I use the CLI without a global install?
Yes. Use npx spaps ....
Does spaps init overwrite an existing env file?
No. It skips .env.local if the file already exists.
What does spaps tools output?
An OpenAI-style tool spec for the local SPAPS surface. The auth section is derived from /health/local-mode when the local server is reachable.
Is the middleware available from a subpath?
Yes. You can import from the main module or spaps/middleware.
License
MIT
