core/cli: click app, chat/providers commands, entry points

Expose plyngent console script and python -m plyngent.
This commit is contained in:
2026-07-14 18:29:46 +08:00
parent 6be0ef5ac9
commit e1f71e7828
5 changed files with 151 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
from . import agent as agent
from . import cli as cli
from . import config as config
from . import memory as memory
from . import runtime as runtime
+4
View File
@@ -0,0 +1,4 @@
from plyngent.cli.app import main
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
from .app import main as main
+4
View File
@@ -0,0 +1,4 @@
from plyngent.cli.app import main
if __name__ == "__main__":
main()
+141
View File
@@ -0,0 +1,141 @@
from __future__ import annotations
import asyncio
from pathlib import Path
from typing import TYPE_CHECKING
import click
import msgspec
from plyngent import config as config_mod
from plyngent.cli.repl import run_repl
from plyngent.cli.selection import select_model, select_provider
from plyngent.cli.state import ReplState
from plyngent.config.models import DatabaseConfig
from plyngent.memory import MemoryStore
from plyngent.runtime import ProviderNotSupportedError, create_client
from plyngent.tools import set_workspace_root
if TYPE_CHECKING:
from plyngent.config.store import ConfigStore
def _load_config(config_path: Path | None) -> ConfigStore:
return config_mod.load(config_path)
def _database_config(store: ConfigStore) -> DatabaseConfig:
return msgspec.convert(dict(store.database), DatabaseConfig)
async def _run_chat( # noqa: PLR0913
*,
config_path: Path | None,
provider_name: str | None,
model: str | None,
tools: bool,
workspace: Path,
session_id: int | None,
) -> None:
store = _load_config(config_path)
if store.bad_providers:
names = ", ".join(sorted(store.bad_providers.keys()))
click.secho(f"warning: ignored bad providers: {names}", fg="yellow")
try:
pname, provider = select_provider(store.providers, preferred=provider_name)
model_id = select_model(provider, preferred=model)
_ = create_client(provider) # fail early if unsupported
except ProviderNotSupportedError as exc:
raise click.ClickException(str(exc)) from exc
_ = set_workspace_root(workspace)
memory = await MemoryStore.open(_database_config(store))
try:
state = ReplState(
config=store,
memory=memory,
workspace=workspace,
provider_name=pname,
provider=provider,
model=model_id,
tools_enabled=tools,
)
if session_id is not None:
await state.resume_session(session_id)
else:
await state.new_session()
await run_repl(state)
finally:
await memory.close()
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.version_option(package_name="plyngent")
def main() -> None:
"""Plyngent — LLM chat and agent toolkit."""
@main.command("chat")
@click.option(
"--config",
"config_path",
type=click.Path(path_type=Path, dir_okay=False),
default=None,
help="Path to plyngent.toml (default: platform config dir).",
)
@click.option("--provider", "provider_name", default=None, help="Provider name from config.")
@click.option("--model", default=None, help="Model id.")
@click.option("--tools/--no-tools", default=True, show_default=True, help="Enable DEFAULT_TOOLS.")
@click.option(
"--workspace",
type=click.Path(path_type=Path, file_okay=False, exists=True),
default=None,
help="Workspace root for tools (default: cwd).",
)
@click.option("--session", "session_id", type=int, default=None, help="Resume session id.")
def chat_cmd( # noqa: PLR0913
config_path: Path | None,
provider_name: str | None,
model: str | None,
tools: bool, # noqa: FBT001
workspace: Path | None,
session_id: int | None,
) -> None:
"""Interactive chat REPL with optional tools and session memory."""
root = workspace if workspace is not None else Path.cwd()
asyncio.run(
_run_chat(
config_path=config_path,
provider_name=provider_name,
model=model,
tools=tools,
workspace=root,
session_id=session_id,
)
)
@main.command("providers")
@click.option(
"--config",
"config_path",
type=click.Path(path_type=Path, dir_okay=False),
default=None,
help="Path to plyngent.toml.",
)
def providers_cmd(config_path: Path | None) -> None:
"""List configured providers."""
store = _load_config(config_path)
if not store.providers:
click.echo("(no providers)")
for name, provider in sorted(store.providers.items()):
tag = type(provider).__struct_config__.tag
models = ", ".join(sorted(provider.models.keys())) or "(none listed)"
click.echo(f"{name}\tpreset={tag}\tmodels={models}")
if store.bad_providers:
click.secho(f"bad: {', '.join(sorted(store.bad_providers.keys()))}", fg="yellow")
if __name__ == "__main__":
main()