wafer-cli 0.2.3__py3-none-any.whl → 0.2.5__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/targets.py CHANGED
@@ -257,6 +257,164 @@ def get_default_target() -> str | None:
257
257
  return data.get("default_target")
258
258
 
259
259
 
260
+ # ── Pool Management ─────────────────────────────────────────────────────────
261
+
262
+
263
+ def get_pool(name: str) -> list[str]:
264
+ """Get list of targets in a named pool.
265
+
266
+ Pools are defined in ~/.wafer/config.toml:
267
+ [pools.my-pool]
268
+ targets = ["target-1", "target-2", "target-3"]
269
+
270
+ Args:
271
+ name: Pool name
272
+
273
+ Returns:
274
+ List of target names in the pool
275
+
276
+ Raises:
277
+ FileNotFoundError: If pool doesn't exist
278
+ """
279
+ if not CONFIG_FILE.exists():
280
+ raise FileNotFoundError(f"Pool not found: {name} (no config file)")
281
+
282
+ with open(CONFIG_FILE, "rb") as f:
283
+ data = tomllib.load(f)
284
+
285
+ pools = data.get("pools", {})
286
+ if name not in pools:
287
+ raise FileNotFoundError(
288
+ f"Pool not found: {name}\n"
289
+ f" Define pools in ~/.wafer/config.toml:\n"
290
+ f" [pools.{name}]\n"
291
+ f' targets = ["target-1", "target-2"]'
292
+ )
293
+
294
+ pool_config = pools[name]
295
+ targets = pool_config.get("targets", [])
296
+
297
+ if not targets:
298
+ raise ValueError(f"Pool '{name}' has no targets defined")
299
+
300
+ return targets
301
+
302
+
303
+ def list_pools() -> list[str]:
304
+ """List all configured pool names.
305
+
306
+ Returns:
307
+ Sorted list of pool names
308
+ """
309
+ if not CONFIG_FILE.exists():
310
+ return []
311
+
312
+ with open(CONFIG_FILE, "rb") as f:
313
+ data = tomllib.load(f)
314
+
315
+ return sorted(data.get("pools", {}).keys())
316
+
317
+
318
+ def save_pool(name: str, targets: list[str]) -> None:
319
+ """Save or update a pool configuration.
320
+
321
+ Args:
322
+ name: Pool name
323
+ targets: List of target names (must all exist)
324
+
325
+ Raises:
326
+ FileNotFoundError: If any target doesn't exist
327
+ """
328
+ # Verify all targets exist
329
+ existing_targets = list_targets()
330
+ missing = [t for t in targets if t not in existing_targets]
331
+ if missing:
332
+ raise FileNotFoundError(f"Targets not found: {', '.join(missing)}")
333
+
334
+ _ensure_dirs()
335
+
336
+ # Load existing config
337
+ if CONFIG_FILE.exists():
338
+ with open(CONFIG_FILE, "rb") as f:
339
+ data = tomllib.load(f)
340
+ else:
341
+ data = {}
342
+
343
+ # Update pools section
344
+ if "pools" not in data:
345
+ data["pools"] = {}
346
+
347
+ data["pools"][name] = {"targets": targets}
348
+
349
+ # Write back - need custom handling for nested structure
350
+ _write_config_with_pools(data)
351
+
352
+
353
+ def _write_config_with_pools(data: dict) -> None:
354
+ """Write config file with pools support.
355
+
356
+ Handles the nested [pools.name] TOML structure and preserves
357
+ existing nested sections like [default], [api], [environments.*].
358
+ """
359
+ lines = []
360
+
361
+ # Collect nested sections to write after top-level keys
362
+ nested_sections: dict[str, dict] = {}
363
+
364
+ # Write top-level keys first (except pools and nested dicts)
365
+ for key, value in data.items():
366
+ if key == "pools":
367
+ continue
368
+ if value is None:
369
+ continue
370
+ if isinstance(value, dict):
371
+ # Save nested sections for later
372
+ nested_sections[key] = value
373
+ elif isinstance(value, str):
374
+ lines.append(f'{key} = "{value}"')
375
+ elif isinstance(value, bool):
376
+ lines.append(f"{key} = {str(value).lower()}")
377
+ elif isinstance(value, int | float):
378
+ lines.append(f"{key} = {value}")
379
+ elif isinstance(value, list):
380
+ if all(isinstance(v, int) for v in value):
381
+ lines.append(f"{key} = {value}")
382
+ else:
383
+ formatted = ", ".join(f'"{v}"' if isinstance(v, str) else str(v) for v in value)
384
+ lines.append(f"{key} = [{formatted}]")
385
+
386
+ # Write nested sections (e.g., [default], [api], [environments.foo])
387
+ for section_name, section_data in nested_sections.items():
388
+ lines.append("")
389
+ lines.append(f"[{section_name}]")
390
+ for key, value in section_data.items():
391
+ if value is None:
392
+ continue
393
+ if isinstance(value, str):
394
+ lines.append(f'{key} = "{value}"')
395
+ elif isinstance(value, bool):
396
+ lines.append(f"{key} = {str(value).lower()}")
397
+ elif isinstance(value, int | float):
398
+ lines.append(f"{key} = {value}")
399
+ elif isinstance(value, list):
400
+ if all(isinstance(v, int) for v in value):
401
+ lines.append(f"{key} = {value}")
402
+ else:
403
+ formatted = ", ".join(f'"{v}"' if isinstance(v, str) else str(v) for v in value)
404
+ lines.append(f"{key} = [{formatted}]")
405
+
406
+ # Write pools
407
+ pools = data.get("pools", {})
408
+ for pool_name, pool_config in pools.items():
409
+ lines.append("")
410
+ lines.append(f"[pools.{pool_name}]")
411
+ targets = pool_config.get("targets", [])
412
+ formatted = ", ".join(f'"{t}"' for t in targets)
413
+ lines.append(f"targets = [{formatted}]")
414
+
415
+ CONFIG_FILE.write_text("\n".join(lines) + "\n")
416
+
417
+
260
418
  def set_default_target(name: str) -> None:
