SYS.CLI / GUIDE

Command line

Your Skills and Constitution are files. The CLI puts them where the agent in your editor already looks — .claude/skills/ — so nothing has to be pasted between a browser and a terminal.

$npm install -g projectskills

This page describes pskl 0.14.3. pskl --version prints yours; if it is older, run the install command again.

01

Install

One package, two binaries. `pskl` is the short one; `projectskills` does the same thing.

  • FREE
    $npm install -g projectskills

    Install the CLI globally.

    Needs Node 22 or newer.

02

Sign in

The browser approves the terminal. No password is ever typed into a shell.

You do not need to create a token by hand. The token page exists for machines that cannot open a browser — CI, a container, a box you only reach over SSH.

  • FREE
    $pskl login

    Connect this terminal to your account.

    Prints an eight-character code and opens the browser. Check the code on screen matches the one in your terminal before approving — that comparison is what stops somebody talking you into approving their login.

    --token <psk_…>
    use a token instead, for CI or a headless machine
  • FREE
    $pskl whoami

    Who this terminal is signed in as, and on which plan.

  • FREE
    $pskl logout

    Remove the credential from this machine.

    Local only, deliberately: a token cannot revoke tokens, so a stolen one cannot lock you out of the page you would use to revoke it. Revoke it for good at /account/tokens.

03

Create a project

From the folder you are working in. Your code does not need to be on a public git host, and no GitHub App has to be installed.

Commit `.projectskills/project.json`. A project id is not a secret — the API answers "not found" to anyone who is not a member of the project.

  • FREE
    $pskl init

    Create a project for this directory and link it.

    Derives a name from the folder and asks before using it. It does not analyse — creating is free and instant, an analysis spends a slice of your daily allowance, so a mistyped name should not cost one. Add --analyze to do both.

    --name <name>
    name it yourself
    --description <text>
    a short description of the project
    --analyze
    start an analysis straight away
    -y, --yes
    accept the derived name without asking
  • FREE
    $pskl link

    Bind this directory to a project that already exists.

    With no argument it lists your projects and asks. Or name one: `pskl link my-app`.

  • FREE
    $pskl unlink

    Remove the link from this directory.

  • FREE
    $pskl status

    Everything the platform knows about this project, on one screen.

    Analysis state and steps, which model ran and whether it answered, the newest snapshot, how many Skills exist, and whether the graph is built.

04

Pull your Skills

The point of the whole tool: get the artefacts onto disk, where the agent in your editor reads them.

Writes to `.claude/skills/<name>/SKILL.md`. The directory is the Skill’s name: Claude Code names the command after it, and Codex and Cursor follow the Agent Skills standard, which refuses a file whose front matter names a different directory. So the layout is not a preference.

  • FREE
    $pskl pull

    Write your Skills and Constitution into this repository.

    Never overwrites a generated file you have edited by hand — it names the file, leaves your version alone, and exits non-zero. Skills that have not been generated yet are skipped rather than reported as failures. Every Skill and the Constitution are audited by the server as they are sent: one with an unresolved high-severity finding — an instruction an agent should not follow — is not written into your agent’s folder, and the command names the rule and line and exits non-zero. Review it on the Skills or Constitution page, where a finding you judge safe stops holding it back.

    --out <dir>
    somewhere other than .claude/skills
    --force
    overwrite files you edited locally
    --prune
    remove artefacts the server no longer lists
    --skills-only
    skip the Constitution
    --constitution-only
    skip the Skills
    --allow-findings
    write a Skill or the Constitution the audit holds back — only once you have read it
05

Send it to somebody

A read-only link to one snapshot’s report, readable with no account — for a client, a reviewer, or somebody deciding whether to work with you.

The link is the credential: anybody holding it can read the report, so it is shown once, stored only as a hash, and expires. Expired, revoked and never-existed all answer with the same sentence — telling somebody a link *was* valid tells them they guessed a real one. The page carries no file contents, no excerpts and no identifiers, and is excluded from search indexes in three places.

  • FREE
    $pskl share create

    Mint a link to the latest snapshot’s report.

    The report leads with what was checked, before any number. Everywhere else a reader can click through to the detectors; here they cannot, and a report saying “3 findings” to somebody who cannot see what ran misleads by construction.

    --days <n>
    how long it lives (1-30, default 7)
    --label <text>
    a note for your own list
    --snapshot <ordinal>
    a snapshot other than the latest
  • FREE
    $pskl share list

    Every link, whether it is live, and how often it was opened.

    The view count is not analytics — it is how you notice a link being read long after you meant it to be.

  • FREE
    $pskl share revoke <id>

    Stop a link working.

    The row survives revocation, because the record that the link existed and was opened is the thing you would want afterwards.

06

Tell another service

A signed POST when an analysis finishes, fails, or turns up something critical.

