Skip to main content

mem

Your shell, remembered.

A privacy-first CLI that captures, ranks, and organizes your terminal history.
Nothing ever leaves your machine.

CI PyPI Python 3.10+ macOS 26+ License: MIT


What mem does

mem silently captures every command you type — with its directory, git repo, exit code and duration — and gives it back ranked, scoped to the repo you're in.

mem deploy               # search your history
mem kubectl -p           # the patterns you actually use
mem save "cmd" -t ops    # save a command to a group
mem run ops              # run the group, step by step

Press Ctrl+R to search interactively, right at your prompt.

Your shell's own Ctrl+R does a literal reverse scan. mem ranks by how often you run a command, how recently, whether it starts with what you typed, and which repo you're standing in. Everything stays on your machine as plain text in ~/.mem/.


Install

# Homebrew (recommended)
brew install matinsaurralde/tap/mem

# pip
pip install cli-mem

# pip, with on-device pattern extraction
pip install "cli-mem[ai]"

Then add the hook for your shell:

# zsh
echo 'eval "$(mem init zsh)"' >> ~/.zshrc
source ~/.zshrc

# bash (macOS terminals read ~/.bash_profile; Ctrl+R needs bash 4+)
echo 'eval "$(mem init bash)"' >> ~/.bash_profile
source ~/.bash_profile

# fish
echo 'mem init fish | source' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish

That's it. From the next command on, everything you type is captured, and Ctrl+R searches it.

To start from the history your shell has already been keeping:

mem import --from-shell-history --dry-run   # see what would be imported
mem import --from-shell-history             # import it

Type mem followed by any words. A command matches when it contains every word, and the best matches come first.

mem kubectl              # one word
mem docker compose       # every word must appear
mem deploy -n 20         # more results (default: 10)
mem deploy --json        # machine-readable output
  1  kubectl apply -f deployment.yaml    infra       2h ago
  2  kubectl get pods -n production      infra       1d ago
  3  kubectl rollout status deploy/api   api         3d ago

When nothing matches literally, mem reads the words through a concept map, so a question finds the command that answers it:

mem "check disk space"   # -> du -sh .

When nothing matches at all, stdout stays empty, one line on stderr says so, and the exit code is still 0.

Ctrl+R

The shell hook binds Ctrl+R to mem's finder. Type to filter, ↑/↓ to move, ⏎ to put the command on your prompt — where you can read and edit it before running it. esc cancels and leaves your line untouched.

mem kube▏  3/12417
────────────────────────────────────────────────────────────
 ▸   kubectl logs -f deploy/api -n prod            infra  2h
     kubectl get pods -w                           infra  1d
   ✗ kubectl rollout undo deploy/api               infra  3d
↑↓ select · ⏎ accept · ^U clear · esc cancel

Commands that failed are marked with ✗, and the finder never runs anything for you.

It also learns from what you pick. Choosing a result is the one moment you say exactly which command you meant, so a pick outweighs anything mem can only infer. If you never open the finder, your ranking is unaffected.

Prefer your shell's own Ctrl+R? Set MEM_NO_KEYBINDING=1 before loading the hook. Capture keeps working.

Patterns

mem learns the structure of the commands you run and shows it with -p:

mem kubectl -p
Patterns for "kubectl":

  kubectl get <resource>
  kubectl describe <resource> <name>
  kubectl logs <pod> [--tail=<n>]
  kubectl apply -f <file>

No manual step: extraction runs in the background every 20 commands, with Apple Foundation Models on your Mac.


Scoping

mem knows which git repo every command was run in.

  • History is stored per repo. Searching from inside a repo favors that repo's commands, and those of sibling checkouts, without hiding the rest.
  • Groups and saved commands live in either repo scope or global scope:
    • inside a git repo they default to repo scope; outside one, to global scope
    • --global / -g forces global scope
    • a repo group shadows a global group with the same name

Groups

Groups are named collections of commands — runbooks you can execute.

mem save "kubectl get pods -n production" --group k8s --comment "list pods"
mem save '!' -t troubleshooting      # the last command you ran

mem list                             # all groups and saved commands
mem list k8s                         # the commands in one group
mem list --json                      # JSON output

mem run k8s                          # pick one step, or run them all
mem run k8s -y                       # run every step without prompting

mem group rename k8s kube            # rename, remove, copy --global, or edit in $EDITOR
mem export k8s --stdout              # print as JSON (without --stdout: copy to the clipboard)
mem import runbook.json -t ops       # import from a file, or from the clipboard with no file

Variables

Saved commands can contain $VAR_NAME placeholders, resolved when the command runs. Values are never stored in group files.

mem save "ssh -i ~/.ssh/\$KEY_NAME ubuntu@\$BASTION_HOST" -t ssh
mem save "kubectl get pods -n \$NAMESPACE" -t k8s --var NAMESPACE=production

When mem run meets a variable, it takes the first value it finds, in this order:

  1. Inline — mem run api API_TOKEN=abc123
  2. Environment — export API_TOKEN=abc123
  3. Variable store — mem vars set API_TOKEN
  4. Default — from --var NAME=default at save time
  5. Prompt — asked once, before any command runs
