Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cecli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from packaging import version

__version__ = "1.0.1.dev"
__version__ = "1.0.2.dev"
safe_version = __version__

try:
Expand Down
10 changes: 7 additions & 3 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import cecli.prompts.utils.system as prompts
from cecli import __version__, models, urls, utils
from cecli.commands import Commands, SwitchCoderSignal
from cecli.decoding import safe_open
from cecli.exceptions import LiteLLMExceptions
from cecli.helpers import command_parser, coroutines, nested, responses
from cecli.helpers.conversation import ConversationService, MessageTag
Expand Down Expand Up @@ -245,6 +246,7 @@ def total_cached_tokens(self, value):
model_kwargs = {}
cost_multiplier = 1
stop_on_empty = True
error_code = None

# Task coordination state variables
input_running = False
Expand Down Expand Up @@ -1429,7 +1431,7 @@ def get_images_message(self, fnames):
if not mime_type:
continue

with open(fname, "rb") as image_file:
with safe_open(fname, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
image_url = f"data:{mime_type};base64,{encoded_string}"
rel_fname = self.get_rel_fname(fname)
Expand Down Expand Up @@ -1873,7 +1875,7 @@ async def run_one(self, user_message, preproc):
if not self.commands.is_command(user_message):
ConversationService.get_chunks(self).flush_removals()
self.last_user_message = user_message

self.error_code = None
# Fire memorizer after each user request
# if self.auto_memory and self.edit_format not in ["subagent"]:
# from cecli.helpers.memory.utils import invoke_memorizer
Expand Down Expand Up @@ -3648,13 +3650,15 @@ async def send(self, messages, model=None, functions=None, tools=None):
self.calculate_and_show_tokens_and_cost(messages, completion)

except litellm_ex.exceptions_tuple() as err:
self.error_code = 1
ex_info = litellm_ex.get_ex_info(err)
if ex_info.name == "ContextWindowExceededError":
# Still calculate costs for context window errors
self.token_profiler.on_error()
self.calculate_and_show_tokens_and_cost(messages, completion)
raise
except (KeyboardInterrupt, asyncio.CancelledError) as kbi:
self.error_code = 130 # apparently standard?
self.keyboard_interrupt()
raise kbi
finally:
Expand Down Expand Up @@ -3755,7 +3759,7 @@ async def show_send_output_stream(self, completion):
completion, self.interrupt_event
):
if self.args.debug:
with open(".cecli/logs/chunks.log", "a") as f:
with safe_open(".cecli/logs/chunks.log", "a") as f:
print(chunk, file=f)

# Check if confirmation is in progress and wait if needed
Expand Down
3 changes: 2 additions & 1 deletion cecli/commands/history_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from cecli.commands.utils.base_command import BaseCommand
from cecli.commands.utils.helpers import format_command_result
from cecli.decoding import safe_open
from cecli.utils import run_fzf


Expand Down Expand Up @@ -68,7 +69,7 @@ def parse_input_history_file(cls, file_path: str) -> List[str]:
return []

try:
with open(file_path, "r") as f:
with safe_open(file_path, "r") as f:
content = f.read()
except (OSError, IOError):
return []
Expand Down
45 changes: 23 additions & 22 deletions cecli/commands/terminal_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from cecli.commands.utils.base_command import BaseCommand
from cecli.commands.utils.helpers import format_command_result
from cecli.decoding import safe_open


class TerminalSetupCommand(BaseCommand):
Expand Down Expand Up @@ -177,7 +178,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
data = {
"keyboard": {"bindings": [{"key": "Return", "mods": "Shift", "chars": "\n"}]}
}
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
tomlkit.dump(data, f)
io.tool_output("Created Alacritty config with shift+enter binding.")
return True
Expand All @@ -189,7 +190,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
io.tool_output(f"DRY-RUN: Would check Alacritty config at {path}")
io.tool_output(f"DRY-RUN: Would add binding: {new_binding}")
try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
data = tomlkit.load(f)

# Check if binding already exists
Expand Down Expand Up @@ -222,7 +223,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
cls._backup_file(path, io)

try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
data = tomlkit.load(f)

# Ensure keyboard section exists
Expand Down Expand Up @@ -253,7 +254,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
data["keyboard"]["bindings"].append(new_binding)

# Write back to file
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
tomlkit.dump(data, f)

io.tool_output("Updated Alacritty config.")
Expand All @@ -280,7 +281,7 @@ def _update_kitty(cls, path, io, dry_run=False):
else:
io.tool_output(f"Creating Kitty config at {path}")
# Create Kitty config with shift+enter binding
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
f.write(cls.KITTY_BINDING)
io.tool_output("Created Kitty config with shift+enter binding.")
return True
Expand All @@ -290,7 +291,7 @@ def _update_kitty(cls, path, io, dry_run=False):
io.tool_output(f"DRY-RUN: Would append binding:\n{cls.KITTY_BINDING.strip()}")
# Simulate checking for duplicates
try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
content = f.read()
if "map shift+enter send_text all \\n" in content:
io.tool_output("DRY-RUN: Kitty already configured.")
Expand All @@ -304,14 +305,14 @@ def _update_kitty(cls, path, io, dry_run=False):

cls._backup_file(path, io)

with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
content = f.read()

if "map shift+enter send_text all \\n" in content:
io.tool_output("Kitty already configured.")
return False

with open(path, "a", encoding="utf-8") as f:
with safe_open(path, "a") as f:
f.write(cls.KITTY_BINDING)
io.tool_output("Updated Kitty config.")
return True
Expand All @@ -336,7 +337,7 @@ def _update_konsole(cls, path, io, dry_run=False):
return False

