kittycode 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {kittycode-0.1.1 → kittycode-0.1.2}/.gitignore +4 -1
  2. {kittycode-0.1.1 → kittycode-0.1.2}/PKG-INFO +46 -2
  3. {kittycode-0.1.1 → kittycode-0.1.2}/README.md +45 -1
  4. {kittycode-0.1.1 → kittycode-0.1.2}/README_CN.md +45 -1
  5. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/__init__.py +1 -1
  6. kittycode-0.1.2/kittycode/agent.py +166 -0
  7. kittycode-0.1.2/kittycode/cli.py +733 -0
  8. kittycode-0.1.2/kittycode/interrupts.py +5 -0
  9. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/llm.py +46 -12
  10. kittycode-0.1.2/kittycode/tools/__init__.py +33 -0
  11. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/agent.py +5 -5
  12. kittycode-0.1.2/kittycode/tools/bash.py +184 -0
  13. {kittycode-0.1.1 → kittycode-0.1.2}/pyproject.toml +1 -1
  14. kittycode-0.1.2/tests/test_bash_streaming.py +96 -0
  15. kittycode-0.1.2/tests/test_cli.py +136 -0
  16. {kittycode-0.1.1 → kittycode-0.1.2}/tests/test_core.py +102 -7
  17. kittycode-0.1.2/tests/test_interrupt_latency.py +159 -0
  18. {kittycode-0.1.1 → kittycode-0.1.2}/tests/test_llm.py +14 -1
  19. kittycode-0.1.1/kittycode/agent.py +0 -125
  20. kittycode-0.1.1/kittycode/cli.py +0 -367
  21. kittycode-0.1.1/kittycode/tools/__init__.py +0 -27
  22. kittycode-0.1.1/kittycode/tools/bash.py +0 -100
  23. {kittycode-0.1.1 → kittycode-0.1.2}/LICENSE +0 -0
  24. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/__main__.py +0 -0
  25. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/config.py +0 -0
  26. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/context.py +0 -0
  27. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/prompt.py +0 -0
  28. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/session.py +0 -0
  29. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/skills.py +0 -0
  30. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/base.py +0 -0
  31. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/edit.py +0 -0
  32. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/glob_tool.py +0 -0
  33. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/grep.py +0 -0
  34. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/read.py +0 -0
  35. {kittycode-0.1.1 → kittycode-0.1.2}/kittycode/tools/write.py +0 -0
  36. {kittycode-0.1.1 → kittycode-0.1.2}/tests/test_skills.py +0 -0
  37. {kittycode-0.1.1 → kittycode-0.1.2}/tests/test_tools.py +0 -0
@@ -1,4 +1,6 @@
1
1
  __pycache__/
2
+ tests/__pycache__/
3
+ kittycode/__pycache__/
2
4
  *.py[cod]
3
5
  *$py.class
4
6
  *.egg-info/
@@ -16,4 +18,5 @@ dist/
16
18
  .pytest_cache/
17
19
  findings.md
18
20
  progress.md
19
- task_plan.md
21
+ task_plan.md
22
+ tests/test_prompt.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kittycode
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Minimal AI coding agent with a simple tool loop and skill support.
5
5
  Project-URL: Homepage, https://github.com/yejiming/KittyCode
6
6
  Project-URL: Repository, https://github.com/yejiming/KittyCode
@@ -39,6 +39,8 @@ The project is intentionally small. It includes the core agent runtime, a compac
39
39
  - Built-in tools for shell commands, file reading, file writing, targeted editing, glob search, regex search, and sub-agents.
40
40
  - Startup skill discovery from `~/.kittycode/skills`, with skill metadata injected into the system prompt each round.
41
41
  - Interactive REPL and one-shot command mode.
42
+ - ANSI pixel-cat startup banner.
43
+ - `Esc` interrupt support for stopping the current agent run at the next safe checkpoint.
42
44
  - Context compression to keep long sessions manageable.
43
45
  - Session save and resume support.
44
46
 
@@ -142,6 +144,8 @@ Run the interactive terminal UI:
142
144
  kittycode
