drydock-cli 3.0.28__tar.gz → 3.0.30__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.28/drydock_cli.egg-info → drydock_cli-3.0.30}/PKG-INFO +1 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/__init__.py +1 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/config.py +1 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tools/__init__.py +10 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tui/app.py +22 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tui/widgets.py +33 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/pyproject.toml +1 -1
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/LICENSE +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/NOTICE +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/README.md +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/agent.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/cli.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/detect.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/guards.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/providers.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/setup.cfg +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_config.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.28 → drydock_cli-3.0.30}/tests/test_tuning.py +0 -0
|
@@ -217,6 +217,16 @@ def undo_last(config: dict) -> str:
|
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
def tool_write(params: dict, config: dict) -> str:
|
|
220
|
+
# Truncated/invalid tool-call JSON arrives as {"_raw": ...} with no
|
|
221
|
+
# file_path/content — usually a big file that overran the response. Say so
|
|
222
|
+
# (and how to recover) instead of the misleading "needs a real file_path".
|
|
223
|
+
if "_raw" in params and not params.get("file_path"):
|
|
224
|
+
return (
|
|
225
|
+
"Error: the Write arguments were cut off or not valid JSON, so "
|
|
226
|
+
"nothing was written. This usually means the file was too large for "
|
|
227
|
+
"one response — write it in smaller pieces (Write the first part, "
|
|
228
|
+
"then Edit to append the rest)."
|
|
229
|
+
)
|
|
220
230
|
fp = params.get("file_path")
|
|
221
231
|
# Empty/whitespace path is the canonical real-use loop bug — reject it
|
|
222
232
|
# clearly (with the content length, so the model knows the write was seen)
|
|
@@ -34,6 +34,7 @@ from drydock.tui.messages import (
|
|
|
34
34
|
AgentTurnDone,
|
|
35
35
|
)
|
|
36
36
|
from drydock.tui.widgets import (
|
|
37
|
+
SLASH_COMMANDS,
|
|
37
38
|
AssistantMessage,
|
|
38
39
|
ErrorMessage,
|
|
39
40
|
PromptArea,
|
|
@@ -325,7 +326,11 @@ class DrydockApp(App):
|
|
|
325
326
|
return ""
|
|
326
327
|
spin = _SPINNER[self._spinner_i % len(_SPINNER)]
|
|
327
328
|
elapsed = _fmt_elapsed(time.monotonic() - self._work_start)
|
|
328
|
-
|
|
329
|
+
# Session output tokens (updated each completed turn) + this turn's live
|
|
330
|
+
# streamed chars. Tool turns are non-streaming, so _work_chars stays 0 —
|
|
331
|
+
# showing the cumulative total keeps the counter meaningful instead of
|
|
332
|
+
# stuck at "0 tokens" through a long multi-step task.
|
|
333
|
+
toks = _fmt_tokens(self.state.total_output_tokens + self._work_chars // 4)
|
|
329
334
|
effort = self.state.current_effort
|
|
330
335
|
eff = f" · thinking with {effort} effort" if effort else ""
|
|
331
336
|
queued = f" · {len(self._queue)} queued" if self._queue else ""
|
|
@@ -351,6 +356,22 @@ class DrydockApp(App):
|
|
|
351
356
|
|
|
352
357
|
# ── input ─────────────────────────────────────────────────────────────
|
|
353
358
|
|
|
359
|
+
def on_text_area_changed(self, event) -> None:
|
|
360
|
+
"""As the user types a bare slash command, surface the matching commands
|
|
361
|
+
in the (idle) activity line — '/m' shows '/model'. Tab completes them."""
|
|
362
|
+
if self._busy:
|
|
363
|
+
return # #working shows live activity while a turn is running
|
|
364
|
+
try:
|
|
365
|
+
text = self.query_one("#prompt", PromptArea).text
|
|
366
|
+
except Exception: # noqa: BLE001 — widget not mounted yet
|
|
367
|
+
return
|
|
368
|
+
hint = ""
|
|
369
|
+
if text.startswith("/") and " " not in text and "\n" not in text:
|
|
370
|
+
matches = [c for c in SLASH_COMMANDS if c.startswith(text.lower())]
|
|
371
|
+
if matches:
|
|
372
|
+
hint = " " + " ".join(matches) + " · Tab to complete"
|
|
373
|
+
self.query_one("#working", Static).update(hint)
|
|
374
|
+
|
|
354
375
|
def on_prompt_area_submitted(self, event: PromptArea.Submitted) -> None:
|
|
355
376
|
text = event.text.strip()
|
|
356
377
|
prompt = self.query_one("#prompt", PromptArea)
|
|
@@ -148,6 +148,25 @@ class PromptHistory:
|
|
|
148
148
|
return self._draft
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
# The slash commands offered for completion (typing "/m" → /model). Keep in
|
|
152
|
+
# sync with app._handle_slash. Shown as a hint as the user types, and Tab
|
|
153
|
+
# completes the prefix.
|
|
154
|
+
SLASH_COMMANDS = [
|
|
155
|
+
"/help", "/model", "/cwd", "/undo", "/back", "/stop", "/status",
|
|
156
|
+
"/clear", "/quit",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _common_prefix(strings: list[str]) -> str:
|
|
161
|
+
if not strings:
|
|
162
|
+
return ""
|
|
163
|
+
pre = strings[0]
|
|
164
|
+
for s in strings[1:]:
|
|
165
|
+
while not s.startswith(pre):
|
|
166
|
+
pre = pre[:-1]
|
|
167
|
+
return pre
|
|
168
|
+
|
|
169
|
+
|
|
151
170
|
class PromptArea(TextArea):
|
|
152
171
|
"""Multi-line prompt box.
|
|
153
172
|
|
|
@@ -187,6 +206,20 @@ class PromptArea(TextArea):
|
|
|
187
206
|
event.prevent_default()
|
|
188
207
|
self.insert("\n")
|
|
189
208
|
return
|
|
209
|
+
if key == "tab":
|
|
210
|
+
t = self.text
|
|
211
|
+
if t.startswith("/") and " " not in t and "\n" not in t:
|
|
212
|
+
matches = [c for c in SLASH_COMMANDS if c.startswith(t.lower())]
|
|
213
|
+
if matches:
|
|
214
|
+
event.stop()
|
|
215
|
+
event.prevent_default()
|
|
216
|
+
# one match → complete it (+ space); several → common prefix.
|
|
217
|
+
self.text = (
|
|
218
|
+
matches[0] + " " if len(matches) == 1
|
|
219
|
+
else _common_prefix(matches)
|
|
220
|
+
)
|
|
221
|
+
self.move_cursor(self.document.end)
|
|
222
|
+
return
|
|
190
223
|
if key == "up" and self.cursor_at_first_line:
|
|
191
224
|
event.stop()
|
|
192
225
|
event.prevent_default()
|
|
@@ -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.30"
|
|
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"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|