pi-ui-extend 0.1.11 → 0.1.13

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 (45) hide show
  1. package/dist/app/app.js +6 -4
  2. package/dist/app/cli/install.d.ts +14 -0
  3. package/dist/app/cli/install.js +19 -7
  4. package/dist/app/cli/startup-info.js +5 -2
  5. package/dist/app/cli/update.d.ts +7 -0
  6. package/dist/app/cli/update.js +11 -3
  7. package/dist/app/commands/shell-command.d.ts +7 -0
  8. package/dist/app/commands/shell-command.js +12 -4
  9. package/dist/app/icons.d.ts +1 -0
  10. package/dist/app/icons.js +2 -0
  11. package/dist/app/input/prompt-enhancer-controller.d.ts +7 -1
  12. package/dist/app/input/prompt-enhancer-controller.js +12 -3
  13. package/dist/app/input/voice-controller.d.ts +49 -1
  14. package/dist/app/input/voice-controller.js +16 -5
  15. package/dist/app/rendering/conversation-entry-renderer.js +2 -11
  16. package/dist/app/rendering/status-line-renderer.js +3 -11
  17. package/dist/app/rendering/toast-renderer.js +10 -13
  18. package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
  19. package/dist/app/rendering/tool-block-renderer.js +3 -2
  20. package/dist/app/screen/clipboard.d.ts +9 -0
  21. package/dist/app/screen/clipboard.js +19 -6
  22. package/dist/app/screen/file-link-opener.d.ts +8 -0
  23. package/dist/app/screen/file-link-opener.js +11 -3
  24. package/dist/app/screen/file-links.js +3 -3
  25. package/dist/app/screen/image-opener.d.ts +12 -0
  26. package/dist/app/screen/image-opener.js +13 -5
  27. package/dist/app/session/queued-message-controller.js +5 -1
  28. package/dist/app/terminal/nerd-font-controller.d.ts +16 -0
  29. package/dist/app/terminal/nerd-font-controller.js +20 -12
  30. package/dist/default-pix-config.js +7 -6
  31. package/dist/schemas/index.d.ts +5 -0
  32. package/dist/schemas/index.js +5 -0
  33. package/dist/schemas/pi-tools-suite-schema.d.ts +177 -0
  34. package/dist/schemas/pi-tools-suite-schema.js +218 -0
  35. package/dist/schemas/pix-schema.d.ts +65 -0
  36. package/dist/schemas/pix-schema.js +91 -0
  37. package/dist/terminal-width.js +73 -56
  38. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +3 -0
  39. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +1 -0
  40. package/external/pi-tools-suite/src/todo/index.ts +4 -2
  41. package/external/pi-tools-suite/src/todo/state/selectors.ts +4 -0
  42. package/external/pi-tools-suite/src/todo/todo.ts +2 -6
  43. package/package.json +12 -3
  44. package/schemas/pi-tools-suite.json +881 -0
  45. package/schemas/pix.json +298 -0
@@ -1,10 +1,23 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { commandExists, runProcess } from "../process.js";
3
3
  const require = createRequire(import.meta.url);
