comptext-codex 5.0.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.
- comptext_codex/__init__.py +50 -0
- comptext_codex/cli.py +91 -0
- comptext_codex/cli_v5.py +330 -0
- comptext_codex/executor.py +248 -0
- comptext_codex/mcp_server_v5.py +343 -0
- comptext_codex/modules/__init__.py +20 -0
- comptext_codex/modules/base.py +33 -0
- comptext_codex/modules/module_a.py +55 -0
- comptext_codex/modules/module_b.py +84 -0
- comptext_codex/modules/module_c.py +199 -0
- comptext_codex/modules/module_d.py +31 -0
- comptext_codex/modules/module_e.py +33 -0
- comptext_codex/modules/module_f.py +31 -0
- comptext_codex/modules/module_g.py +31 -0
- comptext_codex/modules/module_h.py +31 -0
- comptext_codex/modules/module_i.py +42 -0
- comptext_codex/modules/module_j.py +38 -0
- comptext_codex/modules/module_k.py +497 -0
- comptext_codex/modules/module_l.py +536 -0
- comptext_codex/modules/module_m.py +34 -0
- comptext_codex/parser.py +353 -0
- comptext_codex/parser_v5.py +330 -0
- comptext_codex/registry.py +236 -0
- comptext_codex/repl.py +410 -0
- comptext_codex/store.py +363 -0
- comptext_codex/token_reduction.py +115 -0
- comptext_codex/token_report.py +148 -0
- comptext_codex-5.0.0.dist-info/METADATA +466 -0
- comptext_codex-5.0.0.dist-info/RECORD +33 -0
- comptext_codex-5.0.0.dist-info/WHEEL +5 -0
- comptext_codex-5.0.0.dist-info/entry_points.txt +4 -0
- comptext_codex-5.0.0.dist-info/licenses/LICENSE +21 -0
- comptext_codex-5.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Module & command registry with decorator-based auto-registration.
|
|
2
|
+
|
|
3
|
+
Instead of maintaining separate YAML files and manually wiring modules,
|
|
4
|
+
every module class uses ``@codex_module`` and every handler uses
|
|
5
|
+
``@codex_command`` to self-register at import time.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from comptext_codex.registry import codex_module, codex_command
|
|
10
|
+
|
|
11
|
+
@codex_module(code="A", name="Core Commands",
|
|
12
|
+
purpose="Essential text manipulation commands")
|
|
13
|
+
class ModuleA:
|
|
14
|
+
@codex_command(syntax="@A:compress <text>",
|
|
15
|
+
description="Compress text by removing redundancy",
|
|
16
|
+
aliases=["shrink", "condense"],
|
|
17
|
+
token_cost_hint=50)
|
|
18
|
+
def execute_compress(self, text, *, context=None, **kw):
|
|
19
|
+
...
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from typing import Any, Callable, Dict, List, Optional, Type
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Data classes
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CommandMeta:
|
|
37
|
+
"""Metadata attached to a single command handler."""
|
|
38
|
+
command: str # derived from method name (execute_<command>)
|
|
39
|
+
module: str = "" # filled in by @codex_module
|
|
40
|
+
syntax: str = ""
|
|
41
|
+
description: str = ""
|
|
42
|
+
aliases: List[str] = field(default_factory=list)
|
|
43
|
+
examples: List[str] = field(default_factory=list)
|
|
44
|
+
token_cost_hint: int = 0
|
|
45
|
+
mcp_exposed: bool = True
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"module": self.module,
|
|
50
|
+
"command": self.command,
|
|
51
|
+
"syntax": self.syntax,
|
|
52
|
+
"description": self.description,
|
|
53
|
+
"aliases": self.aliases,
|
|
54
|
+
"examples": self.examples,
|
|
55
|
+
"token_cost_hint": self.token_cost_hint,
|
|
56
|
+
"mcp_exposed": self.mcp_exposed,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class ModuleMeta:
|
|
62
|
+
"""Metadata attached to a module class."""
|
|
63
|
+
code: str
|
|
64
|
+
name: str
|
|
65
|
+
purpose: str = ""
|
|
66
|
+
token_priority: str = "medium"
|
|
67
|
+
mcp_exposed: bool = True
|
|
68
|
+
security: Dict[str, Any] = field(default_factory=dict)
|
|
69
|
+
privacy: Dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"code": self.code,
|
|
74
|
+
"name": self.name,
|
|
75
|
+
"purpose": self.purpose,
|
|
76
|
+
"token_priority": self.token_priority,
|
|
77
|
+
"mcp_exposed": self.mcp_exposed,
|
|
78
|
+
"security": self.security,
|
|
79
|
+
"privacy": self.privacy,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Global registry (populated at import time)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
class ModuleRegistry:
|
|
88
|
+
"""Central registry holding all discovered module classes and their commands."""
|
|
89
|
+
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self._modules: Dict[str, Type] = {} # code -> class
|
|
92
|
+
self._module_meta: Dict[str, ModuleMeta] = {}
|
|
93
|
+
self._instances: Dict[str, Any] = {} # lazy singleton per code
|
|
94
|
+
|
|
95
|
+
def register_module(self, cls: Type, meta: ModuleMeta) -> None:
|
|
96
|
+
self._modules[meta.code] = cls
|
|
97
|
+
self._module_meta[meta.code] = meta
|
|
98
|
+
logger.debug("Registered module %s (%s)", meta.code, meta.name)
|
|
99
|
+
|
|
100
|
+
def get_instance(self, code: str) -> Optional[Any]:
|
|
101
|
+
if code not in self._modules:
|
|
102
|
+
return None
|
|
103
|
+
if code not in self._instances:
|
|
104
|
+
self._instances[code] = self._modules[code]()
|
|
105
|
+
return self._instances[code]
|
|
106
|
+
|
|
107
|
+
def list_module_codes(self) -> List[str]:
|
|
108
|
+
return sorted(self._modules.keys())
|
|
109
|
+
|
|
110
|
+
def get_module_meta(self, code: str) -> Optional[ModuleMeta]:
|
|
111
|
+
return self._module_meta.get(code)
|
|
112
|
+
|
|
113
|
+
def all_module_meta(self) -> List[ModuleMeta]:
|
|
114
|
+
return [self._module_meta[c] for c in sorted(self._module_meta)]
|
|
115
|
+
|
|
116
|
+
def all_command_meta(self) -> List[CommandMeta]:
|
|
117
|
+
"""Collect CommandMeta from all registered modules."""
|
|
118
|
+
result: List[CommandMeta] = []
|
|
119
|
+
for code in sorted(self._modules):
|
|
120
|
+
cls = self._modules[code]
|
|
121
|
+
for attr in dir(cls):
|
|
122
|
+
fn = getattr(cls, attr, None)
|
|
123
|
+
cm: Optional[CommandMeta] = getattr(fn, "_command_meta", None)
|
|
124
|
+
if cm is not None:
|
|
125
|
+
result.append(cm)
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
def get_commands_for_module(self, code: str) -> List[CommandMeta]:
|
|
129
|
+
cls = self._modules.get(code)
|
|
130
|
+
if cls is None:
|
|
131
|
+
return []
|
|
132
|
+
result: List[CommandMeta] = []
|
|
133
|
+
for attr in dir(cls):
|
|
134
|
+
fn = getattr(cls, attr, None)
|
|
135
|
+
cm: Optional[CommandMeta] = getattr(fn, "_command_meta", None)
|
|
136
|
+
if cm is not None and cm.module == code:
|
|
137
|
+
result.append(cm)
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def clear(self):
|
|
141
|
+
"""Reset for testing."""
|
|
142
|
+
self._modules.clear()
|
|
143
|
+
self._module_meta.clear()
|
|
144
|
+
self._instances.clear()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# Singleton instance
|
|
148
|
+
registry = ModuleRegistry()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# Decorators
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def codex_module(
|
|
156
|
+
code: str,
|
|
157
|
+
name: str,
|
|
158
|
+
purpose: str = "",
|
|
159
|
+
token_priority: str = "medium",
|
|
160
|
+
mcp_exposed: bool = True,
|
|
161
|
+
security: Optional[Dict[str, Any]] = None,
|
|
162
|
+
privacy: Optional[Dict[str, Any]] = None,
|
|
163
|
+
):
|
|
164
|
+
"""Class decorator that registers a module in the global registry.
|
|
165
|
+
|
|
166
|
+
Also walks the class body and stamps ``module=code`` on every
|
|
167
|
+
``@codex_command``-decorated method.
|
|
168
|
+
"""
|
|
169
|
+
def decorator(cls: Type) -> Type:
|
|
170
|
+
meta = ModuleMeta(
|
|
171
|
+
code=code,
|
|
172
|
+
name=name,
|
|
173
|
+
purpose=purpose,
|
|
174
|
+
token_priority=token_priority,
|
|
175
|
+
mcp_exposed=mcp_exposed,
|
|
176
|
+
security=security or {},
|
|
177
|
+
privacy=privacy or {},
|
|
178
|
+
)
|
|
179
|
+
cls._module_meta = meta # type: ignore[attr-defined]
|
|
180
|
+
|
|
181
|
+
# Patch module code into every command handler defined on this class
|
|
182
|
+
for attr_name in list(vars(cls)):
|
|
183
|
+
fn = getattr(cls, attr_name, None)
|
|
184
|
+
cm: Optional[CommandMeta] = getattr(fn, "_command_meta", None)
|
|
185
|
+
if cm is not None:
|
|
186
|
+
cm.module = code
|
|
187
|
+
|
|
188
|
+
registry.register_module(cls, meta)
|
|
189
|
+
return cls
|
|
190
|
+
|
|
191
|
+
return decorator
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def codex_command(
|
|
195
|
+
syntax: str = "",
|
|
196
|
+
description: str = "",
|
|
197
|
+
aliases: Optional[List[str]] = None,
|
|
198
|
+
examples: Optional[List[str]] = None,
|
|
199
|
+
token_cost_hint: int = 0,
|
|
200
|
+
mcp_exposed: bool = True,
|
|
201
|
+
):
|
|
202
|
+
"""Method decorator that attaches command metadata to ``execute_*`` methods.
|
|
203
|
+
|
|
204
|
+
The *command* name is derived from the method name by stripping the
|
|
205
|
+
``execute_`` prefix.
|
|
206
|
+
"""
|
|
207
|
+
def decorator(fn: Callable) -> Callable:
|
|
208
|
+
name = fn.__name__
|
|
209
|
+
if name.startswith("execute_"):
|
|
210
|
+
cmd_name = name[8:] # strip "execute_"
|
|
211
|
+
else:
|
|
212
|
+
cmd_name = name
|
|
213
|
+
|
|
214
|
+
fn._command_meta = CommandMeta( # type: ignore[attr-defined]
|
|
215
|
+
command=cmd_name,
|
|
216
|
+
syntax=syntax,
|
|
217
|
+
description=description,
|
|
218
|
+
aliases=aliases or [],
|
|
219
|
+
examples=examples or [],
|
|
220
|
+
token_cost_hint=token_cost_hint,
|
|
221
|
+
mcp_exposed=mcp_exposed,
|
|
222
|
+
)
|
|
223
|
+
return fn
|
|
224
|
+
|
|
225
|
+
return decorator
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def ensure_modules_loaded() -> None:
|
|
229
|
+
"""Import all module files so decorators run and modules register."""
|
|
230
|
+
import importlib
|
|
231
|
+
|
|
232
|
+
for letter in "abcdefghijklm":
|
|
233
|
+
try:
|
|
234
|
+
importlib.import_module(f"comptext_codex.modules.module_{letter}")
|
|
235
|
+
except ImportError:
|
|
236
|
+
pass
|
comptext_codex/repl.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""Interactive REPL for CompText Codex.
|
|
2
|
+
|
|
3
|
+
Provides an interactive shell with:
|
|
4
|
+
- Command history and editing
|
|
5
|
+
- Tab completion for modules and commands
|
|
6
|
+
- Syntax highlighting
|
|
7
|
+
- Multi-line input support
|
|
8
|
+
- Built-in help system
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Optional, Dict, Any
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import readline
|
|
20
|
+
READLINE_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
READLINE_AVAILABLE = False
|
|
23
|
+
|
|
24
|
+
from .parser import CompTextParser, CompTextCommand
|
|
25
|
+
from .executor import CompTextExecutor, ExecutionResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CompTextREPL:
|
|
29
|
+
"""Interactive REPL for CompText DSL."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, codex_dir: Optional[str] = None):
|
|
32
|
+
"""Initialize REPL.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
codex_dir: Path to codex directory (defaults to ./codex)
|
|
36
|
+
"""
|
|
37
|
+
self.codex_dir = codex_dir or "codex"
|
|
38
|
+
self.parser = CompTextParser(codex_dir=self.codex_dir)
|
|
39
|
+
self.executor = CompTextExecutor(codex_dir=self.codex_dir)
|
|
40
|
+
self.context: Dict[str, Any] = {}
|
|
41
|
+
self.history: List[str] = []
|
|
42
|
+
self.multiline_buffer: List[str] = []
|
|
43
|
+
|
|
44
|
+
# Available modules and commands for autocomplete
|
|
45
|
+
self.modules = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M']
|
|
46
|
+
self.commands_by_module = self._load_available_commands()
|
|
47
|
+
|
|
48
|
+
# Setup readline if available
|
|
49
|
+
if READLINE_AVAILABLE:
|
|
50
|
+
self._setup_readline()
|
|
51
|
+
|
|
52
|
+
def _load_available_commands(self) -> Dict[str, List[str]]:
|
|
53
|
+
"""Load available commands from executor."""
|
|
54
|
+
commands_by_module = {}
|
|
55
|
+
available = self.executor.get_available_commands()
|
|
56
|
+
|
|
57
|
+
for cmd_info in available:
|
|
58
|
+
module = cmd_info.get('module', 'A')
|
|
59
|
+
command = cmd_info.get('command', '')
|
|
60
|
+
|
|
61
|
+
if module not in commands_by_module:
|
|
62
|
+
commands_by_module[module] = []
|
|
63
|
+
|
|
64
|
+
if command and command not in commands_by_module[module]:
|
|
65
|
+
commands_by_module[module].append(command)
|
|
66
|
+
|
|
67
|
+
return commands_by_module
|
|
68
|
+
|
|
69
|
+
def _setup_readline(self):
|
|
70
|
+
"""Setup readline for history and completion."""
|
|
71
|
+
# Enable tab completion
|
|
72
|
+
readline.parse_and_bind('tab: complete')
|
|
73
|
+
readline.set_completer(self._completer)
|
|
74
|
+
|
|
75
|
+
# Load history file
|
|
76
|
+
history_file = Path.home() / '.comptext_history'
|
|
77
|
+
if history_file.exists():
|
|
78
|
+
try:
|
|
79
|
+
readline.read_history_file(history_file)
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# Save history on exit
|
|
84
|
+
import atexit
|
|
85
|
+
atexit.register(lambda: self._save_history(history_file))
|
|
86
|
+
|
|
87
|
+
def _save_history(self, history_file: Path):
|
|
88
|
+
"""Save command history to file."""
|
|
89
|
+
try:
|
|
90
|
+
readline.write_history_file(history_file)
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def _completer(self, text: str, state: int) -> Optional[str]:
|
|
95
|
+
"""Readline completer for tab completion."""
|
|
96
|
+
options = []
|
|
97
|
+
|
|
98
|
+
# Complete module codes
|
|
99
|
+
if text.startswith('@') and ':' not in text and '[' not in text:
|
|
100
|
+
module_prefix = text[1:]
|
|
101
|
+
options = [f"@{m}:" for m in self.modules if m.startswith(module_prefix.upper())]
|
|
102
|
+
|
|
103
|
+
# Complete commands after module:
|
|
104
|
+
elif ':' in text:
|
|
105
|
+
parts = text.split(':')
|
|
106
|
+
if len(parts) == 2:
|
|
107
|
+
module = parts[0].strip('@')
|
|
108
|
+
cmd_prefix = parts[1]
|
|
109
|
+
|
|
110
|
+
if module in self.commands_by_module:
|
|
111
|
+
options = [
|
|
112
|
+
f"@{module}:{cmd}"
|
|
113
|
+
for cmd in self.commands_by_module[module]
|
|
114
|
+
if cmd.startswith(cmd_prefix.lower())
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
# Complete REPL commands
|
|
118
|
+
elif text.startswith('.'):
|
|
119
|
+
repl_commands = ['.help', '.commands', '.context', '.clear', '.history', '.exit', '.quit']
|
|
120
|
+
options = [cmd for cmd in repl_commands if cmd.startswith(text)]
|
|
121
|
+
|
|
122
|
+
if state < len(options):
|
|
123
|
+
return options[state]
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def print_banner(self):
|
|
127
|
+
"""Print welcome banner."""
|
|
128
|
+
banner = """
|
|
129
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
130
|
+
║ CompText Codex Interactive REPL v3.5.0 ║
|
|
131
|
+
║ ║
|
|
132
|
+
║ Efficient LLM communication through DSL commands ║
|
|
133
|
+
║ 70-88% token reduction | Modular architecture ║
|
|
134
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
135
|
+
|
|
136
|
+
Type .help for available commands | .exit to quit
|
|
137
|
+
"""
|
|
138
|
+
print(banner)
|
|
139
|
+
|
|
140
|
+
def print_help(self):
|
|
141
|
+
"""Print help message."""
|
|
142
|
+
help_text = """
|
|
143
|
+
CompText REPL Commands:
|
|
144
|
+
.help Show this help message
|
|
145
|
+
.commands List all available CompText commands
|
|
146
|
+
.context Show current execution context
|
|
147
|
+
.clear Clear execution context
|
|
148
|
+
.history Show command history
|
|
149
|
+
.exit, .quit Exit the REPL
|
|
150
|
+
|
|
151
|
+
CompText Syntax Examples:
|
|
152
|
+
@A:compress The quick brown fox jumps over the lazy dog
|
|
153
|
+
@B:analyze[complexity, maintainability] def foo(): pass
|
|
154
|
+
@C:format[type=markdown] # My Title\\nContent here
|
|
155
|
+
@AUTOML[task=classification, metric=f1]
|
|
156
|
+
@EXTRACT[source=db] + @TRANSFORM[clean=true] + @LOAD[dest=warehouse]
|
|
157
|
+
|
|
158
|
+
Module Codes:
|
|
159
|
+
A - Core Commands │ H - Database
|
|
160
|
+
B - Analysis │ I - Security
|
|
161
|
+
C - Formatting │ J - DevOps
|
|
162
|
+
D - AI Control │ K - Frontend/UI
|
|
163
|
+
E - ML Pipelines │ L - ETL
|
|
164
|
+
F - Documentation │ M - MCP Integration
|
|
165
|
+
G - Testing │
|
|
166
|
+
|
|
167
|
+
Tips:
|
|
168
|
+
- Press Tab for autocomplete
|
|
169
|
+
- Use arrow keys to navigate history
|
|
170
|
+
- Chain commands with + operator
|
|
171
|
+
- Multi-line input: end line with \\ to continue
|
|
172
|
+
"""
|
|
173
|
+
print(help_text)
|
|
174
|
+
|
|
175
|
+
def print_commands(self):
|
|
176
|
+
"""Print all available commands."""
|
|
177
|
+
print("\n═══ Available Commands ═══\n")
|
|
178
|
+
|
|
179
|
+
for module in sorted(self.commands_by_module.keys()):
|
|
180
|
+
commands = self.commands_by_module[module]
|
|
181
|
+
if commands:
|
|
182
|
+
print(f"Module {module}:")
|
|
183
|
+
for cmd in sorted(commands):
|
|
184
|
+
print(f" @{module}:{cmd}")
|
|
185
|
+
print()
|
|
186
|
+
|
|
187
|
+
def print_context(self):
|
|
188
|
+
"""Print current execution context."""
|
|
189
|
+
if not self.context:
|
|
190
|
+
print("Context is empty")
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
print("\n═══ Execution Context ═══\n")
|
|
194
|
+
for key, value in self.context.items():
|
|
195
|
+
# Truncate long values
|
|
196
|
+
value_str = str(value)
|
|
197
|
+
if len(value_str) > 100:
|
|
198
|
+
value_str = value_str[:97] + "..."
|
|
199
|
+
print(f"{key}: {value_str}")
|
|
200
|
+
print()
|
|
201
|
+
|
|
202
|
+
def print_history(self):
|
|
203
|
+
"""Print command history."""
|
|
204
|
+
if not self.history:
|
|
205
|
+
print("No command history")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
print("\n═══ Command History ═══\n")
|
|
209
|
+
for i, cmd in enumerate(self.history, 1):
|
|
210
|
+
print(f"{i:3d}: {cmd}")
|
|
211
|
+
print()
|
|
212
|
+
|
|
213
|
+
def format_result(self, result: ExecutionResult, command: CompTextCommand) -> str:
|
|
214
|
+
"""Format execution result for display."""
|
|
215
|
+
if not result.success:
|
|
216
|
+
return f"❌ Error: {result.error}"
|
|
217
|
+
|
|
218
|
+
# Check if simulated
|
|
219
|
+
if result.metadata.get('simulated'):
|
|
220
|
+
return f"🔸 [Simulated] {result.result.get('message', 'Command executed')}"
|
|
221
|
+
|
|
222
|
+
# Format result based on type
|
|
223
|
+
result_data = result.result
|
|
224
|
+
|
|
225
|
+
if isinstance(result_data, dict):
|
|
226
|
+
# Pretty print dict results
|
|
227
|
+
lines = []
|
|
228
|
+
for key, value in result_data.items():
|
|
229
|
+
value_str = str(value)
|
|
230
|
+
if len(value_str) > 200:
|
|
231
|
+
value_str = value_str[:197] + "..."
|
|
232
|
+
lines.append(f" {key}: {value_str}")
|
|
233
|
+
return "✓ Result:\n" + "\n".join(lines)
|
|
234
|
+
|
|
235
|
+
elif isinstance(result_data, (list, tuple)):
|
|
236
|
+
if len(result_data) <= 5:
|
|
237
|
+
return f"✓ Result: {result_data}"
|
|
238
|
+
else:
|
|
239
|
+
preview = result_data[:5]
|
|
240
|
+
return f"✓ Result: {preview}... ({len(result_data)} items)"
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
result_str = str(result_data)
|
|
244
|
+
if len(result_str) > 500:
|
|
245
|
+
result_str = result_str[:497] + "..."
|
|
246
|
+
return f"✓ Result: {result_str}"
|
|
247
|
+
|
|
248
|
+
def execute_repl_command(self, command: str) -> bool:
|
|
249
|
+
"""Execute REPL meta-command.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
True to continue REPL, False to exit
|
|
253
|
+
"""
|
|
254
|
+
command = command.strip()
|
|
255
|
+
|
|
256
|
+
if command in ['.exit', '.quit']:
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
elif command == '.help':
|
|
260
|
+
self.print_help()
|
|
261
|
+
|
|
262
|
+
elif command == '.commands':
|
|
263
|
+
self.print_commands()
|
|
264
|
+
|
|
265
|
+
elif command == '.context':
|
|
266
|
+
self.print_context()
|
|
267
|
+
|
|
268
|
+
elif command == '.clear':
|
|
269
|
+
self.context.clear()
|
|
270
|
+
print("✓ Context cleared")
|
|
271
|
+
|
|
272
|
+
elif command == '.history':
|
|
273
|
+
self.print_history()
|
|
274
|
+
|
|
275
|
+
else:
|
|
276
|
+
print(f"Unknown command: {command}")
|
|
277
|
+
print("Type .help for available commands")
|
|
278
|
+
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
def execute_comptext_command(self, command: str):
|
|
282
|
+
"""Execute CompText DSL command."""
|
|
283
|
+
try:
|
|
284
|
+
# Parse and execute
|
|
285
|
+
commands = self.parser.parse(command)
|
|
286
|
+
|
|
287
|
+
if not commands:
|
|
288
|
+
print("❌ Failed to parse command")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
# Execute all commands
|
|
292
|
+
results = self.executor.execute(command, context=self.context)
|
|
293
|
+
|
|
294
|
+
# Display results
|
|
295
|
+
for cmd, result in zip(commands, results):
|
|
296
|
+
print(self.format_result(result, cmd))
|
|
297
|
+
|
|
298
|
+
# Update context with successful results
|
|
299
|
+
if result.success and result.result:
|
|
300
|
+
self.context['_last_result'] = result.result
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print(f"❌ Error: {e}")
|
|
304
|
+
|
|
305
|
+
def read_input(self) -> Optional[str]:
|
|
306
|
+
"""Read input from user, supporting multi-line input.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Complete command string, or None if EOF
|
|
310
|
+
"""
|
|
311
|
+
try:
|
|
312
|
+
# Primary prompt
|
|
313
|
+
prompt = "comptext> " if not self.multiline_buffer else " ... "
|
|
314
|
+
|
|
315
|
+
line = input(prompt)
|
|
316
|
+
|
|
317
|
+
# Check for multi-line continuation
|
|
318
|
+
if line.endswith('\\'):
|
|
319
|
+
self.multiline_buffer.append(line[:-1]) # Remove trailing \
|
|
320
|
+
return self.read_input() # Recursive read
|
|
321
|
+
|
|
322
|
+
# Complete multi-line input
|
|
323
|
+
if self.multiline_buffer:
|
|
324
|
+
self.multiline_buffer.append(line)
|
|
325
|
+
complete = ' '.join(self.multiline_buffer)
|
|
326
|
+
self.multiline_buffer.clear()
|
|
327
|
+
return complete
|
|
328
|
+
|
|
329
|
+
return line
|
|
330
|
+
|
|
331
|
+
except EOFError:
|
|
332
|
+
return None
|
|
333
|
+
except KeyboardInterrupt:
|
|
334
|
+
# Clear multi-line buffer on Ctrl+C
|
|
335
|
+
if self.multiline_buffer:
|
|
336
|
+
self.multiline_buffer.clear()
|
|
337
|
+
print("\n(Multi-line input cancelled)")
|
|
338
|
+
return ""
|
|
339
|
+
print() # New line
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
def run(self):
|
|
343
|
+
"""Run the REPL main loop."""
|
|
344
|
+
self.print_banner()
|
|
345
|
+
|
|
346
|
+
while True:
|
|
347
|
+
try:
|
|
348
|
+
# Read command
|
|
349
|
+
command = self.read_input()
|
|
350
|
+
|
|
351
|
+
# Handle EOF (Ctrl+D) or exit
|
|
352
|
+
if command is None:
|
|
353
|
+
print("\nGoodbye!")
|
|
354
|
+
break
|
|
355
|
+
|
|
356
|
+
# Skip empty lines
|
|
357
|
+
command = command.strip()
|
|
358
|
+
if not command:
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
# Add to history
|
|
362
|
+
self.history.append(command)
|
|
363
|
+
|
|
364
|
+
# Execute REPL command
|
|
365
|
+
if command.startswith('.'):
|
|
366
|
+
should_continue = self.execute_repl_command(command)
|
|
367
|
+
if not should_continue:
|
|
368
|
+
print("\nGoodbye!")
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
# Execute CompText command
|
|
372
|
+
elif command.startswith('@'):
|
|
373
|
+
self.execute_comptext_command(command)
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
print("Commands must start with @ or .")
|
|
377
|
+
print("Type .help for usage information")
|
|
378
|
+
|
|
379
|
+
except KeyboardInterrupt:
|
|
380
|
+
print("\n(Use .exit or Ctrl+D to quit)")
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
except Exception as e:
|
|
384
|
+
print(f"❌ Unexpected error: {e}")
|
|
385
|
+
import traceback
|
|
386
|
+
traceback.print_exc()
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def main():
|
|
390
|
+
"""Entry point for REPL."""
|
|
391
|
+
import argparse
|
|
392
|
+
|
|
393
|
+
parser = argparse.ArgumentParser(
|
|
394
|
+
description="CompText Codex Interactive REPL"
|
|
395
|
+
)
|
|
396
|
+
parser.add_argument(
|
|
397
|
+
'--codex-dir',
|
|
398
|
+
default='codex',
|
|
399
|
+
help='Path to codex directory (default: ./codex)'
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
args = parser.parse_args()
|
|
403
|
+
|
|
404
|
+
# Create and run REPL
|
|
405
|
+
repl = CompTextREPL(codex_dir=args.codex_dir)
|
|
406
|
+
repl.run()
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == '__main__':
|
|
410
|
+
main()
|