Files
Primora/apps/cli/README.md
T
Tomáš DvořákandDevin d44bbcceda
CI / Backend Tests (push) Canceled after 0s
CI / Frontend Tests (push) Canceled after 0s
CI / Build All Services (push) Canceled after 0s
CI / Docker Build (auth) (push) Canceled after 0s
CI / Docker Build (backend) (push) Canceled after 0s
CI / Docker Build (frontend) (push) Canceled after 0s
CI / Publish edge images (auth) (push) Canceled after 0s
CI / Publish edge images (backend) (push) Canceled after 0s
CI / Publish edge images (frontend) (push) Canceled after 0s
CI / Migration Up/Down (push) Canceled after 0s
CI / Compose E2E Smoke (push) Canceled after 0s
CI / Lint (push) Canceled after 0s
fix: QA hardening across desktop connect, CLI, setup, and PWA update flow (#52)
- desktop: probe-then-persist server URL — scheme-less input tries https
  then http; a failed probe no longer saves a dead URL that would trap
  the next launch in a redirect to a broken address
- frontend: close the create-collection modal on successful submit
  (backdrop stayed mounted and intercepted navigation)
- frontend: reload once on service-worker controllerchange so returning
  users don't run a stale cached shell after a deploy
- frontend: gate dashboard empty states on resource loading to stop the
  "No project selected" / "Create your workspace" flash
- cli: resolve scheduled-job refs by name or id for jobs:run, jobs:runs,
  and jobs:rm, matching bucket/org resolution
- cli: correct stale pk_live_/pk_test_ help text to the real prm_ format;
  align README command spellings and demo seed prefixes
- setup: rewrite all public URL vars (auth, vite, backend) and cookie
  domain for the entered host — https for domains, http for IPs — with
  anchored full-line substitutions so stale ports/paths can't survive
- docs: VITE_* vars never reach the frontend image build; the app
  resolves API/auth URLs same-origin at runtime, so host changes need no
  rebuild — noted HTTPS requirement for PWA/secure-context features

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-03 18:52:53 +02:00

5.9 KiB

@primora/cli

primora - command-line client for a Primora deployment, plus a local encrypted secrets vault and compose-stack operator commands.

Build

npm run build --workspace @primora/cli   # → dist/cli.js
node dist/cli.js login                   # or: npx primora login once published

Auth

  • primora login - session sign-in against your deployment.
  • primora login --api-key prm_… - or export PRIMORA_API_KEY.

Context lives in ~/.config/primora/config.json; primora use picks org + project interactively.

Commands

orgs, projects, buckets, objects (list/upload/download/rm), keys, jobs, events:send, audit:list --follow. Every command accepts --json.

primora events:send custom.deploy.done --data '{"sha":"abc123"}'

events:send publishes a custom.* event — realtime subscribers, matching webhooks, and event_pattern functions all fire.

primora documents:list --collection users \
  --filter "status.eq.active,age.gt.18" --order "age.desc"

--filter takes comma-separated field.op.value terms — ops eq neq gt gte lt lte like in is; dotted paths index into document data (meta.city.eq.Prague), id/created_at/updated_at are also filterable. --order is a field.asc|field.desc list.

Vault

A local encrypted vault for secrets the CLI holds - API keys, deployment .env values, anything you would otherwise leave in a dotfile or shell history.

primora vault init                    # create ~/.config/primora/vault.bin
primora secrets set STRIPE_KEY        # value prompted, or --value / stdin
primora secrets set OPENAI --value sk-… --url https://platform.openai.com --notes "prod"
primora secrets import .env           # import KEY=value pairs
primora secrets list                  # names + urls + timestamps, never values
primora secrets get STRIPE_KEY        # prints the value (scriptable)
primora secrets rm STRIPE_KEY --yes

Format: Argon2id (64 MiB / 3 / 4) → XChaCha20-Poly1305 over a single JSON payload; the parameter header is the AEAD's AAD, atomic tmp+rename writes. Clean-room implementation - compatible with nothing but itself.

Password source order: live session → PRIMORA_VAULT_PASSWORD → --password-file → hidden prompt. PRIMORA_VAULT overrides the file location.

Project vault (server)

--remote targets the project's server-side vault instead of the local file — AES-256-GCM at rest in Postgres, shared with the team and the web dashboard's Vault page. No vault password; it uses your primora login session or PRIMORA_API_KEY.

primora secrets set STRIPE_KEY --value sk-… --remote
primora secrets list --remote          # metadata only — names, urls, notes
primora secrets get STRIPE_KEY --remote  # explicit reveal, audit-logged
primora secrets rm STRIPE_KEY --remote --yes
primora secrets import .env --remote --overwrite

Job payloads reference secrets as secret://NAME; the scheduler resolves them at delivery, so payloads on disk never contain plaintext:

{"auth": "secret://STRIPE_KEY"}

Sessions

primora vault unlock --ttl 900 proves the password once, then writes a TTL'd session key (vault.session, 0600) so agents and scripts use the vault without ever seeing the password. primora vault lock revokes immediately; expiry does it on its own. The session file is same-user readable for its lifetime — that is the trade, bounded by the TTL.

Agent broker

primora agent wraps one process: the child gets dummy tokens and a loopback base URL; the broker attaches the real secret on the wire.

primora agent --anthropic CLAUDE_KEY --openai OAI_KEY --ttl 900 -- claude
primora agent --upstream stripe=https://api.stripe.com -- npm test

Presets: anthropic, openai, openrouter, groq, deepseek, xai, github, npm. Each flag takes a vault secret name; --upstream name=https://… adds any Bearer API (vault key <NAME>_KEY).

The child environment contains *_API_KEY=primora-brokered and a *_BASE_URL pointing at the broker — so the tool must honor a base-URL override. gh hardcodes api.github.com and cannot be brokered this way. This is a base-URL broker, not a TLS MITM; no CA trust needed.

Inject

Run one process with vault secrets in its environment - nothing on disk, nothing in argv, parent shell unchanged:

primora inject -- npm run dev                      # all vault secrets
primora inject --env-file .env -- npm start        # resolve primora:// refs

An env-file can mix literals and refs:

DATABASE_URL=postgres://localhost/app
STRIPE_KEY=primora://STRIPE_KEY

Stack

Operator surface for a compose deployment (--dir, defaults to cwd):

primora stack up                  # docker compose up -d
primora stack up --vault          # materialize .env from the vault, remove after
primora stack status | logs [svc] | down | pull
primora stack backup              # pg_dump → ./backups (storage: scripts/backup.sh)

stack up --vault refuses to overwrite an existing .env without --force (the original is restored after the run). For a full backup including the storage volume, scripts/backup.sh remains the tool.

For AI agents

Two safe paths, no secret ever in the agent's context:

  • Wrapped — launch the agent under primora agent … -- claude. It holds dummy tokens; the broker attaches real ones on the wire.
  • MCP — primora_vault_status, primora_vault_list (metadata only), primora_vault_exec (runs primora commands with a secret injected into the process env; vault/secrets/inject/agent/login and keys:create are denied, so output can never carry a credential). Requires an unlocked vault session on the host.
  • MCP project vault — primora_secrets_list (metadata only) and primora_secrets_set (write-only). No reveal, no delete: agents reference secret://NAME in job payloads without ever holding the value.