openstep
v0.2.0
Published
Run a STEPS Builder project's own dev server on your machine against its real development data and platform services.
Readme
openstep
openstep is a command-line tool for developers working on a STEPS Builder
project. It lets you clone your project's synced GitHub repo and run its own
dev server (npm run dev, or whatever your project uses) on your own
machine, while that dev server talks to the real backing services your
project uses on STEPS: the project's development database/storage and the
platform's billed runtime capabilities (LLM calls, email sending, etc. — the
/_platform/* surfaces). Without openstep, a plain local checkout has none
of that: your project's frontend expects those surfaces at same-origin paths
that simply don't exist on localhost, so pages that list real data render
empty.
openstep solves this two ways, both scoped to one project and one machine:
A short-lived runtime credential, not your permanent project token.
openstep devexchanges your CLI login for a 24-hour credential and refreshes it in the background for as long asdevkeeps running. Your machine never holds a long-lived credential capable of acting on the project outside of an activedevsession.A local gateway that stands in for the platform's own edge gateway. It sits in front of your dev server and, per request, routes to one of these places:
/__steps/data/*→ the platform's development data gateway for this project (this is what makes real records show up)/_platform/*→ the platform's runtime services, billed the same way they would be from a live preview/__openstep/*→ answered byopenstep devitself and never forwarded anywhere. Your backend uses one path here to ask for the current runtime credential after a background refresh has rotated it — otherwise the copy it was started with would go stale and every secret read would fail (see below).- everything else → your own dev server, unchanged
Only the first two carry your machine's runtime credential upstream; your own dev server never sees it.
Requirements
- Node.js >= 22 (declared in
cli/package.json'senginesfield). - macOS or Linux. (Windows isn't a tested target for this project;
login's automatic browser-opening step degrades gracefully if it can't find a browser opener, but nothing here has been exercised on Windows.) - A project cloned from STEPS Builder, i.e. one whose repo root contains a
.steps-builder-meta.jsonfile.openstep devreads your project id and builder URL from that file — it never walks up to a parent directory looking for one, so run it from your project's actual root.
Install
The package is published to npm as openstep:
npm install -g openstepUpgrade the same way (npm install -g openstep@latest). openstep --help
is not a command; running openstep with no arguments prints the usage line.
Building from this repo instead
To run the CLI from a checkout (e.g. while changing it):
cd cli
npm install
npm run build # compiles src/ -> dist/, then chmod +x dist/index.js
npm link # exposes the `openstep` command globally via a symlinkAfter npm link, openstep (and npx openstep) resolve from anywhere on
your machine. If you'd rather not touch global npm state, you can instead
invoke the built entry point directly: node /path/to/repo/cli/dist/index.js
<command>.
To pick up a newer commit later, pull the branch and re-run
npm install && npm run build inside cli/ — the npm link symlink stays
valid; there's no need to re-link.
openstep login <builder-url>
Logs this machine in to a specific STEPS builder (e.g.
https://builder-uat.steps.media or your production builder's URL — you can
be logged in to more than one at once, each tracked separately). A terminal
can't hold the browser session cookie that proves who you are, so this works
the way flyctl auth login does:
- The CLI opens a pending login session on the server and prints a URL:
Open this URL to finish logging in: <verify-url>. It also tries to open that URL in your default browser automatically; if that fails (e.g. an SSH session with no display), the printed URL is everything you need — open it yourself. - In the browser, sign in to STEPS Builder if you aren't already, then click Authorize openstep CLI.
- The CLI is polling in the background and exits with
Logged in.the moment you approve. You have 10 minutes to complete the approval before the login session expires (session expired, run \openstep login` again`); a dropped connection or a transient server error while polling is retried automatically and does not count against that.
openstep logout <builder-url>
Revokes this machine's login with the given builder and removes it from the local credential file. If nothing is logged in for that builder, it says so and exits cleanly. If the server can't be reached to revoke the token server-side, the local credential is still cleared (a stale local file believed to be dead is worse than one that wasn't revoked server-side) — the CLI prints a warning telling you to remove it from your account's settings page instead.
openstep dev [--port <n>] [--backend-port <n>] -- <command...>
Runs your project's own dev command against the real thing. Everything
after -- is passed through to the child process exactly as you typed it
(including a -- of its own further along, e.g.
openstep dev -- npm run dev -- --port 1234).
cd my-cloned-project
openstep dev -- npm run devWhat happens:
Reads
.steps-builder-meta.jsonin your current directory and looks up your saved login for that builder. If you're not logged in yet, it tells you to runopenstep login <builder-url>first, with that project's builder URL filled in.Registers a local development runtime with the platform and receives a scoped runtime credential.
Starts the local gateway on an OS-assigned free port on
127.0.0.1and prints its address:openstep dev → http://127.0.0.1:54321 (proxying to :5173)Spawns your command (
npm run devabove) with that credential and a few other platform-provided values (API URL, project id, etc.) injected into its environment — merged on top of your own shell's environment, so a stale value already exported in your shell can never win over what this session just registered.STEPS_PROJECT_TOKENis removed outright rather than overwritten: it's the platform's permanent project token, this command never issues one, and a copy left in your shell would otherwise take precedence over the scoped credential.Refreshes the runtime credential in the background, comfortably before its 24-hour expiry, for as long as the session runs. A refresh failure is a warning, retried 30 seconds later — it never brings your dev server down. Your backend doesn't have to notice: when the platform tells it the credential it holds is stale, it asks
openstep devfor the current one and retries. Nothing in your project needs to do anything for that to work — it's in the platform-ownedfunctions/runtime-config.jsandfunctions/platform.js.On Ctrl-C (or
SIGTERM): relays the signal to your dev server and shuts down gracefully once it exits — closing the gateway and releasing the runtime registration. A second Ctrl-C force-quits immediately if your dev server is slow or ignores the first one. Either way,openstep dev's own exit code matches your dev server's (or128 + signal numberif it was killed by a signal), so it's safe to script around.
--port configures your own dev server's port, not the gateway's. It
defaults to 5173 (Vite's default). The gateway's own port is never a
setting — it's always OS-assigned and printed in the banner above, precisely
so nothing here can collide with another openstep dev session or anything
else already listening on your machine.
Full-stack projects: --backend-port
A project with the server capability has its own backend under
functions/, and its frontend calls it at same-origin /api/... paths —
the platform's edge routes those to the backend container and everything
else to the frontend. --backend-port <n> makes the local gateway split the
same way: /api and /api/* go to 127.0.0.1:<n>, everything else still
goes to --port. Without it, /api/... lands on your frontend dev server,
which answers with index.html.
Run the backend and the frontend as two openstep dev sessions, each in
its own terminal — every session gets its own runtime credential, and the
backend session's gateway simply goes unused:
# terminal 1: the backend, on the port its Dockerfile exposes
openstep dev --port 8080 -- node functions/server.js
# terminal 2: the frontend, with /api/* handed to the backend above
openstep dev --backend-port 8080 -- npm run devOpen terminal 2's gateway address in the browser. The banner names the split so you can see it took effect:
openstep dev → http://127.0.0.1:54322 (proxying to :5173, /api → :8080)Point your browser at the gateway, not at --port
This is the detail that matters most: open the gateway's address (the one
printed in the banner), not http://localhost:5173 or whatever port your
dev server itself uses. Your project's frontend expects the data and
platform paths (/__steps/data/*, /_platform/*) at the same origin it's
served from. Opening your dev server's own port skips the gateway entirely
and reproduces exactly the symptom this tool exists to fix: pages that
should list real records render empty, and the browser console shows 404s
for platform paths like /_platform/analytics.js.
If a request to your own dev server arrives before it's finished starting,
the gateway answers with 502 Bad Gateway rather than hanging — reload once
your dev server's own output shows it's ready.
Credentials: storage and revocation
Login tokens live in a single file, ~/.openstep/credentials.json, keyed by
builder origin (so you can be logged in to a UAT and a production builder at
once). The directory is created 0700 and the file 0600, and writes are
atomic (write-to-temp-then-rename), so a crash mid-write can't corrupt a
login you already had for a different builder.
To revoke a login:
- From this machine:
openstep logout <builder-url>. This asks the server to revoke the token and then clears it locally. - From your account, for any device: open the workspace settings page
in the web app (
/projects/settings, or the settings page of any project — it's the same page) and find the CLI ACCESS section. Your CLI logins belong to your account rather than to one project, so the same list appears there whichever project you came from. It lists the devices currently authorized (labeled by hostname and OS, with a last-used timestamp), each with a Revoke button — use this if a machine is lost, stolen, or you simply can't get back to it to runopenstep logoutyourself. A device you have already revoked drops off the list; it is not shown as a past login.
What revocation does and does not stop
Revoking is what ends a machine's access, but it is worth knowing exactly when:
- Immediately: that machine can no longer log in, start a new
openstep devsession, or renew the runtime credential a running session holds. Renewal is the only thing that keeps a local session alive past a day, and it requires the CLI token on every single refresh — the runtime credential cannot renew itself. - Within its remaining lifetime (24 hours at most, usually far less): a session already running keeps working with the credential it currently holds, until that credential expires. From then on its access to the project's development data, secrets, and platform capabilities is gone and cannot come back.
So revocation caps a stolen machine's remaining access at the current credential's expiry rather than cutting it off mid-second. There is no "disconnect now" control today: if you need the access gone sooner than that window, rotate the affected secrets themselves.
Development data only — production is unreachable
Everything openstep dev reads and writes is the project's development
data — the same development data your project already has in STEPS
Builder, not a private copy made for this session. Two people (or your own
openstep dev session and the project's live preview) can observe each
other's writes and race on the same rows, the same as if you were both
editing the project in the builder itself at the same time.
Production is unreachable by construction. The local gateway only ever proxies to the platform's development data gateway and the project's runtime capability surfaces — there is no route in it to a production release, and the runtime credential it holds is scoped to development access only. There is no flag or configuration that changes this.
End-to-end verification
Last run 2026-09-02 against builder-uat.steps.media with the flower-shop
project below, through the whole loop: openstep login, the frontend and a
functions/ backend both running locally against real development data and
the platform AI (two sessions, --backend-port), a push to the project's
GitHub repo, Sync now in the builder, and Deploy backend
(Development) — after which the preview served the new /api route. The
procedure and its pass condition:
0. Preconditions
- This branch (or
mainwith it merged in) is deployed to the builder you test against, e.g.builder-uat.steps.media. - Node.js: this repo's packages declare
engines.node >= 22. Check the runner's version withnode --versionbefore starting. If it reports something older (this was written on a machine running20.19.4), either switch to Node 22+ first (e.g.nvm use 22) or explicitly record in your results which Node version you actually used — don't silently run under a version the packages declare unsupported.
1. Build the CLI (see Install / build above, if not already done):
cd cli && npm install && npm run build && npm link2. Log in to the builder under test:
openstep login https://builder-uat.steps.mediaApprove in the browser tab that opens (or the URL the CLI prints). Confirm
the CLI prints Logged in..
3. Run the test project's dev server through openstep:
cd /private/tmp/steps-test-website-20260901
openstep dev -- npm run devThis directory's .steps-builder-meta.json already points at
builder-uat.steps.media and project id e0bcc9e4-4ce8-4a03-ac9e-29213cd2a60b
(花语鲜花店 / a flower shop). openstep dev prints a gateway address —
open that address in the browser, not http://localhost:5173.
4. Pass condition — all of the following must be true:
- The home page loads and lists real products (not an empty state, not placeholder/fixture content) — the project's actual development data.
/flowerslikewise lists real products.- The browser's devtools console shows no 404 for
/_platform/analytics.js(the local gateway answers that path itself with an empty script; a 404 there means requests are bypassing the gateway or the gateway isn't running).
If any of those don't hold, capture: the exact URL you opened, the gateway's
own startup banner line, the terminal output from openstep dev, and the
browser console/network tab — then compare against the routing table in
cli/src/gateway.ts's module comment before assuming it's a CLI bug.
5. Clean up: Ctrl-C in the terminal running openstep dev (releases
the runtime registration), then openstep logout https://builder-uat.steps.media
if you don't need to stay logged in on that machine.
