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 @@
-
-
-Skip to content
-{@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}
-
-
- ${installCommand}
-
-
-
-
- Why telecode
-
- {#each capabilities as capability (capability.id)}
- -
-
{capability.title}
- {capability.body}
-
- {/each}
-
-
-
-
- How it works
-
- {#each howItWorks as step (step.stepNumber)}
- -
- {step.stepNumber}
-
-
{step.title}
-
{step.body}
- {#if step.command}
-
- ${step.command}
-
- {/if}
-
-
- {/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