@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.7

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 (223) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/cli.js +1945 -1386
  3. package/dist/types/advisor/advise-tool.d.ts +22 -19
  4. package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
  5. package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
  6. package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
  7. package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
  8. package/dist/types/cli/ttsr-cli.d.ts +39 -0
  9. package/dist/types/commands/ttsr.d.ts +57 -0
  10. package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
  11. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
  12. package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
  13. package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
  14. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
  15. package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
  16. package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
  17. package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
  18. package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
  19. package/dist/types/commit/changelog/generate.d.ts +12 -13
  20. package/dist/types/commit/shared-llm.d.ts +10 -37
  21. package/dist/types/config/config-file.d.ts +4 -4
  22. package/dist/types/config/keybindings.d.ts +5 -0
  23. package/dist/types/config/models-config-schema.d.ts +625 -990
  24. package/dist/types/config/models-config.d.ts +229 -217
  25. package/dist/types/config/settings-schema.d.ts +53 -23
  26. package/dist/types/edit/hashline/params.d.ts +7 -11
  27. package/dist/types/edit/index.d.ts +2 -1
  28. package/dist/types/edit/modes/apply-patch.d.ts +4 -5
  29. package/dist/types/edit/modes/patch.d.ts +15 -24
  30. package/dist/types/edit/modes/replace.d.ts +16 -17
  31. package/dist/types/eval/js/index.d.ts +1 -0
  32. package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
  33. package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
  34. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  35. package/dist/types/extensibility/hooks/types.d.ts +7 -4
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
  38. package/dist/types/extensibility/typebox.d.ts +80 -58
  39. package/dist/types/goals/tools/goal-tool.d.ts +11 -24
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/lsp/index.d.ts +11 -26
  42. package/dist/types/lsp/types.d.ts +12 -28
  43. package/dist/types/mcp/client.d.ts +8 -0
  44. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  46. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  47. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +3 -0
  49. package/dist/types/modes/setup-wizard/index.d.ts +1 -0
  50. package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
  51. package/dist/types/modes/theme/theme.d.ts +1 -1
  52. package/dist/types/modes/types.d.ts +3 -0
  53. package/dist/types/sdk.d.ts +5 -0
  54. package/dist/types/session/agent-session.d.ts +4 -0
  55. package/dist/types/startup-splash.d.ts +12 -0
  56. package/dist/types/task/types.d.ts +47 -48
  57. package/dist/types/tools/ask.d.ts +26 -27
  58. package/dist/types/tools/ast-edit.d.ts +17 -17
  59. package/dist/types/tools/ast-grep.d.ts +12 -13
  60. package/dist/types/tools/bash.d.ts +20 -17
  61. package/dist/types/tools/browser.d.ts +46 -71
  62. package/dist/types/tools/checkpoint.d.ts +14 -15
  63. package/dist/types/tools/debug.d.ts +82 -145
  64. package/dist/types/tools/eval.d.ts +30 -40
  65. package/dist/types/tools/find.d.ts +17 -18
  66. package/dist/types/tools/gh.d.ts +49 -78
  67. package/dist/types/tools/image-gen.d.ts +20 -36
  68. package/dist/types/tools/inspect-image.d.ts +10 -11
  69. package/dist/types/tools/irc.d.ts +22 -33
  70. package/dist/types/tools/job.d.ts +11 -12
  71. package/dist/types/tools/learn.d.ts +21 -28
  72. package/dist/types/tools/manage-skill.d.ts +13 -22
  73. package/dist/types/tools/memory-edit.d.ts +15 -24
  74. package/dist/types/tools/memory-recall.d.ts +7 -8
  75. package/dist/types/tools/memory-reflect.d.ts +9 -10
  76. package/dist/types/tools/memory-retain.d.ts +13 -14
  77. package/dist/types/tools/read.d.ts +7 -8
  78. package/dist/types/tools/resolve.d.ts +11 -18
  79. package/dist/types/tools/review.d.ts +9 -15
  80. package/dist/types/tools/search-tool-bm25.d.ts +9 -10
  81. package/dist/types/tools/search.d.ts +16 -17
  82. package/dist/types/tools/ssh.d.ts +14 -15
  83. package/dist/types/tools/todo.d.ts +27 -43
  84. package/dist/types/tools/tts.d.ts +8 -9
  85. package/dist/types/tools/write.d.ts +9 -10
  86. package/dist/types/tui/index.d.ts +1 -0
  87. package/dist/types/tui/width-aware-text.d.ts +23 -0
  88. package/dist/types/utils/markit.d.ts +10 -1
  89. package/dist/types/web/search/index.d.ts +17 -28
  90. package/dist/types/web/search/providers/perplexity.d.ts +0 -2
  91. package/dist/types/web/search/types.d.ts +32 -26
  92. package/package.json +14 -13
  93. package/scripts/omp +1 -1
  94. package/src/advisor/__tests__/advisor.test.ts +44 -1
  95. package/src/advisor/advise-tool.ts +34 -11
  96. package/src/autoresearch/tools/init-experiment.ts +13 -16
  97. package/src/autoresearch/tools/log-experiment.ts +15 -18
  98. package/src/autoresearch/tools/run-experiment.ts +3 -3
  99. package/src/autoresearch/tools/update-notes.ts +4 -4
  100. package/src/cli/ttsr-cli.ts +995 -0
  101. package/src/cli-commands.ts +1 -0
  102. package/src/cli.ts +7 -1
  103. package/src/commands/ttsr.ts +125 -0
  104. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  105. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  106. package/src/commit/agentic/tools/git-hunk.ts +7 -5
  107. package/src/commit/agentic/tools/git-overview.ts +4 -4
  108. package/src/commit/agentic/tools/propose-changelog.ts +18 -15
  109. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  110. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  111. package/src/commit/agentic/tools/schemas.ts +8 -20
  112. package/src/commit/agentic/tools/split-commit.ts +19 -23
  113. package/src/commit/analysis/summary.ts +7 -5
  114. package/src/commit/changelog/generate.ts +15 -11
  115. package/src/commit/shared-llm.ts +17 -24
  116. package/src/config/config-file.ts +13 -15
  117. package/src/config/keybindings.ts +6 -0
  118. package/src/config/models-config-schema.ts +206 -179
  119. package/src/config/settings-schema.ts +34 -0
  120. package/src/discovery/builtin-rules/index.ts +2 -0
  121. package/src/discovery/builtin-rules/ts-import-type.md +2 -2
  122. package/src/discovery/builtin-rules/ts-no-any.md +11 -2
  123. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  124. package/src/edit/hashline/params.ts +12 -11
  125. package/src/edit/index.ts +5 -4
  126. package/src/edit/modes/apply-patch.ts +4 -4
  127. package/src/edit/modes/patch.ts +15 -18
  128. package/src/edit/modes/replace.ts +13 -17
  129. package/src/edit/renderer.ts +0 -1
  130. package/src/eval/agent-bridge.ts +11 -13
  131. package/src/eval/completion-bridge.ts +25 -17
  132. package/src/eval/js/context-manager.ts +17 -2
  133. package/src/eval/js/index.ts +1 -1
  134. package/src/eval/py/executor.ts +2 -2
  135. package/src/extensibility/custom-commands/loader.ts +5 -3
  136. package/src/extensibility/custom-commands/types.ts +6 -3
  137. package/src/extensibility/custom-tools/loader.ts +4 -2
  138. package/src/extensibility/custom-tools/types.ts +8 -5
  139. package/src/extensibility/extensions/loader.ts +4 -2
  140. package/src/extensibility/extensions/types.ts +6 -3
  141. package/src/extensibility/hooks/loader.ts +5 -2
  142. package/src/extensibility/hooks/types.ts +7 -4
  143. package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
  144. package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
  145. package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
  146. package/src/extensibility/tool-proxy.ts +4 -1
  147. package/src/extensibility/typebox.ts +778 -251
  148. package/src/goals/guided-setup.ts +12 -3
  149. package/src/goals/tools/goal-tool.ts +6 -6
  150. package/src/index.ts +2 -0
  151. package/src/internal-urls/docs-index.generated.ts +11 -9
  152. package/src/lsp/types.ts +13 -27
  153. package/src/main.ts +19 -18
  154. package/src/mcp/client.ts +38 -13
  155. package/src/mcp/render.ts +102 -89
  156. package/src/modes/components/agent-hub.ts +11 -4
  157. package/src/modes/components/btw-panel.ts +5 -1
  158. package/src/modes/components/custom-editor.ts +18 -0
  159. package/src/modes/components/status-line/component.ts +8 -1
  160. package/src/modes/components/tool-execution.ts +17 -10
  161. package/src/modes/controllers/btw-controller.ts +69 -1
  162. package/src/modes/controllers/input-controller.ts +29 -0
  163. package/src/modes/interactive-mode.ts +38 -8
  164. package/src/modes/setup-wizard/index.ts +1 -0
  165. package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
  166. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  167. package/src/modes/theme/theme.ts +133 -143
  168. package/src/modes/types.ts +3 -0
  169. package/src/modes/utils/context-usage.ts +9 -5
  170. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  171. package/src/prompts/system/system-prompt.md +1 -0
  172. package/src/sdk.ts +21 -4
  173. package/src/session/agent-session.ts +173 -33
  174. package/src/session/session-history-format.ts +11 -2
  175. package/src/session/snapcompact-inline.ts +1 -1
  176. package/src/slash-commands/builtin-registry.ts +3 -10
  177. package/src/startup-splash.ts +19 -0
  178. package/src/task/executor.ts +11 -6
  179. package/src/task/types.ts +44 -41
  180. package/src/tool-discovery/tool-index.ts +17 -4
  181. package/src/tools/ask.ts +14 -14
  182. package/src/tools/ast-edit.ts +17 -14
  183. package/src/tools/ast-grep.ts +10 -9
  184. package/src/tools/bash.ts +15 -10
  185. package/src/tools/browser/launch.ts +13 -0
  186. package/src/tools/browser.ts +26 -32
  187. package/src/tools/checkpoint.ts +7 -7
  188. package/src/tools/debug.ts +72 -69
  189. package/src/tools/eval.ts +18 -19
  190. package/src/tools/find.ts +20 -13
  191. package/src/tools/gh.ts +29 -49
  192. package/src/tools/image-gen.ts +27 -32
  193. package/src/tools/inspect-image.ts +8 -9
  194. package/src/tools/irc.ts +12 -12
  195. package/src/tools/job.ts +6 -6
  196. package/src/tools/learn.ts +11 -14
  197. package/src/tools/manage-skill.ts +19 -23
  198. package/src/tools/memory-edit.ts +8 -8
  199. package/src/tools/memory-recall.ts +4 -4
  200. package/src/tools/memory-reflect.ts +5 -5
  201. package/src/tools/memory-retain.ts +9 -11
  202. package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
  203. package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
  204. package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
  205. package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
  206. package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
  207. package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
  208. package/src/tools/read.ts +169 -13
  209. package/src/tools/report-tool-issue.ts +6 -6
  210. package/src/tools/resolve.ts +6 -6
  211. package/src/tools/review.ts +10 -12
  212. package/src/tools/search-tool-bm25.ts +5 -5
  213. package/src/tools/search.ts +20 -29
  214. package/src/tools/ssh.ts +8 -8
  215. package/src/tools/todo.ts +16 -19
  216. package/src/tools/tts.ts +16 -15
  217. package/src/tools/write.ts +5 -5
  218. package/src/tui/index.ts +1 -0
  219. package/src/tui/width-aware-text.ts +58 -0
  220. package/src/utils/markit.ts +17 -2
  221. package/src/web/search/index.ts +9 -9
  222. package/src/web/search/providers/perplexity.ts +373 -126
  223. package/src/web/search/types.ts +28 -48
