Skip to content

Commit be5c3ba

Browse files
authored
Merge pull request #626 from cecli-dev/v1.0.2
V1.0.2
2 parents a4f09aa + 2285191 commit be5c3ba

46 files changed

Lines changed: 3868 additions & 1542 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cecli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from packaging import version
22

3-
__version__ = "1.0.1.dev"
3+
__version__ = "1.0.2.dev"
44
safe_version = __version__
55

66
try:

cecli/coders/base_coder.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import cecli.prompts.utils.system as prompts
3535
from cecli import __version__, models, urls, utils
3636
from cecli.commands import Commands, SwitchCoderSignal
37+
from cecli.decoding import safe_open
3738
from cecli.exceptions import LiteLLMExceptions
3839
from cecli.helpers import command_parser, coroutines, nested, responses
3940
from cecli.helpers.conversation import ConversationService, MessageTag
@@ -245,6 +246,7 @@ def total_cached_tokens(self, value):
245246
model_kwargs = {}
246247
cost_multiplier = 1
247248
stop_on_empty = True
249+
error_code = None
248250

249251
# Task coordination state variables
250252
input_running = False
@@ -1429,7 +1431,7 @@ def get_images_message(self, fnames):
14291431
if not mime_type:
14301432
continue
14311433

1432-
with open(fname, "rb") as image_file:
1434+
with safe_open(fname, "rb") as image_file:
14331435
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
14341436
image_url = f"data:{mime_type};base64,{encoded_string}"
14351437
rel_fname = self.get_rel_fname(fname)
@@ -1873,7 +1875,7 @@ async def run_one(self, user_message, preproc):
18731875
if not self.commands.is_command(user_message):
18741876
ConversationService.get_chunks(self).flush_removals()
18751877
self.last_user_message = user_message
1876-
1878+
self.error_code = None
18771879
# Fire memorizer after each user request
18781880
# if self.auto_memory and self.edit_format not in ["subagent"]:
18791881
# from cecli.helpers.memory.utils import invoke_memorizer
@@ -3648,13 +3650,15 @@ async def send(self, messages, model=None, functions=None, tools=None):
36483650
self.calculate_and_show_tokens_and_cost(messages, completion)
36493651

36503652
except litellm_ex.exceptions_tuple() as err:
3653+
self.error_code = 1
36513654
ex_info = litellm_ex.get_ex_info(err)
36523655
if ex_info.name == "ContextWindowExceededError":
36533656
# Still calculate costs for context window errors
36543657
self.token_profiler.on_error()
36553658
self.calculate_and_show_tokens_and_cost(messages, completion)
36563659
raise
36573660
except (KeyboardInterrupt, asyncio.CancelledError) as kbi:
3661+
self.error_code = 130 # apparently standard?
36583662
self.keyboard_interrupt()
36593663
raise kbi
36603664
finally:
@@ -3755,7 +3759,7 @@ async def show_send_output_stream(self, completion):
37553759
completion, self.interrupt_event
37563760
):
37573761
if self.args.debug:
3758-
with open(".cecli/logs/chunks.log", "a") as f:
3762+
with safe_open(".cecli/logs/chunks.log", "a") as f:
37593763
print(chunk, file=f)
37603764

37613765
# Check if confirmation is in progress and wait if needed

cecli/commands/history_search.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from cecli.commands.utils.base_command import BaseCommand
55
from cecli.commands.utils.helpers import format_command_result
6+
from cecli.decoding import safe_open
67
from cecli.utils import run_fzf
78

89

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

7071
try:
71-
with open(file_path, "r") as f:
72+
with safe_open(file_path, "r") as f:
7273
content = f.read()
7374
except (OSError, IOError):
7475
return []

cecli/commands/terminal_setup.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from cecli.commands.utils.base_command import BaseCommand
1414
from cecli.commands.utils.helpers import format_command_result
15+
from cecli.decoding import safe_open
1516

1617

1718
class TerminalSetupCommand(BaseCommand):
@@ -177,7 +178,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
177178
data = {
178179
"keyboard": {"bindings": [{"key": "Return", "mods": "Shift", "chars": "\n"}]}
179180
}
180-
with open(path, "w", encoding="utf-8") as f:
181+
with safe_open(path, "w") as f:
181182
tomlkit.dump(data, f)
182183
io.tool_output("Created Alacritty config with shift+enter binding.")
183184
return True
@@ -189,7 +190,7 @@ def _update_alacritty(cls, path, io, dry_run=False):
189190
io.tool_output(f"DRY-RUN: Would check Alacritty config at {path}")
190191
io.tool_output(f"DRY-RUN: Would add binding: {new_binding}")
191192
try:
192-
with open(path, "r", encoding="utf-8") as f:
193+
with safe_open(path, "r") as f:
193194
data = tomlkit.load(f)
194195

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

224225
try:
225-
with open(path, "r", encoding="utf-8") as f:
226+
with safe_open(path, "r") as f:
226227
data = tomlkit.load(f)
227228

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

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

