xtrm-tools 0.7.11 → 0.7.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 (26) hide show
  1. package/.xtrm/hooks/specialists/specialists-memory-cache-sync.mjs +57 -0
  2. package/.xtrm/registry.json +477 -389
  3. package/.xtrm/skills/default/premortem/SKILL.md +218 -0
  4. package/.xtrm/skills/default/releasing/SKILL.md +90 -0
  5. package/.xtrm/skills/default/sync-docs/SKILL.md +88 -208
  6. package/.xtrm/skills/default/sync-docs/scripts/pre-context.sh +17 -0
  7. package/.xtrm/skills/default/update-specialists/SKILL.md +448 -0
  8. package/.xtrm/skills/default/update-xt/SKILL.md +34 -0
  9. package/.xtrm/skills/default/using-kpi/SKILL.md +150 -0
  10. package/.xtrm/skills/default/using-specialists-v2/SKILL.md +683 -0
  11. package/cli/dist/index.cjs +839 -429
  12. package/cli/dist/index.cjs.map +1 -1
  13. package/cli/package.json +1 -1
  14. package/package.json +2 -2
  15. package/packages/pi-extensions/.serena/project.yml +119 -0
  16. package/packages/pi-extensions/extensions/pi-serena-compact/index.ts +4 -12
  17. package/packages/pi-extensions/extensions/xtrm-loader/index.ts +0 -1
  18. package/packages/pi-extensions/extensions/xtrm-ui/index.ts +201 -36
  19. package/packages/pi-extensions/extensions/xtrm-ui/themes/pidex-dark-flattools.json +79 -0
  20. package/packages/pi-extensions/extensions/xtrm-ui/themes/pidex-dark.json +85 -0
  21. package/packages/pi-extensions/extensions/xtrm-ui/themes/pidex-light-flattools.json +79 -0
  22. package/packages/pi-extensions/extensions/xtrm-ui/themes/pidex-light.json +85 -0
  23. package/packages/pi-extensions/package.json +1 -1
  24. package/packages/pi-extensions/themes/xtrm-ui/pidex-dark-flattools.json +79 -0
  25. package/packages/pi-extensions/themes/xtrm-ui/pidex-dark.json +3 -3
  26. package/packages/pi-extensions/themes/xtrm-ui/pidex-light-flattools.json +79 -0
@@ -32,7 +32,7 @@ import {
32
32
  createWriteTool,
33
33
  } from "@mariozechner/pi-coding-agent";
