Skip to content

Commit 3d890b7

Browse files
baramgaybaramgayclaude
authored
feat: configuration validation script (closes #1)
Adds scripts/validate_config.py which checks required config keys (machine_id, workspace_root), verifies paths exist on disk, reports missing agent folders, and exits 0/1. Integrated into setup.py so it runs automatically at the end of first-time setup. Co-authored-by: baramgay <lhk@gni.re.kr> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 62f97ab commit 3d890b7

4 files changed

Lines changed: 255 additions & 4 deletions

File tree

README.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ pip install -r requirements.txt
7777
python scripts/setup.py
7878
```
7979

80+
To validate your configuration at any time:
81+
82+
```bash
83+
python scripts/validate_config.py
84+
```
85+
8086
#### 4. Start the server
8187

8288
```bash
@@ -90,6 +96,36 @@ Copy `CLAUDE.md` into your project root (or merge with your existing one) and co
9096

9197
---
9298

99+
## ⌨️ Shell Completion
100+
101+
Tab-complete agent IDs and status values in your terminal:
102+
103+
```bash
104+
# 1. Install argcomplete (included in requirements.txt)
105+
pip install argcomplete
106+
107+
# 2. One-time setup — choose your shell
108+
python scripts/install_completion.py --bash # bash → ~/.bashrc
109+
python scripts/install_completion.py --zsh # zsh → ~/.zshrc
110+
python scripts/install_completion.py --fish # fish → ~/.config/fish/completions/
111+
112+
# 3. Reload your shell
113+
source ~/.bashrc # or ~/.zshrc
114+
115+
# 4. Tab away
116+
python scripts/update_status.py [TAB]
117+
# orchestrator lead-data lead-dev eda-analyst backend frontend ...
118+
119+
python scripts/update_status.py eda-analyst [TAB]
120+
# working review waiting done idle
121+
```
122+
123+
> **Note**: Agent IDs are read live from `agent_status.json`, so newly added agents appear in completion automatically without any extra setup.
124+
>
125+
> To preview the snippet without making changes: `python scripts/install_completion.py --print`
126+
127+
---
128+
93129
## 🗂️ Project Structure
94130

95131
```
@@ -104,10 +140,11 @@ agentops/
104140
│ └── ... # 27 more agents
105141
├── scripts/
106142
│ ├── api_server.py # FastAPI server (dashboard + WebSocket + issue API)
107-
│ ├── update_status.py # CLI: declare agent working/done
108-
│ ├── issue_create.py # CLI: create & query issues
109-
│ ├── wiki_cleanup.py # Wiki MoC coverage maintenance
110-
│ └── setup.py # First-time initialization
143+
│ ├── update_status.py # CLI: declare agent working/done
144+
│ ├── install_completion.py # Shell tab-completion installer
145+
│ ├── issue_create.py # CLI: create & query issues
146+
│ ├── wiki_cleanup.py # Wiki MoC coverage maintenance
147+
│ └── setup.py # First-time initialization
111148
├── wiki/
112149
│ ├── 00_home.md # Wiki index
113150
│ ├── MoC/ # Maps of Content (domain indexes)

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Shell tab-completion for update_status.py (optional but recommended)
2+
argcomplete>=3.0.0
3+
14
fastapi>=0.110.0
25
uvicorn[standard]>=0.27.0
36
pydantic>=2.0.0

scripts/setup.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
import json
66
import os
7+
import subprocess
78
import sys
89
from pathlib import Path
910

@@ -86,6 +87,25 @@ def check_requirements():
8687
print("[WARN] Missing dependencies. Run: pip install -r requirements.txt")
8788

8889

90+
def run_config_validation():
91+
"""Run validate_config.py and surface any issues found."""
92+
validator = Path(__file__).parent / "validate_config.py"
93+
result = subprocess.run(
94+
[sys.executable, str(validator), "--quiet"],
95+
capture_output=True,
96+
text=True,
97+
)
98+
if result.returncode != 0:
99+
# Print whatever the validator printed (errors / warnings)
100+
output = (result.stdout + result.stderr).strip()
101+
if output:
102+
print(output)
103+
print("[WARN] Configuration issues detected.")
104+
print(" Run 'python scripts/validate_config.py' for details.")
105+
else:
106+
print("[OK] Configuration validation passed")
107+
108+
89109
def main():
90110
print("=== agentops setup ===\n")
91111
check_python()
@@ -94,6 +114,7 @@ def main():
94114
init_issues()
95115
init_wiki_dirs()
96116
check_requirements()
117+
run_config_validation()
97118
print("\n=== Setup complete ===")
98119
print("Next: python scripts/api_server.py")
99120
print(" Then open index.html in your browser")

scripts/validate_config.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Validate agentops configuration before starting the server.
4+
5+
Usage:
6+
python scripts/validate_config.py # check config.local.json + structure
7+
python scripts/validate_config.py --config path/to/config.json
8+
python scripts/validate_config.py --env # check environment variables only
9+
10+
Exit codes:
11+
0 -- all checks passed
12+
1 -- one or more checks failed
13+
"""
14+
15+
import argparse
16+
import json
17+
import os
18+
import sys
19+
from pathlib import Path
20+
21+
22+
# Keys that must be present in config.local.json when the file exists.
23+
REQUIRED_KEYS = ["machine_id", "workspace_root"]
24+
25+
# Agent folders that should exist under <agents_home>/agents/
26+
EXPECTED_AGENTS = [
27+
"orchestrator",
28+
"lead-data", "lead-dev", "lead-pptx",
29+
"data-collector", "data-cleaner", "eda-analyst", "statistician",
30+
"ml-engineer", "deep-learning", "gis-specialist", "text-analyst",
31+
"visualizer", "reporter", "realty-analyst",
32+
"requirements", "ux-designer", "frontend", "backend",
33+
"dba", "security", "tester-unit", "tester-qa",
34+
"devops", "tech-writer", "architect", "tester",
35+
"pptx-planner", "pptx-content", "pptx-designer", "pptx-builder", "pptx-reviewer",
36+
"statworkbench",
37+
]
38+
39+
# Subdirectories that must exist directly under agents_home
40+
REQUIRED_DIRS = ["agents", "scripts", "wiki"]
41+
42+
# Files that must exist (relative to agents_home)
43+
CRITICAL_FILES = ["agent_status.json", "scripts/update_status.py"]
44+
45+
46+
# ---------------------------------------------------------------------------
47+
# Individual check functions — each returns a list of error strings
48+
# ---------------------------------------------------------------------------
49+
50+
def check_config_file(config_path: Path) -> list:
51+
"""Validate config.local.json if it exists; return error messages."""
52+
errors = []
53+
54+
if not config_path.exists():
55+
# Absence of config.local.json is not an error — defaults are used.
56+
return []
57+
58+
try:
59+
config = json.loads(config_path.read_text(encoding="utf-8"))
60+
except json.JSONDecodeError as exc:
61+
return [f"{config_path.name} is not valid JSON: {exc}"]
62+
63+
# Required keys
64+
for key in REQUIRED_KEYS:
65+
if key not in config:
66+
errors.append(f"Missing required key: '{key}'")
67+
68+
# workspace_root must point to an existing directory
69+
if "workspace_root" in config:
70+
ws = Path(config["workspace_root"])
71+
if not ws.exists():
72+
errors.append(f"workspace_root path does not exist: {ws}")
73+
elif not ws.is_dir():
74+
errors.append(f"workspace_root is not a directory: {ws}")
75+
76+
return errors
77+
78+
79+
def check_agents_home(agents_home: Path) -> list:
80+
"""Check that the agents_home directory is properly structured."""
81+
errors = []
82+
83+
if not agents_home.exists():
84+
return [f"AGENTS_HOME does not exist: {agents_home}"]
85+
86+
if not agents_home.is_dir():
87+
return [f"AGENTS_HOME is not a directory: {agents_home}"]
88+
89+
# Required subdirectories
90+
for dirname in REQUIRED_DIRS:
91+
target = agents_home / dirname
92+
if not target.is_dir():
93+
errors.append(f"Missing required directory: {target}")
94+
95+
# Critical files
96+
for rel in CRITICAL_FILES:
97+
target = agents_home / rel
98+
if not target.exists():
99+
errors.append(f"Missing critical file: {target}")
100+
101+
# Agent folders
102+
agents_dir = agents_home / "agents"
103+
if agents_dir.is_dir():
104+
missing = [a for a in EXPECTED_AGENTS if not (agents_dir / a).is_dir()]
105+
if missing:
106+
errors.append(f"Missing agent folders ({len(missing)}): {', '.join(missing)}")
107+
108+
return errors
109+
110+
111+
def check_env_variables() -> list:
112+
"""Check environment variables; return warning messages (non-fatal)."""
113+
warnings = []
114+
if not os.environ.get("AGENTS_HOME"):
115+
warnings.append(
116+
"AGENTS_HOME environment variable is not set "
117+
"(current directory used as fallback)"
118+
)
119+
return warnings
120+
121+
122+
# ---------------------------------------------------------------------------
123+
# Main
124+
# ---------------------------------------------------------------------------
125+
126+
def main() -> int:
127+
parser = argparse.ArgumentParser(
128+
description="Validate agentops configuration",
129+
formatter_class=argparse.RawDescriptionHelpFormatter,
130+
epilog=__doc__,
131+
)
132+
parser.add_argument(
133+
"--config",
134+
default=None,
135+
metavar="PATH",
136+
help="Path to config file (default: <agents_home>/config.local.json)",
137+
)
138+
parser.add_argument(
139+
"--env",
140+
action="store_true",
141+
help="Check environment variables only",
142+
)
143+
parser.add_argument(
144+
"--quiet", "-q",
145+
action="store_true",
146+
help="Suppress output when all checks pass",
147+
)
148+
args = parser.parse_args()
149+
150+
# Resolve agents_home: env var > script's grandparent directory
151+
agents_home = Path(os.environ.get("AGENTS_HOME", Path(__file__).parent.parent))
152+
config_path = Path(args.config) if args.config else agents_home / "config.local.json"
153+
154+
all_errors: list = []
155+
all_warnings: list = []
156+
157+
if not args.quiet:
158+
print("Validating agentops configuration...")
159+
160+
# 1. Environment variables
161+
all_warnings.extend(check_env_variables())
162+
163+
if not args.env:
164+
# 2. config.local.json
165+
all_errors.extend(check_config_file(config_path))
166+
167+
# 3. Directory / file structure
168+
all_errors.extend(check_agents_home(agents_home))
169+
170+
# --- Report ---
171+
for w in all_warnings:
172+
print(f" [WARN] {w}")
173+
174+
for e in all_errors:
175+
print(f" [ERROR] {e}")
176+
177+
if not all_errors:
178+
if not args.quiet:
179+
print(f" [OK] Configuration valid (checked {config_path})")
180+
return 0
181+
182+
print(
183+
f"\n{len(all_errors)} error(s) found. "
184+
"Fix the issues above before starting the server."
185+
)
186+
return 1
187+
188+
189+
if __name__ == "__main__":
190+
sys.exit(main())

0 commit comments

Comments
 (0)