532 Commits

Author SHA1 Message Date
metatroncubeswdev
05e64ccd36 Add combined SEO report: Search Console + Analytics + Site Audit in one view
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
Per-project overview pulling together GSC performance totals/top queries,
GA4 organic overview metrics, and the latest completed site audit summary
into a single page under /p/$projectId/report. Each section degrades
independently (not connected / error / ok) so a missing integration never
blocks the rest of the report. Includes a "Download PDF" button that uses
window.print() against print-aware layout classes added to AppShell so the
app chrome is hidden and content flows naturally when printed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 14:37:11 -04:00
metatroncubeswdev
6d40767d95 Site audit: DataForSEO OnPage fallback for bot-blocked crawls
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
Explicit, user-triggered button ("Get report from DataForSEO") shown when
the native crawler is blocked — never an automatic retry. DataForSEO's
OnPage API crawls from its own infrastructure with JS rendering, which
clears blocks a plain fetch() from our server can't.

- audits.crawlSource ("native" | "dataforseo") + dataforseoTaskId columns.
- dataforseo/onpage.ts: task_post (JS rendering + store_raw_html) / summary
  polling / pages listing / raw_html retrieval. Only task_post is billed
  (~$0.00125/page); the rest are free reads of already-billed results, per
  DataForSEO's pricing docs.
- DataForSeoAuditService: hard quota of 5 runs per project per calendar
  month, enforced server-side via the activity log (audit.dataforseo_report)
  before any DataForSEO spend — a rejected 6th run never reaches the API.
- Reuses the native pipeline instead of duplicating it: extracted
  buildAnalyzedPageResult() out of crawlPage() so both sources feed the same
  analyzeHtml -> runPageReporters -> runMultipageChecks -> auditPages/
  auditIssues path. No Cloudflare Workflow backs these audits; the existing
  getAuditStatus poll (already running every 3s while "running") drives an
  advance step each call instead.
- Known gap: broken-internal-link and orphan-page checks are native-only
  (they read the crawl's link graph from the AuditScratchpad Durable Object,
  which only the native crawl populates).
- UI: page-limit picker (25-500) + live quota display on the existing
  "blocked" screen.

Migration: drizzle/0046_*, drizzle-pg/0024_*.