34
34
  import { Box, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
35
- import { existsSync, readFileSync } from "node:fs";
35
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
36
36
  import { basename, join } from "node:path";
37
37
  import {
38
38
  cleanOutputLines,
@@ -54,7 +54,7 @@ import {
54
54
  // Types
55
55
  // ============================================================================
56
56
 
57
- export type XtrmThemeName = "pidex-dark" | "pidex-light";
57
+ export type XtrmThemeName = "pidex-dark" | "pidex-light" | "pidex-dark-flattools" | "pidex-light-flattools";
58
58
  export type XtrmDensity = "compact" | "comfortable";
59
59
 
60
60
  export interface XtrmUiPrefs {
@@ -64,6 +64,8 @@ export interface XtrmUiPrefs {
64
64
  compactTools: boolean;
65
65
  showFooter: boolean; // Our key addition - when false, skip setFooter()
66
66
  forceTheme: boolean; // When false, skip setTheme (allow external theme override)
67
+ toolRowBg: boolean; // Subtle background behind tool text rows (no padding)
68
+ compactExternalToolResults: boolean; // Compact extension tool results (disables full expand output)
67
69
  }
68
70
 
69
71
  // ============================================================================
@@ -79,6 +81,8 @@ export const DEFAULT_PREFS: XtrmUiPrefs = {
79
81
  compactTools: true,
80
82
  showFooter: false, // XTRM: disable pi-dex footer, use custom-footer
81
83
  forceTheme: true,
84
+ toolRowBg: false,
85
+ compactExternalToolResults: true,
82
86
  };
83
87
 
84
88
  // ============================================================================
@@ -95,12 +99,15 @@ function normalizePrefs(input: unknown): XtrmUiPrefs {
95
99
  if (!input || typeof input !== "object") return { ...DEFAULT_PREFS };
96
100
  const source = input as Partial<XtrmUiPrefs>;
97
101
  return {
98
- themeName: source.themeName === "pidex-light" ? "pidex-light" : "pidex-dark",
102
+ themeName: source.themeName === "pidex-light" || source.themeName === "pidex-light-flattools" ? "pidex-light" : "pidex-dark",
99
103
  density: source.density === "comfortable" ? "comfortable" : "compact",
100
104
  showHeader: source.showHeader ?? DEFAULT_PREFS.showHeader,
101
105
  compactTools: source.compactTools ?? DEFAULT_PREFS.compactTools,
102
106
  showFooter: source.showFooter ?? DEFAULT_PREFS.showFooter,
103
107
  forceTheme: source.forceTheme ?? DEFAULT_PREFS.forceTheme,
108
+ toolRowBg: source.toolRowBg ?? DEFAULT_PREFS.toolRowBg,
109
+ compactExternalToolResults:
110
+ source.compactExternalToolResults ?? DEFAULT_PREFS.compactExternalToolResults,
104
111
  };
105
112
  }
106
113
 
@@ -127,6 +134,12 @@ function fitVisible(text: string, width: number): string {
127
134
  return truncated + " ".repeat(Math.max(0, width - visibleWidth(truncated)));
128
135
  }
129
136
 
137
+ function resolveThemeForPrefs(prefs: XtrmUiPrefs): XtrmThemeName {
138
+ const base = prefs.themeName === "pidex-light" || prefs.themeName === "pidex-light-flattools" ? "pidex-light" : "pidex-dark";
139
+ if (!prefs.toolRowBg) return (base + "-flattools") as XtrmThemeName;
140
+ return base as XtrmThemeName;
141
+ }
142
+
130
143
  function formatThinking(level: string): string {
131
144
  return level === "off" ? "standard" : level;
132
145
  }
@@ -137,7 +150,7 @@ function applyXtrmChrome(
137
150
  getThinkingLevel: () => string
138
151
  ): void {
139
152
  // Theme
140
- if (prefs.forceTheme) ctx.ui.setTheme(prefs.themeName);
153
+ ctx.ui.setTheme(resolveThemeForPrefs(prefs));
141
154
 
142
155
  // Tool expansion
143
156
  ctx.ui.setToolsExpanded(!prefs.compactTools);
@@ -222,6 +235,20 @@ function applyXtrmChrome(
222
235
  // If showFooter is false, we do NOT call setFooter - custom-footer will handle it
223
236
  }
224
237
 
238
+
239
+ function writeExternalCompactFlag(enabled: boolean): void {
240
+ try {
241
+ const settingsPath = join(process.env.HOME ?? "", ".pi", "agent", "settings.json");
242
+ if (!settingsPath) return;
243
+ let settings: Record<string, unknown> = {};
244
+ if (existsSync(settingsPath)) {
245
+ try { settings = JSON.parse(readFileSync(settingsPath, "utf8")); } catch {}
246
+ }
247
+ settings.xtrmExternalCompact = enabled;
248
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
249
+ } catch {}
250
+ }
251
+
225
252
  // ============================================================================
226
253
  // Tool Render Helpers
227
254
  // ============================================================================
@@ -318,6 +345,8 @@ function registerCommands(pi: ExtensionAPI, getPrefs: () => XtrmUiPrefs, setPref
318
345
  `Compact tools: ${prefs.compactTools ? "on" : "off"}`,
319
346
  `Show header: ${prefs.showHeader ? "yes" : "no"}`,
320
347
  `Show footer: ${prefs.showFooter ? "yes" : "no"} (custom-footer handles this)`,
348
+ `Tool row background: ${prefs.toolRowBg ? "on" : "off"}`,
349
+ `Compact external tool results: ${prefs.compactExternalToolResults ? "on" : "off"}`,
321
350
  `Model: ${ctx.model?.id ?? "none"}`,
322
351
  `Context: ${contextUsage?.tokens ?? "unknown"}/${contextUsage?.contextWindow ?? "unknown"}`,
323
352
  ];
@@ -357,9 +386,10 @@ function registerCommands(pi: ExtensionAPI, getPrefs: () => XtrmUiPrefs, setPref
357
386
  ctx.ui.notify("Usage: /xtrm-ui-density compact|comfortable", "warning");
358
387
  return;
359
388
  }
360
- const prefs = { ...getPrefs(), density };
389
+ const prefs = { ...getPrefs(), density, compactExternalToolResults: density === "compact" };
361
390
  setPrefs(prefs);
362
391
  persistPrefs(pi, prefs);
392
+ writeExternalCompactFlag(prefs.compactExternalToolResults);
363
393
  applyXtrmChrome(ctx, prefs, getThinkingLevel);
364
394
  ctx.ui.notify(`XTRM UI density set to ${density}`, "info");
365
395
  },
@@ -402,6 +432,51 @@ function registerCommands(pi: ExtensionAPI, getPrefs: () => XtrmUiPrefs, setPref
402
432
  },
403
433
  });
404
434
 
435
+ pi.registerCommand("xtrm-ui-rowbg", {
436
+ description: "Toggle subtle tool-row background: on|off",
437
+ getArgumentCompletions: (prefix) => {
438
+ const values = ["on", "off"].filter((item) => item.startsWith(prefix));
439
+ return values.length > 0 ? values.map((value) => ({ value, label: value })) : null;
440
+ },
441
+ handler: async (args, ctx) => {
442
+ const normalized = args.trim().toLowerCase();
443
+ if (normalized !== "on" && normalized !== "off") {
444
+ ctx.ui.notify("Usage: /xtrm-ui-rowbg on|off", "warning");
445
+ return;
446
+ }
447
+ const toolRowBg = normalized === "on";
448
+ const prefs = { ...getPrefs(), toolRowBg };
449
+ setPrefs(prefs);
450
+ persistPrefs(pi, prefs);
451
+ applyXtrmChrome(ctx, prefs, getThinkingLevel);
452
+ ctx.ui.notify(`Tool row background ${toolRowBg ? "enabled" : "disabled"}.`, "info");
453
+ },
454
+ });
455
+
456
+ pi.registerCommand("xtrm-ui-compact-tools", {
457
+ description: "Compact extension tool results: on|off (off keeps full Ctrl+O expand output)",
458
+ getArgumentCompletions: (prefix) => {
459
+ const values = ["on", "off"].filter((item) => item.startsWith(prefix));
460
+ return values.length > 0 ? values.map((value) => ({ value, label: value })) : null;
461
+ },
462
+ handler: async (args, ctx) => {
463
+ const normalized = args.trim().toLowerCase();
464
+ if (normalized !== "on" && normalized !== "off") {
465
+ ctx.ui.notify("Usage: /xtrm-ui-compact-tools on|off", "warning");
466
+ return;
467
+ }
468
+ const compactExternalToolResults = normalized === "on";
469
+ const prefs = { ...getPrefs(), compactExternalToolResults };
470
+ setPrefs(prefs);
471
+ persistPrefs(pi, prefs);
472
+ writeExternalCompactFlag(compactExternalToolResults);
473
+ ctx.ui.notify(
474
+ `Compact external tool results ${compactExternalToolResults ? "enabled" : "disabled"}.`,
475
+ "info",
476
+ );
477
+ },
478
+ });
479
+
405
480
  pi.registerCommand("xtrm-ui-reset", {
406
481
  description: "Restore XTRM UI defaults",
407
482
  handler: async (_args, ctx) => {
@@ -743,8 +818,84 @@ function summarizeSerenaToolResult(
743
818
  }
744
819
  }
745
820
 
746
- function registerXtrmUiTools(pi: ExtensionAPI): void {
821
+
822
+ function formatHierarchyText(text: string): string {
823
+ const lines = text.split("\n");
824
+ const out: string[] = [];
825
+ for (const raw of lines) {
826
+ const line = raw.trimEnd();
827
+ if (!line.trim()) {
828
+ out.push("");
829
+ continue;
830
+ }
831
+ if (line.startsWith("● ")) {
832
+ out.push(line);
833
+ continue;
834
+ }
835
+ if (/^(Read|Searched|Listed|Update\(|File must be read first|Now )/.test(line.trimStart())) {
836
+ out.push(` └─ ${line.trimStart()}`);
837
+ continue;
838
+ }
839
+ out.push(line);
840
+ }
841
+ return out.join("\n");
842
+ }
843
+
844
+ function normalizeToolLabel(toolName: string): string {
845
+ const gitnexusMap: Record<string, string> = {
846
+ gitnexus_query: "gitnexus query",
847
+ gitnexus_context: "gitnexus context",
848
+ gitnexus_impact: "gitnexus impact",
849
+ gitnexus_detect_changes: "gitnexus detect_changes",
850
+ gitnexus_list_repos: "gitnexus list_repos",
851
+ gitnexus_rename: "gitnexus rename",
852
+ gitnexus_cypher: "gitnexus cypher",
853
+ };
854
+
855
+ if (gitnexusMap[toolName]) return gitnexusMap[toolName];
856
+
857
+ if (toolName.startsWith("gitnexus_")) {
858
+ return `gitnexus ${toolName.slice("gitnexus_".length)}`;
859
+ }
860
+
861
+ const idx = toolName.indexOf("_");
862
+ if (idx > 0) {
863
+ const head = toolName.slice(0, idx);
864
+ const tail = toolName.slice(idx + 1);
865
+ if (head && tail) return `${head} ${tail}`;
866
+ }
867
+
868
+ return toolName;
869
+ }
870
+
871
+ function summarizeGenericToolResult(
872
+ toolName: string,
873
+ input: Record<string, unknown>,
874
+ text: string,
875
+ durationMs: number | undefined,
876
+ ): string {
877
+ const payload = parseJson(text);
878
+ const duration = formatDuration(durationMs);
879
+ const subject = summarizeToolSubject(toolName, input) ?? summarizeSerenaSubject(toolName, input);
880
+ const count = countJsonItems(payload) ?? countLines(text);
881
+ const label = formatLineLabel(count, "line");
882
+ const joined = joinMeta([label, duration]);
883
+ const normalized = normalizeToolLabel(toolName);
884
+ return `• ${normalized}${subject ? ` ${subject}` : ""}${joined ? ` · ${joined}` : ""}`;
885
+ }
886
+
887
+ const XTRM_BUILTIN_TOOLS = new Set(["bash", "read", "edit", "write", "find", "grep", "ls"]);
888
+
889
+ function registerXtrmUiTools(pi: ExtensionAPI, getPrefs: () => XtrmUiPrefs): void {
747
890
  const activeToolCalls = new Map<string, string>();
891
+
892
+ const toolRowText = (theme: any, text: string) =>
893
+ new Text(
894
+ text,
895
+ 0,
896
+ 0,
897
+ getPrefs().toolRowBg ? (line: string) => theme.bg("selectedBg", line) : undefined,
898
+ );
748
899
  const activeSignatureCounts = new Map<string, number>();
749
900
  const toolCallStartTimes = new Map<string, number>();
750
901
 
@@ -769,7 +920,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
769
920
  activeSignatureCounts.has(stableToolSignature(toolName, args));
770
921
 
771
922
  const renderPendingCallIfActive = (toolName: string, args: Record<string, unknown>, theme: any) =>
772
- isToolCallActive(toolName, args) ? renderPendingCall(toolName, args, theme) : new Text("", 0, 0);
923
+ isToolCallActive(toolName, args) ? renderPendingCall(toolName, args, theme) : toolRowText(theme, "");
773
924
 
774
925
  pi.on("tool_call", async (event) => {
775
926
  trackToolCallStart(event.toolCallId, event.toolName, event.input as Record<string, unknown>);
@@ -779,20 +930,28 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
779
930
  trackToolCallEnd(event.toolCallId);
780
931
  });
781
932
 
782
- pi.on("tool_result", async (event: ToolResultEvent, ctx) => {
783
- if (!SERENA_COMPACT_TOOLS.has(event.toolName)) return undefined;
784
- if (ctx.ui.getToolsExpanded()) return undefined;
933
+ pi.on("tool_result", async (event: ToolResultEvent, _ctx) => {
785
934
  if (event.isError) return undefined;
935
+ if (XTRM_BUILTIN_TOOLS.has(event.toolName)) return undefined;
786
936
 
787
937
  const text = getTextContent({ content: event.content as Array<{ type: string; text?: string }> });
788
- if (!text.trim()) return undefined;
789
-
790
938
  const startedAt = toolCallStartTimes.get(event.toolCallId);
791
939
  const durationMs = startedAt != null ? Date.now() - startedAt : undefined;
792
- const compactText = summarizeSerenaToolResult(event.toolName, event.input, text, durationMs);
940
+
941
+ const fallbackText = "[non-text result]";
942
+ const sourceText = text.trim() ? text : fallbackText;
943
+
944
+ const safeInput =
945
+ event.input && typeof event.input === "object" && !Array.isArray(event.input)
946
+ ? (event.input as Record<string, unknown>)
947
+ : {};
948
+
949
+ const compactText = SERENA_COMPACT_TOOLS.has(event.toolName)
950
+ ? summarizeSerenaToolResult(event.toolName, safeInput, sourceText, durationMs)
951
+ : summarizeGenericToolResult(event.toolName, safeInput, sourceText, durationMs);
793
952
 
794
953
  return {
795
- content: [{ type: "text", text: compactText }],
954
+ content: [{ type: "text", text: formatHierarchyText(compactText) }],
796
955
  details: event.details,
797
956
  };
798
957
  });
@@ -802,6 +961,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
802
961
  label: "bash",
803
962
  description: getTools(process.cwd()).bash.description,
804
963
  parameters: getTools(process.cwd()).bash.parameters,
964
+ renderShell: "self",
805
965
  async execute(toolCallId, params, signal, onUpdate, ctx) {
806
966
  const started = Date.now();
807
967
  const result = await getTools(ctx.cwd).bash.execute(toolCallId, params, signal, onUpdate);
@@ -813,7 +973,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
813
973
  const meta = getXtrmMeta<BashToolDetails, Record<string, unknown>>(details);
814
974
  const command = shortenCommand(String(meta?.args.command ?? ""));
815
975
  if (isPartial) {
816
- return new Text(`${theme.fg("accent", "•")} ${theme.fg("toolTitle", "Running ")}${theme.fg("accent", command)}${theme.fg("toolTitle", " in bash")}`, 0, 0);
976
+ return toolRowText(theme, `${theme.fg("accent", "•")} ${theme.fg("toolTitle", "Running ")}${theme.fg("accent", command)}${theme.fg("toolTitle", " in bash")}`);
817
977
  }
818
978
  const output = getTextContent(result as any);
819
979
  const outputLines = cleanOutputLines(output);
@@ -824,7 +984,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
824
984
  let text = `${bullet} ${theme.fg("toolTitle", "Ran ")}${theme.fg("accent", command)}`;
825
985
  if (summary) text += theme.fg("dim", ` · ${summary}`);
826
986
  if (expanded && outputLines.length > 0) text += `\n${renderVerticalPreview(theme, outputLines, 10)}`;
827
- return new Text(text, 0, 0);
987
+ return toolRowText(theme, text);
828
988
  },
829
989
  });
830
990
 
@@ -833,6 +993,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
833
993
  label: "read",
834
994
  description: getTools(process.cwd()).read.description,
835
995
  parameters: getTools(process.cwd()).read.parameters,
996
+ renderShell: "self",
836
997
  async execute(toolCallId, params, signal, onUpdate, ctx) {
837
998
  const started = Date.now();
838
999
  const result = await getTools(ctx.cwd).read.execute(toolCallId, params, signal, onUpdate);
@@ -840,7 +1001,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
840
1001
  },
841
1002
  renderCall: (args, theme) => renderPendingCallIfActive("read", args as Record<string, unknown>, theme),
842
1003
  renderResult(result, { expanded, isPartial }, theme) {
843
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "read", "loading", undefined), 0, 0);
1004
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "read", "loading", undefined));
844
1005
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<ReadToolDetails, Record<string, unknown>>;
845
1006
  const meta = getXtrmMeta<ReadToolDetails, Record<string, unknown>>(details);
846
1007
  const subjectBase = shortenPath(String(meta?.args.path ?? ""));
@@ -848,13 +1009,13 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
848
1009
  const subject = range ? `${subjectBase}:${range}` : subjectBase;
849
1010
  const first = result.content[0];
850
1011
  if (first?.type === "image") {
851
- return new Text(renderToolSummary(theme, "success", "read", subject, joinMeta(["image", formatDuration(meta?.durationMs)])), 0, 0);
1012
+ return toolRowText(theme, renderToolSummary(theme, "success", "read", subject, joinMeta(["image", formatDuration(meta?.durationMs)])));
852
1013
  }
853
1014
  const textContent = getTextContent(result as any);
854
1015
  const lines = textContent.split("\n");
855
1016
  let text = renderToolSummary(theme, "success", "read", subject, joinMeta([formatLineLabel(lines.length, "line"), formatDuration(meta?.durationMs), details.truncation?.truncated ? `from ${details.truncation.totalLines}` : undefined]));
856
1017
  if (expanded && textContent.length > 0) text += `\n${renderOutputPreview(theme, previewLines(textContent, 14), 14)}`;
857
- return new Text(text, 0, 0);
1018
+ return toolRowText(theme, text);
858
1019
  },
859
1020
  });
860
1021
 
@@ -863,6 +1024,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
863
1024
  label: "edit",
864
1025
  description: getTools(process.cwd()).edit.description,
865
1026
  parameters: getTools(process.cwd()).edit.parameters,
1027
+ renderShell: "self",
866
1028
  async execute(toolCallId, params, signal, onUpdate, ctx) {
867
1029
  const started = Date.now();
868
1030
  const result = await getTools(ctx.cwd).edit.execute(toolCallId, params, signal, onUpdate);
@@ -870,17 +1032,17 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
870
1032
  },
871
1033
  renderCall: (args, theme) => renderPendingCallIfActive("edit", args as Record<string, unknown>, theme),
872
1034
  renderResult(result, { expanded, isPartial }, theme) {
873
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "edit", "applying", undefined), 0, 0);
1035
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "edit", "applying", undefined));
874
1036
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<EditToolDetails, Record<string, unknown>>;
875
1037
  const meta = getXtrmMeta<EditToolDetails, Record<string, unknown>>(details);
