wafer-cli 0.2.19__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.19 → wafer_cli-0.2.21}/PKG-INFO +1 -1
  2. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/pyproject.toml +1 -1
  3. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_cli_coverage.py +14 -0
  4. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_wevin_cli.py +86 -7
  5. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/cli.py +58 -20
  6. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/templates/optimize_kernel.py +2 -0
  7. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/wevin_cli.py +45 -17
  8. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer_cli.egg-info/PKG-INFO +1 -1
  9. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/README.md +0 -0
  10. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/setup.cfg +0 -0
  11. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_analytics.py +0 -0
  12. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_auth.py +0 -0
  13. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_billing.py +0 -0
  14. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_cli_parity_integration.py +0 -0
  15. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_config_integration.py +0 -0
  16. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_file_operations_integration.py +0 -0
  17. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_kernel_scope_cli.py +0 -0
  18. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_nsys_analyze.py +0 -0
  19. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_nsys_profile.py +0 -0
  20. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_output.py +0 -0
  21. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_rocprof_compute_integration.py +0 -0
  22. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_skill_commands.py +0 -0
  23. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_ssh_integration.py +0 -0
  24. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_targets_ops.py +0 -0
  25. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/tests/test_workflow_integration.py +0 -0
  26. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/GUIDE.md +0 -0
  27. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/__init__.py +0 -0
  28. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/analytics.py +0 -0
  29. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/api_client.py +0 -0
  30. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/auth.py +0 -0
  31. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/autotuner.py +0 -0
  32. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/billing.py +0 -0
  33. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/config.py +0 -0
  34. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/corpus.py +0 -0
  35. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/evaluate.py +0 -0
  36. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/global_config.py +0 -0
  37. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/gpu_run.py +0 -0
  38. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/inference.py +0 -0
  39. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/kernel_scope.py +0 -0
  40. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/ncu_analyze.py +0 -0
  41. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/nsys_analyze.py +0 -0
  42. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/nsys_profile.py +0 -0
  43. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/output.py +0 -0
  44. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/problems.py +0 -0
  45. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/rocprof_compute.py +0 -0
  46. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/rocprof_sdk.py +0 -0
  47. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/rocprof_systems.py +0 -0
  48. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/skills/wafer-guide/SKILL.md +0 -0
  49. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/ssh_keys.py +0 -0
  50. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/target_lock.py +0 -0
  51. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/targets.py +0 -0
  52. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/targets_ops.py +0 -0
  53. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/templates/__init__.py +0 -0
  54. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/templates/ask_docs.py +0 -0
  55. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/templates/optimize_kernelbench.py +0 -0
  56. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/templates/trace_analyze.py +0 -0
  57. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/tracelens.py +0 -0
  58. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer/workspaces.py +0 -0
  59. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer_cli.egg-info/SOURCES.txt +0 -0
  60. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer_cli.egg-info/dependency_links.txt +0 -0
  61. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer_cli.egg-info/entry_points.txt +0 -0
  62. {wafer_cli-0.2.19 → wafer_cli-0.2.21}/wafer_cli.egg-info/requires.txt +0 -0
  63. {wafer_cli-0.2.19 → 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.19
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.19"
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 = [
@@ -719,3 +719,17 @@ class TestWorkspacesExecFlagPassthrough:
719
719
  "workspaces", "exec", "test-ws", "--", "cmd", "--output=/tmp/out"
720
720
  ])
721
721
  assert "no such option" not in result.output.lower()
722
+
723
+
724
+ class TestAgentNoSandboxOption:
725
+ """Test --no-sandbox option in wafer agent command."""
726
+
727
+ def test_agent_no_sandbox_option_exists(self) -> None:
728
+ """Test that --no-sandbox option is accepted by wafer agent command."""
729
+ result = runner.invoke(app, ["agent", "--help"])
730
+ assert result.exit_code == 0
731
+ # Strip ANSI escape codes before checking (help output may contain color codes)
732
+ ansi_escape = re.compile(r'\x1b\[[0-9;]*m')
733
+ clean_output = ansi_escape.sub('', result.stdout)
734
+ assert "--no-sandbox" in clean_output
735
+ assert "liability" in clean_output.lower() # Warning text should be in help
@@ -634,35 +634,114 @@ def test_streaming_frontend_session_start_state_without_session_id():
634
634
 
635
635
  def test_streaming_frontend_session_start_resumed_then_new():