143
145
  ```
144
146
 
147
+ When the CLI is busy, press `Esc` to interrupt the current run. The stop is cooperative: KittyCode cancels at the next safe checkpoint between LLM streaming, tool dispatch, and loop rounds. A blocking external call may still need to return before the run fully stops.
148
+
145
149
  You can also use the module entry point:
146
150
 
147
151
  ```bash
@@ -184,6 +188,46 @@ Inside the REPL, KittyCode supports:
184
188
  The `/skills` command refreshes the local skill cache if the skill directory has changed and then prints the currently loaded skills.
185
189
  Slash commands also support prefix matching while typing, so entering `/` shows matching commands and skills through completion suggestions.
186
190
 
191
+ ## If `kittycode` Is Still Not Found After `pip install -e .`
192
+
193
+ The project already declares the console entry point in `pyproject.toml`:
194
+
195
+ ```toml
196
+ [project.scripts]
197
+ kittycode = "kittycode.cli:main"
198
+ ```
199
+
200
+ So if `kittycode` is still unavailable, the usual cause is the local Python install location rather than missing packaging metadata.
201
+
202
+ Check which Python/pip you used:
203
+
204
+ ```bash
205
+ python3 -m pip --version
206
+ python3 -m site --user-base
207
+ ```
208
+
209
+ On macOS, a user install commonly places scripts under:
210
+
211
+ ```bash
212
+ ~/Library/Python/3.11/bin
213
+ ```
214
+
215
+ If that directory is not in `PATH`, the editable install may succeed but the shell still will not find `kittycode`.
216
+
217
+ For `zsh`, add the corresponding bin directory to your shell profile:
218
+
219
+ ```bash
220
+ export PATH="$HOME/Library/Python/3.11/bin:$PATH"
221
+ ```
222
+
223
+ Then reload the shell:
224
+
225
+ ```bash
226
+ source ~/.zshrc
227
+ ```
228
+
229
+ If the install itself fails with a permissions error while writing a `.pth` file under `~/Library/Python/.../site-packages`, fix that environment issue first or install into a virtual environment before retrying.
230
+
187
231
  ## Project Layout
188
232
 
189
233
  - `kittycode/agent.py`: core agent loop
@@ -202,4 +246,4 @@ Run the test suite:
202
246
  python -m pytest -q
203
247
  ```
204
248
 
205
- The current test suite covers the exported API, config-file behavior, provider conversion helpers, context compression, session helpers, the default tool registry, and skill discovery/prompt injection.
249
+ The current test suite covers the exported API, config-file behavior, provider conversion helpers, context compression, session helpers, the default tool registry, and skill discovery/prompt injection.
@@ -20,6 +20,8 @@ The project is intentionally small. It includes the core agent runtime, a compac
20
20
  - Built-in tools for shell commands, file reading, file writing, targeted editing, glob search, regex search, and sub-agents.
21
21
  - Startup skill discovery from `~/.kittycode/skills`, with skill metadata injected into the system prompt each round.
22
22
  - Interactive REPL and one-shot command mode.
23
+ - ANSI pixel-cat startup banner.
24
+ - `Esc` interrupt support for stopping the current agent run at the next safe checkpoint.
23
25
  - Context compression to keep long sessions manageable.
24
26
  - Session save and resume support.
25
27
 
@@ -123,6 +125,8 @@ Run the interactive terminal UI:
123
125
  kittycode
124
126
  ```
125
127
 
128
+ When the CLI is busy, press `Esc` to interrupt the current run. The stop is cooperative: KittyCode cancels at the next safe checkpoint between LLM streaming, tool dispatch, and loop rounds. A blocking external call may still need to return before the run fully stops.
129
+
126
130
  You can also use the module entry point:
127
131
 
