# Klynx

[![PyPI version](https://img.shields.io/pypi/v/klynx.svg)](https://pypi.org/project/klynx/)
[![Python versions](https://img.shields.io/pypi/pyversions/klynx.svg)](https://pypi.org/project/klynx/)
[![License](https://img.shields.io/github/license/qzx997/klynx-kit.svg)](https://github.com/qzx997/klynx-kit/blob/main/LICENSE)

> [!WARNING] 
> **当前为测试版本 (Current version is in Beta testing)**
> Klynx 仍在活跃开发中，部分高级功能及 API 不够完善或可能在未来版本发生变动，请谨慎用于生产环境。

**Klynx** is an advanced, highly customizable autonomous agent framework built on top of [LangGraph](https://langchain-ai.github.io/langgraph/) and [LiteLLM](https://github.com/BerriAI/litellm). It is designed to seamless integrate with top-tier LLM providers globally (OpenAI, Anthropic, Google, DeepSeek, Zhipu, Moonshot, etc.) while offering a robust tool-calling infrastructure and built-in interactive terminal interfaces.

## 🚀 Features
- **Universal LLM Routing:** Write code once, use any model. Powered by `litellm`.
- **Advanced Agent Architectures:** Built with `langgraph` core state machines for recursive reasoning, memory, error reflection, and persistent context loops.
- **Robust Tools Environment:** Ships with native tools (e.g. Browser automation with Playwright, OS terminal integration, local filesystem querying).
- **Interactive TUI:** Comes with a built-in rich Text User Interface (`libs/klynx/klynx/tui_app.py`) for streaming console interactions.
- **Proxy-Resistant:** `LiteLLM` adapters are configured internally to gracefully map local network environments without throwing blocking SSL errors.

## 📦 Installation

Install Klynx directly from PyPI:

```bash
pip install klynx
```

To update to the latest version, run:
```bash
pip install --upgrade klynx
```

*(Note: If using browser features, remember to run `playwright install chromium` once).*

## ⚡ Quick Start

With Klynx, creating a multi-modal agent and interacting with it requires just a few lines of code.

### 1. Initialize your Model

Klynx expects explicit passing of your API keys or auto-loading from `.env` files. We fully support major models.

```python
import os
from klynx import setup_model, set_tavily_api

# (Optional) Enable web search capabilities globally
set_tavily_api(os.getenv("TAVILY_API_KEY", ""))

# Setup your agent's brain
api_key = os.getenv("DEEPSEEK_API_KEY")
model = setup_model("deepseek", "deepseek-reasoner", api_key)
```

### 2. Create the Agent

Attach your model to a `KlynxAgent` state machine, optionally granting it all system tools.

```python
from klynx import create_agent

# Initialize agent in current directory
agent = create_agent(
    working_dir=os.getcwd(),
    model=model,
    max_iterations=15 
)

# Grant agent access to all default tools (Browser, Files, Terminal, Web Search)
agent.add_tools("all")
```

### 2.1 Custom Agent Inheritance & Prompt Injection

Klynx supports user-defined agent classes.

For the **agent node**, prompt extension is append-only with exactly two methods:
1. Constructor injection: `append_system_prompt=...`
2. Per-call injection: `invoke(..., system_prompt_append=...)`

The core system prompt remains locked.

For the **ask node**, there is **no prompt injection**. It is a direct answer path.

```python
from langgraph.graph import END, StateGraph
from klynx.agent.agents import KlynxGeneralAgent
from klynx.agent.state import AgentState

class MyDirectAgent(KlynxGeneralAgent):
    # Minimal custom orchestration: ask -> END
    def _build_graph(self) -> StateGraph:
        w = StateGraph(AgentState)
        w.add_node("ask", self._direct_answer_node)
        w.set_entry_point("ask")
        w.add_edge("ask", END)
        return w
```

```python
from klynx import create_agent

agent = create_agent(
    working_dir=".",
    model=model,
    append_system_prompt="Project-specific constraints here.",  # method 1
)

for event in agent.invoke(
    "Do the task with tools",
    system_prompt_append="Session-specific constraints here.",  # method 2
):
    ...
```

Reference example: `tutorials/examples/direct_answer_agent_example.py`.

### 2.2 Skills (Built-in + External)

Klynx supports dynamic skill packages. A skill is a folder containing `SKILL.md`.

Built-in skills (enabled by default):
- `skill-creator`
- `skill-installer`

Disable/enable built-ins:
```python
agent.basic_skills("off")  # disable built-in skills
agent.basic_skills("on")   # enable built-in skills again
```

Install external skills into current agent process:
```python
# signature requested by Klynx runtime:
# add_skills("skill_name", "path", "description")
agent.add_skills("my-skill", r"E:\skills\my-skill", "optional description")

# description can be empty
agent.add_skills("my-skill", r"E:\skills\my-skill", "")
```

You can still use path/name-only style:
```python
agent.add_skills(r"E:\skills\my-skill")
agent.add_skills("my-skill")  # resolved from skills_root
```

Global skills root:
- Default: `~/.klynx/skills`
- Override home via env var: `KLYNX_HOME` (effective root becomes `$KLYNX_HOME/skills`)

Persistent auto-loading from memory workspace:
- When `memory_dir` is set, Klynx auto-creates/uses `<memory_dir>/.klynx/skills/`.
- Any skill folder under this directory is auto-discovered and registered on init.

```python
agent = create_agent(
    working_dir=".",
    model=model,
    memory_dir=".",  # enables .klynx/skills auto-discovery
)
```

Runtime usage in the agent loop:
- The model should call `load_skill(name)` before following a skill workflow.
- In TUI, you can reference a skill directly with `$skill-name` in user input.
  Example: `$skill-creator 帮我生成一个用于发布版本说明的技能`

### 3. Run a Task

There are two primary ways to run instructions: a direct API call or an interactive terminal stream.

#### Option A: Single Invocation (API Level)
Useful when integrating Klynx into your own backend or processing pipeline. It returns the final agent state and summary.

```python
task = "Search the web for the latest Python version changes and write a summary to a file named 'python_updates.md'."
# `invoke` processes the execution loop and streaming yields events
for event in agent.invoke(task):
    event_type = event.get("type", "")
    content = event.get("content", "")
    
    if event_type == "done":
        # Final result state
        if event.get("task_completed"):
            print("Success! Agent Summary:", event.get("summary_content"))
    else:
        print(f"{content}") 
```

#### Option B: Real-time Terminal Streaming
If you want to watch the agent think, execute tools, and log out its process in a beautiful format directly in the console, use `run_terminal_agent_stream`.

```python
from klynx import run_terminal_agent_stream

# Interactive terminal chat loop
while True:
    task = input("\n[User]> ")
    if task.strip().lower() in ["exit", "q", "quit"]:
        break
    
    # This will stream reasoning tags, tool calls, and results to stdout
    result = run_terminal_agent_stream(agent, task, thread_id="my_cli_session")
```

### 4. Built-in TUI App

Klynx includes a powerful Text User Interface for managing and interacting with your agent directly in your terminal without writing CLI scripts.

Launch the interactive app directly from your terminal:
```bash
python -m klynx.tui_app
```

## 📜 License
This software is licensed under the [MIT License](LICENSE).
