terminalcreature 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- terminalcreature-2.0.0/.gitignore +6 -0
- terminalcreature-2.0.0/CHANGELOG.md +91 -0
- terminalcreature-2.0.0/LICENSE +21 -0
- terminalcreature-2.0.0/PKG-INFO +502 -0
- terminalcreature-2.0.0/README.md +459 -0
- terminalcreature-2.0.0/commands/creature-hatch.md +146 -0
- terminalcreature-2.0.0/commands/creature-hide.md +26 -0
- terminalcreature-2.0.0/commands/creature-new.md +54 -0
- terminalcreature-2.0.0/commands/creature-show.md +25 -0
- terminalcreature-2.0.0/commands/creature.md +89 -0
- terminalcreature-2.0.0/install.sh +404 -0
- terminalcreature-2.0.0/pyproject.toml +52 -0
- terminalcreature-2.0.0/scripts/leak-guard.sh +33 -0
- terminalcreature-2.0.0/scripts/plugin-session-start.sh +50 -0
- terminalcreature-2.0.0/terminalcreature/__init__.py +3 -0
- terminalcreature-2.0.0/terminalcreature/cli.py +647 -0
- terminalcreature-2.0.0/terminalcreature/creature.py +142 -0
- terminalcreature-2.0.0/terminalcreature/metric.py +145 -0
- terminalcreature-2.0.0/terminalcreature/release.py +105 -0
- terminalcreature-2.0.0/terminalcreature/render.py +557 -0
- terminalcreature-2.0.0/terminalcreature/sprites.py +150 -0
- terminalcreature-2.0.0/terminalcreature/state.py +354 -0
- terminalcreature-2.0.0/tests/test_terminalcreature.py +1335 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes, newest first. Versions follow semver; the version lives in
|
|
4
|
+
`terminalcreature/__init__.py` and each release is the matching `v*` tag.
|
|
5
|
+
|
|
6
|
+
## 2.0.0
|
|
7
|
+
|
|
8
|
+
- Renamed: brainbuddy is now **terminalcreature**. New package, module, CLI
|
|
9
|
+
(`terminalcreature`), slash commands (`/creature`, `/creature-hatch`,
|
|
10
|
+
`/creature-new`, `/creature-hide`, `/creature-show`), state dir
|
|
11
|
+
(`~/.claude/terminalcreature/`), shim, and `TERMINALCREATURE_TARBALL`.
|
|
12
|
+
- The installer migrates an existing brainbuddy install in place: state, XP
|
|
13
|
+
cache, and settings move over, the old command files are removed, and a
|
|
14
|
+
stub is left at the old shim path so project-level statuslines keep
|
|
15
|
+
drawing. Re-run the bootstrap you installed with; the old URL redirects.
|
|
16
|
+
- Creatures are unchanged. The seed salt keeps its original value on
|
|
17
|
+
purpose, so every buddy hatched under the old name is the same creature.
|
|
18
|
+
- New wordmark on the site and README; site at terminalcreature.com.
|
|
19
|
+
|
|
20
|
+
## 1.3.0
|
|
21
|
+
|
|
22
|
+
- brainbuddy is now **terminalcreature**. This is the last release under the
|
|
23
|
+
old name and it changes nothing else. To move over, re-run the bootstrap you
|
|
24
|
+
installed with (the old URL redirects), or `pipx install terminalcreature`.
|
|
25
|
+
Your creature, roster, and settings migrate in place. New home:
|
|
26
|
+
<https://github.com/smejkaldesign/terminalcreature>.
|
|
27
|
+
|
|
28
|
+
## 1.2.0
|
|
29
|
+
|
|
30
|
+
- Opt-in update alert: `config update_check true` lets the background refresh
|
|
31
|
+
ask pypi once a day for the latest version number, and a yellow `⬆ update`
|
|
32
|
+
chip appears at the end of the statusline when a release is out, leaving on
|
|
33
|
+
its own after you upgrade. Off by default, asked once after the hatch, and
|
|
34
|
+
the render path still never touches the network in either setting; the
|
|
35
|
+
socket test now proves both. Existing installs get a one-time offer from
|
|
36
|
+
`/brainbuddy`, and `doctor` reports the check's state and last run.
|
|
37
|
+
|
|
38
|
+
## 1.1.0
|
|
39
|
+
|
|
40
|
+
- An egg renders as Unhatched everywhere, never as a name. The name is chosen
|
|
41
|
+
at the hatch: two ideas from the new `brainbuddy names`, your own via
|
|
42
|
+
`hatch --name <n>`, or let it name itself and find out at the reveal.
|
|
43
|
+
- The session counter (`+N XP`) moved to the right of the progress bar, so the
|
|
44
|
+
caption reads level, bar, then what this session added.
|
|
45
|
+
|
|
46
|
+
## 1.0.0
|
|
47
|
+
|
|
48
|
+
The first public release: install it with one command, and the owner's costs
|
|
49
|
+
stay at zero because everything lives on your machine.
|
|
50
|
+
|
|
51
|
+
- Fixed: a background refresh no longer writes back the roster it loaded before
|
|
52
|
+
the scan, which could silently revert an egg laid or hatched while it ran.
|
|
53
|
+
- Fixed: the leak guard no longer skips files whose names contain spaces, and it
|
|
54
|
+
now catches capitalized and `/home/` paths too.
|
|
55
|
+
- Fixed: `--uninstall` recognizes a statusline that names the shim through `~`
|
|
56
|
+
instead of deleting the shim and leaving the statusline pointing at it.
|
|
57
|
+
- Fixed: a non-numeric `config xp_max`/`columns`/`sprite_height` or `simulate`
|
|
58
|
+
argument gets a one-line answer instead of a traceback carrying a home path.
|
|
59
|
+
- Upgrades keep your buddy. `state.json` carries a schema version, and a file
|
|
60
|
+
written by any earlier version is brought forward on load: nothing is dropped,
|
|
61
|
+
missing fields are filled, and a focus pointing at a creature that isn't there
|
|
62
|
+
falls back to the roster rather than rendering as no buddy at all.
|
|
63
|
+
- `brainbuddy update` and `brainbuddy doctor --check` ask PyPI whether there's a
|
|
64
|
+
newer version. They're the only two commands that go online, and only when you
|
|
65
|
+
run them; the statusline never does, and a test asserts it. No network is not
|
|
66
|
+
an error, it's one line and exit 0.
|
|
67
|
+
- `doctor` names a project-level statusline. If a repo's own
|
|
68
|
+
`.claude/settings.json` sets `statusLine`, that wins inside the repo and the
|
|
69
|
+
creature never draws there. Doctor says so and prints the two-step fix with
|
|
70
|
+
your own command already in it. It only ever reads that file.
|
|
71
|
+
- Windows: `install.sh` and `bootstrap.sh` name WSL and Git Bash when they're
|
|
72
|
+
run by something that isn't bash. There's no PowerShell version.
|
|
73
|
+
- Hatching with no memory system is a moment rather than a silent Lv0. The
|
|
74
|
+
reveal is unchanged; the level is followed by what it means and what to feed.
|
|
75
|
+
- One-line install: `bootstrap.sh` fetches the latest tagged release, unpacks it
|
|
76
|
+
to a temp dir and runs its `install.sh` with whatever flags you passed, so the
|
|
77
|
+
usual path is one command instead of a clone. It falls back to the default
|
|
78
|
+
branch until a release exists.
|
|
79
|
+
- PyPI packaging: `pipx install brainbuddy` / `uvx brainbuddy` now work; the
|
|
80
|
+
installed entry point is the same `brainbuddy` command the shim calls.
|
|
81
|
+
- Release workflow: tagging `vX.Y.Z` tests, builds, and publishes to PyPI via
|
|
82
|
+
trusted publishing.
|
|
83
|
+
- `brainbuddy doctor` reports the installed version.
|
|
84
|
+
- Installable as a Claude Code plugin: a `SessionStart` hook notices an unwired
|
|
85
|
+
install and offers `install.sh --no-commands`, which ends on the same egg.
|
|
86
|
+
|
|
87
|
+
## 0.1.0
|
|
88
|
+
|
|
89
|
+
Everything before packaging: the creature, the egg-and-hatch flow, three XP
|
|
90
|
+
providers, the statusline wrap installer, five slash commands, the leak guard,
|
|
91
|
+
and the test suite.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eric Smejkal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: terminalcreature
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: a terminal pet for your Claude Code statusline that evolves as your memory grows
|
|
5
|
+
Project-URL: Homepage, https://github.com/smejkaldesign/terminalcreature
|
|
6
|
+
Project-URL: Changelog, https://github.com/smejkaldesign/terminalcreature/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/smejkaldesign/terminalcreature/issues
|
|
8
|
+
Author-email: Smejkal Design <eric@smejkal.design>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Eric Smejkal
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: claude-code,pet,statusline,tamagotchi,terminal
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: MacOS
|
|
37
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Topic :: Games/Entertainment
|
|
40
|
+
Classifier: Topic :: Terminals
|
|
41
|
+
Requires-Python: >=3.9
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<img src="site/assets/terminalcreature-logo.png" width="247" alt="TERMINAL CREATURE" />
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<h3 align="center">A terminal pet that levels off your second brain.</h3>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
A creature that lives in your Claude Code statusline, hatches from an egg,<br />
|
|
52
|
+
and grows on a diet of your own memories. It counts your notes and never reads them.
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<br />
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<a href="https://terminalcreature.com"><strong>Website</strong></a> ·
|
|
59
|
+
<a href="https://pypi.org/project/terminalcreature/"><strong>PyPI</strong></a> ·
|
|
60
|
+
<a href="CHANGELOG.md"><strong>Changelog</strong></a> ·
|
|
61
|
+
<a href="https://github.com/smejkaldesign/terminalcreature/issues"><strong>Report a bug</strong></a> ·
|
|
62
|
+
<a href="CONTRIBUTING.md"><strong>Contribute</strong></a>
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
<a href="https://pypi.org/project/terminalcreature/"><img src="https://img.shields.io/pypi/v/terminalcreature?color=ffb627&label=pypi" alt="PyPI" /></a>
|
|
67
|
+
<img src="https://img.shields.io/badge/python-3.9%E2%80%933.13-3776AB?logo=python&logoColor=white" alt="Python 3.9 to 3.13" />
|
|
68
|
+
<a href="https://github.com/smejkaldesign/terminalcreature/actions/workflows/ci.yml"><img src="https://github.com/smejkaldesign/terminalcreature/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
69
|
+
<img src="https://img.shields.io/badge/dependencies-0-4ade80" alt="Zero dependencies" />
|
|
70
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT" /></a>
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
<pre align="center">
|
|
74
|
+
day one a few hundred notes later
|
|
75
|
+
┌───────────┐ ┌───────────┐
|
|
76
|
+
│ ___ │ ████░░░░░░ my-brain │ .\|/. │ ████░░░░░░ my-brain
|
|
77
|
+
│ / \ │ 🥚 Unhatched │ ( o o ) │ 🥚 Drain · Sage Lv65 █████░ +16 XP
|
|
78
|
+
│ ( ooo ) │ │ /|ooo|\ │
|
|
79
|
+
│ \___/ │ │ |___| │
|
|
80
|
+
└───────────┘ │ / \ │
|
|
81
|
+
└───────────┘
|
|
82
|
+
</pre>
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Why terminalcreature exists
|
|
87
|
+
|
|
88
|
+
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. terminalcreature rewards the one thing that matters: a durable note landed on disk.
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
your notes terminalcreature statusline
|
|
96
|
+
──────────────── ──► ────────────────────── ──► ───────────────────────────
|
|
97
|
+
~/.claude/projects/*/ glob + stat only ┌───────────┐
|
|
98
|
+
memory/*.md (never open()) │ ( ' ' ) │ 🥚 Zask · Adept Lv44
|
|
99
|
+
~/notes/**/*.md │ │ <|+++|> │ ███░░░ +12 XP
|
|
100
|
+
a structured vault weighted count = xp │ /|_|\ │
|
|
101
|
+
│ └───────────┘
|
|
102
|
+
level = 100·√(xp / xp_max)
|
|
103
|
+
│
|
|
104
|
+
sprite = f(level, seed)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
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:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ terminalcreature hatch
|
|
111
|
+
|
|
112
|
+
the egg cracks
|
|
113
|
+
|
|
114
|
+
_^_
|
|
115
|
+
( ' ' )
|
|
116
|
+
/$$$\
|
|
117
|
+
^ ^
|
|
118
|
+
|
|
119
|
+
Zask, a Legendary Nim (shiny)
|
|
120
|
+
Lv41 Adept
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
curl -fsSL https://raw.githubusercontent.com/smejkaldesign/terminalcreature/main/bootstrap.sh | bash
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Then, in Claude Code:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
/creature-hatch
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
Point it at your notes in the same breath; the bootstrap passes flags straight through to the installer:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
curl -fsSL https://raw.githubusercontent.com/smejkaldesign/terminalcreature/main/bootstrap.sh | bash -s -- --folder ~/notes
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Requirements
|
|
146
|
+
|
|
147
|
+
| | |
|
|
148
|
+
| :--- | :--- |
|
|
149
|
+
| **Runtime** | Python 3.9 or newer, stdlib only. No dependencies, ever. |
|
|
150
|
+
| **Installer** | bash, tar, and either curl or wget. Pipe to `bash`, not `sh`. |
|
|
151
|
+
| **macOS / Linux** | Supported, including the stock macOS bash 3.2. |
|
|
152
|
+
| **Windows** | Under **WSL** or **Git Bash**. PowerShell and cmd are not, because the statusline shim is a shell script. |
|
|
153
|
+
|
|
154
|
+
### The first hatch asks three questions
|
|
155
|
+
|
|
156
|
+
`/creature-hatch` guides the first egg, because the things it can't guess are the ones that decide everything afterwards:
|
|
157
|
+
|
|
158
|
+
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`.
|
|
159
|
+
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.
|
|
160
|
+
3. **What's it called?** Two fresh ideas from `terminalcreature names`, your own, or let the egg name itself at the reveal. Until it hatches the statusline just says Unhatched.
|
|
161
|
+
|
|
162
|
+
Later eggs inherit the first two answers. The name is asked for every egg.
|
|
163
|
+
|
|
164
|
+
### Other ways to install
|
|
165
|
+
|
|
166
|
+
| Route | What you get |
|
|
167
|
+
| :--- | :--- |
|
|
168
|
+
| **Clone and run `./install.sh`** | The same installer the bootstrap runs, if you want to read the code first. |
|
|
169
|
+
| **`pipx install terminalcreature`** | The CLI on your PATH and nothing else. It does not wire your statusline, which is most of what terminalcreature is. |
|
|
170
|
+
| **Claude Code plugin** | Coming soon. The manifest and marketplace listing ship in this repo. |
|
|
171
|
+
| **Offline or behind a mirror** | Set `TERMINALCREATURE_TARBALL` to a URL or a tarball on disk and the bootstrap installs from that. |
|
|
172
|
+
|
|
173
|
+
### Installer flags
|
|
174
|
+
|
|
175
|
+
| Flag | What it does |
|
|
176
|
+
| :--- | :--- |
|
|
177
|
+
| `--folder <path>` | count a folder of markdown notes (the usual case for an existing notes dir) |
|
|
178
|
+
| `--vault <path>` | count a structured vault layout |
|
|
179
|
+
| `--statusline <cmd>` | wrap this command instead of the one in `settings.json` |
|
|
180
|
+
| `--inline` | one-line segment after your statusline instead of the boxed column |
|
|
181
|
+
| `--no-wire` | install the library and commands only, wire it yourself |
|
|
182
|
+
| `--no-commands` | skip the slash commands, when something else already ships them |
|
|
183
|
+
| `--uninstall` | unwire, restore your old statusline, remove the commands |
|
|
184
|
+
|
|
185
|
+
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.
|
|
186
|
+
|
|
187
|
+
### How the wiring works
|
|
188
|
+
|
|
189
|
+
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-terminalcreature.bak`, and `--uninstall` puts the original command back.
|
|
190
|
+
|
|
191
|
+
**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:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
./install.sh --statusline "/path/to/repo/.claude/statusline.sh"
|
|
195
|
+
# then in <repo>/.claude/settings.json:
|
|
196
|
+
# "statusLine": { "type": "command", "command": "~/.claude/terminalcreature/statusline-terminalcreature.sh" }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## The Creature
|
|
202
|
+
|
|
203
|
+
### Species and rarity
|
|
204
|
+
|
|
205
|
+
Eight species. The eyes and the body motif come from the species, so a Bramble is recognisable at a glance.
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
_^_ _^_ _^_ _^_
|
|
209
|
+
( o o ) ( - - ) ( ^ ^ ) ( . . )
|
|
210
|
+
<|ooo|> <|~~~|> <|***|> <|...|>
|
|
211
|
+
/ \ / \ / \ / \
|
|
212
|
+
^ ^ ^ ^ ^ ^ ^ ^
|
|
213
|
+
Mote Wisp Ember Pip
|
|
214
|
+
|
|
215
|
+
_^_ _^_ _^_ _^_
|
|
216
|
+
( o o ) ( v v ) ( ' ' ) ( > < )
|
|
217
|
+
<|===|> <|###|> <|+++|> <|///|>
|
|
218
|
+
/ \ / \ / \ / \
|
|
219
|
+
^ ^ ^ ^ ^ ^ ^ ^
|
|
220
|
+
Fen Bramble Nim Quill
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Rarity | Odds | Mark |
|
|
224
|
+
| :--- | :--- | :--- |
|
|
225
|
+
| Common | 60% | |
|
|
226
|
+
| Uncommon | 25% | `+` |
|
|
227
|
+
| Rare | 10% | `*` |
|
|
228
|
+
| Epic | 4% | `**` |
|
|
229
|
+
| Legendary | 1% | `***` |
|
|
230
|
+
|
|
231
|
+
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|>`).
|
|
232
|
+
|
|
233
|
+
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.
|
|
234
|
+
|
|
235
|
+
### The evolution ladder
|
|
236
|
+
|
|
237
|
+
Six sprites: the egg, then five forms gaining detail at every step.
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
___ _^_ _^_ \|/ .\|/. *.\|/.*
|
|
241
|
+
/ \ ( ' ' ) ( ' ' ) ( ' ' ) ( ' ' ) \( ' ' )/
|
|
242
|
+
( ooo ) /+++\ <|+++|> <|+++|> /|+++|\ /|+++|\
|
|
243
|
+
\___/ ^ ^ / \ /|_|\ |___| =|___|=
|
|
244
|
+
^ ^ ^ ^ / \ ^ ^
|
|
245
|
+
|
|
246
|
+
egg Hatchling Fledgling Adept Sage Ascendant
|
|
247
|
+
unhatched 0-19 20-39 40-59 60-79 80-100
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### The egg is a state, not a level
|
|
251
|
+
|
|
252
|
+
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:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
$ terminalcreature card
|
|
256
|
+
|
|
257
|
+
___
|
|
258
|
+
/ \
|
|
259
|
+
( ooo )
|
|
260
|
+
\___/
|
|
261
|
+
|
|
262
|
+
Unhatched
|
|
263
|
+
0 xp eaten and counting
|
|
264
|
+
/creature-hatch to find out what it is
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**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.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## XP and Levelling
|
|
272
|
+
|
|
273
|
+
### Where XP comes from
|
|
274
|
+
|
|
275
|
+
XP is a weighted count of markdown files in a memory system, so terminalcreature needs one to point at. Three providers, set with `/creature config provider <name>`:
|
|
276
|
+
|
|
277
|
+
| Provider | Counts | Point it somewhere |
|
|
278
|
+
| :--- | :--- | :--- |
|
|
279
|
+
| `claude` | stock Claude Code memory, `~/.claude/projects/*/memory/*.md` | default, nothing to set |
|
|
280
|
+
| `folder` | every `.md` under a directory, recursively | `config vault_root ~/notes` |
|
|
281
|
+
| `vault` | a structured vault, weighted per directory | `config vault_root ~/brain` |
|
|
282
|
+
|
|
283
|
+
`terminalcreature doctor` says which one is live, whether the root is there, what it counted, and what your buddy banked of that:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
$ terminalcreature doctor
|
|
287
|
+
provider: folder (folder of notes)
|
|
288
|
+
root: ~/notes (found)
|
|
289
|
+
notes 9
|
|
290
|
+
source xp 18 -> level 10
|
|
291
|
+
Zask banked 18 -> level 10 (Hatchling)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
A zero reading has three causes, and `doctor` names the one you've got:
|
|
295
|
+
|
|
296
|
+
- **the root isn't there**: wrong path, or Claude Code hasn't written memory yet
|
|
297
|
+
- **the root is real but empty**: nothing to do but write things down
|
|
298
|
+
- **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`
|
|
299
|
+
|
|
300
|
+
**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:
|
|
301
|
+
|
|
302
|
+
> "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."
|
|
303
|
+
|
|
304
|
+
### How levelling works
|
|
305
|
+
|
|
306
|
+
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:
|
|
307
|
+
|
|
308
|
+
| Source | Glob | Weight | Excluded |
|
|
309
|
+
| :--- | :--- | :--- | :--- |
|
|
310
|
+
| memories | `auto-memory/*.md` | ×3 | `MEMORY.md`, `index.md` |
|
|
311
|
+
| knowledge | `05-knowledge/*.md` | ×2 | `index.md` |
|
|
312
|
+
| projects | `04-projects/*.md` | ×2 | `index.md` |
|
|
313
|
+
| decisions | `memory/decisions/*.md` | ×2 | |
|
|
314
|
+
| sessions | `memory/sessions/*.md` | ×1 | |
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
level = min(100, floor(100 * sqrt(xp / xp_max)))
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
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.
|
|
321
|
+
|
|
322
|
+
| Level | XP needed | Roughly |
|
|
323
|
+
| :--- | :--- | :--- |
|
|
324
|
+
| 5 | 4 | a couple of notes |
|
|
325
|
+
| 10 | 16 | ~5 durable memories |
|
|
326
|
+
| 20 | 61 | ~20 durable memories |
|
|
327
|
+
| 40 | 241 | ~80 durable memories |
|
|
328
|
+
| 65 | 634 | ~211 durable memories |
|
|
329
|
+
| 100 | 1,500 | ~500 durable memories |
|
|
330
|
+
|
|
331
|
+
Want it slower? `terminalcreature 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.
|
|
332
|
+
|
|
333
|
+
### The session counter
|
|
334
|
+
|
|
335
|
+
Right of the level bar, the caption shows what your buddy has eaten **in this session**:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
🥚 Neux · Sage Lv66 ██░░░░ +16 XP
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
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.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## The Statusline
|
|
346
|
+
|
|
347
|
+
`density` picks how much room the **inline** segment takes (`render`, or an `--inline` install):
|
|
348
|
+
|
|
349
|
+
| Mode | Looks like | Notes |
|
|
350
|
+
| :--- | :--- | :--- |
|
|
351
|
+
| `minimal` | `◔` | one glyph, filling as you evolve (`◌ ○ ◔ ◑ ◕ ●`, or `. o c C O @` in ascii) |
|
|
352
|
+
| `compact` | `<><> Lv65` | default, ~10 columns inline |
|
|
353
|
+
| `full` | `<><> Drain Lv65` | adds the name |
|
|
354
|
+
| `sprite` | the 5-row creature | its own rows, right-aligned to `columns` |
|
|
355
|
+
| `ruler` | a column ruler | a measuring aid, not a creature |
|
|
356
|
+
|
|
357
|
+
`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:
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
┌───────────┐
|
|
361
|
+
│ .\|/. │ ████░░░░░░ my-brain ⎇ main .\|/. ████░░░░░░ my-brain ⎇ main
|
|
362
|
+
│ ( o o ) │ 🥚 Drain · Sage Lv65 █████░ ( o o ) 🥚 Drain · Sage Lv65 █████░
|
|
363
|
+
│ /|ooo|\ │ /|ooo|\
|
|
364
|
+
│ |___| │ |___|
|
|
365
|
+
│ / \ │ / \
|
|
366
|
+
└───────────┘
|
|
367
|
+
border true border false
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
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>`.
|
|
371
|
+
|
|
372
|
+
`/creature-hide` takes the creature out without uninstalling anything. XP keeps banking while it's hidden.
|
|
373
|
+
|
|
374
|
+
### The roster
|
|
375
|
+
|
|
376
|
+
Keep several creatures. Only the **focused** one gains XP; the others hold their level and wait.
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
$ terminalcreature list
|
|
380
|
+
◕ Drain Lv65 Sage Common
|
|
381
|
+
* ○ Zask Lv0 Hatchling Legendary shiny
|
|
382
|
+
|
|
383
|
+
* = focused (the one gaining xp)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`/creature-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.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Commands and Settings
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
terminalcreature new lay an egg (--replace or --add, --yes to confirm)
|
|
394
|
+
terminalcreature hatch [--name <n>] [--from-zero] open the egg, naming it as it opens
|
|
395
|
+
terminalcreature names two fresh name ideas for the egg
|
|
396
|
+
terminalcreature card the full creature card
|
|
397
|
+
terminalcreature list the roster
|
|
398
|
+
terminalcreature focus <name> choose who banks new xp, un-retires
|
|
399
|
+
terminalcreature rename <old> <new>
|
|
400
|
+
terminalcreature retire <name> retires, keeps the record and its xp
|
|
401
|
+
terminalcreature hide / show drop it from the statusline, or bring it back
|
|
402
|
+
terminalcreature config [key val] see settings, or set one
|
|
403
|
+
terminalcreature simulate <xp> preview any level without touching real state
|
|
404
|
+
terminalcreature sources what it can count, and what to do if that's nothing
|
|
405
|
+
terminalcreature doctor what can it see, and why is it zero
|
|
406
|
+
terminalcreature doctor --check the same, plus a version check against pypi
|
|
407
|
+
terminalcreature update ask pypi whether there's a newer terminalcreature
|
|
408
|
+
terminalcreature render the one-line statusline segment
|
|
409
|
+
terminalcreature compose "<text>" your statusline text, creature as a left column
|
|
410
|
+
terminalcreature refresh recompute the xp cache
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Five are slash commands in Claude Code, so plain language reaches them without the CLI: `/creature`, `/creature-new`, `/creature-hatch`, `/creature-hide`, `/creature-show`.
|
|
414
|
+
|
|
415
|
+
After an `install.sh` or bootstrap install there's no `terminalcreature` 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 terminalcreature`:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
alias terminalcreature='PYTHONPATH="$HOME/.claude/terminalcreature/lib" python3 -m terminalcreature.cli'
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
| Setting | Values | Default |
|
|
422
|
+
| :--- | :--- | :--- |
|
|
423
|
+
| `provider` | `claude`, `folder` or `vault` | `claude` |
|
|
424
|
+
| `vault_root` | path, for `folder` and `vault` | |
|
|
425
|
+
| `xp_max` | XP at level 100, the pace dial | `1500` |
|
|
426
|
+
| `density` | `minimal` `compact` `full` `sprite` `ruler` | `compact` |
|
|
427
|
+
| `sprite_height` | `3` or `5` | `5` |
|
|
428
|
+
| `border` | box the `compose` column, costs 2 rows | `true` |
|
|
429
|
+
| `columns` | right-align width for `sprite` | `0` |
|
|
430
|
+
| `unicode` | `true` or `false` | `true` |
|
|
431
|
+
| `hidden` | `true` or `false` | `false` |
|
|
432
|
+
| `update_check` | `true` or `false`, the once-a-day check behind the `⬆ update` chip | `false` |
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Privacy Promise
|
|
437
|
+
|
|
438
|
+
Short enough to check yourself.
|
|
439
|
+
|
|
440
|
+
- **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.
|
|
441
|
+
- **It never prints a path it matched**, in any mode including `doctor`, which reports a home-relative root and counts rather than filenames.
|
|
442
|
+
- **All state is local**, at `~/.claude/terminalcreature/`: the roster, your settings, and the XP cache. Nothing else, nowhere else.
|
|
443
|
+
- **The render never opens a socket, and by default neither does anything it starts.** No telemetry, no analytics, no phone-home.
|
|
444
|
+
- **The only network calls are the ones you allowed.** The installer downloading a release tarball from `api.github.com`; `terminalcreature 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.
|
|
445
|
+
|
|
446
|
+
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.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Under the Hood
|
|
451
|
+
|
|
452
|
+
| | |
|
|
453
|
+
| :--- | :--- |
|
|
454
|
+
| **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 terminalcreature is detected and left alone. |
|
|
455
|
+
| **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. |
|
|
456
|
+
| **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. |
|
|
457
|
+
| **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. |
|
|
458
|
+
| **Counting is all it does** | The measurement path is `glob` and `stat` only, enforced by a runtime trap and a static check. |
|
|
459
|
+
|
|
460
|
+
## Repository Structure
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
terminalcreature/
|
|
464
|
+
├── terminalcreature/ # the package, stdlib only
|
|
465
|
+
│ ├── cli.py # commands
|
|
466
|
+
│ ├── render.py # statusline segment, compose, card
|
|
467
|
+
│ ├── state.py # roster, settings, xp cache
|
|
468
|
+
│ ├── metric.py # providers: glob + stat, nothing else
|
|
469
|
+
│ ├── creature.py # seed → species, rarity, shiny
|
|
470
|
+
│ └── sprites.py # stage templates × species motifs
|
|
471
|
+
├── commands/ # the five Claude Code slash commands
|
|
472
|
+
├── site/ # the website, deployed by pages.yml
|
|
473
|
+
├── install.sh # wraps your statusline, lays the egg
|
|
474
|
+
├── bootstrap.sh # curl | bash entry: fetches a release, runs install.sh
|
|
475
|
+
├── scripts/leak-guard.sh # fails on machine paths and vault-shaped filenames
|
|
476
|
+
├── tests/ # stdlib test suite, incl. the privacy traps
|
|
477
|
+
├── .claude-plugin/ # plugin manifest
|
|
478
|
+
└── marketplace/ # marketplace listing
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Development
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
git clone https://github.com/smejkaldesign/terminalcreature && cd terminalcreature
|
|
485
|
+
python3 tests/test_terminalcreature.py # synthetic fixtures in a temp dir; no real memory touched
|
|
486
|
+
./scripts/leak-guard.sh # the same check CI runs
|
|
487
|
+
git config core.hooksPath .githooks # optional pre-push copy of the guard
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
No virtualenv, nothing to install, no build step. Run the CLI from the clone with `python3 -m terminalcreature.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.
|
|
491
|
+
|
|
492
|
+
## Contributing
|
|
493
|
+
|
|
494
|
+
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](CONTRIBUTING.md).
|
|
495
|
+
|
|
496
|
+
## Provenance
|
|
497
|
+
|
|
498
|
+
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. terminalcreature 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.
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT. See [LICENSE](LICENSE). © 2026 Smejkal Design.
|