drydock-cli 3.0.134__tar.gz → 3.0.135__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.134/drydock_cli.egg-info → drydock_cli-3.0.135}/PKG-INFO +2 -2
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/README.md +1 -1
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/cli.py +24 -14
- {drydock_cli-3.0.134 → drydock_cli-3.0.135/drydock_cli.egg-info}/PKG-INFO +2 -2
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/pyproject.toml +1 -1
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_config_migration.py +3 -3
- drydock_cli-3.0.135/tests/test_first_run_setup.py +81 -0
- drydock_cli-3.0.134/tests/test_first_run_setup.py +0 -34
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/LICENSE +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/NOTICE +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/agent.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-data.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-debug.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-finetune.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-metrics.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-rl.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/ml-train.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/nist-csf.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/cci.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/config.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/detect.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/events.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/extract.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/guards.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/poam.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/providers.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/recipes.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/skills.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/stig.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/suggest.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/task_state.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tool_result.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tui/app.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/verification.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock/web.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/setup.cfg +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_config.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_events.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_recipes.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_rolling_plan.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_suggest.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_task_state.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tool_result.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_verification_gate.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.0.134 → drydock_cli-3.0.135}/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.
|
|
3
|
+
Version: 3.0.135
|
|
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
|
|
@@ -253,7 +253,7 @@ drydock
|
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
Requires Python 3.11+. From source instead:
|
|
256
|
-
`git clone https://github.com/fbobe321/drydock
|
|
256
|
+
`git clone https://github.com/fbobe321/drydock.git && cd drydock && pip install -e .`
|
|
257
257
|
|
|
258
258
|
On first launch with no config, Drydock probes localhost for a running local
|
|
259
259
|
LLM (llama.cpp/vLLM `:8000`, Ollama `:11434`, LM Studio `:1234`) and wires up
|
|
@@ -229,7 +229,7 @@ drydock
|
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
Requires Python 3.11+. From source instead:
|
|
232
|
-
`git clone https://github.com/fbobe321/drydock
|
|
232
|
+
`git clone https://github.com/fbobe321/drydock.git && cd drydock && pip install -e .`
|
|
233
233
|
|
|
234
234
|
On first launch with no config, Drydock probes localhost for a running local
|
|
235
235
|
LLM (llama.cpp/vLLM `:8000`, Ollama `:11434`, LM Studio `:1234`) and wires up
|
|
@@ -248,6 +248,27 @@ def _summarize(inputs: dict) -> str:
|
|
|
248
248
|
|
|
249
249
|
# ── Entry point ───────────────────────────────────────────────────────────
|
|
250
250
|
|
|
251
|
+
def _resolve_first_run(cfg: dict, args, cfg_path, cfgmod, interactive: bool) -> tuple[dict, str]:
|
|
252
|
+
"""First-launch model-server resolution. AUTODETECT a live local server and
|
|
253
|
+
wire it up with ZERO prompts (the frictionless path). Only when nothing is
|
|
254
|
+
detected AND we're interactive do we ask the user to enter their own URL — so
|
|
255
|
+
the easy install (server already running) needs no input, while a custom
|
|
256
|
+
endpoint is still one prompt (or `--base-url`, or `/model url` later)."""
|
|
257
|
+
from drydock import detect
|
|
258
|
+
|
|
259
|
+
found = detect.detect_local_llms()
|
|
260
|
+
if found:
|
|
261
|
+
cfg["provider"] = found[0]["provider"]
|
|
262
|
+
cfg["base_url"] = found[0]["base_url"]
|
|
263
|
+
if found[0].get("models") and not getattr(args, "model", None):
|
|
264
|
+
cfg["model"] = found[0]["models"][0]
|
|
265
|
+
cfgmod.save_file(cfg, cfg_path)
|
|
266
|
+
return cfg, detect.onboarding_message(found)
|
|
267
|
+
if interactive:
|
|
268
|
+
return _first_run_setup(cfg, cfg_path, cfgmod)
|
|
269
|
+
return cfg, detect.onboarding_message(found)
|
|
270
|
+
|
|
271
|
+
|
|
251
272
|
def _first_run_setup(cfg: dict, cfg_path, cfgmod) -> tuple[dict, str]:
|
|
252
273
|
"""Interactive first-launch: ask for the model server URL + model name and
|
|
253
274
|
persist them to config.toml. Autodetect (if anything is listening) supplies
|
|
@@ -346,22 +367,11 @@ def main():
|
|
|
346
367
|
"context_limit": args.context_limit,
|
|
347
368
|
}, cfg_path)
|
|
348
369
|
|
|
349
|
-
# First launch:
|
|
350
|
-
# persist them to config.toml. Interactive only — when stdin isn't a TTY
|
|
351
|
-
# (piped / -p / cron) we fall back to silent autodetect so nothing blocks.
|
|
370
|
+
# First launch: autodetect a local server and wire it up (see _resolve_first_run).
|
|
352
371
|
onboarding = ""
|
|
353
372
|
if first_run and not args.provider and not args.base_url:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
else:
|
|
357
|
-
from drydock import detect
|
|
358
|
-
|
|
359
|
-
found = detect.detect_local_llms()
|
|
360
|
-
onboarding = detect.onboarding_message(found)
|
|
361
|
-
if found:
|
|
362
|
-
cfg["provider"] = found[0]["provider"]
|
|
363
|
-
cfg["base_url"] = found[0]["base_url"]
|
|
364
|
-
cfgmod.save_file(cfg, cfg_path)
|
|
373
|
+
interactive = sys.stdin.isatty() and not args.prompt
|
|
374
|
+
cfg, onboarding = _resolve_first_run(cfg, args, cfg_path, cfgmod, interactive)
|
|
365
375
|
|
|
366
376
|
config = {
|
|
367
377
|
# context_limit now comes from cfg (DEFAULTS < config.toml < --context-limit)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.135
|
|
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
|
|
@@ -253,7 +253,7 @@ drydock
|
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
Requires Python 3.11+. From source instead:
|
|
256
|
-
`git clone https://github.com/fbobe321/drydock
|
|
256
|
+
`git clone https://github.com/fbobe321/drydock.git && cd drydock && pip install -e .`
|
|
257
257
|
|
|
258
258
|
On first launch with no config, Drydock probes localhost for a running local
|
|
259
259
|
LLM (llama.cpp/vLLM `:8000`, Ollama `:11434`, LM Studio `:1234`) and wires up
|
|
@@ -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.135"
|
|
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"
|
|
@@ -77,8 +77,8 @@ def test_partial_config_backfills_missing_keys(tmp_path):
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def test_explicit_user_values_are_respected(tmp_path):
|
|
80
|
-
m = _load(tmp_path, 'base_url = "http://192.
|
|
81
|
-
assert m["base_url"] == "http://192.
|
|
80
|
+
m = _load(tmp_path, 'base_url = "http://192.0.2.10:8000/v1"\nmodel = "gemma4"\n')
|
|
81
|
+
assert m["base_url"] == "http://192.0.2.10:8000/v1"
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def test_default_base_url_is_concrete_not_empty():
|
|
@@ -108,7 +108,7 @@ def test_v2_file_is_migrated_and_backed_up(tmp_path):
|
|
|
108
108
|
def test_real_v3_file_is_left_untouched(tmp_path):
|
|
109
109
|
"""A genuine v3 file (has a known key) must NOT be migrated/rewritten."""
|
|
110
110
|
p = tmp_path / "config.toml"
|
|
111
|
-
p.write_text('base_url = "http://192.
|
|
111
|
+
p.write_text('base_url = "http://192.0.2.20:8000/v1"\n# my hand edit\n')
|
|
112
112
|
cfg.resolve({}, path=p)
|
|
113
113
|
assert not p.with_name("config.toml.bak").exists()
|
|
114
114
|
assert "# my hand edit" in p.read_text() # untouched
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""First-launch setup: prompt for model server URL + name, persist to config."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from drydock import cli
|
|
5
|
+
from drydock import config as cfgmod
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_first_run_setup_persists_url_and_model(tmp_path, monkeypatch):
|
|
9
|
+
# No server running → defaults; user types a custom URL + model.
|
|
10
|
+
monkeypatch.setattr(cli, "_first_run_setup", cli._first_run_setup) # ensure present
|
|
11
|
+
import drydock.detect as detect
|
|
12
|
+
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
13
|
+
answers = iter(["http://127.0.0.1:8000/v1", "gemma4"])
|
|
14
|
+
monkeypatch.setattr("builtins.input", lambda *_: next(answers))
|
|
15
|
+
|
|
16
|
+
cfg_path = tmp_path / "config.toml"
|
|
17
|
+
cfg, onboarding = cli._first_run_setup(dict(cfgmod.DEFAULTS), cfg_path, cfgmod)
|
|
18
|
+
|
|
19
|
+
assert cfg["base_url"] == "http://127.0.0.1:8000/v1"
|
|
20
|
+
assert cfg["model"] == "gemma4"
|
|
21
|
+
# Persisted to disk.
|
|
22
|
+
on_disk = cfgmod.load_file(cfg_path)
|
|
23
|
+
assert on_disk["base_url"] == "http://127.0.0.1:8000/v1"
|
|
24
|
+
assert on_disk["model"] == "gemma4"
|
|
25
|
+
assert "gemma4" in onboarding and "127.0.0.1:8000" in onboarding
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_first_run_setup_uses_defaults_on_empty_input(tmp_path, monkeypatch):
|
|
29
|
+
import drydock.detect as detect
|
|
30
|
+
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
31
|
+
monkeypatch.setattr("builtins.input", lambda *_: "") # user just hits Enter
|
|
32
|
+
cfg, _ = cli._first_run_setup(dict(cfgmod.DEFAULTS), tmp_path / "c.toml", cfgmod)
|
|
33
|
+
assert cfg["base_url"] == "http://127.0.0.1:8000/v1" # the documented default
|
|
34
|
+
assert cfg["model"] == "gemma4"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _Args:
|
|
38
|
+
def __init__(self, model=None): self.model = model
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_autowire_uses_detected_server_no_prompt(tmp_path, monkeypatch):
|
|
42
|
+
"""First launch with a live server: wire it up automatically, NO input()."""
|
|
43
|
+
from drydock import cli, config as cfgmod, detect
|
|
44
|
+
monkeypatch.setattr(detect, "detect_local_llms",
|
|
45
|
+
lambda: [{"provider": "ollama", "base_url": "http://localhost:11434/v1",
|
|
46
|
+
"models": ["llama3"]}])
|
|
47
|
+
# input() must NOT be called on the auto-wire path
|
|
48
|
+
monkeypatch.setattr("builtins.input", lambda *a: (_ for _ in ()).throw(AssertionError("prompted!")))
|
|
49
|
+
cfg_path = tmp_path / "c.toml"
|
|
50
|
+
cfg, onboarding = cli._resolve_first_run(dict(cfgmod.DEFAULTS), _Args(), cfg_path, cfgmod, interactive=True)
|
|
51
|
+
assert cfg["base_url"] == "http://localhost:11434/v1"
|
|
52
|
+
assert cfg["provider"] == "ollama" and cfg["model"] == "llama3"
|
|
53
|
+
assert "Detected" in onboarding and cfg_path.exists() # persisted
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_autowire_respects_explicit_model(tmp_path, monkeypatch):
|
|
57
|
+
from drydock import cli, config as cfgmod, detect
|
|
58
|
+
monkeypatch.setattr(detect, "detect_local_llms",
|
|
59
|
+
lambda: [{"provider": "vllm", "base_url": "http://localhost:8000/v1",
|
|
60
|
+
"models": ["served-model"]}])
|
|
61
|
+
cfg, _ = cli._resolve_first_run(dict(cfgmod.DEFAULTS), _Args(model="gemma4"),
|
|
62
|
+
tmp_path / "c.toml", cfgmod, interactive=True)
|
|
63
|
+
assert cfg["model"] == "gemma4" # --model wins over the detected id
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_nothing_detected_noninteractive_shows_help(tmp_path, monkeypatch):
|
|
67
|
+
from drydock import cli, config as cfgmod, detect
|
|
68
|
+
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
69
|
+
monkeypatch.setattr("builtins.input", lambda *a: (_ for _ in ()).throw(AssertionError("prompted!")))
|
|
70
|
+
cfg, onboarding = cli._resolve_first_run(dict(cfgmod.DEFAULTS), _Args(), tmp_path / "c.toml",
|
|
71
|
+
cfgmod, interactive=False)
|
|
72
|
+
assert "No local LLM detected" in onboarding # tells the user how to set a URL
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_nothing_detected_interactive_prompts(tmp_path, monkeypatch):
|
|
76
|
+
from drydock import cli, config as cfgmod, detect
|
|
77
|
+
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
78
|
+
monkeypatch.setattr("builtins.input", lambda *a: "http://myhost:9000/v1")
|
|
79
|
+
cfg, _ = cli._resolve_first_run(dict(cfgmod.DEFAULTS), _Args(), tmp_path / "c.toml",
|
|
80
|
+
cfgmod, interactive=True)
|
|
81
|
+
assert cfg["base_url"] == "http://myhost:9000/v1" # user's URL honored
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"""First-launch setup: prompt for model server URL + name, persist to config."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from drydock import cli
|
|
5
|
-
from drydock import config as cfgmod
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_first_run_setup_persists_url_and_model(tmp_path, monkeypatch):
|
|
9
|
-
# No server running → defaults; user types a custom URL + model.
|
|
10
|
-
monkeypatch.setattr(cli, "_first_run_setup", cli._first_run_setup) # ensure present
|
|
11
|
-
import drydock.detect as detect
|
|
12
|
-
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
13
|
-
answers = iter(["http://127.0.0.1:8000/v1", "gemma4"])
|
|
14
|
-
monkeypatch.setattr("builtins.input", lambda *_: next(answers))
|
|
15
|
-
|
|
16
|
-
cfg_path = tmp_path / "config.toml"
|
|
17
|
-
cfg, onboarding = cli._first_run_setup(dict(cfgmod.DEFAULTS), cfg_path, cfgmod)
|
|
18
|
-
|
|
19
|
-
assert cfg["base_url"] == "http://127.0.0.1:8000/v1"
|
|
20
|
-
assert cfg["model"] == "gemma4"
|
|
21
|
-
# Persisted to disk.
|
|
22
|
-
on_disk = cfgmod.load_file(cfg_path)
|
|
23
|
-
assert on_disk["base_url"] == "http://127.0.0.1:8000/v1"
|
|
24
|
-
assert on_disk["model"] == "gemma4"
|
|
25
|
-
assert "gemma4" in onboarding and "127.0.0.1:8000" in onboarding
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def test_first_run_setup_uses_defaults_on_empty_input(tmp_path, monkeypatch):
|
|
29
|
-
import drydock.detect as detect
|
|
30
|
-
monkeypatch.setattr(detect, "detect_local_llms", lambda: [])
|
|
31
|
-
monkeypatch.setattr("builtins.input", lambda *_: "") # user just hits Enter
|
|
32
|
-
cfg, _ = cli._first_run_setup(dict(cfgmod.DEFAULTS), tmp_path / "c.toml", cfgmod)
|
|
33
|
-
assert cfg["base_url"] == "http://127.0.0.1:8000/v1" # the documented default
|
|
34
|
-
assert cfg["model"] == "gemma4"
|
|
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
|
|
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
|