Compare commits
	
		
			4 Commits
		
	
	
		
			d80b956ff7
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dea3b0ec7f | |||
| 581fb0c0f0 | |||
| e8d09164ff | |||
| bc0d90d41a | 
							
								
								
									
										1762
									
								
								aiprompts/libtmux_python.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1762
									
								
								aiprompts/libtmux_python.md
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14342
									
								
								aiprompts/psutil_python.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14342
									
								
								aiprompts/psutil_python.md
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										47
									
								
								atest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								atest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | import requests | ||||||
|  | import sys | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | # ---- Config ---- | ||||||
|  | LMSTUDIO_URL = "http://172.22.22.210:1234/v1" | ||||||
|  | AUDIO_FILE = "/Users/despiegk/Downloads/harvard.wav"   # change to your input file | ||||||
|  |  | ||||||
|  | # ---- Step 1: List available models ---- | ||||||
|  | models_resp = requests.get(f"{LMSTUDIO_URL}/models") | ||||||
|  | models_resp.raise_for_status() | ||||||
|  | models = [m["id"] for m in models_resp.json().get("data", [])] | ||||||
|  |  | ||||||
|  | print("Available models:", models) | ||||||
|  |  | ||||||
|  | # ---- Step 2: Find Whisper ---- | ||||||
|  | whisper_model = None | ||||||
|  | for m in models: | ||||||
|  |     if "whisper" in m.lower(): | ||||||
|  |         whisper_model = m | ||||||
|  |         break | ||||||
|  |  | ||||||
|  | if not whisper_model: | ||||||
|  |     print("❌ No Whisper model found in LM Studio. Please download/start one.") | ||||||
|  |     sys.exit(1) | ||||||
|  |  | ||||||
|  | print(f"✅ Found Whisper model: {whisper_model}") | ||||||
|  |  | ||||||
|  | # ---- Step 3: Transcribe ---- | ||||||
|  | if not os.path.exists(AUDIO_FILE): | ||||||
|  |     print(f"❌ Audio file '{AUDIO_FILE}' not found.") | ||||||
|  |     sys.exit(1) | ||||||
|  |  | ||||||
|  | with open(AUDIO_FILE, "rb") as f: | ||||||
|  |     files = {"file": f} | ||||||
|  |     data = {"model": whisper_model} | ||||||
|  |     headers = {"Authorization": "Bearer no-key"}  # LM Studio ignores key | ||||||
|  |     resp = requests.post(f"{LMSTUDIO_URL}/audio/transcriptions",  | ||||||
|  |                          headers=headers,  | ||||||
|  |                          files=files,  | ||||||
|  |                          data=data) | ||||||
|  |     resp.raise_for_status() | ||||||
|  |     result = resp.json() | ||||||
|  |  | ||||||
|  | print("📝 Transcription result:") | ||||||
|  | print(result.get("text", result)) | ||||||
							
								
								
									
										26
									
								
								examples/install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								examples/install.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | cd /root | ||||||
