@oh-my-pi/pi-coding-agent 3.13.1337 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/docs/theme.md +38 -5
  3. package/examples/sdk/11-sessions.ts +2 -2
  4. package/package.json +7 -4
  5. package/src/cli/file-processor.ts +51 -2
  6. package/src/cli/plugin-cli.ts +25 -19
  7. package/src/cli/update-cli.ts +4 -3
  8. package/src/core/agent-session.ts +31 -4
  9. package/src/core/compaction/branch-summarization.ts +4 -32
  10. package/src/core/compaction/compaction.ts +6 -84
  11. package/src/core/compaction/utils.ts +2 -3
  12. package/src/core/custom-tools/types.ts +2 -0
  13. package/src/core/export-html/index.ts +1 -1
  14. package/src/core/hooks/index.ts +1 -1
  15. package/src/core/hooks/tool-wrapper.ts +0 -1
  16. package/src/core/hooks/types.ts +2 -2
  17. package/src/core/plugins/doctor.ts +9 -1
  18. package/src/core/sdk.ts +2 -1
  19. package/src/core/session-manager.ts +552 -41
  20. package/src/core/settings-manager.ts +174 -0
  21. package/src/core/system-prompt.ts +9 -14
  22. package/src/core/title-generator.ts +2 -8
  23. package/src/core/tools/ask.ts +19 -37
  24. package/src/core/tools/bash.ts +2 -37
  25. package/src/core/tools/edit.ts +2 -9
  26. package/src/core/tools/exa/render.ts +52 -48
  27. package/src/core/tools/find.ts +10 -8
  28. package/src/core/tools/grep.ts +45 -17
  29. package/src/core/tools/ls.ts +22 -2
  30. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  31. package/src/core/tools/lsp/clients/index.ts +49 -0
  32. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  33. package/src/core/tools/lsp/config.ts +3 -0
  34. package/src/core/tools/lsp/index.ts +107 -55
  35. package/src/core/tools/lsp/render.ts +192 -79
  36. package/src/core/tools/lsp/types.ts +27 -0
  37. package/src/core/tools/lsp/utils.ts +62 -22
  38. package/src/core/tools/notebook.ts +9 -1
  39. package/src/core/tools/output.ts +37 -14
  40. package/src/core/tools/read.ts +349 -34
  41. package/src/core/tools/renderers.ts +290 -89
  42. package/src/core/tools/review.ts +12 -5
  43. package/src/core/tools/task/agents.ts +5 -5
  44. package/src/core/tools/task/commands.ts +3 -3
  45. package/src/core/tools/task/executor.ts +33 -1
  46. package/src/core/tools/task/index.ts +93 -6
  47. package/src/core/tools/task/render.ts +147 -66
  48. package/src/core/tools/task/types.ts +14 -9
  49. package/src/core/tools/web-fetch.ts +242 -103
  50. package/src/core/tools/web-search/index.ts +64 -20
  51. package/src/core/tools/web-search/providers/exa.ts +68 -172
  52. package/src/core/tools/web-search/render.ts +264 -74
  53. package/src/core/tools/write.ts +2 -8
  54. package/src/main.ts +10 -6
  55. package/src/modes/cleanup.ts +23 -0
  56. package/src/modes/index.ts +9 -4
  57. package/src/modes/interactive/components/bash-execution.ts +6 -3
  58. package/src/modes/interactive/components/branch-summary-message.ts +1 -1
  59. package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
  60. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  61. package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
  62. package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
  63. package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
  64. package/src/modes/interactive/components/hook-message.ts +2 -2
  65. package/src/modes/interactive/components/hook-selector.ts +1 -1
  66. package/src/modes/interactive/components/model-selector.ts +22 -9
  67. package/src/modes/interactive/components/oauth-selector.ts +20 -4
  68. package/src/modes/interactive/components/plugin-settings.ts +4 -2
  69. package/src/modes/interactive/components/session-selector.ts +9 -6
  70. package/src/modes/interactive/components/settings-defs.ts +285 -1
  71. package/src/modes/interactive/components/settings-selector.ts +176 -3
  72. package/src/modes/interactive/components/status-line/index.ts +4 -0
  73. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  74. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  75. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  76. package/src/modes/interactive/components/status-line/types.ts +81 -0
  77. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  78. package/src/modes/interactive/components/status-line.ts +169 -233
  79. package/src/modes/interactive/components/tool-execution.ts +446 -211
  80. package/src/modes/interactive/components/tree-selector.ts +17 -6
  81. package/src/modes/interactive/components/ttsr-notification.ts +4 -4
  82. package/src/modes/interactive/components/welcome.ts +27 -19
  83. package/src/modes/interactive/interactive-mode.ts +98 -13
  84. package/src/modes/interactive/theme/dark.json +3 -2
  85. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  86. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  87. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  88. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  89. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  90. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  91. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  92. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  93. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  94. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  95. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  96. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  97. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  98. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  99. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  100. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  101. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  102. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  103. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  104. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  105. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  106. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  107. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  108. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  109. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  110. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  111. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  112. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  113. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  114. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  115. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  116. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  117. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  118. package/src/modes/interactive/theme/light.json +3 -2
  119. package/src/modes/interactive/theme/theme-schema.json +120 -4
  120. package/src/modes/interactive/theme/theme.ts +1228 -14
  121. package/src/prompts/branch-summary-preamble.md +3 -0
  122. package/src/prompts/branch-summary.md +28 -0
  123. package/src/prompts/compaction-summary.md +34 -0
  124. package/src/prompts/compaction-turn-prefix.md +16 -0
  125. package/src/prompts/compaction-update-summary.md +41 -0
  126. package/src/prompts/init.md +30 -0
  127. package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
  128. package/src/prompts/summarization-system.md +3 -0
  129. package/src/prompts/system-prompt.md +27 -0
  130. package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
  131. package/src/prompts/title-system.md +8 -0
  132. package/src/prompts/tools/ask.md +24 -0
  133. package/src/prompts/tools/bash.md +23 -0
  134. package/src/prompts/tools/edit.md +9 -0
  135. package/src/prompts/tools/find.md +6 -0
  136. package/src/prompts/tools/grep.md +12 -0
  137. package/src/prompts/tools/lsp.md +14 -0
  138. package/src/prompts/tools/output.md +23 -0
  139. package/src/prompts/tools/read.md +25 -0
  140. package/src/prompts/tools/web-fetch.md +8 -0
  141. package/src/prompts/tools/web-search.md +10 -0
  142. package/src/prompts/tools/write.md +10 -0
  143. package/src/commands/init.md +0 -20
  144. /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
  145. /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
  146. /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
  147. /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
  148. /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
  149. /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
