zamokctl
Use when generating/managing the offline root signing key or signing & publishing Zamok product keysets from the terminal (the Swift CLI; the ZamokApp GUI is the co-equal interface). Triggers — "generate root key", "rotate root", "publish keyset", "sign keyset", "escrow root key", "zamokctl", "trusted-roots.json", Tish/Zamok license signature setup.
What this skill does
# zamokctl — Zamok offline signing CLI
`zamokctl` (Swift, target in `apps/macapp`) and the **ZamokApp GUI** are two thin interfaces over
one shared `ZamokSigning` core. They share the **same root key in the macOS Keychain** and the same
`/draft → confirm → verbatim-sign → /publish` flow. There is no bun/TS signing CLI (removed).
## Trust model (read once)
- **Root key = offline trust anchor.** Private lives ONLY locally (macOS Keychain, service
`app.bshk.zamok.root-key`, one item per `kid`); the server never holds it and cannot sign.
- **Per-product roots.** A product's keyset is verified only against that product's roots, listed
(public keys) in the committed `packages/signing-keys/trusted-roots.json`
(`{ "<slug>": [{kid,pem}] }`). Adding/rotating a root = edit that file → PR → deploy (audited;
not env, not admin-mutable).
- **Keychain is `ThisDeviceOnly`** → does NOT survive a wiped/new Mac. **Always escrow** the
exported `.age` + `.identity` to a vault (e.g. 1Password `op document create`). Escrow = recovery.
- The CLI is ad-hoc-signed/unentitled → uses the file/login keychain; **no per-use biometric prompt**
(a future hardening needs a keychain-access-group entitlement + data-protection keychain).
- JWS: `alg=EdDSA`, header `kid`, payload = the **verbatim** `/draft` `payloadJson` bytes.
`kid = "root_" + base64url(sha256(spkiDer))[:16]`.
## Getting the binary
Homebrew (`zamokctl` on PATH) — see `apps/macapp/Distribution/Homebrew/`. Dev build:
`cd apps/macapp && xcodebuild -scheme zamokctl -configuration Debug -derivedDataPath build/cli build`
→ `build/cli/Build/Products/Debug/zamokctl`.
## root-key
```bash
zamokctl root-key generate # new Ed25519 root → Keychain; prints kid + public PEM
# + reminds you to export/escrow immediately
zamokctl root-key list # kid + created date of local roots (no prompt)
zamokctl root-key export --kid <kid> --out ~/zamok-escrow
# writes <kid>.age + <kid>.identity → escrow BOTH in a vault
# (.identity decrypts .age — keep secret)
zamokctl root-key import --kid <kid> ... # restore from an age escrow or a raw private PEM
```
Generate one root **per product** for isolation (compromise/rotation stays scoped to one product).
Escrow to 1Password:
```bash
op document create ~/zamok-escrow/<kid>.age --title "Zamok root [<product>] <kid>.age" --vault Private --tags zamok-root-key,<product>
op document create ~/zamok-escrow/<kid>.identity --title "Zamok root [<product>] <kid>.identity" --vault Private --tags zamok-root-key,<product>
# restore: op document get <uuid> --out-file <kid>.age (+ .identity) → zamokctl root-key import
```
## keyset
```bash
zamokctl keyset publish --product <slug> [--kid <root>]
# 1. GET /api/product-keysets/draft?productId=… (admin auth via `zamokctl auth login`)
# 2. renders the draft FROM the signed bytes: productSlug, version, each key purpose·kid·sha256(pub)[:16]
# + a DIFF vs the currently published keyset — REVIEW added/removed/changed keys
# 3. confirm (or --yes in CI, which echoes the diff) → signs the verbatim payloadJson with the root
# 4. POST /api/product-keysets/publish
```
Signs the **exact bytes it showed you** (single fetch, no refetch) and aborts if the server's
display `payload` ≠ the signed `payloadJson` — a compromised server can't get attacker keys
root-signed without you approving an anomalous key list.
## Full rollout / rotation
1. `zamokctl root-key generate` (per product) → `export --out ~/zamok-escrow` → escrow in vault.
2. Add the root's **public** key under the product slug in `packages/signing-keys/trusted-roots.json`
→ PR + merge + deploy (server now trusts it).
3. `zamokctl auth login`, then `zamokctl keyset publish --product <slug>` per product.
4. Rebuild + ship the client embedding `trustedRootPublicKeysPEM` + `initialProductKeysetJWS` from
`GET /api/product-sdk-setup/<productId>`.
5. Verify a clean client: license activates + cloud responses verify. Server `GET /api/health` is
`degraded` until each product has a trusted root AND a keyset signed by it.
## Gotchas
- Rotating the **root** means re-shipping clients — rare, deliberate. Rotating a **per-product
signing key** = republish the keyset; clients auto-pick it up, no rebuild.
- Keep `trusted-roots.json` changes in PRs — it's the trust anchor; never make it env/DB-mutable.
- `OSStatus -50`/`-34018` from the keychain store = an outdated build; use the file-keychain store
(no data-protection keychain / access groups for the unentitled CLI).
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.