@ramarivera/coding-buddy 0.4.0-alpha.8 → 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 +9 -7
  3. package/LICENSE +1 -1
  4. package/README.md +102 -72
  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 +59 -44
  22. package/cli/biomes.ts +309 -0
  23. package/cli/buddy-shell.ts +818 -0
  24. package/{adapters/claude/install → cli}/disable.ts +14 -32
  25. package/{adapters/claude/install → cli}/doctor.ts +55 -57
  26. package/{adapters/claude/install → cli}/hunt.ts +7 -9
  27. package/cli/index.ts +36 -25
  28. package/{adapters/claude/install → cli}/install.ts +100 -145
  29. package/{adapters/claude/install → cli}/pick.ts +111 -50
  30. package/{adapters/claude/install → cli}/settings.ts +2 -2
  31. package/{adapters/claude/install → cli}/show.ts +4 -4
  32. package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
  33. package/{adapters/claude/install → cli}/test-statusline.ts +11 -11
  34. package/cli/tui.tsx +2333 -0
  35. package/{adapters/claude/install → cli}/uninstall.ts +23 -38
  36. package/cli/upgrade.ts +213 -0
  37. package/cli/validate-species.ts +209 -0
  38. package/cli/verify.ts +3 -3
  39. package/core/achievements.ts +12 -20
  40. package/core/art-data.ts +26 -0
  41. package/core/command-service.ts +99 -97
  42. package/core/engine.ts +83 -1
  43. package/core/ports.ts +10 -0
  44. package/core/reactions.ts +533 -95
  45. package/core/slot-slug.ts +22 -0
  46. package/{adapters/claude/hooks → hooks}/buddy-comment.sh +23 -2
  47. package/hooks/file-type-react.sh +401 -0
  48. package/hooks/hooks.json +52 -0
  49. package/hooks/mood-react.sh +208 -0
  50. package/{adapters/claude/hooks → hooks}/name-react.sh +5 -2
  51. package/hooks/react.sh +1368 -0
  52. package/hooks/suggest.sh +55 -0
  53. package/package.json +113 -59
  54. package/scripts/gen-emoji-widths.ts +34 -0
  55. package/scripts/paths.sh +56 -0
  56. package/scripts/postinstall.cjs +42 -0
  57. package/scripts/snapshot-statusline.sh +54 -0
  58. package/server/achievements.ts +1419 -0
  59. package/server/art.ts +450 -0
  60. package/server/award-xp.ts +48 -0
  61. package/server/check-suggestions.ts +102 -0
  62. package/server/consolidate.ts +33 -0
  63. package/server/index.ts +1340 -0
  64. package/server/mcp-launcher.sh +26 -0
  65. package/server/memory.ts +404 -0
  66. package/server/mood.ts +254 -0
  67. package/server/path.ts +82 -0
  68. package/server/shift-mood.ts +43 -0
  69. package/{adapters/claude/storage → server}/state.ts +200 -7
  70. package/server/suggestions.ts +335 -0
  71. package/server/theme.ts +53 -0
  72. package/server/xp.ts +435 -0
  73. package/{adapters/claude/skills → skills}/buddy/SKILL.md +48 -4
  74. package/statusline/buddy-status.sh +397 -0
  75. package/statusline/combined-status.sh +134 -0
  76. package/statusline/emoji-widths.data +4 -0
  77. package/adapters/claude/hooks/hooks.json +0 -35
  78. package/adapters/claude/hooks/react.sh +0 -204
  79. package/adapters/claude/popup/buddy-popup.sh +0 -92
  80. package/adapters/claude/popup/buddy-render.sh +0 -540
  81. package/adapters/claude/popup/popup-manager.sh +0 -355
  82. package/adapters/claude/rendering/art.ts +0 -264
  83. package/adapters/claude/server/index.ts +0 -752
  84. package/adapters/claude/server/instructions.ts +0 -24
  85. package/adapters/claude/server/resources.ts +0 -38
  86. package/adapters/claude/statusline/buddy-status.sh +0 -389
  87. package/adapters/claude/storage/achievements.ts +0 -253
  88. package/adapters/claude/storage/identity.ts +0 -13
  89. package/adapters/claude/storage/paths.ts +0 -24
  90. package/adapters/claude/storage/settings.ts +0 -41
  91. package/adapters/pi/README.md +0 -83
  92. package/adapters/pi/tools.ts +0 -6
