crewship page
Manage Pages — workspace-scoped dashboards built from panels. A page holds no datasource and no credentials; a panel renders the last payload its declared producer pushed. See the Pages guide for what a page is (and is not), the freshness contract, and the permission model these commands enforce./api/v1/pages/...); the CLI supplies the
workspace from your current context the same way it does for
crewship saved-view and crewship routine.
Every subcommand honours the global --format table|json|yaml|ndjson|quiet.
Machine output is the server’s own document passed through unmodified rather
than re-encoded, so a field this build has never heard of is not lost on the
way out. --format quiet prints one key per line — the slug, the link id, the
webhook id, the action id, the version seq — so a listing can be piped into the
command that consumes it. A listing with nothing in it prints nothing under
quiet, rather than the sentence the human format would use: an empty pipe is
empty, and a line saying so would be read as a key. page export is the
exception in the other direction: it prints YAML unless you ask for
--format json.
Subcommands
crewship page list
GET /api/v1/pages. Returns the pages you may see — a page you
have no read grant for and no crew membership on any of its panels for is
not in the list.
list takes no flags of its own. Columns are SLUG NAME PANELS STATE OWNER REACH LAST DATA, where PANELS counts every panel on the page
including the ones sealed to you, and REACH says how you reach the page —
owner, role, crew:<slug>, panel_crew:<slug>, folder:<slug>, grant,
comma-joined when there is more than one path. folder:<slug> means the folder
the page is filed in is shared with you, one of your crews, or the whole
workspace. A page whose only path is grant is one somebody shared with you. --format quiet prints one slug per line, which is
the form the next command in a pipe wants.
crewship page get <slug>
GET /api/v1/pages/{slug}. Prints the page definition and every
panel you may see. A panel whose owning crew you are not in, and for which you
hold no grant, arrives as a sealed placeholder — its id, its width, its tab
and its owning crew’s name, and nothing else — so the page has the same shape
for every reader. In the human output that is one line:
--format json the panel carries "sealed": true. Key on that field being
present rather than on other fields being absent: a serialisation bug and a
permission decision are opposite failures, and only one of them is safe.
crewship page create --file <file>
--file is a Layer-1 page definition — human-authored YAML, apiVersion: crewship/v1, kind: Page:
sla: 30s is YAML sugar; on the wire it travels as sla_seconds (an integer).
Every panel needs sla; a panel without one does not validate — there is no
default that means “never mind” (§4). owner names the crew whose membership
gates who can see the panel; it is the permission anchor, not decoration.
producer names the routine, script, agent or webhook allowed to push data
into it — see page set below.
Three optional keys shape the page rather than its permissions:
span: is 1–12 and defaults to 12, the full width of the grid. A page holds at
most 24 panels and 256 KiB of spec.
Remember that page update --file replaces
the spec: omit any of these on a re-apply and they are deleted.
--owner crew/<slug> — whose page it is
script- or webhook-produced panel — and
ownership of a crew-owned page counts every member of that crew. Hand the page
to crew/ops and anyone in ops can correct a panel by hand, without a grant and
without stopping the producer that normally writes it. (A workspace admin can do
that on any page, owned or not.)
Note the two different owners. A panel’s owner: decides who may see it.
The page’s owner — and a workspace admin — may hand-write a script- or
webhook-produced panel and edit the spec. A panel produced by a routine or
an agent is writable only by that producer, or by a subject holding an
explicit produce grant, no matter who owns the page. The two owners are set in
different places on purpose: the panel’s is part of the document, the page’s is
not.
--owner takes crew/<slug> and nothing else — not ops, not user/<id>.
Your own id is already the default, and the crew has to exist in this workspace
or the create is refused naming it. Ownership is a create-time decision:
page update re-applies a spec and never moves it, because a re-apply that
silently transferred a page would be a permission change nobody asked for. There
is no transfer command at all — ownership moves only when the owning user is
erased from the workspace, and then only to a crew.
page get and page list both show the owner, so what you set is visible
without reading the database.
--icon and --color — the page’s own avatar
A page wears an icon and a colour the way a crew, an agent and a folder do, so
the list on the left of
/pages can tell eight pages apart by something other
than their freshness glyph. The state (fresh, stale, failed, never produced)
keeps its own glyph beside the avatar, always — the avatar never stands in for
it, and the STATUS filter is unaffected by it.
Like --owner, the avatar is not part of the document: it is stored on the
page row, not in the spec, so a re-apply of the file leaves it alone, a
page rollback does not restore an older one,
and an export bundle does not carry it. page get and page list both return
icon and color (empty strings for none, never a missing field).
A panel may also declare the sensor half — a wake: threshold that opens an
issue on a crew when the pushed payload crosses it, and on_failure: for when
it stops arriving at all:
writes: has to name a panel on this page — a gate pointing at a panel
that is not there is refused when the page is saved. It also does not grant
anything: the woken agent still needs produce authority on that panel, which on
the container door means the panel declares producer: agent/<its slug> or a
human granted it produce. A gate whose target declares producer: routine/…
is answered by running that routine, not by the woken agent writing into it.
The CLI sends both verbatim and the server parses them: when is checked
against the panel’s own schema, and a predicate that panel could never satisfy
is refused here rather than accepted and silently never matched. See
Pages → wake gates for the grammar and
page get plus crewship automation list for
seeing what a gate became.
A panel produced by a routine may also declare refresh: — on:wake or
on:panels-changed, and nothing else. It is the event that runs that panel’s
producer, so the analysis is already on the page when a human arrives instead of
starting when they get there:
refresh: whose producer is not a routine/ (it cannot run a
script, call a webhook producer, or dispatch an agent), an on:wake on a page
that declares no gate, and an on:wake on a panel that declares its own gate —
which is a loop. Like a gate, it compiles to a row crewship automation list
shows. See Pages → refresh.
Backed by POST /api/v1/pages.
crewship page update <slug>
PATCH /api/v1/pages/{slug}.
--file, this replaces the spec, it does not merge into it: a panel field left out
of the file is deleted, including tab:, icon:, public:, actions:,
wake:, on_failure: and refresh:. Panels are reconciled by id, so a panel
that survives the edit keeps its payload ring.
If the document declares a metadata.slug different from the slug you passed,
the CLI refuses before sending: a page’s slug is its address. --owner is
create-only and update never sends it, so a re-apply never moves ownership.
Every save of the document — through this command, the in-app editor, or an
agent with a write grant — becomes a new row in the page’s version history.
See page rollback to undo one. An update
that changes only --icon or --color is not a save of the document: it
records no version and leaves the page’s updated_at where it was.
Replacing the panels requires that you can read every panel in both the
current and the submitted definition. A write grant alone does not give you
that: a panel owned by a crew you are not in is sealed to you, and a document
that omits it would delete it. The server answers a partial reader with a
neutral 403 (exit 4) rather than a truncated save. If somebody else saved
the live definition between your read and your write, the update is refused
with 409 (exit 5) and the newer document is left intact — re-read it and
apply your change again.
crewship page delete <slug>
Backed by
DELETE /api/v1/pages/{slug}.
crewship page set
The single write path for panel data. Everything that produces a number,
status, or table on a page — a cron job, a script on a host you control, a
routine step — ends here.
Backed by
PUT /api/v1/pages/{slug}/panels/{id}/data. page set authenticates
with your session or CLI token the way any other crewship command does, and a
producer script always calls the same command wherever it runs.
A producer running inside a crew container does not use this command, and
could not: there is no The
crewship binary in the sandbox image. It pushes to
its sidecar — PUT http://localhost:9119/pages/{page}/{panel}, body = the
payload, ?state=failed when it ran and could not measure — which attaches
the identity from the agent’s own token and forwards to
PUT /api/v1/internal/pages/{page}/data. The credential stays in the sidecar
and never enters the agent process:fd-3 form is deliberate: a bearer token passed as -H is visible in
the container’s process table.Note the panel: rozbor, not sluzby. rozbor declares producer: agent/riley, and the curl above runs inside riley’s container. sluzby
declares producer: script/watch-services.sh, and this door would refuse it
— which is the rule stated immediately below, so an example that pushed there
would be teaching the opposite of the paragraph it sits in.Declare such a panel producer: agent/<slug>, not producer: script/….
A script producer means “a human’s CLI token pushes this”, and the
unattended path admits only the declared routine/ or agent/. A container
writing a script-declared panel is refused with 403 unless a human issued
it a produce grant. See
Pushing from inside a container.producer, run_id,
produced_at) and the freshness clock. A payload the caller cannot prove they
own — the caller is not the panel’s declared producer — is refused with
403 (exit 4) when the caller can otherwise reach the page, and with
404 (exit 3) when they cannot, so the refusal never confirms a page exists
to someone who may not see it. Either way the refusal itself writes a journal
entry and notifies the page owner (§7.1b rule 3): treat it as a signal to
investigate, not noise to retry past.
A working producer script
The panel above ismetric.v1
({value, unit?, delta?, target?, sparkline?[]}). A cron job or a step inside
a routine can push it with nothing more than jq and the CLI:
status.v1 panel ({items[{name, state: ok|warning|critical, label}]})
pushes a list rather than a number:
table.v1 panel takes keyed row objects matching its declared
columns, not positional arrays — this is the canonical shape the schema
validates against:
Limits that turn into responses, not silent drops
The 2 s floor is the one a
--loop 1 producer meets first, and it is not a
token bucket: it is enforced inside the write itself, so it holds across
replicas and across all four doors. The CLI prints the scope and the wait —
this panel is being pushed faster than its rate limit allows — retry in 2s —
and exits 6, so a loop can sleep exactly that long rather than guess.
crewship page actions
List the actions a panel declares.
GET /api/v1/pages/{slug}/panels/{id}/actions. The list comes from
the page’s stored spec — the same list the server resolves a click against
— so what you see is exactly what can be dispatched. A panel you may not see
answers 404, the same answer an unknown panel gives, so an action list is
never an existence oracle for somebody else’s crew.
Only call actions reach the server. A link navigates to an internal entity
(issue, run, page, agent) and the renderer builds the address — there is
no URL field anywhere in the schema and there will not be one. A toggle is
client-side panel state. A custom action resolves to a handler compiled into
the web client.
--format quiet prints one action id per line — column 0 is the argument
page action takes, so the two compose:
quiet and exits
0. Do not read the empty output as a failure — a panel with no actions and a
panel you may not see are told apart by the exit code, not by the text.
crewship page action
Dispatch one of a panel’s declared actions.
Backed by
POST /api/v1/pages/{slug}/panels/{id}/actions/{actionId}, or with
--publication by POST /api/v1/pages/{slug}/application/actions/{panelId}/{actionId}.
You cannot name a routine here, and that is the design
There is no--routine flag, and the request body carries the
collected inputs and optional intent/publication preconditions. The server resolves the action id against the page’s stored
spec and dispatches the routine named there. A compromised client, an injected
narrative panel, or an agent cannot choose what runs, because the wire format
has no field for it. The allow-list is not a check the server remembers to
perform — it is the only path that exists.
Inputs are validated server-side against the action’s own declaration. An input
the action did not declare is refused, not passed through; a required input
that is missing is refused before anything runs; a fixed param the page author
set is not yours to override.
It returns when the run is queued, not when it finishes
The answer is202 with a pending id. Nothing waits for the run — a page button
on a ten-minute routine must not hold a connection. Watch the run itself on the
page, in the activity feed, or with crewship routine runs.
Retrying is safe, replaying with different inputs is not
Every dispatch carries anIdempotency-Key, generated locally, one per
invocation. Pass --idempotency-key to pin it so a retry from a shell loop or a
CI step resolves to the original dispatch instead of starting a second run.
Reusing a key with different inputs is refused with 409 rather than
silently deduped onto the first run — a replayed key that quietly returned
somebody else’s result would tell you a click succeeded that never happened.
Who may dispatch
Two halves, and both must hold:- You can see the panel — membership of its owning crew, or a workspace
admin role. A caller who would get a sealed placeholder gets
404, not403: the action does not exist for them, and a403would confirm it exists for someone else. - You hold what the routine requires — MANAGER+, and the routine must be
active. A page button is never a cheaper way to run a routine than the routine’s own surface.
page.action.dispatched, carrying who clicked,
which action, and which routine the server resolved it to.
Declaring an action
Actions are declared in the page document, by a human editing the page. An agent can write a panel’s data; it can never author the button underneath it.- an action
kindoutsidecall | link | toggle | custom, or none at all; - a
callthat names no routine, or names one that does not exist here; - a
linkcarrying anything URL-shaped instead of an entity id; - a
toggletargeting a panel that is not on the page; - two actions on one page sharing an id;
- an input named after a fixed param, or an input collecting a secret;
- actions on a
narrative.v1panel — a panel that renders agent-written prose and can also trigger an action is refused in this release.
crewship page grant <slug>
Grants layer on top of a panel’s crew-based visibility — they widen who may
reach the page, never who may reach a crew’s data (§7.1 rule 3, §7.1b).
Only a human can run this command successfully. An agent holding
write on
the page can rebuild its layout freely but cannot issue a grant — not even
to another agent in its own crew — by any route, including this one (§7.1b
rule 1).page.grant_added), so who granted what to
whom is always answerable later.
Backed by the page’s grants endpoint (GET/PUT/DELETE …/grants).
crewship page revoke <slug>
page.grant_removed.
revoke takes the same subject flags as grant — --user, --crew,
--agent, --workspace — plus an optional --level. Omit the level and every level that
subject holds is withdrawn at once.
A grant can also stop working without being revoked. It is live only while
the human who issued it still has the reach they granted from: leave the
workspace, lose ADMIN, or be removed from the page’s owning crew, and every
grant you issued narrows with you. The row stays —
page grants shows it as
inert, with the reason — so you can see what would come back if that person’s
access were restored. Revoking is how you remove a grant; this is how one
stops being borrowed authority nobody is accountable for any more.crewship page grants <slug>
produce) which panels it covers.
crewship page access <slug> --me
Prints the paths by which you reach the page, in the API’s own words —
owner, role, crew:<slug>, panel_crew:<slug>, folder:<slug>, grant
— the same words page list prints in its REACH column. When the page is in
a folder, the folder’s slug and its sharing label (none, people, crews,
people_and_crews, workspace) are printed too: the label, never the names. Anyone who can open
the page may ask; a page you cannot reach is a 404.
Backed by the page’s own-access endpoint (GET …/access/me).
crewship page folder
A folder is a named group of pages owned by one crew, with an icon from the
crew icon set and a colour from the crew palette — the same picker a crew uses.
One level, a page in at most one folder; a page outside any folder is
unfiled. A folder shows you only the pages in it you reach.
A folder can be shared — with a person, a crew, or everyone in the workspace —
and what it is shared with applies to every page in it right now: filing a page
in a shared folder shares it, taking it out unshares it. Can view opens the
pages; can edit also renames the folder, edits the pages inside and takes
pages out of it. A panel owned by another crew stays sealed whatever the folder
says, and a folder is never shared with an agent. Only the owning crew’s
managers and workspace admins see or change a folder’s sharing; everyone else
sees a label (none, people, crews, people_and_crews, workspace) in folder list and folder show,
and their own access in page access --me.
Every folder command is backed by the folder endpoints.
crewship page folder list
PAGES counting the pages in each that you reach.
--format quiet prints one slug per line.
crewship page folder create <slug>
A workspace admin, or a MANAGER (or higher) who belongs to the owning crew,
may create it; the folder is the crew’s from birth.
crewship page folder show <slug>
REACH column. A
folder you reach no page in — and have no standing on as its crew or an admin
— is a 404, indistinguishable from one that does not exist.
crewship page folder update <slug>
Only the flags you pass are sent. The slug and the owning crew are fixed at
creation. An admin, the owning crew’s MANAGER (or higher), or somebody the
folder’s sharing lets edit it.
crewship page folder delete <slug>
Only an empty folder: one still holding a page — including one you cannot see
— is refused with 409, and the refusal does not say how many. Move the pages
out first (
crewship page move <page> --unfiled).
crewship page folder add <folder> <page>
crewship page move fleet-201 --folder ops-board. See
page move for the two authorities it needs and
the fences it carries.
crewship page folder remove <folder> <page>
crewship page move fleet-201 --unfiled, naming the folder
explicitly. The page’s owner, a workspace admin, or somebody the folder’s
sharing lets edit it may — the last on purpose: taking a page out withdraws
the access everyone else inherited from the folder. Taking your own page back
never waits for anyone.
crewship page folder acl <slug>
acl_version. Only a workspace
admin or a MANAGER (or higher) of the owning crew may read this; everyone else
sees the sharing label in folder show and their own access in
page access --me. The owning crew and
admins are not listed: they hold the folder without an entry.
crewship page folder share <slug> <subject>
Exactly one of the two. A subject is
user:<email or id>, crew:<slug>, or
the word workspace for every current member. Never an agent: an agent
reaches a page only by page grant --agent on that page, and the command
refuses agent: before any request.
Sharing applies to every page in the folder now or later. Can edit never
opens a panel owned by another crew, moves a page elsewhere, deletes the
folder or changes its sharing — and it can take pages out, which withdraws the
access everyone else inherited. Only a workspace admin or a MANAGER (or
higher) of the owning crew may run this. The entry belongs to the folder and
stays when whoever set it leaves. Repeating the command is the same state, not
a second entry.
crewship page folder unshare <slug> <subject>
share. A user who has
since left is removed by the id or email folder acl prints. Only a workspace
admin or a MANAGER (or higher) of the owning crew may run this.
Sharing changes are journalled as page.folder_acl_changed.
crewship page move <page>
Exactly one of the two. Moving a page into a shared folder shares it with
whoever the folder is shared with; moving it out unshares it.
Who may. Filing needs both halves of the authority in one caller: you
initiate as the page’s owner or a workspace admin, and the folder accepts you
as an admin, a MANAGER (or higher) of its owning crew, or somebody its sharing
lets edit it. Lacking either, the refusal names who can; can edit alone
never takes somebody else’s page. Unfiling is the page owner’s, an admin’s, or
a can edit holder’s — taking your own page back never waits for anyone.
Several pages. With more than one page and
--folder, the command sends
one request: every page is checked on its own, one refusal moves nothing, and
the error names the page.
The fences. The command reads each page’s pages_version and the folder’s
acl_version right before it writes and sends exactly what it read. A
409 therefore means somebody else moved a page, or changed the folder’s
sharing, between that read and the write; the error prints the current pair —
look again and re-run. There is deliberately no flag to type a fence by hand: a
fence you typed says nothing about what you saw.
Moves and removals are journalled as page.folder_membership_changed; the
folder’s own changes as page.folder_changed.
crewship page access
--subject it answers the reverse: which pages does this
subject reach, and how. Both are read-only renderings of what the server
already enforces; no grant is issued or implied, and an inert grant is not a
path.
A path is one of the server’s own words, printed verbatim:
--subject user:<your email>). Every other caller is refused with the
server’s sentence and a non-zero exit.
--format json prints the server’s document unchanged — the page (or the
subject), the rows with their paths, and next_cursor when more follow.
crewship page export <slug>
crewship routine export
uses for the marketplace — a “page template” is not a separate object, it is
an exported page spec.
Export carries the page spec only, not panel data. Data is state that
belongs to the install it came from; moving it between workspaces would
mean shipping numbers with no producer behind them.
crewship page import [bundle.yaml]
Import either binds every declared reference and creates the page, or refuses
and names which reference it could not resolve — it does not create a page
full of dead panels pointing at a producer that does not exist locally.
--bind is repeatable, once per reference, and is deliberately not
comma-separated: both halves are slugs, and a slug may plausibly contain a
comma where a flag may simply be repeated. Binding one reference twice is
refused rather than resolved last-wins.
crewship page rollback <slug>
Rolls back the page structure — which panels exist, their layout, their
owners and producers. It never resurrects old panel data: a panel a rollback
brings back renders dimmed, in a “waiting for first data” state, even if rows
for it survive in the payload ring. The ring is cleared for the same reason for
a panel whose schema, producer or owning crew the rollback changed — the footer
would otherwise credit a producer that did not produce the number, or show a
payload to a crew the restored spec never admitted. Showing an old payload as current after a
rollback is exactly the dishonesty the freshness
contract exists to prevent. Up to the
last 50 versions are kept.
A rollback replaces the whole definition, so it has the same visibility rule as
page update --file: you must be able to read
every panel in both the current and the archived definition. A partial reader
gets a neutral 403 (exit 4). If the live definition changed after you read
it, the rollback is refused with 409 (exit 5) and the newer document stays;
reload before restoring.
crewship page versions <slug>
page rollback. Up to
the last 50 versions are kept. Panel data is not versioned here; a version is
a record of what the page was shaped like, not of what it showed.
crewship page publish <slug>
Publish a page to someone outside the workspace, behind an expiring link.
The URL is printed once. The token is stored as a SHA-256 digest, so
nothing can show it again — not this command, not
page links, not the
database. Lose it and you mint a new one.
Publishing exposes only the panels explicitly marked public, never the whole
page. See Public pages.
crewship page links <slug>
page publish above for why.
crewship page unpublish <slug>
Withdraws one link without touching the others. Several links per page is the
intended shape — revoking the one you sent a supplier does not break the one on
the wall display.
crewship page webhook
Let a producer that cannot run this binary write one panel: a cron on
someone else’s box, a Zapier step, a PLC gateway, a GitHub Action. Anything
that can run the CLI should use page set
instead — one write path, provenance attached server-side, and no credential
to leak.
create prints the URL once and nothing can show it again: the token is
stored as a SHA-256 digest, the same at-rest shape pipeline
webhooks use. Copy it into the sender’s secret store, or mint a
second one — several tokens per panel is the intended shape, so revoking the
PLC’s does not break the GitHub Action’s.
The sender POSTs the panel’s payload as the body, with no envelope around it:
?state=failed — with whatever payload it has. fresh and stale are the
server’s arithmetic and are not a sender’s to claim, and neither is
produced_at: provenance is attached server-side, so a body carrying a
timestamp or a producer name is refused rather than believed.
What the token can and cannot do
A webhook is aproduce grant in a different coat, and it obeys every rule
that grant does.
A token whose panel is deleted from the page spec dies with it, the same way
the panel’s payload ring does.
Flags
webhook list never shows a token value — the column holds a digest, so there
is nothing to show. A revoked token stays in the listing on purpose: “was it
used after we pulled it” is the question an incident asks, and a deleted row
cannot answer it.
Exit codes
Standard CLI exit codes. What a producer script needs to branch on:
Refused locally, before anything is sent (always exit 2): a page document
that does not validate, a
<page>/<panel> address with no slash, an empty or
non-JSON --data, an --owner that is not crew/<slug>, a missing
--file/--panel/--id, a --to that is not positive, an --input that is
not k=v, a malformed or repeated --bind, a bundle whose format is neither
crewship-page-bundle/v1 nor crewship-page-bundle/v2, and a --file whose metadata.slug disagrees with
the slug you passed to update.
Everything else is the server’s answer — including an unknown slug and an
unknown --level, both of which are checked there and not here.
Custom React projects (experimental)
The opt-in Pages application profile uses React, TypeScript and Vite. Projects are source drafts; saving/importing one does not execute or publish its code. The operator must configure protected source storage, a pinned build image and a separate-site runtime origin. The current profile accepts its fixed dependency set and lockfile, with custom React and CSS. It does not install arbitrary packages. Everypage project subcommand prints the server’s document — JSON unless
--format yaml or ndjson asks otherwise; there is no table form — except
init and pack, which print source YAML. The lifecycle, in order:
page project command that reads or writes
source (get, history, restore, set, build, preview, check,
review, fsck) and page export require Page edit permission, not just
permission to view its panels. The commands that return or replace a whole
definition additionally require that you can read every panel in it: get
(without --source-only), get --revision, export, set and restore.
A write grant does not grant visibility of another crew’s panels, and a
document with a panel missing would delete it on save, so a partial reader is
answered with a neutral 403 (exit 4) rather than a truncated document. set
and restore apply the rule to the live definition, the existing draft and the
proposed definition alike; a refused save leaves the draft revision unchanged.
get --source-only omits the definition and is not gated this way. review
is not refused either: it withholds the panels you cannot read from both
documents and reports excluded_panels and withheld_changed instead (see
below). Publishing, rolling back and withdrawing require Page ownership or
workspace administration. application, action-status and panel-history
are reader operations: they need the page to be visible to you and, for the
last two, the panel too.
crewship page project init
--dir writes its files into a new working directory. An existing
directory is never overwritten. --format json prints the same document as
JSON. Nothing is sent to the server.
crewship page project pack <directory>
set
takes. .git, node_modules and dist are skipped. Nothing is sent to the
server. pack takes no flags of its own.
crewship page project unpack <directory>
crewship page project get <slug>
GET /api/v1/pages/{slug}/project, or with --revision by
GET /api/v1/pages/{slug}/project/history/{revision}; --source-only appends
/source to either. Prints the draft’s revision, its project (the source
files) and its definition (the authored Page document the draft would make
live). --source-only omits the definition, which is the form to use when you
want to read code without requesting the complete panel declaration.
crewship page project set <slug>
PUT /api/v1/pages/{slug}/project. Saves a source document against
the draft revision you expect to be current. The reply carries the new
revision and a git_commit: every save is a Git checkpoint.
crewship page project history <slug>
GET /api/v1/pages/{slug}/project/history. Lists audited source
revisions — who saved each one, when, and its Git checkpoint — at most 50 per
call.
crewship page project restore <slug>
POST /api/v1/pages/{slug}/project/restore. Restores a historical
source revision as a new draft, creating a new revision and Git checkpoint.
It never touches the live application; publishing the restored draft is a
separate, reviewed step.
crewship page project build <slug>
POST /api/v1/pages/{slug}/project/build. Starts an isolated
preview build of the given source revision in the pinned build image. Returns
a job receipt, not a ready preview; preview reads its state. A busy
compiler returns 429 without a waiting queue.
crewship page project preview <slug>
GET /api/v1/pages/{slug}/project/preview. Prints the latest
build’s state (running, ready, failed, interrupted), its diagnostics,
and the artifact once it is ready. preview takes no flags of its own.
crewship page project check <slug>
POST /api/v1/pages/{slug}/project/check. Verifies a built
candidate: stored source and artifact integrity, the compiler result, and
that the routines and crews the definition binds to still resolve. A clean
check is not a review — it still requires browser and code review.
crewship page project review <slug>
GET /api/v1/pages/{slug}/project/review. Prints the snapshot a
publication is fenced against: the candidate draft revision and its build, the
live publication and its retained source, the routine definitions each call
action would run, and machine-readable blockers explaining why publishing is
refused. A baseline it cannot read back reports source_available: false with
a reason — that is a missing comparison, not agreement — and a routine with no
recorded digest is unknown, never unchanged. Before the draft has been
built the snapshot carries build_missing; before there is a draft at all,
no_candidate.
--publication <version> asks the same question about a retained version
instead of the draft: the candidate becomes that publication’s archived source
and its routines become the ones its archived definition declares, which is
what a rollback is fenced on.
review also answers whether the comparison it just printed is the whole
change. Panels belonging to crews you are not in are withheld from it, and
excluded_panels says how many; withheld_changed says whether any of them
actually differs between the live definition and the candidate. When it does,
publish and rollback are refused with 403 and blockers carries
withheld_change.
crewship page project publish <slug>
POST /api/v1/pages/{slug}/project/publish. Publishes the exact
candidate you reviewed: the build, the source revision, and the Page
definition the draft carries. Use the actual build ID, source revision and
publication version returned by the server. --expected-publication 0 creates
the first publication. Exact retries return the original receipt.
Publication requires Page owner/workspace admin authority. An ordinary write
grant does not grant permission to publish executable code for readers.
The fence.
publish and rollback send two fence values.
--expected-definition-digest is the sha256 of the Page definition that was
reviewed, and --expected-routine-digest routine=sha256 (repeatable) is the
routine definition behind each call action. Pass either flag and that half
is sent verbatim.
Omit them and the CLI reads project review itself immediately before
publishing — rollback asks it for the retained version it is restoring —
uses the digests it returns, and prints each one to stderr as it sends the
request. That saves copying hex by hand, and it leaves a record of what the
publication was fenced on. It is not a review step: the command does not
pause, and nobody sees those digests before they are sent. To review first,
run crewship page project review <slug>, read it, and pass the values back
with the flags. If the live definition or a routine script moves between the
snapshot and the write, the server refuses with 409 and names which one.
Withheld panels. --reviewed-code attests that the whole change was
reviewed, and that attestation cannot be made by a publisher who cannot read
all of it — the rollback path included, since restoring a version replaces the
live definition too. When review reports withheld_changed, the server
refuses with 403. Publishing the Page is still yours to do; this particular
claim is not yours to make, so there is no flag that waives it, unlike the
baseline acknowledgement below. Ask a workspace administrator, or a member of
the crew that owns the withheld part, to publish instead. The CLI prints the
refusal ahead of the request, and says how many panels are withheld and
nothing about what they are.
Unavailable baseline. --acknowledge-unavailable-baseline is separate
from the digests and is never filled in for you. When the live publication’s
retained source cannot be read back — history compacted, a checkpoint lost —
there is nothing to compare the candidate against, and the server refuses the
publication with 409 until somebody says so explicitly. The CLI prints what is
missing and what acknowledging it would mean, and then sends the request as
you gave it; passing the flag records that nobody made that comparison. It
proves nothing about the candidate, which is verified in full either way, and
the publication’s checks keep the answer as baseline_source. An initial
publication has no baseline and needs no acknowledgement.
crewship page project rollback <slug>
POST /api/v1/pages/{slug}/project/publish with
rollback_version. Creates a new publication of an earlier artifact and
Page definition, keeping the source draft unchanged. It does not undo work
already performed by routines. The fence, the withheld-panel rule and the
baseline acknowledgement are the same as publish’s, computed against the
retained version being restored. Publication metadata is separate from the
older panel-only page versions/page rollback history.
crewship page project application <slug>
GET /api/v1/pages/{slug}/application. Prints the live publication
receipt (publication, publication_version), the artifact, runtime_url
and whether you may publish. A Page with no live application answers
"publication": null. application takes no flags of its own.
crewship page project publications <slug>
GET /api/v1/pages/{slug}/project/publications. Lists immutable
application publication receipts and the current publication counter, 50 per
request. Use page project get <slug> --revision <source-revision> to inspect
exact source before a reviewed rollback.
crewship page project unpublish <slug>
POST /api/v1/pages/{slug}/project/unpublish. Withdraws the custom
application for all viewers. Requires Page ownership or workspace
administration. Panels, source and retained publication history remain
available. The version counter does not reset; stale requests return 409.
Withdrawal does not cancel routines already queued, and repeating the same
withdrawal is idempotent.
crewship page project action-status <slug> <pending-id>
GET /api/v1/pages/{slug}/application/actions/{pendingId}.
page action --publication uses
the published application action endpoint. The version must still be current
and the Page declaration must match the publication. The existing routine
RBAC/policy still applies. Reuse the same key for an explicit retry; a receipt
means queued, not completed. action-status returns only the current
user’s pending/run identifiers and states, never another reader’s run. A
network timeout does not mean execution stopped. action-status takes no
flags of its own.
crewship page project panel-history <slug> <panel>
GET /api/v1/pages/{slug}/application/panels/{panelId}/history,
the route the @crewship/pages SDK’s getPanelHistory calls. Prints the
panel’s readable payload ring — items of sequence, data, producedAt
and state, newest first — plus next_before (0 at the end) and the
publication it was read through. Nothing about the producer, its run or any
other endpoint is on the wire; this is what a published application is allowed
to see, and no more.
The read is fenced on the publication: --publication is required, and a
version that is no longer current, has been withdrawn, or whose recorded Page
definition no longer matches the live one is answered with 409 (exit 5) —
reload page project application for the current version. A panel you cannot
see, or that does not exist, is 404 either way. Responses are bounded to 1 MiB;
a single entry over that is 413.
crewship page project fsck <slug>
GET /api/v1/pages/{slug}/project/fsck. Checks referenced sources,
checkpoint contents, artifacts and reachable Git objects, with Page edit
permission. Returns healthy, object counts and failures; the command exits
nonzero when unhealthy. It does not repair or publish anything. fsck takes
no flags of its own.
crewship page project compact
POST /api/v1/pages/maintenance. Runs workspace-wide Page storage
maintenance (OWNER or ADMIN required). Without flags, retains the configured
recent history plus current draft/live/running roots. --discard-history --yes explicitly discards optional history across all Pages in this workspace
while keeping those protected roots. This cannot recover space occupied
entirely by protected current content.
Export and import with source
page export automatically uses crewship-page-bundle/v2 for a Page with source:
one YAML contains the authored definition, sources, lockfile and small assets.
Import accepts v1/v2 JSON or YAML, revalidates bindings and creates an inert
draft: the imported page’s live definition carries no actions and nothing is
published, while the draft’s definition keeps the call actions the export
declared, ready to be reviewed and published here. It does not transfer
grants, credentials, run data, Git history, build artifacts or trusted
publication status. The referenced crews/routines must exist at the
destination. Export is not a backup of the complete project history.
Related
- Pages guide — what a page is, the freshness contract, and the full permission model.
crewship routine— the usual producer behind a panel.crewship automation— what a panel’swakegate compiles to.- Internal IPC API — the sidecar trust boundary
page setuses inside a container. - Operate Pages applications — installing the compiler and runtime origin the
page projectcommands need, backups and upgrades.
Agent chat: custom Page sources
Agents use thepage_project tool on the existing crewship-routines MCP
server. The sidecar supplies the workspace, crew and acting agent from its
configuration and per-agent token. No human CLI/API token is needed in a crew.
The operator must configure the protected projects directory, Git and the
optional pinned build image described in the Pages Apps build profile.
- Create the operational Page using
save_page; use its returned slug. - Call
page_projectwithoperation: "init",slugandexpected_revision: 0to create the supported React/TypeScript starter. - Use
operation: "read"to inspect the current revision, file list, Page definition, the page’s path in the browser (page_path) and exact@crewship/pagesSDK source. Addpathto read one source file. The starter importssrc/style.css; arbitrary design can be written in TSX/CSS within the supported dependency profile. - Save a batch of whole-file replacements with the revision just read:
delete_paths removes existing files. definition optionally replaces the
complete portable Page definition in the draft. The complete MCP request must
fit in 1 MiB; use small batches, each against the latest returned revision.
A conflict returns an error: read, reconcile and save again. Never initialize
an existing project to reset it. Source writes create Git checkpoints; the
published Page is unchanged.
- Call
buildwithexpected_revision, then pollstatusforrunning,ready,failedorinterrupted. One build runs per instance; a busy worker refuses another request. Status returns diagnostics and build identity without retransmitting compiled JavaScript. - Call
checkwith the returnedbuild_idand sourceexpected_revision. Report the revision/build andpage_pathto the user: the review, with its preview, is in the page editor’s Content section. A successful static check does not replace browser or source security review.
page_create autonomy policy;
held or denied operations do not mutate a draft or start a build. Reads and
checks require the Page’s exact owning crew and a current agent in that crew.
The onboarding setup crew must explicitly name the target crew, following
save_page delegation rules. Revisions/builds retain the actual author and the
target owner separately. Run attribution is not inferred from an unverified ID.
There is no publish operation in this agent tool. An authorized user publishes
the reviewed build through the page editor (Edit → Content, or History
for a retained version) or crewship page project publish.
After a transport timeout the outcome can be unknown; inspect the current
revision and build status before retrying. Data producers keep pushing through
the existing panel API/page.write path independently of source edits.