opencode-buddy 0.2.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 opencode-buddy contributors
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.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # opencode-buddy
2
+
3
+ A virtual ASCII pet companion for [opencode](https://opencode.ai). Use it as a `/buddy` slash command inside opencode, or as a tmux side pane next to it.
4
+
5
+ ```
6
+ opencode TUI buddy pane
7
+ ┌──────────────────────┐ ┌──────────────────────┐
8
+ │ │ │ ╭─ Quack the duck ─╮ │
9
+ │ your opencode │ │ __ │ │
10
+ │ TUI here │ │ <(o )___ │ │
11
+ │ │ │ ( ._> / │ │
12
+ │ │ │ `--' │ │
13
+ │ │ │ │ │
14
+ │ │ │ hunger ████████░░ 79 │ │
15
+ │ │ │ happy ████████░░ 79 │ │
16
+ │ │ │ energy ██████████ 100 │ │
17
+ │ │ │ keys: f feed · ... │ │
18
+ └──────────────────────┘ └──────────────────────┘
19
+ ```
20
+
21
+ The buddy reacts to what you're doing — idles when you idle, cheers when opencode finishes a turn, gets scared when a session errors, falls asleep when energy runs low.
22
+
23
+ ## Why?
24
+
25
+ Claude Code v2.1.88 had a fun `/buddy` easter egg (an ASCII pet companion). When Anthropic removed it in v2.1.97 the community revolted. This is the opencode equivalent: your own buddy, open source, never going to be taken away.
26
+
27
+ ## Install
28
+
29
+ Requires Node.js ≥ 18. tmux is only needed for the side-pane mode (Mode B).
30
+
31
+ ### Mode A: `/buddy` inside opencode (recommended)
32
+
33
+ ```bash
34
+ npm install -g opencode-buddy
35
+ opencode-buddy install
36
+ ```
37
+
38
+ This writes `~/.config/opencode/commands/buddy.md` and adds
39
+ `opencode-buddy` to your `opencode.json` `plugin` list. **Restart
40
+ opencode.** Then in the TUI:
41
+
42
+ ```
43
+ /buddy show current status
44
+ /buddy feed feed the buddy
45
+ /buddy play play with the buddy (+xp)
46
+ /buddy rest tuck the buddy in
47
+ /buddy switch cat become a cat
48
+ /buddy switch dragon become a dragon
49
+ /buddy rename Mia rename
50
+ /buddy hatch dragon Sparky start fresh
51
+ /buddy ascii render ASCII art inline
52
+ /buddy help list all actions
53
+ ```
54
+
55
+ The buddy's mood is updated automatically: `session.idle` makes it
56
+ celebrate, `session.error` makes it scared, low energy sends it to sleep.
57
+
58
+ ### Mode B: tmux side pane (optional, for a permanent companion)
59
+
60
+ ```bash
61
+ npm install -g opencode-buddy
62
+ ```
63
+
64
+ In any tmux pane (next to your opencode pane):
65
+
66
+ ```bash
67
+ opencode-buddy start
68
+ ```
69
+
70
+ A 28-column side pane splits off to the right and the buddy appears
71
+ inside it, rendered in full ASCII with color. Press keys in the buddy
72
+ pane to interact:
73
+
74
+ | Key | Action |
75
+ | --- | --- |
76
+ | `f` | Feed (+25 hunger) |
77
+ | `p` | Play (+20 happiness, +5 xp) |
78
+ | `z` | Rest (+30 energy) |
79
+ | `r` | Rename (prompts in the pane) |
80
+ | `s` | Switch species (cycles through 6) |
81
+ | `n` | Hatch a new buddy (resets state) |
82
+ | `q` | Quit (kills the side pane) |
83
+
84
+ **Modes A and B share the same persisted state** at
85
+ `~/.config/opencode-buddy/state.json`, so feeding the buddy via
86
+ `/buddy feed` in the TUI will also be reflected in the side pane and
87
+ vice versa.
88
+
89
+ ### Install from source
90
+
91
+ ```bash
92
+ git clone https://github.com/AMark-CS/opencode-buddy
93
+ cd opencode-buddy
94
+ npm install # only for plugin dev deps
95
+ npm link # adds `opencode-buddy` to PATH
96
+ opencode-buddy install
97
+ ```
98
+
99
+ ### Uninstall
100
+
101
+ ```bash
102
+ opencode-buddy uninstall
103
+ npm uninstall -g opencode-buddy
104
+ ```
105
+
106
+ ## Headless commands
107
+
108
+ ```bash
109
+ opencode-buddy stats # one-line status printout
110
+ opencode-buddy feed # feed from any terminal
111
+ opencode-buddy hatch dragon Sparky # start fresh as a dragon named Sparky
112
+ opencode-buddy switch cat # become a cat
113
+ opencode-buddy rename CaptainQuack # rename
114
+ opencode-buddy notify done # nudge the buddy from a hook
115
+ opencode-buddy path # print path to the state file
116
+ ```
117
+
118
+ These work whether or not the TUI or tmux side pane is running.
119
+ | `n` | Hatch a new buddy (resets state) |
120
+ | `q` | Quit (kills the side pane) |
121
+
122
+ ### Headless commands
123
+
124
+ ```bash
125
+ opencode-buddy stats # one-line status printout
126
+ opencode-buddy feed # feed from any terminal
127
+ opencode-buddy hatch dragon Sparky # start fresh as a dragon named Sparky
128
+ opencode-buddy switch cat # become a cat
129
+ opencode-buddy rename CaptainQuack # rename
130
+ opencode-buddy notify done # nudge the buddy from a hook / hookup
131
+ opencode-buddy path # print path to the state file
132
+ ```
133
+
134
+ ## The six species
135
+
136
+ ```
137
+ duck cat dragon
138
+ __ /\\_/\ /\\_/\
139
+ <(o )___ ( o.o ) ( o o ) ~~
140
+ ( ._> / > ^ < > ^ < /
141
+ `--' /| |\\ /| |\\
142
+ ~ idle ~ (_| |_) (_| |_)
143
+ meow rawr
144
+
145
+ axolotl robot ghost
146
+ ^___^ [ O . O ] .-"\"-.
147
+ (o . o) /|#####|\\ ( o . o )
148
+ \\|_|_|/ / |#####| \\ | ~ ~ |
149
+ \\| |/ | | | |
150
+ ) ( /| | | |\\ \\uuuuu/
151
+ ~ ambien beep boo
152
+ ```
153
+
154
+ Each has frames for `idle / working / celebrating / scared / sleeping`.
155
+
156
+ ## How it works
157
+
158
+ * **As an opencode plugin** (`src/plugin.js`): registers a `buddy` tool the LLM can call, and listens to `session.idle` / `session.error` events to update the buddy's mood automatically. The `/buddy` slash command in `~/.config/opencode/commands/buddy.md` is a thin prompt that tells the LLM to use that tool.
159
+ * **As a tmux sidecar** (`bin/opencode-buddy.js start`): runs a separate process in a right-side tmux pane. Uses `tmux capture-pane` to peek at the opencode pane text and infer whether opencode is generating, errored, or idle.
160
+ * **Shared state** at `~/.config/opencode-buddy/state.json`. Both the plugin and the sidecar read and write the same file, so a `/buddy feed` in the TUI is immediately visible in the side pane and vice versa.
161
+ * **Zero runtime dependencies.** Pure Node.js + ANSI escapes for the renderer, `spawnSync("tmux", ...)` for pane control. The only dev-time dependency is `@opencode-ai/plugin` (used to define the opencode tool).
162
+
163
+ ## Limitations
164
+
165
+ * **Activity inference is best-effort.** If opencode's prompt text changes, the heuristics may misclassify. The `notify` subcommand is the explicit way to push events.
166
+ * **The /buddy command is LLM-mediated.** The slash command is a prompt that tells opencode's LLM to call the `buddy` tool. It works only inside an active opencode session. For a direct terminal-friendly interface use `opencode-buddy <subcommand>` or the tmux side pane.
167
+ * **No animations yet.** Frames are static; the renderer just swaps the frame when the state changes. Adding 2-3 frame loops per state is straightforward (PRs welcome).
168
+
169
+ ## Project layout
170
+
171
+ ```
172
+ opencode-buddy/
173
+ ├── bin/
174
+ │ └── opencode-buddy.js # CLI entry
175
+ ├── src/
176
+ │ ├── plugin.js # opencode plugin (exports BuddyPlugin)
177
+ │ ├── install.js # one-shot installer for /buddy + opencode.json
178
+ │ ├── index.js # CLI runtime + headless commands
179
+ │ ├── tui.js # ANSI renderer for the side pane
180
+ │ ├── tmux.js # tmux capture / split helpers
181
+ │ ├── persistence.js # state file at ~/.config/opencode-buddy/
182
+ │ └── buddy/
183
+ │ ├── species.js # ASCII art + color palettes
184
+ │ └── state.js # state machine + attribute math
185
+ ├── test/
186
+ │ ├── smoke.js # state / species unit tests
187
+ │ ├── plugin.smoke.js # plugin tool unit tests
188
+ │ └── e2e.js # full tmux roundtrip
189
+ ├── package.json
190
+ ├── LICENSE # MIT
191
+ ├── README.md
192
+ ├── PUSH.md # GitHub push guide
193
+ └── PUBLISH.md # npm publish guide
194
+ ```
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ npm test # runs all unit tests (smoke + plugin)
200
+ npm run test:e2e # runs end-to-end tmux test (creates & kills a session)
201
+ npm run install-buddy # runs the opencode install command
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry point. Routes to either the headless command runner or the
3
+ // interactive TUI renderer.
4
+
5
+ import { runInteractive, runCommand } from "../src/index.js";
6
+ import { install, uninstall } from "../src/install.js";
7
+
8
+ const argv = process.argv.slice(2);
9
+ const [sub, ...rest] = argv;
10
+
11
+ async function main() {
12
+ if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
13
+ await runCommand([]);
14
+ return;
15
+ }
16
+
17
+ if (sub === "start") {
18
+ await runInteractive({ autoSplit: true });
19
+ return;
20
+ }
21
+
22
+ if (sub === "render") {
23
+ // Re-entered inside the tmux side pane by `start`. Just render.
24
+ await runInteractive({ autoSplit: false });
25
+ return;
26
+ }
27
+
28
+ if (sub === "install") {
29
+ await install();
30
+ return;
31
+ }
32
+
33
+ if (sub === "uninstall") {
34
+ await uninstall();
35
+ return;
36
+ }
37
+
38
+ // Everything else: headless command
39
+ await runCommand(argv);
40
+ }
41
+
42
+ main().catch((err) => {
43
+ console.error(`opencode-buddy: ${err.message || err}`);
44
+ process.exit(1);
45
+ });
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "opencode-buddy",
3
+ "version": "0.2.0",
4
+ "description": "A virtual ASCII pet companion for opencode. Works as a tmux sidecar or an in-TUI /buddy slash command.",
5
+ "type": "module",
6
+ "main": "src/plugin.js",
7
+ "exports": {
8
+ ".": "./src/plugin.js",
9
+ "./cli": "./bin/opencode-buddy.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "bin": {
13
+ "opencode-buddy": "bin/opencode-buddy.js"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "start": "node bin/opencode-buddy.js start",
23
+ "hatch": "node bin/opencode-buddy.js hatch",
24
+ "stats": "node bin/opencode-buddy.js stats",
25
+ "install-buddy": "node bin/opencode-buddy.js install",
26
+ "test": "node --test test/smoke.js test/plugin.smoke.js",
27
+ "test:e2e": "node test/e2e.js"
28
+ },
29
+ "keywords": [
30
+ "opencode",
31
+ "opencode-plugin",
32
+ "tmux",
33
+ "pet",
34
+ "tamagotchi",
35
+ "ascii",
36
+ "tui",
37
+ "companion",
38
+ "buddy",
39
+ "virtual-pet"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "author": "AMark-CS",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/AMark-CS/opencode-buddy.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/AMark-CS/opencode-buddy/issues"
52
+ },
53
+ "homepage": "https://github.com/AMark-CS/opencode-buddy#readme",
54
+ "peerDependencies": {
55
+ "@opencode-ai/plugin": "*"
56
+ },
57
+ "publishConfig": {
58
+ "registry": "https://registry.npmjs.org/",
59
+ "access": "public"
60
+ }
61
+ }