claude-evolve 1.4.11 → 1.4.12

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.
@@ -1,9 +1,45 @@
1
- #!/usr/bin/env python3
2
- """
3
- Auto-updating status display for claude-evolve that fits to terminal size.
4
- Updates in real-time without flicker using ANSI escape sequences.
5
- """
1
+ #!/bin/bash
2
+ set -e
6
3
 
4
+ # Source configuration
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
6
+ source "$SCRIPT_DIR/../lib/config.sh"
7
+
8
+ # Parse arguments
9
+ while [[ $# -gt 0 ]]; do
10
+ case "$1" in
11
+ --working-dir)
12
+ if [[ -n ${2:-} ]]; then
13
+ export CLAUDE_EVOLVE_CONFIG="$2/config.yaml"
14
+ shift 2
15
+ else
16
+ echo "[ERROR] --working-dir requires a directory path" >&2
17
+ exit 1
18
+ fi
19
+ ;;
20
+ -h|--help)
21
+ echo "Usage: claude-evolve-autostatus [--working-dir DIR]"
22
+ echo ""
23
+ echo "Auto-updating status display that fits to terminal size."
24
+ echo "Press 'q' to quit while running."
25
+ exit 0
26
+ ;;
27
+ *)
28
+ echo "[ERROR] Unknown argument: $1" >&2
29
+ exit 1
30
+ ;;
31
+ esac
32
+ done
33
+
34
+ # Load config using the same logic as other commands
35
+ if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
36
+ load_config "$CLAUDE_EVOLVE_CONFIG"
37
+ else
38
+ load_config
39
+ fi
40
+
41
+ # Run the Python autostatus script
42
+ exec "$PYTHON_CMD" -c '
7
43
  import os
8
44
  import sys
9
45
  import time
@@ -11,32 +47,9 @@ import termios
11
47
  import tty
12
48
  import select
13
49
  import signal
14
- import argparse
15
- import subprocess
50
+ import csv
16
51
  from datetime import datetime
17
52
 
18
- # Add parent directory to path for imports
19
- script_dir = os.path.dirname(os.path.abspath(__file__))
20
- parent_dir = os.path.join(script_dir, '..')
21
-
22
- # Try multiple paths to support both development and installed environments
23
- for path in [parent_dir, os.path.join(parent_dir, 'lib'), script_dir]:
24
- if path not in sys.path:
25
- sys.path.insert(0, path)
26
-
27
- try:
28
- from lib.config import Config
29
- from lib.evolution_csv import EvolutionCSV
30
- except ImportError:
31
- # Fallback for installed version where lib might be in a different location
32
- try:
33
- from config import Config
34
- from evolution_csv import EvolutionCSV
35
- except ImportError:
36
- print("Error: Could not import required modules. Please check installation.", file=sys.stderr)
37
- sys.exit(1)
38
-
39
-
40
53
  class TerminalDisplay:
41
54
  """Handles terminal display with ANSI escape sequences for flicker-free updates."""
42
55
 
@@ -47,7 +60,7 @@ class TerminalDisplay:
47
60
  def get_terminal_size(self):
48
61
  """Get current terminal size."""
49
62
  try:
50
- rows, cols = os.popen('stty size', 'r').read().split()
63
+ rows, cols = os.popen("stty size", "r").read().split()
51
64
  return int(rows), int(cols)
52
65
  except:
53
66
  return 24, 80 # Default fallback
@@ -58,90 +71,89 @@ class TerminalDisplay:
58
71
 
59
72
  def clear_screen(self):
60
73
  """Clear the entire screen."""
61
- print('\033[2J\033[H', end='')
74
+ print("\033[2J\033[H", end="")
62
75
 
63
76
  def move_cursor(self, row, col):
64
77
  """Move cursor to specific position."""
65
- print(f'\033[{row};{col}H', end='')
78
+ print(f"\033[{row};{col}H", end="")
66
79
 
67
80
  def clear_line(self):
68
81
  """Clear current line."""
69
- print('\033[2K', end='')
82
+ print("\033[2K", end="")
70
83
 
71
84
  def hide_cursor(self):
72
85
  """Hide the cursor."""
73
- print('\033[?25l', end='')
86
+ print("\033[?25l", end="")
74
87
 
