cliver 0.0.1__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.
- cliver-0.0.1/PKG-INFO +24 -0
- cliver-0.0.1/pyproject.toml +73 -0
- cliver-0.0.1/src/cliver/__init__.py +6 -0
- cliver-0.0.1/src/cliver/builtin_tools.py +83 -0
- cliver-0.0.1/src/cliver/cli.py +170 -0
- cliver-0.0.1/src/cliver/cliver_run.py +4 -0
- cliver-0.0.1/src/cliver/commands/__init__.py +61 -0
- cliver-0.0.1/src/cliver/commands/capabilities.py +71 -0
- cliver-0.0.1/src/cliver/commands/chat.py +428 -0
- cliver-0.0.1/src/cliver/commands/config.py +60 -0
- cliver-0.0.1/src/cliver/commands/llm.py +222 -0
- cliver-0.0.1/src/cliver/commands/mcp.py +253 -0
- cliver-0.0.1/src/cliver/commands/workflow.py +213 -0
- cliver-0.0.1/src/cliver/config.py +551 -0
- cliver-0.0.1/src/cliver/constants.py +8 -0
- cliver-0.0.1/src/cliver/llm/__init__.py +14 -0
- cliver-0.0.1/src/cliver/llm/base.py +174 -0
- cliver-0.0.1/src/cliver/llm/llm.py +746 -0
- cliver-0.0.1/src/cliver/llm/media_utils.py +155 -0
- cliver-0.0.1/src/cliver/llm/ollama_engine.py +244 -0
- cliver-0.0.1/src/cliver/llm/openai_engine.py +408 -0
- cliver-0.0.1/src/cliver/mcp_server_caller.py +130 -0
- cliver-0.0.1/src/cliver/media.py +234 -0
- cliver-0.0.1/src/cliver/media_handler.py +245 -0
- cliver-0.0.1/src/cliver/model_capabilities.py +175 -0
- cliver-0.0.1/src/cliver/prompt_enhancer.py +416 -0
- cliver-0.0.1/src/cliver/tools/__init__.py +4 -0
- cliver-0.0.1/src/cliver/util.py +343 -0
- cliver-0.0.1/src/cliver/workflow/README.md +242 -0
- cliver-0.0.1/src/cliver/workflow/__init__.py +21 -0
- cliver-0.0.1/src/cliver/workflow/persistence/__init__.py +15 -0
- cliver-0.0.1/src/cliver/workflow/persistence/base.py +106 -0
- cliver-0.0.1/src/cliver/workflow/persistence/local_cache.py +350 -0
- cliver-0.0.1/src/cliver/workflow/steps/__init__.py +18 -0
- cliver-0.0.1/src/cliver/workflow/steps/base.py +179 -0
- cliver-0.0.1/src/cliver/workflow/steps/function_step.py +95 -0
- cliver-0.0.1/src/cliver/workflow/steps/human_step.py +88 -0
- cliver-0.0.1/src/cliver/workflow/steps/llm_step.py +131 -0
- cliver-0.0.1/src/cliver/workflow/steps/workflow_step.py +88 -0
- cliver-0.0.1/src/cliver/workflow/workflow_executor.py +409 -0
- cliver-0.0.1/src/cliver/workflow/workflow_manager_base.py +32 -0
- cliver-0.0.1/src/cliver/workflow/workflow_manager_local.py +144 -0
- cliver-0.0.1/src/cliver/workflow/workflow_models.py +130 -0
cliver-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cliver
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: An AI Agent to make your CLI clever and more
|
|
5
|
+
Author: Lin Gao
|
|
6
|
+
Author-email: Lin Gao <aoingl@gmail.com>
|
|
7
|
+
Requires-Dist: click>=8.1.0
|
|
8
|
+
Requires-Dist: rich>=13.0.0
|
|
9
|
+
Requires-Dist: httpx>=0.24.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
13
|
+
Requires-Dist: mcp>=1.2.1
|
|
14
|
+
Requires-Dist: openai>=1.0.0
|
|
15
|
+
Requires-Dist: langchain>=0.3.0
|
|
16
|
+
Requires-Dist: langchain-mcp-adapters>=0.1.4
|
|
17
|
+
Requires-Dist: langchain-openai>=0.3.18
|
|
18
|
+
Requires-Dist: langchain-core>=0.3.63
|
|
19
|
+
Requires-Dist: langchain-ollama>=0.3.3
|
|
20
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
21
|
+
Requires-Dist: jinja2>=3.1.0
|
|
22
|
+
Requires-Dist: requests>=2.32.3
|
|
23
|
+
Requires-Dist: json-repair>=0.52.0
|
|
24
|
+
Requires-Python: >=3.10
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cliver"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "An AI Agent to make your CLI clever and more"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Lin Gao", email = "aoingl@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
"click>=8.1.0",
|
|
12
|
+
"rich>=13.0.0",
|
|
13
|
+
"httpx>=0.24.0",
|
|
14
|
+
"pydantic>=2.0.0",
|
|
15
|
+
"prompt-toolkit>=3.0.0",
|
|
16
|
+
"pyyaml>=6.0.0",
|
|
17
|
+
"mcp>=1.2.1",
|
|
18
|
+
"openai>=1.0.0",
|
|
19
|
+
"langchain>=0.3.0",
|
|
20
|
+
"langchain-mcp-adapters>=0.1.4",
|
|
21
|
+
"langchain-openai>=0.3.18",
|
|
22
|
+
"langchain-core>=0.3.63",
|
|
23
|
+
"langchain-ollama>=0.3.3",
|
|
24
|
+
"python-dotenv>=1.1.0",
|
|
25
|
+
"jinja2>=3.1.0",
|
|
26
|
+
"requests>=2.32.3",
|
|
27
|
+
"json-repair>=0.52.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=7.0.0",
|
|
33
|
+
"pytest-cov",
|
|
34
|
+
"pytest-asyncio>=1.2.0",
|
|
35
|
+
"isort>=5.12.0",
|
|
36
|
+
"mypy>=1.0.0",
|
|
37
|
+
"ruff>=0.0.270",
|
|
38
|
+
"black>=25.1.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
docs = [
|
|
42
|
+
"mkdocs>=1.4.0",
|
|
43
|
+
"mkdocs-material>=9.0.0",
|
|
44
|
+
"mkdocs-minify-plugin>=0.2.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
cliver = "cliver.cli:cliver_main"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.7,<0.8"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[tool.uv]
|
|
55
|
+
package = true
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.10"
|
|
59
|
+
warn_return_any = true
|
|
60
|
+
warn_unused_configs = true
|
|
61
|
+
disallow_untyped_defs = true
|
|
62
|
+
disallow_incomplete_defs = true
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 88
|
|
66
|
+
target-version = "py310"
|
|
67
|
+
select = ["E", "F", "B", "I"]
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
asyncio_mode = "auto"
|
|
72
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
73
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Built-in tools that are always available for the LLM to use."""
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import List, Dict, Any, Union
|
|
4
|
+
from langchain_core.tools import BaseTool
|
|
5
|
+
import cliver.tools
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# The builtin tools should be defined in the module: 'cliver.tools'
|
|
11
|
+
# All builtin tools should have a global unique name either like: 'tool_name' or 'builtin#tool_name'
|
|
12
|
+
def get_builtin_tools() -> List[BaseTool]:
|
|
13
|
+
"""
|
|
14
|
+
Return a list of builtin tools that should always be available.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
List of BaseTool objects representing builtin tools
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
tools: List[BaseTool] = []
|
|
21
|
+
for name, obj in inspect.getmembers(cliver.tools):
|
|
22
|
+
if isinstance(obj, BaseTool):
|
|
23
|
+
logger.debug(f"Register builtin tool: {name}")
|
|
24
|
+
tools.append(obj)
|
|
25
|
+
return tools
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BuiltinTools:
|
|
29
|
+
"""
|
|
30
|
+
A class to manage built-in tools with caching and execution capabilities.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._tools = None # Cache for builtin tools
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def tools(self) -> List[BaseTool]:
|
|
38
|
+
"""Get the list of builtin tools, caching them on first access."""
|
|
39
|
+
if self._tools is None:
|
|
40
|
+
self._tools = get_builtin_tools()
|
|
41
|
+
return self._tools
|
|
42
|
+
|
|
43
|
+
def execute_tool(self, tool_name: str, args: Union[str, Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
44
|
+
"""
|
|
45
|
+
Execute a builtin tool by name with the provided arguments.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
tool_name (str): The name of the tool to execute
|
|
49
|
+
args (Dict[str, Any]): Arguments to pass to the tool
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List[Dict[str, Any]]: The result of the tool execution
|
|
53
|
+
"""
|
|
54
|
+
if args is None:
|
|
55
|
+
args = {}
|
|
56
|
+
|
|
57
|
+
# Find the tool by name
|
|
58
|
+
tool = None
|
|
59
|
+
for t in self.tools:
|
|
60
|
+
if t.name == tool_name or t.name == f"builtin#{tool_name}":
|
|
61
|
+
tool = t
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
if tool is None:
|
|
65
|
+
return [{"error": f"Tool '{tool_name}' not found in builtin tools"}]
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# Execute the tool
|
|
69
|
+
result = tool.invoke(input=args)
|
|
70
|
+
|
|
71
|
+
# Handle different result types
|
|
72
|
+
if isinstance(result, dict):
|
|
73
|
+
return [result]
|
|
74
|
+
elif isinstance(result, list):
|
|
75
|
+
return result
|
|
76
|
+
elif result is None:
|
|
77
|
+
return [{}]
|
|
78
|
+
else:
|
|
79
|
+
return [{"tool_result": str(result)}]
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"Failed to execute builtin tool {tool_name}", exc_info=e)
|
|
83
|
+
return [{"error": str(e)}]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cliver CLI Module
|
|
3
|
+
|
|
4
|
+
The main entrance of the cliver application
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from shlex import split as shell_split
|
|
8
|
+
import click
|
|
9
|
+
import sys
|
|
10
|
+
from prompt_toolkit import PromptSession
|
|
11
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
12
|
+
from prompt_toolkit.completion import WordCompleter
|
|
13
|
+
from prompt_toolkit.history import FileHistory
|
|
14
|
+
from prompt_toolkit.styles import Style
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
|
|
18
|
+
from cliver.config import ConfigManager
|
|
19
|
+
from cliver.llm import TaskExecutor
|
|
20
|
+
from cliver import commands
|
|
21
|
+
from cliver.util import get_config_dir, stdin_is_piped, read_piped_input
|
|
22
|
+
from cliver.constants import *
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Cliver:
|
|
26
|
+
"""
|
|
27
|
+
The global App is the box for all capabilities.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
"""Initialize the Cliver application."""
|
|
33
|
+
# load config
|
|
34
|
+
self.config_dir = get_config_dir()
|
|
35
|
+
dir_str = str(self.config_dir.absolute())
|
|
36
|
+
if dir_str not in sys.path:
|
|
37
|
+
sys.path.append(dir_str)
|
|
38
|
+
self.config_manager = ConfigManager(self.config_dir)
|
|
39
|
+
self.task_executor = TaskExecutor(
|
|
40
|
+
llm_models=self.config_manager.list_llm_models(),
|
|
41
|
+
mcp_servers=self.config_manager.list_mcp_servers_for_mcp_caller(),
|
|
42
|
+
default_model=self.config_manager.get_llm_model(),
|
|
43
|
+
)
|
|
44
|
+
# prepare console for interaction
|
|
45
|
+
self.history_path = self.config_dir / "history"
|
|
46
|
+
self.session = None
|
|
47
|
+
self.console = Console()
|
|
48
|
+
self.piped = stdin_is_piped()
|
|
49
|
+
|
|
50
|
+
# TODO: we may need to maintain the selected model, and other options during the session initiation
|
|
51
|
+
def init_session(self, group: click.Group):
|
|
52
|
+
if self.piped or self.session is not None:
|
|
53
|
+
return
|
|
54
|
+
self.session = PromptSession(
|
|
55
|
+
history=FileHistory(str(self.history_path)),
|
|
56
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
57
|
+
completer=WordCompleter(self.load_commands_names(group), ignore_case=True),
|
|
58
|
+
style=Style.from_dict(
|
|
59
|
+
{
|
|
60
|
+
"prompt": "ansigreen bold",
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def load_commands_names(self, group: click.Group) -> list[str]:
|
|
66
|
+
return commands.list_commands_names(group)
|
|
67
|
+
|
|
68
|
+
def run(self) -> None:
|
|
69
|
+
"""Run the Cliver client."""
|
|
70
|
+
if self.piped:
|
|
71
|
+
user_data = read_piped_input()
|
|
72
|
+
if user_data is None:
|
|
73
|
+
self.console.print(
|
|
74
|
+
"[bold yellow]No data received from stdin.[/bold yellow]"
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
if not user_data.lower() in ("exit", "quit"):
|
|
78
|
+
self.call_cmd(user_data)
|
|
79
|
+
else:
|
|
80
|
+
self.console.print(
|
|
81
|
+
Panel.fit(
|
|
82
|
+
"[bold blue]CLIver[/bold blue] - AI Agent Command Line Interface",
|
|
83
|
+
border_style="blue",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
self.console.print(
|
|
87
|
+
"Type [bold green]/help[/bold green] to see available commands or start typing to interact with the AI."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
while True:
|
|
91
|
+
try:
|
|
92
|
+
# Get user input
|
|
93
|
+
line = self.session.prompt("Cliver> ").strip()
|
|
94
|
+
if line.lower() in ("exit", "quit", "/exit", "/quit"):
|
|
95
|
+
break
|
|
96
|
+
if line.startswith("/") and len(line) > 1:
|
|
97
|
+
# possibly a command
|
|
98
|
+
line = line[1:]
|
|
99
|
+
elif not line.lower().startswith(f"{CMD_CHAT} "):
|
|
100
|
+
line = f"{CMD_CHAT} {line}"
|
|
101
|
+
|
|
102
|
+
self.call_cmd(line)
|
|
103
|
+
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
self.console.print(
|
|
106
|
+
"\n[yellow]Use 'exit' or 'quit' to exit[/yellow]"
|
|
107
|
+
)
|
|
108
|
+
except EOFError:
|
|
109
|
+
break
|
|
110
|
+
except click.exceptions.NoArgsIsHelpError as e:
|
|
111
|
+
self.console.print(f"{e.format_message()}")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.console.print(f"[red]Error: {e}[/red]")
|
|
114
|
+
|
|
115
|
+
# Clean up
|
|
116
|
+
self.cleanup()
|
|
117
|
+
|
|
118
|
+
def call_cmd(self, line: str):
|
|
119
|
+
"""
|
|
120
|
+
Call a command with the given name and arguments.
|
|
121
|
+
"""
|
|
122
|
+
parts = shell_split(line)
|
|
123
|
+
cliver(args=parts, prog_name="cliver", standalone_mode=False, obj=self)
|
|
124
|
+
|
|
125
|
+
def cleanup(self):
|
|
126
|
+
"""
|
|
127
|
+
Clean up the resources opened by the application like the mcp server processes or connections to remote mcp servers, llm providers
|
|
128
|
+
"""
|
|
129
|
+
# TODO
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
pass_cliver = click.make_pass_decorator(Cliver)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@click.group(invoke_without_command=True)
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def cliver(ctx: click.Context):
|
|
139
|
+
"""
|
|
140
|
+
Cliver: An application aims to make your CLI clever
|
|
141
|
+
"""
|
|
142
|
+
cli = None
|
|
143
|
+
if ctx.obj is None:
|
|
144
|
+
cli = Cliver()
|
|
145
|
+
ctx.obj = cli
|
|
146
|
+
|
|
147
|
+
if ctx.invoked_subcommand is None:
|
|
148
|
+
# If no subcommand is invoked, show the help message
|
|
149
|
+
interact(cli)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def interact(cli: Cliver):
|
|
153
|
+
"""
|
|
154
|
+
Start an interactive session with the AI agent.
|
|
155
|
+
"""
|
|
156
|
+
cli.init_session(cliver)
|
|
157
|
+
cli.run()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def loads_commands():
|
|
161
|
+
commands.loads_commands(cliver)
|
|
162
|
+
# Loads extended commands from config dir
|
|
163
|
+
commands.loads_external_commands(cliver)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def cliver_main(*args, **kwargs):
|
|
167
|
+
# loading all click groups and commands before calling it
|
|
168
|
+
loads_commands()
|
|
169
|
+
# bootstrap the cliver application
|
|
170
|
+
cliver()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
import importlib
|
|
6
|
+
from typing import List, Callable
|
|
7
|
+
from cliver.util import get_config_dir
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
def loads_commands(group: click.Group) -> None:
|
|
12
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
+
_load_commands_from_dir(
|
|
14
|
+
current_dir,
|
|
15
|
+
group,
|
|
16
|
+
package_name="cliver.commands",
|
|
17
|
+
filter_fn=lambda f_name: f_name != "__init__.py",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# This will load py modules from config directory
|
|
21
|
+
# This assumes the py modules are safe and should be set up manually.
|
|
22
|
+
def loads_external_commands(group: click.Group) -> None:
|
|
23
|
+
config_dir = get_config_dir()
|
|
24
|
+
dir_str = str(config_dir.absolute() / "commands")
|
|
25
|
+
if dir_str not in sys.path:
|
|
26
|
+
sys.path.append(dir_str)
|
|
27
|
+
_load_commands_from_dir(dir_str, group, log=True)
|
|
28
|
+
|
|
29
|
+
def _load_commands_from_dir(
|
|
30
|
+
commands_dir: str,
|
|
31
|
+
group: click.Group,
|
|
32
|
+
package_name: str = None,
|
|
33
|
+
filter_fn: Callable[[str], bool] = None,
|
|
34
|
+
log: bool = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
if commands_dir and not os.path.exists(commands_dir):
|
|
37
|
+
logger.warning("Commands directory: %s does not exist", commands_dir)
|
|
38
|
+
return
|
|
39
|
+
for filename in os.listdir(commands_dir):
|
|
40
|
+
if filename.endswith(".py"):
|
|
41
|
+
# either we don't filter or filter_fn returns True
|
|
42
|
+
if filter_fn is None or filter_fn(filename):
|
|
43
|
+
if log:
|
|
44
|
+
full = os.path.abspath(os.path.join(commands_dir, filename))
|
|
45
|
+
logger.debug("Loads command from: %s", full)
|
|
46
|
+
grp_name = filename[:-3]
|
|
47
|
+
module_name = f"{grp_name}"
|
|
48
|
+
if package_name is not None:
|
|
49
|
+
module_name = f"{package_name}.{grp_name}"
|
|
50
|
+
module = importlib.import_module(module_name)
|
|
51
|
+
if hasattr(module, grp_name):
|
|
52
|
+
cli_obj = getattr(module, grp_name)
|
|
53
|
+
if isinstance(cli_obj, click.Command):
|
|
54
|
+
group.add_command(cli_obj)
|
|
55
|
+
if hasattr(module, "post_group"):
|
|
56
|
+
pg_obj = getattr(module, "post_group")
|
|
57
|
+
pg_obj()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def list_commands_names(group: click.Group) -> List[str]:
|
|
61
|
+
return [name for name, _ in group.commands.items()]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model capabilities command for Cliver client.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from cliver.cli import Cliver, pass_cliver
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.option("--model", "-m", help="Model name to check capabilities for")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--detailed", "-d", is_flag=True, help="Show detailed modality capabilities"
|
|
15
|
+
)
|
|
16
|
+
@pass_cliver
|
|
17
|
+
def capabilities(cli: Cliver, model: str = None, detailed: bool = False):
|
|
18
|
+
"""Display model capabilities."""
|
|
19
|
+
models = cli.config_manager.list_llm_models()
|
|
20
|
+
|
|
21
|
+
if not models:
|
|
22
|
+
cli.console.print("[yellow]No models configured.[/yellow]")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
if model:
|
|
26
|
+
if model not in models:
|
|
27
|
+
cli.console.print(f"[red]Model '{model}' not found.[/red]")
|
|
28
|
+
return
|
|
29
|
+
models = {model: models[model]}
|
|
30
|
+
|
|
31
|
+
if detailed:
|
|
32
|
+
table = Table(title="Detailed Model Capabilities")
|
|
33
|
+
table.add_column("Model", style="cyan")
|
|
34
|
+
table.add_column("Provider", style="magenta")
|
|
35
|
+
table.add_column("Text", style="green")
|
|
36
|
+
table.add_column("Image In", style="blue")
|
|
37
|
+
table.add_column("Image Out", style="blue")
|
|
38
|
+
table.add_column("Audio In", style="yellow")
|
|
39
|
+
table.add_column("Audio Out", style="yellow")
|
|
40
|
+
table.add_column("Video In", style="purple")
|
|
41
|
+
table.add_column("Video Out", style="purple")
|
|
42
|
+
table.add_column("Tools", style="red")
|
|
43
|
+
|
|
44
|
+
for model_name, model_config in models.items():
|
|
45
|
+
capabilities = model_config.get_model_capabilities()
|
|
46
|
+
modality_caps = capabilities.get_modality_capabilities()
|
|
47
|
+
table.add_row(
|
|
48
|
+
model_name,
|
|
49
|
+
model_config.provider,
|
|
50
|
+
"✓" if modality_caps["text"] else "✗",
|
|
51
|
+
"✓" if modality_caps["image_input"] else "✗",
|
|
52
|
+
"✓" if modality_caps["image_output"] else "✗",
|
|
53
|
+
"✓" if modality_caps["audio_input"] else "✗",
|
|
54
|
+
"✓" if modality_caps["audio_output"] else "✗",
|
|
55
|
+
"✓" if modality_caps["video_input"] else "✗",
|
|
56
|
+
"✓" if modality_caps["video_output"] else "✗",
|
|
57
|
+
"✓" if modality_caps["tool_calling"] else "✗",
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
table = Table(title="Model Capabilities")
|
|
61
|
+
table.add_column("Model", style="cyan")
|
|
62
|
+
table.add_column("Provider", style="magenta")
|
|
63
|
+
table.add_column("Capabilities", style="green")
|
|
64
|
+
|
|
65
|
+
for model_name, model_config in models.items():
|
|
66
|
+
capabilities = model_config.get_capabilities()
|
|
67
|
+
cap_names = [cap.value for cap in capabilities]
|
|
68
|
+
cap_str = ", ".join(cap_names) if cap_names else "None"
|
|
69
|
+
table.add_row(model_name, model_config.provider, cap_str)
|
|
70
|
+
|
|
71
|
+
cli.console.print(table)
|