chattermate-cli 0.2.0__py3-none-any.whl
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.
- chattermate_cli/__init__.py +19 -0
- chattermate_cli/client.py +275 -0
- chattermate_cli/commands/__init__.py +17 -0
- chattermate_cli/commands/agent.py +122 -0
- chattermate_cli/commands/auth.py +202 -0
- chattermate_cli/commands/knowledge.py +104 -0
- chattermate_cli/commands/workflow.py +129 -0
- chattermate_cli/config.py +101 -0
- chattermate_cli/context.py +78 -0
- chattermate_cli/main.py +74 -0
- chattermate_cli/mcp_server.py +253 -0
- chattermate_cli-0.2.0.dist-info/METADATA +113 -0
- chattermate_cli-0.2.0.dist-info/RECORD +17 -0
- chattermate_cli-0.2.0.dist-info/WHEEL +5 -0
- chattermate_cli-0.2.0.dist-info/entry_points.txt +4 -0
- chattermate_cli-0.2.0.dist-info/licenses/LICENSE +18 -0
- chattermate_cli-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Knowledge Commands
|
|
3
|
+
Copyright (C) 2024 ChatterMate
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
|
|
24
|
+
from .. import config
|
|
25
|
+
from ..context import console, get_client, output, print_error, run
|
|
26
|
+
|
|
27
|
+
knowledge_app = typer.Typer(no_args_is_help=True, help="Manage agent knowledge sources.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@knowledge_app.command("add-url")
|
|
31
|
+
def add_url(
|
|
32
|
+
website: List[str] = typer.Option([], "--website", "-w", help="Website URL (repeatable)."),
|
|
33
|
+
pdf_url: List[str] = typer.Option([], "--pdf-url", help="PDF URL (repeatable)."),
|
|
34
|
+
agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Attach to this agent."),
|
|
35
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
36
|
+
):
|
|
37
|
+
"""Add website and/or PDF URLs to the knowledge base."""
|
|
38
|
+
if not website and not pdf_url:
|
|
39
|
+
print_error("Provide at least one --website or --pdf-url.")
|
|
40
|
+
raise typer.Exit(code=1)
|
|
41
|
+
org_id = config.load_config().get("organization_id")
|
|
42
|
+
if not org_id:
|
|
43
|
+
print_error("No organization in session. Run 'chattermate login' or 'whoami' first.")
|
|
44
|
+
raise typer.Exit(code=1)
|
|
45
|
+
client = get_client()
|
|
46
|
+
data = run(lambda: client.add_knowledge(
|
|
47
|
+
org_id=org_id, pdf_urls=list(pdf_url), websites=list(website), agent_id=agent_id,
|
|
48
|
+
))
|
|
49
|
+
if not as_json:
|
|
50
|
+
console.print("[green]Queued knowledge ingestion.[/green]")
|
|
51
|
+
output(data, as_json)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@knowledge_app.command("list")
|
|
55
|
+
def list_knowledge(
|
|
56
|
+
agent_id: str = typer.Argument(..., help="Agent id."),
|
|
57
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
58
|
+
):
|
|
59
|
+
"""List knowledge sources linked to an agent."""
|
|
60
|
+
client = get_client()
|
|
61
|
+
data = run(lambda: client.list_knowledge_for_agent(agent_id))
|
|
62
|
+
|
|
63
|
+
def render(payload):
|
|
64
|
+
rows = payload.get("items", payload) if isinstance(payload, dict) else payload
|
|
65
|
+
table = Table(title="Knowledge sources")
|
|
66
|
+
for col in ("id", "source_type", "source"):
|
|
67
|
+
table.add_column(col)
|
|
68
|
+
for r in rows or []:
|
|
69
|
+
table.add_row(str(r.get("id")), str(r.get("source_type", "")), str(r.get("source", "")))
|
|
70
|
+
console.print(table)
|
|
71
|
+
|
|
72
|
+
output(data, as_json, render)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@knowledge_app.command("link")
|
|
76
|
+
def link(
|
|
77
|
+
knowledge_id: int = typer.Argument(..., help="Knowledge id."),
|
|
78
|
+
agent_id: str = typer.Argument(..., help="Agent id."),
|
|
79
|
+
):
|
|
80
|
+
"""Link a knowledge source to an agent."""
|
|
81
|
+
client = get_client()
|
|
82
|
+
run(lambda: client.link_knowledge(knowledge_id, agent_id))
|
|
83
|
+
console.print(f"[green]Linked[/green] knowledge {knowledge_id} -> agent {agent_id}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@knowledge_app.command("unlink")
|
|
87
|
+
def unlink(
|
|
88
|
+
knowledge_id: int = typer.Argument(..., help="Knowledge id."),
|
|
89
|
+
agent_id: str = typer.Argument(..., help="Agent id."),
|
|
90
|
+
):
|
|
91
|
+
"""Unlink a knowledge source from an agent."""
|
|
92
|
+
client = get_client()
|
|
93
|
+
run(lambda: client.unlink_knowledge(knowledge_id, agent_id))
|
|
94
|
+
console.print(f"[green]Unlinked[/green] knowledge {knowledge_id} from agent {agent_id}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@knowledge_app.command("status")
|
|
98
|
+
def status(
|
|
99
|
+
queue_id: int = typer.Argument(..., help="Ingestion queue id."),
|
|
100
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
101
|
+
):
|
|
102
|
+
"""Check the status of a knowledge ingestion queue item."""
|
|
103
|
+
client = get_client()
|
|
104
|
+
output(run(lambda: client.knowledge_queue_status(queue_id)), as_json)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Workflow Commands
|
|
3
|
+
Copyright (C) 2024 ChatterMate
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
import typer
|
|
24
|
+
|
|
25
|
+
from ..context import console, get_client, output, print_error, run
|
|
26
|
+
|
|
27
|
+
workflow_app = typer.Typer(no_args_is_help=True, help="Create and manage agent workflows.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load_json_arg(data: Optional[str], file: Optional[Path]) -> dict:
|
|
31
|
+
if file is not None:
|
|
32
|
+
try:
|
|
33
|
+
raw = file.read_text()
|
|
34
|
+
except OSError as e:
|
|
35
|
+
print_error(f"Could not read {file}: {e}")
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
elif data is not None:
|
|
38
|
+
raw = data
|
|
39
|
+
else:
|
|
40
|
+
print_error("Provide --data '<json>' or --file <path>.")
|
|
41
|
+
raise typer.Exit(code=1)
|
|
42
|
+
try:
|
|
43
|
+
return json.loads(raw)
|
|
44
|
+
except json.JSONDecodeError as e:
|
|
45
|
+
print_error(f"Invalid JSON: {e}")
|
|
46
|
+
raise typer.Exit(code=1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@workflow_app.command("get")
|
|
50
|
+
def get_workflow(
|
|
51
|
+
agent_id: str = typer.Argument(..., help="Agent id whose workflow to fetch."),
|
|
52
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
53
|
+
):
|
|
54
|
+
"""Get the workflow attached to an agent."""
|
|
55
|
+
client = get_client()
|
|
56
|
+
output(run(lambda: client.get_workflow_for_agent(agent_id)), as_json)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@workflow_app.command("create")
|
|
60
|
+
def create_workflow(
|
|
61
|
+
agent_id: str = typer.Option(..., "--agent-id", help="Agent the workflow belongs to."),
|
|
62
|
+
name: str = typer.Option(..., "--name", help="Workflow name."),
|
|
63
|
+
description: Optional[str] = typer.Option(None, "--description"),
|
|
64
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
65
|
+
):
|
|
66
|
+
"""Create a workflow for an agent."""
|
|
67
|
+
payload = {"name": name, "agent_id": agent_id}
|
|
68
|
+
if description is not None:
|
|
69
|
+
payload["description"] = description
|
|
70
|
+
client = get_client()
|
|
71
|
+
data = run(lambda: client.create_workflow(payload))
|
|
72
|
+
if not as_json:
|
|
73
|
+
console.print(f"[green]Created workflow[/green] {data.get('id')} ({name})")
|
|
74
|
+
output(data, as_json)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@workflow_app.command("update")
|
|
78
|
+
def update_workflow(
|
|
79
|
+
workflow_id: str = typer.Argument(..., help="Workflow id."),
|
|
80
|
+
name: Optional[str] = typer.Option(None, "--name"),
|
|
81
|
+
description: Optional[str] = typer.Option(None, "--description"),
|
|
82
|
+
status: Optional[str] = typer.Option(
|
|
83
|
+
None, "--status", help="draft, published or archived (case-insensitive)."
|
|
84
|
+
),
|
|
85
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
86
|
+
):
|
|
87
|
+
"""Update a workflow's metadata."""
|
|
88
|
+
payload: dict = {}
|
|
89
|
+
if name is not None:
|
|
90
|
+
payload["name"] = name
|
|
91
|
+
if description is not None:
|
|
92
|
+
payload["description"] = description
|
|
93
|
+
if status is not None:
|
|
94
|
+
# API enum values are lowercase (draft, published, archived); normalize.
|
|
95
|
+
payload["status"] = status.lower()
|
|
96
|
+
if not payload:
|
|
97
|
+
print_error("Nothing to update. Pass --name, --description or --status.")
|
|
98
|
+
raise typer.Exit(code=1)
|
|
99
|
+
client = get_client()
|
|
100
|
+
data = run(lambda: client.update_workflow(workflow_id, payload))
|
|
101
|
+
if not as_json:
|
|
102
|
+
console.print(f"[green]Updated workflow[/green] {workflow_id}")
|
|
103
|
+
output(data, as_json)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@workflow_app.command("nodes")
|
|
107
|
+
def get_nodes(
|
|
108
|
+
workflow_id: str = typer.Argument(..., help="Workflow id."),
|
|
109
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
110
|
+
):
|
|
111
|
+
"""Get all nodes and connections for a workflow."""
|
|
112
|
+
client = get_client()
|
|
113
|
+
output(run(lambda: client.get_workflow_nodes(workflow_id)), as_json)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@workflow_app.command("set-nodes")
|
|
117
|
+
def set_nodes(
|
|
118
|
+
workflow_id: str = typer.Argument(..., help="Workflow id."),
|
|
119
|
+
data: Optional[str] = typer.Option(None, "--data", help="Nodes/connections JSON payload."),
|
|
120
|
+
file: Optional[Path] = typer.Option(None, "--file", help="Path to a JSON payload file."),
|
|
121
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
122
|
+
):
|
|
123
|
+
"""Replace a workflow's nodes/connections from a JSON payload."""
|
|
124
|
+
payload = _load_json_arg(data, file)
|
|
125
|
+
client = get_client()
|
|
126
|
+
result = run(lambda: client.update_workflow_nodes(workflow_id, payload))
|
|
127
|
+
if not as_json:
|
|
128
|
+
console.print(f"[green]Updated nodes[/green] for workflow {workflow_id}")
|
|
129
|
+
output(result, as_json)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Configuration
|
|
3
|
+
Copyright (C) 2024 ChatterMate
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
# Hosted ChatterMate API. For a local/self-hosted backend, set
|
|
25
|
+
# CHATTERMATE_API_URL=http://localhost:8000 (or pass --api-url).
|
|
26
|
+
DEFAULT_API_URL = "https://api.chattermate.chat"
|
|
27
|
+
ENV_API_URL = "CHATTERMATE_API_URL"
|
|
28
|
+
ENV_TOKEN = "CHATTERMATE_TOKEN"
|
|
29
|
+
ENV_CONFIG_DIR = "CHATTERMATE_CONFIG_DIR"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def config_dir() -> Path:
|
|
33
|
+
return Path(os.environ.get(ENV_CONFIG_DIR, str(Path.home() / ".chattermate")))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def config_file() -> Path:
|
|
37
|
+
return config_dir() / "config.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_config() -> Dict[str, Any]:
|
|
41
|
+
"""Load the on-disk config, or an empty dict if none exists / is unreadable."""
|
|
42
|
+
path = config_file()
|
|
43
|
+
try:
|
|
44
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
45
|
+
return json.load(fh)
|
|
46
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_config(cfg: Dict[str, Any]) -> None:
|
|
51
|
+
"""Persist config to ~/.chattermate/config.json atomically with 0600 permissions.
|
|
52
|
+
|
|
53
|
+
The file holds tokens, so it is created 0600 from the start (no world-readable
|
|
54
|
+
window) and written via a temp file + atomic replace (no truncated config on crash).
|
|
55
|
+
"""
|
|
56
|
+
d = config_dir()
|
|
57
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
path = config_file()
|
|
59
|
+
tmp = path.with_suffix(".json.tmp")
|
|
60
|
+
# os.open with mode 0600 ensures the secret-bearing file is never world-readable.
|
|
61
|
+
fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
62
|
+
try:
|
|
63
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
64
|
+
json.dump(cfg, fh, indent=2)
|
|
65
|
+
except Exception:
|
|
66
|
+
try:
|
|
67
|
+
os.unlink(tmp)
|
|
68
|
+
finally:
|
|
69
|
+
raise
|
|
70
|
+
os.replace(tmp, path)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def update_config(**values: Any) -> Dict[str, Any]:
|
|
74
|
+
cfg = load_config()
|
|
75
|
+
cfg.update({k: v for k, v in values.items() if v is not None})
|
|
76
|
+
save_config(cfg)
|
|
77
|
+
return cfg
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def clear_auth() -> None:
|
|
81
|
+
"""Remove stored credentials (keeps api_url)."""
|
|
82
|
+
cfg = load_config()
|
|
83
|
+
for key in ("access_token", "refresh_token", "user_id", "organization_id", "email"):
|
|
84
|
+
cfg.pop(key, None)
|
|
85
|
+
save_config(cfg)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def resolve_api_url(override: Optional[str] = None) -> str:
|
|
89
|
+
if override:
|
|
90
|
+
return override
|
|
91
|
+
return os.environ.get(ENV_API_URL) or load_config().get("api_url") or DEFAULT_API_URL
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def resolve_token(override: Optional[str] = None) -> Optional[str]:
|
|
95
|
+
"""Resolve the bearer token: explicit override > env PAT > stored access token."""
|
|
96
|
+
if override:
|
|
97
|
+
return override
|
|
98
|
+
env_token = os.environ.get(ENV_TOKEN)
|
|
99
|
+
if env_token:
|
|
100
|
+
return env_token
|
|
101
|
+
return load_config().get("access_token")
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Shared Context
|
|
3
|
+
Copyright (C) 2024 ChatterMate
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json as _json
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
|
|
25
|
+
from . import config
|
|
26
|
+
from .client import ChatterMateError, Client
|
|
27
|
+
|
|
28
|
+
console = Console()
|
|
29
|
+
err_console = Console(stderr=True)
|
|
30
|
+
|
|
31
|
+
# Set by the root callback so every command can honour a global --api-url.
|
|
32
|
+
_api_url_override: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def set_api_url_override(value: Optional[str]) -> None:
|
|
36
|
+
global _api_url_override
|
|
37
|
+
_api_url_override = value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _persist_tokens(access: str, refresh: Optional[str]) -> None:
|
|
41
|
+
config.update_config(access_token=access, refresh_token=refresh)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_client(require_auth: bool = True) -> Client:
|
|
45
|
+
"""Build a Client from --api-url / env / stored config."""
|
|
46
|
+
api_url = config.resolve_api_url(_api_url_override)
|
|
47
|
+
token = config.resolve_token()
|
|
48
|
+
cfg = config.load_config()
|
|
49
|
+
if require_auth and not token:
|
|
50
|
+
print_error("Not authenticated. Run 'chattermate login' or set CHATTERMATE_TOKEN.")
|
|
51
|
+
raise typer.Exit(code=1)
|
|
52
|
+
return Client(
|
|
53
|
+
api_url=api_url,
|
|
54
|
+
token=token,
|
|
55
|
+
refresh_token=cfg.get("refresh_token"),
|
|
56
|
+
on_tokens=_persist_tokens,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def output(data: Any, as_json: bool, table_renderer=None) -> None:
|
|
61
|
+
"""Print ``data`` as JSON when ``as_json`` is set, else via ``table_renderer``."""
|
|
62
|
+
if as_json or table_renderer is None:
|
|
63
|
+
console.print_json(_json.dumps(data, default=str))
|
|
64
|
+
else:
|
|
65
|
+
table_renderer(data)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def print_error(message: str) -> None:
|
|
69
|
+
err_console.print(f"[bold red]Error:[/bold red] {message}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run(fn):
|
|
73
|
+
"""Execute an API call, turning ChatterMateError into a clean non-zero exit."""
|
|
74
|
+
try:
|
|
75
|
+
return fn()
|
|
76
|
+
except ChatterMateError as e:
|
|
77
|
+
print_error(str(e))
|
|
78
|
+
raise typer.Exit(code=1)
|
chattermate_cli/main.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Entry Point
|
|
3
|
+
Copyright (C) 2024 ChatterMate
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as
|
|
7
|
+
published by the Free Software Foundation, either version 3 of the
|
|
8
|
+
License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from . import __version__, context
|
|
24
|
+
from .commands import auth as auth_cmd
|
|
25
|
+
from .commands.agent import agent_app
|
|
26
|
+
from .commands.knowledge import knowledge_app
|
|
27
|
+
from .commands.workflow import workflow_app
|
|
28
|
+
|
|
29
|
+
app = typer.Typer(
|
|
30
|
+
no_args_is_help=True,
|
|
31
|
+
add_completion=True,
|
|
32
|
+
help="ChatterMate CLI — sign up, log in, and configure agents, workflows and knowledge.",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _version_callback(value: bool):
|
|
37
|
+
if value:
|
|
38
|
+
typer.echo(f"chattermate-cli {__version__}")
|
|
39
|
+
raise typer.Exit()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.callback()
|
|
43
|
+
def main_callback(
|
|
44
|
+
api_url: Optional[str] = typer.Option(
|
|
45
|
+
None, "--api-url",
|
|
46
|
+
help="ChatterMate API base URL (overrides CHATTERMATE_API_URL and stored config).",
|
|
47
|
+
),
|
|
48
|
+
version: bool = typer.Option(
|
|
49
|
+
False, "--version", callback=_version_callback, is_eager=True, help="Show version."
|
|
50
|
+
),
|
|
51
|
+
):
|
|
52
|
+
"""Global options."""
|
|
53
|
+
context.set_api_url_override(api_url)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Top-level auth commands
|
|
57
|
+
app.command("login")(auth_cmd.login)
|
|
58
|
+
app.command("signup")(auth_cmd.signup)
|
|
59
|
+
app.command("logout")(auth_cmd.logout)
|
|
60
|
+
app.command("whoami")(auth_cmd.whoami)
|
|
61
|
+
|
|
62
|
+
# Sub-command groups
|
|
63
|
+
app.add_typer(auth_cmd.token_app, name="token")
|
|
64
|
+
app.add_typer(agent_app, name="agent")
|
|
65
|
+
app.add_typer(workflow_app, name="workflow")
|
|
66
|
+
app.add_typer(knowledge_app, name="knowledge")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main():
|
|
70
|
+
app()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|