reverse-api-engineer 0.4.3__tar.gz → 0.4.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/.claude/settings.local.json +8 -1
  2. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/.gitignore +3 -1
  3. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/CHANGELOG.md +17 -0
  4. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/PKG-INFO +2 -2
  5. reverse_api_engineer-0.4.5/chrome-extension/packed/reverse-api-engineer-chrome.zip +0 -0
  6. reverse_api_engineer-0.4.5/chrome-extension/store-assets/screenshot-1280x800.png +0 -0
  7. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/pyproject.toml +2 -2
  8. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/auto_engineer.py +84 -98
  9. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/base_engineer.py +35 -10
  10. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/browser.py +16 -15
  11. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/cli.py +12 -26
  12. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/collector.py +2 -2
  13. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/collector_ui.py +3 -2
  14. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/copilot_engineer.py +1 -1
  15. reverse_api_engineer-0.4.5/src/reverse_api/engineer.py +239 -0
  16. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/opencode_engineer.py +3 -3
  17. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/opencode_ui.py +8 -5
  18. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/playwright_codegen.py +27 -24
  19. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/tui.py +9 -7
  20. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/uv.lock +977 -1001
  21. reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/QUICKSTART.md +0 -2017
  22. reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/TODO_LIST.md +0 -176
  23. reverse_api_engineer-0.4.3/llm-docs/claude-agent-sdk/TOOLS.md +0 -0
  24. reverse_api_engineer-0.4.3/src/reverse_api/engineer.py +0 -255
  25. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/.claude-plugin/marketplace.json +0 -0
  26. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/.python-version +0 -0
  27. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/CLAUDE.md +0 -0
  28. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/CONTRIBUTING.md +0 -0
  29. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/INTERVIEW.md +0 -0
  30. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/LICENSE +0 -0
  31. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/PROMPT_STASH.md +0 -0
  32. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/README.md +0 -0
  33. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/RELEASING.md +0 -0
  34. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/assets/reverse-api-banner.svg +0 -0
  35. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/assets/reverse-api-engineer.gif +0 -0
  36. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/assets/reverse-api-logo.svg +0 -0
  37. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/.claude/settings.local.json +0 -0
  38. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/components.json +0 -0
  39. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/package-lock.json +0 -0
  40. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/package.json +0 -0
  41. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/postcss.config.js +0 -0
  42. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/_locales/en/messages.json +0 -0
  43. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/icons/icon-128.png +0 -0
  44. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/icons/icon-16.png +0 -0
  45. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/icons/icon-32.png +0 -0
  46. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/icons/icon-48.png +0 -0
  47. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/public/manifest.json +0 -0
  48. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/background/service-worker.ts +0 -0
  49. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/agent-action.tsx +0 -0
  50. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/chat-input.tsx +0 -0
  51. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/icons.tsx +0 -0
  52. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/markdown-renderer.tsx +0 -0
  53. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/mode-selector.tsx +0 -0
  54. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/plan.tsx +0 -0
  55. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/session-selector.tsx +0 -0
  56. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/terminal.tsx +0 -0
  57. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/components/ui/code-block.tsx +0 -0
  58. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/content/codegen-recorder.ts +0 -0
  59. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/index.css +0 -0
  60. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/shared/capture.ts +0 -0
  61. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/shared/native-host.ts +0 -0
  62. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/shared/storage.ts +0 -0
  63. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/shared/types.ts +0 -0
  64. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/sidepanel/index.html +0 -0
  65. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/sidepanel/main.tsx +0 -0
  66. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/sidepanel/side-panel.tsx +0 -0
  67. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/src/vite-env.d.ts +0 -0
  68. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/tailwind.config.js +0 -0
  69. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/tsconfig.json +0 -0
  70. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/chrome-extension/vite.config.ts +0 -0
  71. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/INDEX.md +0 -0
  72. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/QUICKSTART.md +0 -0
  73. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/README.md +0 -0
  74. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/SUMMARY.md +0 -0
  75. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/api_client.py +0 -0
  76. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/extract_job_fields.py +0 -0
  77. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/main.py +0 -0
  78. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/quick_example.py +0 -0
  79. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/apple/requirements.txt +0 -0
  80. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/API_SUMMARY.txt +0 -0
  81. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/QUICKSTART.md +0 -0
  82. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/README.md +0 -0
  83. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/api_client.py +0 -0
  84. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/example_usage.py +0 -0
  85. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ashby/requirements.txt +0 -0
  86. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/autoscout24/README.md +0 -0
  87. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/autoscout24/SUMMARY.md +0 -0
  88. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/autoscout24/api_client.py +0 -0
  89. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ikea/README.md +0 -0
  90. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/ikea/api_client.py +0 -0
  91. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/mintlify/README.md +0 -0
  92. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/mintlify/api_client.py +0 -0
  93. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/API_ANALYSIS_SUMMARY.md +0 -0
  94. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/README.md +0 -0
  95. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/api_client.py +0 -0
  96. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/example_fetch_all_jobs.py +0 -0
  97. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/quick_start.py +0 -0
  98. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/examples/uber/requirements.txt +0 -0
  99. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/llm-docs/OPENCODE_API_SUMMARY.md +0 -0
  100. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/llm-docs/opencode-api.json +0 -0
  101. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/.claude-plugin/plugin.json +0 -0
  102. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/CHANGELOG.md +0 -0
  103. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/LICENSE +0 -0
  104. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/README.md +0 -0
  105. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/agents/api-reverse-engineer.md +0 -0
  106. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/commands/agent.md +0 -0
  107. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/commands/capture.md +0 -0
  108. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/commands/engineer.md +0 -0
  109. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/commands/manual.md +0 -0
  110. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/CHANGELOG.md +0 -0
  111. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/LICENSE +0 -0
  112. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/SKILL.md +0 -0
  113. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/AUTH_PATTERNS.md +0 -0
  114. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/HAR_ANALYSIS.md +0 -0
  115. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_analyze.py +0 -0
  116. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_filter.py +0 -0
  117. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_utils.py +0 -0
  118. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_validate.py +0 -0
  119. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/plugins/reverse-api-engineer/skills/reverse-engineering-api/templates/api_client.py +0 -0
  120. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/scripts/clean_build.sh +0 -0
  121. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/__init__.py +0 -0
  122. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/action_recorder.py +0 -0
  123. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/config.py +0 -0
  124. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/messages.py +0 -0
  125. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/native_host.py +0 -0
  126. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/pricing.py +0 -0
  127. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/session.py +0 -0
  128. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/sync.py +0 -0
  129. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/src/reverse_api/utils.py +0 -0
  130. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/__init__.py +0 -0
  131. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/conftest.py +0 -0
  132. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_action_recorder.py +0 -0
  133. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_auto_engineer.py +0 -0
  134. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_base_engineer.py +0 -0
  135. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_collector.py +0 -0
  136. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_collector_ui.py +0 -0
  137. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_config.py +0 -0
  138. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_engineer.py +0 -0
  139. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_init.py +0 -0
  140. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_messages.py +0 -0
  141. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_native_host.py +0 -0
  142. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_opencode_engineer.py +0 -0
  143. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_opencode_ui.py +0 -0
  144. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_pricing.py +0 -0
  145. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_session.py +0 -0
  146. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_sync.py +0 -0
  147. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/tests/test_tui.py +0 -0
  148. {reverse_api_engineer-0.4.3 → reverse_api_engineer-0.4.5}/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
  }
