autoforge-ai 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/temp_cleanup.py +148 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autoforge-ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Autonomous coding agent with web UI - build complete apps with AI",
5
5
  "license": "AGPL-3.0",
6
6
  "bin": {
@@ -34,6 +34,7 @@
34
34
  "registry.py",
35
35
  "rate_limit_utils.py",
36
36
  "security.py",
37
+ "temp_cleanup.py",
37
38
  "requirements-prod.txt",
38
39
  "pyproject.toml",
39
40
  ".env.example",
@@ -0,0 +1,148 @@
1
+ """
2
+ Temp Cleanup Module
3
+ ===================
4
+
5
+ Cleans up stale temporary files and directories created by AutoForge agents,
6
+ Playwright, Node.js, and other development tools.
7
+
8
+ Called at Maestro (orchestrator) startup to prevent temp folder bloat.
9
+
10
+ Why this exists:
11
+ - Playwright creates browser profiles and artifacts in %TEMP%
12
+ - Node.js creates .node cache files (~7MB each, can accumulate to GBs)
13
+ - MongoDB Memory Server downloads binaries to temp
14
+ - These are never cleaned up automatically
15
+
16
+ When cleanup runs:
17
+ - At Maestro startup (when you click Play or auto-restart after rate limits)
18
+ - Only files/folders older than 1 hour are deleted (safe for running processes)
19
+ """
20
+
21
+ import logging
22
+ import shutil
23
+ import tempfile
24
+ import time
25
+ from pathlib import Path
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Max age in seconds before a temp item is considered stale (1 hour)
30
+ MAX_AGE_SECONDS = 3600
31
+
32
+ # Directory patterns to clean up (glob patterns)
33
+ DIR_PATTERNS = [
34
+ "playwright_firefoxdev_profile-*", # Playwright Firefox profiles
35
+ "playwright-artifacts-*", # Playwright test artifacts
36
+ "playwright-transform-cache", # Playwright transform cache
37
+ "mongodb-memory-server*", # MongoDB Memory Server binaries
38
+ "ng-*", # Angular CLI temp directories
39
+ "scoped_dir*", # Chrome/Chromium temp directories
40
+ ]
41
+
42
+ # File patterns to clean up (glob patterns)
43
+ FILE_PATTERNS = [
44
+ ".78912*.node", # Node.js native module cache (major space consumer, ~7MB each)
45
+ "claude-*-cwd", # Claude CLI working directory temp files
46
+ "mat-debug-*.log", # Material/Angular debug logs
47
+ ]
48
+
49
+
50
+ def cleanup_stale_temp(max_age_seconds: int = MAX_AGE_SECONDS) -> dict:
51
+ """
52
+ Clean up stale temporary files and directories.
53
+
54
+ Only deletes items older than max_age_seconds to avoid
55
+ interfering with currently running processes.
56
+
57
+ Args:
58
+ max_age_seconds: Maximum age in seconds before an item is deleted.
59
+ Defaults to 1 hour (3600 seconds).
60
+
61
+ Returns:
62
+ Dictionary with cleanup statistics:
63
+ - dirs_deleted: Number of directories deleted
64
+ - files_deleted: Number of files deleted
65
+ - bytes_freed: Approximate bytes freed
66
+ - errors: List of error messages (for debugging, not fatal)
67
+ """
68
+ temp_dir = Path(tempfile.gettempdir())
69
+ cutoff_time = time.time() - max_age_seconds
70
+
71
+ stats = {
72
+ "dirs_deleted": 0,
73
+ "files_deleted": 0,
74
+ "bytes_freed": 0,
75
+ "errors": [],
76
+ }
77
+
78
+ # Clean up directories
79
+ for pattern in DIR_PATTERNS:
80
+ for item in temp_dir.glob(pattern):
81
+ if not item.is_dir():
82
+ continue
83
+ try:
84
+ mtime = item.stat().st_mtime
85
+ if mtime < cutoff_time:
86
+ size = _get_dir_size(item)
87
+ shutil.rmtree(item, ignore_errors=True)
88
+ if not item.exists():
89
+ stats["dirs_deleted"] += 1
90
+ stats["bytes_freed"] += size
91
+ logger.debug(f"Deleted temp directory: {item}")
92
+ except Exception as e:
93
+ stats["errors"].append(f"Failed to delete {item}: {e}")
94
+ logger.debug(f"Failed to delete {item}: {e}")
95
+
96
+ # Clean up files
97
+ for pattern in FILE_PATTERNS:
98
+ for item in temp_dir.glob(pattern):
99
+ if not item.is_file():
100
+ continue
101
+ try:
102
+ mtime = item.stat().st_mtime
103
+ if mtime < cutoff_time:
104
+ size = item.stat().st_size
105
+ item.unlink(missing_ok=True)
106
+ if not item.exists():
107
+ stats["files_deleted"] += 1
108
+ stats["bytes_freed"] += size
109
+ logger.debug(f"Deleted temp file: {item}")
110
+ except Exception as e:
111
+ stats["errors"].append(f"Failed to delete {item}: {e}")
112
+ logger.debug(f"Failed to delete {item}: {e}")
113
+
114
+ # Log summary if anything was cleaned
115
+ if stats["dirs_deleted"] > 0 or stats["files_deleted"] > 0:
116
+ mb_freed = stats["bytes_freed"] / (1024 * 1024)
117
+ logger.info(
118
+ f"Temp cleanup: {stats['dirs_deleted']} dirs, "
119
+ f"{stats['files_deleted']} files, {mb_freed:.1f} MB freed"
120
+ )
121
+
122
+ return stats
123
+
124
+
125
+ def _get_dir_size(path: Path) -> int:
126
+ """Get total size of a directory in bytes."""
127
+ total = 0
128
+ try:
129
+ for item in path.rglob("*"):
130
+ if item.is_file():
131
+ try:
132
+ total += item.stat().st_size
133
+ except (OSError, PermissionError):
134
+ pass
135
+ except (OSError, PermissionError):
136
+ pass
137
+ return total
138
+
139
+
140
+ if __name__ == "__main__":
141
+ # Allow running directly for testing/manual cleanup
142
+ logging.basicConfig(level=logging.DEBUG)
143
+ print("Running temp cleanup...")
144
+ stats = cleanup_stale_temp()
145
+ mb_freed = stats["bytes_freed"] / (1024 * 1024)
146
+ print(f"Cleanup complete: {stats['dirs_deleted']} dirs, {stats['files_deleted']} files, {mb_freed:.1f} MB freed")
147
+ if stats["errors"]:
148
+ print(f"Errors (non-fatal): {len(stats['errors'])}")