autoverse 0.1.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.
- autoverse-0.1.0/MANIFEST.in +12 -0
- autoverse-0.1.0/PKG-INFO +41 -0
- autoverse-0.1.0/README.md +23 -0
- autoverse-0.1.0/pyproject.toml +44 -0
- autoverse-0.1.0/setup.cfg +4 -0
- autoverse-0.1.0/src/autoverse/__init__.py +5 -0
- autoverse-0.1.0/src/autoverse/__main__.py +4 -0
- autoverse-0.1.0/src/autoverse/_log.py +44 -0
- autoverse-0.1.0/src/autoverse/cli.py +273 -0
- autoverse-0.1.0/src/autoverse/client.py +336 -0
- autoverse-0.1.0/src/autoverse/credentials.py +202 -0
- autoverse-0.1.0/src/autoverse/errors.py +23 -0
- autoverse-0.1.0/src/autoverse/hosted_mcp.py +255 -0
- autoverse-0.1.0/src/autoverse/login.py +213 -0
- autoverse-0.1.0/src/autoverse/mcp_capabilities.py +186 -0
- autoverse-0.1.0/src/autoverse/mcp_context.py +17 -0
- autoverse-0.1.0/src/autoverse/mcp_server.py +274 -0
- autoverse-0.1.0/src/autoverse/render.py +64 -0
- autoverse-0.1.0/src/autoverse.egg-info/SOURCES.txt +23 -0
- autoverse-0.1.0/tests/conftest.py +30 -0
- autoverse-0.1.0/tests/fake_v1.py +102 -0
- autoverse-0.1.0/tests/test_client.py +161 -0
- autoverse-0.1.0/tests/test_hosted_mcp.py +322 -0
- autoverse-0.1.0/tests/test_login.py +63 -0
- autoverse-0.1.0/tests/test_mcp.py +99 -0
- autoverse-0.1.0/tests/test_new_user_journey.py +232 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include pyproject.toml
|
|
3
|
+
graft src/autoverse
|
|
4
|
+
graft tests
|
|
5
|
+
global-exclude *.py[cod] *.so .DS_Store
|
|
6
|
+
prune .venv
|
|
7
|
+
prune venv
|
|
8
|
+
prune deploy
|
|
9
|
+
prune doc
|
|
10
|
+
prune .pytest_cache
|
|
11
|
+
prune src/autoverse.egg-info
|
|
12
|
+
recursive-exclude src *.egg-info
|
autoverse-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autoverse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for Autoverse literature search
|
|
5
|
+
Project-URL: Homepage, https://app.autoverse.nz
|
|
6
|
+
Project-URL: Documentation, https://app.autoverse.nz/#/docs/cli
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx<1,>=0.27
|
|
10
|
+
Requires-Dist: keyring<26,>=25
|
|
11
|
+
Requires-Dist: mcp<3,>=2
|
|
12
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
13
|
+
Requires-Dist: typer<1,>=0.27
|
|
14
|
+
Requires-Dist: uvicorn[standard]<1,>=0.32
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest<9,>=8.3; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-asyncio<1,>=0.25; extra == "test"
|
|
18
|
+
|
|
19
|
+
# Autoverse
|
|
20
|
+
|
|
21
|
+
Command-line client for Autoverse literature search. The default API is `https://app.autoverse.nz`.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pipx install autoverse
|
|
25
|
+
autoverse login
|
|
26
|
+
autoverse search "HER2-low breast cancer" --domain medicine --limit 5
|
|
27
|
+
autoverse resolve doi:10.4143/crt.2023.1138
|
|
28
|
+
autoverse usage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Sign-in opens a browser for GitHub authorization. Headless or CI environments store a platform key:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
autoverse login --token sr_live_...
|
|
35
|
+
# or
|
|
36
|
+
export AUTOVERSE_API_KEY="sr_live_..."
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Coding assistants should use the hosted MCP at `https://app.autoverse.nz/mcp` with `Authorization: Bearer <platform key>`. Do not install `npx @autoverse/mcp-server`. Do not install `autoverse-cli` from PyPI — that name belongs to a different project.
|
|
40
|
+
|
|
41
|
+
Requires Python 3.12+.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Autoverse
|
|
2
|
+
|
|
3
|
+
Command-line client for Autoverse literature search. The default API is `https://app.autoverse.nz`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pipx install autoverse
|
|
7
|
+
autoverse login
|
|
8
|
+
autoverse search "HER2-low breast cancer" --domain medicine --limit 5
|
|
9
|
+
autoverse resolve doi:10.4143/crt.2023.1138
|
|
10
|
+
autoverse usage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Sign-in opens a browser for GitHub authorization. Headless or CI environments store a platform key:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
autoverse login --token sr_live_...
|
|
17
|
+
# or
|
|
18
|
+
export AUTOVERSE_API_KEY="sr_live_..."
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Coding assistants should use the hosted MCP at `https://app.autoverse.nz/mcp` with `Authorization: Bearer <platform key>`. Do not install `npx @autoverse/mcp-server`. Do not install `autoverse-cli` from PyPI — that name belongs to a different project.
|
|
22
|
+
|
|
23
|
+
Requires Python 3.12+.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "autoverse"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Command-line client for Autoverse literature search"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.27,<1",
|
|
13
|
+
"keyring>=25,<26",
|
|
14
|
+
"mcp>=2,<3",
|
|
15
|
+
"pydantic>=2.10,<3",
|
|
16
|
+
"typer>=0.27,<1",
|
|
17
|
+
"uvicorn[standard]>=0.32,<1",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://app.autoverse.nz"
|
|
22
|
+
Documentation = "https://app.autoverse.nz/#/docs/cli"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = [
|
|
26
|
+
"pytest>=8.3,<9",
|
|
27
|
+
"pytest-asyncio>=0.25,<1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
autoverse = "autoverse.cli:main"
|
|
32
|
+
autoverse-mcp = "autoverse.mcp_server:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools]
|
|
35
|
+
package-dir = {"" = "src"}
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
include = ["autoverse"]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
pythonpath = ["src"]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
_REDACT_KEYS = frozenset(
|
|
8
|
+
{
|
|
9
|
+
"code",
|
|
10
|
+
"state",
|
|
11
|
+
"token",
|
|
12
|
+
"access_token",
|
|
13
|
+
"refresh_token",
|
|
14
|
+
"authorization",
|
|
15
|
+
"code_verifier",
|
|
16
|
+
"code_challenge",
|
|
17
|
+
"api_key",
|
|
18
|
+
"password",
|
|
19
|
+
"secret",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def configure_logging(*, quiet: bool = False) -> None:
|
|
25
|
+
"""Send process logs to stderr only. stdout stays for tables and JSON."""
|
|
26
|
+
|
|
27
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
28
|
+
handler.setFormatter(logging.Formatter("%(levelname)s %(name)s %(message)s"))
|
|
29
|
+
root = logging.getLogger("autoverse")
|
|
30
|
+
root.handlers.clear()
|
|
31
|
+
root.addHandler(handler)
|
|
32
|
+
root.setLevel(logging.WARNING if quiet else logging.INFO)
|
|
33
|
+
root.propagate = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def event(name: str, **fields: Any) -> str:
|
|
37
|
+
parts = [name]
|
|
38
|
+
for key, value in fields.items():
|
|
39
|
+
if value is None:
|
|
40
|
+
continue
|
|
41
|
+
if key in _REDACT_KEYS:
|
|
42
|
+
continue
|
|
43
|
+
parts.append(f"{key}={value}")
|
|
44
|
+
return " ".join(parts)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from autoverse._log import configure_logging
|
|
10
|
+
from autoverse.client import AutoverseClient
|
|
11
|
+
from autoverse.credentials import CredentialStore
|
|
12
|
+
from autoverse.errors import AutoverseError, UsageError
|
|
13
|
+
from autoverse import login as login_flow
|
|
14
|
+
from autoverse.render import emit_error, emit_json, papers_table, response_data, usage_text
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(no_args_is_help=True, add_completion=False)
|
|
17
|
+
mcp_app = typer.Typer(no_args_is_help=True, add_completion=False)
|
|
18
|
+
app.add_typer(mcp_app, name="mcp")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class AppContext:
|
|
23
|
+
json_output: bool
|
|
24
|
+
api_base: str | None
|
|
25
|
+
api_key: str | None
|
|
26
|
+
profile: str
|
|
27
|
+
quiet: bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _client(ctx: typer.Context) -> AutoverseClient:
|
|
31
|
+
state: AppContext = ctx.obj
|
|
32
|
+
return AutoverseClient(
|
|
33
|
+
api_base=state.api_base,
|
|
34
|
+
api_key=state.api_key,
|
|
35
|
+
credentials=CredentialStore(profile=state.profile),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _emit(ctx: typer.Context, payload: dict[str, Any], *, human: str) -> None:
|
|
40
|
+
data, request_id = response_data(payload) if isinstance(payload, dict) else (payload, None)
|
|
41
|
+
if ctx.obj.json_output:
|
|
42
|
+
emit_json(data, request_id=request_id)
|
|
43
|
+
return
|
|
44
|
+
typer.echo(human)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.callback()
|
|
48
|
+
def _root(
|
|
49
|
+
ctx: typer.Context,
|
|
50
|
+
json_output: Annotated[bool, typer.Option("--json", help="Write machine-readable JSON to stdout")] = False,
|
|
51
|
+
api_base: Annotated[str | None, typer.Option("--api-base", envvar="AUTOVERSE_API_BASE")] = None,
|
|
52
|
+
api_key: Annotated[str | None, typer.Option("--api-key", envvar="AUTOVERSE_API_KEY")] = None,
|
|
53
|
+
profile: Annotated[str, typer.Option("--profile")] = "default",
|
|
54
|
+
quiet: Annotated[bool, typer.Option("--quiet")] = False,
|
|
55
|
+
) -> None:
|
|
56
|
+
configure_logging(quiet=quiet)
|
|
57
|
+
ctx.obj = AppContext(
|
|
58
|
+
json_output=json_output,
|
|
59
|
+
api_base=api_base,
|
|
60
|
+
api_key=api_key,
|
|
61
|
+
profile=profile,
|
|
62
|
+
quiet=quiet,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command()
|
|
67
|
+
def login(
|
|
68
|
+
ctx: typer.Context,
|
|
69
|
+
no_browser: Annotated[bool, typer.Option("--no-browser")] = False,
|
|
70
|
+
token: Annotated[str | None, typer.Option("--token")] = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
client = _client(ctx)
|
|
73
|
+
try:
|
|
74
|
+
if token:
|
|
75
|
+
stored = client.login_with_api_key(token)
|
|
76
|
+
_emit(ctx, {"api_base": stored.api_base}, human="API key stored. You can search now.")
|
|
77
|
+
return
|
|
78
|
+
payload = login_flow.authorize(client, open_browser=not no_browser)
|
|
79
|
+
email = ((payload.get("user") or {}) or {}).get("email") or "signed in"
|
|
80
|
+
_emit(ctx, payload, human=f"Logged in as {email}.")
|
|
81
|
+
except AutoverseError as exc:
|
|
82
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
83
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command()
|
|
87
|
+
def logout(
|
|
88
|
+
ctx: typer.Context,
|
|
89
|
+
all_sessions: Annotated[bool, typer.Option("--all")] = False,
|
|
90
|
+
) -> None:
|
|
91
|
+
client = _client(ctx)
|
|
92
|
+
try:
|
|
93
|
+
client.logout(all_sessions=all_sessions)
|
|
94
|
+
_emit(ctx, {"status": "logged_out"}, human="Logged out.")
|
|
95
|
+
except AutoverseError as exc:
|
|
96
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
97
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def whoami(ctx: typer.Context) -> None:
|
|
102
|
+
client = _client(ctx)
|
|
103
|
+
try:
|
|
104
|
+
payload = client.whoami()
|
|
105
|
+
user = payload.get("user") or {}
|
|
106
|
+
usage = payload.get("usage") or {}
|
|
107
|
+
human = f"{user.get('email') or 'api-key'}\n{usage_text(usage)}"
|
|
108
|
+
_emit(ctx, payload, human=human)
|
|
109
|
+
except AutoverseError as exc:
|
|
110
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
111
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def search(
|
|
116
|
+
ctx: typer.Context,
|
|
117
|
+
query: str,
|
|
118
|
+
domain: Annotated[str, typer.Option("--domain")] = "auto",
|
|
119
|
+
mode: Annotated[str, typer.Option("--mode")] = "auto",
|
|
120
|
+
year_from: Annotated[int | None, typer.Option("--year-from")] = None,
|
|
121
|
+
year_to: Annotated[int | None, typer.Option("--year-to")] = None,
|
|
122
|
+
publication_type: Annotated[list[str] | None, typer.Option("--type")] = None,
|
|
123
|
+
limit: Annotated[int, typer.Option("--limit")] = 20,
|
|
124
|
+
cursor: Annotated[str | None, typer.Option("--cursor")] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
if not 1 <= limit <= 50:
|
|
127
|
+
emit_error(UsageError("limit must be between 1 and 50"), as_json=ctx.obj.json_output)
|
|
128
|
+
raise typer.Exit(2)
|
|
129
|
+
client = _client(ctx)
|
|
130
|
+
try:
|
|
131
|
+
payload = client.search(
|
|
132
|
+
query,
|
|
133
|
+
domain=domain,
|
|
134
|
+
mode=mode,
|
|
135
|
+
year_from=year_from,
|
|
136
|
+
year_to=year_to,
|
|
137
|
+
publication_types=publication_type,
|
|
138
|
+
limit=limit,
|
|
139
|
+
cursor=cursor,
|
|
140
|
+
)
|
|
141
|
+
data, _ = response_data(payload)
|
|
142
|
+
items = data.get("items") if isinstance(data, dict) else []
|
|
143
|
+
_emit(ctx, payload, human=papers_table(items if isinstance(items, list) else []))
|
|
144
|
+
except AutoverseError as exc:
|
|
145
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
146
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@app.command()
|
|
150
|
+
def resolve(ctx: typer.Context, identifier: str) -> None:
|
|
151
|
+
client = _client(ctx)
|
|
152
|
+
try:
|
|
153
|
+
payload = client.resolve(identifier)
|
|
154
|
+
data, _ = response_data(payload)
|
|
155
|
+
title = data.get("title") if isinstance(data, dict) else identifier
|
|
156
|
+
_emit(ctx, payload, human=str(title))
|
|
157
|
+
except AutoverseError as exc:
|
|
158
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
159
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.command()
|
|
163
|
+
def batch(
|
|
164
|
+
ctx: typer.Context,
|
|
165
|
+
file: Annotated[Path, typer.Option("-f", "--file")],
|
|
166
|
+
) -> None:
|
|
167
|
+
identifiers = [line.strip() for line in file.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
168
|
+
client = _client(ctx)
|
|
169
|
+
try:
|
|
170
|
+
payload = client.batch(identifiers)
|
|
171
|
+
items = payload.get("items") if isinstance(payload, dict) else []
|
|
172
|
+
human = "\n".join(
|
|
173
|
+
f"{item.get('identifier')} {((item.get('paper') or {}) or {}).get('title') or item.get('error')}"
|
|
174
|
+
for item in items
|
|
175
|
+
)
|
|
176
|
+
_emit(ctx, payload, human=human or "(no items)")
|
|
177
|
+
except AutoverseError as exc:
|
|
178
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
179
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@app.command()
|
|
183
|
+
def authors(ctx: typer.Context, query: str) -> None:
|
|
184
|
+
client = _client(ctx)
|
|
185
|
+
try:
|
|
186
|
+
payload = client.authors(query)
|
|
187
|
+
items = payload.get("items") if isinstance(payload, dict) else []
|
|
188
|
+
human = "\n".join(str(item.get("name") or item) for item in items)
|
|
189
|
+
_emit(ctx, payload, human=human or "(no authors)")
|
|
190
|
+
except AutoverseError as exc:
|
|
191
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
192
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@app.command()
|
|
196
|
+
def usage(ctx: typer.Context) -> None:
|
|
197
|
+
client = _client(ctx)
|
|
198
|
+
try:
|
|
199
|
+
payload = client.usage()
|
|
200
|
+
extra = ""
|
|
201
|
+
try:
|
|
202
|
+
events = client.usage_events(limit=10)
|
|
203
|
+
rows = events.get("items") or []
|
|
204
|
+
if rows:
|
|
205
|
+
extra = "\n" + "\n".join(
|
|
206
|
+
f"{item.get('created_at')} {item.get('price_code')} {item.get('outcome')}"
|
|
207
|
+
for item in rows
|
|
208
|
+
)
|
|
209
|
+
except AutoverseError:
|
|
210
|
+
extra = ""
|
|
211
|
+
_emit(ctx, payload, human=usage_text(payload) + extra)
|
|
212
|
+
except AutoverseError as exc:
|
|
213
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
214
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@app.command()
|
|
218
|
+
def api(
|
|
219
|
+
ctx: typer.Context,
|
|
220
|
+
path: str,
|
|
221
|
+
method: Annotated[str, typer.Option("-X")] = "GET",
|
|
222
|
+
field: Annotated[list[str] | None, typer.Option("-f")] = None,
|
|
223
|
+
) -> None:
|
|
224
|
+
fields: dict[str, str] = {}
|
|
225
|
+
for item in field or []:
|
|
226
|
+
if "=" not in item:
|
|
227
|
+
emit_error(UsageError("use -f key=value"), as_json=ctx.obj.json_output)
|
|
228
|
+
raise typer.Exit(2)
|
|
229
|
+
key, value = item.split("=", 1)
|
|
230
|
+
fields[key] = value
|
|
231
|
+
client = _client(ctx)
|
|
232
|
+
try:
|
|
233
|
+
payload = client.raw(method, path, fields=fields or None)
|
|
234
|
+
_emit(ctx, payload if isinstance(payload, dict) else {"data": payload}, human=str(payload))
|
|
235
|
+
except AutoverseError as exc:
|
|
236
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
237
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@mcp_app.command("serve")
|
|
241
|
+
def mcp_serve(
|
|
242
|
+
host: Annotated[str, typer.Option("--host")] = "127.0.0.1",
|
|
243
|
+
port: Annotated[int, typer.Option("--port")] = 8091,
|
|
244
|
+
api_base: Annotated[str | None, typer.Option("--api-base")] = None,
|
|
245
|
+
toolset: Annotated[str, typer.Option("--toolset")] = "default",
|
|
246
|
+
) -> None:
|
|
247
|
+
from autoverse.hosted_mcp import serve
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
serve(host=host, port=port, api_base=api_base, toolset=toolset)
|
|
251
|
+
except UsageError as exc:
|
|
252
|
+
emit_error(exc, as_json=False)
|
|
253
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@mcp_app.command("install")
|
|
257
|
+
def mcp_install(
|
|
258
|
+
ctx: typer.Context,
|
|
259
|
+
host: str,
|
|
260
|
+
directory: Annotated[Path | None, typer.Option("--dir")] = None,
|
|
261
|
+
) -> None:
|
|
262
|
+
from autoverse.mcp_server import write_host_config
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
path = write_host_config(host, directory=directory)
|
|
266
|
+
_emit(ctx, {"path": str(path), "host": host}, human=f"Wrote {path}")
|
|
267
|
+
except AutoverseError as exc:
|
|
268
|
+
emit_error(exc, as_json=ctx.obj.json_output)
|
|
269
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def main() -> None:
|
|
273
|
+
app()
|