alpiecode 6.1.0__tar.gz → 7.0.0__tar.gz
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.
- {alpiecode-6.1.0 → alpiecode-7.0.0}/PKG-INFO +1 -1
- {alpiecode-6.1.0 → alpiecode-7.0.0}/pyproject.toml +1 -1
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/agent.py +93 -54
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/cli.py +0 -3
- alpiecode-7.0.0/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/guardian.py +5 -2
- alpiecode-7.0.0/src/codeagent/ipython_ext.py +325 -0
- alpiecode-6.1.0/src/codeagent/extension/alpiecode.vsix +0 -0
- alpiecode-6.1.0/src/codeagent/ipython_ext.py +0 -156
- {alpiecode-6.1.0 → alpiecode-7.0.0}/README.md +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/setup.cfg +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode/__init__.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/__init__.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/base.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/openai_backend.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/cache.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/client.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/compaction.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/config.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/context.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/discovery.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/doctor.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/executor.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/github.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/local_model.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/media.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/memory.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/orchestrator.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/progress.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/prompt.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/server.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/session.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/tools.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/updater.py +0 -0
- {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/vscode_installer.py +0 -0
|
@@ -65,45 +65,88 @@ except ImportError:
|
|
|
65
65
|
def _print_reasoning(reasoning: str):
|
|
66
66
|
if not reasoning or not reasoning.strip():
|
|
67
67
|
return
|
|
68
|
+
clean = reasoning.strip()
|
|
68
69
|
if HAS_RICH:
|
|
69
|
-
|
|
70
|
-
console.print(Panel(text, title="💭 Thinking", border_style="dim blue", padding=(0, 1)))
|
|
70
|
+
console.print(Text(f"💭 {clean}", style="dim italic"))
|
|
71
71
|
else:
|
|
72
|
-
console.print(f"
|
|
72
|
+
console.print(f"💭 {clean}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _format_tool_summary(name: str, args: dict) -> str:
|
|
76
|
+
if not isinstance(args, dict):
|
|
77
|
+
return ""
|
|
78
|
+
if name == "bash":
|
|
79
|
+
cmd = args.get("command", "").strip()
|
|
80
|
+
return f"$ {cmd[:80]}..." if len(cmd) > 80 else f"$ {cmd}"
|
|
81
|
+
elif name in ("write_file", "edit_file", "read_file"):
|
|
82
|
+
path = args.get("path", "")
|
|
83
|
+
extra = ""
|
|
84
|
+
if name == "write_file" and "content" in args:
|
|
85
|
+
lines = len(str(args["content"]).splitlines())
|
|
86
|
+
extra = f" ({lines} lines)"
|
|
87
|
+
return f"{path}{extra}"
|
|
88
|
+
elif name == "list_files":
|
|
89
|
+
return args.get("path", ".")
|
|
90
|
+
elif name in ("search", "web_search"):
|
|
91
|
+
return f"'{args.get('query', '')}'"
|
|
92
|
+
elif name == "fetch_web_page":
|
|
93
|
+
return args.get("url", "")
|
|
94
|
+
else:
|
|
95
|
+
parts = [f"{k}={repr(v)[:30]}" for k, v in args.items() if k != "content"]
|
|
96
|
+
return " ".join(parts)
|
|
73
97
|
|
|
74
98
|
|
|
75
99
|
def _print_tool_call(turn: int, name: str, args: dict):
|
|
76
|
-
|
|
77
|
-
for k, v in args.items():
|
|
78
|
-
if isinstance(v, str) and len(v) > 200:
|
|
79
|
-
display_args[k] = v[:200] + "..."
|
|
80
|
-
else:
|
|
81
|
-
display_args[k] = v
|
|
100
|
+
summary = _format_tool_summary(name, args)
|
|
82
101
|
if HAS_RICH:
|
|
83
|
-
|
|
84
|
-
console.print(f"\n🔧 [bold cyan]Tool:[/bold cyan] [bold]{name}[/bold]", highlight=False)
|
|
85
|
-
console.print(Text(f" {args_str}", style="cyan"))
|
|
102
|
+
console.print(f"\n⏺ [bold cyan]{name}[/bold cyan] [white]{summary}[/white]")
|
|
86
103
|
else:
|
|
87
|
-
console.print(f"\n
|
|
104
|
+
console.print(f"\n⏺ {name} {summary}")
|
|
88
105
|
|
|
89
106
|
|
|
90
107
|
def _print_tool_result(result: str):
|
|
91
|
-
|
|
108
|
+
clean = result.strip()
|
|
109
|
+
if '"exit_code"' in clean:
|
|
110
|
+
try:
|
|
111
|
+
data = json.loads(clean.split("\n", 1)[-1] if clean.startswith("⚠️") else clean)
|
|
112
|
+
out = data.get("stdout", "").strip()
|
|
113
|
+
err = data.get("stderr", "").strip()
|
|
114
|
+
code = data.get("exit_code", 0)
|
|
115
|
+
if code == 0:
|
|
116
|
+
clean = out if out else "success"
|
|
117
|
+
else:
|
|
118
|
+
clean = f"exit {code}: {err}" if err else f"exit {code}"
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
lines = clean.splitlines()
|
|
123
|
+
if len(lines) > 8:
|
|
124
|
+
display = "\n".join(lines[:6]) + f"\n ... ({len(lines)-6} lines omitted)"
|
|
125
|
+
else:
|
|
126
|
+
display = clean[:500] + ("..." if len(clean) > 500 else "")
|
|
127
|
+
|
|
92
128
|
if HAS_RICH:
|
|
93
|
-
|
|
129
|
+
style = "dim green" if not ("exit 1" in clean or "error:" in clean.lower()[:30]) else "dim red"
|
|
130
|
+
console.print(Text(f" └ {display}", style=style))
|
|
94
131
|
else:
|
|
95
|
-
console.print(f"
|
|
132
|
+
console.print(f" └ {display}")
|
|
96
133
|
|
|
97
134
|
|
|
98
135
|
def _print_assistant_message(content: str):
|
|
136
|
+
if not content or not content.strip():
|
|
137
|
+
return
|
|
138
|
+
text = content.strip()
|
|
139
|
+
if text.upper().startswith("DONE:"):
|
|
140
|
+
text = text[5:].strip()
|
|
99
141
|
if HAS_RICH:
|
|
100
142
|
try:
|
|
101
|
-
|
|
102
|
-
console.print(
|
|
143
|
+
console.print()
|
|
144
|
+
console.print(Markdown(text))
|
|
103
145
|
except Exception:
|
|
104
|
-
console.print(
|
|
146
|
+
console.print()
|
|
147
|
+
console.print(text)
|
|
105
148
|
else:
|
|
106
|
-
|
|
149
|
+
print(f"\n{text}")
|
|
107
150
|
|
|
108
151
|
|
|
109
152
|
# ── Git helpers ───────────────────────────────────────────────────────
|
|
@@ -227,9 +270,27 @@ def run_agent(
|
|
|
227
270
|
|
|
228
271
|
elif event.type == "start" and verbose:
|
|
229
272
|
data = event.data
|
|
230
|
-
if
|
|
231
|
-
|
|
232
|
-
|
|
273
|
+
if debug:
|
|
274
|
+
if HAS_RICH:
|
|
275
|
+
console.rule("[bold blue]Agent Started (Debug)[/bold blue]")
|
|
276
|
+
console.print(Text(f"📋 Task: {task.splitlines()[0]}", style="bold"))
|
|
277
|
+
console.print(f"📂 Workdir: {workdir}", style="dim")
|
|
278
|
+
mode_str = "[bold green]ONLINE[/bold green]" if not data["is_offline"] else "[bold yellow]OFFLINE[/bold yellow]"
|
|
279
|
+
console.print(f"🌐 Mode: {mode_str}", style="dim")
|
|
280
|
+
comp = data.get("complexity", "low")
|
|
281
|
+
comp_label = {"qa": "Q&A (instant)", "low": "Low (fast)", "medium": "Medium (balanced)", "high": "High (thorough)"}.get(comp, comp)
|
|
282
|
+
console.print(f"⚡ Complexity: {comp_label}", style="dim")
|
|
283
|
+
if last_discovery:
|
|
284
|
+
console.print(f"🔍 Discovery: {last_discovery.get('intent', 'create')} on {last_discovery.get('project_type', 'empty')} project", style="dim")
|
|
285
|
+
else:
|
|
286
|
+
print(f"[Start] task={task.splitlines()[0]}, mode={'offline' if data['is_offline'] else 'online'}")
|
|
287
|
+
else:
|
|
288
|
+
# Clean, professional presentation like Claude Code / Codex
|
|
289
|
+
if data.get("is_offline"):
|
|
290
|
+
if HAS_RICH:
|
|
291
|
+
console.print("[dim yellow]⚡ Offline mode[/dim yellow]")
|
|
292
|
+
else:
|
|
293
|
+
print("⚡ Offline mode")
|
|
233
294
|
if github_repo:
|
|
234
295
|
console.print(f"🐙 GitHub Repo: {github_repo}", style="cyan")
|
|
235
296
|
if image_path:
|
|
@@ -238,36 +299,17 @@ def run_agent(
|
|
|
238
299
|
console.print(f"🎬 Video: {video_path}", style="cyan")
|
|
239
300
|
if url:
|
|
240
301
|
console.print(f"📺 URL: {url}", style="cyan")
|
|
241
|
-
console.print(f"📂 Workdir: {workdir}", style="dim")
|
|
242
|
-
if not data["is_offline"]:
|
|
243
|
-
console.print(f"🌐 Mode: [bold green]ONLINE[/bold green]", style="dim")
|
|
244
|
-
else:
|
|
245
|
-
console.print(f"🧠 Mode: [bold yellow]OFFLINE[/bold yellow]", style="dim")
|
|
246
|
-
comp = data.get("complexity", "low")
|
|
247
|
-
comp_label = {"qa": "Q&A (instant)", "low": "Low (fast)", "medium": "Medium (balanced)", "high": "High (thorough)"}.get(comp, comp)
|
|
248
|
-
comp_color = {"qa": "cyan", "low": "green", "medium": "yellow", "high": "red"}.get(comp, "white")
|
|
249
|
-
console.print(f"⚡ Complexity: [bold {comp_color}]{comp_label}[/bold {comp_color}]", style="dim")
|
|
250
|
-
if last_discovery:
|
|
251
|
-
intent_str = last_discovery.get('intent', 'create').title()
|
|
252
|
-
proj_str = last_discovery.get('project_type', 'empty')
|
|
253
|
-
console.print(f"🔍 Discovery: {intent_str} on {proj_str} project ({last_discovery.get('shell', 'bash')})", style="dim")
|
|
254
|
-
if cfg.enable_thinking:
|
|
255
|
-
console.print(f"🧠 Reasoning: [bold green]ON[/bold green]", style="dim")
|
|
256
|
-
else:
|
|
257
|
-
console.rule("Agent Started")
|
|
258
|
-
console.print(f"📋 Task: {task.splitlines()[0]}")
|
|
259
|
-
console.print(f"📂 Workdir: {workdir}")
|
|
260
302
|
|
|
261
303
|
elif event.type == "adaptive_mode" and verbose and HAS_RICH:
|
|
262
304
|
console.print("⚡ [dim]Adaptive mode: simple task detected, skipping deep reasoning for speed[/dim]")
|
|
263
305
|
|
|
264
306
|
elif event.type == "turn_start":
|
|
265
307
|
current_turn = event.data["turn"]
|
|
266
|
-
if
|
|
308
|
+
if debug:
|
|
267
309
|
if HAS_RICH:
|
|
268
|
-
console.
|
|
310
|
+
console.print(f"[dim]── Step {current_turn} ──[/dim]")
|
|
269
311
|
else:
|
|
270
|
-
|
|
312
|
+
print(f"── Step {current_turn} ──")
|
|
271
313
|
|
|
272
314
|
elif event.type == "compaction" and verbose:
|
|
273
315
|
console.print("🗜️ Compacting context (approaching token limit)...", style="yellow")
|
|
@@ -338,13 +380,10 @@ def run_agent(
|
|
|
338
380
|
print(f"\n🛑 Safety ceiling ({event.data['ceiling']}) reached.")
|
|
339
381
|
|
|
340
382
|
elif event.type == "done":
|
|
341
|
-
summary = event.data
|
|
383
|
+
summary = event.data.get("summary", "")
|
|
342
384
|
_checkpoint(workdir, "checkpoint: done")
|
|
343
|
-
if
|
|
344
|
-
|
|
345
|
-
console.rule("[bold green]✅ Task Complete[/bold green]")
|
|
346
|
-
else:
|
|
347
|
-
console.rule("[bold yellow]💬 Agent Replied[/bold yellow]")
|
|
385
|
+
if debug and HAS_RICH:
|
|
386
|
+
console.rule("[bold green]✅ Complete[/bold green]")
|
|
348
387
|
|
|
349
388
|
elif event.type == "max_turns_reached" and verbose:
|
|
350
389
|
progress = event.data.get("progress", {})
|
|
@@ -410,11 +449,11 @@ def run_chat(workdir: Path, cfg: Config, verbose: bool = True) -> None:
|
|
|
410
449
|
break
|
|
411
450
|
|
|
412
451
|
for event in orchestrator.run_task(session, user_input, cfg):
|
|
413
|
-
if event.type == "turn_start" and
|
|
452
|
+
if event.type == "turn_start" and debug:
|
|
414
453
|
if HAS_RICH:
|
|
415
|
-
console.
|
|
454
|
+
console.print(f"[dim]── Step {event.data['turn']} ──[/dim]")
|
|
416
455
|
else:
|
|
417
|
-
|
|
456
|
+
print(f"── Step {event.data['turn']} ──")
|
|
418
457
|
|
|
419
458
|
elif event.type == "thinking" and verbose:
|
|
420
459
|
_print_reasoning(event.data["content"])
|
|
@@ -162,7 +162,6 @@ def main():
|
|
|
162
162
|
if args.command == "run":
|
|
163
163
|
if args.max_turns:
|
|
164
164
|
cfg.max_turns = args.max_turns
|
|
165
|
-
_show_banner()
|
|
166
165
|
from .agent import run_agent
|
|
167
166
|
run_agent(
|
|
168
167
|
args.task, Path(args.workdir), cfg,
|
|
@@ -182,7 +181,6 @@ def main():
|
|
|
182
181
|
run_chat(Path(args.workdir), cfg, verbose=not args.quiet)
|
|
183
182
|
|
|
184
183
|
elif args.command == "plan":
|
|
185
|
-
_show_banner()
|
|
186
184
|
plan_task = (
|
|
187
185
|
f"PLANNING ONLY — Do NOT make any file edits. "
|
|
188
186
|
f"Analyze the codebase and create a detailed implementation plan for the following task. "
|
|
@@ -206,7 +204,6 @@ def main():
|
|
|
206
204
|
sys.exit(run_doctor())
|
|
207
205
|
|
|
208
206
|
elif args.command == "explain":
|
|
209
|
-
_show_banner()
|
|
210
207
|
target = args.target
|
|
211
208
|
target_path = Path(args.workdir) / target if not Path(target).is_absolute() else Path(target)
|
|
212
209
|
if target_path.exists() and target_path.is_file():
|
|
Binary file
|
|
@@ -48,6 +48,8 @@ SAFE_PREFIXES = [
|
|
|
48
48
|
"python -m pytest", "python3 -m pytest", "pytest", "npm test",
|
|
49
49
|
"npm run test", "make test", "cargo test", "go test",
|
|
50
50
|
"python -c", "python3 -c", "node -e",
|
|
51
|
+
"python3 ", "python ", "node ", "bun ", "deno ", "cargo ", "go ",
|
|
52
|
+
"make", "cmake", "gcc", "g++", "clang", "clang++", "./",
|
|
51
53
|
"tree", "sort", "uniq", "cut", "awk", "sed -n", "diff",
|
|
52
54
|
"env", "printenv", "set",
|
|
53
55
|
"type", "command -v",
|
|
@@ -138,8 +140,9 @@ def gate_command(command: str, auto_approve: bool = False) -> bool:
|
|
|
138
140
|
|
|
139
141
|
if risk == RiskLevel.WARNING:
|
|
140
142
|
if auto_approve:
|
|
141
|
-
if
|
|
142
|
-
|
|
143
|
+
if "Unknown command" not in reason:
|
|
144
|
+
if HAS_RICH:
|
|
145
|
+
console.print(Text(f" ⚠️ {reason}", style="yellow"))
|
|
143
146
|
return True
|
|
144
147
|
# In interactive mode, show warning but proceed
|
|
145
148
|
if HAS_RICH:
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IPython & Jupyter / Google Colab extension for AlpieCode.
|
|
3
|
+
|
|
4
|
+
Enables seamless usage of AlpieCode inside Jupyter Notebooks, JupyterLab,
|
|
5
|
+
and Google Colab via cell/line magic commands and rich interactive displays:
|
|
6
|
+
|
|
7
|
+
%load_ext alpiecode
|
|
8
|
+
%alpie write a quick pandas script to inspect data.csv
|
|
9
|
+
%%alpie
|
|
10
|
+
Create a complete PyTorch model training pipeline.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import html
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import warnings
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional, List, Dict, Any
|
|
19
|
+
|
|
20
|
+
# Suppress noisy Jupyter / Python 3.12+ deprecation warnings
|
|
21
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
22
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
23
|
+
warnings.filterwarnings("ignore", module="jupyter_client.*")
|
|
24
|
+
warnings.filterwarnings("ignore", module="ipykernel.*")
|
|
25
|
+
|
|
26
|
+
from .config import load_config
|
|
27
|
+
from .orchestrator import AgentOrchestrator, AgentEvent, resolve_backend
|
|
28
|
+
from .session import SessionManager
|
|
29
|
+
from .agent import run_agent
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _is_notebook() -> bool:
|
|
33
|
+
"""Check if code is running inside a Jupyter notebook or Google Colab."""
|
|
34
|
+
try:
|
|
35
|
+
from IPython import get_ipython
|
|
36
|
+
shell = get_ipython()
|
|
37
|
+
if shell is None:
|
|
38
|
+
return False
|
|
39
|
+
shell_name = shell.__class__.__name__
|
|
40
|
+
if "ZMQInteractiveShell" in shell_name or "Shell" in shell_name:
|
|
41
|
+
return True
|
|
42
|
+
return False
|
|
43
|
+
except Exception:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _display_html(html_str: str):
|
|
48
|
+
"""Render rich HTML in Jupyter or Google Colab."""
|
|
49
|
+
try:
|
|
50
|
+
from IPython.display import HTML, display
|
|
51
|
+
display(HTML(html_str))
|
|
52
|
+
except Exception:
|
|
53
|
+
print(html_str)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _display_markdown(md_str: str):
|
|
57
|
+
"""Render rich Markdown in Jupyter or Google Colab."""
|
|
58
|
+
try:
|
|
59
|
+
from IPython.display import Markdown, display
|
|
60
|
+
display(Markdown(md_str))
|
|
61
|
+
except Exception:
|
|
62
|
+
print(md_str)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _get_notebook_context() -> str:
|
|
66
|
+
"""Inspect active user variables in the notebook session (DataFrames, tensors, etc.)."""
|
|
67
|
+
try:
|
|
68
|
+
from IPython import get_ipython
|
|
69
|
+
ip = get_ipython()
|
|
70
|
+
if not ip or not hasattr(ip, "user_ns"):
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
var_summaries = []
|
|
74
|
+
for name, val in list(ip.user_ns.items()):
|
|
75
|
+
if name.startswith("_"):
|
|
76
|
+
continue
|
|
77
|
+
type_name = type(val).__name__
|
|
78
|
+
module_name = getattr(type(val), "__module__", "")
|
|
79
|
+
|
|
80
|
+
# Pandas DataFrame
|
|
81
|
+
if "pandas" in module_name and type_name == "DataFrame":
|
|
82
|
+
cols = list(val.columns)[:15]
|
|
83
|
+
var_summaries.append(f"- DataFrame `{name}`: shape={val.shape}, columns={cols}")
|
|
84
|
+
# Pandas Series
|
|
85
|
+
elif "pandas" in module_name and type_name == "Series":
|
|
86
|
+
var_summaries.append(f"- Series `{name}`: shape={val.shape}, dtype={val.dtype}")
|
|
87
|
+
# Numpy ndarray
|
|
88
|
+
elif "numpy" in module_name and type_name == "ndarray":
|
|
89
|
+
var_summaries.append(f"- NumPy Array `{name}`: shape={val.shape}, dtype={val.dtype}")
|
|
90
|
+
# PyTorch Tensor
|
|
91
|
+
elif "torch" in module_name and "Tensor" in type_name:
|
|
92
|
+
shape = list(val.shape) if hasattr(val, "shape") else []
|
|
93
|
+
var_summaries.append(f"- PyTorch Tensor `{name}`: shape={shape}, dtype={getattr(val, 'dtype', 'unknown')}")
|
|
94
|
+
|
|
95
|
+
if not var_summaries:
|
|
96
|
+
return ""
|
|
97
|
+
return "\n[Active In-Memory Notebook Variables]\n" + "\n".join(var_summaries[:8]) + "\n"
|
|
98
|
+
except Exception:
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _extract_primary_code_block(text: str) -> Optional[str]:
|
|
103
|
+
"""Extract Python code block from assistant text to insert into cell."""
|
|
104
|
+
# Match ```python ... ``` or ```py ... ```
|
|
105
|
+
match = re.search(r"```(?:python|py)\n(.*?)```", text, flags=re.DOTALL)
|
|
106
|
+
if match:
|
|
107
|
+
return match.group(1).strip()
|
|
108
|
+
# Match generic ``` ... ```
|
|
109
|
+
match = re.search(r"```\n(.*?)```", text, flags=re.DOTALL)
|
|
110
|
+
if match:
|
|
111
|
+
return match.group(1).strip()
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _render_colab_card(task: str, tool_actions: List[Dict[str, str]], content: str) -> str:
|
|
116
|
+
"""Render a modern, sleek Colab/Jupyter responsive card."""
|
|
117
|
+
task_escaped = html.escape(task[:140])
|
|
118
|
+
|
|
119
|
+
actions_html = ""
|
|
120
|
+
if tool_actions:
|
|
121
|
+
actions_html = '<div style="margin-top: 8px; margin-bottom: 8px; font-family: ui-monospace, monospace; font-size: 12px;">'
|
|
122
|
+
for act in tool_actions:
|
|
123
|
+
name = html.escape(act.get("name", ""))
|
|
124
|
+
summary = html.escape(act.get("summary", ""))
|
|
125
|
+
status = act.get("status", "✓")
|
|
126
|
+
actions_html += (
|
|
127
|
+
f'<div style="display: flex; align-items: center; gap: 6px; padding: 3px 0; color: #94a3b8;">'
|
|
128
|
+
f'<span style="color: #38bdf8;">⏺</span> '
|
|
129
|
+
f'<strong style="color: #f1f5f9;">{name}</strong> '
|
|
130
|
+
f'<span style="color: #64748b;">{summary}</span>'
|
|
131
|
+
f'</div>'
|
|
132
|
+
)
|
|
133
|
+
actions_html += '</div>'
|
|
134
|
+
|
|
135
|
+
card = (
|
|
136
|
+
f'<div style="'
|
|
137
|
+
f'background: #0f172a; border: 1px solid #1e293b; border-left: 4px solid #38bdf8; '
|
|
138
|
+
f'border-radius: 8px; padding: 12px 16px; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;'
|
|
139
|
+
f'box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);">'
|
|
140
|
+
f'<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #1e293b; padding-bottom: 8px;">'
|
|
141
|
+
f'<span style="font-weight: 600; font-size: 13px; color: #38bdf8; display: flex; align-items: center; gap: 6px;">'
|
|
142
|
+
f'⚡ AlpieCode Agent'
|
|
143
|
+
f'</span>'
|
|
144
|
+
f'<span style="font-size: 11px; color: #64748b; background: #1e293b; padding: 2px 8px; border-radius: 9999px;">'
|
|
145
|
+
f'Goal-Driven'
|
|
146
|
+
f'</span>'
|
|
147
|
+
f'</div>'
|
|
148
|
+
f'<div style="font-size: 13px; color: #cbd5e1; margin-top: 8px; font-weight: 500;">{task_escaped}</div>'
|
|
149
|
+
f'{actions_html}'
|
|
150
|
+
f'</div>'
|
|
151
|
+
)
|
|
152
|
+
return card
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def alpie_magic(line: str, cell: Optional[str] = None):
|
|
156
|
+
"""
|
|
157
|
+
%alpie <task> (line magic)
|
|
158
|
+
%%alpie (cell magic)
|
|
159
|
+
<multiline task>
|
|
160
|
+
|
|
161
|
+
Flags:
|
|
162
|
+
--insert, -i: Automatically insert generated code into the next cell
|
|
163
|
+
"""
|
|
164
|
+
raw_task = (cell if cell is not None and cell.strip() else line).strip()
|
|
165
|
+
if not raw_task:
|
|
166
|
+
print("Usage: %alpie <task> OR %%alpie\n<multiline task>")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# Check for --insert flag
|
|
170
|
+
auto_insert = False
|
|
171
|
+
if raw_task.startswith("--insert ") or raw_task.startswith("-i "):
|
|
172
|
+
auto_insert = True
|
|
173
|
+
raw_task = re.sub(r"^(--insert|-i)\s+", "", raw_task).strip()
|
|
174
|
+
|
|
175
|
+
# Append in-memory notebook context (DataFrames, tensors)
|
|
176
|
+
nb_context = _get_notebook_context()
|
|
177
|
+
full_task = f"{raw_task}\n{nb_context}" if nb_context else raw_task
|
|
178
|
+
|
|
179
|
+
cfg = load_config()
|
|
180
|
+
workdir = Path(".").resolve()
|
|
181
|
+
|
|
182
|
+
if not _is_notebook():
|
|
183
|
+
# Running in standard terminal / ipython shell
|
|
184
|
+
run_agent(full_task, workdir, cfg, verbose=True)
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
# In Jupyter or Google Colab: run with rich streaming & UI
|
|
188
|
+
from IPython import get_ipython
|
|
189
|
+
ip = get_ipython()
|
|
190
|
+
|
|
191
|
+
backend = resolve_backend(cfg)
|
|
192
|
+
orchestrator = AgentOrchestrator(backend)
|
|
193
|
+
session_mgr = SessionManager()
|
|
194
|
+
session = session_mgr.create_session(workdir, max_tokens=cfg.n_ctx if not backend.is_available else 262_144)
|
|
195
|
+
|
|
196
|
+
tool_actions: List[Dict[str, str]] = []
|
|
197
|
+
final_content = ""
|
|
198
|
+
|
|
199
|
+
for event in orchestrator.run_task(session=session, task=full_task, cfg=cfg):
|
|
200
|
+
if event.type == "tool_call":
|
|
201
|
+
t_name = event.data.get("name", "")
|
|
202
|
+
t_args = event.data.get("arguments", {})
|
|
203
|
+
if isinstance(t_args, str):
|
|
204
|
+
try:
|
|
205
|
+
import json
|
|
206
|
+
t_args = json.loads(t_args)
|
|
207
|
+
except Exception:
|
|
208
|
+
t_args = {}
|
|
209
|
+
|
|
210
|
+
summary = ""
|
|
211
|
+
if t_name == "bash":
|
|
212
|
+
cmd = t_args.get("command", "")
|
|
213
|
+
summary = f"$ {cmd[:60]}..." if len(cmd) > 60 else f"$ {cmd}"
|
|
214
|
+
elif t_name in ("write_file", "edit_file", "read_file"):
|
|
215
|
+
summary = str(t_args.get("path", ""))
|
|
216
|
+
tool_actions.append({"name": t_name, "summary": summary})
|
|
217
|
+
|
|
218
|
+
elif event.type == "message":
|
|
219
|
+
final_content = event.data.get("content", "")
|
|
220
|
+
|
|
221
|
+
elif event.type == "done":
|
|
222
|
+
if not final_content:
|
|
223
|
+
final_content = event.data.get("summary", "")
|
|
224
|
+
|
|
225
|
+
# Clean up DONE: prefix
|
|
226
|
+
cleaned_content = final_content.strip()
|
|
227
|
+
if cleaned_content.upper().startswith("DONE:"):
|
|
228
|
+
cleaned_content = cleaned_content[5:].strip()
|
|
229
|
+
|
|
230
|
+
# Render modern card & markdown
|
|
231
|
+
_display_html(_render_colab_card(raw_task, tool_actions, cleaned_content))
|
|
232
|
+
if cleaned_content:
|
|
233
|
+
_display_markdown(cleaned_content)
|
|
234
|
+
|
|
235
|
+
# If code block is present, insert into next cell if requested or suggested
|
|
236
|
+
code_block = _extract_primary_code_block(cleaned_content)
|
|
237
|
+
if code_block and (auto_insert or (cell is not None and "code" in raw_task.lower())):
|
|
238
|
+
try:
|
|
239
|
+
ip.set_next_input(code_block, replace=False)
|
|
240
|
+
_display_html(
|
|
241
|
+
'<div style="font-size: 11px; color: #10b981; margin-top: 4px; font-family: sans-serif;">'
|
|
242
|
+
'✨ Code block automatically populated in next cell. Hit <b>Shift+Enter</b> to run.'
|
|
243
|
+
'</div>'
|
|
244
|
+
)
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def alpie_plan_magic(line: str, cell: Optional[str] = None):
|
|
250
|
+
"""%alpie_plan <task> — Generate a structured implementation plan without making edits."""
|
|
251
|
+
task = (cell if cell is not None and cell.strip() else line).strip()
|
|
252
|
+
if not task:
|
|
253
|
+
print("Usage: %alpie_plan <task>")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
plan_task = (
|
|
257
|
+
f"PLANNING ONLY — Do NOT make any file edits. "
|
|
258
|
+
f"Analyze the codebase and create a detailed implementation plan for the following task. "
|
|
259
|
+
f"Use list_files, read_file, and file_search to understand the project. "
|
|
260
|
+
f"Then use update_plan to write a structured plan with deliverables and checks. "
|
|
261
|
+
f"Finish with DONE: when the plan is complete.\n\n"
|
|
262
|
+
f"Task: {task}"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
alpie_magic(plan_task)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def alpie_explain_magic(line: str):
|
|
269
|
+
"""%alpie_explain <file_path_or_concept> — Explain code or architecture."""
|
|
270
|
+
target = line.strip()
|
|
271
|
+
if not target:
|
|
272
|
+
print("Usage: %alpie_explain <file_path_or_concept>")
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
workdir = Path(".").resolve()
|
|
276
|
+
target_path = workdir / target if not Path(target).is_absolute() else Path(target)
|
|
277
|
+
|
|
278
|
+
if target_path.exists() and target_path.is_file():
|
|
279
|
+
content = target_path.read_text(encoding="utf-8", errors="replace")
|
|
280
|
+
explain_task = (
|
|
281
|
+
f"EXPLANATION ONLY — Do NOT make any file edits.\n\n"
|
|
282
|
+
f"Please explain the file `{target}` in detail:\n"
|
|
283
|
+
f"1. Overview of its purpose and role\n"
|
|
284
|
+
f"2. Key functions, classes, and internal logic\n"
|
|
285
|
+
f"3. Dependencies and how it connects to the project\n"
|
|
286
|
+
f"4. Step-by-step walkthrough of how it executes\n\n"
|
|
287
|
+
f"File content ({target}):\n```\n{content[:16000]}\n```"
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
explain_task = (
|
|
291
|
+
f"EXPLANATION ONLY — Do NOT make any file edits.\n\n"
|
|
292
|
+
f"Explain the following codebase concept or architecture in detail:\n"
|
|
293
|
+
f"Topic: {target}\n\n"
|
|
294
|
+
f"Use read_file or file_search if needed to understand codebase context."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
alpie_magic(explain_task)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def alpie_doctor_magic(line: str = ""):
|
|
301
|
+
"""%alpie_doctor — Run system health diagnostic checks."""
|
|
302
|
+
from .doctor import run_doctor
|
|
303
|
+
run_doctor()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def load_ipython_extension(ipython):
|
|
307
|
+
"""Called by IPython when user runs `%load_ext alpiecode`."""
|
|
308
|
+
ipython.register_magic_function(alpie_magic, magic_kind="line_cell", magic_name="alpie")
|
|
309
|
+
ipython.register_magic_function(alpie_plan_magic, magic_kind="line_cell", magic_name="alpie_plan")
|
|
310
|
+
ipython.register_magic_function(alpie_explain_magic, magic_kind="line", magic_name="alpie_explain")
|
|
311
|
+
ipython.register_magic_function(alpie_doctor_magic, magic_kind="line", magic_name="alpie_doctor")
|
|
312
|
+
if _is_notebook():
|
|
313
|
+
_display_html(
|
|
314
|
+
'<div style="background: #0f172a; border-left: 4px solid #38bdf8; border-radius: 6px; padding: 8px 12px; margin: 4px 0; font-family: sans-serif; font-size: 13px; color: #e2e8f0;">'
|
|
315
|
+
'<strong style="color: #38bdf8;">✨ AlpieCode IPython & Colab Extension Loaded!</strong> '
|
|
316
|
+
'<span style="color: #94a3b8; font-size: 12px;">Try: <code>%alpie <task></code> or <code>%%alpie</code> (supports <code>--insert</code> to populate code cells)</span>'
|
|
317
|
+
'</div>'
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
print("✨ AlpieCode IPython/Jupyter Extension Loaded! Try: %alpie <task> or %%alpie")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def unload_ipython_extension(ipython):
|
|
324
|
+
"""Called when user runs `%unload_ext alpiecode`."""
|
|
325
|
+
pass
|
|
Binary file
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
IPython & Jupyter / Google Colab extension for AlpieCode.
|
|
3
|
-
|
|
4
|
-
Enables seamless usage of AlpieCode inside Jupyter Notebooks, JupyterLab,
|
|
5
|
-
and Google Colab via cell/line magic commands and rich interactive displays:
|
|
6
|
-
|
|
7
|
-
%load_ext alpiecode
|
|
8
|
-
%alpie write a quick pandas script to inspect data.csv
|
|
9
|
-
%%alpie
|
|
10
|
-
Create a complete PyTorch model training pipeline.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import sys
|
|
14
|
-
import warnings
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Optional
|
|
17
|
-
|
|
18
|
-
# Suppress noisy Jupyter / Python 3.12+ deprecation warnings (e.g. datetime.utcnow in jupyter_client)
|
|
19
|
-
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
20
|
-
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
21
|
-
warnings.filterwarnings("ignore", module="jupyter_client.*")
|
|
22
|
-
warnings.filterwarnings("ignore", module="ipykernel.*")
|
|
23
|
-
|
|
24
|
-
from .config import load_config
|
|
25
|
-
from .agent import run_agent
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _is_notebook() -> bool:
|
|
29
|
-
"""Check if code is running inside a Jupyter notebook or Google Colab."""
|
|
30
|
-
try:
|
|
31
|
-
from IPython import get_ipython
|
|
32
|
-
shell = get_ipython()
|
|
33
|
-
if shell is None:
|
|
34
|
-
return False
|
|
35
|
-
shell_name = shell.__class__.__name__
|
|
36
|
-
if "ZMQInteractiveShell" in shell_name or "Shell" in shell_name:
|
|
37
|
-
return True
|
|
38
|
-
return False
|
|
39
|
-
except Exception:
|
|
40
|
-
return False
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _display_html(html_str: str):
|
|
44
|
-
"""Render rich HTML in Jupyter or Google Colab."""
|
|
45
|
-
try:
|
|
46
|
-
from IPython.display import HTML, display
|
|
47
|
-
display(HTML(html_str))
|
|
48
|
-
except Exception:
|
|
49
|
-
print(html_str)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _display_markdown(md_str: str):
|
|
53
|
-
"""Render rich Markdown in Jupyter or Google Colab."""
|
|
54
|
-
try:
|
|
55
|
-
from IPython.display import Markdown, display
|
|
56
|
-
display(Markdown(md_str))
|
|
57
|
-
except Exception:
|
|
58
|
-
print(md_str)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def alpie_magic(line: str, cell: Optional[str] = None):
|
|
62
|
-
"""
|
|
63
|
-
%alpie <task> (line magic)
|
|
64
|
-
%%alpie (cell magic)
|
|
65
|
-
<multiline task>
|
|
66
|
-
"""
|
|
67
|
-
task = (cell if cell is not None and cell.strip() else line).strip()
|
|
68
|
-
if not task:
|
|
69
|
-
print("Usage: %alpie <task> OR %%alpie\\n<multiline task>")
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
cfg = load_config()
|
|
73
|
-
workdir = Path(".").resolve()
|
|
74
|
-
|
|
75
|
-
if _is_notebook():
|
|
76
|
-
_display_html(
|
|
77
|
-
f'<div style="border-left: 3px solid #60a5fa; padding: 6px 12px; background: rgba(96,165,250,0.08); border-radius: 4px; font-family: sans-serif; margin-bottom: 8px;">'
|
|
78
|
-
f'<strong style="color: #60a5fa;">⚡ AlpieCode Agent:</strong> {task[:120]}'
|
|
79
|
-
f'</div>'
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
run_agent(task, workdir, cfg, verbose=True)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def alpie_plan_magic(line: str, cell: Optional[str] = None):
|
|
86
|
-
"""%alpie_plan <task> — Generate a structured implementation plan without making edits."""
|
|
87
|
-
task = (cell if cell is not None and cell.strip() else line).strip()
|
|
88
|
-
if not task:
|
|
89
|
-
print("Usage: %alpie_plan <task>")
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
plan_task = (
|
|
93
|
-
f"PLANNING ONLY — Do NOT make any file edits. "
|
|
94
|
-
f"Analyze the codebase and create a detailed implementation plan for the following task. "
|
|
95
|
-
f"Use list_files, read_file, and file_search to understand the project. "
|
|
96
|
-
f"Then use update_plan to write a structured plan with deliverables and checks. "
|
|
97
|
-
f"Finish with DONE: when the plan is complete.\n\n"
|
|
98
|
-
f"Task: {task}"
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
cfg = load_config()
|
|
102
|
-
workdir = Path(".").resolve()
|
|
103
|
-
run_agent(plan_task, workdir, cfg, verbose=True)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def alpie_explain_magic(line: str):
|
|
107
|
-
"""%alpie_explain <file_path_or_concept> — Explain code or architecture."""
|
|
108
|
-
target = line.strip()
|
|
109
|
-
if not target:
|
|
110
|
-
print("Usage: %alpie_explain <file_path_or_concept>")
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
cfg = load_config()
|
|
114
|
-
workdir = Path(".").resolve()
|
|
115
|
-
target_path = workdir / target if not Path(target).is_absolute() else Path(target)
|
|
116
|
-
|
|
117
|
-
if target_path.exists() and target_path.is_file():
|
|
118
|
-
content = target_path.read_text(encoding="utf-8", errors="replace")
|
|
119
|
-
explain_task = (
|
|
120
|
-
f"EXPLANATION ONLY — Do NOT make any file edits.\n\n"
|
|
121
|
-
f"Please explain the file `{target}` in detail:\n"
|
|
122
|
-
f"1. Overview of its purpose and role\n"
|
|
123
|
-
f"2. Key functions, classes, and internal logic\n"
|
|
124
|
-
f"3. Dependencies and how it connects to the project\n"
|
|
125
|
-
f"4. Step-by-step walkthrough of how it executes\n\n"
|
|
126
|
-
f"File content ({target}):\n```\n{content[:16000]}\n```"
|
|
127
|
-
)
|
|
128
|
-
else:
|
|
129
|
-
explain_task = (
|
|
130
|
-
f"EXPLANATION ONLY — Do NOT make any file edits.\n\n"
|
|
131
|
-
f"Explain the following codebase concept or architecture in detail:\n"
|
|
132
|
-
f"Topic: {target}\n\n"
|
|
133
|
-
f"Use read_file or file_search if needed to understand codebase context."
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
run_agent(explain_task, workdir, cfg, verbose=True)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def alpie_doctor_magic(line: str = ""):
|
|
140
|
-
"""%alpie_doctor — Run system health diagnostic checks."""
|
|
141
|
-
from .doctor import run_doctor
|
|
142
|
-
run_doctor()
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def load_ipython_extension(ipython):
|
|
146
|
-
"""Called by IPython when user runs `%load_ext alpiecode`."""
|
|
147
|
-
ipython.register_magic_function(alpie_magic, magic_kind="line_cell", magic_name="alpie")
|
|
148
|
-
ipython.register_magic_function(alpie_plan_magic, magic_kind="line_cell", magic_name="alpie_plan")
|
|
149
|
-
ipython.register_magic_function(alpie_explain_magic, magic_kind="line", magic_name="alpie_explain")
|
|
150
|
-
ipython.register_magic_function(alpie_doctor_magic, magic_kind="line", magic_name="alpie_doctor")
|
|
151
|
-
print("✨ AlpieCode IPython/Jupyter Extension Loaded! Try: %alpie <task> or %%alpie")
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def unload_ipython_extension(ipython):
|
|
155
|
-
"""Called when user runs `%unload_ext alpiecode`."""
|
|
156
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|