876
1038
  const textContent = getTextContent(result as any);
877
1039
  if (/^error/i.test(textContent.trim())) {
878
- return new Text(renderToolSummary(theme, "error", "edit", shortenPath(String(meta?.args.path ?? "")), textContent.split("\n")[0]), 0, 0);
1040
+ return toolRowText(theme, renderToolSummary(theme, "error", "edit", shortenPath(String(meta?.args.path ?? "")), textContent.split("\n")[0]));
879
1041
  }
880
1042
  const stats = details.diff ? diffStats(details.diff) : { additions: 0, removals: 0 };
881
1043
  let text = renderToolSummary(theme, "success", "edit", shortenPath(String(meta?.args.path ?? "")), joinMeta([`+${stats.additions}`, `-${stats.removals}`, formatDuration(meta?.durationMs)]));
882
1044
  if (expanded && details.diff) text += `\n${renderRichDiffPreview(theme, details.diff, 18)}`;
883
- return new Text(text, 0, 0);
1045
+ return toolRowText(theme, text);
884
1046
  },
885
1047
  });
886
1048
 
@@ -889,6 +1051,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
889
1051
  label: "write",
890
1052
  description: getTools(process.cwd()).write.description,
891
1053
  parameters: getTools(process.cwd()).write.parameters,
