@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.
Files changed (92) hide show
  1. package/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
  2. package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
  3. package/LICENSE +1 -1
  4. package/README.md +88 -37
  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 +111 -50
  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 +2 -2
  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 +19 -1
  46. package/hooks/file-type-react.sh +401 -0
  47. package/hooks/hooks.json +52 -0
  48. package/hooks/mood-react.sh +208 -0
  49. package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
  50. package/hooks/react.sh +1368 -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 +17 -1
  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 +540 -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 +397 -0
  74. package/statusline/combined-status.sh +134 -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/rendering/art.ts +0 -264
  80. package/adapters/claude/server/instructions.ts +0 -24
  81. package/adapters/claude/server/path.ts +0 -12
  82. package/adapters/claude/server/resources.ts +0 -38
  83. package/adapters/claude/statusline/buddy-status.sh +0 -408
  84. package/adapters/claude/storage/achievements.ts +0 -253
  85. package/adapters/claude/storage/identity.ts +0 -13
  86. package/adapters/claude/storage/paths.ts +0 -33
  87. package/adapters/claude/storage/settings.ts +0 -91
  88. package/adapters/claude/storage/state.ts +0 -365
  89. package/adapters/pi/README.md +0 -83
  90. package/adapters/pi/tools.ts +0 -6
  91. package/server/engine.ts +0 -1
  92. package/server/reactions.ts +0 -1
