import { z } from 'zod'; import { CallTemplate, Serializer, CommunicationProtocol, IUtcpClient, RegisterManualResult } from '@utcp/sdk'; /** * REQUIRED * Configuration for a single command step in a CLI execution flow. * * Attributes: * command: The command string to execute. Can contain UTCP_ARG_argname_UTCP_END * placeholders that will be replaced with values from tool_args. Can also * reference previous command outputs using $CMD_0_OUTPUT, $CMD_1_OUTPUT, etc. * * Placeholders are NOT inlined as text. Instead the protocol * emits a context-aware shell variable reference (`"$VAR"` / * `${VAR}` / `$env:VAR`) and ships the actual `tool_args` value * to the subprocess via an environment variable, so the shell * expands the value AFTER it has parsed the script. Attacker- * controlled bytes therefore cannot inject commands or escape * any quoting context. * * A placeholder always substitutes a **single logical value** * (never a list of shell words) -- the substituted value cannot * be reinterpreted as additional shell syntax. Several * placeholders may appear within the same quoted region (e.g. * `"https://api/UTCP_ARG_id_UTCP_END/UTCP_ARG_action_UTCP_END"`) * and they compose with the surrounding literal text into one * shell argument. Tools that previously relied on a single * placeholder splitting into multiple flags (e.g. * `UTCP_ARG_flags_UTCP_END` -> `--verbose --debug`) must now * use one placeholder per intended flag. This change ships * with @utcp/cli 1.1.1. * * PowerShell limitation: a placeholder appearing inside a * single-quoted PowerShell string (`'...'`) will throw at * script-build time -- PowerShell does not expand variables * inside single quotes, and rewriting the surrounding token * is too brittle. Use a double-quoted string ("...") instead. * append_to_final_output: Whether this command's output should be included * in the final result. If not specified, defaults to False for all * commands except the last one. * * Examples: * Basic command step: * ```json * { * "command": "git status", * "append_to_final_output": true * } * ``` * * Command with argument placeholders and output reference: * ```json * { * "command": "echo \"Cloning to: UTCP_ARG_target_dir_UTCP_END, previous status: $CMD_0_OUTPUT\"", * "append_to_final_output": true * } * ``` */ interface CommandStep { /** * Command string to execute, may contain UTCP_ARG_argname_UTCP_END * placeholders. Each placeholder substitutes a single value via a * shell-variable reference whose form is chosen for the surrounding * quote context; substituted values cannot be reinterpreted as * additional shell syntax. Several placeholders may appear in the * same quoted region and compose into one argument. */ command: string; /** * Whether to include this command's output in final result. Defaults to False for all except last command */ append_to_final_output?: boolean | null; } /** * Zod schema for CommandStep. */ declare const CommandStepSchema: z.ZodType; /** * REQUIRED * Call template configuration for Command Line Interface (CLI) tools. * * This class defines the configuration for executing command-line tools and * programs as UTCP tool providers. Commands are executed in a single subprocess * to maintain state (like directory changes) between commands. * * **Cross-Platform Script Generation:** * - **Windows**: Commands are converted to a PowerShell script * - **Unix/Linux/macOS**: Commands are converted to a Bash script * * **Command Syntax Requirements:** * - Windows: Use PowerShell syntax (e.g., `Get-ChildItem`, `Set-Location`) * - Unix: Use Bash/shell syntax (e.g., `ls`, `cd`) * * **Referencing Previous Command Output:** * You can reference the output of previous commands using variables: * - **PowerShell**: `$CMD_0_OUTPUT`, `$CMD_1_OUTPUT`, etc. * - **Bash**: `$CMD_0_OUTPUT`, `$CMD_1_OUTPUT`, etc. * * Example: `echo "Previous result: $CMD_0_OUTPUT"` * * **Argument Substitution (@utcp/cli >= 1.1.1):** * `UTCP_ARG_argname_UTCP_END` placeholders are replaced with a * context-aware shell variable reference (`"$VAR"` outside quotes, * `${VAR}` inside double quotes, an adjacent-quote concat trick inside * single-quoted bash). The actual `tool_args` value is shipped to the * subprocess via a fresh, per-invocation env var; the shell expands it * at runtime AFTER it has parsed the script, so attacker-controlled * bytes cannot inject commands or escape any quoting context. * * A placeholder always substitutes a single logical value (never a * list of shell words). Several placeholders may appear in one quoted * region and compose with the surrounding text into one argument * (e.g. `"https://api/UTCP_ARG_id_UTCP_END/UTCP_ARG_action_UTCP_END"`). * If a tool needs multiple separate flags, use one placeholder per * flag in bare position. PowerShell single-quoted strings cannot * expand variables, so a placeholder inside `'...'` on Windows raises * a build-time error; use a double-quoted string instead. * * **Subprocess Environment (@utcp/cli >= 1.1.1):** * The CLI subprocess no longer inherits the full host environment. * Inheritance is controlled by `inherit_env_vars`: * - Omitted / `null`: a built-in default allowlist of host variables * is passed through (e.g. `PATH`, `PATHEXT`, `SYSTEMROOT`, `HOME`, * `LANG`) so shells and binaries can be located normally. * - `[]`: strict mode -- nothing from the host environment is * inherited; only `env_vars` is propagated. * - `["FOO", "BAR"]`: exactly those host variables are passed * through. The default allowlist is NOT merged in, so callers that * still need `PATH` must list it explicitly. * `env_vars` is always applied on top and overrides any inherited * value. Values in `env_vars` may be plain strings or `${VARNAME}` * style placeholders resolved by the UTCP client's variable * substitutor (note: those placeholders are resolved against the UTCP * client's variable sources, not against the host shell -- to forward * a host variable by name use `inherit_env_vars`). This closes the * secret-exfiltration vector. * * Attributes: * call_template_type: The type of the call template. Must be "cli". * commands: A list of CommandStep objects defining the commands to execute * in order. Each command can contain UTCP_ARG_argname_UTCP_END placeholders * that will be replaced with values from tool_args during execution. * Placeholders are shell-quoted and therefore expand to exactly * one shell token (see class docstring). * env_vars: A dictionary of environment variables to set for the command's * execution context. Values can be static strings or placeholders for * variables from the UTCP client's variable substitutor. Always * propagated; overrides anything inherited from the host. * inherit_env_vars: Controls which host environment variables are * passed through to the subprocess. * - `undefined` / `null` (default): the built-in default * allowlist (`PATH`, `HOME`, `LANG` on Unix; `PATH`, * `PATHEXT`, `SYSTEMROOT`, `USERPROFILE`, etc. on Windows) * is inherited so shells and binaries work without extra * configuration. * - `[]`: strict mode -- no host variables are inherited at * all. Only `env_vars` reaches the subprocess. * - `["FOO", "BAR"]`: exactly those host variables are * inherited. The default allowlist is replaced, not * extended, so include `PATH` (and any other required * shell vars) yourself if needed. * Variables named here that are not set on the host are * silently skipped. Use this to expose specific host secrets * such as `OPENAI_API_KEY`, `AWS_PROFILE`, or `NODE_PATH` * without putting their values in the call template. * working_dir: The working directory from which to run the commands. If not * provided, it defaults to the current process's working directory. * auth: Authentication details. Not applicable to the CLI protocol, so it * is always None. * * Examples: * Cross-platform directory operations: * ```json * { * "name": "cross_platform_dir_tool", * "call_template_type": "cli", * "commands": [ * { * "command": "cd UTCP_ARG_target_dir_UTCP_END", * "append_to_final_output": false * }, * { * "command": "ls -la", * "append_to_final_output": true * } * ] * } * ``` * * Referencing previous command output: * ```json * { * "name": "reference_previous_output_tool", * "call_template_type": "cli", * "commands": [ * { * "command": "git status --porcelain", * "append_to_final_output": false * }, * { * "command": "echo \"Found changes: $CMD_0_OUTPUT\"", * "append_to_final_output": true * } * ] * } * ``` * * Command with environment variables and placeholders: * ```json * { * "name": "python_multi_step_tool", * "call_template_type": "cli", * "commands": [ * { * "command": "python setup.py install", * "append_to_final_output": false * }, * { * "command": "python script.py --input UTCP_ARG_input_file_UTCP_END --result \"$CMD_0_OUTPUT\"" * } * ], * "env_vars": { * "PYTHONPATH": "/custom/path", * "API_KEY": "${API_KEY_VAR}" * } * } * ``` * * Security Considerations: * - Commands are executed in a subprocess. Ensure that the commands * specified are from a trusted source. * - Avoid passing unsanitized user input directly into the command string. * Use tool argument validation where possible. * - All placeholders are replaced with string values from tool_args. * - Commands should use the appropriate syntax for the target platform * (PowerShell on Windows, Bash on Unix). * - Previous command outputs are available as variables but should be * used carefully to avoid command injection. */ interface CliCallTemplate extends CallTemplate { call_template_type: 'cli'; commands: CommandStep[]; env_vars?: Record | null; inherit_env_vars?: string[] | null; working_dir?: string | null; auth?: undefined; allowed_communication_protocols?: string[]; } /** * Zod schema for CLI Call Template. */ declare const CliCallTemplateSchema: z.ZodType; /** * REQUIRED * Serializer for converting between `CliCallTemplate` and dictionary representations. * * This class handles the serialization and deserialization of `CliCallTemplate` * objects, ensuring that they can be correctly represented as dictionaries and * reconstructed from them, with validation. */ declare class CliCallTemplateSerializer extends Serializer { /** * REQUIRED * Converts a `CliCallTemplate` instance to its dictionary representation. * * @param obj The `CliCallTemplate` instance to serialize. * @returns A dictionary representing the `CliCallTemplate`. */ toDict(obj: CliCallTemplate): Record; /** * REQUIRED * Validates a dictionary and constructs a `CliCallTemplate` instance. * * @param obj The dictionary to validate and deserialize. * @returns A `CliCallTemplate` instance. * @throws Error if the dictionary is not a valid representation of a `CliCallTemplate`. */ validateDict(obj: Record): CliCallTemplate; } /** * Command Line Interface (CLI) communication protocol for the UTCP client. * * This module provides an implementation of the `CommunicationProtocol` interface * that enables the UTCP client to interact with command-line tools. It supports * discovering tools by executing a command and parsing its output for a UTCP * manual, as well as calling those tools with arguments. * * Key Features: * - Asynchronous execution of shell commands. * - Tool discovery by running a command that outputs a UTCP manual. * - Flexible argument formatting for different CLI conventions. * - Support for environment variables and custom working directories. * - Cross-platform command parsing for Windows and Unix-like systems. * * Security Considerations: * Executing arbitrary command-line tools can be dangerous. This protocol * should only be used with trusted tools. */ /** * REQUIRED * Communication protocol for interacting with CLI-based tool providers. * * This class implements the `CommunicationProtocol` interface to handle * communication with command-line tools. It discovers tools by executing a * command specified in a `CliCallTemplate` and parsing the output for a UTCP * manual. It also executes tool calls by running the corresponding command * with the provided arguments. */ declare class CliCommunicationProtocol implements CommunicationProtocol { /** * Log informational messages. */ private _log_info; /** * Log error messages. */ private _log_error; /** * Default set of host environment variables propagated to the CLI * subprocess when `CliCallTemplate.inherit_env_vars` is not provided * (i.e. undefined / null). Locating binaries (`PATH` / `PATHEXT`), * basic shell + locale state, and Windows runtime paths are needed for * almost any tool to start. Anything else (cloud creds, API keys, * internal tokens) must be opted in by listing the variable name in * `inherit_env_vars`, or its value provided in `env_vars`. * * If `inherit_env_vars === []`, the caller is opting into strict mode * and NOTHING is inherited from the host -- only `env_vars` reaches * the subprocess. * * Backs GHSA-r8j5-8747-88cm (sister advisory of python-utcp's * GHSA-5v57-8rxj-3p2r): the previous implementation handed the full * `process.env` to the subprocess, which combined with the command * injection in `_substitute_utcp_args` let an attacker exfiltrate * every secret in the host process. */ private static readonly _DEFAULT_INHERITED_KEYS_UNIX; private static readonly _DEFAULT_INHERITED_KEYS_WINDOWS; private _default_inherited_keys; /** * Prepare environment variables for command execution. * * Composes the subprocess environment with one layer of host * inheritance (controlled by `provider.inherit_env_vars`) plus * `provider.env_vars` on top: * * - `inherit_env_vars` undefined / null (default): pass through the * built-in default allowlist of host vars (`PATH`, `HOME` / * `PATHEXT`, `SYSTEMROOT`, etc.) so normal shells and binaries * work without extra wiring. * - `inherit_env_vars === []`: strict mode. Nothing from the host * environment reaches the subprocess -- only `env_vars`. * - `inherit_env_vars === [...]`: pass through exactly the named * host variables. The default allowlist is NOT merged in, so * callers who still want `PATH` must include it explicitly. * * `env_vars` is always applied last and overrides anything inherited * from the host. * * @param provider The CLI provider * @returns Environment variables dictionary */ private _prepare_environment; private _executeShellScript; /** * Generate an unguessable nonce that namespaces the env vars used for * argument substitution within a single tool invocation. Prevents a * template author from being able to write a literal * `${__UTCP_ARG__}` reference that collides with our * substitution slot, which would re-introduce * unquoted-variable-expansion injection. */ private static _make_nonce; /** * Compute the env-var name that carries one substituted tool_arg * value into the subprocess. The nonce is fresh per invocation so * `${__UTCP_ARG__}` literals cannot exist in templates * authored before invocation time. */ private static _env_var_name; /** * Substitute `UTCP_ARG__UTCP_END` placeholders in a command * string by emitting context-appropriate shell variable references * and recording the actual values as env vars on the returned object. * The caller wires those env vars into the subprocess (alongside the * call template's `env_vars` and host-inheritance allowlist) so the * shell expands them at runtime, AFTER it has already parsed the * script. As a result, attacker-controlled `toolArgs` never get * spliced into the script source and therefore cannot inject * commands or escape any quoting context. * * Quote-state tracking ensures the emitted reference is correct for * its surrounding context: * * bash (Unix): * - bare: `"$VAR"` (quoted: no word splitting) * - inside double quotes: `${VAR}` (bash expands inside dq) * - inside single quotes: `'"$VAR"'` (close sq, dq with var, * reopen sq -- bash treats * adjacent quoted regions * as a single token) * * powershell (Windows): * - bare: `${env:VAR}` * - inside double quotes: `${env:VAR}` (PS expands inside dq; * braced form prevents * suffix characters * from being consumed * into the var name) * - inside single quotes: error -- PS does not expand inside * single-quoted strings, so * we cannot safely substitute * without rewriting the entire * surrounding token. Author * must use a double-quoted * string. * * Backs the sister advisory of python-utcp's GHSA-33p6-5jxp-p3x4. * An earlier fix that did inline `shlex.quote`-style substitution was * still vulnerable when the placeholder sat inside a surrounding `"` * region: e.g. template `curl "https://api/UTCP_ARG_id_UTCP_END"` * with `id = '"; rm -rf /; "'` produced * `curl "https://api/'"; rm -rf /; "'"`, where bash's parser closed * the outer dq early and ran the injected commands. * * @param command Command string containing UTCP_ARG__UTCP_END placeholders * @param toolArgs Dictionary of argument names and values * @param nonce Per-invocation nonce used to namespace generated env vars * @returns { command, env } where `command` is safe to embed in a * shell script and `env` is the additional env vars the * subprocess must receive for the references to expand. */ private _substitute_utcp_args; private _substitute_bash; private _substitute_powershell; /** * Build a combined shell script from multiple commands. * * Returns both the script and the env-var contributions accumulated * across all command steps. Callers must merge these env vars into * the subprocess environment so the placeholder references the * script writes (`$VAR` / `${VAR}` / `$env:VAR`) actually resolve to * the original tool_arg values at runtime. * * @param commands List of CommandStep objects to combine * @param toolArgs Tool arguments for placeholder substitution * @returns { script, env } — script is the shell script source, * env is the additional UTCP_ARG_* env vars to inject. */ private _build_combined_shell_script; /** * REQUIRED * Registers a CLI-based manual and discovers its tools. * * This method executes the command specified in the `CliCallTemplate`'s * commands. It then attempts to parse the command's output (stdout) as a * UTCP manual in JSON format. * * @param caller The UTCP client instance that is calling this method. * @param manualCallTemplate The `CliCallTemplate` containing the details for * tool discovery, such as the command to run. * @returns A `RegisterManualResult` object indicating whether the registration * was successful and containing the discovered tools. * @throws Error if the `manualCallTemplate` is not an instance of * `CliCallTemplate` or if commands are not set. */ registerManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise; /** * REQUIRED * Deregisters a CLI manual. * * For the CLI protocol, this is a no-op as there are no persistent * connections to terminate. * * @param caller The UTCP client instance that is calling this method. * @param manualCallTemplate The call template of the manual to deregister. */ deregisterManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise; /** * REQUIRED * Calls a CLI tool by executing its command. * * This method constructs and executes the command specified in the * `CliCallTemplate`. It formats the provided `tool_args` as command-line * arguments and runs the command in a subprocess. * * @param caller The UTCP client instance that is calling this method. * @param toolName The name of the tool to call. * @param toolArgs A dictionary of arguments for the tool call. * @param toolCallTemplate The `CliCallTemplate` for the tool. * @returns The result of the command execution. If the command exits with a code * of 0, it returns the content of stdout. If the exit code is non-zero, * it returns the content of stderr. * @throws Error if `toolCallTemplate` is not an instance of * `CliCallTemplate` or if commands are not set. */ callTool(caller: IUtcpClient, toolName: string, toolArgs: Record, toolCallTemplate: CallTemplate): Promise; /** * REQUIRED * Execute a tool call through the CLI transport streamingly. * * The CLI protocol does not natively support streaming, so the command is * executed to completion and the full result is yielded as a single chunk. */ callToolStreaming(caller: IUtcpClient, toolName: string, toolArgs: Record, toolCallTemplate: CallTemplate): AsyncGenerator; close(): Promise; } /** * Registers the CLI protocol's CallTemplate serializer * and its CommunicationProtocol implementation. * This function should be called once when the CLI plugin is loaded. */ declare function register(override?: boolean): void; export { type CliCallTemplate, CliCallTemplateSchema, CliCallTemplateSerializer, CliCommunicationProtocol, type CommandStep, CommandStepSchema, register };