drydock-cli 3.0.76__tar.gz → 3.0.77__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.
Files changed (98) hide show
  1. {drydock_cli-3.0.76/drydock_cli.egg-info → drydock_cli-3.0.77}/PKG-INFO +5 -1
  2. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/README.md +4 -0
  3. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/providers.py +18 -9
  4. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tui/app.py +8 -0
  5. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tuning.py +18 -6
  6. {drydock_cli-3.0.76 → drydock_cli-3.0.77/drydock_cli.egg-info}/PKG-INFO +5 -1
  7. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/pyproject.toml +1 -1
  8. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_vision_input.py +20 -0
  9. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/LICENSE +0 -0
  10. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/NOTICE +0 -0
  11. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/agent.py +0 -0
  14. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/compaction.py +0 -0
  25. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/config.py +0 -0
  26. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/detect.py +0 -0
  27. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/extract.py +0 -0
  28. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/gittools.py +0 -0
  29. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/graphrag.py +0 -0
  30. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/guards.py +0 -0
  31. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/loop_detect.py +0 -0
  32. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/mcp.py +0 -0
  33. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/rmf.py +0 -0
  34. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/rmf_graph.py +0 -0
  35. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/skills.py +0 -0
  36. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/stig.py +0 -0
  37. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tool_registry.py +0 -0
  38. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tools/__init__.py +0 -0
  39. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tui/__init__.py +0 -0
  40. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tui/approval.py +0 -0
  41. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tui/messages.py +0 -0
  42. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/tui/widgets.py +0 -0
  43. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock/web.py +0 -0
  44. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock_cli.egg-info/SOURCES.txt +0 -0
  45. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock_cli.egg-info/dependency_links.txt +0 -0
  46. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock_cli.egg-info/entry_points.txt +0 -0
  47. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock_cli.egg-info/requires.txt +0 -0
  48. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/drydock_cli.egg-info/top_level.txt +0 -0
  49. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/setup.cfg +0 -0
  50. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_approval.py +0 -0
  51. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_back_command.py +0 -0
  52. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_bash_output_bounding.py +0 -0
  53. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_bash_process_group.py +0 -0
  54. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_bash_safety.py +0 -0
  55. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_bash_timeout_network.py +0 -0
  56. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_cci.py +0 -0
  57. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_cli_agents.py +0 -0
  58. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_compact_command.py +0 -0
  59. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_compaction.py +0 -0
  60. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_config.py +0 -0
  61. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_config_migration.py +0 -0
  62. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_context_limit_config.py +0 -0
  63. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_detect.py +0 -0
  64. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_dispatch.py +0 -0
  65. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_e2e_connected.py +0 -0
  66. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_empty_response.py +0 -0
  67. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_extract.py +0 -0
  68. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_failure_loop.py +0 -0
  69. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_first_run_setup.py +0 -0
  70. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_gittools.py +0 -0
  71. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_graphrag.py +0 -0
  72. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_guards_and_tools.py +0 -0
  73. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_hallucinated_tools.py +0 -0
  74. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_leaked_tool_call.py +0 -0
  75. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_loop_detect.py +0 -0
  76. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_mcp.py +0 -0
  77. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_oneshot_unreachable.py +0 -0
  78. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_plan_autocontinue.py +0 -0
  79. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_providers_unreachable.py +0 -0
  80. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_read_index.py +0 -0
  81. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_rmf.py +0 -0
  82. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_rmf_graph.py +0 -0
  83. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_rmf_stig_graph.py +0 -0
  84. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_runaway_repetition.py +0 -0
  85. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_server_probe.py +0 -0
  86. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_skills.py +0 -0
  87. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_stig.py +0 -0
  88. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_stop.py +0 -0
  89. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_streaming_newlines.py +0 -0
  90. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_subagent.py +0 -0
  91. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_system_prompt_help.py +0 -0
  92. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_todo.py +0 -0
  93. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_tool_arg_parsing.py +0 -0
  94. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_tools_undo.py +0 -0
  95. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_tui.py +0 -0
  96. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_tuning.py +0 -0
  97. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_web_tools.py +0 -0
  98. {drydock_cli-3.0.76 → drydock_cli-3.0.77}/tests/test_xccdf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.76
3
+ Version: 3.0.77
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -59,6 +59,10 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
59
59
 
60
60
  - **Files & shell** — `Read` (with a structure index for huge files), `Write`,
61
61
  `Edit`, `Bash`, `Glob`, `Grep`.
62
+ - **Vision (multimodal)** — reference an image path in your message (a `.png`/
63
+ `.jpg` screenshot, mockup, or diagram) and it's attached for a vision-capable
64
+ model to *see* — describe a UI, read text off a screenshot, debug a diagram.
65
+ Works with any `--mmproj`-enabled server (e.g. llama.cpp with a vision model).
62
66
  - **Version control** — `GitStatus`, `GitDiff`, `GitLog`, `GitCommit`
