lcode-cli 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lcode/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """lcode — a local-first terminal coding agent powered by open-weight models via Ollama."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.0"
lcode/agent.py CHANGED
@@ -21,7 +21,7 @@ from rich.markup import escape
21
21
  from rich.panel import Panel
22
22
  from rich.text import Text
23
23
 
24
- from lcode import catalog, limits, sessions, subagents, vision, web
24
+ from lcode import catalog, limits, planning, sessions, subagents, vision, web
25
25
  from lcode import memory as memory_notes
26
26
  from lcode.checkpoints import Checkpoints
27
27
  from lcode.config import format_tokens
@@ -32,6 +32,7 @@ from lcode.render import MarkdownStreamer
32
32
  from lcode.sandbox import Sandbox, SandboxError, project_root
33
33
  from lcode.tools import (
34
34
  MEMORY_SCHEMA,
35
+ PRESENT_PLAN_SCHEMA,
35
36
  SCHEMAS,
36
37
  VIEW_IMAGE_SCHEMA,
37
38
  WEB_FETCH_SCHEMA,
@@ -198,6 +199,7 @@ class Agent:
198
199
  self._memory: memory_notes.Memory | None = None
199
200
  self._agent_types: tuple[Path, dict[str, subagents.AgentType], list[str]] | None = None
200
201
  self.agent_runs: list[subagents.Record] = [] # subagents run in this session, for /agents
202
+ self.plan = "" # the plan the user approved; kept through compaction
201
203
  # Set on subagents (see lcode.subagents):
202
204
  self.allowed_tools: set[str] | None = None # None: every tool
203
205
  self.read_only = False # only read-only shell commands
@@ -259,6 +261,9 @@ class Agent:
259
261
  assert self._agent_types is not None
260
262
  return self._agent_types[2]
261
263
 
264
+ def planning(self) -> bool:
265
+ return self.perms.mode == "plan"
266
+
262
267
  def tool_names(self) -> set[str]:
263
268
  return {s["function"]["name"] for s in self.tool_schemas()}
264
269
 
@@ -283,6 +288,8 @@ class Agent:
283
288
  schemas.append(MEMORY_SCHEMA)
284
289
  if self.settings.subagents:
285
290
  schemas.append(subagents.schema(self.agent_types(), self.settings.max_parallel_agents))
291
+ if self.planning():
292
+ schemas.append(PRESENT_PLAN_SCHEMA)
286
293
  if self.mcp:
287
294
  schemas += self.mcp.schemas(self.settings.context)
288
295
  if self.allowed_tools is not None:
@@ -638,7 +645,8 @@ class Agent:
638
645
 
639
646
  def _run_turn(self, user_text: str, max_steps: int = MAX_STEPS_PER_TURN) -> None:
640
647
  self.prepare_mcp()
641
- self.messages.append({"role": "user", "content": self.expand_mentions(user_text)})
648
+ note = planning.NOTE if self.planning() and self.allowed_tools is None else ""
649
+ self.messages.append({"role": "user", "content": self.expand_mentions(user_text) + note})
642
650
  malformed = 0
643
651
  for _ in range(max_steps):
644
652
  if self.cancel is not None and self.cancel.is_set():
@@ -777,6 +785,8 @@ class Agent:
777
785
  {"role": "user", "content": f"[Summary of our conversation so far]\n\n{summary}"},
778
786
  {"role": "assistant", "content": "Got it — I have the context from the summary and will continue."},
779
787
  ]
788
+ if self.plan: # the approved plan stays word for word
789
+ self.messages[1]["content"] += f"\n\n[The plan the user approved; keep following it]\n\n{self.plan}"
780
790
  self.ctx_used = sum(len(m["content"]) for m in self.messages) // 3
781
791
  self.reflected = len(self.messages)
782
792
  self.console.print(Panel(Markdown(summary), title="Compacted summary", border_style="blue"))
lcode/cli.py CHANGED
@@ -519,7 +519,9 @@ def cmd_chat(args) -> None:
519
519
  context, note = choose_context(ollama, model, spec, requested_ctx, hw)
520
520
  if note:
521
521
  console.print(f"[yellow]Context {format_tokens(context)}: {note}[/]")
522
- mode = "yolo" if args.yolo else ("auto-edit" if args.auto_edit else cfg["permission_mode"])
522
+ mode = (
523
+ "yolo" if args.yolo else ("auto-edit" if args.auto_edit else ("plan" if args.plan else cfg["permission_mode"]))
524
+ )
523
525
  num_batch = cfg["num_batch"]
524
526
  if num_batch is None and spec and spec.num_batch:
525
527
  if model == spec.local_name:
@@ -589,6 +591,7 @@ def build_parser() -> argparse.ArgumentParser:
589
591
  metavar="SESSION",
590
592
  help="resume a saved session: pick from a list, or give its number, name or id",
591
593
  )
594
+ parser.add_argument("--plan", action="store_true", help="start in plan mode: agree on a plan before any change")
592
595
  parser.add_argument("--auto-edit", action="store_true", help="apply file edits without asking")
593
596
  parser.add_argument("--yolo", action="store_true", help="never ask for permission (edits and commands)")
594
597
  parser.add_argument("--no-think", action="store_true", help="disable model reasoning (faster, less accurate)")
lcode/config.py CHANGED
@@ -21,7 +21,7 @@ else:
21
21
  STATE_DIR = Path(os.environ.get("XDG_STATE_HOME") or Path.home() / ".local/state") / "lcode"
22
22
 
23
23
  DEFAULT_MODEL = "qwen3.6-35b"
24
- PERMISSION_MODES = ("ask", "auto-edit", "yolo")
24
+ PERMISSION_MODES = ("ask", "plan", "auto-edit", "yolo") # Shift+Tab cycles in this order
25
25
  WEB_MODES = ("on", "ask", "off")
26
26
  SEARCH_BACKENDS = ("auto", "ollama", "brave", "tavily", "searxng")
27
27
  MCP_TOOL_MODES = ("auto", "direct", "search")
@@ -35,7 +35,7 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
35
35
  "num_batch": (None, int, "prompt batch size; larger reads prompts faster but needs more VRAM"),
36
36
  "keep_alive": ("30m", str, "how long Ollama keeps the model loaded after the last request"),
37
37
  "ollama_host": ("http://localhost:11434", str, "Ollama server URL"),
38
- "permission_mode": ("ask", str, "ask | auto-edit | yolo"),
38
+ "permission_mode": ("ask", str, "ask | plan | auto-edit | yolo"),
39
39
  "think": (True, bool, "let the model reason before answering (slower, better)"),
40
40
  "web": ("on", str, "web search and page fetching for the model: on | ask | off"),
41
41
  "search_backend": ("auto", str, "auto | ollama | brave | tavily | searxng (keys come from environment variables)"),
lcode/mcp/commands.py CHANGED
@@ -216,9 +216,9 @@ def _ask(spec: catalog.Input, console: Console) -> str | None:
216
216
  source = " ".join(spec.from_command)
217
217
  if Confirm.ask(f" Use the token from `{source}`?", default=True, console=console):
218
218
  return suggestion
219
- default = f" [{spec.default}]" if spec.default else ""
219
+ default = f" \\[{escape(spec.default)}]" if spec.default else "" # escaped, or rich takes it for markup
220
220
  while True:
221
- value = console.input(f" {spec.prompt}{default}: ", password=spec.secret).strip()
221
+ value = console.input(f" {escape(spec.prompt)}{default}: ", password=spec.secret).strip()
222
222
  value = value or spec.default or ""
223
223
  if value or spec.optional:
224
224
  return value or None
lcode/memory.py CHANGED
@@ -507,7 +507,7 @@ def reflect(agent: Agent) -> None:
507
507
  where = "every repository" if note.scope == "user" else "this repository"
508
508
  update = memory.find(note.name, note.scope)
509
509
  rows.append(
510
- f"[cyan]{i}[/] [{note.type}] {escape(note.description)} [dim]({where}"
510
+ f"[cyan]{i}[/] \\[{note.type}] {escape(note.description)} [dim]({where}"
511
511
  f"{', updates ' + escape(update.name) if update else ''})[/]"
512
512
  + (f"\n [dim]{escape(note.details)}[/]" if note.details else "")
513
513
  )
lcode/planning.py ADDED
@@ -0,0 +1,138 @@
1
+ """Plan mode: agree on an approach before anything changes.
2
+
3
+ In the `plan` permission mode the model can read, search, run read-only commands, use the web and
4
+ read-only subagents, but can't change anything. It ends by presenting a plan with the
5
+ present_plan tool. The user approves it (lcode switches to `ask` or `auto-edit`, fills the todo
6
+ list from the plan's steps and keeps the plan through compaction), edits it, saves it to
7
+ `.lcode/plans/`, or sends it back with feedback.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ import shlex
15
+ import shutil
16
+ import subprocess
17
+ import tempfile
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING
20
+
21
+ from rich.markdown import Markdown
22
+ from rich.markup import escape
23
+ from rich.panel import Panel
24
+
25
+ from lcode.checkpoints import work_tree_for
26
+ from lcode.memory import slugify
27
+
28
+ if TYPE_CHECKING:
29
+ from lcode.agent import Agent
30
+
31
+ # Tools that can't change anything (bash is limited to read-only commands, agent to read-only types).
32
+ PLAN_MODE_TOOLS = frozenset(
33
+ {
34
+ "read_file", "list_dir", "glob", "grep", "bash", "web_search", "web_fetch", "view_image", "todo_write",
35
+ "agent", "memory", "present_plan", "mcp_find_tools",
36
+ }
37
+ ) # fmt: skip
38
+
39
+ NOTE = """
40
+
41
+ [lcode] Plan mode is on: you can read, search, run read-only commands, use the web and read-only subagents, but you can't change anything yet. Work out how to do this, then call present_plan with: numbered steps, the files you'll change and how, the risks, and how you'll verify the result. Ask the user only about decisions that are genuinely theirs."""
42
+
43
+ BLOCKED = (
44
+ "Plan mode is on, so nothing can be changed yet. Finish working out the plan and present it with "
45
+ "present_plan; once the user approves it, you can make the changes."
46
+ )
47
+
48
+ CHOICES = (
49
+ " [bold]\\[y][/] run it, asking before changes · [bold]\\[a][/] run it, edits without asking (auto-edit)\n"
50
+ " [bold]\\[e][/] edit the plan · [bold]\\[s][/] save it to .lcode/plans/ · [bold]\\[n][/] keep planning (+ what to change)"
51
+ )
52
+
53
+
54
+ def steps(plan: str, limit: int = 20) -> list[str]:
55
+ """The plan's numbered steps (top level only), for the todo list."""
56
+ found = []
57
+ for line in plan.splitlines():
58
+ m = re.match(r"^ {0,3}\d+[.)]\s+(.+)", line)
59
+ if m:
60
+ # Plain text for the todo list: no bold, code or emphasis marks (underscores in names stay).
61
+ text = m.group(1).replace("**", "").replace("__", "").replace("`", "")
62
+ text = re.sub(r"(?<!\w)[*_]([^*_]+)[*_](?!\w)", r"\1", text).strip().rstrip(":").strip()
63
+ found.append(text if len(text) <= 120 else text[:119].rstrip() + "…")
64
+ return found[:limit]
65
+
66
+
67
+ def edit_text(text: str) -> str:
68
+ """Let the user edit `text` in their editor; returns the result."""
69
+ editor = os.environ.get("VISUAL") or os.environ.get("EDITOR") or ("nano" if shutil.which("nano") else "vi")
70
+ with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False) as f:
71
+ f.write(text)
72
+ path = Path(f.name)
73
+ try:
74
+ subprocess.call([*shlex.split(editor), str(path)])
75
+ return path.read_text()
76
+ finally:
77
+ path.unlink(missing_ok=True)
78
+
79
+
80
+ def save(agent: Agent, title: str, plan: str) -> Path:
81
+ folder = work_tree_for(agent.cwd) / ".lcode" / "plans"
82
+ folder.mkdir(parents=True, exist_ok=True)
83
+ path = folder / f"{slugify(title) or 'plan'}.md"
84
+ path.write_text(f"# {title}\n\n{plan.strip()}\n")
85
+ return path
86
+
87
+
88
+ def present(agent: Agent, title: str, plan: str) -> str:
89
+ """Show the plan and let the user decide. Returns the tool result for the model."""
90
+ c = agent.console
91
+ title = " ".join(title.split()) or "Plan"
92
+ plan = plan.strip()
93
+ if not plan:
94
+ return "Error: the plan is empty. Write the steps, the files, the risks and how you'll verify the work."
95
+ edited = False
96
+ while True:
97
+ c.print(Panel(Markdown(plan), title=f"Plan: {escape(title)}", title_align="left", border_style="cyan"))
98
+ if not agent.interactive:
99
+ return "The plan was shown to the user, who will review it later. Stop here and don't change anything."
100
+ c.print(CHOICES)
101
+ try:
102
+ answer = input(" > ").strip()
103
+ except EOFError:
104
+ return "The plan was shown to the user, who will review it later. Stop here and don't change anything."
105
+ word = answer.lower()
106
+ if word in ("", "y", "yes"):
107
+ return approve(agent, title, plan, "ask", edited)
108
+ if word in ("a", "auto", "auto-edit"):
109
+ return approve(agent, title, plan, "auto-edit", edited)
110
+ if word in ("e", "edit"):
111
+ new = edit_text(plan).strip()
112
+ if new and new != plan:
113
+ plan, edited = new, True
114
+ continue
115
+ if word in ("s", "save"):
116
+ path = save(agent, title, plan)
117
+ c.print(f"[green]Saved to {escape(agent.tools.rel(path))}[/]")
118
+ continue
119
+ feedback = re.sub(r"^(no|n)\b[\s,:-]*", "", answer, flags=re.I).strip()
120
+ if feedback:
121
+ return f"The user wants changes to the plan: {feedback}\nStay in plan mode: revise the plan and present it again."
122
+ return "The user didn't approve the plan. Ask them what they'd like to change."
123
+
124
+
125
+ def approve(agent: Agent, title: str, plan: str, mode: str, edited: bool) -> str:
126
+ agent.perms.mode = mode
127
+ agent.plan = f"# {title}\n\n{plan}"
128
+ todo = steps(plan)
129
+ if todo:
130
+ agent.tools.todos = [{"content": step, "status": "pending"} for step in todo]
131
+ agent.tools.show_todos()
132
+ how = "file edits no longer ask (auto-edit)" if mode == "auto-edit" else "changes ask for approval as usual"
133
+ agent.console.print(f"[green]Plan approved.[/] Mode: {mode} — {how}.")
134
+ result = f"The user approved the plan, and plan mode is off ({mode} mode: {how}). Carry it out now, step by step"
135
+ result += ", keeping the todo list up to date." if todo else "."
136
+ if edited:
137
+ result += f"\nThe user edited the plan first. This is the approved version:\n\n{plan}"
138
+ return result
lcode/repl.py CHANGED
@@ -18,6 +18,7 @@ from prompt_toolkit.formatted_text import HTML
18
18
  from prompt_toolkit.history import FileHistory