@@ -1,19 +1,26 @@
1
1
  /**
2
- * claude-buddy installer
2
+ * coding-buddy installer
3
3
  *
4
- * Registers: MCP server (in ~/.claude.json), skill, hooks, status line (in settings.json)
5
- * Checks: bun, jq, ~/.claude/ directory
4
+ * Registers: MCP server (in Claude's user config), skill, hooks, status line
5
+ * (in settings.json). All paths resolve via server/paths.ts, so the installer
6
+ * targets the active Claude profile ($CLAUDE_CONFIG_DIR) or the default
7
+ * ~/.claude/ layout when the env var is unset.
6
8
  */
7
9
 
8
- import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, statSync } from "fs";
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync } from "fs";
9
11
  import { execSync } from "child_process";
10
- import { join, resolve } from "path";
11
-
12
- import { generateBones, renderBuddy, renderFace, RARITY_STARS } from "../../../core/engine.ts";
13
- import { loadCompanion, saveCompanion, writeStatusState } from "../storage/state.ts";
14
- import { resolveUserId } from "../storage/identity.ts";
15
- import { generateFallbackName } from "../../../core/reactions.ts";
16
- import { getBuddySkillDir, getClaudeConfigDir, getClaudeJsonPath, getClaudeSettingsPath } 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"
17
24
 
18
25
  const CYAN = "\x1b[36m";
19
26
  const GREEN = "\x1b[32m";
@@ -23,16 +30,16 @@ const BOLD = "\x1b[1m";
23
30
  const DIM = "\x1b[2m";
24
31
  const NC = "\x1b[0m";
25
32
 
26
- const CLAUDE_DIR = getClaudeConfigDir();
27
- const SETTINGS_FILE = getClaudeSettingsPath();
28
- const BUDDY_DIR = getBuddySkillDir();
29
- const CLAUDE_JSON = getClaudeJsonPath();
30
- 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));
31
38
 
32
39
  function banner() {
33
40
  console.log(`
34
41
  ${CYAN}╔══════════════════════════════════════════════════════════╗${NC}
35
- ${CYAN}║${NC} ${BOLD}claude-buddy${NC} — permanent coding companion ${CYAN}║${NC}
42
+ ${CYAN}║${NC} ${BOLD}coding-buddy${NC} — permanent coding companion ${CYAN}║${NC}
36
43
  ${CYAN}║${NC} ${DIM}MCP + Skill + StatusLine + Hooks${NC} ${CYAN}║${NC}
37
44
  ${CYAN}╚══════════════════════════════════════════════════════════╝${NC}
38
45
  `);
@@ -67,28 +74,25 @@ function preflight(): boolean {
67
74
  execSync("sudo apt-get install -y jq 2>/dev/null || brew install jq 2>/dev/null", { stdio: "ignore" });
68
75
  ok("jq installed");
69
76
  } catch {
70
- err("Could not install jq. Install manually: apt install jq / brew install jq");
77
+ err("Could not install jq. Install manually: apt install jq / brew install jq / windows: install from https://github.com/jqlang/jq/releases/latest and add to PATH");
71
78
  pass = false;
72
79
  }
73
80
  }
74
81
 
75
- // Check Claude config dir exists and is actually a directory
82
+ // Check Claude config dir exists
76
83
  if (!existsSync(CLAUDE_DIR)) {
77
- err(`Claude config dir not found: ${CLAUDE_DIR}`);
78
- pass = false;
79
- } else if (!statSync(CLAUDE_DIR).isDirectory()) {
80
- err(`Claude config path is not a directory: ${CLAUDE_DIR}`);
84
+ err(`${CLAUDE_DIR} not found. Start Claude Code once first, then re-run.`);
81
85
  pass = false;
82
86
  } else {
83
- ok(`Claude config dir found: ${CLAUDE_DIR}`);
87
+ ok(`${CLAUDE_DIR} found`);
84
88
  }
85
89
 
