@ramarivera/coding-buddy 0.4.0-alpha.9 → 0.7.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 (94) hide show
  1. package/.claude-plugin/marketplace.json +45 -0
  2. package/.claude-plugin/plugin.json +30 -0
  3. package/LICENSE +1 -1
  4. package/README.md +96 -45
  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 +136 -57
  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 +46 -13
  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 +22 -9
  46. package/hooks/file-type-react.sh +403 -0
  47. package/hooks/hooks.json +52 -0
  48. package/hooks/mood-react.sh +210 -0
  49. package/{adapters/claude/hooks → hooks}/name-react.sh +3 -4
  50. package/hooks/react.sh +1364 -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 +33 -2
  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 +552 -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 +402 -0
  74. package/statusline/combined-status.sh +135 -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/plugin/marketplace.json +0 -40
  80. package/adapters/claude/plugin/plugin.json +0 -26
  81. package/adapters/claude/rendering/art.ts +0 -264
  82. package/adapters/claude/server/instructions.ts +0 -24
  83. package/adapters/claude/server/path.ts +0 -12
  84. package/adapters/claude/server/resources.ts +0 -38
  85. package/adapters/claude/statusline/buddy-status.sh +0 -408
  86. package/adapters/claude/storage/achievements.ts +0 -253
  87. package/adapters/claude/storage/identity.ts +0 -13
  88. package/adapters/claude/storage/paths.ts +0 -33
  89. package/adapters/claude/storage/settings.ts +0 -91
  90. package/adapters/claude/storage/state.ts +0 -365
  91. package/adapters/pi/README.md +0 -83
  92. package/adapters/pi/tools.ts +0 -6
  93. package/server/engine.ts +0 -1
  94. package/server/reactions.ts +0 -1
@@ -17,13 +17,12 @@
17
17
 
18
18
  import {
19
19
  loadActiveSlot, saveActiveSlot, listCompanionSlots,
20
- loadCompanionSlot, saveCompanionSlot, slugify, unusedName, writeStatusState,
21
- } from "../storage/state.ts";
22
- import {
23
- generateBones, SPECIES, RARITIES, STAT_NAMES, RARITY_STARS,
24
- type Species, type Rarity, type StatName, type BuddyBones, type Companion,
25
- } from "../../../core/engine.ts";
26
- import { renderCompanionCard } from "../rendering/art.ts";
20
+ loadCompanionSlot, saveCompanionSlot, deleteCompanionSlot, slugify, unusedName, writeStatusState,
21
+ } from "../server/state.ts";
22
+ import { generateBones, generatePersonality, SPECIES, RARITIES, STAT_NAMES, RARITY_STARS, EYES, HATS,
23
+ type Species, type Rarity, type StatName, type Eye, type Hat,
24
+ type BuddyBones, type Companion, } from "../core/engine.ts"
25
+ import { renderCompanionCard } from "../server/art.ts";
27
26
  import { randomBytes } from "crypto";
28
27
 
29
28
  // ─── ANSI ─────────────────────────────────────────────────────────────────────