@@ -99,4 +99,6 @@ collector_mode_plan.md
99
99
  chrome-extension/node_modules/
100
100
  chrome-extension/dist/
101
101
  chrome-extension-old
102
- debug/
102
+ debug/
103
+
104
+ .playwright-mcp/
@@ -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.5] - 2026-03-15
9
+
10
+ ### Fixed
11
+ - **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
12
+ - **CLAUDECODE env var leak**: Clear inherited `CLAUDECODE` env var from CLI subprocess to prevent nested session interference when running inside Claude Code
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.4] - 2026-03-15
21
+
22
+ ### Fixed
23
+ - **Stream closed errors (#51)**: Partial fix using `query()` with env var workarounds (superseded by v0.4.5)
24
+
8
25
  ## [0.4.3] - 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
3
+ Version: 0.4.5
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.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reverse-api-engineer"
3
- version = "0.4.3"
3
+ version = "0.4.5"
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.0",
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,14 +11,13 @@ import httpx
11
11
  from claude_agent_sdk import (
12
12
  AssistantMessage,
13
13
  ClaudeAgentOptions,
14
- HookMatcher,
14
+ ClaudeSDKClient,
15
15
  PermissionResultAllow,
16
16
  ResultMessage,
17
17
  TextBlock,
18
18
  ToolPermissionContext,
19
19
  ToolResultBlock,
20
20
  ToolUseBlock,
21
- query,
22
21
  )
23
22
 
24
23
  from .engineer import ClaudeEngineer
@@ -229,9 +228,7 @@ Your final response should confirm the files were created and provide a brief su
229
228
  - Any limitations or caveats
230
229
  """
231
230
 
232
- async def _handle_tool_permission(
233
- self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext
234
- ) -> PermissionResultAllow:
231
+ async def _handle_tool_permission(self, tool_name: str, input_data: dict[str, Any], context: ToolPermissionContext) -> PermissionResultAllow:
235
232
  """Handle tool permission requests, with interactive UI for AskUserQuestion."""
236
233
  if tool_name == "AskUserQuestion":
237
234
  questions = input_data.get("questions", [])
@@ -244,7 +241,7 @@ Your final response should confirm the files were created and provide a brief su
244
241
 
245
242
  async def analyze_and_generate(self) -> dict[str, Any] | None:
246
243
  """Run auto mode with MCP browser integration."""
247
- self.ui.header(self.run_id, self.prompt, self.model)
244
+ self.ui.header(self.run_id, self.prompt, self.model, mode="agent")
248
245
  self.ui.start_analysis()
249
246
  self.message_store.save_prompt(self._build_auto_prompt())
250
247
 
@@ -259,104 +256,93 @@ Your final response should confirm the files were created and provide a brief su
259
256
  ],
260
257
  }
261
258
 
262
- # Required workaround: dummy hook keeps the stream open for can_use_tool
263
- # See: https://platform.claude.com/docs/en/agent-sdk/user-input
264
- async def _dummy_hook(input_data: dict[str, Any], tool_use_id: str | None, context: Any) -> dict[str, Any]:
265
- return {"continue_": True}
266
-
267
- prompt_text = self._build_auto_prompt()
268
-
269
- async def _prompt_stream() -> Any:
270
- yield {
271
- "type": "user",
272
- "message": {"role": "user", "content": prompt_text},
273
- }
274
-
275
259
  options = ClaudeAgentOptions(
276
260
  mcp_servers={"playwright": mcp_config},
261
+ permission_mode="bypassPermissions",
277
262
  can_use_tool=self._handle_tool_permission,
278
- hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[_dummy_hook])]},
279
263
  cwd=str(self.scripts_dir.parent.parent), # Project root
280
264
  model=self.model,
265
+ env={"CLAUDECODE": ""},
266
+ stderr=self._handle_cli_stderr,
281
267
  )
282
268
 
283
269
  try:
284
- async for message in query(prompt=_prompt_stream(), options=options):
285
- # Check for usage metadata
286
- if hasattr(message, "usage") and isinstance(message.usage, dict):
287
- self.usage_metadata.update(message.usage)
288
-
289
- if isinstance(message, AssistantMessage):
290
- last_tool_name = None
291
- for block in message.content:
292
- if isinstance(block, ToolUseBlock):
293
- last_tool_name = block.name
294
- self.ui.tool_start(block.name, block.input)
295
- self.message_store.save_tool_start(block.name, block.input)
296
- elif isinstance(block, ToolResultBlock):
297
- is_error = block.is_error if block.is_error else False
298
-
299
- # Extract output from ToolResultBlock
300
- output = None
301
- if hasattr(block, "content"):
302
- output = block.content
303
- elif hasattr(block, "result"):
304
- output = block.result
305
- elif hasattr(block, "output"):
306
- output = block.output
307
-
308
- tool_name = last_tool_name or "Tool"
309
- self.ui.tool_result(tool_name, is_error, output)
310
- self.message_store.save_tool_result(tool_name, is_error, str(output) if output else None)
311
- elif isinstance(block, TextBlock):
312
- self.ui.thinking(block.text)
313
- self.message_store.save_thinking(block.text)
314
-
315
- elif isinstance(message, ResultMessage):
316
- if message.is_error:
317
- self.ui.error(message.result or "Unknown error")
318
- self.message_store.save_error(message.result or "Unknown error")
319
- return None
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
- result: dict[str, Any] = {
355
- "script_path": script_path,
356
- "usage": self.usage_metadata,
357
- }
358
- self.message_store.save_result(result)
359
- return result
270
+ async with ClaudeSDKClient(options=options) as client:
271
+ await client.query(self._build_auto_prompt())
272
+
273
+ async for message in client.receive_response():
274
+ if hasattr(message, "usage") and isinstance(message.usage, dict):
275
+ self.usage_metadata.update(message.usage)
276
+
277
+ if isinstance(message, AssistantMessage):
278
+ last_tool_name = None
279
+ for block in message.content:
280
+ if isinstance(block, ToolUseBlock):
281
+ last_tool_name = block.name
282
+ self.ui.tool_start(block.name, block.input)
283
+ self.message_store.save_tool_start(block.name, block.input)
284
+ elif isinstance(block, ToolResultBlock):
285
+ is_error = block.is_error if block.is_error else False
286
+
287
+ output = None
288
+ if hasattr(block, "content"):
289
+ output = block.content
290
+ elif hasattr(block, "result"):
291
+ output = block.result
292
+ elif hasattr(block, "output"):
293
+ output = block.output
294
+
295
+ tool_name = last_tool_name or "Tool"
296
+ self.ui.tool_result(tool_name, is_error, output)
297
+ self.message_store.save_tool_result(tool_name, is_error, str(output) if output else None)
298
+ elif isinstance(block, TextBlock):
299
+ self.ui.thinking(block.text)
300
+ self.message_store.save_thinking(block.text)
301
+
302
+ elif isinstance(message, ResultMessage):
303
+ if message.is_error:
304
+ self.ui.error(message.result or "Unknown error")
305
+ self.message_store.save_error(message.result or "Unknown error")
306
+ return None
307
+ else:
308
+ script_path = str(self.scripts_dir / self._get_client_filename())
309
+ local_path = str(self.local_scripts_dir / self._get_client_filename()) if self.local_scripts_dir else None
310
+ self.ui.success(script_path, local_path)
311
+
312
+ if self.usage_metadata:
313
+ input_tokens = self.usage_metadata.get("input_tokens", 0)
314
+ output_tokens = self.usage_metadata.get("output_tokens", 0)
315
+ cache_creation_tokens = self.usage_metadata.get("cache_creation_input_tokens", 0)
316
+ cache_read_tokens = self.usage_metadata.get("cache_read_input_tokens", 0)
317
+
318
+ from .pricing import calculate_cost
319
+
320
+ cost = calculate_cost(
321
+ model_id=self.model,
322
+ input_tokens=input_tokens,
323
+ output_tokens=output_tokens,
324
+ cache_creation_tokens=cache_creation_tokens,
325
+ cache_read_tokens=cache_read_tokens,
326
+ )
327
+ self.usage_metadata["estimated_cost_usd"] = cost
328
+
329
+ self.ui.console.print(" [dim]Usage:[/dim]")
330
+ if input_tokens > 0:
331
+ self.ui.console.print(f" [dim] input: {input_tokens:,} tokens[/dim]")
332
+ if cache_creation_tokens > 0:
333
+ self.ui.console.print(f" [dim] cache creation: {cache_creation_tokens:,} tokens[/dim]")
334
+ if cache_read_tokens > 0:
335
+ self.ui.console.print(f" [dim] cache read: {cache_read_tokens:,} tokens[/dim]")
336
+ if output_tokens > 0:
337
+ self.ui.console.print(f" [dim] output: {output_tokens:,} tokens[/dim]")
338
+ self.ui.console.print(f" [dim] total cost: ${cost:.4f}[/dim]")
339
+
340
+ result: dict[str, Any] = {
341
+ "script_path": script_path,
342
+ "usage": self.usage_metadata,
343
+ }
344
+ self.message_store.save_result(result)
345
+ return result
360
346
 
361
347
  except Exception as e:
362
348
  error_msg = str(e)
@@ -404,7 +390,7 @@ class OpenCodeAutoEngineer(OpenCodeEngineer):
404
390
 
405
391
  async def analyze_and_generate(self) -> dict[str, Any] | None:
406
392
  """Run auto mode with OpenCode MCP integration."""
407
- self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model)
393
+ self.opencode_ui.header(self.run_id, self.prompt, self.opencode_model, mode="agent")
408
394
  self.opencode_ui.start_analysis()
409
395
  self.message_store.save_prompt(self._build_auto_prompt())
410
396
 
@@ -639,7 +625,7 @@ class CopilotAutoEngineer:
639
625
  return None
640
626
 
641
627
  eng = self._engineer
642
- eng.ui.header(eng.run_id, eng.prompt, eng.copilot_model, eng.sdk)
628
+ eng.ui.header(eng.run_id, eng.prompt, eng.copilot_model, eng.sdk, mode="agent")
643
629
  eng.ui.start_analysis()
644
630
 
645
631
  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 = "" if self.output_mode == "docs" else (
536
- "If your first attempt doesn't work, analyze what went wrong and try again. "
537
- "Document each attempt and what you learned.\n\n"
538
- "<attempt_log>\n"
539
- "For each attempt (up to 5), document:\n"
540
- "- Attempt number\n"
541
- "- What approach you tried\n"
542
- "- What error or issue occurred (if any)\n"
543
- "- What you changed for the next attempt\n"
544
- "</attempt_log>\n\n"
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('__ACTION__'):
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('type') == 'navigate':
396
- url = action_data.get('url', '')
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 = ["manual", "engineer", "agent", "collector"]
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 = "manual",
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: similar to manual but uses autonomous browser
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: manual | engineer | agent | collector[/dim]")
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 = "manual"
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", "manual")
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", "manual")
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('_', ' ').title()}
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] [dim]v{__version__}[/dim]")
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]")
@@ -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()