@oh-my-pi/pi-coding-agent 6.1.0 → 6.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 (93) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/docs/sdk.md +1 -1
  3. package/package.json +5 -5
  4. package/scripts/generate-template.ts +6 -6
  5. package/src/cli/args.ts +3 -0
  6. package/src/core/agent-session.ts +39 -0
  7. package/src/core/bash-executor.ts +3 -3
  8. package/src/core/cursor/exec-bridge.ts +95 -88
  9. package/src/core/custom-commands/bundled/review/index.ts +142 -145
  10. package/src/core/custom-commands/bundled/wt/index.ts +68 -66
  11. package/src/core/custom-commands/loader.ts +4 -6
  12. package/src/core/custom-tools/index.ts +2 -2
  13. package/src/core/custom-tools/loader.ts +66 -61
  14. package/src/core/custom-tools/types.ts +4 -4
  15. package/src/core/custom-tools/wrapper.ts +61 -25
  16. package/src/core/event-bus.ts +19 -47
  17. package/src/core/extensions/index.ts +8 -4
  18. package/src/core/extensions/loader.ts +160 -120
  19. package/src/core/extensions/types.ts +4 -4
  20. package/src/core/extensions/wrapper.ts +149 -100
  21. package/src/core/hooks/index.ts +1 -1
  22. package/src/core/hooks/tool-wrapper.ts +96 -70
  23. package/src/core/hooks/types.ts +1 -2
  24. package/src/core/index.ts +1 -0
  25. package/src/core/mcp/index.ts +6 -2
  26. package/src/core/mcp/json-rpc.ts +88 -0
  27. package/src/core/mcp/loader.ts +22 -4
  28. package/src/core/mcp/manager.ts +202 -48
  29. package/src/core/mcp/tool-bridge.ts +143 -55
  30. package/src/core/mcp/tool-cache.ts +122 -0
  31. package/src/core/python-executor.ts +3 -9
  32. package/src/core/sdk.ts +33 -32
  33. package/src/core/session-manager.ts +30 -0
  34. package/src/core/settings-manager.ts +34 -1
  35. package/src/core/ssh/ssh-executor.ts +6 -84
  36. package/src/core/streaming-output.ts +107 -53
  37. package/src/core/tools/ask.ts +92 -93
  38. package/src/core/tools/bash.ts +103 -94
  39. package/src/core/tools/calculator.ts +41 -26
  40. package/src/core/tools/complete.ts +76 -66
  41. package/src/core/tools/context.ts +25 -25
  42. package/src/core/tools/exa/index.ts +1 -1
  43. package/src/core/tools/exa/mcp-client.ts +56 -101
  44. package/src/core/tools/find.ts +250 -253
  45. package/src/core/tools/git.ts +39 -33
  46. package/src/core/tools/grep.ts +440 -427
  47. package/src/core/tools/index.ts +62 -61
  48. package/src/core/tools/ls.ts +119 -114
  49. package/src/core/tools/lsp/clients/biome-client.ts +5 -7
  50. package/src/core/tools/lsp/clients/index.ts +4 -4
  51. package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
  52. package/src/core/tools/lsp/config.ts +2 -2
  53. package/src/core/tools/lsp/index.ts +824 -639
  54. package/src/core/tools/notebook.ts +121 -119
  55. package/src/core/tools/output.ts +163 -147
  56. package/src/core/tools/patch/applicator.ts +1100 -0
  57. package/src/core/tools/patch/diff.ts +362 -0
  58. package/src/core/tools/patch/fuzzy.ts +647 -0
  59. package/src/core/tools/patch/index.ts +430 -0
  60. package/src/core/tools/patch/normalize.ts +220 -0
  61. package/src/core/tools/patch/normative.ts +49 -0
  62. package/src/core/tools/patch/parser.ts +528 -0
  63. package/src/core/tools/patch/shared.ts +228 -0
  64. package/src/core/tools/patch/types.ts +244 -0
  65. package/src/core/tools/python.ts +139 -136
  66. package/src/core/tools/read.ts +237 -216
  67. package/src/core/tools/render-utils.ts +196 -77
  68. package/src/core/tools/renderers.ts +1 -1
  69. package/src/core/tools/ssh.ts +99 -80
  70. package/src/core/tools/task/executor.ts +11 -7
  71. package/src/core/tools/task/index.ts +352 -343
  72. package/src/core/tools/task/worker.ts +13 -23
  73. package/src/core/tools/todo-write.ts +74 -59
  74. package/src/core/tools/web-fetch.ts +54 -47
  75. package/src/core/tools/web-search/index.ts +27 -16
  76. package/src/core/tools/write.ts +89 -41
  77. package/src/core/ttsr.ts +106 -152
  78. package/src/core/voice.ts +49 -39
  79. package/src/index.ts +16 -12
  80. package/src/lib/worktree/index.ts +1 -9
  81. package/src/modes/interactive/components/diff.ts +15 -8
  82. package/src/modes/interactive/components/settings-defs.ts +24 -0
  83. package/src/modes/interactive/components/tool-execution.ts +34 -6
  84. package/src/modes/interactive/controllers/event-controller.ts +6 -19
  85. package/src/modes/interactive/controllers/input-controller.ts +1 -1
  86. package/src/modes/interactive/utils/ui-helpers.ts +5 -1
  87. package/src/modes/rpc/rpc-mode.ts +99 -81
  88. package/src/prompts/tools/patch.md +76 -0
  89. package/src/prompts/tools/read.md +1 -1
  90. package/src/prompts/tools/{edit.md → replace.md} +1 -0
  91. package/src/utils/shell.ts +0 -40
  92. package/src/core/tools/edit-diff.ts +0 -574
  93. package/src/core/tools/edit.ts +0 -326