The destination must be https and reachable from the internet. A private address, a loopback address, or a name that does not resolve publicly is refused with the reason — this service would otherwise be making requests into its own network on your behalf, which is what a link-local address like 169.254.169.254 exists to exploit. The address is checked again, after DNS resolution, before every delivery: a name that resolves publicly today can resolve privately tomorrow.

  • FREE
    $pskl webhooks add <url>

    Register a destination and see the signing secret, once.

    Each delivery carries `x-projectskills-signature: v1=<hmac>`, computed over `timestamp.body` — the timestamp is inside the signature rather than beside it, so a captured request cannot be replayed with a rewritten header. Redirects are not followed: the address that was checked would not be the address fetched.

    --events <list>
    analysis.succeeded, analysis.failed, finding.critical, skill.regenerated
  • FREE
    $pskl webhooks list

    Every destination, and what happened to its last delivery.

    A webhook that stopped working is invisible from the outside. After enough consecutive failures a destination is disabled rather than retried for ever, and the row says so.

  • FREE
    $pskl webhooks deliveries <id>

    What happened to recent deliveries, and why.

    `REFUSED` is not `FAILED`. A failure is your endpoint saying no; a refusal is this service declining to make the request because the address resolved somewhere it will not reach. They look identical in a log and mean opposite things.

    --limit <n>
    how many to show (1-100)
  • FREE
    $pskl webhooks rm <id>

    Stop delivering to a destination.

    Removes the subscription and its delivery history.

07

A bill of materials

CycloneDX 1.6, for whatever consumes one — Dependency-Track, a compliance review, a procurement questionnaire.

It is built from the dependency manifests in the repository, not from an installed tree, so it says `aggregate: incomplete` in the field the standard provides. An SBOM that leaves that out is asserting completeness by silence. It states no licences either: this analysis reads the manifests that name packages and never the packages themselves, and a guessed licence is the one field a compliance reader trusts most.

  • FREE
    $pskl sbom

    Every package the manifests declare, with package URLs.

    Versions come from a lock file where one is readable and are left absent otherwise — never read off a range, because `^4.17.21` permits 4.17.99 and a version in an SBOM is what gets matched against advisories. A Go module keeps its whole path, because `pkg:golang/gin` resolves to nothing.

    --snapshot <ordinal>
    a snapshot other than the latest
    --out <file>
    write to a file instead of stdout
08

Understand a codebase you did not write

A guided read, in the order somebody who knows the code would walk you through it — not a chat, and not a search.

Every section says what it could not establish, next to the claim it qualifies rather than in a footnote. A document that explains a codebase to somebody who cannot check it is the easiest place in this product to be quietly wrong, so a section with no material says so instead of hedging.

  • FREE
    $pskl explain

    What this project is, what it is built with, and where to start reading.

    Composed from the analysis that already exists — the detected stack, the structural scan, the written summary — in a fixed order. Plain language on purpose: no pillar, no verdict, no provenance. Those words are precise and they are exactly the ones that lose somebody on their first day.

    --snapshot <ordinal>
    a snapshot other than the latest
    --markdown
    the document alone, for piping to a file
09

Let the agent ask

Pulling gives an agent the Skills. This connects it to the analysis over MCP — from Claude Code, Codex, Cursor, VS Code, Google Antigravity, PyCharm and the other JetBrains IDEs, and every tool listed below, on macOS, Windows and Linux — so it can ask what the project is, its rules, what depends on a file, and get a short, dated answer — measured on this product’s own repository at about 6× fewer tokens than the output of a text search for the same question, and about 94× fewer than reading the files it names.

