Metadata-Version: 2.4
Name: pyminicode
Version: 0.1.0
Summary: Terminal-native AI coding assistant (Python)
Author: Wangzheng Yuan
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: pydantic>=2.6
Requires-Dist: httpx>=0.27
Requires-Dist: anyio>=4.3
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"

# minicode

Terminal-native AI coding assistant, implemented in Python.

> **当前状态**：已完成**工具层**（[v0]）和**模型层**（[v1]）。能加载 LLM、切换 provider、流式测试连通性。
> 下一版（[v2]）会接入 ReAct 循环（输入 → LLM → 工具调用 → 回灌 → 循环）。

## Quick start

```bash
# 1. install
pip install -e .

# 2. 跑起来（首次会自动建 .minicode/）
minicode

# 一次性命令
minicode --version
minicode --paths
minicode --print-tools       # 列工具
minicode --print-models      # 列 model provider
```

## Layout

```
minicode/
├── pyproject.toml
├── README.md
├── .minicode/                       # per-project config dir
│   ├── skills/                      # skill files
│   │   ├── code-review/SKILL.md
│   │   ├── refactor/SKILL.md
│   │   └── test-gen/SKILL.md
│   ├── mcp.json                     # MCP server config (stdio / http)
│   └── config.yaml                  # LLM provider 配置
├── minicode/                        # source
│   ├── __init__.py
│   ├── __main__.py
│   ├── paths.py                     # .minicode 路径解析
│   ├── config.py                    # config.yaml 加载
│   ├── tool/                        # 工具层
│   │   ├── base.py                  # Tool 抽象基类 + ToolContext + ToolResult
│   │   ├── registry.py              # ToolRegistry
│   │   ├── skill.py                 # SkillLoader
│   │   ├── mcp.py                   # McpClient + McpToolAdapter
│   │   └── builtin/                 # 7 个内置工具
│   ├── model/                       # 模型层
│   │   ├── base.py                  # Model 抽象基类 + ModelResponse/Event
│   │   ├── message.py               # Message / Part / ToolSchema
│   │   ├── openai_compat.py         # OpenAI Chat Completions (覆盖 ollama/vllm/DeepSeek/OpenAI)
│   │   ├── anthropic.py             # Anthropic Messages API
│   │   ├── demo.py                  # 假 provider（echo），无需 apikey
│   │   └── registry.py              # ModelRegistry
│   └── cli/
│       └── app.py                   # REPL + /tools /skills /mcp /models /model /call
└── tests/                           # 单测 + 端到端冒烟
    ├── test_base.py
    ├── test_skill_loader.py
    ├── test_registry.py             # 工具层端到端
    ├── test_config.py               # config.yaml 加载
    ├── test_model_registry.py       # ModelRegistry
    ├── test_openai_compat.py        # 用 httpx MockTransport 模拟 SSE
    ├── test_anthropic.py
    ├── test_demo.py
    ├── demo_mcp_server.py
    └── test_e2e_mcp.py
```

## config.yaml 格式

`.minicode/config.yaml`：

```yaml
default: openai        # 可选：默认 provider id

providers:
  openai:
    type: openai-compat            # openai-compat | anthropic | demo
    api_key: ${OPENAI_API_KEY}     # ${ENV_VAR} 自动展开
    base_url: https://api.openai.com/v1
    model: gpt-4o
    extra:                         # 透传给 provider
      timeout: 60
      temperature: 0.7
      max_tokens: 4096

  anthropic:
    type: anthropic
    api_key: ${ANTHROPIC_API_KEY}
    base_url: https://api.anthropic.com
    model: claude-3-5-sonnet-20241022
    extra:
      max_tokens: 4096

  demo:                            # 假 provider（开箱即用）
    type: demo
    base_url: "(in-process)"
    model: demo-echo
```

`type` 字段：
- `openai-compat` — 任何实现 OpenAI Chat Completions 的服务（OpenAI/DeepSeek/Moonshot/ollama/vllm/...）
- `anthropic` — Anthropic Messages API
- `demo` — 进程内回显，无需网络

`extra` 字段透传给 provider 实现（OpenAI：`temperature`/`top_p`/`presence_penalty`；Anthropic：`max_tokens` 必填/`temperature`/`top_k`）。

## CLI 命令

| 命令 | 作用 |
| --- | --- |
| `/tools` | 列出所有工具（builtin + skill + mcp） |
| `/skills` | 列出 skill |
| `/mcp` | 列出 MCP server + 工具 |
| `/models` | 列出 `config.yaml` 中所有 LLM provider，标 current |
| `/model` | 显示当前 model 详情 |
| `/model <id>` | 切换到指定 provider |
| `/model test` | 流式发 "ping" 测试当前 model 的连通性 |
| `/paths` | 路径解析 |
| `/call <id> [json]` | 手动调用一个工具 |
| `/reload` | 重新 build registry |
| `/help` | 帮助 |
| `/exit` | 退出 |

