Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 31 additions & 28 deletions .github/scripts/validate_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
def validate_json_file(templates_json_path: Path) -> dict:
"""Validate templates.json is valid JSON and return parsed data."""
try:
with open(templates_json_path, 'r', encoding='utf-8') as f:
with open(templates_json_path, "r", encoding="utf-8") as f:
data = json.load(f)
print(f"✓ {templates_json_path} is valid JSON")
return data
Expand All @@ -34,35 +34,35 @@ def validate_template_entry(name: str, config: dict, repo_root: Path) -> list[st
errors = []

# Check required fields
if 'file' not in config:
if "file" not in config:
errors.append(f"Template '{name}' missing required field 'file'")
if 'description' not in config:
if "description" not in config:
errors.append(f"Template '{name}' missing required field 'description'")

if 'file' not in config:
if "file" not in config:
return errors # Can't continue without file field

# Check main file exists
main_file = repo_root / config['file']
main_file = repo_root / config["file"]
if not main_file.exists():
errors.append(f"Template '{name}': main file '{config['file']}' does not exist")

# Validate featured field if present (optional)
if 'featured' in config:
if not isinstance(config['featured'], bool):
if "featured" in config:
if not isinstance(config["featured"], bool):
errors.append(f"Template '{name}': 'featured' field must be a boolean")

# Validate author field if present (optional)
if 'author' in config:
author = config['author']
if "author" in config:
author = config["author"]
if not isinstance(author, dict):
errors.append(f"Template '{name}': 'author' field must be an object")
else:
# All author fields are optional, but validate types if present
optional_author_fields = {
'name': str,
'github_profile': str,
'last_modified_date': str
"name": str,
"github_profile": str,
"last_modified_date": str,
}
for field, expected_type in optional_author_fields.items():
if field in author and not isinstance(author[field], expected_type):
Expand All @@ -71,31 +71,34 @@ def validate_template_entry(name: str, config: dict, repo_root: Path) -> list[st
)

# Check files array if present
if 'files' in config:
for file_spec in config['files']:
if 'source' not in file_spec:
if "files" in config:
for file_spec in config["files"]:
if "source" not in file_spec:
errors.append(f"Template '{name}': file entry missing 'source' field")
continue
if 'dest' not in file_spec:
if "dest" not in file_spec:
errors.append(f"Template '{name}': file entry missing 'dest' field")
continue

source_path = repo_root / file_spec['source']
source_path = repo_root / file_spec["source"]
if not source_path.exists():
errors.append(f"Template '{name}': source file '{file_spec['source']}' does not exist")
errors.append(
f"Template '{name}': source file '{file_spec['source']}' does not exist"
)

# For complex templates, check required files exist
is_complex = len(config['files']) > 1
is_complex = len(config["files"]) > 1
if is_complex:
template_dir = Path(config['file']).parent
required_files = ['README.md', 'pyproject.toml.template', '.env.example.template']
template_dir = Path(config["file"]).parent
required_files = [
"README.md",
"pyproject.toml.template",
".env.example.template",
]

for required in required_files:
# Check if it's in the files array
found = any(
Path(f['source']).name == required
for f in config['files']
)
found = any(Path(f["source"]).name == required for f in config["files"])
if not found:
errors.append(
f"Template '{name}': complex template missing '{required}' in files array"
Expand All @@ -106,7 +109,7 @@ def validate_template_entry(name: str, config: dict, repo_root: Path) -> list[st

def main():
repo_root = Path(__file__).parent.parent.parent
templates_json = repo_root / 'templates.json'
templates_json = repo_root / "templates.json"

print("Validating template registry...\n")

Expand All @@ -126,7 +129,7 @@ def main():
print(f"✗ {error}")

# Summary
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
if all_errors:
print(f"Validation failed with {len(all_errors)} error(s)")
sys.exit(1)
Expand All @@ -135,5 +138,5 @@ def main():
sys.exit(0)


if __name__ == '__main__':
if __name__ == "__main__":
main()
40 changes: 24 additions & 16 deletions .github/scripts/verify_template_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@

def main():
repo_root = Path(__file__).parent.parent.parent
templates_json = repo_root / 'templates.json'
templates_json = repo_root / "templates.json"

# Validate templates.json is valid JSON
try:
with open(templates_json) as f:
templates = json.load(f)
print(f"✓ templates.json is valid JSON")
print("✓ templates.json is valid JSON")
except json.JSONDecodeError as e:
print(f"✗ templates.json is invalid JSON: {e}")
sys.exit(1)
except FileNotFoundError:
print(f"✗ templates.json not found")
print("✗ templates.json not found")
sys.exit(1)

errors = []
Expand All @@ -33,39 +33,44 @@ def main():
print(f"\nValidating template: {template_name}")

# Check required fields
if 'file' not in config:
if "file" not in config:
errors.append(f"✗ {template_name}: missing 'file' field")
print(errors[-1])
continue

if 'description' not in config:
if "description" not in config:
errors.append(f"✗ {template_name}: missing 'description' field")
print(errors[-1])

# Check main file exists
main_file = repo_root / config['file']
main_file = repo_root / config["file"]
if main_file.exists():
print(f" ✓ Main file exists: {config['file']}")

# Try to compile Python files
if config['file'].endswith('.py'):
if config["file"].endswith(".py"):
try:
import py_compile

py_compile.compile(main_file, doraise=True)
print(f" ✓ Python file compiles: {config['file']}")
except py_compile.PyCompileError as e:
errors.append(f"✗ {template_name}: Python compilation error in {config['file']}: {e}")
errors.append(
f"✗ {template_name}: Python compilation error in {config['file']}: {e}"
)
print(errors[-1])
else:
errors.append(f"✗ {template_name}: main file not found: {config['file']}")
print(errors[-1])

# Check all files in complex templates
if 'files' in config:
for file_spec in config['files']:
source = file_spec.get('source')
if "files" in config:
for file_spec in config["files"]:
source = file_spec.get("source")
if not source:
errors.append(f"✗ {template_name}: file spec missing 'source' field")
errors.append(
f"✗ {template_name}: file spec missing 'source' field"
)
print(errors[-1])
continue

Expand All @@ -74,20 +79,23 @@ def main():
print(f" ✓ File exists: {source}")

# Try to compile Python files
if source.endswith('.py'):
if source.endswith(".py"):
try:
import py_compile

py_compile.compile(source_file, doraise=True)
print(f" ✓ Python file compiles: {source}")
except py_compile.PyCompileError as e:
errors.append(f"✗ {template_name}: Python compilation error in {source}: {e}")
errors.append(
f"✗ {template_name}: Python compilation error in {source}: {e}"
)
print(errors[-1])
else:
errors.append(f"✗ {template_name}: source file not found: {source}")
print(errors[-1])

# Print summary
print("\n" + "="*50)
print("\n" + "=" * 50)
if errors:
print(f"✗ Validation failed with {len(errors)} error(s)")
for error in errors:
Expand All @@ -98,5 +106,5 @@ def main():
sys.exit(0)


if __name__ == '__main__':
if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion agentmail/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

load_dotenv()

from browser_use import Agent, Browser, ChatBrowserUse, models
from browser_use import Agent, Browser, ChatBrowserUse
from email_tools import EmailTools

TASK = """
Expand Down
21 changes: 21 additions & 0 deletions scheduler/.env.example.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Browser-Use API Key
# Get your key at: https://cloud.browser-use.com/dashboard/settings?tab=api-keys&new
BROWSER_USE_API_KEY=your-key-here

# Cloud Profile ID (optional - for persistent authentication)
# Get your profile ID from: https://cloud.browser-use.com/#settings/profiles
CLOUD_PROFILE_ID=your-profile-id-here

# Cloud Proxy Country Code (optional - default: us)
CLOUD_PROXY_COUNTRY_CODE=us

# Cloud Session Timeout in minutes (optional - default: 60)
CLOUD_TIMEOUT=60

# Scheduler Interval in minutes (optional - default: 5)
SCHEDULER_INTERVAL_MINUTES=5

# Directory containing agent scripts (optional - default: agents)
# All .py files in this directory will be automatically discovered and run
# Prefix files with _ to disable them (e.g., _disabled_script.py)
SCHEDULER_SCRIPTS_DIR=agents
Loading
Loading