kyber-chat 1.0.0__py3-none-any.whl
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.
- kyber/__init__.py +6 -0
- kyber/__main__.py +8 -0
- kyber/agent/__init__.py +8 -0
- kyber/agent/context.py +224 -0
- kyber/agent/loop.py +687 -0
- kyber/agent/memory.py +109 -0
- kyber/agent/skills.py +244 -0
- kyber/agent/subagent.py +379 -0
- kyber/agent/tools/__init__.py +6 -0
- kyber/agent/tools/base.py +102 -0
- kyber/agent/tools/filesystem.py +191 -0
- kyber/agent/tools/message.py +86 -0
- kyber/agent/tools/registry.py +73 -0
- kyber/agent/tools/shell.py +141 -0
- kyber/agent/tools/spawn.py +65 -0
- kyber/agent/tools/task_status.py +53 -0
- kyber/agent/tools/web.py +163 -0
- kyber/bridge/package.json +26 -0
- kyber/bridge/src/index.ts +50 -0
- kyber/bridge/src/server.ts +104 -0
- kyber/bridge/src/types.d.ts +3 -0
- kyber/bridge/src/whatsapp.ts +185 -0
- kyber/bridge/tsconfig.json +16 -0
- kyber/bus/__init__.py +6 -0
- kyber/bus/events.py +37 -0
- kyber/bus/queue.py +81 -0
- kyber/channels/__init__.py +6 -0
- kyber/channels/base.py +121 -0
- kyber/channels/discord.py +304 -0
- kyber/channels/feishu.py +263 -0
- kyber/channels/manager.py +161 -0
- kyber/channels/telegram.py +302 -0
- kyber/channels/whatsapp.py +141 -0
- kyber/cli/__init__.py +1 -0
- kyber/cli/commands.py +736 -0
- kyber/config/__init__.py +6 -0
- kyber/config/loader.py +95 -0
- kyber/config/schema.py +205 -0
- kyber/cron/__init__.py +6 -0
- kyber/cron/service.py +346 -0
- kyber/cron/types.py +59 -0
- kyber/dashboard/__init__.py +5 -0
- kyber/dashboard/server.py +122 -0
- kyber/dashboard/static/app.js +458 -0
- kyber/dashboard/static/favicon.png +0 -0
- kyber/dashboard/static/index.html +107 -0
- kyber/dashboard/static/kyber_logo.png +0 -0
- kyber/dashboard/static/styles.css +608 -0
- kyber/heartbeat/__init__.py +5 -0
- kyber/heartbeat/service.py +130 -0
- kyber/providers/__init__.py +6 -0
- kyber/providers/base.py +69 -0
- kyber/providers/litellm_provider.py +227 -0
- kyber/providers/transcription.py +65 -0
- kyber/session/__init__.py +5 -0
- kyber/session/manager.py +202 -0
- kyber/skills/README.md +47 -0
- kyber/skills/github/SKILL.md +48 -0
- kyber/skills/skill-creator/SKILL.md +371 -0
- kyber/skills/summarize/SKILL.md +67 -0
- kyber/skills/tmux/SKILL.md +121 -0
- kyber/skills/tmux/scripts/find-sessions.sh +112 -0
- kyber/skills/tmux/scripts/wait-for-text.sh +83 -0
- kyber/skills/weather/SKILL.md +49 -0
- kyber/utils/__init__.py +5 -0
- kyber/utils/helpers.py +91 -0
- kyber_chat-1.0.0.dist-info/METADATA +35 -0
- kyber_chat-1.0.0.dist-info/RECORD +71 -0
- kyber_chat-1.0.0.dist-info/WHEEL +4 -0
- kyber_chat-1.0.0.dist-info/entry_points.txt +2 -0
- kyber_chat-1.0.0.dist-info/licenses/LICENSE +21 -0
kyber/__init__.py
ADDED
kyber/__main__.py
ADDED
kyber/agent/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Agent core module."""
|
|
2
|
+
|
|
3
|
+
from kyber.agent.loop import AgentLoop
|
|
4
|
+
from kyber.agent.context import ContextBuilder
|
|
5
|
+
from kyber.agent.memory import MemoryStore
|
|
6
|
+
from kyber.agent.skills import SkillsLoader
|
|
7
|
+
|
|
8
|
+
__all__ = ["AgentLoop", "ContextBuilder", "MemoryStore", "SkillsLoader"]
|
kyber/agent/context.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Context builder for assembling agent prompts."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import mimetypes
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from kyber.agent.memory import MemoryStore
|
|
9
|
+
from kyber.agent.skills import SkillsLoader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContextBuilder:
|
|
13
|
+
"""
|
|
14
|
+
Builds the context (system prompt + messages) for the agent.
|
|
15
|
+
|
|
16
|
+
Assembles bootstrap files, memory, skills, and conversation history
|
|
17
|
+
into a coherent prompt for the LLM.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
|
|
21
|
+
|
|
22
|
+
def __init__(self, workspace: Path):
|
|
23
|
+
self.workspace = workspace
|
|
24
|
+
self.memory = MemoryStore(workspace)
|
|
25
|
+
self.skills = SkillsLoader(workspace)
|
|
26
|
+
|
|
27
|
+
def build_system_prompt(self, skill_names: list[str] | None = None) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Build the system prompt from bootstrap files, memory, and skills.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
skill_names: Optional list of skills to include.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Complete system prompt.
|
|
36
|
+
"""
|
|
37
|
+
parts = []
|
|
38
|
+
|
|
39
|
+
# Core identity
|
|
40
|
+
parts.append(self._get_identity())
|
|
41
|
+
|
|
42
|
+
# Bootstrap files
|
|
43
|
+
bootstrap = self._load_bootstrap_files()
|
|
44
|
+
if bootstrap:
|
|
45
|
+
parts.append(bootstrap)
|
|
46
|
+
|
|
47
|
+
# Memory context
|
|
48
|
+
memory = self.memory.get_memory_context()
|
|
49
|
+
if memory:
|
|
50
|
+
parts.append(f"# Memory\n\n{memory}")
|
|
51
|
+
|
|
52
|
+
# Skills - progressive loading
|
|
53
|
+
# 1. Always-loaded skills: include full content
|
|
54
|
+
always_skills = self.skills.get_always_skills()
|
|
55
|
+
if always_skills:
|
|
56
|
+
always_content = self.skills.load_skills_for_context(always_skills)
|
|
57
|
+
if always_content:
|
|
58
|
+
parts.append(f"# Active Skills\n\n{always_content}")
|
|
59
|
+
|
|
60
|
+
# 2. Available skills: only show summary (agent uses read_file to load)
|
|
61
|
+
skills_summary = self.skills.build_skills_summary()
|
|
62
|
+
if skills_summary:
|
|
63
|
+
parts.append(f"""# Skills
|
|
64
|
+
|
|
65
|
+
The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
|
|
66
|
+
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
|
|
67
|
+
|
|
68
|
+
{skills_summary}""")
|
|
69
|
+
|
|
70
|
+
return "\n\n---\n\n".join(parts)
|
|
71
|
+
|
|
72
|
+
def _get_identity(self) -> str:
|
|
73
|
+
"""Get the core identity section."""
|
|
74
|
+
from datetime import datetime
|
|
75
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M (%A)")
|
|
76
|
+
workspace_path = str(self.workspace.expanduser().resolve())
|
|
77
|
+
|
|
78
|
+
return f"""# kyber 💎
|
|
79
|
+
|
|
80
|
+
You are kyber, a helpful AI assistant. You have access to tools that allow you to:
|
|
81
|
+
- Read, write, and edit files
|
|
82
|
+
- Execute shell commands
|
|
83
|
+
- Search the web and fetch web pages
|
|
84
|
+
- Send messages to users on chat channels
|
|
85
|
+
- Spawn subagents for complex background tasks
|
|
86
|
+
|
|
87
|
+
## Current Time
|
|
88
|
+
{now}
|
|
89
|
+
|
|
90
|
+
## Workspace
|
|
91
|
+
Your workspace is at: {workspace_path}
|
|
92
|
+
- Memory files: {workspace_path}/memory/MEMORY.md
|
|
93
|
+
- Daily notes: {workspace_path}/memory/YYYY-MM-DD.md
|
|
94
|
+
- Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md
|
|
95
|
+
- Managed skills: ~/.kyber/skills/{{skill-name}}/SKILL.md
|
|
96
|
+
|
|
97
|
+
IMPORTANT: When responding to direct questions or conversations, reply directly with your text response.
|
|
98
|
+
Only use the 'message' tool when you need to send a message to a specific chat channel (like WhatsApp).
|
|
99
|
+
For normal conversation, just respond with text - do not call the message tool.
|
|
100
|
+
|
|
101
|
+
CRITICAL — TOOL USAGE RULES:
|
|
102
|
+
- When asked to edit code, create files, run commands, or make any changes: YOU MUST USE TOOLS. Do not describe or narrate changes — actually call write_file, edit_file, exec, etc.
|
|
103
|
+
- Never say "I've updated the file" or "I've made the changes" unless you literally called a tool and saw the result in this conversation.
|
|
104
|
+
- If you're unsure what to change, ask. If you know what to change, use the tools to do it.
|
|
105
|
+
- For code tasks: read the file first (read_file), make edits (edit_file/write_file), then verify if needed (exec).
|
|
106
|
+
|
|
107
|
+
Always be helpful, accurate, and concise. When using tools, explain what you're doing.
|
|
108
|
+
When remembering something, write to {workspace_path}/memory/MEMORY.md"""
|
|
109
|
+
|
|
110
|
+
def _load_bootstrap_files(self) -> str:
|
|
111
|
+
"""Load all bootstrap files from workspace."""
|
|
112
|
+
parts = []
|
|
113
|
+
|
|
114
|
+
for filename in self.BOOTSTRAP_FILES:
|
|
115
|
+
file_path = self.workspace / filename
|
|
116
|
+
if file_path.exists():
|
|
117
|
+
content = file_path.read_text(encoding="utf-8")
|
|
118
|
+
parts.append(f"## {filename}\n\n{content}")
|
|
119
|
+
|
|
120
|
+
return "\n\n".join(parts) if parts else ""
|
|
121
|
+
|
|
122
|
+
def build_messages(
|
|
123
|
+
self,
|
|
124
|
+
history: list[dict[str, Any]],
|
|
125
|
+
current_message: str,
|
|
126
|
+
skill_names: list[str] | None = None,
|
|
127
|
+
media: list[str] | None = None,
|
|
128
|
+
) -> list[dict[str, Any]]:
|
|
129
|
+
"""
|
|
130
|
+
Build the complete message list for an LLM call.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
history: Previous conversation messages.
|
|
134
|
+
current_message: The new user message.
|
|
135
|
+
skill_names: Optional skills to include.
|
|
136
|
+
media: Optional list of local file paths for images/media.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of messages including system prompt.
|
|
140
|
+
"""
|
|
141
|
+
messages = []
|
|
142
|
+
|
|
143
|
+
# System prompt
|
|
144
|
+
system_prompt = self.build_system_prompt(skill_names)
|
|
145
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
146
|
+
|
|
147
|
+
# History
|
|
148
|
+
messages.extend(history)
|
|
149
|
+
|
|
150
|
+
# Current message (with optional image attachments)
|
|
151
|
+
user_content = self._build_user_content(current_message, media)
|
|
152
|
+
messages.append({"role": "user", "content": user_content})
|
|
153
|
+
|
|
154
|
+
return messages
|
|
155
|
+
|
|
156
|
+
def _build_user_content(self, text: str, media: list[str] | None) -> str | list[dict[str, Any]]:
|
|
157
|
+
"""Build user message content with optional base64-encoded images."""
|
|
158
|
+
if not media:
|
|
159
|
+
return text
|
|
160
|
+
|
|
161
|
+
images = []
|
|
162
|
+
for path in media:
|
|
163
|
+
p = Path(path)
|
|
164
|
+
mime, _ = mimetypes.guess_type(path)
|
|
165
|
+
if not p.is_file() or not mime or not mime.startswith("image/"):
|
|
166
|
+
continue
|
|
167
|
+
b64 = base64.b64encode(p.read_bytes()).decode()
|
|
168
|
+
images.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}})
|
|
169
|
+
|
|
170
|
+
if not images:
|
|
171
|
+
return text
|
|
172
|
+
return images + [{"type": "text", "text": text}]
|
|
173
|
+
|
|
174
|
+
def add_tool_result(
|
|
175
|
+
self,
|
|
176
|
+
messages: list[dict[str, Any]],
|
|
177
|
+
tool_call_id: str,
|
|
178
|
+
tool_name: str,
|
|
179
|
+
result: str
|
|
180
|
+
) -> list[dict[str, Any]]:
|
|
181
|
+
"""
|
|
182
|
+
Add a tool result to the message list.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
messages: Current message list.
|
|
186
|
+
tool_call_id: ID of the tool call.
|
|
187
|
+
tool_name: Name of the tool.
|
|
188
|
+
result: Tool execution result.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Updated message list.
|
|
192
|
+
"""
|
|
193
|
+
messages.append({
|
|
194
|
+
"role": "tool",
|
|
195
|
+
"tool_call_id": tool_call_id,
|
|
196
|
+
"name": tool_name,
|
|
197
|
+
"content": result
|
|
198
|
+
})
|
|
199
|
+
return messages
|
|
200
|
+
|
|
201
|
+
def add_assistant_message(
|
|
202
|
+
self,
|
|
203
|
+
messages: list[dict[str, Any]],
|
|
204
|
+
content: str | None,
|
|
205
|
+
tool_calls: list[dict[str, Any]] | None = None
|
|
206
|
+
) -> list[dict[str, Any]]:
|
|
207
|
+
"""
|
|
208
|
+
Add an assistant message to the message list.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
messages: Current message list.
|
|
212
|
+
content: Message content.
|
|
213
|
+
tool_calls: Optional tool calls.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Updated message list.
|
|
217
|
+
"""
|
|
218
|
+
msg: dict[str, Any] = {"role": "assistant", "content": content or ""}
|
|
219
|
+
|
|
220
|
+
if tool_calls:
|
|
221
|
+
msg["tool_calls"] = tool_calls
|
|
222
|
+
|
|
223
|
+
messages.append(msg)
|
|
224
|
+
return messages
|