From 18a18a910026f9b04f890aac83826aac92ccde53 Mon Sep 17 00:00:00 2001 From: Pouyan Jahangiri Date: Sun, 28 Jun 2026 02:17:41 -0700 Subject: [PATCH] Extract the marketing site to its own repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The landing page now lives in a dedicated repo (telecode-landingpage); remove apps/site from this monorepo. @telecode/ui stays — apps/web still consumes its tokens. Update references in CONTRIBUTING, CHANGELOG, docs/deploy-azure, and .gitignore. --- .gitignore | 3 +- CHANGELOG.md | 3 +- CONTRIBUTING.md | 1 - apps/site/package.json | 28 -- apps/site/src/app.css | 56 ---- apps/site/src/app.d.ts | 11 - apps/site/src/app.html | 12 - apps/site/src/lib/content.test.ts | 70 ----- apps/site/src/lib/content.ts | 122 -------- apps/site/src/routes/+layout.svelte | 9 - apps/site/src/routes/+layout.ts | 4 - apps/site/src/routes/+page.svelte | 416 ---------------------------- apps/site/svelte.config.js | 14 - apps/site/tsconfig.json | 9 - apps/site/vite.config.ts | 6 - apps/site/vitest.config.ts | 9 - docs/deploy-azure.md | 4 +- pnpm-lock.yaml | 40 --- 18 files changed, 5 insertions(+), 812 deletions(-) delete mode 100644 apps/site/package.json delete mode 100644 apps/site/src/app.css delete mode 100644 apps/site/src/app.d.ts delete mode 100644 apps/site/src/app.html delete mode 100644 apps/site/src/lib/content.test.ts delete mode 100644 apps/site/src/lib/content.ts delete mode 100644 apps/site/src/routes/+layout.svelte delete mode 100644 apps/site/src/routes/+layout.ts delete mode 100644 apps/site/src/routes/+page.svelte delete mode 100644 apps/site/svelte.config.js delete mode 100644 apps/site/tsconfig.json delete mode 100644 apps/site/vite.config.ts delete mode 100644 apps/site/vitest.config.ts diff --git a/.gitignore b/.gitignore index cf398f5..5e06612 100644 --- a/.gitignore +++ b/.gitignore @@ -118,8 +118,7 @@ dist # Sveltekit cache directory .svelte-kit/ -# SvelteKit adapter output (apps/site static marketing build; apps/web adapter-node server build) -apps/site/build/ +# SvelteKit adapter output (apps/web adapter-node server build) apps/web/build/ # vitepress build output diff --git a/CHANGELOG.md b/CHANGELOG.md index 031348f..5470aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,8 @@ under **Unreleased**; versioned sections will be added as releases are cut. - **Rich transcript rendering** — a git diff viewer, offline syntax highlighting, and collapsible tool logs. - **`telecode doctor`** diagnostics and a one-line installer for the daemon. -- **Marketing site** (`apps/site`) — a prerendered landing page describing telecode. +- **Marketing site** — a prerendered landing page describing telecode (`telecode.io`), maintained in a + separate repository. - **Hosted-relay hardening** — per-IP HTTP rate limiting (Redis-backed, in-memory fallback), tighter limits on the public pairing endpoints, a request body-size cap, a per-IP WebSocket connection cap, and device-pairing brute-force lockout. Safe to leave a relay running publicly. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 068d9a6..b99d476 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,6 @@ telecode is a TypeScript monorepo (pnpm workspaces + Turborepo): | Path | What it is | | ------------------- | -------------------------------------------------------------------------- | | `apps/web` | The SvelteKit PWA — the product UI (the mobile story; no native app) | -| `apps/site` | The prerendered marketing/landing site | | `apps/relay` | The Node + Fastify + `ws` relay — the outbound-only control plane / broker | | `packages/daemon` | The Node + Claude Agent SDK daemon that runs on your machine | | `packages/protocol` | Shared zod schemas + crypto helpers — the wire contract | diff --git a/apps/site/package.json b/apps/site/package.json deleted file mode 100644 index d019cb3..0000000 --- a/apps/site/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@telecode/site", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "test": "vitest run --passWithNoTests", - "lint": "eslint ." - }, - "dependencies": { - "@telecode/ui": "workspace:^" - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.8", - "@sveltejs/kit": "^2.8.5", - "@sveltejs/vite-plugin-svelte": "^4.0.4", - "svelte": "^5.15.0", - "svelte-check": "^4.1.1", - "typescript": "^5.7.3", - "vite": "^5.4.11", - "vitest": "^2.1.8" - } -} diff --git a/apps/site/src/app.css b/apps/site/src/app.css deleted file mode 100644 index 7fcec3e..0000000 --- a/apps/site/src/app.css +++ /dev/null @@ -1,56 +0,0 @@ -@import '@telecode/ui/tokens.css'; - -*, -*::before, -*::after { - box-sizing: border-box; -} - -html { - color-scheme: dark; - scroll-behavior: smooth; -} - -@media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; - } -} - -body { - margin: 0; - background: var(--bg); - color: var(--text); - font-family: var(--font-sans); - font-size: var(--text-base); - line-height: var(--lh-base); - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; -} - -a { - color: var(--text); -} - -/* Keyboard skip link — first focusable element, visible only on focus. */ -.skip-link { - position: absolute; - left: var(--space-3); - top: -4rem; - z-index: var(--z-toast); - padding: var(--space-2) var(--space-3); - background: var(--surface-raised); - color: var(--text); - border: 1px solid var(--border-strong); - border-radius: var(--radius-md); - transition: top var(--dur-fast) var(--ease); -} -.skip-link:focus { - top: var(--space-3); -} - -@media (prefers-reduced-motion: reduce) { - .skip-link { - transition: none; - } -} diff --git a/apps/site/src/app.d.ts b/apps/site/src/app.d.ts deleted file mode 100644 index 1a445dc..0000000 --- a/apps/site/src/app.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface Platform {} - } -} - -export {}; diff --git a/apps/site/src/app.html b/apps/site/src/app.html deleted file mode 100644 index 14312d3..0000000 --- a/apps/site/src/app.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/apps/site/src/lib/content.test.ts b/apps/site/src/lib/content.test.ts deleted file mode 100644 index 1b3dc22..0000000 --- a/apps/site/src/lib/content.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { links, siteContent, type CapabilityId } from './content'; - -/** - * The landing content is the product's public positioning, so it is asserted as data (walking skeleton for - * Phase 5 Task 5). These are *positive* checks — that the four defining capabilities are present and the - * CTAs point at real destinations. (The "no competitor names" rule is enforced by review + a repo-wide - * grep, never by hardcoding rival names here — that would itself ship a competitor name.) - */ -describe('landing content', () => { - it('leads with a non-empty capability headline and subhead', () => { - expect(siteContent.productName).toBe('telecode'); - expect(siteContent.tagline.trim().length).toBeGreaterThan(0); - expect(siteContent.subhead.trim().length).toBeGreaterThan(0); - }); - - it('covers the four defining capabilities, each with body copy', () => { - const expected: CapabilityId[] = [ - 'open-source', - 'self-hostable', - 'end-to-end-encrypted', - 'agent-sdk-native', - ]; - const ids = siteContent.capabilities.map((c) => c.id); - expect(ids).toEqual(expected); - for (const capability of siteContent.capabilities) { - expect(capability.title.trim().length).toBeGreaterThan(0); - expect(capability.body.trim().length).toBeGreaterThan(0); - } - }); - - it('points both CTAs at real, absolute destinations', () => { - for (const cta of [siteContent.primaryCta, siteContent.secondaryCta]) { - expect(cta.label.trim().length).toBeGreaterThan(0); - expect(cta.href).toMatch(/^https:\/\//); - } - expect(siteContent.secondaryCta.href).toBe(links.repo); - }); - - it('lays out the three-step how-it-works flow with sequential numbering', () => { - expect(siteContent.howItWorks.map((s) => s.stepNumber)).toEqual([1, 2, 3]); - for (const step of siteContent.howItWorks) { - expect(step.title.trim().length).toBeGreaterThan(0); - expect(step.body.trim().length).toBeGreaterThan(0); - } - }); - - it('exposes a footer of absolute links and the AGPL license', () => { - expect(siteContent.footerLinks.length).toBeGreaterThan(0); - for (const link of siteContent.footerLinks) { - expect(link.label.trim().length).toBeGreaterThan(0); - expect(link.href).toMatch(/^https:\/\//); - } - expect(siteContent.license).toBe('AGPL-3.0'); - }); - - it('shows an install command in the hero', () => { - expect(siteContent.installCommand.trim().length).toBeGreaterThan(0); - }); - - it('uses only telecode.io domains for first-party destinations', () => { - const firstParty = Object.values(links).filter((href) => href.includes('telecode')); - for (const href of firstParty) { - // The product app is on app.telecode.io; the legacy pouyan.ai domain must not reappear. - expect(href).not.toContain('telecode.pouyan.ai'); - } - expect(links.app).toBe('https://app.telecode.io'); - }); -}); diff --git a/apps/site/src/lib/content.ts b/apps/site/src/lib/content.ts deleted file mode 100644 index 346da19..0000000 --- a/apps/site/src/lib/content.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * The landing page's content, as data (the house pattern: structured content in a `$lib` module, a thin - * Svelte component renders it; this is what the unit tests assert against). Positioning is **capability- - * based** — telecode is described by what it *is and does* (open · self-hostable · end-to-end encrypted · - * Agent-SDK-native), never by comparison to a named competitor (a BLOCKING project rule). - */ - -/** Canonical off-site destinations. The product PWA lives on a subdomain; the source on GitHub. */ -export const links = { - /** The product web app (the dashboard) — a subdomain of the marketing apex. */ - app: 'https://app.telecode.io', - /** Source + issues. */ - repo: 'https://github.com/PouyanJay/telecode', - /** Getting-started guide (install → pair → launch). */ - docs: 'https://github.com/PouyanJay/telecode/blob/main/docs/getting-started.md', - /** Self-hosting guide. */ - selfHost: 'https://github.com/PouyanJay/telecode/blob/main/docs/self-hosting.md', -} as const; - -/** The four pillars that define telecode. Stable ids let tests assert coverage without matching prose. */ -export type CapabilityId = - | 'open-source' - | 'self-hostable' - | 'end-to-end-encrypted' - | 'agent-sdk-native'; - -export interface Capability { - readonly id: CapabilityId; - readonly title: string; - readonly body: string; -} - -export interface CallToAction { - readonly label: string; - readonly href: string; -} - -export interface HowItWorksStep { - readonly stepNumber: number; - readonly title: string; - readonly body: string; - /** An optional terminal command shown in a mono chip (the data-as-signature house style). */ - readonly command?: string; -} - -export interface FooterLink { - readonly label: string; - readonly href: string; -} - -export interface SiteContent { - readonly productName: string; - /** The hero headline — what telecode lets you do, in one line. */ - readonly tagline: string; - readonly subhead: string; - /** The command shown in the hero terminal chip. */ - readonly installCommand: string; - readonly capabilities: readonly Capability[]; - readonly howItWorks: readonly HowItWorksStep[]; - readonly primaryCta: CallToAction; - readonly secondaryCta: CallToAction; - readonly footerLinks: readonly FooterLink[]; - /** SPDX license id, shown in the footer. */ - readonly license: string; -} - -export const siteContent: SiteContent = { - productName: 'telecode', - tagline: 'Run Claude Code agents on your own machine — drive them from any browser.', - subhead: - 'telecode is an open-source, self-hostable command center for Claude Code agents. Launch, watch, and steer agents that run on your hardware, from your phone or laptop — your code never leaves your machine.', - installCommand: 'npx @telecode/cli', - capabilities: [ - { - id: 'open-source', - title: 'Open source', - body: 'AGPL-3.0 and built in the open. Read every line, fork it, and own your workflow — no black boxes between you and your agents.', - }, - { - id: 'self-hostable', - title: 'Self-hostable', - body: 'Run the whole stack yourself with one command. The relay is a thin multiplexer; your laptop does the work and nothing ever reaches into it.', - }, - { - id: 'end-to-end-encrypted', - title: 'End-to-end encrypted', - body: 'Prompts, output, and diffs are encrypted in your browser. The relay only ever forwards ciphertext plus routing metadata — it cannot read your work.', - }, - { - id: 'agent-sdk-native', - title: 'Agent-SDK-native', - body: 'Built directly on the Claude Agent SDK — real sessions, real tool approvals — not screen-scraping a terminal. Every consequential action waits for your decision.', - }, - ], - howItWorks: [ - { - stepNumber: 1, - title: 'Install on your machine', - body: 'Run one command where your agents should work. The daemon dials out to the relay — nothing ever reaches into your laptop.', - command: 'npx @telecode/cli', - }, - { - stepNumber: 2, - title: 'Pair from the browser', - body: 'The daemon prints a short code. Sign in to the dashboard, enter the code, and this machine is bound to your account.', - }, - { - stepNumber: 3, - title: 'Launch & steer', - body: 'Pick a device, point at a repo, and send a prompt. Watch output stream and approve each consequential tool call — from your phone or laptop.', - }, - ], - primaryCta: { label: 'Get started', href: links.docs }, - secondaryCta: { label: 'View on GitHub', href: links.repo }, - footerLinks: [ - { label: 'GitHub', href: links.repo }, - { label: 'Documentation', href: links.docs }, - { label: 'Self-hosting', href: links.selfHost }, - { label: 'Privacy', href: 'https://github.com/PouyanJay/telecode/blob/main/docs/telemetry.md' }, - ], - license: 'AGPL-3.0', -}; diff --git a/apps/site/src/routes/+layout.svelte b/apps/site/src/routes/+layout.svelte deleted file mode 100644 index a47d29d..0000000 --- a/apps/site/src/routes/+layout.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - -{@render children()} diff --git a/apps/site/src/routes/+layout.ts b/apps/site/src/routes/+layout.ts deleted file mode 100644 index 4ca9035..0000000 --- a/apps/site/src/routes/+layout.ts +++ /dev/null @@ -1,4 +0,0 @@ -// The whole marketing site is static — prerender every route so it ships as plain HTML/CSS to a CDN. -export const prerender = true; -export const ssr = true; -export const csr = false; diff --git a/apps/site/src/routes/+page.svelte b/apps/site/src/routes/+page.svelte deleted file mode 100644 index bbd5884..0000000 --- a/apps/site/src/routes/+page.svelte +++ /dev/null @@ -1,416 +0,0 @@ - - - - {productName} — run Claude Code agents on your machine, from any browser - - - - - -
-
-

