npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

paiskills

v0.6.3

Published

Sync agent skills from a privateaiskills.com workspace to your local agents (Claude Code, Cursor, etc.).

Downloads

45

Readme

paiskills

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 paiskills

Or run without installing:

npx paiskills <command>

Requires Node 18 or newer. Works on macOS, Linux, and Windows.

Quick start

paiskills init    # interactive: pastes your API key, picks groups to sync
paiskills sync    # pulls every skill in your selected groups, decrypts, writes to disk
paiskills watch   # sync once, then poll every 30s
paiskills status  # report drift between local and remote (read-only)

Summoning prompts

Prompts are reusable AI templates with {{VAR}} placeholders, authored in the dashboard and rendered on demand from the CLI. One source of truth, parameterised, encrypted, pipeable. See docs/prompts.md for the full guide (variables, @file expansion, defaults, recipes).

There are two ways to pick a prompt. They both end the same way: each {{VAR}} you didn't supply is asked for one by one, then the rendered prompt is written to stdout.

1. Fully interactive — you don't remember anything.

npx paiskills prompt

The CLI shows a type-to-filter picker over every prompt in the project, then asks for each variable. Type a few letters of either the slug or the human name and matches narrow live. The right mode when you have 50+ prompts and can't recall the slug.

2. By slug — slugs are unique within a project.

npx paiskills prompt standup

Skips the picker. Variables without --var and without a default still get asked interactively (in a TTY) or fail with exit 2 (in CI). This is the everyday case: short, predictable, scriptable.

Pass values upfront with --var:

npx paiskills prompt standup --var TEAM=platform

Pass multi-line content (anything with newlines, quotes, or $) via b64: so the shell doesn't fight you:

npx paiskills prompt standup --var NOTES=b64:$(cat notes.txt | base64)

Output goes to stdout. Pipe it into your AI tool, save it to a file, or copy it to the clipboard.

Variable defaults. Authors can write {{NAME:fallback}} in the dashboard. The CLI uses the default whenever --var NAME=… isn't given, so a prompt with sensible defaults can be invoked with no flags. An explicit --var NAME= (empty value) still counts as a resolved override and beats the default.

Attached files. A @filename reference inside the prompt body is replaced by the file's contents wrapped in a fenced code block tagged with the path:

```text path=style.md
use commas
```

This matches the dashboard preview byte-for-byte. Binary attachments (images, PDFs) are rejected if referenced from the body. Keep them as ambient files synced via paiskills sync.

Commands

| Command | What it does | |---|---| | paiskills 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 ./paiskills.settings.json). | | paiskills 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. Lists skills that have disappeared from the manifest and asks before deleting them locally; pass --yes to skip the prompt. | | paiskills prompt [<slug>] | Render an AI prompt with variable substitution and @file expansion, write to stdout. With no positional, picks interactively. Supply variables with repeated --var NAME=value (use b64: prefix for multi-line content). Authors can declare defaults via {{NAME:fallback}}. Full guide: docs/prompts.md. | | paiskills watch [--interval 30] | Sync once, then re-sync every N seconds. | | paiskills 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>, --yes, --verbose, --var NAME=value (repeatable, prompt only).

Configuration

Settings file (default: ./paiskills.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/skills is 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 paiskills init to list available groups.

Multiple sources (optional)

Instead of the flat apiKey / encryptionKey / groups fields, you can list multiple sources to sync from in one shot:

{
  "target": ".claude/skills",
  "sources": [
    {
      "kind": "project",
      "apiKey": "hsk_...",
      "encryptionKey": "hsk-key-v1_...",
      "groups": ["my-group"]
    },
    {
      "kind": "me",
      "apiKey": "hsk_..."
    }
  ]
}

Each source produces a manifest. When two sources expose a skill with the same slug, personal > organization > project wins (a warning is printed). The flat config (apiKey + encryptionKey + groups at the top level) is still supported for back-compat — it's equivalent to a single project source.

The encryptionKey is required for project sources only; me sources fetch cleartext.

How it works

  1. For each group in settings.json, the CLI fetches /api/v1/groups/<slug> — this returns slugs and SHA-256 checksums of ciphertext.
  2. Skills appearing in multiple groups are deduped by slug and written once.
  3. Variables (org-level + project-level, with project overriding org) are pulled once from /api/v1/sync as ciphertext, decrypted locally, and held in memory for this run.
  4. Each checksum is compared against <target>/.paiskills-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.
  5. 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).
  6. ${VAR_NAME} placeholders in SKILL.md and 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.
  7. The CLI writes each skill into <target>/<slug>/SKILL.md plus any supporting files at their relative paths, preserving the executable bit.
  8. Skills that disappeared from the manifest are listed for confirmation before being removed locally — pass --yes to skip the prompt (CI, scripts, paiskills watch).

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.

paiskills 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 paiskills 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 paiskills init again to refresh the group list.

Same skills on multiple machines? Install the CLI on each machine, run paiskills 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 paiskills 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 paiskills 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 paiskills sync store its state? In <target>/.paiskills-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

paiskills status --json is the read-only equivalent of "would paiskills 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.

paiskills 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