binary-equalab 3.0.0b1__py3-none-any.whl → 3.0.0b3__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.
binary_equalab/cli.py CHANGED
@@ -21,7 +21,7 @@ console = Console()
21
21
 
22
22
  BANNER = """
23
23
  [bold orange1]╔══════════════════════════════════════════════════════════╗
24
- ║ [white]Binary EquaLab CLI[/white] [dim]Aurora v2.0[/dim]
24
+ ║ [white]Binary EquaLab CLI[/white] [dim]Aurora v3.0 (Beta)[/dim]
25
25
  ║ [dim italic]"Las matemáticas también sienten,[/dim italic] ║
26
26
  ║ [dim italic] pero estas no se equivocan."[/dim italic] ║
27
27
  ╚══════════════════════════════════════════════════════════╝[/bold orange1]
@@ -80,8 +80,19 @@ HELP_TEXT = """
80
80
  | `tir(flujo0, flujo1, ...)` | `tir(-1000, 300, 400, 500)` |
81
81
  | `depreciar(costo, residual, años)` | `depreciar(10000, 1000, 5)` |
82
82
  | `interes_simple(capital, tasa, tiempo)` | `interes_simple(1000, 0.05, 3)` |
83
+ | `interes_simple(capital, tasa, tiempo)` | `interes_simple(1000, 0.05, 3)` |
83
84
  | `interes_compuesto(capital, tasa, n, tiempo)` | `interes_compuesto(1000, 0.05, 12, 3)` |
84
85
 
86
+ ### Sistemas Numéricos (NUEVO)
87
+ | Función | Ejemplo |
88
+ |---------|---------|
89
+ | `bin(n)` | `bin(10)` → `0b1010` |
90
+ | `oct(n)` | `oct(10)` → `0o12` |
91
+ | `hex(n)` | `hex(255)` → `0xff` |
92
+ | `base(n, b)` | `base(10, 2)` → `1010` |
93
+
94
+ ### AI Assistant
95
+
85
96
  ### Aliases y Accesos Directos
86
97
  - **Shell**: Puedes ejecutar el programa como `binary-equalab`, `bneqls`, `beq` o `binary-math`.
87
98
  - **Trigonometría**: `seno`=`sin`, `coseno`=`cos`, `tangente`=`tan`.
@@ -99,7 +110,7 @@ def get_prompt_style():
99
110
  def print_banner():
100
111
  """Print the CLI banner using Rich panels."""
101
112
  title = Text("Binary EquaLab CLI", style="bold white")
102
- version = Text("Aurora v2.0.2", style="dim")
113
+ version = Text("Aurora v3.0 (Beta)", style="dim")
103
114
  slogan = Text('"Las matemáticas también sienten,\npero estas no se equivocan."', style="dim italic")
104
115
 
105
116
  content = Text.assemble(title, " ", version, "\n\n", slogan, justify="center")
@@ -219,9 +230,13 @@ def main():
219
230
  if len(sys.argv) > 1 and sys.argv[1] == 'setup-shell':
220
231
  from .shell_setup import run_setup
221
232
  run_setup()
233
+ elif len(sys.argv) > 1 and sys.argv[1] == 'tui':
234
+ from .tui import BinaryTUI
235
+ app = BinaryTUI()
236
+ app.run()
222
237
  elif len(sys.argv) > 1 and sys.argv[1] == 'ai':
223
- # AI Commands Mode
224
- from .kimi_service import kimi_service
238
+ # AI Commands Mode (Kimi K2)
239
+ from .services.kimi_service import kimi_service
225
240
 
226
241
  if len(sys.argv) < 3:
227
242
  console.print("[bold red]Uso:[/bold red] binary ai [solve|explain|exercises] \"consulta\"")
@@ -230,7 +245,6 @@ def main():
230
245
  subcmd = sys.argv[2]
231
246
  query = " ".join(sys.argv[3:])
232
247
 
233
- # 'exercises' command doesn't necessarily need a query if defaults are used, but we'll use query as topic
234
248
  if subcmd != 'exercises' and not query:
