Skip to main content

brainbuddy is now terminalcreature. This package is kept only so existing installs hear about the rename. Re-run the bootstrap you installed with, or pipx install terminalcreature; your creature, roster, and settings move over in place. Nothing below is maintained under this name any more.

TERMINAL CREATURE

A terminal pet that levels off your second brain.

A creature that lives in your Claude Code statusline, hatches from an egg,
and grows on a diet of your own memories. It counts your notes and never reads them.


Website  ·  PyPI  ·  Changelog  ·  Report a bug  ·  Contribute

PyPI Python 3.9 to 3.13 CI Zero dependencies MIT

day one                                  a few hundred notes later
┌───────────┐                            ┌───────────┐
│    ___    │  ████░░░░░░  my-brain      │   .\|/.   │  ████░░░░░░  my-brain
│   /   \   │  🥚 Unhatched              │  ( o o )  │  🥚 Drain · Sage Lv65 █████░ +16 XP
│  ( ooo )  │                            │  /|ooo|\  │
│   \___/   │                            │   |___|   │
└───────────┘                            │  /     \  │
                                         └───────────┘

Why brainbuddy exists

A second brain only pays off if you keep writing to it, and nothing in a terminal rewards that. Streaks punish weekends. Timers reward sitting still. brainbuddy rewards the one thing that matters: a durable note landed on disk.

Every note you write is XP. The creature evolves through five forms as your vault grows, and it never loses a level when you tidy up. Not a streak you can drop, not a timer. Feed it or it sits there. That's the whole loop.

How It Works

your notes                 brainbuddy                       statusline
──────────────── ──►  ──────────────────────  ──►  ───────────────────────────
~/.claude/projects/*/     glob + stat only           ┌───────────┐
  memory/*.md             (never open())             │   ( ' ' ) │  🥚 Zask · Adept Lv44
~/notes/**/*.md                │                     │  <|+++|>  │  ███░░░ +12 XP
a structured vault        weighted count = xp        │   /|_|\   │
                               │                     └───────────┘
                     level = 100·√(xp / xp_max)
                               │
                     sprite = f(level, seed)

The egg banks XP from the moment it exists, including everything you'd written before you installed anything. So the first hatch isn't a blank slate, it's a reveal:

$ brainbuddy hatch

  the egg cracks

       _^_
     ( ' ' )
      /$$$\
       ^ ^

  Zask, a Legendary Nim (shiny)
  Lv41 Adept

Quick Start

curl -fsSL https://raw.githubusercontent.com/smejkaldesign/brainbuddy/main/bootstrap.sh | bash

Then, in Claude Code:

/brainbuddy-hatch

That's it. The installer wraps whatever statusline you already have rather than replacing it, lays your first egg, and tells you how to open it. The hatch is a short guided setup the first time: it looks for your notes, offers what it found with a file count each, and opens the egg at whatever level your writing has already earned.

Point it at your notes in the same breath; the bootstrap passes flags straight through to the installer:

curl -fsSL https://raw.githubusercontent.com/smejkaldesign/brainbuddy/main/bootstrap.sh | bash -s -- --folder ~/notes

Requirements

Runtime Python 3.9 or newer, stdlib only. No dependencies, ever.
Installer bash, tar, and either curl or wget. Pipe to bash, not sh.
macOS / Linux Supported, including the stock macOS bash 3.2.
Windows Under WSL or Git Bash. PowerShell and cmd are not, because the statusline shim is a shell script.

The first hatch asks three questions

/brainbuddy-hatch guides the first egg, because the things it can't guess are the ones that decide everything afterwards:

  1. Where do your memories live? It looks for an Obsidian vault, a notes folder, and Claude Code's own memory, then offers what it found with a file count each. That sets provider and vault_root.
  2. Score what's already written, or start from 0? Scoring is the default and opens the egg several forms in. --from-zero baselines what's there so only new notes count, for people who'd rather have the climb.
  3. What's it called? Two fresh ideas from brainbuddy names, your own, or let the egg name itself at the reveal. Until it hatches the statusline just says Unhatched.

Later eggs inherit the first two answers. The name is asked for every egg.

Other ways to install

