Skip to content

Commit e53e917

Browse files
Copilotindhra
andauthored
fix: hunt dry-run by default; add --yes opt-in; emit privacy prompt before extraction
* Initial plan * fix: hunt dry-run by default, --yes opt-in, privacy prompt, remove non-TTY auto-confirm Agent-Logs-Url: https://github.com/indhra/agent-hunter/sessions/376f621d-d093-4f3d-96be-e38c67475af4 Co-authored-by: indhra <55424779+indhra@users.noreply.github.com> * refactor: address code review - fix comment, extract _SEPARATOR constant Agent-Logs-Url: https://github.com/indhra/agent-hunter/sessions/376f621d-d093-4f3d-96be-e38c67475af4 Co-authored-by: indhra <55424779+indhra@users.noreply.github.com> * fix: pass project_root and intent into _print_dry_run to build correct --yes hint Agent-Logs-Url: https://github.com/indhra/agent-hunter/sessions/39e4e46e-771f-4b59-b79f-0cff50b212ab Co-authored-by: indhra <55424779+indhra@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: indhra <55424779+indhra@users.noreply.github.com>
1 parent 47ea050 commit e53e917

4 files changed

Lines changed: 277 additions & 61 deletions

File tree

‎SKILL.md‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,16 +314,22 @@ Reference something specific from the user's context.
314314

315315
### Step 4: Install (If User Confirms)
316316

317-
If the user wants to install any recommended skills, guide them:
317+
`agent-hunter hunt` runs in **dry-run mode by default** — it shows what would be
318+
installed and prints the exact command to apply it, but makes no changes.
319+
320+
To execute the recommended installs, the user must explicitly run:
318321

319322
```bash
320-
# For each skill they want:
321-
cd ~/.claude/skills && git clone https://github.com/owner/skill-name
323+
agent-hunter hunt . --yes
322324
```
323325

324-
Or use the suggested command from the hunt report.
326+
Or install a skill manually:
327+
328+
```bash
329+
cd ~/.claude/skills && git clone https://github.com/owner/skill-name
330+
```
325331

326-
**Never auto-install.** User must confirm first.
332+
**Never auto-install.** The user must pass `--yes` or run the install command themselves.
327333

328334
---
329335

‎bin/hunt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
# hunt — run the full agent-hunter pipeline (context → GitHub → scan → score → report)
33
# Usage: hunt [project_root] [--intent "intent string"] [--yes] [--no-github]
44
# Output: scored, security-scanned hunt report to stdout
5+
# Dry-run by default; pass --yes to execute recommended installs.
56
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
67
exec "$SKILL_DIR/.venv/bin/python" "$SKILL_DIR/scripts/main.py" hunt "${@:-"."}"

‎scripts/main.py‎

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def _deep_merge(base: dict, override: dict) -> dict:
9090
# Helpers for hunt command (confirm + execute actions)
9191
# ---------------------------------------------------------------------------
9292

93+
_SEPARATOR = "─" * 70
94+
9395

