diff --git a/src/kscli/auth.py b/src/kscli/auth.py index 93a9d45..5f42e27 100644 --- a/src/kscli/auth.py +++ b/src/kscli/auth.py @@ -4,21 +4,18 @@ import os from pathlib import Path -from kscli.config import get_current_environment - _CREDENTIALS_DIR = Path( os.environ.get("KSCLI_CREDENTIALS_PATH", "/tmp/kscli") ) def _credentials_path() -> Path: - """Resolve per-environment credentials file path.""" - env = get_current_environment() - return _CREDENTIALS_DIR / f".credentials_{env}" + """Resolve credentials file path.""" + return _CREDENTIALS_DIR / ".credentials" def save_api_key(api_key: str) -> None: - """Store API key to per-environment credentials file with restricted permissions.""" + """Store API key to credentials file with restricted permissions.""" path = _credentials_path() path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps({"api_key": api_key})) @@ -26,16 +23,15 @@ def save_api_key(api_key: str) -> None: def load_credentials() -> dict[str, str]: - """Load credentials for the current environment.""" + """Load credentials.""" path = _credentials_path() if not path.exists(): - env = get_current_environment() raise SystemExit( - f"Not authenticated for '{env}' environment. Run: kscli login --api-key " + "Not authenticated. Run: kscli login --api-key " ) return json.loads(path.read_text()) def clear_credentials() -> None: - """Remove credentials file for the current environment.""" + """Remove credentials file.""" _credentials_path().unlink(missing_ok=True) diff --git a/src/kscli/commands/auth.py b/src/kscli/commands/auth.py index b841e7f..e8f9e00 100644 --- a/src/kscli/commands/auth.py +++ b/src/kscli/commands/auth.py @@ -1,11 +1,12 @@ """Authentication commands: login, logout, whoami.""" +import certifi import click import ksapi from kscli.auth import clear_credentials, save_api_key from kscli.client import get_api_client, handle_client_errors -from kscli.config import get_current_environment +from kscli.config import get_base_url, write_config from kscli.output import print_result @@ -17,19 +18,37 @@ hide_input=True, help="User-scoped API key (sk-user-...).", ) -def login(api_key: str) -> None: +@click.option( + "--url", + default=None, + help="API base URL. Defaults to the staging instance.", +) +@click.pass_context +def login(ctx: click.Context, api_key: str, url: str | None) -> None: """Authenticate with a user-scoped API key.""" + target = get_base_url(url or ctx.obj.get("base_url")) + verify_ssl = target.startswith("https") + + config = ksapi.Configuration(host=target) + config.verify_ssl = verify_ssl + if verify_ssl: + config.ssl_ca_cert = certifi.where() + client = ksapi.ApiClient(config) + client.default_headers["authorization"] = f"Bearer {api_key}" + + with handle_client_errors(): + ksapi.UsersApi(client).get_me() + save_api_key(api_key) - env = get_current_environment() - click.echo(f"Logged in successfully ({env}).") + write_config({"base_url": target, "verify_ssl": verify_ssl}) + click.echo(f"Logged in successfully ({target}).") @click.command("logout") def logout() -> None: - """Remove stored credentials for the current environment.""" - env = get_current_environment() + """Remove stored credentials.""" clear_credentials() - click.echo(f"Logged out ({env}).") + click.echo("Logged out.") @click.command("whoami") diff --git a/src/kscli/commands/settings.py b/src/kscli/commands/settings.py index aa1cd5a..a3e2d71 100644 --- a/src/kscli/commands/settings.py +++ b/src/kscli/commands/settings.py @@ -1,4 +1,4 @@ -"""Settings commands: environment, show.""" +"""Settings commands: show.""" import click @@ -7,72 +7,29 @@ get_config_path, get_default_format, get_tls_config, - load_config, - write_config, ) from kscli.output import print_result -_ENV_PRESETS: dict[str, dict[str, object]] = { - "local": { - "environment": "local", - "base_url": "http://localhost:18000", - "verify_ssl": False, - }, - "staging": { - "environment": "staging", - "base_url": "https://api-staging.knowledgestack.ai", - "verify_ssl": True, - }, - "prod": { - "environment": "prod", - "base_url": "https://api.knowledgestack.ai", - "verify_ssl": True, - }, -} - @click.group("settings") def settings(): """Manage CLI configuration.""" -@settings.command("environment") -@click.argument("env_name", type=click.Choice(["local", "staging", "prod"])) -@click.option( - "--url", - default=None, - help="Override default API base URL for the selected environment", -) -def environment(env_name: str, url: str | None) -> None: - """Set the environment (local, prod) and associated config.""" - preset = _ENV_PRESETS[env_name].copy() - if url: - preset["base_url"] = url - write_config(preset) - click.echo(f"Environment set to '{env_name}'.") - if "base_url" in preset: - click.echo(f" base_url = {preset['base_url']}") - click.echo(f" verify_ssl = {preset['verify_ssl']}") - - @settings.command("show") @click.pass_context def show(ctx: click.Context) -> None: - """Print current resolved configuration (env + config file + defaults).""" + """Print current resolved configuration.""" base_url = get_base_url(None) verify_ssl, ca_bundle = get_tls_config() format_ = get_default_format() path = get_config_path() - file_config = load_config() - environment_label = file_config.get("environment", "(not set)") - result = { "config_file": str(path), "base_url": base_url, "verify_ssl": verify_ssl, "ca_bundle": ca_bundle or "(default)", "format": format_, - "environment": environment_label, } print_result(ctx, result) diff --git a/src/kscli/config.py b/src/kscli/config.py index 67cab21..e046625 100644 --- a/src/kscli/config.py +++ b/src/kscli/config.py @@ -6,7 +6,7 @@ from typing import Any _DEFAULT_CONFIG_PATH = Path.home() / ".config" / "kscli" / "config.json" -_DEFAULT_BASE_URL = "http://localhost:8000" +_DEFAULT_BASE_URL = "https://api-staging.knowledgestack.ai" _DEFAULT_FORMAT = "table" @@ -26,11 +26,6 @@ def load_config() -> dict[str, Any]: return {} -def get_current_environment() -> str: - """Resolve current environment from config. Defaults to 'local'.""" - return load_config().get("environment", "local") - - def get_base_url(override: str | None = None) -> str: if override: return override diff --git a/tests/e2e/test_cli_settings.py b/tests/e2e/test_cli_settings.py index 10f481a..fd73ad0 100644 --- a/tests/e2e/test_cli_settings.py +++ b/tests/e2e/test_cli_settings.py @@ -18,36 +18,3 @@ def test_settings_show(self, cli_authenticated: dict[str, str]) -> None: assert "base_url" in data assert "format" in data - def test_settings_environment_local(self, cli_authenticated: dict[str, str]) -> None: - """Settings environment local sets the local preset.""" - result = run_kscli_ok( - ["settings", "environment", "local"], - env=cli_authenticated, - format_json=False, - ) - assert "local" in result.stdout - - def test_settings_environment_prod(self, cli_authenticated: dict[str, str]) -> None: - """Settings environment prod sets the prod preset.""" - result = run_kscli_ok( - ["settings", "environment", "prod"], - env=cli_authenticated, - format_json=False, - ) - assert "prod" in result.stdout - - def test_settings_environment_resets_to_local( - self, cli_authenticated: dict[str, str] - ) -> None: - """Settings environment local restores the local preset.""" - run_kscli_ok( - ["settings", "environment", "local"], - env=cli_authenticated, - format_json=False, - ) - result = run_kscli_ok( - ["settings", "environment", "local"], - env=cli_authenticated, - format_json=False, - ) - assert "localhost:18000" in result.stdout