Route What you get
Clone and run ./install.sh The same installer the bootstrap runs, if you want to read the code first.
pipx install brainbuddy The CLI on your PATH and nothing else. It does not wire your statusline, which is most of what brainbuddy is.
Claude Code plugin Coming soon. The manifest and marketplace listing ship in this repo.
Offline or behind a mirror Set BRAINBUDDY_TARBALL to a URL or a tarball on disk and the bootstrap installs from that.

Installer flags

Flag What it does
--folder <path> count a folder of markdown notes (the usual case for an existing notes dir)
--vault <path> count a structured vault layout
--statusline <cmd> wrap this command instead of the one in settings.json
--inline one-line segment after your statusline instead of the boxed column
--no-wire install the library and commands only, wire it yourself
--no-commands skip the slash commands, when something else already ships them
--uninstall unwire, restore your old statusline, remove the commands

Re-running is safe and is how you pick up new commands. It won't wrap itself twice and it leaves an existing buddy alone.

How the wiring works

The installer wraps your existing statusline rather than editing it. It points statusLine.command at a small generated shim; the shim runs whatever command was there before, on the same stdin Claude Code hands it, then draws the creature to the left of that output. Your own script is never modified. It keeps a settings.json.pre-brainbuddy.bak, and --uninstall puts the original command back.

Project-level statuslines need one manual step. The installer only touches ~/.claude/settings.json. If a repo sets its own statusLine in <repo>/.claude/settings.json, wrap it explicitly, then point the project at the shim:

./install.sh --statusline "/path/to/repo/.claude/statusline.sh"
# then in <repo>/.claude/settings.json:
#   "statusLine": { "type": "command", "command": "~/.claude/brainbuddy/statusline-brainbuddy.sh" }

The Creature

Species and rarity

Eight species. The eyes and the body motif come from the species, so a Bramble is recognisable at a glance.

     _^_            _^_            _^_            _^_
   ( o o )        ( - - )        ( ^ ^ )        ( . . )
   <|ooo|>        <|~~~|>        <|***|>        <|...|>
    /   \          /   \          /   \          /   \
    ^   ^          ^   ^          ^   ^          ^   ^
     Mote           Wisp          Ember           Pip

     _^_            _^_            _^_            _^_
   ( o o )        ( v v )        ( ' ' )        ( > < )
   <|===|>        <|###|>        <|+++|>        <|///|>
    /   \          /   \          /   \          /   \
    ^   ^          ^   ^          ^   ^          ^   ^
     Fen          Bramble          Nim           Quill
Rarity Odds Mark
Common 60%
Uncommon 25% +
Rare 10% *
Epic 4% **
Legendary 1% ***

The mark carries the tier without relying on colour, so it reads on a mono terminal or to a colour-blind user. On top of that, a 1% shiny roll remakes the body motif: symbols become $ (<|+++|> becomes <|$$$|>) and letters go uppercase (<|ooo|> becomes <|OOO|>).

All of it is a pure function of the seed. Hand-editing state.json can't promote a Common into a shiny Legendary; derived values are recomputed on every load and win.

The evolution ladder

Six sprites: the egg, then five forms gaining detail at every step.

    ___        _^_        _^_        \|/       .\|/.     *.\|/.*
   /   \     ( ' ' )    ( ' ' )    ( ' ' )    ( ' ' )   \( ' ' )/
  ( ooo )     /+++\     <|+++|>    <|+++|>    /|+++|\    /|+++|\
   \___/       ^ ^       /   \      /|_|\      |___|     =|___|=
                         ^   ^      ^   ^     /     \    ^     ^

    egg     Hatchling  Fledgling    Adept       Sage    Ascendant
 unhatched     0-19      20-39      40-59      60-79     80-100

The egg is a state, not a level

A buddy is an egg until you hatch it, whatever level it is. Level 0 is a Hatchling, a baby with a face, not an egg. Species, rarity, shiny, and stats are fixed the moment the egg exists, so an unhatched egg shows none of it:

$ brainbuddy card

       ___
      /   \
     ( ooo )
      \___/

  Unhatched
  0 xp eaten and counting
  /brainbuddy-hatch to find out what it is

