psy documentation
Everything the install actually does: the exact steps per operating system, every file psy writes and where, the full command set, and how a project tunes the guard.
Install
Everything lands in your home directory — no
sudo, no system paths, no runtime to install.
The only tool the installer needs is curl or
wget.
macOS
$ curl -fsSL https://psykit.psycray.com/install.sh | sh
Prefer to see what runs before it runs? Read
install.sh — it is ~70 lines: it detects your OS and architecture,
downloads psy-darwin-arm64 (or
-amd64) from
https://psykit.psycray.com/latest/, and runs
psy install. It writes nothing the
install itself does not write.
The launcher lands at ~/.local/bin/psy.
If that directory is not on your PATH yet:
$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
Linux
Same one-liner, same script — it picks
psy-linux-amd64 or
-arm64 for your machine:
$ curl -fsSL https://psykit.psycray.com/install.sh | sh
On bash, the PATH line goes to ~/.bashrc:
$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
No package manager route exists yet; the alternative to the script is the same thing by hand — download a binary from the downloads list, then:
$ chmod +x psy-linux-amd64 && ./psy-linux-amd64 install
Windows
The curl | sh one-liner is
POSIX-only, so Windows has two routes of its own.
1 · Automatic (PowerShell). One command downloads the right build for your machine and runs its installer:
PS> irm https://psykit.psycray.com/install.ps1 | iex
Prefer to see what runs first? Read
install.ps1 — it detects your architecture (x86_64 or ARM64),
downloads the matching .exe from
https://psykit.psycray.com/latest/, and runs
psy install. It writes nothing the
install itself does not write.
2 · Manual (downloaded .exe). Grab the
.exe for your machine from the
downloads list, then run it from the
folder you saved it in (use -arm64 on ARM):
PS> .\psy-windows-amd64.exe install
Either way the launcher lands at
%USERPROFILE%\.local\bin\psy.exe, and
install adds that directory to your user PATH
for you — no manual setup: open a new terminal and
psy is on it. If it was already on
PATH the installer leaves it untouched, and
uninstall removes only the entry it added.
Know how native Windows differs before you rely on it:
- Skills are linked with symlinks, which Windows allows only with Developer Mode on (Settings → System → For developers) or an elevated prompt. The installer tells you when that is the blocker.
psy updateworks here too. A running.execannot be overwritten in place, so the old launcher is renamed aside and the new one takes its path; a later run sweeps the leftover.- Both x86_64 and ARM64 builds ship — pick the row that matches your machine.
Verify the install
Open a new terminal (so the PATH change is picked up) and run the doctor. It checks every host it wired and prints one line per check:
$ psy doctor psy doctor — home /Users/you common [PASS] canonical store: 10 skills [PASS] bundles: core [PASS] launcher: present and executable [PASS] binary in config: ~/.local/bin/psy runs claude [PASS] skill symlinks: 10/10 alive [PASS] hook: secret + danger all present [PASS] statusline: present, at statusLine.command [PASS] attribution.commit: "" (set by psy)
The exit code counts FAIL lines only —
a WARN (say, an agent that is installed but
not on PATH) never fails the run. --json
emits the same report for scripts.
To see the guard itself work, ask your agent to read a secret — e.g. “cat .env” in any project. The tool call is refused before it runs; that refusal is the install working, not an error.
What gets installed, exactly
This table is the complete list of what install and
update write, and uninstall reads the same
list back. The other commands write only where you point them or where
their row under Commands says:
switch the account files and the two
Claude sign-in files, env set the
.env file and a backup beside it,
diagram deliver the page you name,
init missing files in your repository. Configs are edited, never rewritten:
your file is parsed, our block is inserted or replaced, and every other
byte survives as it was. Each touched file is backed up first.
| Path | What it is | How it is written |
|---|---|---|
~/.agents/skills/ |
the canonical skill store — real directories, one per skill, named psy-* |
created; skills copied in from the binary's embedded bundle |
~/.agents/.skill-lock.json |
the lockfile: every skill installed and every link made, so uninstall knows exactly what is ours | written each install |
~/.local/bin/psy |
the launcher (.exe on Windows) — the binary everything else calls |
copied, made executable; skipped when already identical |
~/.claude/skills/ |
Claude Code's skill directory | one symlink per skill into the store |
~/.codex/skills/ |
Codex's skill directory | one symlink per skill into the store |
~/.gemini/antigravity/skills/ |
Antigravity's skill root | one symlink per skill into the store |
~/.gemini/antigravity-cli/skills/ |
Antigravity CLI's skill root | one symlink per skill into the store |
~/.claude/settings.json |
secret + danger guards on PreToolUse, the status line, the attribution keys that keep agent trailers off commits and PRs (sessionUrl: false, commit: "", pr: ""), and the instructionFiles mode that has Claude Code read a project's AGENTS.md beside its CLAUDE.md |
JSON edited in place; our hook and status line entries carry our marker; an attribution key or the mode is written only when absent and recorded in the lockfile — one you already set is left alone |
~/.claude/CLAUDE.md |
the base prompt — the general guidelines every agent gets | one block at the end of the file between two marker lines that record the build and a checksum; everything outside the block is yours and is never rewritten; a block you edited inside is left alone and named |
~/.codex/AGENTS.md |
the same base prompt for Codex | the same marked block |
~/.codex/hooks.json |
the same secret + danger guards for Codex | JSON edited in place; our entries carry our marker |
~/.codex/config.toml |
an optional [tui] status line |
a fenced comment block; skipped entirely if you already declare [tui] |
~/.gemini/config/hooks.json |
secret + danger guards for Antigravity | JSON edited in place; our entries carry our marker |
~/.gemini/antigravity-cli/settings.json |
Antigravity's status line | JSON edited in place; our entries carry our marker |
~/.psy-backups/ |
a timestamped directory per run with a copy of every file about to be touched; runs are kept until you delete them | created lazily, on the first real change |
The hooks call ~/.local/bin/psy hook run
<agent> <guard> — a local process with a timeout. The
guard decides locally; the tool call never leaves your machine. The
binary touches the network only when you ask it to — the install
script, install's own downloads (external
skills; --offline skips them), or
psy update.
Install wires all three agents whether or not they are on the
machine, creating the directories above if they are missing — a config
written before an agent is installed is simply ready for it.
doctor tells you which hosts it could
actually verify.
Commands
| Command | What it does |
|---|---|
psy install |
The whole setup, and the upgrade: places the launcher, syncs
skills into the store, links them into every agent, wires hooks and
the status line, writes the base prompt into each agent's
instruction file, and has Claude Code read a project's
AGENTS.md beside its
CLAUDE.md. Safe to re-run — it
reconciles in place.
--from <dir> uses a local skill
source instead of the embedded bundle;
--offline skips external skills;
--wire-only touches no skills at all —
launcher, hooks and status line only;
--with demo adds the n8n demo bundle and
--without demo removes it, remembered
across updates; --with comms adds
psy-translate, the
;;vi / ;;en
/ ;;sl translation shortcuts. |
psy update |
Downloads the latest build for this platform, compares it
byte-for-byte with the launcher, and if it differs lets the
new binary run its own install — so the launcher, the
embedded skills and the wiring always move together.
--check only reports. On
native Windows the old launcher is renamed
aside so a running .exe can be
replaced. |
psy doctor |
One PASS/WARN/FAIL line per check, per host. The exit code
counts FAILs only. --json for
scripts. |
psy uninstall |
Prints exactly what it will remove and asks for the literal
word yes
(--yes skips the prompt). See
Uninstall for what goes and what
stays. |
psy init |
Writes what a repository needs to work with the skills:
AGENTS.md (the routes and the rules
every agent reads — no CLAUDE.md,
Claude Code reads it itself),
CONTEXT.md and
docs/architecture.md to fill in, the
scripts/verify-quick.sh and
verify-full.sh checks, a dependency
check, and a CI workflow that runs them. Creates only what is
missing — a file that exists is kept as it is. |
psy version |
The release tag this binary was built from (or the VCS revision for a local build). |
psy hook run <agent> <guard> |
What the wired hooks execute — you never run it by hand. The
secret and
danger guards each decide one tool
call, payload on stdin. The units an older install wired,
tier and
workflow, let every call through
until psy install rewires the
machine. Agents:
claude,
codex,
antigravity. |
psy statusline <agent> |
Renders the status bar from the session JSON the agent pipes
in (claude and
antigravity; Codex uses its own TOML
status line instead). On Claude it also names the saved account
~/.claude is signed in as. |
psy switch [<name> [--kill]] |
Keeps one ~/.claude for every Claude
account and swaps only who it is signed in as, so sessions, memory
and history stay in one place. save,
import and
list manage the saved accounts; see
Multiple Claude accounts. |
psy track <next|idea|add|status|check> |
Reads and moves the work on the tracker a project opts into
with one Tracker: line in
CLAUDE.local.md (a GitHub project or
GitHub issues), through gh. Never
deletes, archives or creates a project, field or label. |
psy docs record <slug> |
Prints the path of a new decision record,
docs/decisions/<date>-<slug>-<4 hex>.md
(or under docs/adr/ when only that
exists), so two branches recording the same slug still merge cleanly. Creates
nothing. |
psy env set <file> KEY=VALUE… |
Writes keys into an existing .env
file without printing it — the one way through the secret guard.
Says added or
set per key, never whether a value
changed, and backs the file up before overwriting. |
psy diagram <validate|render|deliver|examples> |
Compiles a typed JSON description (architecture, flow, sequence,
state, data) into one self-contained HTML page, naming every
layout fault with the fields allowed to fix it. What the
psy-diagram skill drives. |
psy skills graph [--json] |
Prints who hands work to whom across the installed skills, from
each skill's declared uses. |
psy help [<command>] |
Usage for the tool or for one command. |
Guard & customization
The guard runs before every tool call and blocks two
things by default: reads and writes of secret-shaped files
(.env, keys, credentials) and destructive
command segments (rm -rf,
git rebase, …). A project tunes it with one
optional file.
.aiguard.json — per-project guard rules
psy never writes this file; you create it in the project. The guard uses the nearest one, walking up from the working directory, and stops at the first it finds — a config in a subdirectory shadows the parent's. All four keys are optional arrays:
| Key | Effect |
|---|---|
deny |
Extra secret-file patterns to block, on top of the built-in
list. Glob-matched (*.private,
config/*.key). |
allow |
Exceptions — files lifted out of the deny list, built-in
defaults included. Weighed before
deny. |
dangerAllow |
Regexes; a whole command segment that matches one is let past the danger guard. |
dangerDeny |
Commands this project rules out: objects with a
pattern regex and the command to
use instead, which the block hands the
agent. It also matches past a VAR=…,
sudo or path in front. Weighed before
dangerAllow on the same command, but a
wrapper dangerAllow lets past is not
looked inside. |
{
"deny": ["config/master.key", "*.private"],
"allow": [".env.ci"],
"dangerAllow": ["^git rebase --continue$"],
"dangerDeny": [{ "pattern": "npm( .*)?", "use": "pnpm" }]
}
A broken .aiguard.json fails
closed for everything the project loosened: the guard
falls back to the built-in rules. The project's own
dangerDeny bans are lost with it — the one
way a broken file opens — while a single unreadable entry in an
otherwise valid file blocks every command until it is fixed. And a
first guard refusal in a fresh project is not a bug — it is
the moment to add the one narrow exception the project needs.
allow as narrow as the exception you need.
.aiguard.json is itself on the default deny
list — the file that binds the agent must not be editable by the agent —
and a broad pattern like "allow": ["*.json"]
re-exposes it along with your real secrets.
psy doctor warns when it sees one.Customizing — a project skill, never a copy
The skills are read only from what the installed kit
ships. A project keeps no copy of them: a copy freezes at the version it
was taken from and silently misses what the kit adds later. What a
project needs of its own lives in plain files in the repo, versioned
with the code — psy never updates them, and
psy init writes one only when it is
missing.
| Project file | What it does |
|---|---|
AGENTS.md |
The routes — which skill a kind of work goes through — and
the rules every agent in the project reads.
psy init writes the first
one; from then on it is the project's to edit. |
.aiguard.json |
The guard rules, exactly as above. |
A skill directory in the project (Claude Code:
.claude/skills/<name>/) |
Adds a project-only skill — a ticket column, a compliance
section, a review step, a house protocol. The agent host reads
it natively; psy neither installs nor rewrites those
files. To have one stand in for a shipped skill, name it in
the project's AGENTS.md routes in
place of the shipped one. A
same-named directory does not shadow a shipped skill —
the host resolves personal skills over project ones, and
psy installs at the personal level — and a shipped skill
invoked directly by name still loads. |
The product's own rules are not a kit file:
CONTEXT.md at the repo root holds the terms
and business rules in force, and one record per change lands under
docs/decisions/, named by
psy docs record <slug>;
docs/architecture.md holds the modules and
the data flow.
~/.claude/skills/ and its siblings are the
store's fan-out: every psy install
or psy update rewrites what its
ownership record lists, so an edit there is gone on the next update —
reported only as refreshed. Project skills
live in the repo, travel with git clone,
and survive update, reinstall, and a fresh machine.Multiple Claude accounts
One ~/.claude, several
accounts: psy switch swaps only who
it is signed in as, so sessions, memory and history stay in one place.
Save the account you are signed in as, import the others from their old
config dirs, then switch:
$ psy switch save main $ psy switch import work --from ~/.claude-accounts/work $ psy switch work --kill # from a plain terminal $ psy switch # list; * marks the signed-in one
Each account is one file,
~/.claude-accounts/<name>.json, mode
0600, holding its refresh token; MCP tokens, per-project state and
settings never move. Before a swap the outgoing account's rotated token
is written back to its file and both host files are backed up. A swap
refuses while a Claude Code session runs;
--kill lists them, asks, ends them, swaps,
and prints a claude --resume line for each.
A swap also refuses when the signed-in account was never saved, and
every subcommand refuses while CLAUDE_CONFIG_DIR
is set — switch serves the one ~/.claude, not
the per-directory route below. --kill runs
from a plain terminal, not from inside a session. Not on macOS yet,
where the token lives in the Keychain;
--kill is not on Windows yet. Tokens rotate,
so on another machine sign in there and
save — never copy an account file across
machines.
Or run one Claude account per directory by pointing the
host at each with CLAUDE_CONFIG_DIR, and that
account reads its skills and settings under that dir instead of
~/.claude — so without a step the whole
layer, skills and guard both, is absent there. Install once from a shell
where the variable is set, or name the dir explicitly:
$ CLAUDE_CONFIG_DIR=~/.claude-accounts/work psy install $ psy install --claude-dir ~/.claude-accounts/work # same, named outright
The dir is wired as a full Claude surface — skills,
guards, status line — and recorded, so every later
psy update keeps it wired from any
shell with no re-declaration.
psy doctor lists it under
claude config dirs;
psy uninstall unwires and forgets it,
leaving that account's own settings untouched.
Environment variables
| Variable | Read by | Effect |
|---|---|---|
CLAUDE_CONFIG_DIR |
psy install,
psy switch,
psy statusline |
For install, a dir other than ~/.claude
is declared and wired as an extra Claude surface (multi-account,
above), then remembered across updates. While it is set,
switch refuses and the status line
leaves out the account name. |
PSY_BASE_URL |
install script, psy update |
Download from your own host or mirror instead of the built-in site. |
PSY_VERSION |
install script | Fetch a tagged release directory instead of
latest. |
The status line has no config file; besides
CLAUDE_CONFIG_DIR above, nothing tunes it.
What you see is what ships.
Update
$ psy update # download + reinstall if newer $ psy update --check # just tell me
There is no version negotiation: the latest build is
downloaded and compared byte-for-byte with your
launcher. Identical means you are current; different means the new
binary is verified to run and then performs its own
install — launcher, embedded skills, hooks
and status line move as one, so an update can never leave wiring from
one version pointing at skills from another.
- Re-running the install one-liner is the same upgrade by another
door —
installreconciles in place. - Released binaries know their download site from build time;
PSY_BASE_URLoverrides it. A from-source build with neither refuses to guess and points you at the manual route. - Nothing updates by itself: no daemon, no background check. Updates happen when you run one of the two commands above.
- On native Windows the running
.execannot be overwritten in place, soupdaterenames the old launcher aside and puts the new one at its path.
Uninstall
$ psy uninstall
It prints the full plan first and proceeds only on the
literal word yes. What it removes is read
from the lockfile — the record of what install actually wrote — never
guessed from directory contents.
Removed
- Our marked blocks in every agent config and the base prompt block in each instruction file — the surrounding file is restored byte-for-byte.
- The attribution keys install wrote, when the lockfile records them and they still hold the value install gave them.
- The skill symlinks the lockfile recorded, each re-verified on disk before deletion.
- Those skills from the canonical store, and then the lockfile itself.
Kept
- The launcher
~/.local/bin/psy— reported as kept; delete it by hand. - All backups under
~/.psy-backups/, and the store directory itself. - Saved Claude accounts under
~/.claude-accounts/— each holds a refresh token; delete them by hand if the accounts should go too. - A Codex feature flag install turned on
(
default_mode_request_user_input) — after install its bytes are indistinguishable from a setting you chose yourself, so uninstall leaves it. - Anything that is yours: skills you added, config blocks without our marker, files it cannot prove it wrote.