CLI utilities for UTCP
npm install @utcp/cli@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.
bash
bun add @utcp/cli @utcp/sdk
Or using npm
npm install @utcp/cli @utcp/sdk
`
Note: @utcp/sdk is a peer dependency.
Usage
The CLI plugin registers automatically when you import it—no manual registration needed. Simply import from @utcp/cli to enable CLI support.
`typescript
// 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);
`
Advanced Configuration
$3
Execute multiple commands in sequence within a single subprocess. State (like directory changes) is maintained between commands:
`typescript
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'
});
`
$3
Reference the output of previous commands using variables:
`typescript
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:
- PowerShell (Windows): $CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.
- Bash (Unix/Linux/macOS): $CMD_0_OUTPUT, $CMD_1_OUTPUT, etc.
$3
Use UTCP_ARG_argname_UTCP_END placeholders for dynamic argument substitution:
`typescript
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'
});
`
$3
Configure custom environment variables with UTCP variable substitution:
`typescript
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
}
});
`
$3
Create a CLI provider that discovers its tools dynamically:
`typescript
// 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:
`typescript
// 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...
`
$3
Important: Use platform-appropriate syntax for your commands:
Windows (PowerShell):
`typescript
{
commands: [
{ command: 'Get-ChildItem -Path UTCP_ARG_path_UTCP_END' },
{ command: 'Set-Location -Path UTCP_ARG_new_dir_UTCP_END' }
]
}
`
Unix/Linux/macOS (Bash):
`typescript
{
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.
Output Control
Control which command outputs appear in the final result using append_to_final_output:
`typescript
{
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:
- If append_to_final_output is not specified, only the last command's output is included in the final result.
- Set explicitly to true or false to override this behavior for any command.
JSON Output Handling
The CLI plugin automatically detects and parses JSON output:
`typescript
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.
Command Execution Details
$3
All commands in a template are executed in a single subprocess, which means:
- State is preserved between commands (e.g., directory changes with cd)
- Environment variables persist across commands
- Variables set in one command can be used in later commands
$3
The plugin generates platform-specific scripts:
Windows (PowerShell):
`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):
`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}"
`
$3
Default timeouts:
- Tool Discovery: 30 seconds
- Tool Execution: 120 seconds
These provide ample time for multi-command workflows.
API Reference
$3
`typescript
interface CliCallTemplate {
name?: string;
call_template_type: 'cli';
commands: CommandStep[];
env_vars?: Record | null;
working_dir?: string | null;
auth?: undefined; // Not applicable for CLI
}
`
$3
`typescript
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)
}
`
$3
- Argument Placeholder: UTCP_ARG_argname_UTCP_END
- Replaced with the value from tool_args
- Example: UTCP_ARG_filename_UTCP_END → 'myfile.txt'
- Output Reference: $CMD_N_OUTPUT (where N is the command index starting from 0)
- References the output of previous commands
- Example: $CMD_0_OUTPUT → output from first command
Security Considerations
⚠️ Important Security Notes:
1. Trusted Commands Only: Commands are executed in a subprocess. Only use CLI templates from trusted sources.
2. Avoid Unsanitized Input: Do not pass unsanitized user input directly into command strings.
3. Command Injection: Be cautious when using output references ($CMD_N_OUTPUT) to avoid command injection vulnerabilities.
4. Validate Arguments: Use proper input validation in your tool definitions.
5. Environment Variables: Be careful with sensitive data in environment variables.
Best Practices
1. Use Placeholders: Always use UTCP_ARG_*_UTCP_END placeholders instead of string concatenation.
2. Explicit Output Control: Explicitly set append_to_final_output for clarity in multi-command workflows.
3. Error Handling: Wrap tool calls in try-catch blocks for robust error handling.
4. Working Directory: Specify working_dir when commands depend on specific file locations.
5. Platform-Specific Syntax: Use appropriate command syntax for your target platform(s).
6. Tool Discovery: Implement discovery commands that output valid UTCP manuals for dynamic tool registration.
7. Timeouts: Be mindful of execution time for long-running commands.
Examples
$3
`typescript
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'
});
`
$3
`typescript
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'
}
});
`
$3
`typescript
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'
});
``