63
67
  (structured + truncated; commit is local and reversible).
64
68
  - **Internet** — `WebSearch` + `WebFetch` (DuckDuckGo; offline-safe).
@@ -35,6 +35,10 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
35
35
 
36
36
  - **Files & shell** — `Read` (with a structure index for huge files), `Write`,
37
37
  `Edit`, `Bash`, `Glob`, `Grep`.
38
+ - **Vision (multimodal)** — reference an image path in your message (a `.png`/
39
+ `.jpg` screenshot, mockup, or diagram) and it's attached for a vision-capable
40
+ model to *see* — describe a UI, read text off a screenshot, debug a diagram.
41
+ Works with any `--mmproj`-enabled server (e.g. llama.cpp with a vision model).
38
42
  - **Version control** — `GitStatus`, `GitDiff`, `GitLog`, `GitCommit`
39
43
  (structured + truncated; commit is local and reversible).
40
44
  - **Internet** — `WebSearch` + `WebFetch` (DuckDuckGo; offline-safe).
@@ -250,16 +250,11 @@ _IMAGE_MIME = {
250
250
  }
251
251
 
252
252
 
253
- def _user_content_with_images(content):
254
- """Vision support: if the user's text references image file paths that exist
255
- on disk, attach them as OpenAI multimodal image_url blocks (works with any
256
- --mmproj-enabled server). Text-only prompts pass through unchanged as a plain
257
- string, so display / loop-detection / compaction / token-counting (which all
258
- assume string content) are untouched — the multimodal list is built ONLY here,
259
- at the API boundary."""
253
+ def detect_image_paths(content) -> list[str]:
254
+ """Image file paths referenced in a user message that exist on disk. Shared
255
+ by the multimodal attach (below) and the TUI (to confirm '📎 attached')."""
260
256
  if not isinstance(content, str) or "." not in content:
261
- return content
262
- import base64
257
+ return []
263
258
  import os
264
259
  import re
265
260
 
@@ -281,8 +276,22 @@ def _user_content_with_images(content):
281
276
  p = os.path.expanduser(raw)
282
277
  if os.path.isfile(p) and p not in seen:
283
278
  seen.append(p)
279
+ return seen
280
+
281
+
282
+ def _user_content_with_images(content):
283
+ """Vision support: if the user's text references image file paths that exist
284
+ on disk, attach them as OpenAI multimodal image_url blocks (works with any
285
+ --mmproj-enabled server). Text-only prompts pass through unchanged as a plain
286
+ string, so display / loop-detection / compaction / token-counting (which all
287
+ assume string content) are untouched — the multimodal list is built ONLY here,
288
+ at the API boundary."""
289
+ seen = detect_image_paths(content)
284
290
  if not seen:
285
291
  return content
292
+ import base64
293
+ import os
294
+
286
295
  blocks: list[dict] = [{"type": "text", "text": content}]
287
296
  for p in seen:
288
297
  mime = _IMAGE_MIME.get(os.path.splitext(p)[1].lower(), "image/png")
@@ -409,6 +409,14 @@ class DrydockApp(App):
409
409
  # running, queue this one and drain it when the current turn finishes
410
410
  # instead of dropping it on the floor.
411
411
  self._mount(UserMessage(text))
412
+ # Confirm any image attachments so the user SEES vision is active (the
413
+ # actual attach happens at the API boundary in providers).
414
+ from drydock import providers
415
+ imgs = providers.detect_image_paths(text)
416
+ if imgs:
417
+ import os as _os
418
+ names = ", ".join(_os.path.basename(p) for p in imgs)
419
+ self._info(f"📎 attached {len(imgs)} image(s) for the model to see: {names}")
412
420
  if self._busy:
413
421
  self._queue.append(text)
414
422
  self._refresh_status()
@@ -90,7 +90,11 @@ _DEFAULT_SYSTEM_PROMPT = (
90
90
  "start to lay out the plan, then START WORKING THROUGH IT IMMEDIATELY — do "
91
91
  "NOT stop to ask whether to proceed. Call `todo` again to flip a step to "
92
92
  "done and the next to in-progress as you finish each, and only stop when "
93
- "every step is done. Don't write a TODO.md file for this."
93
+ "every step is done. Don't write a TODO.md file for this.\n"
94
+ "You have VISION: when the user references an image file path (.png/.jpg/"
95
+ ".jpeg/.gif/.webp/.bmp on disk — a screenshot, mockup, diagram, or photo), it "
96
+ "is attached and you can SEE it; describe it, read text from it, or debug "
97
+ "with it — never claim you cannot view images."
94
98
  )
95
99
 
96
100
  # Short, imperative prompt. Small local models do better with "act now" than
