Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions src/reverse_api/base_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BaseEngineer(ABC):
"python": ".py",
"javascript": ".js",
"typescript": ".ts",
"go": ".go",
}

def __init__(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/reverse_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/reverse_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
2 changes: 1 addition & 1 deletion src/reverse_api/engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
2 changes: 1 addition & 1 deletion src/reverse_api/prompts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions src/reverse_api/prompts/partials/_language_go.md
Original file line number Diff line number Diff line change
@@ -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`
17 changes: 17 additions & 0 deletions tests/test_base_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
11 changes: 11 additions & 0 deletions tests/test_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down