#@unicas/admin-cli
UniCAS control-plane management CLI. Logs in through the control-plane BFF:
the browser opens the BFF's /admin/auth/cli/authorize, the BFF runs the
Google OIDC flow (client secret held server-side) and verifies current
Platform Access, then redirects the browser back to the CLI's loopback with a one-time code
that the CLI exchanges (PKCE) for a session cookie + CSRF token, persisted
locally. The CLI never talks to Google and needs no client id or secret.
Commands call the typed @unicas/admin-client over the /admin HTTP API;
unicas mcp exposes the same current App/Account tool contract as the MCP ingress hosted by
@unicas/service-cloudflare as a stdio MCP server backed by that client (for
clients whose MCP support cannot do OAuth, for example DeepSeek Harness).
https://console.unicas.work/admin <- /admin control-plane API (BFF session)
^
| session cookie + CSRF (via @unicas/admin-client)
unicas CLI <- /admin/auth/cli/authorize (BFF does Google OIDC) -> cli/exchange
| persists ~/.unicas/session.json
|
+-- plain commands: unicas account / unicas apps list ...
`-- stdio MCP server: unicas mcp (DSH: command "unicas", args ["mcp"])
#Requirements
- Node.js >= 24
- pnpm (workspace package; build with
tsc)
#Build
pnpm install
pnpm --filter @unicas/admin-cli build
This produces dist/cli.js (the unicas bin target).
#Log in
pnpm --filter @unicas/admin-cli unicas login --provider google
login authorizes through the control-plane BFF:
- Starts a local
127.0.0.1callback server and opens the browser at${UNICAS_ADMIN_URL}/admin/auth/cli/authorize(fixed public client idunicas-cli, S256 PKCE, loopback redirect). - The BFF redirects to the selected Google, personal Microsoft, or GitHub provider (using its confidential client + secret server-side), the operator signs in and consents, and the BFF verifies current Account admission before issuing a session.
- The BFF redirects the browser back to the CLI's loopback with a one-time
code; the CLI validates
state, then POSTs{ code, codeVerifier }to/admin/auth/cli/exchangeand receives the session cookie + CSRF token. - Persists the session to
~/.unicas/session.json(created0600, atomic writes).
#Commands
| Command | MCP tool |
|---|---|
unicas account |
get_current_account |
unicas apps list [--limit N] [--cursor C] |
list_apps |
unicas apps get <appId> |
get_app |
unicas apps create <displayName> [--idempotency-key K] |
create_app |
unicas apps update <appId> [displayName] [--description D] [--etag E] |
update_app |
unicas app-members list <appId> [--limit N] [--cursor C] |
list_app_members |
unicas app-members invite <appId> <email> [--idempotency-key K] |
invite_app_member |
unicas app-members remove <appId> <accountId> --confirm-account-id <accountId> |
remove_app_member |
unicas app-oauth-issuer get <appId> |
get_app_oauth_issuer |
unicas app-oauth-issuer inspect <appId> <issuer> |
inspect_app_oauth_issuer |
unicas app-oauth-issuer activate <appId> <inspectionId> --activation-proof <jws> [--etag E] |
activate_app_oauth_issuer |
unicas app-audit control <appId> [--actor-account-id ID] [--target-account-id ID] [--limit N] [--cursor C] [--after ID] |
list_app_control_audit_events |
unicas app-audit root-refs <appId> [--space-id S] [--limit N] [--cursor C] |
list_app_root_refs |
unicas app-audit root-events <appId> [--space-id S] [--after N] [--limit N] |
list_app_root_events |
unicas platform-access list [--query Q] [--effective-access active|blocked|no_access] [--authority platform.admin|apps.create|none] [--limit N] [--cursor C] |
list_platform_accounts |
unicas platform-access get <accountId> |
get_platform_account |
unicas platform-access grant|revoke <accountId> <authority> --confirm-account-id <accountId> |
grant_platform_authority / revoke_platform_authority |
unicas platform-access block|restore <accountId> --confirm-account-id <accountId> |
block_platform_account / restore_platform_account |
unicas platform-invitations list [--query Q] [--status S] [--limit N] [--cursor C] |
list_platform_invitations |
unicas platform-invitations create <email> --authority A [--authority A] [--idempotency-key K] |
create_platform_invitation |
unicas platform-invitations revoke <invitationId> --etag E --confirm-invitation-id <invitationId> |
revoke_platform_invitation |
unicas platform-audit [--action A] [--actor-account-id ID] [--target-account-id ID] [--created-after MS] [--limit N] [--cursor C] |
list_platform_audit_events |
unicas logout |
RFC 7009 revocation + clears the session |
unicas status |
Local session summary (no network) |
unicas mcp |
Run as a stdio MCP server |
MCP additionally exposes App invitation acceptance. Retired commands (principal,
whoami, stacks, members, oauth-issuer, ref-domains, and audit) are
not supported and have no compatibility aliases. Use the current commands above.
Old credentials without Account binding require a new login instead of migration.
unicas account and the stdio Account tool use the client's
getCurrentAdministrator() operation. Its /admin/me response contains only
Account data, the masked current login method, and Account memberships.
Plain commands print the tool's structuredContent as JSON on stdout;
diagnostics go to stderr.
#Guardrails
- ETags. App update and issuer activation need the current ETag. When
--etagis omitted the CLI reads it first (get_app/get_app_oauth_issuer). - Platform authority. Every platform command requires current
platform.admin;apps createseparately requires currentapps.create. A valid session or MCP scope alone is insufficient. - Confirmations. Destructive operations require their
--confirm-*flag to exactly match the target. Without the flag and a TTY, the CLI prompts; without the flag and no TTY (scripts), the command fails. - Idempotency.
create_appandinvite_app_memberauto-generate a stableunicas-cli:<uuid>idempotency key when--idempotency-keyis omitted. - Never secrets on the wire to the CLI.
activate_app_oauth_issueraccepts only a compact-JWS activation proof signed off-CLI with a private key the discovered issuer advertises; private key material is never a CLI input, and no JWK upload path exists.
#stdio MCP server (unicas mcp)
Spawns a stdio MCP server that advertises the exact same tool contract as the
remote control plane and forwards each tools/call over the authenticated
Streamable HTTP connection. Only MCP protocol frames go to stdout.
{
"mcpServers": {
"unicas-control-plane": {
"command": "unicas",
"args": ["mcp"]
}
}
}
To expose the unicas command on PATH from this checkout:
pnpm --filter @unicas/admin-cli build
# pnpm 10+ removed `pnpm link --global`; install the local package globally instead:
pnpm install --global ./packages/admin-cli
# or point the MCP client directly at the built script:
# node <checkout>\packages\admin-cli\dist\cli.js mcp
Alternatively run any command in-process:
pnpm --filter @unicas/admin-cli unicas apps list.
Windows note: pnpm's global bin is a
.CMDshim. A Node-based MCP client spawningunicas mcpmust either useshell: true, point at the shim path (%LOCALAPPDATA%\pnpm\bin\unicas.CMD), or usecommand: "node"withargs: ["<checkout>/packages/admin-cli/dist/cli.js", "mcp"]— a plainspawn("unicas", …)fails withENOENT/EINVALbecause Node does not resolve.CMDfiles.
#Environment
| Variable | Default | Meaning |
|---|---|---|
UNICAS_ADMIN_URL |
https://console.unicas.work |
/admin API origin |
UNICAS_CONFIG_DIR |
~/.unicas |
Directory holding session.json |
#Network / proxy
The CLI itself never calls Google: only the control-plane BFF talks to
accounts.google.com. unicas login only reaches the BFF origin
(UNICAS_ADMIN_URL), so no proxy configuration is needed on the CLI side
beyond whatever your network requires to reach the control plane.
#Security notes
- The session cookie is stored locally with
0600permissions; the directory is created on demand. - No long-lived bearer tokens are stored: the CLI holds only the BFF session cookie (server-side session, TTL enforced by the BFF) plus the CSRF token.
unicas logoutends the BFF session server-side (POST /admin/auth/logout) and always clears the local session.- The one-time authorization code is exchanged once and never persisted; the session fails closed with a re-login prompt when the BFF rejects the cookie.
#Tests
pnpm --filter @unicas/admin-cli test
pnpm --filter @unicas/admin-cli typecheck
Tests run against an in-memory fake of the /admin BFF API — no network, no
real OAuth. A live unicas login + unicas principal against production is a
manual verification step because it requires a real browser Google sign-in.