diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b65d89..e5643404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Go output language**: `output_language: "go"` is now supported alongside python/javascript/typescript, generating a standard-library-first (`net/http`, `encoding/json`) Go program, with the same auth-hardcoding/refresh and bot-detection-fallback guidance as the other languages. + ## [0.10.0] - 2026-06-01 ### Changed diff --git a/README.md b/README.md index 67e4375f..85a9d2c1 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Settings live in `~/.reverse-api/config.json` and can be edited via `/settings` - **Models**: Sonnet 4.6 (default), Opus 4.6 (most capable), Haiku 4.5 (fastest). For OpenCode see [models.dev](https://models.dev). - **SDK**: `claude` (default), `opencode`, `cursor`, or `copilot` (GitHub Copilot). -- **Output language**: `python`, `javascript`, or `typescript`. +- **Output language**: `python`, `javascript`, `typescript`, or `go`. ## CLI diff --git a/src/reverse_api/base_engineer.py b/src/reverse_api/base_engineer.py index c0af4dea..3692cc52 100644 --- a/src/reverse_api/base_engineer.py +++ b/src/reverse_api/base_engineer.py @@ -33,6 +33,7 @@ class BaseEngineer(ABC): "python": ".py", "javascript": ".js", "typescript": ".ts", + "go": ".go", } def __init__( @@ -439,6 +440,7 @@ def _get_language_name(self) -> str: "python": "Python", "javascript": "JavaScript", "typescript": "TypeScript", + "go": "Go", }.get(self.output_language, "Python") def _get_existing_client_guidance(self) -> str: @@ -467,6 +469,7 @@ def _get_run_command(self) -> str: "python": "python api_client.py", "javascript": "node api_client.js", "typescript": "npx tsx api_client.ts", + "go": "go run api_client.go", }.get(self.output_language, "python api_client.py") def _get_codegen_instructions(self) -> str: @@ -576,6 +579,11 @@ def _get_auto_output_files(self, language_name: str, client_filename: str) -> st return base + f"\n3. `{self.scripts_dir}/package.json` - Only if external dependencies are needed" elif self.output_language == "typescript": return base + f"\n3. `{self.scripts_dir}/package.json` - Dependencies and run scripts" + elif self.output_language == "go": + return base + ( + f"\n3. `{self.scripts_dir}/go.mod` and `{self.scripts_dir}/go.sum` - " + "Only if external dependencies are needed" + ) return base @abstractmethod diff --git a/src/reverse_api/cli.py b/src/reverse_api/cli.py index 5e152ce2..1407d9f4 100644 --- a/src/reverse_api/cli.py +++ b/src/reverse_api/cli.py @@ -1083,6 +1083,7 @@ def handle_settings(mode_color=THEME_PRIMARY): Choice(title="python", value="python"), Choice(title="javascript", value="javascript"), Choice(title="typescript", value="typescript"), + Choice(title="go", value="go"), Choice(title="back", value="back"), ] lang = questionary.select( diff --git a/src/reverse_api/config.py b/src/reverse_api/config.py index 7740f70d..4ddfbb59 100644 --- a/src/reverse_api/config.py +++ b/src/reverse_api/config.py @@ -20,7 +20,7 @@ "opencode_model": "claude-opus-4-6", "opencode_provider": "anthropic", "output_dir": None, # None means use ~/.reverse-api/runs - "output_language": "python", # "python", "javascript", or "typescript" + "output_language": "python", # "python", "javascript", "typescript", or "go" "real_time_sync": True, # Enable real-time file sync during engineering "sdk": "claude", # "claude", "opencode", "copilot", or "cursor" } diff --git a/src/reverse_api/engineer.py b/src/reverse_api/engineer.py index f936e7c4..93c8badd 100644 --- a/src/reverse_api/engineer.py +++ b/src/reverse_api/engineer.py @@ -232,7 +232,7 @@ def run_reverse_engineering( cursor_setting_sources: Optional explicit list (overrides cursor_web_search), e.g. ["project","user","all"]. enable_sync: Enable real-time file syncing during engineering is_fresh: Whether to start fresh (ignore previous scripts) - output_language: Target language - "python", "javascript", or "typescript" + output_language: Target language - "python", "javascript", "typescript", or "go" output_mode: Output mode - "client" for API client code, "docs" for OpenAPI specification """ if sdk == "opencode": diff --git a/src/reverse_api/prompts/__init__.py b/src/reverse_api/prompts/__init__.py index 9c0cb6ee..f2f19973 100644 --- a/src/reverse_api/prompts/__init__.py +++ b/src/reverse_api/prompts/__init__.py @@ -54,7 +54,7 @@ def load_language_partial(language: str, **kwargs: str) -> str: """Load the language-specific codegen instructions partial. Args: - language: One of "python", "javascript", "typescript". + language: One of "python", "javascript", "typescript", "go". **kwargs: Placeholder values (scripts_dir, client_filename, run_command). """ return load(f"partials/_language_{language}", **kwargs) diff --git a/src/reverse_api/prompts/partials/_language_go.md b/src/reverse_api/prompts/partials/_language_go.md new file mode 100644 index 00000000..7d599098 --- /dev/null +++ b/src/reverse_api/prompts/partials/_language_go.md @@ -0,0 +1,24 @@ +**Generate a Go program** that replicates the API calls found in the traffic. The following are guidelines — use your judgment on what's appropriate for the specific API: + +- Prefer the standard library (`net/http`, `encoding/json`) — a single-file program with no external dependencies is the default; only introduce a module and third-party packages if the API genuinely needs something the standard library doesn't provide (e.g. a specific auth scheme's signing library) +- Reuse one `*http.Client` (with cookie jar if the API relies on cookies) across requests rather than creating a new one per call +- Create a separate function for each distinct API endpoint, with a typed struct for its response shape +- Return errors rather than panicking; wrap them with context (`fmt.Errorf("...: %w", err)`) +- Include a `main` function with example usage + +**Authentication & credentials:** +- Hardcode all cookies, tokens, session IDs, and auth headers found in the traffic directly in the program +- The user should be able to run the program immediately with zero configuration — no env vars, no config files, no manual setup +- If the API uses cookies, set them via the client's cookie jar +- If the API uses Bearer tokens or API keys, hardcode them in the request headers +- Handle auth refresh so the program doesn't go stale: if you see a token refresh endpoint, OAuth refresh flow, or login endpoint in the traffic, implement automatic re-authentication when a request returns 401/403. If cookies have expiry, re-fetch them before they expire + +**Testing:** +- If a module was initialized, first run: `go mod tidy` +- Format the code with: `gofmt -w {client_filename}` +- Run with: `{run_command}` +- You have up to 5 attempts to fix issues + +Save the program to: `{scripts_dir}/{client_filename}` +Save documentation to: `{scripts_dir}/README.md` +If external dependencies are used, save the module files: `{scripts_dir}/go.mod` and `{scripts_dir}/go.sum` diff --git a/tests/test_base_engineer.py b/tests/test_base_engineer.py index b82b306a..c2d400af 100644 --- a/tests/test_base_engineer.py +++ b/tests/test_base_engineer.py @@ -193,6 +193,11 @@ def test_get_output_extension_typescript(self, tmp_path): eng = self._make_engineer(tmp_path, output_language="typescript") assert eng._get_output_extension() == ".ts" + def test_get_output_extension_go(self, tmp_path): + """Go extension.""" + eng = self._make_engineer(tmp_path, output_language="go") + assert eng._get_output_extension() == ".go" + def test_get_output_extension_unknown(self, tmp_path): """Unknown language defaults to .py.""" eng = self._make_engineer(tmp_path, output_language="rust") @@ -223,6 +228,11 @@ def test_get_run_command_typescript(self, tmp_path): eng = self._make_engineer(tmp_path, output_language="typescript") assert eng._get_run_command() == "npx tsx api_client.ts" + def test_get_run_command_go(self, tmp_path): + """Run command for Go.""" + eng = self._make_engineer(tmp_path, output_language="go") + assert eng._get_run_command() == "go run api_client.go" + def test_get_run_command_unknown(self, tmp_path): """Unknown language defaults to Python command.""" eng = self._make_engineer(tmp_path, output_language="rust") @@ -269,6 +279,13 @@ def test_typescript_prompt(self, tmp_path): assert "TypeScript module" in system_prompt assert "interfaces" in system_prompt + def test_go_prompt(self, tmp_path): + """Go prompt includes Go-specific instructions.""" + eng = self._make_engineer(tmp_path, output_language="go") + system_prompt, user_message = eng._build_prompts() + assert "Go program" in system_prompt + assert "net/http" in system_prompt + def test_docs_prompt(self, tmp_path): """Docs mode prompt includes OpenAPI instructions.""" eng = self._make_engineer(tmp_path, output_mode="docs") diff --git a/tests/test_prompts.py b/tests/test_prompts.py index 750080e3..47484857 100644 --- a/tests/test_prompts.py +++ b/tests/test_prompts.py @@ -71,6 +71,17 @@ def test_typescript_partial(self): assert "TypeScript module" in text assert "interfaces" in text + def test_go_partial(self): + text = load_language_partial( + "go", + scripts_dir="/tmp/scripts", + client_filename="api_client.go", + run_command="go run api_client.go", + ) + assert "Go program" in text + assert "net/http" in text + assert "/tmp/scripts/api_client.go" in text + class TestEngineerTemplates: """Test engineer system/user templates."""