235
249
  console.print("[bold red]Error:[/bold red] Falta la consulta.")
236
250
  sys.exit(1)
@@ -258,12 +272,15 @@ def main():
258
272
  console.print(Panel(Markdown(response), title=f"Kimi AI: Explicación", border_style="blue"))
259
273
 
260
274
  elif subcmd == "exercises":
261
- # Uso: binary ai exercises "Derivadas" [opcional: count]
262
- # Por simplicidad en sys.argv, asumimos que todo el resto es el topic
263
- exercises = kimi_service.generate_exercises(query if query else "Matemáticas generales")
275
+ count = 3 # default
276
+ # Simple parsing for count if present in args? For now keeping simple.
277
+ exercises = kimi_service.generate_exercises(query if query else "Matemáticas Generales", count)
264
278
 
265
279
  console.print(f"[bold u]Generando ejercicios para:[/bold u] {query}\n")
266
280
 
281
+ if not exercises:
282
+ console.print("No se pudieron generar ejercicios. Verifica tu API Key o intenta de nuevo.")
283
+
267
284
  for i, ex in enumerate(exercises, 1):
268
285
  console.print(Panel(
269
286
  f"[bold]Pregunta:[/bold]\n{ex.get('problem')}\n\n"
@@ -271,10 +288,7 @@ def main():
271
288
  title=f"Ejercicio {i}", border_style="magenta"
272
289
  ))
273
290
  if ex.get('steps'):
274
- with console.status(f"[dim]Ver pasos...[/dim]"):
275
- # Hack para ocultar pasos inicialmente si se quisiera, pero aquí los mostramos
276
- pass
277
- console.print(f"[dim]Pasos: {', '.join(ex.get('steps', []))}[/dim]\n")
291
+ console.print(f"[dim]Pasos: {', '.join(ex.get('steps', []))}[/dim]\n")
278
292
  else:
279
293
  console.print(f"[bold red]Comando desconocido:[/bold red] {subcmd}")
280
294
  sys.exit(1)
binary_equalab/engine.py CHANGED
@@ -74,6 +74,12 @@ class MathEngine:
74
74
  'recta': self._recta,
75
75
  'circulo': self._circulo,
76
76
 
77
+ # Numeral Systems
78
+ 'bin': self._binario,
79
+ 'oct': self._octal,
80
+ 'hex': self._hexadecimal,
81
+ 'base': self._base_n,
82
+
77
83
  # Trigonometry aliases
78
84
  'seno': sin,
79
85
  'coseno': cos,
@@ -394,6 +400,41 @@ class MathEngine:
394
400
  engine = AudioEngine()
395
401
  return engine.generate(str(expr), float(duration), str(filename))
396
402
 
403
+ # ============ NUMERAL SYSTEMS ============
404
+
405
+ def _binario(self, number):
406
+ """Convert to binary: bin(10) → '0b1010'"""
407
+ try:
408
+ return bin(int(number))
409
+ except:
410
+ return bin(int(float(number)))
411
+
412
+ def _octal(self, number):
413
+ """Convert to octal: oct(10) → '0o12'"""
414
+ try:
415
+ return oct(int(number))
416
+ except:
417
+ return oct(int(float(number)))
418
+
419
+ def _hexadecimal(self, number):
420
+ """Convert to hex: hex(255) → '0xff'"""
421
+ try:
422
+ return hex(int(number))
423
+ except:
424
+ return hex(int(float(number)))
425
+
426
+ def _base_n(self, number, base):
427
+ """Convert to arbitrary base: base(10, 2) → '1010'"""
428
+ n = int(number)
429
+ b = int(base)
430
+ if n == 0:
431
+ return "0"
432
+ digits = []
433
+ while n:
434
+ digits.append(int(n % b))
435
+ n //= b
436
+ return "".join(str(d) for d in digits[::-1])
437
+
397
438
  # Convenience functions for direct import
398
439
  def derivar(expr, var=None, n=1):
399
440
  engine = MathEngine()
