UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

943 lines (942 loc) 26.6 kB
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js"; import { s as resolveCommandResolutionFromArgv } from "./exec-command-resolution-CqhcxBz-.js"; import { a as extractShellWrapperInlineCommand, f as POSIX_INLINE_COMMAND_FLAGS, g as resolveInlineCommandMatch, k as splitShellArgs, u as resolveShellWrapperTransportArgv } from "./shell-wrapper-resolution-CFL_Vekh.js"; import "./exec-wrapper-resolution-VZBsT_RG.js"; //#region src/infra/exec-approvals-analysis.ts const DISALLOWED_PIPELINE_TOKENS = new Set([ ">", "<", "`", "\n", "\r", "(", ")" ]); const DOUBLE_QUOTE_ESCAPES = new Set([ "\\", "\"", "$", "`" ]); const MAX_UNQUOTED_HEREDOC_CONTINUATION_LINES = 1024; const MAX_UNQUOTED_HEREDOC_LOGICAL_LINE_LENGTH = 64 * 1024; const WINDOWS_UNSUPPORTED_TOKENS = new Set([ "&", "|", "<", ">", ";", "^", "(", ")", "%", "!", "`", "\n", "\r" ]); function isDoubleQuoteEscape(next) { return Boolean(next && DOUBLE_QUOTE_ESCAPES.has(next)); } function isEscapedLineContinuation(next) { return next === "\n" || next === "\r"; } function isShellCommentStart(source, index) { if (source[index] !== "#") return false; if (index === 0) return true; const prev = source[index - 1]; return Boolean(prev && /\s/.test(prev)); } function splitShellPipeline(command) { const parseHeredocDelimiter = (source, start) => { let i = start; while (i < source.length && (source[i] === " " || source[i] === " ")) i += 1; if (i >= source.length) return null; const first = source[i]; if (first === "'" || first === "\"") { const quote = first; i += 1; let delimiter = ""; while (i < source.length) { const ch = source[i]; if (ch === "\n" || ch === "\r") return null; if (quote === "\"" && ch === "\\" && i + 1 < source.length) { delimiter += source[i + 1]; i += 2; continue; } if (ch === quote) return { delimiter, end: i + 1, quoted: true }; delimiter += ch; i += 1; } return null; } let delimiter = ""; while (i < source.length) { const ch = source[i]; if (/\s/.test(ch) || ch === "|" || ch === "&" || ch === ";" || ch === "<" || ch === ">") break; delimiter += ch; i += 1; } if (!delimiter) return null; return { delimiter, end: i, quoted: false }; }; const segments = []; let buf = ""; let inSingle = false; let inDouble = false; let escaped = false; let emptySegment = false; const pendingHeredocs = []; let inHeredocBody = false; let heredocLine = ""; let unquotedHeredocLogicalChunks = []; let unquotedHeredocLogicalLength = 0; const pushPart = () => { const trimmed = buf.trim(); if (trimmed) segments.push(trimmed); buf = ""; }; const isEscapedInHeredocLine = (line, index) => { let slashes = 0; for (let i = index - 1; i >= 0 && line[i] === "\\"; i -= 1) slashes += 1; return slashes % 2 === 1; }; const hasUnquotedHeredocExpansionToken = (line) => { for (let i = 0; i < line.length; i += 1) { const ch = line[i]; if (ch === "`" && !isEscapedInHeredocLine(line, i)) return true; if (ch === "$" && !isEscapedInHeredocLine(line, i)) { const next = line[i + 1]; if (next === "(" || next === "{" || next === "[" || next !== void 0 && (/^[A-Za-z_]$/.test(next) || /^[0-9]$/.test(next) || "@*?!$#-".includes(next))) return true; } } return false; }; const stripUnquotedHeredocLineContinuation = (line) => { let trailingSlashes = 0; for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) trailingSlashes += 1; if (trailingSlashes % 2 === 1) return { line: line.slice(0, -1), continues: true }; return { line, continues: false }; }; for (let i = 0; i < command.length; i += 1) { const ch = command[i]; const next = command[i + 1]; if (inHeredocBody) { if (ch === "\n" || ch === "\r") { const current = pendingHeredocs[0]; if (current) { const line = current.stripTabs ? heredocLine.replace(/^\t+/, "") : heredocLine; if (current.quoted) { if (line === current.delimiter) pendingHeredocs.shift(); } else if (line === current.delimiter && unquotedHeredocLogicalChunks.length === 0) pendingHeredocs.shift(); else { const continued = stripUnquotedHeredocLineContinuation(line); unquotedHeredocLogicalChunks.push(continued.line); if (unquotedHeredocLogicalChunks.length > MAX_UNQUOTED_HEREDOC_CONTINUATION_LINES) return { ok: false, reason: "heredoc continuation too long", segments: [] }; unquotedHeredocLogicalLength += continued.line.length; if (unquotedHeredocLogicalLength > MAX_UNQUOTED_HEREDOC_LOGICAL_LINE_LENGTH) return { ok: false, reason: "heredoc logical line too large", segments: [] }; if (!continued.continues) { if (hasUnquotedHeredocExpansionToken(unquotedHeredocLogicalChunks.join(""))) return { ok: false, reason: "shell expansion in unquoted heredoc", segments: [] }; unquotedHeredocLogicalChunks = []; unquotedHeredocLogicalLength = 0; } } } heredocLine = ""; if (pendingHeredocs.length === 0) inHeredocBody = false; if (ch === "\r" && next === "\n") i += 1; } else heredocLine += ch; continue; } if (escaped) { buf += ch; escaped = false; emptySegment = false; continue; } if (!inSingle && !inDouble && ch === "\\") { escaped = true; buf += ch; emptySegment = false; continue; } if (inSingle) { if (ch === "'") inSingle = false; buf += ch; emptySegment = false; continue; } if (inDouble) { if (ch === "\\" && isEscapedLineContinuation(next)) return { ok: false, reason: "unsupported shell token: newline", segments: [] }; if (ch === "\\" && isDoubleQuoteEscape(next)) { buf += ch; buf += next; i += 1; emptySegment = false; continue; } if (ch === "$" && next === "(") return { ok: false, reason: "unsupported shell token: $()", segments: [] }; if (ch === "`") return { ok: false, reason: "unsupported shell token: `", segments: [] }; if (ch === "\n" || ch === "\r") return { ok: false, reason: "unsupported shell token: newline", segments: [] }; if (ch === "\"") inDouble = false; buf += ch; emptySegment = false; continue; } if (ch === "'") { inSingle = true; buf += ch; emptySegment = false; continue; } if (ch === "\"") { inDouble = true; buf += ch; emptySegment = false; continue; } if (isShellCommentStart(command, i)) break; if ((ch === "\n" || ch === "\r") && pendingHeredocs.length > 0) { inHeredocBody = true; heredocLine = ""; if (ch === "\r" && next === "\n") i += 1; continue; } if (ch === "|" && next === "|") return { ok: false, reason: "unsupported shell token: ||", segments: [] }; if (ch === "|" && next === "&") return { ok: false, reason: "unsupported shell token: |&", segments: [] }; if (ch === "|") { emptySegment = true; pushPart(); continue; } if (ch === "&" || ch === ";") return { ok: false, reason: `unsupported shell token: ${ch}`, segments: [] }; if (ch === "<" && next === "<") { buf += "<<"; emptySegment = false; i += 1; let scanIndex = i + 1; let stripTabs = false; if (command[scanIndex] === "-") { stripTabs = true; buf += "-"; scanIndex += 1; } const parsed = parseHeredocDelimiter(command, scanIndex); if (parsed) { pendingHeredocs.push({ delimiter: parsed.delimiter, stripTabs, quoted: parsed.quoted }); buf += command.slice(scanIndex, parsed.end); i = parsed.end - 1; } continue; } if (DISALLOWED_PIPELINE_TOKENS.has(ch)) return { ok: false, reason: `unsupported shell token: ${ch}`, segments: [] }; if (ch === "$" && next === "(") return { ok: false, reason: "unsupported shell token: $()", segments: [] }; buf += ch; emptySegment = false; } if (inHeredocBody && pendingHeredocs.length > 0) { const current = pendingHeredocs[0]; const line = current.stripTabs ? heredocLine.replace(/^\t+/, "") : heredocLine; if (!current.quoted && unquotedHeredocLogicalChunks.length > 0) { const continued = stripUnquotedHeredocLineContinuation(line); if (hasUnquotedHeredocExpansionToken([...unquotedHeredocLogicalChunks, continued.line].join(""))) return { ok: false, reason: "shell expansion in unquoted heredoc", segments: [] }; } else if (line === current.delimiter) { pendingHeredocs.shift(); if (pendingHeredocs.length === 0) inHeredocBody = false; } } if (pendingHeredocs.length > 0 || inHeredocBody) return { ok: false, reason: "unterminated heredoc", segments: [] }; if (escaped || inSingle || inDouble) return { ok: false, reason: "unterminated shell quote/escape", segments: [] }; pushPart(); if (emptySegment || segments.length === 0) return { ok: false, reason: segments.length === 0 ? "empty command" : "empty pipeline segment", segments: [] }; return { ok: true, segments }; } const WINDOWS_ALWAYS_UNSAFE_TOKENS = new Set([ "\n", "\r", "%", "`" ]); function findWindowsUnsupportedToken(command) { let inDouble = false; for (let i = 0; i < command.length; i++) { const ch = command[i]; if (ch === "\"") { inDouble = !inDouble; continue; } if (ch === "$") { const next = command[i + 1]; if (next !== void 0 && /[A-Za-z_{(?$]/.test(next)) return "$"; continue; } if (WINDOWS_UNSUPPORTED_TOKENS.has(ch)) { if (inDouble && !WINDOWS_ALWAYS_UNSAFE_TOKENS.has(ch)) continue; if (ch === "\n" || ch === "\r") return "newline"; return ch; } } return null; } function tokenizeWindowsSegment(segment) { const tokens = []; let buf = ""; let inDouble = false; let inSingle = false; let wasQuoted = false; const pushToken = () => { if (buf.length > 0 || wasQuoted) { tokens.push(buf); buf = ""; } wasQuoted = false; }; for (let i = 0; i < segment.length; i += 1) { const ch = segment[i]; if (ch === "\"" && !inSingle) { if (!inDouble) wasQuoted = true; inDouble = !inDouble; continue; } if (ch === "'" && !inDouble) { if (inSingle && segment[i + 1] === "'") { buf += "'"; i += 1; continue; } if (!inSingle) wasQuoted = true; inSingle = !inSingle; continue; } if (!inDouble && !inSingle && /\s/.test(ch)) { pushToken(); continue; } buf += ch; } if (inDouble || inSingle) return null; pushToken(); return tokens.length > 0 ? tokens : null; } /** * Recursively strip transparent Windows shell wrappers from a command string. * * LLMs generate commands with arbitrary nesting of shell wrappers: * powershell -NoProfile -Command "& node 'C:\path' --count 3" * cmd /c "node C:\path --count 3" * & node C:\path --count 3 * * All of these should resolve to: node C:\path --count 3 * * Recognised wrappers (applied repeatedly until stable): * - PowerShell call-operator: `& exe args` * - cmd.exe pass-through: `cmd /c "..."` or `cmd /c ...` * - PowerShell invocation: `powershell [-flags] -Command "..."` */ function stripWindowsShellWrapper(command) { const MAX_DEPTH = 5; let result = command; for (let i = 0; i < MAX_DEPTH; i++) { const prev = result; result = stripWindowsShellWrapperOnce(result.trim()); if (result === prev) break; } return result; } function stripWindowsShellWrapperOnce(command) { const psCallMatch = command.match(/^&\s+(.+)$/s); if (psCallMatch) return psCallMatch[1]; const psFlags = /(?:-(?!c(?:ommand)?\b|-command\b)\w+(?:\s+(?!-)(?:"[^"]*(?:""[^"]*)*"|'[^']*(?:''[^']*)*'|\S+))?\s+)*/i.source; const psCommandFlag = `(?:-command|-c|--command)`; const psInvokeMatch = command.match(new RegExp(`^(?:powershell|pwsh)(?:\\.exe)?\\s+${psFlags}${psCommandFlag}\\s+"(.+)"$`, "is")); if (psInvokeMatch) return psInvokeMatch[1].replace(/""/g, "\""); const psInvokeSingleQuote = command.match(new RegExp(`^(?:powershell|pwsh)(?:\\.exe)?\\s+${psFlags}${psCommandFlag}\\s+'(.+)'$`, "is")); if (psInvokeSingleQuote) return psInvokeSingleQuote[1].replace(/''/g, "'"); const psInvokeNoQuote = command.match(new RegExp(`^(?:powershell|pwsh)(?:\\.exe)?\\s+${psFlags}${psCommandFlag}\\s+(.+)$`, "is")); if (psInvokeNoQuote) return psInvokeNoQuote[1]; return command; } function analyzeWindowsShellCommand(params) { const effective = stripWindowsShellWrapper(params.command.trim()); const unsupported = findWindowsUnsupportedToken(effective); if (unsupported) return { ok: false, reason: `unsupported windows shell token: ${unsupported}`, segments: [] }; const argv = tokenizeWindowsSegment(effective); if (!argv || argv.length === 0) return { ok: false, reason: "unable to parse windows command", segments: [] }; return { ok: true, segments: [{ raw: params.command, argv, resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env, params.platform ?? void 0) }] }; } function isWindowsPlatform(platform) { return normalizeLowercaseStringOrEmpty(platform).startsWith("win"); } function parseSegmentsFromParts(parts, cwd, env, platform) { const segments = []; for (const raw of parts) { const argv = splitShellArgs(raw); if (!argv || argv.length === 0) return null; segments.push({ raw, argv, resolution: resolveCommandResolutionFromArgv(argv, cwd, env, platform ?? void 0) }); } return segments; } /** * Splits a command string by chain operators (&&, ||, ;) while preserving the operators. * Returns null when no chain is present or when the chain is malformed. */ function splitCommandChainWithOperators(command) { const parts = []; let buf = ""; let inSingle = false; let inDouble = false; let escaped = false; let foundChain = false; let invalidChain = false; const pushPart = (opToNext) => { const trimmed = buf.trim(); buf = ""; if (!trimmed) return false; parts.push({ part: trimmed, opToNext }); return true; }; for (let i = 0; i < command.length; i += 1) { const ch = command[i]; const next = command[i + 1]; if (escaped) { buf += ch; escaped = false; continue; } if (!inSingle && !inDouble && ch === "\\") { escaped = true; buf += ch; continue; } if (inSingle) { if (ch === "'") inSingle = false; buf += ch; continue; } if (inDouble) { if (ch === "\\" && isEscapedLineContinuation(next)) { invalidChain = true; break; } if (ch === "\\" && isDoubleQuoteEscape(next)) { buf += ch; buf += next; i += 1; continue; } if (ch === "\"") inDouble = false; buf += ch; continue; } if (ch === "'") { inSingle = true; buf += ch; continue; } if (ch === "\"") { inDouble = true; buf += ch; continue; } if (isShellCommentStart(command, i)) break; if (ch === "&" && next === "&") { if (!pushPart("&&")) invalidChain = true; i += 1; foundChain = true; continue; } if (ch === "|" && next === "|") { if (!pushPart("||")) invalidChain = true; i += 1; foundChain = true; continue; } if (ch === ";") { if (!pushPart(";")) invalidChain = true; foundChain = true; continue; } buf += ch; } if (!foundChain) return null; const trimmed = buf.trim(); if (!trimmed) return null; parts.push({ part: trimmed, opToNext: null }); if (invalidChain || parts.length === 0) return null; return parts; } function shellEscapeSingleArg(value) { return `'${value.replace(/'/g, `'"'"'`)}'`; } const WINDOWS_UNSAFE_CMD_META = /[%`]|\$(?=[A-Za-z_{(?$])/; function windowsEscapeArg(value) { if (value === "") return { ok: true, escaped: "\"\"" }; if (WINDOWS_UNSAFE_CMD_META.test(value)) return { ok: false }; if (/^[a-zA-Z0-9_./:~\\=-]+$/.test(value)) return { ok: true, escaped: value }; return { ok: true, escaped: `"${value.replace(/"/g, "\"\"")}"` }; } function rebuildWindowsShellCommandFromSource(params) { const source = stripWindowsShellWrapper(params.command.trim()); if (!source) return { ok: false, reason: "empty command" }; const unsupported = findWindowsUnsupportedToken(source); if (unsupported) return { ok: false, reason: `unsupported windows shell token: ${unsupported}` }; const rendered = params.renderSegment(source, 0); if (!rendered.ok) return { ok: false, reason: rendered.reason }; return { ok: true, command: `& ${rendered.rendered}`, segmentCount: 1 }; } function rebuildShellCommandFromSource(params) { if (isWindowsPlatform(params.platform ?? null)) return rebuildWindowsShellCommandFromSource(params); const source = params.command.trim(); if (!source) return { ok: false, reason: "empty command" }; const chainParts = splitCommandChainWithOperators(source) ?? [{ part: source, opToNext: null }]; let segmentCount = 0; let out = ""; for (const part of chainParts) { const pipelineSplit = splitShellPipeline(part.part); if (!pipelineSplit.ok) return { ok: false, reason: pipelineSplit.reason ?? "unable to parse pipeline" }; const renderedSegments = []; for (const segmentRaw of pipelineSplit.segments) { const rendered = params.renderSegment(segmentRaw, segmentCount); if (!rendered.ok) return { ok: false, reason: rendered.reason }; renderedSegments.push(rendered.rendered); segmentCount += 1; } out += renderedSegments.join(" | "); if (part.opToNext) out += ` ${part.opToNext} `; } return { ok: true, command: out, segmentCount }; } /** * Builds a shell command string that preserves pipes/chaining, but forces *arguments* to be * literal (no globbing, no env-var expansion) by single-quoting every argv token. * * Used to make "safe bins" actually stdin-only even though execution happens via `shell -c`. */ function buildSafeShellCommand(params) { const isWindows = isWindowsPlatform(params.platform); return finalizeRebuiltShellCommand(rebuildShellCommandFromSource({ command: params.command, platform: params.platform, renderSegment: (segmentRaw) => { const argv = isWindows ? tokenizeWindowsSegment(segmentRaw) ?? [] : splitShellArgs(segmentRaw) ?? []; if (argv.length === 0) return { ok: false, reason: "unable to parse shell segment" }; if (isWindows) return renderWindowsQuotedArgv(argv); return { ok: true, rendered: argv.map((token) => shellEscapeSingleArg(token)).join(" ") }; } })); } function renderWindowsQuotedArgv(argv) { const parts = []; for (const token of argv) { const result = windowsEscapeArg(token); if (!result.ok) return { ok: false, reason: `unsafe windows token: ${token}` }; parts.push(result.escaped); } return { ok: true, rendered: parts.join(" ") }; } function renderQuotedArgv(argv, platform) { if (isWindowsPlatform(platform)) { const result = renderWindowsQuotedArgv(argv); return result.ok ? result.rendered : null; } return argv.map((token) => shellEscapeSingleArg(token)).join(" "); } function finalizeRebuiltShellCommand(rebuilt, expectedSegmentCount) { if (!rebuilt.ok) return { ok: false, reason: rebuilt.reason }; if (typeof expectedSegmentCount === "number" && rebuilt.segmentCount !== expectedSegmentCount) return { ok: false, reason: "segment count mismatch" }; return { ok: true, command: rebuilt.command }; } function resolvePlannedSegmentArgv(segment) { if (segment.resolution?.policyBlocked === true) return null; const baseArgv = segment.resolution?.effectiveArgv && segment.resolution.effectiveArgv.length > 0 ? segment.resolution.effectiveArgv : segment.argv; if (baseArgv.length === 0) return null; const argv = [...baseArgv]; const execution = segment.resolution?.execution; const resolvedExecutable = execution?.resolvedRealPath?.trim() ?? execution?.resolvedPath?.trim() ?? ""; if (resolvedExecutable) argv[0] = resolvedExecutable; return argv; } function renderSafeBinSegmentArgv(segment, platform) { const argv = resolvePlannedSegmentArgv(segment); if (!argv || argv.length === 0) return null; return renderQuotedArgv(argv, platform); } function findSubsequence(haystack, needle) { if (needle.length === 0 || needle.length > haystack.length) return -1; for (let start = 0; start <= haystack.length - needle.length; start += 1) { let matches = true; for (let offset = 0; offset < needle.length; offset += 1) if (haystack[start + offset] !== needle[offset]) { matches = false; break; } if (matches) return start; } return -1; } function replaceShellInlineCommandArgv(params) { const transportArgv = resolveShellWrapperTransportArgv(params.argv); if (!transportArgv) return null; const transportStart = findSubsequence(params.argv, transportArgv); if (transportStart < 0) return null; const match = resolveInlineCommandMatch(transportArgv, POSIX_INLINE_COMMAND_FLAGS, { allowCombinedC: true }); if (match.valueTokenIndex === null) return null; const absoluteValueIndex = transportStart + match.valueTokenIndex; const token = params.argv[absoluteValueIndex]; if (token === void 0) return null; const rewritten = [...params.argv]; if (token === params.oldCommand) { rewritten[absoluteValueIndex] = params.nextCommand; return rewritten; } if (token.endsWith(params.oldCommand)) { rewritten[absoluteValueIndex] = token.slice(0, token.length - params.oldCommand.length) + params.nextCommand; return rewritten; } return null; } function renderInlineChainSegmentArgv(params) { const inlineCommand = extractShellWrapperInlineCommand(params.segment.argv); if (!inlineCommand) return null; const analysis = analyzeShellCommand({ command: inlineCommand, cwd: params.cwd, env: params.env, platform: params.platform }); if (!analysis.ok) return null; const rebuilt = buildEnforcedShellCommand({ command: inlineCommand, segments: analysis.segments, platform: params.platform }); if (!rebuilt.ok || !rebuilt.command) return null; const rewrittenArgv = replaceShellInlineCommandArgv({ argv: params.segment.argv, oldCommand: inlineCommand, nextCommand: rebuilt.command }); return rewrittenArgv ? renderQuotedArgv(rewrittenArgv, params.platform) : null; } /** * Rebuilds a shell command and selectively single-quotes argv tokens for segments that * must be treated as literal (safeBins hardening) while preserving the rest of the * shell syntax (pipes + chaining). */ function buildSafeBinsShellCommand(params) { if (params.segments.length !== params.segmentSatisfiedBy.length) return { ok: false, reason: "segment metadata mismatch" }; return finalizeRebuiltShellCommand(rebuildShellCommandFromSource({ command: params.command, platform: params.platform, renderSegment: (raw, segmentIndex) => { const seg = params.segments[segmentIndex]; const by = params.segmentSatisfiedBy[segmentIndex]; if (!seg || by === void 0) return { ok: false, reason: "segment mapping failed" }; const needsLiteral = by === "safeBins"; if (by === "inlineChain") { const rendered = renderInlineChainSegmentArgv({ segment: seg, cwd: params.cwd, env: params.env, platform: params.platform }); if (!rendered) return { ok: false, reason: "inline chain execution plan unavailable" }; return { ok: true, rendered }; } if (!needsLiteral) return { ok: true, rendered: raw.trim() }; const rendered = renderSafeBinSegmentArgv(seg, params.platform); if (!rendered) return { ok: false, reason: "segment execution plan unavailable" }; return { ok: true, rendered }; } }), params.segments.length); } function buildEnforcedShellCommand(params) { return finalizeRebuiltShellCommand(rebuildShellCommandFromSource({ command: params.command, platform: params.platform, renderSegment: (_raw, segmentIndex) => { const seg = params.segments[segmentIndex]; if (!seg) return { ok: false, reason: "segment mapping failed" }; const argv = resolvePlannedSegmentArgv(seg); if (!argv) return { ok: false, reason: "segment execution plan unavailable" }; const rendered = renderQuotedArgv(argv, params.platform); if (!rendered) return { ok: false, reason: "unsafe windows token in argv" }; return { ok: true, rendered }; } }), params.segments.length); } /** * Splits a command string by chain operators (&&, ||, ;) while respecting quotes. * Returns null when no chain is present or when the chain is malformed. */ function splitCommandChain(command) { const parts = splitCommandChainWithOperators(command); if (!parts) return null; return parts.map((p) => p.part); } function analyzeShellCommand(params) { if (isWindowsPlatform(params.platform)) return analyzeWindowsShellCommand(params); const chainParts = splitCommandChain(params.command); if (chainParts) { const chains = []; const allSegments = []; for (const part of chainParts) { const pipelineSplit = splitShellPipeline(part); if (!pipelineSplit.ok) return { ok: false, reason: pipelineSplit.reason, segments: [] }; const segments = parseSegmentsFromParts(pipelineSplit.segments, params.cwd, params.env, params.platform); if (!segments) return { ok: false, reason: "unable to parse shell segment", segments: [] }; chains.push(segments); allSegments.push(...segments); } return { ok: true, segments: allSegments, chains }; } const split = splitShellPipeline(params.command); if (!split.ok) return { ok: false, reason: split.reason, segments: [] }; const segments = parseSegmentsFromParts(split.segments, params.cwd, params.env, params.platform); if (!segments) return { ok: false, reason: "unable to parse shell segment", segments: [] }; return { ok: true, segments }; } function analyzeArgvCommand(params) { const argv = params.argv.filter((entry) => entry.trim().length > 0); if (argv.length === 0) return { ok: false, reason: "empty argv", segments: [] }; return { ok: true, segments: [{ raw: argv.join(" "), argv, sourceArgv: [...params.argv], resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env, params.platform ?? void 0) }] }; } //#endregion export { buildSafeShellCommand as a, splitCommandChain as c, buildSafeBinsShellCommand as i, splitCommandChainWithOperators as l, analyzeShellCommand as n, isWindowsPlatform as o, buildEnforcedShellCommand as r, resolvePlannedSegmentArgv as s, analyzeArgvCommand as t, windowsEscapeArg as u };