@ramarivera/coding-buddy 0.4.0-alpha.7 โ 0.4.0-alpha.9
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/README.md +18 -39
- package/adapters/claude/hooks/buddy-comment.sh +4 -1
- package/adapters/claude/hooks/name-react.sh +4 -1
- package/adapters/claude/hooks/react.sh +4 -1
- package/adapters/claude/install/backup.ts +36 -118
- package/adapters/claude/install/disable.ts +9 -14
- package/adapters/claude/install/doctor.ts +26 -87
- package/adapters/claude/install/install.ts +39 -66
- package/adapters/claude/install/test-statusline.ts +8 -18
- package/adapters/claude/install/uninstall.ts +18 -26
- package/adapters/claude/plugin/marketplace.json +4 -4
- package/adapters/claude/plugin/plugin.json +3 -5
- package/adapters/claude/server/index.ts +132 -5
- package/adapters/claude/server/path.ts +12 -0
- package/adapters/claude/skills/buddy/SKILL.md +16 -1
- package/adapters/claude/statusline/buddy-status.sh +22 -3
- package/adapters/claude/storage/paths.ts +9 -0
- package/adapters/claude/storage/settings.ts +53 -3
- package/adapters/claude/storage/state.ts +22 -4
- package/adapters/pi/README.md +19 -0
- package/adapters/pi/events.ts +176 -19
- package/adapters/pi/index.ts +3 -1
- package/adapters/pi/logger.ts +52 -0
- package/adapters/pi/prompt.ts +18 -0
- package/adapters/pi/storage.ts +1 -0
- package/cli/biomes.ts +309 -0
- package/cli/buddy-shell.ts +818 -0
- package/cli/index.ts +7 -0
- package/cli/tui.tsx +2244 -0
- package/cli/upgrade.ts +213 -0
- package/core/model.ts +6 -0
- package/package.json +78 -62
- package/scripts/paths.sh +40 -0
- package/server/achievements.ts +15 -0
- package/server/art.ts +1 -0
- package/server/engine.ts +1 -0
- package/server/mcp-launcher.sh +16 -0
- package/server/path.ts +30 -0
- package/server/reactions.ts +1 -0
- package/server/state.ts +3 -0
- package/adapters/claude/popup/buddy-popup.sh +0 -92
- package/adapters/claude/popup/buddy-render.sh +0 -540
- package/adapters/claude/popup/popup-manager.sh +0 -355
package/README.md
CHANGED
|
@@ -72,6 +72,12 @@
|
|
|
72
72
|
<!-- QUICK START -->
|
|
73
73
|
<!-- ============================================================ -->
|
|
74
74
|
|
|
75
|
+
## ๐ Requirements
|
|
76
|
+
|
|
77
|
+
- **[bun](https://bun.sh/install)** on `PATH` โ claude-buddy's MCP server runs on bun. Install once: `curl -fsSL https://bun.sh/install | bash`
|
|
78
|
+
- **Claude Code v2.1.80+**
|
|
79
|
+
- **Linux or macOS** (Windows is experimental)
|
|
80
|
+
|
|
75
81
|
## ๐ Quick Start
|
|
76
82
|
|
|
77
83
|
```bash
|
|
@@ -83,12 +89,21 @@ bun run install-buddy
|
|
|
83
89
|
|
|
84
90
|
Then restart Claude Code and type `/buddy`. That's it.
|
|
85
91
|
|
|
86
|
-
<sub>๐ก Need Bun? โ `curl -fsSL https://bun.sh/install | bash`</sub>
|
|
87
|
-
<br>
|
|
88
92
|
<sub>๐ก Want a global `claude-buddy` command? โ `bun link`</sub>
|
|
89
93
|
<br>
|
|
90
94
|
<sub>๐ก Need help? โ `bun run help` or `claude-buddy help` (if linked) in terminal ยท `/buddy help` in Claude Code</sub>
|
|
91
95
|
|
|
96
|
+
### Multiple Claude profiles?
|
|
97
|
+
|
|
98
|
+
If you run Claude Code with `CLAUDE_CONFIG_DIR` set (e.g. separate work and personal accounts), pass the same env var to install so buddy lands in the active profile and gets its own menagerie:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
CLAUDE_CONFIG_DIR=~/.claude-personal bun run install-buddy
|
|
102
|
+
CLAUDE_CONFIG_DIR=~/.claude-personal bun run uninstall
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The installer prints `Target profile: <path>` at the top so you can see at a glance which profile you're targeting. Each profile gets its own MCP entry, skill, hooks, status line, and `$CLAUDE_CONFIG_DIR/buddy-state/` menagerie โ installs in one profile don't touch another. With `CLAUDE_CONFIG_DIR` unset, behaviour is identical to single-profile (`~/.claude/`, `~/.claude-buddy/`).
|
|
106
|
+
|
|
92
107
|
<br>
|
|
93
108
|
|
|
94
109
|
---
|
|
@@ -338,41 +353,6 @@ bun run cli/uninstall.ts # full clean removal
|
|
|
338
353
|
|
|
339
354
|
---
|
|
340
355
|
|
|
341
|
-
<details>
|
|
342
|
-
<summary><b>๐ฅ๏ธ tmux Popup Mode</b></summary>
|
|
343
|
-
|
|
344
|
-
<br>
|
|
345
|
-
|
|
346
|
-
Inside tmux, buddy appears as a floating popup overlay in the bottom-right corner instead of the status line.
|
|
347
|
-
|
|
348
|
-
**Features:** animated ASCII art with speech bubbles ยท ESC passthrough ยท dynamic resizing ยท full keyboard forwarding
|
|
349
|
-
|
|
350
|
-
| tmux version | Support |
|
|
351
|
-
|---|---|
|
|
352
|
-
| **3.4+** | Full support (borderless) |
|
|
353
|
-
| **3.2 โ 3.3** | Supported with border |
|
|
354
|
-
| **< 3.2** | Falls back to status line |
|
|
355
|
-
|
|
356
|
-
### Recommended `~/.tmux.conf`
|
|
357
|
-
|
|
358
|
-
```
|
|
359
|
-
set -g set-titles on
|
|
360
|
-
set -g set-titles-string "#{pane_title}"
|
|
361
|
-
set -g mouse on
|
|
362
|
-
set -g history-limit 10000
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Scrolling
|
|
366
|
-
|
|
367
|
-
The popup is modal. To scroll:
|
|
368
|
-
1. Press **F12** โ scroll mode (popup hides, copy-mode activates)
|
|
369
|
-
2. Scroll with **mouse wheel** or **arrows**
|
|
370
|
-
3. Press **q** โ exit scroll mode
|
|
371
|
-
|
|
372
|
-
</details>
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
356
|
<details>
|
|
377
357
|
<summary><b>๐ Requirements</b></summary>
|
|
378
358
|
|
|
@@ -382,7 +362,7 @@ The popup is modal. To scroll:
|
|
|
382
362
|
|---|---|
|
|
383
363
|
| **[Bun](https://bun.sh)** | `curl -fsSL https://bun.sh/install \| bash` |
|
|
384
364
|
| **Claude Code** v2.1.80+ | Any version with MCP support |
|
|
385
|
-
| **jq** | `apt install jq` / `brew install jq` |
|
|
365
|
+
| **jq** | `apt install jq` / `brew install jq` / [`windows: download and add 'jq.exe' from jqlang/jq to path`](https://github.com/jqlang/jq/releases/latest)|
|
|
386
366
|
|
|
387
367
|
> **Will I get the same buddy I had?** Yes. claude-buddy uses the exact same algorithm as the original (`wyhash + mulberry32`, same salt, same identity resolution). If your `~/.claude.json` still has your `accountUuid`, you'll get the identical species, rarity, stats, and cosmetics.
|
|
388
368
|
|
|
@@ -395,7 +375,6 @@ The popup is modal. To scroll:
|
|
|
395
375
|
## ๐บ๏ธ Roadmap
|
|
396
376
|
|
|
397
377
|
- [x] **Multi-buddy support** โ menagerie system with named slots, interactive TUI picker ๐[@doctor-ew](https://github.com/doctor-ew)๐
|
|
398
|
-
- [x] **tmux popup mode** โ floating overlay via `tmux display-popup` ๐[@gzenz](https://github.com/gzenz)๐
|
|
399
378
|
- [ ] **Leveling system** โ XP from coding sessions, unlockable reactions and upgrades
|
|
400
379
|
- [ ] **Buddy pair-programming** โ active help during sessions, pattern detection
|
|
401
380
|
- [ ] **Cross-session memory** โ remembers past projects and earlier conversations
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
# This hook extracts it and updates the status line bubble.
|
|
6
6
|
# The HTML comment is invisible in rendered markdown output.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# shellcheck source=../scripts/paths.sh
|
|
9
|
+
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/paths.sh"
|
|
10
|
+
|
|
11
|
+
STATE_DIR="$BUDDY_STATE_DIR"
|
|
9
12
|
# Session ID: sanitized tmux pane number, or "default" outside tmux
|
|
10
13
|
SID="${TMUX_PANE#%}"
|
|
11
14
|
SID="${SID:-default}"
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
# Detects the buddy's name in the user's message โ status line reaction.
|
|
4
4
|
# No cooldown โ name mentions are intentional.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# shellcheck source=../scripts/paths.sh
|
|
7
|
+
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/paths.sh"
|
|
8
|
+
|
|
9
|
+
STATE_DIR="$BUDDY_STATE_DIR"
|
|
7
10
|
STATUS_FILE="$STATE_DIR/status.json"
|
|
8
11
|
# Session ID: sanitized tmux pane number, or "default" outside tmux
|
|
9
12
|
SID="${TMUX_PANE#%}"
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Combined: PR #4 species reactions + PR #6 session isolation + PR #13 field fix
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# shellcheck source=../scripts/paths.sh
|
|
8
|
+
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/paths.sh"
|
|
9
|
+
|
|
10
|
+
STATE_DIR="$BUDDY_STATE_DIR"
|
|
8
11
|
# Session ID: sanitized tmux pane number, or "default" outside tmux
|
|
9
12
|
SID="${TMUX_PANE#%}"
|
|
10
13
|
SID="${SID:-default}"
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
3
|
* claude-buddy backup โ snapshot all claude-buddy related state
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* bun run backup Create a new snapshot
|
|
7
|
-
* bun run backup list List all backups
|
|
8
|
-
* bun run backup show <ts> Show what's in a backup
|
|
9
|
-
* bun run backup restore Restore the latest backup
|
|
10
|
-
* bun run backup restore <ts> Restore a specific backup
|
|
11
|
-
* bun run backup delete <ts> Delete a specific backup
|
|
12
|
-
*
|
|
13
|
-
* Backups are stored in ~/.claude-buddy/backups/YYYY-MM-DD-HHMMSS/
|
|
14
|
-
*
|
|
15
|
-
* What gets backed up:
|
|
16
|
-
* - ~/.claude/settings.json (full file)
|
|
17
|
-
* - ~/.claude.json mcpServers["claude-buddy"] block (only our entry)
|
|
18
|
-
* - ~/.claude/skills/buddy/SKILL.md
|
|
19
|
-
* - ~/.claude-buddy/companion.json
|
|
20
|
-
* - ~/.claude-buddy/status.json
|
|
21
|
-
* - ~/.claude-buddy/reaction.json
|
|
22
4
|
*/
|
|
23
5
|
|
|
24
6
|
import {
|
|
@@ -26,15 +8,13 @@ import {
|
|
|
26
8
|
readdirSync, statSync, rmSync, copyFileSync,
|
|
27
9
|
} from "fs";
|
|
28
10
|
import { dirname, join } from "path";
|
|
29
|
-
import {
|
|
30
|
-
import { getBuddySkillDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
|
|
11
|
+
import { getBuddySkillDir, getBuddyStateDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
|
|
31
12
|
|
|
32
|
-
const HOME = homedir();
|
|
33
|
-
const BACKUPS_DIR = join(HOME, ".claude-buddy", "backups");
|
|
34
13
|
const SETTINGS = getClaudeSettingsPath();
|
|
35
14
|
const CLAUDE_JSON = getClaudeJsonPath();
|
|
36
15
|
const SKILL = join(getBuddySkillDir(), "SKILL.md");
|
|
37
|
-
const STATE_DIR =
|
|
16
|
+
const STATE_DIR = getBuddyStateDir();
|
|
17
|
+
const BACKUPS_DIR = join(STATE_DIR, "backups");
|
|
38
18
|
|
|
39
19
|
const RED = "\x1b[31m";
|
|
40
20
|
const GREEN = "\x1b[32m";
|
|
@@ -62,13 +42,11 @@ function tryRead(path: string): string | null {
|
|
|
62
42
|
function listBackups(): string[] {
|
|
63
43
|
if (!existsSync(BACKUPS_DIR)) return [];
|
|
64
44
|
return readdirSync(BACKUPS_DIR)
|
|
65
|
-
.filter(f => /^\d{4}-\d{2}-\d{2}-\d{6}$/.test(f))
|
|
66
|
-
.filter(f => statSync(join(BACKUPS_DIR, f)).isDirectory())
|
|
45
|
+
.filter((f) => /^\d{4}-\d{2}-\d{2}-\d{6}$/.test(f))
|
|
46
|
+
.filter((f) => statSync(join(BACKUPS_DIR, f)).isDirectory())
|
|
67
47
|
.sort();
|
|
68
48
|
}
|
|
69
49
|
|
|
70
|
-
// โโโ Create backup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
71
|
-
|
|
72
50
|
function createBackup(): string {
|
|
73
51
|
const ts = timestamp();
|
|
74
52
|
const dir = join(BACKUPS_DIR, ts);
|
|
@@ -76,17 +54,15 @@ function createBackup(): string {
|
|
|
76
54
|
|
|
77
55
|
const manifest: { timestamp: string; files: string[] } = { timestamp: ts, files: [] };
|
|
78
56
|
|
|
79
|
-
// 1. settings.json
|
|
80
57
|
const settings = tryRead(SETTINGS);
|
|
81
58
|
if (settings) {
|
|
82
59
|
writeFileSync(join(dir, "settings.json"), settings);
|
|
83
60
|
manifest.files.push("settings.json");
|
|
84
|
-
ok(`Backed up:
|
|
61
|
+
ok(`Backed up: ${SETTINGS}`);
|
|
85
62
|
} else {
|
|
86
|
-
warn(`Skipped:
|
|
63
|
+
warn(`Skipped: ${SETTINGS} (not found)`);
|
|
87
64
|
}
|
|
88
65
|
|
|
89
|
-
// 2. claude.json mcpServers["claude-buddy"]
|
|
90
66
|
const claudeJsonRaw = tryRead(CLAUDE_JSON);
|
|
91
67
|
if (claudeJsonRaw) {
|
|
92
68
|
try {
|
|
@@ -95,45 +71,40 @@ function createBackup(): string {
|
|
|
95
71
|
if (ourMcp) {
|
|
96
72
|
writeFileSync(join(dir, "mcpserver.json"), JSON.stringify(ourMcp, null, 2));
|
|
97
73
|
manifest.files.push("mcpserver.json");
|
|
98
|
-
ok(`Backed up:
|
|
74
|
+
ok(`Backed up: ${CLAUDE_JSON} โ mcpServers["claude-buddy"]`);
|
|
99
75
|
} else {
|
|
100
|
-
warn(`Skipped:
|
|
76
|
+
warn(`Skipped: ${CLAUDE_JSON} mcpServers["claude-buddy"] (not registered)`);
|
|
101
77
|
}
|
|
102
78
|
} catch {
|
|
103
|
-
err(`Failed to parse
|
|
79
|
+
err(`Failed to parse ${CLAUDE_JSON}`);
|
|
104
80
|
}
|
|
105
81
|
}
|
|
106
82
|
|
|
107
|
-
// 3. SKILL.md
|
|
108
83
|
const skill = tryRead(SKILL);
|
|
109
84
|
if (skill) {
|
|
110
85
|
writeFileSync(join(dir, "SKILL.md"), skill);
|
|
111
86
|
manifest.files.push("SKILL.md");
|
|
112
|
-
ok(`Backed up:
|
|
87
|
+
ok(`Backed up: ${SKILL}`);
|
|
113
88
|
} else {
|
|
114
|
-
warn(`Skipped:
|
|
89
|
+
warn(`Skipped: ${SKILL} (not found)`);
|
|
115
90
|
}
|
|
116
91
|
|
|
117
|
-
// 4-6. ~/.claude-buddy/ state files (don't back up the backups dir itself)
|
|
118
92
|
const stateDestDir = join(dir, "claude-buddy");
|
|
119
93
|
mkdirSync(stateDestDir, { recursive: true });
|
|
120
|
-
const stateFiles = ["
|
|
121
|
-
for (const
|
|
122
|
-
const src = join(STATE_DIR,
|
|
94
|
+
const stateFiles = ["menagerie.json", "config.json", "status.json", "events.json", "unlocked.json", "active_days.json"];
|
|
95
|
+
for (const file of stateFiles) {
|
|
96
|
+
const src = join(STATE_DIR, file);
|
|
123
97
|
if (existsSync(src)) {
|
|
124
|
-
copyFileSync(src, join(stateDestDir,
|
|
125
|
-
manifest.files.push(`claude-buddy/${
|
|
126
|
-
ok(`Backed up:
|
|
98
|
+
copyFileSync(src, join(stateDestDir, file));
|
|
99
|
+
manifest.files.push(`claude-buddy/${file}`);
|
|
100
|
+
ok(`Backed up: ${join(STATE_DIR, file)}`);
|
|
127
101
|
}
|
|
128
102
|
}
|
|
129
103
|
|
|
130
104
|
writeFileSync(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
131
|
-
|
|
132
105
|
return ts;
|
|
133
106
|
}
|
|
134
107
|
|
|
135
|
-
// โโโ List backups โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
136
|
-
|
|
137
108
|
function cmdList() {
|
|
138
109
|
const backups = listBackups();
|
|
139
110
|
if (backups.length === 0) {
|
|
@@ -147,9 +118,7 @@ function cmdList() {
|
|
|
147
118
|
const manifest = tryRead(manifestPath);
|
|
148
119
|
let count = "?";
|
|
149
120
|
if (manifest) {
|
|
150
|
-
try {
|
|
151
|
-
count = String(JSON.parse(manifest).files?.length ?? 0);
|
|
152
|
-
} catch {}
|
|
121
|
+
try { count = String(JSON.parse(manifest).files?.length ?? 0); } catch {}
|
|
153
122
|
}
|
|
154
123
|
const isLatest = ts === backups[backups.length - 1];
|
|
155
124
|
const tag = isLatest ? `${GREEN}(latest)${NC}` : "";
|
|
@@ -158,8 +127,6 @@ function cmdList() {
|
|
|
158
127
|
console.log("");
|
|
159
128
|
}
|
|
160
129
|
|
|
161
|
-
// โโโ Show backup contents โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
162
|
-
|
|
163
130
|
function cmdShow(ts: string) {
|
|
164
131
|
const dir = join(BACKUPS_DIR, ts);
|
|
165
132
|
if (!existsSync(dir)) {
|
|
@@ -174,14 +141,10 @@ function cmdShow(ts: string) {
|
|
|
174
141
|
const data = JSON.parse(manifest);
|
|
175
142
|
console.log(`\n${BOLD}Backup ${ts}${NC}\n`);
|
|
176
143
|
console.log(` ${DIM}Files:${NC}`);
|
|
177
|
-
for (const
|
|
178
|
-
console.log(` - ${f}`);
|
|
179
|
-
}
|
|
144
|
+
for (const file of data.files) console.log(` - ${file}`);
|
|
180
145
|
console.log("");
|
|
181
146
|
}
|
|
182
147
|
|
|
183
|
-
// โโโ Restore backup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
184
|
-
|
|
185
148
|
function restoreBackup(ts: string) {
|
|
186
149
|
const dir = join(BACKUPS_DIR, ts);
|
|
187
150
|
if (!existsSync(dir)) {
|
|
@@ -191,51 +154,46 @@ function restoreBackup(ts: string) {
|
|
|
191
154
|
|
|
192
155
|
info(`Restoring backup ${ts}...\n`);
|
|
193
156
|
|
|
194
|
-
// 1. settings.json โ overwrite
|
|
195
157
|
const settingsBak = join(dir, "settings.json");
|
|
196
158
|
if (existsSync(settingsBak)) {
|
|
197
159
|
mkdirSync(dirname(SETTINGS), { recursive: true });
|
|
198
160
|
copyFileSync(settingsBak, SETTINGS);
|
|
199
|
-
ok(
|
|
161
|
+
ok(`Restored: ${SETTINGS}`);
|
|
200
162
|
}
|
|
201
163
|
|
|
202
|
-
// 2. claude.json mcpServers โ merge our entry back in
|
|
203
164
|
const mcpBak = join(dir, "mcpserver.json");
|
|
204
165
|
if (existsSync(mcpBak)) {
|
|
205
166
|
const ourMcp = JSON.parse(readFileSync(mcpBak, "utf8"));
|
|
206
167
|
let claudeJson: { mcpServers?: Record<string, unknown> } = {};
|
|
207
168
|
try {
|
|
208
169
|
claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8")) as { mcpServers?: Record<string, unknown> };
|
|
209
|
-
} catch {
|
|
170
|
+
} catch {}
|
|
210
171
|
if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
|
|
211
172
|
claudeJson.mcpServers["claude-buddy"] = ourMcp;
|
|
173
|
+
mkdirSync(dirname(CLAUDE_JSON), { recursive: true });
|
|
212
174
|
writeFileSync(CLAUDE_JSON, JSON.stringify(claudeJson, null, 2));
|
|
213
|
-
ok(
|
|
175
|
+
ok(`Restored: ${CLAUDE_JSON} โ mcpServers["claude-buddy"]`);
|
|
214
176
|
}
|
|
215
177
|
|
|
216
|
-
// 3. SKILL.md
|
|
217
178
|
const skillBak = join(dir, "SKILL.md");
|
|
218
179
|
if (existsSync(skillBak)) {
|
|
219
180
|
mkdirSync(dirname(SKILL), { recursive: true });
|
|
220
181
|
copyFileSync(skillBak, SKILL);
|
|
221
|
-
ok(
|
|
182
|
+
ok(`Restored: ${SKILL}`);
|
|
222
183
|
}
|
|
223
184
|
|
|
224
|
-
// 4. ~/.claude-buddy/ state files
|
|
225
185
|
const stateDir = join(dir, "claude-buddy");
|
|
226
186
|
if (existsSync(stateDir)) {
|
|
227
187
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
228
|
-
for (const
|
|
229
|
-
copyFileSync(join(stateDir,
|
|
230
|
-
ok(`Restored:
|
|
188
|
+
for (const file of readdirSync(stateDir)) {
|
|
189
|
+
copyFileSync(join(stateDir, file), join(STATE_DIR, file));
|
|
190
|
+
ok(`Restored: ${join(STATE_DIR, file)}`);
|
|
231
191
|
}
|
|
232
192
|
}
|
|
233
193
|
|
|
234
194
|
console.log(`\n${GREEN}Restore complete.${NC} Restart Claude Code to apply.\n`);
|
|
235
195
|
}
|
|
236
196
|
|
|
237
|
-
// โโโ Delete backup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
238
|
-
|
|
239
197
|
function cmdDelete(ts: string) {
|
|
240
198
|
const dir = join(BACKUPS_DIR, ts);
|
|
241
199
|
if (!existsSync(dir)) {
|
|
@@ -246,14 +204,11 @@ function cmdDelete(ts: string) {
|
|
|
246
204
|
ok(`Deleted backup ${ts}`);
|
|
247
205
|
}
|
|
248
206
|
|
|
249
|
-
// โโโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
250
|
-
|
|
251
207
|
const action = process.argv[2] || "create";
|
|
252
208
|
const arg = process.argv[3];
|
|
253
209
|
|
|
254
210
|
switch (action) {
|
|
255
|
-
case "create":
|
|
256
|
-
case undefined: {
|
|
211
|
+
case "create": {
|
|
257
212
|
console.log(`\n${BOLD}Creating claude-buddy backup...${NC}\n`);
|
|
258
213
|
const ts = createBackup();
|
|
259
214
|
console.log(`\n${GREEN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}`);
|
|
@@ -264,74 +219,37 @@ switch (action) {
|
|
|
264
219
|
console.log(`${DIM} Or: bun run backup restore ${ts}${NC}\n`);
|
|
265
220
|
break;
|
|
266
221
|
}
|
|
267
|
-
|
|
268
222
|
case "list":
|
|
269
223
|
case "ls":
|
|
270
224
|
cmdList();
|
|
271
225
|
break;
|
|
272
|
-
|
|
273
|
-
case "show": {
|
|
226
|
+
case "show":
|
|
274
227
|
if (!arg) {
|
|
275
228
|
err("Usage: bun run backup show <timestamp>");
|
|
276
229
|
process.exit(1);
|
|
277
230
|
}
|
|
278
231
|
cmdShow(arg);
|
|
279
232
|
break;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
233
|
case "restore": {
|
|
283
|
-
|
|
234
|
+
const backups = listBackups();
|
|
235
|
+
const ts = arg ?? backups[backups.length - 1];
|
|
284
236
|
if (!ts) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
err("No backups to restore");
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
ts = all[all.length - 1];
|
|
291
|
-
info(`Restoring latest backup: ${ts}`);
|
|
237
|
+
err("No backups found to restore");
|
|
238
|
+
process.exit(1);
|
|
292
239
|
}
|
|
293
240
|
restoreBackup(ts);
|
|
294
241
|
break;
|
|
295
242
|
}
|
|
296
|
-
|
|
297
243
|
case "delete":
|
|
298
|
-
case "rm":
|
|
244
|
+
case "rm":
|
|
299
245
|
if (!arg) {
|
|
300
246
|
err("Usage: bun run backup delete <timestamp>");
|
|
301
247
|
process.exit(1);
|
|
302
248
|
}
|
|
303
249
|
cmdDelete(arg);
|
|
304
250
|
break;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
case "--help":
|
|
308
|
-
case "-h":
|
|
309
|
-
console.log(`
|
|
310
|
-
${BOLD}claude-buddy backup${NC} โ snapshot and restore all claude-buddy state
|
|
311
|
-
|
|
312
|
-
${BOLD}Commands:${NC}
|
|
313
|
-
bun run backup Create a new snapshot
|
|
314
|
-
bun run backup list List all backups
|
|
315
|
-
bun run backup show <ts> Show what's in a backup
|
|
316
|
-
bun run backup restore Restore the latest backup
|
|
317
|
-
bun run backup restore <ts> Restore a specific backup
|
|
318
|
-
bun run backup delete <ts> Delete a specific backup
|
|
319
|
-
|
|
320
|
-
${BOLD}What gets backed up:${NC}
|
|
321
|
-
- ~/.claude/settings.json (full)
|
|
322
|
-
- ~/.claude.json mcpServers["claude-buddy"] (only our entry)
|
|
323
|
-
- ~/.claude/skills/buddy/SKILL.md
|
|
324
|
-
- ~/.claude-buddy/companion.json
|
|
325
|
-
- ~/.claude-buddy/status.json
|
|
326
|
-
- ~/.claude-buddy/reaction.json
|
|
327
|
-
|
|
328
|
-
${BOLD}Backup location:${NC}
|
|
329
|
-
${BACKUPS_DIR}/<timestamp>/
|
|
330
|
-
`);
|
|
331
|
-
break;
|
|
332
|
-
|
|
333
251
|
default:
|
|
334
252
|
err(`Unknown action: ${action}`);
|
|
335
|
-
console.log(
|
|
253
|
+
console.log("Usage: bun run backup [list|show <ts>|restore [ts]|delete <ts>]");
|
|
336
254
|
process.exit(1);
|
|
337
255
|
}
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
* Re-enable with: bun run install-buddy
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync, writeFileSync
|
|
12
|
-
import {
|
|
13
|
-
import { homedir } from "os";
|
|
14
|
-
import { getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
|
|
11
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
12
|
+
import { getBuddyStateDir, getClaudeJsonPath, getClaudeSettingsPath } from "../storage/paths.ts";
|
|
15
13
|
|
|
16
14
|
interface HookCommand {
|
|
17
15
|
type: "command";
|
|
@@ -42,13 +40,12 @@ const NC = "\x1b[0m";
|
|
|
42
40
|
function ok(msg: string) { console.log(`${GREEN}โ${NC} ${msg}`); }
|
|
43
41
|
function warn(msg: string) { console.log(`${YELLOW}โ ${NC} ${msg}`); }
|
|
44
42
|
|
|
45
|
-
const HOME = homedir();
|
|
46
43
|
const CLAUDE_JSON = getClaudeJsonPath();
|
|
47
44
|
const SETTINGS = getClaudeSettingsPath();
|
|
45
|
+
const STATE_DIR = getBuddyStateDir();
|
|
48
46
|
|
|
49
47
|
console.log(`\n${BOLD}Disabling claude-buddy...${NC}\n`);
|
|
50
48
|
|
|
51
|
-
// 1. Remove MCP server from ~/.claude.json
|
|
52
49
|
try {
|
|
53
50
|
const claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"));
|
|
54
51
|
if (claudeJson.mcpServers?.["claude-buddy"]) {
|
|
@@ -63,7 +60,6 @@ try {
|
|
|
63
60
|
warn(`Could not update ${CLAUDE_JSON}`);
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
// 2. Remove status line + hooks from settings.json
|
|
67
63
|
try {
|
|
68
64
|
const settings = JSON.parse(readFileSync(SETTINGS, "utf8")) as ClaudeSettings;
|
|
69
65
|
let changed = false;
|
|
@@ -75,12 +71,10 @@ try {
|
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
if (settings.hooks) {
|
|
78
|
-
for (const hookType of ["PostToolUse", "Stop", "SessionStart", "SessionEnd"]) {
|
|
74
|
+
for (const hookType of ["PostToolUse", "Stop", "SessionStart", "SessionEnd", "UserPromptSubmit"]) {
|
|
79
75
|
if (settings.hooks[hookType]) {
|
|
80
76
|
const before = settings.hooks[hookType].length;
|
|
81
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter(
|
|
82
|
-
(h) => !hasBuddyHook(h),
|
|
83
|
-
);
|
|
77
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter((h) => !hasBuddyHook(h));
|
|
84
78
|
if (settings.hooks[hookType].length < before) changed = true;
|
|
85
79
|
if (settings.hooks[hookType].length === 0) delete settings.hooks[hookType];
|
|
86
80
|
}
|
|
@@ -96,18 +90,19 @@ try {
|
|
|
96
90
|
warn("Could not update settings.json");
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
// 3. Stop tmux popup if running
|
|
100
93
|
try {
|
|
101
94
|
if (process.env.TMUX) {
|
|
102
95
|
const { execSync } = await import("child_process");
|
|
103
96
|
execSync("tmux display-popup -C 2>/dev/null", { stdio: "ignore" });
|
|
104
97
|
}
|
|
105
|
-
} catch {
|
|
98
|
+
} catch {
|
|
99
|
+
// noop
|
|
100
|
+
}
|
|
106
101
|
|
|
107
102
|
console.log(`
|
|
108
103
|
${GREEN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}
|
|
109
104
|
${GREEN} Buddy disabled.${NC}
|
|
110
|
-
${GREEN} Companion data is preserved at
|
|
105
|
+
${GREEN} Companion data is preserved at ${STATE_DIR}${NC}
|
|
111
106
|
${GREEN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}
|
|
112
107
|
|
|
113
108
|
${DIM} Restart Claude Code for changes to take effect.
|