19
19
  from prompt_toolkit.key_binding import KeyBindings
20
20
  from rich.console import Group
21
+ from rich.markdown import Markdown
21
22
  from rich.markup import escape
22
23
  from rich.panel import Panel
23
24
  from rich.rule import Rule
@@ -55,7 +56,8 @@ COMMANDS = {
55
56
  "/models": "List models and how they fit this machine",
56
57
  "/think": "Toggle model reasoning on/off",
57
58
  "/verbose": "Toggle showing the model's reasoning text",
58
- "/mode": "Permission mode: ask | auto-edit | yolo (Shift+Tab cycles)",
59
+ "/plan": "Plan before changing anything: /plan <request>, or /plan to show the approved plan",
60
+ "/mode": "Permission mode: ask | plan | auto-edit | yolo (Shift+Tab cycles)",
59
61
  "/cd": "Change the working directory",
60
62
  "/todos": "Show the current todo list",
61
63
  "/exit": "Quit",
@@ -118,7 +120,7 @@ def build_session(agent: Agent) -> PromptSession:
118
120
  def toolbar():
119
121
  s = agent.settings
120
122
  pct = 100 * agent.ctx_used / s.context
121
- color = {"ask": "ansigreen", "auto-edit": "ansiyellow", "yolo": "ansired"}[agent.perms.mode]
123
+ color = {"ask": "ansigreen", "plan": "ansicyan", "auto-edit": "ansiyellow", "yolo": "ansired"}[agent.perms.mode]
122
124
  return HTML(
123
125
  f" <b>{html.escape(s.model)}</b> · ctx {format_tokens(agent.ctx_used)}/{format_tokens(s.context)} "
124
126
  f"({pct:.0f}%) · mode <{color}>{agent.perms.mode}</{color}> (shift+tab) · "
@@ -772,6 +774,20 @@ def handle_command(agent: Agent, line: str, hardware: Hardware) -> bool:
772
774
  elif cmd == "/verbose":
773
775
  s.show_thinking = not s.show_thinking
774
776
  c.print(f"Showing reasoning text: {'on' if s.show_thinking else 'off'}.")
777
+ elif cmd == "/plan":
778
+ if not arg:
779
+ if agent.plan:
780
+ c.print(Panel(Markdown(agent.plan), title="Approved plan", title_align="left", border_style="cyan"))
781
+ else:
782
+ c.print(
783
+ "No approved plan in this session. [bold]/plan <request>[/] works out a plan first, without "
784
+ "changing anything (or Shift+Tab to plan mode)."
785
+ )
786
+ return True
787
+ agent.perms.mode = "plan"
788
+ c.print("[cyan]Plan mode:[/] the model can look around but not change anything until you approve its plan.")
789
+ c.print(Rule(style="dim"))
790
+ run_safely(agent, arg)
775
791
  elif cmd == "/mode":
776
792
  if arg in PERMISSION_MODES:
777
793
  agent.perms.mode = arg
lcode/tools.py CHANGED
@@ -24,6 +24,7 @@ from rich.text import Text
24
24
 
25
25
  from lcode import web
26
26
  from lcode.permissions import bash_key, is_read_only
27
+ from lcode.planning import BLOCKED, PLAN_MODE_TOOLS
27
28
  from lcode.sandbox import SandboxError
28
29
  from lcode.vision import is_image
29
30
 
@@ -198,8 +199,22 @@ MEMORY_SCHEMA = _fn(
198
199
  },
199
200
  ["action"],
200
201
  )
202
+ PRESENT_PLAN_SCHEMA = _fn(
203
+ "present_plan",
204
+ "Plan mode: show the user your plan and ask for approval. Once approved, plan mode ends and you carry it out.",
205
+ {
206
+ "title": {"type": "string", "description": "A short name for the plan, e.g. 'Add retries to webhooks'"},
207
+ "plan": {
208
+ "type": "string",
209
+ "description": "Markdown: numbered steps, the files you'll change and how, the risks, and how you'll "
210
+ "verify the result",
211
+ },
212
+ },
213
+ ["title", "plan"],
214
+ )
201
215
  TOOL_NAMES = {
202
- s["function"]["name"] for s in [*SCHEMAS, WEB_SEARCH_SCHEMA, WEB_FETCH_SCHEMA, VIEW_IMAGE_SCHEMA, MEMORY_SCHEMA]
216
+ s["function"]["name"]
217
+ for s in [*SCHEMAS, WEB_SEARCH_SCHEMA, WEB_FETCH_SCHEMA, VIEW_IMAGE_SCHEMA, MEMORY_SCHEMA, PRESENT_PLAN_SCHEMA]
203
218
  }
204
219
 
205
220
 
@@ -337,6 +352,10 @@ class Toolbox:
337
352
  allowed = self.agent.allowed_tools
338
353
  if allowed is not None and name not in allowed:
339
354
  return f"Error: {name} isn't available to you. Available: {', '.join(sorted(allowed))}"
355
+ if self.agent.planning() and name not in PLAN_MODE_TOOLS:
356
+ return f"Error: {BLOCKED}"
357
+ if name == "present_plan" and not self.agent.planning():
358
+ return "Error: plan mode is off, so there's nothing to present: carry on with the work."
340
359
  if self.agent.mcp and self.agent.mcp.owns(name):
341
360
  return self._mcp(name, args)
342
361
  fn = getattr(self, f"t_{name}", None)
@@ -376,8 +395,17 @@ class Toolbox:
376
395
  raise ToolError("subagents are turned off")
377
396
  from lcode import subagents
378
397
 
398
+ kind = self.agent.agent_types().get(type)
399
+ if self.agent.planning() and kind and not kind.read_only:
400
+ raise ToolError(f"plan mode is on: only read-only agents can run now ({BLOCKED})")
379
401
  return subagents.run_one(self.agent, {"type": type, "task": task, "description": description})
380
402
 
403
+ # -- plan mode
404
+ def t_present_plan(self, title: str, plan: str) -> str:
405
+ from lcode.planning import present
406
+
407
+ return present(self.agent, title, plan)
408
+
381
409
  # -- memory
382
410
  def t_memory(
383
411
  self, action: str, name: str = "", type: str = "", description: str = "", details: str = "", scope: str = ""
@@ -605,6 +633,8 @@ class Toolbox:
605
633
  # -- shell
606
634
  def t_bash(self, command: str, timeout: int = 180) -> str:
607
635
  sandbox = self.agent.sandbox
636
+ if self.agent.planning() and not is_read_only(command):
637
+ raise ToolError(f"{BLOCKED} Until then, only read-only commands run (ls, cat, grep, git log, …).")
608
638
  if self.agent.read_only and not is_read_only(command):
609
639
  raise ToolError(
610
640
  "you can only run read-only commands (such as ls, cat, grep, find, git log, git diff), one at a "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
5
5
  Project-URL: Homepage, https://nasser1941.github.io/lcode/
6
6
  Project-URL: Documentation, https://nasser1941.github.io/lcode/
@@ -80,6 +80,8 @@ through [Ollama](https://ollama.com), and when it needs current information it c
80
80
  your approval. `auto-edit` and `yolo` modes when you want speed.
81
81
  - **Sandbox (optional).** `lcode --sandbox` runs the model's shell commands in a container (Docker or
82
82
  Podman) that only sees your project, without network access unless you allow it.
83
+ - **Plan mode.** `/plan <request>` or Shift+Tab: the model explores read-only and presents a plan;
84
+ you approve, edit or send it back before anything changes.
83
85
  - **Undo.** lcode saves a checkpoint before the model changes files; `/undo` takes back the last
84
86
  request's edits, new files and shell-command changes, without touching your git history.
85
87
  - **Subagents.** The model hands broad searches, planning and self-contained changes to subagents
@@ -195,7 +197,7 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
195
197
 
196
198
  See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
197
199
 
198
- - [Plan mode](https://github.com/nasser1941/lcode/issues/41) and [custom commands and skills](https://github.com/nasser1941/lcode/issues/42)
200
+ - [Custom commands and skills](https://github.com/nasser1941/lcode/issues/42)
199
201
  - [Ready-made MCP for Metabase, Encord and Valohai](https://github.com/nasser1941/lcode/issues/53)
200
202
  - Then: [smarter context for small windows](https://github.com/nasser1941/lcode/issues/46), [language servers](https://github.com/nasser1941/lcode/issues/44), [a repository map with local embeddings](https://github.com/nasser1941/lcode/issues/45), [hooks](https://github.com/nasser1941/lcode/issues/43)
201
203
  - More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
@@ -1,23 +1,24 @@
1
- lcode/__init__.py,sha256=1p3bazCEuQ0WZP-SgRA-g8KVP4Avchrem5OtlxRTRHc,117
1
+ lcode/__init__.py,sha256=nFv9AKdtRRoIhmSP_gIiY5ftUlCFh_s-FQeBjbqj6fI,117
2
2
  lcode/__main__.py,sha256=s5N5pryX2FAu93GGZ3FxUrIXHWGzxz66lKUoaV_h8Ws,35
3
- lcode/agent.py,sha256=uNQx8pcTzs4k05100wJedoPCZ7dUFjJz2OD2e__49VM,38597
3
+ lcode/agent.py,sha256=fP52AvWe7WfTXmzbM9BH-_rIUtdEf2Ix1iBspeqcTr4,39130
4
4
  lcode/bench.py,sha256=FCn94Gs7L8bpq4hzf6ILoQG8dKrbbd5WC4BRwrTp4LM,37950
5
5
  lcode/catalog.py,sha256=pZ1azX9OH2r4JE2c6E5OfwywpgS39-W2oXzoCHJhmtM,4141
6
6
  lcode/checkpoints.py,sha256=eqPtmPe44-aweWqc2H21ROogwzIt3NpvcUobsaCHoGY,18354
7
- lcode/cli.py,sha256=uc9F2HDflhu6cZbb2wARjllmM7KpN0FB9UfnyyFx_9U,29450
8
- lcode/config.py,sha256=KYgBRO-WN0Sl_YWd-XudqfE5NkV34w9OzQQJ3Apg2bs,7898
7
+ lcode/cli.py,sha256=Bh9wdAe5fZPzfKUdsbebpMObxqKxLZIDsI3OldhddO8,29610
8
+ lcode/config.py,sha256=Wle_Gwv-FWQmD9jBt8jiHNtmdn3O5bOgkrZ6tbD7_9o,7947
9
9
  lcode/hardware.py,sha256=2VbknZCxTZJwJUD8YWdQ7ryEQo_LcjtoOBOHOU9Y0fw,3085
10
10
  lcode/limits.py,sha256=oJzVQoMyTZ0_Fkpu5t3qxrf9v7Hv9S8U8BRQR5M1aKA,1237
11
- lcode/memory.py,sha256=ocr_2zDzmH1OCwrVjC-UmzRn4Sr1I_OeazhbIoSdIf0,22359
11
+ lcode/memory.py,sha256=SWB_xt_fdL2hX1mcUqK0drU7laZmg1CqIs0IIAehnCo,22361
12
12
  lcode/models.toml,sha256=lt2G64zzo_Ryxv2ISRSNZoVD_JqCqYqsM01n8WoXGs8,4098
13
13
  lcode/ollama.py,sha256=0f2rkrm3_5Xcnuyvaxcfx5SeYVyRvHjiROXryHKmRUw,7085
14
14
  lcode/permissions.py,sha256=m8YNtCladVAmXB20ca8AwyfEOzHKS557B0SOvx1oHeM,4528
15
+ lcode/planning.py,sha256=C7_JyArIywPygU9DdfaoN2EfgA6JGNnKafuIilkOzGw,6067
15
16
  lcode/render.py,sha256=gMvf4W3qkLUJ4Vot3az_hP_zRDAItJC9pP-68dHFkfA,1375
16
- lcode/repl.py,sha256=0v_b5fFpxdAQU3TreTpDXdAUjWwePMkDW9MGIPiefNU,39521
17
+ lcode/repl.py,sha256=K04EpyIxyrP6I3oPh9tp8xqoIubplqHRXvWbqFdOKks,40356
17
18
  lcode/sandbox.py,sha256=u_pkrVRXhCGhh-wFDPwuZ3jGG7UeXxXRAeBNsm37ZvE,8608
18
19
  lcode/sessions.py,sha256=wOR-J0EHgJxLSPboKPHnvBcp5VTUYTj3tZWgpsa6-K4,4013
19
20
  lcode/subagents.py,sha256=L5U-USz2fnGOx3Z28_sOA6btWmMKSGXVbFKQYcrZSDM,28147
20
- lcode/tools.py,sha256=179FscWa9zmFGWEpbJekdpuBwheH2Oh_H9XTAP48yxM,33391
21
+ lcode/tools.py,sha256=VNhNkC43x2l20kqv06cbDXmdJHugDncFX0AIUfAr3CE,34820
21
22
  lcode/vision.py,sha256=w33QPmdGwP33wM4mvmAH4amFDZ1xzeXhEsSLJ-STSHY,4243
22
23
  lcode/web.py,sha256=vyxKbSRJTl1Lj3hj7Yd_NwTgbv0Xi6dup2YfUXjqp-Y,14579
23
24
  lcode/mcp/__init__.py,sha256=XujtAmRoXW9S0uq1XPf_CAeCkHh_UvA8FCmoAKP9j1Y,171
@@ -25,14 +26,14 @@ lcode/mcp/auth.py,sha256=vyH3oolgIBNJLYabr2h5Soui99NTY1QxjQEgtczfF6A,16655
25
26
  lcode/mcp/catalog.py,sha256=GnIF1fyeHSut9h2y_VJNAHc2FCJT81EIhLFM40V780k,3504
26
27
  lcode/mcp/catalog.toml,sha256=_kFzKD2FD78KD2URuE5B-kcQehW9Y3Zi2xf5tu3v4Xc,9230
27
28
  lcode/mcp/client.py,sha256=3Gen7zRvWehngXn_dAMOAdKqFkFkQZIQW96hQcW4N4k,7119
28
- lcode/mcp/commands.py,sha256=Rlnc-LFydRjHTiW-CSxjmQqX0d2MSiEViLF9kczYIi4,16420
29
+ lcode/mcp/commands.py,sha256=mnp0G-GW5ZsARi2tkHuT3hl883sBGFUVRAV3AapA5oU,16478
29
30
  lcode/mcp/config.py,sha256=Ej6WX2b_55a84H5WdAwSk9DzERKoq5ifUBQ9uDgSG_o,8780
30
31
  lcode/mcp/http.py,sha256=sG9MajwU4_6sqvZhwD4WfyzRKzaR_Lc8C02jwePz18E,7380
31
32
  lcode/mcp/manager.py,sha256=79nNWoT5pxgoQD_Pv4ZH0obHIm7rQ-LyB8OmJnQgc6k,15104
32
33
  lcode/mcp/protocol.py,sha256=-xwhA2Tz-vzR2zdSdbPx4IjTu29uRrXMaePg0vEz_Vc,5223
33
34
  lcode/mcp/stdio.py,sha256=kC6KHwzMMDjBr-yCZLmyVKS7PZXU299WpZdYRoX5eRQ,6292
34
- lcode_cli-0.8.0.dist-info/METADATA,sha256=R2rlCNEpaoajSJzoThzX77UeTvAV_jv2qwQUlGeOoBI,11282
35
- lcode_cli-0.8.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
- lcode_cli-0.8.0.dist-info/entry_points.txt,sha256=pZ_uDQ98UzVAHUQeXn5HeyQWKGSH3r3T5aPHuSB89eI,41
37
- lcode_cli-0.8.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
38
- lcode_cli-0.8.0.dist-info/RECORD,,
35
+ lcode_cli-0.9.0.dist-info/METADATA,sha256=DW7hETwyh_0QCwMacZY-ofQbFPMPZTgChvrxjXDTb-8,11379
36
+ lcode_cli-0.9.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
37
+ lcode_cli-0.9.0.dist-info/entry_points.txt,sha256=pZ_uDQ98UzVAHUQeXn5HeyQWKGSH3r3T5aPHuSB89eI,41
38
+ lcode_cli-0.9.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
39
+ lcode_cli-0.9.0.dist-info/RECORD,,