@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.
Files changed (92) hide show
  1. package/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
  2. package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
  3. package/LICENSE +1 -1
  4. package/README.md +88 -37
  5. package/adapters/omp/commands.ts +201 -0
  6. package/adapters/omp/context.ts +22 -0
  7. package/adapters/omp/events.ts +551 -0
  8. package/adapters/omp/identity.ts +10 -0
  9. package/adapters/omp/index.ts +39 -0
  10. package/adapters/omp/logger.ts +52 -0
  11. package/adapters/omp/prompt.ts +60 -0
  12. package/adapters/omp/renderers.ts +73 -0
  13. package/adapters/omp/storage.ts +25 -0
  14. package/adapters/omp/ui.ts +85 -0
  15. package/adapters/pi/commands.ts +64 -37
  16. package/adapters/pi/events.ts +147 -72
  17. package/adapters/pi/index.ts +9 -4
  18. package/adapters/pi/storage.ts +14 -285
  19. package/adapters/pi/ui.ts +56 -5
  20. package/adapters/shared/file-storage.ts +526 -0
  21. package/{adapters/claude/install → cli}/backup.ts +130 -33
  22. package/cli/buddy-shell.ts +1 -1
  23. package/{adapters/claude/install → cli}/disable.ts +19 -32
  24. package/cli/doctor.ts +231 -0
  25. package/{adapters/claude/install → cli}/hunt.ts +7 -9
  26. package/cli/index.ts +30 -26
  27. package/{adapters/claude/install → cli}/install.ts +65 -83
  28. package/{adapters/claude/install → cli}/pick.ts +111 -50
  29. package/{adapters/claude/install → cli}/settings.ts +2 -2
  30. package/{adapters/claude/install → cli}/show.ts +4 -4
  31. package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
  32. package/{adapters/claude/install → cli}/test-statusline.ts +21 -11
  33. package/cli/tui.tsx +106 -17
  34. package/{adapters/claude/install → cli}/uninstall.ts +40 -47
  35. package/cli/upgrade.ts +2 -2
  36. package/cli/validate-species.ts +209 -0
  37. package/cli/verify.ts +3 -3
  38. package/core/achievements.ts +12 -20
  39. package/core/art-data.ts +26 -0
  40. package/core/command-service.ts +99 -97
  41. package/core/engine.ts +83 -1
  42. package/core/ports.ts +10 -0
  43. package/core/reactions.ts +533 -95
  44. package/core/slot-slug.ts +22 -0
  45. package/{adapters/claude/hooks → hooks}/buddy-comment.sh +19 -1
  46. package/hooks/file-type-react.sh +401 -0
  47. package/hooks/hooks.json +52 -0
  48. package/hooks/mood-react.sh +208 -0
  49. package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
  50. package/hooks/react.sh +1368 -0
  51. package/hooks/suggest.sh +55 -0
  52. package/package.json +70 -31
  53. package/scripts/gen-emoji-widths.ts +34 -0
  54. package/scripts/paths.sh +17 -1
  55. package/scripts/postinstall.cjs +42 -0
  56. package/scripts/snapshot-statusline.sh +54 -0
  57. package/server/achievements.ts +1419 -15
  58. package/server/art.ts +450 -1
  59. package/server/award-xp.ts +48 -0
  60. package/server/check-suggestions.ts +102 -0
  61. package/server/consolidate.ts +33 -0
  62. package/{adapters/claude/server → server}/index.ts +556 -95
  63. package/server/mcp-launcher.sh +16 -6
  64. package/server/memory.ts +404 -0
  65. package/server/mood.ts +254 -0
  66. package/server/path.ts +70 -18
  67. package/server/shift-mood.ts +43 -0
  68. package/server/state.ts +540 -3
  69. package/server/suggestions.ts +335 -0
  70. package/server/theme.ts +53 -0
  71. package/server/xp.ts +435 -0
  72. package/{adapters/claude/skills → skills}/buddy/SKILL.md +36 -7
  73. package/statusline/buddy-status.sh +397 -0
  74. package/statusline/combined-status.sh +134 -0
  75. package/statusline/emoji-widths.data +4 -0
  76. package/adapters/claude/hooks/hooks.json +0 -35
  77. package/adapters/claude/hooks/react.sh +0 -207
  78. package/adapters/claude/install/doctor.ts +0 -172
  79. package/adapters/claude/rendering/art.ts +0 -264
  80. package/adapters/claude/server/instructions.ts +0 -24
  81. package/adapters/claude/server/path.ts +0 -12
  82. package/adapters/claude/server/resources.ts +0 -38
  83. package/adapters/claude/statusline/buddy-status.sh +0 -408
  84. package/adapters/claude/storage/achievements.ts +0 -253
  85. package/adapters/claude/storage/identity.ts +0 -13
  86. package/adapters/claude/storage/paths.ts +0 -33
  87. package/adapters/claude/storage/settings.ts +0 -91
  88. package/adapters/claude/storage/state.ts +0 -365
  89. package/adapters/pi/README.md +0 -83
  90. package/adapters/pi/tools.ts +0 -6
  91. package/server/engine.ts +0 -1
  92. package/server/reactions.ts +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * claude-buddy hunt — brute-force search for a specific buddy
