import { createBashToolDefinition, getAgentDir, type ExtensionAPI, } from "@earendil-works/pi-coding-agent"; import { loadConfig, type PwshNativeConfig } from "./config.ts"; import { createPowerShellOperations } from "./operations.ts"; import { resolvePowerShellRuntime, type PowerShellRuntime } from "./runtime.ts"; const DISCOVERY_TOOLS = ["ls", "find", "grep"] as const; function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } export function selectActiveTools( activeTools: readonly string[], preserveDiscoveryTools: boolean, pwshAvailable: boolean, ): string[] { const disabled = new Set(["bash", "pwsh"]); if (!preserveDiscoveryTools) { for (const name of DISCOVERY_TOOLS) disabled.add(name); } const selected = activeTools.filter((name) => !disabled.has(name)); if (pwshAvailable) selected.push("pwsh"); return [...new Set(selected)]; } export const PWSH_TOOL_DESCRIPTION = "Execute native PowerShell 7 on Windows. Output is tail-truncated to 2,000 lines or 50 KB; full output is saved to a temporary file. Optional timeout is in seconds."; export function createPromptGuidelines(config: PwshNativeConfig): string[] { return [ "Use pwsh only when a shell is needed; use native PowerShell syntax and $env:NAME for environment variables.", "Prefer dedicated file and search tools over pwsh; otherwise bound recursive output and prefer rg or fd when available.", config.elevationGuidance ? "In pwsh, elevation guidance is enabled, but elevate only after an explicit user request." : "Do not elevate from pwsh unless the user explicitly requests it.", ]; } function unavailableUserCommand(reason: string) { return { result: { output: `pi-pwsh-native is unavailable: ${reason}`, exitCode: 1, cancelled: false, truncated: false, }, }; } export default function piPwshNative(pi: ExtensionAPI): void { if (process.platform !== "win32") { pi.on("session_start", (_event, ctx) => { ctx.ui.notify("pi-pwsh-native is Windows-only; no tools were changed.", "warning"); }); return; } let config: PwshNativeConfig | undefined; let runtime: PowerShellRuntime | undefined; let operations: ReturnType | undefined; let setupError: string | undefined; try { config = loadConfig({ agentDir: getAgentDir() }).config; } catch (error) { setupError = errorMessage(error); } pi.on("session_start", (_event, ctx) => { if (!setupError && config && !runtime) { try { runtime = resolvePowerShellRuntime(config); operations = createPowerShellOperations(runtime, config); } catch (error) { setupError = errorMessage(error); } } if (setupError || !config || !runtime || !operations) { const reason = setupError ?? "unknown setup failure"; pi.setActiveTools(selectActiveTools(pi.getActiveTools(), true, false)); ctx.ui.notify(`pi-pwsh-native: ${reason} Bash was disabled; no shell fallback was activated.`, "error"); return; } const definition = createBashToolDefinition(ctx.cwd, { operations }); pi.registerTool({ ...definition, name: "pwsh", label: "pwsh", description: PWSH_TOOL_DESCRIPTION, promptSnippet: "Execute native PowerShell 7 commands", promptGuidelines: createPromptGuidelines(config), }); pi.setActiveTools( selectActiveTools(pi.getActiveTools(), config.preserveDiscoveryTools, true), ); }); const replaceUserBash = config?.replaceUserBash ?? true; if (replaceUserBash) { pi.on("user_bash", () => operations ? { operations } : unavailableUserCommand(setupError ?? "PowerShell has not been initialized"), ); } }