@@ -112,7 +116,11 @@ _GEMMA_SYSTEM_PROMPT = (
112
116
  "(one task per line, '[ ]'/'[~]'/'[x]'), then IMMEDIATELY start doing the "
113
117
  "steps — do NOT stop to ask whether to proceed. Call `todo` again to update "
114
118
  "it as you finish each step, and only stop when every step is done. Do not "
115
- "write a TODO.md file for this."
119
+ "write a TODO.md file for this.\n"
120
+ "You have VISION: when the user's message references an image file path "
121
+ "(.png/.jpg/.jpeg/.gif/.webp/.bmp that exists on disk — a screenshot, mockup, "
122
+ "diagram, or photo), that image is attached and you can SEE it. Describe it, "
123
+ "read text from it, or use it to debug — never claim you cannot view images."
116
124
  )
117
125
 
118
126
 
@@ -221,11 +229,15 @@ _DRYDOCK_COMMANDS_HELP = (
221
229
  "- Custom skills (reusable `/<name>` prompts): `/skills new <name> <prompt "
222
230
  "text>` creates one (use $ARGS in the prompt for trailing input); `/skills` "
223
231
  "lists them; then they run it as `/<name>`.\n"
232
+ "- Images / vision: the user just references an image file path in their "
233
+ "message (a .png/.jpg screenshot, mockup, or diagram on disk) and it is "
234
+ "attached for you to SEE — no command needed.\n"
224
235
  "- Other: `/model` (model/endpoint), `/cwd`, `/undo` (revert last write), "
225
- "`/back` (rewind a turn), `/compact` (shrink context), `/loop <n> <prompt>` "
226
- "(repeat a prompt), `/mcp` (connected MCP servers), `/status`, `/clear`, "
227
- "`/help`, `/quit`. Internet + git are tools you call yourself "
228
- "(WebSearch/WebFetch, GitStatus/GitDiff/GitLog/GitCommit)."
236
+ "`/back` (rewind a turn), `/compact` (shrink context), `/context` (view/set "
237
+ "the context-window budget), `/loop <n> <prompt>` (repeat a prompt), `/mcp` "
238
+ "(connected MCP servers), `/rmf` & `/stig` (NIST 800-53 / DISA STIG), "
239
+ "`/status`, `/clear`, `/help`, `/quit`. Internet + git are tools you call "
240
+ "yourself (WebSearch/WebFetch, GitStatus/GitDiff/GitLog/GitCommit)."
229
241
  )
230
242
 
231
243
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.76
3
+ Version: 3.0.77
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -59,6 +59,10 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
59
59
 
60
60
  - **Files & shell** — `Read` (with a structure index for huge files), `Write`,
61
61
  `Edit`, `Bash`, `Glob`, `Grep`.
62
+ - **Vision (multimodal)** — reference an image path in your message (a `.png`/
63
+ `.jpg` screenshot, mockup, or diagram) and it's attached for a vision-capable
64
+ model to *see* — describe a UI, read text off a screenshot, debug a diagram.
65
+ Works with any `--mmproj`-enabled server (e.g. llama.cpp with a vision model).
62
66
  - **Version control** — `GitStatus`, `GitDiff`, `GitLog`, `GitCommit`
63
67
  (structured + truncated; commit is local and reversible).
64
68
  - **Internet** — `WebSearch` + `WebFetch` (DuckDuckGo; offline-safe).
@@ -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.76"
10
+ version = "3.0.77"
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"
@@ -82,3 +82,23 @@ def test_image_path_in_backticks_and_punctuation(tmp_path):
82
82
  out = _user_content_with_images(prompt)
83
83
  assert isinstance(out, list), f"not attached: {prompt!r}"
84
84
  assert any(b.get("type") == "image_url" for b in out), prompt
85
+
86
+
87
+ def test_detect_image_paths_shared_helper(tmp_path):
88
+ from drydock.providers import detect_image_paths
89
+ img = _write_png(tmp_path / "shot.png")
90
+ # plain, quoted, backticked, and trailing-punctuation forms all resolve
91
+ assert detect_image_paths(f"look at {img}") == [img]
92
+ assert detect_image_paths(f'see "{img}".') == [img]
93
+ assert detect_image_paths(f"debug `{img}`,") == [img]
94
+ # nonexistent path or text-only → nothing attached
95
+ assert detect_image_paths(f"{tmp_path}/nope.png") == []
96
+ assert detect_image_paths("no images here, just prose") == []
97
+ # de-dupes repeats
98
+ assert detect_image_paths(f"{img} and again {img}") == [img]
99
+
100
+
101
+ def test_vision_in_system_prompt():
102
+ from drydock.tuning import system_prompt_for_model
103
+ assert "VISION" in system_prompt_for_model("gemma4")
104
+ assert "VISION" in system_prompt_for_model("some-other-model")
File without changes
File without changes
File without changes