2
+ * coding-buddy hunt — brute-force search for a specific buddy
3
3
  *
4
4
  * Rules:
5
5
  * - Asks for a name before saving
@@ -7,14 +7,12 @@
7
7
  * - Appends to the manifest — never overwrites an existing slot
8
8
  */
9
9
 
10
- import {
11
- searchBuddy, renderBuddy, SPECIES, RARITIES, STAT_NAMES,
12
- type Species, type Rarity, type StatName, type SearchCriteria,
13
- } from "../../../core/engine.ts";
10
+ import { searchBuddy, renderBuddy, SPECIES, RARITIES, STAT_NAMES,
11
+ type Species, type Rarity, type StatName, type SearchCriteria, } from "../core/engine.ts"
14
12
  import {
15
13
  saveCompanionSlot, saveActiveSlot, writeStatusState,
16
14
  slugify, unusedName, listCompanionSlots,
17
- } from "../storage/state.ts";
15
+ } from "../server/state.ts";
18
16
  import { createInterface } from "readline";
19
17
 
20
18
  const CYAN = "\x1b[36m";
@@ -47,7 +45,7 @@ function pickFromList<T extends string>(label: string, items: readonly T[]): Pro
47
45
  async function main() {
48
46
  console.log(`
49
47
  ${CYAN}╔══════════════════════════════════════════════════════════╗${NC}
50
- ${CYAN}║${NC} ${BOLD}claude-buddy hunt${NC} — find your perfect companion ${CYAN}║${NC}
48
+ ${CYAN}║${NC} ${BOLD}coding-buddy hunt${NC} — find your perfect companion ${CYAN}║${NC}
51
49
  ${CYAN}╚══════════════════════════════════════════════════════════╝${NC}
52
50
  `);
53
51
 
@@ -67,8 +65,8 @@ ${CYAN}╚═══════════════════════
67
65
  const statsAns = await ask(`\n Configure stats? [Y/n]: `);
68
66
  if (statsAns.toLowerCase() !== "n") {
69
67
  wantPeak = await pickFromList("Peak stat (highest):", STAT_NAMES);
70
- const dumpOptions = STAT_NAMES.filter((s): s is Exclude<StatName, typeof wantPeak> => s !== wantPeak);
71
- wantDump = await pickFromList("Dump stat (lowest):", dumpOptions);
68
+ const dumpOptions = STAT_NAMES.filter((s) => s !== wantPeak);
69
+ wantDump = await pickFromList("Dump stat (lowest):", dumpOptions as any);
72
70
  console.log(`${GREEN}✓${NC} peak=${wantPeak} dump=${wantDump}`);
73
71
  }
74
72
 
package/cli/index.ts CHANGED
@@ -1,60 +1,62 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * claude-buddy CLI
3
+ * coding-buddy CLI
4
4
  *
5
5
  * Usage:
6
- * npx claude-buddy Interactive install
7
- * npx claude-buddy install Install MCP + skill + hooks + statusline
8
- * npx claude-buddy show Show current buddy
9
- * npx claude-buddy pick Interactive two-pane buddy picker (saved + search)
10
- * npx claude-buddy hunt Search for a specific buddy (non-interactive)
11
- * npx claude-buddy upgrade Pull latest + reinstall
12
- * npx claude-buddy uninstall Remove all integrations
13
- * npx claude-buddy verify Verify what buddy your ID produces
6
+ * npx -y @ramarivera/coding-buddy Interactive install
7
+ * npx -y @ramarivera/coding-buddy install Install MCP + skill + hooks + statusline
8
+ * npx -y @ramarivera/coding-buddy show Show current buddy
9
+ * npx -y @ramarivera/coding-buddy pick Interactive two-pane buddy picker (saved + search)
10
+ * npx -y @ramarivera/coding-buddy hunt Search for a specific buddy (non-interactive)
11
+ * npx -y @ramarivera/coding-buddy upgrade Pull latest + reinstall
12
+ * npx -y @ramarivera/coding-buddy uninstall Remove all integrations
13
+ * npx -y @ramarivera/coding-buddy verify Verify what buddy your ID produces
14
14
  */
15
15
 
16
+ export {}; // Make this file a module for TypeScript top-level await
17
+
16
18
  const args = process.argv.slice(2);
17
19
  const command = args[0] || "install";
18
20
 
19
21
  switch (command) {
20
22
  case "install":
21
- await import("../adapters/claude/install/install.ts");
23
+ await import("./install.ts");
22
24
  break;
23
25
  case "show":
24
- await import("../adapters/claude/install/show.ts");
26
+ await import("./show.ts");
25
27
  break;
26
28
  case "pick":
27
- await import("../adapters/claude/install/pick.ts");
29
+ await import("./pick.ts");
28
30
  break;
29
31
  case "hunt":
30
- await import("../adapters/claude/install/hunt.ts");
32
+ await import("./hunt.ts");
31
33
  break;
32
34
  case "uninstall":
33
- await import("../adapters/claude/install/uninstall.ts");
35
+ await import("./uninstall.ts");
34
36
  break;
35
37
  case "verify":
36
38
  await import("./verify.ts");
37
39
  break;
38
40
  case "doctor":
39
- await import("../adapters/claude/install/doctor.ts");
41
+ await import("./doctor.ts");
40
42
  break;
41
43
  case "test-statusline":
42
- await import("../adapters/claude/install/test-statusline.ts");
44
+ await import("./test-statusline.ts");
43
45
  break;
44
46
  case "backup":
45
- await import("../adapters/claude/install/backup.ts");
47
+ await import("./backup.ts");
46
48
  break;
47
49
  case "settings":
48
- await import("../adapters/claude/install/settings.ts");
50
+ await import("./settings.ts");
49
51
  break;
50
52
  case "disable":
51
- await import("../adapters/claude/install/disable.ts");
53
+ await import("./disable.ts");
52
54
  break;
53
55
  case "upgrade":
54
56
  await import("./upgrade.ts");
55
57
  break;
56
58
  case "enable":
57
- await import("../adapters/claude/install/install.ts");
59
+ await import("./install.ts");
58
60
  break;
59
61
  case "help":
60
62
  case "--help":
@@ -69,14 +71,14 @@ switch (command) {
69
71
 
70
72
  function showHelp() {
71
73
  console.log(`
72
- claude-buddy — permanent coding companion for Claude Code
74
+ coding-buddy — permanent coding companion for Claude Code, Pi, and Oh My Pi
73
75
 
74
76
  Setup:
75
77
  install-buddy Set up MCP server, skill, hooks, and status line
76
78
  upgrade Pull latest version and reinstall integrations
77
79
  enable Same as install-buddy (re-enable after disable)
78
80
  disable Temporarily deactivate buddy (data preserved)
79
- uninstall Remove all claude-buddy integrations
81
+ uninstall Remove all coding-buddy integrations
80
82
 
81
83
  Buddy:
82
84
  show Display your current buddy
@@ -94,7 +96,7 @@ Settings:
94
96
  Diagnostics:
95
97
  doctor Run diagnostic report (paste output in bug reports)
96
98
  test-statusline Test status line rendering in Claude Code
97
- backup Snapshot or restore all claude-buddy state
99
+ backup Snapshot or restore all coding-buddy state
98
100
 
99
101
  In Claude Code:
100
102
  /buddy Show companion card with ASCII art + stats
@@ -113,10 +115,12 @@ In Claude Code:
113
115
  /buddy style Show or set bubble style (tmux only)
114
116
  /buddy position Show or set bubble position (tmux only)
115
117
  /buddy rarity Show or hide rarity stars (tmux only)
118
+ /buddy width Set bubble text width in chars (10-60, tmux only)
116
119
 
117
120
  Usage:
118
- bun run <command> e.g. bun run show, bun run doctor
119
- claude-buddy <command> if globally linked (bun link)
120
- bun run help Show this help
121
+ npx -y @ramarivera/coding-buddy <command> Run from the registry without cloning
122
+ bun run <command> e.g. bun run show, bun run doctor (in repo)
123
+ coding-buddy <command> if globally linked (bun link)
124
+ bun run help Show this help
121
125
  `);
122
126
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * claude-buddy installer
2
+ * coding-buddy installer
3
3
  *
4
4
  * Registers: MCP server (in Claude's user config), skill, hooks, status line
5
5
  * (in settings.json). All paths resolve via server/paths.ts, so the installer
@@ -7,15 +7,20 @@
7
7
  * ~/.claude/ layout when the env var is unset.
8
8
  */
9
9
 
10
- import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, statSync } from "fs";
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync } from "fs";
11
11
  import { execSync } from "child_process";
12
- import { join, resolve } from "path";
13
-
14
- import { generateBones, renderBuddy, renderFace, RARITY_STARS } from "../../../core/engine.ts";
15
- import { loadCompanion, saveCompanion, writeStatusState } from "../storage/state.ts";
16
- import { resolveUserId } from "../storage/identity.ts";
17
- import { generateFallbackName } from "../../../core/reactions.ts";
18
- import { getBuddySkillDir, getBuddyStateDir, getClaudeConfigDir, getClaudeJsonPath, getClaudeSettingsPath, toUnixPath } from "../storage/paths.ts";
12
+ import { resolve, dirname, join } from "path";
13
+
14
+ import { generateBones, renderBuddy, renderFace, RARITY_STARS } from "../core/engine.ts"
15
+ import {
16
+ claudeConfigDir,
17
+ claudeSettingsPath,
18
+ claudeSkillDir,
19
+ claudeUserConfigPath,
20
+ toUnixPath,
21
+ } from "../server/path.ts";
22
+ import { loadCompanion, saveCompanion, resolveUserId, writeStatusState } from "../server/state.ts";
23
+ import { generateFallbackName } from "../core/reactions.ts"
19
24
 
20
25
  const CYAN = "\x1b[36m";
21
26
  const GREEN = "\x1b[32m";
@@ -25,17 +30,16 @@ const BOLD = "\x1b[1m";
25
30
  const DIM = "\x1b[2m";
26
31
  const NC = "\x1b[0m";
27
32
 
28
- const CLAUDE_DIR = getClaudeConfigDir();
29
- const SETTINGS_FILE = getClaudeSettingsPath();
30
- const BUDDY_DIR = getBuddySkillDir();
31
- const CLAUDE_JSON = getClaudeJsonPath();
32
- const STATE_DIR = getBuddyStateDir();
33
- const PROJECT_ROOT = resolve(import.meta.dir, "../../..");
33
+ const CLAUDE_DIR = claudeConfigDir();
34
+ const SETTINGS_FILE = claudeSettingsPath();
35
+ const BUDDY_DIR = claudeSkillDir("buddy");
36
+ const CLAUDE_JSON_PATH = claudeUserConfigPath();
37
+ const PROJECT_ROOT = resolve(dirname(import.meta.dir));
34
38
 
35
39
  function banner() {
36
40
  console.log(`
37
41
  ${CYAN}╔══════════════════════════════════════════════════════════╗${NC}
38
- ${CYAN}║${NC} ${BOLD}claude-buddy${NC} — permanent coding companion ${CYAN}║${NC}
42
+ ${CYAN}║${NC} ${BOLD}coding-buddy${NC} — permanent coding companion ${CYAN}║${NC}
39
43
  ${CYAN}║${NC} ${DIM}MCP + Skill + StatusLine + Hooks${NC} ${CYAN}║${NC}
40
44
  ${CYAN}╚══════════════════════════════════════════════════════════╝${NC}
41
45
  `);
@@ -75,21 +79,20 @@ function preflight(): boolean {
75
79
  }
76
80
  }
77
81
 
82
+ // Check Claude config dir exists
78
83
  if (!existsSync(CLAUDE_DIR)) {
79
- err(`Claude config dir not found: ${CLAUDE_DIR}. Start Claude Code once first, then re-run.`);
80
- pass = false;
81
- } else if (!statSync(CLAUDE_DIR).isDirectory()) {
82
- err(`Claude config path is not a directory: ${CLAUDE_DIR}`);
84
+ err(`${CLAUDE_DIR} not found. Start Claude Code once first, then re-run.`);
83
85
  pass = false;
84
86
  } else {
85
- ok(`Claude config dir found: ${CLAUDE_DIR}`);
87
+ ok(`${CLAUDE_DIR} found`);
86
88
  }
87
89
 
88
- if (!existsSync(CLAUDE_JSON)) {
89
- err(`Claude config file not found: ${CLAUDE_JSON}. Start Claude Code once first, then re-run.`);
90
+ // Check Claude user config (.claude.json) exists
91
+ if (!existsSync(CLAUDE_JSON_PATH)) {
92
+ err(`${CLAUDE_JSON_PATH} not found. Start Claude Code once first, then re-run.`);
90
93
  pass = false;
91
94
  } else {
92
- ok(`Claude config file found: ${CLAUDE_JSON}`);
95
+ ok(`${CLAUDE_JSON_PATH} found`);
93
96
  }
94
97
 
95
98
  return pass;
@@ -97,50 +100,15 @@ function preflight(): boolean {
97
100
 
98
101
  // ─── Load / update settings.json ────────────────────────────────────────────
99
102
 
100
- interface HookCommand {
101
- type: "command";
102
- command: string;
103
- }
104
-
105
- interface HookMatcherEntry {
106
- matcher?: string;
107
- hooks?: HookCommand[];
108
- }
109
-
110
- interface ClaudeStatusLineConfig {
111
- type: "command";
112
- command: string;
113
- padding: number;
114
- refreshInterval: number;
115
- }
116
-
117
- interface ClaudeSettings {
118
- statusLine?: ClaudeStatusLineConfig;
119
- hooks?: Partial<Record<"SessionStart" | "SessionEnd" | "PostToolUse" | "Stop" | "UserPromptSubmit", HookMatcherEntry[]>>;
120
- permissions?: {
121
- allow?: string[];
122
- };
123
- [key: string]: unknown;
124
- }
125
-
126
- interface ClaudeJsonConfig {
127
- mcpServers?: Record<string, { command: string; args: string[]; cwd: string }>;
128
- [key: string]: unknown;
129
- }
130
-
131
- function hasBuddyHook(entry: HookMatcherEntry): boolean {
132
- return (entry.hooks ?? []).some((hook) => hook.command.includes("claude-buddy"));
133
- }
134
-
135
- function loadSettings(): ClaudeSettings {
103
+ function loadSettings(): Record<string, any> {
136
104
  try {
137
- return JSON.parse(readFileSync(SETTINGS_FILE, "utf8")) as ClaudeSettings;
105
+ return JSON.parse(readFileSync(SETTINGS_FILE, "utf8"));
138
106
  } catch {
139
107
  return {};
140
108
  }
141
109
  }
142
110
 
143
- function saveSettings(settings: ClaudeSettings) {
111
+ function saveSettings(settings: Record<string, any>) {
144
112
  mkdirSync(CLAUDE_DIR, { recursive: true });
145
113
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
146
114
  }
@@ -148,12 +116,11 @@ function saveSettings(settings: ClaudeSettings) {
148
116
  // ─── Step 1: Register MCP server (in ~/.claude.json) ────────────────────────
149
117
 
150
118
  function installMcp() {
151
- const serverPath = join(PROJECT_ROOT, "adapters", "claude", "server", "index.ts");
152
- const claudeJsonPath = CLAUDE_JSON;
119
+ const serverPath = join(PROJECT_ROOT, "server", "index.ts");
153
120
 
154
- let claudeJson: ClaudeJsonConfig = {};
121
+ let claudeJson: Record<string, any> = {};
155
122
  try {
156
- claudeJson = JSON.parse(readFileSync(claudeJsonPath, "utf8")) as ClaudeJsonConfig;
123
+ claudeJson = JSON.parse(readFileSync(CLAUDE_JSON_PATH, "utf8"));
157
124
  } catch { /* fresh config */ }
158
125
 
159
126
  if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
@@ -164,14 +131,14 @@ function installMcp() {
164
131
  cwd: toUnixPath(PROJECT_ROOT),
165
132
  };
166
133
 
167
- writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
168
- ok(`MCP server registered in ${claudeJsonPath}`);
134
+ writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2));
135
+ ok(`MCP server registered in ${CLAUDE_JSON_PATH}`);
169
136
  }
170
137
 
171
138
  // ─── Step 2: Install skill ──────────────────────────────────────────────────
172
139
 
173
140
  function installSkill() {
174
- const srcSkill = join(PROJECT_ROOT, "adapters", "claude", "skills", "buddy", "SKILL.md");
141
+ const srcSkill = join(PROJECT_ROOT, "skills", "buddy", "SKILL.md");
175
142
  mkdirSync(BUDDY_DIR, { recursive: true });
176
143
  cpSync(srcSkill, join(BUDDY_DIR, "SKILL.md"), { force: true });
177
144
  ok(`Skill installed: ${join(BUDDY_DIR, "SKILL.md")}`);
@@ -179,8 +146,8 @@ function installSkill() {
179
146
 
180
147
  // ─── Step 3: Configure status line (with animation refresh) ─────────────────
181
148
 
182
- function installStatusLine(settings: ClaudeSettings) {
183
- const statusScript = join(PROJECT_ROOT, "adapters", "claude", "statusline", "buddy-status.sh");
149
+ function installStatusLine(settings: Record<string, any>) {
150
+ const statusScript = join(PROJECT_ROOT, "statusline", "buddy-status.sh");
184
151
 
185
152
  settings.statusLine = {
186
153
  type: "command",
@@ -213,47 +180,62 @@ function stripLegacyPopupHooks(settings: Record<string, any>) {
213
180
 
214
181
  // ─── Step 4: Register hooks ─────────────────────────────────────────────────
215
182
 
216
- function installHooks(settings: ClaudeSettings) {
217
- const reactHook = join(PROJECT_ROOT, "adapters", "claude", "hooks", "react.sh");
218
- const commentHook = join(PROJECT_ROOT, "adapters", "claude", "hooks", "buddy-comment.sh");
219
- const nameHook = join(PROJECT_ROOT, "adapters", "claude", "hooks", "name-react.sh");
183
+ function installHooks(settings: Record<string, any>) {
184
+ const reactHook = join(PROJECT_ROOT, "hooks", "react.sh");
185
+ const fileTypeHook = join(PROJECT_ROOT, "hooks", "file-type-react.sh");
186
+ const commentHook = join(PROJECT_ROOT, "hooks", "buddy-comment.sh");
187
+ const suggestHook = join(PROJECT_ROOT, "hooks", "suggest.sh");
188
+ const nameHook = join(PROJECT_ROOT, "hooks", "name-react.sh");
189
+ const moodHook = join(PROJECT_ROOT, "hooks", "mood-react.sh");
220
190
 
221
191
  if (!settings.hooks) settings.hooks = {};
222
192
 
223
- // PostToolUse: detect errors/test failures/successes in Bash output
193
+ // PostToolUse: detect errors/test failures/successes in Bash output,
194
+ // plus file-type specific reactions on Write/Edit.
224
195
  if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
225
196
  settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
226
- (h) => !hasBuddyHook(h),
197
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
227
198
  );
228
199
  settings.hooks.PostToolUse.push({
229
200
  matcher: "Bash",
230
201
  hooks: [{ type: "command", command: toUnixPath(reactHook) }],
231
202
  });
203
+ settings.hooks.PostToolUse.push({
204
+ matcher: "Write|Edit",
205
+ hooks: [{ type: "command", command: toUnixPath(fileTypeHook) }],
206
+ });
232
207
 
233
208
  // Stop: extract <!-- buddy: --> comment from Claude's response
234
209
  if (!settings.hooks.Stop) settings.hooks.Stop = [];
235
210
  settings.hooks.Stop = settings.hooks.Stop.filter(
236
- (h) => !hasBuddyHook(h),
211
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
237
212
  );
238
213
  settings.hooks.Stop.push({
239
214
  hooks: [{ type: "command", command: toUnixPath(commentHook) }],
240
215
  });
216
+ settings.hooks.Stop.push({
217
+ hooks: [{ type: "command", command: toUnixPath(suggestHook) }],
218
+ });
241
219
 
242
- // UserPromptSubmit: detect buddy's name in user message → instant status line reaction
220
+ // UserPromptSubmit: detect buddy's name in user message → instant status line
221
+ // reaction, plus mood-react based on prompt content.
243
222
  if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
244
223
  settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
245
- (h) => !hasBuddyHook(h),
224
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
246
225
  );
247
226
  settings.hooks.UserPromptSubmit.push({
248
227
  hooks: [{ type: "command", command: toUnixPath(nameHook) }],
249
228
  });
229
+ settings.hooks.UserPromptSubmit.push({
230
+ hooks: [{ type: "command", command: toUnixPath(moodHook) }],
231
+ });
250
232
 
251
- ok("Hooks registered: PostToolUse + Stop + UserPromptSubmit");
233
+ ok("Hooks registered: PostToolUse (Bash + Write/Edit) + Stop (comment + suggest) + UserPromptSubmit (name + mood)");
252
234
  }
253
235
 
254
236
  // ─── Step 5: Ensure MCP tools are allowed ───────────────────────────────────
255
237
 
256
- function ensurePermissions(settings: ClaudeSettings) {
238
+ function ensurePermissions(settings: Record<string, any>) {
257
239
  if (!settings.permissions) settings.permissions = {};
258
240
  if (!settings.permissions.allow) settings.permissions.allow = [];
259
241
 
@@ -310,7 +292,7 @@ if (!preflight()) {
310
292
  }
311
293
 
312
294
  console.log("");
313
- info("Installing claude-buddy...\n");
295
+ info("Installing coding-buddy...\n");
314
296
 
315
297
  const settings = loadSettings();
316
298