4
+ let deps = {
5
+ commandExists,
6
+ requireResolve: (specifier) => require.resolve(specifier),
7
+ runProcess,
8
+ stdout: process.stdout,
9
+ };
10
+ export function setClipboardTestDeps(overrides) {
11
+ const previous = deps;
12
+ deps = { ...deps, ...overrides };
13
+ return () => {
14
+ deps = previous;
15
+ };
16
+ }
4
17
  export async function copyTextToClipboard(text) {
5
18
  const commands = clipboardCommands();
6
19
  for (const [command, args] of commands) {
7
- const result = await runProcess(command, args, { input: text, maxBufferBytes: 1024 });
20
+ const result = await deps.runProcess(command, args, { input: text, maxBufferBytes: 1024 });
8
21
  if (!result.error && result.status === 0)
9
22
  return;
10
23
  }
@@ -16,7 +29,7 @@ export async function copyTextToClipboard(text) {
16
29
  }
17
30
  export async function clipboardSupportAvailable(env = process.env) {
18
31
  for (const [command] of clipboardCommands()) {
19
- if (await commandExists(command, env))
32
+ if (await deps.commandExists(command, env))
20
33
  return true;
21
34
  }
22
35
  return resolveNativeClipboardEntrypoint() !== undefined;
@@ -57,7 +70,7 @@ async function copyWithNativeClipboard(text) {
57
70
  const clipboard = require(${JSON.stringify(entrypoint)});
58
71
  await clipboard.setText(readFileSync(0, "utf8"));
59
72
  `;
60
- const result = await runProcess(process.execPath, ["--input-type=module", "-e", script], {
73
+ const result = await deps.runProcess(process.execPath, ["--input-type=module", "-e", script], {
61
74
  input: text,
62
75
  timeoutMs: 3_000,
63
76
  maxBufferBytes: 1024,
@@ -65,9 +78,9 @@ async function copyWithNativeClipboard(text) {
65
78
  return !result.error && result.status === 0;
66
79
  }
67
80
  function copyWithOsc52(text) {
68
- if (process.stdout.destroyed || (!process.stdout.isTTY && !process.env.TMUX && !process.env.STY))
81
+ if (deps.stdout.destroyed || (!deps.stdout.isTTY && !process.env.TMUX && !process.env.STY))
69
82
  return false;
70
- process.stdout.write(osc52ClipboardSequence(text));
83
+ deps.stdout.write(osc52ClipboardSequence(text));
71
84
  return true;
72
85
  }
73
86
  export function osc52ClipboardSequence(text, env = process.env) {
@@ -80,7 +93,7 @@ export function osc52ClipboardSequence(text, env = process.env) {
80
93
  }
81
94
  function resolveNativeClipboardEntrypoint() {
82
95
  try {
83
- return require.resolve("@mariozechner/clipboard");
96
+ return deps.requireResolve("@mariozechner/clipboard");
84
97
  }
85
98
  catch {
86
99
  return undefined;
@@ -1,2 +1,10 @@
1
+ import { existsSync } from "node:fs";
2
+ import { spawn } from "node:child_process";
1
3
  import type { RenderedLink } from "./file-links.js";
4
+ type FileLinkOpenerDeps = {
5
+ existsSync: typeof existsSync;
6
+ spawn: typeof spawn;
7
+ };
8
+ export declare function setFileLinkOpenerTestDeps(overrides: Partial<FileLinkOpenerDeps>): () => void;
2
9
  export declare function openFileLink(link: RenderedLink): boolean;
10
+ export {};
@@ -2,6 +2,14 @@ import { existsSync } from "node:fs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { delimiter, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ let deps = { existsSync, spawn };
6
+ export function setFileLinkOpenerTestDeps(overrides) {
7
+ const previous = deps;
8
+ deps = { ...deps, ...overrides };
9
+ return () => {
10
+ deps = previous;
11
+ };
12
+ }
5
13
  export function openFileLink(link) {
6
14
  const filePath = link.filePath ?? filePathFromUrl(link.url);
7
15
  if (!filePath)
@@ -37,7 +45,7 @@ function zedCommandCandidates() {
37
45
  }
38
46
  function trySpawnCandidates(candidates, args) {
39
47
  for (const command of candidates) {
40
- if (command.includes("/") && !existsSync(command))
48
+ if (command.includes("/") && !deps.existsSync(command))
41
49
  continue;
42
50
  if (!command.includes("/") && !commandOnPath(command))
43
51
  continue;
@@ -51,11 +59,11 @@ function commandOnPath(command) {
51
59
  const extensions = process.platform === "win32"
52
60
  ? (process.env.PATHEXT?.split(";") ?? [".EXE", ".CMD", ".BAT", ".COM"])
53
61
  : [""];
54
- return pathEntries.some((entry) => extensions.some((extension) => existsSync(join(entry, `${command}${extension}`))));
62
+ return pathEntries.some((entry) => extensions.some((extension) => deps.existsSync(join(entry, `${command}${extension}`))));
55
63
  }
56
64
  function spawnDetached(command, args) {
57
65
  try {
58
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
66
+ const child = deps.spawn(command, args, { detached: true, stdio: "ignore" });
59
67
  child.on("error", () => { });
60
68
  child.unref();
61
69
  return true;
@@ -2,11 +2,11 @@ import { existsSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- const FILE_PATH_CANDIDATE = /(?<![\p{L}\p{N}_:])((?:file:\/\/\/|~\/|\.{1,2}\/|\/|[A-Za-z0-9_.@-]+\/)[^\s"'`<>]*)/gu;
5
+ const FILE_PATH_CANDIDATE = /(?<![\p{L}\p{N}_:])((?:file:\/\/\/|~[\\/]|\.{1,2}[\\/]|[A-Za-z]:[\\/]|[\\/]|[A-Za-z0-9_.@-]+[\\/])[^\s"'`<>]*)/gu;
6
6
  const TRAILING_PUNCTUATION = new Set([".", ",", ";", ")", "]", "}"]);
7
7
  export function detectFileLinks(text, cwd) {
8
8
  const links = [];
9
- if (!text.includes("/"))
9
+ if (!text.includes("/") && !text.includes("\\"))
10
10
  return links;
11
11
  for (const match of text.matchAll(FILE_PATH_CANDIDATE)) {
12
12
  const raw = match[1];
@@ -89,7 +89,7 @@ function resolveLocalPath(pathText, cwd) {
89
89
  return undefined;
90
90
  }
91
91
  }
92
- if (pathText.startsWith("~/"))
92
+ if (pathText.startsWith("~/") || pathText.startsWith("~\\"))
93
93
  return resolve(homedir(), pathText.slice(2));
94
94
  if (isAbsolute(pathText))
95
95
  return pathText;
@@ -1,2 +1,14 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
1
4
  import type { ImageContent } from "../../input-editor.js";
5
+ type ImageOpenerDeps = {
6
+ existsSync: typeof existsSync;
7
+ mkdirSync: typeof mkdirSync;
8
+ spawn: typeof spawn;
9
+ tmpdir: typeof tmpdir;
10
+ writeFileSync: typeof writeFileSync;
11
+ };
12
+ export declare function setImageOpenerTestDeps(overrides: Partial<ImageOpenerDeps>): () => void;
2
13
  export declare function openImageContent(image: ImageContent): boolean;
14
+ export {};
@@ -3,6 +3,14 @@ import { createHash } from "node:crypto";
3
3
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
+ let deps = { existsSync, mkdirSync, spawn, tmpdir, writeFileSync };
7
+ export function setImageOpenerTestDeps(overrides) {
8
+ const previous = deps;
9
+ deps = { ...deps, ...overrides };
10
+ return () => {
11
+ deps = previous;
12
+ };
13
+ }
6
14
  export function openImageContent(image) {
7
15
  const filePath = writeImageTempFile(image);
8
16
  if (!filePath)
@@ -14,12 +22,12 @@ function writeImageTempFile(image) {
14
22
  const data = Buffer.from(image.data, "base64");
15
23
  if (data.length === 0)
16
24
  return undefined;
17
- const dir = join(tmpdir(), "pix-image-open");
18
- mkdirSync(dir, { recursive: true });
25
+ const dir = join(deps.tmpdir(), "pix-image-open");
26
+ deps.mkdirSync(dir, { recursive: true });
19
27
  const hash = createHash("sha256").update(image.mimeType).update("\0").update(data).digest("hex").slice(0, 24);
20
28
  const filePath = join(dir, `${hash}${imageExtension(image.mimeType)}`);
21
- if (!existsSync(filePath))
22
- writeFileSync(filePath, data, { flag: "wx" });
29
+ if (!deps.existsSync(filePath))
30
+ deps.writeFileSync(filePath, data, { flag: "wx" });
23
31
  return filePath;
24
32
  }
25
33
  catch {
@@ -53,7 +61,7 @@ function openPathWithSystemViewer(filePath) {
53
61
  }
54
62
  function spawnDetached(command, args) {
55
63
  try {
56
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
64
+ const child = deps.spawn(command, args, { detached: true, stdio: "ignore" });
57
65
  child.on("error", () => { });
58
66
  child.unref();
59
67
  return true;
@@ -32,6 +32,10 @@ export class AppQueuedMessageController {
32
32
  }
33
33
  async submitUserMessage(message) {
34
34
  const session = this.host.requireRuntime().session;
35
+ if (session.isStreaming) {
36
+ await this.sendUserMessageToSession(message, { streamingBehavior: "steer" });
37
+ return;
38
+ }
35
39
  if (this.shouldDeferUserMessage(session)) {
36
40
  this.deferUserMessage(message);
37
41
  return;
@@ -228,7 +232,7 @@ export class AppQueuedMessageController {
228
232
  return entry?.kind === "queued" ? entry : undefined;
229
233
  }
230
234
  shouldDeferUserMessage(session) {
231
- return session.isStreaming || session.isCompacting || this.promptSubmissionInFlight;
235
+ return session.isCompacting || this.promptSubmissionInFlight;
232
236
  }
233
237
  deferUserMessage(message) {
234
238
  this.deferredUserMessages.push(message);
@@ -1,3 +1,18 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readdir, writeFile } from "node:fs/promises";
4
+ import { commandExists, runProcess } from "../process.js";
5
+ type NerdFontControllerDeps = {
6
+ commandExists: typeof commandExists;
7
+ existsSync: typeof existsSync;
8
+ fetch: typeof fetch;
9
+ mkdir: typeof mkdir;
10
+ readdir: typeof readdir;
11
+ runProcess: typeof runProcess;
12
+ spawn: typeof spawn;
13
+ writeFile: typeof writeFile;
14
+ };
15
+ export declare function setNerdFontControllerTestDeps(overrides: Partial<NerdFontControllerDeps>): () => void;
1
16
  export type NerdFontInstallHost = {
2
17
  showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
3
18
  render(): void;
@@ -15,3 +30,4 @@ export declare class NerdFontController {
15
30
  export declare function isJetBrainsNerdFontInstalled(): Promise<boolean>;
16
31
  export declare function installJetBrainsNerdFont(): Promise<string>;
17
32
  export declare function userFontInstallPath(): string;
33
+ export {};
@@ -4,6 +4,14 @@ import { mkdir, readdir, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { commandExists, runProcess } from "../process.js";
7
+ let deps = { commandExists, existsSync, fetch, mkdir, readdir, runProcess, spawn, writeFile };
8
+ export function setNerdFontControllerTestDeps(overrides) {
9
+ const previous = deps;
10
+ deps = { ...deps, ...overrides };
11
+ return () => {
12
+ deps = previous;
13
+ };
14
+ }
7
15
  const CASK_NAME = "font-jetbrains-mono-nerd-font";
8
16
  export const FONT_FAMILY_NAME = "JetBrainsMono Nerd Font Mono";
9
17
  export const FONT_FILE_NAME = "JetBrainsMonoNerdFontMono-Regular.ttf";
@@ -43,13 +51,13 @@ export class NerdFontController {
43
51
  }
44
52
  }
45
53
  export async function isJetBrainsNerdFontInstalled() {
46
- if (await commandExists("brew")) {
47
- const result = await runProcess("brew", ["list", "--cask", CASK_NAME], { maxBufferBytes: 1024 });
54
+ if (await deps.commandExists("brew")) {
55
+ const result = await deps.runProcess("brew", ["list", "--cask", CASK_NAME], { maxBufferBytes: 1024 });
48
56
  if (result.status === 0)
49
57
  return true;
50
58
  }
51
- if (process.platform === "linux" && await commandExists("fc-match")) {
52
- const result = await runProcess("fc-match", ["-f", "%{family}", FONT_FAMILY_NAME], { maxBufferBytes: 1024 });
59
+ if (process.platform === "linux" && await deps.commandExists("fc-match")) {
60
+ const result = await deps.runProcess("fc-match", ["-f", "%{family}", FONT_FAMILY_NAME], { maxBufferBytes: 1024 });
53
61
  if (result.status === 0 && /JetBrains.*Nerd/iu.test(result.stdout))
54
62
  return true;
55
63
  }
@@ -61,13 +69,13 @@ export async function isJetBrainsNerdFontInstalled() {
61
69
  return false;
62
70
  }
63
71
  export async function installJetBrainsNerdFont() {
64
- if (process.platform === "darwin" && await commandExists("brew")) {
72
+ if (process.platform === "darwin" && await deps.commandExists("brew")) {
65
73
  await runBrewInstall();
66
74
  return CASK_NAME;
67
75
  }
68
76
  const targetPath = userFontInstallPath();
69
- await mkdir(dirname(targetPath), { recursive: true });
70
- const response = await fetch(FONT_DOWNLOAD_URL, {
77
+ await deps.mkdir(dirname(targetPath), { recursive: true });
78
+ const response = await deps.fetch(FONT_DOWNLOAD_URL, {
71
79
  headers: { "User-Agent": "pix-font-installer" },
72
80
  signal: AbortSignal.timeout(30_000),
73
81
  });
@@ -76,7 +84,7 @@ export async function installJetBrainsNerdFont() {
76
84
  const bytes = new Uint8Array(await response.arrayBuffer());
77
85
  if (bytes.length < 100_000)
78
86
  throw new Error("downloaded font is unexpectedly small");
79
- await writeFile(targetPath, bytes);
87
+ await deps.writeFile(targetPath, bytes);
80
88
  if (process.platform === "linux")
81
89
  await runOptionalCommand("fc-cache", ["-f", dirname(targetPath)]);
82
90
  if (process.platform === "win32")
@@ -107,7 +115,7 @@ function platformFontDirs() {
107
115
  }
108
116
  }
109
117
  async function directoryContainsFont(root) {
110
- if (!existsSync(root))
118
+ if (!deps.existsSync(root))
111
119
  return false;
112
120
  const pending = [{ dir: root, depth: 0 }];
113
121
  let scanned = 0;
@@ -117,7 +125,7 @@ async function directoryContainsFont(root) {
117
125
  continue;
118
126
  let entries;
119
127
  try {
120
- entries = await readdir(current.dir, { withFileTypes: true });
128
+ entries = await deps.readdir(current.dir, { withFileTypes: true });
121
129
  }
122
130
  catch {
123
131
  continue;
@@ -134,7 +142,7 @@ async function directoryContainsFont(root) {
134
142
  }
135
143
  async function runBrewInstall() {
136
144
  await new Promise((resolve, reject) => {
137
- const child = spawn("brew", ["install", "--cask", CASK_NAME], {
145
+ const child = deps.spawn("brew", ["install", "--cask", CASK_NAME], {
138
146
  env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" },
139
147
  stdio: ["ignore", "ignore", "pipe"],
140
148
  });
@@ -163,7 +171,7 @@ async function registerWindowsUserFont(fontPath) {
163
171
  ]);
164
172
  }
165
173
  async function runOptionalCommand(command, args) {
166
- await runProcess(command, args, { maxBufferBytes: 1024 });
174
+ await deps.runProcess(command, args, { maxBufferBytes: 1024 });
167
175
  }
168
176
  function errorMessage(error) {
169
177
  return error instanceof Error ? error.message : String(error);
@@ -1,4 +1,5 @@
1
1
  export const DEFAULT_PIX_CONFIG_JSONC = String.raw `{
2
+ "$schema": "https://unpkg.com/pi-ui-extend/schemas/pix.json",
2
3
  // pix renderer configuration
3
4
  "defaultModel": { "modelRef": "openai-codex/gpt-5.5", "thinking": "medium" },
4
5
 
@@ -26,12 +27,12 @@ export const DEFAULT_PIX_CONFIG_JSONC = String.raw `{
26
27
  "question": { "previewLines": 6, "direction": "tail", "color": "accent" },
27
28
  "subagents": { "previewLines": 0, "direction": "tail", "color": "muted" },
28
29
  "todo": { "hidden": true, "color": "accent" },
29
- "ls": { "color": "success" },
30
- "LS": { "color": "success" },
31
- "grep": { "color": "toolSearch" },
32
- "Grep": { "color": "toolSearch" },
33
- "find": { "color": "toolSearch" },
34
- "Glob": { "color": "toolSearch" },
30
+ "ls": { "previewLines": 6, "direction": "head", "color": "success" },
31
+ "LS": { "previewLines": 6, "direction": "head", "color": "success" },
32
+ "grep": { "previewLines": 6, "direction": "head", "color": "toolSearch" },
33
+ "Grep": { "previewLines": 6, "direction": "head", "color": "toolSearch" },
34
+ "find": { "previewLines": 6, "direction": "head", "color": "toolSearch" },
35
+ "Glob": { "previewLines": 6, "direction": "head", "color": "toolSearch" },
35
36
  "skill": { "previewLines": 0, "color": "toolSearch" }
36
37
  }
37
38
  },
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Schema registry — re-exports all TypeBox schemas.
3
+ */
4
+ export { PixConfigSchema } from "./pix-schema.js";
5
+ export { PiToolsSuiteConfigSchema } from "./pi-tools-suite-schema.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Schema registry — re-exports all TypeBox schemas.
3
+ */
4
+ export { PixConfigSchema } from "./pix-schema.js";
5
+ export { PiToolsSuiteConfigSchema } from "./pi-tools-suite-schema.js";
@@ -0,0 +1,177 @@
1
+ /**
2
+ * TypeBox JSON Schema definitions for pi-tools-suite.jsonc (~/.config/pi/pi-tools-suite.jsonc).
3
+ *
4
+ * All fields are optional because the runtime applies generous defaults.
5
+ * The generated JSON Schema includes `"additionalProperties": true`.
6
+ */
7
+ import { Type, Static } from "typebox";
8
+ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
9
+ $schema: Type.TOptional<Type.TString>;
10
+ enabled: Type.TOptional<Type.TBoolean>;
11
+ disabledModules: Type.TOptional<Type.TArray<Type.TString>>;
12
+ terminalBell: Type.TOptional<Type.TObject<{
13
+ sound: Type.TOptional<Type.TBoolean>;
14
+ }>>;
15
+ dcp: Type.TOptional<Type.TObject<{
16
+ enabled: Type.TOptional<Type.TBoolean>;
17
+ debug: Type.TOptional<Type.TBoolean>;
18
+ manualMode: Type.TOptional<Type.TObject<{
19
+ enabled: Type.TOptional<Type.TBoolean>;
20
+ automaticStrategies: Type.TOptional<Type.TBoolean>;
21
+ }>>;
22
+ compress: Type.TOptional<Type.TObject<{
23
+ maxContextPercent: Type.TOptional<Type.TUnion<[Type.TNumber, Type.TString]>>;
24
+ minContextPercent: Type.TOptional<Type.TUnion<[Type.TNumber, Type.TString]>>;
25
+ modelMaxContextPercent: Type.TOptional<Type.TRecord<"^.*$", Type.TNumber>>;
26
+ modelMinContextPercent: Type.TOptional<Type.TRecord<"^.*$", Type.TNumber>>;
27
+ maxContextLimit: Type.TOptional<Type.TUnion<[Type.TNumber, Type.TString]>>;
28
+ minContextLimit: Type.TOptional<Type.TUnion<[Type.TNumber, Type.TString]>>;
29
+ modelMaxContextLimits: Type.TOptional<Type.TRecord<"^.*$", Type.TUnion<[Type.TNumber, Type.TString]>>>;
30
+ modelMinContextLimits: Type.TOptional<Type.TRecord<"^.*$", Type.TUnion<[Type.TNumber, Type.TString]>>>;
31
+ summaryBuffer: Type.TOptional<Type.TBoolean>;
32
+ nudgeFrequency: Type.TOptional<Type.TNumber>;
33
+ iterationNudgeThreshold: Type.TOptional<Type.TNumber>;
34
+ nudgeForce: Type.TOptional<Type.TUnion<[Type.TLiteral<"strong">, Type.TLiteral<"soft">]>>;
35
+ protectedTools: Type.TOptional<Type.TArray<Type.TString>>;
36
+ protectTags: Type.TOptional<Type.TBoolean>;
37
+ protectUserMessages: Type.TOptional<Type.TBoolean>;
38
+ autoCandidates: Type.TOptional<Type.TObject<{
39
+ enabled: Type.TOptional<Type.TBoolean>;
40
+ minContextPercent: Type.TOptional<Type.TNumber>;
41
+ keepRecentTurns: Type.TOptional<Type.TNumber>;
42
+ minMessages: Type.TOptional<Type.TNumber>;
43
+ minTokens: Type.TOptional<Type.TNumber>;
44
+ }>>;
45
+ messageMode: Type.TOptional<Type.TObject<{
46
+ enabled: Type.TOptional<Type.TBoolean>;
47
+ minContextPercent: Type.TOptional<Type.TNumber>;
48
+ keepRecentTurns: Type.TOptional<Type.TNumber>;
49
+ mediumTokens: Type.TOptional<Type.TNumber>;
50
+ highTokens: Type.TOptional<Type.TNumber>;
51
+ maxSuggestions: Type.TOptional<Type.TNumber>;
52
+ }>>;
53
+ }>>;
54
+ strategies: Type.TOptional<Type.TObject<{
55
+ deduplication: Type.TOptional<Type.TObject<{
56
+ enabled: Type.TOptional<Type.TBoolean>;
57
+ protectedTools: Type.TOptional<Type.TArray<Type.TString>>;
58
+ }>>;
59
+ purgeErrors: Type.TOptional<Type.TObject<{
60
+ enabled: Type.TOptional<Type.TBoolean>;
61
+ turns: Type.TOptional<Type.TNumber>;
62
+ protectedTools: Type.TOptional<Type.TArray<Type.TString>>;
63
+ }>>;
64
+ autoToolPruning: Type.TOptional<Type.TObject<{
65
+ enabled: Type.TOptional<Type.TBoolean>;
66
+ maxOutputTokens: Type.TOptional<Type.TNumber>;
67
+ keepRecentTurns: Type.TOptional<Type.TNumber>;
68
+ readLikeTools: Type.TOptional<Type.TArray<Type.TString>>;
69
+ readLikeTurns: Type.TOptional<Type.TNumber>;
70
+ protectedTools: Type.TOptional<Type.TArray<Type.TString>>;
71
+ }>>;
72
+ }>>;
73
+ protectedFilePatterns: Type.TOptional<Type.TArray<Type.TString>>;
74
+ pruneNotification: Type.TOptional<Type.TUnion<[Type.TLiteral<"off">, Type.TLiteral<"minimal">, Type.TLiteral<"detailed">]>>;
75
+ }>>;
76
+ asyncSubagents: Type.TOptional<Type.TObject<{
77
+ defaultType: Type.TOptional<Type.TString>;
78
+ routing: Type.TOptional<Type.TObject<{
79
+ enabled: Type.TOptional<Type.TBoolean>;
80
+ model: Type.TOptional<Type.TString>;
81
+ maxTaskChars: Type.TOptional<Type.TNumber>;
82
+ maxTokens: Type.TOptional<Type.TNumber>;
83
+ maxRetries: Type.TOptional<Type.TNumber>;
84
+ temperature: Type.TOptional<Type.TNumber>;
85
+ timeoutMs: Type.TOptional<Type.TNumber>;
86
+ debug: Type.TOptional<Type.TBoolean>;
87
+ }>>;
88
+ vision: Type.TOptional<Type.TObject<{
89
+ blindModelPatterns: Type.TOptional<Type.TArray<Type.TString>>;
90
+ }>>;
91
+ presets: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
92
+ description: Type.TOptional<Type.TString>;
93
+ model: Type.TOptional<Type.TString>;
94
+ fallbackModels: Type.TOptional<Type.TArray<Type.TString>>;
95
+ thinking: Type.TOptional<Type.TString>;
96
+ extraArgs: Type.TOptional<Type.TArray<Type.TString>>;
97
+ timeoutMs: Type.TOptional<Type.TNumber>;
98
+ types: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
99
+ model: Type.TOptional<Type.TString>;
100
+ fallbackModels: Type.TOptional<Type.TArray<Type.TString>>;
101
+ thinking: Type.TOptional<Type.TString>;
102
+ extraArgs: Type.TOptional<Type.TArray<Type.TString>>;
103
+ timeoutMs: Type.TOptional<Type.TNumber>;
104
+ }>>>;
105
+ }>>>;
106
+ types: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
107
+ description: Type.TOptional<Type.TString>;
108
+ model: Type.TOptional<Type.TString>;
109
+ fallbackModels: Type.TOptional<Type.TArray<Type.TString>>;
110
+ thinking: Type.TOptional<Type.TString>;
111
+ tools: Type.TOptional<Type.TArray<Type.TString>>;
112
+ extraArgs: Type.TOptional<Type.TArray<Type.TString>>;
113
+ promptAppend: Type.TOptional<Type.TString>;
114
+ promptOverride: Type.TOptional<Type.TString>;
115
+ retry: Type.TOptional<Type.TObject<{
116
+ maxRetries: Type.TOptional<Type.TNumber>;
117
+ backoffMs: Type.TOptional<Type.TNumber>;
118
+ retryableExitCodes: Type.TOptional<Type.TArray<Type.TNumber>>;
119
+ }>>;
120
+ maxResultBytes: Type.TOptional<Type.TNumber>;
121
+ timeoutMs: Type.TOptional<Type.TNumber>;
122
+ }>>>;
123
+ maxConcurrent: Type.TOptional<Type.TNumber>;
124
+ retry: Type.TOptional<Type.TObject<{
125
+ maxRetries: Type.TOptional<Type.TNumber>;
126
+ backoffMs: Type.TOptional<Type.TNumber>;
127
+ retryableExitCodes: Type.TOptional<Type.TArray<Type.TNumber>>;
128
+ }>>;
129
+ maxResultBytes: Type.TOptional<Type.TNumber>;
130
+ timeoutMs: Type.TOptional<Type.TNumber>;
131
+ }>>;
132
+ toolRenderer: Type.TOptional<Type.TObject<{
133
+ default: Type.TOptional<Type.TObject<{
134
+ previewLines: Type.TOptional<Type.TNumber>;
135
+ direction: Type.TOptional<Type.TUnion<[Type.TLiteral<"head">, Type.TLiteral<"tail">]>>;
136
+ color: Type.TOptional<Type.TString>;
137
+ defaultExpanded: Type.TOptional<Type.TBoolean>;
138
+ compactHidden: Type.TOptional<Type.TBoolean>;
139
+ hidden: Type.TOptional<Type.TBoolean>;
140
+ }>>;
141
+ tools: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
142
+ previewLines: Type.TOptional<Type.TNumber>;
143
+ direction: Type.TOptional<Type.TUnion<[Type.TLiteral<"head">, Type.TLiteral<"tail">]>>;
144
+ color: Type.TOptional<Type.TString>;
145
+ defaultExpanded: Type.TOptional<Type.TBoolean>;
146
+ compactHidden: Type.TOptional<Type.TBoolean>;
147
+ hidden: Type.TOptional<Type.TBoolean>;
148
+ }>>>;
149
+ }>>;
150
+ promptCommands: Type.TOptional<Type.TObject<{
151
+ commands: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
152
+ description: Type.TOptional<Type.TString>;
153
+ prompt: Type.TString;
154
+ }>>>;
155
+ }>>;
156
+ lsp: Type.TOptional<Type.TObject<{
157
+ servers: Type.TOptional<Type.TArray<Type.TObject<{
158
+ id: Type.TString;
159
+ enabled: Type.TOptional<Type.TBoolean>;
160
+ include: Type.TOptional<Type.TArray<Type.TString>>;
161
+ exclude: Type.TOptional<Type.TArray<Type.TString>>;
162
+ rootMarkers: Type.TOptional<Type.TArray<Type.TString>>;
163
+ maxFileSizeBytes: Type.TOptional<Type.TNumber>;
164
+ bin: Type.TString;
165
+ args: Type.TOptional<Type.TArray<Type.TString>>;
166
+ cwd: Type.TOptional<Type.TString>;
167
+ env: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
168
+ config: Type.TOptional<Type.TString>;
169
+ languageIdByExtension: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
170
+ startupTimeoutMs: Type.TOptional<Type.TNumber>;
171
+ diagnosticsWaitMs: Type.TOptional<Type.TNumber>;
172
+ initializationOptions: Type.TOptional<Type.TUnknown>;
173
+ settings: Type.TOptional<Type.TUnknown>;
174
+ }>>>;
175
+ }>>;
176
+ }>;
177
+ export type PiToolsSuiteConfigSchemaType = Static<typeof PiToolsSuiteConfigSchema>;