86
- // Check .claude.json exists in the active Claude config location
87
- if (!existsSync(CLAUDE_JSON)) {
88
- err(`Claude config file not found: ${CLAUDE_JSON}`);
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.`);
89
93
  pass = false;
90
94
  } else {
91
- ok(`Claude config file found: ${CLAUDE_JSON}`);
95
+ ok(`${CLAUDE_JSON_PATH} found`);
92
96
  }
93
97
 
94
98
  return pass;
@@ -96,50 +100,15 @@ function preflight(): boolean {
96
100
 
97
101
  // ─── Load / update settings.json ────────────────────────────────────────────
98
102
 
99
- interface HookCommand {
100
- type: "command";
101
- command: string;
102
- }
103
-
104
- interface HookMatcherEntry {
105
- matcher?: string;
106
- hooks?: HookCommand[];
107
- }
108
-
109
- interface ClaudeStatusLineConfig {
110
- type: "command";
111
- command: string;
112
- padding: number;
113
- refreshInterval: number;
114
- }
115
-
116
- interface ClaudeSettings {
117
- statusLine?: ClaudeStatusLineConfig;
118
- hooks?: Partial<Record<"SessionStart" | "SessionEnd" | "PostToolUse" | "Stop" | "UserPromptSubmit", HookMatcherEntry[]>>;
119
- permissions?: {
120
- allow?: string[];
121
- };
122
- [key: string]: unknown;
123
- }
124
-
125
- interface ClaudeJsonConfig {
126
- mcpServers?: Record<string, { command: string; args: string[]; cwd: string }>;
127
- [key: string]: unknown;
128
- }
129
-
130
- function hasBuddyHook(entry: HookMatcherEntry): boolean {
131
- return (entry.hooks ?? []).some((hook) => hook.command.includes("claude-buddy"));
132
- }
133
-
134
- function loadSettings(): ClaudeSettings {
103
+ function loadSettings(): Record<string, any> {
135
104
  try {
136
- return JSON.parse(readFileSync(SETTINGS_FILE, "utf8")) as ClaudeSettings;
105
+ return JSON.parse(readFileSync(SETTINGS_FILE, "utf8"));
137
106
  } catch {
138
107
  return {};
139
108
  }
140
109
  }
141
110
 
142
- function saveSettings(settings: ClaudeSettings) {
111
+ function saveSettings(settings: Record<string, any>) {
143
112
  mkdirSync(CLAUDE_DIR, { recursive: true });
144
113
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
145
114
  }
@@ -147,30 +116,29 @@ function saveSettings(settings: ClaudeSettings) {
147
116
  // ─── Step 1: Register MCP server (in ~/.claude.json) ────────────────────────
148
117
 
149
118
  function installMcp() {
150
- const serverPath = join(PROJECT_ROOT, "adapters", "claude", "server", "index.ts");
151
- const claudeJsonPath = CLAUDE_JSON;
119
+ const serverPath = join(PROJECT_ROOT, "server", "index.ts");
152
120
 
153
- let claudeJson: ClaudeJsonConfig = {};
121
+ let claudeJson: Record<string, any> = {};
154
122
  try {
155
- claudeJson = JSON.parse(readFileSync(claudeJsonPath, "utf8")) as ClaudeJsonConfig;
123
+ claudeJson = JSON.parse(readFileSync(CLAUDE_JSON_PATH, "utf8"));
156
124
  } catch { /* fresh config */ }
157
125
 
158
126
  if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
159
127
 
160
128
  claudeJson.mcpServers["claude-buddy"] = {
161
129
  command: "bun",
162
- args: [serverPath],
163
- cwd: PROJECT_ROOT,
130
+ args: [toUnixPath(serverPath)],
131
+ cwd: toUnixPath(PROJECT_ROOT),
164
132
  };
165
133
 
166
- writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
167
- 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}`);
168
136
  }
169
137
 
170
138
  // ─── Step 2: Install skill ──────────────────────────────────────────────────
171
139
 