@@ -490,3 +531,19 @@ def circulo(centro, radio):
490
531
  def sonify(expr, duration=3.0, filename="output.wav"):
491
532
  engine = MathEngine()
492
533
  return engine._sonify(expr, duration, filename)
534
+
535
+ def binario(number):
536
+ engine = MathEngine()
537
+ return engine._binario(number)
538
+
539
+ def octal(number):
540
+ engine = MathEngine()
541
+ return engine._octal(number)
542
+
543
+ def hexadecimal(number):
544
+ engine = MathEngine()
545
+ return engine._hexadecimal(number)
546
+
547
+ def base(number, n):
548
+ engine = MathEngine()
549
+ return engine._base_n(number, n)
@@ -0,0 +1,127 @@
1
+
2
+ # binary-cli/binary_equalab/services/kimi_service.py
3
+
4
+ import os
5
+ import json
6
+ import requests
7
+ from typing import List, Dict, Optional, Any
8
+
9
+ class KimiService:
10
+ """Service to interact with Kimi K2 (Moonshot AI) from CLI/Desktop"""
11
+
12
+ def __init__(self):
13
+ # Allow checking multiple env locations or just env
14
+ self.api_key = os.getenv('KIMI_API_KEY', '')
15
+ self.base_url = 'https://api.moonshot.cn/v1'
16
+ self.model = 'moonshot-v1-128k'
17
+
18
+ if not self.api_key:
19
+ # Try to read from .env if not loaded (though click/main usually loads it)
20
+ pass
21
+
22
+ def chat(
23
+ self,
24
+ messages: List[Dict[str, str]],
25
+ temperature: float = 0.7,
26
+ max_tokens: int = 4096,
27
+ stream: bool = False
28
+ ) -> Any: # Returns str or iterator
29
+ """Send message to Kimi K2"""
30
+
31
+ if not self.api_key:
32
+ raise ValueError("KIMI_API_KEY not configured. Set it in your environment.")
33
+
34
+ headers = {
35
+ 'Content-Type': 'application/json',
36
+ 'Authorization': f'Bearer {self.api_key}'
37
+ }
38
+
39
+ payload = {
40
+ 'model': self.model,
41
+ 'messages': messages,
42
+ 'temperature': temperature,
43
+ 'max_tokens': max_tokens,
44
+ 'stream': stream
45
+ }
46
+
47
+ response = requests.post(
48
+ f'{self.base_url}/chat/completions',
49
+ headers=headers,
50
+ json=payload,
51
+ stream=stream
52
+ )
53
+
54
+ if not response.ok:
55
+ raise Exception(f"Kimi API Error: {response.status_code} - {response.text}")
56
+
57
+ if stream:
58
+ return self._stream_response(response)
59
+
60
+ data = response.json()
61
+ return data['choices'][0]['message']['content']
62
+
63
+ def _stream_response(self, response):
64
+ for line in response.iter_lines():
65
+ if line:
66
+ line = line.decode('utf-8')
67
+ if line.startswith('data: '):
68
+ data = line[6:]
69
+ if data == '[DONE]':
70
+ break
71
+ try:
72
+ parsed = json.loads(data)
73
+ content = parsed['choices'][0]['delta'].get('content')
74
+ if content:
75
+ yield content
76
+ except:
77
+ continue
78
+
79
+ def solve_math_problem(self, problem: str, show_steps: bool = True) -> Dict[str, Any]:
80
+ """Solve with steps"""
81
+ system_prompt = f"""Eres un asistente matemático experto.
82
+ Resuelve el problema {'mostrando pasos' if show_steps else 'directamente'}.
83
+ Responde en JSON:
84
+ {{
85
+ "solution": "Respuesta LaTeX",
86
+ "steps": ["Paso 1"],
87
+ "reasoning": "...",
88
+ "concepts": []
89
+ }}"""
90
+
91
+ messages = [
92
+ {'role': 'system', 'content': system_prompt},
93
+ {'role': 'user', 'content': problem}
94
+ ]
95
+
96
+ content = self.chat(messages, temperature=0.3)
97
+ try:
98
+ if content.startswith("```json"):
99
+ content = content.replace("```json", "").replace("```", "")
100
+ return json.loads(content)
101
+ except:
102
+ return {"solution": content, "steps": [], "reasoning": "Raw output", "concepts": []}
103
+
104
+ def explain_concept(self, concept: str, level: str = 'intermediate') -> str:
105
+ """Explain concept"""
106
+ system_prompt = f"""Explica {concept} para nivel {level}. Usa Markdown."""
107
+ messages = [
108
+ {'role': 'system', 'content': system_prompt},
109
+ {'role': 'user', 'content': 'Explícalo'}
110
+ ]
111
+ return self.chat(messages, temperature=0.5)
112
+
113
+ def generate_exercises(self, topic: str, count: int = 5, difficulty: str = 'medium') -> List[Dict[str, Any]]:
114
+ """Generate exercises"""
115
+ system_prompt = f"""Genera {count} ejercicios de {topic} ({difficulty}).
116
+ Responde en JSON: [{{ "problem": "...", "solution": "...", "steps": [] }}]"""
117
+
118
+ messages = [{'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': 'Generar'}]
119
+ content = self.chat(messages)
120
+ try:
121
+ if content.startswith("```json"): content = content.replace("```json", "").replace("```","")
122
+ return json.loads(content)
123
+ except:
124
+ return []
125
+
126
+ # Singleton
127
+ kimi_service = KimiService()
binary_equalab/sonify.py CHANGED
@@ -3,6 +3,7 @@ import numpy as np
3
3
  import wave
