@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.
- package/CHANGELOG.md +115 -1
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- package/src/cli/args.ts +13 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +3 -3
- package/src/core/agent-session.ts +189 -29
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +5 -5
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +103 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +127 -52
- package/src/core/session-manager.ts +123 -20
- package/src/core/settings-manager.ts +106 -22
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +94 -0
- package/src/core/tools/bash.ts +33 -157
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +5 -5
- package/src/core/tools/edit.ts +60 -9
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +6 -5
- package/src/core/tools/index.ts +114 -40
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +204 -108
- package/src/core/tools/lsp/config.ts +709 -35
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +432 -30
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/types.ts +5 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +1 -1
- package/src/core/tools/task/bundled-agents/explore.md +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +72 -13
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +70 -7
- package/src/index.ts +33 -17
- package/src/main.ts +60 -34
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +341 -41
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +51 -3
- package/src/modes/interactive/components/settings-selector.ts +13 -16
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +44 -8
- package/src/modes/interactive/components/tree-selector.ts +5 -5
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +42 -5
- package/src/modes/interactive/interactive-mode.ts +169 -48
- package/src/modes/interactive/theme/theme.ts +8 -7
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +21 -11
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- 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
|
+
}
|
package/src/utils/shell.ts
CHANGED
|
@@ -1,7 +1,58 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { SettingsManager } from "../core/settings-manager
|
|
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
|
-
|
|
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: /
|
|
92
|
+
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
30
93
|
* 4. Fallback: sh
|
|
31
94
|
*/
|
|
32
|
-
export function getShellConfig():
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
149
|
+
// Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
|
|
87
150
|
const userShell = process.env.SHELL;
|
|
88
|
-
|
|
89
|
-
|
|
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 =
|
|
175
|
+
cachedShellConfig = buildConfig(bashPath);
|
|
96
176
|
return cachedShellConfig;
|
|
97
177
|
}
|
|
98
178
|
|
|
99
179
|
const shPath = Bun.which("sh");
|
|
100
|
-
cachedShellConfig =
|
|
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
|
|
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
|
-
}
|