wafer-cli 0.2.19__py3-none-any.whl → 0.2.21__py3-none-any.whl

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.py CHANGED
@@ -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
  )
wafer/wevin_cli.py CHANGED
@@ -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
@@ -5,7 +5,7 @@ wafer/api_client.py,sha256=i_Az2b2llC3DSW8yOL-BKqa7LSKuxOr8hSN40s-oQXY,6313
5
5
  wafer/auth.py,sha256=dwss_se5P-FFc9IN38q4kh_dBrA6k-CguDBkivgcdj0,14003
6
6
  wafer/autotuner.py,sha256=41WYP41pTDvMijv2h42vm89bcHtDMJXObDlWmn6xpFU,44416
7
7
  wafer/billing.py,sha256=jbLB2lI4_9f2KD8uEFDi_ixLlowe5hasC0TIZJyIXRg,7163
8
- wafer/cli.py,sha256=lBBTQCcmKREqZDOQh27qSq8i6NedjHW5oh1JiuT9aho,254241
8
+ wafer/cli.py,sha256=h9gvDI1FqE3adouNCzOlwbFOJ5oVtFAo-QC-h1LbylQ,255758
9
9
  wafer/config.py,sha256=h5Eo9_yfWqWGoPNdVQikI9GoZVUeysunSYiixf1mKcw,3411
10
10
  wafer/corpus.py,sha256=x5aFhCsTSAtgzFG9AMFpqq92Ej63mXofL-vvvpjj1sM,12913
11
11
  wafer/evaluate.py,sha256=s1NszUBtxdWRonbi8YR3XWfCiCjNm14g2Pp1lu4kmtY,176125
@@ -26,16 +26,16 @@ wafer/target_lock.py,sha256=SDKhNzv2N7gsphGflcNni9FE5YYuAMuEthngAJEo4Gs,7809
26
26
  wafer/targets.py,sha256=9r-iRWoKSH5cQl1LcamaX-T7cNVOg99ngIm_hlRk-qU,26922
27
27
  wafer/targets_ops.py,sha256=jN1oIBx0mutxRNE9xpIc7SaBxPkVmOyus2eqn0kEKNI,21475
28
28
  wafer/tracelens.py,sha256=g9ZIeFyNojZn4uTd3skPqIrRiL7aMJOz_-GOd3aiyy4,7998
29
- wafer/wevin_cli.py,sha256=vF3GNH-qWXO4hAlXaDg98VZpS4uFexVUp94BHsJjjMU,22179
29
+ wafer/wevin_cli.py,sha256=Nuk7zTCiJrnpmYtdg5Hu0NbzONCqs54xtON6K7AVB9U,23189
30
30
  wafer/workspaces.py,sha256=iUdioK7kA3z_gOTMNVDn9Q87c6qpkdXF4bOhJWkUPg8,32375
31
31
  wafer/skills/wafer-guide/SKILL.md,sha256=KWetJw2TVTbz11_nzqazqOJWWRlbHRFShs4sOoreiWo,3255
32
32
  wafer/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  wafer/templates/ask_docs.py,sha256=Lxs-faz9v5m4Qa4NjF2X_lE8KwM9ES9MNJkxo7ep56o,2256
34
- wafer/templates/optimize_kernel.py,sha256=u6AL7Q3uttqlnBLzcoFdsiPq5lV2TV3bgqwCYYlK9gk,2357
34
+ wafer/templates/optimize_kernel.py,sha256=OvZgN5tm_OymO3lK8Dr0VO48e-5PfNVIIoACrPxpmqk,2446
35
35
  wafer/templates/optimize_kernelbench.py,sha256=aoOA13zWEl89r6QW03xF9NKxQ7j4mWe9rwua6-mlr4Y,4780
36
36
  wafer/templates/trace_analyze.py,sha256=XE1VqzVkIUsZbXF8EzQdDYgg-AZEYAOFpr6B_vnRELc,2880
37
- wafer_cli-0.2.19.dist-info/METADATA,sha256=vtgRtd_9BCx_t0uZV2JQiKd8CvT3JxA2EkAY6CLoQDc,560
38
- wafer_cli-0.2.19.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
- wafer_cli-0.2.19.dist-info/entry_points.txt,sha256=WqB7hB__WhtPY8y1cO2sZiUz7fCq6Ik-usAigpeFvWE,41
40
- wafer_cli-0.2.19.dist-info/top_level.txt,sha256=2MK1IVMWfpLL8BZCQ3E9aG6L6L666gSA_teYlwan4fs,6
41
- wafer_cli-0.2.19.dist-info/RECORD,,
37
+ wafer_cli-0.2.21.dist-info/METADATA,sha256=N8WTOGrE5NIDeG96OF98oN45UVs7KwoPZYxl5X4OGcU,560
38
+ wafer_cli-0.2.21.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ wafer_cli-0.2.21.dist-info/entry_points.txt,sha256=WqB7hB__WhtPY8y1cO2sZiUz7fCq6Ik-usAigpeFvWE,41
40
+ wafer_cli-0.2.21.dist-info/top_level.txt,sha256=2MK1IVMWfpLL8BZCQ3E9aG6L6L666gSA_teYlwan4fs,6
41
+ wafer_cli-0.2.21.dist-info/RECORD,,