drydock-cli 3.0.60__tar.gz → 3.0.61__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.60/drydock_cli.egg-info → drydock_cli-3.0.61}/PKG-INFO +47 -1
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/README.md +46 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/skills.py +19 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/app.py +30 -7
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tuning.py +25 -1
- {drydock_cli-3.0.60 → drydock_cli-3.0.61/drydock_cli.egg-info}/PKG-INFO +47 -1
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/SOURCES.txt +1 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/pyproject.toml +1 -1
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_skills.py +25 -0
- drydock_cli-3.0.61/tests/test_system_prompt_help.py +15 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/LICENSE +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/NOTICE +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/agent.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/cli.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/config.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/detect.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/guards.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/providers.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/web.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/setup.cfg +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_config.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_web_tools.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.61
|
|
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
|
|
@@ -71,6 +71,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
|
|
|
71
71
|
`~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
|
|
72
72
|
- **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
|
|
73
73
|
|
|
74
|
+
## Slash commands
|
|
75
|
+
|
|
76
|
+
Typed into the prompt. The agent also knows these, so you can just **ask it**
|
|
77
|
+
("how do I add my own docs?") and it'll point you to the right one.
|
|
78
|
+
|
|
79
|
+
| Command | What it does |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
|
|
82
|
+
| `/graphrag add <path>` | Incrementally add more documents to the base |
|
|
83
|
+
| `/graphrag query <q>` | Test what the base returns (no model) |
|
|
84
|
+
| `/graphrag status` · `clear` | List indexed sources · wipe the base |
|
|
85
|
+
| `/skills` | List your skills |
|
|
86
|
+
| `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
|
|
87
|
+
| `/<name>` | Run a skill |
|
|
88
|
+
| `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
|
|
89
|
+
| `/mcp` | List connected MCP servers + their tools |
|
|
90
|
+
| `/model` · `/cwd` | Show/set model & endpoint · working directory |
|
|
91
|
+
| `/undo` · `/back` | Revert the last write · rewind the last turn |
|
|
92
|
+
| `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
|
|
93
|
+
| `/help` · `/quit` | Help · exit |
|
|
94
|
+
|
|
95
|
+
### Knowledge base (GraphRAG) — ingesting your documents
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
/graphrag build ./docs # index a file or a whole folder
|
|
99
|
+
/graphrag add ./more_docs # add more later, incrementally
|
|
100
|
+
/graphrag query "how are refunds handled?" # check retrieval
|
|
101
|
+
/graphrag status # what's indexed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
|
|
105
|
+
tool) when a question touches your material. Ingests text formats
|
|
106
|
+
(`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
|
|
107
|
+
(e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
|
|
108
|
+
`<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
|
|
109
|
+
|
|
110
|
+
### Custom skills
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
/skills new commitmsg Write a concise conventional-commit message for: $ARGS
|
|
114
|
+
/commitmsg the staged auth changes # runs the skill with $ARGS substituted
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Skills are markdown files in `~/.drydock/skills/` (personal) or
|
|
118
|
+
`<project>/.drydock/skills/` (project); `/skills new` writes one for you.
|
|
119
|
+
|
|
74
120
|
## Install
|
|
75
121
|
|
|
76
122
|
```bash
|
|
@@ -49,6 +49,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
|
|
|
49
49
|
`~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
|
|
50
50
|
- **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
|
|
51
51
|
|
|
52
|
+
## Slash commands
|
|
53
|
+
|
|
54
|
+
Typed into the prompt. The agent also knows these, so you can just **ask it**
|
|
55
|
+
("how do I add my own docs?") and it'll point you to the right one.
|
|
56
|
+
|
|
57
|
+
| Command | What it does |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
|
|
60
|
+
| `/graphrag add <path>` | Incrementally add more documents to the base |
|
|
61
|
+
| `/graphrag query <q>` | Test what the base returns (no model) |
|
|
62
|
+
| `/graphrag status` · `clear` | List indexed sources · wipe the base |
|
|
63
|
+
| `/skills` | List your skills |
|
|
64
|
+
| `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
|
|
65
|
+
| `/<name>` | Run a skill |
|
|
66
|
+
| `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
|
|
67
|
+
| `/mcp` | List connected MCP servers + their tools |
|
|
68
|
+
| `/model` · `/cwd` | Show/set model & endpoint · working directory |
|
|
69
|
+
| `/undo` · `/back` | Revert the last write · rewind the last turn |
|
|
70
|
+
| `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
|
|
71
|
+
| `/help` · `/quit` | Help · exit |
|
|
72
|
+
|
|
73
|
+
### Knowledge base (GraphRAG) — ingesting your documents
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
/graphrag build ./docs # index a file or a whole folder
|
|
77
|
+
/graphrag add ./more_docs # add more later, incrementally
|
|
78
|
+
/graphrag query "how are refunds handled?" # check retrieval
|
|
79
|
+
/graphrag status # what's indexed
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
|
|
83
|
+
tool) when a question touches your material. Ingests text formats
|
|
84
|
+
(`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
|
|
85
|
+
(e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
|
|
86
|
+
`<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
|
|
87
|
+
|
|
88
|
+
### Custom skills
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
/skills new commitmsg Write a concise conventional-commit message for: $ARGS
|
|
92
|
+
/commitmsg the staged auth changes # runs the skill with $ARGS substituted
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Skills are markdown files in `~/.drydock/skills/` (personal) or
|
|
96
|
+
`<project>/.drydock/skills/` (project); `/skills new` writes one for you.
|
|
97
|
+
|
|
52
98
|
## Install
|
|
53
99
|
|
|
54
100
|
```bash
|
|
@@ -77,6 +77,25 @@ def skills_dirs(cwd: str) -> list[Path]:
|
|
|
77
77
|
return [Path.home() / ".drydock" / "skills", Path(cwd) / ".drydock" / "skills"]
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def create_skill(name: str, body: str, *, description: str = "", scope: str = "user",
|
|
81
|
+
cwd: str = ".") -> Path:
|
|
82
|
+
"""Author a new skill markdown file and return its path. scope='user' writes
|
|
83
|
+
to ~/.drydock/skills (available everywhere); scope='project' writes to
|
|
84
|
+
<cwd>/.drydock/skills. Raises ValueError on a bad name / empty body."""
|
|
85
|
+
name = (name or "").strip().lower()
|
|
86
|
+
body = (body or "").strip()
|
|
87
|
+
if not _NAME_RE.match(name):
|
|
88
|
+
raise ValueError("skill name must be letters/digits/-/_ (e.g. 'review')")
|
|
89
|
+
if not body:
|
|
90
|
+
raise ValueError("a skill needs a prompt body")
|
|
91
|
+
d = (Path(cwd) if scope == "project" else Path.home()) / ".drydock" / "skills"
|
|
92
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
path = d / f"{name}.md"
|
|
94
|
+
front = f"---\nname: {name}\ndescription: {description or 'user skill'}\n---\n"
|
|
95
|
+
path.write_text(front + body + "\n", encoding="utf-8")
|
|
96
|
+
return path
|
|
97
|
+
|
|
98
|
+
|
|
80
99
|
def load_skills(cwd: str) -> dict[str, Skill]:
|
|
81
100
|
"""Load all skills; later dirs (project) override earlier (user) by name."""
|
|
82
101
|
skills: dict[str, Skill] = {}
|
|
@@ -492,7 +492,7 @@ class DrydockApp(App):
|
|
|
492
492
|
" /compact shrink old context to free up the window\n"
|
|
493
493
|
" /graphrag ingest docs into a knowledge base the agent can use\n"
|
|
494
494
|
" build <path> · add <path> · query <q> · status · clear\n"
|
|
495
|
-
" /skills list
|
|
495
|
+
" /skills list skills · /skills new <name> <prompt> to create one\n"
|
|
496
496
|
" /loop /loop <count> <prompt> — repeat a prompt (Esc stops)\n"
|
|
497
497
|
" /mcp list connected MCP servers and their tools\n"
|
|
498
498
|
" /clear reset the conversation\n"
|
|
@@ -504,7 +504,7 @@ class DrydockApp(App):
|
|
|
504
504
|
elif cmd == "/loop":
|
|
505
505
|
self._cmd_loop(arg)
|
|
506
506
|
elif cmd == "/skills":
|
|
507
|
-
self._cmd_skills()
|
|
507
|
+
self._cmd_skills(arg)
|
|
508
508
|
elif cmd[1:] in self._skills:
|
|
509
509
|
self._run_skill(self._skills[cmd[1:]], arg)
|
|
510
510
|
else:
|
|
@@ -554,19 +554,42 @@ class DrydockApp(App):
|
|
|
554
554
|
self._mount(UserMessage(prompt))
|
|
555
555
|
self._begin(prompt)
|
|
556
556
|
|
|
557
|
-
def _cmd_skills(self) -> None:
|
|
557
|
+
def _cmd_skills(self, arg: str = "") -> None:
|
|
558
|
+
"""List skills, or create one: /skills new <name> <prompt…> (use $ARGS in
|
|
559
|
+
the prompt for trailing input). Created skills are usable as /<name>
|
|
560
|
+
immediately."""
|
|
561
|
+
from drydock import skills as skillsmod
|
|
562
|
+
|
|
563
|
+
parts = arg.split(maxsplit=2)
|
|
564
|
+
if parts and parts[0].lower() == "new":
|
|
565
|
+
if len(parts) < 3:
|
|
566
|
+
self._info(
|
|
567
|
+
"usage: /skills new <name> <prompt text>\n"
|
|
568
|
+
" e.g. /skills new review Run GitDiff, then review the changes "
|
|
569
|
+
"for bugs.\n (use $ARGS in the prompt for trailing input)"
|
|
570
|
+
)
|
|
571
|
+
return
|
|
572
|
+
name, body = parts[1], parts[2]
|
|
573
|
+
try:
|
|
574
|
+
path = skillsmod.create_skill(name, body, cwd=self.config.get("cwd") or ".")
|
|
575
|
+
except ValueError as e:
|
|
576
|
+
self._info(f"Couldn't create skill: {e}")
|
|
577
|
+
return
|
|
578
|
+
self._skills = skillsmod.load_skills(self.config.get("cwd") or ".") # reload
|
|
579
|
+
self._info(f"✓ Created skill /{name.lower()} ({path}). Invoke it as /{name.lower()}.")
|
|
580
|
+
return
|
|
558
581
|
if not self._skills:
|
|
559
582
|
self._info(
|
|
560
|
-
"No skills yet. Create one
|
|
561
|
-
"~/.drydock/skills/<name>.md
|
|
562
|
-
"
|
|
563
|
-
"trailing input."
|
|
583
|
+
"No skills yet. Create one with: /skills new <name> <prompt text>\n"
|
|
584
|
+
"(or drop a markdown file in ~/.drydock/skills/<name>.md). Invoke as "
|
|
585
|
+
"/<name>; use $ARGS for trailing input."
|
|
564
586
|
)
|
|
565
587
|
return
|
|
566
588
|
lines = ["Skills (invoke as /<name>):"]
|
|
567
589
|
for name in sorted(self._skills):
|
|
568
590
|
sk = self._skills[name]
|
|
569
591
|
lines.append(f" /{name}" + (f" — {sk.description}" if sk.description else ""))
|
|
592
|
+
lines.append("Create one: /skills new <name> <prompt text>")
|
|
570
593
|
self._info("\n".join(lines))
|
|
571
594
|
|
|
572
595
|
def _run_skill(self, skill, arg: str) -> None:
|
|
@@ -206,9 +206,33 @@ def filter_tool_schemas(tool_schemas: list, model: str | None) -> list:
|
|
|
206
206
|
return [t for t in tool_schemas if t.get("name") not in GEMMA_DISABLED_TOOLS]
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
# Reference for the model so it can answer "how do I …" questions about Drydock
|
|
210
|
+
# itself. The user TYPES these slash commands; you (the model) do NOT run them —
|
|
211
|
+
# you just explain the right one when asked. Appended to every system prompt.
|
|
212
|
+
_DRYDOCK_COMMANDS_HELP = (
|
|
213
|
+
"\n\nAbout Drydock (the tool you run inside). If the user asks how to do "
|
|
214
|
+
"something with Drydock, tell them the slash command — they type it, you do "
|
|
215
|
+
"NOT run it:\n"
|
|
216
|
+
"- Knowledge base from their docs: `/graphrag build <path>` (a file or "
|
|
217
|
+
"folder), `/graphrag add <path>` to add more, `/graphrag query <q>` to test "
|
|
218
|
+
"it, `/graphrag status`, `/graphrag clear`. Once built, you automatically use "
|
|
219
|
+
"the `Knowledge` tool to draw on it. It ingests text formats (md/txt/code/"
|
|
220
|
+
"json/yaml/…); PDFs/Word must be converted to text first.\n"
|
|
221
|
+
"- Custom skills (reusable `/<name>` prompts): `/skills new <name> <prompt "
|
|
222
|
+
"text>` creates one (use $ARGS in the prompt for trailing input); `/skills` "
|
|
223
|
+
"lists them; then they run it as `/<name>`.\n"
|
|
224
|
+
"- 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)."
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
209
232
|
def system_prompt_for_model(model: str | None) -> str:
|
|
210
233
|
"""Return the system prompt best suited to the model."""
|
|
211
|
-
|
|
234
|
+
base = _GEMMA_SYSTEM_PROMPT if is_gemma(model) else _DEFAULT_SYSTEM_PROMPT
|
|
235
|
+
return base + _DRYDOCK_COMMANDS_HELP
|
|
212
236
|
|
|
213
237
|
|
|
214
238
|
def thinking_level_for_turn(turn_count: int, is_user_turn: bool) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.61
|
|
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
|
|
@@ -71,6 +71,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
|
|
|
71
71
|
`~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
|
|
72
72
|
- **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
|
|
73
73
|
|
|
74
|
+
## Slash commands
|
|
75
|
+
|
|
76
|
+
Typed into the prompt. The agent also knows these, so you can just **ask it**
|
|
77
|
+
("how do I add my own docs?") and it'll point you to the right one.
|
|
78
|
+
|
|
79
|
+
| Command | What it does |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
|
|
82
|
+
| `/graphrag add <path>` | Incrementally add more documents to the base |
|
|
83
|
+
| `/graphrag query <q>` | Test what the base returns (no model) |
|
|
84
|
+
| `/graphrag status` · `clear` | List indexed sources · wipe the base |
|
|
85
|
+
| `/skills` | List your skills |
|
|
86
|
+
| `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
|
|
87
|
+
| `/<name>` | Run a skill |
|
|
88
|
+
| `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
|
|
89
|
+
| `/mcp` | List connected MCP servers + their tools |
|
|
90
|
+
| `/model` · `/cwd` | Show/set model & endpoint · working directory |
|
|
91
|
+
| `/undo` · `/back` | Revert the last write · rewind the last turn |
|
|
92
|
+
| `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
|
|
93
|
+
| `/help` · `/quit` | Help · exit |
|
|
94
|
+
|
|
95
|
+
### Knowledge base (GraphRAG) — ingesting your documents
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
/graphrag build ./docs # index a file or a whole folder
|
|
99
|
+
/graphrag add ./more_docs # add more later, incrementally
|
|
100
|
+
/graphrag query "how are refunds handled?" # check retrieval
|
|
101
|
+
/graphrag status # what's indexed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
|
|
105
|
+
tool) when a question touches your material. Ingests text formats
|
|
106
|
+
(`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
|
|
107
|
+
(e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
|
|
108
|
+
`<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
|
|
109
|
+
|
|
110
|
+
### Custom skills
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
/skills new commitmsg Write a concise conventional-commit message for: $ARGS
|
|
114
|
+
/commitmsg the staged auth changes # runs the skill with $ARGS substituted
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Skills are markdown files in `~/.drydock/skills/` (personal) or
|
|
118
|
+
`<project>/.drydock/skills/` (project); `/skills new` writes one for you.
|
|
119
|
+
|
|
74
120
|
## Install
|
|
75
121
|
|
|
76
122
|
```bash
|
|
@@ -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.61"
|
|
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"
|
|
@@ -54,3 +54,28 @@ def test_empty_body_skipped(tmp_path):
|
|
|
54
54
|
|
|
55
55
|
def test_no_skills_dir_is_empty(tmp_path):
|
|
56
56
|
assert skills.load_skills(str(tmp_path)) == {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_create_skill_user_scope(tmp_path, monkeypatch):
|
|
60
|
+
monkeypatch.setattr(skills.Path, "home", lambda: tmp_path)
|
|
61
|
+
path = skills.create_skill("review", "Run GitDiff then review for bugs.",
|
|
62
|
+
description="Review the diff")
|
|
63
|
+
assert path.exists()
|
|
64
|
+
loaded = skills.load_skills(str(tmp_path / "proj")) # user-scope visible anywhere
|
|
65
|
+
assert "review" in loaded and "review for bugs" in loaded["review"].body
|
|
66
|
+
assert loaded["review"].description == "Review the diff"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_create_skill_rejects_bad_name_and_empty_body(tmp_path, monkeypatch):
|
|
70
|
+
monkeypatch.setattr(skills.Path, "home", lambda: tmp_path)
|
|
71
|
+
import pytest
|
|
72
|
+
with pytest.raises(ValueError):
|
|
73
|
+
skills.create_skill("bad name!", "body")
|
|
74
|
+
with pytest.raises(ValueError):
|
|
75
|
+
skills.create_skill("ok", " ")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_create_skill_project_scope(tmp_path):
|
|
79
|
+
path = skills.create_skill("explain", "Explain $ARGS.", scope="project", cwd=str(tmp_path))
|
|
80
|
+
assert str(tmp_path) in str(path)
|
|
81
|
+
assert skills.load_skills(str(tmp_path))["explain"].render("auth.py") == "Explain auth.py."
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""The system prompt must teach the model Drydock's own slash commands so it can
|
|
2
|
+
answer 'how do I add documents / make a skill' when a user asks."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from drydock.tuning import system_prompt_for_model
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_prompt_documents_graphrag_and_skills():
|
|
9
|
+
for model in ("gemma4", "some-other-model"):
|
|
10
|
+
p = system_prompt_for_model(model)
|
|
11
|
+
assert "/graphrag build" in p and "/graphrag add" in p
|
|
12
|
+
assert "/skills new" in p
|
|
13
|
+
assert "Knowledge" in p
|
|
14
|
+
# framed so the model explains, not runs, the command
|
|
15
|
+
assert "you do" in p.lower() and "not run" in p.lower()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|