Skip to content

Commit 63eb695

Browse files
committed
feat(debugger): add watch expressions and call stack panel (v0.3.5)
1 parent f066cc5 commit 63eb695

13 files changed

Lines changed: 1310 additions & 10 deletions

‎CHANGELOG.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ Alle wesentlichen Änderungen an CodeBox werden hier dokumentiert.
5757
- **Machine-Readable LLM Context (`llms.txt`)**:
5858
- Updated `llms.txt` to Stand 2026-09-23, documented 295 passed tests, NOTICE attribution, Level 1 SBOM notes, and statutory disclaimers.
5959

60+
## [0.3.5] - 2026-09-28
61+
62+
### Added
63+
- **Debugger Watch-Expressions & Call-Stack Panel (`core/debugger.py`, `ui/debug_panel.py`)**:
64+
- `core/debugger.py`: Robustes Datenmodell (`StackFrame`, `WatchExpression`), PDB-Stack-Parser (`parse_pdb_stack`) mit Unterstützung für Windows- und POSIX-Dateipfade, Erkennung des aktiven Frames (`>`) und Quelltextzeilen (`->`), PDB-Ausdrucks-Evaluierung (`parse_pdb_eval_response`) und fehlertoleranter Ausdrucksrechner (`safe_eval_expression`).
65+
- `ui/debug_panel.py`: Neues `DebugPanel` mit geteilter Ansicht (`QSplitter`), bestehend aus `WatchTreeWidget` (Variablen- und Ausdrucksüberwachung mit Spalten für Ausdruck, Wert und Typ) und `CallStackTreeWidget` (interaktiver Aufruf-Stapel mit visueller Hervorhebung des aktuellen Frames `▶`).
66+
- Vollständiges CRUD für Variablenüberwachungen: Hinzufügen per Dialog (`Ctrl+Shift+W`), Inline-Bearbeitung (`F2`), Löschen (`Entf`), Kontextmenüs und Schnellauswertungsleiste mit Sofort-Feedback.
67+
- Interaktive Stack-Navigation: Doppelklick oder `Enter` auf einen Stack-Frame öffnet direkt die Datei und navigiert zur exakten Zeile (`frameActivated` Signal verknüpft mit `open_path_at`).
68+
- `core/output.py`: Entkoppeltes Streaming von Debugger-Ausgaben über das Signal `debugOutputReceived` an das Debug-Panel zur synchronen Zustandsaktualisierung.
69+
- `core/editor.py`: Neues Signal `addWatchRequested` und Kontextmenü-Eintrag *"Zu Überwachung hinzufügen: '...' "* für markierten Code oder Symbole unter dem Cursor.
70+
- `ui/main_window.py`: Integration des `DebugPanel` als Tab 5 in den unteren Bereich (`bottom_tabs`), Tastenkürzel `Ctrl+Shift+D` zum Ein-/Ausblenden und Fokussieren, Menüeinträge unter *Ausführen* und *Ansicht*, sowie automatische Aktualisierung bei Debug-Einzelschritten.
71+
- `ui/shortcuts_dialog.py`: Registrierung der Tastenkürzel `Ctrl+Shift+D` und `Ctrl+Shift+W` im Abschnitt *Ausführen*.
72+
- `tests/test_debug_panel.py`: 12 neue automatisierte Unit- und Integrationstests für Parsing, Datenmodelle, Baum-Widgets, Shortcuts, Navigation und Hauptfenster-Integration (100% bestanden).
73+
6074
## [0.3.4] - 2026-09-26
6175

6276
### Added