1054
+ renderShell: "self",
892
1055
  async execute(toolCallId, params, signal, onUpdate, ctx) {
893
1056
  const started = Date.now();
894
1057
  const args = params as Record<string, unknown>;
@@ -901,19 +1064,19 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
901
1064
  },
902
1065
  renderCall: (args, theme) => renderPendingCallIfActive("write", args as Record<string, unknown>, theme),
903
1066
  renderResult(result, { expanded, isPartial }, theme) {
904
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "write", "writing", undefined), 0, 0);
1067
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "write", "writing", undefined));
905
1068
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<Record<string, never>, Record<string, unknown>>;
906
1069
  const meta = getXtrmMeta<Record<string, never>, Record<string, unknown>>(details);
907
1070
  const textContent = getTextContent(result as any);
908
1071
  if (/^error/i.test(textContent.trim())) {
909
- return new Text(renderToolSummary(theme, "error", "write", shortenPath(String(meta?.args.path ?? "")), textContent.split("\n")[0]), 0, 0);
1072
+ return toolRowText(theme, renderToolSummary(theme, "error", "write", shortenPath(String(meta?.args.path ?? "")), textContent.split("\n")[0]));
910
1073
  }
911
1074
 
912
1075
  const subject = shortenPath(String(meta?.args.path ?? ""));