@@ -79,54 +78,71 @@ function rpad(s: string, w: number): string {
79
78
 
80
79
  // ─── Option lists ─────────────────────────────────────────────────────────────
81
80
 
82
- const SP_OPTS = ["any", ...SPECIES] as const;
83
- const RA_OPTS = ["any", ...RARITIES] as const;
84
- const SH_OPTS = ["any", "yes", "no"] as const;
85
- const ST_OPTS = ["any", ...STAT_NAMES] as const;
81
+ const SP_OPTS = ["any", ...SPECIES] as const;
82
+ const RA_OPTS = ["any", ...RARITIES] as const;
83
+ const SH_OPTS = ["any", "yes", "no"] as const;
84
+ const ST_OPTS = ["any", ...STAT_NAMES] as const;
85
+ const EY_OPTS = ["any", ...EYES] as const;
86
+ const HA_OPTS = ["any", ...HATS] as const;
87
+ const MIN_OPTS = ["any", "5", "10", "15", "20", "25", "30", "35", "40", "45",
88
+ "50", "55", "60", "65", "70", "75", "80", "85", "90", "95"] as const;
89
+ const AVG_OPTS = MIN_OPTS;
86
90
 
87
91
  const CRITERIA_ROWS: Array<{ label: string; opts: readonly string[] }> = [
88
- { label: "Species", opts: SP_OPTS },
89
- { label: "Rarity ", opts: RA_OPTS },
90
- { label: "Shiny ", opts: SH_OPTS },
91
- { label: "Peak ", opts: ST_OPTS },
92
- { label: "Dump ", opts: ST_OPTS },
92
+ { label: "Species", opts: SP_OPTS },
93
+ { label: "Rarity ", opts: RA_OPTS },
94
+ { label: "Shiny ", opts: SH_OPTS },
95
+ { label: "Peak ", opts: ST_OPTS },
96
+ { label: "Dump ", opts: ST_OPTS },
97
+ { label: "Eye ", opts: EY_OPTS },
98
+ { label: "Hat ", opts: HA_OPTS },
99
+ { label: "Min avg", opts: AVG_OPTS },
100
+ { label: "Min DBG", opts: MIN_OPTS },
101
+ { label: "Min PAT", opts: MIN_OPTS },
102
+ { label: "Min CHA", opts: MIN_OPTS },
103
+ { label: "Min WIS", opts: MIN_OPTS },
104
+ { label: "Min SNK", opts: MIN_OPTS },
93
105
  ];
94
106
 
95
107
  // ─── State ────────────────────────────────────────────────────────────────────
96
108
 
97
- type Mode = "saved" | "criteria" | "results" | "naming";
109
+ type Mode = "saved" | "criteria" | "searching" | "results" | "naming";
98
110
  interface SlotEntry { slot: string; companion: Companion; }
99
111
  interface BuddyResult { userId: string; bones: BuddyBones; }
100
112
 
101
113
  interface State {
102
114
  mode: Mode;
115
+ searching: boolean;
103
116
  savedSlots: SlotEntry[];
104
117
  savedCursor: number;
105
118
  activeSlot: string;
106
119
  criteriaFocus: number;
107
- ci: number[]; // [speciesIdx, rarityIdx, shinyIdx, peakIdx, dumpIdx]
120
+ ci: number[]; // [speciesIdx, rarityIdx, shinyIdx, peakIdx, dumpIdx, eyeIdx, hatIdx, avgIdx, dbgIdx, patIdx, chaIdx, wisIdx, snkIdx]
108
121
  results: BuddyResult[];
109
122
  resultCursor: number;
110
123
  searchStatus: string;
111
124
  nameInput: string;
112
125
  pendingResult: BuddyResult | null;
126
+ confirmDelete: boolean;
113
127
  message: string;
114
128
  }
115
129
 
116
130
  function fresh(): State {
117
131
  return {
118
132
  mode: "saved",
133
+ searching: false,
119
134
  savedSlots: listCompanionSlots(),
120
135
  savedCursor: 0,
121
136
  activeSlot: loadActiveSlot(),
122
137
  criteriaFocus: 0,
123
- // Default criteria: legendary, any species
124
- ci: [0, RA_OPTS.indexOf("legendary"), 0, 0, 0],
138
+ // Default criteria: legendary, any species/shiny/peak/dump/eye/hat/avg/stats
139
+ ci: [0, RA_OPTS.indexOf("legendary"), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
125
140
  results: [],
126
141
  resultCursor: 0,
127
142
  searchStatus: "",
128
143
  nameInput: "",
129
144
  pendingResult: null,
145
+ confirmDelete: false,
130
146
  message: "",
131
147
  };
132
148
  }
@@ -137,7 +153,7 @@ const LEFT_W = 36;
137
153
 
138
154
  function savedPane(s: State): string[] {
139
155
  const lines: string[] = [];
140
- lines.push(`${B} Your Menagerie${N} ${GR}[s] search${N}`);
156
+ lines.push(`${B} Your Menagerie${N} ${GR}[s] search [d] remove${N}`);
141
157
  lines.push(GR + " " + "─".repeat(LEFT_W - 2) + N);
142
158
 
143
159
  if (s.savedSlots.length === 0) {
@@ -160,6 +176,10 @@ function savedPane(s: State): string[] {
160
176
  }
161
177
 
162
178
  lines.push(GR + " " + "─".repeat(LEFT_W - 2) + N);
179
+ if (s.confirmDelete) {
180
+ const entry = s.savedSlots[s.savedCursor];
181
+ lines.push(` ${YL}remove ${B}${entry?.companion.name ?? "?"}${N}${YL}? [d/y] yes [any] no${N}`);
182
+ }
163
183
  return lines;
164
184
  }
165
185
 
@@ -185,6 +205,16 @@ function criteriaPane(s: State): string[] {
185
205
  return lines;
186
206
  }
187
207
 
208
+ function searchingPane(s: State): string[] {
209
+ const lines: string[] = [];
210
+ lines.push(`${B} Searching...${N}`);
211
+ lines.push(GR + " " + "─".repeat(LEFT_W - 2) + N);
212
+ lines.push(` ${YL}${s.searchStatus || "starting..."}${N}`);
213
+ lines.push(` ${GR}any key to stop${N}`);
214
+ lines.push(GR + " " + "─".repeat(LEFT_W - 2) + N);
215
+ return lines;
216
+ }
217
+
188
218
  function resultsPane(s: State): string[] {
189
219
  const lines: string[] = [];
190
220
  lines.push(`${B} Results${N} ${GR}${s.results.length} found${N}`);
@@ -236,22 +266,20 @@ function previewPane(s: State): string[] {
236
266
  const r = s.results[s.resultCursor];
237
267
  if (r) c = {
238
268
  bones: r.bones, name: "???",
239
- personality: `A ${r.bones.rarity} ${r.bones.species}`,
269
+ personality: generatePersonality(r.bones, r.userId),
240
270
  hatchedAt: Date.now(), userId: r.userId,
241
271
  };
242
272
  } else if (s.mode === "naming" && s.pendingResult) {
243
273
  const r = s.pendingResult;
244
274
  c = {
245
275
  bones: r.bones, name: s.nameInput || "???",
246
- personality: `A ${r.bones.rarity} ${r.bones.species} who watches code with quiet intensity.`,
276
+ personality: generatePersonality(r.bones, r.userId),
247
277
  hatchedAt: Date.now(), userId: r.userId,
248
278
  };
249
279
  }
250
280
 
251
281
  if (!c) return [` ${GR}no preview${N}`];
252
- // Calculate available width for the right pane (total cols - left pane - separator)
253
- const cols = Math.max(80, process.stdout.columns || 80);
254
- const rightW = cols - LEFT_W - 3;
282
+ const rightW = 34;
255
283
  return renderCompanionCard(c.bones, c.name, c.personality, undefined, 0, rightW).split("\n");
256
284
  }
257
285
 
@@ -261,9 +289,10 @@ function drawScreen(s: State): void {
261
289
  const cols = Math.max(80, process.stdout.columns || 80);
262
290
  const rows = Math.max(20, process.stdout.rows || 24);
263
291
 
264
- const leftLines = s.mode === "saved" ? savedPane(s)
265
- : s.mode === "criteria" ? criteriaPane(s)
266
- : s.mode === "results" ? resultsPane(s)
292
+ const leftLines = s.mode === "saved" ? savedPane(s)
293
+ : s.mode === "criteria" ? criteriaPane(s)
294
+ : s.mode === "searching" ? searchingPane(s)
295
+ : s.mode === "results" ? resultsPane(s)
267
296
  : namingPane(s);
268
297
  const rightLines = previewPane(s);
269
298
  const contentH = rows - 2;
@@ -271,7 +300,7 @@ function drawScreen(s: State): void {
271
300
  let out = "\x1b[2J\x1b[H"; // clear + home
272
301
 
273
302
  // Title bar
274
- const title = ` claude-buddy pick `;
303
+ const title = ` coding-buddy pick `;
275
304
  const fill = "─".repeat(Math.max(0, cols - title.length - 2));
276
305
  out += `${CY}─${B}${title}${N}${CY}${fill}─${N}\n`;
277
306
 
@@ -284,10 +313,11 @@ function drawScreen(s: State): void {
284
313
 
285
314
  // Footer — mode-specific help
286
315
  const helpText =
287
- s.mode === "saved" ? "↑↓ navigate enter summon r random s search q quit" :
288
- s.mode === "criteria" ? "↑↓ field ←→ value enter search esc back" :
289
- s.mode === "results" ? "↑↓ navigate enter name+save esc back q quit" :
290
- s.mode === "naming" ? "type name enter save esc cancel" : "";
316
+ s.mode === "saved" ? "↑↓ navigate enter summon r random s search d remove q quit" :
317
+ s.mode === "criteria" ? "↑↓ field ←→ value enter search esc back" :
318
+ s.mode === "searching" ? "any key to stop and show results so far" :
319
+ s.mode === "results" ? "↑↓ navigate enter name+save esc back q quit" :
320
+ s.mode === "naming" ? "type name enter save esc cancel" : "";
291
321
  out += `${GR}─${N} ${GR}${helpText}${N} ${GR}${"─".repeat(Math.max(0, cols - helpText.length - 4))}${N}`;
292
322
 
293
323
  // Message overlay on last line
@@ -300,47 +330,75 @@ function drawScreen(s: State): void {
300
330
 
301
331
  // ─── Search ───────────────────────────────────────────────────────────────────
302
332
 
303
- function runSearch(s: State): void {
304
- const wantSp = SP_OPTS[s.ci[0]] !== "any" ? SP_OPTS[s.ci[0]] as Species : null;
305
- const wantRa = RA_OPTS[s.ci[1]] !== "any" ? RA_OPTS[s.ci[1]] as Rarity : null;
306
- const wantShiny = SH_OPTS[s.ci[2]] === "yes" ? true
307
- : SH_OPTS[s.ci[2]] === "no" ? false : null;
308
- const wantPeak = ST_OPTS[s.ci[3]] !== "any" ? ST_OPTS[s.ci[3]] as StatName : null;
309
- const wantDump = ST_OPTS[s.ci[4]] !== "any" ? ST_OPTS[s.ci[4]] as StatName : null;
333
+ function avgStat(bones: BuddyBones): number {
334
+ const vals = Object.values(bones.stats) as number[];
335
+ return vals.reduce((a, b) => a + b, 0) / vals.length;
336
+ }
337
+
338
+ async function runSearch(s: State): Promise<void> {
339
+ const wantSp = SP_OPTS[s.ci[0]] !== "any" ? SP_OPTS[s.ci[0]] as Species : null;
340
+ const wantRa = RA_OPTS[s.ci[1]] !== "any" ? RA_OPTS[s.ci[1]] as Rarity : null;
341
+ const wantShiny = SH_OPTS[s.ci[2]] === "yes" ? true
342
+ : SH_OPTS[s.ci[2]] === "no" ? false : null;
343
+ const wantPeak = ST_OPTS[s.ci[3]] !== "any" ? ST_OPTS[s.ci[3]] as StatName : null;
344
+ const wantDump = ST_OPTS[s.ci[4]] !== "any" ? ST_OPTS[s.ci[4]] as StatName : null;
345
+ const wantEye = EY_OPTS[s.ci[5]] !== "any" ? EY_OPTS[s.ci[5]] as Eye : null;
346
+ const wantHat = HA_OPTS[s.ci[6]] !== "any" ? HA_OPTS[s.ci[6]] as Hat : null;
347
+ const minAvg = AVG_OPTS[s.ci[7]] !== "any" ? Number(AVG_OPTS[s.ci[7]]) : null;
348
+ const minDBG = MIN_OPTS[s.ci[8]] !== "any" ? Number(MIN_OPTS[s.ci[8]]) : null;
349
+ const minPAT = MIN_OPTS[s.ci[9]] !== "any" ? Number(MIN_OPTS[s.ci[9]]) : null;
350
+ const minCHA = MIN_OPTS[s.ci[10]] !== "any" ? Number(MIN_OPTS[s.ci[10]]) : null;
351
+ const minWIS = MIN_OPTS[s.ci[11]] !== "any" ? Number(MIN_OPTS[s.ci[11]]) : null;
352
+ const minSNK = MIN_OPTS[s.ci[12]] !== "any" ? Number(MIN_OPTS[s.ci[12]]) : null;
310
353
 
311
354
  // Scale attempt budget to rarity difficulty
312
355
  const maxAttempts =
313
- wantRa === "legendary" ? 200_000_000 :
356
+ wantRa === "legendary" ? 10_000_000_000 :
314
357
  wantRa === "epic" ? 50_000_000 :
315
358
  wantRa === "rare" ? 20_000_000 : 10_000_000;
316
359
 
317
360
  const results: BuddyResult[] = [];
318
- const progressRow = (process.stdout.rows || 24) - 1;
361
+ const YIELD_EVERY = 5_000_000;
362
+ const PROGRESS_EVERY = 1_000_000;
319
363
 
320
364
  for (let i = 0; i < maxAttempts && results.length < 20; i++) {
321
- if (i > 0 && i % 1_000_000 === 0) {
322
- s.searchStatus = `${(i / 1e6).toFixed(0)}M checked — ${results.length} found`;
323
- process.stdout.write(
324
- `\x1b[${progressRow};1H ${YL}${s.searchStatus}${N} \x1b[K`,
325
- );
365
+ if (!s.searching) break;
366
+
367
+ if (i > 0 && i % YIELD_EVERY === 0) {
368
+ await new Promise<void>(resolve => setImmediate(resolve));
369
+ }
370
+
371
+ if (i > 0 && i % PROGRESS_EVERY === 0) {
372
+ s.searchStatus = `${(i / 1e6).toFixed(1)}M checked — ${results.length} found`;
373
+ drawScreen(s);
326
374
  }
327
375
 
328
376
  const userId = randomBytes(16).toString("hex");
329
377
  const bones = generateBones(userId);
330
378
 
331
- if (wantSp !== null && bones.species !== wantSp) continue;
332
- if (wantRa !== null && bones.rarity !== wantRa) continue;
333
- if (wantShiny !== null && bones.shiny !== wantShiny) continue;
334
- if (wantPeak !== null && bones.peak !== wantPeak) continue;
335
- if (wantDump !== null && bones.dump !== wantDump) continue;
379
+ if (wantSp !== null && bones.species !== wantSp) continue;
380
+ if (wantRa !== null && bones.rarity !== wantRa) continue;
381
+ if (wantShiny !== null && bones.shiny !== wantShiny) continue;
382
+ if (wantPeak !== null && bones.peak !== wantPeak) continue;
383
+ if (wantDump !== null && bones.dump !== wantDump) continue;
384
+ if (wantEye !== null && bones.eye !== wantEye) continue;
385
+ if (wantHat !== null && bones.hat !== wantHat) continue;
386
+ if (minAvg !== null && avgStat(bones) < minAvg) continue;
387
+ if (minDBG !== null && bones.stats.DEBUGGING < minDBG) continue;
388
+ if (minPAT !== null && bones.stats.PATIENCE < minPAT) continue;
389
+ if (minCHA !== null && bones.stats.CHAOS < minCHA) continue;
390
+ if (minWIS !== null && bones.stats.WISDOM < minWIS) continue;
391
+ if (minSNK !== null && bones.stats.SNARK < minSNK) continue;
336
392
 
337
393
  results.push({ userId, bones });
338
394
  }
339
395
 
396
+ s.searching = false;
340
397
  s.searchStatus = `${results.length} found`;
341
398
  s.results = results;
342
399
  s.resultCursor = 0;
343
400
  s.mode = "results";
401
+ drawScreen(s);
344
402
  }
345
403
 
346
404
  // ─── Key handlers ─────────────────────────────────────────────────────────────
@@ -367,7 +425,7 @@ function onKey(key: string, s: State): boolean {
367
425
  const r = s.pendingResult!;
368
426
  const companion: Companion = {
369
427
  bones: r.bones, name,
370
- personality: `A ${r.bones.rarity} ${r.bones.species} who watches code with quiet intensity.`,
428
+ personality: generatePersonality(r.bones, r.userId),
371
429
  hatchedAt: Date.now(), userId: r.userId,
372
430
  };
373
431
  saveCompanionSlot(companion, slot);
@@ -384,12 +442,26 @@ function onKey(key: string, s: State): boolean {
384
442
  }
385
443
 
386
444
  case "saved": {
445
+ if (s.confirmDelete) {
446
+ if (key === "d" || key === "y") {
447
+ const entry = s.savedSlots[s.savedCursor];
448
+ if (entry) {
449
+ deleteCompanionSlot(entry.slot);
450
+ s.savedSlots = listCompanionSlots();
451
+ s.activeSlot = loadActiveSlot();
452
+ s.savedCursor = clamp(s.savedCursor, 0, Math.max(0, s.savedSlots.length - 1));
453
+ s.message = `✗ ${entry.companion.name} removed`;
454
+ }
455
+ }
456
+ s.confirmDelete = false;
457
+ break;
458
+ }
387
459
  if (key === "q") return true;
388
460
  if (key === "s") { s.mode = "criteria"; break; }
461
+ if (key === "d" && s.savedSlots.length > 0) { s.confirmDelete = true; break; }
389
462
  if (key === "\x1b[A" || key === "k") s.savedCursor = clamp(s.savedCursor - 1, 0, s.savedSlots.length - 1);
390
463
  else if (key === "\x1b[B" || key === "j") s.savedCursor = clamp(s.savedCursor + 1, 0, s.savedSlots.length - 1);
391
464
  else if (key === "r") {
392
- // Random pick from menagerie
393
465
  if (s.savedSlots.length > 0) {
394
466
  const entry = s.savedSlots[Math.floor(Math.random() * s.savedSlots.length)];
395
467
  s.savedCursor = s.savedSlots.indexOf(entry);
@@ -422,13 +494,20 @@ function onKey(key: string, s: State): boolean {
422
494
  const len = CRITERIA_ROWS[s.criteriaFocus].opts.length;
423
495
  s.ci[s.criteriaFocus] = (s.ci[s.criteriaFocus] - 1 + len) % len;
424
496
  } else if (key === "\r" || key === "\n") {
425
- s.searchStatus = "Starting search...";
497
+ s.mode = "searching";
498
+ s.searching = true;
499
+ s.searchStatus = "starting...";
426
500
  drawScreen(s);
427
- runSearch(s);
501
+ runSearch(s); // fire-and-forget async; updates state and redraws when done
428
502
  }
429
503
  break;
430
504
  }
431
505
 
506
+ case "searching": {
507
+ s.searching = false; // any key stops; runSearch will drain and switch to results
508
+ break;
509
+ }
510
+
432
511
  case "results": {
433
512
  if (key === "q") return true;
434
513
  if (key === "\x1b") { s.mode = "criteria"; break; }
@@ -7,7 +7,7 @@
7
7
  * bun run settings cooldown 0 Set comment cooldown (0-300 seconds)
8
8
  */
9
9
 
10
- import { loadConfig, saveConfig } from "../storage/state.ts";
10
+ import { loadConfig, saveConfig } from "../server/state.ts";
11
11
 
12
12
  const args = process.argv.slice(2);
13
13
  const key = args[0];
@@ -16,7 +16,7 @@ const value = args[1];
16
16
  if (!key) {
17
17
  const cfg = loadConfig();
18
18
  console.log(`
19
- claude-buddy settings
19
+ coding-buddy settings
20
20
  ─────────────────────
21
21
  Comment cooldown: ${cfg.commentCooldown}s (0 = no throttling, default 30)
22
22
  Reaction TTL: ${cfg.reactionTTL}s (0 = permanent, default 0)
@@ -1,9 +1,9 @@
1
1
  /**
2
- * claude-buddy show — display current companion in terminal
2
+ * coding-buddy show — display current companion in terminal
3
3
  */
4
4
 
5
- import { renderBuddy, renderFace, RARITY_STARS } from "../../../core/engine.ts";
6
- import { loadCompanion, loadReaction } from "../storage/state.ts";
5
+ import { renderBuddy, renderFace, RARITY_STARS } from "../core/engine.ts"
6
+ import { loadCompanion, loadReaction } from "../server/state.ts";
7
7
 
8
8
  const BOLD = "\x1b[1m";
9
9
  const DIM = "\x1b[2m";
@@ -12,7 +12,7 @@ const NC = "\x1b[0m";
12
12
  const companion = loadCompanion();
13
13
 
14
14
  if (!companion) {
15
- console.log("No companion found. Run 'claude-buddy install' first.");
15
+ console.log("No companion found. Run 'coding-buddy install' first.");
16
16
  process.exit(1);
17
17
  }
18
18
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # claude-buddy diagnostic test status line
2
+ # coding-buddy diagnostic test status line
3
3
  # Outputs multiple padding strategies + multi-line check + width check
4
4
 
5
5
  cat > /dev/null # drain stdin
@@ -12,7 +12,7 @@ GREEN=$'\033[38;2;78;186;101m'
12
12
  BLUE=$'\033[38;2;87;105;247m'
13
13
 
14
14
  # Header
15
- echo "${DIM}--- claude-buddy statusline test ---${NC}"
15
+ echo "${DIM}--- coding-buddy statusline test ---${NC}"
16
16
 
17
17
  # Multi-line check
18
18
  echo "LINE_1_top"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * claude-buddy test-statusline — temporarily install a test status line
3
+ * coding-buddy test-statusline — temporarily install a test status line
4
4
  *
5
5
  * Run: bun run test-statusline # install test
6
6
  * bun run test-statusline restore # restore original
@@ -8,15 +8,16 @@
8
8
  * The test status line outputs multiple padding strategies side-by-side
9
9
  * so you can see in actual Claude Code which one renders correctly.
10
10
  *
11
- * Your original statusLine config is backed up to <state-dir>/statusline.bak.
11
+ * Your original statusLine config is backed up to <state-dir>/statusline.bak
12
+ * (state dir resolves via server/paths.ts).
12
13
  */
13
14
 
14
15
  import { readFileSync, writeFileSync, existsSync, copyFileSync, chmodSync, mkdirSync } from "fs";
15
16
  import { join, dirname, resolve } from "path";
16
- import { getBuddyStateDir, getClaudeSettingsPath } from "../storage/paths.ts";
17
+ import { buddyStateDir, claudeSettingsPath } from "../server/path.ts";
17
18
 
18
- const SETTINGS = getClaudeSettingsPath();
19
- const STATE_DIR = getBuddyStateDir();
19
+ const SETTINGS = claudeSettingsPath();
20
+ const STATE_DIR = buddyStateDir();
20
21
  const BACKUP = join(STATE_DIR, "statusline.bak");
21
22
  const TEST_SCRIPT = join(STATE_DIR, "test-statusline.sh");
22
23
  const SOURCE_SCRIPT = resolve(import.meta.dir, "test-statusline.sh");
@@ -29,22 +30,27 @@ const BOLD = "\x1b[1m";
29
30
  const NC = "\x1b[0m";
30
31
 
31
32
  function ok(msg: string) { console.log(`${GREEN}✓${NC} ${msg}`); }
33
+ function info(msg: string) { console.log(`${CYAN}→${NC} ${msg}`); }
32
34
  function warn(msg: string) { console.log(`${YELLOW}⚠${NC} ${msg}`); }
33
35
  function err(msg: string) { console.log(`${RED}✗${NC} ${msg}`); }
34
36
 
35
37
  const action = process.argv[2] || "install";
36
38
 
39
+ // ─── Install ────────────────────────────────────────────────────────────────
40
+
37
41
  if (action === "install") {
38
- console.log(`\n${BOLD}claude-buddy test status line installer${NC}\n`);
42
+ console.log(`\n${BOLD}coding-buddy test status line installer${NC}\n`);
39
43
 
40
44
  if (!existsSync(SETTINGS)) {
41
- err(`Claude settings not found: ${SETTINGS}`);
45
+ err(`${SETTINGS} not found`);
42
46
  process.exit(1);
43
47
  }
44
48
  if (!existsSync(SOURCE_SCRIPT)) {
45
49
  err(`Source test script missing: ${SOURCE_SCRIPT}`);
46
50
  process.exit(1);
47
51
  }
52
+
53
+ // Backup
48
54
  if (existsSync(BACKUP)) {
49
55
  warn(`Backup already exists at ${BACKUP}`);
50
56
  warn("Run 'bun run test-statusline restore' first to revert");
@@ -55,10 +61,12 @@ if (action === "install") {
55
61
  copyFileSync(SETTINGS, BACKUP);
56
62
  ok(`Backed up settings to ${BACKUP}`);
57
63
 
64
+ // Copy the test script (no inline TS interpolation issues)
58
65
  copyFileSync(SOURCE_SCRIPT, TEST_SCRIPT);
59
66
  chmodSync(TEST_SCRIPT, 0o755);
60
67
  ok(`Test script copied to ${TEST_SCRIPT}`);
61
68
 
69
+ // Update settings to use test script
62
70
  const settings = JSON.parse(readFileSync(SETTINGS, "utf8"));
63
71
  settings.statusLine = {
64
72
  type: "command",
@@ -88,8 +96,10 @@ ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━
88
96
  process.exit(0);
89
97
  }
90
98
 
99
+ // ─── Restore ────────────────────────────────────────────────────────────────
100
+
91
101
  if (action === "restore") {
92
- console.log(`\n${BOLD}claude-buddy test status line restore${NC}\n`);
102
+ console.log(`\n${BOLD}coding-buddy test status line restore${NC}\n`);
93
103
 
94
104
  if (!existsSync(BACKUP)) {
95
105
  err(`No backup found at ${BACKUP}`);
@@ -101,13 +111,13 @@ if (action === "restore") {
101
111
  ok("Original settings.json restored");
102
112
 
103
113
  const { unlinkSync } = require("fs");
104
- try { unlinkSync(BACKUP); ok("Backup file removed"); } catch {}
105
- try { unlinkSync(TEST_SCRIPT); ok("Test script removed"); } catch {}
114
+ try { unlinkSync(BACKUP); ok("Backup file removed"); } catch { /* noop */ }
115
+ try { unlinkSync(TEST_SCRIPT); ok("Test script removed"); } catch { /* noop */ }
106
116
 
107
117
  console.log(`\n${GREEN}Done.${NC} Restart Claude Code to apply.\n`);
108
118
  process.exit(0);
109
119
  }
110
120
 
111
121
  err(`Unknown action: ${action}`);
112
- console.log("Usage: bun run test-statusline [install|restore]");
122
+ console.log(`Usage: bun run test-statusline [install|restore]`);
113
123
  process.exit(1);