No file this writes carries a credential. In a repository, each tool takes the token its own way — VS Code asks for it once and keeps it in its secret storage; the others read `PROJECTSKILLS_TOKEN` from your environment — which is what makes the file safe to commit. On this machine (`--scope user`), the tool starts `pskl mcp serve`, which uses your `pskl login`: nothing to set, and it works for an editor opened from the Dock or the Start menu, which never sees a shell’s variables. A tool that can take the token neither way — Antigravity, JetBrains, Zed and others — is connected on this machine only, because the alternative is a token written into a file.

  • FREE
    $pskl mcp install

    Connect coding tools to this project through MCP — Claude Code by default; name others with `--for`.

    Writes each tool’s own file in its own format and changes only the ProjectSkills entry: other servers, other settings and the comments in JSON-with-comments files survive, and the previous version of a file it changes is kept as `<file>.projectskills.bak`. A config it cannot parse is left alone and reported, and a file that would contain a token is never written. A machine-scope entry names Node and this CLI by absolute path, so the tool starts it without a shell — on Windows too, where `pskl` is a `.cmd` a tool cannot start directly. The connection is read-only: no tool changes anything. Every answer opens with the snapshot it describes and warns when your commit is not the analysed one; text quoted from the repository is marked as data, never instructions. Run `pskl mcp doctor` next: it checks the path each tool will take.

    --for <tools>
    one or more tools, comma-separated (the table below), or all — every tool with a repository config — or detected — every tool installed on this machine
    --scope <scope>
    project: a committable file in this repository (the default where the tool supports it) · user: this machine, every project, through your sign-in
    --print
    write nothing; print each entry and where it goes
  • FREE
    $pskl mcp serve

    The local MCP server a tool starts — you do not run it yourself.

    Relays each message to this server over HTTPS (plain HTTP to localhost only), never follows a redirect, and takes the credential from `PROJECTSKILLS_TOKEN` or your sign-in — never from a flag or a config file. Both MCP protocol generations pass through it. Its standard output is the protocol itself; one line on standard error names the server and where the credential came from, never the credential.

  • FREE
    $pskl mcp doctor

    Check that an agent started from this shell can reach the analysis.

    Tests the path each agent takes, not the CLI’s own login: the token (or, when every tool uses the relay, your sign-in), that the server accepts it, answers and lists its tools, and that the linked project is reachable — then every coding tool configured here, at either scope: each points at this server, uses its own tool’s variable syntax and holds no token, and each relay’s paths still exist and it answers. A shared `.mcp.json` that another installed tool cannot expand is flagged. Each problem prints its fix in your shell — PowerShell, cmd, bash, zsh or fish — and the token is never printed. Exit 0 when nothing failed, so it can gate a script; `--json` prints the checks as data.

