-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcheck_openwolf.py
More file actions
executable file
·246 lines (203 loc) · 7.21 KB
/
Copy pathcheck_openwolf.py
File metadata and controls
executable file
·246 lines (203 loc) · 7.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/usr/bin/env python3
"""
Check if OpenWolf is installed and enabled in all Claude-configured repos.
Discovers repos by finding CLAUDE.md files under a base path, then runs
'openwolf status' in each to verify initialization.
Usage:
check_openwolf.py [--path DIR] [--init] [--dry-run] [--ask]
[--exclude PATH] [--json] [--quiet]
Examples:
check_openwolf.py
check_openwolf.py --path ~/work/repos
check_openwolf.py --init --ask
check_openwolf.py --init --dry-run
check_openwolf.py --json
check_openwolf.py --exclude ~/repos/github.com/almoore/proxmox
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
def check_install(quiet: bool) -> bool:
"""Verify openwolf is installed, attempt npm install if not. Returns True if available."""
if shutil.which("openwolf"):
if not quiet:
print(f"✓ openwolf installed: {shutil.which('openwolf')}")
return True
print("✗ openwolf not found in PATH", file=sys.stderr)
if not shutil.which("npm"):
print(" npm not found — cannot install openwolf automatically", file=sys.stderr)
print(" Install manually: npm install -g openwolf", file=sys.stderr)
return False
print(" Installing openwolf via npm...", file=sys.stderr)
result = subprocess.run(["npm", "install", "-g", "openwolf"])
if result.returncode == 0:
print("✓ openwolf installed successfully")
return True
else:
print("✗ Failed to install openwolf", file=sys.stderr)
return False
def find_repos(base_path: Path, excludes: list[str]) -> list[Path]:
"""Find all repo directories containing CLAUDE.md under base_path."""
repos = []
for claude_file in sorted(base_path.rglob("CLAUDE.md")):
# Limit depth to 5 levels
try:
relative = claude_file.relative_to(base_path)
if len(relative.parts) > 6: # CLAUDE.md itself is 1 part
continue
except ValueError:
continue
repo_dir = claude_file.parent
repo_str = str(repo_dir)
excluded = any(repo_str.startswith(str(Path(e).expanduser())) for e in excludes)
repos.append((repo_dir, excluded))
return repos
def check_repo(repo_dir: Path) -> dict:
"""Check if a repo has OpenWolf initialized, return result dict.
Note: openwolf status exits 0 even when not initialized, so we check
for .wolf/OPENWOLF.md presence as the initialization indicator.
"""
initialized = (repo_dir / ".wolf" / "OPENWOLF.md").exists()
status = "healthy"
if initialized:
result = subprocess.run(
["openwolf", "status"],
cwd=repo_dir,
capture_output=True,
text=True,
)
# Check if status output indicates an issue despite OPENWOLF.md existing
if "error" in (result.stdout + result.stderr).lower():
status = "error"
else:
status = "run: openwolf init"
return {
"path": str(repo_dir),
"initialized": initialized,
"status": status,
}
def run_init(repo_dir: Path, dry_run: bool, ask: bool, quiet: bool) -> bool:
"""Run openwolf init in a repo. Returns True if init was performed."""
if dry_run:
if not quiet:
print(f" [dry-run] would run: openwolf init in {repo_dir}")
return False
if ask:
answer = input(f" Initialize OpenWolf in {repo_dir}? [y/N] ")
if not answer.lower().startswith("y"):
return False
if not quiet:
print(f" Running: openwolf init in {repo_dir}")
result = subprocess.run(["openwolf", "init"], cwd=repo_dir)
return result.returncode == 0
def print_table(results: list[dict]) -> None:
col_repo = 55
col_status = 14
print()
print(f"{'REPO':<{col_repo}} {'OPENWOLF':<{col_status}} STATUS")
print(f"{'-' * col_repo} {'-' * col_status} {'-' * 20}")
for r in results:
path = r["path"]
if r.get("excluded"):
ow_col = "excluded"
status_col = "(skipped)"
elif r["initialized"]:
ow_col = "✓ initialized"
status_col = "healthy"
else:
ow_col = "✗ not init"
status_col = r["status"]
print(f"{path:<{col_repo}} {ow_col:<{col_status}} {status_col}")
def main() -> int:
parser = argparse.ArgumentParser(
description="Check if OpenWolf is installed and enabled in all Claude-configured repos.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"-p", "--path",
default=str(Path.home() / "repos"),
metavar="DIR",
help="Base search path (default: ~/repos)",
)
parser.add_argument(
"-i", "--init",
action="store_true",
help="Run 'openwolf init' on uninitialized repos",
)
parser.add_argument(
"-n", "--dry-run",
action="store_true",
help="Show what would be done without making changes (use with --init)",
)
parser.add_argument(
"-a", "--ask",
action="store_true",
help="Prompt before initializing each repo (use with --init)",
)
parser.add_argument(
"-e", "--exclude",
action="append",
default=[],
metavar="PATH",
help="Exclude a path prefix (repeatable)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results as JSON",
)
parser.add_argument(
"-q", "--quiet",
action="store_true",
help="Suppress all output except errors; exit 1 if any failures",
)
args = parser.parse_args()
# Install check
if not check_install(args.quiet):
return 1
base_path = Path(args.path).expanduser().resolve()
if not base_path.is_dir():
print(f"✗ Path not found: {base_path}", file=sys.stderr)
return 1
repos = find_repos(base_path, args.exclude)
if not repos:
if not args.quiet:
print(f"No CLAUDE.md files found under {base_path}")
return 0
results = []
uninit_repos = []
for repo_dir, excluded in repos:
if excluded:
results.append({"path": str(repo_dir), "initialized": None, "status": "excluded", "excluded": True})
continue
r = check_repo(repo_dir)
results.append(r)
if not r["initialized"]:
uninit_repos.append(repo_dir)
# Output
if args.json:
print(json.dumps(results, indent=2))
elif not args.quiet:
print_table(results)
total = sum(1 for r in results if not r.get("excluded"))
initialized = sum(1 for r in results if r.get("initialized") is True)
print(f"\nSummary: {initialized}/{total} repos initialized")
# Handle --init
if args.init and uninit_repos:
if not args.quiet:
print(f"\nInitializing {len(uninit_repos)} repo(s)...")
for repo_dir in uninit_repos:
run_init(repo_dir, args.dry_run, args.ask, args.quiet)
# Exit code
if uninit_repos:
if args.init and not args.dry_run and not args.ask:
return 0
return 1
return 0
if __name__ == "__main__":
sys.exit(main())