drydock-cli 3.0.90__tar.gz → 3.0.91__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.90/drydock_cli.egg-info → drydock_cli-3.0.91}/PKG-INFO +1 -1
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tools/__init__.py +25 -1
- {drydock_cli-3.0.90 → drydock_cli-3.0.91/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock_cli.egg-info/SOURCES.txt +1 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/pyproject.toml +1 -1
- drydock_cli-3.0.91/tests/test_bash_shell.py +40 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/LICENSE +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/NOTICE +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/README.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/agent.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/cci.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/cli.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/config.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/detect.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/extract.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/guards.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/poam.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/providers.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/skills.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/stig.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tui/app.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock/web.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/setup.cfg +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_config.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.90 → drydock_cli-3.0.91}/tests/test_xccdf.py +0 -0
|
@@ -23,6 +23,29 @@ from pathlib import Path
|
|
|
23
23
|
_MAX_BASH_OUTPUT_BYTES = 256 * 1024 # 256 KB — plenty of context, safe for RAM
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _detect_bash() -> str | None:
|
|
27
|
+
"""Absolute path to bash, or None to fall back to Popen's default /bin/sh.
|
|
28
|
+
|
|
29
|
+
tool_bash runs commands under bash so the bash syntax the model naturally
|
|
30
|
+
writes — [[ ]], <<< herestrings, arrays, {1..n} brace expansion, process
|
|
31
|
+
substitution <(...), $'...' — actually works. On Debian/Ubuntu /bin/sh is
|
|
32
|
+
dash, which silently rejects all of those ("Syntax error: ... unexpected"),
|
|
33
|
+
a confusing failure the model then loops on."""
|
|
34
|
+
import shutil
|
|
35
|
+
found = shutil.which("bash")
|
|
36
|
+
if found:
|
|
37
|
+
return found
|
|
38
|
+
for p in ("/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"):
|
|
39
|
+
if os.path.exists(p):
|
|
40
|
+
return p
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Resolved once at import (in whatever environment drydock runs — host or the
|
|
45
|
+
# task container). None → bash unavailable, fall back to the default shell.
|
|
46
|
+
_BASH_SHELL = _detect_bash()
|
|
47
|
+
|
|
48
|
+
|
|
26
49
|
def _collapse_repeated_lines(text: str, run: int = 20) -> str:
|
|
27
50
|
"""Collapse a run of >= `run` IDENTICAL consecutive lines into one line + a
|
|
28
51
|
count. Repetitive output (`yes`, a spinning progress log) tokenizes densely —
|
|
@@ -910,7 +933,8 @@ def tool_bash(params: dict, config: dict) -> str:
|
|
|
910
933
|
proc = None
|
|
911
934
|
try:
|
|
912
935
|
proc = subprocess.Popen(
|
|
913
|
-
cmd, shell=True,
|
|
936
|
+
cmd, shell=True, executable=_BASH_SHELL,
|
|
937
|
+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
914
938
|
# stdin=DEVNULL so a command that reads stdin gets immediate EOF
|
|
915
939
|
# (correct for a non-interactive tool) instead of inheriting the
|
|
916
940
|
# TUI's terminal — where it would steal the user's keystrokes or hang
|
|
@@ -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.91"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""tool_bash runs commands under bash (executable=_BASH_SHELL) so the bash syntax
|
|
2
|
+
the model naturally writes works — on Debian/Ubuntu /bin/sh is dash, which rejects
|
|
3
|
+
[[ ]], <<<, arrays, {1..n}, and process substitution with confusing syntax errors
|
|
4
|
+
the model then loops on. Found while investigating the /bin/sh limitation."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from drydock.tools import tool_bash, _BASH_SHELL, _detect_bash
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _run(cmd):
|
|
13
|
+
return tool_bash({"command": cmd, "timeout": 6}, {"cwd": "/tmp"}).strip()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_bash_is_detected():
|
|
17
|
+
assert _BASH_SHELL and _BASH_SHELL.endswith("bash")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.skipif(not _BASH_SHELL, reason="bash not installed")
|
|
21
|
+
@pytest.mark.parametrize("cmd,expected", [
|
|
22
|
+
('cat <<< "hello world"', "hello world"),
|
|
23
|
+
('[[ 5 -gt 3 ]] && echo yes', "yes"),
|
|
24
|
+
('echo {1..5}', "1 2 3 4 5"),
|
|
25
|
+
('a=(x y z); echo ${a[1]}', "y"),
|
|
26
|
+
('diff <(echo a) <(echo a) && echo same', "same"),
|
|
27
|
+
])
|
|
28
|
+
def test_bashisms_work(cmd, expected):
|
|
29
|
+
assert expected in _run(cmd)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_plain_commands_and_exit_codes_unaffected():
|
|
33
|
+
assert "hello" in _run("echo hello")
|
|
34
|
+
assert "[exit code: 1]" in _run("false")
|
|
35
|
+
assert "hi" in _run("echo hi | cat")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_detect_bash_returns_path_or_none():
|
|
39
|
+
r = _detect_bash()
|
|
40
|
+
assert r is None or (isinstance(r, str) and "bash" in r)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|