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.
Files changed (72) hide show
  1. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/PKG-INFO +1 -1
  2. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/pyproject.toml +1 -1
  3. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/workspaces.py +88 -0
  4. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/PKG-INFO +1 -1
  5. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/README.md +0 -0
  6. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/setup.cfg +0 -0
  7. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_analytics.py +0 -0
  8. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_auth.py +0 -0
  9. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_billing.py +0 -0
  10. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_cli_coverage.py +0 -0
  11. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_cli_parity_integration.py +0 -0
  12. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_config_integration.py +0 -0
  13. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_file_operations_integration.py +0 -0
  14. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_kernel_scope_cli.py +0 -0
  15. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_nsys_analyze.py +0 -0
  16. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_nsys_profile.py +0 -0
  17. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_output.py +0 -0
  18. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_rocprof_compute_integration.py +0 -0
  19. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_skill_commands.py +0 -0
  20. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_ssh_integration.py +0 -0
  21. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_targets_ops.py +0 -0
  22. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_wevin_cli.py +0 -0
  23. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/tests/test_workflow_integration.py +0 -0
  24. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/GUIDE.md +0 -0
  25. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/__init__.py +0 -0
  26. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/agent_defaults.py +0 -0
  27. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/analytics.py +0 -0
  28. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/api_client.py +0 -0
  29. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/auth.py +0 -0
  30. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/autotuner.py +0 -0
  31. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/baseline.py +0 -0
  32. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/billing.py +0 -0
  33. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/cli.py +0 -0
  34. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/cli_instructions.py +0 -0
  35. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/config.py +0 -0
  36. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/corpus.py +0 -0
  37. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/evaluate.py +0 -0
  38. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/global_config.py +0 -0
  39. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/gpu_run.py +0 -0
  40. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/inference.py +0 -0
  41. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/kernel_scope.py +0 -0
  42. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/ncu_analyze.py +0 -0
  43. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/nsys_analyze.py +0 -0
  44. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/nsys_profile.py +0 -0
  45. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/output.py +0 -0
  46. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/problems.py +0 -0
  47. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_compute.py +0 -0
  48. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_sdk.py +0 -0
  49. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/rocprof_systems.py +0 -0
  50. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/skills/wafer-guide/SKILL.md +0 -0
  51. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/specs_cli.py +0 -0
  52. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/ssh_keys.py +0 -0
  53. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/target_lock.py +0 -0
  54. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets.py +0 -0
  55. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets_cli.py +0 -0
  56. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/targets_ops.py +0 -0
  57. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/__init__.py +0 -0
  58. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/aiter_optimize.py +0 -0
  59. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/ask_docs.py +0 -0
  60. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_kernel.py +0 -0
  61. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_kernelbench.py +0 -0
  62. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/optimize_vllm.py +0 -0
  63. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/templates/trace_analyze.py +0 -0
  64. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/tests/test_eval_cli_parity.py +0 -0
  65. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/trace_compare.py +0 -0
  66. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/tracelens.py +0 -0
  67. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer/wevin_cli.py +0 -0
  68. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/SOURCES.txt +0 -0
  69. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/dependency_links.txt +0 -0
  70. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/entry_points.txt +0 -0
  71. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/requires.txt +0 -0
  72. {wafer_cli-0.2.40 → wafer_cli-0.2.41}/wafer_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafer-cli
3
- Version: 0.2.40
3
+ Version: 0.2.41
4
4
  Summary: CLI for running GPU workloads, managing remote workspaces, and evaluating/optimizing kernels
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wafer-cli"
3
- version = "0.2.40"
3
+ version = "0.2.41"
4
4
  description = "CLI for running GPU workloads, managing remote workspaces, and evaluating/optimizing kernels"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafer-cli
3
- Version: 0.2.40
3
+ Version: 0.2.41
4
4
  Summary: CLI for running GPU workloads, managing remote workspaces, and evaluating/optimizing kernels
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
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