4
4
  import struct
5
5
  import os
6
+ import re
6
7
  from typing import Union, List
7
8
  import sympy as sp
8
9
  from .parser_enhanced import EnhancedParser
@@ -27,9 +28,23 @@ class AudioEngine:
27
28
 
28
29
  # Preprocess using our EnhancedParser (supports 2t, sin^2(t))
29
30
  clean_expr = EnhancedParser.preprocess(expr_str)
31
+ # Custom replacements for Sonify (since it doesn't use Engine's full pipeline)
32
+ replacements = {
33
+ 'seno': 'sin', 'sen': 'sin',
34
+ 'coseno': 'cos', 'tangente': 'tan',
35
+ 'raiz': 'sqrt'
36
+ }
37
+ for es, en in replacements.items():
38
+ clean_expr = re.sub(rf'\b{es}\b', en, clean_expr, flags=re.IGNORECASE)
39
+
30
40
  # Also standardize python power
31
41
  clean_expr = clean_expr.replace('^', '**')
32
42
 
43
+ # Smart variable replacement: if users type 'sin(x)', they mean 'sin(t)' for audio
44
+ if 'x' in clean_expr and 't' not in clean_expr:
45
+ print("ℹ️ Auto-detecting 'x' as time variable...")
46
+ clean_expr = clean_expr.replace('x', 't')
47
+
33
48
  try:
34
49
  # Parse with SymPy
35
50
  sym_expr = sp.sympify(clean_expr)