try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
content = f.read()

import re
Expand All @@ -359,7 +360,7 @@ def _update_konsole(cls, path, io, dry_run=False):

cls._backup_file(path, io)
new_content = re.sub(pattern, new_rule, content, flags=re.MULTILINE)
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
f.write(new_content)
io.tool_output("Updated Konsole keytab rule.")
return True
Expand All @@ -369,7 +370,7 @@ def _update_konsole(cls, path, io, dry_run=False):
return True

cls._backup_file(path, io)
with open(path, "a", encoding="utf-8") as f:
with safe_open(path, "a") as f:
f.write(f"\n{new_rule}\n")
io.tool_output("Added Konsole Return+Shift rule.")
return True
Expand Down Expand Up @@ -399,7 +400,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
io.tool_output(f"Creating Windows Terminal config at {path}")
# Create minimal Windows Terminal config with shift+enter binding
data = {"actions": [cls.WT_ACTION], "keybindings": [cls.WT_KEYBINDING]}
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
json.dump(data, f, indent=4)
io.tool_output("Created Windows Terminal config with shift+enter binding.")
return True
Expand All @@ -412,7 +413,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
)
# Simulate checking for duplicates
try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
data = json.load(f)

# Check if already configured
Expand Down Expand Up @@ -451,7 +452,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
return False

try:
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
data = json.load(f)

# Check if already configured
Expand Down Expand Up @@ -493,7 +494,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):

cls._backup_file(path, io)

with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
json.dump(data, f, indent=4)
io.tool_output("Updated Windows Terminal config.")
return True
Expand Down Expand Up @@ -522,7 +523,7 @@ def _update_vscode(cls, path, io, dry_run=False):
io.tool_output(f"Creating VS Code keybindings.json at {path}")
# Create file with our binding
data = [cls.VSCODE_SHIFT_ENTER_BINDING]
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
json.dump(data, f, indent=4)
io.tool_output("Created VS Code config with shift+enter binding.")
return True
Expand All @@ -536,7 +537,7 @@ def _update_vscode(cls, path, io, dry_run=False):
# Simulate checking for duplicates
try:
content = ""
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
content = f.read()

# Strip comments before parsing
Expand Down Expand Up @@ -580,7 +581,7 @@ def _update_vscode(cls, path, io, dry_run=False):

try:
content = ""
with open(path, "r", encoding="utf-8") as f:
with safe_open(path, "r") as f:
content = f.read()

# Strip comments before parsing
Expand Down Expand Up @@ -618,7 +619,7 @@ def _update_vscode(cls, path, io, dry_run=False):
data.append(cls.VSCODE_SHIFT_ENTER_BINDING)

# Write back to file
with open(path, "w", encoding="utf-8") as f:
with safe_open(path, "w") as f:
json.dump(data, f, indent=4)

io.tool_output("Updated VS Code config.")
Expand Down Expand Up @@ -647,7 +648,7 @@ def _update_vscode_settings(cls, keybindings_path, io, dry_run=False):
try:
if settings_path.exists():
content = ""
with open(settings_path, "r", encoding="utf-8") as f:
with safe_open(settings_path, "r") as f:
content = f.read()

content_no_comments = cls._strip_json_comments(content)
Expand Down Expand Up @@ -703,7 +704,7 @@ def _update_vscode_settings(cls, keybindings_path, io, dry_run=False):
if settings_path.exists():
cls._backup_file(settings_path, io)
content = ""
with open(settings_path, "r", encoding="utf-8") as f:
with safe_open(settings_path, "r") as f:
content = f.read()

# Strip comments before parsing
Expand Down Expand Up @@ -770,7 +771,7 @@ def _update_vscode_settings(cls, keybindings_path, io, dry_run=False):
]

# Write back to file
with open(settings_path, "w", encoding="utf-8") as f:
with safe_open(settings_path, "w") as f:
json.dump(data, f, indent=4)

io.tool_output("Updated VS Code settings.")
Expand Down
6 changes: 4 additions & 2 deletions cecli/commands/utils/save_load_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from pathlib import Path
from typing import List

from cecli.decoding import safe_open


class SaveLoadManager:
"""Manager for saving and loading command files."""
Expand Down Expand Up @@ -35,7 +37,7 @@ def save_commands(self, filename: str) -> Path:
# Ensure parent directory exists
os.makedirs(filepath.parent, exist_ok=True)

with open(filepath, "w", encoding=self.io.encoding) as f:
with safe_open(filepath, "w") as f:
f.write("/drop\n")
# Write commands to add editable files
for fname in sorted(self.coder.abs_fnames):
Expand Down Expand Up @@ -68,7 +70,7 @@ def load_commands(self, filename: str) -> List[str]:
filepath = self.resolve_filepath(filename)

try:
with open(filepath, "r", encoding=self.io.encoding, errors="replace") as f:
with safe_open(filepath, "r", errors="replace") as f:
commands = f.readlines()
return [
cmd.strip() for cmd in commands if cmd.strip() and not cmd.strip().startswith("#")
Expand Down
3 changes: 2 additions & 1 deletion cecli/commands/workspace.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import subprocess

from cecli.commands.utils.base_command import BaseCommand
from cecli.decoding import safe_open


class WorkspaceCommand(BaseCommand):
Expand All @@ -26,7 +27,7 @@ async def execute(cls, io, coder, args, **kwargs):
config = {}
if metadata_path.exists():
try:
with open(metadata_path, "r") as f:
with safe_open(metadata_path, "r") as f:
config = json.load(f)
except Exception:
pass
Expand Down
Loading
Loading