ayechat 0.36.7__py3-none-any.whl → 0.37.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.
- aye/__main_chat__.py +14 -0
- aye/controller/llm_handler.py +35 -10
- aye/controller/repl.py +42 -1
- aye/model/index_manager/index_manager.py +28 -7
- aye/model/index_manager/index_manager_state.py +34 -7
- aye/model/version_checker.py +17 -1
- aye/model/write_validator.py +105 -0
- aye/plugins/shell_executor.py +5 -3
- aye/presenter/repl_ui.py +1 -1
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/METADATA +1 -1
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/RECORD +15 -14
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/WHEEL +1 -1
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/entry_points.txt +0 -0
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {ayechat-0.36.7.dist-info → ayechat-0.37.0.dist-info}/top_level.txt +0 -0
aye/__main_chat__.py
CHANGED
|
@@ -7,6 +7,20 @@ Launches directly into 'aye chat' mode if no arguments provided.
|
|
|
7
7
|
"""
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
+
# Fix Windows console encoding for PyInstaller frozen apps
|
|
11
|
+
# Must be done before importing anything that uses rich/typer
|
|
12
|
+
if sys.platform == 'win32':
|
|
13
|
+
import io
|
|
14
|
+
# Reconfigure stdout/stderr to use UTF-8 with error replacement
|
|
15
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
16
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
17
|
+
else:
|
|
18
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
19
|
+
if hasattr(sys.stderr, 'reconfigure'):
|
|
20
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
21
|
+
else:
|
|
22
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
|
23
|
+
|
|
10
24
|
# If no arguments provided, default to 'chat' command
|
|
11
25
|
if len(sys.argv) == 1:
|
|
12
26
|
sys.argv.append('chat')
|
aye/controller/llm_handler.py
CHANGED
|
@@ -15,6 +15,11 @@ from aye.model.snapshot import apply_updates
|
|
|
15
15
|
from aye.model.file_processor import filter_unchanged_files, make_paths_relative
|
|
16
16
|
from aye.model.models import LLMResponse
|
|
17
17
|
from aye.model.auth import get_user_config
|
|
18
|
+
from aye.model.write_validator import (
|
|
19
|
+
check_files_against_ignore_patterns,
|
|
20
|
+
is_strict_mode_enabled,
|
|
21
|
+
format_ignored_files_warning,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
_HAS_USED_RESTORE_KEY = "has_used_restore"
|
|
@@ -91,16 +96,36 @@ def process_llm_response(
|
|
|
91
96
|
if not updated_files:
|
|
92
97
|
print_no_files_changed(console)
|
|
93
98
|
else:
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
# Check files against ignore patterns (issue #50)
|
|
100
|
+
root_path = Path(conf.root) if hasattr(conf, 'root') else Path.cwd()
|
|
101
|
+
allowed_files, ignored_files = check_files_against_ignore_patterns(
|
|
102
|
+
updated_files, root_path
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Handle ignored files
|
|
106
|
+
strict_mode = is_strict_mode_enabled()
|
|
107
|
+
if ignored_files:
|
|
108
|
+
warning_msg = format_ignored_files_warning(ignored_files, strict_mode)
|
|
109
|
+
console.print(Padding(warning_msg, (1, 4, 0, 4)))
|
|
110
|
+
|
|
111
|
+
if strict_mode:
|
|
112
|
+
# In strict mode, only write allowed files
|
|
113
|
+
updated_files = allowed_files
|
|
114
|
+
# In non-strict mode, continue with all files (just warned)
|
|
115
|
+
|
|
116
|
+
if not updated_files:
|
|
117
|
+
print_no_files_changed(console)
|
|
118
|
+
else:
|
|
119
|
+
# Apply updates to the model (Model update)
|
|
120
|
+
try:
|
|
121
|
+
apply_updates(updated_files, prompt)
|
|
122
|
+
file_names = [item.get("file_name") for item in updated_files if "file_name" in item]
|
|
123
|
+
if file_names:
|
|
124
|
+
# Update the view
|
|
125
|
+
print_files_updated(console, file_names)
|
|
126
|
+
_maybe_print_restore_tip(conf, console)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
rprint(f"[red]Error applying updates:[/] {e}")
|
|
104
129
|
|
|
105
130
|
return new_chat_id
|
|
106
131
|
|
aye/controller/repl.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Optional, Any
|
|
4
|
+
from typing import Optional, Any, List
|
|
5
5
|
import shlex
|
|
6
6
|
import threading
|
|
7
7
|
import glob
|
|
@@ -252,6 +252,34 @@ def create_prompt_session(completer: Any, completion_style: str = "readline") ->
|
|
|
252
252
|
)
|
|
253
253
|
|
|
254
254
|
|
|
255
|
+
def _execute_forced_shell_command(command: str, args: List[str], conf: Any) -> None:
|
|
256
|
+
"""Execute a shell command with force flag (bypasses command validation).
|
|
257
|
+
|
|
258
|
+
Used when user prefixes input with '!' to force shell execution.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
command: The command to execute
|
|
262
|
+
args: List of arguments to pass to the command
|
|
263
|
+
conf: Configuration object with plugin_manager
|
|
264
|
+
"""
|
|
265
|
+
telemetry.record_command(command, has_args=len(args) > 0, prefix=_CMD_PREFIX)
|
|
266
|
+
shell_response = conf.plugin_manager.handle_command(
|
|
267
|
+
"execute_shell_command",
|
|
268
|
+
{"command": command, "args": args, "force": True}
|
|
269
|
+
)
|
|
270
|
+
if shell_response is not None:
|
|
271
|
+
if "stdout" in shell_response or "stderr" in shell_response:
|
|
272
|
+
if shell_response.get("stdout", "").strip():
|
|
273
|
+
rprint(shell_response["stdout"])
|
|
274
|
+
if shell_response.get("stderr", "").strip():
|
|
275
|
+
rprint(f"[yellow]{shell_response['stderr']}[/]")
|
|
276
|
+
if "error" in shell_response:
|
|
277
|
+
rprint(f"[red]Error:[/] {shell_response['error']}")
|
|
278
|
+
elif "message" in shell_response:
|
|
279
|
+
rprint(shell_response["message"])
|
|
280
|
+
else:
|
|
281
|
+
rprint(f"[red]Error:[/] Failed to execute shell command")
|
|
282
|
+
|
|
255
283
|
|
|
256
284
|
def chat_repl(conf: Any) -> None:
|
|
257
285
|
is_first_run = run_first_time_tutorial_if_needed()
|
|
@@ -327,6 +355,14 @@ def chat_repl(conf: Any) -> None:
|
|
|
327
355
|
chat_id = new_chat_id
|
|
328
356
|
continue
|
|
329
357
|
|
|
358
|
+
# Check for '!' prefix - force shell execution
|
|
359
|
+
force_shell = False
|
|
360
|
+
if prompt.strip().startswith('!'):
|
|
361
|
+
force_shell = True
|
|
362
|
+
prompt = prompt.strip()[1:] # Remove the '!'
|
|
363
|
+
if not prompt.strip():
|
|
364
|
+
continue # Nothing after the '!', skip
|
|
365
|
+
|
|
330
366
|
if not prompt.strip():
|
|
331
367
|
continue
|
|
332
368
|
tokens = shlex.split(prompt.strip(), posix=False)
|
|
@@ -340,6 +376,11 @@ def chat_repl(conf: Any) -> None:
|
|
|
340
376
|
|
|
341
377
|
original_first, lowered_first = tokens[0], tokens[0].lower()
|
|
342
378
|
|
|
379
|
+
# If force_shell is True, execute as shell command directly and skip all other checks
|
|
380
|
+
if force_shell:
|
|
381
|
+
_execute_forced_shell_command(original_first, tokens[1:], conf)
|
|
382
|
+
continue
|
|
383
|
+
|
|
343
384
|
# Normalize slash-prefixed commands: /restore -> restore, /model -> model, etc.
|
|
344
385
|
if lowered_first.startswith('/'):
|
|
345
386
|
lowered_first = lowered_first[1:] # Remove leading slash
|
|
@@ -28,6 +28,7 @@ from .index_manager_state import (
|
|
|
28
28
|
ProgressTracker,
|
|
29
29
|
InitializationCoordinator,
|
|
30
30
|
ErrorHandler,
|
|
31
|
+
_is_corruption_error,
|
|
31
32
|
)
|
|
32
33
|
from .index_manager_executor import PhaseExecutor
|
|
33
34
|
|
|
@@ -242,7 +243,15 @@ class IndexManager: # pylint: disable=too-many-instance-attributes
|
|
|
242
243
|
deleted = get_deleted_files(current_paths, old_index)
|
|
243
244
|
if deleted:
|
|
244
245
|
self._error_handler.info(f"Deleted: {len(deleted)} file(s) from index.")
|
|
245
|
-
|
|
246
|
+
try:
|
|
247
|
+
vector_db.delete_from_index(self._init_coordinator.collection, deleted)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
if _is_corruption_error(e):
|
|
250
|
+
rprint(f"[yellow]Detected index corruption during delete: {e}[/]")
|
|
251
|
+
self._init_coordinator.reset_and_recover()
|
|
252
|
+
# Don't re-raise, recovery will rebuild the index
|
|
253
|
+
else:
|
|
254
|
+
raise
|
|
246
255
|
|
|
247
256
|
def _update_state_after_categorization(
|
|
248
257
|
self,
|
|
@@ -549,9 +558,21 @@ class IndexManager: # pylint: disable=too-many-instance-attributes
|
|
|
549
558
|
if not self._init_coordinator.collection:
|
|
550
559
|
return []
|
|
551
560
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
561
|
+
try:
|
|
562
|
+
return vector_db.query_index(
|
|
563
|
+
collection=self._init_coordinator.collection,
|
|
564
|
+
query_text=query_text,
|
|
565
|
+
n_results=n_results,
|
|
566
|
+
min_relevance=min_relevance
|
|
567
|
+
)
|
|
568
|
+
except Exception as e:
|
|
569
|
+
if _is_corruption_error(e):
|
|
570
|
+
rprint(f"[yellow]Detected index corruption during query: {e}[/]")
|
|
571
|
+
if self._init_coordinator.reset_and_recover():
|
|
572
|
+
# Recovery succeeded, index will rebuild in background
|
|
573
|
+
# Return empty results for this query
|
|
574
|
+
return []
|
|
575
|
+
# Recovery failed, code search disabled
|
|
576
|
+
return []
|
|
577
|
+
# Not a corruption error, re-raise
|
|
578
|
+
raise
|
|
@@ -267,6 +267,13 @@ _CORRUPTION_INDICATORS = (
|
|
|
267
267
|
"OperationalError",
|
|
268
268
|
"DatabaseError",
|
|
269
269
|
"IntegrityError",
|
|
270
|
+
# HNSW-related errors (ChromaDB internal index corruption)
|
|
271
|
+
# See: https://github.com/acrotron/aye-chat/issues/203
|
|
272
|
+
"hnsw",
|
|
273
|
+
"segment reader",
|
|
274
|
+
"compactor",
|
|
275
|
+
"executing plan",
|
|
276
|
+
"backfill",
|
|
270
277
|
)
|
|
271
278
|
|
|
272
279
|
|
|
@@ -399,22 +406,22 @@ class InitializationCoordinator:
|
|
|
399
406
|
def _attempt_recovery(self) -> bool:
|
|
400
407
|
"""
|
|
401
408
|
Attempt to recover from ChromaDB corruption.
|
|
402
|
-
|
|
409
|
+
|
|
403
410
|
Strategy:
|
|
404
411
|
1. Quarantine the corrupt chroma_db directory
|
|
405
412
|
2. Invalidate the hash index (so files get re-indexed)
|
|
406
413
|
3. Retry initialization with fresh DB
|
|
407
|
-
|
|
414
|
+
|
|
408
415
|
Returns:
|
|
409
416
|
True if recovery succeeded, False otherwise
|
|
410
417
|
"""
|
|
411
418
|
from aye.model import vector_db
|
|
412
|
-
|
|
419
|
+
|
|
413
420
|
self._recovery_attempted = True
|
|
414
421
|
timestamp = int(time.time())
|
|
415
|
-
|
|
422
|
+
|
|
416
423
|
rprint("[yellow]Attempting automatic recovery...[/]")
|
|
417
|
-
|
|
424
|
+
|
|
418
425
|
# Step 1: Quarantine corrupt ChromaDB
|
|
419
426
|
chroma_path = self.config.chroma_db_path
|
|
420
427
|
if chroma_path.exists():
|
|
@@ -429,7 +436,7 @@ class InitializationCoordinator:
|
|
|
429
436
|
shutil.rmtree(str(chroma_path), ignore_errors=True)
|
|
430
437
|
except Exception:
|
|
431
438
|
pass
|
|
432
|
-
|
|
439
|
+
|
|
433
440
|
# Step 2: Invalidate hash index (so all files get re-indexed)
|
|
434
441
|
hash_index_path = self.config.hash_index_path
|
|
435
442
|
if hash_index_path.exists():
|
|
@@ -443,7 +450,7 @@ class InitializationCoordinator:
|
|
|
443
450
|
hash_index_path.unlink(missing_ok=True)
|
|
444
451
|
except Exception:
|
|
445
452
|
pass
|
|
446
|
-
|
|
453
|
+
|
|
447
454
|
# Step 3: Retry initialization
|
|
448
455
|
try:
|
|
449
456
|
rprint("[cyan]Reinitializing code search index...[/]")
|
|
@@ -459,6 +466,26 @@ class InitializationCoordinator:
|
|
|
459
466
|
self.collection = None
|
|
460
467
|
return False
|
|
461
468
|
|
|
469
|
+
def reset_and_recover(self) -> bool:
|
|
470
|
+
"""
|
|
471
|
+
Reset state and attempt recovery from corruption detected during operations.
|
|
472
|
+
|
|
473
|
+
This is called when corruption is detected during query/update operations
|
|
474
|
+
(not during initialization). It resets the initialization state and
|
|
475
|
+
attempts recovery.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
True if recovery succeeded, False otherwise
|
|
479
|
+
"""
|
|
480
|
+
with self._lock:
|
|
481
|
+
# Reset state to allow re-initialization
|
|
482
|
+
self._is_initialized = False
|
|
483
|
+
self._recovery_attempted = False
|
|
484
|
+
self.collection = None
|
|
485
|
+
|
|
486
|
+
# Now attempt recovery
|
|
487
|
+
return self._attempt_recovery()
|
|
488
|
+
|
|
462
489
|
|
|
463
490
|
# =============================================================================
|
|
464
491
|
# Error Handler
|
aye/model/version_checker.py
CHANGED
|
@@ -32,7 +32,23 @@ def _ssl_verify() -> bool:
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def get_current_version() -> str:
|
|
35
|
-
"""Get the current installed version of the aye package.
|
|
35
|
+
"""Get the current installed version of the aye package.
|
|
36
|
+
|
|
37
|
+
For PyInstaller frozen builds, reads from the baked-in _frozen_version module.
|
|
38
|
+
For normal pip installs, uses importlib.metadata.
|
|
39
|
+
"""
|
|
40
|
+
import sys
|
|
41
|
+
|
|
42
|
+
# Check if running as a PyInstaller frozen executable
|
|
43
|
+
if getattr(sys, 'frozen', False):
|
|
44
|
+
try:
|
|
45
|
+
from aye._frozen_version import __version__
|
|
46
|
+
return __version__
|
|
47
|
+
except ImportError:
|
|
48
|
+
# Frozen build without version file - shouldn't happen in CI builds
|
|
49
|
+
return "0.0.0"
|
|
50
|
+
|
|
51
|
+
# Normal pip install - use importlib.metadata
|
|
36
52
|
try:
|
|
37
53
|
from importlib.metadata import version, packages_distributions
|
|
38
54
|
# Find which distribution provides the 'aye' package
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Validates file writes against ignore patterns.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to check if files being written
|
|
4
|
+
match .gitignore or .ayeignore patterns, and optionally block such writes.
|
|
5
|
+
|
|
6
|
+
See: https://github.com/acrotron/aye-chat/issues/50
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Dict, Tuple
|
|
11
|
+
|
|
12
|
+
from aye.model.ignore_patterns import load_ignore_patterns
|
|
13
|
+
from aye.model.auth import get_user_config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Config key for strict mode (block writes to ignored files)
|
|
17
|
+
BLOCK_IGNORED_WRITES_KEY = "block_ignored_file_writes"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_strict_mode_enabled() -> bool:
|
|
21
|
+
"""Check if strict mode is enabled (block writes to ignored files).
|
|
22
|
+
|
|
23
|
+
Can be set via:
|
|
24
|
+
- Environment variable: AYE_BLOCK_IGNORED_FILE_WRITES=on
|
|
25
|
+
- Config file (~/.ayecfg): block_ignored_file_writes=on
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if strict mode is enabled, False otherwise (default)
|
|
29
|
+
"""
|
|
30
|
+
value = get_user_config(BLOCK_IGNORED_WRITES_KEY, "off")
|
|
31
|
+
return str(value).lower() in ("on", "true", "1", "yes")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_files_against_ignore_patterns(
|
|
35
|
+
files: List[Dict[str, str]],
|
|
36
|
+
root_path: Path
|
|
37
|
+
) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
|
|
38
|
+
"""Check which files match ignore patterns.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
files: List of file dicts with 'file_name' and 'file_content' keys
|
|
42
|
+
root_path: Project root path for loading ignore patterns
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Tuple of (allowed_files, ignored_files)
|
|
46
|
+
- allowed_files: Files that don't match any ignore pattern
|
|
47
|
+
- ignored_files: Files that match ignore patterns
|
|
48
|
+
"""
|
|
49
|
+
if not files:
|
|
50
|
+
return [], []
|
|
51
|
+
|
|
52
|
+
ignore_spec = load_ignore_patterns(root_path)
|
|
53
|
+
|
|
54
|
+
allowed_files = []
|
|
55
|
+
ignored_files = []
|
|
56
|
+
|
|
57
|
+
for file_dict in files:
|
|
58
|
+
file_name = file_dict.get("file_name", "")
|
|
59
|
+
if not file_name:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Normalize path for matching
|
|
63
|
+
# The pathspec library expects forward slashes
|
|
64
|
+
rel_path = file_name.replace("\\", "/")
|
|
65
|
+
|
|
66
|
+
if ignore_spec.match_file(rel_path):
|
|
67
|
+
ignored_files.append(file_dict)
|
|
68
|
+
else:
|
|
69
|
+
allowed_files.append(file_dict)
|
|
70
|
+
|
|
71
|
+
return allowed_files, ignored_files
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def format_ignored_files_warning(
|
|
75
|
+
ignored_files: List[Dict[str, str]],
|
|
76
|
+
strict_mode: bool
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Format a warning message about ignored files.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
ignored_files: List of file dicts that match ignore patterns
|
|
82
|
+
strict_mode: Whether strict mode is enabled
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Formatted warning message
|
|
86
|
+
"""
|
|
87
|
+
file_names = [f.get("file_name", "unknown") for f in ignored_files]
|
|
88
|
+
file_list = ", ".join(file_names)
|
|
89
|
+
|
|
90
|
+
if strict_mode:
|
|
91
|
+
msg = (
|
|
92
|
+
f"[yellow]Blocked write to ignored file(s): {file_list}[/]\n"
|
|
93
|
+
"[dim]These files match patterns in .gitignore or .ayeignore and were "
|
|
94
|
+
"not written.[/]"
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
msg = (
|
|
98
|
+
f"[yellow]Warning: Writing to ignored file(s): {file_list}[/]\n"
|
|
99
|
+
"[dim]These files match patterns in .gitignore or .ayeignore. "
|
|
100
|
+
"Since they weren't read into context, their original content will be overwritten.[/]\n"
|
|
101
|
+
"[dim]To block writes to ignored files, set [bold]block_ignored_file_writes=on[/bold] "
|
|
102
|
+
"in ~/.ayecfg or use [bold]AYE_BLOCK_IGNORED_FILE_WRITES=on[/bold] environment variable.[/]"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return msg
|
aye/plugins/shell_executor.py
CHANGED
|
@@ -10,7 +10,7 @@ from rich import print as rprint
|
|
|
10
10
|
|
|
11
11
|
class ShellExecutorPlugin(Plugin):
|
|
12
12
|
name = "shell_executor"
|
|
13
|
-
version = "1.0.
|
|
13
|
+
version = "1.0.3" # Added force parameter for ! prefix execution
|
|
14
14
|
premium = "free"
|
|
15
15
|
|
|
16
16
|
# Known interactive commands that require a TTY (add more as needed)
|
|
@@ -143,15 +143,17 @@ class ShellExecutorPlugin(Plugin):
|
|
|
143
143
|
"returncode": e.returncode
|
|
144
144
|
}
|
|
145
145
|
except FileNotFoundError:
|
|
146
|
-
return
|
|
146
|
+
return {"error": f"Command not found: {command}", "stdout": "", "stderr": "", "returncode": 127}
|
|
147
147
|
|
|
148
148
|
def on_command(self, command_name: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
149
149
|
"""Handle shell command execution through plugin system."""
|
|
150
150
|
if command_name == "execute_shell_command":
|
|
151
151
|
command = params.get("command", "")
|
|
152
152
|
args = params.get("args", [])
|
|
153
|
+
force = params.get("force", False)
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
# If force is False, validate the command exists first
|
|
156
|
+
if not force and not self._is_valid_command(command):
|
|
155
157
|
return None # Command not found or not executable
|
|
156
158
|
|
|
157
159
|
full_cmd_str = self._build_full_cmd(command, args)
|
aye/presenter/repl_ui.py
CHANGED
|
@@ -87,7 +87,7 @@ def print_help_message():
|
|
|
87
87
|
commands = [
|
|
88
88
|
# Some commands are intentionally undocumented: keep them as such.
|
|
89
89
|
("@filename", "Include a file in your prompt inline (e.g., \"explain @main.py\"). Supports wildcards (e.g., @*.py, @src/*.js)."),
|
|
90
|
-
("
|
|
90
|
+
("!command", "Force shell execution (e.g., \"!echo hello\")."),
|
|
91
91
|
("model", "Select a different model. Selection will persist between sessions."),
|
|
92
92
|
(r"verbose \[on|off]", "Toggle verbose mode to increase or decrease chattiness (on/off, persists between sessions)"),
|
|
93
93
|
(r"completion \[readline|multi]", "Switch auto-completion style (readline or multi, persists between sessions)"),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
aye/.gitignore,sha256=_Inh2v8EoMfV75AtyUtgHjURQOSbWh-uzx3EhbyoZW4,75
|
|
2
2
|
aye/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
aye/__main__.py,sha256=fj7pl0i_mLvpKAbpKtotU3zboLBTivsILSLKuH5M5Sg,5377
|
|
4
|
-
aye/__main_chat__.py,sha256=
|
|
4
|
+
aye/__main_chat__.py,sha256=R6RaidxG3Px5TaYxcoWAuIleE5KUZlceneUB6u_9UVU,1066
|
|
5
5
|
aye/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
aye/controller/command_handlers.py,sha256=C-6beHXVhW8vR_AbH4dCPl794ycFNVGuMBQiHnH4naI,13438
|
|
7
7
|
aye/controller/commands.py,sha256=sXmK_sgNBrw9Fs7mKcr93-wsu740ZlvWSisQfS-1EUE,12278
|
|
8
|
-
aye/controller/llm_handler.py,sha256=
|
|
8
|
+
aye/controller/llm_handler.py,sha256=BSBab6onF9BYiFndYW1647eEy377Vt52trzu0Qjm4bQ,5075
|
|
9
9
|
aye/controller/llm_invoker.py,sha256=p_Vk2a3YrWKwDupLfSVRinR5llDfq1Fb_f7WrYozK6M,14127
|
|
10
10
|
aye/controller/plugin_manager.py,sha256=9ZuITyA5sQJJJU-IntLQ1SsxXsDnbgZKPOF4e9VmsEU,3018
|
|
11
|
-
aye/controller/repl.py,sha256=
|
|
11
|
+
aye/controller/repl.py,sha256=UCgH4bdL3AUYjgD6KPtIOeysCNaYbs0TvEwS3Zql8kY,26552
|
|
12
12
|
aye/controller/tutorial.py,sha256=lc92jOcJOYCVrrjTEF0Suk4-8jn-ku98kTJEIL8taUA,7254
|
|
13
13
|
aye/controller/util.py,sha256=gBmktDEaY63OKhgzZHA2IFrgcWUN_Iphn3e1daEeUBI,2828
|
|
14
14
|
aye/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -26,12 +26,13 @@ aye/model/onnx_manager.py,sha256=cqTLMwxa4ddSBEvH2GJhmBiFGCAgYUeWrq6Tf9OIt4g,364
|
|
|
26
26
|
aye/model/source_collector.py,sha256=KU84H-N7Ypj5UFKFJAqg_wa_gXyQRB5alEG_5wYeV1o,5858
|
|
27
27
|
aye/model/telemetry.py,sha256=qHnwxzTjDe7gbEah89qfKM4nKvBNa7nU_vfybFJTtRo,3255
|
|
28
28
|
aye/model/vector_db.py,sha256=Bw5SKuclGR2wWKfyx7gIWS0UngjXkr5kUGnNIYtTjlw,6329
|
|
29
|
-
aye/model/version_checker.py,sha256=
|
|
29
|
+
aye/model/version_checker.py,sha256=cLAPF9tn-LyI4Q16UdDAj27BenhuzhL4K6-0nxw12IA,6169
|
|
30
|
+
aye/model/write_validator.py,sha256=_tNjR8iL56TxdgIrF2M0IaZz_tY8MiDrqd6F7lVVIlE,3345
|
|
30
31
|
aye/model/index_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
aye/model/index_manager/index_manager.py,sha256=
|
|
32
|
+
aye/model/index_manager/index_manager.py,sha256=TkPguukV3PqJSVNEVxsjrRdUuTdRPUjcCTYppaeuV1I,20912
|
|
32
33
|
aye/model/index_manager/index_manager_executor.py,sha256=rSI8OX9bFld5ta-ajfT8eWU5taYJ7ovr0hA7hH0meqI,9464
|
|
33
34
|
aye/model/index_manager/index_manager_file_ops.py,sha256=petgGU0ZtXOvLkHAQuzSbzqNIew5HygBOAy4yG6rTlk,7656
|
|
34
|
-
aye/model/index_manager/index_manager_state.py,sha256=
|
|
35
|
+
aye/model/index_manager/index_manager_state.py,sha256=LflyC8UbE8fT7Tyag1ueh7TBisPa15cRhs0JI1-jVEw,17587
|
|
35
36
|
aye/model/index_manager/index_manager_utils.py,sha256=B4NCYuScxQcG46WoUEx8mxfWKDjTlrSf3coRQCydiBM,4615
|
|
36
37
|
aye/model/snapshot/__init__.py,sha256=Lqq-W5RBGxu3_7O5DzW_qY5iOF9MhogA46BAqEdu6Mw,6958
|
|
37
38
|
aye/model/snapshot/base.py,sha256=5fzxM_85avxHwocv-w6PwlxDPzvMdTbuoUE_9P6D844,2009
|
|
@@ -44,17 +45,17 @@ aye/plugins/completer.py,sha256=qhxke5Q76P2u0LojSIL3V48RTNG5tWL-5-TK5tNutrE,1389
|
|
|
44
45
|
aye/plugins/local_model.py,sha256=Jj4bHiPYaLMx6zTrKamBPrkiGQQ787jWz0F4ojRCjlQ,14394
|
|
45
46
|
aye/plugins/offline_llm.py,sha256=qFmd1e8Lbl7yiMgXpXjOQkQTNxOk0_WXU7km2DTKXGY,13357
|
|
46
47
|
aye/plugins/plugin_base.py,sha256=t5hTOnA0dZC237BnseAgdXbOqErlSCNLUo_Uul09TSw,1673
|
|
47
|
-
aye/plugins/shell_executor.py,sha256=
|
|
48
|
+
aye/plugins/shell_executor.py,sha256=a0mlZnQeURONdtPM7iageTcQ8PiNLQbjxoY54EsS32o,7502
|
|
48
49
|
aye/plugins/slash_completer.py,sha256=MyrDTC_KwVWhtD_kpHbu0WjSjmSAWp36PwOBQczSuXA,2252
|
|
49
50
|
aye/presenter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
51
|
aye/presenter/cli_ui.py,sha256=8oHqQiMHO8hHXCTdqWoquMkJBshl2_3-YWN6SQnlbKg,8449
|
|
51
52
|
aye/presenter/diff_presenter.py,sha256=cbxfOEqGomPTDvQpKdybfYeNUD2DYVAl85j1uy5--Ww,12512
|
|
52
|
-
aye/presenter/repl_ui.py,sha256=
|
|
53
|
+
aye/presenter/repl_ui.py,sha256=J6QlcFa_qvV00NP1OS5xyhDuJcfNcCrADZq9ZFY2GKQ,7917
|
|
53
54
|
aye/presenter/streaming_ui.py,sha256=_3tBEuNH9UQ9Gyq2yuvRfX4SWVkcGMYirEUGj-MXVJ0,12768
|
|
54
55
|
aye/presenter/ui_utils.py,sha256=6KXR4_ZZZUdF5pCHrPqO8yywlQk7AOzWe-2B4Wj_-ZQ,5441
|
|
55
|
-
ayechat-0.
|
|
56
|
-
ayechat-0.
|
|
57
|
-
ayechat-0.
|
|
58
|
-
ayechat-0.
|
|
59
|
-
ayechat-0.
|
|
60
|
-
ayechat-0.
|
|
56
|
+
ayechat-0.37.0.dist-info/licenses/LICENSE,sha256=U1ou6lkMKmPo16-E9YowIu3goU7sOWKUprGo0AOA72s,1065
|
|
57
|
+
ayechat-0.37.0.dist-info/METADATA,sha256=8BCRHQJzQlqt1wq05u1XNXJrrxc42Y0Hkw0we25mBUE,7699
|
|
58
|
+
ayechat-0.37.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
59
|
+
ayechat-0.37.0.dist-info/entry_points.txt,sha256=KGsOma6szoefNN6vHozg3Pbf1fjZ7ZbmwrOiVwBd0Ik,41
|
|
60
|
+
ayechat-0.37.0.dist-info/top_level.txt,sha256=7WZL0LOx4-GKKvgU1mtI5s4Dhk2OdieVZZvVnxFJHr8,4
|
|
61
|
+
ayechat-0.37.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|