75
88
  def show_cursor(self):
76
89
  """Show the cursor."""
77
- print('\033[?25h', end='')
90
+ print("\033[?25h", end="")
78
91
 
79
92
  def reset(self):
80
93
  """Reset terminal to normal state."""
81
94
  self.show_cursor()
82
- print('\033[0m', end='') # Reset colors
95
+ print("\033[0m", end="") # Reset colors
83
96
 
84
97
 
85
98
  class AutoStatus:
86
99
  """Auto-updating status display."""
87
100
 
88
- def __init__(self, working_dir=None):
89
- self.config = Config()
90
-
91
- # Load config using same mechanism as other commands
92
- # First check CLAUDE_EVOLVE_CONFIG env var
93
- config_env = os.environ.get('CLAUDE_EVOLVE_CONFIG')
94
- if config_env:
95
- self.config.load(config_env)
96
- else:
97
- # Load from working directory or current directory
98
- self.config.load(working_dir=working_dir)
99
-
101
+ def __init__(self, csv_path):
102
+ self.csv_path = csv_path
100
103
  self.display = TerminalDisplay()
101
104
  self.running = True
102
105
 
103
106
  def get_status_data(self):
104
107
  """Get current status data from CSV."""
105
- csv_path = self.config.resolve_path(self.config.data['csv_file'])
108
+ # Read CSV data directly
109
+ with open(self.csv_path, "r") as f:
110
+ reader = csv.DictReader(f)
111
+ rows = list(reader)
106
112
 
107
- with EvolutionCSV(csv_path) as csv:
108
- df = csv.df
109
-
110
- # Count by status
111
- status_counts = {
112
- 'pending': len(df[df['status'] == 'pending']),
113
- 'running': len(df[df['status'] == 'running']),
114
- 'complete': len(df[df['status'] == 'complete']),
115
- 'failed': len(df[df['status'] == 'failed']),
116
- 'total': len(df)
117
- }
118
-
119
- # Get performance stats for completed
120
- completed_df = df[df['status'] == 'complete']
121
- if not completed_df.empty and 'performance' in completed_df.columns:
122
- perf_values = completed_df['performance'].dropna()
123
- if not perf_values.empty:
124
- perf_stats = {
125
- 'min': perf_values.min(),
126
- 'max': perf_values.max(),
127
- 'mean': perf_values.mean(),
128
- 'count': len(perf_values)
129
- }
130
- else:
131
- perf_stats = None
132
- else:
133
- perf_stats = None
134
-
135
- # Get recent candidates (last N that fit on screen)
136
- max_candidates = max(1, self.display.rows - 15) # Reserve space for header/stats
137
- recent = df.tail(max_candidates)
113
+ # Count by status
114
+ status_counts = {
115
+ "pending": 0,
116
+ "running": 0,
117
+ "complete": 0,
118
+ "failed": 0,
119
+ "total": len(rows)
120
+ }
121
+
122
+ # Collect performance values and recent candidates
123
+ perf_values = []
124
+ for row in rows:
125
+ status = row.get("status", "unknown")
126
+ if status in status_counts:
127
+ status_counts[status] += 1
138
128
 
