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.
- {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/publish.yml +16 -0
- execweave-0.6.1/.github/workflows/windows-launcher.yml +43 -0
- {execweave-0.6.0 → execweave-0.6.1}/PKG-INFO +1 -1
- {execweave-0.6.0 → execweave-0.6.1}/pyproject.toml +1 -1
- execweave-0.6.1/scripts/check_windows_launchers.py +96 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/__init__.py +1 -1
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/collector.py +11 -7
- execweave-0.6.1/src/execweave/command.py +70 -0
- execweave-0.6.1/tests/test_command.py +74 -0
- {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/ci.yml +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/.github/workflows/package-benchmark.yml +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/.gitignore +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/LICENSE +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/README.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/README.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/README.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/README.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/README.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/benchmarks/phase1_overhead.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/README.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/v0.6.0-github-actions.json +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/benchmarks/v0.6.0-github-actions.svg +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/claude-code-hooks.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/codex-hooks.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/cursor-hooks.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/gemini-hooks.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/inference-gateway.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/live-graph.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/model-runtime.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/opencode-plugin.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-1-runtime-collection.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/phase-2-execution-graph.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/security-analysis.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.ja.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.ko.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.zh-CN.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/docs/semantic-telemetry.zh-TW.md +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_claude_hook.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_claude_record_graph.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_correlation_graph.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_inference_gateway_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_model_runtime_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_semantic_graph.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/check_viewer_js.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_claude_hook_smoke.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_cursor_hook_smoke.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_gemini_hook_smoke.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/emit_opencode_plugin_smoke.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/scripts/make_semantic_smoke.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/__main__.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/analysis.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/backends.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/benchmark.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_hook_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/claude_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_hook_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/codex_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/correlation.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_hook_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/cursor_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/filesystem.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/focus.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_hook_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/gemini_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/graph.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/graph_ops.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_gateway.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_gateway_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_identity.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/inference_identity_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/live.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/model_runtime.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/model_runtime_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_hook_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_plugin_cli.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/opencode_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/overhead_benchmark.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/provider_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/schema.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/semantic.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/sink.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/strace_backend.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/validate.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/viewer.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/src/execweave/workflow.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_analysis.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_backends.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_claude_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_claude_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_codex_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_codex_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_collector.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_condense.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation_canonical_path.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_correlation_process_exe.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_cursor_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_cursor_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_focus.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_gemini_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_gemini_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_graph.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_graph_ops.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_inference_gateway.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_inference_identity.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_live.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_model_runtime.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_adapter.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_plugin.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_opencode_record.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_overhead_benchmark.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_schema.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_semantic.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_strace_backend.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_validate.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_version.py +0 -0
- {execweave-0.6.0 → execweave-0.6.1}/tests/test_viewer.py +0 -0
- {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.
|
|
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
|
|
@@ -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())
|
|
@@ -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
|
-
|
|
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
|
-
"
|
|
89
|
+
"cursor": "Cursor",
|
|
86
90
|
"opencode": "OpenCode",
|
|
87
|
-
"opencode.exe": "OpenCode",
|
|
88
91
|
}
|
|
89
|
-
return known.get(executable,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|