package/src/tools/read.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
3
4
  import * as path from "node:path";
4
5
  import { formatHashlineHeader, formatNumberedLine, formatNumberedLines } from "@oh-my-pi/hashline";
5
6
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
@@ -8,8 +9,8 @@ import { glob, type SummaryResult, summarizeCode } from "@oh-my-pi/pi-natives";
8
9
  import type { Component } from "@oh-my-pi/pi-tui";
9
10
  import { Text } from "@oh-my-pi/pi-tui";
10
11
  import { getRemoteDir, logger, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
12
+ import { type } from "arktype";
11
13
  import { LRUCache } from "lru-cache/raw";
12
- import { z } from "zod/v4";
13
14
  import {
14
15
  canonicalSnapshotKey,
15
16
  getFileSnapshotStore,
@@ -661,18 +662,40 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
661
662
  const notice = `[Path '${suffixResolution.from}' not found; resolved to '${suffixResolution.to}' via suffix match]`;
662
663
  return text ? `${notice}\n${text}` : notice;
663
664
  }
665
+ const PDF_IMAGE_PLACEHOLDER_RE = /<!--\s*image:\s*([^\s<>]+)(.*?)-->/g;
666
+ const PDF_IMAGE_MEMBER_RE = /^(.*\.pdf):(.*)$/i;
667
+ const PDF_IMAGE_MEMBER_EXTENSION_RE = /\.png$/i;
664
668
 
665
- const readSchema = z
666
- .object({
667
- path: z
668
- .string()
669
- .describe(
670
- 'Local path, internal URI (e.g. "omp://", "issue://123", "pr://123"), or URL; append :<sel> for line ranges or raw mode (e.g. "src/foo.ts:50-100")',
671
- ),
672
- })
673
- .strict();
669
+ function pdfImageMemberPath(pdfPath: string, imageId: string): string {
670
+ const member = PDF_IMAGE_MEMBER_EXTENSION_RE.test(imageId) ? imageId : `${imageId}.png`;
671
+ return `${pdfPath}:${member}`;
672
+ }
673
+
674
+ function rewritePdfImagePlaceholders(markdown: string, pdfPath: string): string {
675
+ return markdown.replace(PDF_IMAGE_PLACEHOLDER_RE, (_match: string, imageId: string, metadataText: string) => {
676
+ const metadata = metadataText.trim();
677
+ const suffix = metadata.length > 0 ? ` (${metadata})` : "";
678
+ return `Image ${imageId}${suffix}: read \`${pdfImageMemberPath(pdfPath, imageId)}\``;
679
+ });
680
+ }
681
+
682
+ function splitPdfImageMemberReadPath(readPath: string): { pdfPath: string; member: string } | null {
683
+ const match = PDF_IMAGE_MEMBER_RE.exec(readPath);
684
+ if (!match) return null;
685
+ const pdfPath = match[1];
686
+ const member = match[2];
687
+ if (pdfPath === undefined || member === undefined) return null;
688
+ if (member.length !== 0 && !PDF_IMAGE_MEMBER_EXTENSION_RE.test(member)) return null;
689
+ return { pdfPath, member };
690
+ }
691
+
692
+ const readSchema = type({
693
+ path: type("string").describe(
694
+ 'Local path, internal URI (e.g. "omp://", "issue://123", "pr://123"), or URL; append :<sel> for line ranges or raw mode (e.g. "src/foo.ts:50-100")',
695
+ ),
696
+ });
674
697
 
675
- export type ReadToolInput = z.infer<typeof readSchema>;
698
+ export type ReadToolInput = typeof readSchema.infer;
676
699
 
677
700
  export interface ReadToolDetails {
678
701
  kind?: "file" | "url";
@@ -983,6 +1006,113 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
983
1006
  return null;
984
1007
  }
985
1008
 
1009
+ #pdfImageCacheDir(absolutePdfPath: string): string {
1010
+ const artifactsDir = this.session.getArtifactsDir?.();
1011
+ let root = artifactsDir ?? undefined;
1012
+ if (root === undefined) {
1013
+ const sessionFile = this.session.getSessionFile();
1014
+ root = sessionFile?.endsWith(".jsonl")
1015
+ ? sessionFile.slice(0, -6)
1016
+ : path.join(os.tmpdir(), "omp-read-pdf-images");
1017
+ }
1018
+ const basename = path.basename(absolutePdfPath).replace(/[^A-Za-z0-9._-]/g, "_");
1019
+ return path.join(root, "read-pdf-images", `${basename}-${Bun.hash(absolutePdfPath).toString(36)}`);
1020
+ }
1021
+
1022
+ async #listPdfImageMembers(imageDir: string): Promise<string[]> {
1023
+ try {
1024
+ const entries = await fs.readdir(imageDir, { withFileTypes: true });
1025
+ const members: string[] = [];
1026
+ for (const entry of entries) {
1027
+ if (entry.isFile() && PDF_IMAGE_MEMBER_EXTENSION_RE.test(entry.name)) members.push(entry.name);
1028
+ }
1029
+ return members.sort();
1030
+ } catch (error) {
1031
+ if (isNotFoundError(error)) return [];
1032
+ throw error;
1033
+ }
1034
+ }
1035
+
1036
+ async #ensurePdfImageCache(absolutePdfPath: string, signal?: AbortSignal): Promise<string> {
1037
+ const imageDir = this.#pdfImageCacheDir(absolutePdfPath);
1038
+ const markerPath = path.join(imageDir, ".extracted");
1039
+ try {
1040
+ await fs.stat(markerPath);
1041
+ return imageDir;
1042
+ } catch (error) {
1043
+ if (!isNotFoundError(error)) throw error;
1044
+ }
1045
+
1046
+ await fs.rm(imageDir, { recursive: true, force: true });
1047
+ await fs.mkdir(imageDir, { recursive: true });
1048
+ const result = await convertFileWithMarkit(absolutePdfPath, signal, { imageDir });
1049
+ if (!result.ok) {
1050
+ await fs.rm(imageDir, { recursive: true, force: true });
1051
+ throw new ToolError(`Cannot extract images from PDF: ${result.error ?? "conversion failed"}`);
1052
+ }
1053
+ await Bun.write(markerPath, "ok");
1054
+ return imageDir;
1055
+ }
1056
+
1057
+ async #readPdfImageMember(
1058
+ absolutePdfPath: string,
1059
+ pdfDisplayPath: string,
1060
+ member: string,
1061
+ suffixResolution: { from: string; to: string } | undefined,
1062
+ signal?: AbortSignal,
1063
+ ): Promise<AgentToolResult<ReadToolDetails>> {
1064
+ const imageDir = await this.#ensurePdfImageCache(absolutePdfPath, signal);
1065
+ const members = await this.#listPdfImageMembers(imageDir);
1066
+ if (member.length === 0) {
1067
+ const text =
1068
+ members.length === 0
1069
+ ? "No extractable PDF image members found."
1070
+ : `Extractable PDF image members:\n${members
1071
+ .map(imageMember => `- read \`${pdfDisplayPath}:${imageMember}\``)
1072
+ .join("\n")}`;
1073
+ return toolResult<ReadToolDetails>({ resolvedPath: absolutePdfPath, suffixResolution })
1074
+ .text(prependSuffixResolutionNotice(text, suffixResolution))
1075
+ .sourcePath(absolutePdfPath)
1076
+ .done();
1077
+ }
1078
+
1079
+ if (!members.includes(member)) {
1080
+ const available = members.length === 0 ? "(none)" : members.join(", ");
1081
+ throw new ToolError(`PDF image member '${member}' not found. Available members: ${available}`);
1082
+ }
1083
+
1084
+ const imagePath = path.join(imageDir, member);
1085
+ const imageStat = await Bun.file(imagePath).stat();
1086
+ if (imageStat.size > MAX_IMAGE_SIZE) {
1087
+ const sizeStr = formatBytes(imageStat.size);
1088
+ const maxStr = formatBytes(MAX_IMAGE_SIZE);
1089
+ throw new ToolError(`Image file too large: ${sizeStr} exceeds ${maxStr} limit.`);
1090
+ }
1091
+ const metadata = await readImageMetadata(imagePath);
1092
+ const mimeType = metadata?.mimeType;
1093
+ if (!mimeType) throw new ToolError(`PDF image member '${member}' is not a supported image.`);
1094
+ const imageInput = await loadImageInput({
1095
+ path: `${pdfDisplayPath}:${member}`,
1096
+ cwd: this.session.cwd,
1097
+ autoResize: this.#autoResizeImages,
1098
+ maxBytes: MAX_IMAGE_SIZE,
1099
+ resolvedPath: imagePath,
1100
+ detectedMimeType: mimeType,
1101
+ excludeWebP: webpExclusionForModel(this.session.getActiveModel?.()),
1102
+ });
1103
+ if (!imageInput) {
1104
+ throw new ToolError(`Read image file [${mimeType}] failed: unsupported image format.`);
1105
+ }
1106
+ const textNote = prependSuffixResolutionNotice(imageInput.textNote, suffixResolution);
1107
+ return toolResult<ReadToolDetails>({ resolvedPath: absolutePdfPath, suffixResolution })
1108
+ .content([
1109
+ { type: "text", text: textNote },
1110
+ { type: "image", data: imageInput.data, mimeType: imageInput.mimeType },
1111
+ ])
1112
+ .sourcePath(imageInput.resolvedPath)
1113
+ .done();
1114
+ }
1115
+
986
1116
  #buildInMemoryTextResult(
987
1117
  text: string,
988
1118
  offset: number | undefined,
@@ -1905,6 +2035,30 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1905
2035
  return this.#readSqlite(sqlitePath, signal);
1906
2036
  }
1907
2037
 
2038
+ const pdfImageMemberPath = splitPdfImageMemberReadPath(readPath);
2039
+ if (pdfImageMemberPath) {
2040
+ let absolutePdfPath = resolveReadPath(pdfImageMemberPath.pdfPath, this.session.cwd);
2041
+ let suffixResolution: { from: string; to: string } | undefined;
2042
+ try {
2043
+ const stat = await Bun.file(absolutePdfPath).stat();
2044
+ if (stat.isDirectory())
2045
+ throw new ToolError(`Path '${pdfImageMemberPath.pdfPath}' is a directory, not a PDF file`);
2046
+ } catch (error) {
2047
+ if (!isNotFoundError(error) || isRemoteMountPath(absolutePdfPath)) throw error;
2048
+ const suffixMatch = await this.#findSuffixMatchCached(suffixCache, pdfImageMemberPath.pdfPath, signal);
2049
+ if (!suffixMatch) throw new ToolError(`Path '${pdfImageMemberPath.pdfPath}' not found`);
2050
+ absolutePdfPath = suffixMatch.absolutePath;
2051
+ suffixResolution = { from: pdfImageMemberPath.pdfPath, to: suffixMatch.displayPath };
2052
+ }
2053
+ return this.#readPdfImageMember(
2054
+ absolutePdfPath,
2055
+ pdfImageMemberPath.pdfPath,
2056
+ pdfImageMemberPath.member,
2057
+ suffixResolution,
2058
+ signal,
2059
+ );
2060
+ }
2061
+
1908
2062
  const localTarget = splitPathAndSel(readPath);
1909
2063
  const localReadPath = localTarget.path;
1910
2064
  const parsed = parseSel(localTarget.sel);
@@ -2056,20 +2210,22 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
2056
2210
  // Convert document via markit.
2057
2211
  const result = await convertFileWithMarkit(absolutePath, signal);
2058
2212
  if (result.ok) {
2213
+ const renderedContent =
2214
+ ext === ".pdf" ? rewritePdfImagePlaceholders(result.content, localReadPath) : result.content;
2059
2215
  // Route the converted markdown through the in-memory text builder
2060
2216
  // so line-range selectors (`file.pdf:50-100`, `:5-16,40-80`) and
2061
2217
  // raw mode apply against the converted output. Without this,
2062
2218
  // `file.pdf:50-100` silently returned the head of the document
2063
2219
  // because only `truncateHead` was being applied.
2064
2220
  if (isMultiRange(parsed) && parsed.kind === "lines") {
2065
- return this.#buildInMemoryMultiRangeResult(result.content, parsed.ranges, {
2221
+ return this.#buildInMemoryMultiRangeResult(renderedContent, parsed.ranges, {
2066
2222
  details: { resolvedPath: absolutePath },
2067
2223
  sourcePath: absolutePath,
2068
2224
  entityLabel: "document",
2069
2225
  });
2070
2226
  }
2071
2227
  const { offset, limit } = selToOffsetLimit(parsed);
2072
- return this.#buildInMemoryTextResult(result.content, offset, limit, {
2228
+ return this.#buildInMemoryTextResult(renderedContent, offset, limit, {
2073
2229
  details: { resolvedPath: absolutePath },
2074
2230
  sourcePath: absolutePath,
2075
2231
  entityLabel: "document",
@@ -23,7 +23,7 @@ import { Database } from "bun:sqlite";
23
23
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
24
24
  import type { FetchImpl } from "@oh-my-pi/pi-ai";
25
25
  import { $env, $flag, getAutoQaDbDir, getInstallId, logger, VERSION } from "@oh-my-pi/pi-utils";
26
- import { z } from "zod/v4";
26
+ import { type } from "arktype";
27
27
  import type { Settings } from "..";
28
28
  import type { ToolSession } from "./index";
29
29
 
@@ -31,12 +31,12 @@ function buildReportToolIssueParams(activeBuiltinNames: readonly string[]) {
31
31
  // Enum gives the model a tight schema; the runtime check in `execute` is the
32
32
  // source of truth (handles models that ignore the enum and the empty-list
33
33
  // fallback used by call sites that don't know the active set yet).
34
- const toolSchema = activeBuiltinNames.length > 0 ? z.enum(activeBuiltinNames as [string, ...string[]]) : z.string();
35
- return z.object({
34
+ const toolSchema = activeBuiltinNames.length > 0 ? type.enumerated(...activeBuiltinNames) : type("string");
35
+ return type({
36
36
  tool: toolSchema.describe("tool name"),
37
- report: z
38
- .string()
39
- .describe("unexpected behavior; generic, NEVER PII (paths, file contents, identifiers, prompt text)"),
37
+ report: type("string").describe(
38
+ "unexpected behavior; generic, NEVER PII (paths, file contents, identifiers, prompt text)",
39
+ ),
40
40
  });
41
41
  }
42
42
 
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
2
2
  import type { Component } from "@oh-my-pi/pi-tui";
3
3
  import { Text } from "@oh-my-pi/pi-tui";
4
4
  import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
5
- import { z } from "zod/v4";
5
+ import { type } from "arktype";
6
6
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
7
7
  import type { Theme } from "../modes/theme/theme";
8
8
  import resolveDescription from "../prompts/tools/resolve.md" with { type: "text" };
@@ -11,13 +11,13 @@ import type { ToolSession } from ".";
11
11
  import { replaceTabs } from "./render-utils";
12
12
  import { ToolError } from "./tool-errors";
13
13
 
14
- const resolveSchema = z.object({
15
- action: z.enum(["apply", "discard"]),
16
- reason: z.string().describe("reason for action"),
17
- extra: z.record(z.string(), z.unknown()).optional().describe("free-form metadata"),
14
+ const resolveSchema = type({
15
+ action: "'apply' | 'discard'",
16
+ reason: type("string").describe("reason for action"),
17
+ "extra?": type("Record<string, unknown>").describe("free-form metadata"),
18
18
  });
19
19
 
20
- type ResolveParams = z.infer<typeof resolveSchema>;
20
+ type ResolveParams = typeof resolveSchema.infer;
21
21
 
22
22
  export interface ResolveToolDetails {
23
23
  action: "apply" | "discard";
@@ -12,7 +12,7 @@ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
12
12
  import type { Component } from "@oh-my-pi/pi-tui";
13
13
  import { Container, Text } from "@oh-my-pi/pi-tui";
14
14
  import { isRecord } from "@oh-my-pi/pi-utils";
15
- import { z } from "zod/v4";
15
+ import { type } from "arktype";
16
16
  import type { Theme, ThemeColor } from "../modes/theme/theme";
17
17
  import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
18
18
  import type { ReviewFinding } from "../task/types";
@@ -52,17 +52,15 @@ function getPriorityDisplay(
52
52
 
53
53
  // report_finding schema
54
54
  // report_finding schema
55
- const ReportFindingParams = z
56
- .object({
57
- title: z.string().describe("prefixed imperative title"),
58
- body: z.string().describe("problem explanation"),
59
- priority: z.enum(["P0", "P1", "P2", "P3"] as const).describe("priority 0-3"),
60
- confidence: z.number().min(0).max(1).describe("confidence score"),
61
- file_path: z.string().describe("file path"),
62
- line_start: z.number().describe("start line"),
63
- line_end: z.number().describe("end line"),
64
- })
65
- .strict();
55
+ const ReportFindingParams = type({
56
+ title: type("string").describe("prefixed imperative title"),
57
+ body: type("string").describe("problem explanation"),
58
+ priority: type("'P0' | 'P1' | 'P2' | 'P3'").describe("priority 0-3"),
59
+ confidence: type("number >= 0 & number <= 1").describe("confidence score"),
60
+ file_path: type("string").describe("file path"),
61
+ line_start: type("number").describe("start line"),
62
+ line_end: type("number").describe("end line"),
63
+ });
66
64
 
67
65
  interface ReportFindingDetails {
68
66
  title: string;
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
2
2
  import type { Component } from "@oh-my-pi/pi-tui";
3
3
  import { Text } from "@oh-my-pi/pi-tui";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { z } from "zod/v4";
5
+ import { type } from "arktype";
6
6
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
7
7
  import type { Theme } from "../modes/theme/theme";
8
8
  import searchToolBm25Description from "../prompts/tools/search-tool-bm25.md" with { type: "text" };
@@ -27,12 +27,12 @@ const COLLAPSED_MATCH_LIMIT = 5;
27
27
  const MATCH_LABEL_LEN = 72;
28
28
  const MATCH_DESCRIPTION_LEN = 96;
29
29
 
30
- const searchToolBm25Schema = z.object({
31
- query: z.string().describe("tool search query"),
32
- limit: z.number().int().min(1).optional().describe("max matches"),
30
+ const searchToolBm25Schema = type({
31
+ query: type("string").describe("tool search query"),
32
+ "limit?": type("number>0").describe("max matches"),
33
33
  });
34
34
 
35
- type SearchToolBm25Params = z.infer<typeof searchToolBm25Schema>;
35
+ type SearchToolBm25Params = typeof searchToolBm25Schema.infer;
36
36
 
37
37
  interface SearchToolBm25Match {
38
38
  name: string;
@@ -7,7 +7,7 @@ import { type GrepMatch, GrepOutputMode, type GrepResult, grep } from "@oh-my-pi
7
7
  import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
9
  import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
10
- import { z } from "zod/v4";
10
+ import { type } from "arktype";
11
11
  import { recordFileSnapshot, recordSeenLinesFromBody } from "../edit/file-snapshot-store";
12
12
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
13
13
  import type { LocalProtocolOptions } from "../internal-urls/local-protocol";
@@ -65,31 +65,24 @@ import {
65
65
  import { ToolError } from "./tool-errors";
66
66
  import { toolResult } from "./tool-result";
67
67
 
68
- const searchPathEntrySchema = z
69
- .string()
70
- .describe(
71
- 'file, directory, glob, internal URL, or "<file>:<lines>" selector (e.g. "src/foo.ts:50-100", "src/foo.ts:50+10", "src/foo.ts:50-100,200-300")',
72
- );
73
- const searchSchema = z
74
- .object({
75
- pattern: z.string().describe("regex pattern"),
76
- paths: z
77
- .union([searchPathEntrySchema, z.array(searchPathEntrySchema)])
78
- .optional()
79
- .describe(
80
- 'file, directory, glob, internal URL, or array of those to search; append `:<lines>` to scope a file to specific line ranges. Omitted or empty -> searches the workspace root (".")',
81
- ),
82
- i: z.boolean().optional().describe("case-insensitive search"),
83
- gitignore: z.boolean().optional().describe("respect gitignore"),
84
- skip: z
85
- .number()
86
- .nullable()
87
- .optional()
88
- .describe("files to skip before collecting results — use to paginate when the prior call hit the file limit"),
89
- })
90
- .strict();
91
-
92
- export type SearchToolInput = z.infer<typeof searchSchema>;
68
+ const searchPathEntry = type("string").describe(
69
+ 'file, directory, glob, internal URL, or "<file>:<lines>" selector (e.g. "src/foo.ts:50-100", "src/foo.ts:50+10", "src/foo.ts:50-100,200-300")',
70
+ );
71
+ const searchSchema = type({
72
+ pattern: type("string").describe("regex pattern"),
73
+ "paths?": searchPathEntry
74
+ .or(searchPathEntry.array())
75
+ .describe(
76
+ 'file, directory, glob, internal URL, or array of those to search; append `:<lines>` to scope a file to specific line ranges. Omitted or empty -> searches the workspace root (".")',
77
+ ),
78
+ "i?": type("boolean").describe("case-insensitive search"),
79
+ "gitignore?": type("boolean").describe("respect gitignore"),
80
+ "skip?": type("number")
81
+ .or("null")
82
+ .describe("files to skip before collecting results — use to paginate when the prior call hit the file limit"),
83
+ });
84
+
85
+ export type SearchToolInput = typeof searchSchema.infer;
93
86
  export function toPathList(input: string | string[] | undefined): string[] {
94
87
  return typeof input === "string" ? [input] : (input ?? []);
95
88
  }
@@ -665,7 +658,7 @@ export interface SearchToolDetails {
665
658
  missingPaths?: string[];
666
659
  }
667
660
 
668
- type SearchParams = z.infer<typeof searchSchema>;
661
+ type SearchParams = typeof searchSchema.infer;
669
662
 
670
663
  export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
671
664
  readonly name = "search";
@@ -884,7 +877,6 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
884
877
  multiline: effectiveMultiline,
885
878
  hidden: true,
886
879
  gitignore: useGitignore,
887
- cache: false,
888
880
  maxCount: INTERNAL_TOTAL_CAP,
889
881
  contextBefore: normalizedContextBefore,
890
882
  contextAfter: normalizedContextAfter,
@@ -932,7 +924,6 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
932
924
  multiline: effectiveMultiline,
933
925
  hidden: true,
934
926
  gitignore: useGitignore,
935
- cache: false,
936
927
  maxCount: INTERNAL_TOTAL_CAP,
937
928
  contextBefore: normalizedContextBefore,
938
929
  contextAfter: normalizedContextAfter,
package/src/tools/ssh.ts CHANGED
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
2
2
  import type { ToolExample } from "@oh-my-pi/pi-ai";
3
3
  import type { Component } from "@oh-my-pi/pi-tui";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { z } from "zod/v4";
5
+ import { type } from "arktype";
6
6
  import type { SSHHost } from "../capability/ssh";
7
7
  import { sshCapability } from "../capability/ssh";
8
8
  import { loadCapability } from "../discovery";
@@ -23,11 +23,11 @@ import { ToolError } from "./tool-errors";
23
23
  import { toolResult } from "./tool-result";
24
24
  import { clampTimeout } from "./tool-timeouts";
25
25
 
26
- const sshSchema = z.object({
27
- host: z.string().describe("ssh host"),
28
- command: z.string().describe("remote command"),
29
- cwd: z.string().optional().describe("remote working directory"),
30
- timeout: z.number().optional().describe("timeout in seconds").default(60),
26
+ const sshSchema = type({
27
+ host: type("string").describe("ssh host"),
28
+ command: type("string").describe("remote command"),
29
+ "cwd?": type("string").describe("remote working directory"),
30
+ "timeout?": type("number").describe("timeout in seconds"),
31
31
  });
32
32
 
33
33
  export interface SSHToolDetails {
@@ -118,7 +118,7 @@ async function loadHosts(session: ToolSession): Promise<{
118
118
  return { hostNames, hostsByName };
119
119
  }
120
120
 
121
- type SshToolParams = z.infer<typeof sshSchema>;
121
+ type SshToolParams = typeof sshSchema.infer;
122
122
 
123
123
  export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
124
124
  readonly name = "ssh";
@@ -136,7 +136,7 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
136
136
  readonly concurrency = "exclusive";
137
137
  readonly strict = true;
138
138
 
139
- readonly examples: readonly ToolExample<z.input<typeof sshSchema>>[] = [
139
+ readonly examples: readonly ToolExample<SshToolParams>[] = [
140
140
  {
141
141
  caption: "List files: Linux (on server1 (10.0.0.1) | linux/bash)",
142
142
  call: { host: "server1", command: "ls -la /home/user" },
package/src/tools/todo.ts CHANGED
@@ -3,8 +3,8 @@ import type { ToolExample } from "@oh-my-pi/pi-ai";
3
3
  import type { Component } from "@oh-my-pi/pi-tui";
4
4
  import { Text } from "@oh-my-pi/pi-tui";
5
5
  import { prompt } from "@oh-my-pi/pi-utils";
6
+ import { type } from "arktype";
6
7
  import chalk from "chalk";
7
- import { z } from "zod/v4";
8
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
9
  import type { Theme } from "../modes/theme/theme";
10
10
  import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
@@ -44,30 +44,27 @@ export interface TodoToolDetails {
44
44
  // Schema
45
45
  // =============================================================================
46
46
 
47
- const TodoOp = z
48
- .enum(["init", "start", "done", "rm", "drop", "append", "view"] as const)
49
- .describe("operation to apply");
47
+ const TodoOp = type('"init" | "start" | "done" | "rm" | "drop" | "append" | "view"').describe("operation to apply");
50
48
 
51
- const InitListEntry = z.object({
52
- phase: z.string().describe("phase name"),
53
- items: z.array(z.string().describe("task content")).min(1).describe("tasks for this phase"),
49
+ const InitListEntry = type({
50
+ phase: type("string").describe("phase name"),
51
+ items: type("string").describe("task content").array().atLeastLength(1).describe("tasks for this phase"),
54
52
  });
55
53
 
56
- const TodoOpEntry = z.object({
54
+ const TodoOpEntry = type({
57
55
  op: TodoOp,
58
- list: z.array(InitListEntry).optional().describe("phased task list (init)"),
59
- task: z.string().optional().describe("task content"),
60
- phase: z.string().optional().describe("phase name"),
61
- items: z.array(z.string().describe("task content")).min(1).optional().describe("tasks to append"),
56
+ "list?": InitListEntry.array().describe("phased task list (init)"),
57
+ "task?": type("string").describe("task content"),
58
+ "phase?": type("string").describe("phase name"),
59
+ "items?": type("string").describe("task content").array().atLeastLength(1).describe("tasks to append"),
62
60
  });
63
61
 
64
- const todoSchema = z
65
- .object({
66
- ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"),
67
- })
68
- .describe("apply ordered todo operations");
62
+ const todoSchema = type({
63
+ ops: TodoOpEntry.array().atLeastLength(1).describe("ordered todo operations"),
64
+ }).describe("apply ordered todo operations");
69
65
 
70
- type TodoParams = z.infer<typeof todoSchema>;
66
+ type TodoParams = TodoSchema;
67
+ type TodoSchema = typeof todoSchema.infer;
71
68
  type TodoOpEntryValue = TodoParams["ops"][number];
72
69
 
73
70
  // =============================================================================
@@ -565,7 +562,7 @@ export class TodoTool implements AgentTool<typeof todoSchema, TodoToolDetails> {
565
562
  readonly concurrency = "exclusive";
566
563
  readonly strict = true;
567
564
 
568
- readonly examples: readonly ToolExample<z.input<typeof todoSchema>>[] = [
565
+ readonly examples: readonly ToolExample<typeof todoSchema.infer>[] = [
569
566
  {
570
567
  caption: "Initial setup (multi-phase)",
571
568
  call: {
package/src/tools/tts.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
7
7
  import { type ApiKey, ProviderHttpError, withAuth } from "@oh-my-pi/pi-ai";
8
- import { z } from "zod/v4";
8
+ import { type } from "arktype";
9
9
  import { settings } from "../config/settings";
10
10
  import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
11
11
  import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
@@ -16,7 +16,6 @@ import { formatPathRelativeToCwd, resolveToCwd } from "./path-utils";
16
16
 
17
17
  // Hermes tts_tool.py L167-171
18
18
  const DEFAULT_XAI_VOICE_ID = "eve" as const;
19
- const DEFAULT_XAI_LANGUAGE = "en" as const;
20
19
  const DEFAULT_XAI_SAMPLE_RATE = 24_000;
21
20
  const DEFAULT_XAI_BIT_RATE = 128_000;
22
21
  const XAI_MAX_TEXT_LENGTH = 15_000;
@@ -31,15 +30,17 @@ const formatVoiceList = (): string =>
31
30
  type TtsCodec = "mp3" | "wav";
32
31
  type TtsBackend = "local" | "xai";
33
32
 
34
- const ttsSchema = z.object({
35
- text: z.string().min(1).max(XAI_MAX_TEXT_LENGTH),
36
- voice_id: z.string().default(DEFAULT_XAI_VOICE_ID),
37
- language: z.string().default(DEFAULT_XAI_LANGUAGE),
38
- output_path: z.string(),
39
- sample_rate: z.number().int().optional(),
40
- bit_rate: z.number().int().optional(),
33
+ const ttsSchema = type({
34
+ text: "1 <= string <= 15000",
35
+ voice_id: "string = 'eve'",
36
+ language: "string = 'en'",
37
+ output_path: "string",
38
+ sample_rate: "number.integer?",
39
+ bit_rate: "number.integer?",
41
40
  });
42
41
 
42
+ type TtsSchemaType = typeof ttsSchema.infer;
43
+
43
44
  interface TtsToolDetails {
44
45
  bytes: number;
45
46
  voiceId: string;
@@ -87,13 +88,13 @@ function readStringSetting(key: "providers.tts" | "tts.localModel" | "tts.localV
87
88
  }
88
89
 
89
90
  async function synthesizeXai(
90
- params: z.infer<typeof ttsSchema>,
91
+ params: TtsSchemaType,
91
92
  ctx: CustomToolContext,
92
93
  outputPath: string,
93
94
  displayPath: string,
94
95
  codec: TtsCodec,
95
96
  signal: AbortSignal | undefined,
96
- ): Promise<AgentToolResult<TtsToolDetails, typeof ttsSchema>> {
97
+ ): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
97
98
  const creds = await resolveXAIHttpCredentials(ctx.modelRegistry);
98
99
  if (!creds) {
99
100
  return {
@@ -187,11 +188,11 @@ async function synthesizeXai(
187
188
  }
188
189
 
189
190
  async function synthesizeLocal(
190
- params: z.infer<typeof ttsSchema>,
191
+ params: TtsSchemaType,
191
192
  cwd: string,
192
193
  outputPath: string,
193
194
  signal: AbortSignal | undefined,
194
- ): Promise<AgentToolResult<TtsToolDetails, typeof ttsSchema>> {
195
+ ): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
195
196
  const modelSetting = readStringSetting("tts.localModel");
196
197
  const modelKey = modelSetting && isTtsLocalModelKey(modelSetting) ? modelSetting : DEFAULT_TTS_LOCAL_MODEL_KEY;
197
198
  const voice = readStringSetting("tts.localVoice") || DEFAULT_TTS_VOICE;
@@ -242,11 +243,11 @@ export const ttsTool: CustomTool<typeof ttsSchema, TtsToolDetails> = {
242
243
  parameters: ttsSchema,
243
244
  async execute(
244
245
  _toolCallId: string,
245
- params: z.infer<typeof ttsSchema>,
246
+ params: TtsSchemaType,
246
247
  _onUpdate,
247
248
  ctx: CustomToolContext,
248
249
  signal?: AbortSignal,
249
- ): Promise<AgentToolResult<TtsToolDetails, typeof ttsSchema>> {
250
+ ): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
250
251
  const cwd = ctx.sessionManager.getCwd();
251
252
  const outputPath = resolveToCwd(params.output_path, cwd);
252
253
  const displayPath = formatPathRelativeToCwd(outputPath, cwd);