
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
The @utcp/cli package enables the UtcpClient to interact with command-line tools and programs as UTCP tool providers. This plugin provides a cross-platform way to execute shell commands, scripts, and CLI utilities with full support for multi-step workflows, environment variables, and output chaining.
$CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.).UTCP_ARG_argname_UTCP_END placeholders for dynamic argument substitution.bun add @utcp/cli @utcp/sdk
# Or using npm
npm install @utcp/cli @utcp/sdk
Note: @utcp/sdk is a peer dependency.
The CLI plugin registers automatically when you import it—no manual registration needed. Simply import from @utcp/cli to enable CLI support.
// From your application's entry point
import { UtcpClient } from '@utcp/sdk';
import { CliCallTemplateSerializer } from '@utcp/cli';
async function main() {
// Define a CLI CallTemplate for single command execution
const serializer = new CliCallTemplateSerializer();
const gitStatusTemplate = serializer.validateDict({
name: 'git_status_tool',
call_template_type: 'cli',
commands: [
{
command: 'git status --porcelain',
append_to_final_output: true
}
],
working_dir: '/path/to/your/repo'
});
const client = await UtcpClient.create(process.cwd(), {
manual_call_templates: [gitStatusTemplate]
});
console.log('CLI Plugin active. Executing tools...');
// Call the CLI tool
try {
const result = await client.callTool('git_status_tool', {});
console.log('Git status result:', result);
} catch (error) {
console.error('Error calling CLI tool:', error);
}
await client.close();
}
main().catch(console.error);
Execute multiple commands in sequence within a single subprocess. State (like directory changes) is maintained between commands:
const serializer = new CliCallTemplateSerializer();
const multiStepTemplate = serializer.validateDict({
name: 'multi_step_workflow',
call_template_type: 'cli',
commands: [
{
command: 'cd UTCP_ARG_target_dir_UTCP_END',
append_to_final_output: false // Don't include this output in final result
},
{
command: 'git pull origin main',
append_to_final_output: false // Don't include this output
},
{
command: 'npm install',
append_to_final_output: false // Don't include this output
},
{
command: 'npm test',
append_to_final_output: true // Include this output in final result
}
]
});
// Call with arguments
const result = await client.callTool('multi_step_workflow', {
target_dir: '/path/to/project'
});
Reference the output of previous commands using variables:
const serializer = new CliCallTemplateSerializer();
const outputChainingTemplate = serializer.validateDict({
name: 'chained_commands',
call_template_type: 'cli',
commands: [
{
command: 'git rev-parse --short HEAD',
append_to_final_output: false
},
{
command: 'echo "Building version: $CMD_0_OUTPUT"',
append_to_final_output: false
},
{
command: 'docker build -t myapp:$CMD_0_OUTPUT .',
append_to_final_output: true
}
]
});
Platform-Specific Variable Reference:
$CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.$CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.Use UTCP_ARG_argname_UTCP_END placeholders for dynamic argument substitution:
const serializer = new CliCallTemplateSerializer();
const templateWithArgs = serializer.validateDict({
name: 'deploy_service',
call_template_type: 'cli',
commands: [
{
command: 'kubectl set image deployment/UTCP_ARG_service_name_UTCP_END ' +
'UTCP_ARG_service_name_UTCP_END=UTCP_ARG_image_tag_UTCP_END',
append_to_final_output: true
},
{
command: 'kubectl rollout status deployment/UTCP_ARG_service_name_UTCP_END',
append_to_final_output: true
}
]
});
// Call with arguments - placeholders will be replaced
const result = await client.callTool('deploy_service', {
service_name: 'api-server',
image_tag: 'myapp:v1.2.3'
});
Configure custom environment variables with UTCP variable substitution:
const serializer = new CliCallTemplateSerializer();
const envVarTemplate = serializer.validateDict({
name: 'python_script',
call_template_type: 'cli',
commands: [
{
command: 'python analysis.py --input UTCP_ARG_input_file_UTCP_END'
}
],
env_vars: {
PYTHONPATH: '/custom/python/path',
API_KEY: '${MY_API_KEY}', // Uses UTCP variable substitution
LOG_LEVEL: 'debug',
DATABASE_URL: '${DATABASE_URL}'
},
working_dir: '/path/to/scripts'
});
const client = await UtcpClient.create(process.cwd(), {
manual_call_templates: [envVarTemplate],
variables: {
python__script_MY_API_KEY: 'secret-key-123', // Namespaced variable
python__script_DATABASE_URL: 'postgresql://localhost/mydb' // Namespaced variable
}
});
Create a CLI provider that discovers its tools dynamically:
// Your CLI script should output a UTCP manual when called with a discovery flag
const serializer = new CliCallTemplateSerializer();
const discoveryTemplate = serializer.validateDict({
name: 'my_cli_tools',
call_template_type: 'cli',
commands: [
{
command: 'node my-cli-tool.js --utcp-discover',
append_to_final_output: true
}
]
});
const client = await UtcpClient.create(process.cwd(), {
manual_call_templates: [discoveryTemplate]
});
// Tools are automatically discovered and registered
const tools = await client.searchTools('my_cli_tools');
console.log('Discovered tools:', tools.map(t => t.name));
Example CLI Tool with Discovery:
// my-cli-tool.ts
if (process.argv.includes('--utcp-discover')) {
const manual = {
utcp_version: "1.0.0",
manual_version: "1.0.0",
tools: [
{
name: "echo_cli",
description: "Echoes a message via CLI.",
inputs: {
type: "object",
properties: { message: { type: "string" } },
required: ["message"]
},
outputs: { type: "string" },
tags: ["cli", "echo"],
tool_call_template: {
name: "my_cli_tools",
call_template_type: "cli"
}
}
]
};
console.log(JSON.stringify(manual));
process.exit(0);
}
// Handle other commands...
Important: Use platform-appropriate syntax for your commands:
Windows (PowerShell):
{
commands: [
{ command: 'Get-ChildItem -Path UTCP_ARG_path_UTCP_END' },
{ command: 'Set-Location -Path UTCP_ARG_new_dir_UTCP_END' }
]
}
Unix/Linux/macOS (Bash):
{
commands: [
{ command: 'ls -la UTCP_ARG_path_UTCP_END' },
{ command: 'cd UTCP_ARG_new_dir_UTCP_END' }
]
}
The CLI plugin automatically detects the platform and executes commands using the appropriate shell.
Control which command outputs appear in the final result using append_to_final_output:
{
commands: [
{
command: 'git pull',
append_to_final_output: false // Don't include in result
},
{
command: 'npm test',
append_to_final_output: true // Include in result
},
{
command: 'npm run build',
append_to_final_output: true // Include in result
}
]
}
Default Behavior:
append_to_final_output is not specified, only the last command's output is included in the final result.true or false to override this behavior for any command.The CLI plugin automatically detects and parses JSON output:
const serializer = new CliCallTemplateSerializer();
const jsonOutputTemplate = serializer.validateDict({
name: 'json_cli_tool',
call_template_type: 'cli',
commands: [
{
command: 'echo \'{"status": "success", "count": 42}\''
}
]
});
const result = await client.callTool('json_cli_tool', {});
// result = { status: "success", count: 42 } // Automatically parsed as JSON
If output doesn't start with { or [, it's returned as plain text.
All commands in a template are executed in a single subprocess, which means:
cd)The plugin generates platform-specific scripts:
Windows (PowerShell):
$ErrorActionPreference = "Stop"
# Variables to store command outputs
$CMD_0_OUTPUT = git status 2>&1 | Out-String
$CMD_1_OUTPUT = echo "Status: $CMD_0_OUTPUT" 2>&1 | Out-String
Write-Output $CMD_1_OUTPUT
Unix (Bash):
#!/bin/bash
# Variables to store command outputs
CMD_0_OUTPUT=$(git status 2>&1)
CMD_1_OUTPUT=$(echo "Status: $CMD_0_OUTPUT" 2>&1)
echo "${CMD_1_OUTPUT}"
Default timeouts:
These provide ample time for multi-command workflows.
interface CliCallTemplate {
name?: string;
call_template_type: 'cli';
commands: CommandStep[];
env_vars?: Record<string, string> | null;
working_dir?: string | null;
auth?: undefined; // Not applicable for CLI
}
interface CommandStep {
command: string; // Command to execute with optional placeholders
append_to_final_output?: boolean; // Include in final result (default: true for last command only)
}
Argument Placeholder: UTCP_ARG_argname_UTCP_END
tool_argsUTCP_ARG_filename_UTCP_END → 'myfile.txt'Output Reference: $CMD_N_OUTPUT (where N is the command index starting from 0)
$CMD_0_OUTPUT → output from first command⚠️ Important Security Notes:
$CMD_N_OUTPUT) to avoid command injection vulnerabilities.UTCP_ARG_*_UTCP_END placeholders instead of string concatenation.append_to_final_output for clarity in multi-command workflows.working_dir when commands depend on specific file locations.const serializer = new CliCallTemplateSerializer();
const fileOpsTemplate = serializer.validateDict({
name: 'file_operations',
call_template_type: 'cli',
commands: [
{
command: 'cat UTCP_ARG_filename_UTCP_END'
}
]
});
const content = await client.callTool('file_operations', {
filename: '/path/to/file.txt'
});
const serializer = new CliCallTemplateSerializer();
const buildPipeline = serializer.validateDict({
name: 'build_and_test',
call_template_type: 'cli',
commands: [
{
command: 'npm run lint',
append_to_final_output: false
},
{
command: 'npm test',
append_to_final_output: true
},
{
command: 'npm run build',
append_to_final_output: true
}
],
env_vars: {
NODE_ENV: 'production',
CI: 'true'
}
});
const serializer = new CliCallTemplateSerializer();
const gitWorkflow = serializer.validateDict({
name: 'git_commit_push',
call_template_type: 'cli',
commands: [
{
command: 'git add .',
append_to_final_output: false
},
{
command: 'git commit -m "UTCP_ARG_message_UTCP_END"',
append_to_final_output: false
},
{
command: 'git push origin UTCP_ARG_branch_UTCP_END',
append_to_final_output: true
}
]
});
const result = await client.callTool('git_commit_push', {
message: 'feat: add new feature',
branch: 'main'
});
This package is part of the UTCP project. See the main repository for license information.
FAQs
CLI utilities for UTCP
We found that @utcp/cli demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.