636
636
  """Test session_start emission when resuming but states have different session_id.
637
-
637
+
638
638
  Edge case: --resume used but states return different session_id (should use states one).
639
639
  """
640
640
  import trio
641
641
 
642
642
  from wafer.wevin_cli import StreamingChunkFrontend
643
-
643
+
644
644
  async def run_test() -> None:
645
645
  # Start with resumed session_id
646
646
  frontend = StreamingChunkFrontend(
647
647
  session_id="resumed-session-123",
648
648
  model="claude-sonnet-4.5"
649
649
  )
650
-
650
+
651
651
  emitted_events = []
652
652
 
653
653
  def mock_emit(obj) -> None:
654
654
  emitted_events.append(obj)
655
-
655
+
656
656
  frontend._emit = mock_emit
657
-
657
+
658
658
  # start() emits session_start for resumed session
659
659
  await frontend.start()
660
660
  assert len(emitted_events) == 1
661
661
  assert emitted_events[0]["session_id"] == "resumed-session-123"
662
-
662
+
663
663
  # If states have different session_id (shouldn't happen, but handle gracefully)
664
664
  # The logic in main() checks `if first_session_id and not session_id`
665
665
  # So if session_id was set, it won't emit again
666
666
  # This is correct behavior - use the one from --resume
667
-
667
+
668
668
  trio.run(run_test)
669
+
670
+
671
+ # =============================================================================
672
+ # --no-sandbox flag tests
673
+ # =============================================================================
674
+
675
+
676
+ def test_no_sandbox_parameter_accepted():
677
+ """Test that no_sandbox parameter exists in wevin_main signature."""
678
+ import inspect
679
+
680
+ from wafer.wevin_cli import main as wevin_main
681
+
682
+ sig = inspect.signature(wevin_main)
683
+ params = sig.parameters
684
+
685
+ # Verify parameter exists
686
+ assert 'no_sandbox' in params
687
+
688
+ # Verify type and default
689
+ assert str(params['no_sandbox'].annotation) in ('bool', "<class 'bool'>")
690
+ assert params['no_sandbox'].default is False
691
+
692
+
693
+ def test_build_environment_accepts_no_sandbox():
694
+ """Test that _build_environment accepts no_sandbox parameter."""
695
+ import inspect
696
+
697
+ from wafer.wevin_cli import _build_environment
698
+
699
+ sig = inspect.signature(_build_environment)
700
+ params = sig.parameters
701
+
702
+ assert 'no_sandbox' in params
703
+ assert params['no_sandbox'].default is False
704
+
705
+
706
+ def test_build_environment_with_no_sandbox_false():
707
+ """Test _build_environment creates env with sandbox ENABLED when no_sandbox=False."""
708
+ from wafer_core.rollouts.templates import TemplateConfig
709
+ from wafer_core.sandbox import SandboxMode
710
+
711
+ from wafer.wevin_cli import _build_environment
712
+
713
+ tpl = TemplateConfig(
714
+ name="test",
715
+ description="Test template",
716
+ system_prompt="Test",
717
+ tools=["read"],
718
+ )
719
+
720
+ # This will raise RuntimeError if sandbox is unavailable on this system
721
+ # That's expected - we're testing that sandbox is ENABLED by default
722
+ try:
723
+ env = _build_environment(tpl, None, None, no_sandbox=False)
724
+ # If we get here, sandbox is available - verify it's enabled
725
+ assert env.sandbox_mode == SandboxMode.ENABLED
726
+ except RuntimeError as e:
727
+ # Sandbox unavailable - that's OK, the error proves ENABLED is set
728
+ assert "sandboxing is not available" in str(e)
729
+
730
+
731
+ def test_build_environment_with_no_sandbox_true():
732
+ """Test _build_environment creates env with sandbox DISABLED when no_sandbox=True."""
733
+ from wafer_core.rollouts.templates import TemplateConfig
734
+ from wafer_core.sandbox import SandboxMode
735
+
736
+ from wafer.wevin_cli import _build_environment
737
+
738
+ tpl = TemplateConfig(
739
+ name="test",
740
+ description="Test template",
741
+ system_prompt="Test",
742
+ tools=["read"],
743
+ )
744
+
745
+ # This should NOT raise - sandbox is disabled
746
+ env = _build_environment(tpl, None, None, no_sandbox=True)
747
+ assert env.sandbox_mode == SandboxMode.DISABLED
@@ -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.
@@ -1327,6 +1340,11 @@ def agent( # noqa: PLR0913
1327
1340
  "-c",
1328
1341
  help="Documentation corpus to use (cuda, cutlass, hip, amd). Must be downloaded first.",
1329
1342
  ),
