Tier 2: check the marketplace before capturing a site - #120
Tier 2: check the marketplace before capturing a site#120giordano-lucas wants to merge 2 commits into
Conversation
Reverse-engineering a site costs a capture run and tokens, so agent mode now asks whether somebody already did it. Before a capture starts, RAE searches the public Anything marketplace for the target domain and, when it finds something, lists the matches and offers to open the marketplace instead. Enter carries on capturing — the suggestion is an offer, not a toll gate. Also adds: - `reverse-api-engineer marketplace search`, with `--site`, `--limit`, and `--json` for scripted use. - A `/cloud` slash command describing the hosted version and its MCP endpoint. - A one-line hint after a successful capture about hosting the client. - A `cloud_suggestions` setting plus the `RAE_NO_CLOUD` env var, which wins over config so CI and wrappers can opt out without editing settings. Design notes: The search endpoint is public, so no key or account is involved. It ranks rather than filters, meaning a site it has never seen still returns other people's functions — `search_for_site` therefore drops every result whose own domain does not sit under the target's registrable domain. A suggestion for the wrong site is worse than no suggestion. Every failure path degrades to "no suggestions": offline, DNS failure, timeout, non-JSON body, malformed entries, and unexpected exceptions all return an empty list. A capture must never fail because a lookup did. The endpoint is serverless and cold-starts at several seconds against ~0.3s warm, so the timeout is generous and the wait is shown as a spinner rather than an unexplained pause. The hook is skipped entirely when non-interactive or headless, so `--json`, `--json-stream`, and `--no-interactive` behaviour is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The search endpoint now filters server-side on `base_url` and `category`, so the client no longer has to over-fetch and narrow the results itself. - `search()` takes `base_url` and `category` alongside `query`; all three are optional and compose. A call with no filter at all returns the most-run functions overall, which is never what a caller here means, so it is refused rather than sent. - `search_for_site()` passes the target URL straight to `base_url` — the endpoint understands full URLs, `www.`, subdomains, and globs — and drops the 20-result over-fetch that existed only to work around client-side filtering. - `marketplace search` gains `--category`, and combining `--site` with a query now composes into one request. `registrable_domain` stays for two jobs: skipping inputs that are not sites at all (localhost, IP literals, free text), and re-checking results afterwards. Server-side matching is deliberately fuzzy — `base_url=nfl` matches nfl.com — so the guard against suggesting the wrong site remains. Verified live: base_url on a full URL, bare host, www host, and glob all scope correctly; jobs.ashbyhq.com correctly returns nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updated:
|
base_url |
total |
|---|---|
nfl.com |
8 |
https://www.nfl.com/standings?x=1 |
8 |
www.nfl.com |
8 |
nfl |
8 |
*.nfl.* |
8 |
fantasy.nfl.com |
1 |
jobs.ashbyhq.com |
0 |
Filters compose too: base_url=nfl.com + q=standings → exactly 1 result.
Also added: --category on marketplace search, and a guard that refuses a call with no filter at all (an unfiltered request returns the most-run functions overall, which is never what a caller here means).
One thing I kept: registrable_domain and the post-fetch domain re-check. Server-side matching is intentionally fuzzy — base_url=nfl matches nfl.com — and registrable_domain also does duty rejecting inputs that aren't sites at all (localhost, IP literals, free text) before any request goes out. The guard is now a cheap backstop rather than the primary mechanism.
Tests updated accordingly: 932 passed, 1 skipped. #121 rebased on top, with the now-incorrect "the endpoint ranks rather than filters" rationale removed from examples/hosted/ and /docs/cloud.
Stack: 2 of 3. Based on #119 — review that first; this PR's diff is against it.
What
The highest-intent moment in the whole funnel: the user is about to spend a capture run and tokens on a site that may already be covered. Agent mode now checks first.
Enter carries on capturing. It's an offer, not a toll gate.
Also adds
reverse-api-engineer marketplace search(with--site,--limit,--json), a/cloudslash command, a one-line post-capture hint about hosting the client, and acloud_suggestionssetting.Design decisions worth reviewing
No false positives. The public endpoint ranks rather than filters — querying a site it has never seen still returns a full page of other people's functions.
search_for_sitetherefore discards every result whose own domain doesn't sit under the target's registrable domain. Verified live:nfl.com→ 8 matches,jobs.ashbyhq.com→ 0. A suggestion for the wrong site would be worse than no suggestion, and would poison trust in the feature permanently.Every failure degrades to silence. Offline, DNS failure, timeout, non-JSON body, malformed entries, unexpected exceptions — all return
[]. A capture must never fail because a marketing lookup did. There's a test for each path.Cold starts, and why the timeout is 12s. I measured the endpoint: ~0.3s warm, but ~5–8s after an idle period and occasionally worse. My first pass used a 2.5s timeout and silently returned zero matches for sites that definitely have them — the worst possible outcome, since the feature looks broken exactly when it would have helped. It's now a generous timeout plus a visible spinner. A very cold call can still exceed it; that degrades to no suggestions, which is acceptable.
Opt-out.
RAE_NO_CLOUD=1wins over the config setting, so CI and scripted wrappers can disable the network call without touching a user's settings file. The hook is also skipped entirely when non-interactive or headless, so--json/--json-stream/--no-interactivebehaviour is completely unchanged.Verification
tests/test_cloud.py,tests/test_cli_marketplace.py), all offline via a stubbedrequests.get; full suite is 926 passed, 1 skipped.--sitehit,--sitemiss, and--json.ruff checkandruff formatclean on all new files._handle_settings_actioncomplexity goes 27 → 29 against a 25 threshold, but it already exceeded it onmain— I added oneelifbranch and did not refactor the function in this PR.Note
Uses
requests(a declared dependency) rather thanhttpx, whichutils.pyimports but which is only present transitively viaclaude-agent-sdk.🤖 Generated with Claude Code