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.
Files changed (43) hide show
  1. {alpiecode-6.1.0 → alpiecode-7.0.0}/PKG-INFO +1 -1
  2. {alpiecode-6.1.0 → alpiecode-7.0.0}/pyproject.toml +1 -1
  3. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/agent.py +93 -54
  5. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/cli.py +0 -3
  6. alpiecode-7.0.0/src/codeagent/extension/alpiecode.vsix +0 -0
  7. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/guardian.py +5 -2
  8. alpiecode-7.0.0/src/codeagent/ipython_ext.py +325 -0
  9. alpiecode-6.1.0/src/codeagent/extension/alpiecode.vsix +0 -0
  10. alpiecode-6.1.0/src/codeagent/ipython_ext.py +0 -156
  11. {alpiecode-6.1.0 → alpiecode-7.0.0}/README.md +0 -0
  12. {alpiecode-6.1.0 → alpiecode-7.0.0}/setup.cfg +0 -0
  13. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode/__init__.py +0 -0
  14. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
  15. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  16. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
  17. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/requires.txt +0 -0
  18. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/alpiecode.egg-info/top_level.txt +0 -0
  19. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/__init__.py +0 -0
  20. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/__init__.py +0 -0
  21. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/base.py +0 -0
  22. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/local_backend.py +0 -0
  23. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/backends/openai_backend.py +0 -0
  24. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/cache.py +0 -0
  25. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/client.py +0 -0
  26. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/compaction.py +0 -0
  27. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/config.py +0 -0
  28. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/context.py +0 -0
  29. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/discovery.py +0 -0
  30. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/doctor.py +0 -0
  31. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/executor.py +0 -0
  32. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/github.py +0 -0
  33. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/local_model.py +0 -0
  34. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/media.py +0 -0
  35. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/memory.py +0 -0
  36. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/orchestrator.py +0 -0
  37. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/progress.py +0 -0
  38. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/prompt.py +0 -0
  39. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/server.py +0 -0
  40. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/session.py +0 -0
  41. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/tools.py +0 -0
  42. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/updater.py +0 -0
  43. {alpiecode-6.1.0 → alpiecode-7.0.0}/src/codeagent/vscode_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 6.1.0
3
+ Version: 7.0.0
4
4
  Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "6.1.0"
7
+ version = "7.0.0"
8
8
  description = "AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 6.1.0
3
+ Version: 7.0.0
4
4
  Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.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
- text = Text(reasoning.strip(), style="dim italic")
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"\n💭 Thinking: {reasoning.strip()}")
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
- display_args = {}
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
- args_str = json.dumps(display_args, indent=2)
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🔧 Tool: {name}({display_args})")
104
+ console.print(f"\n {name} {summary}")
88
105
 
89
106
 
90
107
  def _print_tool_result(result: str):
91
- truncated = result[:1500] + ("..." if len(result) > 1500 else "")
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
- console.print(Text(f" {truncated}", style="green"))
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" {truncated}")
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
- md = Markdown(content)
102
- console.print(Panel(md, title="🤖 Assistant", border_style="green", padding=(0, 1)))
143
+ console.print()
144
+ console.print(Markdown(text))
103
145
  except Exception:
104
- console.print(Panel(Text(content), title="🤖 Assistant", border_style="green", padding=(0, 1)))
146
+ console.print()
147
+ console.print(text)
105
148
  else:
106
- console.print(f"\n🤖 Assistant: {content}")
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 HAS_RICH:
231
- console.rule("[bold blue]Agent Started[/bold blue]")
232
- console.print(Text(f"📋 Task: {task.splitlines()[0]}", style="bold"))
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 verbose:
308
+ if debug:
267
309
  if HAS_RICH:
268
- console.rule(f"[bold]Turn {current_turn}[/bold]", style="blue")
310
+ console.print(f"[dim]── Step {current_turn} ──[/dim]")
269
311
  else:
270
- console.rule(f"Turn {current_turn}")
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["summary"]
383
+ summary = event.data.get("summary", "")
342
384
  _checkpoint(workdir, "checkpoint: done")
343
- if verbose and HAS_RICH:
344
- if "DONE" in summary.upper():
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 verbose:
452
+ if event.type == "turn_start" and debug:
414
453
  if HAS_RICH:
415
- console.rule(f"[bold]Turn {event.data['turn']}[/bold]", style="blue")
454
+ console.print(f"[dim]── Step {event.data['turn']} ──[/dim]")
416
455
  else:
417
- console.rule(f"Turn {event.data['turn']}")
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():
@@ -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 HAS_RICH:
142
- console.print(Text(f" ⚠️ {reason}", style="yellow"))
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 &lt;task&gt;</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
@@ -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