wafer-cli 0.2.40__tar.gz → 0.2.41__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/PKG-INFO +1 -1
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/pyproject.toml +1 -1
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/workspaces.py +88 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/PKG-INFO +1 -1
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/README.md +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/setup.cfg +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_analytics.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_auth.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_billing.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_cli_coverage.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_cli_parity_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_config_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_file_operations_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_kernel_scope_cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_nsys_analyze.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_nsys_profile.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_output.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_rocprof_compute_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_skill_commands.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_ssh_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_targets_ops.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_wevin_cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_workflow_integration.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/GUIDE.md +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/__init__.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/agent_defaults.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/analytics.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/api_client.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/auth.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/autotuner.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/baseline.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/billing.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/cli_instructions.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/config.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/corpus.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/evaluate.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/global_config.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/gpu_run.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/inference.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/kernel_scope.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/ncu_analyze.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/nsys_analyze.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/nsys_profile.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/output.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/problems.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_compute.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_sdk.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_systems.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/skills/wafer-guide/SKILL.md +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/specs_cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/ssh_keys.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/target_lock.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets_cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets_ops.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/__init__.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/aiter_optimize.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/ask_docs.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_kernel.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_kernelbench.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_vllm.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/trace_analyze.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/tests/test_eval_cli_parity.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/trace_compare.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/tracelens.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/wevin_cli.py +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/SOURCES.txt +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/dependency_links.txt +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/entry_points.txt +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/requires.txt +0 -0
- {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/top_level.txt +0 -0
|
@@ -844,6 +844,73 @@ def _parse_sse_content(content: str) -> SSEEvent:
|
|
|
844
844
|
return SSEEvent(output=content, exit_code=None, is_error=False)
|
|
845
845
|
|
|
846
846
|
|
|
847
|
+
def _exec_via_ssh(ssh_host: str, ssh_port: int, ssh_user: str, command: str) -> int:
|
|
848
|
+
"""Execute command via SSH, streaming output to stdout/stderr.
|
|
849
|
+
|
|
850
|
+
Used for baremetal workspaces. The workspace's zsh plugin handles GPU routing.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
Exit code from remote command
|
|
854
|
+
"""
|
|
855
|
+
import selectors
|
|
856
|
+
import shlex
|
|
857
|
+
import subprocess
|
|
858
|
+
import sys
|
|
859
|
+
|
|
860
|
+
assert ssh_host, "SSH host required"
|
|
861
|
+
assert ssh_port > 0, "SSH port must be positive"
|
|
862
|
+
assert ssh_user, "SSH user required"
|
|
863
|
+
assert command, "Command required"
|
|
864
|
+
|
|
865
|
+
ssh_cmd = [
|
|
866
|
+
"ssh",
|
|
867
|
+
"-p", str(ssh_port),
|
|
868
|
+
"-t", # Force TTY for zsh plugin to work
|
|
869
|
+
"-o", "StrictHostKeyChecking=no",
|
|
870
|
+
"-o", "UserKnownHostsFile=/dev/null",
|
|
871
|
+
"-o", "BatchMode=yes",
|
|
872
|
+
"-o", "LogLevel=ERROR",
|
|
873
|
+
f"{ssh_user}@{ssh_host}",
|
|
874
|
+
f"zsh -i -l -c {shlex.quote(command)}", # Interactive login shell to load plugins
|
|
875
|
+
]
|
|
876
|
+
|
|
877
|
+
process = subprocess.Popen(
|
|
878
|
+
ssh_cmd,
|
|
879
|
+
stdout=subprocess.PIPE,
|
|
880
|
+
stderr=subprocess.PIPE,
|
|
881
|
+
text=True,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
assert process.stdout is not None
|
|
885
|
+
assert process.stderr is not None
|
|
886
|
+
|
|
887
|
+
sel = selectors.DefaultSelector()
|
|
888
|
+
sel.register(process.stdout, selectors.EVENT_READ)
|
|
889
|
+
sel.register(process.stderr, selectors.EVENT_READ)
|
|
890
|
+
|
|
891
|
+
while True:
|
|
892
|
+
for key, _ in sel.select(timeout=0.1):
|
|
893
|
+
line = key.fileobj.readline() # type: ignore
|
|
894
|
+
if line:
|
|
895
|
+
if key.fileobj == process.stdout:
|
|
896
|
+
print(line, end="", file=sys.stdout)
|
|
897
|
+
else:
|
|
898
|
+
print(line, end="", file=sys.stderr)
|
|
899
|
+
|
|
900
|
+
if process.poll() is not None:
|
|
901
|
+
for line in process.stdout:
|
|
902
|
+
print(line, end="", file=sys.stdout)
|
|
903
|
+
for line in process.stderr:
|
|
904
|
+
print(line, end="", file=sys.stderr)
|
|
905
|
+
sys.stdout.flush()
|
|
906
|
+
sys.stderr.flush()
|
|
907
|
+
break
|
|
908
|
+
|
|
909
|
+
sel.close()
|
|
910
|
+
assert process.returncode is not None, "SSH process did not terminate properly"
|
|
911
|
+
return process.returncode
|
|
912
|
+
|
|
913
|
+
|
|
847
914
|
def exec_command(
|
|
848
915
|
workspace_id: str,
|
|
849
916
|
command: str,
|
|
@@ -853,11 +920,18 @@ def exec_command(
|
|
|
853
920
|
) -> int:
|
|
854
921
|
"""Execute a command in workspace, streaming output.
|
|
855
922
|
|
|
923
|
+
For baremetal workspaces (with SSH access), commands are executed via SSH.
|
|
924
|
+
The workspace's zsh plugin handles GPU routing automatically, ensuring
|
|
925
|
+
packages installed via pip persist across commands.
|
|
926
|
+
|
|
927
|
+
For Modal workspaces (no SSH), commands are executed via the API.
|
|
928
|
+
|
|
856
929
|
Args:
|
|
857
930
|
workspace_id: Workspace ID or name
|
|
858
931
|
command: Command to execute
|
|
859
932
|
timeout_seconds: Execution timeout (default: 300, from config)
|
|
860
933
|
routing: Routing hint - "auto", "gpu", "cpu", or "baremetal" (default: auto)
|
|
934
|
+
pull_image: Pull image on target if missing (only for API exec)
|
|
861
935
|
|
|
862
936
|
Returns:
|
|
863
937
|
Exit code (0 = success, non-zero = failure)
|
|
@@ -868,6 +942,20 @@ def exec_command(
|
|
|
868
942
|
assert workspace_id, "Workspace ID must be non-empty"
|
|
869
943
|
assert command, "Command must be non-empty"
|
|
870
944
|
|
|
945
|
+
# Get workspace details to check if SSH is available (baremetal)
|
|
946
|
+
workspace = get_workspace_raw(workspace_id)
|
|
947
|
+
ssh_host = workspace.get("ssh_host")
|
|
948
|
+
ssh_port = workspace.get("ssh_port")
|
|
949
|
+
ssh_user = workspace.get("ssh_user")
|
|
950
|
+
|
|
951
|
+
# Baremetal workspaces have SSH access - use SSH for stateful execution
|
|
952
|
+
# This ensures pip installs persist because we're in the workspace container
|
|
953
|
+
# The zsh plugin still handles GPU routing for GPU commands
|
|
954
|
+
if ssh_host and ssh_port and ssh_user:
|
|
955
|
+
return _exec_via_ssh(ssh_host, ssh_port, ssh_user, command)
|
|
956
|
+
|
|
957
|
+
# Modal workspaces (no SSH) - use API exec
|
|
958
|
+
# Modal Named Sandboxes persist state within their lifetime
|
|
871
959
|
api_url, headers = _get_client()
|
|
872
960
|
|
|
873
961
|
# Base64 encode command to avoid escaping issues
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|