drydock-cli 3.0.21__tar.gz → 3.0.23__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.
- {drydock_cli-3.0.21/drydock_cli.egg-info → drydock_cli-3.0.23}/PKG-INFO +1 -1
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/__init__.py +1 -1
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/detect.py +5 -3
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tools/__init__.py +6 -1
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tui/app.py +55 -7
- {drydock_cli-3.0.21 → drydock_cli-3.0.23/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/pyproject.toml +1 -1
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_detect.py +2 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_guards_and_tools.py +9 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/LICENSE +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/NOTICE +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/README.md +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/agent.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/cli.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/config.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/guards.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/providers.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/setup.cfg +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_config.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.21 → drydock_cli-3.0.23}/tests/test_tuning.py +0 -0
|
@@ -61,9 +61,11 @@ def onboarding_message(found: list[dict]) -> str:
|
|
|
61
61
|
if not found:
|
|
62
62
|
ports = ", ".join(b.split("//")[1] for _, b in CANDIDATES)
|
|
63
63
|
return (
|
|
64
|
-
"No local LLM detected (" + ports + ").
|
|
65
|
-
"
|
|
66
|
-
"
|
|
64
|
+
"No local LLM detected (" + ports + "). Point Drydock at your model "
|
|
65
|
+
"right here:\n"
|
|
66
|
+
" /model url <http://host:port/v1> then /model <model-name>\n"
|
|
67
|
+
"(saved to ~/.drydock/config.toml). Or start a local server "
|
|
68
|
+
"(llama.cpp / vLLM / Ollama / LM Studio) and restart."
|
|
67
69
|
)
|
|
68
70
|
best = found[0]
|
|
69
71
|
model = best["models"][0] if best["models"] else "?"
|
|
@@ -460,7 +460,12 @@ def tool_bash(params: dict, config: dict) -> str:
|
|
|
460
460
|
output += f"\n[exit code: {result.returncode}]"
|
|
461
461
|
return output.strip() or "(no output)"
|
|
462
462
|
except subprocess.TimeoutExpired:
|
|
463
|
-
|
|
463
|
+
bigger = min(timeout * 4, 600)
|
|
464
|
+
return (
|
|
465
|
+
f"Error: command timed out after {timeout}s. If it is legitimately "
|
|
466
|
+
f"slow (a big query, build, download, or test run), retry with a "
|
|
467
|
+
f"larger timeout — pass timeout: {bigger}. Otherwise it may be hung."
|
|
468
|
+
)
|
|
464
469
|
except Exception as e:
|
|
465
470
|
return f"Error: {e}"
|
|
466
471
|
|
|
@@ -405,7 +405,8 @@ class DrydockApp(App):
|
|
|
405
405
|
self._info(
|
|
406
406
|
"Commands:\n"
|
|
407
407
|
" /help this help\n"
|
|
408
|
-
" /model
|
|
408
|
+
" /model show/set model, provider, endpoint URL\n"
|
|
409
|
+
" /model <name> · /model url <url> · /model provider <p>\n"
|
|
409
410
|
" /cwd [path] show or change the working directory\n"
|
|
410
411
|
" /undo revert the last file write/edit\n"
|
|
411
412
|
" /back rewind the last turn from the model's context\n"
|
|
@@ -418,14 +419,61 @@ class DrydockApp(App):
|
|
|
418
419
|
else:
|
|
419
420
|
self._mount(ErrorMessage(f"unknown command: {cmd} (try /help)"))
|
|
420
421
|
|
|
421
|
-
def
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
def _persist_config(self) -> None:
|
|
423
|
+
"""Save the persistable settings (model/provider/base_url/… — save_file
|
|
424
|
+
filters to those) to ~/.drydock/config.toml so setup survives restart."""
|
|
425
|
+
from drydock import config as cfgmod
|
|
426
|
+
|
|
427
|
+
ok = cfgmod.save_file(self.config, cfgmod.default_config_path())
|
|
428
|
+
if not ok:
|
|
429
|
+
self._mount(ErrorMessage("could not write ~/.drydock/config.toml"))
|
|
430
|
+
|
|
431
|
+
def _cmd_model(self, arg: str) -> None:
|
|
432
|
+
"""Model + endpoint setup. Subcommands persist so they survive restart:
|
|
433
|
+
/model show model, provider, endpoint
|
|
434
|
+
/model <name> set the model name
|
|
435
|
+
/model url <base_url> set the server URL (e.g. http://host:8000/v1)
|
|
436
|
+
/model provider <name> vllm | ollama | lmstudio | openai
|
|
437
|
+
"""
|
|
438
|
+
arg = (arg or "").strip()
|
|
439
|
+
if not arg:
|
|
440
|
+
prov = self.config.get("provider") or "(default)"
|
|
441
|
+
url = self.config.get("base_url") or "(provider default)"
|
|
442
|
+
self._info(
|
|
443
|
+
f"model: {self.config.get('model')}\n"
|
|
444
|
+
f"provider: {prov}\n"
|
|
445
|
+
f"endpoint: {url}\n"
|
|
446
|
+
"Set up: /model <name> · /model url <http://host:port/v1> · "
|
|
447
|
+
"/model provider <vllm|ollama|lmstudio|openai>"
|
|
448
|
+
)
|
|
449
|
+
return
|
|
450
|
+
parts = arg.split(maxsplit=1)
|
|
451
|
+
sub = parts[0].lower()
|
|
452
|
+
val = parts[1].strip() if len(parts) > 1 else ""
|
|
453
|
+
if sub == "url":
|
|
454
|
+
if not val:
|
|
455
|
+
self._info("usage: /model url <http://host:port/v1>")
|
|
456
|
+
return
|
|
457
|
+
self.config["base_url"] = val
|
|
458
|
+
self._persist_config()
|
|
459
|
+
self._info(f"endpoint → {val} (saved). Send a prompt to test it.")
|
|
460
|
+
return
|
|
461
|
+
if sub == "provider":
|
|
462
|
+
from drydock.providers import PROVIDERS
|
|
463
|
+
|
|
464
|
+
if val not in PROVIDERS:
|
|
465
|
+
self._info(f"unknown provider {val!r}. Choose: {', '.join(PROVIDERS)}")
|
|
466
|
+
return
|
|
467
|
+
self.config["provider"] = val
|
|
468
|
+
self._persist_config()
|
|
469
|
+
self._info(f"provider → {val} (saved)")
|
|
424
470
|
return
|
|
425
|
-
|
|
426
|
-
self.
|
|
471
|
+
# Otherwise: set the model name (the whole arg, so names with spaces work).
|
|
472
|
+
self.config["model"] = arg
|
|
473
|
+
self.system = self._build_system(arg) # prompt may be model-specific
|
|
474
|
+
self._persist_config()
|
|
427
475
|
self._refresh_status()
|
|
428
|
-
self._info(f"
|
|
476
|
+
self._info(f"model → {arg} (saved)")
|
|
429
477
|
|
|
430
478
|
def _cmd_cwd(self, path: str) -> None:
|
|
431
479
|
if not path:
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.0.
|
|
10
|
+
version = "3.0.23"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -71,3 +71,5 @@ def test_onboarding_message_when_nothing_found():
|
|
|
71
71
|
msg = detect.onboarding_message([])
|
|
72
72
|
assert "No local LLM detected" in msg
|
|
73
73
|
assert "config.toml" in msg
|
|
74
|
+
# New users must learn how to point Drydock at their model from the TUI.
|
|
75
|
+
assert "/model url" in msg and "/model" in msg
|
|
@@ -313,6 +313,15 @@ def test_file_tools_honor_config_cwd(tmp_path):
|
|
|
313
313
|
assert "x.py" in tool_bash({"command": "ls sub"}, cfg)
|
|
314
314
|
|
|
315
315
|
|
|
316
|
+
def test_tool_bash_timeout_suggests_larger_timeout():
|
|
317
|
+
from drydock.tools import tool_bash
|
|
318
|
+
out = tool_bash({"command": "sleep 2", "timeout": 1}, {})
|
|
319
|
+
assert "timed out after 1s" in out
|
|
320
|
+
# Must tell the model it can retry with a bigger timeout (so a legitimately
|
|
321
|
+
# slow query/build doesn't dead-end — what derailed query-optimize).
|
|
322
|
+
assert "timeout: 4" in out
|
|
323
|
+
|
|
324
|
+
|
|
316
325
|
def test_absolute_paths_pass_through(tmp_path):
|
|
317
326
|
fp = str(tmp_path / "abs.py")
|
|
318
327
|
tool_write({"file_path": fp, "content": "a=1\n"}, {"cwd": "/nonexistent"})
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|