execweave 0.6.0__tar.gz → 0.6.1__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 (169) hide show
  1. {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/publish.yml +16 -0
  2. execweave-0.6.1/.github/workflows/windows-launcher.yml +43 -0
  3. {execweave-0.6.0 → execweave-0.6.1}/PKG-INFO +1 -1
  4. {execweave-0.6.0 → execweave-0.6.1}/pyproject.toml +1 -1
  5. execweave-0.6.1/scripts/check_windows_launchers.py +96 -0
  6. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/__init__.py +1 -1
  7. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/collector.py +11 -7
  8. execweave-0.6.1/src/execweave/command.py +70 -0
  9. execweave-0.6.1/tests/test_command.py +74 -0
  10. {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/ci.yml +0 -0
  11. {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/package-benchmark.yml +0 -0
  12. {execweave-0.6.0 → execweave-0.6.1}/.gitignore +0 -0
  13. {execweave-0.6.0 → execweave-0.6.1}/LICENSE +0 -0
  14. {execweave-0.6.0 → execweave-0.6.1}/README.ja.md +0 -0
  15. {execweave-0.6.0 → execweave-0.6.1}/README.ko.md +0 -0
  16. {execweave-0.6.0 → execweave-0.6.1}/README.md +0 -0
  17. {execweave-0.6.0 → execweave-0.6.1}/README.zh-CN.md +0 -0
  18. {execweave-0.6.0 → execweave-0.6.1}/README.zh-TW.md +0 -0
  19. {execweave-0.6.0 → execweave-0.6.1}/benchmarks/phase1_overhead.py +0 -0
  20. {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/README.md +0 -0
  21. {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/v0.6.0-github-actions.json +0 -0
  22. {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/v0.6.0-github-actions.svg +0 -0
  23. {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.ja.md +0 -0
  24. {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.ko.md +0 -0
  25. {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.md +0 -0
  26. {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.zh-CN.md +0 -0
  27. {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.zh-TW.md +0 -0
  28. {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.ja.md +0 -0
  29. {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.ko.md +0 -0
  30. {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.md +0 -0
  31. {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.zh-CN.md +0 -0
  32. {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.zh-TW.md +0 -0
  33. {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.ja.md +0 -0
  34. {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.ko.md +0 -0
  35. {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.md +0 -0
  36. {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.zh-CN.md +0 -0
  37. {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.zh-TW.md +0 -0
  38. {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.ja.md +0 -0
  39. {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.ko.md +0 -0
  40. {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.md +0 -0
  41. {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.zh-CN.md +0 -0
  42. {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.zh-TW.md +0 -0
  43. {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.ja.md +0 -0
  44. {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.ko.md +0 -0
  45. {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.md +0 -0
  46. {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.zh-CN.md +0 -0
  47. {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.zh-TW.md +0 -0
  48. {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.ja.md +0 -0
  49. {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.ko.md +0 -0
  50. {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.md +0 -0
  51. {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.zh-CN.md +0 -0
  52. {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.zh-TW.md +0 -0
  53. {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.ja.md +0 -0
  54. {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.ko.md +0 -0
  55. {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.md +0 -0
  56. {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.zh-CN.md +0 -0
  57. {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.zh-TW.md +0 -0
  58. {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.ja.md +0 -0
  59. {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.ko.md +0 -0
  60. {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.md +0 -0
  61. {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.zh-CN.md +0 -0
  62. {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.zh-TW.md +0 -0
  63. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.ja.md +0 -0
  64. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.ko.md +0 -0
  65. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.md +0 -0
  66. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.zh-CN.md +0 -0
  67. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.zh-TW.md +0 -0
  68. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.ja.md +0 -0
  69. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.ko.md +0 -0
  70. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.md +0 -0
  71. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.zh-CN.md +0 -0
  72. {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.zh-TW.md +0 -0
  73. {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.ja.md +0 -0
  74. {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.ko.md +0 -0
  75. {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.md +0 -0
  76. {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.zh-CN.md +0 -0
  77. {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.zh-TW.md +0 -0
  78. {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.ja.md +0 -0
  79. {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.ko.md +0 -0
  80. {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.md +0 -0
  81. {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.zh-CN.md +0 -0
  82. {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.zh-TW.md +0 -0
  83. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_claude_hook.py +0 -0
  84. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_claude_record_graph.py +0 -0
  85. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_correlation_graph.py +0 -0
  86. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_inference_gateway_cli.py +0 -0
  87. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_model_runtime_cli.py +0 -0
  88. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_semantic_graph.py +0 -0
  89. {execweave-0.6.0 → execweave-0.6.1}/scripts/check_viewer_js.py +0 -0
  90. {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_claude_hook_smoke.py +0 -0
  91. {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_cursor_hook_smoke.py +0 -0
  92. {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_gemini_hook_smoke.py +0 -0
  93. {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_opencode_plugin_smoke.py +0 -0
  94. {execweave-0.6.0 → execweave-0.6.1}/scripts/make_semantic_smoke.py +0 -0
  95. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/__main__.py +0 -0
  96. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/analysis.py +0 -0
  97. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/backends.py +0 -0
  98. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/benchmark.py +0 -0
  99. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_adapter.py +0 -0
  100. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_hook_cli.py +0 -0
  101. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_record.py +0 -0
  102. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cli.py +0 -0
  103. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_adapter.py +0 -0
  104. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_hook_cli.py +0 -0
  105. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_record.py +0 -0
  106. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/correlation.py +0 -0
  107. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_adapter.py +0 -0
  108. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_hook_cli.py +0 -0
  109. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_record.py +0 -0
  110. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/filesystem.py +0 -0
  111. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/focus.py +0 -0
  112. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_adapter.py +0 -0
  113. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_hook_cli.py +0 -0
  114. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_record.py +0 -0
  115. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/graph.py +0 -0
  116. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/graph_ops.py +0 -0
  117. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_gateway.py +0 -0
  118. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_gateway_cli.py +0 -0
  119. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_identity.py +0 -0
  120. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_identity_cli.py +0 -0
  121. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/live.py +0 -0
  122. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/model_runtime.py +0 -0
  123. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/model_runtime_cli.py +0 -0
  124. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_adapter.py +0 -0
  125. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_hook_cli.py +0 -0
  126. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_plugin_cli.py +0 -0
  127. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_record.py +0 -0
  128. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/overhead_benchmark.py +0 -0
  129. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/provider_record.py +0 -0
  130. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/schema.py +0 -0
  131. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/semantic.py +0 -0
  132. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/sink.py +0 -0
  133. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/strace_backend.py +0 -0
  134. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/validate.py +0 -0
  135. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/viewer.py +0 -0
  136. {execweave-0.6.0 → execweave-0.6.1}/src/execweave/workflow.py +0 -0
  137. {execweave-0.6.0 → execweave-0.6.1}/tests/test_analysis.py +0 -0
  138. {execweave-0.6.0 → execweave-0.6.1}/tests/test_backends.py +0 -0
  139. {execweave-0.6.0 → execweave-0.6.1}/tests/test_claude_adapter.py +0 -0
  140. {execweave-0.6.0 → execweave-0.6.1}/tests/test_claude_record.py +0 -0
  141. {execweave-0.6.0 → execweave-0.6.1}/tests/test_codex_adapter.py +0 -0
  142. {execweave-0.6.0 → execweave-0.6.1}/tests/test_codex_record.py +0 -0
  143. {execweave-0.6.0 → execweave-0.6.1}/tests/test_collector.py +0 -0
  144. {execweave-0.6.0 → execweave-0.6.1}/tests/test_condense.py +0 -0
  145. {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation.py +0 -0
  146. {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation_canonical_path.py +0 -0
  147. {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation_process_exe.py +0 -0
  148. {execweave-0.6.0 → execweave-0.6.1}/tests/test_cursor_adapter.py +0 -0
  149. {execweave-0.6.0 → execweave-0.6.1}/tests/test_cursor_record.py +0 -0
  150. {execweave-0.6.0 → execweave-0.6.1}/tests/test_focus.py +0 -0
  151. {execweave-0.6.0 → execweave-0.6.1}/tests/test_gemini_adapter.py +0 -0
  152. {execweave-0.6.0 → execweave-0.6.1}/tests/test_gemini_record.py +0 -0
  153. {execweave-0.6.0 → execweave-0.6.1}/tests/test_graph.py +0 -0
  154. {execweave-0.6.0 → execweave-0.6.1}/tests/test_graph_ops.py +0 -0
  155. {execweave-0.6.0 → execweave-0.6.1}/tests/test_inference_gateway.py +0 -0
  156. {execweave-0.6.0 → execweave-0.6.1}/tests/test_inference_identity.py +0 -0
  157. {execweave-0.6.0 → execweave-0.6.1}/tests/test_live.py +0 -0
  158. {execweave-0.6.0 → execweave-0.6.1}/tests/test_model_runtime.py +0 -0
  159. {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_adapter.py +0 -0
  160. {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_plugin.py +0 -0
  161. {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_record.py +0 -0
  162. {execweave-0.6.0 → execweave-0.6.1}/tests/test_overhead_benchmark.py +0 -0
  163. {execweave-0.6.0 → execweave-0.6.1}/tests/test_schema.py +0 -0
  164. {execweave-0.6.0 → execweave-0.6.1}/tests/test_semantic.py +0 -0
  165. {execweave-0.6.0 → execweave-0.6.1}/tests/test_strace_backend.py +0 -0
  166. {execweave-0.6.0 → execweave-0.6.1}/tests/test_validate.py +0 -0
  167. {execweave-0.6.0 → execweave-0.6.1}/tests/test_version.py +0 -0
  168. {execweave-0.6.0 → execweave-0.6.1}/tests/test_viewer.py +0 -0
  169. {execweave-0.6.0 → execweave-0.6.1}/tests/test_workflow.py +0 -0
@@ -59,3 +59,19 @@ jobs:
59
59
  path: dist/
60
60
  - name: Publish package distributions to PyPI
61
61
  uses: pypa/gh-action-pypi-publish@release/v1
62
+
63
+ release-assets:
64
+ needs: [build, publish]
65
+ runs-on: ubuntu-latest
66
+ permissions:
67
+ contents: write
68
+ steps:
69
+ - uses: actions/download-artifact@v4
70
+ with:
71
+ name: python-package-distributions
72
+ path: dist/
73
+ - name: Attach distributions to GitHub Release
74
+ env:
75
+ GH_TOKEN: ${{ github.token }}
76
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
77
+ run: gh release upload "$RELEASE_TAG" dist/* --clobber
@@ -0,0 +1,43 @@
1
+ name: Windows Launcher Compatibility
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "src/**"
8
+ - "tests/**"
9
+ - "scripts/check_windows_launchers.py"
10
+ - "pyproject.toml"
11
+ - ".github/workflows/windows-launcher.yml"
12
+ pull_request:
13
+ paths:
14
+ - "src/**"
15
+ - "tests/**"
16
+ - "scripts/check_windows_launchers.py"
17
+ - "pyproject.toml"
18
+ - ".github/workflows/windows-launcher.yml"
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+
24
+ concurrency:
25
+ group: windows-launcher-${{ github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ jobs:
29
+ smoke:
30
+ runs-on: windows-latest
31
+ timeout-minutes: 15
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.12"
37
+ cache: pip
38
+ - name: Install package
39
+ run: python -m pip install -e ".[dev]"
40
+ - name: Command resolver tests
41
+ run: pytest -q tests/test_command.py
42
+ - name: CMD and Windows PowerShell Codex/Cursor smoke
43
+ run: python scripts/check_windows_launchers.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execweave
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Turn AI-agent runtime behavior into a local interactive execution graph.
5
5
  Project-URL: Homepage, https://github.com/Irish-kw/ExecWeave
6
6
  Project-URL: Repository, https://github.com/Irish-kw/ExecWeave
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execweave"
7
- version = "0.6.0"
7
+ version = "0.6.1"
8
8
  description = "Turn AI-agent runtime behavior into a local interactive execution graph."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+
11
+ def _write_cmd_shim(path: Path) -> None:
12
+ path.write_text(
13
+ '@echo off\r\n"%EW_TEST_PYTHON%" -c "import time; time.sleep(0.15)"\r\n'
14
+ "exit /b %ERRORLEVEL%\r\n",
15
+ encoding="utf-8",
16
+ )
17
+
18
+
19
+ def _verify_runtime_artifacts(root: Path, output_dir: str) -> None:
20
+ run_dir = root / output_dir
21
+ for name in ("events.jsonl", "graph.json", "viewer.html"):
22
+ path = run_dir / name
23
+ if not path.is_file() or path.stat().st_size == 0:
24
+ raise RuntimeError(f"missing Windows launcher smoke artifact: {path}")
25
+
26
+
27
+ def _run_cmd(command: str, *, cwd: Path, env: dict[str, str]) -> None:
28
+ comspec = os.environ.get("COMSPEC") or shutil.which("cmd.exe")
29
+ if not comspec:
30
+ raise RuntimeError("cmd.exe is not available")
31
+ subprocess.run([comspec, "/d", "/s", "/c", command], cwd=cwd, env=env, check=True)
32
+
33
+
34
+ def _run_powershell(command: str, *, cwd: Path, env: dict[str, str]) -> None:
35
+ powershell = shutil.which("powershell.exe")
36
+ if not powershell:
37
+ raise RuntimeError("Windows PowerShell is not available")
38
+ subprocess.run(
39
+ [powershell, "-NoLogo", "-NoProfile", "-Command", command],
40
+ cwd=cwd,
41
+ env=env,
42
+ check=True,
43
+ )
44
+
45
+
46
+ def main() -> int:
47
+ if os.name != "nt":
48
+ print("Windows launcher smoke skipped on non-Windows host")
49
+ return 0
50
+
51
+ with tempfile.TemporaryDirectory(prefix="execweave-win-launch-") as directory:
52
+ root = Path(directory)
53
+ _write_cmd_shim(root / "codex.cmd")
54
+ _write_cmd_shim(root / "cursor.cmd")
55
+
56
+ env = os.environ.copy()
57
+ env["PATH"] = f"{root}{os.pathsep}{env.get('PATH', '')}"
58
+ env["EW_TEST_PYTHON"] = sys.executable
59
+
60
+ cases = [
61
+ (
62
+ _run_cmd,
63
+ "codex-cmd",
64
+ "execweave-codex-record --backend portable --no-files --no-network "
65
+ "--output-dir codex-cmd -- codex",
66
+ ),
67
+ (
68
+ _run_powershell,
69
+ "codex-powershell",
70
+ "execweave-codex-record --backend portable --no-files --no-network "
71
+ "--output-dir codex-powershell -- codex",
72
+ ),
73
+ (
74
+ _run_cmd,
75
+ "cursor-cmd",
76
+ "execweave-cursor-record --backend portable --no-files --no-network "
77
+ "--output-dir cursor-cmd -- cursor",
78
+ ),
79
+ (
80
+ _run_powershell,
81
+ "cursor-powershell",
82
+ "execweave-cursor-record --backend portable --no-files --no-network "
83
+ "--output-dir cursor-powershell -- cursor",
84
+ ),
85
+ ]
86
+
87
+ for runner, output_dir, command in cases:
88
+ runner(command, cwd=root, env=env)
89
+ _verify_runtime_artifacts(root, output_dir)
90
+
91
+ print("Windows CMD/PowerShell Codex/Cursor launcher smoke passed")
92
+ return 0
93
+
94
+
95
+ if __name__ == "__main__":
96
+ raise SystemExit(main())
@@ -1,3 +1,3 @@
1
1
  """ExecWeave runtime graph package."""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.6.1"
@@ -9,6 +9,7 @@ from typing import Iterable
9
9
 
10
10
  import psutil
11
11
 
12
+ from .command import resolve_launch_command
12
13
  from .filesystem import FileWatcher
13
14
  from .schema import Entity, RuntimeEvent
14
15
  from .sink import JsonlSink
@@ -75,18 +76,20 @@ def infer_agent_name(command: Iterable[str]) -> str:
75
76
  parts = list(command)
76
77
  if not parts:
77
78
  return "unknown-agent"
78
- executable = Path(parts[0]).name.lower()
79
+ basename = Path(parts[0]).name
80
+ executable = basename.lower()
81
+ for suffix in (".exe", ".cmd", ".bat", ".ps1"):
82
+ if executable.endswith(suffix):
83
+ executable = executable[: -len(suffix)]
84
+ break
79
85
  known = {
80
86
  "claude": "Claude Code",
81
- "claude.exe": "Claude Code",
82
87
  "codex": "OpenAI Codex",
83
- "codex.exe": "OpenAI Codex",
84
88
  "gemini": "Gemini CLI",
85
- "gemini.exe": "Gemini CLI",
89
+ "cursor": "Cursor",
86
90
  "opencode": "OpenCode",
87
- "opencode.exe": "OpenCode",
88
91
  }
89
- return known.get(executable, Path(parts[0]).name)
92
+ return known.get(executable, basename)
90
93
 
91
94
 
92
95
  class RuntimeCollector:
@@ -117,6 +120,7 @@ class RuntimeCollector:
117
120
  if not command:
118
121
  raise ValueError("command must not be empty")
119
122
 
123
+ launch_command = resolve_launch_command(command)
120
124
  agent_name = infer_agent_name(command)
121
125
  agent = Entity(type="agent", id=f"agent:{agent_name}", name=agent_name)
122
126
  session = Entity(
@@ -155,7 +159,7 @@ class RuntimeCollector:
155
159
  process: subprocess.Popen[bytes] | None = None
156
160
  return_code = 1
157
161
  try:
158
- process = subprocess.Popen(command, cwd=str(self.watch_root))
162
+ process = subprocess.Popen(launch_command, cwd=str(self.watch_root))
159
163
  root = psutil.Process(process.pid)
160
164
  snapshot = _safe_process_snapshot(root)
161
165
  if snapshot is not None:
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+
8
+ def _looks_like_path(value: str) -> bool:
9
+ return (
10
+ Path(value).is_absolute()
11
+ or value.startswith((".", "~"))
12
+ or "/" in value
13
+ or "\\" in value
14
+ )
15
+
16
+
17
+ def _resolve_executable(executable: str, *, path: str | None = None) -> str:
18
+ if not executable.strip():
19
+ raise ValueError("command executable must not be empty")
20
+
21
+ if _looks_like_path(executable):
22
+ candidate = Path(executable).expanduser()
23
+ if not candidate.is_file():
24
+ raise FileNotFoundError(f"command executable not found: {executable!r}")
25
+ resolved = str(candidate.resolve())
26
+ else:
27
+ resolved = shutil.which(executable, path=path)
28
+ if resolved is None:
29
+ raise FileNotFoundError(
30
+ f"command executable {executable!r} was not found on PATH; "
31
+ "install it or make its launcher available on PATH"
32
+ )
33
+
34
+ if os.name != "nt" and not os.access(resolved, os.X_OK):
35
+ raise PermissionError(f"command executable is not executable: {resolved!r}")
36
+ return resolved
37
+
38
+
39
+ def resolve_launch_command(command: list[str], *, path: str | None = None) -> list[str]:
40
+ """Resolve a shell-visible command into an argv safe for ``subprocess``.
41
+
42
+ ExecWeave launches commands without ``shell=True``. On POSIX, ``shutil.which``
43
+ mirrors PATH lookup for normal executables and shebang scripts. On Windows it
44
+ also honors PATHEXT, which is required for npm-style ``.cmd``/``.bat`` shims
45
+ such as Codex, Cursor, Claude, Gemini, and OpenCode launchers.
46
+
47
+ An explicitly supplied PowerShell ``.ps1`` file is launched through pwsh or
48
+ Windows PowerShell because CreateProcess cannot execute a PowerShell script by
49
+ itself. The caller's original command should remain the evidence-facing value;
50
+ this function only returns the argv used to start the child process.
51
+ """
52
+ if not command:
53
+ raise ValueError("command must not be empty")
54
+
55
+ resolved = _resolve_executable(command[0], path=path)
56
+ arguments = list(command[1:])
57
+
58
+ if os.name == "nt" and Path(resolved).suffix.lower() == ".ps1":
59
+ search_path = path if path is not None else os.environ.get("PATH")
60
+ powershell = shutil.which("pwsh.exe", path=search_path) or shutil.which(
61
+ "powershell.exe", path=search_path
62
+ )
63
+ if powershell is None:
64
+ raise FileNotFoundError(
65
+ "PowerShell script requested, but neither pwsh.exe nor powershell.exe "
66
+ "was found on PATH"
67
+ )
68
+ return [powershell, "-NoLogo", "-NoProfile", "-File", resolved, *arguments]
69
+
70
+ return [resolved, *arguments]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ from execweave.collector import infer_agent_name
11
+ from execweave.command import resolve_launch_command
12
+
13
+
14
+ def _prepend_path(monkeypatch, directory: Path) -> None:
15
+ current = os.environ.get("PATH", "")
16
+ value = str(directory) if not current else f"{directory}{os.pathsep}{current}"
17
+ monkeypatch.setenv("PATH", value)
18
+
19
+
20
+ def test_infer_agent_name_normalizes_windows_launcher_suffixes() -> None:
21
+ assert infer_agent_name(["codex.cmd"]) == "OpenAI Codex"
22
+ assert infer_agent_name(["claude.exe"]) == "Claude Code"
23
+ assert infer_agent_name(["gemini.bat"]) == "Gemini CLI"
24
+ assert infer_agent_name(["cursor.ps1"]) == "Cursor"
25
+ assert infer_agent_name(["opencode.cmd"]) == "OpenCode"
26
+
27
+
28
+ def test_missing_command_has_actionable_error(monkeypatch) -> None:
29
+ monkeypatch.setenv("PATH", "")
30
+ with pytest.raises(FileNotFoundError, match="was not found on PATH"):
31
+ resolve_launch_command(["execweave-command-that-does-not-exist-83a219"])
32
+
33
+
34
+ @pytest.mark.skipif(os.name == "nt", reason="POSIX launcher test")
35
+ def test_posix_path_script_is_resolved_and_launchable(tmp_path: Path, monkeypatch) -> None:
36
+ shim = tmp_path / "codex"
37
+ shim.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
38
+ shim.chmod(0o755)
39
+ _prepend_path(monkeypatch, tmp_path)
40
+
41
+ resolved = resolve_launch_command(["codex"])
42
+
43
+ assert Path(resolved[0]).resolve() == shim.resolve()
44
+ assert subprocess.run(resolved, cwd=tmp_path, check=False).returncode == 0
45
+
46
+
47
+ @pytest.mark.skipif(os.name != "nt", reason="Windows launcher test")
48
+ @pytest.mark.parametrize("agent", ["codex", "cursor", "claude", "gemini", "opencode"])
49
+ def test_windows_cmd_shim_is_resolved_and_launchable(
50
+ agent: str, tmp_path: Path, monkeypatch
51
+ ) -> None:
52
+ shim = tmp_path / f"{agent}.cmd"
53
+ shim.write_text("@echo off\r\nexit /b 0\r\n", encoding="utf-8")
54
+ _prepend_path(monkeypatch, tmp_path)
55
+
56
+ resolved = resolve_launch_command([agent])
57
+
58
+ assert Path(resolved[0]).resolve() == shim.resolve()
59
+ assert subprocess.run(resolved, cwd=tmp_path, check=False).returncode == 0
60
+
61
+
62
+ @pytest.mark.skipif(os.name != "nt", reason="Windows PowerShell launcher test")
63
+ def test_explicit_powershell_script_uses_powershell(tmp_path: Path) -> None:
64
+ powershell = shutil.which("pwsh.exe") or shutil.which("powershell.exe")
65
+ if powershell is None:
66
+ pytest.skip("PowerShell is not installed")
67
+
68
+ script = tmp_path / "probe.ps1"
69
+ script.write_text("exit 0\n", encoding="utf-8")
70
+ resolved = resolve_launch_command([str(script)])
71
+
72
+ assert Path(resolved[0]).resolve() == Path(powershell).resolve()
73
+ assert "-File" in resolved
74
+ assert subprocess.run(resolved, cwd=tmp_path, check=False).returncode == 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