913
1076
  const preview = details.xtrmWritePreview;
914
1077
 
915
1078
  if (preview?.kind === "unchanged") {
916
- return new Text(renderToolSummary(theme, "success", "write", subject, joinMeta(["no changes", formatDuration(meta?.durationMs)])), 0, 0);
1079
+ return toolRowText(theme, renderToolSummary(theme, "success", "write", subject, joinMeta(["no changes", formatDuration(meta?.durationMs)])));
917
1080
  }
918
1081
 
919
1082
  if (preview?.kind === "updated") {
@@ -925,15 +1088,14 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
925
1088
  joinMeta([`+${preview.additions}`, `-${preview.removals}`, formatDuration(meta?.durationMs)]),
926
1089
  );
927
1090
  if (expanded && preview.diff) text += `\n${renderRichDiffPreview(theme, preview.diff, 18)}`;
928
- return new Text(text, 0, 0);
1091
+ return toolRowText(theme, text);
929
1092
  }
930
1093
 
931
1094
  const lines = preview?.kind === "created"
932
1095
  ? preview.lineCount
933
1096
  : lineCount(String(meta?.args.content ?? ""));
934
1097
 
935
- return new Text(
936
- renderToolSummary(
1098
+ return toolRowText(theme, renderToolSummary(
937
1099
  theme,
938
1100
  "success",
939
1101
  "write",
@@ -951,6 +1113,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
951
1113
  label: "find",
952
1114
  description: getTools(process.cwd()).find.description,
953
1115
  parameters: getTools(process.cwd()).find.parameters,
1116
+ renderShell: "self",
954
1117
  async execute(toolCallId, params, signal, onUpdate, ctx) {
955
1118
  const started = Date.now();
956
1119
  const result = await getTools(ctx.cwd).find.execute(toolCallId, params, signal, onUpdate);
@@ -958,14 +1121,14 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
958
1121
  },
959
1122
  renderCall: (args, theme) => renderPendingCallIfActive("find", args as Record<string, unknown>, theme),
960
1123
  renderResult(result, { expanded, isPartial }, theme) {
961
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "find", "searching", undefined), 0, 0);
1124
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "find", "searching", undefined));
962
1125
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<FindToolDetails, Record<string, unknown>>;
963
1126
  const meta = getXtrmMeta<FindToolDetails, Record<string, unknown>>(details);
964
1127
  const textContent = getTextContent(result as any);
965
1128
  const count = summarizeCount(textContent);
966
1129
  let text = renderToolSummary(theme, "success", "find", String(meta?.args.pattern ?? ""), joinMeta([formatLineLabel(count, "match"), formatDuration(meta?.durationMs), details.resultLimitReached ? "limit reached" : undefined]));
967
1130
  if (expanded && count > 0) text += `\n${renderOutputPreview(theme, previewLines(textContent, 10), 10)}`;
968
- return new Text(text, 0, 0);
1131
+ return toolRowText(theme, text);
969
1132
  },
