wafer-cli 0.2.20__tar.gz → 0.2.21__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 (63) hide show
  1. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/PKG-INFO +1 -1
  2. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/pyproject.toml +1 -1
  3. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/cli.py +43 -20
  4. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/optimize_kernel.py +2 -0
  5. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/wevin_cli.py +39 -16
  6. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/PKG-INFO +1 -1
  7. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/README.md +0 -0
  8. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/setup.cfg +0 -0
  9. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_analytics.py +0 -0
  10. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_auth.py +0 -0
  11. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_billing.py +0 -0
  12. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_cli_coverage.py +0 -0
  13. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_cli_parity_integration.py +0 -0
  14. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_config_integration.py +0 -0
  15. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_file_operations_integration.py +0 -0
  16. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_kernel_scope_cli.py +0 -0
  17. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_nsys_analyze.py +0 -0
  18. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_nsys_profile.py +0 -0
  19. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_output.py +0 -0
  20. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_rocprof_compute_integration.py +0 -0
  21. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_skill_commands.py +0 -0
  22. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_ssh_integration.py +0 -0
  23. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_targets_ops.py +0 -0
  24. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_wevin_cli.py +0 -0
  25. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_workflow_integration.py +0 -0
  26. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/GUIDE.md +0 -0
  27. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/__init__.py +0 -0
  28. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/analytics.py +0 -0
  29. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/api_client.py +0 -0
  30. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/auth.py +0 -0
  31. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/autotuner.py +0 -0
  32. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/billing.py +0 -0
  33. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/config.py +0 -0
  34. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/corpus.py +0 -0
  35. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/evaluate.py +0 -0
  36. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/global_config.py +0 -0
  37. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/gpu_run.py +0 -0
  38. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/inference.py +0 -0
  39. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/kernel_scope.py +0 -0
  40. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/ncu_analyze.py +0 -0
  41. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/nsys_analyze.py +0 -0
  42. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/nsys_profile.py +0 -0
  43. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/output.py +0 -0
  44. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/problems.py +0 -0
  45. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_compute.py +0 -0
  46. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_sdk.py +0 -0
  47. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_systems.py +0 -0
  48. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/skills/wafer-guide/SKILL.md +0 -0
  49. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/ssh_keys.py +0 -0
  50. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/target_lock.py +0 -0
  51. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/targets.py +0 -0
  52. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/targets_ops.py +0 -0
  53. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/__init__.py +0 -0
  54. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/ask_docs.py +0 -0
  55. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/optimize_kernelbench.py +0 -0
  56. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/trace_analyze.py +0 -0
  57. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/tracelens.py +0 -0
  58. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/workspaces.py +0 -0
  59. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/SOURCES.txt +0 -0
  60. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/dependency_links.txt +0 -0
  61. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/entry_points.txt +0 -0
  62. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/requires.txt +0 -0
  63. {wafer_cli-0.2.20 → wafer_cli-0.2.21}/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.20
3
+ Version: 0.2.21
4
4
  Summary: CLI tool for running commands on remote GPUs and GPU kernel optimization agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: typer>=0.12.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wafer-cli"
3
- version = "0.2.20"
3
+ version = "0.2.21"
4
4
  description = "CLI tool for running commands on remote GPUs and GPU kernel optimization agent"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
@@ -1,6 +1,8 @@
1
- # ruff: noqa: PLR0913
1
+ # ruff: noqa: PLR0913, E402
2
2
  # PLR0913 (too many arguments) is suppressed because Typer CLI commands
3
3
  # naturally have many parameters - each --flag becomes a function argument.
