@skhema/cli
v0.17.0
Published
Skhema CLI - Authentication and AI skills management for agent platforms
Readme
[!IMPORTANT] Generated mirror of a private source repo — do not open PRs here. This repository is an automatically generated, read-only mirror of a package built from Skhema's private monorepo. Pull requests are disabled and any change pushed here is overwritten on the next release. Questions and bug reports: [email protected]. Security: see SECURITY.md.
@skhema/cli
The Skhema command-line interface (skhema). Authenticate as a human or an
agent, then drive the Skhema Public API from your
terminal, CI, or an autonomous agent — projects, elements, components,
strategies, exports, resources, and webhooks, plus one-command agent onboarding.
Install
npm i -g @skhema/cli
skhema --helpRequires Node ≥ 18.
Agent quickstart
Point an agent (OpenClaw / Hermes / any CI job) at Skhema in one line — install skills, register the MCP server, and verify an API connection:
SKHEMA_API_KEY=sk_live_… npx @skhema/cli init --yes --jsonskhema init:
- Verifies a credential — resolves
--api-key/SKHEMA_API_KEY/ a stored key / an OAuth session (same precedence as every command), exchanges + probes it, and reports the org and permission. An explicit--api-keyis stored for future commands (never overwriting a different stored key without--force). - Installs skills into every detected agent platform (idempotent — reruns
report
unchanged). - Registers the MCP server (
https://mcp.skhema.com/mcp, Streamable HTTP) in detected clients by safely merging a dedicated config file and writing a.bakbackup — or printing copy-paste instructions when a client's config isn't cleanly writable.--yesregisters non-interactively; without it,initreports what it would do and prompts only on a TTY. MCP auth is per-client browser OAuth on first use — the CLI only wires the URL, never a credential.
--yes never prompts and does everything resolvable, reporting what it skipped.
Re-running is safe: every step reports already-done work as such.
Raw API passthrough
Reach any current or future gateway route without waiting for a dedicated verb — with the CLI's credential resolution, error mapping, and exit codes:
skhema api GET /projects # leading /v1 is optional
skhema api POST /projects/ws_1/elements --data @element.json
skhema api GET /projects --query limit=10 --query type=strategy
echo '{"name":"Q3"}' | skhema api POST /projects --data -
skhema api routes # every live route from the OpenAPI spec--data takes a JSON literal, @file, or - (stdin). --query k=v repeats. The
passthrough refuses any host other than the Skhema gateway.
Reading the public element repository
Beyond your own project, Skhema publishes a public repository of elements anyone can read and fork. Both element read commands can include it:
skhema element list --include-public
skhema element search "grassroots sport" --include-public --limit 10
skhema element list --collection sport-in-australia # one curated public setPublic results come back as their own table, because the two lanes are
different: only your organization's elements are yours to edit. Each row shows
Author — who added the element to Skhema — and Source, who originated
the content when it was mapped from an external document. skhema element get
prints the full citation:
Source: Example Sports Commission — National Sport Strategy, 2024–2027
https://example.com/strategyAn element original to its author shows no Source line at all. A forked element
whose content has since drifted reads Derived from: instead.
A bare skhema element get reads your organization's elements. To get an id you
copied out of a Public results table, pass the same flag:
skhema element get el_... --include-public--collection <slug> is public-only: organization elements are omitted and
--include-public is implied. An unknown or empty slug returns an empty result
rather than an error — the Public results table then reads (0), so a mistyped
slug never looks like an empty project.
Strategy document imports
Import a local strategy document or public URL through the same reviewable proposal used by the web and MCP surfaces:
skhema import start --file ./strategy.pdf --wait
skhema import get imp_... # inspect the mapped proposal
skhema import decide imp_... --file decisions.json
skhema import apply imp_... --project-name "Imported strategy" --yesUse --json for a stable machine envelope. Applying or discarding requires an
interactive confirmation, or an explicit --yes in scripts and CI.
Agent login (no API key yet)
The quickstart above assumes you already have an SKHEMA_API_KEY. A brand-new
agent — a personal assistant a user just pointed at Skhema for the first time —
has no key. API keys can't bootstrap themselves (auth key create needs a
logged-in session), so device login is the front door, and it's built to
survive an agent harness that kills long-running commands.
The classic skhema auth login --headless blocks one process polling until the
human approves — agents get interrupted and the login is lost. Instead, split it
into two short commands that persist state between them:
# 1. Request a code and EXIT IMMEDIATELY (no blocking). Relay the URL to the human.
skhema auth login --headless --no-wait --json
# → { "ok": true, "command": "auth login", "data": {
# "pending": true, "user_code": "WDJB-MJHT",
# "verification_uri": "https://auth.skhema.com/device",
# "verification_uri_complete": "https://auth.skhema.com/device?code=WDJB-MJHT",
# "expires_at": "2026-07-23T12:15:00.000Z", "interval": 5 } }
# 2. After the human taps the URL and approves, poll once to finish. Repeat until done.
skhema auth login --complete --json
# pending → exit 6, error.code "authorization_pending" → wait a few seconds, run it again
# success → exit 0, data { "authenticated": true, "user": {…}, "organization": {…} }The poll loop an agent runs:
skhema auth login --headless --no-wait --json→ relayverification_uri_completeto the human.skhema auth login --complete --jsonon a timer. Poll no faster than theintervalseconds from step 1 (default 5s): the CLI self-throttles across processes, so a--completefired inside that window just returnsauthorization_pendingagain without contacting the server.- exit 6 (
authorization_pending) → not approved yet; wait ≥intervaland retry. - exit 0 (
authenticated: true) → done. - exit 1 (
expired_token/access_denied/invalid_grant) → the code died; start over from step 1.
- exit 6 (
- Re-running step 1 while a code is still live returns the same code
(idempotent — it never invalidates a code the human is mid-approving). Pass
--forceto discard it and mint a fresh one. - For harnesses that tolerate a slightly longer command,
--complete --wait <seconds>polls for up to N seconds (honouring the server interval) instead of a single round.
Once logged in, move to the durable API-key lane so later sessions skip the browser entirely:
skhema auth key create --name my-agent --use # creates a key AND stores it as the credentialFrom then on the agent authenticates with the stored key (or SKHEMA_API_KEY) —
no device flow, no browser.
Credential lanes
The CLI accepts two kinds of credential and resolves them in a fixed precedence order (highest first):
--api-key <sk_live_…>global flag (this invocation only)SKHEMA_API_KEYenvironment variable- A stored API key (
skhema auth key use <key>) - A stored OAuth session (
skhema auth login)
API keys (lanes 1–3) are for CI and agents — no browser, no device flow. The OAuth session (lane 4) is for interactive human use. The organization is baked into an API key, so you never pass an org id when using one.
skhema auth status reports exactly which lane is active and for which org:
skhema auth status
# Credential: SKHEMA_API_KEY environment variable
# Key: sk_live_...a1b2
# Verified: Yes
# Permission: read-write
# Organization: org_…Managing API keys
skhema auth login # interactive OAuth (needed to manage keys)
skhema auth key create --name ci --permission read # prints the key ONCE
skhema auth key create --name ci --use # …and store it as the CLI credential
skhema auth key list # masked, with usage + status
skhema auth key revoke <key-id>
skhema auth key use <sk_live_…> # store a key you already have
skhema auth key remove # forget the stored keycreate / list / revoke call the same server-side api-key-manage function
the web dashboard uses, authenticated with your OAuth session — issuance
authorization (org owner/admin, plan limits, one-time key display, security
emails) is enforced there, unchanged. Raw keys are printed only at creation and
are otherwise always masked.
Headless / CI
export SKHEMA_API_KEY=sk_live_…
skhema --json project use <project-id> # every command now just worksCommand conventions
Every command supports the global flags --json, --quiet, --verbose, and
--api-key <key>.
--json envelope
Under --json, commands emit one stable envelope on stdout:
// success
{ "ok": true, "command": "project use", "data": { "defaultProjectId": "ws_1" } }
// failure
{ "ok": false, "command": "auth key create", "error": { "code": "forbidden", "message": "…", "requestId": "req_…" } }Exit codes
Deterministic, so agents can branch on them:
| Code | Meaning |
| ---- | ------------------------------------------------------------------------------- |
| 0 | Success |
| 1 | Generic / API error |
| 2 | Usage error (bad or missing arguments) |
| 3 | Auth required (no usable credential) |
| 4 | Permission or plan denied (HTTP 403 / 402) |
| 5 | Rate limited (HTTP 429) |
| 6 | Device authorization still pending (auth login --complete — not approved yet) |
Default project
skhema project use <project-id> # stored in ~/.skhema/config.json (0600)
skhema project use --clearLater project-scoped commands read this default when --project is omitted.
Contributor commands
Approved contributors get a contribute command group for generating embeds and
shareable links, and for inspecting their account, analytics, and payouts. Every
command is gated on approved-contributor status and honours --json.
skhema contribute generate embed --element-type key_challenge \
--content "..." --contributor-id contrib_… # HTML <skhema-element> snippet
skhema contribute generate link --element-type key_challenge \
--content "..." --contributor-id contrib_… # save + share links
skhema contribute status # approval status, ID, display name
skhema contribute profile # public profile (edit at contribute.skhema.com/account)
skhema contribute analytics --time-range 30d # embeds, loads, clicks, CTR + top content
skhema contribute analytics --content-hash <hash> # per-content detail with top pages
skhema contribute payouts # payout history (20% commission, 60-day hold)Source provenance
When the content is largely unedited from someone else's published document, add the source alongside your own author attribution. You stay the author (who added it to Skhema); the source records who originated it.
| Flag | Meaning |
| ------------------------------ | ------------------------------------------------------------------------------------------ |
| --provenance-org <name> | Source organization. Activates the group — the other flags are an error without it. |
| --provenance-document <name> | Source document name |
| --provenance-url <url> | Absolute http(s) URL of the source document |
| --provenance-date <date> | YYYY, YYYY-MM, YYYY-MM-DD, or a range like 2024..2027 (both ends same granularity) |
skhema contribute generate link --element-type key_challenge \
--content "Participation in community sport is declining" \
--contributor-id contrib_… \
--provenance-org "Australian Sports Commission" \
--provenance-document "Sport Strategy" \
--provenance-url https://www.example.org/strategy.pdf \
--provenance-date 2024..2027Note --source-url is a different thing: it records the page hosting the embed,
for analytics attribution, not where the content came from.
Generated snippets carry the source as flat provenance-* attributes, so an
embedded card shows the Source line alongside your byline. A
<skhema-component> snippet has one outer tag and therefore one source, so it
carries only what is true of every element: elements from two papers by the same
organization still show Source: {organization}, with the differing document,
URL and date dropped. Elements from different organizations share nothing, and
the CLI says so rather than citing the component to one of them.
Provenance rides on the publish call, so --save-only (which never publishes)
rejects the flags rather than dropping them. The server records provenance
without ever failing a publish over it — when it declines to (a re-publish of
identical content that already carries a different source, say), the CLI prints
the reason as a warning rather than reporting a clean success.
In a --from-file batch, an element may carry its own provenance object
({ organization, documentName?, url?, documentDate? | dateRange? }), which
overrides the batch-level flags for that element:
[
{ "elementType": "key_challenge", "content": "…" },
{
"elementType": "guiding_policy",
"content": "…",
"provenance": { "organization": "Sport Australia", "documentDate": "2019" }
}
]Generated embed snippets pin the CDN to a specific @skhema/embed version
(https://unpkg.com/@skhema/embed@<version>/dist/embed.min.js), so a published
snippet renders identically forever rather than tracking latest.
Configuration & storage
| What | Where |
| ----------------------------- | -------------------------------------------------------------------- |
| OAuth session | macOS Keychain (com.skhema.cli) → ~/.skhema/credentials.json |
| Stored API key | macOS Keychain (com.skhema.cli/api-key) → ~/.skhema/api-key.json |
| Preferences (default project) | ~/.skhema/config.json |
Environment overrides for non-production environments: SKHEMA_API_URL,
SKHEMA_AUTH_URL. The underlying API client is
@skhema/sdk — use it directly
when you want Skhema in your own code rather than your terminal.
License
MIT