Every tool it connects

  • Claude Codepskl mcp install --for claude

    In the repository: `.mcp.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: prints the `claude mcp add --scope user` command to run

  • Codexpskl mcp install --for codex

    In the repository: `.codex/config.toml` — reads `PROJECTSKILLS_TOKEN`

    On this machine: ~/.codex/config.toml — starts `pskl mcp serve`, which uses your sign-in

  • Cursorpskl mcp install --for cursor

    In the repository: `.cursor/mcp.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: ~/.cursor/mcp.json — starts `pskl mcp serve`, which uses your sign-in

  • VS Codepskl mcp install --for vscode

    In the repository: `.vscode/mcp.json` — asks for the token once and keeps it in its secret storage

    On this machine: VS Code user mcp.json — starts `pskl mcp serve`, which uses your sign-in

  • Google Antigravitypskl mcp install --for antigravity

    In the repository: not offered: Antigravity does not expand environment variables, so a repository config would have to contain the token.

    On this machine: ~/.gemini/config/mcp_config.json — starts `pskl mcp serve`, which uses your sign-in

  • JetBrains IDEs (PyCharm, IntelliJ IDEA …)pskl mcp install --for jetbrains

    In the repository: not offered: JetBrains AI Assistant is configured in the IDE’s settings, not in a repository file.

    On this machine: prints what to paste — Settings | Tools | AI Assistant | Model Context Protocol (MCP) → Add → As JSON

  • Junie (JetBrains)pskl mcp install --for junie

    In the repository: not offered: Junie does not document expanding environment variables in a repository config.

    On this machine: ~/.junie/mcp/mcp.json — starts `pskl mcp serve`, which uses your sign-in

  • Gemini CLIpskl mcp install --for gemini

    In the repository: `.gemini/settings.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: ~/.gemini/settings.json — starts `pskl mcp serve`, which uses your sign-in

  • GitHub Copilot CLIpskl mcp install --for copilot

    In the repository: not offered: Copilot CLI no longer expands ${VAR} in its config (issue #1403), so the token would have to be written in.

    On this machine: ~/.copilot/mcp-config.json — starts `pskl mcp serve`, which uses your sign-in

  • Devin Desktop (formerly Windsurf)pskl mcp install --for devin

    In the repository: not offered: Devin Desktop has no repository-level MCP config.

    On this machine: mcp_config.json (Devin, or Windsurf’s for older installs) — starts `pskl mcp serve`, which uses your sign-in

  • Kiropskl mcp install --for kiro

    In the repository: `.kiro/settings/mcp.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: ~/.kiro/settings/mcp.json — starts `pskl mcp serve`, which uses your sign-in

  • Roo Codepskl mcp install --for roo

    In the repository: `.roo/mcp.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: Roo’s mcp_settings.json in VS Code — starts `pskl mcp serve`, which uses your sign-in

  • Clinepskl mcp install --for cline

    In the repository: not offered: Cline writes a resolved variable back into its settings when a server is toggled (issue #9065); only the relay keeps the token out.

    On this machine: Cline’s cline_mcp_settings.json in VS Code — starts `pskl mcp serve`, which uses your sign-in

  • Zedpskl mcp install --for zed

    In the repository: not offered: Zed does not document expanding environment variables in headers.

    On this machine: Zed settings.json (context_servers) — starts `pskl mcp serve`, which uses your sign-in

  • Claude Desktoppskl mcp install --for claude-desktop

    In the repository: not offered: Claude Desktop has no repository config.

    On this machine: claude_desktop_config.json — starts `pskl mcp serve`, which uses your sign-in

  • Visual Studiopskl mcp install --for visual-studio

    In the repository: not offered: Visual Studio reads .vscode/mcp.json and .cursor/mcp.json in the repository; configure those instead.

    On this machine: %USERPROFILE%\.mcp.json — starts `pskl mcp serve`, which uses your sign-in

  • OpenCodepskl mcp install --for opencode

    In the repository: `opencode.json` — reads `PROJECTSKILLS_TOKEN`

    On this machine: ~/.config/opencode/opencode.json — starts `pskl mcp serve`, which uses your sign-in

  • Continuepskl mcp install --for continue

    In the repository: not offered: Continue does not document headers for remote servers.

    On this machine: prints what to paste — .continue/mcpServers/projectskills.yaml

What the agent can ask

list_projects
The projects this account has, with each one’s latest analysis.
project_overview
What the project is, what it is built with, and where to start reading.
get_constitution
The rules any AI must follow in this project.
list_skills
Which Skills this project has, and when to use each.
get_skill
One Skill’s full text, exactly as pskl pull writes it.
get_findings
What the deterministic detectors found, and which did not run.
graph_find
The exact node key for a file, symbol or route name. Premium.
graph_impact
What depends on a file or symbol — what breaks if it changes. Premium.
graph_ask
Which part of the code a plain-language question touches. Premium.
graph_path
How two parts of the code are connected. Premium.
10

Choose the model

Which AI model reads your projects, who pays for the run, and what each one costs.

Provider keys are added in the browser, at /account/ai-providers, and never here — these routes refuse a CLI token on purpose, so a token that leaked off a laptop cannot point your AI spend at somebody else’s key. The terminal reads the resulting list and chooses from it, which is everything `pskl models` needs. A change applies to your next analysis; nothing already produced changes, because every Skill version records the model that made it.

  • FREE
    $pskl models

    What is in use now, and everything else on offer.

    Two sources in one list: models your own key can reach, which your provider bills you for, and the platform’s own, which come out of the free allowance. Shows the rate per million tokens, which rows your plan allows, and which the server holds no credentials for — three different reasons a model cannot be picked, and only some of them are yours to fix.

  • PREMIUMmodels: all
    $pskl models --select

    Choose from the list, interactively.

    The entitlement applies to the platform’s models only. Anything reached by a key you connected is selectable on any plan — your credential is not ours to ration — so a Free account with a key connected can choose freely here.

    --set <provider/model>
    pick one directly, without the prompt
    --clear
    go back to whatever the server is set to
11

Manage the Skills

Read them, rewrite them, ask for one the analysis did not think to write, and delete the ones you asked for.

Only Skills you requested can be deleted. A generated Skill exists because the project justified it — deleting a finding is not something the product lets you do, and `skills rm` says so before it tries rather than letting you discover it through an error. Both `regenerate` and `rm` ask before acting, and refuse rather than assume when there is no terminal to ask on; `-y` is how a script says it meant it.

  • FREE
    $pskl skills list

    Every Skill, separated into generated and requested.

    Where the real slugs are, which the other four commands need. A card at version 0 is one that exists and has never been written into — shown as “not generated”, never as a quality of zero.

  • FREE
    $pskl skills freshness

    Whether each Skill still describes the code it was written from.

    An installed Skill rots silently: the file keeps loading into your agent after the code moves, nothing errors, and the agent answers confidently about a shape the project no longer has. This names the files each Skill draws on that have changed since — and says “cannot be checked” rather than “unchanged” when part of the history was never compared, because those are different statements.

  • FREE
    $pskl skills show <slug>

    One Skill in full, with its review and the model that wrote it.

    The review and the audit come above the document, because they are the reason to trust or distrust what follows. `--markdown` prints the document alone so it can be redirected to a file. A Skill the audit holds back is not printed in any form until a person has judged its findings on the Skills page.

    --markdown
    the document alone, for piping
    --allow-findings
    print a Skill the audit holds back — only once you have read it
  • PREMIUMcustomSkills
    $pskl skills new "<subject>"

    Ask for a Skill the analysis did not write.

    The one command here that needs Premium, and the entitlement says why: regenerating re-runs a role the analysis already justified, but this is a prompt you write, and its cost is not bounded by anything the project told us. Your plan is checked before you are asked to describe anything — being asked to compose a description and only then refused is the wrong order.

    --detail <text>
    what it should do here; asked for if omitted
  • FREE
    $pskl skills regenerate [slug]

    Rewrite a Skill against the current analysis.

    Free, because it re-runs a role the analysis already justified. Omitting the slug regenerates every generated Skill — a different scale of thing, and the only difference in what you type is a missing argument, so it asks first.

    --yes
    do not ask before regenerating everything
  • FREE
    $pskl skills rm <slug>

    Delete a Skill you requested.

    The versions and reviews go with it and nothing on the server brings the prompt back, so it asks first. A slug that is not shaped like a slug is refused before any request is made — it would otherwise become a URL path segment.

    --yes
    do not ask first
12

Ask what breaks

The questions the graph exists to answer: what depends on this, how are these two connected, and what changed since last time.

Direction is the whole of `impact`. `in` — the default — is what depends on this: what breaks. `--out` is what this depends on: what you must understand before changing it. They are not interchangeable, and answering one with the other gives you a real list of real files that confidently answers the opposite question.

  • PREMIUMgraphQueries
    $pskl impact <node>

    What breaks if this changes.

    Each result is printed at the *call site* — the line that calls the thing you are changing — not at the callee’s own declaration, which looks just as plausible and is the wrong place to open. A name matching several nodes is never resolved by guessing: it asks, or it stops and lists the candidates.

    --out
    invert it: what this depends on
    --depth <n>
    how many hops to trace (default 2)
    --limit <n>
    how many nodes to return
  • PREMIUMgraphQueries
    $pskl why <a> <b>

    How these two are connected.

    A missing endpoint is reported as missing, never as “no path”. Those look identical if you only read the result, and the second one reads as “these are decoupled, which is usually a good sign” — a confident finding about two things, one of which does not exist.

    --max-hops <n>
    how far to search (default 8)
    --undirected
    follow edges in either direction
  • PREMIUMgraphQueries
    $pskl ask "<question>"

    The part of the graph a question touches.

    Not a chat: nothing is generated. The answer is a subgraph, and every node in it is a real file or symbol extracted from your repository. What the question was understood to be *about* is printed first — when an answer looks wrong it is almost always the seeds.

  • PREMIUMgraphExplorer
    $pskl insights

    Cycles, hubs and edges that should not exist.

    Read from what the analysis already computed, so it is cheap enough to be a first stop. Each subsystem name says where it came from: a derived label is the directory its files live in and you can check it, a model label is a judgement.

  • PREMIUMgraphExplorer
    $pskl diff

    What changed in the graph between two snapshots.

    Defaults to the two most recent analysed snapshots. When the two graphs were built by different builder versions it says so before the numbers, not after — otherwise “we improved the parser” reads as “somebody added 400 nodes”.

    --from <ordinal>
    the older snapshot
    --to <ordinal>
    the newer snapshot
    --list
    which snapshots can be compared
13

Design with KEEL

A staged interview that ends in a build plan, a file tree, and a record of how every decision was reached. One command per exchange — the session lives on the server, so closing the terminal loses nothing.

Every claim carries where it came from, here as in the web app: `■` verified was read from your code, `▶` stated is what you said, `◤` inferred is the agent’s reasoning, `□` unknown means nobody established it. `KEEL.md` ends addressed to a coding agent and is written to be pasted into one — which is exactly why a verified badge has to mean *go and look* rather than *trust me*. The model doing the work is named in `pskl keel show`, so you can see that the model you chose is the one answering.

  • FREE
    $pskl keel start --quadrant <name>

    Open a session.

    The quadrant decides what the interview is *for*, and getting it wrong costs the whole session, so there is no default. `FOUND` — you have code and want to know what to build next. `ORIGIN` — an idea and no code. `FIT` — code and a feature, and you want to know where it goes. `CHART` — code and no plan. FIT and CHART read a repository and need a linked project; the other two must not have one. With no opening message the agent leads, which is the entrance for anyone who cannot yet answer “what is this, in one line?”

    --quadrant <name>
    FOUND · ORIGIN · FIT · CHART
    -m, --message <text>
    your opening line; the agent leads if omitted
  • FREE
    $pskl keel say "<text>"

    Send one turn and wait for the reply.

    The exchange the session is made of, and the thing that costs. A session that cannot take another turn is refused here rather than at the server, so you are told why — out of turns, out of allowance, or standing at the deliverables gate — before anything is spent.

    --session <id>
    when more than one is active
  • FREE
    $pskl keel show

    Where the interview is, and what it has settled.

    Costs nothing. Prints the stage strip with how each finished stage ended, every settled answer with its provenance and citations, the open questions from the newest turn only, and the model that answered.

    --session <id>
    when more than one is active
  • FREE
    $pskl keel list

    Every session on this account.

  • FREE
    $pskl keel skip <slot>

    Record that nobody knows this yet.

    Different from moving on, and the difference lands in the plan. A skipped question reads as *somebody addressed this and the answer does not exist today*; a forced stage reads as *you walked out*. Before this existed people pressed move-on because it was the only button, and the session blamed them for something they had not done.

    --session <id>
    when more than one is active
  • FREE
    $pskl keel next

    Close this stage and move on, whatever is still open.

    Always available, deliberately: a feature that can trap you gets abandoned the first time it looks like it might. The stage is recorded as ended early, by you.

    --session <id>
    when more than one is active
  • PREMIUMkeelDeliverables
    $pskl keel pull

    Write the plan, the session and the tree into this directory.

    Produces `KEEL.md`, `keel-session.md`, and `keel-scaffold/`. Files that already exist are reported and left alone — the tree names files to *change* as well as files to add, and overwriting your work to deliver a header comment is the wrong trade. Every scaffold path is checked against this directory before anything is written.

    --out <dir>
    where to write; the working directory by default
    --force
    overwrite files that already exist
    --session <id>
    when more than one is active
  • FREE
    $pskl keel stale

    Whether the plan is still true of the code it was written about.

    Not “your plan is wrong”. A session freezes its evidence, so months later the plan sits there as green as the day it was written. This re-runs the citation check against the newest snapshot and says which verified findings cite files that are no longer there — they can no longer be *checked*, which is a different claim from being false.

    --session <id>
    when more than one is active
  • PREMIUMkeelDeliverables
    $pskl keel retry <kind>

    Rebuild a document whose composition failed.

    Takes `plan` or `scaffold`. Spends model budget and is held to the same monthly allowance a turn is, so a session at its turn cap can still have its plan rebuilt.

    --session <id>
    when more than one is active
  • FREE
    $pskl keel abandon <id>

    Close a session for good.

    Its documents remain; the frozen evidence it read is swept. The id is never resolved for you — this is the one command here that destroys something, and guessing which session you meant is not a risk worth taking to save a paste.

14

Read what it found

The analysis output, in the terminal — conclusions, detected stack, and the deterministic fact sheet underneath both.

Every claim arrives with how far to trust it: `■` verified means it was found directly in your files, `◤` inferred means it was reasoned from them, `□` unknown means the evidence did not settle it. The glyphs are the same ones the web uses, and they are never dropped — a claim without its confidence is exactly the confident-sounding AI summary this product is designed not to produce.

  • FREE
    $pskl findings

    What the deterministic detectors found, and which of them actually ran.

    Prints the detector statuses before the findings, and that order is the point: twelve findings from four detectors with two failed is a different document from twelve findings from six that all succeeded, and only one of them is a reason to relax. Each detector also lists what it could not do — dependencies left unchecked, files that failed to parse, results truncated — so a count reads as the floor it is rather than a total. A pillar with no successful detector reads “not checked”, never zero. No model is involved at any stage: every finding points at a line a parser read.

    --severity <level>
    critical, high, medium, low or info
    --category <name>
    security, supply-chain, architecture or quality
    --detector <id>
    one detector, e.g. security.insecure-config
    --snapshot <ordinal>
    an earlier snapshot, as `pskl status` numbers them
    --limit <n>
    how many findings to list
    --fail-on <severity>
    exit non-zero at or above this severity — off by default
  • FREE
    $pskl findings hotspots

    The files where findings, change and dependency all land together.

    Only files that carry a finding are listed. Churn and reach are multipliers on an established risk, never a source of one — a file that changes constantly and that everything imports is interesting, and calling it a risk would be inventing one. Every row prints the facts behind its position rather than a score, because the list exists so somebody opens the file.

    --snapshot <ordinal>
    a snapshot other than the latest
    --limit <n>
    how many files to list (1-50)
  • FREE
    $pskl findings trend

    Whether this project is getting better, across snapshots.

    A snapshot where a detector failed has fewer findings recorded, not fewer findings — so it is drawn as a break in the line with the reason beside it, never as a lower number. A downward line on a security chart is read as progress before it is read at all, and that is the one claim this refuses to make by accident.

    --limit <n>
    how many snapshots to include (2-50)
  • FREE
    $pskl findings diff

    Findings that are new, persisting or resolved between two snapshots.

    A finding counts as resolved only when the detector that raised it ran again and succeeded without it. Anything that vanished because its detector crashed is listed separately as unaccounted for — calling that “fixed” would close a ticket on the strength of a bug, which is the one thing this whole surface exists to prevent.

    --base <ordinal>
    the older snapshot
    --head <ordinal>
    the newer snapshot
    --fail-on <severity>
    exit non-zero on a NEW finding at or above this severity
    --markdown
    a pull-request comment body, for CI to post
  • FREE
    $pskl skills audit

    What the deterministic rules found in the Skills this project generated.

    This product writes Skills from your repository, and a Skill is prose an agent executes with your own privileges — so the material it was written from, a README or a request in your own words, is untrusted by construction. The audit reads the finished Skill for text that would make an agent exfiltrate data, reach a credential, override its own safety rules or act unasked. No model is involved, so it runs on every plan. It leads with how many Skills nobody has audited, because a catalogue with two findings and eleven unexamined Skills is a different document from one with two findings. It also names the risk categories the rule set does not check: a rule that cannot tell a prohibition from the act it prohibits is left out rather than shipped noisy, and saying which is more useful than a silent gap. Exit stays zero whatever it finds unless you ask for a gate.

    --slug <slug>
    one Skill rather than the catalogue
    --run
    audit now, rather than reporting what is stored
    --fail-on <level>
    findings, or unaudited — the last treats a Skill nobody checked as not passing
  • FREE
    $pskl constraints

    The floor this project holds itself to, and whether the last snapshot cleared it.

    Findings say what the detectors noticed; constraints say what the project decided was not allowed, which is the question a pipeline asks. The outcome comes first, and beside it the number of checks that never reached a conclusion — because a PASS over nine executed constraints and three that could not run is not the same claim as a PASS over twelve, and only the first of those is a reason to relax. Those appear under their own heading, before the ones that concluded, with the reason each gives. NOT_SUPPORTED is the common one and it is not a fault: a constraint that shells out to a package script cannot run where there is no sandbox, and saying so is more useful than a blank. Exit stays zero whatever the outcome unless you ask for a gate.

    --dimension <name>
    one dimension, e.g. security or architecture
    --status <name>
    pass, warn, fail, not-run, not-supported, skipped or unknown
    --snapshot <ordinal>
    an earlier snapshot, as `pskl status` numbers them
    --fail-on <level>
    fail, warn, or incomplete — the last treats a check that never ran as not passing
  • FREE
    $pskl snapshots

    Every capture of this project, how it was taken and what it changed.

    A commit and a working tree are shown as different things, because they are: a commit SHA is a promise anybody with the repository can reproduce exactly what was analysed, and a working tree is one person’s uncommitted state at one moment. A zip with no Git history says so rather than borrowing an identity. Each line also carries what the capture changed against the one before — and a comparison that could not see everything says “at least”, never a total.

  • FREE
    $pskl changes

    Which files moved between two snapshots, and which could not be compared.

    Added, modified, deleted and renamed — plus the ones nothing could decide. A secret file present in both snapshots is listed as not compared rather than unchanged, because its contents are never read and calling it unchanged would be a claim about bytes nobody looked at. A rename is only reported when exactly one deleted path and one added path share a hash; anything ambiguous stays two rows, because a wrong rename cannot be told from a right one.

    --snapshot <ordinal>
    a specific snapshot, as `pskl snapshots` numbers them
    --kind <kind>
    added, modified, deleted, renamed, unknown or unknown-secret
    --limit <n>
    how many paths to list
  • FREE
    $pskl intelligence

    What the analysis concluded about the project, section by section.

    Leads with the confidence score, because it frames everything below it — 40% verified means most of what follows is the model reasoning rather than reading. Ends with what the evidence could not establish, which is the half a summary always drops and the half that tells you what you still have to find out.

    --section <name>
    one of the seventeen, e.g. security or technical-debt
    --evidence
    the files each claim rests on
    --snapshot <ordinal>
    an earlier snapshot, as `pskl status` numbers them
  • FREE
    $pskl stack

    The technologies detected, grouped by category.

    Deterministic — no model produced any of it. A dependency read out of a manifest is verified; a framework guessed from a directory layout is inferred, and the difference is why the confidence is shown here too. A version that was not found stays blank rather than becoming a guessed “latest”.

    --category <name>
    one category only
    --evidence
    every supporting file, not just the first
  • FREE
    $pskl architecture

    Routes, tables, entry points, languages and the largest modules.

    Facts a parser could state outright. When the scan hit its file ceiling the count is shown as “3,214 of 41,880”, never as a bare number — a fact sheet built from part of a project must say so. The architecture style is the one guess on the screen and carries its own confidence.

    --routes
    list every route, not just the count
  • FREE
    $pskl files

    How the snapshot classified what it ingested.

    A summary rather than a listing. Files classified `secret` are called out as a warning rather than shown as a tidy row: their contents were never read and never sent to a model, but they did cross the network — and this is the only place you can see that after the fact.

15

See what it costs

Where the month went, how much of each limit is left, and what every run actually spent.

A cost is never rounded up into a confident figure. When a model has no catalogue price the requests it served cannot be costed, so the total is shown as a floor — `≥ $12.40`, with the number of unpriced requests beside it — and as “not priced” when none of them could be costed at all. `$0.00` would say the work was free; the truth is that nobody recorded a rate.

  • FREE
    $pskl usage

    Tokens and cost for the month, broken down by project, model and task.

    Sorted by tokens rather than by cost, because cost is missing for any unpriced model — sorting on it would push exactly the rows whose spend is unknown to the bottom, where they look small.

    --project [slug]
    one project only, with its last requests — bare for the linked one
    --from <YYYY-MM-DD>
    start of the window (default: this month)
    --to <YYYY-MM-DD>
    end of the window
  • FREE
    $pskl quota

    How much of each limit is left, and when it resets.

    The answer to an analysis refused for quota: the refusal names the limit, this names the distance to it. Four limits are reported because four are what the server counts — projects held, analyses per day, monthly tokens and monthly cost.

  • FREE
    $pskl history

    Every analysis run, with its cost, duration and what it produced.

    One row per run, not per project: a project analysed four times is four events with four costs. Failed runs keep their error, and a run that hit the file ceiling is marked — its Skills describe part of the project, which is not visible anywhere else.

    --limit <n>
    how many runs to list (default 20)
    --status <status>
    only queued, running, succeeded, failed or cancelled
    --all
    every project, not just the linked one
16

Explore the graph

The dependency graph the analysis built, in the terminal.

`graph summary` answers on every plan — it is how you find out whether the graph is missing, still building, or simply not included in your plan. The rest need Premium.

  • FREE
    $pskl graph summary

    Size and readiness. Answers on every plan.

  • PREMIUMgraphExplorer
    $pskl tree

    The dependency tree, drawn as a tree.

    Says so when symbol-level detail is hidden by your plan, because the server returns a thinner tree with no error and that otherwise reads as a bug.

    --depth <n>
    how deep to draw (default 3)
  • PREMIUMgraphQueries
    $pskl graph resolve <query>

    Find node keys.

    The mandatory first step before any lookup: the graph takes exact keys like `file:src/app.ts`, not free text.

    --limit <n>
    how many matches (default 10)
  • PREMIUMgraphExplorer
    $pskl graph node <key>

    One node, with its neighbours and the evidence behind each edge.

  • PREMIUMgraphExplorer
    $pskl graph community <n>

    What is in one subsystem.

    The ordinal comes from `pskl insights` — a subsystem has no name you would guess, so the two read as one two-step. Cohesion is shown beside the members: a subsystem at 0.35 is barely one, since only a third of its edges stay inside, and the label alone would present a weak cluster as a real module.

  • PREMIUMgraphExplorer
    $pskl graph export <format>

    Export the graph as json, graphml or mermaid.

    --out <file>
    write to a file instead of stdout
17

Run an analysis

Start a run from the terminal and watch it.

In a git repository it archives HEAD, so uncommitted work is excluded — it tells you how many changes it left behind. Use --include-uncommitted to send them too.

  • FREE
    $pskl analyze --watch

    Analyse this directory and follow the job.

    Works on a private repository, or a folder with no git at all. Everything is packed through git so .gitignore is honoured by the rules your own tooling uses — node_modules, build output and a .env stay out. Your staging area is never touched.

    --include-uncommitted
    send your working tree, not just what is committed
    --allow-secrets
    upload even when files look like secrets — refused by default, and they are still excluded before any model reads the code
    --github <owner/repo>
    analyse a connected GitHub repository
    --public <url>
    analyse a public repository by URL
    --zip <file>
    upload an archive you made yourself
    --ref <ref>
    a branch or tag, with --github or --public
  • FREE
    $pskl analyze --local

    Analyse without uploading the code.

    Two things leave the machine: an index — one row per file, with its path, its size and a SHA-256 of its contents — and the dependency manifests themselves, because a manifest declares what a project depends on rather than being source. A file matching the secret rules is listed and never opened, so its bytes are not even in the process. What this produces is the file index, the dependency list and the bill of materials. Not the dependency graph, the architecture map or the findings: every one of those reads the code, and the code did not leave.

  • FREE
    $pskl analyze status --watch

    Follow the most recent analysis, step by step.

EVERY COMMAND ACCEPTS
--json
machine-readable output, for scripts. For an agent reading the answer, the text is the compact one: `ask --json` carries every node and edge, many times the size
--project [slug]
act on a project other than the linked one
--api <url>
point at a different deployment
--no-color
disable colour
-v, --version
the installed version — a command this page lists and the binary does not know means an older install: npm install -g projectskills
EXIT CODES
0
fine
1
failed
2
wrong usage
3
not signed in
4
your plan does not include it
5
not found
6
rate limited
7
not ready yet

Distinct on purpose. A tool that answers 1 for everything cannot be branched on, and “not on your plan” and “not built yet” need opposite responses.

ENVIRONMENT
PROJECTSKILLS_TOKEN
Overrides the stored credential and is never written to disk. This is the CI path.
PROJECTSKILLS_API_URL
Point the CLI at a different deployment without passing --api every time.
WHERE THINGS GO
ON DISK
.claude/skills/<name>/SKILL.md
The artefacts. The directory is the Skill’s name — the Agent Skills standard Codex and Cursor follow refuses a file whose front matter names another.
.projectskills/project.json
Which project this directory is. Commit it — a project id is not a secret.
.projectskills/artifacts.json
What was written and its hash. This is how pull knows not to overwrite a file you edited.
~/.config/projectskills/credentials.json
Your token, written 0600. Never committed, never printed.
CREATE A TOKEN FOR CI →PLANS