@ramarivera/coding-buddy 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/omp/ui.ts +13 -31
- package/adapters/pi/ui.ts +13 -31
- package/adapters/shared/file-storage.ts +1 -0
- package/adapters/shared/widget-layout.ts +125 -11
- package/cli/install.ts +21 -3
- package/core/model.ts +1 -0
- package/package.json +1 -2
- package/scripts/ci/check-statusline.sh +231 -118
- package/scripts/ci/render-statusline-pty.py +12 -4
- package/scripts/ci/render-vhs-goldens.sh +10 -19
- package/scripts/ci/statusline.tape +28 -2
- package/scripts/ci/vhs-density-fixtures.sh +27 -0
- package/scripts/ci/vhs-run-statusline.sh +13 -1
- package/scripts/ci/write-density-status.ts +86 -0
- package/server/art.ts +29 -8
- package/server/hooks/buddy-comment.ts +89 -10
- package/server/state.ts +85 -25
- package/statusline/buddy-status.sh +161 -41
- package/scripts/postinstall.cjs +0 -42
package/adapters/omp/ui.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import type { OmpBuddyUiContext } from "./context.ts";
|
|
2
2
|
import type { Achievement } from "../../core/achievements.ts";
|
|
3
3
|
import type { Companion, ReactionState } from "../../core/model.ts";
|
|
4
|
-
import { renderAchievementsSummary
|
|
4
|
+
import { renderAchievementsSummary } from "./renderers.ts";
|
|
5
5
|
import { OmpBuddyStorage } from "./storage.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { BuddyWidget } from "../shared/widget-layout.ts";
|
|
7
7
|
|
|
8
8
|
export class OmpBuddyUI {
|
|
9
9
|
private readonly timers: Array<{ timer: Timer; clear: (timer: Timer) => void }> = [];
|
|
10
|
-
private
|
|
11
|
-
private rendered: {
|
|
12
|
-
ctx: OmpBuddyUiContext;
|
|
13
|
-
companion: Companion;
|
|
14
|
-
reaction: ReactionState | null;
|
|
15
|
-
achievements: Achievement[];
|
|
16
|
-
} | null = null;
|
|
10
|
+
private readonly buddy = new BuddyWidget();
|
|
17
11
|
|
|
18
12
|
constructor(private readonly storage: OmpBuddyStorage) {}
|
|
19
13
|
|
|
@@ -29,9 +23,7 @@ export class OmpBuddyUI {
|
|
|
29
23
|
|
|
30
24
|
dispose(): void {
|
|
31
25
|
this.cancelTimers();
|
|
32
|
-
this.
|
|
33
|
-
this.resizeUnsubscribe = null;
|
|
34
|
-
this.rendered = null;
|
|
26
|
+
this.buddy.dispose();
|
|
35
27
|
}
|
|
36
28
|
|
|
37
29
|
refresh(
|
|
@@ -42,12 +34,15 @@ export class OmpBuddyUI {
|
|
|
42
34
|
): void {
|
|
43
35
|
const currentReaction = reaction ?? null;
|
|
44
36
|
const muted = this.storage.isMuted();
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
const scheduler = getScheduler(ctx);
|
|
38
|
+
|
|
47
39
|
ctx.ui.setStatus("buddy", undefined);
|
|
48
|
-
|
|
49
|
-
"buddy",
|
|
50
|
-
|
|
40
|
+
this.buddy.refresh(
|
|
41
|
+
(lines) => ctx.ui.setWidget("buddy", lines),
|
|
42
|
+
companion,
|
|
43
|
+
muted ? null : currentReaction,
|
|
44
|
+
[...achievements],
|
|
45
|
+
scheduler ?? undefined,
|
|
51
46
|
);
|
|
52
47
|
|
|
53
48
|
this.cancelTimers();
|
|
@@ -55,8 +50,6 @@ export class OmpBuddyUI {
|
|
|
55
50
|
|
|
56
51
|
const ttl = this.storage.loadConfig().reactionTTL;
|
|
57
52
|
if (ttl <= 0) return;
|
|
58
|
-
|
|
59
|
-
const scheduler = getScheduler(ctx);
|
|
60
53
|
if (!scheduler) return;
|
|
61
54
|
|
|
62
55
|
const reactionTimestamp = currentReaction.timestamp;
|
|
@@ -83,21 +76,10 @@ export class OmpBuddyUI {
|
|
|
83
76
|
unlocked: Array<{ achievement: Achievement; unlockedAt: number; slot?: string }>,
|
|
84
77
|
remaining: Achievement[],
|
|
85
78
|
): void {
|
|
86
|
-
this.
|
|
79
|
+
this.buddy.clear();
|
|
87
80
|
ctx.ui.setWidget("buddy", renderAchievementsSummary(unlocked, remaining));
|
|
88
81
|
ctx.ui.notify(`Achievements: ${unlocked.length} unlocked`, "info");
|
|
89
82
|
}
|
|
90
|
-
|
|
91
|
-
private subscribeToResize(): void {
|
|
92
|
-
if (this.resizeUnsubscribe) return;
|
|
93
|
-
const resizeHandler = () => {
|
|
94
|
-
if (!this.rendered) return;
|
|
95
|
-
const { ctx, companion, reaction, achievements } = this.rendered;
|
|
96
|
-
const muted = this.storage.isMuted();
|
|
97
|
-
ctx.ui.setWidget("buddy", renderBuddyWidget(companion, muted ? null : reaction, achievements, getWidgetWidth()));
|
|
98
|
-
};
|
|
99
|
-
this.resizeUnsubscribe = subscribeToWidgetResize(resizeHandler);
|
|
100
|
-
}
|
|
101
83
|
}
|
|
102
84
|
|
|
103
85
|
function getScheduler(
|
package/adapters/pi/ui.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { Achievement } from "../../core/achievements.ts";
|
|
3
3
|
import type { Companion, ReactionState } from "../../core/model.ts";
|
|
4
|
-
import { renderAchievementsSummary
|
|
4
|
+
import { renderAchievementsSummary } from "./renderers.ts";
|
|
5
5
|
import { PiBuddyStorage } from "./storage.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { BuddyWidget } from "../shared/widget-layout.ts";
|
|
7
7
|
|
|
8
8
|
type PiBuddyUiContext = ExtensionContext & {
|
|
9
9
|
setTimeout?: (callback: () => void, ms?: number) => Timer;
|
|
@@ -12,13 +12,7 @@ type PiBuddyUiContext = ExtensionContext & {
|
|
|
12
12
|
|
|
13
13
|
export class PiBuddyUI {
|
|
14
14
|
private readonly timers: Array<{ timer: Timer; clear: (timer: Timer) => void }> = [];
|
|
15
|
-
private
|
|
16
|
-
private rendered: {
|
|
17
|
-
ctx: PiBuddyUiContext;
|
|
18
|
-
companion: Companion;
|
|
19
|
-
reaction: ReactionState | null;
|
|
20
|
-
achievements: Achievement[];
|
|
21
|
-
} | null = null;
|
|
15
|
+
private readonly buddy = new BuddyWidget();
|
|
22
16
|
|
|
23
17
|
constructor(private readonly storage: PiBuddyStorage) {}
|
|
24
18
|
|
|
@@ -34,9 +28,7 @@ export class PiBuddyUI {
|
|
|
34
28
|
|
|
35
29
|
dispose(): void {
|
|
36
30
|
this.cancelTimers();
|
|
37
|
-
this.
|
|
38
|
-
this.resizeUnsubscribe = null;
|
|
39
|
-
this.rendered = null;
|
|
31
|
+
this.buddy.dispose();
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
refresh(
|
|
@@ -47,12 +39,15 @@ export class PiBuddyUI {
|
|
|
47
39
|
): void {
|
|
48
40
|
const currentReaction = reaction ?? null;
|
|
49
41
|
const muted = this.storage.isMuted();
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
const scheduler = getScheduler(ctx);
|
|
43
|
+
|
|
52
44
|
ctx.ui.setStatus("buddy", undefined);
|
|
53
|
-
|
|
54
|
-
"buddy",
|
|
55
|
-
|
|
45
|
+
this.buddy.refresh(
|
|
46
|
+
(lines) => ctx.ui.setWidget("buddy", lines),
|
|
47
|
+
companion,
|
|
48
|
+
muted ? null : currentReaction,
|
|
49
|
+
[...achievements],
|
|
50
|
+
scheduler ?? undefined,
|
|
56
51
|
);
|
|
57
52
|
|
|
58
53
|
this.cancelTimers();
|
|
@@ -60,8 +55,6 @@ export class PiBuddyUI {
|
|
|
60
55
|
|
|
61
56
|
const ttl = this.storage.loadConfig().reactionTTL;
|
|
62
57
|
if (ttl <= 0) return;
|
|
63
|
-
|
|
64
|
-
const scheduler = getScheduler(ctx);
|
|
65
58
|
if (!scheduler) return;
|
|
66
59
|
|
|
67
60
|
const reactionTimestamp = currentReaction.timestamp;
|
|
@@ -88,21 +81,10 @@ export class PiBuddyUI {
|
|
|
88
81
|
unlocked: Array<{ achievement: Achievement; unlockedAt: number; slot?: string }>,
|
|
89
82
|
remaining: Achievement[],
|
|
90
83
|
): void {
|
|
91
|
-
this.
|
|
84
|
+
this.buddy.clear();
|
|
92
85
|
ctx.ui.setWidget("buddy", renderAchievementsSummary(unlocked, remaining));
|
|
93
86
|
ctx.ui.notify(`Achievements: ${unlocked.length} unlocked`, "info");
|
|
94
87
|
}
|
|
95
|
-
|
|
96
|
-
private subscribeToResize(): void {
|
|
97
|
-
if (this.resizeUnsubscribe) return;
|
|
98
|
-
const resizeHandler = () => {
|
|
99
|
-
if (!this.rendered) return;
|
|
100
|
-
const { ctx, companion, reaction, achievements } = this.rendered;
|
|
101
|
-
const muted = this.storage.isMuted();
|
|
102
|
-
ctx.ui.setWidget("buddy", renderBuddyWidget(companion, muted ? null : reaction, achievements, getWidgetWidth()));
|
|
103
|
-
};
|
|
104
|
-
this.resizeUnsubscribe = subscribeToWidgetResize(resizeHandler);
|
|
105
|
-
}
|
|
106
88
|
}
|
|
107
89
|
|
|
108
90
|
function getScheduler(
|
|
@@ -164,16 +164,20 @@ function padLine(line: string, width: number): string {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function frameReaction(reaction: string, achievements: Achievement[], innerWidth: number): string[] {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
const maxContentLines = WIDGET_MAX_LINES - 2;
|
|
168
|
+
const trimmedReaction = reaction.trim();
|
|
169
|
+
const achievementLines = achievements.map((achievement) => `🏆 ${achievement.name}`);
|
|
170
|
+
const reactionMax = Math.max(0, maxContentLines - achievementLines.length);
|
|
171
|
+
const reactionLines = trimmedReaction ? wrapReaction(trimmedReaction, innerWidth, reactionMax) : [];
|
|
172
|
+
const content = reactionLines.concat(achievementLines).slice(0, maxContentLines);
|
|
173
|
+
const paddedContent = content.length === 1 ? ["", ...content] : content;
|
|
174
|
+
|
|
172
175
|
const top = `.${"-".repeat(innerWidth + 2)}.`;
|
|
176
|
+
const bottom = `\`${"-".repeat(innerWidth + 2)}'`;
|
|
173
177
|
return [
|
|
174
178
|
top,
|
|
175
|
-
...
|
|
176
|
-
|
|
179
|
+
...paddedContent.map((line) => `| ${DIM_ITALIC}${padLine(line, innerWidth)}${RESET} |`),
|
|
180
|
+
bottom,
|
|
177
181
|
];
|
|
178
182
|
}
|
|
179
183
|
|
|
@@ -205,16 +209,21 @@ export function renderCompanionWidget(
|
|
|
205
209
|
const maxBubbleInner = safeWidth - spriteWidth - tailWidth - 4;
|
|
206
210
|
const hasContent = Boolean(reaction?.reaction?.trim()) || achievements.length > 0;
|
|
207
211
|
const canFrame = hasContent && maxBubbleInner >= minBubbleInner;
|
|
208
|
-
const bubbleInner = canFrame ?
|
|
212
|
+
const bubbleInner = canFrame ? maxBubbleInner : 0;
|
|
209
213
|
const bubble = canFrame ? frameReaction(reaction?.reaction ?? "", achievements, bubbleInner) : [];
|
|
210
214
|
const bubbleWidth = bubbleInner + 4;
|
|
211
215
|
const cardWidth = canFrame ? bubbleWidth + tailWidth + spriteWidth : spriteWidth;
|
|
212
|
-
|
|
216
|
+
|
|
217
|
+
const achievementSprites = canFrame
|
|
218
|
+
? []
|
|
219
|
+
: achievements.map((achievement) => `${DIM_ITALIC}${padLine(`🏆 ${achievement.name}`, spriteWidth)}${RESET}`);
|
|
220
|
+
const spriteBody = [...clippedArt, clippedLabel, ...achievementSprites];
|
|
221
|
+
const height = Math.max(spriteBody.length, bubble.length);
|
|
213
222
|
const connectorRow = canFrame ? Math.min(bubble.length - 2, Math.max(1, Math.floor(bubble.length / 2))) : -1;
|
|
214
223
|
|
|
215
224
|
return Array.from({ length: Math.min(WIDGET_MAX_LINES, height) }, (_, index) => {
|
|
216
|
-
const
|
|
217
|
-
const sprite = padLine(
|
|
225
|
+
const spriteLine = spriteBody[index] ?? "";
|
|
226
|
+
const sprite = padLine(spriteLine, spriteWidth);
|
|
218
227
|
let body: string;
|
|
219
228
|
if (canFrame) {
|
|
220
229
|
const bubbleLine = bubble[index] ?? " ".repeat(bubbleWidth);
|
|
@@ -226,3 +235,108 @@ export function renderCompanionWidget(
|
|
|
226
235
|
return `${" ".repeat(Math.max(0, safeWidth - cardWidth))}${body}`;
|
|
227
236
|
});
|
|
228
237
|
}
|
|
238
|
+
|
|
239
|
+
export interface BuddyWidgetScheduler {
|
|
240
|
+
setTimeout(callback: () => void, ms?: number): Timer;
|
|
241
|
+
clearTimer(timer: Timer): void;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Width-aware buddy widget manager.
|
|
246
|
+
*
|
|
247
|
+
* Renders once when state arrives, then re-renders with the current terminal
|
|
248
|
+
* width after coalescing rapid process.stdout resize events. Callers supply
|
|
249
|
+
* only their harness `setWidget` and the current state; this class owns the
|
|
250
|
+
* resize subscription, debounce, and re-render logic.
|
|
251
|
+
*/
|
|
252
|
+
export class BuddyWidget {
|
|
253
|
+
private readonly getWidth: () => number;
|
|
254
|
+
private scheduler: BuddyWidgetScheduler;
|
|
255
|
+
private readonly coalesceMs: number;
|
|
256
|
+
private setWidget: ((lines: string[]) => void) | null = null;
|
|
257
|
+
private state: { companion: Companion; reaction: ReactionState | null; achievements: Achievement[] } | null = null;
|
|
258
|
+
private resizeUnsubscribe: (() => void) | null = null;
|
|
259
|
+
private timer: Timer | null = null;
|
|
260
|
+
|
|
261
|
+
constructor(options?: {
|
|
262
|
+
getWidth?: () => number;
|
|
263
|
+
scheduler?: BuddyWidgetScheduler;
|
|
264
|
+
coalesceMs?: number;
|
|
265
|
+
}) {
|
|
266
|
+
this.getWidth = options?.getWidth ?? getWidgetWidth;
|
|
267
|
+
this.scheduler = options?.scheduler ?? {
|
|
268
|
+
setTimeout: globalThis.setTimeout.bind(globalThis),
|
|
269
|
+
clearTimer: globalThis.clearTimeout.bind(globalThis),
|
|
270
|
+
};
|
|
271
|
+
this.coalesceMs = options?.coalesceMs ?? 50;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Render immediately with the current state and start (or renew) the shared
|
|
276
|
+
* resize subscription. The `setWidget` callback is the adapter's harness
|
|
277
|
+
* setter bound to the current UI context.
|
|
278
|
+
*/
|
|
279
|
+
refresh(
|
|
280
|
+
setWidget: (lines: string[]) => void,
|
|
281
|
+
companion: Companion,
|
|
282
|
+
reaction: ReactionState | null,
|
|
283
|
+
achievements: Achievement[] = [],
|
|
284
|
+
scheduler?: BuddyWidgetScheduler,
|
|
285
|
+
): void {
|
|
286
|
+
// Cancel any pending resize timer before swapping the scheduler or state
|
|
287
|
+
// so clearTimer is always paired with the scheduler that created the timer.
|
|
288
|
+
this.cancelTimer();
|
|
289
|
+
this.setWidget = setWidget;
|
|
290
|
+
if (scheduler) this.scheduler = scheduler;
|
|
291
|
+
this.state = { companion, reaction, achievements };
|
|
292
|
+
this.renderAndSet();
|
|
293
|
+
this.subscribe();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Stop reacting to resizes without unsubscribing from the shared listener. */
|
|
297
|
+
clear(): void {
|
|
298
|
+
this.cancelTimer();
|
|
299
|
+
this.state = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Tear down timers and the shared resize listener. */
|
|
303
|
+
dispose(): void {
|
|
304
|
+
this.cancelTimer();
|
|
305
|
+
this.resizeUnsubscribe?.();
|
|
306
|
+
this.resizeUnsubscribe = null;
|
|
307
|
+
this.state = null;
|
|
308
|
+
this.setWidget = null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private subscribe(): void {
|
|
312
|
+
if (this.resizeUnsubscribe) return;
|
|
313
|
+
this.resizeUnsubscribe = subscribeToWidgetResize(() => this.onResize());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private onResize(): void {
|
|
317
|
+
// A cleared widget has no state/setter to render; don't schedule work.
|
|
318
|
+
if (!this.state || !this.setWidget) return;
|
|
319
|
+
this.cancelTimer();
|
|
320
|
+
this.timer = this.scheduler.setTimeout(() => {
|
|
321
|
+
this.timer = null;
|
|
322
|
+
try {
|
|
323
|
+
this.renderAndSet();
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error("BuddyWidget render failed:", error);
|
|
326
|
+
}
|
|
327
|
+
}, this.coalesceMs);
|
|
328
|
+
}
|
|
329
|
+
private renderAndSet(): void {
|
|
330
|
+
if (!this.state || !this.setWidget) return;
|
|
331
|
+
const width = this.getWidth();
|
|
332
|
+
const lines = renderCompanionWidget(this.state.companion, this.state.reaction, this.state.achievements, width);
|
|
333
|
+
this.setWidget(lines);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private cancelTimer(): void {
|
|
337
|
+
if (this.timer !== null) {
|
|
338
|
+
this.scheduler.clearTimer(this.timer);
|
|
339
|
+
this.timer = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
package/cli/install.ts
CHANGED
|
@@ -24,6 +24,17 @@ import { loadCompanion, saveCompanion, resolveUserId, writeStatusState } from ".
|
|
|
24
24
|
import { generateFallbackName } from "../core/reactions.ts"
|
|
25
25
|
import { copyRuntimeApp, stableRuntimePaths } from "./runtime-app.ts";
|
|
26
26
|
|
|
27
|
+
/** Recognise any buddy-owned hook command, however it is pathed. */
|
|
28
|
+
function isBuddyHookCommand(command: unknown): boolean {
|
|
29
|
+
if (typeof command !== "string") return false;
|
|
30
|
+
return (
|
|
31
|
+
command.includes("coding-buddy") ||
|
|
32
|
+
command.includes("claude-buddy") ||
|
|
33
|
+
command.includes("buddy-state") ||
|
|
34
|
+
/\/(buddy-comment|suggest|react|mood-react|name-react|file-type-react)\.(sh|ts)\b/.test(command)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
const CYAN = "\x1b[36m";
|
|
28
39
|
const GREEN = "\x1b[32m";
|
|
29
40
|
const YELLOW = "\x1b[33m";
|
|
@@ -203,7 +214,7 @@ function installHooks(settings: Record<string, any>, appDir: string) {
|
|
|
203
214
|
// plus file-type specific reactions on Write/Edit.
|
|
204
215
|
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
205
216
|
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
|
|
206
|
-
(h: any) => !h.hooks?.some((hh: any) =>
|
|
217
|
+
(h: any) => !h.hooks?.some((hh: any) => isBuddyHookCommand(hh.command)),
|
|
207
218
|
);
|
|
208
219
|
settings.hooks.PostToolUse.push({
|
|
209
220
|
matcher: "Bash",
|
|
@@ -216,8 +227,15 @@ function installHooks(settings: Record<string, any>, appDir: string) {
|
|
|
216
227
|
|
|
217
228
|
// Stop: extract <!-- buddy: --> comment from Claude's response
|
|
218
229
|
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
230
|
+
// Match on the hook script filenames, not the package name: the installed
|
|
231
|
+
// path is <state>/app/hooks/buddy-comment.sh, which contains neither
|
|
232
|
+
// "coding-buddy" nor "claude-buddy". The old filter therefore never matched
|
|
233
|
+
// and every install appended another pair — observed at 8 Stop entries on a
|
|
234
|
+
// dogfooding machine. Duplicates are not harmless: the first invocation
|
|
235
|
+
// adopts a buddy_react reaction and stamps the stop marker, then the rest
|
|
236
|
+
// see it as stale and overwrite the bubble with a canned pool line.
|
|
219
237
|
settings.hooks.Stop = settings.hooks.Stop.filter(
|
|
220
|
-
(h: any) => !h.hooks?.some((hh: any) =>
|
|
238
|
+
(h: any) => !h.hooks?.some((hh: any) => isBuddyHookCommand(hh.command)),
|
|
221
239
|
);
|
|
222
240
|
settings.hooks.Stop.push({
|
|
223
241
|
hooks: [commandHook(commentHook)],
|
|
@@ -230,7 +248,7 @@ function installHooks(settings: Record<string, any>, appDir: string) {
|
|
|
230
248
|
// reaction, plus mood-react based on prompt content.
|
|
231
249
|
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
232
250
|
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
|
|
233
|
-
(h: any) => !h.hooks?.some((hh: any) =>
|
|
251
|
+
(h: any) => !h.hooks?.some((hh: any) => isBuddyHookCommand(hh.command)),
|
|
234
252
|
);
|
|
235
253
|
settings.hooks.UserPromptSubmit.push({
|
|
236
254
|
hooks: [commandHook(nameHook)],
|
package/core/model.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface BuddyConfig {
|
|
|
38
38
|
bubblePosition: "top" | "left";
|
|
39
39
|
showRarity: boolean;
|
|
40
40
|
statusLineEnabled: boolean;
|
|
41
|
+
statuslineDensity?: "auto" | "full" | "compact" | "minimal";
|
|
41
42
|
turnCommentModel?: BuddyTurnCommentModelConfig;
|
|
42
43
|
/** Optional per-host bound for end-of-turn reaction LLM calls (ms). */
|
|
43
44
|
turnCommentTimeoutMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramarivera/coding-buddy",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Persistent coding companion for Claude Code, Pi, and Oh My Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"omp": {
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"coding-buddy": "cli/index.ts"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"postinstall": "node scripts/postinstall.cjs",
|
|
21
20
|
"server": "bun run server/index.ts",
|
|
22
21
|
"pick": "bun run cli/pick.ts",
|
|
23
22
|
"hunt": "bun run cli/hunt.ts",
|