chattermate-cli 0.2.3__tar.gz → 0.3.0__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.
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/PKG-INFO +1 -1
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/__init__.py +1 -1
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/client.py +29 -3
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/commands/auth.py +12 -1
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/commands/knowledge.py +62 -4
- chattermate_cli-0.3.0/chattermate_cli/commands/widget.py +63 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/main.py +2 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/PKG-INFO +1 -1
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/SOURCES.txt +4 -1
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/pyproject.toml +1 -1
- chattermate_cli-0.3.0/tests/test_knowledge.py +82 -0
- chattermate_cli-0.3.0/tests/test_widget.py +53 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/LICENSE +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/README.md +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/commands/__init__.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/commands/agent.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/commands/workflow.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/config.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/context.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli/mcp_server.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/dependency_links.txt +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/entry_points.txt +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/requires.txt +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/top_level.txt +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/setup.cfg +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/tests/test_client.py +0 -0
- {chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/tests/test_mcp.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chattermate-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ChatterMate CLI and MCP server — sign up, authenticate, and configure agents, workflows and knowledge from the terminal or an AI agent.
|
|
5
5
|
Author: ChatterMate
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
19
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
20
20
|
|
|
21
21
|
import httpx
|
|
22
22
|
|
|
@@ -112,6 +112,7 @@ class Client:
|
|
|
112
112
|
json: Any = None,
|
|
113
113
|
params: Optional[Dict[str, Any]] = None,
|
|
114
114
|
data: Optional[Dict[str, Any]] = None,
|
|
115
|
+
files: Any = None,
|
|
115
116
|
auth: bool = True,
|
|
116
117
|
_retry: bool = True,
|
|
117
118
|
) -> Any:
|
|
@@ -119,14 +120,16 @@ class Client:
|
|
|
119
120
|
headers = self._auth_headers() if auth else {}
|
|
120
121
|
try:
|
|
121
122
|
resp = self._http.request(
|
|
122
|
-
method, url, json=json, params=params, data=data, headers=headers
|
|
123
|
+
method, url, json=json, params=params, data=data, files=files, headers=headers
|
|
123
124
|
)
|
|
124
125
|
except httpx.HTTPError as e:
|
|
125
126
|
raise ChatterMateError(f"Could not reach {self.api_url}: {e}") from e
|
|
126
127
|
|
|
127
128
|
if resp.status_code == 401 and auth and _retry and self._refresh():
|
|
129
|
+
# files are (name, bytes, type) tuples — safe to re-send on retry.
|
|
128
130
|
return self.request(
|
|
129
|
-
method, path, json=json, params=params, data=data,
|
|
131
|
+
method, path, json=json, params=params, data=data, files=files,
|
|
132
|
+
auth=auth, _retry=False,
|
|
130
133
|
)
|
|
131
134
|
|
|
132
135
|
if resp.status_code >= 400:
|
|
@@ -256,6 +259,18 @@ class Client:
|
|
|
256
259
|
body["agent_id"] = agent_id
|
|
257
260
|
return self.request("POST", "/knowledge/add/urls", json=body)
|
|
258
261
|
|
|
262
|
+
def add_knowledge_files(
|
|
263
|
+
self, org_id: str, files: List[Tuple[str, bytes]], agent_id: Optional[str] = None,
|
|
264
|
+
) -> Any:
|
|
265
|
+
"""Upload local PDF files. ``files`` is a list of ``(filename, content_bytes)``."""
|
|
266
|
+
multipart = [
|
|
267
|
+
("files", (name, content, "application/pdf")) for name, content in files
|
|
268
|
+
]
|
|
269
|
+
data: Dict[str, Any] = {"org_id": org_id}
|
|
270
|
+
if agent_id:
|
|
271
|
+
data["agent_id"] = agent_id
|
|
272
|
+
return self.request("POST", "/knowledge/upload/pdf", data=data, files=multipart)
|
|
273
|
+
|
|
259
274
|
def list_knowledge_for_agent(self, agent_id: str) -> Any:
|
|
260
275
|
return self.request("GET", f"/knowledge/agent/{agent_id}")
|
|
261
276
|
|
|
@@ -273,3 +288,14 @@ class Client:
|
|
|
273
288
|
|
|
274
289
|
def knowledge_queue_status(self, queue_id: int) -> Any:
|
|
275
290
|
return self.request("GET", f"/knowledge/queue/{queue_id}")
|
|
291
|
+
|
|
292
|
+
# -- widgets -----------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
def create_widget(self, name: str, agent_id: Optional[str] = None) -> Any:
|
|
295
|
+
body: Dict[str, Any] = {"name": name}
|
|
296
|
+
if agent_id:
|
|
297
|
+
body["agent_id"] = agent_id
|
|
298
|
+
return self.request("POST", "/widgets", json=body)
|
|
299
|
+
|
|
300
|
+
def list_widgets_for_agent(self, agent_id: str) -> Any:
|
|
301
|
+
return self.request("GET", f"/widgets/agent/{agent_id}")
|
|
@@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
import json
|
|
20
|
+
import sys
|
|
20
21
|
from typing import Optional
|
|
21
22
|
|
|
22
23
|
import typer
|
|
@@ -92,8 +93,18 @@ def signup(
|
|
|
92
93
|
client = get_client(require_auth=False)
|
|
93
94
|
|
|
94
95
|
if not community:
|
|
95
|
-
|
|
96
|
+
# Only request a fresh OTP when the caller hasn't already supplied one.
|
|
97
|
+
# Requesting again would email a NEW code and invalidate the --otp passed in.
|
|
96
98
|
if not otp:
|
|
99
|
+
run(lambda: client.signup_request_otp(admin_email))
|
|
100
|
+
if not sys.stdin.isatty():
|
|
101
|
+
# Non-interactive (AI agent / CI): can't prompt. The code was just
|
|
102
|
+
# emailed — tell them how to finish without re-sending it.
|
|
103
|
+
print_error(
|
|
104
|
+
f"A one-time code was emailed to {admin_email}. "
|
|
105
|
+
f"Re-run this command with --otp <code> to complete signup."
|
|
106
|
+
)
|
|
107
|
+
raise typer.Exit(1)
|
|
97
108
|
is_local = any(h in client.api_url for h in ("localhost", "127.0.0.1", "0.0.0.0"))
|
|
98
109
|
hint = " (localhost test code: 123456)" if is_local else ""
|
|
99
110
|
otp = typer.prompt(f"Enter the one-time code emailed to {admin_email}{hint}")
|
|
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
from pathlib import Path
|
|
19
20
|
from typing import List, Optional
|
|
20
21
|
|
|
21
22
|
import typer
|
|
@@ -26,6 +27,25 @@ from ..context import console, get_client, output, print_error, run
|
|
|
26
27
|
|
|
27
28
|
knowledge_app = typer.Typer(no_args_is_help=True, help="Manage agent knowledge sources.")
|
|
28
29
|
|
|
30
|
+
PDF_MAGIC = b"%PDF-"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _resolve_org_id(client) -> str:
|
|
34
|
+
"""Resolve the current organization id from the saved session, else via whoami.
|
|
35
|
+
|
|
36
|
+
The config file is only populated by login/signup; token (PAT) auth has none, so
|
|
37
|
+
fall back to the authenticated user's org for headless/CI/agent use.
|
|
38
|
+
"""
|
|
39
|
+
org_id = config.load_config().get("organization_id")
|
|
40
|
+
if org_id:
|
|
41
|
+
return str(org_id)
|
|
42
|
+
me = run(client.whoami)
|
|
43
|
+
org_id = me.get("organization_id") if isinstance(me, dict) else None
|
|
44
|
+
if not org_id:
|
|
45
|
+
print_error("Could not determine your organization. Run 'chattermate whoami' to check auth.")
|
|
46
|
+
raise typer.Exit(code=1)
|
|
47
|
+
return str(org_id)
|
|
48
|
+
|
|
29
49
|
|
|
30
50
|
@knowledge_app.command("add-url")
|
|
31
51
|
def add_url(
|
|
@@ -38,11 +58,8 @@ def add_url(
|
|
|
38
58
|
if not website and not pdf_url:
|
|
39
59
|
print_error("Provide at least one --website or --pdf-url.")
|
|
40
60
|
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
61
|
client = get_client()
|
|
62
|
+
org_id = _resolve_org_id(client)
|
|
46
63
|
data = run(lambda: client.add_knowledge(
|
|
47
64
|
org_id=org_id, pdf_urls=list(pdf_url), websites=list(website), agent_id=agent_id,
|
|
48
65
|
))
|
|
@@ -51,6 +68,47 @@ def add_url(
|
|
|
51
68
|
output(data, as_json)
|
|
52
69
|
|
|
53
70
|
|
|
71
|
+
@knowledge_app.command("add-file")
|
|
72
|
+
def add_file(
|
|
73
|
+
paths: List[Path] = typer.Argument(..., help="Local PDF file path(s) to upload."),
|
|
74
|
+
agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Attach to this agent."),
|
|
75
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
76
|
+
):
|
|
77
|
+
"""Upload local PDF file(s) to the knowledge base."""
|
|
78
|
+
files = []
|
|
79
|
+
for path in paths:
|
|
80
|
+
if not path.is_file():
|
|
81
|
+
print_error(f"Not a file: {path}")
|
|
82
|
+
raise typer.Exit(code=1)
|
|
83
|
+
if path.suffix.lower() != ".pdf":
|
|
84
|
+
print_error(f"Not a .pdf file: {path}")
|
|
85
|
+
raise typer.Exit(code=1)
|
|
86
|
+
try:
|
|
87
|
+
content = path.read_bytes()
|
|
88
|
+
except OSError as exc:
|
|
89
|
+
print_error(f"Cannot read {path}: {exc}")
|
|
90
|
+
raise typer.Exit(code=1)
|
|
91
|
+
if not content.startswith(PDF_MAGIC):
|
|
92
|
+
print_error(f"Not a valid PDF (missing %PDF- header): {path}")
|
|
93
|
+
raise typer.Exit(code=1)
|
|
94
|
+
files.append((path.name, content))
|
|
95
|
+
|
|
96
|
+
client = get_client()
|
|
97
|
+
org_id = _resolve_org_id(client)
|
|
98
|
+
data = run(lambda: client.add_knowledge_files(org_id=org_id, files=files, agent_id=agent_id))
|
|
99
|
+
|
|
100
|
+
if as_json:
|
|
101
|
+
output(data, True)
|
|
102
|
+
return
|
|
103
|
+
console.print(f"[green]Queued {len(files)} file(s) for ingestion.[/green]")
|
|
104
|
+
items = data.get("queue_items", []) if isinstance(data, dict) else []
|
|
105
|
+
for it in items:
|
|
106
|
+
console.print(
|
|
107
|
+
f" queue {it.get('id')} — {it.get('status')} "
|
|
108
|
+
f"(track with: chattermate knowledge status {it.get('id')})"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
54
112
|
@knowledge_app.command("list")
|
|
55
113
|
def list_knowledge(
|
|
56
114
|
agent_id: str = typer.Argument(..., help="Agent id."),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Widget 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 typer
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
|
|
22
|
+
from ..context import console, get_client, output, run
|
|
23
|
+
|
|
24
|
+
widget_app = typer.Typer(no_args_is_help=True, help="Manage embeddable chat widgets.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@widget_app.command("create")
|
|
28
|
+
def create_widget(
|
|
29
|
+
agent_id: str = typer.Option(..., "--agent-id", help="Agent to attach the widget to."),
|
|
30
|
+
name: str = typer.Option("Website widget", "--name", help="Widget name."),
|
|
31
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
32
|
+
):
|
|
33
|
+
"""Create an embeddable widget for an agent and print its widget id + embed snippet."""
|
|
34
|
+
client = get_client()
|
|
35
|
+
data = run(lambda: client.create_widget(name=name, agent_id=agent_id))
|
|
36
|
+
if as_json:
|
|
37
|
+
output(data, True)
|
|
38
|
+
return
|
|
39
|
+
widget_id = data.get("id") if isinstance(data, dict) else None
|
|
40
|
+
console.print(f"[green]Created widget[/green] {widget_id}")
|
|
41
|
+
console.print("Embed it on your site:")
|
|
42
|
+
console.print(f" <script>window.chattermateId='{widget_id}';</script>")
|
|
43
|
+
console.print(' <script src="https://app.chattermate.chat/webclient/chattermate.min.js"></script>')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@widget_app.command("list")
|
|
47
|
+
def list_widgets(
|
|
48
|
+
agent_id: str = typer.Argument(..., help="Agent id."),
|
|
49
|
+
as_json: bool = typer.Option(False, "--json", help="Output raw JSON."),
|
|
50
|
+
):
|
|
51
|
+
"""List the widgets attached to an agent (their ids are what you embed)."""
|
|
52
|
+
client = get_client()
|
|
53
|
+
data = run(lambda: client.list_widgets_for_agent(agent_id))
|
|
54
|
+
|
|
55
|
+
def render(rows):
|
|
56
|
+
table = Table(title="Widgets")
|
|
57
|
+
for col in ("id", "name", "agent_id"):
|
|
58
|
+
table.add_column(col)
|
|
59
|
+
for r in rows or []:
|
|
60
|
+
table.add_row(str(r.get("id")), str(r.get("name", "")), str(r.get("agent_id", "")))
|
|
61
|
+
console.print(table)
|
|
62
|
+
|
|
63
|
+
output(data, as_json, render)
|
|
@@ -24,6 +24,7 @@ from . import __version__, context
|
|
|
24
24
|
from .commands import auth as auth_cmd
|
|
25
25
|
from .commands.agent import agent_app
|
|
26
26
|
from .commands.knowledge import knowledge_app
|
|
27
|
+
from .commands.widget import widget_app
|
|
27
28
|
from .commands.workflow import workflow_app
|
|
28
29
|
|
|
29
30
|
app = typer.Typer(
|
|
@@ -62,6 +63,7 @@ app.command("whoami")(auth_cmd.whoami)
|
|
|
62
63
|
# Sub-command groups
|
|
63
64
|
app.add_typer(auth_cmd.token_app, name="token")
|
|
64
65
|
app.add_typer(agent_app, name="agent")
|
|
66
|
+
app.add_typer(widget_app, name="widget")
|
|
65
67
|
app.add_typer(workflow_app, name="workflow")
|
|
66
68
|
app.add_typer(knowledge_app, name="knowledge")
|
|
67
69
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chattermate-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ChatterMate CLI and MCP server — sign up, authenticate, and configure agents, workflows and knowledge from the terminal or an AI agent.
|
|
5
5
|
Author: ChatterMate
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -17,6 +17,9 @@ chattermate_cli/commands/__init__.py
|
|
|
17
17
|
chattermate_cli/commands/agent.py
|
|
18
18
|
chattermate_cli/commands/auth.py
|
|
19
19
|
chattermate_cli/commands/knowledge.py
|
|
20
|
+
chattermate_cli/commands/widget.py
|
|
20
21
|
chattermate_cli/commands/workflow.py
|
|
21
22
|
tests/test_client.py
|
|
22
|
-
tests/
|
|
23
|
+
tests/test_knowledge.py
|
|
24
|
+
tests/test_mcp.py
|
|
25
|
+
tests/test_widget.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chattermate-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "ChatterMate CLI and MCP server — sign up, authenticate, and configure agents, workflows and knowledge from the terminal or an AI agent."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Knowledge Command Tests
|
|
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
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from typer.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
from chattermate_cli.client import Client
|
|
15
|
+
from chattermate_cli.commands import knowledge as kn
|
|
16
|
+
from chattermate_cli.commands.knowledge import knowledge_app
|
|
17
|
+
|
|
18
|
+
runner = CliRunner()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_client(handler, token="cmat_x"):
|
|
22
|
+
client = Client(api_url="http://test", token=token)
|
|
23
|
+
client._http = httpx.Client(transport=httpx.MockTransport(handler))
|
|
24
|
+
return client
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_add_knowledge_files_sends_multipart():
|
|
28
|
+
seen = {}
|
|
29
|
+
|
|
30
|
+
def handler(request):
|
|
31
|
+
seen["ctype"] = request.headers.get("content-type", "")
|
|
32
|
+
seen["body"] = request.content
|
|
33
|
+
seen["path"] = request.url.path
|
|
34
|
+
return httpx.Response(200, json={"message": "ok", "queue_items": [{"id": 7, "status": "pending"}]})
|
|
35
|
+
|
|
36
|
+
client = make_client(handler)
|
|
37
|
+
data = client.add_knowledge_files(
|
|
38
|
+
org_id="org-1", files=[("a.pdf", b"%PDF-1.4 body")], agent_id="ag-1"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert seen["path"] == "/api/v1/knowledge/upload/pdf"
|
|
42
|
+
assert "multipart/form-data" in seen["ctype"]
|
|
43
|
+
body = seen["body"]
|
|
44
|
+
assert b'name="files"' in body and b"a.pdf" in body and b"%PDF-1.4" in body
|
|
45
|
+
assert b'name="org_id"' in body and b"org-1" in body
|
|
46
|
+
assert b'name="agent_id"' in body and b"ag-1" in body
|
|
47
|
+
assert data["queue_items"][0]["id"] == 7
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_add_file_rejects_missing_path(tmp_path):
|
|
51
|
+
result = runner.invoke(knowledge_app, ["add-file", str(tmp_path / "nope.pdf")])
|
|
52
|
+
assert result.exit_code == 1
|
|
53
|
+
assert "Not a file" in result.output
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_add_file_rejects_non_pdf_extension(tmp_path):
|
|
57
|
+
p = tmp_path / "notes.txt"
|
|
58
|
+
p.write_text("hello")
|
|
59
|
+
result = runner.invoke(knowledge_app, ["add-file", str(p)])
|
|
60
|
+
assert result.exit_code == 1
|
|
61
|
+
assert ".pdf" in result.output
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_add_file_rejects_bad_magic_bytes(tmp_path):
|
|
65
|
+
p = tmp_path / "fake.pdf"
|
|
66
|
+
p.write_bytes(b"this is not a pdf")
|
|
67
|
+
result = runner.invoke(knowledge_app, ["add-file", str(p)])
|
|
68
|
+
assert result.exit_code == 1
|
|
69
|
+
assert "valid PDF" in result.output
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_resolve_org_id_prefers_config(monkeypatch):
|
|
73
|
+
monkeypatch.setattr(kn.config, "load_config", lambda: {"organization_id": "org-from-config"})
|
|
74
|
+
# whoami must NOT be called when config has the org.
|
|
75
|
+
client = make_client(lambda req: httpx.Response(500, json={"detail": "should not be called"}))
|
|
76
|
+
assert kn._resolve_org_id(client) == "org-from-config"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_resolve_org_id_falls_back_to_whoami(monkeypatch):
|
|
80
|
+
monkeypatch.setattr(kn.config, "load_config", lambda: {})
|
|
81
|
+
client = make_client(lambda req: httpx.Response(200, json={"organization_id": "org-xyz"}))
|
|
82
|
+
assert kn._resolve_org_id(client) == "org-xyz"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChatterMate - CLI Widget Command Tests
|
|
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
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from chattermate_cli.client import Client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def make_client(handler, token="cmat_x"):
|
|
19
|
+
client = Client(api_url="http://test", token=token)
|
|
20
|
+
client._http = httpx.Client(transport=httpx.MockTransport(handler))
|
|
21
|
+
return client
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_create_widget_posts_name_and_agent():
|
|
25
|
+
seen = {}
|
|
26
|
+
|
|
27
|
+
def handler(request):
|
|
28
|
+
seen["method"] = request.method
|
|
29
|
+
seen["path"] = request.url.path
|
|
30
|
+
seen["json"] = json.loads(request.content)
|
|
31
|
+
return httpx.Response(200, json={"id": "w-123", "agent_id": "ag-1", "name": "Site"})
|
|
32
|
+
|
|
33
|
+
client = make_client(handler)
|
|
34
|
+
data = client.create_widget(name="Site", agent_id="ag-1")
|
|
35
|
+
|
|
36
|
+
assert seen["method"] == "POST"
|
|
37
|
+
assert seen["path"] == "/api/v1/widgets"
|
|
38
|
+
assert seen["json"] == {"name": "Site", "agent_id": "ag-1"}
|
|
39
|
+
assert data["id"] == "w-123"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_list_widgets_for_agent_gets_by_agent():
|
|
43
|
+
seen = {}
|
|
44
|
+
|
|
45
|
+
def handler(request):
|
|
46
|
+
seen["path"] = request.url.path
|
|
47
|
+
return httpx.Response(200, json=[{"id": "w-1", "name": "Site", "agent_id": "ag-1"}])
|
|
48
|
+
|
|
49
|
+
client = make_client(handler)
|
|
50
|
+
rows = client.list_widgets_for_agent("ag-1")
|
|
51
|
+
|
|
52
|
+
assert seen["path"] == "/api/v1/widgets/agent/ag-1"
|
|
53
|
+
assert rows[0]["id"] == "w-1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chattermate_cli-0.2.3 → chattermate_cli-0.3.0}/chattermate_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|