Eggs bank XP while closed, so waiting costs nothing. A buddy added later with --add starts at 0 and hatches as a Hatchling, because XP banks per creature.


XP and Levelling

Where XP comes from

XP is a weighted count of markdown files in a memory system, so brainbuddy needs one to point at. Three providers, set with /brainbuddy config provider <name>:

Provider Counts Point it somewhere
claude stock Claude Code memory, ~/.claude/projects/*/memory/*.md default, nothing to set
folder every .md under a directory, recursively config vault_root ~/notes
vault a structured vault, weighted per directory config vault_root ~/brain

brainbuddy doctor says which one is live, whether the root is there, what it counted, and what your buddy banked of that:

$ brainbuddy doctor
provider: folder (folder of notes)
root: ~/notes (found)
  notes      9
source xp 18 -> level 10
Zask banked 18 -> level 10 (Hatchling)

A zero reading has three causes, and doctor names the one you've got:

  • the root isn't there: wrong path, or Claude Code hasn't written memory yet
  • the root is real but empty: nothing to do but write things down
  • the root has markdown the provider's layout doesn't match: pointing vault at a plain notes folder does this, and the fix is provider folder

No memory system at all? Then your buddy sits at level 0, which is a fair reading rather than a bug. The installer and doctor both hand you a prompt for it:

"Set up a persistent memory system for this project: one markdown file per durable fact in your memory directory, an index listing them, and write to it as we work."

How levelling works

Durable facts are worth more than session logs because they cost more to produce, and generated index files are excluded so they can't inflate the count for free. claude counts one source at ×3, folder counts every note at ×2, and vault weights per directory:

Source Glob Weight Excluded
memories auto-memory/*.md ×3 MEMORY.md, index.md
knowledge 05-knowledge/*.md ×2 index.md
projects 04-projects/*.md ×2 index.md
decisions memory/decisions/*.md ×2
sessions memory/sessions/*.md ×1
level = min(100, floor(100 * sqrt(xp / xp_max)))

A square root curve, so early memories move the needle hard and later ones don't. Level 100 is fully grown and the curve stops there. xp_max is the XP at level 100 and the one dial that controls pace; the default of 1500 puts a well-established vault around 65.

Level XP needed Roughly
5 4 a couple of notes
10 16 ~5 durable memories
20 61 ~20 durable memories
40 241 ~80 durable memories
65 634 ~211 durable memories
100 1,500 ~500 durable memories

Want it slower? brainbuddy config xp_max 5000 triples the distance. Deleting memories never de-levels anyone: the high-water mark only rises, because tidying up shouldn't be punished.

The session counter

Right of the level bar, the caption shows what your buddy has eaten in this session:

🥚 Neux · Sage Lv66 ██░░░░ +16 XP

It baselines the first time a session draws itself and is tracked per session id, since several are usually open at once. It stays hidden until there's something to show.


The Statusline

density picks how much room the inline segment takes (render, or an --inline install):

Mode Looks like Notes
minimal one glyph, filling as you evolve (◌ ○ ◔ ◑ ◕ ●, or . o c C O @ in ascii)
compact <><> Lv65 default, ~10 columns inline
full <><> Drain Lv65 adds the name
sprite the 5-row creature its own rows, right-aligned to columns
ruler a column ruler a measuring aid, not a creature

compose "<text>" is what the installed shim uses by default: your own text with the creature as a left column, sharing row one. The column is boxed in dark grey; config border false drops the box and gets two rows of height back. sprite_height 3 cuts the creature to three rows and keeps the evolution beats:

┌───────────┐
│   .\|/.   │  ████░░░░░░  my-brain ⎇ main          .\|/.    ████░░░░░░  my-brain ⎇ main
│  ( o o )  │  🥚 Drain · Sage Lv65 █████░         ( o o )   🥚 Drain · Sage Lv65 █████░
│  /|ooo|\  │                                      /|ooo|\
│   |___|   │                                       |___|
│  /     \  │                                      /     \
└───────────┘
   border true                                        border false

Either way the column is pinned to the creature's widest form, so your text doesn't shift the day it evolves into an Ascendant. sprite needs a width and a statusline script is handed no terminal, so density ruler prints a ruler: read the last digit you can see and pass it to config columns <n>.

/brainbuddy-hide takes the creature out without uninstalling anything. XP keeps banking while it's hidden.

The roster

Keep several creatures. Only the focused one gains XP; the others hold their level and wait.

$ brainbuddy list
  ◕ Drain      Lv65   Sage       Common
* ○ Zask       Lv0    Hatchling  Legendary shiny

* = focused (the one gaining xp)

/brainbuddy-new asks before it acts: --replace retires the current buddy and focuses a new egg, --add keeps it active and focuses a new egg. Neither deletes anything. A retired buddy keeps its banked XP and focus <name> brings it back. There's no level requirement, since the tradeoff is identical at level 12 and level 99.


Commands and Settings

brainbuddy new               lay an egg (--replace or --add, --yes to confirm)
brainbuddy hatch [--name <n>] [--from-zero]  open the egg, naming it as it opens
brainbuddy names             two fresh name ideas for the egg
brainbuddy card              the full creature card
brainbuddy list              the roster
brainbuddy focus <name>      choose who banks new xp, un-retires
brainbuddy rename <old> <new>
brainbuddy retire <name>     retires, keeps the record and its xp
brainbuddy hide / show       drop it from the statusline, or bring it back
brainbuddy config [key val]  see settings, or set one
brainbuddy simulate <xp>     preview any level without touching real state
brainbuddy sources           what it can count, and what to do if that's nothing
brainbuddy doctor            what can it see, and why is it zero
brainbuddy doctor --check    the same, plus a version check against pypi
brainbuddy update            ask pypi whether there's a newer brainbuddy
brainbuddy render            the one-line statusline segment
brainbuddy compose "<text>"  your statusline text, creature as a left column
brainbuddy refresh           recompute the xp cache

Five are slash commands in Claude Code, so plain language reaches them without the CLI: /brainbuddy, /brainbuddy-new, /brainbuddy-hatch, /brainbuddy-hide, /brainbuddy-show.

After an install.sh or bootstrap install there's no brainbuddy on your PATH: the library is imported by the statusline, not installed as a binary. The slash commands reach everything you'd normally want. For the rest, alias it, or pipx install brainbuddy:

alias brainbuddy='PYTHONPATH="$HOME/.claude/brainbuddy/lib" python3 -m brainbuddy.cli'
Setting Values Default
provider claude, folder or vault claude
vault_root path, for folder and vault
xp_max XP at level 100, the pace dial 1500
density minimal compact full sprite ruler compact
sprite_height 3 or 5 5
border box the compose column, costs 2 rows true
columns right-align width for sprite 0
unicode true or false true
hidden true or false false
update_check true or false, the once-a-day check behind the ⬆ update chip false

Privacy Promise

Short enough to check yourself.

  • It counts your files without ever opening them. The only filesystem calls in metric.py are glob and stat. It never calls open() on a note, never reads a byte of content, never parses frontmatter. That isn't a promise about what it does with your data; it's a statement that it never has your data.
  • It never prints a path it matched, in any mode including doctor, which reports a home-relative root and counts rather than filenames.
  • All state is local, at ~/.claude/brainbuddy/: the roster, your settings, and the XP cache. Nothing else, nowhere else.
  • The render never opens a socket, and by default neither does anything it starts. No telemetry, no analytics, no phone-home.
  • The only network calls are the ones you allowed. The installer downloading a release tarball from api.github.com; brainbuddy update and doctor --check reading pypi's public package metadata; and, if you opt in with config update_check true, that same version request once a day from the background refresh so the yellow ⬆ update chip can appear. It's off by default and asked as a question. Every one is an unauthenticated GET that sends nothing about you or your memory.

Three tests enforce this. A runtime trap patches every file-reading builtin and asserts none fire during a measurement. A static pass tokenizes metric.py and fails if a reader appears in the code at all. A third guards every socket call, including in the background processes a render spawns: opted out, an aged cache plus a render produces zero network from any process; opted in, exactly one attempt per day, never from the render itself. A leak guard runs in CI and as a pre-push hook, failing the build if an absolute home path or a vault-shaped filename ever reaches the repo.


Under the Hood

The shim wraps, it doesn't edit statusLine.command points at a generated shim; your original command is saved next to it and run by it. A script that already calls brainbuddy is detected and left alone.
A cache on the hot path render reads a cached XP value and spawns the recount in the background, blocking only on a cold start.
High-water mark New XP is the delta above the highest total ever seen, so deleting notes can't take a level away, and a render before your first hatch can't burn the XP waiting for it.
Derived beats persisted Species, rarity, shiny, and accent are recomputed from the seed on every load and overwrite whatever is on disk. Only id, seed, name, hatch time, and banked XP persist.
Counting is all it does The measurement path is glob and stat only, enforced by a runtime trap and a static check.

Repository Structure

brainbuddy/
├── brainbuddy/            # the package, stdlib only
│   ├── cli.py             #   commands
│   ├── render.py          #   statusline segment, compose, card
│   ├── state.py           #   roster, settings, xp cache
│   ├── metric.py          #   providers: glob + stat, nothing else
│   ├── creature.py        #   seed → species, rarity, shiny
│   └── sprites.py         #   stage templates × species motifs
├── commands/              # the five Claude Code slash commands
├── site/                  # the website, deployed by pages.yml
├── install.sh             # wraps your statusline, lays the egg
├── bootstrap.sh           # curl | bash entry: fetches a release, runs install.sh
├── scripts/leak-guard.sh  # fails on machine paths and vault-shaped filenames
├── tests/                 # stdlib test suite, incl. the privacy traps
├── .claude-plugin/        # plugin manifest
└── marketplace/           # marketplace listing

Development

git clone https://github.com/smejkaldesign/brainbuddy && cd brainbuddy
python3 tests/test_brainbuddy.py     # synthetic fixtures in a temp dir; no real memory touched
./scripts/leak-guard.sh              # the same check CI runs
git config core.hooksPath .githooks  # optional pre-push copy of the guard

No virtualenv, nothing to install, no build step. Run the CLI from the clone with python3 -m brainbuddy.cli card. CI runs the suite on Python 3.9 through 3.13, builds the wheel, and installs under the stock macOS bash 3.2.

Contributing

Small project, short rules: stdlib only, Python 3.9 floor, one change per PR, new behavior gets a test, and the privacy tests must keep passing. See CONTRIBUTING.md.

Provenance

Anthropic shipped a terminal pet in Claude Code (/buddy, April 2026). Nice idea, no progression: species and stats are recomputed from your user ID every session and never change. brainbuddy is a clean-room rebuild of the concept with the missing half added, growth you actually earn. No code, species names, sprite art, stat names, or hashing details were taken from it. Not affiliated with or endorsed by Anthropic.

License

MIT. See LICENSE. © 2026 Smejkal Design.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brainbuddy-1.3.0.tar.gz (65.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brainbuddy-1.3.0-py3-none-any.whl (41.4 kB view details)

Uploaded Python 3

File details

Details for the file brainbuddy-1.3.0.tar.gz.

File metadata

  • Download URL: brainbuddy-1.3.0.tar.gz
  • Upload date:
  • Size: 65.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for brainbuddy-1.3.0.tar.gz
Algorithm Hash digest
SHA256 eccea49f78090b706dd6d175d23333628aa3a7b35af2e8e60b22278ef0934e81
MD5 f30d592b7a078dcac0ee3ead78c9d139
BLAKE2b-256 85781b1f6bd46ca1c88b40d68d14ff8a875d51c096b8810e53a094c3aa6067f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for brainbuddy-1.3.0.tar.gz:

Publisher: publish.yml on smejkaldesign/brainbuddy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file brainbuddy-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: brainbuddy-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 41.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for brainbuddy-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8fb8950fd15b8c7e119dd73d5f3aaeec95880e1a67bb86c246940460600851a
MD5 f3b2c51c339cc2b855e5a4f9a3f3dab6
BLAKE2b-256 e84ca68ecff5798889ae942183ab73640db6ba5e39420f9963e4b17152f1a356

See more details on using hashes here.

Provenance

The following attestation bundles were made for brainbuddy-1.3.0-py3-none-any.whl:

Publisher: publish.yml on smejkaldesign/brainbuddy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

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