reverse-api-engineer 0.4.2__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.2 → reverse_api_engineer-0.4.4}/.claude/settings.local.json +10 -1
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/.gitignore +3 -1
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/CHANGELOG.md +17 -0
- {reverse_api_engineer-0.4.2 → 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.2 → reverse_api_engineer-0.4.4}/pyproject.toml +2 -2
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/auto_engineer.py +112 -100
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/base_engineer.py +35 -10
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/browser.py +16 -15
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/cli.py +12 -26
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/collector.py +2 -2
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/collector_ui.py +3 -2
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/copilot_engineer.py +1 -1
- reverse_api_engineer-0.4.4/src/reverse_api/engineer.py +253 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/opencode_engineer.py +3 -3
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/opencode_ui.py +8 -5
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/playwright_codegen.py +27 -24
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/tui.py +9 -7
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/uv.lock +977 -1001
- reverse_api_engineer-0.4.2/llm-docs/claude-agent-sdk/QUICKSTART.md +0 -2017
- reverse_api_engineer-0.4.2/llm-docs/claude-agent-sdk/TODO_LIST.md +0 -176
- reverse_api_engineer-0.4.2/llm-docs/claude-agent-sdk/TOOLS.md +0 -0
- reverse_api_engineer-0.4.2/src/reverse_api/engineer.py +0 -253
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/.claude-plugin/marketplace.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/.python-version +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/CLAUDE.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/CONTRIBUTING.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/INTERVIEW.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/LICENSE +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/PROMPT_STASH.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/RELEASING.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/assets/reverse-api-banner.svg +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/assets/reverse-api-engineer.gif +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/assets/reverse-api-logo.svg +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/.claude/settings.local.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/components.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/package-lock.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/package.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/postcss.config.js +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/_locales/en/messages.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-128.png +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-16.png +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-32.png +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/icons/icon-48.png +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/public/manifest.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/background/service-worker.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/agent-action.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/chat-input.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/icons.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/markdown-renderer.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/mode-selector.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/plan.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/session-selector.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/terminal.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/components/ui/code-block.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/content/codegen-recorder.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/index.css +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/capture.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/native-host.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/storage.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/shared/types.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/index.html +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/main.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/sidepanel/side-panel.tsx +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/src/vite-env.d.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/tailwind.config.js +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/tsconfig.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/chrome-extension/vite.config.ts +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/INDEX.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/extract_job_fields.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/main.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/quick_example.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/apple/requirements.txt +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/API_SUMMARY.txt +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/example_usage.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ashby/requirements.txt +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/autoscout24/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/autoscout24/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/autoscout24/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ikea/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/ikea/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/mintlify/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/mintlify/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/API_ANALYSIS_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/api_client.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/example_fetch_all_jobs.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/quick_start.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/examples/uber/requirements.txt +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/llm-docs/OPENCODE_API_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/llm-docs/opencode-api.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/.claude-plugin/plugin.json +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/LICENSE +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/README.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/agents/api-reverse-engineer.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/agent.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/capture.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/engineer.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/commands/manual.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/LICENSE +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/plugins/reverse-api-engineer/skills/reverse-engineering-api/SKILL.md +0 -0
- {reverse_api_engineer-0.4.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → reverse_api_engineer-0.4.4}/scripts/clean_build.sh +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/__init__.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/action_recorder.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/config.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/messages.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/native_host.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/pricing.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/session.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/sync.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/src/reverse_api/utils.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/__init__.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/conftest.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_action_recorder.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_auto_engineer.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_base_engineer.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_collector.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_collector_ui.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_config.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_engineer.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_init.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_messages.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_native_host.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_opencode_engineer.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_opencode_ui.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_pricing.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_session.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_sync.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_tui.py +0 -0
- {reverse_api_engineer-0.4.2 → reverse_api_engineer-0.4.4}/tests/test_utils.py +0 -0
|
@@ -111,7 +111,16 @@
|
|
|
111
111
|
"Bash(pip show:*)",
|
|
112
112
|
"Bash(npm uninstall:*)",
|
|
113
113
|
"Bash(npx rollup-plugin-visualizer:*)",
|
|
114
|
-
"Bash(git pull:*)"
|
|
114
|
+
"Bash(git pull:*)",
|
|
115
|
+
"WebFetch(domain:docs.anthropic.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)"
|
|
115
124
|
]
|
|
116
125
|
}
|
|
117
126
|
}
|
|
@@ -5,6 +5,23 @@ 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
|
+
|
|
20
|
+
## [0.4.3] - 2026-03-12
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **AskUserQuestion interactive prompt**: Fixed interactive questionary UI not rendering in engineer and agent modes. Switched from `ClaudeSDKClient` to `query()` function, as `ClaudeSDKClient` silently auto-approves `AskUserQuestion` without triggering the `can_use_tool` callback
|
|
24
|
+
|
|
8
25
|
## [0.4.2] - 2026-03-12
|
|
9
26
|
|
|
10
27
|
### 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",
|
|
@@ -11,11 +11,14 @@ import httpx
|
|
|
11
11
|
from claude_agent_sdk import (
|
|
12
12
|
AssistantMessage,
|
|
13
13
|
ClaudeAgentOptions,
|
|
14
|
-
|
|
14
|
+
HookMatcher,
|
|
15
|
+
PermissionResultAllow,
|
|
15
16
|
ResultMessage,
|
|
16
17
|
TextBlock,
|
|
18
|
+
ToolPermissionContext,
|
|
17
19
|
ToolResultBlock,
|
|
18
20
|
ToolUseBlock,
|
|
21
|
+
query,
|
|
19
22
|
)
|
|
20
23
|
|
|
21
24
|
from .engineer import ClaudeEngineer
|
|
@@ -226,9 +229,20 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
226
229
|
- Any limitations or caveats
|
|
227
230
|
"""
|
|
228
231
|
|
|
232
|
+
async def _handle_tool_permission(self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext) -> PermissionResultAllow:
|
|
233
|
+
"""Handle tool permission requests, with interactive UI for AskUserQuestion."""
|
|
234
|
+
if tool_name == "AskUserQuestion":
|
|
235
|
+
questions = input_data.get("questions", [])
|
|
236
|
+
answers = await self._ask_user_interactive(questions)
|
|
237
|
+
return PermissionResultAllow(
|
|
238
|
+
updated_input={"questions": questions, "answers": answers},
|
|
239
|
+
)
|
|
240
|
+
# Auto-approve all other tools
|
|
241
|
+
return PermissionResultAllow(updated_input=input_data)
|
|
242
|
+
|
|
229
243
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
230
244
|
"""Run auto mode with MCP browser integration."""
|
|
231
|
-
self.ui.header(self.run_id, self.prompt, self.model)
|
|
245
|
+
self.ui.header(self.run_id, self.prompt, self.model, mode="agent")
|
|
232
246
|
self.ui.start_analysis()
|
|
233
247
|
self.message_store.save_prompt(self._build_auto_prompt())
|
|
234
248
|
|
|
@@ -243,126 +257,124 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
243
257
|
],
|
|
244
258
|
}
|
|
245
259
|
|
|
260
|
+
# Required: dummy hook keeps the stream open for can_use_tool
|
|
261
|
+
async def _dummy_hook(input_data: dict[str, Any], tool_use_id: str | None, context: Any) -> dict[str, Any]:
|
|
262
|
+
return {"continue_": True}
|
|
263
|
+
|
|
264
|
+
async def _prompt_stream():
|
|
265
|
+
yield {
|
|
266
|
+
"type": "user",
|
|
267
|
+
"message": {"role": "user", "content": self._build_auto_prompt()},
|
|
268
|
+
}
|
|
269
|
+
|
|
246
270
|
options = ClaudeAgentOptions(
|
|
247
271
|
mcp_servers={"playwright": mcp_config},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"Read",
|
|
251
|
-
"Write",
|
|
252
|
-
"Bash",
|
|
253
|
-
"Glob",
|
|
254
|
-
"Grep",
|
|
255
|
-
"WebSearch",
|
|
256
|
-
"WebFetch",
|
|
257
|
-
],
|
|
272
|
+
can_use_tool=self._handle_tool_permission,
|
|
273
|
+
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[_dummy_hook])]},
|
|
258
274
|
cwd=str(self.scripts_dir.parent.parent), # Project root
|
|
259
275
|
model=self.model,
|
|
276
|
+
env={"CLAUDECODE": "", "CLAUDE_CODE_STREAM_CLOSE_TIMEOUT": "1800000"},
|
|
277
|
+
stderr=self._handle_cli_stderr,
|
|
260
278
|
)
|
|
261
279
|
|
|
280
|
+
final_result: dict[str, Any] | None = None
|
|
281
|
+
|
|
262
282
|
try:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
#
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
output =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
self.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
result: dict[str, Any] = {
|
|
340
|
-
"script_path": script_path,
|
|
341
|
-
"usage": self.usage_metadata,
|
|
342
|
-
}
|
|
343
|
-
self.message_store.save_result(result)
|
|
344
|
-
return result
|
|
283
|
+
# Do not break/return inside this loop — the SDK requires the
|
|
284
|
+
# async generator to be fully consumed to avoid cancel-scope errors.
|
|
285
|
+
async for message in query(prompt=_prompt_stream(), options=options):
|
|
286
|
+
# Check for usage metadata
|
|
287
|
+
if hasattr(message, "usage") and isinstance(message.usage, dict):
|
|
288
|
+
self.usage_metadata.update(message.usage)
|
|
289
|
+
|
|
290
|
+
if isinstance(message, AssistantMessage):
|
|
291
|
+
last_tool_name = None
|
|
292
|
+
for block in message.content:
|
|
293
|
+
if isinstance(block, ToolUseBlock):
|
|
294
|
+
last_tool_name = block.name
|
|
295
|
+
self.ui.tool_start(block.name, block.input)
|
|
296
|
+
self.message_store.save_tool_start(block.name, block.input)
|
|
297
|
+
elif isinstance(block, ToolResultBlock):
|
|
298
|
+
is_error = block.is_error if block.is_error else False
|
|
299
|
+
|
|
300
|
+
# Extract output from ToolResultBlock
|
|
301
|
+
output = None
|
|
302
|
+
if hasattr(block, "content"):
|
|
303
|
+
output = block.content
|
|
304
|
+
elif hasattr(block, "result"):
|
|
305
|
+
output = block.result
|
|
306
|
+
elif hasattr(block, "output"):
|
|
307
|
+
output = block.output
|
|
308
|
+
|
|
309
|
+
tool_name = last_tool_name or "Tool"
|
|
310
|
+
self.ui.tool_result(tool_name, is_error, output)
|
|
311
|
+
self.message_store.save_tool_result(tool_name, is_error, str(output) if output else None)
|
|
312
|
+
elif isinstance(block, TextBlock):
|
|
313
|
+
self.ui.thinking(block.text)
|
|
314
|
+
self.message_store.save_thinking(block.text)
|
|
315
|
+
|
|
316
|
+
elif isinstance(message, ResultMessage):
|
|
317
|
+
if message.is_error:
|
|
318
|
+
self.ui.error(message.result or "Unknown error")
|
|
319
|
+
self.message_store.save_error(message.result or "Unknown error")
|
|
320
|
+
else:
|
|
321
|
+
script_path = str(self.scripts_dir / self._get_client_filename())
|
|
322
|
+
local_path = str(self.local_scripts_dir / self._get_client_filename()) if self.local_scripts_dir else None
|
|
323
|
+
self.ui.success(script_path, local_path)
|
|
324
|
+
|
|
325
|
+
# Calculate estimated cost if we have usage data
|
|
326
|
+
if self.usage_metadata:
|
|
327
|
+
input_tokens = self.usage_metadata.get("input_tokens", 0)
|
|
328
|
+
output_tokens = self.usage_metadata.get("output_tokens", 0)
|
|
329
|
+
cache_creation_tokens = self.usage_metadata.get("cache_creation_input_tokens", 0)
|
|
330
|
+
cache_read_tokens = self.usage_metadata.get("cache_read_input_tokens", 0)
|
|
331
|
+
|
|
332
|
+
from .pricing import calculate_cost
|
|
333
|
+
|
|
334
|
+
cost = calculate_cost(
|
|
335
|
+
model_id=self.model,
|
|
336
|
+
input_tokens=input_tokens,
|
|
337
|
+
output_tokens=output_tokens,
|
|
338
|
+
cache_creation_tokens=cache_creation_tokens,
|
|
339
|
+
cache_read_tokens=cache_read_tokens,
|
|
340
|
+
)
|
|
341
|
+
self.usage_metadata["estimated_cost_usd"] = cost
|
|
342
|
+
|
|
343
|
+
self.ui.console.print(" [dim]Usage:[/dim]")
|
|
344
|
+
if input_tokens > 0:
|
|
345
|
+
self.ui.console.print(f" [dim] input: {input_tokens:,} tokens[/dim]")
|
|
346
|
+
if cache_creation_tokens > 0:
|
|
347
|
+
self.ui.console.print(f" [dim] cache creation: {cache_creation_tokens:,} tokens[/dim]")
|
|
348
|
+
if cache_read_tokens > 0:
|
|
349
|
+
self.ui.console.print(f" [dim] cache read: {cache_read_tokens:,} tokens[/dim]")
|
|
350
|
+
if output_tokens > 0:
|
|
351
|
+
self.ui.console.print(f" [dim] output: {output_tokens:,} tokens[/dim]")
|
|
352
|
+
self.ui.console.print(f" [dim] total cost: ${cost:.4f}[/dim]")
|
|
353
|
+
|
|
354
|
+
final_result = {
|
|
355
|
+
"script_path": script_path,
|
|
356
|
+
"usage": self.usage_metadata,
|
|
357
|
+
}
|
|
358
|
+
self.message_store.save_result(final_result)
|
|
345
359
|
|
|
346
360
|
except Exception as e:
|
|
347
361
|
error_msg = str(e)
|
|
348
362
|
self.ui.error(error_msg)
|
|
349
363
|
self.message_store.save_error(error_msg)
|
|
350
364
|
|
|
351
|
-
# Handle screenshot buffer size errors specifically
|
|
352
365
|
if "buffer size" in error_msg.lower() or "1048576" in error_msg or "exceeded maximum buffer" in error_msg.lower():
|
|
353
|
-
self.ui.console.print("\n[yellow]
|
|
366
|
+
self.ui.console.print("\n[yellow]Screenshot too large (exceeds 1MB limit)[/yellow]")
|
|
354
367
|
self.ui.console.print("[dim]Tip: The AI should take element-specific screenshots instead of full-page screenshots.[/dim]")
|
|
355
368
|
self.ui.console.print(
|
|
356
369
|
"[dim]Consider using browser_snapshot() for accessibility tree information when screenshots aren't needed.[/dim]"
|
|
357
370
|
)
|
|
358
|
-
# Provide helpful error messages
|
|
359
371
|
elif "MCP server" in error_msg or "npx" in error_msg:
|
|
360
372
|
self.ui.console.print("\n[dim]Make sure rae-playwright-mcp is installed: npm install -g rae-playwright-mcp[/dim]")
|
|
361
373
|
else:
|
|
362
374
|
self.ui.console.print("\n[dim]Make sure Claude Code CLI is installed: npm install -g @anthropic-ai/claude-code[/dim]")
|
|
363
375
|
return None
|
|
364
376
|
|
|
365
|
-
return
|
|
377
|
+
return final_result
|
|
366
378
|
|
|
367
379
|
|
|
368
380
|
class OpenCodeAutoEngineer(OpenCodeEngineer):
|
|
@@ -391,7 +403,7 @@ class OpenCodeAutoEngineer(OpenCodeEngineer):
|
|
|
391
403
|
|
|
392
404
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
393
405
|
"""Run auto mode with OpenCode MCP integration."""
|
|
394
|
-
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")
|
|
395
407
|
self.opencode_ui.start_analysis()
|
|
396
408
|
self.message_store.save_prompt(self._build_auto_prompt())
|
|
397
409
|
|
|
@@ -626,7 +638,7 @@ class CopilotAutoEngineer:
|
|
|
626
638
|
return None
|
|
627
639
|
|
|
628
640
|
eng = self._engineer
|
|
629
|
-
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")
|
|
630
642
|
eng.ui.start_analysis()
|
|
631
643
|
|
|
632
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}
|