4
+ # E402 (module level import not at top) is suppressed because we intentionally
5
+ # load .env files before importing other modules that may read env vars.
4
6
  """Wafer CLI - GPU development toolkit for LLM coding agents.
5
7
 
6
8
  Core commands:
@@ -27,6 +29,12 @@ from pathlib import Path
27
29
 
28
30
  import trio
29
31
  import typer
32
+ from dotenv import load_dotenv
33
+
34
+ # Auto-load .env from current directory and ~/.wafer/.env
35
+ # This runs at import time so env vars are available before any config is accessed
36
+ load_dotenv() # cwd/.env
37
+ load_dotenv(Path.home() / ".wafer" / ".env") # ~/.wafer/.env
30
38
 
31
39
  from .config import WaferConfig, WaferEnvironment
32
40
  from .inference import infer_upload_files, resolve_environment
@@ -42,6 +50,7 @@ from .problems import (
42
50
  app = typer.Typer(
43
51
  help="GPU development toolkit for LLM coding agents",
44
52
  no_args_is_help=True,
53
+ pretty_exceptions_show_locals=False, # Don't dump local vars (makes tracebacks huge)
45
54
  )
46
55
 
47
56
  # =============================================================================
@@ -58,11 +67,11 @@ def _show_version() -> None:
58
67
  """Show CLI version and environment, then exit."""
59
68
  from .analytics import _get_cli_version
60
69
  from .global_config import load_global_config
61
-
70
+
62
71
  version = _get_cli_version()
63
72
  config = load_global_config()
64
73
  environment = config.environment
65
-
74
+
66
75
  typer.echo(f"wafer-cli {version} ({environment})")
67
76
  raise typer.Exit()
68
77
 
@@ -110,7 +119,7 @@ def main_callback(
110
119
  if version:
111
120
  _show_version()
112
121
  return
113
-
122
+
114
123
  global _command_start_time, _command_outcome
115
124
  _command_start_time = time.time()
116
125
  _command_outcome = "success" # Default to success, mark failure on exceptions
@@ -121,6 +130,7 @@ def main_callback(
121
130
  analytics.init_analytics()
122
131
 
123
132
  # Install exception hook to catch SystemExit and mark failures
133
+ # Also prints error message FIRST so it's visible even when traceback is truncated
124
134
  original_excepthook = sys.excepthook
125
135
 
126
136
  def custom_excepthook(
@@ -136,7 +146,9 @@ def main_callback(
136
146
  _command_outcome = "failure"
137
147
  else:
138
148
  _command_outcome = "failure"
139
- # Call original excepthook
149
+ # Print error summary FIRST (before traceback) so it's visible even if truncated
150
+ print(f"\n\033[1;31m>>> ERROR: {exc_type.__name__}: {exc_value}\033[0m\n", file=sys.stderr)
151
+ # Call original excepthook (prints the full traceback)
140
152
  original_excepthook(exc_type, exc_value, exc_traceback)
141
153
 
142
154
  sys.excepthook = custom_excepthook
@@ -591,7 +603,7 @@ app.add_typer(provider_auth_app, name="auth")
591
603
  def provider_auth_login(
592
604
  provider: str = typer.Argument(
593
605
  ...,
594
- help="Provider name: runpod, digitalocean, or modal",
606
+ help="Provider name: runpod, digitalocean, modal, anthropic, or openai",
595
607
  ),
596
608
  api_key: str | None = typer.Option(
597
609
  None,
@@ -600,15 +612,16 @@ def provider_auth_login(
600
612
  help="API key (if not provided, reads from stdin)",
601
613
  ),
602
614
  ) -> None:
603
- """Save API key for a cloud GPU provider.
615
+ """Save API key for a provider.
604
616
 
605
617
  Stores the key in ~/.wafer/auth.json. Environment variables
606
- (e.g., WAFER_RUNPOD_API_KEY) take precedence over stored keys.
618
+ (e.g., ANTHROPIC_API_KEY) take precedence over stored keys.
607
619
 
608
620
  Examples:
621
+ wafer auth login anthropic --api-key sk-ant-xxx
609
622
  wafer auth login runpod --api-key rp_xxx
610
- wafer auth login digitalocean --api-key dop_v1_xxx
611
- echo $API_KEY | wafer auth login runpod
623
+ wafer auth login openai --api-key sk-xxx
624
+ echo $API_KEY | wafer auth login anthropic
612
625
  """
613
626
  import sys
614
627
 
