dayhoff-tools 1.1.10__py3-none-any.whl → 1.13.12__py3-none-any.whl

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 (41) hide show
  1. dayhoff_tools/__init__.py +10 -0
  2. dayhoff_tools/cli/cloud_commands.py +179 -43
  3. dayhoff_tools/cli/engine1/__init__.py +323 -0
  4. dayhoff_tools/cli/engine1/engine_core.py +703 -0
  5. dayhoff_tools/cli/engine1/engine_lifecycle.py +136 -0
  6. dayhoff_tools/cli/engine1/engine_maintenance.py +431 -0
  7. dayhoff_tools/cli/engine1/engine_management.py +505 -0
  8. dayhoff_tools/cli/engine1/shared.py +501 -0
  9. dayhoff_tools/cli/engine1/studio_commands.py +825 -0
  10. dayhoff_tools/cli/engines_studios/__init__.py +6 -0
  11. dayhoff_tools/cli/engines_studios/api_client.py +351 -0
  12. dayhoff_tools/cli/engines_studios/auth.py +144 -0
  13. dayhoff_tools/cli/engines_studios/engine-studio-cli.md +1230 -0
  14. dayhoff_tools/cli/engines_studios/engine_commands.py +1151 -0
  15. dayhoff_tools/cli/engines_studios/progress.py +260 -0
  16. dayhoff_tools/cli/engines_studios/simulators/cli-simulators.md +151 -0
  17. dayhoff_tools/cli/engines_studios/simulators/demo.sh +75 -0
  18. dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py +319 -0
  19. dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py +369 -0
  20. dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py +476 -0
  21. dayhoff_tools/cli/engines_studios/simulators/simulator_utils.py +180 -0
  22. dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py +374 -0
  23. dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py +164 -0
  24. dayhoff_tools/cli/engines_studios/studio_commands.py +755 -0
  25. dayhoff_tools/cli/main.py +106 -7
  26. dayhoff_tools/cli/utility_commands.py +896 -179
  27. dayhoff_tools/deployment/base.py +70 -6
  28. dayhoff_tools/deployment/deploy_aws.py +165 -25
  29. dayhoff_tools/deployment/deploy_gcp.py +78 -5
  30. dayhoff_tools/deployment/deploy_utils.py +20 -7
  31. dayhoff_tools/deployment/job_runner.py +9 -4
  32. dayhoff_tools/deployment/processors.py +230 -418
  33. dayhoff_tools/deployment/swarm.py +47 -12
  34. dayhoff_tools/embedders.py +28 -26
  35. dayhoff_tools/fasta.py +181 -64
  36. dayhoff_tools/warehouse.py +268 -1
  37. {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/METADATA +20 -5
  38. dayhoff_tools-1.13.12.dist-info/RECORD +54 -0
  39. {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/WHEEL +1 -1
  40. dayhoff_tools-1.1.10.dist-info/RECORD +0 -32
  41. {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,260 @@
1
+ """Progress display utilities for async operations."""
2
+
3
+ import time
4
+ from typing import Any, Callable, Dict, Optional
5
+
6
+ import click
7
+
8
+
9
+ def wait_with_progress(
10
+ status_func: Callable[[], Dict[str, Any]],
11
+ is_complete_func: Callable[[Dict[str, Any]], bool],
12
+ label: str = "Progress",
13
+ timeout_seconds: int = 300,
14
+ poll_interval: float = 2.0,
15
+ show_stages: bool = True,
16
+ ) -> Dict[str, Any]:
17
+ """Wait for an async operation with progress display.
18
+
19
+ Args:
20
+ status_func: Function that returns current status dict
21
+ is_complete_func: Function that checks if operation is complete
22
+ label: Label for progress bar
23
+ timeout_seconds: Maximum time to wait
24
+ poll_interval: Seconds between status checks
25
+ show_stages: Whether to show stage/step updates
26
+
27
+ Returns:
28
+ Final status dict
29
+
30
+ Raises:
31
+ TimeoutError: If operation exceeds timeout
32
+ """
33
+
34
+ stages_shown = set()
35
+ start_time = time.time()
36
+
37
+ with click.progressbar(length=100, label=label) as bar:
38
+ while True:
39
+ # Check timeout
40
+ if time.time() - start_time > timeout_seconds:
41
+ raise TimeoutError(f"Operation exceeded {timeout_seconds}s timeout")
42
+
43
+ # Get current status
44
+ try:
45
+ status = status_func()
46
+ except Exception as e:
47
+ click.echo(f"\nError fetching status: {e}", err=True)
48
+ time.sleep(poll_interval)
49
+ continue
50
+
51
+ # Update progress bar
52
+ progress = status.get("progress_percent", 0)
53
+ if progress > bar.pos:
54
+ bar.update(progress - bar.pos)
55
+
56
+ # Show stage/step updates
57
+ if show_stages:
58
+ current_stage = status.get("current_stage") or status.get(
59
+ "current_step"
60
+ )
61
+ if current_stage and current_stage not in stages_shown:
62
+ stages_shown.add(current_stage)
63
+ elapsed = int(time.time() - start_time)
64
+ display_name = current_stage.replace("_", " ").title()
65
+ click.echo(f" [{elapsed}s] {display_name}")
66
+
67
+ # Check completion
68
+ if is_complete_func(status):
69
+ bar.update(100 - bar.pos)
70
+ return status
71
+
72
+ # Check for failure
73
+ status_value = status.get("status", "").lower()
74
+ if status_value == "failed" or status_value == "error":
75
+ error = status.get("error", "Unknown error")
76
+ raise Exception(f"Operation failed: {error}")
77
+
78
+ time.sleep(poll_interval)
79
+
80
+
81
+ def format_sensor_status(sensor_data: Dict[str, Any]) -> str:
82
+ """Format sensor status for display.
83
+
84
+ Args:
85
+ sensor_data: Sensor data dict
86
+
87
+ Returns:
88
+ Formatted string
89
+ """
90
+ active = sensor_data.get("active", False)
91
+ reason = sensor_data.get("reason", "No reason provided")
92
+
93
+ if active:
94
+ return f"🟢\n {reason}"
95
+ else:
96
+ return "⚪"
97
+
98
+
99
+ def format_idle_state(
100
+ idle_state: Dict[str, Any],
101
+ detailed: bool = False,
102
+ attached_studios: Optional[list] = None,
103
+ ) -> str:
104
+ """Format idle state for display.
105
+
106
+ Args:
107
+ idle_state: Idle state dict
108
+ detailed: Whether to show detailed sensor information
109
+ attached_studios: Optional list of attached studio dicts
110
+
111
+ Returns:
112
+ Formatted string
113
+ """
114
+ is_idle = idle_state.get("is_idle", False)
115
+
116
+ lines = []
117
+
118
+ # Status line
119
+ icon = "🟡 IDLE" if is_idle else "🟢 ACTIVE"
120
+ lines.append(f"Idle Status: {icon}")
121
+
122
+ # Timing information
123
+ if idle_state.get("idle_seconds"):
124
+ timeout = idle_state.get("timeout_seconds", 1800)
125
+ elapsed = idle_state["idle_seconds"]
126
+ remaining = max(0, timeout - elapsed)
127
+ lines.append(f"Idle Time: {elapsed}s / {timeout}s")
128
+ if remaining > 0:
129
+ minutes = remaining // 60
130
+ # Yellow text using ANSI escape codes
131
+ lines.append(f"\033[33mWill shutdown in: {remaining}s ({minutes}m)\033[0m")
132
+
133
+ # Attached studios (show before sensors)
134
+ if attached_studios:
135
+ # Purple text for studio names
136
+ studio_names = ", ".join(
137
+ [
138
+ f"\033[35m{s.get('user', s.get('studio_id', 'unknown'))}\033[0m"
139
+ for s in attached_studios
140
+ ]
141
+ )
142
+ lines.append(f"\nAttached Studios: {studio_names}")
143
+ else:
144
+ # Normal text for "None"
145
+ lines.append(f"\nAttached Studios: None")
146
+
147
+ # Detailed sensor information with colorful emojis
148
+ if detailed and idle_state.get("sensors"):
149
+ lines.append(f"\n{'═'*60}")
150
+ lines.append("🔍 Activity Sensors:")
151
+ lines.append(f"{'═'*60}")
152
+
153
+ # Sensor emoji mapping
154
+ sensor_emojis = {
155
+ "coffee": "☕",
156
+ "ssh": "🐚",
157
+ "ide": "💻",
158
+ "docker": "🐳",
159
+ }
160
+
161
+ for sensor_name, sensor_data in idle_state["sensors"].items():
162
+ emoji = sensor_emojis.get(sensor_name.lower(), "📊")
163
+ active = sensor_data.get("active", False)
164
+
165
+ # Special formatting for coffee sensor
166
+ if sensor_name.lower() == "coffee" and active:
167
+ # Extract minutes from details for cleaner display
168
+ details = sensor_data.get("details", {})
169
+ remaining_seconds = int(details.get("remaining_seconds", 0))
170
+ remaining_minutes = remaining_seconds // 60
171
+ lines.append(f"\n{emoji} {sensor_name.upper()} 🟢")
172
+ lines.append(f" Caffeinated for another {remaining_minutes}m")
173
+ else:
174
+ status_icon = format_sensor_status(sensor_data)
175
+ # Format: emoji NAME status_icon (on same line for inactive, split for active)
176
+ if active:
177
+ lines.append(
178
+ f"\n{emoji} {sensor_name.upper()} {status_icon.split(chr(10))[0]}"
179
+ )
180
+ # Add reason on next line
181
+ reason_line = (
182
+ status_icon.split("\n")[1] if "\n" in status_icon else ""
183
+ )
184
+ if reason_line:
185
+ lines.append(reason_line)
186
+ else:
187
+ lines.append(f"\n{emoji} {sensor_name.upper()} {status_icon}")
188
+
189
+ # Show details if available (skip for active coffee sensor with special formatting)
190
+ if sensor_name.lower() == "coffee" and active:
191
+ continue # Already showed coffee details in special format above
192
+
193
+ details = sensor_data.get("details", {})
194
+ if details:
195
+ for key, value in details.items():
196
+ # Skip internal bookkeeping fields and redundant info
197
+ if key in [
198
+ "unique_flavor_count",
199
+ "unique_pid_count",
200
+ "expires_at",
201
+ "flavors",
202
+ "remaining_seconds", # Redundant with time shown in reason
203
+ "pid_count", # Redundant with connections list
204
+ "connections", # Redundant, connections shown in sessions/containers
205
+ ]:
206
+ continue
207
+
208
+ if isinstance(value, list):
209
+ if value: # Only show non-empty lists
210
+ # Show workload containers with clear header
211
+ if key == "containers":
212
+ # Show actual workload container names that are keeping engine active
213
+ for item in value[:5]:
214
+ lines.append(f" • {item}")
215
+ elif key in ["connections", "sessions"]:
216
+ # Just show the items with bullets, no header
217
+ for item in value[:5]:
218
+ lines.append(f" • {item}")
219
+ elif key == "ignored":
220
+ # Ignored list at same indentation level, with header
221
+ lines.append(f" {key}:")
222
+ for item in value[:5]:
223
+ lines.append(f" • {item}")
224
+ else:
225
+ # Other lists get shown with bullets only
226
+ for item in value[:5]:
227
+ lines.append(f" • {item}")
228
+ elif not isinstance(value, (dict, list)):
229
+ lines.append(f" ℹ️ {key}: {value}")
230
+
231
+ return "\n".join(lines)
232
+
233
+
234
+ def format_time_ago(timestamp: str) -> str:
235
+ """Format timestamp as time ago.
236
+
237
+ Args:
238
+ timestamp: ISO format timestamp
239
+
240
+ Returns:
241
+ Human readable time ago string
242
+ """
243
+ from datetime import datetime
244
+
245
+ try:
246
+ dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
247
+ now = datetime.now(dt.tzinfo)
248
+ delta = now - dt
249
+
250
+ seconds = int(delta.total_seconds())
251
+ if seconds < 60:
252
+ return f"{seconds}s ago"
253
+ elif seconds < 3600:
254
+ return f"{seconds // 60}m ago"
255
+ elif seconds < 86400:
256
+ return f"{seconds // 3600}h ago"
257
+ else:
258
+ return f"{seconds // 86400}d ago"
259
+ except:
260
+ return timestamp
@@ -0,0 +1,151 @@
1
+ # CLI Output Simulators
2
+
3
+ This directory contains simulators for iterating on CLI output design without needing AWS access. They're useful for:
4
+
5
+ - Rapid UI iteration during development
6
+ - Visualizing edge cases and different states
7
+ - Testing output formatting without live infrastructure
8
+ - Documentation and examples
9
+
10
+ ## Available Simulators
11
+
12
+ ### 1. Engine List Simulator
13
+
14
+ Simulates `dh engine2 list` output with different configurations.
15
+
16
+ **Usage:**
17
+ ```bash
18
+ # Show all scenarios
19
+ python dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py
20
+
21
+ # Show specific scenario
22
+ python dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py --scenario few
23
+
24
+ # Override environment
25
+ python dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py --scenario many --env prod
26
+ ```
27
+
28
+ **Scenarios:**
29
+ - `single` - Single running engine
30
+ - `few` - Few engines with mixed states (running, stopped, starting)
31
+ - `many` - Many engines (production-like)
32
+ - `empty` - No engines
33
+ - `transitions` - All transitional states (starting, stopping, pending)
34
+ - `all` - Show all scenarios
35
+
36
+ ### 2. Engine Status Simulator
37
+
38
+ Simulates `dh engine2 status` output for different engine states.
39
+
40
+ **Usage:**
41
+ ```bash
42
+ # Show all scenarios
43
+ python dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py
44
+
45
+ # Show specific scenario
46
+ python dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py --scenario running_active
47
+
48
+ # Override environment
49
+ python dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py --scenario stopped --env sand
50
+ ```
51
+
52
+ **Scenarios:**
53
+ - `running_idle` - Running engine with all sensors idle
54
+ - `running_active` - Running engine with SSH, IDE, and coffee active
55
+ - `stopped` - Stopped engine (no idle detection)
56
+ - `starting` - Engine in starting state
57
+ - `stopping` - Engine in stopping state
58
+ - `running_docker` - Running engine with Docker containers
59
+ - `all` - Show all scenarios
60
+
61
+ ### 3. Studio Status Simulator
62
+
63
+ Simulates `dh studio2 status` output for different studio states.
64
+
65
+ **Usage:**
66
+ ```bash
67
+ # Show all scenarios
68
+ python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py
69
+
70
+ # Show specific scenario
71
+ python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py --scenario attached
72
+
73
+ # Override environment
74
+ python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py --scenario large --env prod
75
+ ```
76
+
77
+ **Scenarios:**
78
+ - `available` - Available studio (not attached)
79
+ - `attached` - Studio attached to an engine
80
+ - `large` - Large production studio
81
+ - `new` - Newly created studio
82
+ - `modifying` - Studio being modified
83
+ - `all` - Show all scenarios
84
+
85
+ ### 4. Idle Status Simulator
86
+
87
+ Simulates detailed idle detection output with various sensor configurations.
88
+
89
+ **Usage:**
90
+ ```bash
91
+ # Show all scenarios
92
+ python dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py
93
+
94
+ # Show specific scenario
95
+ python dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py --scenario coffee_lock
96
+
97
+ # Use more emojis
98
+ python dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py --colorful
99
+ ```
100
+
101
+ **Scenarios:**
102
+ - `idle` - Completely idle engine (all sensors inactive)
103
+ - `active_ssh_docker` - Active SSH sessions and Docker containers
104
+ - `active_ide` - Active IDE connection (Cursor)
105
+ - `coffee_lock` - Coffee lock active (prevents shutdown)
106
+ - `near_timeout` - Engine near idle timeout
107
+ - `initializing` - Engine just started (initializing state)
108
+ - `stopped` - Stopped engine (no idle detection)
109
+ - `all` - Show all scenarios
110
+
111
+ ## Implementation Notes
112
+
113
+ ### Standalone Design
114
+
115
+ The simulators are completely standalone and don't require:
116
+ - AWS credentials
117
+ - Network access
118
+ - Installed Python packages (beyond stdlib)
119
+
120
+ They use `simulator_utils.py` which contains formatting functions copied from the main codebase but with no external dependencies.
121
+
122
+ ### Synchronization
123
+
124
+ When modifying formatting in the actual CLI code (`engine_commands.py`, `studio_commands.py`, `progress.py`), update the corresponding simulators to match:
125
+
126
+ 1. **Output changes** → Update simulator `format_*_output()` functions
127
+ 2. **New scenarios** → Add scenarios to `generate_scenarios()`
128
+ 3. **New formatters** → Copy to `simulator_utils.py` (without dependencies)
129
+
130
+ ### Color Codes
131
+
132
+ The simulators use ANSI escape codes for colors:
133
+ - `\033[32m` - Green (running states, active)
134
+ - `\033[31m` - Red (stopped/terminated)
135
+ - `\033[33m` - Yellow (transitional states, warnings)
136
+ - `\033[34m` - Blue (names, environments)
137
+ - `\033[35m` - Purple (studios)
138
+ - `\033[0m` - Reset
139
+
140
+ ## Development Workflow
141
+
142
+ When designing new CLI output:
143
+
144
+ 1. **Add scenarios** to the appropriate simulator
145
+ 2. **Iterate quickly** by running the simulator
146
+ 3. **Review output** for readability and information density
147
+ 4. **Implement** in actual CLI code
148
+ 5. **Update simulator** to match final implementation
149
+
150
+ This is much faster than deploying infrastructure changes and running real CLI commands for every tweak.
151
+
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+ # Demo script to showcase all CLI output simulators
3
+
4
+ set -e
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ cd "$SCRIPT_DIR"
8
+
9
+ echo "╔════════════════════════════════════════════════════════════════════════════╗"
10
+ echo "║ CLI Output Simulators Demo ║"
11
+ echo "╚════════════════════════════════════════════════════════════════════════════╝"
12
+ echo ""
13
+ echo "These simulators let you iterate on CLI design without AWS access."
14
+ echo ""
15
+
16
+ read -p "Press Enter to start the demo..."
17
+
18
+ # Engine List Simulator
19
+ echo ""
20
+ echo "┌────────────────────────────────────────────────────────────────────────────┐"
21
+ echo "│ 1. ENGINE LIST - Few engines in different states │"
22
+ echo "└────────────────────────────────────────────────────────────────────────────┘"
23
+ echo ""
24
+ python engine_list_simulator.py --scenario few
25
+ read -p "Press Enter to continue..."
26
+
27
+ # Engine Status - Running with activity
28
+ echo ""
29
+ echo "┌────────────────────────────────────────────────────────────────────────────┐"
30
+ echo "│ 2. ENGINE STATUS - Running engine with SSH, IDE, and Coffee active │"
31
+ echo "└────────────────────────────────────────────────────────────────────────────┘"
32
+ echo ""
33
+ python engine_status_simulator.py --scenario running_active
34
+ read -p "Press Enter to continue..."
35
+
36
+ # Engine Status - Stopped
37
+ echo ""
38
+ echo "┌────────────────────────────────────────────────────────────────────────────┐"
39
+ echo "│ 3. ENGINE STATUS - Stopped engine (no idle detection) │"
40
+ echo "└────────────────────────────────────────────────────────────────────────────┘"
41
+ echo ""
42
+ python engine_status_simulator.py --scenario stopped
43
+ read -p "Press Enter to continue..."
44
+
45
+ # Studio Status - Attached
46
+ echo ""
47
+ echo "┌────────────────────────────────────────────────────────────────────────────┐"
48
+ echo "│ 4. STUDIO STATUS - Studio attached to engine │"
49
+ echo "└────────────────────────────────────────────────────────────────────────────┘"
50
+ echo ""
51
+ python studio_status_simulator.py --scenario attached
52
+ read -p "Press Enter to continue..."
53
+
54
+ # Idle Status - Coffee lock
55
+ echo ""
56
+ echo "┌────────────────────────────────────────────────────────────────────────────┐"
57
+ echo "│ 5. IDLE STATUS - Engine with coffee lock active │"
58
+ echo "└────────────────────────────────────────────────────────────────────────────┘"
59
+ echo ""
60
+ python idle_status_simulator.py --scenario coffee_lock
61
+ read -p "Press Enter to continue..."
62
+
63
+ echo ""
64
+ echo "╔════════════════════════════════════════════════════════════════════════════╗"
65
+ echo "║ Demo Complete! ║"
66
+ echo "╚════════════════════════════════════════════════════════════════════════════╝"
67
+ echo ""
68
+ echo "Try running simulators with different scenarios:"
69
+ echo " • python engine_list_simulator.py --scenario many"
70
+ echo " • python engine_status_simulator.py --scenario running_docker"
71
+ echo " • python studio_status_simulator.py --scenario all"
72
+ echo " • python idle_status_simulator.py --scenario all"
73
+ echo ""
74
+ echo "See README.md for full documentation."
75
+