139
- return {
140
- 'counts': status_counts,
141
- 'performance': perf_stats,
142
- 'recent': recent,
143
- 'csv_path': csv_path
129
+ # Collect performance for completed
130
+ if status == "complete" and "performance" in row and row["performance"]:
131
+ try:
132
+ perf_values.append(float(row["performance"]))
133
+ except ValueError:
134
+ pass
135
+
136
+ # Calculate performance stats
137
+ if perf_values:
138
+ perf_stats = {
139
+ "min": min(perf_values),
140
+ "max": max(perf_values),
141
+ "mean": sum(perf_values) / len(perf_values),
142
+ "count": len(perf_values)
144
143
  }
144
+ else:
145
+ perf_stats = None
146
+
147
+ # Get recent candidates (last N that fit on screen)
148
+ max_candidates = max(1, self.display.rows - 15) # Reserve space for header/stats
149
+ recent = rows[-max_candidates:] if rows else []
150
+
151
+ return {
152
+ "counts": status_counts,
153
+ "performance": perf_stats,
154
+ "recent": recent,
155
+ "csv_path": self.csv_path
156
+ }
145
157
 
146
158
  def format_duration(self, seconds):
147
159
  """Format duration in human-readable form."""
@@ -177,12 +189,12 @@ class AutoStatus:
177
189
  # Timestamp
178
190
  self.display.move_cursor(row, 1)
179
191
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
180
- print(f"Updated: {timestamp} | Press 'q' to quit")
192
+ print(f"Updated: {timestamp} | Press '\''q'\'' to quit")
181
193
  row += 2
182
194
 
183
195
  # File info
184
196
  self.display.move_cursor(row, 1)
185
- print(f"CSV: {data['csv_path']}")
197
+ print(f"CSV: {data['\''csv_path'\'']}")
186
198
  row += 2
187
199
 
188
200
  # Status summary
@@ -190,38 +202,38 @@ class AutoStatus:
190
202
  print("\033[1mStatus Summary:\033[0m")
191
203
  row += 1
192
204
 
193
- counts = data['counts']
194
- status_line = (f" Total: {counts['total']} | "
195
- f"\033[33mPending: {counts['pending']}\033[0m | "
196
- f"\033[36mRunning: {counts['running']}\033[0m | "
197
- f"\033[32mComplete: {counts['complete']}\033[0m | "
198
- f"\033[31mFailed: {counts['failed']}\033[0m")
205
+ counts = data["counts"]
206
+ status_line = (f" Total: {counts['\''total'\'']} | "
207
+ f"\033[33mPending: {counts['\''pending'\'']}\033[0m | "
208
+ f"\033[36mRunning: {counts['\''running'\'']}\033[0m | "
209
+ f"\033[32mComplete: {counts['\''complete'\'']}\033[0m | "
210
+ f"\033[31mFailed: {counts['\''failed'\'']}\033[0m")
199
211
 
200
212
  self.display.move_cursor(row, 1)
201
213
  print(status_line)
202
214
  row += 2
203
215
 
204
216
  # Performance stats
205
- if data['performance']:
217
+ if data["performance"]:
206
218
  self.display.move_cursor(row, 1)
207
219
  print("\033[1mPerformance Stats:\033[0m")
208
220
  row += 1
209
221
 
210
- perf = data['performance']
222
+ perf = data["performance"]
211
223
  self.display.move_cursor(row, 1)
212
- print(f" Min: {perf['min']:.4f} | Max: {perf['max']:.4f} | "
213
- f"Mean: {perf['mean']:.4f} | Count: {perf['count']}")
224
+ print(f" Min: {perf['\''min'\'']:.4f} | Max: {perf['\''max'\'']:.4f} | "
225
+ f"Mean: {perf['\''mean'\'']:.4f} | Count: {perf['\''count'\'']}")
214
226
  row += 2
215
227
 
216
228
  # Recent candidates
217
- if not data['recent'].empty:
229
+ if data["recent"]:
218
230
  self.display.move_cursor(row, 1)
219
231
  print("\033[1mRecent Candidates:\033[0m")
220
232
  row += 1
221
233
 
222
234
  # Table header
223
235
  self.display.move_cursor(row, 1)
224
- header_fmt = f"{'ID':>8} | {'Status':^10} | {'Performance':>11} | {'Description'}"
236
+ header_fmt = f"{"ID":>8} | {"Status":^10} | {"Performance":>11} | {"Description"}"
225
237
  print(header_fmt[:self.display.cols])
226
238
  row += 1
227
239
 
@@ -230,38 +242,41 @@ class AutoStatus:
230
242
  row += 1
231
243
 
232
244
  # Table rows
233
- for _, candidate in data['recent'].iterrows():
245
+ for candidate in data["recent"]:
234
246
  if row >= self.display.rows - 1: # Leave room for bottom
235
247
  break
236
248
 
237
249
  self.display.move_cursor(row, 1)
238
250
 
239
251
  # Color based on status
240
- status = candidate.get('status', 'unknown')
241
- if status == 'complete':
242
- color = '\033[32m' # Green
243
- elif status == 'running':
244
- color = '\033[36m' # Cyan
245
- elif status == 'failed':
246
- color = '\033[31m' # Red
247
- elif status == 'pending':
248
- color = '\033[33m' # Yellow
252
+ status = candidate.get("status", "unknown")
253
+ if status == "complete":
254
+ color = "\033[32m" # Green
255
+ elif status == "running":
256
+ color = "\033[36m" # Cyan
257
+ elif status == "failed":
258
+ color = "\033[31m" # Red
259
+ elif status == "pending":
260
+ color = "\033[33m" # Yellow
249
261
  else:
250
- color = '\033[0m' # Default
262
+ color = "\033[0m" # Default
251
263
 
252
264
  # Format performance
253
- if status == 'complete' and 'performance' in candidate:
254
- perf = f"{candidate['performance']:.4f}"
265
+ if status == "complete" and "performance" in candidate and candidate["performance"]:
266
+ try:
267
+ perf = f"{float(candidate['\''performance'\'']):.4f}"
268
+ except ValueError:
269
+ perf = "-"
255
270
  else:
256
271
  perf = "-"
257
272
 
258
273
  # Truncate description to fit
259
- desc = candidate.get('description', '')
274
+ desc = candidate.get("description", "")
260
275
  max_desc_len = self.display.cols - 35 # Account for other columns
261
276
  if len(desc) > max_desc_len:
262
277
  desc = desc[:max_desc_len-3] + "..."
263
278
 
264
- line = f"{candidate['id']:>8} | {color}{status:^10}\033[0m | {perf:>11} | {desc}"
279
+ line = f"{candidate['\''id'\'']:>8} | {color}{status:^10}\033[0m | {perf:>11} | {desc}"
265
280
  print(line[:self.display.cols])
266
281
  row += 1
267
282
 
@@ -273,7 +288,7 @@ class AutoStatus:
273
288
  """Check for keyboard input without blocking."""
274
289
  if select.select([sys.stdin], [], [], 0)[0]:
275
290
  char = sys.stdin.read(1)
276
- if char.lower() == 'q':
291
+ if char.lower() == "q":
277
292
  self.running = False
278
293
  return True
279
294
  return False
@@ -284,13 +299,21 @@ class AutoStatus:
284
299
  old_settings = termios.tcgetattr(sys.stdin)
285
300
 
286
301
  try:
287
- # Set terminal to raw mode for immediate input
288
- tty.setraw(sys.stdin.fileno())
302
+ # Set terminal to cbreak mode (allows Ctrl-C) instead of raw mode
303
+ tty.setcbreak(sys.stdin.fileno())
289
304
 
290
305
  self.display.hide_cursor()
291
306
 
292
307
  while self.running:
293
- self.render()
308
+ try:
309
+ self.render()
310
+ except Exception as e:
311
+ # Show error at bottom of screen
312
+ self.display.move_cursor(self.display.rows - 1, 1)
313
+ self.display.clear_line()
314
+ print(f"\033[31mError: {str(e)}\033[0m", end="")
315
+ sys.stdout.flush()
316
+ time.sleep(2) # Give time to read error
294
317
 
295
318
  # Check for input and wait
296
319
  for _ in range(10): # Check 10 times per second
@@ -299,7 +322,7 @@ class AutoStatus:
299
322
  time.sleep(0.1)
300
323
 
301
324
  except KeyboardInterrupt:
302
- pass
325
+ self.running = False
303
326
 
304
327
  finally:
305
328
  # Restore terminal settings
@@ -310,23 +333,8 @@ class AutoStatus:
310
333
  print("Exiting auto-status...")
311
334
 
312
335
 
313
- def main():
314
- """Main entry point."""
315
- parser = argparse.ArgumentParser(
316
- description="Auto-updating status display for claude-evolve that fits to terminal size.",
317
- epilog="Press 'q' to quit while running."
318
- )
319
- parser.add_argument(
320
- '--working-dir',
321
- help='Working directory containing claude-evolve.yaml config file'
322
- )
323
-
324
- args = parser.parse_args()
325
-
326
- # Run auto-status
327
- auto_status = AutoStatus(working_dir=args.working_dir)
328
- auto_status.run()
329
-
330
-
331
- if __name__ == '__main__':
332
- main()
336
+ # Main execution
337
+ csv_path = "'"$FULL_CSV_PATH"'"
338
+ auto_status = AutoStatus(csv_path)
339
+ auto_status.run()
340
+ '
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.4.11",
3
+ "version": "1.4.12",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",