@@ -642,7 +655,7 @@ def provider_auth_login(
642
655
  def provider_auth_logout(
643
656
  provider: str = typer.Argument(
644
657
  ...,
645
- help="Provider name: runpod, digitalocean, or modal",
658
+ help="Provider name: runpod, digitalocean, modal, anthropic, or openai",
646
659
  ),
647
660
  ) -> None:
648
661
  """Remove stored API key for a cloud GPU provider.
@@ -4406,9 +4419,13 @@ def workspaces_list(
4406
4419
  @workspaces_app.command("create")
4407
4420
  def workspaces_create(
4408
4421
  name: str = typer.Argument(..., help="Workspace name"),
4409
- gpu_type: str = typer.Option("B200", "--gpu", "-g", help="GPU type: MI300X (AMD) or B200 (NVIDIA, default)"),
4422
+ gpu_type: str = typer.Option(
4423
+ "B200", "--gpu", "-g", help="GPU type: MI300X (AMD) or B200 (NVIDIA, default)"
4424
+ ),
4410
4425
  image: str | None = typer.Option(None, "--image", "-i", help="Docker image (optional)"),
4411
- wait: bool = typer.Option(False, "--wait", "-w", help="Wait for provisioning and show SSH credentials"),
4426
+ wait: bool = typer.Option(
4427
+ False, "--wait", "-w", help="Wait for provisioning and show SSH credentials"
4428
+ ),
4412
4429
  json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
4413
4430
  ) -> None:
4414
4431
  """Create a new workspace.
@@ -4717,19 +4734,25 @@ def workspaces_ssh(
4717
4734
  ssh_host = ws.get("ssh_host")
4718
4735
  ssh_port = ws.get("ssh_port")
4719
4736
  ssh_user = ws.get("ssh_user")
4720
-
4737
+
4721
4738
  if not ssh_host or not ssh_port or not ssh_user:
4722
4739
  typer.echo("Error: Workspace not ready. Wait a few seconds and retry.", err=True)
4723
4740
  raise typer.Exit(1)
4724
4741
 
4725
4742
  # Connect via SSH
4726
- os.execvp("ssh", [
4743
+ os.execvp(
4727
4744
  "ssh",
4728
- "-p", str(ssh_port),
4729
- "-o", "StrictHostKeyChecking=no",
4730
- "-o", "UserKnownHostsFile=/dev/null",
4731
- f"{ssh_user}@{ssh_host}",
4732
- ])
4745
+ [
4746
+ "ssh",
4747
+ "-p",
4748
+ str(ssh_port),
4749
+ "-o",
4750
+ "StrictHostKeyChecking=no",
4751
+ "-o",
4752
+ "UserKnownHostsFile=/dev/null",
4753
+ f"{ssh_user}@{ssh_host}",
4754
+ ],
4755
+ )
4733
4756
 
4734
4757
 
4735
4758
  @workspaces_app.command("sync")
@@ -68,4 +68,6 @@ IMPORTANT: Always verify correctness with wafer evaluate before claiming success
68
68
  "kernel": "./kernel.cu",
69
69
  "target": "H100",
70
70
  },
71
+ # Enable skill discovery (agent can load wafer-guide, etc.)
72
+ include_skills=True,
71
73
  )
