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.
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/PKG-INFO +1 -1
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/pyproject.toml +1 -1
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/cli.py +43 -20
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/optimize_kernel.py +2 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/wevin_cli.py +39 -16
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/PKG-INFO +1 -1
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/README.md +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/setup.cfg +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_analytics.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_auth.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_billing.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_cli_coverage.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_cli_parity_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_config_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_file_operations_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_kernel_scope_cli.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_nsys_analyze.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_nsys_profile.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_output.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_rocprof_compute_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_skill_commands.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_ssh_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_targets_ops.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_wevin_cli.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/tests/test_workflow_integration.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/GUIDE.md +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/__init__.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/analytics.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/api_client.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/auth.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/autotuner.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/billing.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/config.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/corpus.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/evaluate.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/global_config.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/gpu_run.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/inference.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/kernel_scope.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/ncu_analyze.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/nsys_analyze.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/nsys_profile.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/output.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/problems.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_compute.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_sdk.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/rocprof_systems.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/skills/wafer-guide/SKILL.md +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/ssh_keys.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/target_lock.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/targets.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/targets_ops.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/__init__.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/ask_docs.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/optimize_kernelbench.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/templates/trace_analyze.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/tracelens.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer/workspaces.py +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/SOURCES.txt +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/dependency_links.txt +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/entry_points.txt +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/requires.txt +0 -0
- {wafer_cli-0.2.20 → wafer_cli-0.2.21}/wafer_cli.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
|
615
|
+
"""Save API key for a provider.
|
|
604
616
|
|
|
605
617
|
Stores the key in ~/.wafer/auth.json. Environment variables
|
|
606
|
-
(e.g.,
|
|
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
|
|
611
|
-
echo $API_KEY | wafer auth login
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
4743
|
+
os.execvp(
|
|
4727
4744
|
"ssh",
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
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")
|
|
@@ -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(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|