542 Commits

Author SHA1 Message Date
metatroncubeswdev
4f52d02df1 vite.config.ts: use loadEnv() instead of bare process.env
process.env inside defineConfig() only sees whatever the OS process
environment already has — it does not automatically contain a project's
.env file contents just because Vite loads that file elsewhere (envPrefix
only reaches import.meta.env in app code, a separate mechanism). The
previous commit's PORT/ALLOWED_HOST read from process.env worked only by
coincidence of how a process happened to be launched, and silently did
nothing when it wasn't. loadEnv() is Vite's own documented API for reading
.env files inside the config itself, independent of the launch method.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-17 20:09:51 -04:00
metatroncubeswdev
bed0504418 vite.config.ts: actually read PORT/ALLOWED_HOST from env
Both were documented self-host knobs (SELF_HOSTING_DOCKER.md, compose.yaml)
but hardcoded to port 3001 and seo.thedomainnest.com instead, so any second
instance on a different port/domain got either a port clash or Vite's
preview host-check ("Blocked request... not allowed"). Defaults preserve
exact current behavior for deployments that don't set these.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-17 19:55:16 -04:00
metatroncubeswdev
324c9e064b Revert "vite.config.ts: actually read PORT/ALLOWED_HOST from env"
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
This reverts commit 1146dc2f6a9c8551007c3761f3539fb73c619800.
2026-10-17 19:55:01 -04:00
metatroncubeswdev
1146dc2f6a vite.config.ts: actually read PORT/ALLOWED_HOST from env
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
Both were documented self-host knobs (SELF_HOSTING_DOCKER.md, compose.yaml)
but hardcoded to port 3001 and seo.thedomainnest.com instead, so any second
instance on a different port/domain got either a port clash or Vite's
preview host-check ("Blocked request... not allowed"). Defaults preserve
exact current behavior for deployments that don't set these.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-17 19:49:21 -04:00
metatroncubeswdev
65a01af4d3 Fix critical billing bugs, add local usage caps, publish sales demo
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
Three launch blockers from the Oct 17 direct-Stripe billing work:

- verifyStripeCheckoutSession defaulted an unconfirmed checkout session
  to "active"/isPaying:true, and had no billing permission check, so an
  abandoned (unpaid) checkout marked the org as a paying subscriber for
  free. Now defaults to "incomplete" and only trusts a real subscription
  status or a confirmed paid payment_status; also pins org id to the
  caller's own organization instead of trusting session metadata.
- The Stripe webhook fell back to parsing an unsigned request body as a
  real event whenever STRIPE_WEBHOOK_SECRET was unset, letting anyone POST
  a fake checkout.session.completed and grant any org a paid plan.
  STRIPE_WEBHOOK_SECRET is now required; unsigned requests are refused.
  Also maps Stripe's subscription.status properly (trialing counted as
  active) instead of collapsing anything but "active" to "canceled".
- meterDataforseoCall and every credit-check in subscription.ts skipped
  all usage metering/capping whenever AUTUMN_SECRET_KEY was unset — which
  is exactly the mode the Oct 17 work introduced for self-host direct
  Stripe billing. Added a local per-org monthly usage ledger
  (usage_ledger table, sqlite + pg) checked against each plan's monthly
  data budget (src/shared/crawlerxPlanBudgets.ts, from the PRD's unit
  economics table) so DataForSEO spend is capped even without Autumn.

Also: documented STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET/AUTUMN_SECRET_KEY
in .env.example, and published the CrawlerX product demo as a static page
at /demo.html (self-contained, no backend dependency).

Verified: tsc --noEmit clean, schema-parity test green, full vitest suite
green except two pre-existing unrelated failures (confirmed via stash),
knip/oxlint show no new findings over the pre-change baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-17 16:24:38 -04:00
Alaguraj0361
98694c56ea docs: add comprehensive work changelog and architecture documentation for Oct 17
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-10-17 20:01:03 +05:30
Alaguraj0361
5d5484c0b0 add subscription billing flow and Stripe integration components
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-10-17 19:58:12 +05:30
Alaguraj0361
df5f593369 add billing and subscription management system with Stripe integration
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-10-17 19:40:15 +05:30
Alaguraj0361
5ae39ebfd4 add authentication routes, components, and styling for CrawlerX app
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-10-17 15:02:39 +05:30
metatroncubeswdev
a3b13a112a Add agency edition implementation plan for team handover
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
16-week plan for taking the CrawlerX fork to external agency sales:
decisions, phased tasks with code pointers, risks, and a handover
checklist. Provided as Markdown and a standalone HTML page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 12:24:38 -04:00
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