@@ -10,25 +10,30 @@ import type {
10
10
  import { isBashToolResult } from "@mariozechner/pi-coding-agent";
11
11
  import type { AgentMessage } from "@mariozechner/pi-agent-core";
12
12
  import { complete, type AssistantMessage, type TextContent, type UserMessage } from "@mariozechner/pi-ai";
13
- import { BuddyCommandService } from "../../core/command-service.ts";
13
+ import { BuddyCommandService, type ReactionResult } from "../../core/command-service.ts";
14
14
  import type { Achievement } from "../../core/achievements.ts";
15
15
  import type { BuddyTurnCommentModelConfig, Companion } from "../../core/model.ts";
16
- import { getNameReaction, getSuccessReaction } from "../../core/reactions.ts";
16
+ import { getNameReaction, getSuccessReaction, isNameMentioned } from "../../core/reactions.ts";
17
17
  import { PiBuddyStorage } from "./storage.ts";
18
18
  import { buildBuddyReactionPrompt, buildBuddyReactionSystemPrompt, normalizeBuddyComment, stripBuddyComments } from "./prompt.ts";
19
19
  import { PiBuddyUI } from "./ui.ts";
20
- import { PiBuddyLogger } from "./logger.ts";
21
20
 
21
+ export interface PiBuddyLog {
22
+ info(event: string, data?: Record<string, unknown>): void;
23
+ warn(event: string, data?: Record<string, unknown>): void;
24
+ error(event: string, data?: Record<string, unknown>): void;
25
+ debug(event: string, data?: Record<string, unknown>): void;
26
+ }
22
27
  interface RegisterBuddyEventsDeps {
23
28
  service: BuddyCommandService;
24
29
  storage: PiBuddyStorage;
25
30
  ui: PiBuddyUI;
26
- logger: PiBuddyLogger;
31
+ logger: PiBuddyLog;
27
32
  }
28
33
 
29
34
  export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsDeps): void {
30
35
  pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
31
- const result = deps.service.ensureCompanion();
36
+ const result = deps.service.startSession();
32
37
  deps.logger.info("session_start", {
33
38
  companion: result.companion.name,
34
39
  species: result.companion.bones.species,
@@ -37,8 +42,8 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
37
42
  deps.ui.refresh(ctx, result.companion, deps.storage.loadLatest(), result.achievements);
38
43
  if (result.created) {
39
44
  ctx.ui.notify(`A new buddy hatched: ${result.companion.name}`, "info");
40
- deps.ui.notifyAchievements(ctx, result.achievements);
41
45
  }
46
+ deps.ui.notifyAchievements(ctx, result.achievements);
42
47
  });
43
48
 
44
49
  pi.on("input", async (event: InputEvent, ctx: ExtensionContext): Promise<InputEventResult> => {
@@ -51,15 +56,14 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
51
56
  }
52
57
 
53
58
  const companion = deps.service.ensureCompanion().companion;
54
- const pattern = new RegExp(`\\b${escapeRegExp(companion.name)}\\b`, "i");
55
- if (!pattern.test(event.text)) {
59
+ if (!isNameMentioned(event.text, companion.name)) {
56
60
  return { action: "continue" };
57
61
  }
58
62
 
59
63
  deps.logger.info("name_mention_detected", {
60
64
  companion: companion.name,
61
65
  species: companion.bones.species,
62
- textPreview: event.text.slice(0, 160),
66
+ textLength: event.text.length,
63
67
  });
64
68
 
65
69
  const reaction = getNameReaction(companion.bones.species);
@@ -74,44 +78,68 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
74
78
  deps.logger.debug("tool_result_skipped", { reason: "muted" });
75
79
  return;
76
80
  }
77
- if (!shouldEmitPassiveReaction(deps.storage)) {
78
- deps.logger.debug("tool_result_skipped", { reason: "cooldown" });
79
- return;
80
- }
81
81
 
82
82
  const text = extractToolText(event);
83
- let result:
84
- | ReturnType<BuddyCommandService["recordToolError"]>
85
- | ReturnType<BuddyCommandService["recordTestFailure"]>
86
- | ReturnType<BuddyCommandService["recordLargeDiff"]>
87
- | ReturnType<BuddyCommandService["recordComment"]>
88
- | undefined;
89
-
83
+ const canEmit = shouldEmitPassiveReaction(deps.storage);
84
+ let result: ReactionResult | undefined;
90
85
  const companion = deps.service.ensureCompanion().companion;
91
- if (event.isError) {
92
- result = deps.service.recordToolError(undefined, firstLineNumber(text));
93
- } else if (looksLikeTestFailure(text)) {
94
- result = deps.service.recordTestFailure(undefined, extractFailureCount(text));
95
- } else {
96
- const diffLines = extractLargeDiffLines(text);
97
- if (diffLines >= 80) {
98
- result = deps.service.recordLargeDiff(diffLines);
99
- } else if (looksLikeSuccess(text)) {
100
- result = deps.service.recordComment(getSuccessReaction(companion.bones.species), "turn");
86
+ if (isBashToolResult(event)) {
87
+ if (looksLikeTestFailure(text)) {
88
+ const count = extractFailureCount(text);
89
+ if (canEmit) {
90
+ result = deps.service.recordTestFailure(undefined, count);
91
+ } else {
92
+ const achievements = deps.service.trackTestFailure(undefined, count);
93
+ if (achievements.length > 0) { deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements); }
94
+ }
95
+ } else if (event.isError) {
96
+ const line = firstLineNumber(text);
97
+ if (canEmit) {
98
+ result = deps.service.recordToolError(undefined, line);
99
+ } else {
100
+ const achievements = deps.service.trackToolError(undefined, line);
101
+ if (achievements.length > 0) { deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements); }
102
+ }
103
+ } else {
104
+ const diffLines = extractLargeDiffLines(text);
105
+ if (diffLines >= 80) {
106
+ if (canEmit) {
107
+ result = deps.service.recordLargeDiff(diffLines);
108
+ } else {
109
+ const achievements = deps.service.trackLargeDiff(diffLines);
110
+ if (achievements.length > 0) { deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements); }
111
+ }
112
+ } else if (canEmit && looksLikeSuccess(text)) {
113
+ result = deps.service.recordComment(getSuccessReaction(companion.bones.species), "turn");
114
+ }
101
115
  }
116
+ } else if (event.isError) {
117
+ const line = firstLineNumber(text);
118
+ if (canEmit) {
119
+ result = deps.service.recordToolError(undefined, line);
120
+ } else {
121
+ const achievements = deps.service.trackToolError(undefined, line);
122
+ if (achievements.length > 0) { deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements); }
123
+ }
124
+ }
125
+
126
+ if (!canEmit) {
127
+ deps.logger.debug("tool_result_skipped", { reason: "cooldown" });
128
+ return;
102
129
  }
103
130
 
104
131
  if (!result) {
105
- deps.logger.debug("tool_result_ignored", {
106
- isError: event.isError,
107
- textPreview: text.slice(0, 200),
108
- });
132
+ const ignoredData: Record<string, unknown> = { isError: event.isError, textLength: text.length };
133
+ if (diagnosticPreviewsEnabled()) {
134
+ ignoredData.textPreview = text.slice(0, 200);
135
+ }
136
+ deps.logger.debug("tool_result_ignored", ignoredData);
109
137
  return;
110
138
  }
