@pskills/cli
v0.4.1
Published
Sync agent skills from a privateaiskills.com workspace to your local agents (Claude Code, Cursor, etc.).
Maintainers
Readme
pskills
Sync agent skills from a PrivateAISkills workspace down to your local agent (Claude Code, Cursor, etc.). Skill content is end-to-end encrypted; the server never sees plaintext, and the CLI decrypts on your machine before writing to disk.
Skills are edited in the dashboard. The CLI is sync-only as of 0.3.0. Binary supporting files (images, PDFs, etc.) sync byte-for-byte starting in 0.4.0.
Install
npm i -g pskillsOr run without installing:
npx pskills <command>Requires Node 18 or newer. Works on macOS, Linux, and Windows.
Quick start
pskills init # interactive: pastes your API key, picks groups to sync
pskills sync # pulls every skill in your selected groups, decrypts, writes to disk
pskills watch # sync once, then poll every 30s
pskills status # report drift between local and remote (read-only)Commands
| Command | What it does |
|---|---|
| pskills init [path] | Interactive setup. Prompts for your API key and lists your workspace's groups, lets you pick which groups to sync, asks for your encryption key and a target directory, and writes the settings file (defaults to ./pskills.settings.json). |
| pskills sync | Pulls every skill in your selected groups, decrypts locally with your encryption key, and writes the bundle to the target directory. Variables defined at the org or project level in the dashboard are decrypted and interpolated locally by the CLI at sync time, after the server delivers ciphertext — manage them in the dashboard. |
| pskills watch [--interval 30] | Sync once, then re-sync every N seconds. |
| pskills status [--json] | Compare local synced skills to the remote workspace. Read-only — never writes. Default output is human-readable; --json emits a structured report you can parse in CI to decide whether to fail a build (the exit code is only about command success, not drift). |
Flags: --config <path>, --target <path>, --verbose.
Configuration
Settings file (default: ./pskills.settings.json):
{
"apiKey": "hsk_...",
"encryptionKey": "hsk-key-v1_...",
"target": ".claude/skills",
"groups": ["deploy-skills", "research-skills"]
}- apiKey — Issued from the workspace settings page. SHA-256 hashed at rest. Optionally scoped to specific groups in the web app.
- encryptionKey — Your workspace's symmetric AES-GCM key, shown once at workspace creation. Without it, ciphertext cannot be decrypted. Save it to a password manager — it cannot be recovered.
- target — Directory the CLI writes skills into.
.claude/skillsis the conventional location for Claude Code; use whatever path your agent reads from. - groups — Required, non-empty. The CLI syncs by group; pick the groups you want on this machine. Run
pskills initto list available groups.
How it works
- For each group in
settings.json, the CLI fetches/api/v1/groups/<slug>— this returns slugs and SHA-256 checksums of ciphertext. - Skills appearing in multiple groups are deduped by slug and written once.
- Variables (org-level + project-level, with project overriding org) are pulled once from
/api/v1/syncas ciphertext, decrypted locally, and held in memory for this run. - Each checksum is compared against
<target>/.pskills-state.json. If the variable set changed since the last sync, every skill is re-rendered even when its ciphertext checksum is unchanged — the on-disk interpolated output would otherwise be stale. - For changed skills, the CLI fetches the full body and decrypts with
encryptionKey(AES-GCM, IV is the first 12 bytes of the ciphertext blob). ${VAR_NAME}placeholders inSKILL.mdand supporting text files are substituted with the matching variable value. Escape with a backslash (\${VAR}) to keep the literal text. Undefined references are left untouched and surfaced as a single warning at the end of the run. Files flagged binary by the server (isBinary: true) skip interpolation and are written byte-for-byte after AES-GCM decryption.- The CLI writes each skill into
<target>/<slug>/SKILL.mdplus any supporting files at their relative paths, preserving the executable bit. - Skills that disappeared from the manifest are removed locally.
FAQ
I lost my encryption key — can I recover my skills? No. The server only ever stores ciphertext; the key is generated client-side at workspace creation and shown once. If lost, the encrypted skills become unreadable and you'll need to recreate the workspace. Save it to a password manager.
pskills sync says nothing changed, but I just edited a skill in the web app.
Make sure the save finished — the editor shows the save state at the top right. The CLI sees the new checksum only after the server has the new ciphertext.
How do variables work?
Define them under your project's Variables card (or your org's settings for cross-project values — project values override org values with the same name). They're stored encrypted with the same workspace key as your skills, fetched at sync time, decrypted locally, and substituted into any ${VAR_NAME} placeholders in SKILL.md or supporting files. Only the CLI ever sees the plaintext.
The CLI warned that some variables are undefined — what now?
The warning lists each missing name and where it was referenced (e.g. deploy-checks:scripts/run.sh). The placeholder is left as-is in the output. Either define the variable in the dashboard or remove the reference from the skill. Use a leading backslash (\${VAR}) when you actually want the literal text.
Can a skill ship binary files (images, PDFs, model weights)? Yes, since 0.4.0. Drop them into the skill folder in the dashboard, or include them in a GitHub-imported skill — the server flags them on ingest, encrypts them with the same workspace key as the rest of the bundle, and the CLI writes the decrypted bytes verbatim to disk. They skip variable interpolation. There's a 1 MB per-file ceiling enforced server-side.
I get 401 Unauthorized.
The API key is wrong, was revoked, or doesn't match the endpoint. Re-run pskills init and paste a fresh key.
I get group not found.
The slug in settings.json is mistyped, or the group was deleted/renamed. Run pskills init again to refresh the group list.
Same skills on multiple machines?
Install the CLI on each machine, run pskills init, paste the same API key + encryption key, and pick the same target. The CLI is stateless — same inputs produce the same output.
Different agents (Claude Code, Cursor, Cline)?
Each agent reads from a different directory. Run pskills init in each project root and set target accordingly (.claude/skills, .cursor/skills, …). One settings.json per project.
How do I rotate the API key?
Revoke the current key in the web app, generate a new one, and run pskills init again. Confirm overwrite when prompted.
How do I rotate the encryption key? You can't rotate in place — the key is the workspace's identity. To change it, create a new workspace, re-encrypt your skills against the new key, and re-init the CLI.
Does this work on Windows? Yes. The CLI is plain Node — runs in PowerShell, cmd, and WSL. Path separators are normalized.
Where does pskills sync store its state?
In <target>/.pskills-state.json. Safe to delete; the next sync rewrites everything from the server.
Can I sync into a directory that already has files?
Yes, but the CLI manages the slug subdirectories under target. Files outside those subdirectories are left alone; files inside a slug directory may be overwritten or removed to match the server.
CI integration
pskills status --json is the read-only equivalent of "would pskills sync change anything?". It exits 0 on success regardless of whether drift was found — the JSON body tells you what's out of sync. CI scripts decide what counts as failure.
pskills status --json > drift.json
node -e 'process.exit(JSON.parse(require("fs").readFileSync("drift.json")).inSync ? 0 : 1)'The JSON shape:
{
"inSync": false,
"target": "/repo/.claude/skills",
"summary": { "added": 1, "updated": 1, "removed": 0, "unchanged": 5 },
"skills": [
{ "slug": "new-skill", "status": "added", "remoteVersion": "1" },
{ "slug": "rollout-checks", "status": "updated", "localVersion": "2", "remoteVersion": "3" },
{ "slug": "stable", "status": "unchanged", "localVersion": "1", "remoteVersion": "1" }
]
}Entries are sorted by slug. localVersion is omitted on added; remoteVersion is omitted on removed. localVersion is also omitted for state files written before this version of the CLI (the field is optional).
License
MIT
