@ramarivera/coding-buddy 0.4.0-alpha.9 → 0.6.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/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
- package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
- package/LICENSE +1 -1
- package/README.md +88 -37
- package/adapters/omp/commands.ts +201 -0
- package/adapters/omp/context.ts +22 -0
- package/adapters/omp/events.ts +551 -0
- package/adapters/omp/identity.ts +10 -0
- package/adapters/omp/index.ts +39 -0
- package/adapters/omp/logger.ts +52 -0
- package/adapters/omp/prompt.ts +60 -0
- package/adapters/omp/renderers.ts +73 -0
- package/adapters/omp/storage.ts +25 -0
- package/adapters/omp/ui.ts +85 -0
- package/adapters/pi/commands.ts +64 -37
- package/adapters/pi/events.ts +147 -72
- package/adapters/pi/index.ts +9 -4
- package/adapters/pi/storage.ts +14 -285
- package/adapters/pi/ui.ts +56 -5
- package/adapters/shared/file-storage.ts +526 -0
- package/{adapters/claude/install → cli}/backup.ts +130 -33
- package/cli/buddy-shell.ts +1 -1
- package/{adapters/claude/install → cli}/disable.ts +19 -32
- package/cli/doctor.ts +231 -0
- package/{adapters/claude/install → cli}/hunt.ts +7 -9
- package/cli/index.ts +30 -26
- package/{adapters/claude/install → cli}/install.ts +65 -83
- package/{adapters/claude/install → cli}/pick.ts +111 -50
- package/{adapters/claude/install → cli}/settings.ts +2 -2
- package/{adapters/claude/install → cli}/show.ts +4 -4
- package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
- package/{adapters/claude/install → cli}/test-statusline.ts +21 -11
- package/cli/tui.tsx +106 -17
- package/{adapters/claude/install → cli}/uninstall.ts +40 -47
- package/cli/upgrade.ts +2 -2
- package/cli/validate-species.ts +209 -0
- package/cli/verify.ts +3 -3
- package/core/achievements.ts +12 -20
- package/core/art-data.ts +26 -0
- package/core/command-service.ts +99 -97
- package/core/engine.ts +83 -1
- package/core/ports.ts +10 -0
- package/core/reactions.ts +533 -95
- package/core/slot-slug.ts +22 -0
- package/{adapters/claude/hooks → hooks}/buddy-comment.sh +19 -1
- package/hooks/file-type-react.sh +401 -0
- package/hooks/hooks.json +52 -0
- package/hooks/mood-react.sh +208 -0
- package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
- package/hooks/react.sh +1368 -0
- package/hooks/suggest.sh +55 -0
- package/package.json +70 -31
- package/scripts/gen-emoji-widths.ts +34 -0
- package/scripts/paths.sh +17 -1
- package/scripts/postinstall.cjs +42 -0
- package/scripts/snapshot-statusline.sh +54 -0
- package/server/achievements.ts +1419 -15
- package/server/art.ts +450 -1
- package/server/award-xp.ts +48 -0
- package/server/check-suggestions.ts +102 -0
- package/server/consolidate.ts +33 -0
- package/{adapters/claude/server → server}/index.ts +556 -95
- package/server/mcp-launcher.sh +16 -6
- package/server/memory.ts +404 -0
- package/server/mood.ts +254 -0
- package/server/path.ts +70 -18
- package/server/shift-mood.ts +43 -0
- package/server/state.ts +540 -3
- package/server/suggestions.ts +335 -0
- package/server/theme.ts +53 -0
- package/server/xp.ts +435 -0
- package/{adapters/claude/skills → skills}/buddy/SKILL.md +36 -7
- package/statusline/buddy-status.sh +397 -0
- package/statusline/combined-status.sh +134 -0
- package/statusline/emoji-widths.data +4 -0
- package/adapters/claude/hooks/hooks.json +0 -35
- package/adapters/claude/hooks/react.sh +0 -207
- package/adapters/claude/install/doctor.ts +0 -172
- package/adapters/claude/rendering/art.ts +0 -264
- package/adapters/claude/server/instructions.ts +0 -24
- package/adapters/claude/server/path.ts +0 -12
- package/adapters/claude/server/resources.ts +0 -38
- package/adapters/claude/statusline/buddy-status.sh +0 -408
- package/adapters/claude/storage/achievements.ts +0 -253
- package/adapters/claude/storage/identity.ts +0 -13
- package/adapters/claude/storage/paths.ts +0 -33
- package/adapters/claude/storage/settings.ts +0 -91
- package/adapters/claude/storage/state.ts +0 -365
- package/adapters/pi/README.md +0 -83
- package/adapters/pi/tools.ts +0 -6
- package/server/engine.ts +0 -1
- package/server/reactions.ts +0 -1
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* coding-buddy backup — snapshot all coding-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 <state-dir>/backups/YYYY-MM-DD-HHMMSS/, where
|
|
14
|
+
* <state-dir> resolves via server/paths.ts ($CLAUDE_CONFIG_DIR/buddy-state
|
|
15
|
+
* when that env var is set, else ~/.claude-buddy).
|
|
16
|
+
*
|
|
17
|
+
* What gets backed up:
|
|
18
|
+
* - settings.json (full file)
|
|
19
|
+
* - .claude.json mcpServers["claude-buddy"] block (only our entry)
|
|
20
|
+
* - skills/buddy/SKILL.md
|
|
21
|
+
* - <state-dir>/menagerie.json
|
|
22
|
+
* - <state-dir>/config.json
|
|
23
|
+
* - <state-dir>/status.json
|
|
24
|
+
* - <state-dir>/events.json
|
|
25
|
+
* - <state-dir>/unlocked.json
|
|
26
|
+
* - <state-dir>/active_days.json
|
|
4
27
|
*/
|
|
5
28
|
|
|
6
29
|
import {
|
|
@@ -8,12 +31,18 @@ import {
|
|
|
8
31
|
readdirSync, statSync, rmSync, copyFileSync,
|
|
9
32
|
} from "fs";
|
|
10
33
|
import { dirname, join } from "path";
|
|
11
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
buddyStateDir,
|
|
36
|
+
claudeSettingsPath,
|
|
37
|
+
claudeSkillDir,
|
|
38
|
+
claudeUserConfigPath,
|
|
39
|
+
} from "../server/path.ts";
|
|
12
40
|
|
|
13
|
-
const SETTINGS =
|
|
14
|
-
const CLAUDE_JSON =
|
|
15
|
-
const
|
|
16
|
-
const
|
|
41
|
+
const SETTINGS = claudeSettingsPath();
|
|
42
|
+
const CLAUDE_JSON = claudeUserConfigPath();
|
|
43
|
+
const SKILL_DIR = claudeSkillDir("buddy");
|
|
44
|
+
const SKILL = join(SKILL_DIR, "SKILL.md");
|
|
45
|
+
const STATE_DIR = buddyStateDir();
|
|
17
46
|
const BACKUPS_DIR = join(STATE_DIR, "backups");
|
|
18
47
|
|
|
19
48
|
const RED = "\x1b[31m";
|
|
@@ -42,18 +71,21 @@ function tryRead(path: string): string | null {
|
|
|
42
71
|
function listBackups(): string[] {
|
|
43
72
|
if (!existsSync(BACKUPS_DIR)) return [];
|
|
44
73
|
return readdirSync(BACKUPS_DIR)
|
|
45
|
-
.filter(
|
|
46
|
-
.filter(
|
|
74
|
+
.filter(f => /^\d{4}-\d{2}-\d{2}-\d{6}$/.test(f))
|
|
75
|
+
.filter(f => statSync(join(BACKUPS_DIR, f)).isDirectory())
|
|
47
76
|
.sort();
|
|
48
77
|
}
|
|
49
78
|
|
|
79
|
+
// ─── Create backup ──────────────────────────────────────────────────────────
|
|
80
|
+
|
|
50
81
|
function createBackup(): string {
|
|
51
82
|
const ts = timestamp();
|
|
52
83
|
const dir = join(BACKUPS_DIR, ts);
|
|
53
84
|
mkdirSync(dir, { recursive: true });
|
|
54
85
|
|
|
55
|
-
const manifest:
|
|
86
|
+
const manifest: Record<string, any> = { timestamp: ts, files: [] };
|
|
56
87
|
|
|
88
|
+
// 1. settings.json
|
|
57
89
|
const settings = tryRead(SETTINGS);
|
|
58
90
|
if (settings) {
|
|
59
91
|
writeFileSync(join(dir, "settings.json"), settings);
|
|
@@ -63,6 +95,7 @@ function createBackup(): string {
|
|
|
63
95
|
warn(`Skipped: ${SETTINGS} (not found)`);
|
|
64
96
|
}
|
|
65
97
|
|
|
98
|
+
// 2. claude.json mcpServers["claude-buddy"]
|
|
66
99
|
const claudeJsonRaw = tryRead(CLAUDE_JSON);
|
|
67
100
|
if (claudeJsonRaw) {
|
|
68
101
|
try {
|
|
@@ -80,6 +113,7 @@ function createBackup(): string {
|
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
115
|
|
|
116
|
+
// 3. SKILL.md
|
|
83
117
|
const skill = tryRead(SKILL);
|
|
84
118
|
if (skill) {
|
|
85
119
|
writeFileSync(join(dir, "SKILL.md"), skill);
|
|
@@ -89,22 +123,26 @@ function createBackup(): string {
|
|
|
89
123
|
warn(`Skipped: ${SKILL} (not found)`);
|
|
90
124
|
}
|
|
91
125
|
|
|
126
|
+
// 4+. ~/.claude-buddy/ state files (don't back up the backups dir itself)
|
|
92
127
|
const stateDestDir = join(dir, "claude-buddy");
|
|
93
128
|
mkdirSync(stateDestDir, { recursive: true });
|
|
94
129
|
const stateFiles = ["menagerie.json", "config.json", "status.json", "events.json", "unlocked.json", "active_days.json"];
|
|
95
|
-
for (const
|
|
96
|
-
const src = join(STATE_DIR,
|
|
130
|
+
for (const f of stateFiles) {
|
|
131
|
+
const src = join(STATE_DIR, f);
|
|
97
132
|
if (existsSync(src)) {
|
|
98
|
-
copyFileSync(src, join(stateDestDir,
|
|
99
|
-
manifest.files.push(`claude-buddy/${
|
|
100
|
-
ok(`Backed up: ${join(STATE_DIR,
|
|
133
|
+
copyFileSync(src, join(stateDestDir, f));
|
|
134
|
+
manifest.files.push(`claude-buddy/${f}`);
|
|
135
|
+
ok(`Backed up: ${join(STATE_DIR, f)}`);
|
|
101
136
|
}
|
|
102
137
|
}
|
|
103
138
|
|
|
104
139
|
writeFileSync(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
140
|
+
|
|
105
141
|
return ts;
|
|
106
142
|
}
|
|
107
143
|
|
|
144
|
+
// ─── List backups ───────────────────────────────────────────────────────────
|
|
145
|
+
|
|
108
146
|
function cmdList() {
|
|
109
147
|
const backups = listBackups();
|
|
110
148
|
if (backups.length === 0) {
|
|
@@ -112,13 +150,15 @@ function cmdList() {
|
|
|
112
150
|
info(`Run '${BOLD}bun run backup${NC}' to create one.`);
|
|
113
151
|
return;
|
|
114
152
|
}
|
|
115
|
-
console.log(`\n${BOLD}
|
|
153
|
+
console.log(`\n${BOLD}coding-buddy backups${NC}\n`);
|
|
116
154
|
for (const ts of backups) {
|
|
117
155
|
const manifestPath = join(BACKUPS_DIR, ts, "manifest.json");
|
|
118
156
|
const manifest = tryRead(manifestPath);
|
|
119
157
|
let count = "?";
|
|
120
158
|
if (manifest) {
|
|
121
|
-
try {
|
|
159
|
+
try {
|
|
160
|
+
count = String(JSON.parse(manifest).files?.length ?? 0);
|
|
161
|
+
} catch {}
|
|
122
162
|
}
|
|
123
163
|
const isLatest = ts === backups[backups.length - 1];
|
|
124
164
|
const tag = isLatest ? `${GREEN}(latest)${NC}` : "";
|
|
@@ -127,6 +167,8 @@ function cmdList() {
|
|
|
127
167
|
console.log("");
|
|
128
168
|
}
|
|
129
169
|
|
|
170
|
+
// ─── Show backup contents ───────────────────────────────────────────────────
|
|
171
|
+
|
|
130
172
|
function cmdShow(ts: string) {
|
|
131
173
|
const dir = join(BACKUPS_DIR, ts);
|
|
132
174
|
if (!existsSync(dir)) {
|
|
@@ -141,10 +183,14 @@ function cmdShow(ts: string) {
|
|
|
141
183
|
const data = JSON.parse(manifest);
|
|
142
184
|
console.log(`\n${BOLD}Backup ${ts}${NC}\n`);
|
|
143
185
|
console.log(` ${DIM}Files:${NC}`);
|
|
144
|
-
for (const
|
|
186
|
+
for (const f of data.files) {
|
|
187
|
+
console.log(` - ${f}`);
|
|
188
|
+
}
|
|
145
189
|
console.log("");
|
|
146
190
|
}
|
|
147
191
|
|
|
192
|
+
// ─── Restore backup ─────────────────────────────────────────────────────────
|
|
193
|
+
|
|
148
194
|
function restoreBackup(ts: string) {
|
|
149
195
|
const dir = join(BACKUPS_DIR, ts);
|
|
150
196
|
if (!existsSync(dir)) {
|
|
@@ -154,6 +200,7 @@ function restoreBackup(ts: string) {
|
|
|
154
200
|
|
|
155
201
|
info(`Restoring backup ${ts}...\n`);
|
|
156
202
|
|
|
203
|
+
// 1. settings.json — overwrite
|
|
157
204
|
const settingsBak = join(dir, "settings.json");
|
|
158
205
|
if (existsSync(settingsBak)) {
|
|
159
206
|
mkdirSync(dirname(SETTINGS), { recursive: true });
|
|
@@ -161,39 +208,46 @@ function restoreBackup(ts: string) {
|
|
|
161
208
|
ok(`Restored: ${SETTINGS}`);
|
|
162
209
|
}
|
|
163
210
|
|
|
211
|
+
// 2. claude.json mcpServers — merge our entry back in
|
|
164
212
|
const mcpBak = join(dir, "mcpserver.json");
|
|
165
213
|
if (existsSync(mcpBak)) {
|
|
166
214
|
const ourMcp = JSON.parse(readFileSync(mcpBak, "utf8"));
|
|
167
|
-
let claudeJson:
|
|
215
|
+
let claudeJson: Record<string, any> = {};
|
|
168
216
|
try {
|
|
169
|
-
claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"))
|
|
170
|
-
} catch {}
|
|
217
|
+
claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"));
|
|
218
|
+
} catch { /* empty */ }
|
|
171
219
|
if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
|
|
172
220
|
claudeJson.mcpServers["claude-buddy"] = ourMcp;
|
|
221
|
+
// Under CLAUDE_CONFIG_DIR the parent dir is not guaranteed to exist
|
|
222
|
+
// if settings.json wasn't in this backup (and so step 1 was skipped).
|
|
173
223
|
mkdirSync(dirname(CLAUDE_JSON), { recursive: true });
|
|
174
224
|
writeFileSync(CLAUDE_JSON, JSON.stringify(claudeJson, null, 2));
|
|
175
225
|
ok(`Restored: ${CLAUDE_JSON} → mcpServers["claude-buddy"]`);
|
|
176
226
|
}
|
|
177
227
|
|
|
228
|
+
// 3. SKILL.md
|
|
178
229
|
const skillBak = join(dir, "SKILL.md");
|
|
179
230
|
if (existsSync(skillBak)) {
|
|
180
|
-
mkdirSync(
|
|
231
|
+
mkdirSync(SKILL_DIR, { recursive: true });
|
|
181
232
|
copyFileSync(skillBak, SKILL);
|
|
182
233
|
ok(`Restored: ${SKILL}`);
|
|
183
234
|
}
|
|
184
235
|
|
|
236
|
+
// 4. ~/.claude-buddy/ state files
|
|
185
237
|
const stateDir = join(dir, "claude-buddy");
|
|
186
238
|
if (existsSync(stateDir)) {
|
|
187
239
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
188
|
-
for (const
|
|
189
|
-
copyFileSync(join(stateDir,
|
|
190
|
-
ok(`Restored: ${join(STATE_DIR,
|
|
240
|
+
for (const f of readdirSync(stateDir)) {
|
|
241
|
+
copyFileSync(join(stateDir, f), join(STATE_DIR, f));
|
|
242
|
+
ok(`Restored: ${join(STATE_DIR, f)}`);
|
|
191
243
|
}
|
|
192
244
|
}
|
|
193
245
|
|
|
194
246
|
console.log(`\n${GREEN}Restore complete.${NC} Restart Claude Code to apply.\n`);
|
|
195
247
|
}
|
|
196
248
|
|
|
249
|
+
// ─── Delete backup ──────────────────────────────────────────────────────────
|
|
250
|
+
|
|
197
251
|
function cmdDelete(ts: string) {
|
|
198
252
|
const dir = join(BACKUPS_DIR, ts);
|
|
199
253
|
if (!existsSync(dir)) {
|
|
@@ -204,12 +258,15 @@ function cmdDelete(ts: string) {
|
|
|
204
258
|
ok(`Deleted backup ${ts}`);
|
|
205
259
|
}
|
|
206
260
|
|
|
261
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
262
|
+
|
|
207
263
|
const action = process.argv[2] || "create";
|
|
208
264
|
const arg = process.argv[3];
|
|
209
265
|
|
|
210
266
|
switch (action) {
|
|
211
|
-
case "create":
|
|
212
|
-
|
|
267
|
+
case "create":
|
|
268
|
+
case undefined: {
|
|
269
|
+
console.log(`\n${BOLD}Creating coding-buddy backup...${NC}\n`);
|
|
213
270
|
const ts = createBackup();
|
|
214
271
|
console.log(`\n${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
|
|
215
272
|
console.log(`${GREEN} Backup created: ${ts}${NC}`);
|
|
@@ -219,37 +276,77 @@ switch (action) {
|
|
|
219
276
|
console.log(`${DIM} Or: bun run backup restore ${ts}${NC}\n`);
|
|
220
277
|
break;
|
|
221
278
|
}
|
|
279
|
+
|
|
222
280
|
case "list":
|
|
223
281
|
case "ls":
|
|
224
282
|
cmdList();
|
|
225
283
|
break;
|
|
226
|
-
|
|
284
|
+
|
|
285
|
+
case "show": {
|
|
227
286
|
if (!arg) {
|
|
228
287
|
err("Usage: bun run backup show <timestamp>");
|
|
229
288
|
process.exit(1);
|
|
230
289
|
}
|
|
231
290
|
cmdShow(arg);
|
|
232
291
|
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
233
294
|
case "restore": {
|
|
234
|
-
|
|
235
|
-
const ts = arg ?? backups[backups.length - 1];
|
|
295
|
+
let ts = arg;
|
|
236
296
|
if (!ts) {
|
|
237
|
-
|
|
238
|
-
|
|
297
|
+
const all = listBackups();
|
|
298
|
+
if (all.length === 0) {
|
|
299
|
+
err("No backups to restore");
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
ts = all[all.length - 1];
|
|
303
|
+
info(`Restoring latest backup: ${ts}`);
|
|
239
304
|
}
|
|
240
305
|
restoreBackup(ts);
|
|
241
306
|
break;
|
|
242
307
|
}
|
|
308
|
+
|
|
243
309
|
case "delete":
|
|
244
|
-
case "rm":
|
|
310
|
+
case "rm": {
|
|
245
311
|
if (!arg) {
|
|
246
312
|
err("Usage: bun run backup delete <timestamp>");
|
|
247
313
|
process.exit(1);
|
|
248
314
|
}
|
|
249
315
|
cmdDelete(arg);
|
|
250
316
|
break;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
case "--help":
|
|
320
|
+
case "-h":
|
|
321
|
+
console.log(`
|
|
322
|
+
${BOLD}coding-buddy backup${NC} — snapshot and restore all coding-buddy state
|
|
323
|
+
|
|
324
|
+
${BOLD}Commands:${NC}
|
|
325
|
+
bun run backup Create a new snapshot
|
|
326
|
+
bun run backup list List all backups
|
|
327
|
+
bun run backup show <ts> Show what's in a backup
|
|
328
|
+
bun run backup restore Restore the latest backup
|
|
329
|
+
bun run backup restore <ts> Restore a specific backup
|
|
330
|
+
bun run backup delete <ts> Delete a specific backup
|
|
331
|
+
|
|
332
|
+
${BOLD}What gets backed up:${NC}
|
|
333
|
+
- ${SETTINGS}
|
|
334
|
+
- ${CLAUDE_JSON} mcpServers["claude-buddy"] (only our entry)
|
|
335
|
+
- ${SKILL}
|
|
336
|
+
- ${STATE_DIR}/menagerie.json
|
|
337
|
+
- ${STATE_DIR}/config.json
|
|
338
|
+
- ${STATE_DIR}/status.json
|
|
339
|
+
- ${STATE_DIR}/events.json
|
|
340
|
+
- ${STATE_DIR}/unlocked.json
|
|
341
|
+
- ${STATE_DIR}/active_days.json
|
|
342
|
+
|
|
343
|
+
${BOLD}Backup location:${NC}
|
|
344
|
+
${BACKUPS_DIR}/<timestamp>/
|
|
345
|
+
`);
|
|
346
|
+
break;
|
|
347
|
+
|
|
251
348
|
default:
|
|
252
349
|
err(`Unknown action: ${action}`);
|
|
253
|
-
console.log(
|
|
350
|
+
console.log(`Run 'bun run backup --help' for usage.`);
|
|
254
351
|
process.exit(1);
|
|
255
352
|
}
|
package/cli/buddy-shell.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { fileURLToPath } from "node:url";
|
|
|
38
38
|
|
|
39
39
|
import { buddyStateDir } from "../server/path.ts";
|
|
40
40
|
import { getArtFrame, HAT_ART } from "../server/art.ts";
|
|
41
|
-
import type { Species, Eye, Hat } from "../
|
|
41
|
+
import type { Species, Eye, Hat } from "../core/engine.ts"
|
|
42
42
|
import { getBiome, listBiomes } from "./biomes.ts";
|
|
43
43
|
import xtermPkg from "@xterm/headless";
|
|
44
44
|
import serializePkg from "@xterm/addon-serialize";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* coding-buddy disable — temporarily deactivate buddy without losing data
|
|
4
4
|
*
|
|
5
5
|
* Removes: MCP server, status line, hooks
|
|
6
6
|
* Keeps: companion data, backups, skill files
|
|
@@ -8,28 +8,12 @@
|
|
|
8
8
|
* Re-enable with: bun run install-buddy
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface HookMatcherEntry {
|
|
20
|
-
matcher?: string;
|
|
21
|
-
hooks?: HookCommand[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface ClaudeSettings {
|
|
25
|
-
statusLine?: { command?: string };
|
|
26
|
-
hooks?: Record<string, HookMatcherEntry[]>;
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function hasBuddyHook(entry: HookMatcherEntry): boolean {
|
|
31
|
-
return (entry.hooks ?? []).some((hook) => hook.command.includes("claude-buddy"));
|
|
32
|
-
}
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
12
|
+
import {
|
|
13
|
+
buddyStateDir,
|
|
14
|
+
claudeSettingsPath,
|
|
15
|
+
claudeUserConfigPath,
|
|
16
|
+
} from "../server/path.ts";
|
|
33
17
|
|
|
34
18
|
const GREEN = "\x1b[32m";
|
|
35
19
|
const YELLOW = "\x1b[33m";
|
|
@@ -40,12 +24,13 @@ const NC = "\x1b[0m";
|
|
|
40
24
|
function ok(msg: string) { console.log(`${GREEN}✓${NC} ${msg}`); }
|
|
41
25
|
function warn(msg: string) { console.log(`${YELLOW}⚠${NC} ${msg}`); }
|
|
42
26
|
|
|
43
|
-
const CLAUDE_JSON =
|
|
44
|
-
const SETTINGS =
|
|
45
|
-
const STATE_DIR =
|
|
27
|
+
const CLAUDE_JSON = claudeUserConfigPath();
|
|
28
|
+
const SETTINGS = claudeSettingsPath();
|
|
29
|
+
const STATE_DIR = buddyStateDir();
|
|
46
30
|
|
|
47
|
-
console.log(`\n${BOLD}Disabling
|
|
31
|
+
console.log(`\n${BOLD}Disabling coding-buddy...${NC}\n`);
|
|
48
32
|
|
|
33
|
+
// 1. Remove MCP server from ~/.claude.json
|
|
49
34
|
try {
|
|
50
35
|
const claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"));
|
|
51
36
|
if (claudeJson.mcpServers?.["claude-buddy"]) {
|
|
@@ -60,8 +45,9 @@ try {
|
|
|
60
45
|
warn(`Could not update ${CLAUDE_JSON}`);
|
|
61
46
|
}
|
|
62
47
|
|
|
48
|
+
// 2. Remove status line + hooks from settings.json
|
|
63
49
|
try {
|
|
64
|
-
const settings = JSON.parse(readFileSync(SETTINGS, "utf8"))
|
|
50
|
+
const settings = JSON.parse(readFileSync(SETTINGS, "utf8"));
|
|
65
51
|
let changed = false;
|
|
66
52
|
|
|
67
53
|
if (settings.statusLine?.command?.includes("buddy")) {
|
|
@@ -74,7 +60,9 @@ try {
|
|
|
74
60
|
for (const hookType of ["PostToolUse", "Stop", "SessionStart", "SessionEnd", "UserPromptSubmit"]) {
|
|
75
61
|
if (settings.hooks[hookType]) {
|
|
76
62
|
const before = settings.hooks[hookType].length;
|
|
77
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter(
|
|
63
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(
|
|
64
|
+
(h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
|
|
65
|
+
);
|
|
78
66
|
if (settings.hooks[hookType].length < before) changed = true;
|
|
79
67
|
if (settings.hooks[hookType].length === 0) delete settings.hooks[hookType];
|
|
80
68
|
}
|
|
@@ -90,14 +78,13 @@ try {
|
|
|
90
78
|
warn("Could not update settings.json");
|
|
91
79
|
}
|
|
92
80
|
|
|
81
|
+
// 3. Stop tmux popup if running
|
|
93
82
|
try {
|
|
94
83
|
if (process.env.TMUX) {
|
|
95
84
|
const { execSync } = await import("child_process");
|
|
96
85
|
execSync("tmux display-popup -C 2>/dev/null", { stdio: "ignore" });
|
|
97
86
|
}
|
|
98
|
-
} catch {
|
|
99
|
-
// noop
|
|
100
|
-
}
|
|
87
|
+
} catch { /* not in tmux */ }
|
|
101
88
|
|
|
102
89
|
console.log(`
|
|
103
90
|
${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
|
package/cli/doctor.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* coding-buddy doctor — comprehensive diagnostic report
|
|
4
|
+
*
|
|
5
|
+
* Run: bun run doctor
|
|
6
|
+
*
|
|
7
|
+
* Outputs a complete environment report for bug reports.
|
|
8
|
+
* Copy the entire output and paste it in a GitHub issue.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
12
|
+
import { execSync } from "child_process";
|
|
13
|
+
import { join, resolve, dirname } from "path";
|
|
14
|
+
import {
|
|
15
|
+
buddyStateDir,
|
|
16
|
+
claudeConfigDir,
|
|
17
|
+
claudeSettingsPath,
|
|
18
|
+
claudeSkillDir,
|
|
19
|
+
claudeUserConfigPath,
|
|
20
|
+
} from "../server/path.ts";
|
|
21
|
+
|
|
22
|
+
const PROJECT_ROOT = resolve(dirname(import.meta.dir));
|
|
23
|
+
const CLAUDE_DIR = claudeConfigDir();
|
|
24
|
+
const CLAUDE_JSON = claudeUserConfigPath();
|
|
25
|
+
const STATE_DIR = buddyStateDir();
|
|
26
|
+
const SETTINGS = claudeSettingsPath();
|
|
27
|
+
const SKILL_DIR = claudeSkillDir("buddy");
|
|
28
|
+
const STATUS_SCRIPT = join(PROJECT_ROOT, "statusline", "buddy-status.sh");
|
|
29
|
+
|
|
30
|
+
const RED = "\x1b[31m";
|
|
31
|
+
const GREEN = "\x1b[32m";
|
|
32
|
+
const YELLOW = "\x1b[33m";
|
|
33
|
+
const CYAN = "\x1b[36m";
|
|
34
|
+
const BOLD = "\x1b[1m";
|
|
35
|
+
const DIM = "\x1b[2m";
|
|
36
|
+
const NC = "\x1b[0m";
|
|
37
|
+
|
|
38
|
+
function section(title: string) {
|
|
39
|
+
console.log(`\n${CYAN}${BOLD}━━━ ${title} ${"━".repeat(Math.max(0, 60 - title.length))}${NC}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function row(label: string, value: string) {
|
|
43
|
+
console.log(` ${DIM}${label.padEnd(28)}${NC} ${value}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ok(msg: string) { console.log(` ${GREEN}✓${NC} ${msg}`); }
|
|
47
|
+
function warn(msg: string) { console.log(` ${YELLOW}⚠${NC} ${msg}`); }
|
|
48
|
+
function err(msg: string) { console.log(` ${RED}✗${NC} ${msg}`); }
|
|
49
|
+
|
|
50
|
+
function tryExec(cmd: string, fallback = "(failed)"): string {
|
|
51
|
+
try {
|
|
52
|
+
return execSync(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
53
|
+
} catch {
|
|
54
|
+
return fallback;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function tryRead(path: string): string | null {
|
|
59
|
+
try { return readFileSync(path, "utf8"); } catch { return null; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function tryParseJson(text: string | null): any | null {
|
|
63
|
+
if (!text) return null;
|
|
64
|
+
try { return JSON.parse(text); } catch { return null; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Header ─────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
console.log(`${CYAN}${BOLD}
|
|
70
|
+
╔══════════════════════════════════════════════════════════╗
|
|
71
|
+
║ coding-buddy doctor — diagnostic report ║
|
|
72
|
+
╚══════════════════════════════════════════════════════════╝${NC}`);
|
|
73
|
+
|
|
74
|
+
console.log(`\n${DIM}Copy this entire output into your GitHub issue.${NC}`);
|
|
75
|
+
|
|
76
|
+
// ─── Environment ────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
section("Environment");
|
|
79
|
+
row("OS", tryExec("uname -srm"));
|
|
80
|
+
row("Hostname", tryExec("uname -n"));
|
|
81
|
+
row("User shell", process.env.SHELL ?? "(unset)");
|
|
82
|
+
row("Bash version", tryExec("bash --version | head -1"));
|
|
83
|
+
row("Bun version", tryExec("bun --version"));
|
|
84
|
+
row("Node version", tryExec("node --version", "(not installed)"));
|
|
85
|
+
row("jq version", tryExec("jq --version", "(not installed)"));
|
|
86
|
+
row("Claude Code version", tryExec("claude --version", "(not in PATH)"));
|
|
87
|
+
|
|
88
|
+
// ─── Terminal ───────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
section("Terminal");
|
|
91
|
+
row("TERM", process.env.TERM ?? "(unset)");
|
|
92
|
+
row("COLORTERM", process.env.COLORTERM ?? "(unset)");
|
|
93
|
+
row("TERM_PROGRAM", process.env.TERM_PROGRAM ?? "(unset)");
|
|
94
|
+
row("LANG", process.env.LANG ?? "(unset)");
|
|
95
|
+
row("COLUMNS env var", process.env.COLUMNS ?? "(unset in subprocess)");
|
|
96
|
+
row("stty size", tryExec("stty size 2>/dev/null", "(no tty)"));
|
|
97
|
+
row("tput cols", tryExec("tput cols 2>/dev/null", "(failed)"));
|
|
98
|
+
|
|
99
|
+
// ─── Filesystem checks ──────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
section("Filesystem");
|
|
102
|
+
const procExists = existsSync("/proc");
|
|
103
|
+
row("/proc exists", procExists ? `${GREEN}yes${NC} (Linux)` : `${RED}no${NC} (macOS/BSD)`);
|
|
104
|
+
row("CLAUDE_CONFIG_DIR", process.env.CLAUDE_CONFIG_DIR ?? "(unset)");
|
|
105
|
+
row(`${CLAUDE_DIR} exists`, existsSync(CLAUDE_DIR) ? "yes" : "no");
|
|
106
|
+
row(`${CLAUDE_JSON} exists`, existsSync(CLAUDE_JSON) ? "yes" : "no");
|
|
107
|
+
row(`${STATE_DIR} exists`, existsSync(STATE_DIR) ? "yes" : "no");
|
|
108
|
+
row("Project root", PROJECT_ROOT);
|
|
109
|
+
row("Status script exists", existsSync(STATUS_SCRIPT) ? "yes" : `${RED}no${NC}`);
|
|
110
|
+
|
|
111
|
+
// ─── coding-buddy state ─────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
section("coding-buddy state");
|
|
114
|
+
const menagerie = tryParseJson(tryRead(join(STATE_DIR, "menagerie.json")));
|
|
115
|
+
const status = tryParseJson(tryRead(join(STATE_DIR, "status.json")));
|
|
116
|
+
|
|
117
|
+
if (menagerie) {
|
|
118
|
+
const activeSlot = menagerie.active ?? "buddy";
|
|
119
|
+
const companion = menagerie.companions?.[activeSlot];
|
|
120
|
+
row("Active slot", activeSlot);
|
|
121
|
+
row("Total slots", String(Object.keys(menagerie.companions ?? {}).length));
|
|
122
|
+
if (companion) {
|
|
123
|
+
row("Companion name", companion.name ?? "(none)");
|
|
124
|
+
row("Species", companion.bones?.species ?? "(none)");
|
|
125
|
+
row("Rarity", companion.bones?.rarity ?? "(none)");
|
|
126
|
+
row("Hat", companion.bones?.hat ?? "(none)");
|
|
127
|
+
row("Eye", companion.bones?.eye ?? "(none)");
|
|
128
|
+
row("Shiny", String(companion.bones?.shiny ?? false));
|
|
129
|
+
} else {
|
|
130
|
+
err(`No companion found in active slot "${activeSlot}"`);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
err(`No manifest found at ${join(STATE_DIR, "menagerie.json")}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (status) {
|
|
137
|
+
row("Status muted", String(status.muted ?? false));
|
|
138
|
+
row("Current reaction", status.reaction || "(none)");
|
|
139
|
+
} else {
|
|
140
|
+
warn(`No status state at ${join(STATE_DIR, "status.json")}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── settings.json ──────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
section("Claude Code config");
|
|
146
|
+
const settings = tryParseJson(tryRead(SETTINGS));
|
|
147
|
+
const claudeJson = tryParseJson(tryRead(CLAUDE_JSON));
|
|
148
|
+
|
|
149
|
+
if (settings?.statusLine) {
|
|
150
|
+
console.log(` ${DIM}statusLine:${NC}`);
|
|
151
|
+
console.log(` ${JSON.stringify(settings.statusLine, null, 2).split("\n").join("\n ")}`);
|
|
152
|
+
} else {
|
|
153
|
+
warn(`No statusLine in ${SETTINGS}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (settings?.hooks) {
|
|
157
|
+
console.log(` ${DIM}hooks:${NC}`);
|
|
158
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
159
|
+
const count = settings.hooks[event]?.length ?? 0;
|
|
160
|
+
row(` ${event}`, `${count} entr${count === 1 ? "y" : "ies"}`);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
warn("No hooks configured");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (claudeJson?.mcpServers?.["claude-buddy"]) {
|
|
167
|
+
ok(`MCP server registered in ${CLAUDE_JSON}`);
|
|
168
|
+
console.log(` ${JSON.stringify(claudeJson.mcpServers["claude-buddy"], null, 2).split("\n").join("\n ")}`);
|
|
169
|
+
} else {
|
|
170
|
+
err(`MCP server NOT registered in ${CLAUDE_JSON}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const skillPath = join(SKILL_DIR, "SKILL.md");
|
|
174
|
+
if (existsSync(skillPath)) {
|
|
175
|
+
ok(`Skill installed: ${skillPath}`);
|
|
176
|
+
} else {
|
|
177
|
+
err(`Skill missing: ${skillPath}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Live status line test ──────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
section("Live status line output");
|
|
183
|
+
console.log(` ${DIM}(running: echo '{}' | ${STATUS_SCRIPT})${NC}\n`);
|
|
184
|
+
const liveOutput = tryExec(`echo '{}' | bash "${STATUS_SCRIPT}" 2>&1`, "(script failed)");
|
|
185
|
+
const lines = liveOutput.split("\n");
|
|
186
|
+
console.log(lines.map(l => ` │ ${l}`).join("\n"));
|
|
187
|
+
console.log(` ${DIM}(${lines.length} lines, total ${liveOutput.length} bytes)${NC}`);
|
|
188
|
+
|
|
189
|
+
// ─── Padding strategy test ──────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
section("Padding strategy test");
|
|
192
|
+
console.log(` ${DIM}Each row should appear right-aligned with marker '|END'.${NC}`);
|
|
193
|
+
console.log(` ${DIM}If a row is misaligned, that strategy is broken in this terminal.${NC}\n`);
|
|
194
|
+
|
|
195
|
+
const PAD = 40;
|
|
196
|
+
const strategies: [string, string][] = [
|
|
197
|
+
["space (will be trimmed!)", " "],
|
|
198
|
+
["braille blank U+2800", "\u2800"],
|
|
199
|
+
["non-breaking space U+00A0", "\u00A0"],
|
|
200
|
+
["dot .", "."],
|
|
201
|
+
["middle dot ·", "\u00B7"],
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
for (const [name, ch] of strategies) {
|
|
205
|
+
const padding = ch.repeat(PAD);
|
|
206
|
+
console.log(` ${padding}|END ${DIM}← ${name}${NC}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── string-width comparison ────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
section("Display width vs string-width (npm)");
|
|
212
|
+
console.log(` ${DIM}Most terminals render Braille Blank as 2 columns.${NC}`);
|
|
213
|
+
console.log(` ${DIM}But the npm 'string-width' package counts it as 1.${NC}`);
|
|
214
|
+
console.log(` ${DIM}Claude Code uses string-width for layout calculations.${NC}\n`);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// Try to load string-width if available
|
|
218
|
+
const sw = require("string-width");
|
|
219
|
+
row("string-width(' ')", String(sw(" ")));
|
|
220
|
+
row("string-width('\\u2800')", String(sw("\u2800")));
|
|
221
|
+
row("string-width('\\u00A0')", String(sw("\u00A0")));
|
|
222
|
+
row("string-width('-o-OO-o-')", String(sw("-o-OO-o-")));
|
|
223
|
+
row("string-width('⠀⠀⠀⠀⠀-o-OO-o-')", String(sw("\u2800\u2800\u2800\u2800\u2800-o-OO-o-")));
|
|
224
|
+
} catch {
|
|
225
|
+
warn("string-width not installed in project — skipping comparison");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Footer ─────────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
console.log(`\n${CYAN}${BOLD}━━━ End of report ${"━".repeat(46)}${NC}\n`);
|
|
231
|
+
console.log(`${DIM}Copy everything above into your GitHub issue.${NC}\n`);
|