111
139
  deps.logger.info("tool_result_reaction", {
112
140
  reason: result.state.reason,
113
- reaction: result.state.reaction,
114
- textPreview: text.slice(0, 200),
141
+ reactionLength: result.state.reaction.length,
142
+ textLength: text.length,
115
143
  });
116
144
  deps.ui.refresh(ctx, result.companion, result.state, result.achievements);
117
145
  deps.ui.notifyAchievements(ctx, result.achievements);
@@ -125,18 +153,24 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
125
153
  return;
126
154
  }
127
155
 
128
- const generated = await generateTurnComment(ctx, progress.companion, event, deps.logger);
156
+ const generated = await generateTurnComment(
157
+ ctx,
158
+ progress.companion,
159
+ event,
160
+ deps.logger,
161
+ complete,
162
+ deps.storage.loadPiConfig().turnCommentModel,
163
+ );
129
164
  if (!generated.comment) {
130
- deps.logger.warn("turn_end_comment_missing", {
131
- assistantPreview: isAssistantMessage(event.message) ? getAssistantText(event.message).slice(0, 200) : "",
132
- });
165
+ const assistantLength = isAssistantMessage(event.message) ? getAssistantText(event.message).length : 0;
166
+ deps.logger.warn("turn_end_comment_missing", { assistantLength });
133
167
  deps.ui.refresh(ctx, progress.companion, deps.storage.loadLatest(), progress.achievements);
134
168
  return;
135
169
  }
136
170
 
137
171
  deps.logger.info("turn_end_reaction", {
138
172
  source: generated.source,
139
- reaction: generated.comment,
173
+ reactionLength: generated.comment.length,
140
174
  });
141
175
 
142
176
  const reaction = deps.service.recordComment(generated.comment, "turn");
@@ -144,6 +178,13 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
144
178
  deps.ui.refresh(ctx, reaction.companion, reaction.state, achievements);
145
179
  deps.ui.notifyAchievements(ctx, achievements);
146
180
  });
181
+ pi.on("session_shutdown", () => {
182
+ deps.ui.cancelTimers();
183
+ });
184
+ }
185
+ function diagnosticPreviewsEnabled(): boolean {
186
+ const value = process.env.BUDDY_DIAGNOSTIC_PREVIEWS;
187
+ return value === "1" || value === "true";
147
188
  }
148
189
 
