@patientos/config-cli
v0.2.0
Published
pconfig — configure a PatientOS clinic (appointment types, intake, workflows, document templates, marketing website) from the command line. Config-only, no PHI access.
Maintainers
Readme
@patientos/config-cli (pconfig)
A thin CLI that configures a single PatientOS clinic from the terminal —
appointment types, intake forms, automation workflows, clinical document
types/templates, task types, and the marketing website. It has no access to
PHI: it dispatches to the same /agent/tools catalogue the dashboard's AI agent
drives (auth → org gate → per-capability role gate → each tool's zod schema →
the shared *Core under RLS).
Install
No install needed — run it with npx (requires Node ≥ 20):
npx @patientos/config-cli login --url https://yourclinic.example
npx @patientos/config-cli toolsOr install the pconfig command globally:
npm i -g @patientos/config-cli
pconfig login --url https://yourclinic.exampleWorking in this repo instead, run it with Bun: bun run config <args>, or link it
(cd packages/config-cli && bun link) to get pconfig on your PATH.
It keeps itself up to date (PAT-639)
The installed pconfig is a thin bootstrap: on each run it downloads the current
CLI code from the server it targets (GET <server>/cli/pconfig.mjs), caches it under
~/.patientos/cli/, and runs it. So the CLI stays in lockstep with the product —
you install once and never npm i -g again; a server deploy ships the matching
CLI. It revalidates at most every ~10 minutes, and falls back to the last-good cached
copy when offline (it only errors on the very first run if it can't reach the server).
Because of that lockstep, old versions are refused server-side: a pre-bootstrap
CLI (≤ 0.1.x) calling the config endpoints gets a one-time
426 — update once with npm i -g @patientos/config-cli@latest, which lands it on the
self-updating bootstrap. The bootstrap also prints a once-a-day stderr nag if the
bootstrap package itself is behind on npm (the rare manual update).
The authoritative usage guide (auth, the config model, worked examples) is the repo-local
clinic-configskill (.claude/skills/clinic-config/SKILL.md). This README is a quick command reference.
Direct-live writes (no Review & publish)
pconfig is a direct-live admin power tool by design (PAT-381). The staff
dashboard routes edits to the staged config areas — appointment types, cancel
reasons, intake forms, task types, document types, automations, pathways — through
the org-level Review & publish sandbox (a sandbox org clone → diff → apply;
apps/web/src/lib/sandbox). The CLI does not: it binds the config cores
directly, so a write to a staged area lands on the LIVE clinic immediately.
There are deliberately no sandbox verbs and no forced staging. To make it
visible, pconfig call prints one line to stderr whenever a call performs such
a write:
note: writes apply to the live clinic immediately (no Review & publish step)Read-only tools (list… / get…Detail / backtest) and the pconfig website
build verb (its own explicit lifecycle) never print it. The trigger is a
single server-supplied livesImmediately flag on each /agent/tools entry — the
CLI keeps no tool-name list of its own. Prefer the dashboard's Review & publish
when a batch of config changes should be reviewed before any of it goes live.
Auth
pconfig login [--url <baseUrl>] # opens the browser, pick a clinic, Authorise
pconfig whoami # show the signed-in clinic
pconfig logout # forget saved credentialsbaseUrl defaults to $PATIENTOS_URL or https://patientos.com.au (override
with --url, e.g. http://localhost:5001 in dev). The bearer
token is saved to ~/.patientos/cli.json. You must be an admin of the clinic.
Generic tool access
pconfig tools [--json] # list the config tools you can run (+ schemas)
pconfig call <tool> [<json>] # run one tool (JSON input arg or piped on stdin)pconfig website — the marketing website (non-PHI)
Push an externally-authored website (a directory of text source files) into a PatientOS website and build it — no manual upload. Publishing is UI-only (see below).
pconfig website list [--json]
pconfig website create --name <name> # prints the new website id on stdout
pconfig website push <dir> --website <id> [--prune] # upsert every text file under <dir>
pconfig website pull <dir> --website <id> [--force] # write the website's files into <dir>
pconfig website assets push <dir> --website <id> # upload the images under <dir> (idempotent)
pconfig website assets list --website <id> [--json] # the image library (filename → URL)
pconfig website build --website <id> [--mode preview|publish] # prints a preview URL on successThe CLI cannot publish or unpublish. Making a build live is a deliberate human
action taken in the dashboard's website builder (Publish / Unpublish); the CLI only
authors, builds, and previews. build prints a preview URL so a human can review
the unpublished build before publishing it.
Notes:
pushwalks<dir>(skipping.git/node_modules/ OS junk), skips binary files with a message (images belong to the asset library — seeassets push), and upserts each text file at its POSIX-relative path (pages/index.tsx,styles/app.css, …). It runs all its tool calls under one stablethreadIdso the server's read-before-overwrite gate (D14) is satisfied: for a path that already exists it reads the file (stamping the read-set) then overwrites it; a brand-new path is written directly.--prunedeletes remote files absent locally (needs thewebsite_delete_filetool from PAT-350; if the server doesn't expose it yet, prune is reported and skipped, not fatal).assets push(PAT-362) is the way to import binary imagery (the sourcepushdeliberately skips it). It walks<dir>, uploads everypng/jpg/jpeg/webpthe website doesn't already have (idempotent — dedup by filename againstwebsite_asset_list), and prints a filename → public-URL mapping on stdout. Each upload is re-validated server-side from the decoded bytes (real magic bytes + 8 MB cap); a rejected image is reported, not fatal. Assets are website-scoped, so--website <id>is required. Paste a URL into a site file as<img src="…">, then re-push/build(a human publishes in the dashboard).buildis synchronous — it prints the outcome (Build OK — version Nor the structured build error) and exits non-zero on failure. No polling needed.pullrefuses to overwrite a non-empty directory without--force.
Import a website end-to-end
id=$(pconfig website create --name "Acme Family Clinic")
pconfig website push ./acme-site --website "$id" # text source
pconfig website assets push ./acme-site/photos --website "$id" # imagery → prints filename → URL
# …paste the printed URLs into your pages as <img src="…">, then re-push the edited source…
pconfig website build --website "$id" # prints a preview URL
# …then a human reviews the preview and clicks Publish in the dashboard (the CLI can't publish).Tests
bun test # unit tests (dir-walk, binary detection, path normalisation)