tsc / oxlint / knip clean. New onpage.test.ts (7) + DataForSeoAuditService
.test.ts (5, including the quota-rejection path); full suite otherwise
unchanged (1195 pass, pre-existing samSkills Windows-CRLF failure only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 11:49:54 -04:00
metatroncubeswdev
17dfca406f Audit crawler: send a browser navigation header set (tier-1 anti-bot)
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
The crawler identified as `OpenSEO-Audit/1.0` with almost no headers, which
naive bot filters and security plugins block outright.

- New crawl-request.ts: AUDIT_USER_AGENT (a current Chrome string),
  buildAuditHeaders() (Accept, Accept-Language, Sec-Fetch-*, Sec-Ch-Ua,
  Upgrade-Insecure-Requests), and fetchForAudit() — fetch + those headers +
  one retry on a transient 429/503.
- Wired into the page crawl (site-audit-workflow-helpers), robots.txt +
  sitemap discovery, and start-URL redirect probing.

Gets past the naive tier; still reported as "blocked" for JS/TLS challenges
(Cloudflare Managed Challenge, DataDome) — those need a real browser. Doc note
points operators at WAF IP/UA allowlisting for their own sites.

No env dependency (keeps the audit lib importable without a cloudflare:workers
mock). tsc / oxlint / knip clean; new crawl-request.test.ts (5); suite
otherwise unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:35:50 -04:00
metatroncubeswdev
ce75d45141 Phase 4: activity log — who did what in the workspace
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
- activity_log table (sqlite + pg, structurally identical; schema-parity
  covers it). Plain-text columns, no FKs — an append-only trail that must
  outlive the projects/users it references, so target_label snapshots a
  human-readable name at write time.
- ActivityRepository: record() (fire-and-forget, never breaks the caller) +
  list() (org-scoped, actor/action filters, keyset pagination) + listActors().
- Recording wired into the mutations worth tracking: project
  create/archive/restore/domain, audit start, team user create/remove/
  password-reset, invitation sent.
- getActivityLog / getActivityActors server functions (owner/admin gated) +
  Settings → Activity tab (ActivityLogView: filter by user & action, load
  more).
- Migration: drizzle/0045_*, drizzle-pg/0023_*. The pipeline does not run
  migrations — see docs/SELF_HOSTING_TEAM_MODE.md step 5 for the one-time
  `drizzle-kit migrate` on the server. Writes fail silently until the table
  exists.

tsc / oxlint / knip clean. New ActivityRepository.test.ts (4) + schema-parity
picks up the new table; suite otherwise unchanged (pre-existing samSkills
CRLF failure only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 16:00:09 -04:00
metatroncubeswdev
29bda614a4 Fix team-mode first-run: /setup no longer bounces to /sign-in
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
- _auth.setup.tsx: the "owner already exists?" check ran in beforeLoad, which
  executes during SSR where fetchTeamSetupStatus's relative fetch to
  /api/team-setup fails — so it always concluded an owner existed and
  redirected to /sign-in, making the create-owner screen unreachable. Move the
  check into a client-side effect with a loading state.
- setup-status.ts: add BETTER_AUTH_URL to CHECK_ENV_VARS so /api/health stops
  falsely reporting "team mode requires BETTER_AUTH_URL" when it is set (the
  Docker preflight already saw it; only the runtime health check's env
  allowlist was missing it).

Verified locally end to end against a D1 build in AUTH_MODE=team: owner
bootstrap, self-disable + 409 on repeat, Better Auth sign-in issues a session
cookie, and get-session resolves the shared organization.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 14:19:28 -04:00
metatroncubeswdev
acc35c055c Phase 3: team-mode owner setup, user management, shared workspace
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
Makes AUTH_MODE=team usable end to end.

- resolveTeamContext (middleware/ensure-user/team.ts): a session resolves to a
  membership in the single shared workspace. No per-user fallback org — a
  signed-in user with no membership is treated as signed out, so the owner can
  actually remove people.
- teamProvisioning.ts: one path that writes user + credential account + member
  together (hashPassword from better-auth/crypto). Shared by both entry points.
- /api/team-setup (raw route, outside auth middleware): GET reports whether an
  owner is needed; POST creates the first owner + the shared org, then
  self-disables once any user exists.
- /setup route + sign-in redirect: first run sends you to create the owner.
- teamUsers server functions (owner/admin-gated): list / create (with temp
  password) / reset password / remove. Removal drops membership + sessions,
  keeps the user row for historical attribution.
- Settings gains a "Users" tab in team mode (TeamUsers.tsx).
- docs/SELF_HOSTING_TEAM_MODE.md: activation runbook (.env, build, first owner).

No DB migration — all rows are existing better-auth tables. tsc / oxlint / knip
clean. New teamProvisioning.test.ts (4 cases) passes; suite otherwise unchanged
(pre-existing samSkills.test.ts CRLF failure only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 08:13:01 -04:00
metatroncubeswdev
c47b032f1a Add team auth mode (backend, inert until AUTH_MODE=team)
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
Introduces a fourth AUTH_MODE, `team`: Better Auth email/password with the
existing organization/member/role/invitation stack, but none of the hosted
SaaS coupling (no Autumn billing, Turnstile, Loops email, Google social
login, onboarding chat, PostHog, disposable-email block, dub referrals).

- auth-mode.ts: add `team`; add isTeamAuthMode / isSessionAuthMode /
  isSessionClientAuthMode ("is there a login session?" vs isHostedAuthMode's
  "is this the billed product?").
- auth.ts: createAuth() builds a valid instance for `team` — verification
  off, self-serve signup disabled, no captcha/Loops/social. hasTeamAuthConfig
  (BETTER_AUTH_URL + BETTER_AUTH_SECRET only) + hasSessionAuthConfig.
- ensure-user: resolve.ts routes `team` through resolveHostedContext;
  requireHostedSession + selfHostedOAuth callback accept any session mode.
- api/auth/$.ts: mount the Better Auth handler for `team` too.
- Client: route guards, sidebar account menu / sign-out, settings
  Organization tab, invitation accept, and error cards switch from
  isHostedClientAuthMode to isSessionClientAuthMode where they mean "has a
  session". Sign-in goes straight to the email form (no Google button);
  sign-up shows an invite-only notice.
- selfhost-preflight: validate `team` (requires BETTER_AUTH_URL +
  BETTER_AUTH_SECRET >= 32 chars).
- .env.example: document `team`.

Ships inert: AUTH_MODE stays local_noauth. tsc / oxlint / knip clean;
test suite unchanged (1164 pass, 1 pre-existing Windows-CRLF failure in
samSkills.test.ts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 00:51:20 -04:00
metatroncubeswdev
b489f78129 Rebrand app to CrawlerX / MetatronCube theme
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
- DaisyUI themes openseo/openseo-dark -> crawlerx/crawlerx-dark on the
  MetatronCube palette (primary #3779b9, secondary #0f1d34, accent #4faede)
- Load Inter as the app font; add --brand-gradient / .brand-gradient
- Cube mark + "CrawlerX" wordmark in the sidebar and mobile top bar
- Add public/crawlerx-mark.png; point transparent-logo.png at the cube mark
- <title>, site.webmanifest, and all user-facing "OpenSEO" copy across
  src/client and src/routes -> "CrawlerX"

Deferred: favicons (need resizing), agent-facing identity (MCP server
name, SAM/onboarding prompts, fact sheet), web/ marketing site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 00:00:27 -04:00
ea8becd433 Update compose.yaml
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
2026-09-08 21:20:39 +00:00
Developer
aefbf86e08 Migrate project to Gitea
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
2026-09-08 21:08:29 +00:00
Jeremy Rivera
3632f40852
Blog: What Broke the $99 Ceiling (#277) 2026-09-03 11:54:34 -04:00
Jeremy Rivera
5b242b225a
Blog: Two Surfaces, Two Timelines (#278) 2026-09-03 11:54:23 -04:00
Ben Senescu
ac9ee482d2
Revert "fix(audit): back off and retry on 429 instead of recording the page a…" (#564)
This reverts commit bb099ad65ae9ac50a6d900c5f61bd0a942661333.
2026-08-28 16:16:44 -04:00
Ben Senescu
ad2d28ea6f
fix(ga4): honor full date ranges instead of clamping to 90 days (#563) 2026-08-28 16:15:22 -04:00
Ben Senescu
bb099ad65a
fix(audit): back off and retry on 429 instead of recording the page as blocked (#562) 2026-08-28 15:27:23 -04:00
Ben Senescu
accac73e16
feat(billing): collect business name, address, and tax ID at checkout (#558) 2026-08-27 18:56:26 -04:00
Ben Senescu
b0248f2acd
release: v0.1.7 (#554) 2026-08-27 18:31:03 -04:00
Ben Senescu
749b38118d
fix: show GA4 MCP rows in Claude text output (#553) 2026-08-27 18:02:13 -04:00
Ben Senescu
ea162a4391
feat(orgs): multi-user workspaces — roles, invitations, membership enforcement (#473) 2026-08-26 16:54:08 -04:00
Ben Senescu
4d7fb661f0
Switch chat agents to GPT-5.6 Luna (max reasoning) (#499) 2026-08-26 16:39:51 -04:00
Ben Senescu
61b32ec647
fix: extend OAuth state cookie lifetime to match the 10-minute state TTL (#544) 2026-08-26 14:37:46 -04:00
Ben Senescu
b32c0bd841
fix: surface Google connect failures to the user instead of dead-ending (#549) 2026-08-26 13:23:20 -04:00
Ben Senescu
9b12e073a8
fix: replace the broken per-key API rate limit with a real 5000/min per-user /mcp limit (#545) 2026-08-26 12:09:31 -04:00
Ben Senescu
f35bbb8753
chore: add internal observability-triage skill (query recipes + known-noise filters) (#540) 2026-08-26 12:08:50 -04:00
Ben Senescu
11a874ed3b
chore: log Lighthouse runtime errors at warn and include strategy (#539) 2026-08-26 11:51:59 -04:00
Ben Senescu
c7ff28c30a
chore: log rank-check live call failures with error code and keyword, warn on upstream flakes (#542) 2026-08-26 11:51:26 -04:00
Ben Senescu
824e914eb1
fix(mcp): refuse subscriptions/listen streams — MCP serving is stateless (EVE-95) (#548) 2026-08-26 11:48:20 -04:00
Ben Senescu
0abde80109
fix: add 10s timeout to Loops fetches (#543) 2026-08-26 11:41:22 -04:00
Ben Senescu
95551a425a
chore: log expected credit refusals in backlink snapshot refresh at info (#541) 2026-08-26 11:39:50 -04:00
Ben Senescu
7918fdb8c7
fix: remove duplicate retry:false in useKeywordSerpAnalysis query options (#547) 2026-08-26 11:34:57 -04:00
Ben Senescu
022a7f1944
SERP analysis: depth 20 by default, load top 100 on demand; classify DataForSEO timeouts (#523) 2026-08-26 11:17:18 -04:00
Ben Senescu
a1c6eb6b11
fix: meta-only-response test used toSorted, banned by the ES2022 lib floor (#537) 2026-08-26 10:37:50 -04:00
Ben Senescu
0311e72a96
PostHog reports every server error as affecting as many users as it has events (#534) 2026-08-26 10:23:03 -04:00
Ben Senescu
8752269149
Stop cloning the raw Lighthouse report through Zod (#528) 2026-08-26 10:11:14 -04:00
Ben Senescu
94b730124b
DataForSEO's own server errors show as an unexpected error and retry 3x (#526) 2026-08-26 10:06:40 -04:00
Ben Senescu
67d68281e3
fix(mcp): accept expected partial outputs (#449) 2026-08-26 10:04:40 -04:00
Ben Senescu
b631715112
Blank page on older browsers: replace ES2023 array methods with Remeda (#535) 2026-08-26 10:02:03 -04:00
Ben Senescu
8db0dd3246
First site audit over MCP fails with a raw billing error (#525) 2026-08-26 09:59:35 -04:00
Ben Senescu
25c8f7be26
fix(client): quick fixes for top PostHog errors (#436) 2026-08-26 09:48:43 -04:00
Ben Senescu
3fc1f4ec34
fix(dataforseo): accept empty live SERPs (#451) 2026-08-26 09:48:32 -04:00
Ben Senescu
ce7570e898
Stop reporting GA4 quota exhaustion as an app exception (#527) 2026-08-26 09:40:47 -04:00
Ben Senescu
b592dc5043
fix(mcp): classify project input validation (#455) 2026-08-26 09:39:09 -04:00
Ben Senescu
7f9d73102e
Throttle chat store updates to fix React #185 in SAM chat (#522) 2026-08-26 09:38:33 -04:00
Ben Senescu
044d712580
Ignore TanStack Query CancelledError in client error tracking (#524) 2026-08-26 09:33:43 -04:00
Ben Senescu
fcb35a8145
fix: move the site-audit engine to a dedicated open-seo-audit worker (stops audit OOMs) (#530) 2026-08-25 23:12:26 -04:00
Ben Senescu
215ead8152
Replace dataforseo-client SDK with a thin fetch client (#532) 2026-08-25 22:23:55 -04:00
Ben Senescu
ac7ebfe13a
Add Dub referral conversion tracking (leads + sales) (#531) 2026-08-25 21:49:08 -04:00
Ben Senescu
7a611a7bad
Reorder onboarding source options by observed frequency, add Instagram (#533) 2026-08-25 20:49:46 -04:00
Ben Senescu
e0ed902f39
Allow commercial use in hosted terms Section 2.1 (#529)
Replace leftover 'personal, noncommercial' boilerplate with a license
covering internal business use and client-services work, permit using
generated Outputs in client deliverables, and carve Outputs out of the
Section 2.2 commercial-exploitation restriction.
2026-08-23 21:05:30 -04:00
Ben Senescu
c469a48ae9
Add Cursor marketplace plugin package (#521) 2026-08-23 20:18:24 -04:00