vibecore 0.2.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.

Potentially problematic release.


This version of vibecore might be problematic. Click here for more details.

Files changed (63) hide show
  1. vibecore/__init__.py +0 -0
  2. vibecore/agents/default.py +79 -0
  3. vibecore/agents/prompts.py +12 -0
  4. vibecore/agents/task_agent.py +66 -0
  5. vibecore/cli.py +150 -0
  6. vibecore/context.py +24 -0
  7. vibecore/handlers/__init__.py +5 -0
  8. vibecore/handlers/stream_handler.py +231 -0
  9. vibecore/main.py +506 -0
  10. vibecore/main.tcss +0 -0
  11. vibecore/mcp/__init__.py +6 -0
  12. vibecore/mcp/manager.py +167 -0
  13. vibecore/mcp/server_wrapper.py +109 -0
  14. vibecore/models/__init__.py +5 -0
  15. vibecore/models/anthropic.py +239 -0
  16. vibecore/prompts/common_system_prompt.txt +64 -0
  17. vibecore/py.typed +0 -0
  18. vibecore/session/__init__.py +5 -0
  19. vibecore/session/file_lock.py +127 -0
  20. vibecore/session/jsonl_session.py +236 -0
  21. vibecore/session/loader.py +193 -0
  22. vibecore/session/path_utils.py +81 -0
  23. vibecore/settings.py +161 -0
  24. vibecore/tools/__init__.py +1 -0
  25. vibecore/tools/base.py +27 -0
  26. vibecore/tools/file/__init__.py +5 -0
  27. vibecore/tools/file/executor.py +282 -0
  28. vibecore/tools/file/tools.py +184 -0
  29. vibecore/tools/file/utils.py +78 -0
  30. vibecore/tools/python/__init__.py +1 -0
  31. vibecore/tools/python/backends/__init__.py +1 -0
  32. vibecore/tools/python/backends/terminal_backend.py +58 -0
  33. vibecore/tools/python/helpers.py +80 -0
  34. vibecore/tools/python/manager.py +208 -0
  35. vibecore/tools/python/tools.py +27 -0
  36. vibecore/tools/shell/__init__.py +5 -0
  37. vibecore/tools/shell/executor.py +223 -0
  38. vibecore/tools/shell/tools.py +156 -0
  39. vibecore/tools/task/__init__.py +5 -0
  40. vibecore/tools/task/executor.py +51 -0
  41. vibecore/tools/task/tools.py +51 -0
  42. vibecore/tools/todo/__init__.py +1 -0
  43. vibecore/tools/todo/manager.py +31 -0
  44. vibecore/tools/todo/models.py +36 -0
  45. vibecore/tools/todo/tools.py +111 -0
  46. vibecore/utils/__init__.py +5 -0
  47. vibecore/utils/text.py +28 -0
  48. vibecore/widgets/core.py +332 -0
  49. vibecore/widgets/core.tcss +63 -0
  50. vibecore/widgets/expandable.py +121 -0
  51. vibecore/widgets/expandable.tcss +69 -0
  52. vibecore/widgets/info.py +25 -0
  53. vibecore/widgets/info.tcss +17 -0
  54. vibecore/widgets/messages.py +232 -0
  55. vibecore/widgets/messages.tcss +85 -0
  56. vibecore/widgets/tool_message_factory.py +121 -0
  57. vibecore/widgets/tool_messages.py +483 -0
  58. vibecore/widgets/tool_messages.tcss +289 -0
  59. vibecore-0.2.0.dist-info/METADATA +407 -0
  60. vibecore-0.2.0.dist-info/RECORD +63 -0
  61. vibecore-0.2.0.dist-info/WHEEL +4 -0
  62. vibecore-0.2.0.dist-info/entry_points.txt +2 -0
  63. vibecore-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,232 @@
