@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.
- package/.claude-plugin/marketplace.json +45 -0
- package/.claude-plugin/plugin.json +30 -0
- package/LICENSE +1 -1
- package/README.md +96 -45
- 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 +136 -57
- 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 +46 -13
- 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 +22 -9
- package/hooks/file-type-react.sh +403 -0
- package/hooks/hooks.json +52 -0
- package/hooks/mood-react.sh +210 -0
- package/{adapters/claude/hooks → hooks}/name-react.sh +3 -4
- package/hooks/react.sh +1364 -0
- package/hooks/suggest.sh +55 -0
- package/package.json +70 -31
- package/scripts/gen-emoji-widths.ts +34 -0
- package/scripts/paths.sh +33 -2
- 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 +552 -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 +402 -0
- package/statusline/combined-status.sh +135 -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/plugin/marketplace.json +0 -40
- package/adapters/claude/plugin/plugin.json +0 -26
- 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
|
@@ -17,13 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
loadActiveSlot, saveActiveSlot, listCompanionSlots,
|
|
20
|
-
loadCompanionSlot, saveCompanionSlot, slugify, unusedName, writeStatusState,
|
|
21
|
-
} from "../
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from "
|
|
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
|
|
83
|
-
const RA_OPTS
|
|
84
|
-
const SH_OPTS
|
|
85
|
-
const ST_OPTS
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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"
|
|
265
|
-
: s.mode === "criteria"
|
|
266
|
-
: s.mode === "
|
|
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 = `
|
|
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"
|
|
288
|
-
s.mode === "criteria"
|
|
289
|
-
s.mode === "
|
|
290
|
-
s.mode === "
|
|
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
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
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" ?
|
|
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
|
|
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 (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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)
|
|
332
|
-
if (wantRa !== null && bones.rarity !== wantRa)
|
|
333
|
-
if (wantShiny !== null && bones.shiny !== wantShiny)
|
|
334
|
-
if (wantPeak !== null && bones.peak !== wantPeak)
|
|
335
|
-
if (wantDump !== null && bones.dump !== wantDump)
|
|
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:
|
|
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.
|
|
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 "../
|
|
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
|
-
|
|
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
|
-
*
|
|
2
|
+
* coding-buddy show — display current companion in terminal
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { renderBuddy, renderFace, RARITY_STARS } from "
|
|
6
|
-
import { loadCompanion, loadReaction } from "../
|
|
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 '
|
|
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
|
-
#
|
|
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}---
|
|
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
|
-
*
|
|
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 {
|
|
17
|
+
import { buddyStateDir, claudeSettingsPath } from "../server/path.ts";
|
|
17
18
|
|
|
18
|
-
const SETTINGS =
|
|
19
|
-
const STATE_DIR =
|
|
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}
|
|
42
|
+
console.log(`\n${BOLD}coding-buddy test status line installer${NC}\n`);
|
|
39
43
|
|
|
40
44
|
if (!existsSync(SETTINGS)) {
|
|
41
|
-
err(
|
|
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}
|
|
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(
|
|
122
|
+
console.log(`Usage: bun run test-statusline [install|restore]`);
|
|
113
123
|
process.exit(1);
|