172
140
  function installSkill() {
173
- const srcSkill = join(PROJECT_ROOT, "adapters", "claude", "skills", "buddy", "SKILL.md");
141
+ const srcSkill = join(PROJECT_ROOT, "skills", "buddy", "SKILL.md");
174
142
  mkdirSync(BUDDY_DIR, { recursive: true });
175
143
  cpSync(srcSkill, join(BUDDY_DIR, "SKILL.md"), { force: true });
176
144
  ok(`Skill installed: ${join(BUDDY_DIR, "SKILL.md")}`);
@@ -178,12 +146,12 @@ function installSkill() {
178
146
 
179
147
  // ─── Step 3: Configure status line (with animation refresh) ─────────────────
180
148
 
181
- function installStatusLine(settings: ClaudeSettings) {
182
- 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");
183
151
 
184
152
  settings.statusLine = {
185
153
  type: "command",
186
- command: statusScript,
154
+ command: toUnixPath(statusScript),
187
155
  padding: 1,
188
156
  refreshInterval: 1, // 1 second — drives the buddy animation
189
157
  };
@@ -191,90 +159,83 @@ function installStatusLine(settings: ClaudeSettings) {
191
159
  ok("Status line configured (with animation refresh)");
192
160
  }
193
161
 
194
- // ─── Step 3b: Configure tmux popup mode (if in tmux) ────────────────────────
195
-
196
- function detectTmux(): boolean {
197
- if (!process.env.TMUX) return false;
198
- try {
199
- const ver = execSync("tmux -V 2>/dev/null", { encoding: "utf8" }).trim();
200
- const match = ver.match(/(\d+)\.(\d+)/);
201
- if (!match) return false;
202
- const major = parseInt(match[1]), minor = parseInt(match[2]);
203
- return major > 3 || (major === 3 && minor >= 2);
204
- } catch {
205
- return false;
162
+ // The tmux popup mode was removed in favour of the status line / buddy-shell
163
+ // — its modal `tmux display-popup` intercepted the `Ctrl+b` prefix, breaking
164
+ // every tmux binding while the buddy was visible (issue #57). For backwards
165
+ // compatibility any legacy SessionStart/SessionEnd hooks that reference the
166
+ // popup manager are stripped out below when re-installing.
167
+
168
+ function stripLegacyPopupHooks(settings: Record<string, any>) {
169
+ if (!settings.hooks) return;
170
+ for (const hookType of ["SessionStart", "SessionEnd"] as const) {
171
+ if (!settings.hooks[hookType]) continue;
172
+ settings.hooks[hookType] = settings.hooks[hookType].filter(
173
+ (h: any) => !h.hooks?.some((hh: any) =>
174
+ hh.command?.includes("popup-manager") || hh.command?.includes("claude-buddy/popup"),
175
+ ),
176
+ );
177
+ if (settings.hooks[hookType].length === 0) delete settings.hooks[hookType];
206
178
  }
207
179
  }
208
180
 
209
- function installPopupHooks(settings: ClaudeSettings) {
210
- const popupManager = join(PROJECT_ROOT, "adapters", "claude", "popup", "popup-manager.sh");
211
-
212
- if (!settings.hooks) settings.hooks = {};
213
-
214
- // SessionStart: open popup
215
- if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
216
- settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
217
- (h) => !hasBuddyHook(h),
218
- );
219
- settings.hooks.SessionStart.push({
220
- hooks: [{ type: "command", command: `${popupManager} start` }],
221
- });
222
-
223
- // SessionEnd: close popup
224
- if (!settings.hooks.SessionEnd) settings.hooks.SessionEnd = [];
225
- settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(
226
- (h) => !hasBuddyHook(h),
227
- );
228
- settings.hooks.SessionEnd.push({
229
- hooks: [{ type: "command", command: `${popupManager} stop` }],
230
- });
231
-
232
- ok("Popup hooks registered: SessionStart + SessionEnd");
233
- }
234
-
235
181
  // ─── Step 4: Register hooks ─────────────────────────────────────────────────
236
182
 
237
- function installHooks(settings: ClaudeSettings) {
238
- const reactHook = join(PROJECT_ROOT, "adapters", "claude", "hooks", "react.sh");
239
- const commentHook = join(PROJECT_ROOT, "adapters", "claude", "hooks", "buddy-comment.sh");
240
- 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");
241
190
 
242
191
  if (!settings.hooks) settings.hooks = {};
243
192
 
244
- // 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.
245
195
  if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
246
196
  settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
247
- (h) => !hasBuddyHook(h),
197
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
248
198
  );
249
199
  settings.hooks.PostToolUse.push({
250
200
  matcher: "Bash",
251
- hooks: [{ type: "command", command: reactHook }],
201
+ hooks: [{ type: "command", command: toUnixPath(reactHook) }],
202
+ });
203
+ settings.hooks.PostToolUse.push({
204
+ matcher: "Write|Edit",
205
+ hooks: [{ type: "command", command: toUnixPath(fileTypeHook) }],
252
206
  });
253
207
 
254
208
  // Stop: extract <!-- buddy: --> comment from Claude's response
255
209
  if (!settings.hooks.Stop) settings.hooks.Stop = [];
256
210
  settings.hooks.Stop = settings.hooks.Stop.filter(
257
- (h) => !hasBuddyHook(h),
211
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
258
212
  );
259
213
  settings.hooks.Stop.push({
260
- hooks: [{ type: "command", command: commentHook }],
214
+ hooks: [{ type: "command", command: toUnixPath(commentHook) }],
215
+ });
216
+ settings.hooks.Stop.push({
217
+ hooks: [{ type: "command", command: toUnixPath(suggestHook) }],
261
218
  });
262
219
 
263
- // 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.
264
222
  if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
265
223
  settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
266
- (h) => !hasBuddyHook(h),
224
+ (h: any) => !h.hooks?.some((hh: any) => hh.command?.includes("coding-buddy") || hh.command?.includes("claude-buddy")),
267
225
  );
268
226
  settings.hooks.UserPromptSubmit.push({
269
- hooks: [{ type: "command", command: nameHook }],
227
+ hooks: [{ type: "command", command: toUnixPath(nameHook) }],
228
+ });
229
+ settings.hooks.UserPromptSubmit.push({
230
+ hooks: [{ type: "command", command: toUnixPath(moodHook) }],
270
231
  });
271
232
 
272
- ok("Hooks registered: PostToolUse + Stop + UserPromptSubmit");
233
+ ok("Hooks registered: PostToolUse (Bash + Write/Edit) + Stop (comment + suggest) + UserPromptSubmit (name + mood)");
273
234
  }
274
235
 
275
236
  // ─── Step 5: Ensure MCP tools are allowed ───────────────────────────────────
276
237
 
277
- function ensurePermissions(settings: ClaudeSettings) {
238
+ function ensurePermissions(settings: Record<string, any>) {
278
239
  if (!settings.permissions) settings.permissions = {};
279
240
  if (!settings.permissions.allow) settings.permissions.allow = [];
280
241
 
@@ -319,6 +280,11 @@ function initCompanion() {
319
280
 
320
281
  banner();
321
282
 
283
+ const profileSource = process.env.CLAUDE_CONFIG_DIR
284
+ ? "from CLAUDE_CONFIG_DIR"
285
+ : "CLAUDE_CONFIG_DIR unset — single-profile default";
286
+ info(`Target profile: ${CLAUDE_DIR} ${DIM}(${profileSource})${NC}\n`);
287
+
322
288
  info("Checking requirements...\n");
323
289
  if (!preflight()) {
324
290
  console.log(`\n${RED}Installation aborted. Fix the issues above and retry.${NC}\n`);
@@ -326,25 +292,15 @@ if (!preflight()) {
326
292
  }
327
293
 
328
294
  console.log("");
329
- info("Installing claude-buddy...\n");
295
+ info("Installing coding-buddy...\n");
330
296
 
331
297
  const settings = loadSettings();
332
298
 
333
299
  installMcp();
334
300
  installSkill();
335
301
 
336
- const useTmuxPopup = detectTmux();
337
- if (useTmuxPopup) {
338
- info("tmux detected (>= 3.2) -- using popup overlay mode");
339
- installPopupHooks(settings);
340
- // Disable status line to avoid duplicate buddy rendering
341
- if (settings.statusLine?.command?.includes("buddy")) {
342
- delete settings.statusLine;
343
- ok("Status line disabled (popup replaces it)");
344
- }
345
- } else {
346
- installStatusLine(settings);
347
- }
302
+ stripLegacyPopupHooks(settings);
303
+ installStatusLine(settings);
348
304
 
349
305
  installHooks(settings);
350
306
  ensurePermissions(settings);
@@ -359,10 +315,9 @@ console.log("");
359
315
  console.log(` ${BOLD}${companion.name}${NC} -- ${companion.personality}`);
360
316
  console.log("");
361
317
 
362
- const modeMsg = useTmuxPopup ? "popup overlay" : "status line";
363
318
  console.log(`${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
364
319
  console.log(`${GREEN} Done! Restart Claude Code and type /buddy${NC}`);
365
- console.log(`${GREEN} Display mode: ${modeMsg}${NC}`);
320
+ console.log(`${GREEN} Display mode: status line${NC}`);
366
321
  console.log(`${GREEN} Your companion is now permanent -- survives any update.${NC}`);
367
322
  console.log(`${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
368
323
  console.log("");