1
+ from enum import StrEnum
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.content import Content
5
+ from textual.reactive import reactive
6
+ from textual.widget import Widget
7
+ from textual.widgets import Markdown, Static
8
+
9
+
10
+ class MessageStatus(StrEnum):
11
+ """Status values for messages."""
12
+
13
+ IDLE = "idle"
14
+ EXECUTING = "executing"
15
+ SUCCESS = "success"
16
+ ERROR = "error"
17
+
18
+
19
+ class MessageHeader(Widget):
20
+ """A widget to display a message header."""
21
+
22
+ text: reactive[str] = reactive("")
23
+ status: reactive[MessageStatus] = reactive(MessageStatus.IDLE)
24
+ _prefix_visible: reactive[bool] = reactive(False, init=False)
25
+
26
+ def __init__(
27
+ self, prefix: str, text: str, status: MessageStatus = MessageStatus.IDLE, use_markdown: bool = False, **kwargs
28
+ ) -> None:
29
+ """
30
+ Construct a MessageHeader.
31
+
32
+ Args:
33
+ text: The text to display.
34
+ **kwargs: Additional keyword arguments for Static.
35
+ """
36
+ super().__init__(**kwargs)
37
+ self.prefix = prefix
38
+ self.set_reactive(MessageHeader.text, text)
39
+ self.set_reactive(MessageHeader.status, status)
40
+ self._update_status_class(status)
41
+ self.use_markdown = use_markdown
42
+
43
+ def _update_status_class(self, status: MessageStatus) -> None:
44
+ """Update the status class based on the current status."""
45
+ self.set_class(status == MessageStatus.IDLE, "status-idle")
46
+ self.set_class(status == MessageStatus.EXECUTING, "status-executing")
47
+ self.set_class(status == MessageStatus.SUCCESS, "status-success")
48
+ self.set_class(status == MessageStatus.ERROR, "status-error")
49
+
50
+ def watch_status(self, status: MessageStatus) -> None:
51
+ """Watch for changes in the status and update classes accordingly."""
52
+ self._update_status_class(status)
53
+ if status == MessageStatus.EXECUTING:
54
+ self.blink_timer.resume()
55
+ else:
56
+ self._prefix_visible = True
57
+ self.blink_timer.pause()
58
+
59
+ def watch_text(self, text: str) -> None:
60
+ """Watch for changes in the text and update the header."""
61
+ if self.use_markdown:
62
+ self.query_one(".text", Markdown).update(text)
63
+ else:
64
+ # Use Content to prevent markup interpretation
65
+ self.query_one(".text", Static).update(Content(text))
66
+
67
+ def watch__prefix_visible(self, visible: bool) -> None:
68
+ """Watch for changes in the prefix visibility."""
69
+ self.query_one(".prefix").visible = visible
70
+
71
+ def compose(self) -> ComposeResult:
72
+ """Create child widgets for the message header."""
73
+ yield Static(self.prefix, classes="prefix")
74
+ if self.use_markdown:
75
+ yield Markdown(self.text, classes="text")
76
+ else:
77
+ # Use Content to prevent markup interpretation of square brackets
78
+ yield Static(Content(self.text), classes="text")
79
+
80
+ def _toggle_cursor_blink_visible(self) -> None:
81
+ """Toggle visibility of the cursor for the purposes of 'cursor blink'."""
82
+ self._prefix_visible = not self._prefix_visible
83
+ # self.query_one(".prefix").visible = self._prefix_visible
84
+
85
+ def _on_mount(self, event) -> None:
86
+ self.blink_timer = self.set_interval(
87
+ 0.5,
88
+ self._toggle_cursor_blink_visible,
89
+ pause=(self.status != MessageStatus.EXECUTING),
90
+ )
91
+
92
+
93
+ class BaseMessage(Widget):
94
+ """Base class for all message widgets."""
95
+
96
+ status: reactive[MessageStatus] = reactive(MessageStatus.IDLE)
97
+
98
+ def __init__(self, status: MessageStatus = MessageStatus.IDLE, **kwargs) -> None:
99
+ """
100
+ Construct a BaseMessage.
101
+
102
+ Args:
103
+ status: The status of the message.
104
+ **kwargs: Additional keyword arguments for Widget.
105
+ """
106
+ super().__init__(**kwargs)
107
+ self.set_reactive(BaseMessage.status, status)
108
+ self.add_class("message")
109
+
110
+ def get_header_params(self) -> tuple[str, str, bool]:
111
+ """
112
+ Get parameters for MessageHeader.
113
+
114
+ Returns:
115
+ A tuple of (prefix, text, use_markdown).
116
+ """
117
+ raise NotImplementedError("Subclasses must implement get_header_params")
118
+
119
+ def compose(self) -> ComposeResult:
120
+ """Create child widgets for the message."""
121
+ prefix, text, use_markdown = self.get_header_params()
122
+ yield MessageHeader(prefix, text, status=self.status, use_markdown=use_markdown)
123
+
124
+ def watch_status(self, status: MessageStatus) -> None:
125
+ """Watch for changes in the status and update classes accordingly."""
126
+ self.query_one(MessageHeader).status = status
127
+
128
+
129
+ class UserMessage(BaseMessage):
130
+ """A widget to display user messages."""
131
+
132
+ def __init__(self, text: str, status: MessageStatus = MessageStatus.IDLE, **kwargs) -> None:
133
+ """
134
+ Construct a UserMessage.
135
+
136
+ Args:
137
+ text: The text to display.
138
+ status: The status of the message.
139
+ **kwargs: Additional keyword arguments for Widget.
140
+ """
141
+ super().__init__(status=status, **kwargs)
142
+ self.text = text
143
+
144
+ def get_header_params(self) -> tuple[str, str, bool]:
145
+ """Get parameters for MessageHeader."""
146
+ return (">", self.text, False)
147
+
148
+
149
+ class AgentMessage(BaseMessage):
150
+ """A widget to display agent messages."""
151
+
152
+ text: reactive[str] = reactive("")
153
+
154
+ def __init__(self, text: str, status: MessageStatus = MessageStatus.IDLE, **kwargs) -> None:
155
+ """
156
+ Construct an AgentMessage.
157
+
158
+ Args:
159
+ text: The text to display.
160
+ status: The status of the message.
161
+ **kwargs: Additional keyword arguments for Widget.
162
+ """
163
+ super().__init__(status=status, **kwargs)
164
+ self.set_reactive(AgentMessage.text, text)
165
+
166
+ def get_header_params(self) -> tuple[str, str, bool]:
167
+ """Get parameters for MessageHeader."""
168
+ return ("⏺", self.text, True)
169
+
170
+ def update(self, text: str, status: MessageStatus | None = None) -> None:
171
+ """Update the text of the agent message."""
172
+ self.text = text
173
+ if status is not None:
174
+ self.status = status
175
+
176
+ def watch_text(self, text: str) -> None:
177
+ """Watch for changes in the text and update the header."""
178
+ self.query_one(MessageHeader).text = text
179
+
180
+
181
+ class SystemMessage(BaseMessage):
182
+ """A widget to display system messages."""
183
+
184
+ def __init__(self, text: str, status: MessageStatus = MessageStatus.SUCCESS, **kwargs) -> None:
185
+ """
186
+ Construct a SystemMessage.
187
+
188
+ Args:
189
+ text: The text to display.
190
+ status: The status of the message.
191
+ **kwargs: Additional keyword arguments for Widget.
192
+ """
193
+ super().__init__(status=status, **kwargs)
194
+ self.text = text
195
+ self.add_class("system-message")
196
+
197
+ def get_header_params(self) -> tuple[str, str, bool]:
198
+ """Get parameters for MessageHeader."""
199
+ return ("!", self.text, False)
200
+
201
+
202
+ class ReasoningMessage(BaseMessage):
203
+ """A widget to display reasoning summaries from AI agents."""
204
+
205
+ text: reactive[str] = reactive("")
206
+
207
+ def __init__(self, text: str = "", status: MessageStatus = MessageStatus.IDLE, **kwargs) -> None:
208
+ """
209
+ Construct a ReasoningMessage.
210
+
211
+ Args:
212
+ text: The reasoning summary text to display.
213
+ status: The status of the message.
214
+ **kwargs: Additional keyword arguments for Widget.
215
+ """
216
+ super().__init__(status=status, **kwargs)
217
+ self.set_reactive(ReasoningMessage.text, text)
218
+ self.add_class("reasoning-message")
219
+
220
+ def get_header_params(self) -> tuple[str, str, bool]:
221
+ """Get parameters for MessageHeader."""
222
+ return ("*", self.text, True)
223
+
224
+ def update(self, text: str, status: MessageStatus | None = None) -> None:
225
+ """Update the text of the reasoning message."""
226
+ self.text = text
227
+ if status is not None:
228
+ self.status = status
229
+
230
+ def watch_text(self, text: str) -> None:
231
+ """Watch for changes in the text and update the header."""
232
+ self.query_one(MessageHeader).text = text
@@ -0,0 +1,85 @@
1
+ MessageHeader {
2
+ height: auto;
3
+ layout: horizontal;
4
+
5
+ Static.prefix {
6
+ width: 2;
7
+ height: 1;
8
+ }
9
+
10
+ .text {
11
+ width: 1fr;
12
+ }
13
+
14
+ Markdown {
15
+ padding: 0;
16
+ background: $background;
17
+
18
+ MarkdownFence {
19
+ margin: 0;
20
+ padding: -1 0 0 0;
21
+ max-height: 100%;
22
+ }
23
+
24
+ MarkdownFence {
25
+ background: $background;
26
+
27
+ &:light {
28
+ background: $background;
29
+ }
30
+ }
31
+
32
+ MarkdownFence > * {
33
+ width: 1fr;
34
+ }
35
+
36
+ &> MarkdownParagraph:last-child {
37
+ margin: 0 0 0 0;
38
+ }
39
+ }
40
+ }
41
+
42
+ MessageHeader.status-executing > .prefix {
43
+ color: $text;
44
+ }
45
+
46
+ MessageHeader.status-success > .prefix {
47
+ color: $success;
48
+ }
49
+
50
+ MessageHeader.status-error > .prefix {
51
+ color: $error;
52
+ }
53
+
54
+ .message {
55
+ height: auto;
56
+ padding-bottom: 1;
57
+ }
58
+
59
+ UserMessage {
60
+ color: $text-muted;
61
+ }
62
+
63
+ AgentMessage {
64
+ color: $text;
65
+ }
66
+
67
+ SystemMessage {
68
+ color: $accent;
69
+ }
70
+
71
+ SystemMessage > MessageHeader > .prefix {
72
+ color: $accent;
73
+ }
74
+
75
+ ReasoningMessage {
76
+ color: $text-muted;
77
+
78
+ MessageHeader.status-executing > .prefix {
79
+ color: $text-muted;
80
+ }
81
+
82
+ MessageHeader > Markdown {
83
+ color: $text-muted;
84
+ }
85
+ }
@@ -0,0 +1,121 @@
1
+ """Factory for creating tool-specific message widgets.
2
+
3
+ This module provides a centralized way to create the appropriate message widget
4
+ based on tool name and arguments, avoiding duplication between stream handling
5
+ and session loading.
6
+ """
7
+
8
+ import contextlib
9
+ import json
10
+ from typing import Any
11
+
12
+ from vibecore.widgets.messages import MessageStatus
13
+ from vibecore.widgets.tool_messages import (
14
+ BaseToolMessage,
15
+ MCPToolMessage,
16
+ PythonToolMessage,
17
+ ReadToolMessage,
18
+ TaskToolMessage,
19
+ TodoWriteToolMessage,
20
+ ToolMessage,
21
+ WriteToolMessage,
22
+ )
23
+
24
+
25
+ def create_tool_message(
26
+ tool_name: str,
27
+ arguments: str,
28
+ output: str | None = None,
29
+ status: MessageStatus = MessageStatus.EXECUTING,
30
+ ) -> BaseToolMessage:
31
+ """Create the appropriate tool message widget based on tool name.
32
+
33
+ This factory function centralizes the logic for creating tool-specific
34
+ message widgets, ensuring consistency between streaming and session loading.
35
+
36
+ Args:
37
+ tool_name: Name of the tool being called
38
+ arguments: JSON string of tool arguments
39
+ output: Optional output from tool execution
40
+ status: Status of the tool execution
41
+
42
+ Returns:
43
+ The appropriate tool message widget for the given tool
44
+ """
45
+ # Try to parse arguments for specific tool types
46
+ args_dict: dict[str, Any] = {}
47
+ with contextlib.suppress(json.JSONDecodeError, KeyError):
48
+ args_dict = json.loads(arguments)
49
+
50
+ # Check if this is an MCP tool based on the naming pattern
51
+ if tool_name.startswith("mcp__"):
52
+ # Extract server name and original tool name from the pattern: mcp__servername__toolname
53
+ parts = tool_name.split("__", 2) # Split into at most 3 parts
54
+ if len(parts) == 3:
55
+ _, server_name, original_tool_name = parts
56
+ if output is not None:
57
+ return MCPToolMessage(
58
+ server_name=server_name,
59
+ tool_name=original_tool_name,
60
+ arguments=arguments,
61
+ output=output,
62
+ status=status,
63
+ )
64
+ else:
65
+ return MCPToolMessage(
66
+ server_name=server_name,
67
+ tool_name=original_tool_name,
68
+ arguments=arguments,
69
+ status=status,
70
+ )
71
+ else:
72
+ # Malformed MCP tool name, fall back to generic tool message
73
+ if output is not None:
74
+ return ToolMessage(tool_name=tool_name, command=arguments, output=output, status=status)
75
+ else:
76
+ return ToolMessage(tool_name=tool_name, command=arguments, status=status)
77
+
78
+ # Create tool-specific messages based on tool name
79
+ elif tool_name == "execute_python":
80
+ code = args_dict.get("code", "") if args_dict else ""
81
+ if output is not None:
82
+ return PythonToolMessage(code=code, output=output, status=status)
83
+ else:
84
+ return PythonToolMessage(code=code, status=status)
85
+
86
+ elif tool_name == "todo_write":
87
+ todos = args_dict.get("todos", []) if args_dict else []
88
+ if output is not None:
89
+ return TodoWriteToolMessage(todos=todos, output=output, status=status)
90
+ else:
91
+ return TodoWriteToolMessage(todos=todos, status=status)
92
+
93
+ elif tool_name == "read":
94
+ file_path = args_dict.get("file_path", "") if args_dict else ""
95
+ if output is not None:
96
+ return ReadToolMessage(file_path=file_path, output=output, status=status)
97
+ else:
98
+ return ReadToolMessage(file_path=file_path, status=status)
99
+
100
+ elif tool_name == "task":
101
+ description = args_dict.get("description", "") if args_dict else ""
102
+ prompt = args_dict.get("prompt", "") if args_dict else ""
103
+ if output is not None:
104
+ return TaskToolMessage(description=description, prompt=prompt, output=output, status=status)
105
+ else:
106
+ return TaskToolMessage(description=description, prompt=prompt, status=status)
107
+
108
+ elif tool_name == "write":
109
+ file_path = args_dict.get("file_path", "") if args_dict else ""
110
+ content = args_dict.get("content", "") if args_dict else ""
111
+ if output is not None:
112
+ return WriteToolMessage(file_path=file_path, content=content, output=output, status=status)
113
+ else:
114
+ return WriteToolMessage(file_path=file_path, content=content, status=status)
115
+
116
+ # Default to generic ToolMessage for all other tools
117
+ else:
118
+ if output is not None:
119
+ return ToolMessage(tool_name=tool_name, command=arguments, output=output, status=status)
120
+ else:
121
+ return ToolMessage(tool_name=tool_name, command=arguments, status=status)