@ramarivera/coding-buddy 0.4.0-alpha.9 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
- package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
- package/LICENSE +1 -1
- package/README.md +88 -37
- 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 +111 -50
- 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 +2 -2
- 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 +19 -1
- package/hooks/file-type-react.sh +401 -0
- package/hooks/hooks.json +52 -0
- package/hooks/mood-react.sh +208 -0
- package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
- package/hooks/react.sh +1368 -0
- package/hooks/suggest.sh +55 -0
- package/package.json +70 -31
- package/scripts/gen-emoji-widths.ts +34 -0
- package/scripts/paths.sh +17 -1
- 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 +540 -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 +397 -0
- package/statusline/combined-status.sh +134 -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/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
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude-backed achievement storage and rendering.
|
|
3
|
-
*
|
|
4
|
-
* Pure achievement definitions and threshold logic live in core/achievements.ts.
|
|
5
|
-
* This adapter persists counters and unlocked state under ~/.claude-buddy/.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
10
|
-
import { homedir } from "os";
|
|
11
|
-
import {
|
|
12
|
-
ACHIEVEMENTS,
|
|
13
|
-
EMPTY_GLOBAL,
|
|
14
|
-
EMPTY_SLOT,
|
|
15
|
-
GLOBAL_KEYS,
|
|
16
|
-
SLOT_KEYS,
|
|
17
|
-
getUnlockedAchievements,
|
|
18
|
-
type Achievement,
|
|
19
|
-
type EventCounters,
|
|
20
|
-
type GlobalCounters,
|
|
21
|
-
type SlotCounters,
|
|
22
|
-
} from "../../../core/achievements.ts";
|
|
23
|
-
import type { UnlockedAchievement } from "../../../core/model.ts";
|
|
24
|
-
|
|
25
|
-
const STATE_DIR = join(homedir(), ".claude-buddy");
|
|
26
|
-
const EVENTS_FILE = join(STATE_DIR, "events.json");
|
|
27
|
-
const DAYS_FILE = join(STATE_DIR, "active_days.json");
|
|
28
|
-
const UNLOCKED_FILE = join(STATE_DIR, "unlocked.json");
|
|
29
|
-
|
|
30
|
-
function slotEventsFile(slot: string): string {
|
|
31
|
-
return join(STATE_DIR, `events.${slot}.json`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function ensureDir(): void {
|
|
35
|
-
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function atomicWrite(path: string, data: string): void {
|
|
39
|
-
ensureDir();
|
|
40
|
-
const tmp = path + ".tmp";
|
|
41
|
-
writeFileSync(tmp, data);
|
|
42
|
-
renameSync(tmp, path);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function loadGlobalEvents(): GlobalCounters {
|
|
46
|
-
try {
|
|
47
|
-
const parsed = JSON.parse(readFileSync(EVENTS_FILE, "utf8"));
|
|
48
|
-
return { ...EMPTY_GLOBAL, ...parsed };
|
|
49
|
-
} catch {
|
|
50
|
-
return { ...EMPTY_GLOBAL };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function saveGlobalEvents(events: GlobalCounters): void {
|
|
55
|
-
atomicWrite(EVENTS_FILE, JSON.stringify(events, null, 2));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function loadSlotEvents(slot: string): SlotCounters {
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(readFileSync(slotEventsFile(slot), "utf8"));
|
|
61
|
-
return { ...EMPTY_SLOT, ...parsed };
|
|
62
|
-
} catch {
|
|
63
|
-
return { ...EMPTY_SLOT };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function saveSlotEvents(slot: string, events: SlotCounters): void {
|
|
68
|
-
atomicWrite(slotEventsFile(slot), JSON.stringify(events, null, 2));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function loadEvents(slot?: string): EventCounters {
|
|
72
|
-
const global = loadGlobalEvents();
|
|
73
|
-
if (!slot) {
|
|
74
|
-
return { ...global, pets: 0, reactions_given: 0 };
|
|
75
|
-
}
|
|
76
|
-
const slotEvents = loadSlotEvents(slot);
|
|
77
|
-
return {
|
|
78
|
-
...global,
|
|
79
|
-
pets: slotEvents.pets,
|
|
80
|
-
reactions_given: slotEvents.reactions_given,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function isSlotKey(key: keyof EventCounters): key is keyof SlotCounters {
|
|
85
|
-
return (SLOT_KEYS as readonly string[]).includes(key);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function isGlobalKey(key: keyof EventCounters): key is keyof GlobalCounters {
|
|
89
|
-
return (GLOBAL_KEYS as readonly string[]).includes(key);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function incrementEvent(
|
|
93
|
-
key: keyof EventCounters,
|
|
94
|
-
amount: number = 1,
|
|
95
|
-
slot?: string,
|
|
96
|
-
): EventCounters {
|
|
97
|
-
if (isSlotKey(key) && slot) {
|
|
98
|
-
const slotEvents = loadSlotEvents(slot);
|
|
99
|
-
slotEvents[key] += amount;
|
|
100
|
-
saveSlotEvents(slot, slotEvents);
|
|
101
|
-
} else {
|
|
102
|
-
const global = loadGlobalEvents();
|
|
103
|
-
if (isGlobalKey(key)) {
|
|
104
|
-
global[key] += amount;
|
|
105
|
-
}
|
|
106
|
-
saveGlobalEvents(global);
|
|
107
|
-
}
|
|
108
|
-
return loadEvents(slot);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { loadEvents as loadGlobalEventsCompat, loadGlobalEvents as loadGlobalEventsDirect };
|
|
112
|
-
|
|
113
|
-
interface DayTracker {
|
|
114
|
-
lastDate: string;
|
|
115
|
-
totalDays: number;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function trackActiveDay(): void {
|
|
119
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
120
|
-
let tracker: DayTracker;
|
|
121
|
-
try {
|
|
122
|
-
tracker = JSON.parse(readFileSync(DAYS_FILE, "utf8"));
|
|
123
|
-
} catch {
|
|
124
|
-
tracker = { lastDate: "", totalDays: 0 };
|
|
125
|
-
}
|
|
126
|
-
if (tracker.lastDate === today) return;
|
|
127
|
-
|
|
128
|
-
tracker.lastDate = today;
|
|
129
|
-
tracker.totalDays += 1;
|
|
130
|
-
atomicWrite(DAYS_FILE, JSON.stringify(tracker, null, 2));
|
|
131
|
-
|
|
132
|
-
const events = loadGlobalEvents();
|
|
133
|
-
events.days_active = tracker.totalDays;
|
|
134
|
-
saveGlobalEvents(events);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function loadUnlocked(): UnlockedAchievement[] {
|
|
138
|
-
try {
|
|
139
|
-
return JSON.parse(readFileSync(UNLOCKED_FILE, "utf8"));
|
|
140
|
-
} catch {
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function saveUnlocked(unlocked: UnlockedAchievement[]): void {
|
|
146
|
-
atomicWrite(UNLOCKED_FILE, JSON.stringify(unlocked, null, 2));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function checkAndAward(slot?: string): Achievement[] {
|
|
150
|
-
const events = loadEvents(slot);
|
|
151
|
-
const unlocked = loadUnlocked();
|
|
152
|
-
const unlockedIds = new Set(unlocked.map((u) => u.id));
|
|
153
|
-
|
|
154
|
-
const newlyUnlocked = getUnlockedAchievements(events, unlockedIds);
|
|
155
|
-
if (newlyUnlocked.length > 0) {
|
|
156
|
-
unlocked.push(
|
|
157
|
-
...newlyUnlocked.map((ach) => ({
|
|
158
|
-
id: ach.id,
|
|
159
|
-
unlockedAt: Date.now(),
|
|
160
|
-
slot: slot ?? undefined,
|
|
161
|
-
})),
|
|
162
|
-
);
|
|
163
|
-
saveUnlocked(unlocked);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return newlyUnlocked;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const GOLD = "\x1b[38;2;255;193;7m";
|
|
170
|
-
const NC = "\x1b[0m";
|
|
171
|
-
const BOLD = "\x1b[1m";
|
|
172
|
-
const DIM = "\x1b[2m";
|
|
173
|
-
|
|
174
|
-
export function renderAchievementsCard(): string {
|
|
175
|
-
const unlocked = loadUnlocked();
|
|
176
|
-
const unlockedIds = new Set(unlocked.map((u) => u.id));
|
|
177
|
-
|
|
178
|
-
const W = 40;
|
|
179
|
-
const hr = "─".repeat(W - 2);
|
|
180
|
-
const sep = `├${"╌".repeat(W - 2)}┤`;
|
|
181
|
-
const lines: string[] = [];
|
|
182
|
-
|
|
183
|
-
const total = ACHIEVEMENTS.length;
|
|
184
|
-
const earned = unlockedIds.size;
|
|
185
|
-
|
|
186
|
-
lines.push(`${GOLD}╭${hr}╮${NC}`);
|
|
187
|
-
|
|
188
|
-
const header = "🏆 ACHIEVEMENTS";
|
|
189
|
-
lines.push(`${GOLD}│${NC} ${BOLD}${header}${NC}${"".padEnd(W - header.length - 4)}${GOLD}│${NC}`);
|
|
190
|
-
|
|
191
|
-
const barFilled = total > 0 ? Math.round((earned / total) * 20) : 0;
|
|
192
|
-
const bar = "█".repeat(barFilled) + "░".repeat(20 - barFilled);
|
|
193
|
-
const barText = `${bar} ${earned}/${total}`;
|
|
194
|
-
lines.push(`${GOLD}│${NC} ${barText}${"".padEnd(W - barText.length - 4)}${GOLD}│${NC}`);
|
|
195
|
-
|
|
196
|
-
lines.push(`${GOLD}${sep}${NC}`);
|
|
197
|
-
|
|
198
|
-
for (const ach of ACHIEVEMENTS) {
|
|
199
|
-
if (ach.secret && !unlockedIds.has(ach.id)) continue;
|
|
200
|
-
|
|
201
|
-
const done = unlockedIds.has(ach.id);
|
|
202
|
-
const status = done ? "✅" : "☐";
|
|
203
|
-
const content = ` ${ach.icon}${status} ${ach.name}`;
|
|
204
|
-
const descContent = ` ${ach.description}`;
|
|
205
|
-
|
|
206
|
-
if (done) {
|
|
207
|
-
lines.push(`${GOLD}│${NC} ${BOLD}${content}${NC}${"".padEnd(W - content.length - 3)}${GOLD}│${NC}`);
|
|
208
|
-
} else {
|
|
209
|
-
lines.push(`${GOLD}│${NC} ${DIM}${content}${NC}${"".padEnd(W - content.length - 3)}${GOLD}│${NC}`);
|
|
210
|
-
}
|
|
211
|
-
lines.push(`${GOLD}│${NC} ${DIM}${descContent}${NC}${"".padEnd(W - descContent.length - 3)}${GOLD}│${NC}`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (earned > 0 && earned === ACHIEVEMENTS.length) {
|
|
215
|
-
lines.push(`${GOLD}${sep}${NC}`);
|
|
216
|
-
const complete = "✨ ALL ACHIEVEMENTS UNLOCKED! ✨";
|
|
217
|
-
lines.push(`${GOLD}│${NC} ${BOLD}${complete}${NC}${"".padEnd(W - complete.length - 4)}${GOLD}│${NC}`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
lines.push(`${GOLD}╰${hr}╯${NC}`);
|
|
221
|
-
|
|
222
|
-
return lines.join("\n");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function renderAchievementsCardMarkdown(): string {
|
|
226
|
-
const unlocked = loadUnlocked();
|
|
227
|
-
const unlockedIds = new Set(unlocked.map((u) => u.id));
|
|
228
|
-
const total = ACHIEVEMENTS.length;
|
|
229
|
-
const earned = unlockedIds.size;
|
|
230
|
-
|
|
231
|
-
const barFilled = total > 0 ? Math.round((earned / total) * 20) : 0;
|
|
232
|
-
const bar = "█".repeat(barFilled) + "░".repeat(20 - barFilled);
|
|
233
|
-
|
|
234
|
-
const parts: string[] = [];
|
|
235
|
-
parts.push(`### 🏆 Achievements — ${earned}/${total}`);
|
|
236
|
-
parts.push("");
|
|
237
|
-
parts.push(`\`${bar}\``);
|
|
238
|
-
parts.push("");
|
|
239
|
-
|
|
240
|
-
for (const ach of ACHIEVEMENTS) {
|
|
241
|
-
if (ach.secret && !unlockedIds.has(ach.id)) continue;
|
|
242
|
-
const done = unlockedIds.has(ach.id);
|
|
243
|
-
const status = done ? "✅" : "☐";
|
|
244
|
-
parts.push(`${ach.icon}${status} **${ach.name}** — ${ach.description}`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (earned > 0 && earned === ACHIEVEMENTS.length) {
|
|
248
|
-
parts.push("");
|
|
249
|
-
parts.push("✨ **ALL ACHIEVEMENTS UNLOCKED!** ✨");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return parts.join("\n");
|
|
253
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
2
|
-
import { getClaudeJsonPath } from "./paths.ts";
|
|
3
|
-
|
|
4
|
-
export function resolveUserId(): string {
|
|
5
|
-
try {
|
|
6
|
-
const claudeJson = JSON.parse(
|
|
7
|
-
readFileSync(getClaudeJsonPath(), "utf8"),
|
|
8
|
-
);
|
|
9
|
-
return claudeJson.oauthAccount?.accountUuid ?? claudeJson.userID ?? "anon";
|
|
10
|
-
} catch {
|
|
11
|
-
return "anon";
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { join, resolve } from "path";
|
|
3
|
-
|
|
4
|
-
export function toUnixPath(path: string): string {
|
|
5
|
-
return path.replace(/\\/g, "/");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function claudeConfigDirEnv(): string | null {
|
|
9
|
-
const value = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
10
|
-
return value ? resolve(value) : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getClaudeConfigDir(): string {
|
|
14
|
-
return claudeConfigDirEnv() ?? join(homedir(), ".claude");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function getClaudeJsonPath(): string {
|
|
18
|
-
const configDir = claudeConfigDirEnv();
|
|
19
|
-
return configDir ? join(configDir, ".claude.json") : join(homedir(), ".claude.json");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getClaudeSettingsPath(): string {
|
|
23
|
-
return join(getClaudeConfigDir(), "settings.json");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function getBuddySkillDir(): string {
|
|
27
|
-
return join(getClaudeConfigDir(), "skills", "buddy");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getBuddyStateDir(): string {
|
|
31
|
-
const configDir = claudeConfigDirEnv();
|
|
32
|
-
return configDir ? join(configDir, "buddy-state") : join(homedir(), ".claude-buddy");
|
|
33
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { getBuddyStateDir, getClaudeSettingsPath, toUnixPath } from "./paths.ts";
|
|
4
|
-
|
|
5
|
-
export const CLAUDE_SETTINGS_PATH = getClaudeSettingsPath();
|
|
6
|
-
|
|
7
|
-
export function setBuddyStatusLine(
|
|
8
|
-
statusScript: string,
|
|
9
|
-
settingsPath: string = CLAUDE_SETTINGS_PATH,
|
|
10
|
-
): boolean {
|
|
11
|
-
try {
|
|
12
|
-
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
13
|
-
settings.statusLine = {
|
|
14
|
-
type: "command",
|
|
15
|
-
command: toUnixPath(statusScript),
|
|
16
|
-
padding: 1,
|
|
17
|
-
refreshInterval: 1,
|
|
18
|
-
};
|
|
19
|
-
const tmp = settingsPath + ".tmp";
|
|
20
|
-
writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
|
|
21
|
-
renameSync(tmp, settingsPath);
|
|
22
|
-
return true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function unsetBuddyStatusLine(
|
|
29
|
-
settingsPath: string = CLAUDE_SETTINGS_PATH,
|
|
30
|
-
): boolean {
|
|
31
|
-
try {
|
|
32
|
-
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
33
|
-
if (!settings.statusLine?.command?.includes("buddy-status.sh")) return false;
|
|
34
|
-
delete settings.statusLine;
|
|
35
|
-
const tmp = settingsPath + ".tmp";
|
|
36
|
-
writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
|
|
37
|
-
renameSync(tmp, settingsPath);
|
|
38
|
-
return true;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface CleanupResult {
|
|
45
|
-
statusLineRemoved: boolean;
|
|
46
|
-
foreignStatusLineKept: boolean;
|
|
47
|
-
transientFilesRemoved: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const TRANSIENT_PREFIXES = [
|
|
51
|
-
"popup-stop.",
|
|
52
|
-
"popup-resize.",
|
|
53
|
-
"popup-env.",
|
|
54
|
-
"popup-scroll.",
|
|
55
|
-
"popup-reopen-pid.",
|
|
56
|
-
"reaction.",
|
|
57
|
-
".last_reaction.",
|
|
58
|
-
".last_comment.",
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
export function cleanupPluginState(
|
|
62
|
-
settingsPath: string = CLAUDE_SETTINGS_PATH,
|
|
63
|
-
stateDir: string = getBuddyStateDir(),
|
|
64
|
-
): CleanupResult {
|
|
65
|
-
const statusLineRemoved = unsetBuddyStatusLine(settingsPath);
|
|
66
|
-
|
|
67
|
-
let foreignStatusLineKept = false;
|
|
68
|
-
try {
|
|
69
|
-
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
70
|
-
const cmd = settings.statusLine?.command;
|
|
71
|
-
if (cmd && !cmd.includes("buddy-status.sh")) foreignStatusLineKept = true;
|
|
72
|
-
} catch {
|
|
73
|
-
// ignore missing settings
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let transientFilesRemoved = 0;
|
|
77
|
-
try {
|
|
78
|
-
if (existsSync(stateDir)) {
|
|
79
|
-
for (const file of readdirSync(stateDir)) {
|
|
80
|
-
if (TRANSIENT_PREFIXES.some((prefix) => file.startsWith(prefix))) {
|
|
81
|
-
rmSync(join(stateDir, file), { force: true });
|
|
82
|
-
transientFilesRemoved++;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
// ignore unreadable state dir
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { statusLineRemoved, foreignStatusLineKept, transientFilesRemoved };
|
|
91
|
-
}
|