@@ -274,7 +274,12 @@ def _build_environment(
274
274
  from wafer_core.sandbox import SandboxMode
275
275
 
276
276
  working_dir = Path(corpus_path) if corpus_path else Path.cwd()
277
- resolved_tools = tools_override or tpl.tools
277
+ resolved_tools = list(tools_override or tpl.tools)
278
+
279
+ # Add skill tool if skills are enabled
280
+ if tpl.include_skills and "skill" not in resolved_tools:
281
+ resolved_tools.append("skill")
282
+
278
283
  sandbox_mode = SandboxMode.DISABLED if no_sandbox else SandboxMode.ENABLED
279
284
  env: Environment = CodingEnvironment(
280
285
  working_dir=working_dir,
@@ -378,6 +383,7 @@ def main( # noqa: PLR0913, PLR0915
378
383
 
379
384
  # Handle --get-session: load session by ID and print
380
385
  if get_session:
386
+
381
387
  async def _get_session() -> None:
382
388
  try:
383
389
  session, err = await session_store.get(get_session)
@@ -398,16 +404,18 @@ def main( # noqa: PLR0913, PLR0915
398
404
  error_msg = f"Failed to serialize messages: {e}"
399
405
  print(json.dumps({"error": error_msg}))
400
406
  sys.exit(1)
401
-
402
- print(json.dumps({
403
- "session_id": session.session_id,
404
- "status": session.status.value,
405
- "model": session.endpoint.model if session.endpoint else None,
406
- "created_at": session.created_at,
407
- "updated_at": session.updated_at,
408
- "messages": messages_data,
409
- "tags": session.tags,
410
- }))
407
+
408
+ print(
409
+ json.dumps({
410
+ "session_id": session.session_id,
411
+ "status": session.status.value,
412
+ "model": session.endpoint.model if session.endpoint else None,
413
+ "created_at": session.created_at,
414
+ "updated_at": session.updated_at,
415
+ "messages": messages_data,
416
+ "tags": session.tags,
417
+ })
418
+ )
411
419
  else:
412
420
  print(f"Session: {session.session_id}")
413
421
  print(f"Status: {session.status.value}")
@@ -495,7 +503,7 @@ def main( # noqa: PLR0913, PLR0915
495
503
  print(f"Error loading template: {err}", file=sys.stderr)
496
504
  sys.exit(1)
497
505
  tpl = loaded_template
498
- system_prompt = tpl.interpolate_prompt(template_args or {})
506
+ base_system_prompt = tpl.interpolate_prompt(template_args or {})
499
507
  # Show template info when starting without a prompt
500
508
  if not prompt and tpl.description:
501
509
  print(f"Template: {tpl.name}", file=sys.stderr)
@@ -503,7 +511,20 @@ def main( # noqa: PLR0913, PLR0915
503
511
  print(file=sys.stderr)
504
512
  else:
505
513
  tpl = _get_default_template()
506
- system_prompt = tpl.system_prompt
514
+ base_system_prompt = tpl.system_prompt
515
+
516
+ # Append skill metadata if skills are enabled
517
+ if tpl.include_skills:
518
+ from wafer_core.rollouts.skills import discover_skills, format_skill_metadata_for_prompt
519
+
520
+ skill_metadata = discover_skills()
521
+ if skill_metadata:
522
+ skill_section = format_skill_metadata_for_prompt(skill_metadata)
523
+ system_prompt = base_system_prompt + "\n\n" + skill_section
524
+ else:
525
+ system_prompt = base_system_prompt
526
+ else:
527
+ system_prompt = base_system_prompt
507
528
 
508
529
  # CLI args override template values
509
530
  resolved_single_turn = single_turn if single_turn is not None else tpl.single_turn
@@ -550,7 +571,7 @@ def main( # noqa: PLR0913, PLR0915
550
571
  else:
551
572
  if json_output:
552
573
  # Emit session_start if we have a session_id (from --resume)
553
- model_name = endpoint.model if hasattr(endpoint, 'model') else None
574
+ model_name = endpoint.model if hasattr(endpoint, "model") else None
554
575
  frontend = StreamingChunkFrontend(session_id=session_id, model=model_name)
555
576
  else:
556
577
  frontend = NoneFrontend(show_tool_calls=True, show_thinking=False)
@@ -565,9 +586,11 @@ def main( # noqa: PLR0913, PLR0915
565
586
  # Emit session_start for new sessions (if session_id was None and we got one)
566
587
  # Check first state to emit as early as possible
567
588
  if json_output and isinstance(frontend, StreamingChunkFrontend):
568
- first_session_id = states[0].session_id if states and states[0].session_id else None
589
+ first_session_id = (
590
+ states[0].session_id if states and states[0].session_id else None
591
+ )
569
592
  if first_session_id and not session_id: # New session created
570
- model_name = endpoint.model if hasattr(endpoint, 'model') else None
593
+ model_name = endpoint.model if hasattr(endpoint, "model") else None
571
594
  frontend.emit_session_start(first_session_id, model_name)
572
595
  # Print resume command with full wafer agent prefix
573
596
  if states and states[-1].session_id:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafer-cli
3
- Version: 0.2.20
3
+ Version: 0.2.21
4
4
  Summary: CLI tool for running commands on remote GPUs and GPU kernel optimization agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: typer>=0.12.0
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