vibecore 0.3.2__py3-none-any.whl → 0.4.1__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.
- vibecore/context.py +37 -0
- vibecore/main.py +1 -1
- vibecore/settings.py +48 -0
- vibecore/tools/file/executor.py +51 -13
- vibecore/tools/file/tools.py +4 -4
- vibecore/tools/path_validator.py +251 -0
- vibecore/tools/shell/executor.py +63 -7
- vibecore/tools/shell/tools.py +4 -4
- vibecore/widgets/messages.py +15 -1
- vibecore/widgets/messages.tcss +28 -0
- {vibecore-0.3.2.dist-info → vibecore-0.4.1.dist-info}/METADATA +35 -2
- {vibecore-0.3.2.dist-info → vibecore-0.4.1.dist-info}/RECORD +15 -14
- {vibecore-0.3.2.dist-info → vibecore-0.4.1.dist-info}/WHEEL +0 -0
- {vibecore-0.3.2.dist-info → vibecore-0.4.1.dist-info}/entry_points.txt +0 -0
- {vibecore-0.3.2.dist-info → vibecore-0.4.1.dist-info}/licenses/LICENSE +0 -0
vibecore/context.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
from typing import TYPE_CHECKING, Optional
|
|
3
4
|
|
|
4
5
|
from vibecore.tools.python.manager import PythonExecutionManager
|
|
@@ -7,6 +8,7 @@ from vibecore.tools.todo.manager import TodoManager
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from vibecore.main import VibecoreApp
|
|
9
10
|
from vibecore.mcp import MCPManager
|
|
11
|
+
from vibecore.tools.path_validator import PathValidator
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
@@ -17,8 +19,43 @@ class VibecoreContext:
|
|
|
17
19
|
context_fullness: float = 0.0
|
|
18
20
|
mcp_manager: Optional["MCPManager"] = None
|
|
19
21
|
|
|
22
|
+
# Path confinement configuration
|
|
23
|
+
allowed_directories: list[Path] = field(default_factory=list)
|
|
24
|
+
path_validator: "PathValidator" = field(init=False) # Always initialized, never None
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
"""Initialize path validator with allowed directories."""
|
|
28
|
+
from vibecore.tools.path_validator import PathValidator
|
|
29
|
+
|
|
30
|
+
if not self.allowed_directories:
|
|
31
|
+
# Load from settings if not explicitly provided
|
|
32
|
+
from vibecore.settings import settings
|
|
33
|
+
|
|
34
|
+
if settings.path_confinement.enabled:
|
|
35
|
+
self.allowed_directories = settings.path_confinement.allowed_directories
|
|
36
|
+
# Add home directory if configured
|
|
37
|
+
if settings.path_confinement.allow_home:
|
|
38
|
+
self.allowed_directories.append(Path.home())
|
|
39
|
+
# Add temp directories if configured
|
|
40
|
+
if settings.path_confinement.allow_temp:
|
|
41
|
+
import tempfile
|
|
42
|
+
|
|
43
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
44
|
+
if temp_dir not in self.allowed_directories:
|
|
45
|
+
self.allowed_directories.append(temp_dir)
|
|
46
|
+
else:
|
|
47
|
+
# If path confinement is disabled, allow CWD only (but validator won't be used)
|
|
48
|
+
self.allowed_directories = [Path.cwd()]
|
|
49
|
+
|
|
50
|
+
self.path_validator = PathValidator(self.allowed_directories)
|
|
51
|
+
|
|
20
52
|
def reset_state(self) -> None:
|
|
21
53
|
"""Reset all context state for a new session."""
|
|
22
54
|
self.todo_manager = TodoManager()
|
|
23
55
|
self.python_manager = PythonExecutionManager()
|
|
24
56
|
self.context_fullness = 0.0
|
|
57
|
+
# Preserve allowed_directories across resets
|
|
58
|
+
# Re-initialize validator in case directories changed
|
|
59
|
+
from vibecore.tools.path_validator import PathValidator
|
|
60
|
+
|
|
61
|
+
self.path_validator = PathValidator(self.allowed_directories)
|
vibecore/main.py
CHANGED
|
@@ -269,7 +269,7 @@ class VibecoreApp(App):
|
|
|
269
269
|
if self.agent_status == "waiting_user_input":
|
|
270
270
|
self.message_queue.append(event.text)
|
|
271
271
|
self.user_input_event.set()
|
|
272
|
-
|
|
272
|
+
elif self.agent_status == "running":
|
|
273
273
|
# If agent is running, queue the message
|
|
274
274
|
self.message_queue.append(event.text)
|
|
275
275
|
log(f"Message queued: {event.text}")
|
vibecore/settings.py
CHANGED
|
@@ -39,6 +39,48 @@ class SessionSettings(BaseModel):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class PathConfinementSettings(BaseModel):
|
|
43
|
+
"""Configuration for path confinement."""
|
|
44
|
+
|
|
45
|
+
enabled: bool = Field(
|
|
46
|
+
default=True,
|
|
47
|
+
description="Enable path confinement for file and shell tools",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
allowed_directories: list[Path] = Field(
|
|
51
|
+
default_factory=lambda: [Path.cwd()],
|
|
52
|
+
description="List of directories that tools can access",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
allow_home: bool = Field(
|
|
56
|
+
default=False,
|
|
57
|
+
description="Allow access to user's home directory",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
allow_temp: bool = Field(
|
|
61
|
+
default=True,
|
|
62
|
+
description="Allow access to system temp directories",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
strict_mode: bool = Field(
|
|
66
|
+
default=False,
|
|
67
|
+
description="Strict mode prevents any path traversal attempts",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@field_validator("allowed_directories", mode="before")
|
|
71
|
+
@classmethod
|
|
72
|
+
def resolve_paths(cls, v: list[str | Path]) -> list[Path]:
|
|
73
|
+
"""Resolve and validate directory paths."""
|
|
74
|
+
paths = []
|
|
75
|
+
for p in v:
|
|
76
|
+
path = Path(p).expanduser().resolve()
|
|
77
|
+
if not path.exists():
|
|
78
|
+
# Create directory if it doesn't exist
|
|
79
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
paths.append(path)
|
|
81
|
+
return paths
|
|
82
|
+
|
|
83
|
+
|
|
42
84
|
class MCPServerConfig(BaseModel):
|
|
43
85
|
"""Configuration for an MCP server."""
|
|
44
86
|
|
|
@@ -155,6 +197,12 @@ class Settings(BaseSettings):
|
|
|
155
197
|
description="List of MCP servers to connect to",
|
|
156
198
|
)
|
|
157
199
|
|
|
200
|
+
# Path confinement configuration
|
|
201
|
+
path_confinement: PathConfinementSettings = Field(
|
|
202
|
+
default_factory=PathConfinementSettings,
|
|
203
|
+
description="Path confinement configuration",
|
|
204
|
+
)
|
|
205
|
+
|
|
158
206
|
rich_tool_names: list[str] = Field(
|
|
159
207
|
default_factory=list,
|
|
160
208
|
description="List of tools to render with RichToolMessage (temporary settings)",
|
vibecore/tools/file/executor.py
CHANGED
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from agents import RunContextWrapper
|
|
6
6
|
|
|
7
|
+
from vibecore.context import VibecoreContext
|
|
8
|
+
from vibecore.settings import settings
|
|
9
|
+
from vibecore.tools.file.utils import PathValidationError
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
from .utils import format_line_with_number
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def read_file(
|
|
15
|
+
ctx: RunContextWrapper[VibecoreContext], file_path: str, offset: int | None = None, limit: int | None = None
|
|
16
|
+
) -> str:
|
|
9
17
|
"""Read a file and return its contents in cat -n format.
|
|
10
18
|
|
|
11
19
|
Args:
|
|
20
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
12
21
|
file_path: The path to the file to read
|
|
13
22
|
offset: The line number to start reading from (1-based)
|
|
14
23
|
limit: The maximum number of lines to read
|
|
@@ -17,8 +26,14 @@ async def read_file(file_path: str, offset: int | None = None, limit: int | None
|
|
|
17
26
|
The file contents with line numbers, or an error message
|
|
18
27
|
"""
|
|
19
28
|
try:
|
|
20
|
-
# Validate the file path
|
|
21
|
-
|
|
29
|
+
# Validate the file path using context if path confinement is enabled
|
|
30
|
+
if settings.path_confinement.enabled:
|
|
31
|
+
validated_path = ctx.context.path_validator.validate_path(file_path, operation="read")
|
|
32
|
+
else:
|
|
33
|
+
# Fall back to simple validation against CWD
|
|
34
|
+
from .utils import validate_file_path
|
|
35
|
+
|
|
36
|
+
validated_path = validate_file_path(file_path)
|
|
22
37
|
|
|
23
38
|
# Check if file exists
|
|
24
39
|
if not validated_path.exists():
|
|
@@ -84,10 +99,13 @@ async def read_file(file_path: str, offset: int | None = None, limit: int | None
|
|
|
84
99
|
return f"Error: Unexpected error reading file: {e}"
|
|
85
100
|
|
|
86
101
|
|
|
87
|
-
async def edit_file(
|
|
102
|
+
async def edit_file(
|
|
103
|
+
ctx: RunContextWrapper[VibecoreContext], file_path: str, old_string: str, new_string: str, replace_all: bool = False
|
|
104
|
+
) -> str:
|
|
88
105
|
"""Edit a file by replacing strings.
|
|
89
106
|
|
|
90
107
|
Args:
|
|
108
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
91
109
|
file_path: The path to the file to edit
|
|
92
110
|
old_string: The text to replace
|
|
93
111
|
new_string: The text to replace it with
|
|
@@ -97,8 +115,14 @@ async def edit_file(file_path: str, old_string: str, new_string: str, replace_al
|
|
|
97
115
|
Success message or error message
|
|
98
116
|
"""
|
|
99
117
|
try:
|
|
100
|
-
# Validate the file path
|
|
101
|
-
|
|
118
|
+
# Validate the file path using context if path confinement is enabled
|
|
119
|
+
if settings.path_confinement.enabled:
|
|
120
|
+
validated_path = ctx.context.path_validator.validate_path(file_path, operation="edit")
|
|
121
|
+
else:
|
|
122
|
+
# Fall back to simple validation against CWD
|
|
123
|
+
from .utils import validate_file_path
|
|
124
|
+
|
|
125
|
+
validated_path = validate_file_path(file_path)
|
|
102
126
|
|
|
103
127
|
# Check if file exists
|
|
104
128
|
if not validated_path.exists():
|
|
@@ -158,10 +182,11 @@ async def edit_file(file_path: str, old_string: str, new_string: str, replace_al
|
|
|
158
182
|
return f"Error: Unexpected error editing file: {e}"
|
|
159
183
|
|
|
160
184
|
|
|
161
|
-
async def multi_edit_file(file_path: str, edits: list[dict[str, Any]]) -> str:
|
|
185
|
+
async def multi_edit_file(ctx: RunContextWrapper[VibecoreContext], file_path: str, edits: list[dict[str, Any]]) -> str:
|
|
162
186
|
"""Edit a file by applying multiple replacements sequentially.
|
|
163
187
|
|
|
164
188
|
Args:
|
|
189
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
165
190
|
file_path: The path to the file to edit
|
|
166
191
|
edits: List of edit operations, each containing old_string, new_string, and optional replace_all
|
|
167
192
|
|
|
@@ -169,8 +194,14 @@ async def multi_edit_file(file_path: str, edits: list[dict[str, Any]]) -> str:
|
|
|
169
194
|
Success message or error message
|
|
170
195
|
"""
|
|
171
196
|
try:
|
|
172
|
-
# Validate the file path
|
|
173
|
-
|
|
197
|
+
# Validate the file path using context if path confinement is enabled
|
|
198
|
+
if settings.path_confinement.enabled:
|
|
199
|
+
validated_path = ctx.context.path_validator.validate_path(file_path, operation="multi_edit")
|
|
200
|
+
else:
|
|
201
|
+
# Fall back to simple validation against CWD
|
|
202
|
+
from .utils import validate_file_path
|
|
203
|
+
|
|
204
|
+
validated_path = validate_file_path(file_path)
|
|
174
205
|
|
|
175
206
|
# Check if file exists
|
|
176
207
|
if not validated_path.exists():
|
|
@@ -239,10 +270,11 @@ async def multi_edit_file(file_path: str, edits: list[dict[str, Any]]) -> str:
|
|
|
239
270
|
return f"Error: Unexpected error editing file: {e}"
|
|
240
271
|
|
|
241
272
|
|
|
242
|
-
async def write_file(file_path: str, content: str) -> str:
|
|
273
|
+
async def write_file(ctx: RunContextWrapper[VibecoreContext], file_path: str, content: str) -> str:
|
|
243
274
|
"""Write content to a file.
|
|
244
275
|
|
|
245
276
|
Args:
|
|
277
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
246
278
|
file_path: The path to the file to write
|
|
247
279
|
content: The content to write to the file
|
|
248
280
|
|
|
@@ -250,8 +282,14 @@ async def write_file(file_path: str, content: str) -> str:
|
|
|
250
282
|
Success message or error message
|
|
251
283
|
"""
|
|
252
284
|
try:
|
|
253
|
-
# Validate the file path
|
|
254
|
-
|
|
285
|
+
# Validate the file path using context if path confinement is enabled
|
|
286
|
+
if settings.path_confinement.enabled:
|
|
287
|
+
validated_path = ctx.context.path_validator.validate_path(file_path, operation="write")
|
|
288
|
+
else:
|
|
289
|
+
# Fall back to simple validation against CWD
|
|
290
|
+
from .utils import validate_file_path
|
|
291
|
+
|
|
292
|
+
validated_path = validate_file_path(file_path)
|
|
255
293
|
|
|
256
294
|
# Check if it's a directory
|
|
257
295
|
if validated_path.exists() and validated_path.is_dir():
|
vibecore/tools/file/tools.py
CHANGED
|
@@ -49,7 +49,7 @@ async def read(
|
|
|
49
49
|
Returns:
|
|
50
50
|
The file contents with line numbers in cat -n format, or an error message
|
|
51
51
|
"""
|
|
52
|
-
return await read_file(file_path, offset, limit)
|
|
52
|
+
return await read_file(ctx, file_path, offset, limit)
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@function_tool
|
|
@@ -86,7 +86,7 @@ async def edit(
|
|
|
86
86
|
Returns:
|
|
87
87
|
Success message or error message
|
|
88
88
|
"""
|
|
89
|
-
return await edit_file(file_path, old_string, new_string, replace_all)
|
|
89
|
+
return await edit_file(ctx, file_path, old_string, new_string, replace_all)
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
@function_tool
|
|
@@ -153,7 +153,7 @@ async def multi_edit(
|
|
|
153
153
|
"""
|
|
154
154
|
# Convert EditOperation objects to dictionaries
|
|
155
155
|
edit_dicts = [edit.model_dump() for edit in edits]
|
|
156
|
-
return await multi_edit_file(file_path, edit_dicts)
|
|
156
|
+
return await multi_edit_file(ctx, file_path, edit_dicts)
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
@function_tool
|
|
@@ -181,4 +181,4 @@ async def write(
|
|
|
181
181
|
Returns:
|
|
182
182
|
Success message or error message
|
|
183
183
|
"""
|
|
184
|
-
return await write_file(file_path, content)
|
|
184
|
+
return await write_file(ctx, file_path, content)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Path validation module for vibecore tools.
|
|
2
|
+
|
|
3
|
+
This module provides path validation functionality to confine file and shell
|
|
4
|
+
operations to a configurable list of allowed directories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import shlex
|
|
8
|
+
from contextlib import suppress
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from textual import log
|
|
12
|
+
|
|
13
|
+
from vibecore.tools.file.utils import PathValidationError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PathValidator:
|
|
17
|
+
"""Validates paths against a list of allowed directories."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, allowed_directories: list[Path]):
|
|
20
|
+
"""Initialize with list of allowed directories.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
allowed_directories: List of directories to allow access to.
|
|
24
|
+
Defaults to [CWD] if empty.
|
|
25
|
+
"""
|
|
26
|
+
self.allowed_directories = (
|
|
27
|
+
[d.resolve() for d in allowed_directories] if allowed_directories else [Path.cwd().resolve()]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def validate_path(self, path: str | Path, operation: str = "access") -> Path:
|
|
31
|
+
"""Validate a path against allowed directories.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
path: The path to validate
|
|
35
|
+
operation: Description of the operation (for error messages)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The validated absolute Path object
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
PathValidationError: If path is outside allowed directories
|
|
42
|
+
"""
|
|
43
|
+
# Convert to Path object
|
|
44
|
+
path_obj = Path(path) if isinstance(path, str) else path
|
|
45
|
+
|
|
46
|
+
# Resolve to absolute path (follows symlinks)
|
|
47
|
+
try:
|
|
48
|
+
absolute_path = path_obj.resolve()
|
|
49
|
+
except (OSError, RuntimeError) as e:
|
|
50
|
+
# Handle cases where path resolution fails
|
|
51
|
+
raise PathValidationError(f"Cannot resolve path '{path}': {e}") from e
|
|
52
|
+
|
|
53
|
+
# Check if path is under any allowed directory
|
|
54
|
+
if not self.is_path_allowed(absolute_path):
|
|
55
|
+
allowed_dirs_str = ", ".join(f"'{d}'" for d in self.allowed_directories)
|
|
56
|
+
raise PathValidationError(
|
|
57
|
+
f"Path '{absolute_path}' is outside the allowed directories. "
|
|
58
|
+
f"Access is restricted to {allowed_dirs_str} and their subdirectories."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return absolute_path
|
|
62
|
+
|
|
63
|
+
def validate_command_paths(self, command: str) -> None:
|
|
64
|
+
"""Validate paths referenced in a shell command.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
command: The shell command to validate
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
PathValidationError: If command references paths outside allowed directories
|
|
71
|
+
"""
|
|
72
|
+
# Parse the command to extract potential file paths
|
|
73
|
+
try:
|
|
74
|
+
# First, replace shell operators with spaces around them to ensure proper splitting
|
|
75
|
+
# This handles cases like "cd /path;ls" which shlex doesn't split properly
|
|
76
|
+
for op in [";", "&&", "||", "|", "&"]:
|
|
77
|
+
command = command.replace(op, f" {op} ")
|
|
78
|
+
|
|
79
|
+
# Use shlex to properly parse the command
|
|
80
|
+
tokens = shlex.split(command)
|
|
81
|
+
except ValueError as e:
|
|
82
|
+
# If shlex fails, the command might be malformed
|
|
83
|
+
raise PathValidationError(f"Cannot parse command: {e}") from e
|
|
84
|
+
|
|
85
|
+
# Commands that take path arguments
|
|
86
|
+
path_commands = {
|
|
87
|
+
"cat",
|
|
88
|
+
"ls",
|
|
89
|
+
"cd",
|
|
90
|
+
"cp",
|
|
91
|
+
"mv",
|
|
92
|
+
"rm",
|
|
93
|
+
"mkdir",
|
|
94
|
+
"rmdir",
|
|
95
|
+
"touch",
|
|
96
|
+
"chmod",
|
|
97
|
+
"chown",
|
|
98
|
+
"head",
|
|
99
|
+
"tail",
|
|
100
|
+
"less",
|
|
101
|
+
"more",
|
|
102
|
+
"grep",
|
|
103
|
+
"find",
|
|
104
|
+
"sed",
|
|
105
|
+
"awk",
|
|
106
|
+
"wc",
|
|
107
|
+
"du",
|
|
108
|
+
"df",
|
|
109
|
+
"tar",
|
|
110
|
+
"zip",
|
|
111
|
+
"unzip",
|
|
112
|
+
"vim",
|
|
113
|
+
"vi",
|
|
114
|
+
"nano",
|
|
115
|
+
"emacs",
|
|
116
|
+
"code",
|
|
117
|
+
"open",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Check each token that might be a path
|
|
121
|
+
current_command = None
|
|
122
|
+
piped_command = False # Track if command comes after a pipe
|
|
123
|
+
for i, token in enumerate(tokens):
|
|
124
|
+
# Skip shell operators
|
|
125
|
+
if token in ["&&", "||", ";", "|", "&", ">", ">>", "<", "2>", "&>"]:
|
|
126
|
+
if token == "|":
|
|
127
|
+
piped_command = True
|
|
128
|
+
elif token in ["&&", "||", ";"]:
|
|
129
|
+
piped_command = False
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Skip flags and options
|
|
133
|
+
if token.startswith("-"):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Check if this is a command
|
|
137
|
+
if i == 0 or tokens[i - 1] in ["&&", "||", ";", "|"]:
|
|
138
|
+
current_command = token.split("/")[-1] # Get base command name
|
|
139
|
+
# Don't validate grep/awk/sed arguments after pipes - they're patterns not paths
|
|
140
|
+
if piped_command and current_command in ["grep", "awk", "sed", "sort", "uniq", "wc"]:
|
|
141
|
+
current_command = None
|
|
142
|
+
if tokens[i - 1] in ["&&", "||", ";"]:
|
|
143
|
+
piped_command = False
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# Check for redirections
|
|
147
|
+
if i > 0 and tokens[i - 1] in [">", ">>", "<", "2>", "&>"]:
|
|
148
|
+
# This is a file path for redirection
|
|
149
|
+
self._validate_path_token(token, f"redirect to/from '{token}'")
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# Check if current command takes path arguments
|
|
153
|
+
if current_command in path_commands:
|
|
154
|
+
# Skip if it looks like an option value
|
|
155
|
+
if i > 0 and tokens[i - 1].startswith("-"):
|
|
156
|
+
continue
|
|
157
|
+
# This might be a path argument
|
|
158
|
+
self._validate_path_token(token, f"access '{token}'")
|
|
159
|
+
|
|
160
|
+
# Check for paths in other contexts (if they look like paths)
|
|
161
|
+
elif "/" in token or token in [".", "..", "~"]:
|
|
162
|
+
# This looks like a path, validate it
|
|
163
|
+
with suppress(PathValidationError):
|
|
164
|
+
# It might not be a path, just a string with slash
|
|
165
|
+
# We'll be lenient here if it fails
|
|
166
|
+
self._validate_path_token(token, f"access '{token}'")
|
|
167
|
+
|
|
168
|
+
def _validate_path_token(self, token: str, operation: str) -> None:
|
|
169
|
+
"""Validate a single path token from a command.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
token: The token that might be a path
|
|
173
|
+
operation: Description of the operation
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
PathValidationError: If the path is not allowed
|
|
177
|
+
"""
|
|
178
|
+
# Expand user home directory
|
|
179
|
+
if token.startswith("~"):
|
|
180
|
+
token = str(Path(token).expanduser())
|
|
181
|
+
|
|
182
|
+
# Skip URLs and remote paths
|
|
183
|
+
if (
|
|
184
|
+
token.startswith("http://")
|
|
185
|
+
or token.startswith("https://")
|
|
186
|
+
or token.startswith("ftp://")
|
|
187
|
+
or token.startswith("ssh://")
|
|
188
|
+
or token.startswith("git@")
|
|
189
|
+
or ":" in token.split("/")[0]
|
|
190
|
+
): # user@host:path
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# Try to validate as a path
|
|
194
|
+
try:
|
|
195
|
+
path = Path(token)
|
|
196
|
+
# If it's a relative path, resolve it from CWD
|
|
197
|
+
if not path.is_absolute():
|
|
198
|
+
path = Path.cwd() / path
|
|
199
|
+
self.validate_path(path, operation)
|
|
200
|
+
except (ValueError, OSError):
|
|
201
|
+
# Not a valid path, skip validation
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
def is_path_allowed(self, path: Path) -> bool:
|
|
205
|
+
"""Check if a path is within allowed directories.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
path: The path to check (should be absolute)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if path is allowed, False otherwise
|
|
212
|
+
"""
|
|
213
|
+
# Ensure path is absolute
|
|
214
|
+
path = path.resolve()
|
|
215
|
+
log(f"Validating path: {path}")
|
|
216
|
+
|
|
217
|
+
# Check if path is under any allowed directory
|
|
218
|
+
for allowed_dir in self.allowed_directories:
|
|
219
|
+
try:
|
|
220
|
+
# Check if path is relative to allowed_dir
|
|
221
|
+
path.relative_to(allowed_dir)
|
|
222
|
+
return True
|
|
223
|
+
except ValueError:
|
|
224
|
+
# path is not relative to this allowed_dir
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def _is_parent_of(self, parent: Path, child: Path) -> bool:
|
|
230
|
+
"""Check if parent is a parent directory of child.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
parent: Potential parent path
|
|
234
|
+
child: Potential child path
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
True if parent is a parent of child
|
|
238
|
+
"""
|
|
239
|
+
try:
|
|
240
|
+
child.relative_to(parent)
|
|
241
|
+
return True
|
|
242
|
+
except ValueError:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
def get_allowed_directories(self) -> list[Path]:
|
|
246
|
+
"""Get the list of allowed directories.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of allowed directory paths
|
|
250
|
+
"""
|
|
251
|
+
return self.allowed_directories.copy()
|
vibecore/tools/shell/executor.py
CHANGED
|
@@ -6,13 +6,20 @@ import re
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from agents import RunContextWrapper
|
|
10
|
+
|
|
11
|
+
from vibecore.context import VibecoreContext
|
|
12
|
+
from vibecore.settings import settings
|
|
9
13
|
from vibecore.tools.file.utils import PathValidationError, validate_file_path
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
async def bash_executor(
|
|
16
|
+
async def bash_executor(
|
|
17
|
+
ctx: RunContextWrapper[VibecoreContext], command: str, timeout: int | None = None
|
|
18
|
+
) -> tuple[str, int]:
|
|
13
19
|
"""Execute a bash command asynchronously.
|
|
14
20
|
|
|
15
21
|
Args:
|
|
22
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
16
23
|
command: The bash command to execute
|
|
17
24
|
timeout: Optional timeout in milliseconds (max 600000)
|
|
18
25
|
|
|
@@ -32,6 +39,13 @@ async def bash_executor(command: str, timeout: int | None = None) -> tuple[str,
|
|
|
32
39
|
# Convert timeout to seconds
|
|
33
40
|
timeout_seconds = timeout / 1000.0
|
|
34
41
|
|
|
42
|
+
# Validate command paths if path confinement is enabled
|
|
43
|
+
if settings.path_confinement.enabled:
|
|
44
|
+
try:
|
|
45
|
+
ctx.context.path_validator.validate_command_paths(command)
|
|
46
|
+
except PathValidationError as e:
|
|
47
|
+
return f"Error: {e}", 1
|
|
48
|
+
|
|
35
49
|
process = None
|
|
36
50
|
try:
|
|
37
51
|
# Create subprocess
|
|
@@ -66,10 +80,11 @@ async def bash_executor(command: str, timeout: int | None = None) -> tuple[str,
|
|
|
66
80
|
return f"Error executing command: {e}", 1
|
|
67
81
|
|
|
68
82
|
|
|
69
|
-
async def glob_files(pattern: str, path: str | None = None) -> list[str]:
|
|
83
|
+
async def glob_files(ctx: RunContextWrapper[VibecoreContext], pattern: str, path: str | None = None) -> list[str]:
|
|
70
84
|
"""Find files matching a glob pattern.
|
|
71
85
|
|
|
72
86
|
Args:
|
|
87
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
73
88
|
pattern: The glob pattern to match
|
|
74
89
|
path: Optional directory to search in (defaults to CWD)
|
|
75
90
|
|
|
@@ -78,7 +93,23 @@ async def glob_files(pattern: str, path: str | None = None) -> list[str]:
|
|
|
78
93
|
"""
|
|
79
94
|
try:
|
|
80
95
|
# Validate and resolve the path
|
|
81
|
-
|
|
96
|
+
if path is None:
|
|
97
|
+
search_path = Path.cwd()
|
|
98
|
+
# Validate CWD is in allowed directories if path confinement is enabled
|
|
99
|
+
if settings.path_confinement.enabled:
|
|
100
|
+
try:
|
|
101
|
+
ctx.context.path_validator.validate_path(search_path, operation="glob")
|
|
102
|
+
except PathValidationError as e:
|
|
103
|
+
return [f"Error: {e}"]
|
|
104
|
+
else:
|
|
105
|
+
# Validate the provided path
|
|
106
|
+
if settings.path_confinement.enabled:
|
|
107
|
+
try:
|
|
108
|
+
search_path = ctx.context.path_validator.validate_path(path, operation="glob")
|
|
109
|
+
except PathValidationError as e:
|
|
110
|
+
return [f"Error: {e}"]
|
|
111
|
+
else:
|
|
112
|
+
search_path = validate_file_path(path)
|
|
82
113
|
|
|
83
114
|
# Validate path is a directory
|
|
84
115
|
if not search_path.is_dir():
|
|
@@ -110,10 +141,13 @@ async def glob_files(pattern: str, path: str | None = None) -> list[str]:
|
|
|
110
141
|
return [f"Error: {e}"]
|
|
111
142
|
|
|
112
143
|
|
|
113
|
-
async def grep_files(
|
|
144
|
+
async def grep_files(
|
|
145
|
+
ctx: RunContextWrapper[VibecoreContext], pattern: str, path: str | None = None, include: str | None = None
|
|
146
|
+
) -> list[str]:
|
|
114
147
|
"""Search file contents using regular expressions.
|
|
115
148
|
|
|
116
149
|
Args:
|
|
150
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
117
151
|
pattern: The regex pattern to search for
|
|
118
152
|
path: Directory to search in (defaults to CWD)
|
|
119
153
|
include: File pattern to include (e.g. "*.js")
|
|
@@ -123,7 +157,23 @@ async def grep_files(pattern: str, path: str | None = None, include: str | None
|
|
|
123
157
|
"""
|
|
124
158
|
try:
|
|
125
159
|
# Validate and resolve the path
|
|
126
|
-
|
|
160
|
+
if path is None:
|
|
161
|
+
search_path = Path.cwd()
|
|
162
|
+
# Validate CWD is in allowed directories if path confinement is enabled
|
|
163
|
+
if settings.path_confinement.enabled:
|
|
164
|
+
try:
|
|
165
|
+
ctx.context.path_validator.validate_path(search_path, operation="grep")
|
|
166
|
+
except PathValidationError as e:
|
|
167
|
+
return [f"Error: {e}"]
|
|
168
|
+
else:
|
|
169
|
+
# Validate the provided path
|
|
170
|
+
if settings.path_confinement.enabled:
|
|
171
|
+
try:
|
|
172
|
+
search_path = ctx.context.path_validator.validate_path(path, operation="grep")
|
|
173
|
+
except PathValidationError as e:
|
|
174
|
+
return [f"Error: {e}"]
|
|
175
|
+
else:
|
|
176
|
+
search_path = validate_file_path(path)
|
|
127
177
|
|
|
128
178
|
# Validate path is a directory
|
|
129
179
|
if not search_path.is_dir():
|
|
@@ -175,10 +225,13 @@ async def grep_files(pattern: str, path: str | None = None, include: str | None
|
|
|
175
225
|
return [f"Error: {e}"]
|
|
176
226
|
|
|
177
227
|
|
|
178
|
-
async def list_directory(
|
|
228
|
+
async def list_directory(
|
|
229
|
+
ctx: RunContextWrapper[VibecoreContext], path: str, ignore: list[str] | None = None
|
|
230
|
+
) -> list[str]:
|
|
179
231
|
"""List files and directories in a given path.
|
|
180
232
|
|
|
181
233
|
Args:
|
|
234
|
+
ctx: The context wrapper containing the VibecoreContext
|
|
182
235
|
path: The absolute path to list
|
|
183
236
|
ignore: Optional list of glob patterns to ignore
|
|
184
237
|
|
|
@@ -187,7 +240,10 @@ async def list_directory(path: str, ignore: list[str] | None = None) -> list[str
|
|
|
187
240
|
"""
|
|
188
241
|
try:
|
|
189
242
|
# Validate and resolve the path
|
|
190
|
-
|
|
243
|
+
if settings.path_confinement.enabled:
|
|
244
|
+
dir_path = ctx.context.path_validator.validate_path(path, operation="list")
|
|
245
|
+
else:
|
|
246
|
+
dir_path = validate_file_path(path)
|
|
191
247
|
|
|
192
248
|
# Validate path is a directory
|
|
193
249
|
if not dir_path.is_dir():
|
vibecore/tools/shell/tools.py
CHANGED
|
@@ -58,7 +58,7 @@ async def bash(
|
|
|
58
58
|
Returns:
|
|
59
59
|
The command output or error message
|
|
60
60
|
"""
|
|
61
|
-
output, exit_code = await bash_executor(command, timeout)
|
|
61
|
+
output, exit_code = await bash_executor(ctx, command, timeout)
|
|
62
62
|
if exit_code != 0:
|
|
63
63
|
return f"{output}\nExit code: {exit_code}"
|
|
64
64
|
return output
|
|
@@ -90,7 +90,7 @@ async def glob(
|
|
|
90
90
|
Returns:
|
|
91
91
|
List of matching file paths, one per line
|
|
92
92
|
"""
|
|
93
|
-
files = await glob_files(pattern, path)
|
|
93
|
+
files = await glob_files(ctx, pattern, path)
|
|
94
94
|
if files and files[0].startswith("Error:"):
|
|
95
95
|
return files[0]
|
|
96
96
|
return "\n".join(files) if files else "No files found matching pattern"
|
|
@@ -124,7 +124,7 @@ async def grep(
|
|
|
124
124
|
Returns:
|
|
125
125
|
List of file paths containing matches, one per line
|
|
126
126
|
"""
|
|
127
|
-
files = await grep_files(pattern, path, include)
|
|
127
|
+
files = await grep_files(ctx, pattern, path, include)
|
|
128
128
|
if files and files[0].startswith("Error:"):
|
|
129
129
|
return files[0]
|
|
130
130
|
return "\n".join(files) if files else "No files found containing pattern"
|
|
@@ -150,7 +150,7 @@ async def ls(
|
|
|
150
150
|
Returns:
|
|
151
151
|
List of entries in the directory, one per line
|
|
152
152
|
"""
|
|
153
|
-
entries = await list_directory(path, ignore)
|
|
153
|
+
entries = await list_directory(ctx, path, ignore)
|
|
154
154
|
if entries and entries[0].startswith("Error:"):
|
|
155
155
|
return entries[0]
|
|
156
156
|
return "\n".join(entries) if entries else "Empty directory"
|
vibecore/widgets/messages.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
|
+
from textual.containers import Horizontal
|
|
4
5
|
from textual.content import Content
|
|
5
6
|
from textual.reactive import reactive
|
|
6
7
|
from textual.widget import Widget
|
|
7
|
-
from textual.widgets import Markdown, Static
|
|
8
|
+
from textual.widgets import Button, Markdown, Static
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class MessageStatus(StrEnum):
|
|
@@ -167,6 +168,19 @@ class AgentMessage(BaseMessage):
|
|
|
167
168
|
"""Get parameters for MessageHeader."""
|
|
168
169
|
return ("⏺", self.text, True)
|
|
169
170
|
|
|
171
|
+
def compose(self) -> ComposeResult:
|
|
172
|
+
"""Create child widgets for the agent message."""
|
|
173
|
+
prefix, text, use_markdown = self.get_header_params()
|
|
174
|
+
with Horizontal(classes="agent-message-header"):
|
|
175
|
+
yield MessageHeader(prefix, text, status=self.status, use_markdown=use_markdown)
|
|
176
|
+
yield Button("Copy", classes="copy-button", variant="primary")
|
|
177
|
+
|
|
178
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
179
|
+
"""Handle button press events."""
|
|
180
|
+
if event.button.has_class("copy-button"):
|
|
181
|
+
# Copy the markdown text to clipboard
|
|
182
|
+
self.app.copy_to_clipboard(self.text)
|
|
183
|
+
|
|
170
184
|
def update(self, text: str, status: MessageStatus | None = None) -> None:
|
|
171
185
|
"""Update the text of the agent message."""
|
|
172
186
|
self.text = text
|
vibecore/widgets/messages.tcss
CHANGED
|
@@ -62,6 +62,34 @@ UserMessage {
|
|
|
62
62
|
|
|
63
63
|
AgentMessage {
|
|
64
64
|
color: $text;
|
|
65
|
+
|
|
66
|
+
Horizontal.agent-message-header {
|
|
67
|
+
height: auto;
|
|
68
|
+
width: 1fr;
|
|
69
|
+
layers: main button;
|
|
70
|
+
|
|
71
|
+
.copy-button {
|
|
72
|
+
layer: button;
|
|
73
|
+
dock: right;
|
|
74
|
+
height: 1;
|
|
75
|
+
width: 8;
|
|
76
|
+
min-width: 8;
|
|
77
|
+
margin: 0;
|
|
78
|
+
padding: 0;
|
|
79
|
+
background: $secondary;
|
|
80
|
+
color: $text;
|
|
81
|
+
border: none;
|
|
82
|
+
|
|
83
|
+
&:hover {
|
|
84
|
+
background: $secondary-lighten-1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:focus {
|
|
88
|
+
background: $secondary-lighten-1;
|
|
89
|
+
text-style: bold;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
65
93
|
}
|
|
66
94
|
|
|
67
95
|
SystemMessage {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibecore
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Build your own AI-powered automation tools in the terminal with this extensible agent framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/serialx/vibecore
|
|
6
6
|
Project-URL: Repository, https://github.com/serialx/vibecore
|
|
@@ -414,6 +414,38 @@ uv run ruff check . && uv run ruff format --check . && uv run pyright . && uv ru
|
|
|
414
414
|
|
|
415
415
|
## Configuration
|
|
416
416
|
|
|
417
|
+
### Path Confinement (Security)
|
|
418
|
+
|
|
419
|
+
vibecore includes a path confinement system that restricts file and shell operations to specified directories for enhanced security. This prevents agents from accessing sensitive system files or directories outside your project.
|
|
420
|
+
|
|
421
|
+
#### Configuration Options
|
|
422
|
+
|
|
423
|
+
```yaml
|
|
424
|
+
# config.yaml
|
|
425
|
+
path_confinement:
|
|
426
|
+
enabled: true # Enable/disable path confinement (default: true)
|
|
427
|
+
allowed_directories: # List of allowed directories (default: [current working directory])
|
|
428
|
+
- /home/user/projects
|
|
429
|
+
- /tmp
|
|
430
|
+
allow_home: false # Allow access to user's home directory (default: false)
|
|
431
|
+
allow_temp: true # Allow access to system temp directory (default: true)
|
|
432
|
+
strict_mode: false # Strict validation mode (default: false)
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Or via environment variables:
|
|
436
|
+
```bash
|
|
437
|
+
export VIBECORE_PATH_CONFINEMENT__ENABLED=true
|
|
438
|
+
export VIBECORE_PATH_CONFINEMENT__ALLOWED_DIRECTORIES='["/home/user/projects", "/tmp"]'
|
|
439
|
+
export VIBECORE_PATH_CONFINEMENT__ALLOW_HOME=false
|
|
440
|
+
export VIBECORE_PATH_CONFINEMENT__ALLOW_TEMP=true
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
When enabled, the path confinement system:
|
|
444
|
+
- Validates all file read/write/edit operations
|
|
445
|
+
- Checks paths in shell commands before execution
|
|
446
|
+
- Resolves symlinks to prevent escapes
|
|
447
|
+
- Blocks access to files outside allowed directories
|
|
448
|
+
|
|
417
449
|
### Reasoning Effort
|
|
418
450
|
|
|
419
451
|
- Set default via env var: `VIBECORE_REASONING_EFFORT` (minimal | low | medium | high)
|
|
@@ -475,6 +507,7 @@ vibecore is built with a modular, extensible architecture:
|
|
|
475
507
|
|
|
476
508
|
## Recent Updates
|
|
477
509
|
|
|
510
|
+
- **Path Confinement**: New security feature to restrict file and shell operations to specified directories
|
|
478
511
|
- **Reasoning View**: New ReasoningMessage widget with live reasoning summaries during streaming
|
|
479
512
|
- **Context Usage Bar & CWD**: Footer shows token usage progress and current working directory
|
|
480
513
|
- **Keyboard & Commands**: Ctrl+Shift+D toggles theme, Esc cancels, Ctrl+D double-press to exit, `/help` and `/clear` commands
|
|
@@ -487,7 +520,7 @@ vibecore is built with a modular, extensible architecture:
|
|
|
487
520
|
- [x] More custom tool views (Python, Read, Todo widgets)
|
|
488
521
|
- [x] Automation (vibecore -p "prompt")
|
|
489
522
|
- [x] MCP (Model Context Protocol) support
|
|
490
|
-
- [
|
|
523
|
+
- [x] Path confinement for security
|
|
491
524
|
- [ ] Multi-agent system (agent-as-tools)
|
|
492
525
|
- [ ] Plugin system for custom tools
|
|
493
526
|
- [ ] Automated workflow
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
vibecore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
vibecore/cli.py,sha256=HU2cjvEDzMWlauoArUItBlTpsVAdgVuKFMLshtaqvUE,7119
|
|
3
|
-
vibecore/context.py,sha256=
|
|
3
|
+
vibecore/context.py,sha256=SZdWOOBKOPBRVC66Y3NohkWdxR5dbAycCHynuqhmEFo,2577
|
|
4
4
|
vibecore/flow.py,sha256=ZaKzMsz4YBvgelVzJOIHnTJzMWTmvkfvudwW_hllq6U,3384
|
|
5
|
-
vibecore/main.py,sha256=
|
|
5
|
+
vibecore/main.py,sha256=m-MYYVvbeki7j61eurjschUbaxG-_TV2Ul3jte73Cts,20177
|
|
6
6
|
vibecore/main.tcss,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
vibecore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
vibecore/settings.py,sha256=
|
|
8
|
+
vibecore/settings.py,sha256=0hugs44VOKB47ysmkJ1BqZnfR8-Lvm_8OmEt3VTUjak,8471
|
|
9
9
|
vibecore/agents/default.py,sha256=wxeP3Hsq9MVBChyMF_sNkVHCtFFXgy5ghDIxy9eH_fQ,2287
|
|
10
10
|
vibecore/agents/prompts.py,sha256=0oO9QzcytIbzgZcKJQjWD9fSRNQqBqqK5ku0fcYZZrA,324
|
|
11
11
|
vibecore/agents/task.py,sha256=cdhZDzKDA8eHHNYPhogFIKBms3oVAMvYeiBB2GqYNuE,1898
|
|
@@ -34,9 +34,10 @@ vibecore/session/loader.py,sha256=vmDwzjtedFEeWhaFa6HbygjL32-bSNXM6KccQC9hyJs,68
|
|
|
34
34
|
vibecore/session/path_utils.py,sha256=_meng4PnOR59ekPWp_WICkt8yVkokt8c6oePZvk3m-4,2544
|
|
35
35
|
vibecore/tools/__init__.py,sha256=nppfKiflvkQRUotBrj9nFU0veWex1DE_YX1fg67SRlw,37
|
|
36
36
|
vibecore/tools/base.py,sha256=POI1bM89qDWlQ5VfcdUFoIg_Tv5Rlrd6sQTRLj-4YmQ,658
|
|
37
|
+
vibecore/tools/path_validator.py,sha256=3Dob33-A6aNy92UeFAEEQME10V_P4N6ZODf7o5H6fYU,8566
|
|
37
38
|
vibecore/tools/file/__init__.py,sha256=EhdebEC6JTaiROkpItoJWK2lGEq2B5ReNL_IRqxZX5w,147
|
|
38
|
-
vibecore/tools/file/executor.py,sha256=
|
|
39
|
-
vibecore/tools/file/tools.py,sha256=
|
|
39
|
+
vibecore/tools/file/executor.py,sha256=KRwXZD_n2ypQ8m7kAz06WFgboIW8lJonOBZ_z-fZFo4,12079
|
|
40
|
+
vibecore/tools/file/tools.py,sha256=qkR_IxYHaNfjWkLjiA3Y5Uvq6jXycZWvlMAYWE_cCkM,8345
|
|
40
41
|
vibecore/tools/file/utils.py,sha256=0Gef8HZgq520pqYgsF8n4cL9FNtzA7nYEr8bBCZVnro,2356
|
|
41
42
|
vibecore/tools/python/__init__.py,sha256=bqSKgP2pY3bArCmQxOsWFflfmASq3SybOlrmZiz9y4s,35
|
|
42
43
|
vibecore/tools/python/helpers.py,sha256=y-qwCQ4aRMVUZU6F9OpjgrbzsIGn8i16idXEWR5MBNU,2918
|
|
@@ -45,8 +46,8 @@ vibecore/tools/python/tools.py,sha256=OBz9csUUk90Pq3QwHEkz49NcJhgK-3MhaCeCd-mGPO
|
|
|
45
46
|
vibecore/tools/python/backends/__init__.py,sha256=RTfU7AzlVyDSaldfVNdKAgv4759RQAl07-UFGqc70Oo,50
|
|
46
47
|
vibecore/tools/python/backends/terminal_backend.py,sha256=3PA4haJN-dyIvnudx6qfx58ThjaeT7DULnCvhacADbw,1908
|
|
47
48
|
vibecore/tools/shell/__init__.py,sha256=Ias6qmBMDK29q528VtUGtCQeYD4RU_Yx73SIAJrB8No,133
|
|
48
|
-
vibecore/tools/shell/executor.py,sha256=
|
|
49
|
-
vibecore/tools/shell/tools.py,sha256=
|
|
49
|
+
vibecore/tools/shell/executor.py,sha256=9bEXHU83fXo5zCIS0ZcOeF6ZabCcE4zaNqVGLHJnPKk,9333
|
|
50
|
+
vibecore/tools/shell/tools.py,sha256=EgbNP5d-MaGfy_UM451GHDLsYUJsSvspQUCvpKzbu9s,6849
|
|
50
51
|
vibecore/tools/task/__init__.py,sha256=Fyw33zGiBArMnPuRMm7qwSYE6ZRPCZVbHK6eIUJDiJY,112
|
|
51
52
|
vibecore/tools/task/executor.py,sha256=gRIdq0f2gjDKxnWH-b5Rbmk1H2garIs56EDYFVKfUiw,1606
|
|
52
53
|
vibecore/tools/task/tools.py,sha256=m6MBOQC3Pz07TZgd3lVAHPGQu9M-Ted-YOxQvIPrGvo,2257
|
|
@@ -73,13 +74,13 @@ vibecore/widgets/expandable.py,sha256=GIcXXzVGr4BdyATphUrHZqB30DF_WgeyrccjEIf7FW
|
|
|
73
74
|
vibecore/widgets/expandable.tcss,sha256=zmm5zDDabvXiePwwsuSGLPkxHUYunEjmwkp5XrTjxSw,1343
|
|
74
75
|
vibecore/widgets/info.py,sha256=hXtsRUOE13oHbIm9FNe1GCUX_FCht28pgT9SQWeJ69I,1567
|
|
75
76
|
vibecore/widgets/info.tcss,sha256=v30IqNt1two-ezIcm18ZEInKRKcRkAW-h-UH2r8QzSo,201
|
|
76
|
-
vibecore/widgets/messages.py,sha256=
|
|
77
|
-
vibecore/widgets/messages.tcss,sha256=
|
|
77
|
+
vibecore/widgets/messages.py,sha256=2m821HLKH2K0Tigt0rN5hMH3uoIEJdoVguLvPJvD16o,8849
|
|
78
|
+
vibecore/widgets/messages.tcss,sha256=Dhz6X1Fkj2XN9bVGVH_hBelDF7WXNE6hHMkGQRQy1QA,1911
|
|
78
79
|
vibecore/widgets/tool_message_factory.py,sha256=yrZorT4HKo5b6rWUc0dgQle7q7cvLyq8JllE772RZS0,5730
|
|
79
80
|
vibecore/widgets/tool_messages.py,sha256=hJOolN3iLTAjqfotfH1elXqsdDo1r_UHjsyRVH0GAeo,29415
|
|
80
81
|
vibecore/widgets/tool_messages.tcss,sha256=gdChmHClURqn_sD9GkcOGQcQVYvUUl75mLUYp85sKz8,8442
|
|
81
|
-
vibecore-0.
|
|
82
|
-
vibecore-0.
|
|
83
|
-
vibecore-0.
|
|
84
|
-
vibecore-0.
|
|
85
|
-
vibecore-0.
|
|
82
|
+
vibecore-0.4.1.dist-info/METADATA,sha256=-X_au5trMbYnifXziPSQvbtUUxUqH34D8Nt1phDCyBc,19550
|
|
83
|
+
vibecore-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
84
|
+
vibecore-0.4.1.dist-info/entry_points.txt,sha256=i9mOKvpz07ciV_YYisxNCYZ53_Crjkn9mciiQ3aA6QM,51
|
|
85
|
+
vibecore-0.4.1.dist-info/licenses/LICENSE,sha256=KXxxifvrcreHrZ4aOYgP-vA8DRHHueW389KKOeEbtjc,1069
|
|
86
|
+
vibecore-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|