149
190
  function shouldEmitPassiveReaction(storage: PiBuddyStorage): boolean {
@@ -167,8 +208,33 @@ function extractToolText(event: ToolResultEvent): string {
167
208
  return "";
168
209
  }
169
210
 
170
- function looksLikeTestFailure(text: string): boolean {
171
- return /(FAIL|failed|failing|test(s)? failed|not ok)/i.test(text);
211
+ const EXPLICIT_FAILURE_PATTERN = /\b(?:[1-9][0-9]*\s+)?(?:tests?|specs?)\s+(?:failed|failing|did\s+not\s+pass)\b/i;
212
+
213
+ function hasTestContext(line: string): boolean {
214
+ return /\b(?:bun\s+test|npm\s+test|yarn\s+test|pnpm\s+test|vitest|jest|mocha|ava|tap|pytest|cargo\s+test|go\s+test|dotnet\s+test|phpunit|rspec|test\s+suite|test\s+runner|tests?\s+(?:passed|failed|ok)|TAP\s*version|1\.\.\d+|#\s+(?:tests|pass|fail|todo|skip|ok))\b/i.test(line);
215
+ }
216
+
217
+ function isGenericFailureMarker(line: string): boolean {
218
+ return /^\s*(?:FAIL|not\s+ok|[✗✘×])\s*$/i.test(line) ||
219
+ /\b[✗✘×]\b/.test(line) ||
220
+ /(?:^|[\r\n])\s*FAIL(?:\s|$)/i.test(line) ||
221
+ /\bnot\s+ok\b/i.test(line);
222
+ }
223
+
224
+ export function looksLikeTestFailure(text: string): boolean {
225
+ if (EXPLICIT_FAILURE_PATTERN.test(text)) return true;
226
+
227
+ const lines = text.split(/\r?\n/);
228
+ const contextWindow: string[] = [];
229
+ for (const rawLine of lines) {
230
+ const line = rawLine.trim();
231
+ contextWindow.push(line);
232
+ if (contextWindow.length > 3) contextWindow.shift();
233
+ if (isGenericFailureMarker(line) && contextWindow.some((contextLine) => hasTestContext(contextLine))) {
234
+ return true;
235
+ }
236
+ }
237
+ return false;
172
238
  }
173
239
 
174
240
  function looksLikeSuccess(text: string): boolean {
@@ -198,10 +264,6 @@ function firstLineNumber(text: string): number | undefined {
198
264
  return match ? Number(match[1]) : undefined;
199
265
  }
200
266
 
201
- function escapeRegExp(value: string): string {
202
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
203
- }
204
-
205
267
  function mergeAchievements(first: Achievement[], second: Achievement[]): Achievement[] {
206
268
  const merged = new Map<string, Achievement>();
207
269
  for (const achievement of [...first, ...second]) {
@@ -221,11 +283,18 @@ function getAssistantText(message: AssistantMessage): string {
221
283
  .join("\n");
222
284
  }
223
285
 
224
- async function generateTurnComment(
286
+ type TurnCommentCompleter = (
287
+ model: Parameters<typeof complete>[0],
288
+ context: Parameters<typeof complete>[1],
289
+ options: Parameters<typeof complete>[2],
290
+ ) => Promise<AssistantMessage>;
291
+ export async function generateTurnComment(
225
292
  ctx: ExtensionContext,
226
293
  companion: Companion,
227
294
  event: TurnEndEvent,
228
- logger: PiBuddyLogger,
295
+ logger: PiBuddyLog,
296
+ completeTurnComment: TurnCommentCompleter = complete,
297
+ modelOverride?: BuddyTurnCommentModelConfig,
229
298
  ): Promise<{ comment: string | null; source: "llm" | "fallback" | "none" }> {
230
299
  const assistantText = isAssistantMessage(event.message) ? getAssistantText(event.message) : "";
231
300
  if (!assistantText.trim()) {
@@ -233,7 +302,7 @@ async function generateTurnComment(
233
302
  return { comment: null, source: "none" };
234
303
  }
235
304
 
236
- const turnCommentModel = resolveTurnCommentModel(ctx, logger);
305
+ const turnCommentModel = resolveTurnCommentModel(ctx, logger, modelOverride);
237
306
  if (turnCommentModel) {
238
307
  const toolResultsText = getToolResultsText(event);
239
308
  const userText = getUserPromptText(ctx);
@@ -242,21 +311,26 @@ async function generateTurnComment(
242
311
  logger.info("turn_comment_llm_attempt", {
243
312
  modelProvider: turnCommentModel.provider,
244
313
  modelId: turnCommentModel.id,
245
- assistantPreview: assistantText.slice(0, 200),
246
- toolPreview: toolResultsText.slice(0, 200),
247
314
  assistantLength: assistantText.length,
248
315
  toolLength: toolResultsText.length,
316
+ userTextLength: userText.length,
249
317
  promptLength: promptText.length,
250
318
  systemPromptLength: systemPrompt.length,
251
319
  toolResultCount: event.toolResults.length,
252
320
  });
253
- logger.debug("turn_comment_llm_prompt", {
254
- systemPromptPreview: systemPrompt.slice(0, 800),
255
- promptPreview: promptText.slice(0, 1200),
256
- userText,
257
- assistantText,
258
- toolResultsText,
259
- });
321
+ const promptDebugData: Record<string, unknown> = {
322
+ assistantLength: assistantText.length,
323
+ toolLength: toolResultsText.length,
324
+ userTextLength: userText.length,
325
+ };
326
+ if (diagnosticPreviewsEnabled()) {
327
+ promptDebugData.systemPromptPreview = systemPrompt.slice(0, 800);
328
+ promptDebugData.promptPreview = promptText.slice(0, 1200);
329
+ promptDebugData.userTextPreview = userText.slice(0, 200);
330
+ promptDebugData.assistantPreview = assistantText.slice(0, 200);
331
+ promptDebugData.toolPreview = toolResultsText.slice(0, 200);
332
+ }
333
+ logger.debug("turn_comment_llm_prompt", promptDebugData);
260
334
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(turnCommentModel);
261
335
  logger.debug("turn_comment_auth", {
262
336
  ok: auth.ok,
@@ -271,7 +345,7 @@ async function generateTurnComment(
271
345
  };
272
346
 
273
347
  try {
274
- const response = await complete(
348
+ const response = await completeTurnComment(
275
349
  turnCommentModel,
276
350
  {
277
351
  systemPrompt,
@@ -294,14 +368,11 @@ async function generateTurnComment(
294
368
  errorMessage: "errorMessage" in response ? (response as { errorMessage?: string }).errorMessage : undefined,
295
369
  contentTypes: response.content.map((block) => block.type),
296
370
  contentCount: response.content.length,
297
- rawPreview: text.slice(0, 200),
298
371
  rawLength: text.length,
299
- normalized,
372
+ normalizedLength: normalized.length,
300
373
  });
301
374
  if (normalized) return { comment: normalized, source: "llm" };
302
- logger.warn("turn_comment_llm_empty", {
303
- rawPreview: text.slice(0, 200),
304
- });
375
+ logger.warn("turn_comment_llm_empty", { rawLength: text.length });
305
376
  }
306
377
  } catch (error) {
307
378
  logger.error("turn_comment_llm_error", {
@@ -321,19 +392,23 @@ async function generateTurnComment(
321
392
 
322
393
  const fallback = deriveTurnComment(companion, event.message);
323
394
  logger.warn("turn_comment_fallback", {
324
- fallback,
325
- assistantPreview: assistantText.slice(0, 200),
395
+ fallbackLength: fallback?.length ?? 0,
396
+ assistantLength: assistantText.length,
326
397
  });
327
398
  return { comment: fallback, source: fallback ? "fallback" : "none" };
328
399
  }
329
400
 
401
+ export interface PiTurnCommentModelContext {
402
+ model: ExtensionContext["model"];
403
+ modelRegistry: Pick<ExtensionContext["modelRegistry"], "find">;
404
+ }
405
+
330
406
  export function resolveTurnCommentModel(
331
- ctx: ExtensionContext,
332
- logger?: PiBuddyLogger,
407
+ ctx: PiTurnCommentModelContext,
408
+ logger?: PiBuddyLog,
333
409
  override?: BuddyTurnCommentModelConfig,
334
- ): NonNullable<ExtensionContext["model"]> | null {
335
- const configuredOverride = override ?? new PiBuddyStorage().loadPiConfig().turnCommentModel;
336
- const activeOverride = configuredOverride;
410
+ ): NonNullable<PiTurnCommentModelContext["model"]> | null {
411
+ const activeOverride = override;
337
412
  if (activeOverride?.provider && activeOverride.model) {
338
413
  const model = ctx.modelRegistry.find(activeOverride.provider, activeOverride.model);
339
414
  if (model) {
@@ -5,11 +5,17 @@ import { PiBuddyLogger } from "./logger.ts";
5
5
  import { registerBuddyCommands } from "./commands.ts";
6
6
  import { registerBuddyEvents } from "./events.ts";
7
7
  import { PiBuddyStorage } from "./storage.ts";
8
- import { registerBuddyTools } from "./tools.ts";
9
8
  import { PiBuddyUI } from "./ui.ts";
10
9
 
11
- export default function registerPiBuddyExtension(pi: ExtensionAPI): void {
12
- const storage = new PiBuddyStorage();
10
+ export interface PiBuddyExtensionOptions {
11
+ stateDir?: string;
12
+ }
13
+
14
+ export default function registerPiBuddyExtension(
15
+ pi: ExtensionAPI,
16
+ options: PiBuddyExtensionOptions = {},
17
+ ): void {
18
+ const storage = new PiBuddyStorage(options.stateDir);
13
19
  const identity = new PiIdentityProvider(storage);
14
20
  const logger = new PiBuddyLogger(storage);
15
21
  const service = new BuddyCommandService({
@@ -23,5 +29,4 @@ export default function registerPiBuddyExtension(pi: ExtensionAPI): void {
23
29
 
24
30
  registerBuddyCommands(pi, { service, storage, ui });
25
31
  registerBuddyEvents(pi, { service, storage, ui, logger });
26
- registerBuddyTools(pi);
27
32
  }