reverse-api-engineer 0.4.3__tar.gz → 0.4.4__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.
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/.claude/settings.local.json +8 -1
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/.gitignore +3 -1
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/CHANGELOG.md +12 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/PKG-INFO +2 -2
- reverse_api_engineer-0.4.4/chrome-extension/packed/reverse-api-engineer-chrome.zip +0 -0
- reverse_api_engineer-0.4.4/chrome-extension/store-assets/screenshot-1280x800.png +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/pyproject.toml +2 -2
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/auto_engineer.py +16 -17
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/base_engineer.py +35 -10
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/browser.py +16 -15
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/cli.py +12 -26
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/collector.py +2 -2
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/collector_ui.py +3 -2
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/copilot_engineer.py +1 -1
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/engineer.py +14 -16
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/opencode_engineer.py +3 -3
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/opencode_ui.py +8 -5
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/playwright_codegen.py +27 -24
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/tui.py +9 -7
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/uv.lock +977 -1001
- reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/QUICKSTART.md +0 -2017
- reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/TODO_LIST.md +0 -176
- reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/TOOLS.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/.claude-plugin/marketplace.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/.python-version +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/CLAUDE.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/CONTRIBUTING.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/INTERVIEW.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/LICENSE +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/PROMPT_STASH.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/RELEASING.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/assets/reverse-api-banner.svg +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/assets/reverse-api-engineer.gif +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/assets/reverse-api-logo.svg +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/.claude/settings.local.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/components.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/package-lock.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/package.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/postcss.config.js +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/_locales/en/messages.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-128.png +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-16.png +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-32.png +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-48.png +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/public/manifest.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/background/service-worker.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/agent-action.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/chat-input.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/icons.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/markdown-renderer.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/mode-selector.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/plan.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/session-selector.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/terminal.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/ui/code-block.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/content/codegen-recorder.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/index.css +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/capture.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/native-host.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/storage.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/types.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/index.html +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/main.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/side-panel.tsx +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/src/vite-env.d.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/tailwind.config.js +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/tsconfig.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/chrome-extension/vite.config.ts +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/INDEX.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/extract_job_fields.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/main.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/quick_example.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/apple/requirements.txt +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/API_SUMMARY.txt +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/example_usage.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ashby/requirements.txt +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/autoscout24/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/autoscout24/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/autoscout24/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ikea/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/ikea/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/mintlify/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/mintlify/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/API_ANALYSIS_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/example_fetch_all_jobs.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/quick_start.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/examples/uber/requirements.txt +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/llm-docs/OPENCODE_API_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/llm-docs/opencode-api.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/.claude-plugin/plugin.json +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/LICENSE +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/README.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/agents/api-reverse-engineer.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/agent.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/capture.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/engineer.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/manual.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/LICENSE +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/SKILL.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/AUTH_PATTERNS.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/HAR_ANALYSIS.md +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_analyze.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_filter.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_utils.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_validate.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/templates/api_client.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/scripts/clean_build.sh +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/__init__.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/action_recorder.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/config.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/messages.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/native_host.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/pricing.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/session.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/sync.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/utils.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/__init__.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/conftest.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_action_recorder.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_auto_engineer.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_base_engineer.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_collector.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_collector_ui.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_config.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_engineer.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_init.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_messages.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_native_host.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_opencode_engineer.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_opencode_ui.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_pricing.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_session.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_sync.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_tui.py +0 -0
- {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/tests/test_utils.py +0 -0
|
@@ -113,7 +113,14 @@
|
|
|
113
113
|
"Bash(npx rollup-plugin-visualizer:*)",
|
|
114
114
|
"Bash(git pull:*)",
|
|
115
115
|
"WebFetch(domain:docs.anthropic.com)",
|
|
116
|
-
"WebFetch(domain:docs.claude.com)"
|
|
116
|
+
"WebFetch(domain:docs.claude.com)",
|
|
117
|
+
"Bash(uv lock:*)",
|
|
118
|
+
"Bash(cd /Users/kalilbouzigues/Projects/browgents/reverse-api/chrome-extension/dist && zip -r ../packed/reverse-api-engineer-chrome.zip . && ls -lh ../packed/)",
|
|
119
|
+
"mcp__plugin_reverse-api-engineer_rae-playwright-mcp__browser_resize",
|
|
120
|
+
"mcp__plugin_reverse-api-engineer_rae-playwright-mcp__browser_take_screenshot",
|
|
121
|
+
"Bash(gh issue:*)",
|
|
122
|
+
"Bash(CLAUDECODE= uv run python3 /tmp/test_hook.py 2>&1)",
|
|
123
|
+
"Bash(CLAUDECODE=1 uv run python3 /tmp/test_hook4.py 2>&1)"
|
|
117
124
|
]
|
|
118
125
|
}
|
|
119
126
|
}
|
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.4] - 2026-03-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Stream closed errors (#51)**: Fixed "Error in hook callback hook_0: Stream closed" by clearing inherited `CLAUDECODE` env var from CLI subprocess and extending stream close timeout
|
|
12
|
+
- **Async generator cleanup**: Avoid early return inside `query()` loop to prevent cancel-scope errors
|
|
13
|
+
- **CLI stderr noise**: Filter minified JS stack traces into a single clean error line (use `DEBUG=1` for full output)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **claude-agent-sdk**: Bumped minimum version to 0.1.48
|
|
17
|
+
- **Agent mode**: No longer prompts for URL (agent navigates autonomously)
|
|
18
|
+
- **Header UI**: Version and task labels now use mode-specific colors (agent=coral, engineer=blue, collector=gold)
|
|
19
|
+
|
|
8
20
|
## [0.4.3] - 2026-03-12
|
|
9
21
|
|
|
10
22
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reverse-api-engineer
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: A tool to capture browser traffic for API reverse engineering
|
|
5
5
|
Project-URL: Homepage, https://github.com/kalil0321/reverse-api-engineer
|
|
6
6
|
Project-URL: Repository, https://github.com/kalil0321/reverse-api-engineer
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
|
|
|
22
22
|
Requires-Dist: aiohttp>=3.12.15
|
|
23
23
|
Requires-Dist: anthropic>=0.40.0
|
|
24
24
|
Requires-Dist: brotli>=1.2.0
|
|
25
|
-
Requires-Dist: claude-agent-sdk>=0.1.
|
|
25
|
+
Requires-Dist: claude-agent-sdk>=0.1.48
|
|
26
26
|
Requires-Dist: click>=8.1.0
|
|
27
27
|
Requires-Dist: playwright-stealth>=1.0.0
|
|
28
28
|
Requires-Dist: playwright>=1.40.0
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "reverse-api-engineer"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.4"
|
|
4
4
|
description = "A tool to capture browser traffic for API reverse engineering"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -24,7 +24,7 @@ dependencies = [
|
|
|
24
24
|
"playwright>=1.40.0",
|
|
25
25
|
"playwright-stealth>=1.0.0",
|
|
26
26
|
"click>=8.1.0",
|
|
27
|
-
"claude-agent-sdk>=0.1.
|
|
27
|
+
"claude-agent-sdk>=0.1.48",
|
|
28
28
|
"rich>=13.0.0",
|
|
29
29
|
"questionary>=2.0.0",
|
|
30
30
|
"requests>=2.32.5",
|
|
@@ -229,9 +229,7 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
229
229
|
- Any limitations or caveats
|
|
230
230
|
"""
|
|
231
231
|
|
|
232
|
-
async def _handle_tool_permission(
|
|
233
|
-
self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext
|
|
234
|
-
) -> PermissionResultAllow:
|
|
232
|
+
async def _handle_tool_permission(self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext) -> PermissionResultAllow:
|
|
235
233
|
"""Handle tool permission requests, with interactive UI for AskUserQuestion."""
|
|
236
234
|
if tool_name == "AskUserQuestion":
|
|
237
235
|
questions = input_data.get("questions", [])
|
|
@@ -244,7 +242,7 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
244
242
|
|
|
245
243
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
246
244
|
"""Run auto mode with MCP browser integration."""
|
|
247
|
-
self.ui.header(self.run_id, self.prompt, self.model)
|
|
245
|
+
self.ui.header(self.run_id, self.prompt, self.model, mode="agent")
|
|
248
246
|
self.ui.start_analysis()
|
|
249
247
|
self.message_store.save_prompt(self._build_auto_prompt())
|
|
250
248
|
|
|
@@ -259,17 +257,14 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
259
257
|
],
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
# Required
|
|
263
|
-
# See: https://platform.claude.com/docs/en/agent-sdk/user-input
|
|
260
|
+
# Required: dummy hook keeps the stream open for can_use_tool
|
|
264
261
|
async def _dummy_hook(input_data: dict[str, Any], tool_use_id: str | None, context: Any) -> dict[str, Any]:
|
|
265
262
|
return {"continue_": True}
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
async def _prompt_stream() -> Any:
|
|
264
|
+
async def _prompt_stream():
|
|
270
265
|
yield {
|
|
271
266
|
"type": "user",
|
|
272
|
-
"message": {"role": "user", "content":
|
|
267
|
+
"message": {"role": "user", "content": self._build_auto_prompt()},
|
|
273
268
|
}
|
|
274
269
|
|
|
275
270
|
options = ClaudeAgentOptions(
|
|
@@ -278,9 +273,15 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
278
273
|
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[_dummy_hook])]},
|
|
279
274
|
cwd=str(self.scripts_dir.parent.parent), # Project root
|
|
280
275
|
model=self.model,
|
|
276
|
+
env={"CLAUDECODE": "", "CLAUDE_CODE_STREAM_CLOSE_TIMEOUT": "1800000"},
|
|
277
|
+
stderr=self._handle_cli_stderr,
|
|
281
278
|
)
|
|
282
279
|
|
|
280
|
+
final_result: dict[str, Any] | None = None
|
|
281
|
+
|
|
283
282
|
try:
|
|
283
|
+
# Do not break/return inside this loop — the SDK requires the
|
|
284
|
+
# async generator to be fully consumed to avoid cancel-scope errors.
|
|
284
285
|
async for message in query(prompt=_prompt_stream(), options=options):
|
|
285
286
|
# Check for usage metadata
|
|
286
287
|
if hasattr(message, "usage") and isinstance(message.usage, dict):
|
|
@@ -316,7 +317,6 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
316
317
|
if message.is_error:
|
|
317
318
|
self.ui.error(message.result or "Unknown error")
|
|
318
319
|
self.message_store.save_error(message.result or "Unknown error")
|
|
319
|
-
return None
|
|
320
320
|
else:
|
|
321
321
|
script_path = str(self.scripts_dir / self._get_client_filename())
|
|
322
322
|
local_path = str(self.local_scripts_dir / self._get_client_filename()) if self.local_scripts_dir else None
|
|
@@ -351,12 +351,11 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
351
351
|
self.ui.console.print(f" [dim] output: {output_tokens:,} tokens[/dim]")
|
|
352
352
|
self.ui.console.print(f" [dim] total cost: ${cost:.4f}[/dim]")
|
|
353
353
|
|
|
354
|
-
|
|
354
|
+
final_result = {
|
|
355
355
|
"script_path": script_path,
|
|
356
356
|
"usage": self.usage_metadata,
|
|
357
357
|
}
|
|
358
|
-
self.message_store.save_result(
|
|
359
|
-
return result
|
|
358
|
+
self.message_store.save_result(final_result)
|
|
360
359
|
|
|
361
360
|
except Exception as e:
|
|
362
361
|
error_msg = str(e)
|
|
@@ -375,7 +374,7 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
375
374
|
self.ui.console.print("\n[dim]Make sure Claude Code CLI is installed: npm install -g @anthropic-ai/claude-code[/dim]")
|
|
376
375
|
return None
|
|
377
376
|
|
|
378
|
-
return
|
|
377
|
+
return final_result
|
|
379
378
|
|
|
380
379
|
|
|
381
380
|
class OpenCodeAutoEngineer(OpenCodeEngineer):
|
|
@@ -404,7 +403,7 @@ class OpenCodeAutoEngineer(OpenCodeEngineer):
|
|
|
404
403
|
|
|
405
404
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
406
405
|
"""Run auto mode with OpenCode MCP integration."""
|
|
407
|
-
self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model)
|
|
406
|
+
self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model, mode="agent")
|
|
408
407
|
self.opencode_ui.start_analysis()
|
|
409
408
|
self.message_store.save_prompt(self._build_auto_prompt())
|
|
410
409
|
|
|
@@ -639,7 +638,7 @@ class CopilotAutoEngineer:
|
|
|
639
638
|
return None
|
|
640
639
|
|
|
641
640
|
eng = self._engineer
|
|
642
|
-
eng.ui.header(eng.run_id, eng.prompt, eng.copilot_model, eng.sdk)
|
|
641
|
+
eng.ui.header(eng.run_id, eng.prompt, eng.copilot_model, eng.sdk, mode="agent")
|
|
643
642
|
eng.ui.start_analysis()
|
|
644
643
|
|
|
645
644
|
auto_prompt = ClaudeAutoEngineer._build_auto_prompt(eng)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Abstract base class for API reverse engineering."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
@@ -12,6 +13,8 @@ from .sync import FileSyncWatcher, get_available_directory
|
|
|
12
13
|
from .tui import THEME_PRIMARY, THEME_SECONDARY, ClaudeUI
|
|
13
14
|
from .utils import generate_folder_name, get_docs_dir, get_history_path, get_scripts_dir
|
|
14
15
|
|
|
16
|
+
DEBUG = os.environ.get("DEBUG", "0") == "1"
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
class BaseEngineer(ABC):
|
|
17
20
|
"""Abstract base class for API reverse engineering implementations."""
|
|
@@ -60,6 +63,24 @@ class BaseEngineer(ABC):
|
|
|
60
63
|
self.existing_client_path = self._get_existing_client_path()
|
|
61
64
|
self.sync_watcher: FileSyncWatcher | None = None
|
|
62
65
|
self.local_scripts_dir: Path | None = None
|
|
66
|
+
self._stderr_error_shown = False
|
|
67
|
+
|
|
68
|
+
def _handle_cli_stderr(self, line: str) -> None:
|
|
69
|
+
"""Filter CLI subprocess stderr. Shows full output in DEBUG mode, otherwise shows a single clean error."""
|
|
70
|
+
if DEBUG:
|
|
71
|
+
self.ui.console.print(f"[dim] stderr: {line.rstrip()}[/dim]")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# Known noisy errors from the CLI control protocol — show once
|
|
75
|
+
if "Error in hook callback" in line or "Stream closed" in line:
|
|
76
|
+
if not self._stderr_error_shown:
|
|
77
|
+
self._stderr_error_shown = True
|
|
78
|
+
self.ui.console.print(" [dim]![/dim] [dim]cli stream error (set DEBUG=1 for details)[/dim]")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# Suppress other common noise (stack traces, source maps)
|
|
82
|
+
if line.startswith(" at ") or "| " in line[:20]:
|
|
83
|
+
return
|
|
63
84
|
|
|
64
85
|
def start_sync(self):
|
|
65
86
|
"""Start real-time file sync if enabled."""
|
|
@@ -532,16 +553,20 @@ Your OpenAPI spec should be production-ready and suitable for:
|
|
|
532
553
|
mode_description = f"reverse engineer API calls and generate production-ready {language_name} code that replicates"
|
|
533
554
|
task_description = f"{language_name} API client"
|
|
534
555
|
|
|
535
|
-
attempt_log_section =
|
|
536
|
-
"
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
556
|
+
attempt_log_section = (
|
|
557
|
+
""
|
|
558
|
+
if self.output_mode == "docs"
|
|
559
|
+
else (
|
|
560
|
+
"If your first attempt doesn't work, analyze what went wrong and try again. "
|
|
561
|
+
"Document each attempt and what you learned.\n\n"
|
|
562
|
+
"<attempt_log>\n"
|
|
563
|
+
"For each attempt (up to 5), document:\n"
|
|
564
|
+
"- Attempt number\n"
|
|
565
|
+
"- What approach you tried\n"
|
|
566
|
+
"- What error or issue occurred (if any)\n"
|
|
567
|
+
"- What you changed for the next attempt\n"
|
|
568
|
+
"</attempt_log>\n\n"
|
|
569
|
+
)
|
|
545
570
|
)
|
|
546
571
|
after_verb = "documenting" if self.output_mode == "docs" else "testing"
|
|
547
572
|
output_type = "spec" if self.output_mode == "docs" else "code"
|
|
@@ -210,11 +210,11 @@ class ManualBrowser:
|
|
|
210
210
|
self.output_dir = output_dir
|
|
211
211
|
self.use_real_chrome = use_real_chrome
|
|
212
212
|
self.enable_action_recording = enable_action_recording
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
self.har_dir = get_har_dir(run_id, output_dir)
|
|
215
215
|
self.har_path = self.har_dir / "recording.har"
|
|
216
216
|
self.metadata_path = self.har_dir / "metadata.json"
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
self._playwright = None
|
|
219
219
|
self._browser: Browser | None = None
|
|
220
220
|
self._context: BrowserContext | None = None
|
|
@@ -226,15 +226,15 @@ class ManualBrowser:
|
|
|
226
226
|
|
|
227
227
|
def _inject_action_recorder(self, page: Page) -> None:
|
|
228
228
|
"""Inject action recording script into page.
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
Uses console.log + page.on('console') for reliable capture.
|
|
231
231
|
Works best with stealth Chromium mode (not real Chrome).
|
|
232
232
|
"""
|
|
233
233
|
if not self.enable_action_recording:
|
|
234
234
|
return
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
# Simple JS that logs actions to console with a special prefix
|
|
237
|
-
recorder_js =
|
|
237
|
+
recorder_js = """
|
|
238
238
|
window.__recordedActions = [];
|
|
239
239
|
window.__lastUrl = null;
|
|
240
240
|
|
|
@@ -378,34 +378,35 @@ class ManualBrowser:
|
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
383
|
# Listen to console for actions
|
|
384
384
|
import json
|
|
385
|
+
|
|
385
386
|
last_url = [None] # Mutable to track last URL
|
|
386
|
-
|
|
387
|
+
|
|
387
388
|
def on_console(msg):
|
|
388
389
|
text = msg.text
|
|
389
|
-
if text.startswith(
|
|
390
|
+
if text.startswith("__ACTION__"):
|
|
390
391
|
try:
|
|
391
392
|
action_json = text[10:] # Remove '__ACTION__' prefix
|
|
392
393
|
action_data = json.loads(action_json)
|
|
393
|
-
|
|
394
|
+
|
|
394
395
|
# Filter duplicate navigations
|
|
395
|
-
if action_data.get(
|
|
396
|
-
url = action_data.get(
|
|
396
|
+
if action_data.get("type") == "navigate":
|
|
397
|
+
url = action_data.get("url", "")
|
|
397
398
|
if url == last_url[0]:
|
|
398
399
|
return # Skip duplicate
|
|
399
400
|
last_url[0] = url
|
|
400
|
-
|
|
401
|
+
|
|
401
402
|
if self.action_recorder:
|
|
402
403
|
self.action_recorder.add_action(RecordedAction(**action_data))
|
|
403
404
|
except Exception as e:
|
|
404
405
|
console.print(f" [dim]action parse error: {e}[/dim]")
|
|
405
|
-
|
|
406
|
+
|
|
406
407
|
page.on("console", on_console)
|
|
407
408
|
page.add_init_script(recorder_js)
|
|
408
|
-
|
|
409
|
+
|
|
409
410
|
console.print(" [dim]action recording enabled[/dim]")
|
|
410
411
|
|
|
411
412
|
def _save_metadata(self, end_time: str) -> None:
|
|
@@ -54,7 +54,7 @@ config_manager = ConfigManager(get_config_path())
|
|
|
54
54
|
session_manager = SessionManager(get_history_path())
|
|
55
55
|
|
|
56
56
|
# Mode definitions
|
|
57
|
-
MODES = ["
|
|
57
|
+
MODES = ["agent", "manual", "engineer", "collector"]
|
|
58
58
|
MODE_DESCRIPTIONS = {
|
|
59
59
|
"manual": "full pipeline",
|
|
60
60
|
"engineer": "reverse engineer only",
|
|
@@ -68,7 +68,7 @@ def prompt_interactive_options(
|
|
|
68
68
|
url: str | None = None,
|
|
69
69
|
reverse_engineer: bool | None = None,
|
|
70
70
|
model: str | None = None,
|
|
71
|
-
current_mode: str = "
|
|
71
|
+
current_mode: str = "agent",
|
|
72
72
|
) -> dict:
|
|
73
73
|
"""Prompt user for essential options interactively (Browgents style).
|
|
74
74
|
|
|
@@ -267,29 +267,15 @@ def prompt_interactive_options(
|
|
|
267
267
|
"model": model or config_manager.get("claude_code_model", "claude-sonnet-4-6"),
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
# Agent mode:
|
|
270
|
+
# Agent mode: autonomous browser, no URL needed (agent navigates on its own)
|
|
271
271
|
if result_mode == "agent":
|
|
272
|
-
if url is None:
|
|
273
|
-
try:
|
|
274
|
-
url = questionary.text(
|
|
275
|
-
" > url",
|
|
276
|
-
instruction="(Enter for none)",
|
|
277
|
-
qmark="",
|
|
278
|
-
style=questionary.Style(
|
|
279
|
-
[
|
|
280
|
-
("question", f"fg:{THEME_SECONDARY}"),
|
|
281
|
-
("instruction", f"fg:{THEME_DIM} italic"),
|
|
282
|
-
]
|
|
283
|
-
),
|
|
284
|
-
).ask()
|
|
285
|
-
if url is None: # questionary returns None on Ctrl+C
|
|
286
|
-
raise click.Abort()
|
|
287
|
-
except KeyboardInterrupt:
|
|
288
|
-
raise click.Abort()
|
|
289
|
-
|
|
290
272
|
if model is None:
|
|
291
273
|
model = config_manager.get("claude_code_model", "claude-sonnet-4-6")
|
|
292
274
|
|
|
275
|
+
mode_color = MODE_COLORS.get("agent", THEME_PRIMARY)
|
|
276
|
+
console = Console()
|
|
277
|
+
console.print(f" [{mode_color}]autonomous[/{mode_color}] [dim]agent will navigate on its own[/dim]")
|
|
278
|
+
|
|
293
279
|
return {
|
|
294
280
|
"mode": result_mode,
|
|
295
281
|
"prompt": prompt,
|
|
@@ -372,7 +358,7 @@ def repl_loop():
|
|
|
372
358
|
model = config_manager.get("claude_code_model", "claude-sonnet-4-6")
|
|
373
359
|
|
|
374
360
|
display_banner(console, sdk=sdk, model=model)
|
|
375
|
-
console.print(" [dim]shift+tab to cycle modes:
|
|
361
|
+
console.print(" [dim]shift+tab to cycle modes: agent | manual | engineer | collector[/dim]")
|
|
376
362
|
display_footer(console)
|
|
377
363
|
|
|
378
364
|
# Show update message if background check has completed
|
|
@@ -386,14 +372,14 @@ def repl_loop():
|
|
|
386
372
|
finally:
|
|
387
373
|
update_executor.shutdown(wait=False)
|
|
388
374
|
|
|
389
|
-
current_mode = "
|
|
375
|
+
current_mode = "agent"
|
|
390
376
|
|
|
391
377
|
while True:
|
|
392
378
|
try:
|
|
393
379
|
options = prompt_interactive_options(current_mode=current_mode)
|
|
394
380
|
|
|
395
381
|
# Update current mode for next iteration
|
|
396
|
-
current_mode = options.get("mode", "
|
|
382
|
+
current_mode = options.get("mode", "agent")
|
|
397
383
|
|
|
398
384
|
if "command" in options:
|
|
399
385
|
cmd = options["command"]
|
|
@@ -430,7 +416,7 @@ def repl_loop():
|
|
|
430
416
|
console.print(" [dim]Available commands: /settings, /history, /messages, /help, /exit[/dim]")
|
|
431
417
|
continue
|
|
432
418
|
|
|
433
|
-
mode = options.get("mode", "
|
|
419
|
+
mode = options.get("mode", "agent")
|
|
434
420
|
|
|
435
421
|
# Handle different modes
|
|
436
422
|
if mode == "engineer":
|
|
@@ -1106,9 +1092,9 @@ def handle_help(mode_color=THEME_PRIMARY):
|
|
|
1106
1092
|
modes_table.add_column(style=f"{mode_color} bold", justify="left", width=15)
|
|
1107
1093
|
modes_table.add_column(style="dim", justify="left")
|
|
1108
1094
|
|
|
1095
|
+
modes_table.add_row("agent", "Autonomous agent + capture")
|
|
1109
1096
|
modes_table.add_row("manual", "Full pipeline: browser + reverse engineering")
|
|
1110
1097
|
modes_table.add_row("engineer", "Reverse engineer only (enter run_id)")
|
|
1111
|
-
modes_table.add_row("agent", "Autonomous agent + capture")
|
|
1112
1098
|
|
|
1113
1099
|
console.print(" [bold white]Modes[/bold white] [dim]Shift+Tab to cycle[/dim]")
|
|
1114
1100
|
console.print(modes_table)
|
|
@@ -66,7 +66,7 @@ class Collector:
|
|
|
66
66
|
Returns:
|
|
67
67
|
Result dict with output_path and collected_items, or None on error
|
|
68
68
|
"""
|
|
69
|
-
self.ui.header(self.run_id, self.prompt, self.model)
|
|
69
|
+
self.ui.header(self.run_id, self.prompt, self.model, mode="collector")
|
|
70
70
|
self.ui.start_collecting()
|
|
71
71
|
|
|
72
72
|
self._folder_name = generate_folder_name(self.prompt)
|
|
@@ -288,7 +288,7 @@ When complete, briefly summarize what was collected.
|
|
|
288
288
|
def _export_readme(self, readme_path: Path, items: list[dict[str, Any]], sources: set[str]) -> None:
|
|
289
289
|
"""Generate README with collection metadata."""
|
|
290
290
|
folder_name = self._folder_name or "collection"
|
|
291
|
-
readme_content = f"""# {folder_name.replace(
|
|
291
|
+
readme_content = f"""# {folder_name.replace("_", " ").title()}
|
|
292
292
|
|
|
293
293
|
## Query
|
|
294
294
|
{self.prompt}
|
|
@@ -15,12 +15,12 @@ class CollectorUI:
|
|
|
15
15
|
self.verbose = verbose
|
|
16
16
|
self._items_collected = 0
|
|
17
17
|
|
|
18
|
-
def header(self, run_id: str, prompt: str, model: str | None = None) -> None:
|
|
18
|
+
def header(self, run_id: str, prompt: str, model: str | None = None, **kwargs) -> None:
|
|
19
19
|
"""Display the collector session header."""
|
|
20
20
|
from . import __version__
|
|
21
21
|
|
|
22
22
|
self.console.print()
|
|
23
|
-
self.console.print(f" [white]reverse-api[/white] [
|
|
23
|
+
self.console.print(f" [white]reverse-api[/white] [{COLLECTOR_COLOR}]v{__version__}[/{COLLECTOR_COLOR}]")
|
|
24
24
|
self.console.print(f" [dim]━[/dim] [white]{run_id}[/white]")
|
|
25
25
|
self.console.print(f" [{COLLECTOR_COLOR}]collector[/{COLLECTOR_COLOR}] [dim]|[/dim] [dim]model[/dim] [white]{model or '---'}[/white]")
|
|
26
26
|
self.console.print(f" [{COLLECTOR_COLOR}]task[/{COLLECTOR_COLOR}] [white]{prompt[:80]}{'...' if len(prompt) > 80 else ''}[/white]")
|
|
@@ -86,6 +86,7 @@ class CollectorUI:
|
|
|
86
86
|
def error(self, message: str) -> None:
|
|
87
87
|
"""Display error message."""
|
|
88
88
|
from .tui import ERROR_CTA
|
|
89
|
+
|
|
89
90
|
self.console.print()
|
|
90
91
|
self.console.print(f" [dim]![/dim] [red]error:[/red] {message}")
|
|
91
92
|
self.console.print(f" [dim]{ERROR_CTA}[/dim]")
|
{reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/copilot_engineer.py
RENAMED
|
@@ -69,7 +69,7 @@ class CopilotEngineer(BaseEngineer):
|
|
|
69
69
|
)
|
|
70
70
|
return None
|
|
71
71
|
|
|
72
|
-
self.ui.header(self.run_id, self.prompt, self.copilot_model, self.sdk)
|
|
72
|
+
self.ui.header(self.run_id, self.prompt, self.copilot_model, self.sdk, mode="engineer")
|
|
73
73
|
self.ui.start_analysis()
|
|
74
74
|
|
|
75
75
|
prompt = self._build_analysis_prompt()
|
|
@@ -28,9 +28,7 @@ logging.getLogger("claude_agent_sdk._internal.transport.subprocess_cli").setLeve
|
|
|
28
28
|
class ClaudeEngineer(BaseEngineer):
|
|
29
29
|
"""Uses Claude Agent SDK to analyze HAR files and generate Python API scripts."""
|
|
30
30
|
|
|
31
|
-
async def _handle_tool_permission(
|
|
32
|
-
self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext
|
|
33
|
-
) -> PermissionResultAllow:
|
|
31
|
+
async def _handle_tool_permission(self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext) -> PermissionResultAllow:
|
|
34
32
|
"""Handle tool permission requests, with interactive UI for AskUserQuestion."""
|
|
35
33
|
if tool_name == "AskUserQuestion":
|
|
36
34
|
questions = input_data.get("questions", [])
|
|
@@ -44,21 +42,19 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
44
42
|
|
|
45
43
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
46
44
|
"""Run the reverse engineering analysis with Claude."""
|
|
47
|
-
self.ui.header(self.run_id, self.prompt, self.model, self.sdk)
|
|
45
|
+
self.ui.header(self.run_id, self.prompt, self.model, self.sdk, mode="engineer")
|
|
48
46
|
self.ui.start_analysis()
|
|
49
47
|
self.message_store.save_prompt(self._build_analysis_prompt())
|
|
50
48
|
|
|
51
|
-
# Required
|
|
49
|
+
# Required: dummy hook keeps the stream open for can_use_tool
|
|
52
50
|
# See: https://platform.claude.com/docs/en/agent-sdk/user-input
|
|
53
51
|
async def _dummy_hook(input_data: dict[str, Any], tool_use_id: str | None, context: Any) -> dict[str, Any]:
|
|
54
52
|
return {"continue_": True}
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
async def _prompt_stream() -> Any:
|
|
54
|
+
async def _prompt_stream():
|
|
59
55
|
yield {
|
|
60
56
|
"type": "user",
|
|
61
|
-
"message": {"role": "user", "content":
|
|
57
|
+
"message": {"role": "user", "content": self._build_analysis_prompt()},
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
options = ClaudeAgentOptions(
|
|
@@ -66,9 +62,15 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
66
62
|
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[_dummy_hook])]},
|
|
67
63
|
cwd=str(self.scripts_dir.parent.parent), # Project root
|
|
68
64
|
model=self.model,
|
|
65
|
+
env={"CLAUDECODE": "", "CLAUDE_CODE_STREAM_CLOSE_TIMEOUT": "1800000"},
|
|
66
|
+
stderr=self._handle_cli_stderr,
|
|
69
67
|
)
|
|
70
68
|
|
|
69
|
+
final_result: dict[str, Any] | None = None
|
|
70
|
+
|
|
71
71
|
try:
|
|
72
|
+
# Do not break/return inside this loop — the SDK requires the
|
|
73
|
+
# async generator to be fully consumed to avoid cancel-scope errors.
|
|
72
74
|
async for message in query(prompt=_prompt_stream(), options=options):
|
|
73
75
|
# Check for usage metadata in message if applicable
|
|
74
76
|
if hasattr(message, "usage") and isinstance(message.usage, dict):
|
|
@@ -104,7 +106,6 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
104
106
|
if message.is_error:
|
|
105
107
|
self.ui.error(message.result or "Unknown error")
|
|
106
108
|
self.message_store.save_error(message.result or "Unknown error")
|
|
107
|
-
return None
|
|
108
109
|
else:
|
|
109
110
|
script_path = str(self.scripts_dir / self._get_client_filename())
|
|
110
111
|
local_path = str(self.local_scripts_dir / self._get_client_filename()) if self.local_scripts_dir else None
|
|
@@ -117,7 +118,6 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
117
118
|
cache_creation_tokens = self.usage_metadata.get("cache_creation_input_tokens", 0)
|
|
118
119
|
cache_read_tokens = self.usage_metadata.get("cache_read_input_tokens", 0)
|
|
119
120
|
|
|
120
|
-
# Calculate cost using shared pricing module
|
|
121
121
|
from .pricing import calculate_cost
|
|
122
122
|
|
|
123
123
|
cost = calculate_cost(
|
|
@@ -129,7 +129,6 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
129
129
|
)
|
|
130
130
|
self.usage_metadata["estimated_cost_usd"] = cost
|
|
131
131
|
|
|
132
|
-
# Display usage breakdown
|
|
133
132
|
self.ui.console.print(" [dim]Usage:[/dim]")
|
|
134
133
|
if input_tokens > 0:
|
|
135
134
|
self.ui.console.print(f" [dim] input: {input_tokens:,} tokens[/dim]")
|
|
@@ -141,12 +140,11 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
141
140
|
self.ui.console.print(f" [dim] output: {output_tokens:,} tokens[/dim]")
|
|
142
141
|
self.ui.console.print(f" [dim] total cost: ${cost:.4f}[/dim]")
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
final_result = {
|
|
145
144
|
"script_path": script_path,
|
|
146
145
|
"usage": self.usage_metadata,
|
|
147
146
|
}
|
|
148
|
-
self.message_store.save_result(
|
|
149
|
-
return result
|
|
147
|
+
self.message_store.save_result(final_result)
|
|
150
148
|
|
|
151
149
|
except Exception as e:
|
|
152
150
|
self.ui.error(str(e))
|
|
@@ -154,7 +152,7 @@ class ClaudeEngineer(BaseEngineer):
|
|
|
154
152
|
self.ui.console.print("\n[dim]Make sure Claude Code CLI is installed: npm install -g @anthropic-ai/claude-code[/dim]")
|
|
155
153
|
return None
|
|
156
154
|
|
|
157
|
-
return
|
|
155
|
+
return final_result
|
|
158
156
|
|
|
159
157
|
|
|
160
158
|
# Keep old class name for backwards compatibility
|
{reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.4}/src/reverse_api/opencode_engineer.py
RENAMED
|
@@ -136,7 +136,7 @@ class OpenCodeEngineer(BaseEngineer):
|
|
|
136
136
|
|
|
137
137
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
138
138
|
"""Run the reverse engineering analysis with OpenCode."""
|
|
139
|
-
self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model, self.sdk)
|
|
139
|
+
self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model, self.sdk, mode="engineer")
|
|
140
140
|
self.opencode_ui.start_analysis()
|
|
141
141
|
|
|
142
142
|
# Save the prompt to messages
|
|
@@ -555,7 +555,7 @@ class OpenCodeEngineer(BaseEngineer):
|
|
|
555
555
|
if part_type == "text":
|
|
556
556
|
text = part.get("text", "")
|
|
557
557
|
debug_log(f"Handling text part: id={part_id}, delta={'yes' if delta else 'no'}, len={len(text)}")
|
|
558
|
-
|
|
558
|
+
|
|
559
559
|
# Filter out known prompt text patterns that get echoed back
|
|
560
560
|
# This prevents the tag context section from appearing in streaming output
|
|
561
561
|
# Check for the specific tag context pattern that appears at the end of prompts
|
|
@@ -563,7 +563,7 @@ class OpenCodeEngineer(BaseEngineer):
|
|
|
563
563
|
if tag_context_pattern in text and "Note: Full message history is available" in text:
|
|
564
564
|
debug_log(f"Filtering out echoed tag context from streaming output")
|
|
565
565
|
return
|
|
566
|
-
|
|
566
|
+
|
|
567
567
|
# Use delta for incremental updates if available
|
|
568
568
|
self.opencode_ui.update_text(text, delta)
|
|
569
569
|
|