128
132
  ```bash
@@ -165,6 +169,46 @@ Inside the REPL, KittyCode supports:
165
169
  The `/skills` command refreshes the local skill cache if the skill directory has changed and then prints the currently loaded skills.
166
170
  Slash commands also support prefix matching while typing, so entering `/` shows matching commands and skills through completion suggestions.
167
171
 
172
+ ## If `kittycode` Is Still Not Found After `pip install -e .`
173
+
174
+ The project already declares the console entry point in `pyproject.toml`:
175
+
176
+ ```toml
177
+ [project.scripts]
178
+ kittycode = "kittycode.cli:main"
179
+ ```
180
+
181
+ So if `kittycode` is still unavailable, the usual cause is the local Python install location rather than missing packaging metadata.
182
+
183
+ Check which Python/pip you used:
184
+
185
+ ```bash
186
+ python3 -m pip --version
187
+ python3 -m site --user-base
188
+ ```
189
+
190
+ On macOS, a user install commonly places scripts under:
191
+
192
+ ```bash
193
+ ~/Library/Python/3.11/bin
194
+ ```
195
+
196
+ If that directory is not in `PATH`, the editable install may succeed but the shell still will not find `kittycode`.
197
+
198
+ For `zsh`, add the corresponding bin directory to your shell profile:
199
+
200
+ ```bash
201
+ export PATH="$HOME/Library/Python/3.11/bin:$PATH"
202
+ ```
203
+
204
+ Then reload the shell:
205
+
206
+ ```bash
207
+ source ~/.zshrc
208
+ ```
209
+
210
+ If the install itself fails with a permissions error while writing a `.pth` file under `~/Library/Python/.../site-packages`, fix that environment issue first or install into a virtual environment before retrying.
211
+
168
212
  ## Project Layout
169
213
 
170
214
  - `kittycode/agent.py`: core agent loop
@@ -183,4 +227,4 @@ Run the test suite:
183
227
  python -m pytest -q
184
228
  ```
185
229
 
186
- The current test suite covers the exported API, config-file behavior, provider conversion helpers, context compression, session helpers, the default tool registry, and skill discovery/prompt injection.
230
+ The current test suite covers the exported API, config-file behavior, provider conversion helpers, context compression, session helpers, the default tool registry, and skill discovery/prompt injection.
@@ -20,6 +20,8 @@ KittyCode 的基本工作方式如下:
20
20
  - 内置 Bash、读文件、写文件、精确替换编辑、Glob 搜索、Grep 搜索、子代理等工具。
21
21
  - 启动时自动扫描 `~/.kittycode/skills`,并在每轮系统 prompt 开头注入可用 skill 列表。
22
22
  - 支持交互式 REPL 和单次命令模式。
23
+ - 启动时展示 ANSI 彩色像素猫。
24
+ - 支持在执行过程中按 `Esc` 中断当前任务。
23
25
  - 支持长会话上下文压缩。
24
26
  - 支持保存和恢复历史会话。
25
27
 
@@ -123,6 +125,8 @@ KittyCode 启动时会扫描 `~/.kittycode/skills` 目录。每个 skill 使用
123
125
  kittycode
124
126
  ```
125
127
 
128
+ 当 CLI 正在执行时,可以按 `Esc` 中断当前任务。这个中断是协作式的:KittyCode 会在下一处安全检查点停止,例如 LLM 流式输出过程中、工具调度前后或下一轮循环开始前。如果某个底层外部调用本身是阻塞的,仍然可能要等该调用返回后才会完全结束。
129
+
126
130
  也可以直接通过模块启动:
127
131
 
128
132
  ```bash
@@ -165,6 +169,46 @@ kittycode --interface anthropic --model claude-3-7-sonnet-latest
165
169
  `/skills` 命令会在检测到 skill 目录变化时刷新本地缓存,并输出当前已加载的 skill 列表。
166
170
  当输入以 `/` 开头时,CLI 还会根据前缀自动补全可用命令和 skill。
167
171
 
172
+ ## 如果执行 `pip install -e .` 后仍然找不到 `kittycode`
173
+
174
+ 项目本身已经在 `pyproject.toml` 中声明了控制台入口:
175
+
176
+ ```toml
177
+ [project.scripts]
178
+ kittycode = "kittycode.cli:main"
179
+ ```
180
+
181
+ 所以如果仍然不能直接执行 `kittycode`,通常不是因为项目没有声明入口,而是 Python 本地安装路径或 shell `PATH` 的问题。
182
+
183
+ 先确认你实际使用的是哪个 Python/pip:
184
+
185
+ ```bash
186
+ python3 -m pip --version
187
+ python3 -m site --user-base
188
+ ```
189
+
190
+ 在 macOS 上,用户级安装通常会把脚本放到类似下面的目录:
191
+
192
+ ```bash
193
+ ~/Library/Python/3.11/bin
194
+ ```
195
+
196
+ 如果这个目录不在 `PATH` 中,即使 editable install 成功,shell 也依然找不到 `kittycode`。
197
+
198
+ 对于 `zsh`,可以把对应目录加入配置:
199
+
200
+ ```bash
201
+ export PATH="$HOME/Library/Python/3.11/bin:$PATH"
202
+ ```
203
+
204
+ 然后重新加载 shell:
205
+
206
+ ```bash
207
+ source ~/.zshrc
208
+ ```
209
+
210
+ 如果安装过程本身在往 `~/Library/Python/.../site-packages` 写 `.pth` 文件时就报权限错误,那要先修复本地 Python 环境权限,或者改用虚拟环境后再重新安装。
211
+
168
212
  ## 目录结构