970
1133
  });
971
1134
 
@@ -974,6 +1137,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
974
1137
  label: "grep",
975
1138
  description: getTools(process.cwd()).grep.description,
976
1139
  parameters: getTools(process.cwd()).grep.parameters,
1140
+ renderShell: "self",
977
1141
  async execute(toolCallId, params, signal, onUpdate, ctx) {
978
1142
  const started = Date.now();
979
1143
  const result = await getTools(ctx.cwd).grep.execute(toolCallId, params, signal, onUpdate);
@@ -981,14 +1145,14 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
981
1145
  },
982
1146
  renderCall: (args, theme) => renderPendingCallIfActive("grep", args as Record<string, unknown>, theme),
983
1147
  renderResult(result, { expanded, isPartial }, theme) {
984
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "grep", "searching", undefined), 0, 0);
1148
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "grep", "searching", undefined));
985
1149
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<GrepToolDetails, Record<string, unknown>>;
986
1150
  const meta = getXtrmMeta<GrepToolDetails, Record<string, unknown>>(details);
987
1151
  const textContent = getTextContent(result as any);
988
1152
  const count = countPrefixedItems(textContent, ["-- "]) || summarizeCount(textContent);
989
1153
  let text = renderToolSummary(theme, "success", "grep", String(meta?.args.pattern ?? ""), joinMeta([formatLineLabel(count, "match"), formatDuration(meta?.durationMs), details.matchLimitReached ? "limit reached" : undefined]));
990
1154
  if (expanded && textContent.length > 0) text += `\n${renderOutputPreview(theme, previewLines(textContent, 12), 12)}`;
991
- return new Text(text, 0, 0);
1155
+ return toolRowText(theme, text);
992
1156
  },
993
1157
  });
994
1158
 
@@ -997,6 +1161,7 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
997
1161
  label: "ls",
998
1162
  description: getTools(process.cwd()).ls.description,
999
1163
  parameters: getTools(process.cwd()).ls.parameters,
1164
+ renderShell: "self",
1000
1165
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1001
1166
  const started = Date.now();
1002
1167
  const result = await getTools(ctx.cwd).ls.execute(toolCallId, params, signal, onUpdate);
@@ -1004,14 +1169,14 @@ function registerXtrmUiTools(pi: ExtensionAPI): void {
1004
1169
  },
1005
1170
  renderCall: (args, theme) => renderPendingCallIfActive("ls", args as Record<string, unknown>, theme),
1006
1171
  renderResult(result, { expanded, isPartial }, theme) {
1007
- if (isPartial) return new Text(renderToolSummary(theme, "pending", "ls", "listing", undefined), 0, 0);
1172
+ if (isPartial) return toolRowText(theme, renderToolSummary(theme, "pending", "ls", "listing", undefined));
1008
1173
  const details = (result.details ?? {}) as DetailsWithXtrmMeta<LsToolDetails, Record<string, unknown>>;
1009
1174
  const meta = getXtrmMeta<LsToolDetails, Record<string, unknown>>(details);
1010
1175
  const textContent = getTextContent(result as any);
1011
1176
  const count = summarizeCount(textContent);
1012
1177
  let text = renderToolSummary(theme, "success", "ls", shortenPath(String(meta?.args.path ?? ".")), joinMeta([formatLineLabel(count, "entry"), formatDuration(meta?.durationMs), details.entryLimitReached ? "limit reached" : undefined]));
1013
1178
  if (expanded && count > 0) text += `\n${renderOutputPreview(theme, previewLines(textContent, 12), 12)}`;
1014
- return new Text(text, 0, 0);
1179
+ return toolRowText(theme, text);
1015
1180
  },
