lcode-cli 0.2.1__py3-none-any.whl → 0.2.2__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 +1 -1
- lcode/repl.py +113 -25
- {lcode_cli-0.2.1.dist-info → lcode_cli-0.2.2.dist-info}/METADATA +2 -2
- {lcode_cli-0.2.1.dist-info → lcode_cli-0.2.2.dist-info}/RECORD +7 -7
- {lcode_cli-0.2.1.dist-info → lcode_cli-0.2.2.dist-info}/WHEEL +0 -0
- {lcode_cli-0.2.1.dist-info → lcode_cli-0.2.2.dist-info}/entry_points.txt +0 -0
- {lcode_cli-0.2.1.dist-info → lcode_cli-0.2.2.dist-info}/licenses/LICENSE +0 -0
lcode/__init__.py
CHANGED
lcode/repl.py
CHANGED
|
@@ -21,7 +21,7 @@ from rich.table import Table
|
|
|
21
21
|
from rich.text import Text
|
|
22
22
|
|
|
23
23
|
from lcode import __version__, catalog, limits, sessions, web
|
|
24
|
-
from lcode.agent import INIT_PROMPT, Agent
|
|
24
|
+
from lcode.agent import AUTO_COMPACT_RATIO, INIT_PROMPT, Agent
|
|
25
25
|
from lcode.catalog import MIN_USEFUL_CONTEXT
|
|
26
26
|
from lcode.config import PERMISSION_MODES, STATE_DIR, ConfigError, format_tokens, parse_context
|
|
27
27
|
from lcode.hardware import Hardware
|
|
@@ -35,8 +35,8 @@ COMMANDS = {
|
|
|
35
35
|
"/rename": "Name this session so you can find it later, e.g. /rename auth refactor",
|
|
36
36
|
"/resume": "Resume a saved session: pick from a list, or /resume <number|name> (/resume all: every folder)",
|
|
37
37
|
"/compact": "Summarize the conversation to free context (optional: what to focus on)",
|
|
38
|
-
"/context": "Show context
|
|
39
|
-
"/ctx": "
|
|
38
|
+
"/context": "Show context usage and change the window size: pick from a list, or /context 128k",
|
|
39
|
+
"/ctx": "Shortcut for /context",
|
|
40
40
|
"/model": "Show or switch model, e.g. /model qwen3.5-9b",
|
|
41
41
|
"/models": "List models and how they fit this machine",
|
|
42
42
|
"/think": "Toggle model reasoning on/off",
|
|
@@ -145,6 +145,114 @@ def banner(agent: Agent) -> None:
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
CONTEXT_CHOICES = (16384, 32768, 65536, 131072, 262144, 524288, 1048576)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def context_options(agent: Agent, hardware: Hardware) -> list[dict]:
|
|
152
|
+
"""The context sizes this model supports, with how each one fits this machine."""
|
|
153
|
+
s = agent.settings
|
|
154
|
+
spec = catalog.find(s.model)
|
|
155
|
+
try:
|
|
156
|
+
max_ctx = agent.ollama.max_context(s.model) or (spec.max_context if spec else None)
|
|
157
|
+
except OllamaError:
|
|
158
|
+
max_ctx = spec.max_context if spec else None
|
|
159
|
+
sizes = {c for c in CONTEXT_CHOICES if not max_ctx or c <= max_ctx} | {s.context}
|
|
160
|
+
if max_ctx:
|
|
161
|
+
sizes.add(max_ctx)
|
|
162
|
+
learned = limits.get(s.model)
|
|
163
|
+
recommended = limits.cap(s.model, spec.fit(hardware)[0]) if spec and spec.fit(hardware)[0] else None
|
|
164
|
+
options = []
|
|
165
|
+
for size in sorted(sizes):
|
|
166
|
+
if spec:
|
|
167
|
+
memory = spec.memory_gib(size)
|
|
168
|
+
if hardware.unified:
|
|
169
|
+
fit = "fits" if memory <= hardware.budget_gib - catalog.HEADROOM_GIB else "too large"
|
|
170
|
+
elif hardware.gpu and memory <= hardware.vram_gib:
|
|
171
|
+
fit = "on GPU"
|
|
172
|
+
elif memory <= hardware.budget_gib - catalog.HEADROOM_GIB:
|
|
173
|
+
fit = "GPU + RAM" if spec.moe else "GPU + RAM, slow"
|
|
174
|
+
else:
|
|
175
|
+
fit = "too large"
|
|
176
|
+
note = f"~{memory:.0f} GB · {fit}"
|
|
177
|
+
else:
|
|
178
|
+
note = "no memory estimate for this model"
|
|
179
|
+
if learned and size > learned:
|
|
180
|
+
note += " · ran out of memory here before"
|
|
181
|
+
options.append({"size": size, "note": note, "current": size == s.context, "recommended": size == recommended})
|
|
182
|
+
return options
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def context_command(agent: Agent, arg: str, hardware: Hardware) -> None:
|
|
186
|
+
"""Show context usage and change the window size, from a list or directly (/context 128k)."""
|
|
187
|
+
c, s = agent.console, agent.settings
|
|
188
|
+
c.print(
|
|
189
|
+
f"In use: {format_tokens(agent.ctx_used)} of {format_tokens(s.context)} tokens "
|
|
190
|
+
f"({100 * agent.ctx_used / s.context:.0f}%) in {len(agent.messages)} message"
|
|
191
|
+
f"{'' if len(agent.messages) == 1 else 's'}; auto-compacts at 85%."
|
|
192
|
+
)
|
|
193
|
+
if arg:
|
|
194
|
+
try:
|
|
195
|
+
apply_context(agent, parse_context(arg), hardware)
|
|
196
|
+
except ConfigError as e:
|
|
197
|
+
c.print(f"[red]{e}[/]")
|
|
198
|
+
return
|
|
199
|
+
options = context_options(agent, hardware)
|
|
200
|
+
table = Table(title=f"Context window for {s.model}", title_justify="left", header_style="bold")
|
|
201
|
+
table.add_column("#", justify="right", style="cyan")
|
|
202
|
+
table.add_column("Size", justify="right")
|
|
203
|
+
table.add_column("Memory · fit")
|
|
204
|
+
table.add_column("")
|
|
205
|
+
for i, option in enumerate(options, 1):
|
|
206
|
+
marks = []
|
|
207
|
+
if option["current"]:
|
|
208
|
+
marks.append("[bold]current[/]")
|
|
209
|
+
if option["recommended"]:
|
|
210
|
+
marks.append("[cyan]recommended[/]")
|
|
211
|
+
table.add_row(str(i), format_tokens(option["size"]), option["note"], ", ".join(marks))
|
|
212
|
+
c.print(table)
|
|
213
|
+
try:
|
|
214
|
+
answer = input(f" Choose a number, a size like 96k, or press Enter to keep {format_tokens(s.context)}: ")
|
|
215
|
+
except EOFError:
|
|
216
|
+
return
|
|
217
|
+
answer = answer.strip()
|
|
218
|
+
if not answer:
|
|
219
|
+
c.print(f"Keeping {format_tokens(s.context)}.")
|
|
220
|
+
return
|
|
221
|
+
if answer.isdigit() and 1 <= int(answer) <= len(options):
|
|
222
|
+
size = options[int(answer) - 1]["size"]
|
|
223
|
+
else:
|
|
224
|
+
try:
|
|
225
|
+
size = parse_context(answer)
|
|
226
|
+
except ConfigError as e:
|
|
227
|
+
c.print(f"[red]{e}[/]")
|
|
228
|
+
return
|
|
229
|
+
apply_context(agent, size, hardware)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def apply_context(agent: Agent, size: int, hardware: Hardware) -> None:
|
|
233
|
+
c, s = agent.console, agent.settings
|
|
234
|
+
if size == s.context:
|
|
235
|
+
c.print(f"The context window is already {format_tokens(size)}.")
|
|
236
|
+
return
|
|
237
|
+
if agent.has_conversation() and agent.ctx_used > AUTO_COMPACT_RATIO * size:
|
|
238
|
+
c.print(
|
|
239
|
+
f"This conversation uses {format_tokens(agent.ctx_used)} tokens, too much for {format_tokens(size)}; "
|
|
240
|
+
"summarizing it first."
|
|
241
|
+
)
|
|
242
|
+
try:
|
|
243
|
+
agent.compact()
|
|
244
|
+
except OllamaError as e:
|
|
245
|
+
c.print(f"[red]Couldn't summarize the conversation, so the context stays the same: {e}[/]")
|
|
246
|
+
return
|
|
247
|
+
c.print(agent.set_context(size))
|
|
248
|
+
spec = catalog.find(s.model)
|
|
249
|
+
if spec and spec.memory_gib(s.context) > hardware.budget_gib:
|
|
250
|
+
c.print(
|
|
251
|
+
f"[yellow]~{spec.memory_gib(s.context):.0f} GB needed but ~{hardware.budget_gib:.0f} GB available; "
|
|
252
|
+
"this may run out of memory, in which case lcode falls back to a smaller size.[/]"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
148
256
|
def web_status(agent: Agent) -> str:
|
|
149
257
|
if agent.settings.web == "off":
|
|
150
258
|
return "off"
|
|
@@ -196,28 +304,8 @@ def handle_command(agent: Agent, line: str, hardware: Hardware) -> bool:
|
|
|
196
304
|
resume_session(agent, info)
|
|
197
305
|
elif cmd == "/compact":
|
|
198
306
|
agent.compact(arg)
|
|
199
|
-
elif cmd
|
|
200
|
-
|
|
201
|
-
f"Context: {format_tokens(agent.ctx_used)} of {format_tokens(s.context)} tokens "
|
|
202
|
-
f"({100 * agent.ctx_used / s.context:.1f}%) in {len(agent.messages)} messages; "
|
|
203
|
-
"auto-compacts at 85%."
|
|
204
|
-
)
|
|
205
|
-
elif cmd == "/ctx":
|
|
206
|
-
if not arg:
|
|
207
|
-
spec = catalog.find(s.model)
|
|
208
|
-
extra = f" · model maximum {format_tokens(spec.max_context)}" if spec else ""
|
|
209
|
-
c.print(f"Context window: {format_tokens(s.context)} tokens{extra}. Change it with /ctx 128k")
|
|
210
|
-
else:
|
|
211
|
-
try:
|
|
212
|
-
c.print(agent.set_context(parse_context(arg)))
|
|
213
|
-
except ConfigError as e:
|
|
214
|
-
c.print(f"[red]{e}[/]")
|
|
215
|
-
spec = catalog.find(s.model)
|
|
216
|
-
if spec and spec.memory_gib(s.context) > hardware.budget_gib:
|
|
217
|
-
c.print(
|
|
218
|
-
f"[yellow]~{spec.memory_gib(s.context):.0f} GB needed but ~{hardware.budget_gib:.0f} GB "
|
|
219
|
-
"available; this may run out of memory.[/]"
|
|
220
|
-
)
|
|
307
|
+
elif cmd in ("/context", "/ctx"):
|
|
308
|
+
context_command(agent, arg, hardware)
|
|
221
309
|
elif cmd == "/models":
|
|
222
310
|
print_models(agent.ollama, hardware, s.model)
|
|
223
311
|
elif cmd == "/model":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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/
|
|
@@ -124,7 +124,7 @@ lcode
|
|
|
124
124
|
| `lcode --model qwen3.5-9b --context 128k` | pick a model and context window for this session |
|
|
125
125
|
| `lcode models` / `lcode doctor` | what fits this machine / check the installation |
|
|
126
126
|
| `/rename`, `/resume` | name the current session, resume a saved one |
|
|
127
|
-
| `/model`, `/
|
|
127
|
+
| `/model`, `/context`, `/compact`, `/help` | switch model, resize the context window (pick from a list), summarize, list commands |
|
|
128
128
|
|
|
129
129
|
## Models
|
|
130
130
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lcode/__init__.py,sha256=
|
|
1
|
+
lcode/__init__.py,sha256=f69CJWRNbXrayd_uWS82hWDQJcvsIH6fRtjvpXj-I8s,117
|
|
2
2
|
lcode/__main__.py,sha256=s5N5pryX2FAu93GGZ3FxUrIXHWGzxz66lKUoaV_h8Ws,35
|
|
3
3
|
lcode/agent.py,sha256=ppO9sMxymIVvFPeHS-5hqTyryQYBOK_0OAucaX6SeWU,24535
|
|
4
4
|
lcode/catalog.py,sha256=pZ1azX9OH2r4JE2c6E5OfwywpgS39-W2oXzoCHJhmtM,4141
|
|
@@ -10,12 +10,12 @@ lcode/models.toml,sha256=lt2G64zzo_Ryxv2ISRSNZoVD_JqCqYqsM01n8WoXGs8,4098
|
|
|
10
10
|
lcode/ollama.py,sha256=fELwqw7xqLH9TM7XvdI1pxqYZFeIRJBv-VSz9REdvwA,6516
|
|
11
11
|
lcode/permissions.py,sha256=wJKwhJQx44AzjOFZCWwM4M2oUlPFUNYI2cIVfoBGGgc,3898
|
|
12
12
|
lcode/render.py,sha256=gMvf4W3qkLUJ4Vot3az_hP_zRDAItJC9pP-68dHFkfA,1375
|
|
13
|
-
lcode/repl.py,sha256=
|
|
13
|
+
lcode/repl.py,sha256=1InDmkMz286vuCOwRHNCTdBih_KekYkM4Ol52_g7C4s,20001
|
|
14
14
|
lcode/sessions.py,sha256=wOR-J0EHgJxLSPboKPHnvBcp5VTUYTj3tZWgpsa6-K4,4013
|
|
15
15
|
lcode/tools.py,sha256=97wPiabQuztMr4qsMgf_beNAFfraxSlU2195QJffFkg,25835
|
|
16
16
|
lcode/web.py,sha256=vyxKbSRJTl1Lj3hj7Yd_NwTgbv0Xi6dup2YfUXjqp-Y,14579
|
|
17
|
-
lcode_cli-0.2.
|
|
18
|
-
lcode_cli-0.2.
|
|
19
|
-
lcode_cli-0.2.
|
|
20
|
-
lcode_cli-0.2.
|
|
21
|
-
lcode_cli-0.2.
|
|
17
|
+
lcode_cli-0.2.2.dist-info/METADATA,sha256=XQ3SNVJtZ6MAxIM90xpJVSLQ17uCvulOJ00kyhSNbco,9232
|
|
18
|
+
lcode_cli-0.2.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
19
|
+
lcode_cli-0.2.2.dist-info/entry_points.txt,sha256=pZ_uDQ98UzVAHUQeXn5HeyQWKGSH3r3T5aPHuSB89eI,41
|
|
20
|
+
lcode_cli-0.2.2.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
|
|
21
|
+
lcode_cli-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|