9496
def _list_installed_skills() -> set[str]:
9597
"""Return set of currently installed skill directory names."""
@@ -142,9 +144,9 @@ def _prompt_confirm_actions(
142144
if not actions:
143145
return []
144146

145-
print("\n" + "─" * 70)
147+
print("\n" + _SEPARATOR)
146148
print(" READY TO ACT — here's what I'll do:")
147-
print("─" * 70)
149+
print(_SEPARATOR)
148150

149151
installs = [a for a in actions if a.action == "install"]
150152
disables = [a for a in actions if a.action == "disable"]
@@ -163,14 +165,15 @@ def _prompt_confirm_actions(
163165

164166
print("\n Note: YELLOW skills are included — review security findings")
165167
print(" above before confirming. You can remove any from the list.")
166-
print("\n" + "─" * 70)
168+
print("\n" + _SEPARATOR)
167169

168170
# --yes flag: auto-confirm all
169171
if auto_yes:
170172
print(" Auto-confirmed (--yes).")
171173
return actions
172174

173-
# Get user input
175+
# Get user input — callers are responsible for checking sys.stdin.isatty()
176+
# before calling this function without auto_yes=True.
174177
response = input(" Proceed? [y/N] or type numbers to skip (e.g. '1,3'): ").strip().lower()
175178

176179
if response in ("y", "yes"):
@@ -193,6 +196,45 @@ def _prompt_confirm_actions(
193196
return []
194197

195198

199+
def _print_dry_run(
200+
actions: list[PendingAction],
201+
project_root: str = ".",
202+
intent: str | None = None,
203+
) -> None:
204+
"""Print what would be done without executing anything (dry-run mode).
205+
206+
Args:
207+
actions: Pending install/disable actions that would be executed.
208+
project_root: The project root that was scanned (used to build the hint command).
209+
intent: The --intent value that was passed, if any (included in hint command).
210+
"""
211+
print("\n" + _SEPARATOR)
212+
print(" HUNT COMPLETE — dry-run mode (no changes made)")
213+
print(_SEPARATOR)
214+
215+
installs = [a for a in actions if a.action == "install"]
216+
disables = [a for a in actions if a.action == "disable"]
217+
218+
if installs:
219+
print(f"\n Would install ({len(installs)} skill(s) → ~/.claude/skills/):")
220+
for i, act in enumerate(installs, 1):
221+
print(f" {i}. {act.skill_name:40} {act.repo_url}")
222+
if act.reason:
223+
print(f" ({act.reason})")
224+
225+
if disables:
226+
print(f"\n Would disable ({len(disables)} dangerous skill(s) — soft-disable, reversible):")
227+
for i, act in enumerate(disables, 1):
228+
print(f" {i}. {act.skill_name:40} {act.reason}")
229+
230+
hint_cmd = f"agent-hunter hunt {project_root} --yes"
231+
if intent:
232+
hint_cmd += f' --intent "{intent}"'
233+
print("\n To apply these changes, re-run with --yes:")
234+
print(f" {hint_cmd}")
235+
print(_SEPARATOR + "\n")
236+
237+
196238
# ---------------------------------------------------------------------------
197239
# Command: hunt
198240
# ---------------------------------------------------------------------------
@@ -243,6 +285,13 @@ def cmd_hunt(args: list[str]) -> int:
243285
config = _load_config()
244286
hunt_cfg = config.get("hunt", {})
245287

288+
# --- Privacy notice (emitted before any project files are read) ---
289+
print(
290+
"[agent-hunter] I'll read your project files to identify your tech stack. "
291+
"Only framework/library names will be used — no file paths, variable names, "
292+
"or project-specific code."
293+
)
294+
246295
# --- Extract context ---
247296
print(f"[agent-hunter] Extracting context from: {root_path}")
248297
if intent:
@@ -353,7 +402,7 @@ def cmd_hunt(args: list[str]) -> int:
353402
print("[agent-hunter] No new actions to take. All recommendations are already installed.")
354403
return 0
355404

356-
# --- Step 8: Confirm actions ---
405+
# --- Step 8: Dry-run by default; execute only with --yes ---
357406
if print_actions:
358407
# SKILL.md mode: print JSON for the LLM to read, then exit without executing.
359408
# SKILL.md presents this list in chat and calls installer directly.
@@ -371,39 +420,25 @@ def cmd_hunt(args: list[str]) -> int:
371420
print(json.dumps(output, indent=2))
372421
return 0
373422

374-
# Non-interactive without --yes: dry-run only. Print commands, do not execute.
375-
# Satisfies SKILL.md §Core Constraints: "No auto-install. Show the command, user runs it."
376-
if not yes and not sys.stdin.isatty():
377-
print("\n[agent-hunter] Non-interactive mode — pass --yes to execute:")
378-
for a in actions:
379-
if a.action == "install":
380-
print(f" git clone {a.repo_url} ~/.claude/skills/{a.skill_name}")
381-
elif a.action == "disable":
382-
print(f" mv ~/.claude/skills/{a.skill_name} ~/.claude/skills/_{a.skill_name}")
383-
elif a.action == "uninstall":
384-
print(f" rm -rf ~/.claude/skills/{a.skill_name}")
423+
if not yes:
424+
# Dry-run mode: show what would happen but do not execute.
425+
_print_dry_run(actions, project_root=project_root, intent=intent)
385426
return 0
386427

387-
confirmed_actions = _prompt_confirm_actions(actions, auto_yes=yes)
388-
389-
if not confirmed_actions:
390-
print("[agent-hunter] No actions confirmed. Exiting.")
391-
return 1
392-
393-
# --- Step 9: Execute confirmed actions ---
394-
print("\n[agent-hunter] Executing confirmed actions...\n")
428+
# --- Step 9: Execute confirmed actions (--yes provided) ---
429+
print("\n[agent-hunter] Executing confirmed actions (--yes)...\n")
395430
installer = Installer()
396-
results = installer.execute_actions(confirmed_actions)
431+
results = installer.execute_actions(actions)
397432

398433
# Summary
399434
successful = sum(1 for r in results if r.success)
400435
failed = len(results) - successful
401436

402-
print("\n" + "─" * 70)
437+
print("\n" + _SEPARATOR)
403438
print(f" Summary: {successful}/{len(results)} action(s) succeeded")
404439
if failed > 0:
405440
print(f" ⚠️ {failed} action(s) failed — review messages above")
406-
print("─" * 70 + "\n")
441+
print(_SEPARATOR + "\n")
407442

408443
return 0 if failed == 0 else 1
409444

@@ -456,13 +491,17 @@ def cmd_rollback(_args: list[str]) -> int:
456491
agent-hunter v0.1.0 — Repo-aware skill package manager for Claude Code
457492
458493
Usage:
459-
agent-hunter hunt [project_root] Find top 3 skills/MCPs for your project
460-
agent-hunter audit Health-check installed skills
461-
agent-hunter rollback Restore registry to last good state
494+
agent-hunter hunt [project_root] [--yes] Find top 3 skills/MCPs for your project
495+
(dry-run by default; --yes to install)
496+
agent-hunter audit Health-check installed skills
497+
agent-hunter rollback Restore registry to last good state
462498
463499
Options:
464500
-h, --help Show this message and exit
465501
502+
Flags (hunt):
503+
--yes Execute recommended installs/disables (default: report-only)
504+
466505
Environment:
467506
GITHUB_TOKEN GitHub personal access token (optional, enables broader discovery)
468507

0 commit comments

Comments
 (0)