Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.0 kB · 183 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184import { randomUUID } from 'crypto'import type { Tool, ToolUseContext } from '../Tool.js'import { BashTool } from '../tools/BashTool/BashTool.js'import { logForDebugging } from './debug.js'import { errorMessage, MalformedCommandError, ShellError } from './errors.js'import type { FrontmatterShell } from './frontmatterParser.js'import { createAssistantMessage } from './messages.js'import { hasPermissionsToUseTool } from './permissions/permissions.js'import { processToolResultBlock } from './toolResultStorage.js'
// Narrow structural slice both BashTool and PowerShellTool satisfy. We can't// use the base Tool type: it marks call()'s canUseTool/parentMessage as// required, but both concrete tools have them optional and the original code// called BashTool.call({ command }, ctx) with just 2 args. We can't use// `typeof BashTool` either: BashTool's input schema has fields (e.g.// _simulatedSedEdit) that PowerShellTool's does not.// NOTE: call() is invoked directly here, bypassing validateInput — any// load-bearing check must live in call() itself (see PR #23311).type ShellOut = { stdout: string; stderr: string; interrupted: boolean }type PromptShellTool = Tool & { call( input: { command: string }, context: ToolUseContext, ): Promise<{ data: ShellOut }>}
import { isPowerShellToolEnabled } from './shell/shellToolUtils.js'
// Lazy: this file is on the startup import chain (main → commands →// loadSkillsDir → here). A static import would load PowerShellTool.ts// (and transitively parser.ts, validators, etc.) at startup on all// platforms, defeating tools.ts's lazy require. Deferred until the// first skill with `shell: powershell` actually runs./* eslint-disable @typescript-eslint/no-require-imports */const getPowerShellTool = (() => { let cached: PromptShellTool | undefined return (): PromptShellTool => { if (!cached) { cached = ( require('../tools/PowerShellTool/PowerShellTool.js') as typeof import('../tools/PowerShellTool/PowerShellTool.js') ).PowerShellTool } return cached }})()/* eslint-enable @typescript-eslint/no-require-imports */
// Pattern for code blocks: ```! command ```const BLOCK_PATTERN = /```!\s*\n?([\s\S]*?)\n?```/g
// Pattern for inline: !`command`// Uses a positive lookbehind to require whitespace or start-of-line before !// This prevents false matches inside markdown inline code spans like `!!` or// adjacent spans like `foo`!`bar`, and shell variables like $!// eslint-disable-next-line custom-rules/no-lookbehind-regex -- gated by text.includes('!`') below (PR#22986)const INLINE_PATTERN = /(?<=^|\s)!`([^`]+)`/gm
/** * Parses prompt text and executes any embedded shell commands. * Supports two syntaxes: * - Code blocks: ```! command ``` * - Inline: !`command` * * @param shell - Shell to route commands through. Defaults to bash. * This is *never* read from settings.defaultShell — it comes from .md * frontmatter (author's choice) or is undefined for built-in commands. * See docs/design/ps-shell-selection.md §5.3. */export async function executeShellCommandsInPrompt( text: string, context: ToolUseContext, slashCommandName: string, shell?: FrontmatterShell,): Promise<string> { let result = text
// Resolve the tool once. `shell === undefined` and `shell === 'bash'` both // hit BashTool. PowerShell only when the runtime gate allows — a skill // author's frontmatter choice doesn't override the user's opt-in/out. const shellTool: PromptShellTool = shell === 'powershell' && isPowerShellToolEnabled() ? getPowerShellTool() : BashTool
// INLINE_PATTERN's lookbehind is ~100x slower than BLOCK_PATTERN on large // skill content (265µs vs 2µs @ 17KB). 93% of skills have no !` at all, // so gate the expensive scan on a cheap substring check. BLOCK_PATTERN // (```!) doesn't require !` in the text, so it's always scanned. const blockMatches = text.matchAll(BLOCK_PATTERN) const inlineMatches = text.includes('!`') ? text.matchAll(INLINE_PATTERN) : []
await Promise.all( [...blockMatches, ...inlineMatches].map(async match => { const command = match[1]?.trim() if (command) { try { // Check permissions before executing const permissionResult = await hasPermissionsToUseTool( shellTool, { command }, context, createAssistantMessage({ content: [] }), '', )
if (permissionResult.behavior !== 'allow') { logForDebugging( `Shell command permission check failed for command in ${slashCommandName}: ${command}. Error: ${permissionResult.message}`, ) throw new MalformedCommandError( `Shell command permission check failed for pattern "${match[0]}": ${permissionResult.message || 'Permission denied'}`, ) }
const { data } = await shellTool.call({ command }, context) // Reuse the same persistence flow as regular Bash tool calls const toolResultBlock = await processToolResultBlock( shellTool, data, randomUUID(), ) // Extract the string content from the block const output = typeof toolResultBlock.content === 'string' ? toolResultBlock.content : formatBashOutput(data.stdout, data.stderr) // Function replacer — String.replace interprets $$, $&, $`, $' in // the replacement string even with a string search pattern. Shell // output (especially PowerShell: $env:PATH, $$, $PSVersionTable) // is arbitrary user data; a bare string arg would corrupt it. result = result.replace(match[0], () => output) } catch (e) { if (e instanceof MalformedCommandError) { throw e } formatBashError(e, match[0]) } } }), )
return result}
function formatBashOutput( stdout: string, stderr: string, inline = false,): string { const parts: string[] = []
if (stdout.trim()) { parts.push(stdout.trim()) }
if (stderr.trim()) { if (inline) { parts.push(`[stderr: ${stderr.trim()}]`) } else { parts.push(`[stderr]\n${stderr.trim()}`) } }
return parts.join(inline ? ' ' : '\n')}
function formatBashError(e: unknown, pattern: string, inline = false): never { if (e instanceof ShellError) { if (e.interrupted) { throw new MalformedCommandError( `Shell command interrupted for pattern "${pattern}": [Command interrupted]`, ) } const output = formatBashOutput(e.stdout, e.stderr, inline) throw new MalformedCommandError( `Shell command failed for pattern "${pattern}": ${output}`, ) }
const message = errorMessage(e) const formatted = inline ? `[Error: ${message}]` : `[Error]\n${message}` throw new MalformedCommandError(formatted)}