binary_equalab/tui.py ADDED
@@ -0,0 +1,252 @@
1
+ from textual.app import App, ComposeResult
2
+ from textual.widgets import Header, Footer, Input, RichLog, Static
3
+ from textual.containers import Container
4
+ from textual import on, events
5
+ from textual.suggester import Suggester
6
+ from sympy import pretty
7
+ from .engine import MathEngine
8
+
9
+ class MathSuggester(Suggester):
10
+ """Basic auto-complete for math commands."""
11
+ def __init__(self):
12
+ super().__init__(use_cache=False)
13
+ self.suggestions = [
14
+ "derivar(", "integrar(", "limite(", "sumatoria(",
15
+ "simplificar(", "expandir(", "factorizar(", "resolver(",
16
+ "sonify(", "distancia(", "recta(", "circulo(",
17
+ "sin(", "cos(", "tan(", "sqrt(", "log(",
18
+ "matrix(", "help", "clear", "exit", "quit"
19
+ ]
20
+
21
+ async def get_suggestion(self, value: str) -> str | None:
22
+ if not value:
23
+ return None
24
+ # Simple prefix match
25
+ word = value.split(" ")[-1] # last word
26
+ if not word: return None
27
+
28
+ for s in self.suggestions:
29
+ if s.startswith(word) and s != word:
30
+ # Return the ending part to complete the word
31
+ return s[len(word):]
32
+ return None
33
+
34
+ class BinaryTUI(App):
35
+ """A Textual App for Binary EquaLab (Jupyter-Lite Style)."""
36
+
37
+ CSS = """
38
+ Screen {
39
+ layout: vertical;
40
+ background: #0f172a; /* Slate 900 */
41
+ }
42
+
43
+ RichLog {
44
+ background: #1e293b; /* Slate 800 */
45
+ color: #e2e8f0; /* Slate 200 */
46
+ border: none;
47
+ height: 1fr;
48
+ padding: 1;
49
+ scrollbar-background: #0f172a;
50
+ scrollbar-color: #3b82f6;
51
+ }
52
+
53
+ Input {
54
+ dock: bottom;
55
+ margin: 0;
56
+ border: wide #3b82f6; /* Blue 500 */
57
+ background: #0f172a;
58
+ color: #fb923c; /* Orange 400 */
59
+ height: 3;
60
+ }
61
+
62
+ #welcome {
63
+ color: #fb923c;
64
+ text-style: bold;
65
+ text-align: center;
66
+ background: #0f172a;
67
+ padding: 1;
68
+ border-bottom: solid #3b82f6;
69
+ }
70
+
71
+ .in-prompt {
72
+ color: #38bdf8; /* Sky 400 */
73
+ text-style: bold;
74
+ }
75
+
76
+ .out-prompt {
77
+ color: #fb923c; /* Orange 400 */
78
+ text-style: bold;
79
+ }
80
+
81
+ #help-bar {
82
+ background: #0f172a;
83
+ color: #94a3b8; /* Slate 400 */
84
+ padding-left: 1;
85
+ height: 1;
86
+ dock: bottom; /* Sit right above the input */
87
+ }
88
+ """
89
+
90
+ BINDINGS = [
91
+ ("ctrl+q", "quit", "Quit"),
92
+ ("ctrl+c", "copy_or_ignore", "Copy/Ignore"), # Prevent accidental exit
93
+ ("ctrl+l", "clear_screen", "Clear"),
94
+ ]
95
+
96
+ def action_copy_or_ignore(self) -> None:
97
+ """Handle Ctrl+C safely."""
98
+ self.notify("Usa Ctrl+Q o escribe 'exit' para salir.", title="No te vayas aún...", severity="warning")
99
+
100
+ def __init__(self):
101
+ super().__init__()
102
+ self.engine = MathEngine()
103
+ self.history = []
104
+ self.history_index = -1
105
+ self.execution_count = 1
106
+
107
+ # Help Dictionary for Contextual Hints
108
+ HELP_DOCS = {
109
+ "derivar": "💡 derivar(expr, var) - Calcula la derivada. Ej: derivar(x^2, x)",
110
+ "integrar": "💡 integrar(expr, var, [a, b]) - Integral indefinida o definida. Ej: integrar(sin(x), x)",
111
+ "limite": "💡 limite(expr, var, punto) - Calcula el límite. Ej: limite(sin(x)/x, x, 0)",
112
+ "sumatoria": "💡 sumatoria(expr, var, a, b) - Suma de a hasta b. Ej: sumatoria(n^2, n, 1, 10)",
113
+ "simplificar": "💡 simplificar(expr) - Reduce la expresión. Ej: simplificar((x^2-1)/(x-1))",
114
+ "expandir": "💡 expandir(expr) - Expande polinomios. Ej: expandir((x+1)^2)",
115
+ "factorizar": "💡 factorizar(expr) - Factoriza polinomios. Ej: factorizar(x^2 - 1)",
116
+ "resolver": "💡 resolver(expr, var) - Encuentra las raíces. Ej: resolver(x^2 - 4, x)",
117
+ "sonify": "💡 sonify(expr) - Genera y reproduce audio. Ej: sonify(sin(440*2*pi*t))",
118
+ "distancia": "💡 distancia(p1, p2) - Distancia Euclidiana. Ej: distancia((0,0), (3,4))",
119
+ "matrix": "💡 matrix([[a,b],[c,d]]) - Crea una matriz. Ej: matrix([[1,2],[3,4]])",
120
+ "help": "💡 presiona Enter para ver la ayuda completa.",
121
+ "clear": "💡 Limpia la pantalla.",
122
+ }
123
+
124
+ def compose(self) -> ComposeResult:
125
+ """Compose the UI."""
126
+ yield Header(show_clock=True)
127
+ yield Static("Binary EquaLab PRO terminal", id="welcome")
128
+ yield RichLog(id="output", markup=True, wrap=True)
129
+ yield Static("", id="help-bar") # New Help Bar
130
+ yield Input(
131
+ placeholder=">>> Escribe una expresión (ej. integrate(x^2))...",
132
+ id="input",
133
+ suggester=MathSuggester()
134
+ )
135
+ yield Footer()
136
+
137
+ def on_mount(self) -> None:
138
+ """Called when app starts."""
139
+ self.title = "Binary EquaLab TUI"
140
+ log = self.query_one(RichLog)
141
+ log.write("[bold green]System Ready.[/] Type 'help' for commands.")
142
+ log.write("[dim italic]Consejo: Usa 'sonify(sin(440*t))' para escuchar ecuaciones.[/dim italic]")
143
+
144
+ @on(Input.Changed)
145
+ def handle_input_change(self, event: Input.Changed) -> None:
146
+ """Update help bar based on input."""
147
+ val = event.value.strip().lower()
148
+ help_bar = self.query_one("#help-bar", Static)
149
+
150
+ # Simple prefix check
151
+ found = False
152
+ for cmd, doc in self.HELP_DOCS.items():
153
+ if val.startswith(cmd):
154
+ help_bar.update(doc)
155
+ help_bar.styles.color = "#38bdf8" # Sky blue
156
+ found = True
157
+ break
158
+
159
+ if not found:
160
+ help_bar.update("")
161
+
162
+ @on(Input.Submitted)
163
+ def handle_input(self, event: Input.Submitted) -> None:
164
+ """Handle input submission."""
165
+ command = event.value.strip()
166
+ if not command:
167
+ return
168
+
169
+ log = self.query_one(RichLog)
170
+ input_widget = self.query_one(Input)
171
+ help_bar = self.query_one("#help-bar", Static)
172
+
173
+ # Clear help
174
+ help_bar.update("")
175
+
176
+ # Add to local history
177
+ self.history.append(command)
178
+ self.history_index = len(self.history)
179
+
180
+ # Echo Input (Jupyter style)
181
+ count = self.execution_count
182
+ log.write(f"\n[bold blue]In [{count}]:[/] [white]{command}[/]")
183
+
184
+ # Clear input
185
+ input_widget.value = ""
186
+
187
+ if command.lower() in ('exit', 'quit'):
188
+ self.exit()
189
+ return
190
+
191
+ if command.lower() == 'clear':
192
+ log.clear()
193
+ return
194
+
195
+ # Process logic
196
+ try:
197
+ result = self.engine.evaluate(command)
198
+
199
+ if result is not None:
200
+ # Pretty print result (ASCII/Unicode Art)
201
+ pretty_result = pretty(result, use_unicode=True)
202
+
203
+ # Check for audio file output (Sonify)
204
+ if isinstance(result, str) and result.endswith('.wav'):
205
+ import os
206
+ import platform
207
+ if platform.system() == "Windows":
208
+ # Auto-play on Windows
209
+ try:
210
+ os.startfile(result)
211
+ log.write("[bold green]🎵 Reproduciendo audio...[/]")
212
+ except Exception as e:
213
+ log.write(f"[bold red]Error reproduciendo audio: {e}[/]")
214
+
215
+ # Format output
216
+ log.write(f"[bold orange1]Out[{count}]:[/]")
217
+ log.write(pretty_result)
218
+
219
+ except Exception as e:
220
+ log.write(f"[bold red]Error:[/bold red] {e}")
221
+
222
+ self.execution_count += 1
223
+
224
+ def on_key(self, event: events.Key) -> None:
225
+ """Handle global key events for history navigation."""
226
+ input_widget = self.query_one(Input)
227
+
228
+ if not self.history:
229
+ return
230
+
231
+ if event.key == "up":
232
+ self.history_index = max(0, self.history_index - 1)
233
+ if self.history_index < len(self.history):
234
+ input_widget.value = self.history[self.history_index]
235
+ input_widget.cursor_position = len(input_widget.value)
236
+ event.stop() # Prevent default behavior
237
+
238
+ elif event.key == "down":
239
+ self.history_index = min(len(self.history), self.history_index + 1)
240
+ if self.history_index < len(self.history):
241
+ input_widget.value = self.history[self.history_index]
242
+ else:
243
+ input_widget.value = ""
244
+ input_widget.cursor_position = len(input_widget.value)
245
+ event.stop()
246
+
247
+ def action_clear_screen(self) -> None:
248
+ self.query_one(RichLog).clear()
249
+
250
+ if __name__ == "__main__":
251
+ app = BinaryTUI()
252
+ app.run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: binary-equalab
3
- Version: 3.0.0b1
3
+ Version: 3.0.0b3
4
4
  Summary: Binary Equalab is a symbolic mathematics toolkit focused on clarity, performance and educational value. Designed for students, engineers and curious minds who want math to feel alive.
