reverse-api-engineer 0.4.4__tar.gz → 0.5.0__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.4 → reverse_api_engineer-0.5.0}/CHANGELOG.md +20 -3
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/PKG-INFO +1 -6
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/pyproject.toml +1 -6
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/auto_engineer.py +33 -99
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/base_engineer.py +56 -3
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/cli.py +76 -5
- reverse_api_engineer-0.5.0/src/reverse_api/engineer.py +292 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/utils.py +1 -1
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_engineer.py +267 -127
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/uv.lock +1 -159
- reverse_api_engineer-0.4.4/src/reverse_api/engineer.py +0 -253
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/.claude/settings.local.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/.claude-plugin/marketplace.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/.gitignore +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/.python-version +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/CLAUDE.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/CONTRIBUTING.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/INTERVIEW.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/LICENSE +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/PROMPT_STASH.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/RELEASING.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/assets/reverse-api-banner.svg +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/assets/reverse-api-engineer.gif +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/assets/reverse-api-logo.svg +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/.claude/settings.local.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/components.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/package-lock.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/package.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/packed/reverse-api-engineer-chrome.zip +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/postcss.config.js +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/_locales/en/messages.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/icons/icon-128.png +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/icons/icon-16.png +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/icons/icon-32.png +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/icons/icon-48.png +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/public/manifest.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/background/service-worker.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/agent-action.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/chat-input.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/icons.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/markdown-renderer.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/mode-selector.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/plan.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/session-selector.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/terminal.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/components/ui/code-block.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/content/codegen-recorder.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/index.css +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/shared/capture.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/shared/native-host.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/shared/storage.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/shared/types.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/sidepanel/index.html +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/sidepanel/main.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/sidepanel/side-panel.tsx +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/src/vite-env.d.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/store-assets/screenshot-1280x800.png +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/tailwind.config.js +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/tsconfig.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/chrome-extension/vite.config.ts +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/INDEX.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/extract_job_fields.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/main.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/quick_example.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/apple/requirements.txt +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/API_SUMMARY.txt +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/QUICKSTART.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/example_usage.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ashby/requirements.txt +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/autoscout24/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/autoscout24/SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/autoscout24/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ikea/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/ikea/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/mintlify/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/mintlify/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/API_ANALYSIS_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/example_fetch_all_jobs.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/quick_start.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/examples/uber/requirements.txt +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/llm-docs/OPENCODE_API_SUMMARY.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/llm-docs/opencode-api.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/.claude-plugin/plugin.json +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/LICENSE +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/README.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/agents/api-reverse-engineer.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/commands/agent.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/commands/capture.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/commands/engineer.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/commands/manual.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/CHANGELOG.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/LICENSE +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/SKILL.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/AUTH_PATTERNS.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/HAR_ANALYSIS.md +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_analyze.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_filter.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_utils.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_validate.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/plugins/reverse-api-engineer/skills/reverse-engineering-api/templates/api_client.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/scripts/clean_build.sh +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/__init__.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/action_recorder.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/browser.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/collector.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/collector_ui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/config.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/copilot_engineer.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/messages.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/native_host.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/opencode_engineer.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/opencode_ui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/playwright_codegen.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/pricing.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/session.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/sync.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/src/reverse_api/tui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/__init__.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/conftest.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_action_recorder.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_auto_engineer.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_base_engineer.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_collector.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_collector_ui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_config.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_init.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_messages.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_native_host.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_opencode_engineer.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_opencode_ui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_pricing.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_session.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_sync.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_tui.py +0 -0
- {reverse_api_engineer-0.4.4 → reverse_api_engineer-0.5.0}/tests/test_utils.py +0 -0
|
@@ -5,11 +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.
|
|
8
|
+
## [0.5.0] - 2026-03-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Follow-up chat**: After a run completes, type follow-up messages to iterate in the same session without creating new run IDs or folders. Press Enter to finish
|
|
12
|
+
- **Abort run (Ctrl+C)**: Gracefully cancel a running agent/engineer session and return to the REPL instead of exiting the app
|
|
13
|
+
- **AskUserQuestion free mode**: All select/checkbox prompts now include "Other (type your answer)" so users can always provide free-text input. Updated agent prompt to document free-form, multi-select, and multi-question capabilities
|
|
14
|
+
- **Random task suggestions (Ctrl+R)**: Press Ctrl+R in agent mode to fill the prompt with a random curated task idea. Press again to cycle
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Usage tracking across follow-ups**: Token counts now accumulate across all turns in a session instead of being overwritten by the last turn
|
|
18
|
+
- **Agent mode follow-up**: Auto engineer (agent mode) was using a copy-pasted streaming loop without follow-up support; now reuses the shared conversation loop
|
|
19
|
+
|
|
20
|
+
## [0.4.5] - 2026-03-15
|
|
9
21
|
|
|
10
22
|
### Fixed
|
|
11
|
-
- **Stream closed errors (#51)**:
|
|
12
|
-
- **
|
|
23
|
+
- **Stream closed errors (#51)**: Reverted from `query()` to `ClaudeSDKClient` which maintains a persistent bidirectional connection, eliminating "Error in hook callback hook_0: Stream closed" errors. The original AUQ fix (v0.4.3) unnecessarily switched APIs — only the `can_use_tool` callback signature needed updating
|
|
24
|
+
- **CLAUDECODE env var leak**: Clear inherited `CLAUDECODE` env var from CLI subprocess to prevent nested session interference when running inside Claude Code
|
|
13
25
|
- **CLI stderr noise**: Filter minified JS stack traces into a single clean error line (use `DEBUG=1` for full output)
|
|
14
26
|
|
|
15
27
|
### Changed
|
|
@@ -17,6 +29,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
29
|
- **Agent mode**: No longer prompts for URL (agent navigates autonomously)
|
|
18
30
|
- **Header UI**: Version and task labels now use mode-specific colors (agent=coral, engineer=blue, collector=gold)
|
|
19
31
|
|
|
32
|
+
## [0.4.4] - 2026-03-15
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **Stream closed errors (#51)**: Partial fix using `query()` with env var workarounds (superseded by v0.4.5)
|
|
36
|
+
|
|
20
37
|
## [0.4.3] - 2026-03-12
|
|
21
38
|
|
|
22
39
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reverse-api-engineer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -19,18 +19,13 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
|
-
Requires-Dist: aiohttp>=3.12.15
|
|
23
|
-
Requires-Dist: anthropic>=0.40.0
|
|
24
|
-
Requires-Dist: brotli>=1.2.0
|
|
25
22
|
Requires-Dist: claude-agent-sdk>=0.1.48
|
|
26
23
|
Requires-Dist: click>=8.1.0
|
|
27
24
|
Requires-Dist: playwright-stealth>=1.0.0
|
|
28
25
|
Requires-Dist: playwright>=1.40.0
|
|
29
26
|
Requires-Dist: questionary>=2.0.0
|
|
30
|
-
Requires-Dist: requests>=2.32.5
|
|
31
27
|
Requires-Dist: rich>=13.0.0
|
|
32
28
|
Requires-Dist: setproctitle>=1.3.0
|
|
33
|
-
Requires-Dist: sprites-py>=0.0.1a1
|
|
34
29
|
Requires-Dist: watchdog>=3.0.0
|
|
35
30
|
Provides-Extra: agent
|
|
36
31
|
Requires-Dist: stagehand; extra == 'agent'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "reverse-api-engineer"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A tool to capture browser traffic for API reverse engineering"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -27,13 +27,8 @@ dependencies = [
|
|
|
27
27
|
"claude-agent-sdk>=0.1.48",
|
|
28
28
|
"rich>=13.0.0",
|
|
29
29
|
"questionary>=2.0.0",
|
|
30
|
-
"requests>=2.32.5",
|
|
31
|
-
"anthropic>=0.40.0",
|
|
32
|
-
"brotli>=1.2.0",
|
|
33
|
-
"aiohttp>=3.12.15",
|
|
34
30
|
"watchdog>=3.0.0",
|
|
35
31
|
"setproctitle>=1.3.0",
|
|
36
|
-
"sprites-py>=0.0.1a1",
|
|
37
32
|
]
|
|
38
33
|
|
|
39
34
|
[project.optional-dependencies]
|
|
@@ -9,16 +9,10 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
from claude_agent_sdk import (
|
|
12
|
-
AssistantMessage,
|
|
13
12
|
ClaudeAgentOptions,
|
|
14
|
-
|
|
13
|
+
ClaudeSDKClient,
|
|
15
14
|
PermissionResultAllow,
|
|
16
|
-
ResultMessage,
|
|
17
|
-
TextBlock,
|
|
18
15
|
ToolPermissionContext,
|
|
19
|
-
ToolResultBlock,
|
|
20
|
-
ToolUseBlock,
|
|
21
|
-
query,
|
|
22
16
|
)
|
|
23
17
|
|
|
24
18
|
from .engineer import ClaudeEngineer
|
|
@@ -241,7 +235,10 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
241
235
|
return PermissionResultAllow(updated_input=input_data)
|
|
242
236
|
|
|
243
237
|
async def analyze_and_generate(self) -> dict[str, Any] | None:
|
|
244
|
-
"""Run auto mode with MCP browser integration.
|
|
238
|
+
"""Run auto mode with MCP browser integration.
|
|
239
|
+
|
|
240
|
+
Reuses _process_streaming_response and follow-up loop from ClaudeEngineer.
|
|
241
|
+
"""
|
|
245
242
|
self.ui.header(self.run_id, self.prompt, self.model, mode="agent")
|
|
246
243
|
self.ui.start_analysis()
|
|
247
244
|
self.message_store.save_prompt(self._build_auto_prompt())
|
|
@@ -257,105 +254,44 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
257
254
|
],
|
|
258
255
|
}
|
|
259
256
|
|
|
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
|
-
|
|
270
257
|
options = ClaudeAgentOptions(
|
|
271
258
|
mcp_servers={"playwright": mcp_config},
|
|
259
|
+
permission_mode="bypassPermissions",
|
|
272
260
|
can_use_tool=self._handle_tool_permission,
|
|
273
|
-
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[_dummy_hook])]},
|
|
274
261
|
cwd=str(self.scripts_dir.parent.parent), # Project root
|
|
275
262
|
model=self.model,
|
|
276
|
-
env={"CLAUDECODE": ""
|
|
263
|
+
env={"CLAUDECODE": ""},
|
|
277
264
|
stderr=self._handle_cli_stderr,
|
|
278
265
|
)
|
|
279
266
|
|
|
280
|
-
|
|
267
|
+
last_result: dict[str, Any] | None = None
|
|
281
268
|
|
|
282
269
|
try:
|
|
283
|
-
|
|
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
|
-
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)
|
|
270
|
+
async with ClaudeSDKClient(options=options) as client:
|
|
271
|
+
await client.query(self._build_auto_prompt())
|
|
272
|
+
|
|
273
|
+
# Process initial response
|
|
274
|
+
last_result = await self._process_streaming_response(client)
|
|
275
|
+
if last_result is None:
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
# Conversation loop: prompt for follow-ups
|
|
279
|
+
while True:
|
|
280
|
+
follow_up = await self._prompt_follow_up()
|
|
281
|
+
if not follow_up:
|
|
282
|
+
return last_result
|
|
283
|
+
|
|
284
|
+
self.ui.console.print()
|
|
285
|
+
self.message_store.save_prompt(follow_up)
|
|
286
|
+
await client.query(follow_up)
|
|
287
|
+
|
|
288
|
+
result = await self._process_streaming_response(client)
|
|
289
|
+
if result is not None:
|
|
290
|
+
last_result = result
|
|
291
|
+
|
|
292
|
+
except KeyboardInterrupt:
|
|
293
|
+
self.ui.console.print("\n [dim]run aborted[/dim]")
|
|
294
|
+
return last_result
|
|
359
295
|
|
|
360
296
|
except Exception as e:
|
|
361
297
|
error_msg = str(e)
|
|
@@ -374,8 +310,6 @@ Your final response should confirm the files were created and provide a brief su
|
|
|
374
310
|
self.ui.console.print("\n[dim]Make sure Claude Code CLI is installed: npm install -g @anthropic-ai/claude-code[/dim]")
|
|
375
311
|
return None
|
|
376
312
|
|
|
377
|
-
return final_result
|
|
378
|
-
|
|
379
313
|
|
|
380
314
|
class OpenCodeAutoEngineer(OpenCodeEngineer):
|
|
381
315
|
"""Auto mode using OpenCode SDK: Register MCP server dynamically."""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Abstract base class for API reverse engineering."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import os
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from pathlib import Path
|
|
@@ -15,6 +16,8 @@ from .utils import generate_folder_name, get_docs_dir, get_history_path, get_scr
|
|
|
15
16
|
|
|
16
17
|
DEBUG = os.environ.get("DEBUG", "0") == "1"
|
|
17
18
|
|
|
19
|
+
OTHER_OPTION = "Other (type your answer)"
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class BaseEngineer(ABC):
|
|
20
23
|
"""Abstract base class for API reverse engineering implementations."""
|
|
@@ -172,6 +175,7 @@ class BaseEngineer(ABC):
|
|
|
172
175
|
for opt in options
|
|
173
176
|
]
|
|
174
177
|
if choices:
|
|
178
|
+
choices.append(OTHER_OPTION)
|
|
175
179
|
selected = await questionary.checkbox(
|
|
176
180
|
f" > {question_text}",
|
|
177
181
|
choices=choices,
|
|
@@ -188,7 +192,20 @@ class BaseEngineer(ABC):
|
|
|
188
192
|
if selected is None:
|
|
189
193
|
raise KeyboardInterrupt
|
|
190
194
|
|
|
191
|
-
|
|
195
|
+
has_other = OTHER_OPTION in selected
|
|
196
|
+
labels = [s.split(" - ")[0] if " - " in s else s for s in selected if s != OTHER_OPTION]
|
|
197
|
+
|
|
198
|
+
if has_other:
|
|
199
|
+
other_text = await questionary.text(
|
|
200
|
+
" > Your answer: ",
|
|
201
|
+
qmark="",
|
|
202
|
+
style=questionary.Style([("question", f"fg:{THEME_SECONDARY}")]),
|
|
203
|
+
).ask_async()
|
|
204
|
+
if other_text is None:
|
|
205
|
+
raise KeyboardInterrupt
|
|
206
|
+
if other_text.strip():
|
|
207
|
+
labels.append(other_text.strip())
|
|
208
|
+
|
|
192
209
|
answers[question_text] = ", ".join(labels)
|
|
193
210
|
else:
|
|
194
211
|
answer = await questionary.text(
|
|
@@ -207,6 +224,7 @@ class BaseEngineer(ABC):
|
|
|
207
224
|
for opt in options
|
|
208
225
|
]
|
|
209
226
|
if choices:
|
|
227
|
+
choices.append(OTHER_OPTION)
|
|
210
228
|
answer = await questionary.select(
|
|
211
229
|
f" > {question_text}",
|
|
212
230
|
choices=choices,
|
|
@@ -222,8 +240,18 @@ class BaseEngineer(ABC):
|
|
|
222
240
|
if answer is None:
|
|
223
241
|
raise KeyboardInterrupt
|
|
224
242
|
|
|
225
|
-
|
|
226
|
-
|
|
243
|
+
if answer == OTHER_OPTION:
|
|
244
|
+
answer = await questionary.text(
|
|
245
|
+
" > Your answer: ",
|
|
246
|
+
qmark="",
|
|
247
|
+
style=questionary.Style([("question", f"fg:{THEME_SECONDARY}")]),
|
|
248
|
+
).ask_async()
|
|
249
|
+
if answer is None:
|
|
250
|
+
raise KeyboardInterrupt
|
|
251
|
+
answers[question_text] = answer.strip()
|
|
252
|
+
else:
|
|
253
|
+
label = answer.split(" - ")[0] if " - " in answer else answer
|
|
254
|
+
answers[question_text] = label
|
|
227
255
|
else:
|
|
228
256
|
answer = await questionary.text(
|
|
229
257
|
f" > {question_text}",
|
|
@@ -243,6 +271,23 @@ class BaseEngineer(ABC):
|
|
|
243
271
|
self.ui.console.print()
|
|
244
272
|
return answers
|
|
245
273
|
|
|
274
|
+
async def _prompt_follow_up(self) -> str | None:
|
|
275
|
+
"""Prompt user for a follow-up message. Returns None to finish.
|
|
276
|
+
|
|
277
|
+
Uses plain input() via executor instead of questionary to avoid
|
|
278
|
+
terminal state issues after the SDK subprocess exits.
|
|
279
|
+
"""
|
|
280
|
+
self.ui.console.print()
|
|
281
|
+
self.ui.console.print(f" [{THEME_PRIMARY}]─[/{THEME_PRIMARY}] [dim]type a follow-up or press Enter to finish[/dim]")
|
|
282
|
+
try:
|
|
283
|
+
loop = asyncio.get_event_loop()
|
|
284
|
+
answer = await loop.run_in_executor(None, lambda: input(" > "))
|
|
285
|
+
if not answer or not answer.strip():
|
|
286
|
+
return None
|
|
287
|
+
return answer.strip()
|
|
288
|
+
except (KeyboardInterrupt, EOFError):
|
|
289
|
+
return None
|
|
290
|
+
|
|
246
291
|
@staticmethod
|
|
247
292
|
def _get_opt_field(opt: Any, field: str) -> str:
|
|
248
293
|
"""Get a field from an option, supporting both dict and object access."""
|
|
@@ -593,6 +638,14 @@ Here is the output directory where you should save your generated files:
|
|
|
593
638
|
**IMPORTANT: You have access to the AskUserQuestion tool to ask clarifying questions during your analysis.**
|
|
594
639
|
Use this tool when you need to clarify functional requirements, prioritize features, choose between implementation approaches, or gather any other information that would help you generate better {task_description}.
|
|
595
640
|
|
|
641
|
+
The AskUserQuestion tool supports:
|
|
642
|
+
- **Multiple questions** in a single call (list of question objects)
|
|
643
|
+
- **Single-select** questions with predefined options (user picks one, or types a custom answer)
|
|
644
|
+
- **Multi-select** questions with predefined options (user picks multiple, or adds a custom answer)
|
|
645
|
+
- **Free-form** questions with no options (user types their answer freely)
|
|
646
|
+
|
|
647
|
+
Use free-form questions (empty options list) for open-ended input like URLs, credentials, descriptions, or any question where predefined choices don't make sense.
|
|
648
|
+
|
|
596
649
|
Your task is to:
|
|
597
650
|
|
|
598
651
|
1. **Read and analyze the HAR file** to understand all API calls that were captured. Look for:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
import random
|
|
3
4
|
import re
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -62,6 +63,29 @@ MODE_DESCRIPTIONS = {
|
|
|
62
63
|
"collector": "ai-powered data collection",
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
AGENT_TASK_SUGGESTIONS = [
|
|
67
|
+
"Go to github.com/trending and capture the top 10 trending repos' API calls",
|
|
68
|
+
"Navigate to news.ycombinator.com, browse the front page and capture API interactions",
|
|
69
|
+
"Go to weather.com, search for New York weather and capture the forecast API",
|
|
70
|
+
"Visit reddit.com/r/programming, browse posts and capture the Reddit API calls",
|
|
71
|
+
"Go to maps.google.com, search for restaurants near Times Square and capture API calls",
|
|
72
|
+
"Navigate to twitter.com/explore and capture trending topics API interactions",
|
|
73
|
+
"Go to amazon.com, search for 'mechanical keyboard' and capture product search API",
|
|
74
|
+
"Visit spotify.com/search, search for an artist and capture the search API",
|
|
75
|
+
"Navigate to stackoverflow.com, search for 'python async' and capture the search API",
|
|
76
|
+
"Go to npmjs.com, search for 'express' and capture the package registry API",
|
|
77
|
+
"Visit producthunt.com and capture the feed/listing API calls",
|
|
78
|
+
"Go to crunchbase.com and browse company profiles to capture their API",
|
|
79
|
+
"Navigate to linkedin.com/jobs, search for 'software engineer' and capture job search API",
|
|
80
|
+
"Go to airbnb.com, search for stays in Paris and capture the listing search API",
|
|
81
|
+
"Visit imdb.com, search for a movie and capture the title/search API calls",
|
|
82
|
+
"Go to wolframalpha.com, run a query and capture the computation API",
|
|
83
|
+
"Navigate to booking.com, search for hotels in Tokyo and capture the search API",
|
|
84
|
+
"Go to zillow.com, search for homes in San Francisco and capture the listing API",
|
|
85
|
+
"Visit translate.google.com, translate a paragraph and capture the translation API",
|
|
86
|
+
"Go to unsplash.com, search for 'mountains' and capture the photo search API",
|
|
87
|
+
]
|
|
88
|
+
|
|
65
89
|
|
|
66
90
|
def prompt_interactive_options(
|
|
67
91
|
prompt: str | None = None,
|
|
@@ -217,6 +241,15 @@ def prompt_interactive_options(
|
|
|
217
241
|
# If no completion, just move cursor right
|
|
218
242
|
buff.cursor_right()
|
|
219
243
|
|
|
244
|
+
@kb.add("c-r") # Ctrl+R: random task suggestion (agent mode)
|
|
245
|
+
def random_suggestion(event):
|
|
246
|
+
"""Fill prompt with a random task suggestion for agent mode."""
|
|
247
|
+
if mode_state["mode"] == "agent":
|
|
248
|
+
suggestion = random.choice(AGENT_TASK_SUGGESTIONS)
|
|
249
|
+
buff = event.app.current_buffer
|
|
250
|
+
buff.text = suggestion
|
|
251
|
+
buff.cursor_position = len(suggestion)
|
|
252
|
+
|
|
220
253
|
def get_prompt():
|
|
221
254
|
"""Generate prompt with current mode indicator."""
|
|
222
255
|
mode = mode_state["mode"]
|
|
@@ -330,12 +363,20 @@ def prompt_interactive_options(
|
|
|
330
363
|
}
|
|
331
364
|
|
|
332
365
|
|
|
333
|
-
|
|
366
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
|
334
370
|
@click.pass_context
|
|
335
371
|
@click.version_option(version=__version__)
|
|
336
372
|
def main(ctx: click.Context):
|
|
337
|
-
"""
|
|
373
|
+
"""reverse-api-engineer: reverse engineer apis.
|
|
374
|
+
|
|
375
|
+
Run without a subcommand to start the interactive REPL; use agent, manual,
|
|
376
|
+
or engineer for CLI mode.
|
|
377
|
+
"""
|
|
338
378
|
setproctitle.setproctitle("reverse-api-engineer")
|
|
379
|
+
setproctitle.setthreadtitle("reverse-api-engineer")
|
|
339
380
|
if ctx.invoked_subcommand is None:
|
|
340
381
|
repl_loop()
|
|
341
382
|
|
|
@@ -358,7 +399,7 @@ def repl_loop():
|
|
|
358
399
|
model = config_manager.get("claude_code_model", "claude-sonnet-4-6")
|
|
359
400
|
|
|
360
401
|
display_banner(console, sdk=sdk, model=model)
|
|
361
|
-
console.print(" [dim]shift+tab to cycle modes
|
|
402
|
+
console.print(" [dim]shift+tab to cycle modes | ctrl+r for random task (agent)[/dim]")
|
|
362
403
|
display_footer(console)
|
|
363
404
|
|
|
364
405
|
# Show update message if background check has completed
|
|
@@ -413,7 +454,7 @@ def repl_loop():
|
|
|
413
454
|
else:
|
|
414
455
|
# Unknown command - show error and available commands
|
|
415
456
|
console.print(f" [red]Unknown command:[/red] {cmd}")
|
|
416
|
-
console.print(" [dim]Available commands: /settings, /history, /messages, /help, /exit[/dim]")
|
|
457
|
+
console.print(" [dim]Available commands: /settings, /history, /messages, /help, /commands, /exit[/dim]")
|
|
417
458
|
continue
|
|
418
459
|
|
|
419
460
|
mode = options.get("mode", "agent")
|
|
@@ -976,6 +1017,9 @@ def handle_agent_help(mode_color=THEME_PRIMARY):
|
|
|
976
1017
|
table.add_row("", "")
|
|
977
1018
|
|
|
978
1019
|
table.add_row("Shift+Tab", "Cycle to other modes (Manual, Engineer).")
|
|
1020
|
+
table.add_row("", "")
|
|
1021
|
+
|
|
1022
|
+
table.add_row("Ctrl+R", "Fill prompt with a random task suggestion.\n[dim]Press multiple times to cycle through ideas.[/dim]")
|
|
979
1023
|
|
|
980
1024
|
console.print(table)
|
|
981
1025
|
console.print()
|
|
@@ -1078,7 +1122,10 @@ def handle_help(mode_color=THEME_PRIMARY):
|
|
|
1078
1122
|
)
|
|
1079
1123
|
commands_table.add_row("", "")
|
|
1080
1124
|
|
|
1081
|
-
commands_table.add_row(
|
|
1125
|
+
commands_table.add_row(
|
|
1126
|
+
"/help or /commands",
|
|
1127
|
+
"Show this help message\n[dim]Usage: /help[/dim]",
|
|
1128
|
+
)
|
|
1082
1129
|
commands_table.add_row("", "")
|
|
1083
1130
|
|
|
1084
1131
|
commands_table.add_row("/exit or /quit", "Exit the application\n[dim]Usage: /exit[/dim]")
|
|
@@ -1095,6 +1142,7 @@ def handle_help(mode_color=THEME_PRIMARY):
|
|
|
1095
1142
|
modes_table.add_row("agent", "Autonomous agent + capture")
|
|
1096
1143
|
modes_table.add_row("manual", "Full pipeline: browser + reverse engineering")
|
|
1097
1144
|
modes_table.add_row("engineer", "Reverse engineer only (enter run_id)")
|
|
1145
|
+
modes_table.add_row("collector", "AI-powered data collection")
|
|
1098
1146
|
|
|
1099
1147
|
console.print(" [bold white]Modes[/bold white] [dim]Shift+Tab to cycle[/dim]")
|
|
1100
1148
|
console.print(modes_table)
|
|
@@ -1181,6 +1229,27 @@ def manual(prompt, url, reverse_engineer, model, output_dir):
|
|
|
1181
1229
|
run_manual_capture(prompt, url, reverse_engineer, model, output_dir)
|
|
1182
1230
|
|
|
1183
1231
|
|
|
1232
|
+
@main.command()
|
|
1233
|
+
@click.option("--prompt", "-p", default=None, help="Instruction for the autonomous agent.")
|
|
1234
|
+
@click.option("--url", "-u", default=None, help="Optional starting URL.")
|
|
1235
|
+
@click.option(
|
|
1236
|
+
"--reverse-engineer/--no-engineer",
|
|
1237
|
+
"reverse_engineer",
|
|
1238
|
+
default=True,
|
|
1239
|
+
help="Run reverse engineering after capture.",
|
|
1240
|
+
)
|
|
1241
|
+
@click.option(
|
|
1242
|
+
"--model",
|
|
1243
|
+
"-m",
|
|
1244
|
+
type=click.Choice(["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"]),
|
|
1245
|
+
default=None,
|
|
1246
|
+
)
|
|
1247
|
+
@click.option("--output-dir", "-o", default=None, help="Custom output directory.")
|
|
1248
|
+
def agent(prompt, url, reverse_engineer, model, output_dir):
|
|
1249
|
+
"""Run autonomous agent browser session."""
|
|
1250
|
+
run_agent_capture(prompt=prompt, url=url, reverse_engineer=reverse_engineer, model=model, output_dir=output_dir)
|
|
1251
|
+
|
|
1252
|
+
|
|
1184
1253
|
def run_manual_capture(prompt=None, url=None, reverse_engineer=True, model=None, output_dir=None):
|
|
1185
1254
|
"""Shared logic for manual capture."""
|
|
1186
1255
|
output_dir = output_dir or config_manager.get("output_dir")
|
|
@@ -1521,6 +1590,8 @@ def run_auto_capture(prompt=None, url=None, model=None, output_dir=None):
|
|
|
1521
1590
|
|
|
1522
1591
|
try:
|
|
1523
1592
|
result = asyncio.run(engineer.analyze_and_generate())
|
|
1593
|
+
except KeyboardInterrupt:
|
|
1594
|
+
result = None
|
|
1524
1595
|
finally:
|
|
1525
1596
|
# Always stop sync when done
|
|
1526
1597
|
engineer.stop_sync()
|