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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
1. You give it a website and a goal ("fetch all Apple jobs").
2. A browser visits the site, either driven by you or by an AI agent.
3. Network traffic is captured to a HAR file.
4. Your configured model reads the traffic and writes a working API client in Python, JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, or C.
4. Your configured model reads the traffic and writes a working API client in Python, JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, C, or PowerShell.

No more manually opening DevTools, copying cURL commands, and gluing together a client.

Expand Down Expand Up @@ -116,7 +116,7 @@ Settings live in `~/.reverse-api/config.json` and can be edited via `/settings`
- **SDK**: `claude` (default), `opencode`, `cursor`, or `copilot` (GitHub Copilot).
- **OpenCode setup**: with `sdk: "opencode"`, RAE reuses an existing server or downloads/starts `opencode-ai@latest` through `npx`; a global OpenCode installation is not required. Fresh configurations default to the free `opencode/big-pickle` model. `/settings` shows a loading spinner, then offers a searchable **OpenCode Provider / Model** picker populated from the server's connected, tool-capable catalog and marks free OpenCode options. Before creating a session, RAE validates the saved pair again and suggests currently available free models when configuration is invalid. Compatible older servers are reused with a version warning. Node.js 20+ is required for automatic startup. Password-protected servers use `OPENCODE_SERVER_PASSWORD` and optional `OPENCODE_SERVER_USERNAME`. Override startup with `OPENCODE_BASE_URL`, `RAE_OPENCODE_PACKAGE`, or `RAE_OPENCODE_AUTO_START=0`.
- **Ollama through OpenCode**: choose provider `ollama` in `/settings`; RAE starts an installed Ollama daemon if needed, lists only installed models with tool calling and 64k+ context, and supplies OpenCode's provider config inline. Models are never downloaded silently. Override with `RAE_OLLAMA_BASE_URL` or `RAE_OLLAMA_AUTO_START=0`.
- **Output language**: `python`, `javascript`, `typescript`, `go`, `java`, `csharp`, `php`, `ruby`, or `c`. C needs a POSIX toolchain (`cc`, libcurl headers) — macOS/Linux, or WSL/MSYS2 on Windows.
- **Output language**: `python`, `javascript`, `typescript`, `go`, `java`, `csharp`, `php`, `ruby`, `c`, or `powershell`. C needs a POSIX toolchain (`cc`, libcurl headers) — macOS/Linux, or WSL/MSYS2 on Windows. PowerShell needs `pwsh` 7+ (PowerShell Core, not Windows PowerShell 5.1).

## CLI

Expand Down
21 changes: 21 additions & 0 deletions src/reverse_api/base_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ def _get_language_name(self) -> str:
"php": "PHP",
"ruby": "Ruby",
"c": "C",
"powershell": "PowerShell",
}.get(self.output_language, "Python")