1343
+ no_sandbox: bool = typer.Option(
1344
+ False,
1345
+ "--no-sandbox",
1346
+ help="Disable OS-level sandboxing (YOU accept liability for any damage caused by the agent)",
1347
+ ),
1330
1348
  ) -> None:
1331
1349
  """AI assistant for GPU kernel development.
1332
1350
 
@@ -1408,6 +1426,13 @@ def agent( # noqa: PLR0913
1408
1426
  raise typer.Exit(1) from None
1409
1427
  corpus_path = str(path)
1410
1428
 
1429
+ # Warn user about sandbox disabled
1430
+ if no_sandbox:
1431
+ print(
1432
+ "Warning: Sandbox disabled. You accept liability for any damage caused by the agent.",
1433
+ file=sys.stderr,
1434
+ )
1435
+
1411
1436
  wevin_main(
1412
1437
  prompt=actual_prompt,
1413
1438
  interactive=use_tui,
@@ -1425,6 +1450,7 @@ def agent( # noqa: PLR0913
1425
1450
  template=template,
1426
1451
  template_args=parsed_template_args,
1427
1452
  corpus_path=corpus_path,
1453
+ no_sandbox=no_sandbox,
1428
1454
  )
1429
1455
 
1430
1456
 
@@ -1455,6 +1481,7 @@ def _make_agent_alias(name: str, doc: str) -> None:
1455
1481
  template: str | None = typer.Option(None, "--template", "-t"),
1456
1482
  template_args: list[str] | None = typer.Option(None, "--args"),
1457
1483
  corpus: str | None = typer.Option(None, "--corpus"),
1484
+ no_sandbox: bool = typer.Option(False, "--no-sandbox"),
1458
1485
  ) -> None:
1459
1486
  agent(
1460
1487
  prompt=prompt,
@@ -1474,6 +1501,7 @@ def _make_agent_alias(name: str, doc: str) -> None:
1474
1501
  template=template,
1475
1502
  template_args=template_args,
1476
1503
  corpus=corpus,
1504
+ no_sandbox=no_sandbox,
1477
1505
  )
1478
1506
 
1479
1507
  alias_cmd.__doc__ = doc
@@ -4391,9 +4419,13 @@ def workspaces_list(
4391
4419
  @workspaces_app.command("create")
4392
4420
  def workspaces_create(
4393
4421
  name: str = typer.Argument(..., help="Workspace name"),
4394
- 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
+ ),
4395
4425
  image: str | None = typer.Option(None, "--image", "-i", help="Docker image (optional)"),
4396
- 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
+ ),
4397
4429
  json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
4398
4430
  ) -> None:
4399
4431
  """Create a new workspace.
@@ -4702,19 +4734,25 @@ def workspaces_ssh(
4702
4734
  ssh_host = ws.get("ssh_host")
4703
4735
  ssh_port = ws.get("ssh_port")
4704
4736
  ssh_user = ws.get("ssh_user")
4705
-
4737
+
4706
4738
  if not ssh_host or not ssh_port or not ssh_user:
4707
4739
  typer.echo("Error: Workspace not ready. Wait a few seconds and retry.", err=True)
4708
4740
  raise typer.Exit(1)
4709
4741
 
4710
4742
  # Connect via SSH
4711
- os.execvp("ssh", [
4743
+ os.execvp(
4712
4744
  "ssh",
4713
- "-p", str(ssh_port),
4714
- "-o", "StrictHostKeyChecking=no",
4715
- "-o", "UserKnownHostsFile=/dev/null",
4716
- f"{ssh_user}@{ssh_host}",
4717
- ])
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
+ )
4718
4756
 
4719
4757
 
4720
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
  )
@@ -266,18 +266,27 @@ def _build_environment(
266
266
  tpl: TemplateConfig,
267
267
  tools_override: list[str] | None,
268
268
  corpus_path: str | None,
269
+ no_sandbox: bool = False,
269
270
  ) -> Environment:
270
271
  """Build a CodingEnvironment from template config."""
271
272
  from wafer_core.environments.coding import CodingEnvironment
272
273
  from wafer_core.rollouts.templates import DANGEROUS_BASH_COMMANDS
274
+ from wafer_core.sandbox import SandboxMode
273
275
 
274
276
  working_dir = Path(corpus_path) if corpus_path else Path.cwd()
