178 Commits

Author SHA1 Message Date
Ben Senescu
ac7ebfe13a
Add Dub referral conversion tracking (leads + sales) (#531) 2026-08-25 21:49:08 -04:00
Ben Senescu
6e02186a2b Add ChatGPT plugin submission manifest (#519) 2026-08-23 18:18:17 -04:00
Ben Senescu
4ba2d6c175 Require exact hosted MCP origins (#515)
* Require exact hosted MCP origins

* Clarify exact MCP origin policy
2026-08-23 18:18:17 -04:00
Ben Senescu
e5e961bf48 Audit crawl: conservative OOM retries + window carry-over across chunks (#517)
* Audit crawl: retry chunks conservatively and stop re-learning the window per chunk

Production audits on heavy-page sites (500-700KB/page) died with
exceededMemory in the first crawl chunk, and the single step retry re-ran
the exact same profile and died again.

- claimChunk now reports isRetry (leftover leases from a dead attempt);
  a retried chunk crawls under RETRY_CRAWL_WINDOW (start 3, max 5,
  halved byte budget) instead of restarting at window 10.
- The adapted window carries across chunks via durable step results, so
  every ~200 pages no longer re-spikes to the initial window.
- First persist sub-batch shrinks to 5 pages so the byte bound sees the
  site's page weight before a full 25-page batch is in flight.
- In-flight HTML budget halved to 8 MiB (16 MiB never constrained the
  observed ~650KB pages: bound was 25, above the 20 max); growth now
  requires a full 25-page sample.
- Parse-time caps: 1,000 extracted links/images per page so mega-menu and
  crawler-trap pages can't bloat retained persist batches.

* Guard endWindow for instances replaying pre-deploy step results

* ci: un-export internal-only interfaces (knip)
2026-08-23 18:18:17 -04:00
Ben Senescu
6470b875ad Allow SurfMind Chrome extension MCP origin (#513) 2026-08-23 18:18:17 -04:00
Ben Senescu
47883b5d5a Fix GA4 measurement health for sparse API responses (#512) 2026-08-23 18:18:17 -04:00
Ben Senescu
43265048df
Tame audit polling: SAM's status tool waits server-side; Lighthouse becomes opt-in for agents (#510) 2026-08-19 14:22:33 -04:00
Ben Senescu
a6f96c516e
Remove GA4 launch gating; dashboard shows an Organic traffic card once connected (#505) 2026-08-19 13:19:55 -04:00
Ben Senescu
a5a953e478
Project memory + SAM skills: shared per-project AI context (spec 0010) (#493) 2026-08-19 09:46:07 -04:00
Ben Senescu
cfa5bc2cf5
Gate GA4 MCP tools per-user; fix false ga4_malformed_response on empty comparison periods (#497) 2026-08-18 15:56:38 -04:00
Ben Senescu
7738f72333
Local SEO MCP tools: business profile, reviews, updates, categories, rank grid (#489) 2026-08-17 23:21:34 -04:00
Ben Senescu
c8b3eb9b0a
Add exact URL / subfolder / domain / subdomain research scopes (EVE-56) (#487) 2026-08-14 21:52:32 -04:00
Ben Senescu
db45a0dd6d
Fix MCP saved keyword metrics (#483) 2026-08-13 20:16:20 -04:00
Ben Senescu
72eaa09c32
Hide Analytics section when GA4 OAuth app is pending (#475) 2026-08-12 19:57:29 -04:00
Ben Senescu
84e8d0be99
Serve legacy MCP JSON requests statelessly to stop per-request server retention (#478) 2026-08-11 20:15:48 -04:00
Ben Senescu
16eb599270
MCP API key support (hosted mode) (#438) 2026-08-11 19:20:44 -04:00
Ben Senescu
9edb18db60
feat(gdpr): user data erasure workflow (EVE-46) (#468) 2026-08-08 19:17:41 -04:00
Ben Senescu
13ada5b441
fix(sam): out-of-credits CoT leak, streaming scroll lock, rank-tracking language gap (#470)
* fix(sam): stream canned refusals without a provider call

Out-of-credits and session-gone refusals ran a real LLM turn with a
200-token cap; MiniMax M3 could spend the whole budget on reasoning
tokens, leaving the user a raw truncated chain-of-thought (which also
named the backing model) and no reply. Refusal turns now swap in a
static LanguageModelV3 that streams the refusal text through Think's
normal pipeline — rendered and persisted like any assistant message,
with no provider request at all.

Fixes every-app/open-seo#161

* fix(chat): stop pinning the transcript to the bottom while the user scrolls up

Both chat surfaces (SAM and onboarding) forced scrollTop to the bottom
on every streamed chunk, so scrolling up mid-reply was undone within
milliseconds. A shared stick-to-bottom hook now tracks pinned-ness from
real scroll events: scrolling away releases the pin, returning to the
bottom (or sending a message) re-arms it.

Fixes every-app/open-seo#160

* fix(rank-tracking): allow any SERP language for any country

The Add Domain modal restricted the language picker to the Labs
per-country subset and disabled it when only one option existed, so
e.g. tracking English searches in Czechia was impossible — even though
rank tracking runs against the SERP API, which serves every supported
language in every country. The picker now offers the full SERP language
list; create/update schemas validate codes against the master list so
unknown codes still fail before DataForSEO charges for them; and
keyword-metrics refreshes resolve a Labs-served language so an
unserved pair never reaches a charged Labs call.

Fixes every-app/open-seo#183

* fix(chat): surface silent turn failures in Workers logs

A provider stream dying mid-turn (chat:request:failed) and a DO restart
whose recovery gives up (chat:recovery:exhausted) leave the user a
replayed "Something went wrong" banner and a half-streamed message, but
never reach the onChatError hook — their only signal is the agents:chat
diagnostics channel, which was unsubscribed, so the chat agents' most
common failure modes produced zero log lines. A module-level
subscription now logs both for every chat DO.

SamChatAgent.onChatError also returns the error now: Think uses the
return value as the stored chat-terminal body that reconnecting clients
replay, and returning void stored the literal string "undefined".
2026-08-08 18:47:41 -04:00
Ben Senescu
17e7515e82
feat(self-host): share one workspace across Cloudflare Access users (#467) 2026-08-08 18:23:31 -04:00
Ben Senescu
2cedcda5db
fix(billing): activate paid plans immediately after checkout (EVE-44) (#469) 2026-08-08 13:06:45 -04:00
Ben Senescu
cfc8456767
feat(mcp): migrate to MCP SDK v2 stateless handler (#464) 2026-08-08 12:44:48 -04:00
Ben Senescu
eec998a762
feat(rank-tracking): raise scheduler throughput 5x with rate-limit-derived sizing (#465)
* feat(rank-tracking): raise scheduler budget to 2000 units/tick

The 200-unit budget used ~2-6% of DataForSEO's 2,000 req/min account
cap and would take days to drain the post-#462 backlog. Scheduled checks
run through the task queue (1 task_post per 100 units + free task_get
polls), so a full 2,000-unit tick peaks around 1,200 req/min — still
leaving headroom for the other DataForSEO products on the account.

Also raise the due-config fetch limit to 500 so skip-heavy stretches
(free orgs, keywordless configs) drain more than 200 rows per tick.

* fix(rank-tracking): retune budget to 1000 with accurate sizing and a tick deadline

Review corrections to the 10x bump: task_get polling is one call per unit
per round and rounds wake synchronized per tick, with up to three ~15-min
poll windows overlapping the */5 cron — 2000 units/tick could saturate
DataForSEO's 2000 req/min cap, silently aging throttled polls into the
~3x-cost live fallback billed to customers. 1000/tick keeps real headroom
and is still ~45x steady-state demand.

Add a 3-minute wall-clock deadline to the per-config loop (stoppedByDeadline
in the tick summary): a skip-heavy 500-candidate tick pays serial Autumn
round-trips per distinct org and could otherwise run into the 15-minute cron
kill. Name the fetch limit (DUE_CONFIGS_PER_TICK) and correct its comment.

Test fixtures now derive from MAX_KEYWORDS_PER_CONFIG instead of asserting
an unreachable 1500-keyword config.

* fix(cron): run the audit watchdog before the rank loop

reconcileStaleAudits ran after runScheduledRankChecks in the same
invocation, so a slow rank tick would delay the watchdog and a wall-clock
kill would skip it entirely.

* fix(cron): preserve watchdog failure signal; codex review polish

Rethrow a caught reconcileStaleAudits error after the rank loop so the
invocation still reports failed (matching pre-reorder semantics), use an
inclusive deadline comparison, and note overlapping-tick poll residue in
the sizing comment.
2026-08-07 16:44:16 -04:00
Ben Senescu
f048dc3bd1
fix(rank-tracking): drain due configs deterministically (clean-room alternative to #456) (#462) 2026-08-07 13:53:30 -04:00
Ben Senescu
e2c84803f2
feat: add GA4 MCP insights and rank tracking management (#461) 2026-08-07 13:47:18 -04:00
Ben Senescu
c40a04459c
fix(billing): retry missing Autumn balance (#450) 2026-08-06 21:33:25 -04:00
Ben Senescu
211907ae32
fix(audit): make Lighthouse billing retry-safe (#460) 2026-08-06 21:19:39 -04:00
Ben Senescu
8a728563d4
fix(mcp): stop breaking public OAuth clients at token refresh (#420)
* fix(mcp): stop breaking public OAuth clients at token refresh

The DCR shim force-upgraded every public client (token_endpoint_auth_method
"none" or omitted) to client_secret_post so Perplexity would accept the
registration response. That made the stored client confidential, so the
token endpoint demanded client authentication on every grant — and MCP
clients that discard the secret (Codex) lost their session at first token
expiry with "invalid_client: missing client_secret".

Register those clients as true public clients instead (PKCE + the
provider's grant-to-client binding secure that flow), and satisfy
Perplexity by decorating only the registration response with a placeholder
client_secret and client_secret_post. The provider skips secret validation
for public clients, so clients that send the placeholder and clients that
never store it both keep working, including on refresh.

* refactor(mcp): only rebuild DCR requests that actually change

* fix(mcp): satisfy type-aware lint in DCR shims

oxlint --type-aware rejected the Record<string, unknown> assertions used to
read untrusted DCR payloads. Parse both with loose Zod schemas instead, per
the repo's trust-boundary convention, which also replaces the hand-rolled
object guards.
2026-08-05 16:10:28 -04:00
Ben Senescu
f6ddf654df
fix(ci): make crawl backpressure lintable (#452) 2026-08-01 14:50:49 -04:00
Ben Senescu
145324138e
Cap site-audit crawl memory: byte-budgeted window, 1 MiB read cap, persist backpressure (#445) 2026-08-01 13:22:26 -04:00
Ben Senescu
1e8a924c4c
Site Audit - Improve reliability and performance with Durable Objects 2026-07-30 00:51:50 -04:00
Ben Senescu
625b76ebee
Make self hosting better (#428) 2026-07-29 23:10:01 -04:00
Ben Senescu
c81fd7a130
Rank positions: report organic rank (rank_group), not absolute SERP slot (#429)
A user's #1 organic result showed as position 3-4 because rank_absolute
counts SERP features (local pack, PAA, AI overviews) as positions. Rank
tracker, keyword-research SERP list, and onboarding market snapshot now
prefer rank_group; MCP get-serp-results keeps rank_absolute since it
returns all SERP item types.
2026-07-29 10:46:15 -04:00
Jeremy Rivera
a4ede67ebb
Keyword Research Strategy Library + feature-page guides section + Search Everywhere interview (#128) 2026-07-23 11:37:58 -07:00
Amine Benboubker
b0b5528a3a
feat(mcp): add create_project tool (#114) 2026-07-23 10:07:02 -04:00
Slim Bouras
2c153f9a20
feat(mcp): accept locationCode/languageCode on Labs research tools (#113)
Co-authored-by: isatimur <isatimur@users.noreply.github.com>
2026-07-23 10:05:43 -04:00
Shuvam Kumar
3edbd0c11e
fix(gsc): clamp month-based date ranges instead of overflowing short months (#133) 2026-07-23 09:57:25 -04:00
Shuvam Kumar
8ca2e0b332
fix(domain): base pagination hasMore on fetched rows, not filtered rows (#135) 2026-07-23 09:51:31 -04:00
Ben Senescu
7990c4db7f
fix(billing): confirm credit depletion before refusing chat turns (#407) 2026-07-20 18:36:02 -04:00
bookingseo
4aaeade3b0
fix: prompt explorer citations always empty (annotations have no type field) (#82) 2026-07-19 02:02:31 -04:00
Ben Senescu
cd550729a2
Pricing page redesign: usage estimator + flat layout (#368) 2026-07-19 00:51:51 -04:00
Ben Senescu
6ad65f59e1
Bound audit crawl resource usage (#401)
Cap crawled HTML bodies at 2 MiB before Cheerio parsing, bulk-insert
audit links on Postgres instead of per-row Hyperdrive round trips, and
raise the Worker CPU ceiling to the 5-minute paid max for link-heavy
crawl steps.
2026-07-18 23:49:30 -04:00
Ben Senescu
7f2e730a1c
Hide rank tracking card from project dashboard (#399)
Remove the RankMovementCard (setup pitch + rank movement stats) from the
dashboard grid until the rank tracking experience gets more polish. The
overview endpoint still returns the rank summary, so restoring the card
is a small revert of this commit.
2026-07-18 22:22:23 -04:00
Ben Senescu
b981e83bf8
feat: tweak telemetry (telemachus!) heartbeat (#397) 2026-07-18 22:22:01 -04:00
Ben Senescu
eda5e1e354
Project dashboard: onboarding checklist + domain overview (#398) 2026-07-18 21:52:35 -04:00
Ben Senescu
040282511a
fix: treat empty-string env vars as unset in getEnvValueSync (#396) 2026-07-18 20:08:20 -04:00
Ben Senescu
1398605ddc
feat: anonymous self-host telemetry heartbeat (#395) 2026-07-18 20:07:42 -04:00
bookingseo
0f08437cd0
feat: configurable default market for location/language fallbacks (#72) 2026-07-16 10:46:49 -04:00
bookingseo
172126aec7
fix: register cache writes with waitUntil so workerd persists them (#73)
void setCached() leaves the R2 put unregistered, and workerd cancels
unregistered pending I/O once the response is sent — the write never
lands, so the caches behind domain overview, domain keyword/page pages,
and SERP research are silently re-fetched (and re-charged) on every
call. Register the writes with waitUntil, matching the pattern already
used in instrumentation.ts and brandLookup.ts.
2026-07-13 12:55:52 -04:00
Ben Senescu
c11517908d
Handle billing KV failures and preserve Lighthouse start-page matching (#375) 2026-07-09 22:23:03 -04:00
Ben Senescu
dae0067233
feat(gsc): support multiple Google accounts per user (#373) 2026-07-09 21:49:08 -04:00