1016
1181
  });
1017
1182
  }
@@ -1033,7 +1198,7 @@ export default function xtrmUiExtension(pi: ExtensionAPI): void {
1033
1198
  const setPrefs = (p: XtrmUiPrefs) => { prefs = p; };
1034
1199
  const getThinkingLevel = () => formatThinking(pi.getThinkingLevel());
1035
1200
 
1036
- registerXtrmUiTools(pi);
1201
+ registerXtrmUiTools(pi, getPrefs);
1037
1202
  registerCommands(pi, getPrefs, setPrefs, getThinkingLevel);
1038
1203
 
1039
1204
  const refresh = (ctx: ExtensionContext) => {
@@ -1052,7 +1217,7 @@ export default function xtrmUiExtension(pi: ExtensionAPI): void {
1052
1217
  refresh(ctx);
1053
1218
 
1054
1219
  setTimeout(() => {
1055
- if (prefs.forceTheme) ctx.ui.setTheme(prefs.themeName);
1220
+ ctx.ui.setTheme(resolveThemeForPrefs(prefs));
1056
1221
  }, 0);
1057
1222
  });
1058
1223
 
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "pidex-dark-flattools",
4
+ "vars": {
5
+ "accentBlue": "#b8d3ff",
6
+ "accentCyan": "#b8d3ff",
7
+ "accentTeal": "#c7d2e0",
8
+ "successGreen": "#9fd59f",
9
+ "errorRed": "#ff9a9a",
10
+ "warningAmber": "#d2b48c",
11
+ "surface": "#000000",
12
+ "surfaceAlt": "#111111",
13
+ "surfaceMuted": "#1a1a1a",
14
+ "surfaceUser": "#1f1f1f",
15
+ "surfaceCustom": "#161616",
16
+ "gray": "#a7a7a7",
17
+ "dimGray": "#8a8a8a",
18
+ "borderGray": "#666666",
19
+ "borderBright": "#9a9a9a"
20
+ },
21
+ "colors": {
22
+ "accent": "accentBlue",
23
+ "border": "borderGray",
24
+ "borderAccent": "borderBright",
25
+ "borderMuted": "borderGray",
26
+ "success": "successGreen",
27
+ "error": "errorRed",
28
+ "warning": "warningAmber",
29
+ "muted": "gray",
30
+ "dim": "dimGray",
31
+ "text": "",
32
+ "thinkingText": "gray",
33
+ "selectedBg": "surfaceMuted",
34
+ "userMessageBg": "surfaceUser",
35
+ "userMessageText": "",
36
+ "customMessageBg": "surfaceCustom",
37
+ "customMessageText": "",
38
+ "customMessageLabel": "accentBlue",
39
+ "toolPendingBg": "",
40
+ "toolSuccessBg": "",
41
+ "toolErrorBg": "",
42
+ "toolTitle": "",
43
+ "toolOutput": "gray",
44
+ "mdHeading": "warningAmber",
45
+ "mdLink": "accentBlue",
46
+ "mdLinkUrl": "dimGray",
47
+ "mdCode": "accentCyan",
48
+ "mdCodeBlock": "gray",
49
+ "mdCodeBlockBorder": "borderGray",
50
+ "mdQuote": "gray",
51
+ "mdQuoteBorder": "borderGray",
52
+ "mdHr": "borderGray",
53
+ "mdListBullet": "accentTeal",
54
+ "toolDiffAdded": "successGreen",
55
+ "toolDiffRemoved": "errorRed",
56
+ "toolDiffContext": "gray",
57
+ "syntaxComment": "#6b7280",
58
+ "syntaxKeyword": "#7aa2f7",
59
+ "syntaxFunction": "#c0caf5",
60
+ "syntaxVariable": "#a9b1d6",
61
+ "syntaxString": "#9ece6a",
62
+ "syntaxNumber": "#ff9e64",
63
+ "syntaxType": "#73daca",
64
+ "syntaxOperator": "#c0caf5",
65
+ "syntaxPunctuation": "#8f9bb3",
66
+ "thinkingOff": "borderGray",
67
+ "thinkingMinimal": "#707070",
68
+ "thinkingLow": "#7a7a7a",
69
+ "thinkingMedium": "#858585",
70
+ "thinkingHigh": "#8f8f8f",
71
+ "thinkingXhigh": "#999999",
72
+ "bashMode": "accentTeal"
73
+ },
74
+ "export": {
75
+ "pageBg": "#12161d",
76
+ "cardBg": "#171b22",
77
+ "infoBg": "#28230f"
78
+ }
79
+ }