261
419
  """Set default target.
262
420
 
wafer/wevin_cli.py CHANGED
@@ -86,8 +86,21 @@ class StreamingChunkFrontend:
86
86
  })
87
87
 
88
88
  elif isinstance(event, ToolResultReceived):
89
- # Emit tool_result event
90
- self._emit({"type": "tool_result", "is_error": event.is_error})
89
+ # Emit tool_result event with error details
90
+ result_event = {"type": "tool_result", "is_error": event.is_error}
91
+ # Include error message and content if available
92
+ if event.error:
93
+ result_event["error"] = event.error
94
+ if event.content:
95
+ # Convert content to string if it's a list
96
+ if isinstance(event.content, list):
97
+ result_event["content"] = "\n".join(
98
+ str(item) if not isinstance(item, dict) else item.get("text", str(item))
99
+ for item in event.content
100
+ )
101
+ else:
102
+ result_event["content"] = str(event.content)
103
+ self._emit(result_event)
91
104
 
92
105
  elif isinstance(event, StreamDone):
93
106
  # Will be handled by stop()
@@ -240,6 +253,7 @@ def _build_environment(
240
253
  ) -> Environment:
241
254
  """Build a CodingEnvironment from template config."""
242
255
  from wafer_core.environments.coding import CodingEnvironment
256
+ from wafer_core.rollouts.templates import DANGEROUS_BASH_COMMANDS
243
257
 
244
258
  working_dir = Path(corpus_path) if corpus_path else Path.cwd()
245
259
  resolved_tools = tools_override or tpl.tools
@@ -247,6 +261,7 @@ def _build_environment(
247
261
  working_dir=working_dir,
248
262
  enabled_tools=resolved_tools,
249
263
  bash_allowlist=tpl.bash_allowlist,
264
+ bash_denylist=DANGEROUS_BASH_COMMANDS,
250
265
  ) # type: ignore[assignment]
251
266
  return env
252
267
 
@@ -347,6 +362,11 @@ def main( # noqa: PLR0913, PLR0915
347
362
  print(f" {s.session_id} {preview}")
348
363
  return
349
364
 
365
+ # Emit early event for JSON mode before heavy imports
366
+ # This gives immediate feedback that the CLI started correctly
367
+ if json_output:
368
+ print(json.dumps({"type": "initializing"}), flush=True)
369
+
350
370
  import trio
351
371
  from wafer_core.rollouts import FileSessionStore, Message, Trajectory
352
372
  from wafer_core.rollouts.frontends import NoneFrontend, RunnerConfig, run_interactive
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafer-cli
3
- Version: 0.2.3
3
+ Version: 0.2.5
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,29 +5,31 @@ wafer/api_client.py,sha256=cPULiTxqOAYYSfDTNJgd-6Pqrt3IM4Gm9903U7yGIwY,6163
5
5
  wafer/auth.py,sha256=ZLsXZ73GDLD8GL7Rij1ELtuLqyJ5EU_uPBUMPVKwExA,10703
6
6
  wafer/autotuner.py,sha256=6gH0Ho7T58EFerMQcHQxshWe3DF4qU7fb5xthAh5SPM,44364
7
7
  wafer/billing.py,sha256=jbLB2lI4_9f2KD8uEFDi_ixLlowe5hasC0TIZJyIXRg,7163
8
- wafer/cli.py,sha256=kscK-JBiAu0H7roMdcaZ_lT7xlgR6mUJ1nBruidUdsE,184745
8
+ wafer/cli.py,sha256=rdR84w5ubXO1W2xnrURTrX3AtXy3VeUFVekeGw0djyA,211114
9
9
  wafer/config.py,sha256=h5Eo9_yfWqWGoPNdVQikI9GoZVUeysunSYiixf1mKcw,3411
10
10
  wafer/corpus.py,sha256=yTF3UA5bOa8BII2fmcXf-3WsIsM5DX4etysv0AzVknE,8912
11
- wafer/evaluate.py,sha256=7E1BqVl0um7kfU2_4FkXZrNc-PQJIHe09G1-L07PXmg,114971
11
+ wafer/evaluate.py,sha256=D_0WqIw1HysH7XXiyjxRpv6BUuOoPljN9-1vjPt4xFo,166765
12
12
  wafer/global_config.py,sha256=fhaR_RU3ufMksDmOohH1OLeQ0JT0SDW1hEip_zaP75k,11345
13
- wafer/gpu_run.py,sha256=gUbzMsMPsw3UHcn00bI-zTSHrU8c2FEpDvbcsczlDPo,9557
13
+ wafer/gpu_run.py,sha256=TwqXy72T7f2I7e6n5WWod3xgxCPnDhU0BgLsB4CUoQY,9716
14
14
  wafer/inference.py,sha256=tZCO5i05FKY27ewis3CSBHFBeFbXY3xwj0DSjdoMY9s,4314
15
15
  wafer/ncu_analyze.py,sha256=rAWzKQRZEY6E_CL3gAWUaW3uZ4kvQVZskVCPDpsFJuE,24633
16
16
  wafer/nsys_analyze.py,sha256=dRsYNYp1IqzGSPrQuEMW5vRbIxr-VrQwQbotLSrPvlY,6795
17
+ wafer/problems.py,sha256=ce2sy10A1nnNUG3VGsseTS8jL7LZsku4dE8zVf9JHQ4,11296
17
18
  wafer/rocprof_compute.py,sha256=Tu16Vb05b2grvheFWi1XLGlAr6m48NEDeZoDyw_4Uzw,19885
18
19
  wafer/rocprof_sdk.py,sha256=fAYCxpfJa5BZTTkIMBOXg4KsYK4i_wNOKrJJn1ZfypM,10086
19
20
  wafer/rocprof_systems.py,sha256=4IWbMcbYk1x_8iS7P3FC_u5sgH6EXADCtR2lV9id80M,18629
20
- wafer/targets.py,sha256=WE5TJgFPGtEIh7VaTQHZ4wB2t4kW0c5K8-UmQ_39Ock,10254
21
+ wafer/target_lock.py,sha256=QW0NMlu9Paa28O5iSAvGtN11j3kU2di0lADBrfwr2js,5160
22
+ wafer/targets.py,sha256=JlLvi18IHtOkgtBdkv_nUrzBweVmFoOQH-9tQW5s1yQ,15250
21
23
  wafer/tracelens.py,sha256=g9ZIeFyNojZn4uTd3skPqIrRiL7aMJOz_-GOd3aiyy4,7998
22
- wafer/wevin_cli.py,sha256=tuvHM_wsJ0qo4uUV8rj93vNBL7UgDIMY6f3YXCPf4lg,15821
24
+ wafer/wevin_cli.py,sha256=1_o2P47namZmPkbt47TnyYDmwhEzQYbSg5zjHffu2JQ,16802
23
25
  wafer/workspaces.py,sha256=92LG1mtkzNz-ap3XzcqY6KnQ9SUCFG8VBIOUj1Who64,25757
24
26
  wafer/skills/wafer-guide/SKILL.md,sha256=UfBeIe5GKFzOYcbPmcs8U2nrjbfr-jSMRwg0jQDBfb0,3058
25
27
  wafer/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
28
  wafer/templates/ask_docs.py,sha256=Lxs-faz9v5m4Qa4NjF2X_lE8KwM9ES9MNJkxo7ep56o,2256
27
29
  wafer/templates/optimize_kernel.py,sha256=u6AL7Q3uttqlnBLzcoFdsiPq5lV2TV3bgqwCYYlK9gk,2357
28
30
  wafer/templates/trace_analyze.py,sha256=XE1VqzVkIUsZbXF8EzQdDYgg-AZEYAOFpr6B_vnRELc,2880
29
- wafer_cli-0.2.3.dist-info/METADATA,sha256=MKzRmMwc1zhKUVeu_NE4h5_17EFnX1bjLE4Jcox0AAk,559
30
- wafer_cli-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- wafer_cli-0.2.3.dist-info/entry_points.txt,sha256=WqB7hB__WhtPY8y1cO2sZiUz7fCq6Ik-usAigpeFvWE,41
32
- wafer_cli-0.2.3.dist-info/top_level.txt,sha256=2MK1IVMWfpLL8BZCQ3E9aG6L6L666gSA_teYlwan4fs,6
33
- wafer_cli-0.2.3.dist-info/RECORD,,
31
+ wafer_cli-0.2.5.dist-info/METADATA,sha256=O1JyAJtPtr5j4sz5jKiDfyB4d14Mb3tSSDEnQftyzwE,559
32
+ wafer_cli-0.2.5.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
33
+ wafer_cli-0.2.5.dist-info/entry_points.txt,sha256=WqB7hB__WhtPY8y1cO2sZiUz7fCq6Ik-usAigpeFvWE,41
34
+ wafer_cli-0.2.5.dist-info/top_level.txt,sha256=2MK1IVMWfpLL8BZCQ3E9aG6L6L666gSA_teYlwan4fs,6
35
+ wafer_cli-0.2.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5