‎README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
[![Python 3.10+](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org/)
88
[![Platform: Windows | Linux | macOS](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]()
99
[![CI](https://github.com/dev-bricks/CodeBox/actions/workflows/ci.yml/badge.svg)](https://github.com/dev-bricks/CodeBox/actions/workflows/ci.yml)
10-
[![Tests](https://img.shields.io/badge/tests-299%20passed%20%7C%20100%25-brightgreen.svg)]()
10+
[![Tests](https://img.shields.io/badge/tests-329%20passed%20%7C%20100%25-brightgreen.svg)]()
1111
[![Privacy: Zero-Egress](https://img.shields.io/badge/privacy-100%25%20local--first%20%7C%20zero--egress-success.svg)](SECURITY.md)
1212
[![Security Policy](https://img.shields.io/badge/security-bilingual%20policy-blue.svg)](SECURITY.md)
1313
[![Ecosystem: dev-bricks](https://img.shields.io/badge/ecosystem-dev--bricks-blue.svg)](https://github.com/dev-bricks)
1414
[![Part of: open-bricks](https://img.shields.io/badge/part%20of-open--bricks-blue.svg)](https://github.com/open-bricks)
1515
[![LSP Ready](https://img.shields.io/badge/LSP-ready-purple.svg)]()
16-
[![Version: 0.3.4](https://img.shields.io/badge/version-0.3.4-green.svg)](CHANGELOG.md)
16+
[![Version: 0.3.5](https://img.shields.io/badge/version-0.3.5-green.svg)](CHANGELOG.md)
1717
[![SBOM Level 1](https://img.shields.io/badge/SBOM-Level%201%20Audited-blue.svg)](THIRD_PARTY_LICENSES.md)
18-
[![Last Checked](https://img.shields.io/badge/last%20checked-2026--09--25-informational.svg)](llms.txt)
18+
[![Last Checked](https://img.shields.io/badge/last%20checked-2026--09--28-informational.svg)](llms.txt)
1919
[![llms.txt](https://img.shields.io/badge/llms.txt-available-green.svg)](llms.txt)
2020

2121
[Deutsch](README_de.md) | English
@@ -231,6 +231,8 @@ The following matrix benchmarks CodeBox against 4 prevalent desktop development
231231
<a id="features"></a><a id="funktionsumfang"></a>
232232
## Features & Capabilities
233233

234+
- **Debugger Watch-Expressions & Call-Stack Panel**: Interactive variable and expression monitoring (`Ctrl+Shift+W`), call stack frame inspection with direct source-line jumping, instant expression evaluation bar, and live PDB session synchronization (`Ctrl+Shift+D`).
235+
- **Snippet Manager & Tab-Trigger Expansion**: Native tab-stop navigation (`$1`, `${1:default}`, `$0`) via `Tab` / `Shift+Tab`, built-in snippet catalogs for 10 programming and markup languages, persistent custom JSON storage, and interactive `SnippetsDialog` (`Ctrl+Shift+J`).
234236
- **Quick-Open & Command Palette**: Instant file fuzzy matching (`Ctrl+P`) and interactive command palette (`Ctrl+Shift+P`) for keyboard-driven navigation.
235237
- **Git Staging & Commit Dialog**: Built-in Git staging (`git add`, `git restore --staged`), discard changes, diff inspection, and commit dialog (`Ctrl+Alt+C`) directly from the project sidebar and view menu.
236238
- **Integrated Git Diff-Viewer**: Side-by-side and unified diffs directly in the IDE (`Ctrl+Alt+D`).

‎README_de.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
[![Python 3.10+](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org/)
88
[![Plattform: Windows | Linux | macOS](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]()
99
[![CI](https://github.com/dev-bricks/CodeBox/actions/workflows/ci.yml/badge.svg)](https://github.com/dev-bricks/CodeBox/actions/workflows/ci.yml)
10-
[![Tests](https://img.shields.io/badge/tests-299%20passed%20%7C%20100%25-brightgreen.svg)]()
10+
[![Tests](https://img.shields.io/badge/tests-329%20passed%20%7C%20100%25-brightgreen.svg)]()
1111
[![Datenschutz: Zero-Egress](https://img.shields.io/badge/datenschutz-100%25%20local--first%20%7C%20zero--egress-success.svg)](SECURITY.md)
1212
[![Sicherheitsrichtlinie](https://img.shields.io/badge/sicherheit-zweisprachige%20policy-blue.svg)](SECURITY.md)
1313
[![Ökosystem: dev-bricks](https://img.shields.io/badge/ecosystem-dev--bricks-blue.svg)](https://github.com/dev-bricks)
1414
[![Dachverband: open-bricks](https://img.shields.io/badge/part%20of-open--bricks-blue.svg)](https://github.com/open-bricks)
1515
[![LSP Ready](https://img.shields.io/badge/LSP-ready-purple.svg)]()
16-
[![Version: 0.3.4](https://img.shields.io/badge/version-0.3.4-green.svg)](CHANGELOG.md)
16+
[![Version: 0.3.5](https://img.shields.io/badge/version-0.3.5-green.svg)](CHANGELOG.md)
1717
[![SBOM Level 1](https://img.shields.io/badge/SBOM-Level%201%20Auditiert-blue.svg)](THIRD_PARTY_LICENSES.md)
18-
[![Letzte Prüfung](https://img.shields.io/badge/letzte%20pr%C3%BCfung-2026--09--25-informational.svg)](llms.txt)
18+
[![Letzte Prüfung](https://img.shields.io/badge/letzte%20pr%C3%BCfung-2026--09--28-informational.svg)](llms.txt)
1919
[![llms.txt](https://img.shields.io/badge/llms.txt-verf%C3%BCgbar-green.svg)](llms.txt)
2020

2121
[English](README.md) | Deutsch
@@ -231,6 +231,8 @@ Die folgende Matrix vergleicht CodeBox mit 4 verbreiteten Entwicklungsumgebungen
231231
<a id="features"></a><a id="funktionsumfang"></a>
232232
## Funktionsumfang
233233

234+
- **Debugger Watch-Expressions & Call-Stack Panel**: Umfassende Variablen- und Ausdrucksüberwachung (`Ctrl+Shift+W`), interaktiver Aufruf-Stapel mit Direktsprung in den Quellcode, Schnellauswertungsleiste und Live-Synchronisation mit PDB-Debug-Sessions (`Ctrl+Shift+D`).
235+
- **Snippet-Manager mit Tab-Trigger-Erweiterung**: Platzhalter-Navigation (`$1`, `${1:default}`, `$0`) via `Tab` / `Shift+Tab`, Standard-Vorlagenkatalog für 10 Sprachen, persistente JSON-Verwaltung und interaktiver `SnippetsDialog` (`Ctrl+Shift+J`).
234236
- **Projektweite Dateisuche (Find in Files / Grep-Tool)**: Schnelle Textsuche über alle Projektordner und Dateien (`Ctrl+Shift+F`) mit regulären Ausdrücken, Groß-/Kleinschreibung, Ganzwort-Suche, Glob-Filtern (Einschließen/Ausschließen), asynchronem Hintergrund-Worker und Direktsprung in den Editor.
235237
- **Multi-Root-Arbeitsbereiche**: Verwaltung mehrerer Projektordner in einer gemeinsamen Instanz mit dem `.codebox-workspace`-Format und portablen relativen Pfaden (`Ctrl+Shift+O`).
236238
- **Schnellauswahl & Befehlspalette**: Schneller Datei-Finder (`Ctrl+P`) mit intelligentem Treffer-Scoring und interaktive Befehlspalette (`Ctrl+Shift+P`) zur tastaturgesteuerten IDE-Bedienung.

‎core/debugger.py‎

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""Debugger-Modul für CodeBox — Datenstrukturen und Parser für Stack-Frames und Watch-Expressions."""
4+
5+
from __future__ import annotations
6+
7+
from dataclasses import dataclass, field
8+
from pathlib import Path
9+
import re
10+
from typing import Any, Dict, List, Optional, Tuple
11+
12+
13+
@dataclass
14+
class StackFrame:
15+
"""Repräsentiert einen einzelnen Frame im Aufruf-Stapel (Call-Stack)."""
16+
17+
file_path: str
18+
line_number: int
19+
function_name: str
20+
code_line: str = ""
21+
is_current: bool = False
22+
frame_index: int = 0
23+
24+
@property
25+
def file_name(self) -> str:
26+
"""Liefert den reinen Dateinamen des Stack-Frames."""
27+
if not self.file_path:
28+
return ""
29+
return Path(self.file_path).name
30+
31+
def display_frame(self) -> str:
32+
"""Formatierte Anzeige für die Baumansicht."""
33+
prefix = "▶ " if self.is_current else " "
34+
func = self.function_name if self.function_name else "<unknown>"
35+
return f"{prefix}[{self.frame_index}] {func}"
36+
37+
def location(self) -> str:
38+
"""Kurze Pfadangabe mit Dateiname und Zeilennummer."""
39+
return f"{self.file_name}:{self.line_number}"
40+
41+
def to_dict(self) -> Dict[str, Any]:
42+
"""Serialisiert den Frame zu einem Dictionary."""
43+
return {
44+
"file_path": self.file_path,
45+
"line_number": self.line_number,
46+
"function_name": self.function_name,
47+
"code_line": self.code_line,
48+
"is_current": self.is_current,
49+
"frame_index": self.frame_index,
50+
}
51+
52+
53+
@dataclass
54+
class WatchExpression:
55+
"""Repräsentiert einen benutzerdefinierten Überwachungsausdruck."""
56+
57+
expression: str
58+
value: str = "<nicht ausgewertet>"
59+
type_name: str = ""
60+
status: str = "pending" # "ok", "error", "pending"
61+
metadata: Dict[str, Any] = field(default_factory=dict)
62+
63+
def to_dict(self) -> Dict[str, Any]:
64+
"""Serialisiert die Watch-Expression zu einem Dictionary."""
65+
return {
66+
"expression": self.expression,
67+
"value": self.value,
68+
"type_name": self.type_name,
69+
"status": self.status,
70+
"metadata": dict(self.metadata),
71+
}
72+
73+
74+
_PDB_FRAME_REGEX = re.compile(
75+
r"^\s*(?P<current>>)?\s*(?P<path>[a-zA-Z]:[\\/][^():\r\n]+|/[^():\r\n]+|[a-zA-Z0-9_.\-]+(?:\.py|\.pyw)?)\((?P<line>\d+)\)\s*(?P<func>[^\r\n]*)"
76+
)
77+
_PDB_CODE_REGEX = re.compile(r"^\s*->\s*(?P<code>.*)$")
78+
79+
80+
def parse_pdb_stack(text: str) -> List[StackFrame]:
81+
"""Parst die Textausgabe eines PDB 'where'- oder Traceback-Befehls in eine Liste von StackFrames.
82+
83+
Unterstützt sowohl Windows- als auch POSIX-Pfade, aktuelle Frame-Markierungen ('>')
84+
und Quelltext-Pfeile ('->').
85+
"""
86+
if not text:
87+
return []
88+
89+
lines = text.splitlines()
90+
frames: List[StackFrame] = []
91+
idx = 0
92+
i = 0
93+
94+
while i < len(lines):
95+
line = lines[i]
96+
match = _PDB_FRAME_REGEX.match(line)
97+
if match:
98+
is_current = bool(match.group("current"))
99+
fpath = match.group("path").strip()
100+
try:
101+
fline = int(match.group("line"))
102+
except ValueError:
103+
fline = 1
104+
105+
raw_func = match.group("func").strip()
106+
# Bereinige Funktionssignaturen wie "foo()" oder "<module>()"
107+
func_name = raw_func if raw_func else "<module>()"
108+
109+
code_line = ""
110+
if i + 1 < len(lines):
111+
code_match = _PDB_CODE_REGEX.match(lines[i + 1])
112+
if code_match:
113+
code_line = code_match.group("code").strip()
114+
i += 1
115+
116+
frame = StackFrame(
117+
file_path=fpath,
118+
line_number=fline,
119+
function_name=func_name,
120+
code_line=code_line,
121+
is_current=is_current,
122+
frame_index=idx,
123+
)
124+
frames.append(frame)
125+
idx += 1
126+
i += 1
127+
128+
return frames
129+
130+
131+
def parse_pdb_eval_response(expression: str, output: str) -> Tuple[str, str, str]:
132+
"""Parst die PDB-Ausgabe nach Ausführung von 'p <expression>' oder 'print(...)'.
133+
134+
Gibt (value_str, type_str, status) zurück.
135+
"""
136+
if not output:
137+
return ("<keine Ausgabe>", "", "pending")
138+
139+
clean = output.strip()
140+
# PDB Prompts und führende Echos entfernen
141+
lines = [
142+
ln.strip()
143+
for ln in clean.splitlines()
144+
if ln.strip() and not ln.strip().startswith("(Pdb)")
145+
]
146+
if not lines:
147+
return ("None", "NoneType", "ok")
148+
149+
full_text = "\n".join(lines)
150+
151+
# Auf Python-Fehlermeldungen prüfen
152+
if "***" in full_text or "Error:" in full_text:
153+
# Extrahiere Fehlertyp wenn möglich
154+
err_match = re.search(r"(\w+Error):\s*(.*)", full_text)
155+
if err_match:
156+
err_type = err_match.group(1)
157+
err_msg = err_match.group(2).strip()
158+
return (f"<{err_type}: {err_msg}>", err_type, "error")
159+
return (f"<{full_text}>", "Error", "error")
160+
161+
# Ergebnis-Typ heuristisch ableiten
162+
first_val = lines[-1]
163+
if (first_val.startswith("'") and first_val.endswith("'")) or (
164+
first_val.startswith('"') and first_val.endswith('"')
165+
):
166+
return (first_val, "str", "ok")
167+
if first_val in ("True", "False"):
168+
return (first_val, "bool", "ok")
169+
if first_val == "None":
170+
return ("None", "NoneType", "ok")
171+
if re.match(r"^-?\d+$", first_val):
172+
return (first_val, "int", "ok")
173+
if re.match(r"^-?\d+\.\d+$", first_val):
174+
return (first_val, "float", "ok")
175+
if first_val.startswith("[") and first_val.endswith("]"):
176+
return (first_val, "list", "ok")
177+
if first_val.startswith("{") and first_val.endswith("}"):
178+
return (first_val, "dict", "ok")
179+
if first_val.startswith("(") and first_val.endswith(")"):
180+
return (first_val, "tuple", "ok")
181+
182+
return (first_val, "object", "ok")
183+
184+
185+
def safe_eval_expression(
186+
expression: str, context: Optional[Dict[str, Any]] = None
187+
) -> Tuple[str, str, str]:
188+
"""Wertet einen Ausdruck in einem kontrollierten Kontext sicher aus.
189+
190+
Gibt (value_repr, type_name, status) zurück.
191+
"""
192+
expr_clean = expression.strip()
193+
if not expr_clean:
194+
return ("", "", "pending")
195+
196+
eval_ctx = dict(context or {})
197+
# Erlaube grundlegende Builtins zur Auswertung arithmetischer und logischer Ausdrücke
198+
safe_builtins = {
199+
"len": len,
200+
"str": str,
201+
"int": int,
202+
"float": float,
203+
"bool": bool,
204+
"list": list,
205+
"dict": dict,
206+
"set": set,
207+
"tuple": tuple,
208+
"min": min,
209+
"max": max,
210+
"sum": sum,
211+
"abs": abs,
212+
"round": round,
213+
"repr": repr,
214+
"type": type,
215+
}
216+
globals_dict = {"__builtins__": safe_builtins}
217+
218+
try:
219+
result = eval(expr_clean, globals_dict, eval_ctx)
220+
val_str = repr(result)
221+
type_str = type(result).__name__
222+
return (val_str, type_str, "ok")
223+
except Exception as exc:
224+
err_type = type(exc).__name__
225+
return (f"<{err_type}: {exc}>", err_type, "error")

‎core/editor.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ class CodeEditor(QPlainTextEdit):
243243
definitionRequested = Signal(int, int, str) # LSP/Fallback: Zeile, Spalte, Symbol (0-basiert)
244244
referencesRequested = Signal(int, int, str) # LSP/Fallback: Zeile, Spalte, Symbol (0-basiert)
245245
breakpointsChanged = Signal(object) # List[int] mit 1-basierten Zeilennummern
246+
addWatchRequested = Signal(str) # Debugger: Ausdruck zur Überwachung hinzufügen
246247
modificationChanged = Signal(bool)
247248
focusReceived = Signal()
248249

@@ -482,6 +483,14 @@ def contextMenuEvent(self, event):
482483
act_comment = menu.addAction("Zeilenkommentar umschalten\tCtrl+/")
483484
act_comment.triggered.connect(self.toggle_comment)
484485

486+
# Debugger Watch-Aktion
487+
selected_text = self.textCursor().selectedText().strip()
488+
watch_candidate = selected_text or symbol
489+
if watch_candidate:
490+
short_cand = watch_candidate[:25] + ("..." if len(watch_candidate) > 25 else "")
491+
act_watch = menu.addAction(f"Zu Überwachung hinzufügen: '{short_cand}'")
492+
act_watch.triggered.connect(lambda: self.addWatchRequested.emit(watch_candidate))
493+
485494
menu.exec(event.globalPos())
486495

487496

‎core/output.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class OutputPanel(QWidget):
3131

3232
processFinished = Signal(int, str) # exit_code, output
3333
inputSent = Signal(str) # text sent to process or entered
34+
debugOutputReceived = Signal(str) # Für Debugger-Auswertung (PDB Output)
3435

3536
def __init__(self, parent=None):
3637
super().__init__(parent)
@@ -306,6 +307,7 @@ def append_text(self, text: str, color: str = None):
306307
def _on_stdout(self):
307308
data = self.process.readAllStandardOutput().data().decode('utf-8', errors='replace')
308309
self.append_text(data)
310+
self.debugOutputReceived.emit(data)
309311
if self._initial_commands and self.is_running():
310312
cmds = list(self._initial_commands)
311313
self._initial_commands.clear()
@@ -315,6 +317,7 @@ def _on_stdout(self):
315317
def _on_stderr(self):
316318
data = self.process.readAllStandardError().data().decode('utf-8', errors='replace')
317319
self.append_text(data, color="#ff8888")
320+
self.debugOutputReceived.emit(data)
318321

319322
def _on_finished(self, exit_code, exit_status):
320323
self.stop_btn.setEnabled(False)

‎llms.txt‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CodeBox - Local PySide6 Desktop Code Editor
22

3-
## Last-checked: 2026-09-26
3+
## Last-checked: 2026-09-28
44

55
> CodeBox is a local-first desktop IDE for Windows, Linux, and macOS developers who want a lightweight PySide6 code editor with multi-tab workspace, project tree, integrated terminal, Git status porcelain indicators, syntax highlighting, Language Server Protocol (LSP) diagnostics, and an extensible JSON/Python language plugin architecture.
66

@@ -19,6 +19,7 @@ Part of the dev-bricks family. Python, MIT.
1919

2020
## Features & Architecture
2121

22+
- **Debugger Watch-Expressions & Call-Stack Panel**: Interactive variable and expression monitoring (`Ctrl+Shift+W`), call stack frame inspection with direct source-line jumping, instant expression evaluation bar, and live PDB session synchronization (`Ctrl+Shift+D`).
2223
- **Snippet Manager & Tab-Trigger Expansion**: Native tab-stop navigation (`$1`, `${1:default}`, `$0`) via `Tab` / `Shift+Tab`, built-in snippets across 10 programming and markup languages, persistent custom JSON storage, and interactive `SnippetsDialog` (`Ctrl+Shift+J`).
2324
- **Multi-Root Workspaces**: Load and manage multiple project folders simultaneously with portable `.codebox-workspace` files, dynamic tree switcher, cross-folder Quick Open, and per-root Git status.
2425
- **Quick-Open & Command Palette**: Centralized keyboard-driven launcher (`Ctrl+P` for project file finder, `Ctrl+Shift+P` for interactive command palette).
@@ -41,7 +42,7 @@ Part of the dev-bricks family. Python, MIT.
4142
- `python main.py`: Launches the CodeBox desktop interface.
4243
- `python main.py --open <file>`: Launches and directly opens a target file path.
4344
- `build_exe.bat`: Uses PyInstaller to bundle the application into a standalone executable (`dist/CodeBox.exe`).
44-
- `pytest`: Runs the automated test suite (299 passed, 1 skipped, 100%).
45+
- `pytest`: Runs the automated test suite (329 passed, 1 skipped, 100%).
4546
- `ruff check .`: Runs the codebase linting suite.
4647

4748
## Related Projects (dev-bricks & ellmos family)

0 commit comments

Comments
 (0)