def _get_existing_client_guidance(self) -> str:
Expand Down Expand Up @@ -613,6 +614,24 @@ def _get_run_command(self) -> str:
cjson = self._quote_path(str(resolved / "cJSON.c"))
binary = self._quote_path(str(resolved / "api_client"))
return f"cc {source} {cjson} -lcurl -o {binary} && {binary}"
if self.output_language == "powershell":
# Unlike python/node/npx (which happily take a plain relative
# filename regardless of the agent's actual cwd, scripts_dir.
# parent.parent — see analyze_and_generate's ClaudeAgentOptions),
# the module itself (api_client.psm1) isn't runnable — the
# command targets the fixed companion Example.ps1, which Imports
# the module and calls its exported functions, the same
# project-file indirection used for Java's pom.xml and C#'s
# csproj. shlex.quote()-equivalent via _quote_path(), not manual
# double-quoting — output_dir (and so scripts_dir) isn't
# guaranteed free of shell metacharacters, and naive f'"{path}"'
# still lets $()/backticks expand inside double quotes.
# .resolve(): a relative --output-dir would otherwise be
# re-interpreted against the agent's cwd (scripts_dir.parent.
# parent) instead of the original cwd it was relative to,
# pointing -File at the wrong, doubly-nested location.
example = self._quote_path(str(self.scripts_dir.resolve() / "Example.ps1"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: PowerShell runs on Linux/macOS can fail before exercising the generated client because this new command executes the example whose module import uses a Windows-style \ path. Generating the import path with Join-Path $PSScriptRoot '{client_filename}' (or a platform-neutral separator) would keep the advertised cross-platform pwsh support working.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/reverse_api/base_engineer.py, line 633:

<comment>PowerShell runs on Linux/macOS can fail before exercising the generated client because this new command executes the example whose module import uses a Windows-style `\` path. Generating the import path with `Join-Path $PSScriptRoot '{client_filename}'` (or a platform-neutral separator) would keep the advertised cross-platform `pwsh` support working.</comment>

<file context>
@@ -613,6 +614,24 @@ def _get_run_command(self) -> str:
+            # re-interpreted against the agent's cwd (scripts_dir.parent.
+            # parent) instead of the original cwd it was relative to,
+            # pointing -File at the wrong, doubly-nested location.
+            example = self._quote_path(str(self.scripts_dir.resolve() / "Example.ps1"))
+            return f"pwsh -NoProfile -File {example}"
         return {
</file context>

return f"pwsh -NoProfile -File {example}"
return {
"python": "python api_client.py",
"javascript": "node api_client.js",
Expand Down Expand Up @@ -751,6 +770,8 @@ def _get_auto_output_files(self, language_name: str, client_filename: str) -> st
f"\n3. `{self.scripts_dir}/cJSON.c` and `{self.scripts_dir}/cJSON.h` - "
"Vendored JSON library"
)
elif self.output_language == "powershell":
return base + f"\n3. `{self.scripts_dir}/Example.ps1` - Imports the module and demonstrates usage"
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 @@ -1269,6 +1269,7 @@ def _handle_settings_action(mode_color=THEME_PRIMARY) -> bool:
Choice(title="php", value="php"),
Choice(title="ruby", value="ruby"),
Choice(title="c", value="c"),
Choice(title="powershell", value="powershell"),
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 @@ -28,7 +28,7 @@
"ollama_auto_start": True,
"ollama_base_url": "http://127.0.0.1:11434",
"output_dir": None, # None means use ~/.reverse-api/runs
"output_language": "python", # "python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", or "c"
"output_language": "python", # "python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c", or "powershell"
"real_time_sync": True, # Enable real-time file sync during engineering
"sdk": "claude", # "claude", "opencode", "copilot", or "cursor"
}
Expand Down
3 changes: 2 additions & 1 deletion src/reverse_api/prompts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def load_language_partial(language: str, **kwargs: str) -> str:
"""Load the language-specific codegen instructions partial.

Args:
language: One of "python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c".
language: One of "python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c",
"powershell".
**kwargs: Placeholder values (scripts_dir, client_filename, run_command).
"""
return load(f"partials/_language_{language}", **kwargs)
Expand Down
34 changes: 34 additions & 0 deletions src/reverse_api/prompts/partials/_language_powershell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
**Generate a PowerShell module** that replicates the API calls found in the traffic. The following are guidelines — use your judgment on what's appropriate for the specific API:

- Target PowerShell 7+ (`pwsh`), not Windows PowerShell 5.1. Do not use Windows-only cmdlets or `System.Web` types that require .NET Framework
- Module file is a single `.psm1` with one or more advanced functions. Every public function:
- Uses an approved verb (`Get-Verb` list) — `Get-`, `Invoke-`, `New-`, `Set-`, `Remove-`, etc. Never invent unapproved verbs like `Fetch-` or `Do-`
- Has `[CmdletBinding()]` and typed `[Parameter()]` blocks (mandatory/optional, `[string]`, `[hashtable]`, `[switch]` etc. — no untyped params)
- Is exported explicitly via `Export-ModuleMember -Function <Name>` at the bottom of the file. Do not use wildcard export (`Export-ModuleMember -Function *`)
- HTTP calls use `Invoke-RestMethod` (or `Invoke-WebRequest` only when raw headers/status codes are needed). Never shell out to `curl.exe` or `curl`
- Session/cookie handling: use `-SessionVariable`/`-WebSession` with `[Microsoft.PowerShell.Commands.WebRequestSession]`, not manual cookie header construction, unless the API requires a cookie value that PowerShell's cookie jar can't express
- Error handling: every network call wrapped in a `try`/`catch` block with `-ErrorAction Stop` on the call itself. Catch blocks should surface `$_.Exception.Message` and, where the failure is an HTTP error, the response body if retrievable, not swallow the error silently
- Use `[PSCustomObject]` for structured return values, not raw hashtables, so downstream `ConvertTo-Json` and property access behave predictably
- Prefer `ConvertTo-Json`/`ConvertFrom-Json` (built-in) over any third-party JSON handling
- No `Write-Host` for data output — use `Write-Output`/return values. `Write-Verbose`/`Write-Error` are fine for diagnostics
- Create a separate exported function for each distinct API endpoint

**Authentication & credentials:**
- Hardcode all cookies, tokens, session IDs, and auth headers found in the traffic directly in the module
- The user should be able to run the example immediately with zero configuration — no env vars, no config files, no manual setup
- If the API uses cookies, populate a `WebRequestSession` with them and reuse it across calls
- If the API uses Bearer tokens or API keys, hardcode them in the request headers
- Handle auth refresh so the module 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:**
- Run: `{run_command}`
- You have up to 5 attempts to fix issues

Save the module to: `{scripts_dir}/{client_filename}`
Save documentation to: `{scripts_dir}/README.md`
Save the example script to: `{scripts_dir}/Example.ps1`, which does:
```powershell
Import-Module "$PSScriptRoot\{client_filename}" -Force
# example invocation(s) of the exported function(s)
```
Do not generate a `.psd1` module manifest.
8 changes: 8 additions & 0 deletions src/reverse_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"php": ".php",
"ruby": ".rb",
"c": ".c",
"powershell": ".psm1",
}

SCRIPT_EXTENSIONS = frozenset(OUTPUT_LANGUAGE_EXTENSIONS.values())
Expand Down Expand Up @@ -829,6 +830,13 @@ def build_script_commands(script: Path, script_args: tuple[str, ...] = ()) -> tu
compile_cmd.append(str(cjson))
compile_cmd += ["-lcurl", "-o", str(binary)]
return [compile_cmd, [str(binary), *script_args]], "cc"
if suffix == ".psm1":
# A .psm1 is a module, not a runnable entry point — like Java/C#'s
# pom.xml/csproj, the actual command targets a fixed companion file
# (Example.ps1) that Imports the module and calls its exported
# functions, not the script argument itself.
example = d / "Example.ps1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Selecting a PowerShell module other than api_client.psm1 still runs the fixed Example.ps1, whose generated import targets api_client.psm1, so the selected module is ignored or the run fails when that file is absent. The command path should derive the module filename from script or otherwise validate that only the generated module can be selected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/reverse_api/utils.py, line 838:

<comment>Selecting a PowerShell module other than `api_client.psm1` still runs the fixed `Example.ps1`, whose generated import targets `api_client.psm1`, so the selected module is ignored or the run fails when that file is absent. The command path should derive the module filename from `script` or otherwise validate that only the generated module can be selected.</comment>

<file context>
@@ -829,6 +830,13 @@ def build_script_commands(script: Path, script_args: tuple[str, ...] = ()) -> tu
+        # pom.xml/csproj, the actual command targets a fixed companion file
+        # (Example.ps1) that Imports the module and calls its exported
+        # functions, not the script argument itself.
+        example = d / "Example.ps1"
+        return [["pwsh", "-NoProfile", "-File", str(example), *script_args]], "pwsh"
     raise ValueError(f"unsupported script type: {script.name}")
</file context>

return [["pwsh", "-NoProfile", "-File", str(example), *script_args]], "pwsh"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: PowerShell run arguments can be silently ineffective: --args are appended to the fixed example script, while generated Example.ps1 has no required mechanism to forward them to the module's function call. The implementation should either make the example argument-aware or reject script_args explicitly instead of implying client argument support.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/reverse_api/utils.py, line 839:

<comment>PowerShell run arguments can be silently ineffective: `--args` are appended to the fixed example script, while generated `Example.ps1` has no required mechanism to forward them to the module's function call. The implementation should either make the example argument-aware or reject `script_args` explicitly instead of implying client argument support.</comment>

<file context>
@@ -829,6 +830,13 @@ def build_script_commands(script: Path, script_args: tuple[str, ...] = ()) -> tu
+        # (Example.ps1) that Imports the module and calls its exported
+        # functions, not the script argument itself.
+        example = d / "Example.ps1"
+        return [["pwsh", "-NoProfile", "-File", str(example), *script_args]], "pwsh"
     raise ValueError(f"unsupported script type: {script.name}")
 
</file context>
Suggested change
return [["pwsh", "-NoProfile", "-File", str(example), *script_args]], "pwsh"
if script_args:
raise ValueError(
"script arguments are not supported for PowerShell clients: the generated Example.ps1 invocation is fixed"
)
return [["pwsh", "-NoProfile", "-File", str(example)]], "pwsh"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Example wrapper drops run arguments

When a user supplies arguments to run, this branch passes them to Example.ps1, but the generation prompt does not require that wrapper to declare parameters or forward values to the module functions. As a result, generated PowerShell clients cannot reliably consume caller-provided run arguments: PowerShell may reject the unbound values, or the wrapper will never pass them to the exported API call.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/reverse_api/utils.py
Line: 839

Comment:
**Example wrapper drops run arguments**

When a user supplies arguments to `run`, this branch passes them to `Example.ps1`, but the generation prompt does not require that wrapper to declare parameters or forward values to the module functions. As a result, generated PowerShell clients cannot reliably consume caller-provided run arguments: PowerShell may reject the unbound values, or the wrapper will never pass them to the exported API call.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

raise ValueError(f"unsupported script type: {script.name}")


Expand Down
49 changes: 48 additions & 1 deletion tests/test_base_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ def test_get_output_extension_c(self, tmp_path):
"""C extension."""
eng = self._make_engineer(tmp_path, output_language="c")
assert eng._get_output_extension() == ".c"
def test_get_output_extension_powershell(self, tmp_path):
"""PowerShell extension."""
eng = self._make_engineer(tmp_path, output_language="powershell")
assert eng._get_output_extension() == ".psm1"

def test_get_output_extension_unknown(self, tmp_path):
"""Unknown language defaults to .py."""
Expand All @@ -229,6 +233,11 @@ def test_get_client_filename_python(self, tmp_path):
eng = self._make_engineer(tmp_path, output_language="python")
assert eng._get_client_filename() == "api_client.py"

def test_get_client_filename_powershell(self, tmp_path):
"""Client filename for PowerShell."""
eng = self._make_engineer(tmp_path, output_language="powershell")
assert eng._get_client_filename() == "api_client.psm1"

def test_get_client_filename_docs(self, tmp_path):
"""Client filename for docs mode."""
eng = self._make_engineer(tmp_path, output_mode="docs")
Expand Down Expand Up @@ -427,6 +436,38 @@ def test_get_run_command_c_resolves_relative_output_dir(self, tmp_path):
assert tokens[2] == str(resolved / "cJSON.c")
assert tokens[5] == str(resolved / "api_client")

def test_get_run_command_powershell(self, tmp_path):
"""Run command for PowerShell points -File at this run's own
(resolved, shell-quoted) Example.ps1, not the module itself — the
agent's cwd is scripts_dir.parent.parent (see analyze_and_generate),
and api_client.psm1 isn't directly runnable, only importable."""
eng = self._make_engineer(tmp_path, output_language="powershell")
expected_example = shlex.quote(str(eng.scripts_dir.resolve() / "Example.ps1"))
assert eng._get_run_command() == f"pwsh -NoProfile -File {expected_example}"

def test_get_run_command_powershell_quotes_metacharacters(self, tmp_path):
"""A scripts_dir containing shell metacharacters must round-trip
back to the literal path, not be left open to $()/backtick
expansion — what the naive f'"{path}"' approach got wrong."""
eng = self._make_engineer(tmp_path, output_language="powershell")
eng.scripts_dir = Path("/tmp/weird$(rm -rf ~) dir")
tokens = shlex.split(eng._get_run_command())
assert tokens[:3] == ["pwsh", "-NoProfile", "-File"]
assert tokens[3] == str(eng.scripts_dir.resolve() / "Example.ps1")

def test_get_run_command_powershell_resolves_relative_output_dir(self, tmp_path):
"""A relative scripts_dir must be resolved to an absolute path before
being embedded in the command — otherwise, once the agent's cwd
moves to scripts_dir.parent.parent, the same relative string gets
re-interpreted from there and points at the wrong, doubly-nested
location."""
eng = self._make_engineer(tmp_path, output_language="powershell")
eng.scripts_dir = Path("relative_output/scripts/run123")
tokens = shlex.split(eng._get_run_command())
example_arg = tokens[3]
assert Path(example_arg).is_absolute()
assert example_arg == str(eng.scripts_dir.resolve() / "Example.ps1")

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 All @@ -442,7 +483,7 @@ def test_get_codegen_instructions_base_version_has_no_verification_instruction(s
review: an earlier version of this appended it here directly,
which would have told every other backend's agent to call a tool
that was never registered in its environment."""
for language in ("python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c"):
for language in ("python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c", "powershell"):
eng = self._make_engineer(tmp_path, output_language=language, output_mode="client")
assert REPORT_CLIENT_VERIFIED_INSTRUCTION not in eng._get_codegen_instructions(), language

Expand Down Expand Up @@ -540,6 +581,12 @@ def test_c_prompt(self, tmp_path):
system_prompt, user_message = eng._build_prompts()
assert "C program" in system_prompt
assert "libcurl" in system_prompt
def test_powershell_prompt(self, tmp_path):
"""PowerShell prompt includes PowerShell-specific instructions."""
eng = self._make_engineer(tmp_path, output_language="powershell")
system_prompt, user_message = eng._build_prompts()
assert "PowerShell module" in system_prompt
assert "Invoke-RestMethod" in system_prompt

def test_docs_prompt(self, tmp_path):
"""Docs mode prompt includes OpenAPI instructions."""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ def test_get_codegen_instructions_appends_for_every_language(self, tmp_path):
way, since it's appended in Python after loading whichever one."""
from reverse_api.base_engineer import REPORT_CLIENT_VERIFIED_INSTRUCTION

for language in ("python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c"):
for language in ("python", "javascript", "typescript", "go", "java", "csharp", "php", "ruby", "c", "powershell"):
eng = self._make_engineer(tmp_path, output_language=language, output_mode="client")
assert eng._get_codegen_instructions().endswith(REPORT_CLIENT_VERIFIED_INSTRUCTION), language

Expand Down
12 changes: 10 additions & 2 deletions tests/test_run_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,12 @@ def test_finds_all_language_extensions(self, scripts_dir_empty, tmp_path):
for name in [
"api_client.py", "api_client.js", "api_client.ts", "api_client.go",
"api_client.java", "api_client.cs", "api_client.php",
"api_client.rb", "api_client.c",
"api_client.rb", "api_client.c", "api_client.psm1",
]:
(scripts_dir_empty / name).write_text("")
with patch("reverse_api.utils.get_base_output_dir", return_value=tmp_path):
scripts = discover_scripts("abc123def456")
assert len(scripts) == 9
assert len(scripts) == 10

def test_excludes_vendored_cjson(self, scripts_dir_empty, tmp_path):
(scripts_dir_empty / "api_client.c").write_text("")
Expand Down Expand Up @@ -813,6 +813,14 @@ def test_c_without_cjson(self, tmp_path):
steps, _ = build_script_commands(script)
assert steps[0] == ["cc", str(script), "-lcurl", "-o", str(tmp_path / "api_client")]

def test_powershell(self, tmp_path):
from reverse_api.utils import build_script_commands
script = tmp_path / "api_client.psm1"
steps, tool = build_script_commands(script, ("--flag",))
example = str(tmp_path / "Example.ps1")
assert steps == [["pwsh", "-NoProfile", "-File", example, "--flag"]]
assert tool == "pwsh"

def test_unsupported_extension_raises(self, tmp_path):
from reverse_api.utils import build_script_commands
with pytest.raises(ValueError, match="unsupported script type"):
Expand Down
10 changes: 5 additions & 5 deletions website/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Capture browser traffic and turn it into a production-ready typed A

**Reverse API Engineer** is a CLI tool that captures browser traffic and uses
your configured AI SDK to generate production-ready API clients in Python,
JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, or C. No more manual reverse
JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, C, or PowerShell. No more manual reverse
engineering: browse, capture, and get clean API code.

<Demo
Expand Down Expand Up @@ -117,11 +117,11 @@ Generated scripts include type hints, structured error handling, and inline
documentation. They're not "demo" snippets; they're meant to be checked into
your codebase and called from production.

### Output in nine languages
### Output in ten languages

Switch between Python, JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, and C
in `/settings`. Same capture, different code. Useful when you want the same API
client surfaced to multiple stacks.
Switch between Python, JavaScript, TypeScript, Go, Java, C#, PHP, Ruby, C, and
PowerShell in `/settings`. Same capture, different code. Useful when you want
the same API client surfaced to multiple stacks.

## Limitations

Expand Down
4 changes: 2 additions & 2 deletions website/src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { StepBrowse, StepCapture, StepGenerate, StepReview } from '@/components/
import { JsonLd } from '@/components/json-ld';

const homeDescription =
'The agent that turns any website into a typed API client in nine languages — generated from the requests the site actually makes.';
'The agent that turns any website into a typed API client in ten languages — generated from the requests the site actually makes.';

export const metadata: Metadata = {
title: 'Turn websites into APIs',
Expand Down Expand Up @@ -60,7 +60,7 @@ const softwareJsonLd = {
downloadUrl: pypiUrl,
codeRepository: githubUrl,
license: 'https://opensource.org/licenses/MIT',
programmingLanguage: ['Python', 'JavaScript', 'TypeScript', 'Go', 'Java', 'C#', 'PHP', 'Ruby', 'C'],
programmingLanguage: ['Python', 'JavaScript', 'TypeScript', 'Go', 'Java', 'C#', 'PHP', 'Ruby', 'C', 'PowerShell'],
offers: {
'@type': 'Offer',
price: '0',
Expand Down
Loading