169
213
 
170
214
  - `kittycode/agent.py`:核心代理循环
@@ -183,4 +227,4 @@ kittycode --interface anthropic --model claude-3-7-sonnet-latest
183
227
  python -m pytest -q
184
228
  ```
185
229
 
186
- 当前测试主要覆盖导出 API、config.json 读取、provider 转换辅助逻辑、上下文压缩、会话辅助函数、默认工具注册表,以及 skill 发现与 prompt 注入逻辑。
230
+ 当前测试主要覆盖导出 API、config.json 读取、provider 转换辅助逻辑、上下文压缩、会话辅助函数、默认工具注册表,以及 skill 发现与 prompt 注入逻辑。
@@ -1,6 +1,6 @@
1
1
  """KittyCode - minimal AI coding agent."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.2"
4
4
 
5
5
  from kittycode.agent import Agent
6
6
  from kittycode.config import Config
@@ -0,0 +1,166 @@
1
+ """Core agent loop.
2
+
3
+ This is the heart of KittyCode.
4
+
5
+ user message -> LLM (with tools) -> tool calls? -> execute -> loop
6
+ -> text reply? -> return to user
7
+
8
+ It keeps looping until the LLM responds with plain text, which means it is
9
+ done working and ready to report back.
10
+ """
11
+
12
+ import concurrent.futures
13
+ import copy
14
+ import inspect
15
+
16
+ from .context import ContextManager
17
+ from .interrupts import CancellationRequested
18
+ from .llm import LLM
19
+ from .prompt import system_prompt
20
+ from .skills import load_skills
21
+ from .tools import create_tool_instances, get_tool
22
+ from .tools.agent import AgentTool
23
+ from .tools.base import Tool
24
+
25
+
26
+ class Agent:
27
+ def __init__(
28
+ self,
29
+ llm: LLM,
30
+ tools: list[Tool] | None = None,
31
+ max_context_tokens: int = 128_000,
32
+ max_rounds: int = 50,
33
+ ):
34
+ self.llm = llm
35
+ self.tools = list(tools) if tools is not None else create_tool_instances()
36
+ self.messages: list[dict] = []
37
+ self.context = ContextManager(max_tokens=max_context_tokens)
38
+ self.max_rounds = max_rounds
39
+ self.skills = []
40
+ self._system = ""
41
+ self.refresh_skills(force_reload=True)
42
+
43
+ for tool in self.tools:
44
+ if isinstance(tool, AgentTool):
45
+ tool._parent_agent = self
46
+
47
+ def _full_messages(self) -> list[dict]:
48
+ self.refresh_skills()
49
+ return [{"role": "system", "content": self._system}] + self.messages
50
+
51
+ def _tool_schemas(self) -> list[dict]:
52
+ return [tool.schema() for tool in self.tools]
53
+
54
+ def fork(self):
55
+ worker = Agent(
56
+ llm=self.llm.clone() if hasattr(self.llm, "clone") else self.llm,
57
+ tools=[type(tool)() for tool in self.tools],
58
+ max_context_tokens=self.context.max_tokens,
59
+ max_rounds=self.max_rounds,
60
+ )
61
+ worker.messages = copy.deepcopy(self.messages)
62
+ worker.skills = list(self.skills)
63
+ worker._system = self._system
64
+ return worker
65
+
66
+ def chat(self, user_input: str, on_token=None, on_tool=None, on_tool_output=None, cancel_event=None) -> str:
67
+ """Process one user message. May involve multiple LLM/tool rounds."""
68
+ self.messages.append({"role": "user", "content": user_input})
69
+ self.context.maybe_compress(self.messages, self.llm)
70
+
71
+ try:
72
+ self._raise_if_cancelled(cancel_event)
73
+
74
+ for _ in range(self.max_rounds):
75
+ self._raise_if_cancelled(cancel_event)
76
+ response = self.llm.chat(
77
+ messages=self._full_messages(),
78
+ tools=self._tool_schemas(),
79
+ on_token=on_token,
80
+ cancel_event=cancel_event,
81
+ )
82
+
83
+ if not response.tool_calls:
84
+ self.messages.append(response.message)
85
+ return response.content
86
+
87
+ self.messages.append(response.message)
88
+
89
+ if len(response.tool_calls) == 1:
90
+ tool_call = response.tool_calls[0]
91
+ if on_tool:
92
+ on_tool(tool_call.name, tool_call.arguments)
93
+ self._raise_if_cancelled(cancel_event)
94
+ result = self._exec_tool(tool_call, cancel_event=cancel_event, on_output=on_tool_output)
95
+ self.messages.append(
96
+ {
97
+ "role": "tool",
98
+ "tool_call_id": tool_call.id,
99
+ "content": result,
100
+ }
101
+ )
102
+ else:
103
+ results = self._exec_tools_parallel(
104
+ response.tool_calls,
105
+ on_tool=on_tool,
106
+ on_tool_output=on_tool_output,
107
+ cancel_event=cancel_event,
108
+ )
109
+ for tool_call, result in zip(response.tool_calls, results):
110
+ self.messages.append(
111
+ {
112
+ "role": "tool",
113
+ "tool_call_id": tool_call.id,
114
+ "content": result,
115
+ }
116
+ )
117
+
118
+ self.context.maybe_compress(self.messages, self.llm)
119
+
120
+ return "(reached maximum tool-call rounds)"
121
+ except CancellationRequested:
122
+ return "(interrupted)"
123
+
124
+ def _exec_tool(self, tool_call, cancel_event=None, on_output=None) -> str:
125
+ """Execute a single tool call and return the result string."""
126
+ self._raise_if_cancelled(cancel_event)
127
+ tool = get_tool(tool_call.name, self.tools)
128
+ if tool is None:
129
+ return f"Error: unknown tool '{tool_call.name}'"
130
+ try:
131
+ execute_kwargs = dict(tool_call.arguments)
132
+ parameters = inspect.signature(tool.execute).parameters
133
+ if on_output is not None and "stream_callback" in parameters:
134
+ execute_kwargs["stream_callback"] = lambda text: on_output(tool_call.name, text)
135
+ if cancel_event is not None and "cancel_event" in parameters:
136
+ execute_kwargs["cancel_event"] = cancel_event
137
+ return tool.execute(**execute_kwargs)
138
+ except TypeError as exc:
139
+ return f"Error: bad arguments for {tool_call.name}: {exc}"
140
+ except Exception as exc:
141
+ return f"Error executing {tool_call.name}: {exc}"
142
+
143
+ def _exec_tools_parallel(self, tool_calls, on_tool=None, on_tool_output=None, cancel_event=None) -> list[str]:
144
+ """Run multiple tool calls concurrently using threads."""
145
+ for tool_call in tool_calls:
146
+ if on_tool:
147
+ on_tool(tool_call.name, tool_call.arguments)
148
+ self._raise_if_cancelled(cancel_event)
149
+
150
+ with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
151
+ futures = [pool.submit(self._exec_tool, tool_call, cancel_event, on_tool_output) for tool_call in tool_calls]
152
+ return [future.result() for future in futures]
153
+
154
+ def reset(self):
155
+ """Clear conversation history."""
156
+ self.messages.clear()
157
+
158
+ def refresh_skills(self, force_reload: bool = False):
159
+ """Refresh cached skill metadata and rebuild the system prompt."""
160
+ self.skills = load_skills(force_reload=force_reload)
161
+ self._system = system_prompt(self.tools, self.skills)
162
+
163
+ @staticmethod
164
+ def _raise_if_cancelled(cancel_event):
165
+ if cancel_event is not None and cancel_event.is_set():
166
+ raise CancellationRequested()