Skip to content

Tier 2: check the marketplace before capturing a site - #120

Open
giordano-lucas wants to merge 2 commits into
promote/cloud-tier1-linksfrom
promote/cloud-tier2-cli
Open

Tier 2: check the marketplace before capturing a site#120
giordano-lucas wants to merge 2 commits into
promote/cloud-tier1-linksfrom
promote/cloud-tier2-cli

Conversation

@giordano-lucas

Copy link
Copy Markdown
Member

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.

 anything already has 3 functions for nfl.com
   · get_current_nfl_week (8 runs)
     Returns the NFL season, week number, season and week types...
     https://anything.notte.cc/marketplace/b9d2b07c-...

? Open the marketplace instead of capturing? (y/N)

Enter carries on capturing. It's an offer, not a toll gate.

Also adds reverse-api-engineer marketplace search (with --site, --limit, --json), a /cloud slash command, a one-line post-capture hint about hosting the client, and a cloud_suggestions setting.

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_site therefore 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=1 wins 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-interactive behaviour is completely unchanged.

Verification

  • 62 new tests (tests/test_cloud.py, tests/test_cli_marketplace.py), all offline via a stubbed requests.get; full suite is 926 passed, 1 skipped.
  • Smoke-tested live against the real endpoint: bare group, --site hit, --site miss, and --json.
  • ruff check and ruff format clean on all new files. _handle_settings_action complexity goes 27 → 29 against a 25 threshold, but it already exceeded it on main — I added one elif branch and did not refactor the function in this PR.

Note

Uses requests (a declared dependency) rather than httpx, which utils.py imports but which is only present transitively via claude-agent-sdk.

🤖 Generated with Claude Code

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>
@giordano-lucas

Copy link
Copy Markdown
Member Author

Updated: base_url and category now filter server-side

The search endpoint gained real server-side filtering, so the client-side workaround this PR originally shipped is gone (0cfea2f).

Before — over-fetch 20 results from q=<registrable domain>, then discard everything whose domain didn't match.
Now — pass the target URL straight to base_url and let the endpoint scope it.

Verified live, all forms work and all return the same 8 nfl.com results:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant