utcp-cli 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: utcp-cli
3
+ Version: 1.0.2
4
+ Summary: UTCP communication protocol plugin for wrapping local command-line tools.
5
+ Author: UTCP Contributors
6
+ License-Expression: MPL-2.0
7
+ Project-URL: Homepage, https://utcp.io
8
+ Project-URL: Source, https://github.com/universal-tool-calling-protocol/python-utcp
9
+ Project-URL: Issues, https://github.com/universal-tool-calling-protocol/python-utcp/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: pyyaml>=6.0
18
+ Requires-Dist: utcp>=1.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: build; extra == "dev"
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: coverage; extra == "dev"
25
+ Requires-Dist: twine; extra == "dev"
26
+
27
+ # UTCP CLI Plugin
28
+
29
+ [![PyPI Downloads](https://static.pepy.tech/badge/utcp-cli)](https://pepy.tech/projects/utcp-cli)
30
+
31
+ Command-line interface plugin for UTCP, enabling integration with command-line tools and processes.
32
+
33
+ ## Features
34
+
35
+ - **Command Execution**: Run any command-line tool as a UTCP tool
36
+ - **Environment Variables**: Secure credential and configuration passing
37
+ - **Working Directory Control**: Execute commands in specific directories
38
+ - **Input/Output Handling**: Support for stdin, stdout, stderr processing
39
+ - **Cross-Platform**: Works on Windows, macOS, and Linux
40
+ - **Timeout Management**: Configurable execution timeouts
41
+ - **Argument Validation**: Optional input sanitization
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install utcp-cli
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from utcp.utcp_client import UtcpClient
53
+
54
+ # Basic CLI tool
55
+ client = await UtcpClient.create(config={
56
+ "manual_call_templates": [{
57
+ "name": "file_tools",
58
+ "call_template_type": "cli",
59
+ "command_name": "ls -la ${path}"
60
+ }]
61
+ })
62
+
63
+ result = await client.call_tool("file_tools.list", {"path": "/home"})
64
+ ```
65
+
66
+ ## Configuration Examples
67
+
68
+ ### Basic Command
69
+ ```json
70
+ {
71
+ "name": "file_ops",
72
+ "call_template_type": "cli",
73
+ "command_name": "ls -la ${path}",
74
+ "working_dir": "/tmp"
75
+ }
76
+ ```
77
+
78
+ ### With Environment Variables
79
+ ```json
80
+ {
81
+ "name": "python_script",
82
+ "call_template_type": "cli",
83
+ "command_name": "python script.py ${input}",
84
+ "env_vars": {
85
+ "PYTHONPATH": "/custom/path",
86
+ "API_KEY": "${API_KEY}"
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Processing JSON with jq
92
+ ```json
93
+ {
94
+ "name": "json_processor",
95
+ "call_template_type": "cli",
96
+ "command_name": "jq '.data'",
97
+ "stdin": "${json_input}",
98
+ "timeout": 10
99
+ }
100
+ ```
101
+
102
+ ### Git Operations
103
+ ```json
104
+ {
105
+ "name": "git_tools",
106
+ "call_template_type": "cli",
107
+ "command_name": "git ${operation} ${args}",
108
+ "working_dir": "${repo_path}",
109
+ "env_vars": {
110
+ "GIT_AUTHOR_NAME": "${author_name}",
111
+ "GIT_AUTHOR_EMAIL": "${author_email}"
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## Security Considerations
117
+
118
+ - Commands run in isolated subprocesses
119
+ - Environment variables provide secure credential passing
120
+ - Working directory restrictions limit file system access
121
+ - Input validation prevents command injection
122
+
123
+ ```json
124
+ {
125
+ "name": "safe_grep",
126
+ "call_template_type": "cli",
127
+ "command_name": "grep ${pattern} ${file}",
128
+ "working_dir": "/safe/directory",
129
+ "allowed_args": {
130
+ "pattern": "^[a-zA-Z0-9_-]+$",
131
+ "file": "^[a-zA-Z0-9_./-]+\\.txt$"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Error Handling
137
+
138
+ ```python
139
+ from utcp.exceptions import ToolCallError
140
+ import subprocess
141
+
142
+ try:
143
+ result = await client.call_tool("cli_tool.command", {"arg": "value"})
144
+ except ToolCallError as e:
145
+ if isinstance(e.__cause__, subprocess.CalledProcessError):
146
+ print(f"Command failed with exit code {e.__cause__.returncode}")
147
+ print(f"stderr: {e.__cause__.stderr}")
148
+ ```
149
+
150
+ ## Common Use Cases
151
+
152
+ - **File Operations**: ls, find, grep, awk, sed
153
+ - **Data Processing**: jq, sort, uniq, cut
154
+ - **System Monitoring**: ps, top, df, netstat
155
+ - **Development Tools**: git, npm, pip, docker
156
+ - **Custom Scripts**: Python, bash, PowerShell scripts
157
+
158
+ ## Testing CLI Tools
159
+
160
+ ```python
161
+ import pytest
162
+ from utcp.utcp_client import UtcpClient
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_cli_tool():
166
+ client = await UtcpClient.create(config={
167
+ "manual_call_templates": [{
168
+ "name": "test_cli",
169
+ "call_template_type": "cli",
170
+ "command_name": "echo ${message}"
171
+ }]
172
+ })
173
+
174
+ result = await client.call_tool("test_cli.echo", {"message": "hello"})
175
+ assert "hello" in result["stdout"]
176
+ ```
177
+
178
+ ## Related Documentation
179
+
180
+ - [Main UTCP Documentation](../../../README.md)
181
+ - [Core Package Documentation](../../../core/README.md)
182
+ - [HTTP Plugin](../http/README.md)
183
+ - [MCP Plugin](../mcp/README.md)
184
+ - [Text Plugin](../text/README.md)
185
+
186
+ ## Examples
187
+
188
+ For complete examples, see the [UTCP examples repository](https://github.com/universal-tool-calling-protocol/utcp-examples).
@@ -0,0 +1,162 @@
1
+ # UTCP CLI Plugin
2
+
3
+ [![PyPI Downloads](https://static.pepy.tech/badge/utcp-cli)](https://pepy.tech/projects/utcp-cli)
4
+
5
+ Command-line interface plugin for UTCP, enabling integration with command-line tools and processes.
6
+
7
+ ## Features
8
+
9
+ - **Command Execution**: Run any command-line tool as a UTCP tool
10
+ - **Environment Variables**: Secure credential and configuration passing
11
+ - **Working Directory Control**: Execute commands in specific directories
12
+ - **Input/Output Handling**: Support for stdin, stdout, stderr processing
13
+ - **Cross-Platform**: Works on Windows, macOS, and Linux
14
+ - **Timeout Management**: Configurable execution timeouts
15
+ - **Argument Validation**: Optional input sanitization
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install utcp-cli
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from utcp.utcp_client import UtcpClient
27
+
28
+ # Basic CLI tool
29
+ client = await UtcpClient.create(config={
30
+ "manual_call_templates": [{
31
+ "name": "file_tools",
32
+ "call_template_type": "cli",
33
+ "command_name": "ls -la ${path}"
34
+ }]
35
+ })
36
+
37
+ result = await client.call_tool("file_tools.list", {"path": "/home"})
38
+ ```
39
+
40
+ ## Configuration Examples
41
+
42
+ ### Basic Command
43
+ ```json
44
+ {
45
+ "name": "file_ops",
46
+ "call_template_type": "cli",
47
+ "command_name": "ls -la ${path}",
48
+ "working_dir": "/tmp"
49
+ }
50
+ ```
51
+
52
+ ### With Environment Variables
53
+ ```json
54
+ {
55
+ "name": "python_script",
56
+ "call_template_type": "cli",
57
+ "command_name": "python script.py ${input}",
58
+ "env_vars": {
59
+ "PYTHONPATH": "/custom/path",
60
+ "API_KEY": "${API_KEY}"
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### Processing JSON with jq
66
+ ```json
67
+ {
68
+ "name": "json_processor",
69
+ "call_template_type": "cli",
70
+ "command_name": "jq '.data'",
71
+ "stdin": "${json_input}",
72
+ "timeout": 10
73
+ }
74
+ ```
75
+
76
+ ### Git Operations
77
+ ```json
78
+ {
79
+ "name": "git_tools",
80
+ "call_template_type": "cli",
81
+ "command_name": "git ${operation} ${args}",
82
+ "working_dir": "${repo_path}",
83
+ "env_vars": {
84
+ "GIT_AUTHOR_NAME": "${author_name}",
85
+ "GIT_AUTHOR_EMAIL": "${author_email}"
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Security Considerations
91
+
92
+ - Commands run in isolated subprocesses
93
+ - Environment variables provide secure credential passing
94
+ - Working directory restrictions limit file system access
95
+ - Input validation prevents command injection
96
+
97
+ ```json
98
+ {
99
+ "name": "safe_grep",
100
+ "call_template_type": "cli",
101
+ "command_name": "grep ${pattern} ${file}",
102
+ "working_dir": "/safe/directory",
103
+ "allowed_args": {
104
+ "pattern": "^[a-zA-Z0-9_-]+$",
105
+ "file": "^[a-zA-Z0-9_./-]+\\.txt$"
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Error Handling
111
+
112
+ ```python
113
+ from utcp.exceptions import ToolCallError
114
+ import subprocess
115
+
116
+ try:
117
+ result = await client.call_tool("cli_tool.command", {"arg": "value"})
118
+ except ToolCallError as e:
119
+ if isinstance(e.__cause__, subprocess.CalledProcessError):
120
+ print(f"Command failed with exit code {e.__cause__.returncode}")
121
+ print(f"stderr: {e.__cause__.stderr}")
122
+ ```
123
+
124
+ ## Common Use Cases
125
+
126
+ - **File Operations**: ls, find, grep, awk, sed
127
+ - **Data Processing**: jq, sort, uniq, cut
128
+ - **System Monitoring**: ps, top, df, netstat
129
+ - **Development Tools**: git, npm, pip, docker
130
+ - **Custom Scripts**: Python, bash, PowerShell scripts
131
+
132
+ ## Testing CLI Tools
133
+
134
+ ```python
135
+ import pytest
136
+ from utcp.utcp_client import UtcpClient
137
+
138
+ @pytest.mark.asyncio
139
+ async def test_cli_tool():
140
+ client = await UtcpClient.create(config={
141
+ "manual_call_templates": [{
142
+ "name": "test_cli",
143
+ "call_template_type": "cli",
144
+ "command_name": "echo ${message}"
145
+ }]
146
+ })
147
+
148
+ result = await client.call_tool("test_cli.echo", {"message": "hello"})
149
+ assert "hello" in result["stdout"]
150
+ ```
151
+
152
+ ## Related Documentation
153
+
154
+ - [Main UTCP Documentation](../../../README.md)
155
+ - [Core Package Documentation](../../../core/README.md)
156
+ - [HTTP Plugin](../http/README.md)
157
+ - [MCP Plugin](../mcp/README.md)
158
+ - [Text Plugin](../text/README.md)
159
+
160
+ ## Examples
161
+
162
+ For complete examples, see the [UTCP examples repository](https://github.com/universal-tool-calling-protocol/utcp-examples).
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "utcp-cli"
7
- version = "1.0.0"
7
+ version = "1.0.2"
8
8
  authors = [
9
9
  { name = "UTCP Contributors" },
10
10
  ]
11
- description = "Universal Tool Calling Protocol (UTCP) client library for Python"
11
+ description = "UTCP communication protocol plugin for wrapping local command-line tools."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
14
  dependencies = [
@@ -0,0 +1,110 @@
1
+ from typing import Optional, Dict, Literal
2
+ from pydantic import Field
3
+
4
+ from utcp.data.call_template import CallTemplate
5
+ from utcp.interfaces.serializer import Serializer
6
+ from utcp.exceptions import UtcpSerializerValidationError
7
+ import traceback
8
+
9
+ class CliCallTemplate(CallTemplate):
10
+ """REQUIRED
11
+ Call template configuration for Command Line Interface (CLI) tools.
12
+
13
+ This class defines the configuration for executing command-line tools and
14
+ programs as UTCP tool providers. It supports environment variable injection,
15
+ custom working directories, and defines the command to be executed.
16
+
17
+ Attributes:
18
+ call_template_type: The type of the call template. Must be "cli".
19
+ command_name: The command or path of the program to execute. It can
20
+ contain placeholders for arguments that will be substituted at
21
+ runtime (e.g., `${arg_name}`).
22
+ env_vars: A dictionary of environment variables to set for the command's
23
+ execution context. Values can be static strings or placeholders for
24
+ variables from the UTCP client's variable substitutor.
25
+ working_dir: The working directory from which to run the command. If not
26
+ provided, it defaults to the current process's working directory.
27
+ auth: Authentication details. Not applicable to the CLI protocol, so it
28
+ is always None.
29
+
30
+ Examples:
31
+ Basic CLI command:
32
+ ```json
33
+ {
34
+ "name": "list_files_tool",
35
+ "call_template_type": "cli",
36
+ "command_name": "ls -la",
37
+ "working_dir": "/tmp"
38
+ }
39
+ ```
40
+
41
+ Command with environment variables and argument placeholders:
42
+ ```json
43
+ {
44
+ "name": "python_script_tool",
45
+ "call_template_type": "cli",
46
+ "command_name": "python script.py --input ${input_file}",
47
+ "env_vars": {
48
+ "PYTHONPATH": "/custom/path",
49
+ "API_KEY": "${API_KEY_VAR}"
50
+ }
51
+ }
52
+ ```
53
+
54
+ Security Considerations:
55
+ - Commands are executed in a subprocess. Ensure that the commands
56
+ specified are from a trusted source.
57
+ - Avoid passing unsanitized user input directly into the command string.
58
+ Use tool argument validation where possible.
59
+ """
60
+
61
+ call_template_type: Literal["cli"] = "cli"
62
+ command_name: str
63
+ env_vars: Optional[Dict[str, str]] = Field(
64
+ default=None, description="Environment variables to set when executing the command"
65
+ )
66
+ working_dir: Optional[str] = Field(
67
+ default=None, description="Working directory for command execution"
68
+ )
69
+ auth: None = None
70
+
71
+
72
+ class CliCallTemplateSerializer(Serializer[CliCallTemplate]):
73
+ """REQUIRED
74
+ Serializer for converting between `CliCallTemplate` and dictionary representations.
75
+
76
+ This class handles the serialization and deserialization of `CliCallTemplate`
77
+ objects, ensuring that they can be correctly represented as dictionaries and
78
+ reconstructed from them, with validation.
79
+ """
80
+
81
+ def to_dict(self, obj: CliCallTemplate) -> dict:
82
+ """REQUIRED
83
+ Converts a `CliCallTemplate` instance to its dictionary representation.
84
+
85
+ Args:
86
+ obj: The `CliCallTemplate` instance to serialize.
87
+
88
+ Returns:
89
+ A dictionary representing the `CliCallTemplate`.
90
+ """
91
+ return obj.model_dump()
92
+
93
+ def validate_dict(self, obj: dict) -> CliCallTemplate:
94
+ """REQUIRED
95
+ Validates a dictionary and constructs a `CliCallTemplate` instance.
96
+
97
+ Args:
98
+ obj: The dictionary to validate and deserialize.
99
+
100
+ Returns:
101
+ A `CliCallTemplate` instance.
102
+
103
+ Raises:
104
+ UtcpSerializerValidationError: If the dictionary is not a valid
105
+ representation of a `CliCallTemplate`.
106
+ """
107
+ try:
108
+ return CliCallTemplate.model_validate(obj)
109
+ except Exception as e:
110
+ raise UtcpSerializerValidationError("Invalid CliCallTemplate: " + traceback.format_exc()) from e
@@ -1,28 +1,27 @@
1
- """Command Line Interface (CLI) transport for UTCP client.
1
+ """Command Line Interface (CLI) communication protocol for the UTCP client.
2
2
 
3
- This module provides the CLI transport implementation that enables UTCP clients
4
- to interact with command-line tools and processes. It handles tool discovery
5
- through startup commands, tool execution with proper argument formatting,
6
- and output processing with JSON parsing capabilities.
3
+ This module provides an implementation of the `CommunicationProtocol` interface
4
+ that enables the UTCP client to interact with command-line tools. It supports
5
+ discovering tools by executing a command and parsing its output for a UTCP
6
+ manual, as well as calling those tools with arguments.
7
7
 
8
8
  Key Features:
9
- - Asynchronous command execution with timeout handling
10
- - Tool discovery via startup commands that output UTCP manuals
11
- - Flexible argument formatting for command-line flags
12
- - Environment variable support for authentication and configuration
13
- - JSON output parsing with fallback to raw text
14
- - Cross-platform command parsing (Windows/Unix)
15
- - Working directory control for command execution
16
-
17
- Security:
18
- - Command execution is isolated through subprocess
19
- - Environment variables can be controlled per provider
20
- - Working directory can be restricted
9
+ - Asynchronous execution of shell commands.
10
+ - Tool discovery by running a command that outputs a UTCP manual.
11
+ - Flexible argument formatting for different CLI conventions.
12
+ - Support for environment variables and custom working directories.
13
+ - Automatic parsing of JSON output with a fallback to raw text.
14
+ - Cross-platform command parsing for Windows and Unix-like systems.
15
+
16
+ Security Considerations:
17
+ Executing arbitrary command-line tools can be dangerous. This protocol
18
+ should only be used with trusted tools.
21
19
  """
22
20
  import asyncio
23
21
  import json
24
22
  import os
25
23
  import shlex
24
+ import sys
26
25
  from typing import Dict, Any, List, Optional, Callable, AsyncGenerator
27
26
 
28
27
  from utcp.interfaces.communication_protocol import CommunicationProtocol
@@ -33,37 +32,26 @@ from utcp.data.register_manual_response import RegisterManualResult
33
32
  from utcp_cli.cli_call_template import CliCallTemplate, CliCallTemplateSerializer
34
33
  import logging
35
34
 
36
- logger = logging.getLogger(__name__)
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s"
38
+ )
37
39
 
40
+ logger = logging.getLogger(__name__)
38
41
 
39
42
  class CliCommunicationProtocol(CommunicationProtocol):
40
- """Transport implementation for CLI-based tool providers.
41
-
42
- Handles communication with command-line tools by executing processes
43
- and managing their input/output. Supports both tool discovery and
44
- execution phases with comprehensive error handling and timeout management.
45
-
46
- Features:
47
- - Asynchronous subprocess execution with proper cleanup
48
- - Tool discovery through startup commands returning UTCP manuals
49
- - Flexible argument formatting for various CLI conventions
50
- - Environment variable injection for authentication
51
- - JSON output parsing with graceful fallback to text
52
- - Cross-platform command parsing and execution
53
- - Configurable working directories and timeouts
54
- - Process lifecycle management with proper termination
55
-
56
- Architecture:
57
- CLI tools are discovered by executing the provider's command_name
58
- and parsing the output for UTCP manual JSON. Tool calls execute
59
- the same command with formatted arguments and return processed output.
60
-
61
- Attributes:
62
- _log: Logger function for debugging and error reporting.
43
+ """REQUIRED
44
+ Communication protocol for interacting with CLI-based tool providers.
45
+
46
+ This class implements the `CommunicationProtocol` interface to handle
47
+ communication with command-line tools. It discovers tools by executing a
48
+ command specified in a `CliCallTemplate` and parsing the output for a UTCP
49
+ manual. It also executes tool calls by running the corresponding command
50
+ with the provided arguments.
63
51
  """
64
52
 
65
53
  def __init__(self):
66
- """Initialize the CLI transport."""
54
+ """Initializes the `CliCommunicationProtocol`."""
67
55
 
68
56
  def _log_info(self, message: str):
69
57
  """Log informational messages."""
@@ -154,9 +142,25 @@ class CliCommunicationProtocol(CommunicationProtocol):
154
142
  raise
155
143
 
156
144
  async def register_manual(self, caller, manual_call_template: CallTemplate) -> RegisterManualResult:
157
- """Register a CLI manual and discover its tools.
158
-
159
- Executes the call template's command_name and looks for a UTCP manual JSON in the output.
145
+ """REQUIRED
146
+ Registers a CLI-based manual and discovers its tools.
147
+
148
+ This method executes the command specified in the `CliCallTemplate`'s
149
+ `command_name` field. It then attempts to parse the command's output
150
+ (stdout) as a UTCP manual in JSON format.
151
+
152
+ Args:
153
+ caller: The UTCP client instance that is calling this method.
154
+ manual_call_template: The `CliCallTemplate` containing the details for
155
+ tool discovery, such as the command to run.
156
+
157
+ Returns:
158
+ A `RegisterManualResult` object indicating whether the registration
159
+ was successful and containing the discovered tools.
160
+
161
+ Raises:
162
+ ValueError: If the `manual_call_template` is not an instance of
163
+ `CliCallTemplate` or if `command_name` is not set.
160
164
  """
161
165
  if not isinstance(manual_call_template, CliCallTemplate):
162
166
  raise ValueError("CliCommunicationProtocol can only be used with CliCallTemplate")
@@ -193,7 +197,7 @@ class CliCommunicationProtocol(CommunicationProtocol):
193
197
  return RegisterManualResult(
194
198
  success=False,
195
199
  manual_call_template=manual_call_template,
196
- manual=UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=[]),
200
+ manual=UtcpManual(manual_version="0.0.0", tools=[]),
197
201
  errors=[
198
202
  f"No output from discovery command for CLI provider '{manual_call_template.name}'"
199
203
  ],
@@ -212,7 +216,7 @@ class CliCommunicationProtocol(CommunicationProtocol):
212
216
  return RegisterManualResult(
213
217
  success=False,
214
218
  manual_call_template=manual_call_template,
215
- manual=UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=[]),
219
+ manual=UtcpManual(manual_version="0.0.0", tools=[]),
216
220
  errors=[error_msg],
217
221
  )
218
222
 
@@ -232,12 +236,21 @@ class CliCommunicationProtocol(CommunicationProtocol):
232
236
  return RegisterManualResult(
233
237
  success=False,
234
238
  manual_call_template=manual_call_template,
235
- manual=UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=[]),
239
+ manual=UtcpManual(manual_version="0.0.0", tools=[]),
236
240
  errors=[error_msg],
237
241
  )
238
242
 
239
243
  async def deregister_manual(self, caller, manual_call_template: CallTemplate) -> None:
240
- """Deregister a CLI manual (no-op)."""
244
+ """REQUIRED
245
+ Deregisters a CLI manual.
246
+
247
+ For the CLI protocol, this is a no-op as there are no persistent
248
+ connections to terminate.
249
+
250
+ Args:
251
+ caller: The UTCP client instance that is calling this method.
252
+ manual_call_template: The call template of the manual to deregister.
253
+ """
241
254
  if isinstance(manual_call_template, CliCallTemplate):
242
255
  self._log_info(
243
256
  f"Deregistering CLI manual '{manual_call_template.name}' (no-op)"
@@ -285,12 +298,12 @@ class CliCommunicationProtocol(CommunicationProtocol):
285
298
  # Fallback: try to parse tools from possibly-legacy structure
286
299
  tools = self._parse_tool_data(data, provider_name)
287
300
  if tools:
288
- return UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=tools)
301
+ return UtcpManual(manual_version="0.0.0", tools=tools)
289
302
  return None
290
303
  # Fallback: try to parse as tools
291
304
  tools = self._parse_tool_data(data, provider_name)
292
305
  if tools:
293
- return UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=tools)
306
+ return UtcpManual(manual_version="0.0.0", tools=tools)
294
307
  except json.JSONDecodeError:
295
308
  pass
296
309
 
@@ -313,7 +326,7 @@ class CliCommunicationProtocol(CommunicationProtocol):
313
326
  # Fallback: try to parse tools from possibly-legacy structure
314
327
  tools = self._parse_tool_data(data, provider_name)
315
328
  if tools:
316
- return UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=tools)
329
+ return UtcpManual(manual_version="0.0.0", tools=tools)
317
330
  return None
318
331
  found_tools = self._parse_tool_data(data, provider_name)
319
332
  aggregated_tools.extend(found_tools)
@@ -321,7 +334,7 @@ class CliCommunicationProtocol(CommunicationProtocol):
321
334
  continue
322
335
 
323
336
  if aggregated_tools:
324
- return UtcpManual(utcp_version="1.0.0", manual_version="0.0.0", tools=aggregated_tools)
337
+ return UtcpManual(manual_version="0.0.0", tools=aggregated_tools)
325
338
 
326
339
  return None
327
340
 
@@ -399,23 +412,28 @@ class CliCommunicationProtocol(CommunicationProtocol):
399
412
  return tools
400
413
 
401
414
  async def call_tool(self, caller, tool_name: str, tool_args: Dict[str, Any], tool_call_template: CallTemplate) -> Any:
402
- """Call a CLI tool.
403
-
404
- Executes the command specified by provider.command_name with the provided arguments.
405
-
415
+ """REQUIRED
416
+ Calls a CLI tool by executing its command.
417
+
418
+ This method constructs and executes the command specified in the
419
+ `CliCallTemplate`. It formats the provided `tool_args` as command-line
420
+ arguments and runs the command in a subprocess.
421
+
406
422
  Args:
407
- caller: The UTCP client that is calling this method.
408
- tool_name: Name of the tool to call
409
- tool_args: Arguments for the tool call
410
- tool_call_template: The CliCallTemplate for the tool
411
-
423
+ caller: The UTCP client instance that is calling this method.
424
+ tool_name: The name of the tool to call.
425
+ tool_args: A dictionary of arguments for the tool call.
426
+ tool_call_template: The `CliCallTemplate` for the tool.
427
+
412
428
  Returns:
413
- The output from the command execution based on exit code:
414
- - If exit code is 0: stdout (parsed as JSON if possible, otherwise raw string)
415
- - If exit code is not 0: stderr
416
-
429
+ The result of the command execution. If the command exits with a code
430
+ of 0, it returns the content of stdout. If the exit code is non-zero,
431
+ it returns the content of stderr. The output is parsed as JSON if
432
+ possible; otherwise, it is returned as a raw string.
433
+
417
434
  Raises:
418
- ValueError: If provider is not a CliProvider or command_name is not set
435
+ ValueError: If `tool_call_template` is not an instance of
436
+ `CliCallTemplate` or if `command_name` is not set.
419
437
  """
420
438
  if not isinstance(tool_call_template, CliCallTemplate):
421
439
  raise ValueError("CliCommunicationProtocol can only be used with CliCallTemplate")
@@ -471,12 +489,10 @@ class CliCommunicationProtocol(CommunicationProtocol):
471
489
  raise
472
490
 
473
491
  async def call_tool_streaming(self, caller, tool_name: str, tool_args: Dict[str, Any], tool_call_template: CallTemplate) -> AsyncGenerator[Any, None]:
474
- """Streaming calls are not supported for CLI protocol."""
475
- raise NotImplementedError("Streaming is not supported by the CLI communication protocol.")
476
-
477
- async def close(self) -> None:
478
- """Close the transport.
479
-
480
- This is a no-op for CLI transports since they don't maintain connections.
492
+ """REQUIRED
493
+ Streaming calls are not supported for the CLI protocol.
494
+
495
+ Raises:
496
+ NotImplementedError: Always, as this functionality is not supported.
481
497
  """
482
- self._log_info("Closing CLI transport (no-op)")
498
+ raise NotImplementedError("Streaming is not supported by the CLI communication protocol.")
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: utcp-cli
3
+ Version: 1.0.2
4
+ Summary: UTCP communication protocol plugin for wrapping local command-line tools.
5
+ Author: UTCP Contributors
6
+ License-Expression: MPL-2.0
7
+ Project-URL: Homepage, https://utcp.io
8
+ Project-URL: Source, https://github.com/universal-tool-calling-protocol/python-utcp
9
+ Project-URL: Issues, https://github.com/universal-tool-calling-protocol/python-utcp/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: pyyaml>=6.0
18
+ Requires-Dist: utcp>=1.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: build; extra == "dev"
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: coverage; extra == "dev"
25
+ Requires-Dist: twine; extra == "dev"
26
+
27
+ # UTCP CLI Plugin
28
+
29
+ [![PyPI Downloads](https://static.pepy.tech/badge/utcp-cli)](https://pepy.tech/projects/utcp-cli)
30
+
31
+ Command-line interface plugin for UTCP, enabling integration with command-line tools and processes.
32
+
33
+ ## Features
34
+
35
+ - **Command Execution**: Run any command-line tool as a UTCP tool
36
+ - **Environment Variables**: Secure credential and configuration passing
37
+ - **Working Directory Control**: Execute commands in specific directories
38
+ - **Input/Output Handling**: Support for stdin, stdout, stderr processing
39
+ - **Cross-Platform**: Works on Windows, macOS, and Linux
40
+ - **Timeout Management**: Configurable execution timeouts
41
+ - **Argument Validation**: Optional input sanitization
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install utcp-cli
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ from utcp.utcp_client import UtcpClient
53
+
54
+ # Basic CLI tool
55
+ client = await UtcpClient.create(config={
56
+ "manual_call_templates": [{
57
+ "name": "file_tools",
58
+ "call_template_type": "cli",
59
+ "command_name": "ls -la ${path}"
60
+ }]
61
+ })
62
+
63
+ result = await client.call_tool("file_tools.list", {"path": "/home"})
64
+ ```
65
+
66
+ ## Configuration Examples
67
+
68
+ ### Basic Command
69
+ ```json
70
+ {
71
+ "name": "file_ops",
72
+ "call_template_type": "cli",
73
+ "command_name": "ls -la ${path}",
74
+ "working_dir": "/tmp"
75
+ }
76
+ ```
77
+
78
+ ### With Environment Variables
79
+ ```json
80
+ {
81
+ "name": "python_script",
82
+ "call_template_type": "cli",
83
+ "command_name": "python script.py ${input}",
84
+ "env_vars": {
85
+ "PYTHONPATH": "/custom/path",
86
+ "API_KEY": "${API_KEY}"
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Processing JSON with jq
92
+ ```json
93
+ {
94
+ "name": "json_processor",
95
+ "call_template_type": "cli",
96
+ "command_name": "jq '.data'",
97
+ "stdin": "${json_input}",
98
+ "timeout": 10
99
+ }
100
+ ```
101
+
102
+ ### Git Operations
103
+ ```json
104
+ {
105
+ "name": "git_tools",
106
+ "call_template_type": "cli",
107
+ "command_name": "git ${operation} ${args}",
108
+ "working_dir": "${repo_path}",
109
+ "env_vars": {
110
+ "GIT_AUTHOR_NAME": "${author_name}",
111
+ "GIT_AUTHOR_EMAIL": "${author_email}"
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## Security Considerations
117
+
118
+ - Commands run in isolated subprocesses
119
+ - Environment variables provide secure credential passing
120
+ - Working directory restrictions limit file system access
121
+ - Input validation prevents command injection
122
+
123
+ ```json
124
+ {
125
+ "name": "safe_grep",
126
+ "call_template_type": "cli",
127
+ "command_name": "grep ${pattern} ${file}",
128
+ "working_dir": "/safe/directory",
129
+ "allowed_args": {
130
+ "pattern": "^[a-zA-Z0-9_-]+$",
131
+ "file": "^[a-zA-Z0-9_./-]+\\.txt$"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Error Handling
137
+
138
+ ```python
139
+ from utcp.exceptions import ToolCallError
140
+ import subprocess
141
+
142
+ try:
143
+ result = await client.call_tool("cli_tool.command", {"arg": "value"})
144
+ except ToolCallError as e:
145
+ if isinstance(e.__cause__, subprocess.CalledProcessError):
146
+ print(f"Command failed with exit code {e.__cause__.returncode}")
147
+ print(f"stderr: {e.__cause__.stderr}")
148
+ ```
149
+
150
+ ## Common Use Cases
151
+
152
+ - **File Operations**: ls, find, grep, awk, sed
153
+ - **Data Processing**: jq, sort, uniq, cut
154
+ - **System Monitoring**: ps, top, df, netstat
155
+ - **Development Tools**: git, npm, pip, docker
156
+ - **Custom Scripts**: Python, bash, PowerShell scripts
157
+
158
+ ## Testing CLI Tools
159
+
160
+ ```python
161
+ import pytest
162
+ from utcp.utcp_client import UtcpClient
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_cli_tool():
166
+ client = await UtcpClient.create(config={
167
+ "manual_call_templates": [{
168
+ "name": "test_cli",
169
+ "call_template_type": "cli",
170
+ "command_name": "echo ${message}"
171
+ }]
172
+ })
173
+
174
+ result = await client.call_tool("test_cli.echo", {"message": "hello"})
175
+ assert "hello" in result["stdout"]
176
+ ```
177
+
178
+ ## Related Documentation
179
+
180
+ - [Main UTCP Documentation](../../../README.md)
181
+ - [Core Package Documentation](../../../core/README.md)
182
+ - [HTTP Plugin](../http/README.md)
183
+ - [MCP Plugin](../mcp/README.md)
184
+ - [Text Plugin](../text/README.md)
185
+
186
+ ## Examples
187
+
188
+ For complete examples, see the [UTCP examples repository](https://github.com/universal-tool-calling-protocol/utcp-examples).
@@ -1,3 +1,4 @@
1
+ README.md
1
2
  pyproject.toml
2
3
  src/utcp_cli/__init__.py
3
4
  src/utcp_cli/cli_call_template.py
@@ -39,7 +39,7 @@ def main():
39
39
  if len(sys.argv) == 1:
40
40
  # Return UTCP manual
41
41
  tools_data = {
42
- "version": "1.0.0",
42
+ "manual_version": "1.0.0",
43
43
  "name": "Mock CLI Tools",
44
44
  "description": "Mock CLI tools for testing",
45
45
  "tools": [
utcp_cli-1.0.0/PKG-INFO DELETED
@@ -1,25 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: utcp-cli
3
- Version: 1.0.0
4
- Summary: Universal Tool Calling Protocol (UTCP) client library for Python
5
- Author: UTCP Contributors
6
- License-Expression: MPL-2.0
7
- Project-URL: Homepage, https://utcp.io
8
- Project-URL: Source, https://github.com/universal-tool-calling-protocol/python-utcp
9
- Project-URL: Issues, https://github.com/universal-tool-calling-protocol/python-utcp/issues
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: pydantic>=2.0
17
- Requires-Dist: pyyaml>=6.0
18
- Requires-Dist: utcp>=1.0
19
- Provides-Extra: dev
20
- Requires-Dist: build; extra == "dev"
21
- Requires-Dist: pytest; extra == "dev"
22
- Requires-Dist: pytest-asyncio; extra == "dev"
23
- Requires-Dist: pytest-cov; extra == "dev"
24
- Requires-Dist: coverage; extra == "dev"
25
- Requires-Dist: twine; extra == "dev"
@@ -1,44 +0,0 @@
1
- from typing import Optional, Dict, Literal
2
- from pydantic import Field
3
-
4
- from utcp.data.call_template import CallTemplate
5
- from utcp.interfaces.serializer import Serializer
6
- from utcp.exceptions import UtcpSerializerValidationError
7
- import traceback
8
-
9
- class CliCallTemplate(CallTemplate):
10
- """Call template configuration for Command Line Interface tools.
11
-
12
- Enables execution of command-line tools and programs as UTCP providers.
13
- Supports environment variable injection and custom working directories.
14
-
15
- Attributes:
16
- call_template_type: Always "cli" for CLI providers.
17
- command_name: The name or path of the command to execute.
18
- env_vars: Optional environment variables to set during command execution.
19
- working_dir: Optional custom working directory for command execution.
20
- auth: Always None - CLI providers don't support authentication.
21
- """
22
-
23
- call_template_type: Literal["cli"] = "cli"
24
- command_name: str
25
- env_vars: Optional[Dict[str, str]] = Field(
26
- default=None, description="Environment variables to set when executing the command"
27
- )
28
- working_dir: Optional[str] = Field(
29
- default=None, description="Working directory for command execution"
30
- )
31
- auth: None = None
32
-
33
-
34
- class CliCallTemplateSerializer(Serializer[CliCallTemplate]):
35
- """Serializer for CliCallTemplate."""
36
-
37
- def to_dict(self, obj: CliCallTemplate) -> dict:
38
- return obj.model_dump()
39
-
40
- def validate_dict(self, obj: dict) -> CliCallTemplate:
41
- try:
42
- return CliCallTemplate.model_validate(obj)
43
- except Exception as e:
44
- raise UtcpSerializerValidationError("Invalid CliCallTemplate: " + traceback.format_exc()) from e
@@ -1,25 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: utcp-cli
3
- Version: 1.0.0
4
- Summary: Universal Tool Calling Protocol (UTCP) client library for Python
5
- Author: UTCP Contributors
6
- License-Expression: MPL-2.0
7
- Project-URL: Homepage, https://utcp.io
8
- Project-URL: Source, https://github.com/universal-tool-calling-protocol/python-utcp
9
- Project-URL: Issues, https://github.com/universal-tool-calling-protocol/python-utcp/issues
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: pydantic>=2.0
17
- Requires-Dist: pyyaml>=6.0
18
- Requires-Dist: utcp>=1.0
19
- Provides-Extra: dev
20
- Requires-Dist: build; extra == "dev"
21
- Requires-Dist: pytest; extra == "dev"
22
- Requires-Dist: pytest-asyncio; extra == "dev"
23
- Requires-Dist: pytest-cov; extra == "dev"
24
- Requires-Dist: coverage; extra == "dev"
25
- Requires-Dist: twine; extra == "dev"
File without changes