Skip to content

Command line

Your memory, from a terminal

The same memory your assistants read, reachable from the place you already work. Capture the thing you just worked out, pipe a command’s output straight into it, and ask for any of it back weeks later. One command to install, three to be useful.

zsh

$ pm auth login

Opening your browser to sign in.

Signed in to https://api.persistmemory.com as profile "default".

# capture a decision, with the reasoning

$ pm remember "we chose Postgres with pgvector over Pinecone — cost and lock-in"

Accepted for processing. Job job_01j8x…

# and ask for it back, months later

$ pm search "why postgres"

SCORE TYPE TITLE CONTENT 0.914 decision Chose Postgres+pgvector Cost and lock-in were the deciding…

Install

Node 20 or newer is the only requirement, on every platform. The last one installs nothing at all.

macOS and Linux

shell
curl -fsSL https://persistmemory.com/install.sh | sh

Windows

PowerShell
irm https://persistmemory.com/install.ps1 | iex

Any platform

npm, or nothing at all
npm install -g @persistmemory/cli

# or run it without installing
npx @persistmemory/cli auth login

Piping a script into a shell is worth being careful about, so read ours first — install.sh and install.ps1. Both refuse to run rather than guessing when they cannot find a supported platform or a recent enough Node, neither ever escalates to sudo or Administrator on your behalf, and both tell you what to do instead when npm’s global directory is not yours to write to.

pm auth login

Signing in

Two ways, and both are first-class. A browser when there is one, and a key when there is not — because a CLI whose only login needs a browser is a CLI that cannot run in a pipeline.

At a desk, with a browser

pm auth login

Opens your browser, asks you to approve four named permissions, and stores a token that renews itself. Over SSH, where nothing can open, it prints the URL and waits — so the same command works on a server.

In CI, with no browser

export PERSISTMEMORY_API_KEY=pm_live_…
pm search "last release notes"

An environment variable outranks any stored login, so a runner authenticates without writing a file. Create a key on the settings page, or interactively with pm auth login --api-key, which prompts without echoing so the key never reaches your shell history.

The browser holds the password, not the CLI

Signing in opens your browser and uses OAuth 2.1 with PKCE. Your password is never typed into a terminal and never passes through the CLI. What comes back is a token scoped to what you approved on the consent screen.

A stolen code is worthless on its own

The CLI invents a one-time secret, sends only its hash to the browser, and reveals the secret when redeeming the code. Anything that intercepts the redirect on a shared machine gets a code it cannot exchange.

Credentials are kept apart from settings

Tokens live in ~/.persistmemory/credentials.json at mode 0600. The config file beside it holds only which server you talk to, so the file people paste into a bug report is the harmless one.

Commands

The grammar is pm <verb> <noun>, so a command you have not run yet is usually the one you would guess. pm --help has the full list.

Signing in

pm auth loginopen the browser and sign in
pm auth login --api-keypaste a key instead — for CI and headless machines
pm auth statuswho am I, and does the server still accept it
pm auth logoutforget the stored credential

Capturing

pm remember "we chose Postgres"capture a line
git log -1 | pm remember -capture whatever is piped in
pm remember --file notes.mdcapture a file

A session

pmstart a session — ask, read files, write answers back
pm chat --resume <id>pick up where you left off
/read <path>read a file into the conversation
/capture <path>read a file AND remember it
/write <path>write the last reply to a file — always asks first
/usagewhat this session has cost

Asking

pm search "what did we decide about Postgres"search your memory
pm list memories --limit 20the most recent memories
pm get memory mem_01j…one memory, in full
pm list spacesyour Spaces

Readable by a person, parseable by a script

Output is a table when it is going to a terminal and JSON when it is going to a pipe, so both work without your having to know a flag exists. Say --output yaml|csv|tsv when you want something else.

# a table, because this is a terminal
$ pm list memories --limit 3
ID          TYPE      TITLE               CONFIDENCE  UPDATED
mem_01j8x…  decision  Chose Postgres      0.91        2026-08-25 14:02
mem_01j8w…  task      Send Northwind PDF  0.78        2026-08-24 09:41
# json, because this is a pipe
$ pm search "postgres" | jq -r '.results[].memory.title'
Chose Postgres with pgvector

# and the exit code says which failure it was
$ pm search x; echo $?
3   # not signed in