diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b6b6e6..a47fe72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,11 +67,11 @@ jobs: - name: Set up environment uses: ./.github/actions/setup-python-env - - name: Build kai - run: uv build --package kai --no-sources --out-dir dist/kai + - name: Build kcastle-ai + run: uv build --package kcastle-ai --no-sources --out-dir dist/kcastle-ai - - name: Build kagent - run: uv build --package kagent --no-sources --out-dir dist/kagent + - name: Build kcastle-agent + run: uv build --package kcastle-agent --no-sources --out-dir dist/kcastle-agent - name: Build kcastle run: uv build --package kcastle --no-sources --out-dir dist/kcastle diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ffee186 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +name: Release + +on: + release: + types: [published] + +concurrency: + group: release + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v6 + + - name: Set up environment + uses: ./.github/actions/setup-python-env + + - name: Build kcastle-ai + run: uv build --package kcastle-ai --no-sources --out-dir dist/kcastle-ai + + - name: Build kcastle-agent + run: uv build --package kcastle-agent --no-sources --out-dir dist/kcastle-agent + + - name: Build kcastle + run: uv build --package kcastle --no-sources --out-dir dist/kcastle + + - name: Upload kcastle-ai artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-kcastle-ai + path: dist/kcastle-ai/ + + - name: Upload kcastle-agent artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-kcastle-agent + path: dist/kcastle-agent/ + + - name: Upload kcastle artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-kcastle + path: dist/kcastle/ + + publish-kcastle-ai: + runs-on: ubuntu-latest + needs: build + permissions: + id-token: write + environment: pypi + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist-kcastle-ai + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + skip-existing: true + + publish-kcastle-agent: + runs-on: ubuntu-latest + needs: publish-kcastle-ai + permissions: + id-token: write + environment: pypi + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist-kcastle-agent + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + skip-existing: true + + publish-kcastle: + runs-on: ubuntu-latest + needs: publish-kcastle-agent + permissions: + id-token: write + environment: pypi + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist-kcastle + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + skip-existing: true diff --git a/README.md b/README.md index a5f9523..d60b06d 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,30 @@ -# K +# K in Castle -Tools for building AI agents in Python. +K is an agent living in a castle. -## Packages +> [!WARNING] +> K has not formally entered Castle yet. The project is still in a rapid +> development stage, and breaking changes may be introduced at any time. -| Package | Description | -|---------|-------------| -| **[kai](packages/kai)** | Unified multi-provider LLM API | -| **[kagent](packages/kagent)** | Agent runtime with tool calling and state management | -| **[kcastle](packages/kcastle)** | Agent application with multi-endpoint support | +## Quick Start -## Development +Install as a uv tool: ```bash -uv sync # Install all dependencies -uv run pytest # Run tests -uv run ruff check # Lint -uv run pyright # Type check +uv tool install kcastle ``` -## License +Then run: + +```bash +kcastle +``` + + +## Packages -MIT +| Package | PyPI | Description | +|---------|------|-------------| +| **[kai](packages/kai)** | [![PyPI](https://img.shields.io/pypi/v/kcastle-ai?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-ai/) | Unified multi-provider LLM API | +| **[kagent](packages/kagent)** | [![PyPI](https://img.shields.io/pypi/v/kcastle-agent?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-agent/) | Agent runtime with tool calling and state management | +| **[kcastle](packages/kcastle)** | [![PyPI](https://img.shields.io/pypi/v/kcastle?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle/) | Agent application with multi-endpoint support | diff --git a/packages/kagent/README.md b/packages/kagent/README.md index 46ac7bc..740ef84 100644 --- a/packages/kagent/README.md +++ b/packages/kagent/README.md @@ -1,5 +1,7 @@ # kagent +[![PyPI](https://img.shields.io/pypi/v/kcastle-agent?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-agent/) + Core agent runtime for the K agent framework — a context-first, three-level agent SDK built on `kai`. ## Key Principles diff --git a/packages/kagent/pyproject.toml b/packages/kagent/pyproject.toml index bdf3c5d..ae94532 100644 --- a/packages/kagent/pyproject.toml +++ b/packages/kagent/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "kagent" -version = "0.1.0" +name = "kcastle-agent" +version = "0.0.1a0" description = "Core agent framework for the k agent framework" readme = "README.md" authors = [ @@ -8,7 +8,7 @@ authors = [ ] requires-python = ">=3.12" dependencies = [ - "kai", + "kcastle-ai", ] [project.optional-dependencies] @@ -35,7 +35,7 @@ module-name = ["kagent"] source-exclude = ["tests/**/*"] [tool.uv.sources] -kai = { workspace = true } +"kcastle-ai" = { workspace = true } [tool.ruff] line-length = 100 diff --git a/packages/kai/README.md b/packages/kai/README.md index 53c26ef..77b57ba 100644 --- a/packages/kai/README.md +++ b/packages/kai/README.md @@ -1,5 +1,7 @@ # kai +[![PyPI](https://img.shields.io/pypi/v/kcastle-ai?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-ai/) + Unified multi-provider LLM API for the k agent framework. kai provides a simple, provider-agnostic interface for streaming LLM completions with tool calling support. It has two entry points — `stream()` for real-time events and `complete()` for one-shot responses. @@ -7,7 +9,7 @@ kai provides a simple, provider-agnostic interface for streaming LLM completions ## Installation ```bash -uv add kai +uv add kcastle-ai ``` ## Quick Start diff --git a/packages/kai/pyproject.toml b/packages/kai/pyproject.toml index 2139038..9e8c8a3 100644 --- a/packages/kai/pyproject.toml +++ b/packages/kai/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "kai" -version = "0.1.0" +name = "kcastle-ai" +version = "0.0.1a0" description = "AI provider abstractions for the k agent framework" readme = "README.md" authors = [ diff --git a/packages/kcastle/README.md b/packages/kcastle/README.md index 6d116e9..9be1a08 100644 --- a/packages/kcastle/README.md +++ b/packages/kcastle/README.md @@ -1,5 +1,7 @@ # kcastle +[![PyPI](https://img.shields.io/pypi/v/kcastle?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle/) + Agent application with multi-endpoint support for the K agent framework. kcastle is a general-purpose agent application built on `kagent`. It provides a unified diff --git a/packages/kcastle/docs/arch.md b/packages/kcastle/docs/arch.md index ff867ed..f79112f 100644 --- a/packages/kcastle/docs/arch.md +++ b/packages/kcastle/docs/arch.md @@ -121,7 +121,7 @@ The ID format depends on who creates the session: | Creator | Session ID format | Example | |---------|------------------|---------| | CLI (default) | UUID hex (short) | `a1b2c3d4` | -| CLI (`k -S `) | user-specified | `my-project` | +| CLI (`kcastle -S `) | user-specified | `my-project` | | Telegram private | `tg-u{user_id}` | `tg-u123456` | | Telegram group | `tg-g{chat_id}` | `tg-g-987654` | @@ -130,13 +130,13 @@ directly — no lookup table needed. Telegram private chats and groups each get their own session automatically. **Cross-channel resume**: any channel can resume any session by its ID. -`k -S tg-u123456` opens a Telegram user's session from the CLI. This works +`kcastle -S tg-u123456` opens a Telegram user's session from the CLI. This works naturally because the session directory is the same regardless of which channel accesses it. **Session resolution heuristics** (no persisted mapping): -- **CLI** `k -C`: resume the most recently active session (by `last_active_at`). -- **CLI** `k -S `: explicit session selection. +- **CLI** `kcastle -C`: resume the most recently active session (by `last_active_at`). +- **CLI** `kcastle -S `: explicit session selection. - **Telegram**: deterministic `tg-u{user_id}` / `tg-g{chat_id}` — auto-created on first message, resumed on subsequent messages. @@ -146,7 +146,7 @@ channel accesses it. kcastle/ ├── __init__.py # Public API re-exports ├── castle.py # Castle — top-level lifecycle orchestrator -├── cli.py # `k` command entry point +├── cli.py # `kcastle` command entry point ├── config.py # CastleConfig, YAML + env var loading, built-in registry ├── daemon.py # Daemon process management (start/stop/status/restart) ├── setup.py # First-run env var detection + config generation @@ -238,7 +238,7 @@ At load time, `load_config` merges built-in providers with user config: ### First-run setup -On first launch, `k` detects that `config.yaml` is missing and runs a +On first launch, `kcastle` detects that `config.yaml` is missing and runs a non-interactive setup: 1. Scan for known API key env vars (`DEEPSEEK_API_KEY`, `MINIMAX_API_KEY`). @@ -498,15 +498,15 @@ Castle Interactive terminal using `prompt_toolkit`. ``` -$ k # New session (auto-generated ID) -$ k -C # Continue most recently active session -$ k -S # Resume specific session by ID -$ k -d # Daemon mode (foreground, no interactive CLI) +$ kcastle # New session (auto-generated ID) +$ kcastle -C # Continue most recently active session +$ kcastle -S # Resume specific session by ID +$ kcastle -d # Daemon mode (foreground, no interactive CLI) -$ k start # Start daemon in background -$ k stop # Stop the background daemon -$ k status # Show daemon status -$ k restart # Restart the daemon +$ kcastle start # Start daemon in background +$ kcastle stop # Stop the background daemon +$ kcastle status # Show daemon status +$ kcastle restart # Restart the daemon k> hello # User input → session.run("hello") # Stream AgentEvent → render to terminal diff --git a/packages/kcastle/pyproject.toml b/packages/kcastle/pyproject.toml index 383447a..efd5ef9 100644 --- a/packages/kcastle/pyproject.toml +++ b/packages/kcastle/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kcastle" -version = "0.1.0" +version = "0.0.1a0" description = "Agent application with multi-endpoint support for the k agent framework" readme = "README.md" authors = [ @@ -8,14 +8,14 @@ authors = [ ] requires-python = ">=3.12" dependencies = [ - "kagent", + "kcastle-agent", "pyyaml>=6.0", "python-telegram-bot>=21.0", "telegramify-markdown>=0.5.4", ] [project.scripts] -k = "kcastle.cli:main" +kcastle = "kcastle.cli:main" [dependency-groups] dev = [ @@ -30,7 +30,7 @@ requires = ["uv_build>=0.9.18,<0.10.0"] build-backend = "uv_build" [tool.uv.sources] -kagent = { workspace = true } +"kcastle-agent" = { workspace = true } [tool.ruff] line-length = 100 diff --git a/packages/kcastle/src/kcastle/__init__.py b/packages/kcastle/src/kcastle/__init__.py index 236dd44..35a1ba3 100644 --- a/packages/kcastle/src/kcastle/__init__.py +++ b/packages/kcastle/src/kcastle/__init__.py @@ -12,9 +12,9 @@ Or from the command line:: - $ k # New session - $ k -C # Continue latest session - $ k -S # Resume specific session + $ kcastle # New session + $ kcastle -C # Continue latest session + $ kcastle -S # Resume specific session """ from kcastle.castle import Castle diff --git a/packages/kcastle/src/kcastle/channels/cli.py b/packages/kcastle/src/kcastle/channels/cli.py index 62eeb65..9cab7b2 100644 --- a/packages/kcastle/src/kcastle/channels/cli.py +++ b/packages/kcastle/src/kcastle/channels/cli.py @@ -2,9 +2,9 @@ Usage:: - $ k # New session (auto-generated ID) - $ k -C # Continue most recently active session - $ k -S # Resume specific session by ID + $ kcastle # New session (auto-generated ID) + $ kcastle -C # Continue most recently active session + $ kcastle -S # Resume specific session by ID """ from __future__ import annotations diff --git a/packages/kcastle/src/kcastle/cli/__init__.py b/packages/kcastle/src/kcastle/cli/__init__.py index 0889e82..8fb2e01 100644 --- a/packages/kcastle/src/kcastle/cli/__init__.py +++ b/packages/kcastle/src/kcastle/cli/__init__.py @@ -2,14 +2,14 @@ Usage:: - $ k # New session (auto-generated ID) - $ k -C # Continue most recently active session - $ k -S # Resume specific session by ID - $ k -d # Daemon mode (no interactive CLI, foreground) - $ k start # Start daemon in background - $ k stop # Stop the background daemon - $ k status # Show daemon status - $ k restart # Restart the daemon + $ kcastle # New session (auto-generated ID) + $ kcastle -C # Continue most recently active session + $ kcastle -S # Resume specific session by ID + $ kcastle -d # Daemon mode (no interactive CLI, foreground) + $ kcastle start # Start daemon in background + $ kcastle stop # Stop the background daemon + $ kcastle status # Show daemon status + $ kcastle restart # Restart the daemon """ from __future__ import annotations @@ -21,9 +21,9 @@ def main() -> None: - """Main entry point for the ``k`` command.""" + """Main entry point for the ``kcastle`` command.""" parser = argparse.ArgumentParser( - prog="k", + prog="kcastle", description="kcastle — AI agent with session management", ) parser.add_argument( diff --git a/packages/kcastle/src/kcastle/cli/daemon.py b/packages/kcastle/src/kcastle/cli/daemon.py index e8cc7a1..21d3e7d 100644 --- a/packages/kcastle/src/kcastle/cli/daemon.py +++ b/packages/kcastle/src/kcastle/cli/daemon.py @@ -1,7 +1,7 @@ """Daemon process management for kcastle. -Provides start/stop/status/restart operations for the background ``k`` -process. The daemon is a detached ``k -d`` subprocess whose PID is +Provides start/stop/status/restart operations for the background ``kcastle`` +process. The daemon is a detached ``kcastle -d`` subprocess whose PID is tracked in ``~/.kcastle/k.pid`` and whose output is logged to ``~/.kcastle/k.log``. """ diff --git a/packages/kcastle/src/kcastle/cli/setup.py b/packages/kcastle/src/kcastle/cli/setup.py index 1cdccc8..44073f6 100644 --- a/packages/kcastle/src/kcastle/cli/setup.py +++ b/packages/kcastle/src/kcastle/cli/setup.py @@ -72,7 +72,7 @@ def _print_missing_keys_hint() -> None: print(f"\n {_YELLOW}No API keys found.{_RESET}") print(" Set one of the above environment variables, e.g.:\n") print(f' {_DIM}export DEEPSEEK_API_KEY="sk-..."{_RESET}\n') - print(" Then run `k` again.\n") + print(" Then run `kcastle` again.\n") def _confirm_write(path: Path) -> bool: @@ -122,5 +122,5 @@ def run_setup(home: Path | None = None) -> Path: _write_minimal_config(path, chosen) - print(f"\n {_GREEN}✓{_RESET} Done. Run {_BOLD}k{_RESET} to start.\n") + print(f"\n {_GREEN}✓{_RESET} Done. Run {_BOLD}kcastle{_RESET} to start.\n") return path diff --git a/pyproject.toml b/pyproject.toml index c0f1cb6..dfd99c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,14 @@ [project] name = "k" -version = "0.1.0" +version = "0.0.1a0" description = "The k agent framework" readme = "README.md" requires-python = ">=3.12" [dependency-groups] dev = [ - "kai", - "kagent", + "kcastle-ai", + "kcastle-agent", "kcastle", "pyright>=1.1.407", "pytest>=9.0.2", @@ -20,8 +20,8 @@ dev = [ members = ["packages/*"] [tool.uv.sources] -kai = { workspace = true } -kagent = { workspace = true } +"kcastle-ai" = { workspace = true } +"kcastle-agent" = { workspace = true } kcastle = { workspace = true } [tool.ruff] diff --git a/uv.lock b/uv.lock index 7a597f1..d590ecd 100644 --- a/uv.lock +++ b/uv.lock @@ -10,9 +10,9 @@ resolution-markers = [ [manifest] members = [ "k", - "kagent", - "kai", "kcastle", + "kcastle-agent", + "kcastle-ai", ] [[package]] @@ -282,14 +282,14 @@ wheels = [ [[package]] name = "k" -version = "0.1.0" +version = "0.0.1a0" source = { virtual = "." } [package.dev-dependencies] dev = [ - { name = "kagent" }, - { name = "kai" }, { name = "kcastle" }, + { name = "kcastle-agent" }, + { name = "kcastle-ai" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -300,9 +300,9 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "kagent", editable = "packages/kagent" }, - { name = "kai", editable = "packages/kai" }, { name = "kcastle", editable = "packages/kcastle" }, + { name = "kcastle-agent", editable = "packages/kagent" }, + { name = "kcastle-ai", editable = "packages/kai" }, { name = "pyright", specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, @@ -310,18 +310,14 @@ dev = [ ] [[package]] -name = "kagent" -version = "0.1.0" -source = { editable = "packages/kagent" } +name = "kcastle" +version = "0.0.1a0" +source = { editable = "packages/kcastle" } dependencies = [ - { name = "kai" }, -] - -[package.optional-dependencies] -otel = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-sdk" }, + { name = "kcastle-agent" }, + { name = "python-telegram-bot" }, + { name = "pyyaml" }, + { name = "telegramify-markdown" }, ] [package.dev-dependencies] @@ -334,12 +330,11 @@ dev = [ [package.metadata] requires-dist = [ - { name = "kai", editable = "packages/kai" }, - { name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.39.1" }, - { name = "opentelemetry-exporter-otlp-proto-grpc", marker = "extra == 'otel'", specifier = ">=1.39.1" }, - { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.39.1" }, + { name = "kcastle-agent", editable = "packages/kagent" }, + { name = "python-telegram-bot", specifier = ">=21.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "telegramify-markdown", specifier = ">=0.5.4" }, ] -provides-extras = ["otel"] [package.metadata.requires-dev] dev = [ @@ -350,13 +345,18 @@ dev = [ ] [[package]] -name = "kai" -version = "0.1.0" -source = { editable = "packages/kai" } +name = "kcastle-agent" +version = "0.0.1a0" +source = { editable = "packages/kagent" } dependencies = [ - { name = "anthropic" }, - { name = "openai" }, - { name = "pydantic" }, + { name = "kcastle-ai" }, +] + +[package.optional-dependencies] +otel = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-sdk" }, ] [package.dev-dependencies] @@ -364,35 +364,34 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "rich" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ - { name = "anthropic", specifier = ">=0.55.0" }, - { name = "openai", specifier = ">=1.93.3" }, - { name = "pydantic", specifier = ">=2.11.7" }, + { name = "kcastle-ai", editable = "packages/kai" }, + { name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.39.1" }, + { name = "opentelemetry-exporter-otlp-proto-grpc", marker = "extra == 'otel'", specifier = ">=1.39.1" }, + { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.39.1" }, ] +provides-extras = ["otel"] [package.metadata.requires-dev] dev = [ { name = "pyright", specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "rich", specifier = ">=14.0.0" }, { name = "ruff", specifier = ">=0.14.10" }, ] [[package]] -name = "kcastle" -version = "0.1.0" -source = { editable = "packages/kcastle" } +name = "kcastle-ai" +version = "0.0.1a0" +source = { editable = "packages/kai" } dependencies = [ - { name = "kagent" }, - { name = "python-telegram-bot" }, - { name = "pyyaml" }, - { name = "telegramify-markdown" }, + { name = "anthropic" }, + { name = "openai" }, + { name = "pydantic" }, ] [package.dev-dependencies] @@ -400,15 +399,15 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "rich" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ - { name = "kagent", editable = "packages/kagent" }, - { name = "python-telegram-bot", specifier = ">=21.0" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "telegramify-markdown", specifier = ">=0.5.4" }, + { name = "anthropic", specifier = ">=0.55.0" }, + { name = "openai", specifier = ">=1.93.3" }, + { name = "pydantic", specifier = ">=2.11.7" }, ] [package.metadata.requires-dev] @@ -416,6 +415,7 @@ dev = [ { name = "pyright", specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "rich", specifier = ">=14.0.0" }, { name = "ruff", specifier = ">=0.14.10" }, ]