5
5
  Project-URL: Homepage, https://github.com/AldrasTeam/BinaryEquaLab
6
6
  Project-URL: Repository, https://github.com/AldrasTeam/BinaryEquaLab
@@ -26,6 +26,7 @@ Requires-Dist: requests>=2.31.0
26
26
  Requires-Dist: rich>=13.0.0
27
27
  Requires-Dist: scipy>=1.10.0
28
28
  Requires-Dist: sympy>=1.12
29
+ Requires-Dist: textual>=0.40.0
29
30
  Requires-Dist: typer>=0.9.0
30
31
  Provides-Extra: dev
31
32
  Requires-Dist: black>=23.0; extra == 'dev'
@@ -91,7 +92,21 @@ Soporta:
91
92
 
92
93
  ## 🚀 Uso del CLI
93
94
 
94
- ### REPL Mode
95
+ ### 🎮 Interactive TUI (Nuevo v3.0)
96
+ La nueva interfaz inmersiva tipo "Jupyter-Lite".
97
+
98
+ ```bash
99
+ binary-math tui
100
+ ```
101
+
102
+ **Features:**
103
+ - ✨ **Autocompletado & Hints:** Escribe y recibe ayuda contextual.
104
+ - 🎹 **Sonificación:** `sonify(sin(440*t))` reproduce el audio automáticamente.
105
+ - 📜 **Historial:** Navega con ↑ / ↓.
106
+ - 🖥️ **Pretty Print:** Ecuaciones renderizadas con Unicode.
107
+
108
+ ### Rápido REPL Mode
109
+ Para consultas rápidas sin interfaz gráfica:
95
110
  ```bash
96
111
  binary-math
97
112
  ```