@@ -224,154 +224,152 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
224
224
  });
225
225
  }
226
226
 
227
- export function createReviewCommand(api: CustomCommandAPI): CustomCommand {
228
- return {
229
- name: "review",
230
- description: "Launch interactive code review",
231
-
232
- async execute(_args: string[], ctx: HookCommandContext): Promise<string | undefined> {
233
- if (!ctx.hasUI) {
234
- return "Use the Task tool to run the 'reviewer' agent to review recent code changes.";
227
+ export class ReviewCommand implements CustomCommand {
228
+ name = "review";
229
+ description = "Launch interactive code review";
230
+
231
+ constructor(private api: CustomCommandAPI) {}
232
+
233
+ async execute(_args: string[], ctx: HookCommandContext): Promise<string | undefined> {
234
+ if (!ctx.hasUI) {
235
+ return "Use the Task tool to run the 'reviewer' agent to review recent code changes.";
236
+ }
237
+
238
+ const mode = await ctx.ui.select("Review Mode", [
239
+ "1. Review against a base branch (PR Style)",
240
+ "2. Review uncommitted changes",
241
+ "3. Review a specific commit",
242
+ "4. Custom review instructions",
243
+ ]);
244
+
245
+ if (!mode) return undefined;
246
+
247
+ const modeNum = parseInt(mode[0], 10);
248
+
249
+ switch (modeNum) {
250
+ case 1: {
251
+ // PR-style review against base branch
252
+ const branches = await getGitBranches(this.api);
253
+ if (branches.length === 0) {
254
+ ctx.ui.notify("No git branches found", "error");
255
+ return undefined;
256
+ }
257
+
258
+ const baseBranch = await ctx.ui.select("Select base branch to compare against", branches);
259
+ if (!baseBranch) return undefined;
260
+
261
+ const currentBranch = await getCurrentBranch(this.api);
262
+ const diffResult = await this.api.exec("git", ["diff", `${baseBranch}...${currentBranch}`], {
263
+ timeout: 30000,
264
+ });
265
+ if (diffResult.code !== 0) {
266
+ ctx.ui.notify(`Failed to get diff: ${diffResult.stderr}`, "error");
267
+ return undefined;
268
+ }
269
+
270
+ if (!diffResult.stdout.trim()) {
271
+ ctx.ui.notify(`No changes between ${baseBranch} and ${currentBranch}`, "warning");
272
+ return undefined;
273
+ }
274
+
275
+ const stats = parseDiff(diffResult.stdout);
276
+ if (stats.files.length === 0) {
277
+ ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
278
+ return undefined;
279
+ }
280
+
281
+ return buildReviewPrompt(
282
+ `Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
283
+ stats,
284
+ diffResult.stdout,
285
+ );
235
286
  }
236
287
 
237
- const mode = await ctx.ui.select("Review Mode", [
238
- "1. Review against a base branch (PR Style)",
239
- "2. Review uncommitted changes",
240
- "3. Review a specific commit",
241
- "4. Custom review instructions",
242
- ]);
243
-
244
- if (!mode) return undefined;
245
-
246
- const modeNum = parseInt(mode[0], 10);
247
-
248
- switch (modeNum) {
249
- case 1: {
250
- // PR-style review against base branch
251
- const branches = await getGitBranches(api);
252
- if (branches.length === 0) {
253
- ctx.ui.notify("No git branches found", "error");
254
- return undefined;
255
- }
256
-
257
- const baseBranch = await ctx.ui.select("Select base branch to compare against", branches);
258
- if (!baseBranch) return undefined;
259
-
260
- const currentBranch = await getCurrentBranch(api);
261
- const diffResult = await api.exec("git", ["diff", `${baseBranch}...${currentBranch}`], {
262
- timeout: 30000,
263
- });
264
- if (diffResult.code !== 0) {
265
- ctx.ui.notify(`Failed to get diff: ${diffResult.stderr}`, "error");
266
- return undefined;
267
- }
268
-
269
- if (!diffResult.stdout.trim()) {
270
- ctx.ui.notify(`No changes between ${baseBranch} and ${currentBranch}`, "warning");
271
- return undefined;
272
- }
288
+ case 2: {
289
+ // Uncommitted changes - combine staged and unstaged
290
+ const status = await getGitStatus(this.api);
291
+ if (!status.trim()) {
292
+ ctx.ui.notify("No uncommitted changes found", "warning");
293
+ return undefined;
294
+ }
295
+
296
+ const [unstagedResult, stagedResult] = await Promise.all([
297
+ this.api.exec("git", ["diff"], { timeout: 30000 }),
298
+ this.api.exec("git", ["diff", "--cached"], { timeout: 30000 }),
299
+ ]);
273
300
 
274
- const stats = parseDiff(diffResult.stdout);
275
- if (stats.files.length === 0) {
276
- ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
277
- return undefined;
278
- }
301
+ const combinedDiff = [unstagedResult.stdout, stagedResult.stdout].filter(Boolean).join("\n");
279
302
 
280
- return buildReviewPrompt(
281
- `Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
282
- stats,
283
- diffResult.stdout,
284
- );
303
+ if (!combinedDiff.trim()) {
304
+ ctx.ui.notify("No diff content found", "warning");
305
+ return undefined;
285
306
  }
286
307
 
287
- case 2: {
288
- // Uncommitted changes - combine staged and unstaged
289
- const status = await getGitStatus(api);
290
- if (!status.trim()) {
291
- ctx.ui.notify("No uncommitted changes found", "warning");
292
- return undefined;
293
- }
294
-
295
- const [unstagedResult, stagedResult] = await Promise.all([
296
- api.exec("git", ["diff"], { timeout: 30000 }),
297
- api.exec("git", ["diff", "--cached"], { timeout: 30000 }),
298
- ]);
299
-
300
- const combinedDiff = [unstagedResult.stdout, stagedResult.stdout].filter(Boolean).join("\n");
301
-
302
- if (!combinedDiff.trim()) {
303
- ctx.ui.notify("No diff content found", "warning");
304
- return undefined;
305
- }
306
-
307
- const stats = parseDiff(combinedDiff);
308
- if (stats.files.length === 0) {
309
- ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
310
- return undefined;
311
- }
312
-
313
- return buildReviewPrompt("Reviewing uncommitted changes (staged + unstaged)", stats, combinedDiff);
308
+ const stats = parseDiff(combinedDiff);
309
+ if (stats.files.length === 0) {
310
+ ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
311
+ return undefined;
314
312
  }
315
313
 
316
- case 3: {
317
- // Specific commit
318
- const commits = await getRecentCommits(api, 20);
319
- if (commits.length === 0) {
320
- ctx.ui.notify("No commits found", "error");
321
- return undefined;
322
- }
323
-
324
- const selected = await ctx.ui.select("Select commit to review", commits);
325
- if (!selected) return undefined;
326
-
327
- // Extract commit hash from selection (format: "abc1234 message")
328
- const hash = selected.split(" ")[0];
329
-
330
- // Get the commit diff (with timeout)
331
- const showResult = await api.exec("git", ["show", "--format=", hash], { timeout: 30000 });
332
- if (showResult.code !== 0) {
333
- ctx.ui.notify(`Failed to get commit: ${showResult.stderr}`, "error");
334
- return undefined;
335
- }
336
-
337
- if (!showResult.stdout.trim()) {
338
- ctx.ui.notify("Commit has no diff content", "warning");
339
- return undefined;
340
- }
341
-
342
- const stats = parseDiff(showResult.stdout);
343
- if (stats.files.length === 0) {
344
- ctx.ui.notify("No reviewable files in commit (all changes filtered out)", "warning");
345
- return undefined;
346
- }
347
-
348
- return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, showResult.stdout);
314
+ return buildReviewPrompt("Reviewing uncommitted changes (staged + unstaged)", stats, combinedDiff);
315
+ }
316
+
317
+ case 3: {
318
+ // Specific commit
319
+ const commits = await getRecentCommits(this.api, 20);
320
+ if (commits.length === 0) {
321
+ ctx.ui.notify("No commits found", "error");
322
+ return undefined;
323
+ }
324
+
325
+ const selected = await ctx.ui.select("Select commit to review", commits);
326
+ if (!selected) return undefined;
327
+
328
+ // Extract commit hash from selection (format: "abc1234 message")
329
+ const hash = selected.split(" ")[0];
330
+
331
+ // Get the commit diff (with timeout)
332
+ const showResult = await this.api.exec("git", ["show", "--format=", hash], { timeout: 30000 });
333
+ if (showResult.code !== 0) {
334
+ ctx.ui.notify(`Failed to get commit: ${showResult.stderr}`, "error");
335
+ return undefined;
336
+ }
337
+
338
+ if (!showResult.stdout.trim()) {
339
+ ctx.ui.notify("Commit has no diff content", "warning");
340
+ return undefined;
341
+ }
342
+
343
+ const stats = parseDiff(showResult.stdout);
344
+ if (stats.files.length === 0) {
345
+ ctx.ui.notify("No reviewable files in commit (all changes filtered out)", "warning");
346
+ return undefined;
347
+ }
348
+
349
+ return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, showResult.stdout);
350
+ }
351
+
352
+ case 4: {
353
+ // Custom instructions - still uses the old approach since user provides context
354
+ const instructions = await ctx.ui.editor("Enter custom review instructions", "Review the following:\n\n");
355
+ if (!instructions?.trim()) return undefined;
356
+
357
+ // For custom, we still try to get current diff for context
358
+ const diffResult = await this.api.exec("git", ["diff", "HEAD"], { timeout: 30000 });
359
+ const hasDiff = diffResult.code === 0 && diffResult.stdout.trim();
360
+
361
+ if (hasDiff) {
362
+ const stats = parseDiff(diffResult.stdout);
363
+ // Even if all files filtered, include the custom instructions
364
+ return `${buildReviewPrompt(
365
+ `Custom review: ${instructions.split("\n")[0].slice(0, 60)}...`,
366
+ stats,
367
+ diffResult.stdout,
368
+ )}\n\n### Additional Instructions\n\n${instructions}`;
349
369
  }
350
370
 
351
- case 4: {
352
- // Custom instructions - still uses the old approach since user provides context
353
- const instructions = await ctx.ui.editor(
354
- "Enter custom review instructions",
355
- "Review the following:\n\n",
356
- );
357
- if (!instructions?.trim()) return undefined;
358
-
359
- // For custom, we still try to get current diff for context
360
- const diffResult = await api.exec("git", ["diff", "HEAD"], { timeout: 30000 });
361
- const hasDiff = diffResult.code === 0 && diffResult.stdout.trim();
362
-
363
- if (hasDiff) {
364
- const stats = parseDiff(diffResult.stdout);
365
- // Even if all files filtered, include the custom instructions
366
- return `${buildReviewPrompt(
367
- `Custom review: ${instructions.split("\n")[0].slice(0, 60)}...`,
368
- stats,
369
- diffResult.stdout,
370
- )}\n\n### Additional Instructions\n\n${instructions}`;
371
- }
372
-
373
- // No diff available, just pass instructions
374
- return `## Code Review Request
371
+ // No diff available, just pass instructions
372
+ return `## Code Review Request
375
373
 
376
374
  ### Mode
377
375
  Custom review instructions
@@ -381,13 +379,12 @@ Custom review instructions
381
379
  ${instructions}
382
380
 
383
381
  Use the Task tool with \`agent: "reviewer"\` to execute this review.`;
384
- }
385
-
386
- default:
387
- return undefined;
388
382
  }
389
- },
390
- };
383
+
384
+ default:
385
+ return undefined;
386
+ }
387
+ }
391
388
  }
392
389
 
393
390
  async function getGitBranches(api: CustomCommandAPI): Promise<string[]> {
@@ -434,4 +431,4 @@ async function getRecentCommits(api: CustomCommandAPI, count: number): Promise<s
434
431
  }
435
432
  }
436
433
 
437
- export default createReviewCommand;
434
+ export default ReviewCommand;
@@ -358,76 +358,78 @@ async function handleParallel(args: ParallelTask[], ctx: HookCommandContext): Pr
358
358
  return [`Parallel execution complete (${args.length} agents)`, "", "Results:", ...mergeResults].join("\n");
359
359
  }