4
4
  import { getAgentDir } from "../config";
5
5
  import { loadSync } from "../discovery";
6
+ import type { SymbolPreset } from "../modes/interactive/theme/theme";
6
7
 
7
8
  export interface CompactionSettings {
8
9
  enabled?: boolean; // default: true
@@ -68,6 +69,8 @@ export interface EditSettings {
68
69
  fuzzyMatch?: boolean; // default: true (accept high-confidence fuzzy matches for whitespace/indentation)
69
70
  }
70
71
 
72
+ export type { SymbolPreset };
73
+
71
74
  export interface TtsrSettings {
72
75
  enabled?: boolean; // default: true
73
76
  /** What to do with partial output when TTSR triggers: "keep" shows interrupted attempt, "discard" removes it */
@@ -78,6 +81,45 @@ export interface TtsrSettings {
78
81
  repeatGap?: number; // default: 10
79
82
  }
80
83
 
84
+ export type StatusLineSegmentId =
85
+ | "pi"
86
+ | "model"
87
+ | "path"
88
+ | "git"
89
+ | "subagents"
90
+ | "token_in"
91
+ | "token_out"
92
+ | "token_total"
93
+ | "cost"
94
+ | "context_pct"
95
+ | "context_total"
96
+ | "time_spent"
97
+ | "time"
98
+ | "session"
99
+ | "hostname"
100
+ | "cache_read"
101
+ | "cache_write";
102
+
103
+ export type StatusLineSeparatorStyle = "powerline" | "powerline-thin" | "slash" | "pipe" | "block" | "none" | "ascii";
104
+
105
+ export type StatusLinePreset = "default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "custom";
106
+
107
+ export interface StatusLineSegmentOptions {
108
+ model?: { showThinkingLevel?: boolean };
109
+ path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
110
+ git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
111
+ time?: { format?: "12h" | "24h"; showSeconds?: boolean };
112
+ }
113
+
114
+ export interface StatusLineSettings {
115
+ preset?: StatusLinePreset;
116
+ leftSegments?: StatusLineSegmentId[];
117
+ rightSegments?: StatusLineSegmentId[];
118
+ separator?: StatusLineSeparatorStyle;
119
+ segmentOptions?: StatusLineSegmentOptions;
120
+ showHookStatus?: boolean;
121
+ }
122
+
81
123
  export interface Settings {
82
124
  lastChangelogVersion?: string;
83
125
  /** Model roles map: { default: "provider/modelId", small: "provider/modelId", ... } */
@@ -86,6 +128,7 @@ export interface Settings {
86
128
  queueMode?: "all" | "one-at-a-time";
87
129
  interruptMode?: "immediate" | "wait";
88
130
  theme?: string;
131
+ symbolPreset?: SymbolPreset; // default: uses theme's preset or "unicode"
89
132
  compaction?: CompactionSettings;
90
133
  branchSummary?: BranchSummarySettings;
91
134
  retry?: RetrySettings;
@@ -106,6 +149,7 @@ export interface Settings {
106
149
  ttsr?: TtsrSettings;
107
150
  disabledProviders?: string[]; // Discovery provider IDs that are disabled
108
151
  disabledExtensions?: string[]; // Individual extension IDs that are disabled (e.g., "skill:commit")
152
+ statusLine?: StatusLineSettings; // Status line configuration
109
153
  }
110
154
 
111
155
  /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
@@ -298,6 +342,15 @@ export class SettingsManager {
298
342
  this.save();
299
343
  }
300
344
 
345
+ getSymbolPreset(): SymbolPreset | undefined {
346
+ return this.settings.symbolPreset;
347
+ }
348
+
349
+ setSymbolPreset(preset: SymbolPreset): void {
350
+ this.globalSettings.symbolPreset = preset;
351
+ this.save();
352
+ }
353
+
301
354
  getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined {
302
355
  return this.settings.defaultThinkingLevel;
303
356
  }
@@ -683,4 +736,125 @@ export class SettingsManager {
683
736
  this.globalSettings.ttsr.repeatGap = gap;
684
737
  this.save();
685
738
  }
739
+
740
+ // ═══════════════════════════════════════════════════════════════════════════
741
+ // Status Line Settings
742
+ // ═══════════════════════════════════════════════════════════════════════════
743
+
744
+ getStatusLineSettings(): StatusLineSettings {
745
+ return this.settings.statusLine ? { ...this.settings.statusLine } : {};
746
+ }
747
+
748
+ getStatusLinePreset(): StatusLinePreset {
749
+ return this.settings.statusLine?.preset ?? "default";
750
+ }
751
+
752
+ setStatusLinePreset(preset: StatusLinePreset): void {
753
+ if (!this.globalSettings.statusLine) {
754
+ this.globalSettings.statusLine = {};
755
+ }
756
+ if (preset !== "custom") {
757
+ delete this.globalSettings.statusLine.leftSegments;
758
+ delete this.globalSettings.statusLine.rightSegments;
759
+ delete this.globalSettings.statusLine.segmentOptions;
760
+ }
761
+ this.globalSettings.statusLine.preset = preset;
762
+ this.save();
763
+ }
764
+
765
+ getStatusLineSeparator(): StatusLineSeparatorStyle {
766
+ return this.settings.statusLine?.separator ?? "powerline-thin";
767
+ }
768
+
769
+ setStatusLineSeparator(separator: StatusLineSeparatorStyle): void {
770
+ if (!this.globalSettings.statusLine) {
771
+ this.globalSettings.statusLine = {};
772
+ }
773
+ this.globalSettings.statusLine.separator = separator;
774
+ this.save();
775
+ }
776
+
777
+ getStatusLineLeftSegments(): StatusLineSegmentId[] {
778
+ return [...(this.settings.statusLine?.leftSegments ?? [])];
779
+ }
780
+
781
+ setStatusLineLeftSegments(segments: StatusLineSegmentId[]): void {
782
+ if (!this.globalSettings.statusLine) {
783
+ this.globalSettings.statusLine = {};
784
+ }
785
+ this.globalSettings.statusLine.leftSegments = segments;
786
+ // Setting segments explicitly implies custom preset
787
+ if (this.globalSettings.statusLine.preset !== "custom") {
788
+ this.globalSettings.statusLine.preset = "custom";
789
+ }
790
+ this.save();
791
+ }
792
+
793
+ getStatusLineRightSegments(): StatusLineSegmentId[] {
794
+ return [...(this.settings.statusLine?.rightSegments ?? [])];
795
+ }
796
+
797
+ setStatusLineRightSegments(segments: StatusLineSegmentId[]): void {
798
+ if (!this.globalSettings.statusLine) {
799
+ this.globalSettings.statusLine = {};
800
+ }
801
+ this.globalSettings.statusLine.rightSegments = segments;
802
+ // Setting segments explicitly implies custom preset
803
+ if (this.globalSettings.statusLine.preset !== "custom") {
804
+ this.globalSettings.statusLine.preset = "custom";
805
+ }
806
+ this.save();
807
+ }
808
+
809
+ getStatusLineSegmentOptions(): StatusLineSegmentOptions {
810
+ return { ...this.settings.statusLine?.segmentOptions };
811
+ }
812
+
813
+ setStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
814
+ segment: K,
815
+ option: keyof NonNullable<StatusLineSegmentOptions[K]>,
816
+ value: boolean | number | string,
817
+ ): void {
818
+ if (!this.globalSettings.statusLine) {
819
+ this.globalSettings.statusLine = {};
820
+ }
821
+ if (!this.globalSettings.statusLine.segmentOptions) {
822
+ this.globalSettings.statusLine.segmentOptions = {};
823
+ }
824
+ if (!this.globalSettings.statusLine.segmentOptions[segment]) {
825
+ this.globalSettings.statusLine.segmentOptions[segment] = {} as NonNullable<StatusLineSegmentOptions[K]>;
826
+ }
827
+ (this.globalSettings.statusLine.segmentOptions[segment] as Record<string, unknown>)[option as string] = value;
828
+ this.save();
829
+ }
830
+
831
+ clearStatusLineSegmentOption<K extends keyof StatusLineSegmentOptions>(
832
+ segment: K,
833
+ option: keyof NonNullable<StatusLineSegmentOptions[K]>,
834
+ ): void {
835
+ const segmentOptions = this.globalSettings.statusLine?.segmentOptions;
836
+ if (!segmentOptions || !segmentOptions[segment]) {
837
+ return;
838
+ }
839
+ delete (segmentOptions[segment] as Record<string, unknown>)[option as string];
840
+ if (Object.keys(segmentOptions[segment] as Record<string, unknown>).length === 0) {
841
+ delete segmentOptions[segment];
842
+ }
843
+ if (Object.keys(segmentOptions).length === 0) {
844
+ delete this.globalSettings.statusLine?.segmentOptions;
845
+ }
846
+ this.save();
847
+ }
848
+
849
+ getStatusLineShowHookStatus(): boolean {
850
+ return this.settings.statusLine?.showHookStatus ?? true;
851
+ }
852
+
853
+ setStatusLineShowHookStatus(show: boolean): void {
854
+ if (!this.globalSettings.statusLine) {
855
+ this.globalSettings.statusLine = {};
856
+ }
857
+ this.globalSettings.statusLine.showHookStatus = show;
858
+ this.save();
859
+ }
686
860
  }
@@ -9,6 +9,7 @@ import type { Rule } from "../capability/rule";
9
9
  import { systemPromptCapability } from "../capability/system-prompt";
10
10
  import { getDocsPath, getExamplesPath, getReadmePath } from "../config";
11
11
  import { type ContextFile, loadSync, type SystemPrompt as SystemPromptFile } from "../discovery/index";
12
+ import systemPromptTemplate from "../prompts/system-prompt.md" with { type: "text" };
12
13
  import type { SkillsSettings } from "./settings-manager";
13
14
  import { formatSkillsForPrompt, loadSkills, type Skill } from "./skills";
14
15
  import type { ToolName } from "./tools/index";
@@ -390,20 +391,14 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
390
391
  const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
391
392
 
392
393
  // Build the prompt with anti-bash rules prominently placed
393
- let prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.
394
-
395
- Available tools:
396
- ${toolsList}
397
- ${antiBashSection ? `\n${antiBashSection}\n` : ""}
398
- Guidelines:
399
- ${guidelines}
400
-
401
- Documentation:
402
- - Main documentation: ${readmePath}
403
- - Additional docs: ${docsPath}
404
- - Examples: ${examplesPath} (hooks, custom tools, SDK)
405
- - When asked to create: custom models/providers (README.md), hooks (docs/hooks.md, examples/hooks/), custom tools (docs/custom-tools.md, docs/tui.md, examples/custom-tools/), themes (docs/theme.md), skills (docs/skills.md)
406
- - Always read the doc, examples, AND follow .md cross-references before implementing`;
394
+ const antiBashBlock = antiBashSection ? `\n${antiBashSection}\n` : "";
395
+ let prompt = systemPromptTemplate
396
+ .replaceAll("{{toolsList}}", toolsList)
397
+ .replaceAll("{{antiBashSection}}", antiBashBlock)
398
+ .replaceAll("{{guidelines}}", guidelines)
399
+ .replaceAll("{{readmePath}}", readmePath)
400
+ .replaceAll("{{docsPath}}", docsPath)
401
+ .replaceAll("{{examplesPath}}", examplesPath);
407
402
 
408
403
  if (appendSection) {
409
404
  prompt += appendSection;
@@ -4,18 +4,12 @@
4
4
 
5
5
  import type { Model } from "@oh-my-pi/pi-ai";
6
6
  import { completeSimple } from "@oh-my-pi/pi-ai";
7
+ import titleSystemPrompt from "../prompts/title-system.md" with { type: "text" };
7
8
  import { logger } from "./logger";
8
9
  import type { ModelRegistry } from "./model-registry";
9
10
  import { findSmolModel } from "./model-resolver";
10
11
 
11
- const TITLE_SYSTEM_PROMPT = `Generate a very short title (3-6 words) for a coding session based on the user's first message. The title should capture the main task or topic. Output ONLY the title, nothing else. No quotes, no punctuation at the end.
12
-
13
- Examples:
14
- - "Fix TypeScript compilation errors"
15
- - "Add user authentication"
16
- - "Refactor database queries"
17
- - "Debug payment webhook"
18
- - "Update React components"`;
12
+ const TITLE_SYSTEM_PROMPT = titleSystemPrompt;
19
13
 
20
14
  const MAX_INPUT_CHARS = 2000;
21
15
 
@@ -17,6 +17,8 @@
17
17
 
18
18
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
19
  import { Type } from "@sinclair/typebox";
20
+ import { theme } from "../../modes/interactive/theme/theme";
21
+ import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
20
22
 
21
23
  // =============================================================================
22
24
  // Types
@@ -53,36 +55,9 @@ export interface AskToolDetails {
53
55
  // =============================================================================
54
56
 
55
57
  const OTHER_OPTION = "Other (type your own)";
56
- const DONE_OPTION = "✓ Done selecting";
57
-
58
- const DESCRIPTION = `Use this tool when you need to ask the user questions during execution. This allows you to:
59
- 1. Gather user preferences or requirements
60
- 2. Clarify ambiguous instructions
61
- 3. Get decisions on implementation choices as you work
62
- 4. Offer choices to the user about what direction to take.
63
-
64
- Usage notes:
65
- - Users will always be able to select "Other" to provide custom text input
66
- - Use multi: true to allow multiple answers to be selected for a question
67
- - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
68
-
69
- Example usage:
70
-
71
- <example>
72
- assistant: Let me ask which features you want to include.
73
- assistant: Uses the ask tool:
74
- {
75
- "question": "Which features should I implement?",
76
- "options": [
77
- {"label": "Authentication"},
78
- {"label": "API endpoints"},
79
- {"label": "Database models"},
80
- {"label": "Unit tests"},
81
- {"label": "Documentation"}
82
- ],
83
- "multi": true
58
+ function getDoneOptionLabel(): string {
59
+ return `${theme.status.success} Done selecting`;
84
60
  }
85
- </example>`;
86
61
 
87
62
  // =============================================================================
88
63
  // Tool Implementation
@@ -92,7 +67,7 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
92
67
  return {
93
68
  name: "ask",
94
69
  label: "Ask",
95
- description: DESCRIPTION,
70
+ description: askDescription,
96
71
  parameters: askSchema,
97
72
 
98
73
  async execute(
@@ -104,6 +79,7 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
104
79
  ) {
105
80
  const { question, options, multi = false } = params;
106
81
  const optionLabels = options.map((o) => o.label);
82
+ const doneLabel = getDoneOptionLabel();
107
83
 
108
84
  // Headless fallback - return error if no UI available
109
85
  if (!context?.hasUI || !context.ui) {
@@ -137,12 +113,12 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
137
113
 
138
114
  // Add "Done" option if any selected
139
115
  if (selected.size > 0) {
140
- opts.push(DONE_OPTION);
116
+ opts.push(doneLabel);
141
117
  }
142
118
 
143
- // Add all options with [X] or [ ] prefix
119
+ // Add all options with checkbox prefix
144
120
  for (const opt of optionLabels) {
145
- const checkbox = selected.has(opt) ? "[X]" : "[ ]";
121
+ const checkbox = selected.has(opt) ? theme.checkbox.checked : theme.checkbox.unchecked;
146
122
  opts.push(`${checkbox} ${opt}`);
147
123
  }
148
124
 
@@ -152,7 +128,7 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
152
128
  const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
153
129
  const choice = await ui.select(`${prefix}${question}`, opts);
154
130
 
155
- if (choice === undefined || choice === DONE_OPTION) break;
131
+ if (choice === undefined || choice === doneLabel) break;
156
132
 
157
133
  if (choice === OTHER_OPTION) {
158
134
  const input = await ui.input("Enter your response:");
@@ -161,9 +137,15 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
161
137
  }
162
138
 
163
139
  // Toggle selection - extract the actual option name
164
- const optMatch = choice.match(/^\[.\] (.+)$/);
165
- if (optMatch) {
166
- const opt = optMatch[1];
140
+ const checkedPrefix = `${theme.checkbox.checked} `;
141
+ const uncheckedPrefix = `${theme.checkbox.unchecked} `;
142
+ let opt: string | undefined;
143
+ if (choice.startsWith(checkedPrefix)) {
144
+ opt = choice.slice(checkedPrefix.length);
145
+ } else if (choice.startsWith(uncheckedPrefix)) {
146
+ opt = choice.slice(uncheckedPrefix.length);
147
+ }
148
+ if (opt) {
167
149
  if (selected.has(opt)) {
168
150
  selected.delete(opt);
169
151
  } else {
@@ -1,5 +1,6 @@
1
1
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
2
  import { Type } from "@sinclair/typebox";
3
+ import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
3
4
  import { executeBash } from "../bash-executor";
4
5
  import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
5
6
 
@@ -17,43 +18,7 @@ export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
17
18
  return {
18
19
  name: "bash",
19
20
  label: "Bash",
20
- description: `Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
21
-
22
- IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
23
-
24
- Before executing the command, please follow these steps:
25
-
26
- 1. Directory Verification:
27
- - If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
28
- - For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists and is the intended parent directory
29
-
30
- 2. Command Execution:
31
- - Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
32
- - Examples of proper quoting:
33
- - cd "/Users/name/My Documents" (correct)
34
- - cd /Users/name/My Documents (incorrect - will fail)
35
- - python "/path/with spaces/script.py" (correct)
36
- - python /path/with spaces/script.py (incorrect - will fail)
37
- - After ensuring proper quoting, execute the command.
38
- - Capture the output of the command.
39
-
40
- Usage notes:
41
- - The command argument is required.
42
- - You can specify an optional timeout in seconds.
43
- - It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
44
- - If the output exceeds 50KB characters, output will be truncated before being returned to you.
45
- - Avoid using Bash with the \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands:
46
- - File search: Use find (NOT find or ls)
47
- - Content search: Use grep (NOT grep or rg)
48
- - Read files: Use read (NOT cat/head/tail)
49
- - Edit files: Use edit (NOT sed/awk)
50
- - Write files: Use write (NOT echo >/cat <<EOF)
51
- - Communication: Output text directly (NOT echo/printf)
52
- - When issuing multiple commands:
53
- - If the commands are independent and can run in parallel, make multiple bash tool calls in a single message. For example, if you need to run "git status" and "git diff", send a single message with two bash tool calls in parallel.
54
- - If the commands depend on each other and must run sequentially, use a single bash call with '&&' to chain them together (e.g., \`git add . && git commit -m "message" && git push\`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead.
55
- - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
56
- - DO NOT use newlines to separate commands (newlines are ok in quoted strings)`,
21
+ description: bashDescription,
57
22
  parameters: bashSchema,
58
23
  execute: async (
59
24
  _toolCallId: string,
@@ -1,5 +1,6 @@
1
1
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
2
  import { Type } from "@sinclair/typebox";
3
+ import editDescription from "../../prompts/tools/edit.md" with { type: "text" };
3
4
  import {
4
5
  DEFAULT_FUZZY_THRESHOLD,
5
6
  detectLineEnding,
@@ -43,15 +44,7 @@ export function createEditTool(cwd: string, options: EditToolOptions = {}): Agen
43
44
  return {
44
45
  name: "edit",
45
46
  label: "Edit",
46
- description: `Performs string replacements in files with fuzzy whitespace matching.
47
-
48
- Usage:
49
- - You must use your read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
50
- - Fuzzy matching handles minor whitespace/indentation differences automatically - you don't need to match indentation exactly.
51
- - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
52
- - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
53
- - The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string.
54
- - Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
47
+ description: editDescription,
55
48
  parameters: editSchema,
56
49
  execute: async (
57
50
  _toolCallId: string,