@@ -0,0 +1,16 @@
1
+ binary_equalab/__init__.py,sha256=WFF_aioc3t6xkz0qU_RI4FeqY4UT92c8wE5cDDJWenY,675
2
+ binary_equalab/cli.py,sha256=XQ-PDw8KfQfkaUr7kjG_fNXObMp9IKk9CAeauS6kQG8,12510
3
+ binary_equalab/engine.py,sha256=0ZC6QcTmEKuKl0GEFioj5BR1wK0e7P8_F1W20F0zk9s,17474
4
+ binary_equalab/functions.py,sha256=F6GPCXoScCHVZ4CDEE2Rns5i3x89DAcKRVBte88j9oE,569
5
+ binary_equalab/geometry.py,sha256=YPn59oQtTY_hb4LZyIEJUvN1pWJz_y5uk8gVBio1Gwo,2217
6
+ binary_equalab/giac_poc.py,sha256=_XdxE2lrvFH7M6qDfzSQHRdplxPzKhKfIWAcsOQq8Ho,2226
7
+ binary_equalab/kimi_service.py,sha256=wofChDT1tB9lN72jwC6DcMdd8Xg2VfbJj2Mt5seQ-R0,5491
8
+ binary_equalab/parser_enhanced.py,sha256=P7oMnGHWn3xoqVChkPJ6B_i_IR9TeJMJXBjKoCO13ts,2735
9
+ binary_equalab/shell_setup.py,sha256=9Ax_QcHEAopYIjShuBaHglg4HPysXJKbNL3D_FjexrU,4518
10
+ binary_equalab/sonify.py,sha256=6SUVzfVB4A78Elr-6tz9KxGLp1C0KbslAaTOkkavUNE,3954
11
+ binary_equalab/tui.py,sha256=ZID98jgy4upEUxLjeRAPmP_VUbcmspMkII_hawTHZcc,9140
12
+ binary_equalab/services/kimi_service.py,sha256=rhEWWbw2NG-bBQR5STZi7MQuJzRNW4qmMBEc-FLic-k,4594
13
+ binary_equalab-3.0.0b3.dist-info/METADATA,sha256=qIZnHv4uJXFHBk18N5yxuqeBzLiuLG-69mSoApX9514,5845
14
+ binary_equalab-3.0.0b3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ binary_equalab-3.0.0b3.dist-info/entry_points.txt,sha256=2Io1Wi069dvgPHmGXmAT1a6rNA-DIq7tChai69jtbF0,119
16
+ binary_equalab-3.0.0b3.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- binary_equalab/__init__.py,sha256=WFF_aioc3t6xkz0qU_RI4FeqY4UT92c8wE5cDDJWenY,675
2
- binary_equalab/cli.py,sha256=kEA7Vw3gQZrRJJRFxDppqUo253jem424ZuczIwEjz60,12219
3
- binary_equalab/engine.py,sha256=WhkKpdDCg2zqH9cMXDPMQ7Ut7Awyn4WzkVrRVP7_dtE,15931
4
- binary_equalab/functions.py,sha256=F6GPCXoScCHVZ4CDEE2Rns5i3x89DAcKRVBte88j9oE,569
5
- binary_equalab/geometry.py,sha256=YPn59oQtTY_hb4LZyIEJUvN1pWJz_y5uk8gVBio1Gwo,2217
6
- binary_equalab/giac_poc.py,sha256=_XdxE2lrvFH7M6qDfzSQHRdplxPzKhKfIWAcsOQq8Ho,2226
7
- binary_equalab/kimi_service.py,sha256=wofChDT1tB9lN72jwC6DcMdd8Xg2VfbJj2Mt5seQ-R0,5491
8
- binary_equalab/parser_enhanced.py,sha256=P7oMnGHWn3xoqVChkPJ6B_i_IR9TeJMJXBjKoCO13ts,2735
9
- binary_equalab/shell_setup.py,sha256=9Ax_QcHEAopYIjShuBaHglg4HPysXJKbNL3D_FjexrU,4518
10
- binary_equalab/sonify.py,sha256=HfAYPtXLRyb3miLBsd8liqk4D5hvXVBHEEyV6fj9Za8,3274
11
- binary_equalab-3.0.0b1.dist-info/METADATA,sha256=is9DrY6iTKa1t6Z38P2SvgFbGfvzND3BhCWWGyP7_Jw,5357
12
- binary_equalab-3.0.0b1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
- binary_equalab-3.0.0b1.dist-info/entry_points.txt,sha256=2Io1Wi069dvgPHmGXmAT1a6rNA-DIq7tChai69jtbF0,119
14
- binary_equalab-3.0.0b1.dist-info/RECORD,,