259260
io.tool_output("Updated Alacritty config.")
@@ -280,7 +281,7 @@ def _update_kitty(cls, path, io, dry_run=False):
280281
else:
281282
io.tool_output(f"Creating Kitty config at {path}")
282283
# Create Kitty config with shift+enter binding
283-
with open(path, "w", encoding="utf-8") as f:
284+
with safe_open(path, "w") as f:
284285
f.write(cls.KITTY_BINDING)
285286
io.tool_output("Created Kitty config with shift+enter binding.")
286287
return True
@@ -290,7 +291,7 @@ def _update_kitty(cls, path, io, dry_run=False):
290291
io.tool_output(f"DRY-RUN: Would append binding:\n{cls.KITTY_BINDING.strip()}")
291292
# Simulate checking for duplicates
292293
try:
293-
with open(path, "r", encoding="utf-8") as f:
294+
with safe_open(path, "r") as f:
294295
content = f.read()
295296
if "map shift+enter send_text all \\n" in content:
296297
io.tool_output("DRY-RUN: Kitty already configured.")
@@ -304,14 +305,14 @@ def _update_kitty(cls, path, io, dry_run=False):
304305

305306
cls._backup_file(path, io)
306307

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

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

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

338339
try:
339-
with open(path, "r", encoding="utf-8") as f:
340+
with safe_open(path, "r") as f:
340341
content = f.read()
341342

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

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

371372
cls._backup_file(path, io)
372-
with open(path, "a", encoding="utf-8") as f:
373+
with safe_open(path, "a") as f:
373374
f.write(f"\n{new_rule}\n")
374375
io.tool_output("Added Konsole Return+Shift rule.")
375376
return True
@@ -399,7 +400,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
399400
io.tool_output(f"Creating Windows Terminal config at {path}")
400401
# Create minimal Windows Terminal config with shift+enter binding
401402
data = {"actions": [cls.WT_ACTION], "keybindings": [cls.WT_KEYBINDING]}
402-
with open(path, "w", encoding="utf-8") as f:
403+
with safe_open(path, "w") as f:
403404
json.dump(data, f, indent=4)
404405
io.tool_output("Created Windows Terminal config with shift+enter binding.")
405406
return True
@@ -412,7 +413,7 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
412413
)
413414
# Simulate checking for duplicates
414415
try:
415-
with open(path, "r", encoding="utf-8") as f:
416+
with safe_open(path, "r") as f:
416417
data = json.load(f)
417418

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

453454
try:
454-
with open(path, "r", encoding="utf-8") as f:
455+
with safe_open(path, "r") as f:
455456
data = json.load(f)
456457

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

494495
cls._backup_file(path, io)
495496

496-
with open(path, "w", encoding="utf-8") as f:
497+
with safe_open(path, "w") as f:
497498
json.dump(data, f, indent=4)
498499
io.tool_output("Updated Windows Terminal config.")
499500
return True
@@ -522,7 +523,7 @@ def _update_vscode(cls, path, io, dry_run=False):
522523
io.tool_output(f"Creating VS Code keybindings.json at {path}")
523524
# Create file with our binding
524525
data = [cls.VSCODE_SHIFT_ENTER_BINDING]
525-
with open(path, "w", encoding="utf-8") as f:
526+
with safe_open(path, "w") as f:
526527
json.dump(data, f, indent=4)
527528
io.tool_output("Created VS Code config with shift+enter binding.")
528529
return True
@@ -536,7 +537,7 @@ def _update_vscode(cls, path, io, dry_run=False):
536537
# Simulate checking for duplicates
537538
try:
538539
content = ""
539-
with open(path, "r", encoding="utf-8") as f:
540+
with safe_open(path, "r") as f:
540541
content = f.read()
541542

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

581582
try:
582583
content = ""
583-
with open(path, "r", encoding="utf-8") as f:
584+
with safe_open(path, "r") as f:
584585
content = f.read()
585586

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

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

624625
io.tool_output("Updated VS Code config.")
@@ -647,7 +648,7 @@ def _update_vscode_settings(cls, keybindings_path, io, dry_run=False):
647648
try:
648649
if settings_path.exists():
649650
content = ""
650-
with open(settings_path, "r", encoding="utf-8") as f:
651+
with safe_open(settings_path, "r") as f:
651652
content = f.read()
652653

653654
content_no_comments = cls._strip_json_comments(content)
@@ -703,7 +704,7 @@ def _update_vscode_settings(cls, keybindings_path, io, dry_run=False):
703704
if settings_path.exists():
704705
cls._backup_file(settings_path, io)
705706
content = ""
706-
with open(settings_path, "r", encoding="utf-8") as f:
707+
with safe_open(settings_path, "r") as f:
707708
content = f.read()
708709

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

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

776777
io.tool_output("Updated VS Code settings.")

cecli/commands/utils/save_load_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from pathlib import Path
33
from typing import List
44

5+
from cecli.decoding import safe_open
6+
57

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

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

7072
try:
71-
with open(filepath, "r", encoding=self.io.encoding, errors="replace") as f:
73+
with safe_open(filepath, "r", errors="replace") as f:
7274
commands = f.readlines()
7375
return [
7476
cmd.strip() for cmd in commands if cmd.strip() and not cmd.strip().startswith("#")

cecli/commands/workspace.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22

33
from cecli.commands.utils.base_command import BaseCommand
4+
from cecli.decoding import safe_open
45

56

67
class WorkspaceCommand(BaseCommand):
@@ -26,7 +27,7 @@ async def execute(cls, io, coder, args, **kwargs):
2627
config = {}
2728
if metadata_path.exists():
2829
try:
29-
with open(metadata_path, "r") as f:
30+
with safe_open(metadata_path, "r") as f:
3031
config = json.load(f)
3132
except Exception:
3233
pass

0 commit comments

Comments
 (0)