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.
Files changed (71) hide show
  1. kyber/__init__.py +6 -0
  2. kyber/__main__.py +8 -0
  3. kyber/agent/__init__.py +8 -0
  4. kyber/agent/context.py +224 -0
  5. kyber/agent/loop.py +687 -0
  6. kyber/agent/memory.py +109 -0
  7. kyber/agent/skills.py +244 -0
  8. kyber/agent/subagent.py +379 -0
  9. kyber/agent/tools/__init__.py +6 -0
  10. kyber/agent/tools/base.py +102 -0
  11. kyber/agent/tools/filesystem.py +191 -0
  12. kyber/agent/tools/message.py +86 -0
  13. kyber/agent/tools/registry.py +73 -0
  14. kyber/agent/tools/shell.py +141 -0
  15. kyber/agent/tools/spawn.py +65 -0
  16. kyber/agent/tools/task_status.py +53 -0
  17. kyber/agent/tools/web.py +163 -0
  18. kyber/bridge/package.json +26 -0
  19. kyber/bridge/src/index.ts +50 -0
  20. kyber/bridge/src/server.ts +104 -0
  21. kyber/bridge/src/types.d.ts +3 -0
  22. kyber/bridge/src/whatsapp.ts +185 -0
  23. kyber/bridge/tsconfig.json +16 -0
  24. kyber/bus/__init__.py +6 -0
  25. kyber/bus/events.py +37 -0
  26. kyber/bus/queue.py +81 -0
  27. kyber/channels/__init__.py +6 -0
  28. kyber/channels/base.py +121 -0
  29. kyber/channels/discord.py +304 -0
  30. kyber/channels/feishu.py +263 -0
  31. kyber/channels/manager.py +161 -0
  32. kyber/channels/telegram.py +302 -0
  33. kyber/channels/whatsapp.py +141 -0
  34. kyber/cli/__init__.py +1 -0
  35. kyber/cli/commands.py +736 -0
  36. kyber/config/__init__.py +6 -0
  37. kyber/config/loader.py +95 -0
  38. kyber/config/schema.py +205 -0
  39. kyber/cron/__init__.py +6 -0
  40. kyber/cron/service.py +346 -0
  41. kyber/cron/types.py +59 -0
  42. kyber/dashboard/__init__.py +5 -0
  43. kyber/dashboard/server.py +122 -0
  44. kyber/dashboard/static/app.js +458 -0
  45. kyber/dashboard/static/favicon.png +0 -0
  46. kyber/dashboard/static/index.html +107 -0
  47. kyber/dashboard/static/kyber_logo.png +0 -0
  48. kyber/dashboard/static/styles.css +608 -0
  49. kyber/heartbeat/__init__.py +5 -0
  50. kyber/heartbeat/service.py +130 -0
  51. kyber/providers/__init__.py +6 -0
  52. kyber/providers/base.py +69 -0
  53. kyber/providers/litellm_provider.py +227 -0
  54. kyber/providers/transcription.py +65 -0
  55. kyber/session/__init__.py +5 -0
  56. kyber/session/manager.py +202 -0
  57. kyber/skills/README.md +47 -0
  58. kyber/skills/github/SKILL.md +48 -0
  59. kyber/skills/skill-creator/SKILL.md +371 -0
  60. kyber/skills/summarize/SKILL.md +67 -0
  61. kyber/skills/tmux/SKILL.md +121 -0
  62. kyber/skills/tmux/scripts/find-sessions.sh +112 -0
  63. kyber/skills/tmux/scripts/wait-for-text.sh +83 -0
  64. kyber/skills/weather/SKILL.md +49 -0
  65. kyber/utils/__init__.py +5 -0
  66. kyber/utils/helpers.py +91 -0
  67. kyber_chat-1.0.0.dist-info/METADATA +35 -0
  68. kyber_chat-1.0.0.dist-info/RECORD +71 -0
  69. kyber_chat-1.0.0.dist-info/WHEEL +4 -0
  70. kyber_chat-1.0.0.dist-info/entry_points.txt +2 -0
  71. kyber_chat-1.0.0.dist-info/licenses/LICENSE +21 -0
kyber/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """
2
+ kyber - A lightweight AI agent framework
3
+ """
4
+
5
+ __version__ = "1.0.0"
6
+ __logo__ = "💎"
kyber/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Entry point for running kyber as a module: python -m kyber
3
+ """
4
+
5
+ from kyber.cli.commands import app
6
+
7
+ if __name__ == "__main__":
8
+ app()
@@ -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