## 工具协议

所有工具实现同一接口（[minicode/tool/base.py](minicode/tool/base.py)）：

```python
class Tool(ABC):
    kind: ToolKind = ToolKind.BUILTIN

    @property
    def id(self) -> str: ...
    @property
    def description(self) -> str: ...
    @property
    def parameters(self) -> type[BaseModel]: ...
    @abstractmethod
    async def execute(self, args: BaseModel, ctx: ToolContext) -> ToolResult: ...
```

- `parameters` 是 Pydantic BaseModel，自动生成 JSON Schema 给 LLM
- `ToolKind`: BUILTIN / SKILL / MCP / CUSTOM

## Model 协议

所有 Model 实现同一接口（[minicode/model/base.py](minicode/model/base.py)）：

```python
class Model(ABC):
    @property
    def info(self) -> ModelInfo: ...        # id / type / base_url / model
    @property
    def api_key(self) -> str: ...
    @property
    def extra(self) -> dict: ...

    @abstractmethod
    async def stream(
        self,
        messages: List[Message],
        tools: Optional[List[ToolSchema]] = None,
        system: Optional[str] = None,
    ) -> AsyncIterator[ModelEvent]: ...    # 流式产出 text_delta / tool_call_delta / usage / finish / error

    async def complete(...) -> ModelResponse:   # 基类默认实现：把 stream 攒起来
        ...
```

- 协议不感知 Tool/MCP，只接收 `ToolSchema`（与 `ToolDef` 解耦）
- HTTP 错误用 `yield ModelEvent(type="error", error=...)` 表达，不抛

## 架构

```
                .minicode/skills/                .minicode/mcp.json            .minicode/config.yaml
                       │                                │                              │
                  scan ▼                          load ▼                            load ▼
              ┌─────────────┐              ┌─────────────────┐              ┌─────────────────┐
              │SkillLoader  │              │  McpClient      │              │ ModelRegistry   │
              │             │              │  (stdio+http)   │              │  (openai/anthropic/demo) │
              └──────┬──────┘              └────────┬────────┘              └────────┬────────┘
                     │                             │                                │
                     │ inject                      │ tools/list                     │ build
                     ▼                             ▼                                ▼
              ┌─────────────┐              ┌─────────────────┐              ┌─────────────────┐
              │ SkillTool   │              │ McpToolAdapter  │              │ OpenAICompat    │
              │ (builtin)   │              │ (kind=MCP)      │              │ Anthropic       │
              └─────────────┘              └─────────────────┘              │ Demo            │
                                                                             └─────────────────┘
                     │                             │                                │
                     │        ┌────────────────────┴────────────────────────┐       │
                     └───────▶│           ToolRegistry                     │       │
                              │         id → ToolDef (all kinds)            │       │
                              └────────────────────┬────────────────────────┘       │
                                                   │                                │
                                                   │ tools                          │ model
                                                   ▼                                ▼
                                            ┌──────────────────────────┐  ┌────────────────┐
                                            │    ReAct 循环（v2 待做）  │  │  /model test   │
                                            └──────────────────────────┘  └────────────────┘
```

## 与原 mimo code 的差异

| 维度 | mimo code (TS) | minicode (Py) |
| --- | --- | --- |
| 语言 | TypeScript + Effect | Python 3.10+ + Pydantic |
| 异步模型 | Effect.gen | async/await |
| 工具 schema | zod | Pydantic BaseModel |
| MCP SDK | 官方 @modelcontextprotocol/sdk | 自研最小 JSON-RPC over stdio/http |
| 内置工具数 | 21 | 7（保留核心） |
| Skill 协议 | 完整（嵌套 + 外部目录 + 远程拉取） | 简化（`<name>/SKILL.md` + frontmatter） |
| Model 协议 | Vercel AI SDK | 自研（`stream()` + `complete()`） |
| provider | 11 种 | 2 种（openai-compat + anthropic）+ 1 demo |
| LLM 循环 | 接好 | **未接**（v2） |

## 下一步

1. **ReAct 循环**（v2）：把 `ToolRegistry` 的工具转成 `ToolSchema` 给 LLM，处理 `tool_call_delta` → 调 `ToolRegistry.execute` → 灌回 `Message.tool_result` → 下一轮
2. **权限系统**：完善 `ctx.ask` 的实际行为（allow/deny/ask）
3. **truncate 服务**：超过阈值的输出写入临时文件，原始位置放占位
4. **plugin / hooks**：参照 mimo code 的 plugin system
5. **Google provider**（v3）：Gemini generateContent

## 测试

```bash
pip install pytest pytest-asyncio
python -m pytest tests/ -v
```

应该看到 39 个测试全过。

## 已知问题

- Windows 终端默认 GBK 编码会乱码。在自己的终端跑前请先 `chcp 65001` 或换 Windows Terminal + Cascadia Code。
- v2 才接 LLM 循环。/model test 用来单点测试连通性。