mem vars set API_TOKEN           # hidden input, like sudo
mem vars list                    # names, never values
mem vars remove API_TOKEN

Stored values live in the macOS Keychain, under the service mem-cli-vars, never in a file. mem hands them to /usr/bin/security over a pipe, so they never appear on a command line where ps could see them.

mem list <group> shows whether each variable is ready:

● global / api
  ──────────────────────────────────────────────────
  1. curl -H "Authorization: Bearer $API_TOKEN" .../users/$USER_ID
     ✓ $API_TOKEN  from environment
     ⚠ $USER_ID  unset — pass inline: mem run api USER_ID=<value>

Other commands

mem fix                          # what fixed the last command that failed
mem stats                        # top commands, repos, totals
mem forget "API_KEY=sk-..."      # permanently delete matching commands
mem tui -- kubectl               # open the Ctrl+R finder on a query
mem init zsh                     # print the shell hook (also: bash, fish)

How it works

You type a command
       │
       ▼
  Shell hook (preexec / precmd)
       │
       ▼
  mem _capture              ← in the background; never blocks your prompt
       │
       ├─→ append to ~/.mem/repos/<repo>-<hash>.jsonl
       └─→ every 20 captures: pattern extraction and retention, in the background

Ranking

score = 0.40 × picks + 0.21 × frequency + 0.21 × recency + 0.09 × prefix + 0.09 × context
Signal Meaning
Picks How often you chose the command in Ctrl+R, halving every 21 days
Frequency How often you ran it: log1p(n) / log1p(50), capped at 1
Recency Exponential decay with a 7-day half-life
Prefix 1 when the command starts with your query
Context 1 in the current repo, 0.5 in a sibling checkout, 0 otherwise

Every signal is normalized to [0, 1] and the weights sum to 1, so a score reads as a fraction. The weights, and the measurement behind them, are in ADR-009.


Storage

Everything lives in ~/.mem/ as plain text:

~/.mem/
  repos/
    Users-you-code-myapp-3f9a1c07.jsonl   # commands captured in one git repo
    _global.jsonl                         # commands run outside any repo
  patterns/
    kubectl.json                          # extracted command patterns
  groups/
    repos/
      Users-you-code-myapp.json           # repo-scoped groups and saved commands
    _global.json                          # global groups and saved commands
  picks.json                              # what you chose in Ctrl+R
  vars.json                               # which variables exist (values are in the Keychain)

The suffix on a history file is the first 8 hex characters of the SHA-256 of the repo's path, so two repos whose paths slugify the same never share a history.

tail -f ~/.mem/repos/_global.jsonl     # watch commands arrive in real time
grep docker ~/.mem/repos/*.jsonl        # search across every repo

Captured commands are kept for 90 days; patterns are kept forever.


Privacy

  • Zero network requests — not even update checks. The test suite runs mem with sockets disabled to keep it that way.
  • Zero telemetry — no analytics, no crash reports.
  • On-device AI only — Apple Foundation Models, on your Mac.
  • Plain-text storage — your data is yours to read, grep and delete. The one exception is variable values, which belong in the Keychain rather than in a file.

Read more in PHILOSOPHY.md.


Requirements

Requirement Version
macOS 26.0+
Python 3.10+
Apple Intelligence Optional, for pattern extraction

Uninstall

mem vars clear              # remove stored variables from the Keychain
brew uninstall mem          # or: pip uninstall cli-mem
rm -rf ~/.mem               # remove all captured data

Then remove the hook line from your shell config.


Development

git clone https://github.com/matinsaurralde/mem.git
cd mem
pip install -e ".[dev]"
pytest

CI runs the suite on macOS against Python 3.10–3.13; the end-to-end tests drive the real binary inside real interactive shells. Design decisions and the measurements behind them are recorded as ADRs. ARCHITECTURE.md maps the code, and PHILOSOPHY.md explains the principles it follows.

License

MIT

Metadata

Release files for cli-mem 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cli-mem 0.5.2
File Size Uploaded Provenance Actions
cli_mem-0.5.2.tar.gz 390.6 kB Without provenance Details for cli_mem-0.5.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cli-mem 0.5.2
File Interpreter ABI Platform Actions
cli_mem-0.5.2-py3-none-any.whl Python 3 none any Details for cli_mem-0.5.2-py3-none-any.whl

Total release size: 571.7 kB

Release files / cli_mem-0.5.2.tar.gz

Download URL cli_mem-0.5.2.tar.gz
Size 390.6 kB
Tags Source
SHA-256 checksum
How to use checksums
aa802d618a31d1da588f5128aa779440760d982546b38dccd3bee9304636880d
BLAKE2b-256 checksum
How to use checksums
a2733c281251bea0b1d0edf0fb64d8cb25eb17a6cfca22980592efbc693c8f1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / cli_mem-0.5.2-py3-none-any.whl

Download URL cli_mem-0.5.2-py3-none-any.whl
Size 181.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45daa3fc5da529d960098d7688b3f24f1c0c8670ee8dd9e58b8d2cb66e222e69
BLAKE2b-256 checksum
How to use checksums
1e3fef7b60fa88ea1a5a4816a55a2eb1538e686d6a7c34e369ede4e28a05f81d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Security for cli-mem 0.5.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page