Open · self-hostable · end-to-end encrypted

-

{tagline}

-

{subhead}

- - -
- -
-

Why telecode

-
    - {#each capabilities as capability (capability.id)} -
  • -

    {capability.title}

    -

    {capability.body}

    -
  • - {/each} -
-
- -
-

How it works

-
    - {#each howItWorks as step (step.stepNumber)} -
  1. - -
    -

    {step.title}

    -

    {step.body}

    - {#if step.command} -
    - {step.command} -
    - {/if} -
    -
  2. - {/each} -
-
- -
-

Your machine. Your keys. Your agents.

- -
-
- - - - diff --git a/apps/site/svelte.config.js b/apps/site/svelte.config.js deleted file mode 100644 index 5b9ac90..0000000 --- a/apps/site/svelte.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import adapter from '@sveltejs/adapter-static'; -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; - -// The marketing site is fully prerendered (every route is static — see src/routes/+layout.ts), so it -// deploys to any static host / CDN (telecode.io) with no server. The product PWA lives separately. -/** @type {import('@sveltejs/kit').Config} */ -const config = { - preprocess: vitePreprocess(), - kit: { - adapter: adapter(), - }, -}; - -export default config; diff --git a/apps/site/tsconfig.json b/apps/site/tsconfig.json deleted file mode 100644 index d791d6a..0000000 --- a/apps/site/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "strict": true, - "noUncheckedIndexedAccess": true, - "esModuleInterop": true, - "moduleResolution": "bundler" - } -} diff --git a/apps/site/vite.config.ts b/apps/site/vite.config.ts deleted file mode 100644 index 2e920e4..0000000 --- a/apps/site/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()], -}); diff --git a/apps/site/vitest.config.ts b/apps/site/vitest.config.ts deleted file mode 100644 index 5af66f2..0000000 --- a/apps/site/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -// Pure-TS unit tests for the site's content/logic modules (the house pattern: logic in $lib/*.ts with -// thin Svelte renderers). No jsdom / component-render harness. -export default defineConfig({ - test: { - include: ['src/**/*.test.ts'], - }, -}); diff --git a/docs/deploy-azure.md b/docs/deploy-azure.md index 67c501c..b181e3a 100644 --- a/docs/deploy-azure.md +++ b/docs/deploy-azure.md @@ -5,8 +5,8 @@ This is the production runbook for hosting telecode's control plane on **Azure C - **Web app** (`apps/web`, the SvelteKit PWA) → `https://app.telecode.io` - **Relay** (`apps/relay`, the Fastify + ws broker) → `wss://relay.telecode.io` -**Postgres** is your **managed Supabase** (not provisioned here). The **marketing site** (`apps/site`, -`telecode.io`) is deployed separately (e.g. Vercel) and is out of scope for this guide. +**Postgres** is your **managed Supabase** (not provisioned here). The **marketing site** (`telecode.io`) +lives in its own repository and is deployed separately (e.g. Vercel) — out of scope for this guide. > **Remember:** agents do not run in the cloud. The daemon runs on each user's own machine via > `npx @telecode/cli`; Azure only hosts the relay + web. Execution stays local — that's the product promise. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f03b6c..a106283 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,37 +100,6 @@ importers: specifier: ^2.1.8 version: 2.1.9(@types/node@22.20.0) - apps/site: - dependencies: - '@telecode/ui': - specifier: workspace:^ - version: link:../../packages/ui - devDependencies: - '@sveltejs/adapter-static': - specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0))) - '@sveltejs/kit': - specifier: ^2.8.5 - version: 2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0)) - '@sveltejs/vite-plugin-svelte': - specifier: ^4.0.4 - version: 4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)) - svelte: - specifier: ^5.15.0 - version: 5.56.3(@typescript-eslint/types@8.62.0) - svelte-check: - specifier: ^4.1.1 - version: 4.6.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3) - typescript: - specifier: ^5.7.3 - version: 5.9.3 - vite: - specifier: ^5.4.11 - version: 5.4.21(@types/node@22.20.0) - vitest: - specifier: ^2.1.8 - version: 2.1.9(@types/node@22.20.0) - apps/web: dependencies: '@telecode/protocol': @@ -1247,11 +1216,6 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/adapter-static@3.0.10': - resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} - peerDependencies: - '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.67.0': resolution: {integrity: sha512-JXHbsDwRes1Wgyof3q5ApzzpbCWvinKXMQCiV67TFO6xlZPYLoK0fq3xQMqSicDMgCtFGqLkrQXkseOcASdZ8A==} engines: {node: '>=18.13'} @@ -3487,10 +3451,6 @@ snapshots: '@sveltejs/kit': 2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0)) rollup: 4.62.2 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0)))': - dependencies: - '@sveltejs/kit': 2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0)) - '@sveltejs/kit@2.67.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.56.3(@typescript-eslint/types@8.62.0))(vite@5.4.21(@types/node@22.20.0)))(svelte@5.56.3(@typescript-eslint/types@8.62.0))(typescript@5.9.3)(vite@5.4.21(@types/node@22.20.0))': dependencies: '@standard-schema/spec': 1.1.0