275
- 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
+
283
+ sandbox_mode = SandboxMode.DISABLED if no_sandbox else SandboxMode.ENABLED
276
284
  env: Environment = CodingEnvironment(
277
285
  working_dir=working_dir,
278
286
  enabled_tools=resolved_tools,
279
287
  bash_allowlist=tpl.bash_allowlist,
280
288
  bash_denylist=DANGEROUS_BASH_COMMANDS,
289
+ sandbox_mode=sandbox_mode,
281
290
  ) # type: ignore[assignment]
282
291
  return env
283
292
 
@@ -362,6 +371,7 @@ def main( # noqa: PLR0913, PLR0915
362
371
  list_sessions: bool = False,
363
372
  get_session: str | None = None,
364
373
  json_output: bool = False,
374
+ no_sandbox: bool = False,
365
375
  ) -> None:
366
376
  """Run wevin agent in-process via rollouts."""
367
377
  from dataclasses import asdict
@@ -373,6 +383,7 @@ def main( # noqa: PLR0913, PLR0915
373
383
 
374
384
  # Handle --get-session: load session by ID and print
375
385
  if get_session:
386
+
376
387
  async def _get_session() -> None:
377
388
  try:
378
389
  session, err = await session_store.get(get_session)
@@ -393,16 +404,18 @@ def main( # noqa: PLR0913, PLR0915
393
404
  error_msg = f"Failed to serialize messages: {e}"
394
405
  print(json.dumps({"error": error_msg}))
395
406
  sys.exit(1)
396
-
397
- print(json.dumps({
398
- "session_id": session.session_id,
399
- "status": session.status.value,
400
- "model": session.endpoint.model if session.endpoint else None,
401
- "created_at": session.created_at,
402
- "updated_at": session.updated_at,
403
- "messages": messages_data,
404
- "tags": session.tags,
405
- }))
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
+ )
406
419
  else:
407
420
  print(f"Session: {session.session_id}")
408
421
  print(f"Status: {session.status.value}")
@@ -490,7 +503,7 @@ def main( # noqa: PLR0913, PLR0915
490
503
  print(f"Error loading template: {err}", file=sys.stderr)
491
504
  sys.exit(1)
492
505
  tpl = loaded_template
493
- system_prompt = tpl.interpolate_prompt(template_args or {})
506
+ base_system_prompt = tpl.interpolate_prompt(template_args or {})
494
507
  # Show template info when starting without a prompt
495
508
  if not prompt and tpl.description:
496
509
  print(f"Template: {tpl.name}", file=sys.stderr)
@@ -498,14 +511,27 @@ def main( # noqa: PLR0913, PLR0915
498
511
  print(file=sys.stderr)
499
512
  else:
500
513
  tpl = _get_default_template()
501
- 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
502
528
 
503
529
  # CLI args override template values
504
530
  resolved_single_turn = single_turn if single_turn is not None else tpl.single_turn
505
531
 
506
532
  # Build endpoint and environment
507
533
  endpoint = _build_endpoint(tpl, model, api_base, api_key)
508
- environment = _build_environment(tpl, tools, corpus_path)
534
+ environment = _build_environment(tpl, tools, corpus_path, no_sandbox)
509
535
 
510
536
  # Session store
511
537
  session_store = FileSessionStore()
@@ -545,7 +571,7 @@ def main( # noqa: PLR0913, PLR0915
545
571
  else:
546
572
  if json_output:
547
573
  # Emit session_start if we have a session_id (from --resume)
548
- model_name = endpoint.model if hasattr(endpoint, 'model') else None
574
+ model_name = endpoint.model if hasattr(endpoint, "model") else None
549
575
  frontend = StreamingChunkFrontend(session_id=session_id, model=model_name)
550
576
  else:
551
577
  frontend = NoneFrontend(show_tool_calls=True, show_thinking=False)
@@ -560,9 +586,11 @@ def main( # noqa: PLR0913, PLR0915
560
586
  # Emit session_start for new sessions (if session_id was None and we got one)
561
587
  # Check first state to emit as early as possible
562
588
  if json_output and isinstance(frontend, StreamingChunkFrontend):
563
- 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
+ )
564
592
  if first_session_id and not session_id: # New session created
565
- model_name = endpoint.model if hasattr(endpoint, 'model') else None
593
+ model_name = endpoint.model if hasattr(endpoint, "model") else None
566
594
  frontend.emit_session_start(first_session_id, model_name)
567
595
  # Print resume command with full wafer agent prefix
568
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.19
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