360
360
 
361
- export function createWorktreeCommand(_api: CustomCommandAPI): CustomCommand {
362
- return {
363
- name: "wt",
364
- description: "Git worktree management",
365
- async execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> {
366
- if (args.length === 0) return formatUsage();
367
-
368
- const subcommand = args[0];
369
- const rest = args.slice(1);
370
-
371
- try {
372
- switch (subcommand) {
373
- case "new": {
374
- const parsed = parseFlags(rest);
375
- const branch = parsed.positionals[0];
376
- if (!branch) return formatUsage();
377
- const base = getFlagValue(parsed.flags, "base");
378
- if (parsed.flags.get("base") === true) {
379
- return "Missing value for --base";
380
- }
381
- return await handleNew({ branch, base });
382
- }
383
- case "list":
384
- return await handleList(ctx);
385
- case "merge": {
386
- const parsed = parseFlags(rest);
387
- const source = parsed.positionals[0];
388
- const target = parsed.positionals[1];
389
- if (!source) return formatUsage();
390
- const strategyRaw = getFlagValue(parsed.flags, "strategy");
391
- if (parsed.flags.get("strategy") === true) {
392
- return "Missing value for --strategy";
393
- }
394
- const strategy = strategyRaw as CollapseStrategy | undefined;
395
- const keep = getFlagBoolean(parsed.flags, "keep");
396
- return await handleMerge({ source, target, strategy, keep });
397
- }
398
- case "rm": {
399
- const parsed = parseFlags(rest);
400
- const name = parsed.positionals[0];
401
- if (!name) return formatUsage();
402
- const force = getFlagBoolean(parsed.flags, "force");
403
- return await handleRm({ name, force });
361
+ export class WorktreeCommand implements CustomCommand {
362
+ name = "wt";
363
+ description = "Git worktree management";
364
+
365
+ // biome-ignore lint/complexity/noUselessConstructor: interface conformance - loader passes API to all commands
366
+ constructor(_api: CustomCommandAPI) {}
367
+
368
+ async execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> {
369
+ if (args.length === 0) return formatUsage();
370
+
371
+ const subcommand = args[0];
372
+ const rest = args.slice(1);
373
+
374
+ try {
375
+ switch (subcommand) {
376
+ case "new": {
377
+ const parsed = parseFlags(rest);
378
+ const branch = parsed.positionals[0];
379
+ if (!branch) return formatUsage();
380
+ const base = getFlagValue(parsed.flags, "base");
381
+ if (parsed.flags.get("base") === true) {
382
+ return "Missing value for --base";
404
383
  }
405
- case "status":
406
- return await handleStatus();
407
- case "spawn": {
408
- const parsed = parseFlags(rest);
409
- const task = parsed.positionals[0];
410
- if (!task) return formatUsage();
411
- const scope = getFlagValue(parsed.flags, "scope");
412
- if (parsed.flags.get("scope") === true) {
413
- return "Missing value for --scope";
414
- }
415
- const name = getFlagValue(parsed.flags, "name");
416
- return await handleSpawn({ task, scope, name }, ctx);
384
+ return await handleNew({ branch, base });
385
+ }
386
+ case "list":
387
+ return await handleList(ctx);
388
+ case "merge": {
389
+ const parsed = parseFlags(rest);
390
+ const source = parsed.positionals[0];
391
+ const target = parsed.positionals[1];
392
+ if (!source) return formatUsage();
393
+ const strategyRaw = getFlagValue(parsed.flags, "strategy");
394
+ if (parsed.flags.get("strategy") === true) {
395
+ return "Missing value for --strategy";
417
396
  }
418
- case "parallel": {
419
- const tasks = parseParallelTasks(rest);
420
- if (tasks.length === 0) return formatUsage();
421
- return await handleParallel(tasks, ctx);
397
+ const strategy = strategyRaw as CollapseStrategy | undefined;
398
+ const keep = getFlagBoolean(parsed.flags, "keep");
399
+ return await handleMerge({ source, target, strategy, keep });
400
+ }
401
+ case "rm": {
402
+ const parsed = parseFlags(rest);
403
+ const name = parsed.positionals[0];
404
+ if (!name) return formatUsage();
405
+ const force = getFlagBoolean(parsed.flags, "force");
406
+ return await handleRm({ name, force });
407
+ }
408
+ case "status":
409
+ return await handleStatus();
410
+ case "spawn": {
411
+ const parsed = parseFlags(rest);
412
+ const task = parsed.positionals[0];
413
+ if (!task) return formatUsage();
414
+ const scope = getFlagValue(parsed.flags, "scope");
415
+ if (parsed.flags.get("scope") === true) {
416
+ return "Missing value for --scope";
422
417
  }
423
- default:
424
- return formatUsage();
418
+ const name = getFlagValue(parsed.flags, "name");
419
+ return await handleSpawn({ task, scope, name }, ctx);
420
+ }
421
+ case "parallel": {
422
+ const tasks = parseParallelTasks(rest);
423
+ if (tasks.length === 0) return formatUsage();
424
+ return await handleParallel(tasks, ctx);
425
425
  }
426
- } catch (err) {
427
- return formatError(err);
426
+ default:
427
+ return formatUsage();
428
428
  }
429
- },
430
- };
429
+ } catch (err) {
430
+ return formatError(err);
431
+ }
432
+ }
431
433
  }
432
434
 
433
- export default createWorktreeCommand;
435
+ export default WorktreeCommand;
@@ -12,8 +12,8 @@ import { getAgentDir, getConfigDirs } from "../../config";
12
12
  import * as piCodingAgent from "../../index";
13
13
  import { execCommand } from "../exec";
14
14
  import { logger } from "../logger";
15
- import { createReviewCommand } from "./bundled/review";
16
- import { createWorktreeCommand } from "./bundled/wt";
15
+ import { ReviewCommand } from "./bundled/review";
16
+ import { WorktreeCommand } from "./bundled/wt";
17
17
  import type {
18
18
  CustomCommand,
19
19
  CustomCommandAPI,
@@ -146,19 +146,17 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
146
146
  const bundled: LoadedCustomCommand[] = [];
147
147
 
148
148
  // Add bundled commands here
149
- const reviewCommand = createReviewCommand(sharedApi);
150
149
  bundled.push({
151
150
  path: "bundled:review",
152
151
  resolvedPath: "bundled:review",
153
- command: reviewCommand,
152
+ command: new ReviewCommand(sharedApi),
154
153
  source: "bundled",
155
154
  });
156
155
 
157
- const worktreeCommand = createWorktreeCommand(sharedApi);
158
156
  bundled.push({
159
157
  path: "bundled:wt",
160
158
  resolvedPath: "bundled:wt",
161
- command: worktreeCommand,
159
+ command: new WorktreeCommand(sharedApi),
162
160
  source: "bundled",
163
161
  });
164
162
 
@@ -2,7 +2,7 @@
2
2
  * Custom tools module.
3
3
  */
4
4
 
5
- export { discoverAndLoadCustomTools, loadCustomTools } from "./loader";
5
+ export { CustomToolLoader, discoverAndLoadCustomTools, loadCustomTools } from "./loader";
6
6
  export type {
7
7
  AgentToolResult,
8
8
  AgentToolUpdateCallback,
@@ -19,4 +19,4 @@ export type {
19
19
  RenderResultOptions,
20
20
  ToolLoadError,
21
21
  } from "./types";
22
- export { wrapCustomTool, wrapCustomTools } from "./wrapper";
22
+ export { CustomToolAdapter } from "./wrapper";