@oh-my-pi/pi-coding-agent 1.340.0 → 2.0.1337

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 (153) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +5 -3
  5. package/src/cli/args.ts +13 -6
  6. package/src/cli/file-processor.ts +3 -3
  7. package/src/cli/list-models.ts +2 -2
  8. package/src/cli/plugin-cli.ts +1 -1
  9. package/src/cli/session-picker.ts +2 -2
  10. package/src/cli.ts +1 -1
  11. package/src/config.ts +3 -3
  12. package/src/core/agent-session.ts +189 -29
  13. package/src/core/bash-executor.ts +50 -10
  14. package/src/core/compaction/branch-summarization.ts +5 -5
  15. package/src/core/compaction/compaction.ts +3 -3
  16. package/src/core/compaction/index.ts +3 -3
  17. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  18. package/src/core/custom-commands/index.ts +15 -0
  19. package/src/core/custom-commands/loader.ts +232 -0
  20. package/src/core/custom-commands/types.ts +112 -0
  21. package/src/core/custom-tools/index.ts +3 -3
  22. package/src/core/custom-tools/loader.ts +10 -8
  23. package/src/core/custom-tools/types.ts +11 -6
  24. package/src/core/custom-tools/wrapper.ts +2 -1
  25. package/src/core/exec.ts +22 -12
  26. package/src/core/export-html/index.ts +5 -5
  27. package/src/core/file-mentions.ts +54 -0
  28. package/src/core/hooks/index.ts +5 -5
  29. package/src/core/hooks/loader.ts +21 -16
  30. package/src/core/hooks/runner.ts +6 -6
  31. package/src/core/hooks/tool-wrapper.ts +2 -2
  32. package/src/core/hooks/types.ts +12 -15
  33. package/src/core/index.ts +6 -6
  34. package/src/core/logger.ts +112 -0
  35. package/src/core/mcp/client.ts +3 -3
  36. package/src/core/mcp/config.ts +1 -1
  37. package/src/core/mcp/index.ts +12 -12
  38. package/src/core/mcp/loader.ts +2 -2
  39. package/src/core/mcp/manager.ts +6 -6
  40. package/src/core/mcp/tool-bridge.ts +3 -3
  41. package/src/core/mcp/transports/http.ts +1 -1
  42. package/src/core/mcp/transports/index.ts +2 -2
  43. package/src/core/mcp/transports/stdio.ts +1 -1
  44. package/src/core/messages.ts +22 -0
  45. package/src/core/model-registry.ts +2 -2
  46. package/src/core/model-resolver.ts +103 -2
  47. package/src/core/plugins/doctor.ts +1 -1
  48. package/src/core/plugins/index.ts +6 -6
  49. package/src/core/plugins/installer.ts +4 -4
  50. package/src/core/plugins/loader.ts +4 -9
  51. package/src/core/plugins/manager.ts +5 -5
  52. package/src/core/plugins/paths.ts +3 -3
  53. package/src/core/sdk.ts +127 -52
  54. package/src/core/session-manager.ts +123 -20
  55. package/src/core/settings-manager.ts +106 -22
  56. package/src/core/skills.ts +5 -5
  57. package/src/core/slash-commands.ts +60 -45
  58. package/src/core/system-prompt.ts +6 -6
  59. package/src/core/title-generator.ts +94 -0
  60. package/src/core/tools/bash.ts +33 -157
  61. package/src/core/tools/context.ts +2 -2
  62. package/src/core/tools/edit-diff.ts +5 -5
  63. package/src/core/tools/edit.ts +60 -9
  64. package/src/core/tools/exa/company.ts +3 -3
  65. package/src/core/tools/exa/index.ts +16 -17
  66. package/src/core/tools/exa/linkedin.ts +3 -3
  67. package/src/core/tools/exa/mcp-client.ts +9 -9
  68. package/src/core/tools/exa/render.ts +5 -5
  69. package/src/core/tools/exa/researcher.ts +3 -3
  70. package/src/core/tools/exa/search.ts +6 -5
  71. package/src/core/tools/exa/types.ts +5 -6
  72. package/src/core/tools/exa/websets.ts +3 -3
  73. package/src/core/tools/find.ts +3 -3
  74. package/src/core/tools/grep.ts +6 -5
  75. package/src/core/tools/index.ts +114 -40
  76. package/src/core/tools/ls.ts +4 -4
  77. package/src/core/tools/lsp/client.ts +204 -108
  78. package/src/core/tools/lsp/config.ts +709 -35
  79. package/src/core/tools/lsp/edits.ts +2 -2
  80. package/src/core/tools/lsp/index.ts +432 -30
  81. package/src/core/tools/lsp/render.ts +2 -2
  82. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  83. package/src/core/tools/lsp/types.ts +5 -0
  84. package/src/core/tools/lsp/utils.ts +1 -1
  85. package/src/core/tools/notebook.ts +1 -1
  86. package/src/core/tools/output.ts +175 -0
  87. package/src/core/tools/read.ts +7 -7
  88. package/src/core/tools/renderers.ts +92 -13
  89. package/src/core/tools/review.ts +268 -0
  90. package/src/core/tools/task/agents.ts +1 -1
  91. package/src/core/tools/task/bundled-agents/explore.md +1 -1
  92. package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
  93. package/src/core/tools/task/discovery.ts +2 -2
  94. package/src/core/tools/task/executor.ts +145 -28
  95. package/src/core/tools/task/index.ts +78 -30
  96. package/src/core/tools/task/model-resolver.ts +72 -13
  97. package/src/core/tools/task/parallel.ts +1 -1
  98. package/src/core/tools/task/render.ts +219 -30
  99. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  100. package/src/core/tools/task/types.ts +36 -2
  101. package/src/core/tools/web-fetch.ts +5 -3
  102. package/src/core/tools/web-search/auth.ts +1 -1
  103. package/src/core/tools/web-search/index.ts +17 -15
  104. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  105. package/src/core/tools/web-search/providers/exa.ts +3 -5
  106. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  107. package/src/core/tools/web-search/render.ts +3 -3
  108. package/src/core/tools/write.ts +70 -7
  109. package/src/index.ts +33 -17
  110. package/src/main.ts +60 -34
  111. package/src/migrations.ts +3 -3
  112. package/src/modes/index.ts +5 -5
  113. package/src/modes/interactive/components/armin.ts +1 -1
  114. package/src/modes/interactive/components/assistant-message.ts +1 -1
  115. package/src/modes/interactive/components/bash-execution.ts +4 -4
  116. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  117. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  118. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  119. package/src/modes/interactive/components/diff.ts +1 -1
  120. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  121. package/src/modes/interactive/components/footer.ts +5 -5
  122. package/src/modes/interactive/components/hook-editor.ts +2 -2
  123. package/src/modes/interactive/components/hook-input.ts +2 -2
  124. package/src/modes/interactive/components/hook-message.ts +3 -3
  125. package/src/modes/interactive/components/hook-selector.ts +2 -2
  126. package/src/modes/interactive/components/model-selector.ts +341 -41
  127. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  128. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  129. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  130. package/src/modes/interactive/components/session-selector.ts +24 -11
  131. package/src/modes/interactive/components/settings-defs.ts +51 -3
  132. package/src/modes/interactive/components/settings-selector.ts +13 -16
  133. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  134. package/src/modes/interactive/components/theme-selector.ts +2 -2
  135. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  136. package/src/modes/interactive/components/tool-execution.ts +44 -8
  137. package/src/modes/interactive/components/tree-selector.ts +5 -5
  138. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  139. package/src/modes/interactive/components/user-message.ts +1 -1
  140. package/src/modes/interactive/components/welcome.ts +42 -5
  141. package/src/modes/interactive/interactive-mode.ts +169 -48
  142. package/src/modes/interactive/theme/theme.ts +8 -7
  143. package/src/modes/print-mode.ts +4 -3
  144. package/src/modes/rpc/rpc-client.ts +4 -4
  145. package/src/modes/rpc/rpc-mode.ts +21 -11
  146. package/src/modes/rpc/rpc-types.ts +3 -3
  147. package/src/utils/changelog.ts +2 -2
  148. package/src/utils/clipboard.ts +1 -1
  149. package/src/utils/shell-snapshot.ts +218 -0
  150. package/src/utils/shell.ts +93 -13
  151. package/src/utils/tools-manager.ts +1 -1
  152. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  153. package/src/core/tools/exa/logger.ts +0 -56
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Shell environment snapshot for preserving user aliases, functions, and options.
3
+ *
4
+ * Creates a snapshot file that captures the user's shell environment from their
5
+ * .bashrc/.zshrc, which can be sourced before each command to provide a familiar
6
+ * shell experience.
7
+ */
8
+
9
+ import { existsSync, mkdirSync, unlinkSync } from "node:fs";
10
+ import { homedir, tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ let cachedSnapshotPath: string | null = null;
14
+ let cleanupRegistered = false;
15
+
16
+ /**
17
+ * Get the user's shell config file path.
18
+ */
19
+ function getShellConfigFile(shell: string): string {
20
+ const home = homedir();
21
+ if (shell.includes("zsh")) return join(home, ".zshrc");
22
+ if (shell.includes("bash")) return join(home, ".bashrc");
23
+ return join(home, ".profile");
24
+ }
25
+
26
+ /**
27
+ * Generate the snapshot creation script.
28
+ * This script sources the user's rc file and extracts functions, aliases, and options.
29
+ * Matches Claude Code's snapshot generation logic.
30
+ */
31
+ function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: string): string {
32
+ const hasRcFile = existsSync(rcFile);
33
+ const isZsh = shell.includes("zsh");
34
+
35
+ // Escape the snapshot path for shell
36
+ const escapedPath = snapshotPath.replace(/'/g, "'\\''");
37
+
38
+ // Function extraction differs between bash and zsh
39
+ const functionScript = isZsh
40
+ ? `
41
+ echo "# Functions" >> "$SNAPSHOT_FILE"
42
+ # Force autoload all functions first
43
+ typeset -f > /dev/null 2>&1
44
+ # Get user function names - filter system/private ones
45
+ typeset +f 2>/dev/null | grep -vE '^(_|__)' | while read func; do
46
+ typeset -f "$func" >> "$SNAPSHOT_FILE" 2>/dev/null
47
+ done
48
+ `
49
+ : `
50
+ echo "# Functions" >> "$SNAPSHOT_FILE"
51
+ # Force autoload all functions first
52
+ declare -f > /dev/null 2>&1
53
+ # Get user function names - filter system/private ones, use base64 for special chars
54
+ declare -F 2>/dev/null | cut -d' ' -f3 | grep -vE '^(_|__)' | while read func; do
55
+ encoded_func=$(declare -f "$func" | base64)
56
+ echo "eval \\"\\$(echo '$encoded_func' | base64 -d)\\" > /dev/null 2>&1" >> "$SNAPSHOT_FILE"
57
+ done
58
+ `;
59
+
60
+ // Shell options extraction
61
+ const optionsScript = isZsh
62
+ ? `
63
+ echo "# Shell Options" >> "$SNAPSHOT_FILE"
64
+ setopt 2>/dev/null | sed 's/^/setopt /' | head -n 1000 >> "$SNAPSHOT_FILE"
65
+ `
66
+ : `
67
+ echo "# Shell Options" >> "$SNAPSHOT_FILE"
68
+ shopt -p 2>/dev/null | head -n 1000 >> "$SNAPSHOT_FILE"
69
+ set -o 2>/dev/null | grep "on" | awk '{print "set -o " $1}' | head -n 1000 >> "$SNAPSHOT_FILE"
70
+ echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"
71
+ `;
72
+
73
+ return `
74
+ SNAPSHOT_FILE='${escapedPath}'
75
+
76
+ # Source user's rc file if it exists
77
+ ${hasRcFile ? `source "${rcFile}" < /dev/null 2>/dev/null` : "# No user config file to source"}
78
+
79
+ # Create/clear the snapshot file
80
+ echo "# Shell snapshot - generated by pi agent" >| "$SNAPSHOT_FILE"
81
+
82
+ # Unalias everything first to avoid conflicts when sourced
83
+ echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"
84
+
85
+ ${functionScript}
86
+
87
+ ${optionsScript}
88
+
89
+ # Export aliases (limit to 1000)
90
+ echo "# Aliases" >> "$SNAPSHOT_FILE"
91
+ # Filter out winpty aliases on Windows to avoid "stdin is not a tty" errors
92
+ if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
93
+ alias 2>/dev/null | grep -v "='winpty " | sed 's/^alias //g' | sed 's/^/alias -- /' | head -n 1000 >> "$SNAPSHOT_FILE"
94
+ else
95
+ alias 2>/dev/null | sed 's/^alias //g' | sed 's/^/alias -- /' | head -n 1000 >> "$SNAPSHOT_FILE"
96
+ fi
97
+
98
+ # Export PATH
99
+ echo "export PATH='$PATH'" >> "$SNAPSHOT_FILE"
100
+
101
+ # Verify snapshot was created
102
+ if [ ! -f "$SNAPSHOT_FILE" ]; then
103
+ echo "Error: Snapshot file was not created" >&2
104
+ exit 1
105
+ fi
106
+ `.trim();
107
+ }
108
+
109
+ /**
110
+ * Create a shell snapshot, caching the result.
111
+ * Returns the path to the snapshot file, or null if creation failed.
112
+ */
113
+ export async function getOrCreateSnapshot(
114
+ shell: string,
115
+ env: Record<string, string | undefined>,
116
+ ): Promise<string | null> {
117
+ // Return cached snapshot if valid
118
+ if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
119
+ return cachedSnapshotPath;
120
+ }
121
+
122
+ // Skip on Windows (no .bashrc in standard location)
123
+ if (process.platform === "win32") {
124
+ return null;
125
+ }
126
+
127
+ const rcFile = getShellConfigFile(shell);
128
+
129
+ // Create snapshot directory
130
+ const snapshotDir = join(tmpdir(), "pi-shell-snapshots");
131
+ try {
132
+ mkdirSync(snapshotDir, { recursive: true });
133
+ } catch {
134
+ return null;
135
+ }
136
+
137
+ // Generate unique snapshot path
138
+ const timestamp = Date.now();
139
+ const random = Math.random().toString(36).substring(2, 8);
140
+ const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : "sh";
141
+ const snapshotPath = join(snapshotDir, `snapshot-${shellName}-${timestamp}-${random}.sh`);
142
+
143
+ // Generate and execute snapshot script
144
+ const script = generateSnapshotScript(shell, snapshotPath, rcFile);
145
+
146
+ try {
147
+ const result = Bun.spawnSync([shell, "-l", "-c", script], {
148
+ stdin: "ignore",
149
+ stdout: "pipe",
150
+ stderr: "pipe",
151
+ env,
152
+ timeout: 10000, // 10 second timeout
153
+ });
154
+
155
+ if (result.exitCode === 0 && existsSync(snapshotPath)) {
156
+ cachedSnapshotPath = snapshotPath;
157
+ registerCleanup();
158
+ return snapshotPath;
159
+ }
160
+ } catch {
161
+ // Snapshot creation failed, proceed without it
162
+ }
163
+
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * Get the command prefix to source the snapshot.
169
+ * Returns empty string if no snapshot available.
170
+ */
171
+ export function getSnapshotSourceCommand(snapshotPath: string | null): string {
172
+ if (!snapshotPath) return "";
173
+ // Escape for shell
174
+ const escaped = snapshotPath.replace(/'/g, "'\\''");
175
+ return `source '${escaped}' 2>/dev/null && `;
176
+ }
177
+
178
+ /**
179
+ * Register cleanup handler to delete snapshot on process exit.
180
+ */
181
+ function registerCleanup(): void {
182
+ if (cleanupRegistered) return;
183
+ cleanupRegistered = true;
184
+
185
+ const cleanup = () => {
186
+ if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
187
+ try {
188
+ unlinkSync(cachedSnapshotPath);
189
+ } catch {
190
+ // Ignore cleanup errors
191
+ }
192
+ }
193
+ };
194
+
195
+ process.on("exit", cleanup);
196
+ process.on("SIGINT", () => {
197
+ cleanup();
198
+ process.exit(130);
199
+ });
200
+ process.on("SIGTERM", () => {
201
+ cleanup();
202
+ process.exit(143);
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Clear the cached snapshot (for testing or forced refresh).
208
+ */
209
+ export function clearSnapshotCache(): void {
210
+ if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
211
+ try {
212
+ unlinkSync(cachedSnapshotPath);
213
+ } catch {
214
+ // Ignore
215
+ }
216
+ }
217
+ cachedSnapshotPath = null;
218
+ }
@@ -1,7 +1,58 @@
1
- import { existsSync } from "node:fs";
2
- import { SettingsManager } from "../core/settings-manager.js";
1
+ import { accessSync, constants, existsSync } from "node:fs";
2
+ import { SettingsManager } from "../core/settings-manager";
3
+
4
+ export interface ShellConfig {
5
+ shell: string;
6
+ args: string[];
7
+ env: Record<string, string | undefined>;
8
+ prefix: string | undefined;
9
+ }
10
+
11
+ let cachedShellConfig: ShellConfig | null = null;
12
+
13
+ /**
14
+ * Check if a shell binary is executable.
15
+ */
16
+ function isExecutable(path: string): boolean {
17
+ try {
18
+ accessSync(path, constants.X_OK);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Build the spawn environment (cached).
27
+ */
28
+ function buildSpawnEnv(shell: string): Record<string, string | undefined> {
29
+ const noCI = process.env.PI_BASH_NO_CI || process.env.CLAUDE_BASH_NO_CI;
30
+ return {
31
+ ...process.env,
32
+ SHELL: shell,
33
+ GIT_EDITOR: "true",
34
+ GPG_TTY: "not a tty",
35
+ PICODE: "1",
36
+ CLAUDECODE: "1",
37
+ ...(noCI ? {} : { CI: "true" }),
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Get shell args, optionally including login shell flag.
43
+ * Supports PI_BASH_NO_LOGIN and CLAUDE_BASH_NO_LOGIN to skip -l.
44
+ */
45
+ function getShellArgs(): string[] {
46
+ const noLogin = process.env.PI_BASH_NO_LOGIN || process.env.CLAUDE_BASH_NO_LOGIN;
47
+ return noLogin ? ["-c"] : ["-l", "-c"];
48
+ }
3
49
 
4
- let cachedShellConfig: { shell: string; args: string[] } | null = null;
50
+ /**
51
+ * Get shell prefix for wrapping commands (profilers, strace, etc.).
52
+ */
53
+ function getShellPrefix(): string | undefined {
54
+ return process.env.PI_SHELL_PREFIX || process.env.CLAUDE_CODE_SHELL_PREFIX;
55
+ }
5
56
 
6
57
  /**
7
58
  * Find bash executable on PATH (Windows)
@@ -21,15 +72,27 @@ function findBashOnPath(): string | null {
21
72
  return null;
22
73
  }
23
74
 
75
+ /**
76
+ * Build full shell config from a shell path.
77
+ */
78
+ function buildConfig(shell: string): ShellConfig {
79
+ return {
80
+ shell,
81
+ args: getShellArgs(),
82
+ env: buildSpawnEnv(shell),
83
+ prefix: getShellPrefix(),
84
+ };
85
+ }
86
+
24
87
  /**
25
88
  * Get shell configuration based on platform.
26
89
  * Resolution order:
27
90
  * 1. User-specified shellPath in settings.json
28
91
  * 2. On Windows: Git Bash in known locations, then bash on PATH
29
- * 3. On Unix: /bin/bash
92
+ * 3. On Unix: $SHELL if bash/zsh, then fallback paths
30
93
  * 4. Fallback: sh
31
94
  */
32
- export function getShellConfig(): { shell: string; args: string[] } {
95
+ export function getShellConfig(): ShellConfig {
33
96
  if (cachedShellConfig) {
34
97
  return cachedShellConfig;
35
98
  }
@@ -40,7 +103,7 @@ export function getShellConfig(): { shell: string; args: string[] } {
40
103
  // 1. Check user-specified shell path
41
104
  if (customShellPath) {
42
105
  if (existsSync(customShellPath)) {
43
- cachedShellConfig = { shell: customShellPath, args: ["-c"] };
106
+ cachedShellConfig = buildConfig(customShellPath);
44
107
  return cachedShellConfig;
45
108
  }
46
109
  throw new Error(
@@ -62,7 +125,7 @@ export function getShellConfig(): { shell: string; args: string[] } {
62
125
 
63
126
  for (const path of paths) {
64
127
  if (existsSync(path)) {
65
- cachedShellConfig = { shell: path, args: ["-c"] };
128
+ cachedShellConfig = buildConfig(path);
66
129
  return cachedShellConfig;
67
130
  }
68
131
  }
@@ -70,7 +133,7 @@ export function getShellConfig(): { shell: string; args: string[] } {
70
133
  // 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)
71
134
  const bashOnPath = findBashOnPath();
72
135
  if (bashOnPath) {
73
- cachedShellConfig = { shell: bashOnPath, args: ["-c"] };
136
+ cachedShellConfig = buildConfig(bashOnPath);
74
137
  return cachedShellConfig;
75
138
  }
76
139
 
@@ -83,21 +146,38 @@ export function getShellConfig(): { shell: string; args: string[] } {
83
146
  );
84
147
  }
85
148
 
86
- // Unix: prefer user's shell from $SHELL, fallback to bash, then sh
149
+ // Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
87
150
  const userShell = process.env.SHELL;
88
- if (userShell && existsSync(userShell)) {
89
- cachedShellConfig = { shell: userShell, args: ["-c"] };
151
+ const isValidShell = userShell && (userShell.includes("bash") || userShell.includes("zsh"));
152
+ if (isValidShell && isExecutable(userShell)) {
153
+ cachedShellConfig = buildConfig(userShell);
90
154
  return cachedShellConfig;
91
155
  }
92
156
 
157
+ // Fallback paths (Claude's approach: check known locations)
158
+ const fallbackPaths = ["/bin", "/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"];
159
+ const preferZsh = !userShell?.includes("bash");
160
+ const shellOrder = preferZsh ? ["zsh", "bash"] : ["bash", "zsh"];
161
+
162
+ for (const shellName of shellOrder) {
163
+ for (const dir of fallbackPaths) {
164
+ const shellPath = `${dir}/${shellName}`;
165
+ if (isExecutable(shellPath)) {
166
+ cachedShellConfig = buildConfig(shellPath);
167
+ return cachedShellConfig;
168
+ }
169
+ }
170
+ }
171
+
172
+ // Last resort: use Bun.which
93
173
  const bashPath = Bun.which("bash");
94
174
  if (bashPath) {
95
- cachedShellConfig = { shell: bashPath, args: ["-c"] };
175
+ cachedShellConfig = buildConfig(bashPath);
96
176
  return cachedShellConfig;
97
177
  }
98
178
 
99
179
  const shPath = Bun.which("sh");
100
- cachedShellConfig = { shell: shPath || "sh", args: ["-c"] };
180
+ cachedShellConfig = buildConfig(shPath || "sh");
101
181
  return cachedShellConfig;
102
182
  }
103
183
 
@@ -2,7 +2,7 @@ import { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync
2
2
  import { arch, platform } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import chalk from "chalk";
5
- import { APP_NAME, getToolsDir } from "../config.js";
5
+ import { APP_NAME, getToolsDir } from "../config";
6
6
 
7
7
  const TOOLS_DIR = getToolsDir();
8
8
 
@@ -1,35 +0,0 @@
1
- ---
2
- name: reviewer
3
- description: Code review specialist for quality and security analysis
4
- tools: read, grep, find, ls, bash
5
- model: claude-sonnet-4-5
6
- ---
7
-
8
- You are a senior code reviewer. Analyze code for quality, security, and maintainability.
9
-
10
- Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
11
- Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
12
-
13
- Strategy:
14
- 1. Run `git diff` to see recent changes (if applicable)
15
- 2. Read the modified files
16
- 3. Check for bugs, security issues, code smells
17
-
18
- Output format:
19
-
20
- ## Files Reviewed
21
- - `path/to/file.ts` (lines X-Y)
22
-
23
- ## Critical (must fix)
24
- - `file.ts:42` - Issue description
25
-
26
- ## Warnings (should fix)
27
- - `file.ts:100` - Issue description
28
-
29
- ## Suggestions (consider)
30
- - `file.ts:150` - Improvement idea
31
-
32
- ## Summary
33
- Overall assessment in 2-3 sentences.
34
-
35
- Be specific with file paths and line numbers.
@@ -1,56 +0,0 @@
1
- /**
2
- * Exa Error Logger
3
- *
4
- * Append-only logging to ~/.pi/ for debugging production issues.
5
- */
6
-
7
- import { appendFileSync, existsSync, mkdirSync } from "fs";
8
- import { homedir } from "os";
9
- import { join } from "path";
10
- import { CONFIG_DIR_NAME } from "../../../config.js";
11
-
12
- /** Get the base config directory (e.g., ~/.pi/) */
13
- function getConfigDir(): string {
14
- return join(homedir(), CONFIG_DIR_NAME);
15
- }
16
-
17
- /** Log file paths */
18
- const LOG_FILES = {
19
- exa: "exa_errors.log",
20
- view: "view_errors.log",
21
- } as const;
22
-
23
- type LogType = keyof typeof LOG_FILES;
24
-
25
- /** Format a log entry with timestamp */
26
- function formatEntry(message: string, context?: Record<string, unknown>): string {
27
- const timestamp = new Date().toISOString();
28
- const contextStr = context ? ` ${JSON.stringify(context)}` : "";
29
- return `[${timestamp}] ${message}${contextStr}\n`;
30
- }
31
-
32
- /** Append to log file (creates directory if needed) */
33
- export function logError(type: LogType, message: string, context?: Record<string, unknown>): void {
34
- try {
35
- const configDir = getConfigDir();
36
- if (!existsSync(configDir)) {
37
- mkdirSync(configDir, { recursive: true });
38
- }
39
-
40
- const logPath = join(configDir, LOG_FILES[type]);
41
- const entry = formatEntry(message, context);
42
- appendFileSync(logPath, entry);
43
- } catch {
44
- // Silently ignore logging failures - we don't want to break tool execution
45
- }
46
- }
47
-
48
- /** Log MCP fetch/call errors */
49
- export function logExaError(message: string, context?: Record<string, unknown>): void {
50
- logError("exa", message, context);
51
- }
52
-
53
- /** Log render/view errors */
54
- export function logViewError(message: string, context?: Record<string, unknown>): void {
55
- logError("view", message, context);
56
- }