|  | curl -LsSf https://astral.sh/uv/install.sh | sh | ||||||
|  | uv venv | ||||||
|  |  | ||||||
|  | apt update | ||||||
|  | apt install mc curl htop git -y | ||||||
|  | apt install -y build-essential python3.12-dev | ||||||
|  |  | ||||||
|  | source /root/.local/bin/env | ||||||
|  | export PATH=/root/.venv/bin/:$PATH | ||||||
|  | VLLM_ATTENTION_BACKEND=FLASHINFER | ||||||
|  |  | ||||||
|  | uv pip install --upgrade pip setuptools wheel ninja | ||||||
|  | uv pip install --upgrade tiktoken ipython numpy psutil | ||||||
|  | # uv pip install --pre torch==2.9.0.dev20250804+cu128 --index-url https://download.pytorch.org/whl/nightly/cu128 | ||||||
|  | uv pip install vllm --torch-backend=auto | ||||||
|  | uv pip install flash-attn --no-build-isolation | ||||||
|  | uv pip install triton | ||||||
|  | uv pip install xformers | ||||||
|  |  | ||||||
|  | git clone https://github.com/flashinfer-ai/flashinfer.git | ||||||
|  | cd flashinfer | ||||||
|  | pip install . | ||||||
|  |  | ||||||
|  | # uv pip install --upgrade vllm --torch-backend=auto | ||||||
|  | # uv pip install --upgrade flash-attn --no-build-isolation | ||||||
							
								
								
									
										72
									
								
								examples/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								examples/readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | [](https://docs.vllm.ai/projects/recipes/en/latest/Ernie/Ernie4.5.html) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | vllm bench throughput   --model baidu/ERNIE-4.5-21B-A3B-PT   --dataset-name random   --input-len 8000   --output-len 1000   --num-prompts 16 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | vllm bench throughput \ | ||||||
|  |   --model baidu/ERNIE-4.5-21B-A3B-PT \ | ||||||
|  |   --dataset-name random \ | ||||||
|  |   --input-len 9000 \ | ||||||
|  |   --output-len 4000 \ | ||||||
|  |   --num-prompts 5 \ | ||||||
|  |   --max-model-len 13000 \ | ||||||
|  |   --gpu-memory-utilization 0.7 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | vllm bench throughput \ | ||||||
|  |   --model Qwen/Qwen3-30B-A3B-FP8 \ | ||||||
|  |   --dataset-name random \ | ||||||
|  |   --input-len 9000 \ | ||||||
|  |   --output-len 6000 \ | ||||||
|  |   --num-prompts 4 \ | ||||||
|  |   --max-model-len 15000 \ | ||||||
|  |   --gpu-memory-utilization 0.7 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | vllm bench throughput \ | ||||||
|  |   --model Qwen/Qwen3-30B-A3B-FP8 \ | ||||||
|  |   --dataset-name random \ | ||||||
|  |   --input-len 9000 \ | ||||||
|  |   --output-len 6000 \ | ||||||
|  |   --num-prompts 10 \ | ||||||
|  |   --max-model-len 15000 \ | ||||||
|  |   --gpu-memory-utilization 0.7 \ | ||||||
|  |   --swap-space 256 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   vllm bench throughput \ | ||||||
|  |   --model nvidia/NVIDIA-Nemotron-Nano-9B-v2 \ | ||||||
|  |   --dataset-name random \ | ||||||
|  |   --input-len 9000 \ | ||||||
|  |   --output-len 6000 \ | ||||||
|  |   --num-prompts 2 \ | ||||||
|  |   --max-model-len 15000 \ | ||||||
|  |   --gpu-memory-utilization 0.7 \ | ||||||
|  |   --trust-remote-code \ | ||||||
|  |   --max-num-seqs 64 \ | ||||||
|  |   --mamba-ssm-cache-dtype float16 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #IS A GOOD ONE: | ||||||
|  | vllm bench throughput \ | ||||||
|  |   --model Qwen/Qwen3-30B-A3B-FP8 \ | ||||||
|  |   --dataset-name random \ | ||||||
|  |   --input-len 12000 \ | ||||||
|  |   --output-len 6000 \ | ||||||
|  |   --num-prompts 12 \ | ||||||
|  |   --max-model-len 20000 \ | ||||||
|  |   --gpu-memory-utilization 0.7 \ | ||||||
|  |   --kv-cache-dtype fp8 \ | ||||||
|  |   --swap-space 128 | ||||||
|  |  | ||||||
|  | //be careful with swap space, it can make it slower if too high, PCI bandwidth is limited | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | VLLM_ATTENTION_BACKEND=TRITON_ATTN_VLLM_V1  | ||||||
|  | vllm bench throughput   --model openai/gpt-oss-20b   --dataset-name random   --input-len 12000   --output-len 6000   --num-prompts 8   --max-model-len 20000   --gpu-memory-utilization 0.7   --kv-cache-dtype fp8 | ||||||
							
								
								
									
										17
									
								
								examples/tmuxrunner_start.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								examples/tmuxrunner_start.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from herolib.infra.tmuxrunner.task_runner_enhanced import TaskOrchestrator | ||||||
|  |  | ||||||
|  | def main():     | ||||||
|  |     tasks_dir = sys.argv[1] | ||||||
|  |     api_port = int(sys.argv[2]) if len(sys.argv) > 2 else 8000 | ||||||
|  |     orchestrator = TaskOrchestrator(tasks_dir, api_port) | ||||||
|  |     orchestrator.run() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     if len(sys.argv) < 2: | ||||||
|  |         print("Usage: python enhanced_runner.py <tasks_directory_path> [api_port]") | ||||||
|  |         sys.exit(1) | ||||||
|  |     main() | ||||||
							
								
								
									
										32
									
								
								examples/tmuxrunnerexamples/functions/base.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								examples/tmuxrunnerexamples/functions/base.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | # Common error handling setup | ||||||
|  | # set -euo pipefail | ||||||
|  |  | ||||||
|  | SCRIPT="${BASH_SOURCE[-1]}"   # last sourced = the actual script file | ||||||
|  | ERROR_FILE="$SCRIPT.error" | ||||||
|  | DONE_FILE="$SCRIPT.done" | ||||||
|  |  | ||||||
|  | # Reset markers | ||||||
|  | rm -f "$ERROR_FILE" "$DONE_FILE" | ||||||
|  |  | ||||||
|  | error_handler() { | ||||||
|  |     local exit_code=$? | ||||||
|  |     local line_no=$1 | ||||||
|  |     local cmd="$2" | ||||||
|  |     { | ||||||
|  |         echo "EXIT_CODE=$exit_code" | ||||||
|  |         echo "LINE=$line_no" | ||||||
|  |         echo "COMMAND=$cmd" | ||||||
|  |     } > "$ERROR_FILE" | ||||||
|  |  | ||||||
|  |     # If we are inside a sourced script, don't kill the shell | ||||||
|  |     if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then | ||||||
|  |         return $exit_code | ||||||
|  |     else | ||||||
|  |         exit $exit_code | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | trap 'error_handler ${LINENO} "$BASH_COMMAND"' ERR | ||||||
|  |  | ||||||
|  | mark_done() { | ||||||
|  |     touch "$DONE_FILE" | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								examples/tmuxrunnerexamples/functions/hpy.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								examples/tmuxrunnerexamples/functions/hpy.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | hpy() { | ||||||
|  |     if [ ! -f ".venv/bin/activate" ]; then | ||||||
|  |         echo "Error: .venv not found in current directory" >&2 | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Activate venv in a subshell so it doesn’t pollute caller | ||||||
|  |     ( | ||||||
|  |         source .venv/bin/activate | ||||||
|  |         python "$@" | ||||||
|  |     ) | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								examples/tmuxrunnerexamples/functions/tmuxlib.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								examples/tmuxrunnerexamples/functions/tmuxlib.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | get_session() { | ||||||
|  |     local sessions | ||||||
|  |     sessions=$(tmux ls 2>/dev/null | cut -d: -f1) | ||||||
|  |  | ||||||
|  |     local count | ||||||
|  |     count=$(echo "$sessions" | wc -l) | ||||||
|  |  | ||||||
|  |     if [ "$count" -eq 0 ]; then | ||||||
|  |         echo "Error: no tmux sessions found." >&2 | ||||||
|  |         return 1 | ||||||
|  |     elif [ "$count" -gt 1 ]; then | ||||||
|  |         echo "Error: more than one tmux session found:" >&2 | ||||||
|  |         echo "$sessions" >&2 | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     export SESSIONNAME="$sessions" | ||||||
|  |     echo "$SESSIONNAME" | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								examples/tmuxrunnerexamples/localenv.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/tmuxrunnerexamples/localenv.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  |  | ||||||
|  | export SSH_SERVER=38.79.155.162 | ||||||
|  | export SSH_PORT=61092 | ||||||
							
								
								
									
										0
									
								
								examples/tmuxrunnerexamples/tasks/1/.meta.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								examples/tmuxrunnerexamples/tasks/1/.meta.toml
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										10
									
								
								examples/tmuxrunnerexamples/tasks/1/base_tools.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								examples/tmuxrunnerexamples/tasks/1/base_tools.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -euo pipefail | ||||||
|  | source ../../functions/base.sh | ||||||
|  |  | ||||||
|  | apt update | ||||||
|  | apt upgrade -y  | ||||||
|  | apt install -y tmux btop nvtop psutils htop | ||||||
|  |  | ||||||
|  | mark_done | ||||||
							
								
								
									
										57
									
								
								examples/tmuxrunnerexamples/tasks/1/tmux_config.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										57
									
								
								examples/tmuxrunnerexamples/tasks/1/tmux_config.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | set -euo pipefail | ||||||
|  | source source ../../functions/base.sh | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # --- create ~/.tmux.conf --- | ||||||
|  | TMUX_CONF="$HOME/.tmux.conf" | ||||||
|  |  | ||||||
|  | cat > "$TMUX_CONF" <<'EOF' | ||||||
|  | # ~/.tmux.conf | ||||||
|  |  | ||||||
|  | # Enable mouse support (scroll, resize, select panes/windows) | ||||||
|  | set -g mouse on | ||||||
|  |  | ||||||
|  | # Use the mouse wheel to scroll in copy mode automatically | ||||||
|  | bind -T root WheelUpPane if-shell -F -t = "#{mouse_any_flag}" \ | ||||||
|  |     "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'" | ||||||
|  |  | ||||||
|  | # Allow resizing panes by dragging borders | ||||||
|  | setw -g aggressive-resize on | ||||||
|  |  | ||||||
|  | # Easier navigation in copy mode | ||||||
|  | setw -g mode-keys vi | ||||||
|  |  | ||||||
|  | # Status bar improvements | ||||||
|  | set -g status-bg black | ||||||
|  | set -g status-fg green | ||||||
|  | set -g status-left-length 40 | ||||||
|  | set -g status-left '#S ' | ||||||
|  | set -g status-right '#(whoami)@#H %Y-%m-%d %H:%M' | ||||||
|  |  | ||||||
|  | # Pane borders more visible | ||||||
|  | set -g pane-border-style fg=cyan | ||||||
|  | set -g pane-active-border-style fg=yellow | ||||||
|  |  | ||||||
|  | # Reload config quickly | ||||||
|  | bind r source-file ~/.tmux.conf \; display-message "Reloaded tmux.conf" | ||||||
|  |  | ||||||
|  | # Use system clipboard on macOS | ||||||
|  | if-shell "command -v pbcopy >/dev/null 2>&1" \ | ||||||
|  |     "bind -T copy-mode-vi y send -X copy-pipe-and-cancel 'pbcopy'" \ | ||||||
|  |     "bind -T copy-mode-vi y send -X copy-pipe-and-cancel 'xclip -selection clipboard -in'" | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | echo "✅ Wrote $TMUX_CONF" | ||||||
|  |  | ||||||
|  | # --- apply config if tmux is running --- | ||||||
|  | if pgrep -x tmux >/dev/null 2>&1; then | ||||||
|  |     echo "🔄 Reloading tmux config..." | ||||||
|  |     tmux source-file "$TMUX_CONF" | ||||||
|  | else | ||||||
|  |     echo "ℹ️ tmux is not running yet. Config will apply on next start." | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | mark_done | ||||||
							
								
								
									
										42
									
								
								examples/tmuxrunnerexamples/tasks/2/install_ollama.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								examples/tmuxrunnerexamples/tasks/2/install_ollama.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -euo pipefail | ||||||
|  | source source ../../functions/base.sh | ||||||
|  |  | ||||||
|  | mark_done | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  |  | ||||||
|  | URL="https://github.com/ollama/ollama/releases/download/v0.11.6/ollama-linux-amd64.tgz" | ||||||
|  | TGZ="/tmp/ollama.tgz" | ||||||
|  | INSTALL_PATH="/usr/bin/ollama" | ||||||
|  |  | ||||||
|  | echo "[*] Checking for running ollama serve..." | ||||||
|  | if pgrep -x "ollama" > /dev/null; then | ||||||
|  |     echo "[*] Stopping running ollama process..." | ||||||
|  |     pkill -9 ollama | ||||||
|  |     sleep 2 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | echo "[*] Downloading ollama..." | ||||||
|  | curl -L "$URL" -o "$TGZ" | ||||||
|  |  | ||||||
|  | echo "[*] Extracting..." | ||||||
|  | tar -xzf "$TGZ" -C /tmp | ||||||
|  |  | ||||||
|  | echo "[*] Installing to $INSTALL_PATH..." | ||||||
|  | sudo mv /tmp/ollama "$INSTALL_PATH" | ||||||
|  | sudo chmod +x "$INSTALL_PATH" | ||||||
|  |  | ||||||
|  | pkill -9 ollama | ||||||
|  |  | ||||||
|  | SESSION=$(tmux display-message -p '#S') | ||||||
|  |  | ||||||
|  | echo "[*] Using tmux session: $SESSION" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | echo "[*] Started ollama pulls in tmux windows." | ||||||
|  |  | ||||||
|  | ollama pull adhishtanaka/llama_3.2_1b-SQL | ||||||
|  |  | ||||||
|  | mark_done | ||||||
							
								
								
									
										17
									
								
								examples/tmuxrunnerexamples/tasks/2/install_vllm.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/tmuxrunnerexamples/tasks/2/install_vllm.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -euo pipefail | ||||||
|  | source source ../../functions/base.sh | ||||||
|  |  | ||||||
|  |  | ||||||
|  | curl -LsSf https://astral.sh/uv/install.sh | sh | ||||||
|  |  | ||||||
|  | source $HOME/.local/bin/env | ||||||
|  |  | ||||||
|  | # vllm serve openai/gpt-oss-20b | ||||||
|  | # vllm serve openai/gpt-oss-20b --tensor-parallel-size 8 | ||||||
|  |   | ||||||
|  | # For 120B | ||||||
|  | # vllm serve openai/gpt-oss-120b --tensor-parallel-size 8 | ||||||
|  |  | ||||||
|  | mark_done | ||||||
							
								
								
									
										47
									
								
								examples/tmuxrunnerexamples/tasks/3/vllm.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								examples/tmuxrunnerexamples/tasks/3/vllm.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -euo pipefail | ||||||
|  | source source ../../functions/base.sh | ||||||
|  |  | ||||||
|  | mark_done | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  |  | ||||||
|  | # uv pip install --pre torch --index-url https://download.pytorch.org/whl/nightly/cu121 | ||||||
|  | # uv pip install --pre torch --index-url https://download.pytorch.org/whl/nightly/cu128 | ||||||
|  |  | ||||||
|  | touch "$0.done" | ||||||
|  | exit 0 | ||||||
|  |  | ||||||
|  | cd /root | ||||||
|  |  | ||||||
|  | uv venv | ||||||
|  | source .venv/bin/activate | ||||||
|  |  | ||||||
|  | uv pip install --pre torch==2.9.0.dev20250804+cu129 \ | ||||||
|  |   --index-url https://download.pytorch.org/whl/nightly/cu129 | ||||||
|  |  | ||||||
|  | uv pip install tiktoken ipython numpy psutil | ||||||
|  |  | ||||||
|  | # 4. Confirm it's correct | ||||||
|  | python -c "import torch; print(torch.__version__, torch.version.cuda)" | ||||||
|  | # 2.9.0.dev20250804+cu128 12.8 | ||||||
|  |  | ||||||
|  | source .venv/bin/activate | ||||||
|  | uv pip install --upgrade pip setuptools wheel ninja | ||||||
|  | export MAX_JOBS=8 | ||||||
|  | export TORCH_CUDA_ARCH_LIST="12.0" | ||||||
|  | export NCCL_P2P_DISABLE=0 | ||||||
|  | export NCCL_DEBUG=INFO | ||||||
|  | export CUDA_DEVICE_MAX_CONNECTIONS=1 | ||||||
|  |  | ||||||
|  | uv pip install vllm --torch-backend=auto | ||||||
|  | uv pip install flash-attn --no-build-isolation | ||||||
|  |  | ||||||
|  | # uv pip install --pre vllm==0.10.1+gptoss \ | ||||||
|  | #     --extra-index-url https://wheels.vllm.ai/gpt-oss/ \ | ||||||
|  | #     --extra-index-url https://download.pytorch.org/whl/nightly/cu128 \ | ||||||
|  | #     --index-strategy unsafe-best-match | ||||||
|  |  | ||||||
|  |  | ||||||
|  | mark_done | ||||||
| @@ -6,6 +6,13 @@ Author-email: Kilo Code <kilo.code@example.com> | |||||||
| Requires-Python: >=3.12 | Requires-Python: >=3.12 | ||||||
| Description-Content-Type: text/markdown | Description-Content-Type: text/markdown | ||||||
| Requires-Dist: peewee | Requires-Dist: peewee | ||||||
|  | Requires-Dist: psutil>=5.9.0 | ||||||
|  | Requires-Dist: fastapi>=0.100.0 | ||||||
|  | Requires-Dist: uvicorn>=0.23.0 | ||||||
|  | Requires-Dist: toml>=0.10.2 | ||||||
|  | Requires-Dist: libtmux>=0.25.0 | ||||||
|  | Requires-Dist: lmstudio | ||||||
|  | Requires-Dist: requests | ||||||
|  |  | ||||||
| # herolib_python | # herolib_python | ||||||
|  |  | ||||||
|   | |||||||
| @@ -64,6 +64,10 @@ herolib/downloader/scrape_scapegraph/scrape_md.py | |||||||
| herolib/downloader/scrape_scapegraph/scrape_search.py | herolib/downloader/scrape_scapegraph/scrape_search.py | ||||||
| herolib/downloader/scrape_scapegraph/scrape_with_local_llm.py | herolib/downloader/scrape_scapegraph/scrape_with_local_llm.py | ||||||
| herolib/downloader/scrape_scapegraph/scrape_with_local_llm_search.py | herolib/downloader/scrape_scapegraph/scrape_with_local_llm_search.py | ||||||
|  | herolib/infra/tmuxrunner/model.py | ||||||
|  | herolib/infra/tmuxrunner/process_monitor.py | ||||||
|  | herolib/infra/tmuxrunner/task_runner.py | ||||||
|  | herolib/infra/tmuxrunner/task_runner_api.py | ||||||
| herolib/tools/__init__.py | herolib/tools/__init__.py | ||||||
| herolib/tools/extensions.py | herolib/tools/extensions.py | ||||||
| herolib/tools/gitscanner.py | herolib/tools/gitscanner.py | ||||||
|   | |||||||
| @@ -1 +1,8 @@ | |||||||
| peewee | peewee | ||||||
|  | psutil>=5.9.0 | ||||||
|  | fastapi>=0.100.0 | ||||||
|  | uvicorn>=0.23.0 | ||||||
|  | toml>=0.10.2 | ||||||
|  | libtmux>=0.25.0 | ||||||
|  | lmstudio | ||||||
|  | requests | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								herolib/infra/tmuxrunner/instructions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								herolib/infra/tmuxrunner/instructions.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										64
									
								
								herolib/infra/tmuxrunner/model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								herolib/infra/tmuxrunner/model.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | from typing import Dict, List, Optional | ||||||
|  | from dataclasses import dataclass, field | ||||||
|  | from datetime import datetime | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class ProcessMetrics: | ||||||
|  |     """Metrics for a running process and its children.""" | ||||||
|  |     cpu_percent: float = 0.0 | ||||||
|  |     memory_rss: int = 0  # Resident Set Size in bytes | ||||||
|  |     memory_vms: int = 0  # Virtual Memory Size in bytes | ||||||
|  |     memory_percent: float = 0.0 | ||||||
|  |     num_threads: int = 0 | ||||||
|  |     num_children: int = 0 | ||||||
|  |     children_cpu_percent: float = 0.0 | ||||||
|  |     children_memory_rss: int = 0 | ||||||
|  |     last_updated: str = "" | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class TaskStatus: | ||||||
|  |     """Status of an individual task (script).""" | ||||||
|  |     script_path: str | ||||||
|  |     script_name: str | ||||||
|  |     state: str = "PENDING"  # PENDING, WAITING, RUNNING, DONE, ERROR, CRASHED, TIMED_OUT | ||||||
|  |     start_time: Optional[str] = None | ||||||
|  |     end_time: Optional[str] = None | ||||||
|  |     duration_seconds: float = 0.0 | ||||||
|  |     exit_code: Optional[int] = None | ||||||
|  |     error_message: Optional[str] = None | ||||||
|  |     pane_id: Optional[str] = None | ||||||
|  |     process_metrics: ProcessMetrics = field(default_factory=ProcessMetrics) | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class DirectoryStatus: | ||||||
|  |     """Status of a directory containing tasks.""" | ||||||
|  |     directory_num: int | ||||||
|  |     directory_path: str | ||||||
|  |     state: str = "PENDING"  # PENDING, RUNNING, DONE, ERROR, TIMED_OUT | ||||||
|  |     timeout: int = 600 | ||||||
|  |     start_time: Optional[str] = None | ||||||
|  |     end_time: Optional[str] = None | ||||||
|  |     duration_seconds: float = 0.0 | ||||||
|  |     tasks: List[TaskStatus] = field(default_factory=list) | ||||||
|  |     window_name: Optional[str] = None | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class DAGStructure: | ||||||
|  |     """Complete DAG structure for the task run.""" | ||||||
|  |     run_name: str | ||||||
|  |     run_id: str | ||||||
|  |     state: str = "INITIALIZING"  # INITIALIZING, RUNNING, COMPLETED, FAILED | ||||||
|  |     start_time: str = "" | ||||||
|  |     end_time: Optional[str] = None | ||||||
|  |     duration_seconds: float = 0.0 | ||||||
|  |     total_directories: int = 0 | ||||||
|  |     completed_directories: int = 0 | ||||||
|  |     failed_directories: int = 0 | ||||||
|  |     directories: List[DirectoryStatus] = field(default_factory=list) | ||||||
|  |     last_updated: str = "" | ||||||
|  |  | ||||||
|  | class MetaData: | ||||||
|  |     """Class to hold metadata for a task directory.""" | ||||||
|  |     def __init__(self, timeout: int = 600):  # Default timeout to 10 minutes (600 seconds) | ||||||
|  |         self.timeout = timeout | ||||||
|  |         # Add more attributes here in the future | ||||||
							
								
								
									
										89
									
								
								herolib/infra/tmuxrunner/process_monitor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								herolib/infra/tmuxrunner/process_monitor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | import psutil | ||||||
|  | from typing import List, Optional, Tuple | ||||||
|  | from datetime import datetime | ||||||
|  | from libtmux.pane import Pane | ||||||
|  | from .model import ProcessMetrics | ||||||
|  |  | ||||||
|  | class ProcessMonitor: | ||||||
|  |     """Monitor processes running in tmux panes using psutil.""" | ||||||
|  |      | ||||||
|  |     @staticmethod | ||||||
|  |     def get_pane_process_tree(pane: Pane) -> Tuple[Optional[psutil.Process], List[psutil.Process]]: | ||||||
|  |         """Get the main process and all child processes for a tmux pane.""" | ||||||
|  |         try: | ||||||
|  |             pane_pid = pane.pane_pid | ||||||
|  |             if pane_pid is None: | ||||||
|  |                 return None, [] | ||||||
|  |              | ||||||
|  |             # Get the main process | ||||||
|  |             try: | ||||||
|  |                 main_process = psutil.Process(int(pane_pid)) | ||||||
|  |             except (psutil.NoSuchProcess, ValueError): | ||||||
|  |                 return None, [] | ||||||
|  |              | ||||||
|  |             # Get all children recursively | ||||||
|  |             children = [] | ||||||
|  |             try: | ||||||
|  |                 children = main_process.children(recursive=True) | ||||||
|  |             except psutil.NoSuchProcess: | ||||||
|  |                 pass | ||||||
|  |              | ||||||
|  |             return main_process, children | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"Error getting process tree: {e}") | ||||||
|  |             return None, [] | ||||||
|  |      | ||||||
|  |     @staticmethod | ||||||
|  |     def get_process_metrics(pane: Pane) -> ProcessMetrics: | ||||||
|  |         """Get CPU and memory metrics for all processes in a pane.""" | ||||||
|  |         metrics = ProcessMetrics() | ||||||
|  |         metrics.last_updated = datetime.now().isoformat() | ||||||
|  |          | ||||||
|  |         main_proc, children = ProcessMonitor.get_pane_process_tree(pane) | ||||||
|  |          | ||||||
|  |         if main_proc is None: | ||||||
|  |             return metrics | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Get main process metrics | ||||||
|  |             if main_proc.is_running(): | ||||||
|  |                 metrics.cpu_percent = main_proc.cpu_percent(interval=0.1) | ||||||
|  |                 mem_info = main_proc.memory_info() | ||||||
|  |                 metrics.memory_rss = mem_info.rss | ||||||
|  |                 metrics.memory_vms = mem_info.vms | ||||||
|  |                 metrics.memory_percent = main_proc.memory_percent() | ||||||
|  |                 metrics.num_threads = main_proc.num_threads() | ||||||
|  |              | ||||||
|  |             # Get children metrics | ||||||
|  |             metrics.num_children = len(children) | ||||||
|  |             for child in children: | ||||||
|  |                 try: | ||||||
|  |                     if child.is_running(): | ||||||
|  |                         metrics.children_cpu_percent += child.cpu_percent(interval=0.1) | ||||||
|  |                         child_mem = child.memory_info() | ||||||
|  |                         metrics.children_memory_rss += child_mem.rss | ||||||
|  |                 except (psutil.NoSuchProcess, psutil.AccessDenied): | ||||||
|  |                     pass | ||||||
|  |                      | ||||||
|  |         except (psutil.NoSuchProcess, psutil.AccessDenied) as e: | ||||||
|  |             print(f"Error getting process metrics: {e}") | ||||||
|  |              | ||||||
|  |         return metrics | ||||||
|  |      | ||||||
|  |     @staticmethod | ||||||
|  |     def is_process_running_command(pane: Pane, command_pattern: str) -> bool: | ||||||
|  |         """Check if a specific command is running in the pane.""" | ||||||
|  |         main_proc, children = ProcessMonitor.get_pane_process_tree(pane) | ||||||
|  |          | ||||||
|  |         all_processes = [main_proc] + children if main_proc else children | ||||||
|  |          | ||||||
|  |         for proc in all_processes: | ||||||
|  |             try: | ||||||
|  |                 if proc and proc.is_running(): | ||||||
|  |                     cmdline = " ".join(proc.cmdline()) | ||||||
|  |                     if command_pattern in cmdline: | ||||||
|  |                         return True | ||||||
|  |             except (psutil.NoSuchProcess, psutil.AccessDenied): | ||||||
|  |                 pass | ||||||
|  |          | ||||||
|  |         return False | ||||||
							
								
								
									
										559
									
								
								herolib/infra/tmuxrunner/task_runner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										559
									
								
								herolib/infra/tmuxrunner/task_runner.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,559 @@ | |||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import re | ||||||
|  | import toml | ||||||
|  | import libtmux | ||||||
|  | from libtmux.pane import Pane | ||||||
|  | from libtmux.window import Window | ||||||
|  | from libtmux.session import Session | ||||||
|  | from typing import Dict, List, Optional, Tuple | ||||||
|  | from dataclasses import asdict | ||||||
|  | from datetime import datetime | ||||||
|  | import uuid | ||||||
|  | from pathlib import Path | ||||||
|  | import threading | ||||||
|  |  | ||||||
|  | from .model import DAGStructure, DirectoryStatus, TaskStatus, MetaData | ||||||
|  | from .process_monitor import ProcessMonitor | ||||||
|  |  | ||||||
|  | # Configuration | ||||||
|  | WAITING_MESSAGE = "WAITING FOR JOBS" | ||||||
|  | HPY_SH_PATH = "/root/heromonkey/functions/hpy.sh"  # Path to hpy.sh | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TaskRunner: | ||||||
|  |     def __init__(self, tasks_root_dir: str): | ||||||
|  |         self.tasks_root_dir = tasks_root_dir | ||||||
|  |         self.run_name = os.path.basename(os.path.abspath(tasks_root_dir))  # Derive run_name | ||||||
|  |         self.session = self._get_current_tmux_session() | ||||||
|  |         self.all_tasks_with_meta = self._get_sorted_tasks_with_meta(tasks_root_dir) | ||||||
|  |         self.window_panes = {}  # {window_idx: [pane1, pane2, ...]} | ||||||
|  |         self.run_id = str(uuid.uuid4()) | ||||||
|  |         self.dag = self._initialize_dag() | ||||||
|  |         self.dag_file_path = Path(tasks_root_dir) / ".dag.toml" | ||||||
|  |         self.process_monitor = ProcessMonitor() | ||||||
|  |         self._save_dag() | ||||||
|  |      | ||||||
|  |     def _initialize_dag(self) -> DAGStructure: | ||||||
|  |         """Initialize the DAG structure.""" | ||||||
|  |         dag = DAGStructure( | ||||||
|  |             run_name=self.run_name, | ||||||
|  |             run_id=self.run_id, | ||||||
|  |             state="INITIALIZING", | ||||||
|  |             start_time=datetime.now().isoformat(), | ||||||
|  |             total_directories=len(self.all_tasks_with_meta) | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |         # Create directory entries | ||||||
|  |         for dir_num, (scripts, metadata) in self.all_tasks_with_meta.items(): | ||||||
|  |             dir_status = DirectoryStatus( | ||||||
|  |                 directory_num=dir_num, | ||||||
|  |                 directory_path=os.path.dirname(scripts[0]) if scripts else "", | ||||||
|  |                 timeout=metadata.timeout, | ||||||
|  |                 window_name=f"{self.run_name}_{dir_num}" | ||||||
|  |             ) | ||||||
|  |              | ||||||
|  |             # Create task entries | ||||||
|  |             for script_path in scripts: | ||||||
|  |                 task = TaskStatus( | ||||||
|  |                     script_path=script_path, | ||||||
|  |                     script_name=os.path.basename(script_path) | ||||||
|  |                 ) | ||||||
|  |                 dir_status.tasks.append(task) | ||||||
|  |              | ||||||
|  |             dag.directories.append(dir_status) | ||||||
|  |          | ||||||
|  |         return dag | ||||||
|  |      | ||||||
|  |     def _save_dag(self): | ||||||
|  |         """Save the DAG structure to a TOML file.""" | ||||||
|  |         try: | ||||||
|  |             dag_dict = asdict(self.dag) | ||||||
|  |             with open(self.dag_file_path, 'w') as f: | ||||||
|  |                 toml.dump(dag_dict, f) | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"Error saving DAG: {e}") | ||||||
|  |      | ||||||
|  |     def _update_task_state(self, dir_idx: int, task_idx: int,  | ||||||
|  |                           state: str, error_message: Optional[str] = None): | ||||||
|  |         """Update task state and save DAG.""" | ||||||
|  |         task = self.dag.directories[dir_idx].tasks[task_idx] | ||||||
|  |         old_state = task.state | ||||||
|  |         task.state = state | ||||||
|  |          | ||||||
|  |         if state == "RUNNING" and old_state != "RUNNING": | ||||||
|  |             task.start_time = datetime.now().isoformat() | ||||||
|  |         elif state in ["DONE", "ERROR", "CRASHED", "TIMED_OUT"]: | ||||||
|  |             task.end_time = datetime.now().isoformat() | ||||||
|  |             if task.start_time: | ||||||
|  |                 start = datetime.fromisoformat(task.start_time) | ||||||
|  |                 end = datetime.fromisoformat(task.end_time) | ||||||
|  |                 task.duration_seconds = (end - start).total_seconds() | ||||||
|  |          | ||||||
|  |         if error_message: | ||||||
|  |             task.error_message = error_message | ||||||
|  |          | ||||||
|  |         self.dag.last_updated = datetime.now().isoformat() | ||||||
|  |         self._save_dag() | ||||||
|  |      | ||||||
|  |     def _update_directory_state(self, dir_idx: int, state: str): | ||||||
|  |         """Update directory state and save DAG.""" | ||||||
|  |         directory = self.dag.directories[dir_idx] | ||||||
|  |         old_state = directory.state | ||||||
|  |         directory.state = state | ||||||
|  |          | ||||||
|  |         if state == "RUNNING" and old_state != "RUNNING": | ||||||
|  |             directory.start_time = datetime.now().isoformat() | ||||||
|  |         elif state in ["DONE", "ERROR", "TIMED_OUT"]: | ||||||
|  |             directory.end_time = datetime.now().isoformat() | ||||||
|  |             if directory.start_time: | ||||||
|  |                 start = datetime.fromisoformat(directory.start_time) | ||||||
|  |                 end = datetime.fromisoformat(directory.end_time) | ||||||
|  |                 directory.duration_seconds = (end - start).total_seconds() | ||||||
|  |              | ||||||
|  |             if state == "DONE": | ||||||
|  |                 self.dag.completed_directories += 1 | ||||||
|  |             else: | ||||||
|  |                 self.dag.failed_directories += 1 | ||||||
|  |          | ||||||
|  |         self._save_dag() | ||||||
|  |      | ||||||
|  |     def _check_task_status(self, script_path: str, pane: Pane) -> Tuple[str, Optional[str]]: | ||||||
|  |         """ | ||||||
|  |         Comprehensive task status checking. | ||||||
|  |         Returns: (state, error_message) | ||||||
|  |         """ | ||||||
|  |         script_basename = os.path.basename(script_path) | ||||||
|  |         done_file = f"{script_path}.done" | ||||||
|  |         error_file = f"{script_path}.error" | ||||||
|  |         ok_file = f"{script_path}.ok" | ||||||
|  |          | ||||||
|  |         # Check file markers | ||||||
|  |         if os.path.exists(done_file) or os.path.exists(ok_file): | ||||||
|  |             # Create .ok file if it doesn't exist | ||||||
|  |             if not os.path.exists(ok_file): | ||||||
|  |                 Path(ok_file).touch() | ||||||
|  |             return "DONE", None | ||||||
|  |          | ||||||
|  |         if os.path.exists(error_file): | ||||||
|  |             error_msg = None | ||||||
|  |             try: | ||||||
|  |                 with open(error_file, 'r') as f: | ||||||
|  |                     error_msg = f.read().strip() | ||||||
|  |             except: | ||||||
|  |                 error_msg = "Unknown error" | ||||||
|  |             return "ERROR", error_msg | ||||||
|  |          | ||||||
|  |         # Check if hpy command is running | ||||||
|  |         if self.process_monitor.is_process_running_command(pane, f"hpy {script_basename}"): | ||||||
|  |             return "RUNNING", None | ||||||
|  |          | ||||||
|  |         # Check if pane has any running process | ||||||
|  |         if self._is_pane_running(pane): | ||||||
|  |             # Might be setting up or running something else | ||||||
|  |             return "RUNNING", None | ||||||
|  |          | ||||||
|  |         # If we get here, the process finished without markers | ||||||
|  |         # This is likely a crash | ||||||
|  |         error_msg = f"Process terminated without completion marker" | ||||||
|  |         # Create error file | ||||||
|  |         with open(error_file, 'w') as f: | ||||||
|  |             f.write(error_msg) | ||||||
|  |         return "CRASHED", error_msg | ||||||
|  |      | ||||||
|  |     def _monitor_directory_tasks(self, dir_idx: int, timeout: int) -> bool: | ||||||
|  |         """ | ||||||
|  |         Monitor tasks in a directory with comprehensive status checking. | ||||||
|  |         Returns: True if all tasks completed successfully, False otherwise. | ||||||
|  |         """ | ||||||
|  |         directory = self.dag.directories[dir_idx] | ||||||
|  |         scripts, metadata = self.all_tasks_with_meta[directory.directory_num] | ||||||
|  |         panes = self.window_panes[dir_idx] | ||||||
|  |          | ||||||
|  |         self._update_directory_state(dir_idx, "RUNNING") | ||||||
|  |          | ||||||
|  |         start_time = time.time() | ||||||
|  |         all_success = True | ||||||
|  |          | ||||||
|  |         while True: | ||||||
|  |             all_finished = True | ||||||
|  |             has_errors = False | ||||||
|  |              | ||||||
|  |             for task_idx, (script_path, pane) in enumerate(zip(scripts, panes)): | ||||||
|  |                 task = directory.tasks[task_idx] | ||||||
|  |                  | ||||||
|  |                 # Get process metrics if running | ||||||
|  |                 if task.state == "RUNNING": | ||||||
|  |                     metrics = self.process_monitor.get_process_metrics(pane) | ||||||
|  |                     task.process_metrics = metrics | ||||||
|  |                  | ||||||
|  |                 # Check task status | ||||||
|  |                 new_state, error_msg = self._check_task_status(script_path, pane) | ||||||
|  |                  | ||||||
|  |                 if new_state != task.state: | ||||||
|  |                     self._update_task_state(dir_idx, task_idx, new_state, error_msg) | ||||||
|  |                     print(f"  Task {task.script_name}: {task.state}") | ||||||
|  |                  | ||||||
|  |                 if new_state == "RUNNING": | ||||||
|  |                     all_finished = False | ||||||
|  |                 elif new_state in ["ERROR", "CRASHED", "TIMED_OUT"]: | ||||||
|  |                     has_errors = True | ||||||
|  |                     all_success = False | ||||||
|  |              | ||||||
|  |             # Save DAG periodically | ||||||
|  |             self._save_dag() | ||||||
|  |              | ||||||
|  |             if all_finished: | ||||||
|  |                 if has_errors: | ||||||
|  |                     self._update_directory_state(dir_idx, "ERROR") | ||||||
|  |                 else: | ||||||
|  |                     self._update_directory_state(dir_idx, "DONE") | ||||||
|  |                 break | ||||||
|  |              | ||||||
|  |             # Check timeout | ||||||
|  |             elapsed = time.time() - start_time | ||||||
|  |             if elapsed > timeout: | ||||||
|  |                 print(f"  Directory {directory.directory_num} timed out!") | ||||||
|  |                 for task_idx, task in enumerate(directory.tasks): | ||||||
|  |                     if task.state == "RUNNING": | ||||||
|  |                         self._update_task_state(dir_idx, task_idx, "TIMED_OUT") | ||||||
|  |                         panes[task_idx].send_keys("C-c", literal=True) | ||||||
|  |                 self._update_directory_state(dir_idx, "TIMED_OUT") | ||||||
|  |                 all_success = False | ||||||
|  |                 break | ||||||
|  |              | ||||||
|  |             time.sleep(2)  # Check every 2 seconds | ||||||
|  |          | ||||||
|  |         return all_success | ||||||
|  |      | ||||||
|  |     # def run(self): | ||||||
|  |     #     """Enhanced run method with DAG tracking.""" | ||||||
|  |     #     print(f"Starting enhanced task orchestration for '{self.run_name}'") | ||||||
|  |     #     print(f"Run ID: {self.run_id}") | ||||||
|  |     #     print(f"DAG file: {self.dag_file_path}") | ||||||
|  |          | ||||||
|  |     #     self.dag.state = "RUNNING" | ||||||
|  |     #     self._save_dag() | ||||||
|  |          | ||||||
|  |     #     # Initialize windows and panes (similar to original) | ||||||
|  |     #     self._setup_windows_and_panes() | ||||||
|  |          | ||||||
|  |     #     # Process directories sequentially | ||||||
|  |     #     overall_success = True | ||||||
|  |     #     for dir_idx in range(len(self.dag.directories)): | ||||||
|  |     #         directory = self.dag.directories[dir_idx] | ||||||
|  |     #         print(f"\n--- Processing Directory {directory.directory_num} ---") | ||||||
|  |              | ||||||
|  |     #         # Start tasks if not the first directory | ||||||
|  |     #         if dir_idx > 0: | ||||||
|  |     #             self._start_directory_tasks(dir_idx) | ||||||
|  |              | ||||||
|  |     #         # Monitor tasks | ||||||
|  |     #         success = self._monitor_directory_tasks( | ||||||
|  |     #             dir_idx,  | ||||||
|  |     #             directory.timeout | ||||||
|  |     #         ) | ||||||
|  |              | ||||||
|  |     #         if not success: | ||||||
|  |     #             overall_success = False | ||||||
|  |          | ||||||
|  |     #     # Update final DAG state | ||||||
|  |     #     self.dag.state = "COMPLETED" if overall_success else "FAILED" | ||||||
|  |     #     self.dag.end_time = datetime.now().isoformat() | ||||||
|  |     #     if self.dag.start_time: | ||||||
|  |     #         start = datetime.fromisoformat(self.dag.start_time) | ||||||
|  |     #         end = datetime.fromisoformat(self.dag.end_time) | ||||||
|  |     #         self.dag.duration_seconds = (end - start).total_seconds() | ||||||
|  |     #     self._save_dag() | ||||||
|  |          | ||||||
|  |     #     print(f"\nTask orchestration completed: {self.dag.state}") | ||||||
|  |     #     print(f"Total duration: {self.dag.duration_seconds:.2f} seconds") | ||||||
|  |  | ||||||
|  |     def reset(self): | ||||||
|  |         """Kills all processes and panes inside task windows, removes windows, and deletes .done/.error/.ok files.""" | ||||||
|  |         print(f"\n--- Resetting run '{self.run_name}' ---") | ||||||
|  |         self.cleanup() # First, kill all associated tmux windows | ||||||
|  |  | ||||||
|  |         # Then, remove all .done, .error, and .ok files | ||||||
|  |         print("  Removing .done, .error, and .ok files...") | ||||||
|  |         for dir_num, (scripts, metadata) in self.all_tasks_with_meta.items(): | ||||||
|  |             for script_path in scripts: | ||||||
|  |                 done_file = f"{script_path}.done" | ||||||
|  |                 error_file = f"{script_path}.error" | ||||||
|  |                 ok_file = f"{script_path}.ok" | ||||||
|  |                 if os.path.exists(done_file): | ||||||
|  |                     os.remove(done_file) | ||||||
|  |                     print(f"    Removed: {done_file}") | ||||||
|  |                 if os.path.exists(error_file): | ||||||
|  |                     os.remove(error_file) | ||||||
|  |                     print(f"    Removed: {error_file}") | ||||||
|  |                 if os.path.exists(ok_file): | ||||||
|  |                     os.remove(ok_file) | ||||||
|  |                     print(f"    Removed: {ok_file}") | ||||||
|  |          | ||||||
|  |         # Also remove the .dag.toml file if it exists | ||||||
|  |         if hasattr(self, 'dag_file_path') and self.dag_file_path.exists(): | ||||||
|  |             os.remove(self.dag_file_path) | ||||||
|  |             print(f"    Removed: {self.dag_file_path}") | ||||||
|  |         print("Reset complete.") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _get_sorted_tasks_with_meta(self, tasks_root): | ||||||
|  |         """ | ||||||
|  |         Reads all scripts and .meta.toml from the tasks_root, sorts them by directory, | ||||||
|  |         and then by script name within each directory. | ||||||
|  |         Returns a dictionary where keys are directory numbers (e.g., 1, 2) | ||||||
|  |         and values are tuples of (list_of_full_script_paths, MetaData_object). | ||||||
|  |         """ | ||||||
|  |         tasks_with_meta = {} | ||||||
|  |         for dirpath, dirnames, filenames in os.walk(tasks_root): | ||||||
|  |             if dirpath == tasks_root: | ||||||
|  |                 dirnames[:] = sorted([d for d in dirnames if d.isdigit()], key=int) | ||||||
|  |              | ||||||
|  |             relative_path = os.path.relpath(dirpath, tasks_root) | ||||||
|  |             if relative_path != "." and relative_path.isdigit(): | ||||||
|  |                 dir_num = int(relative_path) | ||||||
|  |                 scripts = sorted([os.path.join(dirpath, f) for f in filenames if f.endswith(".sh")]) | ||||||
|  |                  | ||||||
|  |                 metadata_file = os.path.join(dirpath, ".meta.toml") | ||||||
|  |                 metadata = MetaData()  # Default metadata | ||||||
|  |                 if os.path.exists(metadata_file): | ||||||
|  |                     try: | ||||||
|  |                         with open(metadata_file, 'r') as f: | ||||||
|  |                             meta_data_dict = toml.load(f) | ||||||
|  |                             if 'timeout' in meta_data_dict: | ||||||
|  |                                 metadata.timeout = int(meta_data_dict['timeout']) | ||||||
|  |                     except Exception as e: | ||||||
|  |                         print(f"Warning: Could not read or parse .meta.toml for directory {dir_num}: {e}") | ||||||
|  |                  | ||||||
|  |                 if scripts: | ||||||
|  |                     tasks_with_meta[dir_num] = (scripts, metadata) | ||||||
|  |          | ||||||
|  |         sorted_tasks_with_meta = dict(sorted(tasks_with_meta.items())) | ||||||
|  |         return sorted_tasks_with_meta | ||||||
|  |  | ||||||
|  |     def _get_current_tmux_session(self) -> Session: | ||||||
|  |         """Gets the current tmux session based on TMUX environment variable.""" | ||||||
|  |         server = libtmux.Server() | ||||||
|  |         tmux_env = os.environ.get('TMUX') | ||||||
|  |          | ||||||
|  |         if not tmux_env: | ||||||
|  |             raise Exception("Not running inside a tmux session. The 'TMUX' environment variable is not set.") | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # TMUX variable format: /tmp/tmux-1000/default,12345,0 | ||||||
|  |             # The last part '0' is the session index. | ||||||
|  |             match = re.search(r',(\d+)$', tmux_env) | ||||||
|  |             if not match: | ||||||
|  |                 raise Exception(f"Could not parse session index from TMUX environment variable: {tmux_env}") | ||||||
|  |              | ||||||
|  |             session_index_from_env = match.group(1) | ||||||
|  |              | ||||||
|  |             found_session = None | ||||||
|  |             for s in server.sessions: | ||||||
|  |                 if s.session_id == f"${session_index_from_env}": | ||||||
|  |                     found_session = s | ||||||
|  |                     break | ||||||
|  |              | ||||||
|  |             if not found_session: | ||||||
|  |                 raise Exception(f"Could not find tmux session with ID: ${session_index_from_env}") | ||||||
|  |              | ||||||
|  |             print(f"Attached to current tmux session: '{found_session.name}' via TMUX env var.") | ||||||
|  |             return found_session | ||||||
|  |         except Exception as e: | ||||||
|  |             raise Exception(f"Error getting current tmux session: {e}") | ||||||
|  |  | ||||||
|  |     def _create_tmux_window(self, window_name: str) -> Window: | ||||||
|  |         """Creates a new tmux window.""" | ||||||
|  |         window = self.session.new_window(attach=False, window_name=window_name) | ||||||
|  |         print(f"  Tmux window '{window_name}' created.") | ||||||
|  |         return window | ||||||
|  |  | ||||||
|  |     def _create_tmux_pane(self, window: Window, pane_index: int, command: str) -> Pane: | ||||||
|  |         """Creates a tmux pane and sends a command.""" | ||||||
|  |         if pane_index == 0: | ||||||
|  |             pane = window.active_pane | ||||||
|  |             pane.send_keys("clear", enter=True) | ||||||
|  |         else: | ||||||
|  |             pane = window.split(attach=False) | ||||||
|  |          | ||||||
|  |         pane.send_keys(command, enter=True) | ||||||
|  |         print(f"    Pane {pane_index}: Command sent: '{command}'") | ||||||
|  |         return pane | ||||||
|  |  | ||||||
|  |     def _is_pane_running(self, pane: Pane) -> bool: | ||||||
|  |         """Checks if a tmux pane is still running a process.""" | ||||||
|  |         try: | ||||||
|  |             pane_pid = pane.pane_pid | ||||||
|  |             if pane_pid is not None: | ||||||
|  |                 try: | ||||||
|  |                     pid_int = int(pane_pid) | ||||||
|  |                     if pid_int > 0: | ||||||
|  |                         os.kill(pid_int, 0) | ||||||
|  |                         return True | ||||||
|  |                 except (ValueError, OSError): | ||||||
|  |                     return False | ||||||
|  |             return False | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"Error checking pane status for {pane.window_name}:{pane.pane_index}: {e}") | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     def _setup_windows_and_panes(self): | ||||||
|  |         """Initial setup of tmux windows and panes for all tasks.""" | ||||||
|  |         all_dir_nums = sorted(self.all_tasks_with_meta.keys()) | ||||||
|  |         print("\n--- Initial Tmux Setup: Creating windows and panes ---") | ||||||
|  |         for window_idx, dir_num in enumerate(all_dir_nums): | ||||||
|  |             scripts, metadata = self.all_tasks_with_meta[dir_num] | ||||||
|  |             window_name = f"{self.run_name}_{dir_num}" | ||||||
|  |             window = self._create_tmux_window(window_name) | ||||||
|  |             self.window_panes[window_idx] = [] | ||||||
|  |              | ||||||
|  |             for pane_idx, script_path in enumerate(scripts): | ||||||
|  |                 script_dir = os.path.dirname(script_path) | ||||||
|  |                 script_basename = os.path.basename(script_path) | ||||||
|  |                  | ||||||
|  |                 if window_idx == 0: | ||||||
|  |                     # Send cd command first, then the hpy command | ||||||
|  |                     pane = self._create_tmux_pane(window, pane_idx, f"cd {script_dir}") | ||||||
|  |                     pane.send_keys(f"source {HPY_SH_PATH} && hpy {script_basename}; echo \"Script {script_basename} finished.\"", enter=True) | ||||||
|  |                     print(f"    Pane {pane_idx}: Command sent: 'cd {script_dir}' and 'source {HPY_SH_PATH} && hpy {script_basename}'") | ||||||
|  |                 else: | ||||||
|  |                     command = f"echo '{WAITING_MESSAGE} for {script_basename}'" | ||||||
|  |                     pane = self._create_tmux_pane(window, pane_idx, command) | ||||||
|  |                 self.window_panes[window_idx].append(pane) | ||||||
|  |              | ||||||
|  |             if window_idx == 0: | ||||||
|  |                 print(f"  Window '{window_name}' (Directory {dir_num}) tasks started.") | ||||||
|  |             else: | ||||||
|  |                 print(f"  Window '{window_name}' (Directory {dir_num}) panes set to '{WAITING_MESSAGE}'.") | ||||||
|  |  | ||||||
|  |     def _start_directory_tasks(self, dir_idx: int): | ||||||
|  |         """Starts tasks in a specific directory (window).""" | ||||||
|  |         directory = self.dag.directories[dir_idx] | ||||||
|  |         scripts, metadata = self.all_tasks_with_meta[directory.directory_num] | ||||||
|  |         panes_in_current_window = self.window_panes[dir_idx] | ||||||
|  |  | ||||||
|  |         print(f"\n--- Activating tasks in window '{directory.window_name}' (Directory {directory.directory_num}) ---") | ||||||
|  |         for pane_idx, script_path in enumerate(scripts): | ||||||
|  |             script_dir = os.path.dirname(script_path) | ||||||
|  |             script_basename = os.path.basename(script_path) | ||||||
|  |              | ||||||
|  |             pane = panes_in_current_window[pane_idx] | ||||||
|  |             pane.send_keys("C-c", literal=True)  # Clear any previous command/output | ||||||
|  |              | ||||||
|  |             # Send cd command first, then the hpy command | ||||||
|  |             pane.send_keys(f"cd {script_dir}", enter=True) | ||||||
|  |             pane.send_keys(f"source {HPY_SH_PATH} && hpy {script_basename}; echo \"Script {script_basename} finished.\"", enter=True) | ||||||
|  |             print(f"    Pane {pane_idx}: Command sent: 'cd {script_dir}' and 'source {HPY_SH_PATH} && hpy {script_basename}'") | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         """Enhanced run method with DAG tracking.""" | ||||||
|  |         print(f"Starting enhanced task orchestration for '{self.run_name}'") | ||||||
|  |         print(f"Run ID: {self.run_id}") | ||||||
|  |         print(f"DAG file: {self.dag_file_path}") | ||||||
|  |          | ||||||
|  |         self.dag.state = "RUNNING" | ||||||
|  |         self._save_dag() | ||||||
|  |          | ||||||
|  |         if not self.all_tasks_with_meta: | ||||||
|  |             print("No tasks found to execute.") | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         print("Detected tasks:") | ||||||
|  |         for dir_num, (scripts, metadata) in self.all_tasks_with_meta.items(): | ||||||
|  |             print(f"  Directory {dir_num} (Timeout: {metadata.timeout}s):") | ||||||
|  |             for script in scripts: | ||||||
|  |                 print(f"    - {script}") | ||||||
|  |          | ||||||
|  |         # Initialize windows and panes | ||||||
|  |         self._setup_windows_and_panes() | ||||||
|  |          | ||||||
|  |         # Process directories sequentially | ||||||
|  |         overall_success = True | ||||||
|  |         for dir_idx in range(len(self.dag.directories)): | ||||||
|  |             directory = self.dag.directories[dir_idx] | ||||||
|  |             print(f"\n--- Processing Directory {directory.directory_num} ---") | ||||||
|  |              | ||||||
|  |             # Start tasks if not the first directory | ||||||
|  |             if dir_idx > 0: | ||||||
|  |                 self._start_directory_tasks(dir_idx) | ||||||
|  |              | ||||||
|  |             # Monitor tasks | ||||||
|  |             success = self._monitor_directory_tasks( | ||||||
|  |                 dir_idx,  | ||||||
|  |                 directory.timeout | ||||||
|  |             ) | ||||||
|  |              | ||||||
|  |             if not success: | ||||||
|  |                 overall_success = False | ||||||
|  |          | ||||||
|  |         # Update final DAG state | ||||||
|  |         self.dag.state = "COMPLETED" if overall_success else "FAILED" | ||||||
|  |         self.dag.end_time = datetime.now().isoformat() | ||||||
|  |         if self.dag.start_time: | ||||||
|  |             start = datetime.fromisoformat(self.dag.start_time) | ||||||
|  |             end = datetime.fromisoformat(self.dag.end_time) | ||||||
|  |             self.dag.duration_seconds = (end - start).total_seconds() | ||||||
|  |         self._save_dag() | ||||||
|  |          | ||||||
|  |         print(f"\nTask orchestration completed: {self.dag.state}") | ||||||
|  |         print(f"Total duration: {self.dag.duration_seconds:.2f} seconds") | ||||||
|  |         print(f"You can attach to the tmux session to review: tmux attach -t {self.session.name}") | ||||||
|  |  | ||||||
|  |     def cleanup(self): | ||||||
|  |         """Removes all tmux windows created by this run.""" | ||||||
|  |         print(f"\n--- Cleaning up tmux windows for run '{self.run_name}' ---") | ||||||
|  |         print(f"  Current session name: '{self.session.name}'") | ||||||
|  |         all_session_windows = [w.name for w in self.session.windows if w.name] | ||||||
|  |         print(f"  All windows in current session: {all_session_windows}") | ||||||
|  |          | ||||||
|  |         windows_to_kill = [] | ||||||
|  |         expected_prefix = f"{self.run_name}_" | ||||||
|  |         print(f"  Looking for windows starting with prefix: '{expected_prefix}'") | ||||||
|  |  | ||||||
|  |         for window in self.session.windows: | ||||||
|  |             if window.name and window.name.startswith(expected_prefix): | ||||||
|  |                 windows_to_kill.append(window) | ||||||
|  |          | ||||||
|  |         if not windows_to_kill: | ||||||
|  |             print(f"  No windows found to kill with prefix '{expected_prefix}'.") | ||||||
|  |             print("Cleanup complete.") | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         print(f"  Identified {len(windows_to_kill)} windows to kill: {[w.name for w in windows_to_kill]}") | ||||||
|  |         for window in windows_to_kill: | ||||||
|  |             try: | ||||||
|  |                 window.kill() | ||||||
|  |                 print(f"  Killed window: '{window.name}'") | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(f"  Error killing window '{window.name}': {e}") | ||||||
|  |         print("Cleanup complete.") | ||||||
|  |  | ||||||
|  |     def reset(self): | ||||||
|  |         """Kills all processes and panes inside task windows, removes windows, and deletes .done/.error/.ok files.""" | ||||||
|  |         print(f"\n--- Resetting run '{self.run_name}' ---") | ||||||
|  |         self.cleanup()  # First, kill all associated tmux windows | ||||||
|  |  | ||||||
|  |         # Then, remove all .done, .error, and .ok files | ||||||
|  |         print("  Removing .done, .error, and .ok files...") | ||||||
|  |         for dir_num, (scripts, metadata) in self.all_tasks_with_meta.items(): | ||||||
|  |             for script_path in scripts: | ||||||
|  |                 done_file = f"{script_path}.done" | ||||||
|  |                 error_file = f"{script_path}.error" | ||||||
|  |                 ok_file = f"{script_path}.ok" | ||||||
|  |                 if os.path.exists(done_file): | ||||||
|  |                     os.remove(done_file) | ||||||
|  |                     print(f"    Removed: {done_file}") | ||||||
|  |                 if os.path.exists(error_file): | ||||||
|  |                     os.remove(error_file) | ||||||
|  |                     print(f"    Removed: {error_file}") | ||||||
|  |                 if os.path.exists(ok_file): | ||||||
|  |                     os.remove(ok_file) | ||||||
|  |                     print(f"    Removed: {ok_file}") | ||||||
|  |          | ||||||
|  |         # Also remove the .dag.toml file if it exists | ||||||
|  |         if hasattr(self, 'dag_file_path') and self.dag_file_path.exists(): | ||||||
|  |             os.remove(self.dag_file_path) | ||||||
|  |             print(f"    Removed: {self.dag_file_path}") | ||||||
|  |         print("Reset complete.") | ||||||
							
								
								
									
										167
									
								
								herolib/infra/tmuxrunner/task_runner_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								herolib/infra/tmuxrunner/task_runner_api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | import threading | ||||||
|  | import time | ||||||
|  | from typing import Dict, List, Optional | ||||||
|  | from dataclasses import asdict | ||||||
|  | from datetime import datetime | ||||||
|  | import uvicorn | ||||||
|  | from fastapi import FastAPI, HTTPException | ||||||
|  | from fastapi.middleware.cors import CORSMiddleware | ||||||
|  |  | ||||||
|  | from .task_runner import TaskRunner | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TaskRunnerAPI: | ||||||
|  |     """FastAPI interface for the task runner.""" | ||||||
|  |      | ||||||
|  |     def __init__(self, runner: TaskRunner): | ||||||
|  |         self.runner = runner | ||||||
|  |         self.app = FastAPI(title="Task Runner API", version="1.0.0") | ||||||
|  |          | ||||||
|  |         # Add CORS middleware | ||||||
|  |         self.app.add_middleware( | ||||||
|  |             CORSMiddleware, | ||||||
|  |             allow_origins=["*"], | ||||||
|  |             allow_credentials=True, | ||||||
|  |             allow_methods=["*"], | ||||||
|  |             allow_headers=["*"], | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |         self._setup_routes() | ||||||
|  |      | ||||||
|  |     def _setup_routes(self): | ||||||
|  |         """Setup API routes.""" | ||||||
|  |          | ||||||
|  |         @self.app.get("/") | ||||||
|  |         async def root(): | ||||||
|  |             """Get API information.""" | ||||||
|  |             return { | ||||||
|  |                 "name": "Task Runner API", | ||||||
|  |                 "version": "1.0.0", | ||||||
|  |                 "run_id": self.runner.run_id, | ||||||
|  |                 "run_name": self.runner.run_name | ||||||
|  |             } | ||||||
|  |          | ||||||
|  |         @self.app.get("/status") | ||||||
|  |         async def get_status(): | ||||||
|  |             """Get current run status.""" | ||||||
|  |             return { | ||||||
|  |                 "run_id": self.runner.run_id, | ||||||
|  |                 "run_name": self.runner.run_name, | ||||||
|  |                 "state": self.runner.dag.state, | ||||||
|  |                 "start_time": self.runner.dag.start_time, | ||||||
|  |                 "end_time": self.runner.dag.end_time, | ||||||
|  |                 "duration_seconds": self.runner.dag.duration_seconds, | ||||||
|  |                 "total_directories": self.runner.dag.total_directories, | ||||||
|  |                 "completed_directories": self.runner.dag.completed_directories, | ||||||
|  |                 "failed_directories": self.runner.dag.failed_directories | ||||||
|  |             } | ||||||
|  |          | ||||||
|  |         @self.app.get("/directories") | ||||||
|  |         async def get_directories(): | ||||||
|  |             """Get all directory statuses.""" | ||||||
|  |             return [ | ||||||
|  |                 { | ||||||
|  |                     "directory_num": d.directory_num, | ||||||
|  |                     "directory_path": d.directory_path, | ||||||
|  |                     "state": d.state, | ||||||
|  |                     "timeout": d.timeout, | ||||||
|  |                     "start_time": d.start_time, | ||||||
|  |                     "end_time": d.end_time, | ||||||
|  |                     "duration_seconds": d.duration_seconds, | ||||||
|  |                     "task_count": len(d.tasks), | ||||||
|  |                     "tasks_done": sum(1 for t in d.tasks if t.state == "DONE"), | ||||||
|  |                     "tasks_error": sum(1 for t in d.tasks if t.state in ["ERROR", "CRASHED", "TIMED_OUT"]) | ||||||
|  |                 } | ||||||
|  |                 for d in self.runner.dag.directories | ||||||
|  |             ] | ||||||
|  |          | ||||||
|  |         @self.app.get("/directories/{dir_num}/tasks") | ||||||
|  |         async def get_directory_tasks(dir_num: int): | ||||||
|  |             """Get tasks for a specific directory.""" | ||||||
|  |             for d in self.runner.dag.directories: | ||||||
|  |                 if d.directory_num == dir_num: | ||||||
|  |                     return d.tasks | ||||||
|  |             raise HTTPException(status_code=404, detail="Directory not found") | ||||||
|  |          | ||||||
|  |         @self.app.get("/tasks/{dir_num}/{task_name}") | ||||||
|  |         async def get_task_details(dir_num: int, task_name: str): | ||||||
|  |             """Get detailed information about a specific task.""" | ||||||
|  |             for d in self.runner.dag.directories: | ||||||
|  |                 if d.directory_num == dir_num: | ||||||
|  |                     for t in d.tasks: | ||||||
|  |                         if t.script_name == task_name: | ||||||
|  |                             return t | ||||||
|  |             raise HTTPException(status_code=404, detail="Task not found") | ||||||
|  |          | ||||||
|  |         @self.app.get("/metrics") | ||||||
|  |         async def get_metrics(): | ||||||
|  |             """Get current process metrics for all running tasks.""" | ||||||
|  |             metrics = [] | ||||||
|  |             for d in self.runner.dag.directories: | ||||||
|  |                 for t in d.tasks: | ||||||
|  |                     if t.state == "RUNNING": | ||||||
|  |                         metrics.append({ | ||||||
|  |                             "directory": d.directory_num, | ||||||
|  |                             "task": t.script_name, | ||||||
|  |                             "cpu_percent": t.process_metrics.cpu_percent, | ||||||
|  |                             "memory_rss_mb": t.process_metrics.memory_rss / (1024 * 1024), | ||||||
|  |                             "memory_percent": t.process_metrics.memory_percent, | ||||||
|  |                             "num_threads": t.process_metrics.num_threads, | ||||||
|  |                             "num_children": t.process_metrics.num_children | ||||||
|  |                         }) | ||||||
|  |             return metrics | ||||||
|  |          | ||||||
|  |         @self.app.get("/dag") | ||||||
|  |         async def get_full_dag(): | ||||||
|  |             """Get the complete DAG structure.""" | ||||||
|  |             return asdict(self.runner.dag) | ||||||
|  |      | ||||||
|  |     def start(self, host: str = "0.0.0.0", port: int = 8000): | ||||||
|  |         """Start the FastAPI server.""" | ||||||
|  |         uvicorn.run(self.app, host=host, port=port) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TaskOrchestrator: | ||||||
|  |     """Main orchestrator that runs tasks and API server.""" | ||||||
|  |      | ||||||
|  |     def __init__(self, tasks_dir: str, api_port: int = 8000): | ||||||
|  |         self.runner = TaskRunner(tasks_dir) | ||||||
|  |         self.api = TaskRunnerAPI(self.runner) | ||||||
|  |         self.api_thread = None | ||||||
|  |         self.api_port = api_port | ||||||
|  |      | ||||||
|  |     def start_api_server(self, port: int = 8000): | ||||||
|  |         """Start API server in a separate thread.""" | ||||||
|  |         self.api_thread = threading.Thread( | ||||||
|  |             target=self.api.start, | ||||||
|  |             args=("0.0.0.0", port), | ||||||
|  |             daemon=True | ||||||
|  |         ) | ||||||
|  |         self.api_thread.start() | ||||||
|  |         print(f"API server started on http://0.0.0.0:{port}") | ||||||
|  |      | ||||||
|  |     def run(self): | ||||||
|  |         """Run the task orchestration.""" | ||||||
|  |         # Start API server | ||||||
|  |         self.start_api_server(self.api_port) | ||||||
|  |          | ||||||
|  |         # Reset and run tasks | ||||||
|  |         self.runner.reset() | ||||||
|  |         try: | ||||||
|  |             self.runner.run() | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"Error during execution: {e}") | ||||||
|  |             self.runner.dag.state = "FAILED" | ||||||
|  |             self.runner.dag.end_time = datetime.now().isoformat() | ||||||
|  |             self.runner._save_dag() | ||||||
|  |          | ||||||
|  |         print("\nExecution completed. API server still running.") | ||||||
|  |         print(f"Access API at: http://localhost:{self.api_port}") | ||||||
|  |         print("Press Ctrl+C to stop the API server.") | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Keep the main thread alive for API access | ||||||
|  |             while True: | ||||||
|  |                 time.sleep(1) | ||||||
|  |         except KeyboardInterrupt: | ||||||
|  |             print("\nShutting down...") | ||||||
| @@ -48,12 +48,3 @@ else | |||||||
|     uv pip install herolib@git+https://git.ourworld.tf/herocode/herolib_python.git --force-reinstall --no-cache-dir |     uv pip install herolib@git+https://git.ourworld.tf/herocode/herolib_python.git --force-reinstall --no-cache-dir | ||||||
| fi | fi | ||||||
| echo -e "${GREEN}✅ Dependencies installed${NC}" | echo -e "${GREEN}✅ Dependencies installed${NC}" | ||||||
|  |  | ||||||
| # Create necessary directories |  | ||||||
| mkdir -p static/css static/js static/images |  | ||||||
| mkdir -p templates |  | ||||||
| mkdir -p md |  | ||||||
|  |  | ||||||
| echo -e "${GREEN}✅ Directory structure verified${NC}" |  | ||||||
|  |  | ||||||
| echo -e "${GREEN}🎉 Installation complete! You can now run start_server.sh${NC}" |  | ||||||
| @@ -7,7 +7,7 @@ authors = [ | |||||||
| ] | ] | ||||||
| readme = "README.md" | readme = "README.md" | ||||||
| requires-python = ">=3.12" | requires-python = ">=3.12" | ||||||
| dependencies = ["peewee"] | dependencies = ["peewee", "psutil>=5.9.0", "fastapi>=0.100.0", "uvicorn>=0.23.0", "toml>=0.10.2", "libtmux>=0.25.0","lmstudio","requests"] | ||||||
|  |  | ||||||
|  |  | ||||||
| [build-system] | [build-system] | ||||||
| @@ -18,4 +18,4 @@ build-backend = "setuptools.build_meta" | |||||||
|  |  | ||||||
| [tool.setuptools.packages.find] | [tool.setuptools.packages.find] | ||||||
| where = ["."] | where = ["."] | ||||||
| include = ["herolib*"] | include = ["herolib*"] | ||||||
|   | |||||||
							
								
								
									
										414
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										414
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,20 +1,430 @@ | |||||||
| version = 1 | version = 1 | ||||||
| revision = 3 | revision = 3 | ||||||
| requires-python = "==3.12" | requires-python = ">=3.12" | ||||||
|  | resolution-markers = [ | ||||||
|  |     "python_full_version >= '3.13'", | ||||||
|  |     "python_full_version < '3.13'", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "annotated-types" | ||||||
|  | version = "0.7.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "anyio" | ||||||
|  | version = "4.10.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "idna" }, | ||||||
|  |     { name = "sniffio" }, | ||||||
|  |     { name = "typing-extensions", marker = "python_full_version < '3.13'" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "certifi" | ||||||
|  | version = "2025.8.3" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "charset-normalizer" | ||||||
|  | version = "3.4.3" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "click" | ||||||
|  | version = "8.2.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "colorama", marker = "sys_platform == 'win32'" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "colorama" | ||||||
|  | version = "0.4.6" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "fastapi" | ||||||
|  | version = "0.116.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "pydantic" }, | ||||||
|  |     { name = "starlette" }, | ||||||
|  |     { name = "typing-extensions" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "h11" | ||||||
|  | version = "0.16.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "herolib" | name = "herolib" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| source = { editable = "." } | source = { editable = "." } | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |     { name = "fastapi" }, | ||||||
|  |     { name = "libtmux" }, | ||||||
|  |     { name = "lmstudio" }, | ||||||
|     { name = "peewee" }, |     { name = "peewee" }, | ||||||
|  |     { name = "psutil" }, | ||||||
|  |     { name = "requests" }, | ||||||
|  |     { name = "toml" }, | ||||||
|  |     { name = "uvicorn" }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.metadata] | [package.metadata] | ||||||
| requires-dist = [{ name = "peewee" }] | requires-dist = [ | ||||||
|  |     { name = "fastapi", specifier = ">=0.100.0" }, | ||||||
|  |     { name = "libtmux", specifier = ">=0.25.0" }, | ||||||
|  |     { name = "lmstudio" }, | ||||||
|  |     { name = "peewee" }, | ||||||
|  |     { name = "psutil", specifier = ">=5.9.0" }, | ||||||
|  |     { name = "requests" }, | ||||||
|  |     { name = "toml", specifier = ">=0.10.2" }, | ||||||
|  |     { name = "uvicorn", specifier = ">=0.23.0" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "httpcore" | ||||||
|  | version = "1.0.9" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "certifi" }, | ||||||
|  |     { name = "h11" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "httpx" | ||||||
|  | version = "0.28.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "anyio" }, | ||||||
|  |     { name = "certifi" }, | ||||||
|  |     { name = "httpcore" }, | ||||||
|  |     { name = "idna" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "httpx-ws" | ||||||
|  | version = "0.7.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "anyio" }, | ||||||
|  |     { name = "httpcore" }, | ||||||
|  |     { name = "httpx" }, | ||||||
|  |     { name = "wsproto" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/56/ba/e310ccdb8f18a2b894bfacd085ef390cf6cc70bb10ff9f109d58d94f6b47/httpx_ws-0.7.2.tar.gz", hash = "sha256:93edea6c8fc313464fc287bff7d2ad20e6196b7754c76f946f73b4af79886d4e", size = 24513, upload-time = "2025-03-28T13:20:03.039Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/03/3d/2113a5c7af9a13663fa026882d0302ed4142960388536f885dacd6be7038/httpx_ws-0.7.2-py3-none-any.whl", hash = "sha256:dd7bf9dbaa96dcd5cef1af3a7e1130cfac068bebecce25a74145022f5a8427a3", size = 14424, upload-time = "2025-03-28T13:20:04.238Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "idna" | ||||||
|  | version = "3.10" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "libtmux" | ||||||
|  | version = "0.46.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/9c/aa/7e1dcaa097156d6f3a7d8669be4389dced997feeb81744e3ff4681d65ee8/libtmux-0.46.2.tar.gz", hash = "sha256:9a398fec5d714129c8344555d466e1a903dfc0f741ba07aabe75a8ceb25c5dda", size = 346887, upload-time = "2025-05-26T19:40:04.096Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d6/2f/9d207039fcfa00d3b30e4d765f062fbcc42c873c7518a8cfebb3eafd00e0/libtmux-0.46.2-py3-none-any.whl", hash = "sha256:6c32dbf22bde8e5e33b2714a4295f6e838dc640f337cd4c085a044f6828c7793", size = 60873, upload-time = "2025-05-26T19:40:02.284Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "lmstudio" | ||||||
|  | version = "1.5.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "anyio" }, | ||||||
|  |     { name = "httpx" }, | ||||||
|  |     { name = "httpx-ws" }, | ||||||
|  |     { name = "msgspec" }, | ||||||
|  |     { name = "typing-extensions" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/db/6d/40876f14c759fa2072ccbc844cc3ef7c74c1824e17e7d19b2cf4ff2cea2c/lmstudio-1.5.0.tar.gz", hash = "sha256:458c34fe1f94a7dcc521d4226b4cee82b8af7ea3da8c40b31bbdac558d9a74d4", size = 200230, upload-time = "2025-08-22T13:52:42.487Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b8/51/dcf7a86872a9de6529d67c68db0319b6f2afa093294d1fb8f1affcc86cc8/lmstudio-1.5.0-py3-none-any.whl", hash = "sha256:0b1e5a1cf013744a6ce0a80960204af5bf468dbbb2a505acb4dfb67185fffbe9", size = 139490, upload-time = "2025-08-22T13:52:41.456Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "msgspec" | ||||||
|  | version = "0.19.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498, upload-time = "2024-12-27T17:40:00.427Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950, upload-time = "2024-12-27T17:40:04.219Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647, upload-time = "2024-12-27T17:40:05.606Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563, upload-time = "2024-12-27T17:40:10.516Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996, upload-time = "2024-12-27T17:40:12.244Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087, upload-time = "2024-12-27T17:40:14.881Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432, upload-time = "2024-12-27T17:40:16.256Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "peewee" | name = "peewee" | ||||||
| version = "3.18.2" | version = "3.18.2" | ||||||
| source = { registry = "https://pypi.org/simple" } | source = { registry = "https://pypi.org/simple" } | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" } | sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" } | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "psutil" | ||||||
|  | version = "7.0.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "pydantic" | ||||||
|  | version = "2.11.7" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "annotated-types" }, | ||||||
|  |     { name = "pydantic-core" }, | ||||||
|  |     { name = "typing-extensions" }, | ||||||
|  |     { name = "typing-inspection" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "pydantic-core" | ||||||
|  | version = "2.33.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "typing-extensions" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "requests" | ||||||
|  | version = "2.32.5" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "certifi" }, | ||||||
|  |     { name = "charset-normalizer" }, | ||||||
|  |     { name = "idna" }, | ||||||
|  |     { name = "urllib3" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "sniffio" | ||||||
|  | version = "1.3.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "starlette" | ||||||
|  | version = "0.47.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "anyio" }, | ||||||
|  |     { name = "typing-extensions", marker = "python_full_version < '3.13'" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "toml" | ||||||
|  | version = "0.10.2" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "typing-extensions" | ||||||
|  | version = "4.14.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "typing-inspection" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "typing-extensions" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "urllib3" | ||||||
|  | version = "2.5.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "uvicorn" | ||||||
|  | version = "0.35.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "click" }, | ||||||
|  |     { name = "h11" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "wsproto" | ||||||
|  | version = "1.2.0" | ||||||
|  | source = { registry = "https://pypi.org/simple" } | ||||||
|  | dependencies = [ | ||||||
|  |     { name = "h11" }, | ||||||
|  | ] | ||||||
|  | sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } | ||||||
|  | wheels = [ | ||||||
|  |     { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, | ||||||
|  | ] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user