diff --git a/apps/marketing/.env.example b/apps/marketing/.env.example
index 3b67af84ed..54db03639a 100644
--- a/apps/marketing/.env.example
+++ b/apps/marketing/.env.example
@@ -10,6 +10,13 @@ NEXT_PUBLIC_MARKETING_URL=http://localhost:3004
# Base URL for the main web app (for auth redirects)
NEXT_PUBLIC_APP_URL=http://localhost:3000
+# The docs and blog are separate published PageSpace drives on their own hosts,
+# not routes in this app — every link to them has to be absolute. Both default
+# to the production hosts, so these only need setting to point a local build at
+# somewhere else.
+# NEXT_PUBLIC_DOCS_URL=https://docs.pagespace.ai
+# NEXT_PUBLIC_BLOG_URL=https://blog.pagespace.ai
+
# Cross-subdomain cookie domain (e.g. .pagespace.ai for sharing cookies between www and app)
# Leave empty for local dev (cookies default to current hostname)
# NEXT_PUBLIC_COOKIE_DOMAIN=
diff --git a/apps/marketing/package.json b/apps/marketing/package.json
index 27530bc325..1a8df77022 100644
--- a/apps/marketing/package.json
+++ b/apps/marketing/package.json
@@ -14,7 +14,6 @@
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
- "@radix-ui/react-scroll-area": "^1.2.10",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -24,8 +23,6 @@
"radix-ui": "^1.4.3",
"react": "^19.1.2",
"react-dom": "^19.1.2",
- "react-markdown": "^10.1.0",
- "remark-gfm": "^4.0.1",
"@pagespace/lib": "workspace:*",
"resend": "^6.1.2",
"tailwind-merge": "^3.3.1"
diff --git a/apps/marketing/public/blog/agents-get-a-computer.png b/apps/marketing/public/blog/agents-get-a-computer.png
deleted file mode 100644
index 4be9dce1c4..0000000000
Binary files a/apps/marketing/public/blog/agents-get-a-computer.png and /dev/null differ
diff --git a/apps/marketing/public/blog/ai-versioning-safety.png b/apps/marketing/public/blog/ai-versioning-safety.png
deleted file mode 100644
index 6066d23256..0000000000
Binary files a/apps/marketing/public/blog/ai-versioning-safety.png and /dev/null differ
diff --git a/apps/marketing/public/blog/google-calendar-sync-setup.png b/apps/marketing/public/blog/google-calendar-sync-setup.png
deleted file mode 100644
index bb2579b85a..0000000000
Binary files a/apps/marketing/public/blog/google-calendar-sync-setup.png and /dev/null differ
diff --git a/apps/marketing/public/blog/pagespace-memory-coding-agents.png b/apps/marketing/public/blog/pagespace-memory-coding-agents.png
deleted file mode 100644
index 4371d0307c..0000000000
Binary files a/apps/marketing/public/blog/pagespace-memory-coding-agents.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/agent-context.png b/apps/marketing/public/blog/support-bot/agent-context.png
deleted file mode 100644
index ba0fbab0fc..0000000000
Binary files a/apps/marketing/public/blog/support-bot/agent-context.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/agent-tools.png b/apps/marketing/public/blog/support-bot/agent-tools.png
deleted file mode 100644
index b3bc079247..0000000000
Binary files a/apps/marketing/public/blog/support-bot/agent-tools.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/bot-answer.png b/apps/marketing/public/blog/support-bot/bot-answer.png
deleted file mode 100644
index 9b6a31ab44..0000000000
Binary files a/apps/marketing/public/blog/support-bot/bot-answer.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/docs-admin.png b/apps/marketing/public/blog/support-bot/docs-admin.png
deleted file mode 100644
index 2a2162d408..0000000000
Binary files a/apps/marketing/public/blog/support-bot/docs-admin.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/docs-browser.png b/apps/marketing/public/blog/support-bot/docs-browser.png
deleted file mode 100644
index aebc0df297..0000000000
Binary files a/apps/marketing/public/blog/support-bot/docs-browser.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/inside-pagespace.png b/apps/marketing/public/blog/support-bot/inside-pagespace.png
deleted file mode 100644
index 6da7cfe3c3..0000000000
Binary files a/apps/marketing/public/blog/support-bot/inside-pagespace.png and /dev/null differ
diff --git a/apps/marketing/public/blog/support-bot/public-bot.png b/apps/marketing/public/blog/support-bot/public-bot.png
deleted file mode 100644
index d929a5cef9..0000000000
Binary files a/apps/marketing/public/blog/support-bot/public-bot.png and /dev/null differ
diff --git a/apps/marketing/public/blog/usage-based-pricing-and-built-for-scale.png b/apps/marketing/public/blog/usage-based-pricing-and-built-for-scale.png
deleted file mode 100644
index f3c2368e54..0000000000
Binary files a/apps/marketing/public/blog/usage-based-pricing-and-built-for-scale.png and /dev/null differ
diff --git a/apps/marketing/public/blog/workspace-is-the-context.png b/apps/marketing/public/blog/workspace-is-the-context.png
deleted file mode 100644
index c39b1408b9..0000000000
Binary files a/apps/marketing/public/blog/workspace-is-the-context.png and /dev/null differ
diff --git a/apps/marketing/src/app/blog/[slug]/ShareButtons.tsx b/apps/marketing/src/app/blog/[slug]/ShareButtons.tsx
deleted file mode 100644
index fb526f6c11..0000000000
--- a/apps/marketing/src/app/blog/[slug]/ShareButtons.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { Twitter, Linkedin, Share2, Check } from "lucide-react";
-import { Button } from "@/components/ui/button";
-
-export function ShareButtons({ title }: { title: string }) {
- const [copied, setCopied] = useState(false);
-
- const shareOnTwitter = () => {
- window.open(
- `https://twitter.com/intent/tweet?url=${encodeURIComponent(window.location.href)}&text=${encodeURIComponent(title)}`,
- "_blank",
- "noopener,noreferrer"
- );
- };
-
- const shareOnLinkedIn = () => {
- window.open(
- `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(window.location.href)}`,
- "_blank",
- "noopener,noreferrer"
- );
- };
-
- const copyLink = async () => {
- try {
- await navigator.clipboard.writeText(window.location.href);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- } catch {
- window.prompt("Copy this link:", window.location.href);
- }
- };
-
- return (
-
-
-
-
-
- );
-}
diff --git a/apps/marketing/src/app/blog/[slug]/data.ts b/apps/marketing/src/app/blog/[slug]/data.ts
deleted file mode 100644
index 25b71c6af6..0000000000
--- a/apps/marketing/src/app/blog/[slug]/data.ts
+++ /dev/null
@@ -1,888 +0,0 @@
-export interface BlogPost {
- slug: string;
- title: string;
- description: string;
- content: string;
- author: string;
- date: string;
- readTime: string;
- category: string;
- featured?: boolean;
- image?: string;
-}
-
-export function formatDate(dateString: string): string {
- const date = new Date(`${dateString}T00:00:00`);
- return date.toLocaleDateString("en-US", {
- month: "long",
- day: "numeric",
- year: "numeric",
- });
-}
-
-export const blogPosts: Record = {
- "agents-get-a-computer": {
- slug: "agents-get-a-computer",
- title: "Your Agents Just Got a Computer",
- description:
- "PageSpace agents now run on a real machine in the cloud — with a real terminal, a real filesystem, and real code execution. Split the Agents screen into panes to run several at once. And scheduled automations can now write and run code themselves, unattended. Here's what shipped, and what it means for what you can build.",
- content: `
-## We said this was coming
-
-Back in June, we told you agents would run real code in sandboxed containers, right inside your workspace. Not a snippet you copy into your own terminal. The agent, actually running it. We said "more on that as it lands."
-
-It landed. Today it's open to every Pro plan and above.
-
-## Your agent gets a machine
-
-Start an agent session in PageSpace and your agent isn't just talking to you anymore. It's provisioning a real cloud machine behind the conversation: its own filesystem, its own shell, its own life for as long as the session runs. When it reads a file, it's reading an actual file. When it runs a build, a real build runs. When it calls git, that's a real repository, with real history. Your agent stops describing the work and starts doing it.
-
-That's a bigger shift than it sounds. What your agent builds in there is real: not a snippet for you to copy out and run somewhere else, but an actual script, an actual small app, written and run right there in the session, ready to open and use. PageSpace stops being a place where you talk about the software you want and becomes a place where it gets built.
-
-## The machine is yours too
-
-That cloud machine isn't sealed off behind the conversation. Open a terminal pane on the same session and you're looking at the same filesystem your agent is working in, live. Run a command yourself. Check a file it just touched. Nothing your agent does is happening somewhere you can't see or reach.
-
-And because it's a genuine machine, not a sealed sandbox with one door, you're never fenced in. If you've got a CLI tool you already reach for, it runs there too. That's a side benefit of building this the right way, not the headline. The headline is that your PageSpace agent, the one you configured, the one that already knows your workspace, now has somewhere real to work.
-
-## One screen, built for more than one agent at a time
-
-The second half of this release is the screen you actually work in. The Agents screen now splits into panes: side-by-side columns, and rows stacked inside each one.
-
-Put your agent next to the terminal it's driving and watch the work happen in real time instead of taking it on faith. Put it next to the task list it's supposed to be working through, or next to the document it's supposed to be editing, and watch both update as the conversation moves. Run two, three, however many agents you need side by side — a researcher next to a coder next to the doc they're both writing into — and watch them work as a team instead of one at a time in a queue.
-
-This is the difference between a chatbot and a workspace. A chatbot gives you one window and asks you to hold everything else in your head: what it's doing, what it should be doing, what else is true right now. Panes let you keep all of that in view, because none of it is separate from your work. It's the same page.
-
-## Automations that write and run code, on their own
-
-There's a third piece to this release, and it might be the one that matters most if you're not planning to sit and watch: PageSpace Workflows — the automations you already trigger on a schedule, on a webhook, when a task comes due, when a calendar event fires — can now write and run real code too, the same way an interactive session can.
-
-That means an automation isn't limited to reading and summarizing anymore. A workflow that fires every morning can pull data, run a script against it, and commit the result. A webhook from your CI system can trigger an agent that actually runs a fix, not just files a note about one. The same sandbox eligibility applies here as everywhere else — the workflow's agent needs sandbox access turned on, and the drive's owner needs to be on Pro or above — but once that's true, "automation" stops meaning "summary" and starts meaning "does the work while you're not looking."
-
-## Why this is the point
-
-We didn't build sandboxed sessions so agents could run code in a black box, and we didn't build panes so you'd have more windows to manage, and we didn't wire code execution into Workflows just to check a box. We built all three because the real work of building software has never been one conversation, watched once, by one person. It's a plan, a terminal, a file, a task list, and something — a person, an agent, a scheduled job — moving all of them forward, sometimes while you're asleep. Panes make room for that on screen. The sandbox makes sure what's happening in that room is real. Workflows mean it can keep happening without you.
-
-## What it costs
-
-Every PageSpace plan gets the full interface: sessions, chat, panes, and Workflows. The sandbox itself — the actual cloud machine, the terminal, real code execution, whether you trigger it yourself or a workflow does — is a Pro-plan feature — and eligibility follows whoever pays for the workspace. If the drive you're working in is owned by a Pro account, every member with edit access gets the sandbox in that drive's sessions, even members whose own accounts are on Free — your personal Global Assistant still runs on your own plan. If the payer is on Free — your own drives, your own Global Assistant sessions — you'll see everything and can use everything except the machine underneath it, with a clear upgrade prompt wherever that boundary shows up. On Pro and above, sandbox time draws from the same credit balance as everything else in PageSpace — there's no separate line-item charge, just the plan you're already on and the credits you already have.
-
-## It's live
-
-Agent sessions, panes, and code-executing Workflows are open to everyone today. Start a session from the Agents screen, split a pane, open a terminal. If you're on Pro or above, all of it just works. If you're not yet, [upgrade to Pro](/pricing) and it will.
- `,
- author: "Jono",
- date: "2026-08-03",
- readTime: "7 min read",
- category: "Product",
- featured: true,
- image: "/blog/agents-get-a-computer.png",
- },
- "build-a-chat-app-on-pagespace": {
- slug: "build-a-chat-app-on-pagespace",
- title: "Turn Your PageSpace Docs Into a Support Bot",
- description:
- "Your help docs already live in PageSpace. With two connections, a chat API for the conversation and the SDK for your content, they become a support bot on your site and a help center your team runs, with every conversation saved back in PageSpace.",
- image: "/blog/support-bot/bot-answer.png",
- content: `
-## What you'll have when you're done
-
-A support bot on your website that answers customers from your own help docs, with every conversation saved back in PageSpace for your team to read or jump into. You build the chat box; PageSpace handles everything behind it: the AI, the instructions it follows, the search through your docs, and the record of every conversation.
-
-Normally a support bot is a week of work that has nothing to do with support. You pick an AI model and pay to run it. You teach it how to answer and keep those instructions up to date. You build a way for it to search your help articles and pull up the right one, the part most teams find hardest. And you store every conversation somewhere your team can see it. If your help content already lives in a PageSpace drive, all of that is already done.
-
-You connect to it in two ways, and both are worth knowing up front:
-
-- **A chat API** runs the conversation. It works the same way most AI chat tools already do, so any chat box can talk to it. This powers the live bot.
-- **The [SDK](/docs/features/sdk)** (a small set of ready-made code a developer plugs into your app) handles your content: reading, searching, and editing the docs the bot answers from. This powers the browsable help center and the admin your team uses to keep the docs current.
-
-One drive sits behind both. Here is what that looks like.
-
-## See it working
-
-Everything in this guide runs in a small reference app we built on exactly those two connections. Three surfaces, one drive behind them.
-
-Keep one thing in mind as you look: this is your own app, so it can look however you want. The screenshots below are one design we mocked up to show the pieces. Because PageSpace is only the backend, the chat box, the help center, and the admin are entirely your frontend: your layout, your branding, your fonts and colors, your components. Nothing about the look is locked to a PageSpace widget. You build the interface; PageSpace supplies the answers and the content behind it.
-
-**Customers ask the bot.** A chat box on your site that answers straight from the docs in your drive, appearing word by word as the bot types:
-
-
-
-*The bot on your site is a PageSpace agent (the AI assistant that reads your docs and answers), working through the chat API. Your website only shows the chat box; the AI, its instructions, and the search through your docs all live in PageSpace.*
-
-
-
-*Ask a real question and the bot finds the right help page and answers from it, with the actual steps. You never built the part that searches your docs; the agent reads your drive directly.*
-
-**Customers browse the same docs.** The same pages the bot reads also become a clean, searchable help center customers can browse themselves:
-
-
-
-*The docs are the single source of truth. The bot and the help center read from the exact same content, so they can never fall out of sync.*
-
-**You manage the docs in your own admin.** The bot's knowledge is just those pages, so a support lead can edit an answer in your own admin screen:
-
-
-
-*Edit an answer here and the next customer question is answered from the new version. No developer, no redeploy: the docs are the bot's knowledge, and updating them updates the bot.*
-
-The help center and the admin above are built with that same **[SDK](/docs/features/sdk)**. Listing, reading, and searching your docs is a few lines, so a developer can build whatever interface you want on top:
-
-\`\`\`ts
-import { PageSpaceClient, StaticTokenProvider } from "@pagespace/sdk";
-
-const ps = new PageSpaceClient({
- baseUrl: "https://pagespace.ai",
- auth: new StaticTokenProvider(process.env.PAGESPACE_TOKEN),
-});
-
-const { pages } = await ps.pages.list({ driveId, recursive: true, ls: true }); // the help-center list
-const doc = await ps.pages.read({ operation: "read", pageId }); // one page to show
-const hits = await ps.search.regex({ driveId, pattern: "reset password", searchIn: "content" }); // the search box
-\`\`\`
-
-And because the drive is a real PageSpace workspace, you can skip the custom admin entirely and manage everything natively in the app: edit the docs, adjust the bot, and read every customer conversation as a page your team can open.
-
-
-
-*The agent, the docs it reads, and the threads it produces are all pages in one drive. A teammate can open any conversation and take over.*
-
-Now let's build it.
-
-## Set up the support agent
-
-**1. Create the agent in your support drive.** An agent is an AI Chat page. Create one and keep the id it returns:
-
-\`\`\`bash
-pagespace pages create "Support Bot" AI_CHAT --drive --json
-# -> { "id": "", "type": "AI_CHAT", ... }
-\`\`\`
-
-**2. Tell it how to answer, and let it read the drive.** An AI Chat page has a system prompt and a set of tools. Set the prompt so it behaves like support:
-
-\`\`\`bash
-pagespace agents config --set systemPrompt="You are the support assistant for Acme. Answer only from the docs in this drive. Be concise and friendly. If the docs do not cover a question, say so and offer to hand off to a human. Never invent product behavior."
-\`\`\`
-
-A brand-new agent has no tools enabled, which means it cannot actually search or read the drive and will make things up. Turn on the read-only tools so it answers from your docs instead:
-
-\`\`\`bash
-pagespace agents config --set enabledTools='["multi_drive_search","regex_search","glob_search","list_pages","read_page"]'
-\`\`\`
-
-Pick a specific model the same way if you want one (\`--set aiModel=\`, and \`pagespace models list\` shows the options), or set all of this in the agent's settings tab in the app. Check your work with \`pagespace agents list --drive --json\`: it shows the model, whether a system prompt is set, and the enabled tools.
-
-If you would rather point and click, the same settings live on the agent page in PageSpace:
-
-
-
-*Turn on the read-only search and read tools and nothing else. A support bot should answer from your docs, not write to them.*
-
-
-
-*Hand the agent the drive's page tree so it knows what documentation exists before it starts searching.*
-
-**3. Create a key for your server.** The endpoint runs the agent's tools, which need edit access to the page, so create a key that inherits your own access to the drive (leave \`--role\` off). A plain \`member\` key is view-only on an agent page and would get a 403:
-
-\`\`\`bash
-pagespace keys create --drive --name support-bot --show-token
-# prints PAGESPACE_TOKEN=mcp_... once. Store it as PAGESPACE_TOKEN on your server, never in the browser.
-\`\`\`
-
-Scope the key to the drive that holds your public help docs and nothing sensitive. If the key cannot see a page, neither can the agent.
-
-## Wire it into your site
-
-**4. Call the agent exactly like OpenAI.** It speaks Chat Completions, so you change three things: the base URL, the key, and the model. The model is your agent, addressed as \`ps-agent://\`:
-
-\`\`\`ts
-import OpenAI from "openai";
-
-const client = new OpenAI({
- baseURL: "https://pagespace.ai/api/v1",
- apiKey: process.env.PAGESPACE_TOKEN, // the mcp_ token, server side only
-});
-
-const stream = await client.chat.completions.create({
- model: "ps-agent://",
- stream: true, // the API only streams; stream: false is rejected
- messages: [{ role: "user", content: "How do I reset my password?" }],
-});
-\`\`\`
-
-The agent answers with its own system prompt and runs its own tools on the server. It searches the drive, reads the right doc, and returns the answer, all inside the key's scope. This is the same credential the [SDK](/docs/features/sdk) and [CLI](/docs/features/cli) use.
-
-**5. Stream it to the customer.** The response is an OpenAI stream, so a Next.js route handler that pipes it to the browser is a dozen lines:
-
-\`\`\`ts
-// app/api/support/route.ts
-export async function POST(req: Request) {
- const { messages } = await req.json();
-
- const stream = await client.chat.completions.create({
- model: "ps-agent://",
- stream: true,
- messages,
- });
-
- const encoder = new TextEncoder();
- return new Response(
- new ReadableStream({
- async start(controller) {
- for await (const chunk of stream) {
- const delta = chunk.choices[0]?.delta?.content ?? "";
- if (delta) controller.enqueue(encoder.encode(delta));
- }
- controller.close();
- },
- }),
- { headers: { "Content-Type": "text/plain; charset=utf-8" } },
- );
-}
-\`\`\`
-
-Your chat box sends the conversation so far to that route and shows the reply as it streams in, word by word. That is the bot.
-
-## Every conversation lands in PageSpace
-
-A support bot is only half done if the conversations vanish. Pass a \`conversation_id\` and the whole conversation is saved in PageSpace: every message is stored, it appears on the AI Chat page in the app, and your support team can read it or open the same thread and reply as a human.
-
-Give each customer session its own \`conversation_id\`. Set \`client_manages_history: true\` so your app keeps control of the running history and the chat interface. With that flag set, PageSpace creates the thread the first time it is used, owned by your key, and stores every message under it, while your app stays in charge of the chat.
-
-Both fields go on the same \`create\` call, right beside \`messages\`:
-
-\`\`\`ts
-const stream = await client.chat.completions.create({
- model: "ps-agent://",
- stream: true,
- messages,
- conversation_id: conversationId, // a fresh id you generate per customer session
- client_manages_history: true, // your app owns the running history
-});
-\`\`\`
-
-Those last two are extra fields in the request body. The OpenAI client sends them through as-is; they are not part of its built-in types, so a TypeScript app adds a small cast on the object. (Prefer to create threads up front? \`POST /api/v1/conversations\` with a \`drive_id\` returns an id you reuse.) To let a customer resume where they left off, read the stored messages back:
-
-\`\`\`bash
-curl https://pagespace.ai/api/v1/conversations/ \\
- -H "Authorization: Bearer mcp_your_key_here"
-# -> { "messages": [ { "role": "user", "content": "..." }, ... ] }
-\`\`\`
-
-Refill the chat box from those saved messages and the customer picks up mid conversation. A teammate can open the same thread in PageSpace and take over. The conversation is not trapped in your database. It is a page in the workspace.
-
-## Before you make it public
-
-This is the working bot, but not yet locked down for real public traffic. You do not have to build much more, but two things are on you before it goes live.
-
-First, keep the token on your server. The \`mcp_\` key inherits your drive access, so it never belongs in the browser. Your route handler holds it and the customer only ever talks to your route. The examples above already do this.
-
-Second, add rate limiting. The chat API does not limit how often each visitor can send messages. It caps how many calls run at once and stops when your credits run out, but nothing stops one visitor from sending request after request, and every request spends your drive's credits. On a public page that is an open door to drain your balance. Put a limit in the route that sits in front of the chat API: throttle by IP or session, cap messages per minute, and reject anything over the line before it reaches PageSpace. A few lines in the same route handler that already holds the token.
-
-Look at everything you did not have to build: no AI model to choose and pay a separate vendor to run, no instructions to keep up to date in your code (they live on the agent page, editable by your support lead), no search system to build and maintain so the bot finds the right article (the agent reads your drive directly, since [the drive is the context](/blog/your-workspace-is-the-context)), and no database of past conversations (they are already pages in your workspace). You brought a chat box and a key. PageSpace brought the rest. The full technical reference is in the [Agent API docs](/docs/features/agent-api) and the [SDK docs](/docs/features/sdk).
-`,
- author: "PageSpace Team",
- date: "2026-07-14",
- readTime: "7 min read",
- category: "Guide",
- },
- "usage-based-pricing-and-built-for-scale": {
- slug: "usage-based-pricing-and-built-for-scale",
- title:
- "Credits Replace Daily Limits: Pay for What You Use, Run the Models You Want",
- description:
- "PageSpace AI is now usage-based. Every plan gets a monthly pool of credits you spend however you like, paid plans unlock frontier models, and you can top up any amount from $5 to $500. Here's how it works, plus the infrastructure move that makes it scale.",
- image: "/blog/usage-based-pricing-and-built-for-scale.png",
- content: `
-## Daily limits were a stopgap. We outgrew them.
-
-When PageSpace was small, the simplest way to meter AI was a daily call count. Free got 50 calls a day, Pro got 200. Cross the line and you were done until midnight, even if every call that day was a one-line throwaway. It shipped fast and it was easy to understand. It was also a blunt instrument that didn't really scale, for you or for us.
-
-A quick yes/no question and a ten-step research agent counted the same: one call. The number on the wall had nothing to do with the cost behind it. And we kept leaning into longer, more agentic work: agents that plan, call tools, and run for many steps on your behalf, where a single task can do the work of a hundred old "calls." Stack that on top of better, more expensive models and the call-count math fell apart. We could cap you harder or quietly eat costs we couldn't sustain, and neither is how you build something meant to last.
-
-So we put our big-boy pants on. AI in PageSpace is now usage-based: a monthly pool of credits you spend on exactly what you use, priced on what each model actually costs. It's the model we should have started with. It's what lets us scale, and it's what lets us hand you genuinely better models instead of holding them back.
-
-## Credits, not call counts
-
-Every plan comes with a monthly pool of credits:
-
-- **Free:** 5/month in credits
-- **Pro:** 15/month in credits
-- **Founder:** 50/month in credits
-- **Business:** 100/month in credits
-
-Each period adds to your balance — unused credits carry over, so nothing is lost. You spend it however you like: long agent runs, quick questions, voice, whatever the work needs. There's no per-day ceiling and no separate bucket for "standard" versus "heavy" usage. It's one balance, and you decide where it goes.
-
-Each call draws from your balance based on what that model actually costs. No per-model multipliers, no rounding a fraction-of-a-cent call up to something absurd. A cheap model costs you a little and an expensive one costs you more — keeping the math simple is what lets us open up the best models from every major provider instead of charging extra for the good ones.
-
-That's the whole point of usage-based pricing: what you pay tracks what you actually do.
-
-## Pick the model that fits the job
-
-Here's the part daily limits could never give you: when billing is usage-based, model choice opens up.
-
-**Free** runs on fast, capable standard models: GPT-5.6 Luna by default, plus Claude Haiku 4.5, Gemini 3.5 Flash Lite, and the GPT-5.4 mini and nano variants. They're quick, they're cheap, and your credits stretch a long way across them.
-
-**Pro, Founder, and Business** unlock the frontier. Spend your credits on Claude Opus 4.8, the GPT-5.5 family, Gemini 3.1 Pro, whatever the task calls for. A throwaway question doesn't need Opus, so reach for a light model and your credits last. A gnarly refactor or a long research synthesis is worth the spend, so reach for the flagship. You make that trade-off per task instead of having it made for you.
-
-## When you run low
-
-If your balance runs out before your next renewal, you're not locked out until tomorrow. Top up.
-
-You can add **any amount from $5 to $500** in one click, or grab a quick-pick pack of $10, $25, or $50. Top-up credits never expire, so anything you add during a busy week carries over.
-
-Run low, top up, keep going. No more waiting out a daily reset.
-
-## Built to scale
-
-Pricing is only half of this. PageSpace now runs on scalable, modern infrastructure built to grow:
-
-- **Built to scale out.** Storage and request handling are stateless, so PageSpace can spread across many machines and bring more online as demand rises. Capacity grows with usage instead of capping it.
-- **Faster file delivery.** Your uploads and attachments serve straight from object storage instead of round-tripping through a single app server. Less waiting, more consistent speed.
-- **Bigger uploads, including video.** With files on object storage instead of one machine's disk, upload limits go up and video is in.
-
-This is the foundation the credit model needs. Usage-based pricing only works if the platform can actually absorb the usage, and now it can.
-
-## What's next
-
-**Agents that run real code.** AI agents will execute code in isolated, sandboxed containers right inside your workspace. Not "here's a snippet to copy," but the agent actually running it in a throwaway sandbox. More on that as it lands.
-
-## What doesn't change
-
-Credits only meter AI. Your documents, tasks, channels, files, and collaboration cost nothing and work exactly as before. Nothing about how you and your team work together is changing.
-
-Open your plan to see your balance, pick your models, and top up when you want. Pay for what you use, run what you need.
- `,
- author: "Jono",
- date: "2026-06-03",
- readTime: "6 min read",
- category: "Product",
- featured: false,
- },
- "your-workspace-is-the-context": {
- slug: "your-workspace-is-the-context",
- title:
- "Your Workspace Is the Context: How PageSpace Teaches AI Where It Is",
- description:
- "Most AI tools dump flat text into a prompt. PageSpace gives AI a map — a tree structure that encodes location, hierarchy, and meaning. Here's how workspace organization becomes AI understanding.",
- image: "/blog/workspace-is-the-context.png",
- content: `
-## The Problem with Flat Context
-
-Most AI tools work like this: take a blob of text, shove it into the system prompt, hope the model figures it out.
-
-There's no structure. No location. No hierarchy. The AI doesn't know where it is, what's around it, or how the information relates to anything else. It's reading a book with no table of contents, no chapters, no page numbers.
-
-You end up doing the work the AI should be doing — explaining what project this is, what files are relevant, what conventions apply. Every session. Every time.
-
-The problem isn't the AI's capability. It's that nobody gave it a map.
-
-## The Tree as a Semantic Structure
-
-PageSpace organizes everything in a page tree. Folders, documents, AI agents, chat channels, spreadsheets, canvases — they all live in a hierarchical structure you design.
-
-This isn't just a UI convenience. The tree *is* the context model.
-
-Where a page lives tells the AI what it means. A document called "API Design" under \`/Engineering/Backend/\` carries different weight than a document with the same name under \`/Archive/Old Drafts/\`. The path encodes intent, scope, and relevance — without you writing a single annotation.
-
-When you organize your workspace, you're not just tidying up for yourself. You're teaching the AI how your work is structured.
-
-## Breadcrumb Path Injection
-
-When you chat with an AI agent in PageSpace, the system builds a breadcrumb path from the page's position in the tree and injects it directly into the system prompt.
-
-Here's what the AI actually receives — the real \`PAGE CONTEXT\` block from the system prompt builder:
-
-\`\`\`
-PAGE CONTEXT:
-• Location: /engineering/backend/api-design
-• Type: DOCUMENT
-• Path: Engineering > Backend > API Design
-• When users say "here", they mean this page
-\`\`\`
-
-The AI now knows it's looking at an API design document inside the backend section of the engineering workspace. When you say "here," it means something specific. When you say "update this," the AI knows exactly what "this" refers to.
-
-No ambiguity. No guessing. The tree position resolves it.
-
-## Workspace Structure as a Map
-
-Beyond knowing its own location, the AI can receive the entire workspace tree — formatted as a visual hierarchy that mirrors the structure you built.
-
-Here's the real format, generated from PageSpace's tree formatter:
-
-\`\`\`
-├── 📁 Engineering
-│ ├── 📄 Architecture Overview
-│ ├── 🤖 Code Review Agent
-│ └── 📁 Backend
-│ ├── 📄 API Design
-│ └── 📄 Database Schema
-└── 📁 Product
- ├── 📄 Roadmap
- └── 💬 Team Discussion
-\`\`\`
-
-The AI can see sibling pages, parent folders, and the full organizational context. It knows that "Architecture Overview" and "Code Review Agent" sit alongside the "Backend" folder. It can decide what to read based on structure, not guesswork.
-
-When someone asks "what do we know about the backend?" the AI doesn't search blindly — it looks at the Backend folder, sees what's there, and reads what's relevant. The tree tells it where to look.
-
-## Inline Instructions — What the AI Knows About Itself
-
-Beyond location, the AI receives contextual rules that define how it should behave in this specific context. These come from PageSpace's inline instruction system:
-
-\`\`\`
-CONTEXT:
-• Current location: "API Design" [DOCUMENT] at /eng/backend/api-design in "Engineering"
-• DriveSlug: eng, DriveId: abc123
-• When user says "here" or "this", they mean this location
-• Explore current drive first (list_pages) before other drives
-\`\`\`
-
-The AI also receives page type documentation — what each type can do, what operations make sense. It knows that a DOCUMENT page supports rich text editing and content updates. It knows that a SPREADSHEET page has structured data. It knows that an AI_CHAT page is a conversation.
-
-Add workspace-level rules — your team's conventions, writing style preferences, tool restrictions — and the AI's behavior becomes specific to where it's operating, not generic across all contexts.
-
-## The Tools — How the AI Explores
-
-PageSpace doesn't just give the AI a static context dump. It gives the AI real tools to explore your workspace actively:
-
-- **\`list_pages\`** — Browse the tree structure, see what's inside any folder
-- **\`read_page\`** — Read the full content of any page in the workspace
-- **\`regex_search\`** — Search content across pages with regex patterns, returns matches with line numbers and semantic paths
-- **\`glob_search\`** — Find pages by path patterns like \`**/meeting-notes/*\` or \`engineering/**\`
-- **\`multi_drive_search\`** — Search across all workspaces at once when the answer might live somewhere else
-- **\`ask_agent\`** — Call another AI agent in the workspace, delegating specialized questions to agents with domain expertise
-
-The AI doesn't just receive context passively — it can actively navigate. The tree gives it a map. The tools let it move through it. When the AI needs to find related documents, it doesn't ask you to paste them. It searches, reads, and synthesizes on its own.
-
-## Page Agents: Context-Aware AI in the Tree
-
-Page agents are AI_CHAT pages that live in the tree alongside your documents. They're not separate from your workspace — they're part of it.
-
-Each agent can be configured with:
-
-- **Custom system prompts** — specific instructions for what this agent knows and how it should behave
-- **Tool permissions** — allow only read tools for a research agent, or write tools for an editor agent
-- **Page tree visibility** — see only children of the current folder, or the full drive structure
-- **Drive prompt inclusion** — inherit workspace-level instructions and conventions
-
-Here's why the tree matters for agents: an agent nested under \`/Engineering/Backend/\` naturally has context about backend engineering. Its position in the tree encodes its purpose. A "Code Review Agent" sitting next to "Architecture Overview" and "API Design" inherently understands its scope — it's there to review code in the context of that architecture and those APIs.
-
-You don't need to write elaborate prompts explaining what the agent should focus on. The tree already told it.
-
-## The Composable Prompt
-
-All of this comes together in how PageSpace assembles the final system prompt. It's not one monolithic block — it's built from discrete, composable layers:
-
-1. **Core role definition** — the base personality and capabilities
-2. **User personalization** — your bio, writing style, custom rules
-3. **Location context** — breadcrumbs, drive info, page type
-4. **Workspace structure** — the tree, visible as a map
-5. **Inline instructions** — contextual rules for this specific location
-6. **Timestamp context** — current date, time, and timezone
-7. **Agent awareness** — list of other agents available to consult
-
-Each layer is independently cacheable. Each is determined by the tree position — different locations produce different prompts. An agent in the Engineering folder gets engineering context. An agent in Product gets product context. Same underlying model, different understanding.
-
-The tree isn't just organizing your files. It's programming the AI.
-
-## Why This Matters
-
-AI that knows where it is makes better decisions.
-
-It doesn't need you to explain the project structure — it can see the tree. It doesn't need you to point at related documents — it can find them. It understands that a page under "Architecture Decisions" is different from a page under "Meeting Notes" even if they mention the same topic.
-
-Structure is meaning. A flat list of documents is just noise. A tree is a semantic model — every folder boundary, every nesting level, every sibling relationship carries information about how your work fits together.
-
-When you organize your PageSpace workspace, you're not doing busywork. You're building the context model that makes your AI actually useful. The workspace *is* the prompt.
-
-Your workspace is the context. Make it a good one.
- `,
- author: "Jono",
- date: "2026-02-17",
- readTime: "9 min read",
- category: "Product",
- featured: false,
- },
- "pagespace-as-memory-for-coding-agents": {
- slug: "pagespace-as-memory-for-coding-agents",
- title: "Using PageSpace as Memory for Your Coding Agent",
- description:
- "Coding agents are stateless. Every session starts from scratch. Here's how to give them persistent memory with PageSpace and MCP — including cloud agents that intelligently retrieve the right context.",
- image: "/blog/pagespace-memory-coding-agents.png",
- content: `
-## The Problem: Your Coding Agent Has Amnesia
-
-Every time you start a new session with your coding agent, it forgets everything. The architecture decisions you explained yesterday. The conventions your team agreed on last week. The debugging session where you finally figured out that weird race condition.
-
-You re-explain the same context over and over. You paste the same docs into chat. You point at the same files and say "remember, we do it this way."
-
-CLAUDE.md files help — they give your agent a starting point. But they're static, local to one repo, and limited in what they can capture. They're a sticky note on the monitor, not a knowledge base.
-
-What if your coding agent could actually remember?
-
-## What If Your Agent Had a Knowledge Base?
-
-Imagine a persistent, organized workspace your coding agent can read from, write to, and search across. Not files buried in your repo — a structured knowledge system that:
-
-- **Persists across sessions** — context survives after you close your terminal
-- **Works across projects** — your React conventions apply whether you're in the frontend repo or the monorepo
-- **Shares across machines** — same knowledge base on your laptop and your CI server
-- **Stays organized** — not a flat dump of text, but a hierarchy of pages, folders, and agents
-
-That's what PageSpace gives your coding agent.
-
-## PageSpace + MCP: The Connection
-
-PageSpace publishes an MCP server — \`pagespace-mcp\` on npm — that exposes your knowledge base to any coding agent that supports the Model Context Protocol.
-
-Install it, point it at your PageSpace instance with a token, and your coding agent gets direct access to search, read, and write pages in your workspace.
-
-MCP is the protocol. PageSpace is the memory.
-
-## Setup
-
-Add PageSpace to your coding agent's MCP configuration:
-
-\`\`\`json
-{
- "mcpServers": {
- "pagespace": {
- "command": "npx",
- "args": ["-y", "pagespace-mcp@latest"],
- "env": {
- "PAGESPACE_API_URL": "https://your-instance.pagespace.ai",
- "PAGESPACE_AUTH_TOKEN": "your-mcp-token"
- }
- }
- }
-}
-\`\`\`
-
-Generate an MCP token from your PageSpace workspace settings. The token is scoped to a specific drive, so you control exactly what your agent can access.
-
-That's it. Your coding agent can now search your knowledge base, read pages, create new ones, and update existing content — all through natural tool calls.
-
-## The Real Differentiator: Cloud Agents
-
-Here's where PageSpace goes beyond "document storage your agent can read."
-
-PageSpace has **page agents** — cloud AI agents with custom instructions that live inside your knowledge base. They're not just documents. They're intelligent retrieval and processing layers that sit between your coding agent and your knowledge.
-
-When your coding agent calls PageSpace via MCP, it can talk to these page agents. That means:
-
-**Smart retrieval, not keyword search.** Instead of your coding agent trying to guess which document has the answer, it can ask a page agent: "What are our conventions for error handling in API routes?" The page agent knows the knowledge base, understands the question, and returns the relevant context.
-
-**Summarization on demand.** A page agent can digest a 20-page architecture doc into the three paragraphs your coding agent actually needs for its current task.
-
-**Knowledge base maintenance.** Page agents can organize incoming information. Your coding agent writes a raw note about a decision you made — a page agent files it properly, links it to related docs, and keeps the knowledge base clean.
-
-**Shared context across agents.** Multiple coding agents — yours, your teammate's, your CI pipeline's — all read from and write to the same workspace. Page agents ensure consistency. One source of truth, many consumers.
-
-This is the difference between giving your agent a filing cabinet and giving it a research assistant.
-
-## What to Put in Your Knowledge Base
-
-Start with what you find yourself re-explaining to your coding agent:
-
-- **Architecture decisions and rationale** — why you chose that database, why the auth works that way, why you split that service
-- **Coding conventions and patterns** — how you structure components, naming rules, error handling patterns, test conventions
-- **API documentation** — internal APIs, external integrations, authentication flows
-- **Debugging playbooks** — "when X happens, check Y" knowledge that's hard to capture in code comments
-- **Meeting notes and decisions** — the context behind the code, not just the code itself
-- **Project roadmaps** — what's planned, what's in progress, what's blocked and why
-
-The key insight: anything you'd explain verbally to a new team member belongs in your knowledge base. Your coding agent is a new team member every single session.
-
-## Security
-
-Giving an AI agent access to your knowledge base requires trust in the access model:
-
-- **Drive-scoped tokens** — each MCP token is scoped to a single drive. Your agent sees only what you explicitly share
-- **Audit logging** — every read and write through MCP is logged
-- **Fail-closed permissions** — if the token doesn't grant access, the request is denied. No fallbacks, no defaults
-
-You control the boundary. The agent works within it.
-
-## Getting Started
-
-1. Create a PageSpace account and set up a drive for your project knowledge
-2. Add your architecture docs, conventions, and patterns as pages
-3. Create page agents with instructions tailored to your workflow
-4. Generate an MCP token and add the config to your coding agent
-5. Start a session and ask your agent to check the knowledge base
-
-Your coding agent just got a memory. Use it.
- `,
- author: "Jono",
- date: "2026-02-17",
- readTime: "8 min read",
- category: "Guide",
- },
- "google-calendar-sync-setup": {
- slug: "google-calendar-sync-setup",
- title: "How to Connect Google Calendar and Let AI See Your Schedule",
- description:
- "A step-by-step guide to connecting Google Calendar with PageSpace. Two-way sync, calendar selection, and how to use AI agents that can actually check your availability and schedule meetings.",
- image: "/blog/google-calendar-sync-setup.png",
- content: `
-## Why This Matters
-
-Your AI agent can read your documents, search your workspace, write content, and manage tasks. It has no idea what's on your calendar.
-
-Every scheduling suggestion is a guess. Every prioritization ignores the meeting you have in 30 minutes. Every "when should we do this?" turns into an alt-tab to Google Calendar and a manual relay back.
-
-Connecting Google Calendar to PageSpace fixes that. Your events sync in, your AI agents get real tools to query your schedule and create events that push back to Google. This guide walks through setup.
-
-## Step 1: Connect Your Google Account
-
-Open **Settings** in PageSpace. Under integrations, find **Google Calendar** and click **Connect Google Calendar**.
-
-You'll be redirected to Google's consent screen. PageSpace requests read and write access to your calendars. Write access is needed so AI-created events can push back to Google.
-
-Grant the permissions. Google redirects you back to PageSpace. You'll see a green "Connected" badge with your Google email on the settings page.
-
-If the flow fails, PageSpace tells you why. Most common: you clicked cancel ("Access denied") or waited too long to complete it ("State expired"). Click Connect again.
-
-## Step 2: Pick Your Calendars
-
-Once connected, PageSpace fetches your available Google calendars and displays them as a checklist. Your primary calendar is selected by default.
-
-You'll see every calendar on your Google account. Primary, work, holidays, shared team calendars, that "Birthdays" calendar you forgot you had. Each one shows its color dot and name.
-
-Check the ones you want to sync. Uncheck the ones you don't. Changes save immediately, and PageSpace triggers a sync for any newly selected calendar so events appear right away.
-
-You need at least one calendar selected. If you try to uncheck all of them, PageSpace blocks it.
-
-## Step 3: First Sync
-
-After connecting, PageSpace runs an initial sync automatically. It pulls events from the past 30 days through the next 90 days. This gives your AI agents enough historical context for patterns and enough future context for scheduling.
-
-The first sync might take a few seconds depending on how many events you have. After that, sync is incremental. Only changed events transfer. Google sends push notifications to PageSpace in real time when events change, and a background job polls every 15 minutes as a fallback. Between the two, your PageSpace calendar stays current without you doing anything.
-
-You can always hit **Sync Now** on the settings page to force an immediate sync. The page shows your last sync time and how many events are currently synced.
-
-## Two-Way Sync
-
-Events from Google appear in your PageSpace calendar. Events created in PageSpace push back to Google. A colleague reschedules through Google, PageSpace picks it up. Your AI agent creates a "Project Review" event, it shows up on your phone.
-
-When an agent schedules a meeting, it's not creating a PageSpace-only event nobody else can see. It's a real calendar event that lands in Google Calendar for every attendee.
-
-## What Your AI Can Do With Your Calendar
-
-Once connected, your AI agents get calendar tools. Not a read-only view. Real tools with real parameters.
-
-**See your schedule.** The agent lists events in any date range across all synced calendars. "What do I have this week?" gets a real answer.
-
-**Check availability.** The agent queries a date range and gets back free time slots. It respects working hours. It merges overlapping events. A meeting from 2:00 to 3:00 and a call from 2:30 to 3:30 show as one busy block, not two.
-
-**Schedule meetings.** The agent creates events with title, time, duration, location, recurrence, attendees, and visibility. The event pushes to Google Calendar automatically.
-
-**Manage the full lifecycle.** Update events, cancel them, RSVP on your behalf, add or remove attendees.
-
-These are the tools an executive assistant would need.
-
-## Scheduling Example
-
-You're in a PageSpace AI chat working on a project, and you type:
-
-*"Schedule a 30-minute project review with the team for sometime Thursday afternoon. Find a slot that works."*
-
-The agent calls \`check_calendar_availability\` for Thursday afternoon. Finds 2:00 to 2:30 open. Calls \`create_calendar_event\` with the title, time slot, and your workspace members as attendees. The event syncs to Google Calendar. Everyone sees it on their phone. You never left the chat.
-
-That's the difference between a calendar display widget and calendar tools.
-
-## Time-Triggered Agents
-
-Calendar events can trigger AI agents to run at event time.
-
-When you create an event in PageSpace, you can attach an AI agent page and a prompt. When the event arrives, the agent wakes up with full event context and executes.
-
-Practical example: create a recurring event, "Weekly Metrics Review," every Monday at 9am. Attach your analytics agent with instructions to read the latest data pages in your workspace and write a summary document. Every Monday morning, the agent runs. It reads live data. It writes the report. No human involvement. The summary is always current because the agent reads what exists now, not a cached snapshot.
-
-Another one. Project deadline is Friday. Create an event, "Pre-deadline check," Thursday at 4pm. Attach a project agent with instructions to review the open tasks and post a status summary. Thursday afternoon, the agent runs a check for you while you're still in meetings.
-
-The trigger system checks drive access, agent page existence, and your available credits before executing. If any check fails, nothing runs and nothing breaks.
-
-The calendar becomes a scheduler for AI work.
-
-## Security and Permissions
-
-AI calendar tools go through the exact same permission system as every other tool in PageSpace. If you can't see an event, your AI agent can't either. If you're not a member of a drive, calendar queries for that drive return nothing.
-
-Tokens are encrypted at rest. Webhook authentication is cryptographically signed. Token refresh happens automatically before expiration so sync never fails mid-request.
-
-Your calendar data is encrypted, never shared with third parties, and you can disconnect at any time from the settings page. Disconnecting revokes your token on Google's side and stops all sync.
-
-Connect your calendar. Let your AI see your schedule.
- `,
- author: "Jono",
- date: "2026-04-15",
- readTime: "5 min read",
- category: "Guide",
- },
- "ai-versioning-safety": {
- slug: "ai-versioning-safety",
- title:
- "The Undo Button for AI: How Three Layers of Versioning Make Full Agent Access Safe",
- description:
- "AI needs write access to be useful, but write access without a safety net is reckless. PageSpace builds versioning and rollback so deep that any AI change can be reversed instantly — at the page, conversation, or entire workspace level.",
- image: "/blog/ai-versioning-safety.png",
- content: `
-## The Access Dilemma
-
-There's a tension at the center of every AI-powered workspace tool, and most of them pretend it doesn't exist.
-
-If your AI can only read your content, it's a search engine with a personality. It can summarize, answer questions, maybe find a document you forgot about. Useful, but limited. You still do all the actual work — creating pages, editing text, organizing files, updating spreadsheets.
-
-If your AI can write, edit, create, and delete — now it's a collaborator. It can draft documents, reorganize your workspace, update task lists, edit code, build out entire project structures. That's where the real productivity gains live. An AI that can only look at your work is an observer. An AI that can change your work is a partner.
-
-But write access is terrifying.
-
-What happens when the AI misunderstands your request and rewrites your carefully crafted architecture document? What happens when it deletes pages it shouldn't have touched? What happens when a multi-step AI operation goes sideways halfway through — three pages updated, two created, one renamed — and you need to get back to where you were?
-
-Most platforms pick a side. Either they restrict the AI to keep things safe (and lose most of the value), or they hand over full access and hope their model is good enough to not break things (and it isn't, always).
-
-There's a third option: give the AI real tools, and make everything it does reversible.
-
-## What "Reversible" Actually Requires
-
-Saying "we have version history" isn't enough. A basic version history that saves a copy every time you hit save doesn't solve the AI problem.
-
-AI doesn't edit like humans do. A human opens a document, reads it, makes a few changes, saves. An AI agent might update five pages in a single conversation turn. It might create new pages, edit existing ones, and reorganize the tree — all in response to a single prompt. The changes are fast, distributed across multiple resources, and linked by conversational context that a simple version timeline knows nothing about.
-
-To make AI actions truly reversible, you need versioning that understands three things:
-
-1. **What changed on each individual page** — so you can restore a single page without affecting everything else
-2. **What the AI did across an entire conversation** — so you can undo a multi-page operation as one atomic action
-3. **What your entire workspace looked like at a point in time** — so you can recover from anything, no matter how many changes were made
-
-PageSpace builds all three.
-
-## Layer 1: Page Versions — The Automatic Snapshot
-
-Every time a page is modified in PageSpace, a version is created. This happens whether a human or an AI made the change. But here's the detail that matters for AI safety: the system tags versions differently depending on who initiated the edit.
-
-When a human edits a page, the version source is tagged as \`auto\`. When an AI agent edits a page, the version source is tagged as \`pre_ai\`.
-
-This isn't a label for display purposes. It's a first-class concept in the database schema — a dedicated enum value that the entire versioning system understands. The system knows, at the data level, that this version exists because an AI was about to change something.
-
-Why does this matter? Because when you're looking at a page's version history, you can see exactly where the AI intervened. You can see the state of the page immediately before the AI touched it. And you can restore to that exact point with one click.
-
-Each version captures:
-
-- **The full page content** — stored with a SHA-256 content reference, optionally compressed for large pages
-- **The content format** — whether it's rich text, markdown, JSON, or raw HTML
-- **A state hash** — a computed fingerprint of the page's complete state (title, content, position, settings), so the system can detect if the page has been modified since the version was created
-- **The page revision number** — a monotonically increasing counter that detects concurrent edits
-
-Versions are retained for 30 days by default. If a version is important — say, it's the last known-good state before a major AI-driven rewrite — you can pin it, and it's exempt from expiration.
-
-This isn't "undo" in the Ctrl+Z sense. It's a complete, content-addressable snapshot of every page state, with AI changes explicitly marked as a distinct source.
-
-## Layer 2: Conversation Undo — Reversing What the AI Did
-
-Page versions let you restore individual pages. But AI agents don't just edit one page at a time.
-
-In a single conversation, an AI agent in PageSpace might create a new document, edit an existing one, update a spreadsheet, rename a page, and move something into a different folder. Each of those actions creates its own page version and activity log entry. But from the user's perspective, those five changes were one thing: "the AI did what I asked."
-
-If the result isn't what you wanted, you don't want to manually find and revert five separate page versions. You want to undo everything the AI did from that point forward.
-
-That's what conversation undo does.
-
-When you trigger an undo on any message in a PageSpace AI conversation, the system finds every change that the AI made at or after that message. It traces the connection through the conversation ID that's stamped on every activity log entry — every page create, update, delete, rename, and move that the AI performed during that conversation.
-
-You get two options:
-
-**Messages only** — Remove the conversation messages from that point forward (soft-delete, not permanent destruction). The AI's changes to pages stay in place. Use this when the AI said something unhelpful but the actual edits were fine.
-
-**Messages and changes** — Remove the messages AND roll back every change the AI made to your workspace. Every page edit reverted. Every page creation undone. Every rename reversed. All of it, atomically, in a single database transaction.
-
-Before any of this executes, you get a preview. The system shows you exactly how many messages will be removed, exactly which pages and activities will be affected, and whether any of those pages have been modified since the AI touched them (conflict detection). If someone else — or you — made additional edits on top of the AI's changes, the system warns you. You can force the rollback if you want, but you'll know what you're overriding.
-
-This isn't just "go back to a previous version." This is "identify every side effect of a multi-step AI operation and reverse all of them as one unit."
-
-## Layer 3: Drive Backups — The Full Workspace Snapshot
-
-Pages and conversations are fine-grained. But sometimes you need the nuclear option.
-
-Maybe you handed an AI agent a broad instruction and it reorganized half your workspace. Maybe you're about to let a new AI agent loose on your knowledge base and you want a checkpoint first. Maybe it's Tuesday and you just want a backup.
-
-Drive backups capture everything:
-
-- **Every page in the workspace** — including content, metadata, and tree position (you can optionally include trashed pages too)
-- **All permissions** — who has access to what, at what level
-- **All members and their roles** — the full team structure
-- **All files** — attachments, uploads, everything stored in the drive
-
-Backups can be created manually (click a button), on a schedule (automated), or automatically before a restore operation (so restoring from a backup doesn't destroy your current state — the system snapshots what you have before overwriting it).
-
-Each backup records page count, total content size, custom labels, and an optional reason field. You can annotate your backups: "Before letting the new research agent run" or "Pre-migration checkpoint."
-
-This is the "I don't know exactly what went wrong but I need to get back to last Thursday" layer.
-
-## The Audit Trail — Knowing Exactly What Happened
-
-Versioning lets you go back. The audit trail lets you understand what happened in the first place.
-
-Every action an AI takes in PageSpace is logged with full attribution:
-
-- **Which AI provider and model** performed the action (not just "AI did this" — you know it was Claude 3.5 Sonnet, or GPT-4, or whatever model you're using)
-- **Which conversation** the action was part of — linking the change back to the exact chat where you gave the instruction
-- **What changed** — previous values and new values for every modified field
-- **State hashes before and after** — cryptographic proof of what the page looked like before and after the change
-- **The agent chain** — if a sub-agent was involved (one AI agent delegating to another), the full chain of delegation is recorded
-
-This matters for teams. When three people are using AI agents in the same workspace, and someone notices a document looks different, the audit trail tells you which agent changed it, which conversation initiated it, and which user was driving that conversation. No ambiguity. No "the AI did it" with no further details.
-
-## Permission Checks Still Apply
-
-A common fear with AI agents is that they'll access things they shouldn't — reading private documents, editing pages in someone else's project, deleting things outside their scope.
-
-In PageSpace, AI agents go through the exact same permission system as human users. Every single tool call — create, edit, delete, rename, move — checks permissions before executing.
-
-If a user doesn't have edit access to a page, their AI agent doesn't either. If a drive is restricted to certain members, an AI agent operating on behalf of a non-member gets denied. There's no backdoor, no elevated privilege, no "the AI needs access so we'll skip the check."
-
-The permission functions are centralized — the same code path that validates a human's edit request validates the AI's. One system. One set of rules. Whether you're clicking a button or the AI is calling a tool, the access check is identical.
-
-## Why This Approach Beats the Alternatives
-
-**Restricting AI to read-only** means you're leaving most of the value on the table. An AI that can't actually do things for you is an expensive search box. You still write every document, organize every folder, update every spreadsheet yourself. The AI watches.
-
-**Giving AI full access with no safety net** works until it doesn't. And when it doesn't, the cost is high — lost content, broken organization, hours of manual recovery. The more powerful the AI, the more damage a single bad instruction can cause. "Just be careful with your prompts" is not a safety strategy.
-
-**Making AI actions reversible at every level** is the approach that lets you actually use AI for real work without anxiety. You don't need to craft the perfect prompt. You don't need to review every change before it's made. You give the AI an instruction, see what it does, and if the result isn't right, you roll it back — one page, one conversation, or the entire workspace.
-
-The safety isn't in preventing the AI from acting. It's in making every action undoable.
-
-## What This Looks Like in Practice
-
-You're working on a product launch. You ask your AI agent to draft documentation for three new features across three separate pages.
-
-The AI creates the pages, writes the content, organizes them under the right folder. Each page gets a \`pre_ai\` version snapshot automatically. Every action is logged with the conversation ID, the model used, and the full before/after state.
-
-You review the drafts. Two are great. One completely missed the point.
-
-You have options. You can restore that one page to its pre-AI state using the page version history. Or if the AI also made structural changes you don't like — moved things around, renamed the folder — you can undo the entire conversation, reverting every change the AI made in that session. Or if you realize this after a week of additional work and just want to grab the pre-AI content from that one page, the version is still there, tagged and searchable.
-
-At no point did you lose anything. At no point were you at the mercy of the AI getting it right the first time.
-
-## The Uncomfortable Truth About AI Access
-
-Here's what the industry doesn't want to talk about: AI models will make mistakes. They'll misinterpret instructions. They'll take actions you didn't intend. This isn't a bug that will be fixed in the next model release — it's an inherent property of working with systems that interpret natural language.
-
-The question isn't whether your AI will ever do the wrong thing. It's what happens when it does.
-
-If the answer is "you lose your work" or "you spend an hour manually fixing things" — that's a platform problem, not an AI problem. The model will get better over time. But even a perfect model operating on an ambiguous instruction will sometimes produce the wrong result.
-
-The platforms that win won't be the ones with the most powerful AI. They'll be the ones where using powerful AI is safe. Where you can give an AI agent real tools — create, edit, delete, reorganize — and know that no matter what happens, you can get back to where you were.
-
-That's what three layers of versioning gets you. Not a restriction on what AI can do. A guarantee that whatever it does can be undone.
- `,
- author: "Jono",
- date: "2026-04-10",
- readTime: "10 min read",
- category: "Product",
- },
-};
diff --git a/apps/marketing/src/app/blog/[slug]/page.tsx b/apps/marketing/src/app/blog/[slug]/page.tsx
deleted file mode 100644
index 67a8f9727f..0000000000
--- a/apps/marketing/src/app/blog/[slug]/page.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import Link from "next/link";
-import Image from "next/image";
-import { notFound } from "next/navigation";
-import { ArrowLeft, Calendar, Clock, User } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { SiteNavbar } from "@/components/SiteNavbar";
-import { SiteFooter } from "@/components/SiteFooter";
-import type { Metadata } from "next";
-import ReactMarkdown from "react-markdown";
-import remarkGfm from "remark-gfm";
-import { blogPosts, formatDate } from "./data";
-import { ShareButtons } from "./ShareButtons";
-import { MONTHLY_CREDITS } from "@/lib/credits";
-import { APP_URL, SITE_URL } from "@/lib/metadata";
-import { JsonLd, createArticleSchema } from "@/lib/schema";
-
-export async function generateStaticParams() {
- return Object.keys(blogPosts).map((slug) => ({ slug }));
-}
-
-export async function generateMetadata(
- props: { params: Promise<{ slug: string }> }
-): Promise {
- const { slug } = await props.params;
- const post = blogPosts[slug];
-
- if (!post) {
- return { title: "Post Not Found | PageSpace Blog" };
- }
-
- return {
- title: `${post.title} | PageSpace Blog`,
- description: post.description,
- alternates: {
- canonical: `${SITE_URL}/blog/${slug}`,
- },
- openGraph: {
- title: post.title,
- description: post.description,
- type: "article",
- url: `${SITE_URL}/blog/${slug}`,
- publishedTime: post.date,
- authors: [post.author],
- ...(post.image && { images: [{ url: `${SITE_URL}${post.image}` }] }),
- },
- };
-}
-
-export default async function BlogPostPage(
- props: { params: Promise<{ slug: string }> }
-) {
- const { slug } = await props.params;
- const post = blogPosts[slug];
-
- if (!post) {
- notFound();
- }
-
- return (
-
-
-
-
- {/* Back Link */}
-
-
-
- Back to Blog
-
-
-
- {/* Article */}
-
-
-
- {/* Header */}
-
-
- {post.category}
-
-
- {post.title}
-
-
- {post.description}
-
-
-
-
- {post.author}
-
-
-
- {formatDate(post.date)}
-
-
-
- {post.readTime}
-
-
-
-
- {/* Feature Image */}
- {post.image ? (
-
-
-
- ) : (
-
- )}
-
- {/* Content */}
-
-
- {post.content}
-
-
-
- {/* Share */}
-
-
- Share this article
-
-
-
-
-
-
-
- {/* CTA */}
-
-
-
-
Ready to try PageSpace?
-
- Start free with {MONTHLY_CREDITS.free}/month in credits. No
- credit card required.
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/marketing/src/app/blog/page.tsx b/apps/marketing/src/app/blog/page.tsx
deleted file mode 100644
index a75cf14b98..0000000000
--- a/apps/marketing/src/app/blog/page.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import Link from "next/link";
-import Image from "next/image";
-import { Calendar, Clock, User, PenLine } from "lucide-react";
-import { SiteNavbar } from "@/components/SiteNavbar";
-import { SiteFooter } from "@/components/SiteFooter";
-import { pageMetadata } from "@/lib/metadata";
-import { blogPosts as blogPostsRecord, formatDate } from "./[slug]/data";
-
-export const metadata = pageMetadata.blog;
-
-const blogPosts = Object.values(blogPostsRecord);
-
-export default function BlogPage() {
- const featuredPost = blogPosts.find((post) => post.featured);
- const regularPosts = blogPosts.filter((post) => !post.featured);
-
- return (
-
-
-
- {/* Hero */}
-
-
-
-
- Blog
-
-
- Product updates, technical deep dives, and thoughts on the future of AI-native collaboration.
-
-
-
-
-
- {/* Featured Post */}
- {featuredPost && (
-
-
- We're working on new guides, deep dives, and product updates.
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/apps/marketing/src/app/docs/core-concepts/page.tsx b/apps/marketing/src/app/docs/core-concepts/page.tsx
deleted file mode 100644
index dca5506388..0000000000
--- a/apps/marketing/src/app/docs/core-concepts/page.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Core Concepts",
- description: "Foundational principles of PageSpace: pages as primitives, tree-structured hierarchy, context inheritance, and recursive composition.",
- path: "/docs/core-concepts",
- keywords: ["core concepts", "architecture", "pages", "hierarchy", "context"],
-});
-
-const content = `
-# Core Concepts
-
-These are the foundational principles that underpin every data model, API, and UI pattern in PageSpace.
-
-## 1. Pages Are the Universal Primitive
-
-Everything in PageSpace is a **page**: documents, folders, AI chats, channels, task lists, sheets, canvases, code files, and uploaded files. This single recursive content model enables:
-
-- **Nestable composition**: A document can contain a folder, which contains a task list, which contains an AI chat
-- **Unified search**: All content types are searchable through the same API
-- **Consistent permissions**: One permission model for all content types
-- **Universal mentions**: @mention any page type from anywhere
-
-\`\`\`
-📁 Project/ ← FOLDER page
-├── 📄 Requirements ← DOCUMENT page
-├── 📋 Sprint Board ← TASK_LIST page
-├── 💬 Team Chat ← CHANNEL page
-├── 🤖 Project AI ← AI_CHAT page
-└── 📁 Assets/ ← FOLDER page
- └── 📎 brief.pdf ← FILE page
-\`\`\`
-
-## 2. Structure Encodes Meaning
-
-The tree hierarchy isn't just organization — it's **semantics**. Moving a page to a new parent changes its meaning, just like moving a word in a sentence changes the sentence's meaning.
-
-This spatial-semantic model drives three systems:
-
-- **Permissions**: Drive members see the drive's non-private pages by default; owners and admins see everything, and per-page grants or privacy handle the exceptions
-- **AI context**: An AI agent inside a project folder understands that project
-- **Navigation**: Breadcrumbs, tree views, and search all operate on the hierarchy
-
-## 3. Context Flows Through the Tree
-
-**Drive membership is open by default.** Members see every page in the drive that isn't marked private, so a teammate added to the drive can already navigate the whole tree — no per-page grants required. Mark a page private to drop it from that baseline. Explicit per-page grants (for private pages, or for people who aren't drive members) are per-page only and don't inherit from a parent to its children.
-
-**AI agents are location-aware.** An agent's system prompt is automatically populated with its drive, its breadcrumb path, and the page it lives on.
-
-\`\`\`
-📁 Marketing Campaign/
-├── 📄 Brand Guidelines
-├── 📄 Target Audience
-└── 🤖 Campaign AI ← Knows its path is "Marketing Campaign / Campaign AI"
-\`\`\`
-
-That path is the hook. To pull in Brand Guidelines or Target Audience, the agent calls workspace tools like \`list_pages\` or \`read_page\` — the tree tells it where to look; the tools fetch the content.
-
-## 4. Drives Are the Root of Ownership
-
-A **drive** is a top-level workspace. Every drive has a single owner with irrevocable full access.
-
-- Drives contain pages in a tree structure
-- Team members are added at the drive level with roles: \`OWNER\`, \`ADMIN\`, or \`MEMBER\`
-- Each user gets a personal drive on signup
-- You can create unlimited additional drives for different projects or teams
-
-## 5. AI Is a First-Class Citizen
-
-AI conversations aren't bolted on — they're **pages in the tree**. This means:
-
-- AI conversations are searchable, shareable, and mentionable
-- Multiple users can chat with the same AI simultaneously
-- AI agents inherit context from their position in the hierarchy
-- Different agents can have different providers, models, tools, and system prompts
-- Agents can consult each other via the \`ask_agent\` tool
-
-## 6. Database-First Persistence
-
-Every message, every edit, every tool call is persisted to PostgreSQL immediately. This is not client-side state management — it's durable, queryable storage.
-
-Benefits:
-- **Multi-user collaboration**: Database is the single source of truth
-- **Searchable history**: Find information across all conversations
-- **Audit trails**: Complete record of all AI interactions
-- **Real-time sync**: Socket.IO broadcasts database changes to all connected users
-- **No data loss**: Messages are never stored only in memory
-
-## 7. Recursive UI Mirrors the Data Model
-
-The frontend isn't a set of disjoint apps — it's a recursive viewer of a recursive model. The same page tree powers:
-
-- The sidebar navigation
-- Breadcrumb trails
-- Search results
-- AI context windows
-- Permission management
-- Drag-and-drop reordering
-
-One data model powers every interface, making the system both extensible and predictable.
-`;
-
-export default function CoreConceptsPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/docs-nav.ts b/apps/marketing/src/app/docs/docs-nav.ts
deleted file mode 100644
index c2332ca605..0000000000
--- a/apps/marketing/src/app/docs/docs-nav.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import {
- Book,
- Sparkles,
- Server,
- Monitor,
- Shield,
- KeyRound,
- Users,
- Eye,
- HardDrive,
- LayoutGrid,
- FileText,
- Blocks,
- MessageSquare,
- Search,
- Lightbulb,
- ListChecks,
- Upload,
- Folder,
- Palette,
- Table,
- Code,
- Bot,
- Layers,
- Plug,
- Calendar,
- Github,
- Package,
- Terminal,
- Webhook,
- Cpu,
-} from "lucide-react";
-
-export interface NavItem {
- title: string;
- href: string;
- icon?: typeof Book;
-}
-
-export interface NavSection {
- title: string;
- icon: typeof Book;
- items: NavItem[];
-}
-
-export const docsNav: NavSection[] = [
- {
- title: "Getting Started",
- icon: Book,
- items: [
- { title: "Overview", href: "/docs" },
- { title: "Quick Start", href: "/docs/getting-started", icon: Book },
- { title: "Core Concepts", href: "/docs/core-concepts", icon: Blocks },
- ],
- },
- {
- title: "Page Types",
- icon: LayoutGrid,
- items: [
- { title: "Overview", href: "/docs/page-types", icon: LayoutGrid },
- { title: "Document", href: "/docs/page-types/document", icon: FileText },
- { title: "Folder", href: "/docs/page-types/folder", icon: Folder },
- { title: "AI Chat", href: "/docs/page-types/ai-chat", icon: Bot },
- { title: "Channel", href: "/docs/page-types/channel", icon: MessageSquare },
- { title: "Sheet", href: "/docs/page-types/sheet", icon: Table },
- { title: "Canvas", href: "/docs/page-types/canvas", icon: Palette },
- { title: "Code", href: "/docs/page-types/code", icon: Code },
- { title: "Task List", href: "/docs/page-types/task-list", icon: ListChecks },
- { title: "File", href: "/docs/page-types/file", icon: Upload },
- ],
- },
- {
- title: "Features",
- icon: Lightbulb,
- items: [
- { title: "Overview", href: "/docs/features", icon: Lightbulb },
- { title: "Pages", href: "/docs/features/pages", icon: Layers },
- { title: "Drives & Workspaces", href: "/docs/features/drives", icon: HardDrive },
- { title: "AI in your Workspace", href: "/docs/features/ai", icon: Sparkles },
- { title: "Agent Sessions & Sandbox", href: "/docs/features/agent-workspaces", icon: Cpu },
- { title: "Sharing & Permissions", href: "/docs/features/sharing", icon: Users },
- { title: "Search", href: "/docs/features/search", icon: Search },
- { title: "Accounts & Sign In", href: "/docs/features/accounts", icon: KeyRound },
- { title: "PageSpace SDK", href: "/docs/features/sdk", icon: Package },
- { title: "PageSpace CLI", href: "/docs/features/cli", icon: Terminal },
- { title: "Agent API", href: "/docs/features/agent-api", icon: Bot },
- ],
- },
- {
- title: "Integrations",
- icon: Plug,
- items: [
- { title: "Overview", href: "/docs/integrations", icon: Plug },
- { title: "Google Calendar", href: "/docs/integrations/google-calendar", icon: Calendar },
- { title: "GitHub", href: "/docs/integrations/github", icon: Github },
- { title: "MCP", href: "/docs/integrations/mcp", icon: Server },
- { title: "Desktop MCP", href: "/docs/integrations/mcp/desktop", icon: Monitor },
- { title: "Incoming Webhooks", href: "/docs/integrations/incoming-webhooks", icon: Webhook },
- ],
- },
- {
- title: "Security & Trust",
- icon: Shield,
- items: [
- { title: "Overview", href: "/docs/security", icon: Shield },
- { title: "Authentication", href: "/docs/security/authentication", icon: KeyRound },
- { title: "Permissions", href: "/docs/security/permissions", icon: Users },
- { title: "Zero-Trust", href: "/docs/security/zero-trust", icon: Eye },
- ],
- },
-];
-
-/** Flat list of all nav items for prev/next navigation */
-export const flatNavItems: NavItem[] = docsNav.flatMap((section) => section.items);
-
-export function getNavContext(href: string) {
- const idx = flatNavItems.findIndex((item) => item.href === href);
- if (idx === -1) return { current: null, prev: null, next: null };
- return {
- current: flatNavItems[idx] ?? null,
- prev: idx > 0 ? flatNavItems[idx - 1] : null,
- next: idx < flatNavItems.length - 1 ? flatNavItems[idx + 1] : null,
- };
-}
-
-export function getBreadcrumbs(href: string): { title: string; href: string }[] {
- for (const section of docsNav) {
- const item = section.items.find((i) => i.href === href);
- if (item) {
- const crumbs = [{ title: "Docs", href: "/docs" }];
- if (item.href !== "/docs") {
- const sectionHref = section.items[0].href;
- if (sectionHref !== "/docs") {
- crumbs.push({ title: section.title, href: sectionHref });
- }
- if (item.href !== section.items[0].href) {
- crumbs.push({ title: item.title, href: item.href });
- }
- }
- return crumbs;
- }
- }
- return [{ title: "Docs", href: "/docs" }];
-}
diff --git a/apps/marketing/src/app/docs/features/accounts/page.tsx b/apps/marketing/src/app/docs/features/accounts/page.tsx
deleted file mode 100644
index 1800cc7b7b..0000000000
--- a/apps/marketing/src/app/docs/features/accounts/page.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Accounts & Sign In",
- description: "How accounts work in PageSpace — passkeys, magic links, Google and Apple sign-in, devices, and why there's no password to forget.",
- path: "/docs/features/accounts",
- keywords: ["accounts", "sign in", "passkeys", "magic links", "Google sign in", "Apple sign in", "devices", "passwordless"],
-});
-
-const content = `
-# Accounts & Sign In
-
-PageSpace is passwordless. You sign in with a passkey, a one-time email link, or — on the hosted pagespace.ai service — your Google or Apple account. There is no password to set, forget, or reset.
-
-## What you can do
-
-- Sign in with a passkey using Touch ID, Face ID, Windows Hello, or a phone scanning a QR code.
-- Sign in with a one-time link sent to your email.
-- Sign in with Google or Apple on the hosted service.
-- Add, rename, and remove passkeys from **Settings → Account**.
-- Stay signed in on the desktop and mobile apps without signing in again every day.
-- See every device currently signed in to your account, and revoke any of them.
-- "Log out everywhere" to kill every active session at once.
-- Create an MCP token from **Settings → MCP** so Claude Desktop, Cursor, or another MCP client can act on your behalf.
-- Verify your email address and delete your account from **Settings → Account**.
-
-## How it works
-
-You don't have a password. There is no password stored for your account — there's nothing to type, nothing to reset, nothing a database leak could expose. Signing in means proving you hold something: a device that can use a passkey, control of your email inbox, or a valid Google or Apple account.
-
-**Passkeys** are the preferred method. Your browser or OS stores a private key bound to your device's biometric or hardware security; the matching public key lives on our side. When you sign in, your device signs a fresh challenge — the private key never leaves it. That means a passkey can't be phished, reused on the wrong site, or leaked in a database breach.
-
-**Magic links** are the fallback. Ask for one, and PageSpace emails a signed URL that's valid for 5 minutes and works exactly once. Click it and the browser you clicked from is signed in.
-
-**Google and Apple sign-in** use OAuth. You prove to Google or Apple that you're you, they confirm it to us, and we issue a session. PageSpace never sees your Google or Apple password.
-
-**Sessions** are stored in a secure browser cookie that lasts 7 days. Signing out revokes the session immediately on the server — it isn't just cleared on your machine, so someone who copied the cookie beforehand still can't use it.
-
-**Desktop and mobile apps** don't use the web cookie. When you sign in inside the app, it trades your sign-in for a long-lived **device token** that rotates every time it refreshes. If a token ever leaks, the old one is already retired.
-
-**Too many failed attempts** against the same account trigger a temporary lock, regardless of which IP the attempts came from. A successful sign-in clears the counter.
-
-## Related
-
-- [Sharing & Permissions](/docs/features/sharing) — what signing in actually lets you see once you're in.
-- [Authentication (Security)](/docs/security/authentication) — the architecture behind passkeys, opaque tokens, account lockout, and the audit log.
-`;
-
-export default function HowItWorksAccountsPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/agent-api/page.tsx b/apps/marketing/src/app/docs/features/agent-api/page.tsx
deleted file mode 100644
index f1f74fdc61..0000000000
--- a/apps/marketing/src/app/docs/features/agent-api/page.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Agent API",
- description: "Call any PageSpace agent as an OpenAI-compatible Chat Completions endpoint — the same mcp_ key, your agent's system prompt and tools, running server-side. The backend for a chat app.",
- path: "/docs/features/agent-api",
- keywords: ["Agent API", "OpenAI-compatible", "chat completions", "ps-agent", "chat app backend", "developers", "@pagespace/cli"],
-});
-
-const content = `
-# Agent API
-
-Every PageSpace agent — any AI Chat page — is also an OpenAI-compatible endpoint. Point anything that speaks the OpenAI Chat Completions format at an agent and it answers with **its own system prompt and its own tools**, run server-side under your permissions. That is the whole backend for a chat app: you bring the UI, PageSpace brings the model, the context, and the tools.
-
-It shares credentials with the rest of the developer surface — the same \`mcp_\` key does inference — so [the CLI](/docs/features/cli) mints it and [the SDK](/docs/features/sdk) and this API consume it.
-
-## The endpoint
-
-- **Base URL** — \`https://pagespace.ai/api/v1\`
-- **API key** — a drive-scoped key (\`mcp_...\`) from \`pagespace keys\` or **Settings > MCP**, minted with edit access (an inherit key, created without \`--role\`). Keep it server-side.
-- **Model** — \`ps-agent://\`, the id of the AI Chat page to run. Copy it from the agent's settings tab, or list every agent a key can reach with \`GET /api/v1/models\`.
-
-Because it is the OpenAI Chat Completions shape, the OpenAI SDKs work unchanged — you only swap the base URL and key.
-
-\`\`\`ts
-import OpenAI from "openai";
-
-const client = new OpenAI({
- baseURL: "https://pagespace.ai/api/v1",
- apiKey: process.env.PAGESPACE_KEY, // an mcp_ key, kept server-side
-});
-
-const stream = await client.chat.completions.create({
- model: "ps-agent://",
- stream: true, // required
- messages: [{ role: "user", content: "Summarize this drive's latest notes." }],
-});
-
-for await (const chunk of stream) {
- process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
-}
-\`\`\`
-
-The same call with curl:
-
-\`\`\`bash
-curl https://pagespace.ai/api/v1/chat/completions \\
- -H "Authorization: Bearer mcp_your_key_here" \\
- -H "Content-Type: application/json" \\
- -d '{
- "model": "ps-agent://",
- "stream": true,
- "messages": [{ "role": "user", "content": "Summarize this drive'\\''s latest notes." }]
- }'
-\`\`\`
-
-## It is an agent, not a bare model
-
-The page you name in \`model\` runs as the agent you configured, so each request comes with:
-
-- **its system prompt** — whatever that AI Chat page is set up to be;
-- **its tools, executed server-side** — it searches the drive, reads pages, and writes back on its own, and returns the result; and
-- **only the tools it has enabled** — a bare agent starts with none and cannot reach the drive; enable read (and, if you want, write) tools on the agent page, or with \`pagespace agents config --set enabledTools='["multi_drive_search","read_page"]'\`; and
-- **your permissions and the key's scope** — it can only reach what the key can reach. If you cannot see a page in the app, the agent cannot either.
-
-Because the agent runs write tools on your behalf, the endpoint requires **edit** access to the agent page. A key without edit gets a \`403\`. The simplest edit-capable key inherits your own drive access, so create it without a \`--role\` (a plain \`--role member\` key is view-only on an agent page and will 403).
-
-## Streaming only
-
-Responses always stream. Set \`stream: true\`; an explicit \`stream: false\` is rejected with a \`400\` (omitting it streams anyway). This keeps a chat UI responsive and matches how the in-app agent renders.
-
-## Store and resume conversations
-
-Each call is stateless by default: you send the messages, the agent replies, nothing is kept. Pass a \`conversation_id\` and the thread becomes **durable** — every message is stored in PageSpace, appears on the AI Chat page in the app, and can be read back later to resume.
-
-This holds even when your app owns the conversation UX. Set \`client_manages_history: true\` and your harness keeps its own context window — it resends the history it wants on each call — while PageSpace still records the thread under the \`conversation_id\` you pass. So you handle conversations in your harness *and* they live in PageSpace, resumable by any client (or a human in the app) that can reach them.
-
-With \`client_manages_history\` set, you don't even have to pre-create the thread: pass a fresh \`conversation_id\` and PageSpace creates it on first use, owned by your key. (On the default path, an unknown \`conversation_id\` returns 404 — create it first with \`POST /api/v1/conversations\`.)
-
-\`\`\`bash
-# Optional: create a thread up front (drive-scoped, titled) — or just pass a new
-# conversation_id on the completions call and let PageSpace adopt it.
-curl https://pagespace.ai/api/v1/conversations \\
- -H "Authorization: Bearer mcp_your_key_here" \\
- -H "Content-Type: application/json" \\
- -d '{ "drive_id": "", "title": "Support bot" }'
-# -> { "id": "", "drive_id": "", "title": "Support bot", ... }
-
-# Resume later: read the stored messages back to rehydrate your UI.
-curl https://pagespace.ai/api/v1/conversations/ \\
- -H "Authorization: Bearer mcp_your_key_here"
-# -> { "id": "", ..., "messages": [ { "role": "...", "content": "..." }, ... ] }
-\`\`\`
-
-Pass \`conversation_id\` alongside \`messages\` on each \`chat/completions\` call to append to it. The conversations API is: \`POST /api/v1/conversations\` (create), \`GET /api/v1/conversations?drive_id=\` (list), \`GET /api/v1/conversations/\` (read messages), \`DELETE /api/v1/conversations/\` (remove). A key can only touch conversations it owns.
-
-## Listing agents
-
-\`\`\`bash
-curl https://pagespace.ai/api/v1/models \\
- -H "Authorization: Bearer mcp_your_key_here"
-\`\`\`
-
-Returns every AI Chat page the key can reach, each as \`{ "id": "ps-agent://", "object": "model", "owned_by": "pagespace" }\` — drop an id straight into \`model\`.
-
-## Build a chat app on it
-
-This endpoint is meant to be the backend for your own chat UI: your server holds the \`mcp_\` key, calls the agent, and streams tokens to the browser — no model keys, no prompt plumbing, no tool wiring of your own. Full walkthrough: **[Build a chat app with PageSpace as the backend](/blog/build-a-chat-app-on-pagespace)**.
-
-## Next steps
-
-- **[PageSpace CLI](/docs/features/cli)** — mint and scope the key this API uses
-- **[PageSpace SDK](/docs/features/sdk)** — the typed client for the rest of the API
-- **[MCP Integration](/docs/integrations/mcp)** — connect Claude Desktop, Claude Code, or Cursor
-`;
-
-export default function AgentApiPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/agent-sessions/page.tsx b/apps/marketing/src/app/docs/features/agent-sessions/page.tsx
deleted file mode 100644
index 4a122843a3..0000000000
--- a/apps/marketing/src/app/docs/features/agent-sessions/page.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Agent Sessions & Sandbox",
- description: "Agent sessions give an agent a real cloud machine — a terminal, a filesystem, real code execution. How sessions and panes work, what's free vs. Pro, and how Workflows can run code too.",
- path: "/docs/features/agent-workspaces",
- keywords: ["agent sessions", "sandbox", "terminal", "code execution", "panes", "workflows", "cloud"],
-});
-
-const content = `
-# Agent Sessions & Sandbox
-
-An **agent session** is a real cloud machine behind an agent conversation — its own filesystem, its own shell, its own life for as long as the session runs. Sessions, chat, and the **panes** that lay them out are free for every account. The machine itself — the sandbox where an agent actually runs code — is a **Pro plan and above** feature.
-
-## What you can do
-
-- Start a session from the **Agents** screen — a drive-scoped session for one of that drive's agents, or a driveless session with the Global Assistant.
-- Split the Agents screen into **panes**: side-by-side columns, and rows stacked inside each one. A pane can hold an agent conversation, a terminal, or any other page (a document, a task list, a sheet).
-- Open a **terminal** pane on a session to work in the same machine your agent is using — the same filesystem, live.
-- On Pro and above, let an agent actually run code: read and write real files, run a build, call \`git\`, all inside its own sandboxed machine.
-- Run more than one agent in the same session's panes at once — an agent next to the terminal it's driving, or next to the document or task list it's working on.
-- Turn on **code execution** for any of your own agents from that agent's settings — off by default, so an agent only gets sandbox tools when you deliberately grant them.
-- Trigger **Workflows** (scheduled, webhook, task, or calendar-triggered automations) that write and run code themselves, the same way an interactive session does.
-
-## How it works
-
-**Sessions vs. the sandbox.** Starting a session — opening the panes UI, chatting, splitting panes, working with any page — costs nothing and works on every plan. The sandbox is a separate, deliberate step: a session only provisions a real cloud machine when you (or your agent) actually need one, such as opening a terminal.
-
-**Who can use the sandbox.** Sandbox access follows the session's **payer** — the drive's owner, or the session's own owner for a driveless Global Assistant session — not necessarily whoever is using it in the moment. If a drive is owned by a Pro-plan account, every member of that drive with edit access can use the sandbox in a session scoped to it, even if their own personal account is on Free. When the resolved payer is on Free — your own drives, or your own Global Assistant sessions, while your account is on Free — you still get the full session/chat/panes interface; only the terminal and code-execution affordances are disabled, with a prompt to upgrade.
-
-**Cost.** Sandbox time draws from the same credit balance as everything else you do in PageSpace on Pro and above — there's no separate charge or add-on.
-
-**Panes.** A pane grid is two levels: columns side by side, and rows stacked inside each column. Each pane independently holds a chat, a terminal, or a page. Panes are saved per session, so reopening one restores exactly what you had open.
-
-**Per-agent code execution.** Every AI Chat agent has its own sandbox toggle, off by default. An agent with it off never sees bash, file, or git tools — no allow-list can re-grant them. Turning it on is what lets that agent actually run code the next time it's in a session with a sandbox.
-
-**Workflows.** A scheduled or triggered Workflow honors the exact same rules as an interactive session: the agent it runs needs code execution turned on, and the drive it runs in needs a Pro-and-above payer. When both are true, a workflow can write and run real code unattended — pull data, run a script, commit a result — not just read and summarize.
-
-**Bring your own tools.** The sandbox is a genuine machine, not a single-purpose box. If you already work with a particular coding-agent CLI, it runs in the terminal the same way any other command does.
-
-## Related
-
-- [AI in your Workspace](/docs/features/ai) — the agent model sessions build on: permissions, tools, and models.
-- [AI Chat](/docs/page-types/ai-chat) — where an agent's code-execution toggle and other settings live.
-- [Task Lists](/docs/page-types/task-list) — a common pane pairing with an agent driving the work.
-- [PageSpace CLI](/docs/features/cli) — the terminal-driven way to work with PageSpace outside the browser.
-`;
-
-export default function FeaturesAgentSessionsPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/ai/page.tsx b/apps/marketing/src/app/docs/features/ai/page.tsx
deleted file mode 100644
index 9e771dac9f..0000000000
--- a/apps/marketing/src/app/docs/features/ai/page.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "AI in your Workspace",
- description: "How AI works across PageSpace — @mentioning agents from any page, agent-to-agent consultation, the permissions model, supported providers, and what agents can and can't touch.",
- path: "/docs/features/ai",
- keywords: ["AI", "agents", "mentions", "providers", "permissions", "tool calling"],
-});
-
-const content = `
-# AI in your Workspace
-
-AI in PageSpace isn't bolted on — it's a behaviour the whole product shares. Any **AI Chat page** can be @mentioned from any document, channel, sheet cell, or other chat; agents can consult other agents; and when an agent acts, it acts under your permissions, using real workspace tools.
-
-## What you can do
-
-- @mention an AI Chat page from a document, channel, sheet cell, or another AI Chat to pull that agent into the thread.
-- Use the **global assistant** in the right-hand sidebar to work across every drive you can see.
-- Ask one agent to consult another by name — the called agent runs under its own prompt and tools, then returns a single response.
-- Pick your model in **Settings > AI** from one catalogue spanning every vendor — your account-level pick sets the default, and any individual AI Chat page can override it.
-- Restrict any agent to a specific toolset by editing its AI Chat page's allow-list.
-- Toggle **Read-only mode** on any AI Chat page to guarantee it cannot create, edit, or delete anything.
-- Toggle **Web search** on any AI Chat page to let that agent look things up outside your workspace.
-- Undo what an agent did on any turn — preview the edits that assistant message caused, then revert them as a group.
-
-## How it works
-
-**Models.** PageSpace gives you one catalogue of models from many vendors — OpenAI, Anthropic, Google, xAI, and more — all in a single picker. PageSpace holds the credentials, so you never paste an API key or manage a provider account. Each call draws from your plan's monthly credit allowance based on the model's real cost; free accounts pick from a curated set of efficient models, while paid plans unlock the full catalogue across every vendor, plus top-up credits whenever you need more.
-
-**Permissions.** When an agent acts, it acts as **you**. It can only see pages you can see and only change pages you can change. Share a page with a teammate and *their* agents can now see it too, under their account. Revoke access and the agents lose access immediately — there is no AI back-door.
-
-**Tools.** Every AI Chat page carries an allow-list of tools it may use. If the list is empty, the agent can chat but cannot act. The read-only toggle strips every write tool (create, edit, delete, send) on top of that list; the web-search toggle adds or removes the one web-lookup tool.
-
-**@mentions.** Any AI Chat page can be @mentioned from a document, channel, sheet cell, or another chat. Mentioning an agent inside a channel pulls it into that thread as a participant — it reads the context around the mention and replies there.
-
-**Agent-to-agent.** One agent can consult another by name. The called agent runs under its own system prompt and tools, but still with the *calling user's* permissions, and returns a single response. Chains are depth-capped so agents can't spiral into each other forever.
-
-## Related
-
-- [Agent Sessions & Sandbox](/docs/features/agent-sessions) — give an agent a real cloud machine to run code and a terminal in.
-- [AI Chat](/docs/page-types/ai-chat) — the page type the agent lives in, and its per-page configuration.
-- [Channels](/docs/page-types/channel) — where @-mentioning an agent pulls it into a live thread.
-- [Task Lists](/docs/page-types/task-list) — assigning work to an agent so it picks it up.
-- [Sharing & Permissions](/docs/features/sharing) — the rules an agent inherits when it acts.
-- [Search](/docs/features/search) — what the agent's search tools can reach.
-- [MCP Integration](/docs/integrations/mcp) — connecting external AI clients like Claude Desktop and Cursor.
-`;
-
-export default function FeaturesAIPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/cli/page.tsx b/apps/marketing/src/app/docs/features/cli/page.tsx
deleted file mode 100644
index 2899a7a9d9..0000000000
--- a/apps/marketing/src/app/docs/features/cli/page.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "PageSpace CLI",
- description: "The official pagespace command-line tool — log in, mint scoped keys, manage drives and pages from your shell, and run the pagespace mcp server.",
- path: "/docs/features/cli",
- keywords: ["CLI", "command line", "terminal", "@pagespace/cli", "pagespace mcp", "developers"],
-});
-
-const content = `
-# PageSpace CLI
-
-\`@pagespace/cli\` puts your workspace in your shell. Read and write pages, run searches, manage tasks, ask agents — as scriptable commands with JSON output. It also ships \`pagespace mcp\`, the MCP server that connects Claude Desktop, Claude Code, and Cursor to PageSpace.
-
-It's built on the [PageSpace SDK](/docs/features/sdk), from the same operation registry, so the CLI, the SDK, and the MCP tool surface always match.
-
-## Install
-
-\`\`\`bash
-npm install -g @pagespace/cli
-\`\`\`
-
-Or run it without installing:
-
-\`\`\`bash
-npx -y -p @pagespace/cli pagespace
-\`\`\`
-
-The current version is **1.6.1**. Installing gives you two binaries: \`pagespace\` (the CLI) and \`pagespace-mcp\` (the MCP server).
-
-## Sign in
-
-\`\`\`bash
-pagespace login
-\`\`\`
-
-This opens a browser, completes an OAuth login, and stores a credential locally. That credential manages keys and nothing else — it has **zero content access of its own**, and \`pagespace mcp\` will refuse to start on it. It exists so you, a human, can mint the scoped keys that actually do the work.
-
-Check who you are at any time with \`pagespace whoami\`.
-
-## Mint a key
-
-Content commands need a drive-scoped key. The guided wizard walks you through it:
-
-\`\`\`bash
-pagespace keys
-\`\`\`
-
-It lists your existing keys, creates new ones, sets the active key, and revokes old ones. Prefer flags?
-
-\`\`\`bash
-# Mint a key scoped to one drive
-pagespace keys create --drive --role member --name agent
-
-# Activate it — subsequent commands need no flags at all
-pagespace keys use agent
-
-# Print the raw mcp_ token once, for another machine or CI
-pagespace keys create --drive --role member --name ci --show-token
-
-# Unrestricted: every drive you own, including future ones
-pagespace keys create --all-drives --name ci --yes
-\`\`\`
-
-Minting always opens a browser for a one-time consent screen — it is never something an agent can do silently. \`--show-token\` prints the token exactly once; only a hash is stored server-side. Note that \`--role\` binds to the \`--drive\` immediately before it, so \`--drive a --drive b --role admin\` grants admin on \`b\` only.
-
-Credentials resolve in this order: the \`--token\` / \`--key\` flags, then the \`PAGESPACE_TOKEN\` / \`PAGESPACE_KEY\` environment variables, then your active key. If none are present, the command refuses loudly rather than guessing.
-
-## Core commands
-
-Every content command takes \`--json\` for machine-readable output on stdout (status messages go to stderr, so pipes stay clean), plus \`--host\`, \`--token\`, \`--key\`, and \`--yes\`.
-
-**Drives**
-
-\`\`\`bash
-pagespace drives list --json
-pagespace drives create "My Drive"
-pagespace drives rename "New Name"
-pagespace drives set-home-page
-pagespace drives trash --yes
-\`\`\`
-
-**Pages** — the tree, and the content inside it.
-
-\`\`\`bash
-pagespace pages list --drive --json
-pagespace pages tree --drive
-pagespace pages create "My Doc" DOCUMENT --drive
-pagespace pages read # content, with line numbers
-pagespace pages read --start 5 --end 10 # a line range
-pagespace pages replace-lines --start 5 --end 10 --file content.txt
-pagespace pages move
-pagespace pages export --format md --out -
-pagespace pages trash --yes
-\`\`\`
-
-Page types are \`FOLDER\`, \`DOCUMENT\`, \`CHANNEL\`, \`AI_CHAT\`, \`CANVAS\`, \`FILE\`, \`SHEET\`, \`TASK_LIST\`, and \`CODE\`.
-
-**Search**
-
-\`\`\`bash
-pagespace search text "roadmap" --all-drives --json
-pagespace search regex "TODO.*urgent" --drive --in both --json
-pagespace search glob "**/config*" --drive --json
-\`\`\`
-
-**Tasks**
-
-\`\`\`bash
-pagespace tasks list --json
-pagespace tasks create --title "Fix bug" --priority high
-pagespace tasks update --status done
-pagespace tasks assigned --json # your tasks across every drive
-\`\`\`
-
-**Agents**
-
-\`\`\`bash
-pagespace agents list --all-drives --json
-pagespace agents ask "Summarize this drive"
-pagespace agents ask "Follow up" --conversation-id
-pagespace agents config --set model=gpt-4o
-\`\`\`
-
-Also available: \`pagespace channels send\`, \`pagespace activity\`, \`pagespace trash list\`, \`pagespace sheets edit-cells\`, and \`pagespace models list\`.
-
-Because \`--json\` is clean on stdout, commands compose:
-
-\`\`\`bash
-DRIVE_ID=$(pagespace drives list --json | jq -r '.[] | select(.name=="My Drive") | .id')
-pagespace pages list --drive $DRIVE_ID --json | jq -r '.[].title'
-\`\`\`
-
-Exit codes: \`0\` success, \`1\` API or runtime error, \`2\` usage error.
-
-## MCP server
-
-\`pagespace mcp\` exposes every one of those operations as MCP tools over stdio, for any MCP-compatible client. Point your tool's config at it — by key name, so no secret ever lands in the config file:
-
-\`\`\`json
-{
- "mcpServers": {
- "pagespace": {
- "command": "npx",
- "args": ["-y", "-p", "@pagespace/cli", "pagespace-mcp"],
- "env": {
- "PAGESPACE_KEY": "agent"
- }
- }
- }
-}
-\`\`\`
-
-Globally installed? Use \`"command": "pagespace", "args": ["mcp"]\`. On a headless machine with no keychain, pass a raw token with \`PAGESPACE_TOKEN\` instead.
-
-The MCP server never falls back to your active key — it must be told which credential to use, explicitly, via the environment. See the [MCP Integration guide](/docs/integrations/mcp) for per-tool setup.
-
-## A note on isolation
-
-Minting a key requires a human in a browser. Once minted, a key is just bytes: any process running as the same OS user can read your keychain and your environment. A scoped key limits what the *server* will allow — it does not limit what a same-machine process can do with it.
-
-To genuinely isolate an agent, run it as its own OS user or in its own container, give it exactly one scoped token via \`PAGESPACE_TOKEN\`, and never run \`pagespace login\` as that user.
-
-## Next steps
-
-- **[PageSpace SDK](/docs/features/sdk)** — the typed TypeScript client the CLI is built on
-- **[MCP Integration](/docs/integrations/mcp)** — connect Claude Desktop, Claude Code, or Cursor
-- **[Sharing & Permissions](/docs/features/sharing)** — what a drive-scoped key can reach
-`;
-
-export default function CliPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/drives/page.tsx b/apps/marketing/src/app/docs/features/drives/page.tsx
deleted file mode 100644
index 003aaa1b9e..0000000000
--- a/apps/marketing/src/app/docs/features/drives/page.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Drives & Workspaces",
- description: "A drive is a PageSpace workspace — the top-level container that holds a page tree, members, roles, integrations, and backups.",
- path: "/docs/features/drives",
- keywords: ["drives", "workspaces", "members", "roles", "invitations", "integrations", "backups"],
-});
-
-const content = `
-# Drives & Workspaces
-
-A drive is a PageSpace workspace — the top-level container that holds a page tree and a set of people. Every page you create lives inside exactly one drive, and everything about access, sharing, and integrations is scoped to that drive.
-
-## What you can do
-
-- Create a new drive for a team, project, or client, and build its page tree from scratch.
-- Invite people by email — even before they have an account — as a member or an admin, or share specific pages with someone who isn't on the drive at all.
-- Share a drive with a link that lets anyone who opens it join, with the role you choose, and revoke that link at any time.
-- Define custom roles like "Editor" or "Reviewer" with preset view, edit, and share permissions, then hand out that role when inviting people.
-- Attach [integrations](/docs/integrations) to a drive so agents working inside it can reach external tools.
-- Write a drive prompt — custom AI instructions that AI Chat pages in the drive can opt into.
-- Take a snapshot backup of a drive before a risky import, rename, or restructure.
-- See who accessed the drive recently and jump back into the ones you touched today.
-- Open a drive's history feed to see what changed, when, and by whom.
-
-## How it works
-
-A drive owns a tree of pages and a list of members. When you create a drive you become its owner. The drive has its own URL slug, its own trash, its own [search](/docs/features/search), its own integrations list, and its own history feed.
-
-There are a few distinct ways to have access to a drive, and the difference matters:
-
-- **Owner** — the account that created the drive. Exactly one per drive. Automatic full access to every page, and some drive-level operations are owner-only.
-- **Admin** — a member with the admin role. Automatic full access to every page, can invite and remove members, can run backups.
-- **Member** — listed on the drive's member roster with the member role or a custom role. Can be assigned tasks. A member sees every page in the drive that isn't marked private, and can post in its channels, without anyone granting each page one by one. Mark a page private to keep it off the member baseline.
-- **Agents and apps** — AI agent pages and connected apps (MCP tokens) also appear on the member roster, each with its own role. Their access follows that role just like a person's — a member-level agent or app sees the drive's non-private pages; give it a narrower role to limit what it can reach.
-- **Page-level collaborator** — someone who was granted access to specific pages but not added to the drive itself. They can navigate to those pages, but they aren't on the member list, can't be selected as a task assignee, and can't be used as a scope for a drive-level service token. They have a keyhole into the drive, not a key to the whole building.
-
-Invitations are a two-step handshake. When someone is invited, a pending membership is created — they appear on the member list but their access isn't active until they accept. Until then, permission checks treat them as if they're not there.
-
-Backups are point-in-time snapshots of the drive — every page, every permission grant, every member, every custom role, and every file reference. Only owners and admins can trigger or view them. A backup gives you a rollback point before a risky change.
-
-## Good to know
-
-- **Members see the drive by default; lock pages down by exception.** A plain member sees every page that isn't marked private — no per-page setup. When something shouldn't be visible to the whole drive, mark that page private so only the owner, admins, and explicitly-added people can reach it.
-- **Deleting a drive goes to trash first.** A deleted drive sits in the drive trash with all its pages intact, and stays there until you restore or purge it.
-
-## Related
-
-- [Pages](/docs/features/pages) — what lives inside a drive.
-- [Sharing & Permissions](/docs/features/sharing) — how roles, page grants, and invitations combine in practice.
-- [AI in your Workspace](/docs/features/ai) — how the drive prompt shapes what AI can see and do.
-- [Integrations](/docs/integrations) — the external tools a drive can connect to.
-`;
-
-export default function HowItWorksDrivesPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/page.tsx b/apps/marketing/src/app/docs/features/page.tsx
deleted file mode 100644
index 31e5ada1cf..0000000000
--- a/apps/marketing/src/app/docs/features/page.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import Link from "next/link";
-import { ArrowRight, Layers, HardDrive, Sparkles, Users, Search, KeyRound, Package, Terminal, Bot } from "lucide-react";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Features",
- description: "Plain-language reference for the behaviours every page in PageSpace shares — pages, drives, AI, sharing, search, and accounts.",
- path: "/docs/features",
- keywords: ["features", "pages", "drives", "AI", "sharing", "search", "accounts"],
-});
-
-const features = [
- { title: "Pages", href: "/docs/features/pages", icon: Layers, description: "The universal container — create, nest, share, version, export, trash, restore." },
- { title: "Drives & Workspaces", href: "/docs/features/drives", icon: HardDrive, description: "How drives group pages and people, and how you move between them." },
- { title: "AI in your Workspace", href: "/docs/features/ai", icon: Sparkles, description: "How AI works across every page: @mentions, agent-to-agent, permissions, and providers." },
- { title: "Sharing & Permissions", href: "/docs/features/sharing", icon: Users, description: "Who sees what — drive roles and per-page grants in plain English." },
- { title: "Search", href: "/docs/features/search", icon: Search, description: "Find pages, drives, people, and text across everything you can see." },
- { title: "Accounts & Sign In", href: "/docs/features/accounts", icon: KeyRound, description: "Passkeys, magic links, devices — and why there's no password to forget." },
- { title: "PageSpace SDK", href: "/docs/features/sdk", icon: Package, description: "The typed TypeScript client — call drives, pages, tasks, search, and agents from your own code." },
- { title: "PageSpace CLI", href: "/docs/features/cli", icon: Terminal, description: "Your workspace in the shell — scriptable commands, scoped keys, and the pagespace mcp server." },
- { title: "Agent API", href: "/docs/features/agent-api", icon: Bot, description: "Call any agent as an OpenAI-compatible endpoint — its system prompt and tools, server-side. The backend for a chat app." },
-];
-
-export default function FeaturesIndexPage() {
- return (
-
-
Features
-
- Every page type shares the same set of behaviours — sharing, search, AI, accounts. This section covers what each of those behaviours does, how it works, and what's good to know before you use it. (Versioning and export are per-type — see the individual page types for which ones support them.)
-
- );
-}
diff --git a/apps/marketing/src/app/docs/features/pages/page.tsx b/apps/marketing/src/app/docs/features/pages/page.tsx
deleted file mode 100644
index 0dc0b490c3..0000000000
--- a/apps/marketing/src/app/docs/features/pages/page.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Pages",
- description: "How pages work in PageSpace — the universal container you create, nest, share, version, export, trash, and restore.",
- path: "/docs/features/pages",
- keywords: ["pages", "move", "share", "version history", "export", "trash", "restore"],
-});
-
-const content = `
-# Pages
-
-A page is the one thing everything in PageSpace is made of — documents, folders, spreadsheets, AI chats, uploaded files are all pages. You work with all of them the same way: give it a title, drop it into the tree, share it, version it, export it, trash it.
-
-## What you can do
-
-- Create a page from the **+** button next to a folder in the sidebar, or from the slash menu inside another page.
-- Drag a page in the sidebar to reorder it among its siblings or to nest it under a different parent.
-- Right-click a page to rename, move, copy, or trash it.
-- Select multiple pages and move, copy, or trash them in one action — across drives if you need to.
-- Link pages to each other by typing **@** and picking a page; the link follows the page if you rename it later.
-- Open a page's **Version History** panel to see every edit, who made it, diff any two points, and roll back a change.
-- Download a document as **Markdown** or **.docx**, or a sheet as **.csv** or **.xlsx**.
-- Share a page with a specific teammate at view, edit, share, or delete level — separate from their drive access.
-- Send a page to the **Trash** view for the drive, then restore it with its children intact.
-
-## How it works
-
-Every page has a title, a type, and a position in a single recursive tree that belongs to one drive. Any page can parent any other page, so a folder can hold an AI chat, a chat can sit next to a sheet, and an uploaded file lands as a child page wherever you drop it. Page types change the editor you get and the toolbar that comes with it, but the container around them is the same.
-
-Editing is real-time and optimistic. Every save carries the revision number you started from, so when two people edit at once the second save sees the first change instead of silently overwriting it. Other viewers see your edits as they happen over the realtime channel.
-
-Moving a page is a change to its parent and its position in the sibling order. The tree blocks loops — you can't drop a page inside one of its own descendants — and keeps the order you dragged. Bulk move and bulk copy can target a different drive, which is how pages travel between workspaces.
-
-Trashing is a soft delete. The page and everything beneath it get marked as trashed, disappear from the main tree, and show up under **Trash** for that drive. Content and parent links are kept. Restoring walks the subtree back into place, and any child that was moved out while the parent was in the trash is returned to where it used to live.
-
-Version history is an activity log of the page's edits. You can scroll it, diff any two entries, and roll back any entry to restore the page to that state. Versions are kept for 30 days by default — older ones are cleaned up in the background.
-
-Permissions resolve from two places at once: your role in the drive the page lives in, plus any per-page grant on this specific page. There is no inheritance — a grant on a folder doesn't carry to its children. Every read, save, and export re-checks them, so changing someone's access takes effect on their next action.
-
-## Good to know
-
-- **Sharing is per-person, not per-link.** Access is always tied to a PageSpace account — invite by email or grant a specific account. There are no "anyone with the link" URLs.
-- **Version history runs for 30 days by default.** Older entries are cleaned up in the background and can't be rolled back to.
-
-## Related
-
-- [Page Types](/docs/page-types) — the nine types a page can be, and what each editor gives you.
-- [Drives & Workspaces](/docs/features/drives) — the container pages live inside, and where drive-level access starts.
-- [Sharing & Permissions](/docs/features/sharing) — how view/edit/share/delete grants combine with drive roles.
-- [Files & Uploads](/docs/page-types/file) — how uploads become file pages in the tree.
-- [AI in your Workspace](/docs/features/ai) — how agents read and edit pages, and how @mentions pull them into a document.
-`;
-
-export default function HowItWorksPagesPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/sdk/page.tsx b/apps/marketing/src/app/docs/features/sdk/page.tsx
deleted file mode 100644
index eb23b25477..0000000000
--- a/apps/marketing/src/app/docs/features/sdk/page.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "PageSpace SDK",
- description: "The typed TypeScript client for the PageSpace API — install, authenticate, and call drives, pages, tasks, search, and agents from your own code.",
- path: "/docs/features/sdk",
- keywords: ["SDK", "TypeScript", "API", "client", "@pagespace/sdk", "developers"],
-});
-
-const content = `
-# PageSpace SDK
-
-\`@pagespace/sdk\` is the typed TypeScript client for the PageSpace API. Everything you can do in the app — create pages, edit documents, run searches, manage tasks, ask agents — you can do from your own code, with full type inference on every call.
-
-It's the same client the [\`pagespace\` CLI](/docs/features/cli) and the \`pagespace mcp\` server are built on. All three surfaces are generated from one operation registry, so they can't drift apart.
-
-## Install
-
-\`\`\`bash
-npm install @pagespace/sdk
-\`\`\`
-
-ESM only, with a single runtime dependency (\`zod\`). The current version is **2.1.0**.
-
-## Quickstart
-
-\`\`\`typescript
-import { PageSpaceClient, StaticTokenProvider } from '@pagespace/sdk';
-
-const client = new PageSpaceClient({
- baseUrl: 'https://pagespace.ai',
- auth: new StaticTokenProvider(process.env.PAGESPACE_TOKEN!),
-});
-
-const drives = await client.drives.list({});
-
-const page = await client.pages.create({
- driveId: drives[0].id,
- title: 'Release Notes',
- type: 'DOCUMENT',
-});
-
-await client.pages.replaceLines({
- pageId: page.id,
- startLine: 1,
- endLine: 1,
- content: '# Release Notes\\n\\nShipped today.',
-});
-\`\`\`
-
-## Authentication
-
-The client takes an \`auth\` provider. There are two.
-
-**\`StaticTokenProvider\`** — wraps a fixed \`mcp_\` API key. This is what you want for scripts, CI jobs, and service accounts. Mint a key with \`pagespace keys create --drive --role member --show-token\`, or from **Settings > MCP** in the app, and pass it straight in:
-
-\`\`\`typescript
-new StaticTokenProvider(process.env.PAGESPACE_TOKEN!)
-\`\`\`
-
-**\`OAuthTokenProvider\`** — for apps that log a *user* in and act on their behalf. You give it an initial token pair and a refresh function; it refreshes proactively before expiry and hands you the new pair to persist.
-
-\`\`\`typescript
-import { OAuthTokenProvider } from '@pagespace/sdk';
-
-const auth = new OAuthTokenProvider({
- initialTokens: { accessToken, accessExpiresAt, refreshToken, refreshExpiresAt },
- refreshAccessToken: (refreshToken) => exchangeRefreshToken(refreshToken),
- onTokensUpdated: (tokens) => saveTokens(tokens),
-});
-\`\`\`
-
-Building your own browser-based login? The SDK ships the PKCE helpers too — \`generateCodeVerifier\` and \`deriveCodeChallenge\` (async; it runs on Web Crypto so it works in a browser bundle).
-
-An \`mcp_\` key works for every namespace except \`client.tokens\`, which manages keys themselves and requires an OAuth token.
-
-## Resource namespaces
-
-Every operation hangs off a namespace on the client. Inputs and outputs are schema-validated, so your editor knows the shape of both.
-
-| Namespace | What it does |
-|-----------|-------------|
-| \`drives\` | \`list\`, \`create\`, \`rename\`, \`updateContext\`, \`setHomePage\`, \`trash\`, \`restore\` |
-| \`pages\` | \`list\`, \`listTrash\`, \`create\`, \`details\`, \`rename\`, \`move\`, \`trash\`, \`restore\` — plus content editing: \`read\`, \`replaceLines\`, \`insertLines\`, \`deleteLines\`, \`editCells\` |
-| \`tasks\` | \`create\`, \`update\`, \`delete\`, \`reorder\`, \`getAssigned\`, \`createStatus\`, \`setTrigger\`, \`deleteTrigger\` |
-| \`search\` | \`regex\`, \`glob\`, \`multiDrive\` |
-| \`agents\` | \`list\`, \`listMultiDrive\`, \`ask\`, \`updateConfig\`, \`listModels\` |
-| \`conversations\` | \`list\`, \`read\` — full transcripts of an agent's conversations |
-| \`channels\` | \`send\`, \`delete\` |
-| \`calendar\` | \`list\`, \`get\`, \`create\`, \`update\`, \`delete\`, \`rsvp\`, \`inviteAttendees\`, \`removeAttendee\`, \`setTrigger\`, \`deleteTrigger\` |
-| \`roles\` | \`list\`, \`get\`, \`create\`, \`update\`, \`delete\`, \`setPagePermissions\`, \`setDriveWidePermissions\`, \`removePagePermissions\` |
-| \`members\` | \`list\` — who's on a drive |
-| \`collaborators\` | \`list\` — people you share any drive with |
-| \`commands\` | \`list\`, \`create\`, \`update\`, \`delete\` — slash commands |
-| \`workflows\` | \`list\`, \`create\`, \`update\`, \`delete\` |
-| \`activity\` | \`get\` — a drive's activity feed |
-| \`export\` | \`pageMarkdown\`, \`sheetCsv\` |
-| \`tokens\` | \`list\`, \`revoke\` — API keys (OAuth only) |
-
-Reading and writing document content lives on \`pages\`, not a separate namespace:
-
-\`\`\`typescript
-// Read with line numbers, or a range
-const doc = await client.pages.read({ pageId, lineStart: 1, lineEnd: 50 });
-
-// Line-addressed edits
-await client.pages.insertLines({ pageId, anchor: '## Changelog', content: '- Fixed a bug', position: 'after' });
-await client.pages.deleteLines({ pageId, startLine: 10, endLine: 12 });
-
-// Sheet cells
-await client.pages.editCells({ pageId, cells: [{ address: 'A1', value: 'Hello' }] });
-\`\`\`
-
-Need an endpoint the SDK doesn't wrap? \`defineOperation\` lets you declare one with its own Zod schemas and call it through \`client.invoke\`, keeping the same typing, auth, and retry behaviour.
-
-## Error handling
-
-Every failure is a typed subclass of \`PageSpaceError\`, each with a matching \`is*Error()\` type guard — so you can branch on what went wrong without string-matching messages.
-
-\`\`\`typescript
-import { isRateLimitError, isPermissionDeniedError, isValidationError } from '@pagespace/sdk';
-
-try {
- await client.pages.create({ driveId, title: 'Notes', type: 'DOCUMENT' });
-} catch (error) {
- if (isValidationError(error)) {
- // 400 — input rejected; error.details has field-level info
- } else if (isPermissionDeniedError(error)) {
- // 403 — the credential's role doesn't allow this
- } else if (isRateLimitError(error)) {
- // 429 — error.retryAfterMs is set when the server sends Retry-After
- }
- throw error;
-}
-\`\`\`
-
-| Error | When |
-|-------|------|
-| \`ValidationError\` | 400 — the input was rejected |
-| \`AuthenticationError\` | 401 — token missing, invalid, or expired |
-| \`PermissionDeniedError\` | 403 — the credential's role doesn't permit it |
-| \`NotFoundError\` | 404 |
-| \`RateLimitError\` | 429 — carries \`retryAfterMs\` |
-| \`ServerError\` | 5xx |
-| \`NetworkError\` | the request never left (DNS, connection refused) |
-| \`TimeoutError\` | the request exceeded \`timeoutMs\` |
-| \`IncompatibleServerError\` | the server's API version is too old for this SDK |
-| \`ResponseValidationError\` | the server returned a shape the SDK didn't expect |
-| \`HttpError\` | any other status (402, 409, …) |
-
-## Retries
-
-Reads retry themselves. A failed GET — network error, timeout, 429, or 5xx — is retried with full-jitter exponential backoff: 3 retries, starting at 250 ms, capped at 5 s. A \`Retry-After\` header is honoured when the server sends one, clamped to the same ceiling.
-
-Writes are never replayed. POST, PUT, PATCH, and DELETE fail straight through to you, so a retry can never duplicate a page or a task.
-
-Tune it per-client:
-
-\`\`\`typescript
-new PageSpaceClient({
- baseUrl,
- auth,
- retryPolicy: { maxRetries: 5, maxDelayMs: 30_000 },
- timeoutMs: 15_000,
-});
-\`\`\`
-
-## Server compatibility
-
-On its first successful response, the SDK compares the server's API version against the minimum it supports. If the server is too old, it fails closed with \`IncompatibleServerError\` rather than making calls that might silently misbehave. You can bypass the check with \`skipVersionCheck: true\`, but you generally shouldn't.
-
-## Next steps
-
-- **[PageSpace CLI](/docs/features/cli)** — the same operations from your shell, plus the \`pagespace mcp\` server
-- **[MCP Integration](/docs/integrations/mcp)** — connect Claude Desktop, Claude Code, or Cursor to your workspace
-- **[Sharing & Permissions](/docs/features/sharing)** — what a drive-scoped key can and can't reach
-`;
-
-export default function SdkPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/search/page.tsx b/apps/marketing/src/app/docs/features/search/page.tsx
deleted file mode 100644
index 5c20448a1f..0000000000
--- a/apps/marketing/src/app/docs/features/search/page.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Search — How it Works",
- description: "How search works in PageSpace: the header search box, @-mentions, content matching rules, and what search doesn't cover.",
- path: "/docs/features/search",
- keywords: ["search", "find", "mentions", "autocomplete", "how it works"],
-});
-
-const content = `
-# Search
-
-Find pages, drives, people, and text across the workspace — from the top bar on every screen, from the \`@\` autocomplete inside documents and channels, and from anything you ask an AI agent.
-
-## What you can do
-
-- Search for a page by title from the top-bar search box on any screen.
-- Find a word inside the body of a **Document** page — the page surfaces as a content match.
-- Jump to drives you own and people with public profiles.
-- Type \`@\` inside a document or channel to pull up a live list of pages and people to link.
-- Ask an AI agent in the sidebar to search for something — it can run text, regex, or glob patterns across every drive you've given it access to.
-
-## How it works
-
-The top-bar search box runs every keystroke against three things at once: page titles, drive names, and public user profiles. Document pages also get their body text searched. The mobile magnifying-glass button opens the same search in a dialog.
-
-Every result is filtered through a permission check before you see it. A page you don't have access to never appears, even if it matches perfectly. The same rule applies to the \`@\`-mention picker and to AI-run searches — the search runs against the whole drive, then the results get filtered down to what you (or the agent) can actually open.
-
-Multi-word queries are strict on titles and lenient on bodies. Searching *alpha budget* matches a page called *Project Alpha Budget Q3*, in any word order, but only when every word appears in the title. In document bodies, any single matching word is enough. Ranking is deterministic: exact title match beats title-starts-with, which beats title-contains, which beats body-contains; shorter titles win ties.
-
-The \`@\`-mention picker is a separate, drive-scoped search. When your query is empty it falls back to the most recently updated pages in the current drive, so you can pick recent work without typing. When you do type, it uses the same multi-word title rules as the main box.
-
-AI agents get stronger tools than the search box. Through their sidebar they can pattern-match with regex, glob across page paths in a drive, or sweep every drive you've given them access to in one pass — useful for finding a quote whose exact wording you've half-forgotten.
-
-## Good to know
-
-- **Content search reads Document bodies.** The words inside channel messages, sheet cells, canvas source, code pages, and AI Chat transcripts aren't indexed by the header search — only their titles are. Ask an AI agent for those.
-- **The header search covers drives you own.** For pages in drives you're a member of, open that drive's tree or use an AI agent — agents can search across every drive you can see.
-
-## Related
-
-- [Pages](/docs/features/pages) — the Document page type, whose body is the only one the main search reads.
-- [Drives & Workspaces](/docs/features/drives) — why owned drives and member drives behave differently in search.
-- [AI in your Workspace](/docs/features/ai) — regex, glob, and cross-drive search through an agent.
-- [Channels](/docs/page-types/channel) — where \`@\`-mentions are used.
-`;
-
-export default function HowItWorksSearchPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/features/sharing/page.tsx b/apps/marketing/src/app/docs/features/sharing/page.tsx
deleted file mode 100644
index 7a05e75be2..0000000000
--- a/apps/marketing/src/app/docs/features/sharing/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Sharing & Permissions",
- description: "Who sees what in PageSpace — open-by-default drive membership, page privacy, custom roles, email and link invites, and per-page grants, in plain English.",
- path: "/docs/features/sharing",
- keywords: ["sharing", "permissions", "access", "collaboration", "roles", "drive members"],
-});
-
-const content = `
-# Sharing & Permissions
-
-Who sees what in PageSpace. Access is open within a drive and private by exception: members see the drive's pages by default, you mark individual pages **private** to lock them down, and per-page grants extend access to specific pages or to people who aren't drive members. Four flags — **View**, **Edit**, **Share**, **Delete** — describe what each person can do. View is the foundation; Edit, Share, and Delete only make sense on top of it.
-
-## What you can do
-
-- Invite someone to a drive as a member or admin — by email, even if they don't have an account yet. The invite waits for them and becomes access the moment they sign up. Members see the whole drive except pages marked private; admins get full access, the same as the owner.
-- Share a drive or a single page with a link. Opening either link adds the person to the drive — a drive link with the role you set, a page link as a member with that page's permissions — so they'll also see the drive's other non-private pages; mark anything sensitive private first. Links are revocable and can carry an expiry.
-- Share a single page with a specific person using the four flags: View, Edit, Share, Delete. Edit, Share, and Delete require View to be on; otherwise you combine them however you like. A person can have View + Share without Edit, for example.
-- Create custom roles (like "Editor" or "Reviewer") that carry their own view/edit/share permissions, pick a color for the badge, optionally make one the default for new members, and assign a role right from an invite or share link.
-- See who has access to a page from the **Share** dialog, and see every page a given member can reach from the drive-level members view.
-- Revoke any grant, share link, or pending invite instantly.
-
-## How it works
-
-Access is resolved in order, and the first answer wins.
-
-If you're the drive **owner**, you can see, edit, share, and delete every page in that drive. Nothing else overrides this, and no grant can take it away.
-
-If you're a drive **admin** who has accepted the invitation, you get the same full access as the owner across every page in the drive.
-
-If you're a drive **member** — the default role — you can see every page in the drive that isn't marked **private**, and post in its channels, without anyone granting each page one by one. To hide a page from the membership, mark it private; then only the owner, admins, and the people or roles explicitly granted access can reach it. Editing, sharing, or deleting other pages still needs a grant or a role.
-
-Per-page grants are how you reach a *private* page, or how someone who isn't a drive member gets in. Each grant is one record per person per page, with four flags, and **explicit grants don't inherit** — a grant on a folder says nothing about the pages inside it; each is checked on its own. (Drive-wide visibility for members is the separate open-by-default baseline above, not an inherited grant.)
-
-Custom roles let owners and admins bundle access under a name. A role carries its own per-page view/edit/share map, and the access check consults it directly — so assigning someone the "Reviewer" role grants exactly what that role defines, and a role can also explicitly withhold view on a page. Assign a role when you invite someone or hand it out later.
-
-## Good to know
-
-- **Invite by email, share by link, or grant per-person.** You can invite someone who has no account yet — the invite is held against their email and becomes access on signup. A share link adds whoever opens it to the drive — a drive link with the role you choose, a page link as a member with that page's permissions — so they also see the drive's non-private pages; mark sensitive pages private first. Links stay revocable.
-- **Explicit grants don't cascade to children.** Granting access to a folder lets someone open the folder page itself; each child page is checked on its own grants. Drive members already see non-private pages across the tree — that's the membership baseline, separate from explicit grants.
-
-## Related
-
-- [Drives & Workspaces](/docs/features/drives) — how drives group pages and members.
-- [Pages](/docs/features/pages) — the thing you're actually sharing.
-- [Security → Permissions](/docs/security/permissions) — the formal access model and exact resolution rules.
-`;
-
-export default function HowItWorksSharingPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/getting-started/page.tsx b/apps/marketing/src/app/docs/getting-started/page.tsx
deleted file mode 100644
index 6743873b43..0000000000
--- a/apps/marketing/src/app/docs/getting-started/page.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-import { MONTHLY_CREDITS } from "@/lib/credits";
-
-export const metadata = createMetadata({
- title: "Getting Started",
- description: "Set up PageSpace: passwordless signup, drives and pages, documents, AI agents, MCP integration, and inviting a team.",
- path: "/docs/getting-started",
- keywords: ["getting started", "setup", "quickstart", "tutorial"],
-});
-
-const content = `
-# Getting Started
-
-This guide takes you from a fresh account to an AI agent calling workspace tools inside one of your drives.
-
-## 1. Create Your Account
-
-Sign up at **pagespace.ai** — there are no passwords. Pick one:
-
-- **Passkey** (recommended): use Touch ID, Face ID, Windows Hello, or a hardware key.
-- **Magic link**: enter your email and click the link we send.
-- **Google** or **Apple** OAuth.
-
-No credit card required. The free tier includes 500 MB storage, ${MONTHLY_CREDITS.free}/month of credits, and a 20 MB max file size.
-
-After signup, PageSpace sets you up with a starter **drive** (workspace) so you can jump straight in.
-
-## 2. Understand Drives and Pages
-
-PageSpace organizes everything into **drives** and **pages**:
-
-- **Drives** are top-level workspaces. You can create multiple drives and invite members to each.
-- **Pages** are the universal content primitive. Everything is a page — documents, folders, AI chats, channels, task lists, sheets, canvases, code files, and uploaded files.
-
-Pages form a tree hierarchy. A document can live inside a folder, which lives inside another folder. This structure gives AI meaningful context; access is controlled by drive membership and explicit per-page permissions (no inheritance from parent folders).
-
-\`\`\`
-📁 Marketing/
-├── 📁 Q1 Campaign/
-│ ├── 📄 Brand Guidelines
-│ ├── 📋 Task List
-│ ├── 💬 Team Channel
-│ └── 🤖 Campaign AI Agent
-├── 📁 Assets/
-│ └── 📎 logo.png
-└── 🤖 Marketing Assistant
-\`\`\`
-
-## 3. Create Your First Document
-
-Click the **+** button in the sidebar or right-click to create a new page. Select **Document** and start typing.
-
-Documents use a TipTap rich-text editor with:
-- Markdown input shortcuts (type \`# \` for a heading, \`- \` for a list)
-- Real-time collaboration via Socket.IO
-- Bubble and floating toolbars for formatting
-- Drag-and-drop file uploads
-- An HTML / Markdown storage toggle per page
-
-## 4. Set Up AI
-
-PageSpace gives you one catalogue of models from many vendors, organised by vendor, so you reach them all without supplying any API keys — PageSpace manages the credentials. The default model is \`openai/gpt-5.6-luna\`.
-
-| Vendor | What it is |
-|--------|-----------|
-| OpenAI | GPT-5.6 / 5.5 / 5.4 families |
-| Anthropic | Claude 5 and 4 families |
-| Google | Gemini 3.6, 3.5, and 3.1 families |
-| xAI | Grok 4 family |
-| DeepSeek, Qwen, Mistral, Moonshot, MiniMax, Meta, and more | Additional open and frontier models in the catalogue |
-
-Open **Settings > AI** to pick a model. The model you pick becomes your account-level default; any individual AI Chat page can override it. Each call draws from your plan's monthly credit allowance based on the model's real cost. Free accounts use a curated allowlist — \`openai/gpt-5.6-luna\` (default), the GPT-5.4 nano and mini models, Claude Haiku 4.5, and the Gemini Flash family — while paid plans unlock the full catalogue.
-
-## 5. Create an AI Agent
-
-AI Chat pages are specialized AI conversations with custom configuration:
-
-1. Right-click in the file tree and select **New AI Chat**
-2. Open the agent's settings to configure:
- - **System prompt**: custom instructions for the agent's behavior
- - **Enabled tools**: which of the 79 workspace tools the agent can call
- - **Read-only toggle**: when on, the agent can only read and search — no writes, no trash, no task updates
- - **Web search toggle**: enables the \`web_search\` tool
- - **Provider / Model**: which AI model powers this agent
-3. The agent inherits context from its position in the hierarchy — an agent inside a project folder understands that project.
-
-Agents can also consult each other via the \`ask_agent\` tool, enabling multi-agent collaboration.
-
-## 6. Connect External AI Tools (MCP)
-
-If you use Claude Desktop, Claude Code, Cursor, or other MCP-compatible tools, you can connect them to your PageSpace workspace:
-
-1. Go to **Settings > MCP** and create a token
-2. Add the PageSpace MCP server to your tool's config:
-
-\`\`\`json
-{
- "mcpServers": {
- "pagespace": {
- "command": "npx",
- "args": ["-y", "-p", "@pagespace/cli", "pagespace-mcp"],
- "env": {
- "PAGESPACE_TOKEN": "mcp_your_token_here"
- }
- }
- }
-}
-\`\`\`
-
-Prefer the \`pagespace\` CLI end to end? \`npm install -g @pagespace/cli\`, then \`pagespace login\` and the \`pagespace keys\` wizard mint a drive-scoped key without ever copying a token. See the [PageSpace CLI reference](/docs/features/cli) for every command, or the [MCP Integration guide](/docs/integrations/mcp) for full setup details. Building against PageSpace from your own code instead? Reach for the [PageSpace SDK](/docs/features/sdk).
-
-## 7. Invite Your Team
-
-To collaborate:
-
-1. Go to **Drive Settings > Members**
-2. Add members by email — they join as \`MEMBER\` by default
-3. Promote members to \`ADMIN\` for drive management access
-4. Set page-level permissions for fine-grained access control
-
-Members can collaborate in real-time on documents, channels, and AI conversations.
-
-## Next Steps
-
-- **[Core Concepts](/docs/core-concepts)** — pages as primitives, context inheritance, the tree model
-- **[Page Types](/docs/page-types)** — the 9 built-in page types and what each one is for
-- **[Features](/docs/features)** — plain-language reference for the behaviours every page shares
-- **[MCP Integration](/docs/integrations/mcp)** — connect Claude Desktop, Cursor, or your own MCP client
-`;
-
-export default function GettingStartedPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/github/page.tsx b/apps/marketing/src/app/docs/integrations/github/page.tsx
deleted file mode 100644
index f8a20c910a..0000000000
--- a/apps/marketing/src/app/docs/integrations/github/page.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "GitHub — Integration",
- description: "How GitHub works as a PageSpace integration: OAuth once, drive-scoped visibility, and one-click tool bundles for reading repos and acting on issues and pull requests.",
- path: "/docs/integrations/github",
- keywords: ["GitHub", "integration", "pull requests", "issues", "code search", "AI tools"],
-});
-
-const content = `
-# GitHub
-
-Give your agents a GitHub identity. Once connected, they can read repositories, search code, browse branches and commits, and — with write tools enabled — open issues, leave PR reviews, and comment on behalf of you. Every action on GitHub happens under your account.
-
-## What you can do
-
-- Connect GitHub from **Settings → Integrations**. One OAuth click and you're in.
-- Choose who in your workspace can use the connection — keep it **private**, expose it to **drives you own**, or share with **every drive you're in**.
-- Pick a tool **bundle** for each [AI Chat](/docs/page-types/ai-chat) agent — *Read-only*, *Code review*, *Issue triage*, or *Full access* — or hand-pick individual tools. Newly enabled connections start on **Read-only** by default.
-- Ask an agent to find a function across a repo, summarize recent commits, or pull the diff of a PR.
-- Have an agent file a bug, close a stale issue, or post a structured review on a pull request with inline comments.
-- Leave GitHub off a chat entirely — an AI Chat with no GitHub tools allow-listed cannot touch your repos.
-
-## How it works
-
-**One OAuth flow, scopes you can read.** Connecting asks for \`repo\` and \`read:user\`. That's broad — it covers private repos and write access to code, issues, and PRs on any repo your GitHub account can reach. There is no narrower OAuth variant; if you want to cap what an agent can actually *do*, cap it with the per-agent tool bundle (or hand-picked tools) on each [AI Chat](/docs/page-types/ai-chat).
-
-**The token is yours. Visibility is the lever.** You control which agents can discover the connection by setting visibility to *private*, *owned drives*, or *all drives* when you connect. But tools always execute with your GitHub token and your GitHub permissions — if a teammate's agent uses your connection, it's still your account on the other end.
-
-**Agents get a set of GitHub tools**, split into read and write and surfaced as bundles. Read covers repositories, file trees and contents, branches, commits, code search, issues (list + comments), and pull requests (get, list, changed files, reviews, inline review comments). Write covers creating and updating issues, posting comments, creating a PR review, and leaving inline PR review comments. An agent only loads the tools its bundle allows, and write tools are rate-limited more tightly than read tools.
-
-**Nothing renders inside PageSpace.** GitHub is agent-only — there's no inline PR or issue view on a page, no webhook that files a GitHub issue as a PageSpace task. If you want to see what happened, you look at GitHub.
-
-## Good to know
-
-- **Agent actions on GitHub show up as you.** A comment an agent posts on a PR is indistinguishable from one you typed — same username, same avatar, same audit trail. Plan accordingly.
-- **OAuth scopes are broad, so restrict at the tool layer.** GitHub only offers a \`repo\` scope that grants full read and write on repos you can see. To keep an agent read-only, leave it on the **Read-only** bundle (the default for a newly enabled connection) — there is no OAuth-level read-only variant.
-- **It's an agent-driven integration.** No PR summaries render on pages, no issue cards in docs. All interaction happens through a chat asking an agent to do something.
-
-## Related
-
-- [AI in your Workspace](/docs/features/ai) — how agents call external tools under your identity.
-- [AI Chat](/docs/page-types/ai-chat) — the per-page tool bundles (and hand-picked allow-list) that decide which GitHub tools a given agent can use.
-- [Drives & Workspaces](/docs/features/drives) — how visibility scopes (private / owned / all drives) line up with drive membership.
-- [Sharing & Permissions](/docs/features/sharing) — the PageSpace-side rules that decide who can invoke an agent in the first place.
-`;
-
-export default function IntegrationGithubPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/google-calendar/page.tsx b/apps/marketing/src/app/docs/integrations/google-calendar/page.tsx
deleted file mode 100644
index bce023aca4..0000000000
--- a/apps/marketing/src/app/docs/integrations/google-calendar/page.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Google Calendar — Integration",
- description: "How Google Calendar integrates with PageSpace: two-way sync, a dedicated calendar surface, and AI tools that can read availability and schedule meetings for you.",
- path: "/docs/integrations/google-calendar",
- keywords: ["Google Calendar", "calendar", "sync", "integration", "scheduling", "availability"],
-});
-
-const content = `
-# Google Calendar
-
-Connect your Google account once and your calendars sync into PageSpace in both directions. You get a dedicated calendar surface inside the app, and an agent can read your availability, schedule meetings, and invite attendees — all acting as you.
-
-## What you can do
-
-- Connect your personal Google account from **Settings → Integrations → Google Calendar**.
-- Pick exactly which of your Google calendars sync in — uncheck the ones you don't want to see.
-- See your events in the **Calendar dashboard** — a personal view at the top level, and a separate per-drive calendar for drive-scoped events. Month, week, day, and agenda layouts.
-- Create, edit, and delete events inside PageSpace — anything you create here is pushed back to Google so it also shows up on your phone and in your other clients.
-- Ask an agent to check your availability for a slot, find a time that works for a group, or draft an invite.
-- Let an agent schedule a meeting directly, complete with attendees, and have it land on everyone's Google Calendar.
-- Set an event's visibility to the whole drive, to attendees only, or to just yourself.
-
-## How it works
-
-**The connection is per-user.** Each teammate connects their own Google account — there is no drive-shared Google Calendar. Your events are yours, and an agent in a drive only sees your calendar if *you* are the one asking.
-
-**Sync runs both directions.** PageSpace pulls events from Google using incremental sync tokens, and Google pushes real-time updates back over a webhook whenever something changes there. A fallback cron reconciles every six hours in case a push is missed. In the other direction, events you create *inside* PageSpace are sent up to Google. Events that originally came *from* Google aren't pushed back — if you edit a Google-sourced event in PageSpace, the next pull overwrites your local change. That tradeoff exists so the two sides can't silently disagree about the same event.
-
-**Agents use dedicated calendar tools.** When you @mention an [AI Chat](/docs/page-types/ai-chat) or talk to the global assistant, it has access to read-side tools (\`list_calendar_events\`, \`get_calendar_event\`, \`check_calendar_availability\`) and write-side tools (\`create_calendar_event\`, \`update_calendar_event\`, \`delete_calendar_event\`, \`invite_calendar_attendees\`, \`remove_calendar_attendee\`, \`rsvp_calendar_event\`). These act on the PageSpace event table — anything the agent creates is then pushed up to Google by the sync service, so a meeting it books for you lands on your Google Calendar like any other. Two trigger tools — \`set_calendar_trigger\` and \`delete_calendar_trigger\` — let you attach an AI agent to a calendar event so the agent fires automatically when that event occurs.
-
-**Visibility is enforced on every read.** Events are tagged **drive-wide**, **attendees only**, or **private**. Even when an agent queries the calendar, it only sees events you'd see — a private event you haven't shared is never returned.
-
-## Good to know
-
-- **The connection is per-user, not drive-shared.** Sharing a drive doesn't share your calendar. If you want your teammate's agent to see your availability, they need their own Google connection.
-- **Local edits to Google-sourced events are overwritten.** Edit an event that originated on your phone, and the next pull sync will replace your change with the Google version. Edit PageSpace-native events freely — those round-trip cleanly.
-- **The calendar dashboard is its own surface, not a page.** It lives at its own URL and doesn't appear in the page tree, so you won't find it under a drive's folders.
-
-## Related
-
-- [AI in your Workspace](/docs/features/ai) — how agents call calendar tools under your identity.
-- [Drives & Workspaces](/docs/features/drives) — where per-drive calendar views live.
-- [Accounts & Sign In](/docs/features/accounts) — managing the Google account tied to your PageSpace login.
-- [AI Chat](/docs/page-types/ai-chat) — where the calendar tools get allow-listed for an agent.
-`;
-
-export default function IntegrationGoogleCalendarPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/incoming-webhooks/page.tsx b/apps/marketing/src/app/docs/integrations/incoming-webhooks/page.tsx
deleted file mode 100644
index 395245a666..0000000000
--- a/apps/marketing/src/app/docs/integrations/incoming-webhooks/page.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Incoming Webhooks — Integration",
- description: "Mint a signed, page-scoped URL so an external system (CI, monitoring, a script) can push events into a PageSpace channel or trigger a workflow — HMAC signing, curl example, and how it composes with workflow triggers.",
- path: "/docs/integrations/incoming-webhooks",
- keywords: ["webhooks", "incoming webhooks", "HMAC", "signature", "CI", "monitoring", "workflow trigger", "curl"],
-});
-
-const content = `
-# Incoming Webhooks
-
-Incoming Webhooks let an external system — CI, a monitoring tool, a cron job, any script that can send an HTTP POST — push events into PageSpace, without handing it a full account credential. Mint a URL scoped to one page, and every delivery to it is signed and verified.
-
-> **Not the same thing as the "Generic Webhook" AI tool.** PageSpace also has an *outbound* AI tool provider (used by agents to call out to an arbitrary URL as part of a tool call). Incoming Webhooks is the opposite direction: an external system calling *into* PageSpace. If you're looking for how an agent posts data out to a webhook, that's a different feature — this page is about receiving.
-
-## What you can do
-
-- Mint a named webhook on any non-trashed page — only the drive's **owner** or an **admin** can create, toggle, rotate, or delete one. The **Incoming Webhooks** dialog that does this from the UI is currently wired up on **Channel** and **AI Chat (agent)** pages; other page types can still mint one via the API.
-- Get back a URL (\`/api/webhooks/\`) and a secret shown exactly once — save it, PageSpace never shows it again and can't recover it for you.
-- Rotate the secret in place (**Rotate secret** in the dialog, or \`POST /api/pages//webhooks//rotate\`) — the URL stays the same, so the external sender only swaps the secret. The old secret stops verifying the instant the rotation lands, and the new one is shown exactly once, just like at creation.
-- POST any JSON object to that URL, signed with the secret. A **Channel** webhook with no other wiring posts the payload's \`content\` into the channel verbatim, as if a bot had typed it.
-- Bind one or more **workflows** to a webhook (via the API — see below) so the same delivery also kicks off an agent run, with the full payload handed to it as context.
-- Disable a webhook without deleting it (its URL stops accepting deliveries but its history and bindings stay), or delete it outright.
-
-## The POST contract
-
-- **Body**: any JSON object, up to **64KB** raw. Larger bodies are rejected with \`413\` before PageSpace even attempts to parse them.
-- **Headers**: \`x-pagespace-signature\` and \`x-pagespace-timestamp\` (see signing below). A missing or invalid signature, or a timestamp more than 5 minutes old, gets a generic \`403\`.
-- **Unknown or disabled webhook**: a generic \`404\` — PageSpace never reveals whether a token used to exist.
-- **Channel pages** additionally require \`{ "content": string, "username"?: string }\` — this is deliberately Discord's incoming-webhook shape. \`content\` is posted verbatim (up to 4000 characters); \`username\`, if set, overrides the webhook's configured name as the message's displayed sender (up to 80 characters).
-- **Pages with no default action** (anything that isn't a Channel today) still accept any JSON object — there's just nothing to post it into unless a workflow trigger is bound.
-
-## Signing a delivery
-
-PageSpace's native scheme (\`v0\`) is HMAC-SHA256 over \`v0:{timestamp}:{rawBody}\`, sent as two headers:
-
-- \`x-pagespace-timestamp\` — Unix seconds when you signed the request.
-- \`x-pagespace-signature\` — \`v0=\`.
-
-The signature must be computed over the **exact bytes** you send as the body — sign first, then send that same string unmodified.
-
-**Bash / openssl:**
-
-\`\`\`bash
-SECRET="the-secret-shown-when-you-created-the-webhook"
-URL="https://your-pagespace-host/api/webhooks/"
-BODY='{"content":"Deploy finished ✅","username":"CI"}'
-
-TIMESTAMP=$(date +%s)
-SIGNATURE="v0=$(printf '%s' "v0:\${TIMESTAMP}:\${BODY}" \\
- | openssl dgst -sha256 -hmac "$SECRET" \\
- | sed 's/^.* //')"
-
-curl -X POST "$URL" \\
- -H "Content-Type: application/json" \\
- -H "x-pagespace-timestamp: $TIMESTAMP" \\
- -H "x-pagespace-signature: $SIGNATURE" \\
- -d "$BODY"
-\`\`\`
-
-**Node.js:**
-
-\`\`\`js
-import { createHmac } from "crypto";
-
-const secret = "the-secret-shown-when-you-created-the-webhook";
-const url = "https://your-pagespace-host/api/webhooks/";
-const timestamp = Math.floor(Date.now() / 1000);
-const body = JSON.stringify({ content: "Deploy finished ✅", username: "CI" });
-const signature = "v0=" + createHmac("sha256", secret).update(\`v0:\${timestamp}:\${body}\`).digest("hex");
-
-await fetch(url, {
- method: "POST",
- headers: {
- "content-type": "application/json",
- "x-pagespace-timestamp": String(timestamp),
- "x-pagespace-signature": signature,
- },
- body,
-});
-\`\`\`
-
-A \`200\` means the default action ran (a Channel post, for example). A \`202 { accepted: true, action: "none" | "triggers" }\` means the delivery was accepted but had no channel action, bound workflows, or both — \`action\` tells you which. Retry a \`429\` (rate limited), a \`409\` (an identical delivery is still being processed — see below), or a \`5xx\`/\`503\` (transient) — those are the sole retryable outcomes. \`400\` (malformed payload), \`403\` (bad/missing signature), \`404\` (unknown or disabled token), and \`413\` (body too large) are permanent failures: fix the request before sending it again, since an unmodified retry can only fail the same way, and retrying it in a loop risks a retry storm against your own integration.
-
-**Deliveries are idempotent per signed request.** Each signed request (its exact timestamp + signature pair) is normally processed at most once: re-sending the identical bytes — a network-timeout retry, for example — returns \`200 { ok: true, duplicate: true }\` without posting or firing anything again (the duplicate acknowledgment always has this \`200\` shape, even when the original delivery answered \`202\`). An identical request that arrives while the first is still being processed gets a \`409\` with a \`Retry-After\` — keep retrying it: you'll see the duplicate acknowledgment once the first attempt commits, or deliver fresh if it failed or its short in-flight claim lapsed (claims from a crashed attempt free up after about a minute, well inside the 5-minute signature window). A retry you **re-sign** with a fresh timestamp counts as a new delivery, so keep retrying with the original signed bytes when you want at-most-once behavior, and re-sign only when the previous attempt definitively failed with a retryable error. Two things to know at the edges: signatures have 1-second granularity, so two **distinct** events with byte-identical bodies signed in the same second are indistinguishable from a retry and collapse to one delivery — include a unique field (an event id or timestamp) in the payload if you emit identical bodies at sub-second rates; and dedup requires the store to be reachable — during a database outage deliveries fail retryably rather than dedup silently.
-
-## Composability: one delivery, two actions
-
-A single signed delivery can do **both** things at once — post the default action for the page type **and** fire every enabled workflow bound to that webhook. They're not alternatives; binding a workflow doesn't turn off the channel post, and the channel post doesn't block the workflow from also running.
-
-Bindings are managed through the API (there's no dedicated UI for this yet). Managing webhooks (minting, and binding triggers) is deliberately a human console action — a *drive-scoped* \`mcp_...\` token is rejected outright, on purpose. Script it with an **all-drives** token instead: \`pagespace keys create --all-drives --name ci-webhooks --show-token --yes\` (see [MCP](/docs/integrations/mcp)), or mint one from **Settings > MCP**. That token carries your own owner/admin authority and authenticates as a \`Bearer\` token, which also sidesteps this write endpoint's CSRF check (CSRF only applies to cookie-based session auth):
-
-\`\`\`bash
-# Bind a workflow to a webhook — owner/admin only, workflow must be in the same drive as the page
-curl -X POST "https://your-pagespace-host/api/pages//webhooks//triggers" \\
- -H "Content-Type: application/json" \\
- -H "Authorization: Bearer mcp_your_token_here" \\
- -d '{"workflowId":""}'
-\`\`\`
-
-Once bound, the workflow receives the full JSON payload as context (wrapped so the agent sees it verbatim) prepended to its configured prompt, and runs under the workflow owner's billing and credit limits — the sender never waits on it, since it fires after the HTTP response goes out.
-
-### Try it yourself (manual end-to-end check)
-
-1. Create a Channel page and open its **Incoming Webhooks** dialog (the webhook icon in the page toolbar, titled "Incoming Webhooks") to mint a webhook. Save the URL and secret.
-2. Create a workflow from the drive's **Workflows** dashboard (\`/dashboard//workflows\`) — it needs a name, a prompt, an AI Chat agent page to run against, and a cron schedule (the schedule still applies; binding a trigger just adds "also run on a webhook delivery" on top of it). Bind it to the webhook with the API call above.
-3. Send one signed \`POST\` with \`{"content": "Deploy finished"}\` using either signing example above.
-4. Confirm **both** things happened from that single request: the message \`Deploy finished\` appears verbatim in the channel, **and** a new run shows up for the bound workflow (its agent page picks up the delivery as context).
-
-## Good to know
-
-- **Least privilege by design.** A webhook secret only ever authenticates deliveries to the one page it was minted on — it can't read, list, or act on anything else in the drive, unlike a full API key or OAuth token.
-- **No dedupe, no event-type filtering (yet).** Every enabled trigger on a webhook fires on every accepted delivery; if you need "only fire on this kind of event," filter in the payload you send or in the workflow's own prompt.
-- **A page moved to a different drive after a trigger was bound** is re-checked at fire time — if the page's current drive no longer matches the workflow's drive, that trigger is skipped and recorded as a stale binding rather than silently executed.
-- **The secret is shown once.** If you lose it (or suspect it leaked), rotate it — **Rotate secret** in the dialog mints a new one for the **same URL**, so your sender's configuration only changes the secret. PageSpace stores it encrypted and can't display it again; deleting and re-minting is never necessary just to replace a secret.
-
-## Related
-
-- [Channels](/docs/page-types/channel) — the page type with a built-in default webhook action today.
-- [AI Chat](/docs/page-types/ai-chat) — where bound workflows run and how agent prompts work.
-- [AI in your Workspace](/docs/features/ai) — how workflows and agent runs fit into the bigger picture.
-- [Zero-Trust](/docs/security/zero-trust) — the broader signing/verification model this shares with Google Calendar and Zoom.
-`;
-
-export default function IncomingWebhooksPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/mcp/desktop/page.tsx b/apps/marketing/src/app/docs/integrations/mcp/desktop/page.tsx
deleted file mode 100644
index 899c806f8e..0000000000
--- a/apps/marketing/src/app/docs/integrations/mcp/desktop/page.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Desktop MCP Servers",
- description: "Run local MCP servers from PageSpace Desktop — filesystem access, documentation lookup, and any other MCP-compatible tool.",
- path: "/docs/integrations/mcp/desktop",
- keywords: ["MCP", "desktop", "local servers", "filesystem", "context7", "npx"],
-});
-
-const content = `
-# Desktop MCP Servers
-
-PageSpace Desktop can run **local MCP servers**, giving your workspace AI access to external tools — files on your machine, library documentation lookup, web fetch, or anything else in the MCP ecosystem.
-
-This is the inverse of the [PageSpace MCP server](/docs/integrations/mcp): that flow lets external tools read your PageSpace; this flow lets PageSpace call out to external tools.
-
-\`\`\`
-PageSpace Desktop AI → Local MCP server → External tool
- ├── Filesystem
- ├── Documentation (Context7)
- └── Any MCP server
-\`\`\`
-
-Local MCP servers are bound to the desktop app on your machine. They are not exposed to the web version and cannot be triggered by other users.
-
-## Setup
-
-1. Open **Settings > Local MCP Servers** in PageSpace Desktop.
-2. Paste a standard MCP \`mcpServers\` configuration (same shape as Claude Desktop's \`claude_desktop_config.json\`).
-3. Servers start on launch and shut down with the app.
-
-The desktop app stores the config at \`~/.pagespace/local-mcp-config.json\` (or the platform userData equivalent) and validates every entry before spawning a subprocess.
-
-## Configuration format
-
-\`\`\`json
-{
- "mcpServers": {
- "filesystem": {
- "command": "npx",
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/directory"]
- },
- "context7": {
- "command": "npx",
- "args": ["-y", "@upstash/context7-mcp"]
- }
- }
-}
-\`\`\`
-
-## Example servers
-
-### Filesystem
-
-Read and write local files. Scope the directory you pass.
-
-\`\`\`json
-{
- "command": "npx",
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects"]
-}
-\`\`\`
-
-### Context7
-
-Up-to-date library documentation lookup — the agent can pull current docs for the framework or package it's working with instead of guessing from its training data.
-
-\`\`\`json
-{
- "command": "npx",
- "args": ["-y", "@upstash/context7-mcp"]
-}
-\`\`\`
-
-### Anything else
-
-The [MCP ecosystem](https://github.com/modelcontextprotocol/servers) has servers for databases, code hosts, search, email, and more. Any server that speaks MCP and runs as a subprocess can drop into the config above — \`command\` + \`args\` + an optional \`env\` block for credentials. If a server needs an API key or token, it goes in \`env\`.
-
-## Trust model
-
-Local MCP servers inherit the desktop app's privileges on your machine:
-
-- Processes run as your OS user.
-- Filesystem access is limited to the directories you pass each server.
-- Network access depends on the server's own implementation.
-- Servers are unreachable from the web app or from other users.
-- Tool calls and results are logged in the conversation history.
-
-This is a deliberate trade-off: the desktop app can do things the browser sandbox cannot, in exchange for your local authority over what it runs.
-
-## Desktop-only
-
-The web app cannot spawn local MCP servers. If you need to integrate external services from the web, use the [PageSpace MCP server](/docs/integrations/mcp) — external AI clients connect to your PageSpace with a token.
-`;
-
-export default function DesktopMCPPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/mcp/page.tsx b/apps/marketing/src/app/docs/integrations/mcp/page.tsx
deleted file mode 100644
index 526cb8ecb5..0000000000
--- a/apps/marketing/src/app/docs/integrations/mcp/page.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "MCP Integration",
- description: "Connect AI tools like Claude Desktop, Claude Code, and Cursor to PageSpace via the Model Context Protocol. Log in with the pagespace CLI, or use a scoped token for agents and CI.",
- path: "/docs/integrations/mcp",
- keywords: ["MCP", "Model Context Protocol", "AI integration", "Claude", "Cursor", "API tokens", "pagespace CLI"],
-});
-
-const content = `
-# MCP Integration
-
-PageSpace speaks the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) — an open standard for giving AI tools access to external data and actions. The \`pagespace\` CLI runs \`pagespace mcp\`, a local MCP server that lets tools like Claude Desktop, Claude Code, and Cursor read and write your PageSpace workspace.
-
-\`\`\`
-AI tool (MCP client) → pagespace mcp (MCP server) → PageSpace API
- ↓
- Your workspace
-\`\`\`
-
-Every operation runs with the permissions of whoever (or whatever) authenticated — always a scoped credential naming specific drives, never your full personal account: \`pagespace mcp\` refuses to start on nothing but a bare \`pagespace login\`, by design (see below).
-
-## Step 1: Install the CLI and authenticate
-
-\`\`\`bash
-npm install -g @pagespace/cli
-pagespace login
-\`\`\`
-
-(The full command reference lives in the [PageSpace CLI](/docs/features/cli) docs.)
-
-\`pagespace login\` opens a browser, completes an OAuth login, and stores a credential locally — but that credential is scoped to key management only, with **zero content access of its own**. It's for you, personally, to create/list/edit/revoke your own scoped keys; \`pagespace mcp\` won't run on it alone.
-
-Mint the drive-scoped key \`pagespace mcp\` actually needs with the guided wizard:
-
-\`\`\`bash
-pagespace keys
-\`\`\`
-
-Or, flag-driven (same thing, no interactive wizard prompts — for scripting the *setup* step itself, run once by a human):
-
-\`\`\`bash
-pagespace keys create --drive --role member --name agent
-\`\`\`
-
-Either way opens a browser for a one-time consent screen (minting is always a deliberate, human-approved step, never a silent agent-runnable call) and stores the result locally under the key name you chose. Need a portable \`mcp_...\` token for a *different* machine, CI, or a service account? Add \`--show-token\` to the mint — it prints the token **exactly once** (never again; only a SHA3-256 hash is stored server-side) — or mint one from **Settings > MCP** in the app. Either way, scoping to specific drives joins those drives as an **app** on the member list, governed by the role you give it there; scoped credentials cannot create new drives.
-
-## Step 2: Configure your AI tool
-
-Minted a key with the CLI on *this* machine? Point the config at it by name with \`PAGESPACE_KEY\` — no secret ever appears in the config file:
-
-\`\`\`json
-{
- "mcpServers": {
- "pagespace": {
- "command": "pagespace",
- "args": ["mcp"],
- "env": {
- "PAGESPACE_KEY": "agent"
- }
- }
- }
-}
-\`\`\`
-
-Using a portable token instead (minted from **Settings > MCP**, for a different machine, CI, headless):
-
-\`\`\`json
-{
- "mcpServers": {
- "pagespace": {
- "command": "pagespace",
- "args": ["mcp"],
- "env": {
- "PAGESPACE_TOKEN": "mcp_your_token_here"
- }
- }
- }
-}
-\`\`\`
-
-\`PAGESPACE_API_URL\` overrides the default \`https://pagespace.ai\` host for self-hosted instances.
-
-### Claude Desktop
-
-Edit \`claude_desktop_config.json\`:
-- **macOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
-- **Windows**: \`%APPDATA%\\\\Claude\\\\claude_desktop_config.json\`
-
-### Claude Code
-
-\`\`\`bash
-claude mcp add pagespace -- pagespace mcp
-\`\`\`
-
-### Cursor
-
-**Settings > MCP Servers** → add the \`mcpServers\` block above.
-
-## Step 3: Capabilities
-
-\`pagespace mcp\` generates its tool list mechanically from the same operation registry that powers the \`pagespace\` CLI and [\`@pagespace/sdk\`](/docs/features/sdk), so the tool surface can't drift from what the CLI itself supports.
-
-At a minimum the server covers:
-
-- **Drives** — list accessible drives; create new drives (unscoped tokens only).
-- **Pages** — list and navigate the page tree, create pages, read page content, perform line operations and sheet cell edits (\`read\`, \`replace\`, \`insert\`, \`delete\`, \`edit-cells\`).
-- **Search** — global and multi-drive search.
-- **Tasks** — query and manage tasks on \`TASK_LIST\` pages.
-- **Calendar** — read availability, schedule events, invite attendees, RSVP, and set agent triggers on calendar events.
-- **Slash Commands** — create and manage \`/commands\` that invoke pages as executable skills.
-- **Role Management** — drive role CRUD and per-page permission assignment.
-- **Agent Triggers** — attach agent runs to calendar events and tasks so agents fire automatically when those events occur.
-- **Scheduled Workflows** — cron-based recurring agent automation.
-- **Drive Members** — list drive members and connections/collaborators.
-- **AI Models** — list available AI providers and models for dynamic model selection.
-
-Every tool respects the caller's permissions. If you cannot view a page in the web UI, the MCP server cannot see it either.
-
-## Use an agent as an OpenAI-compatible model
-
-Any token also unlocks an **OpenAI-compatible API**, so any tool that speaks the OpenAI Chat Completions format can talk to one of your PageSpace agents as if it were a model.
-
-- **Base URL** — \`https://pagespace.ai/api/v1\`
-- **API key** — your token (\`mcp_...\`)
-- **Model** — \`ps-agent://\`, the id of the AI Chat page you want to run. Copy it from the agent's settings tab.
-
-\`\`\`bash
-curl https://pagespace.ai/api/v1/chat/completions \\
- -H "Authorization: Bearer mcp_your_token_here" \\
- -H "Content-Type: application/json" \\
- -d '{
- "model": "ps-agent://",
- "stream": true,
- "messages": [{ "role": "user", "content": "Summarize the latest notes in this drive." }]
- }'
-\`\`\`
-
-The agent replies with its own system prompt and tools, and runs those tools server-side under the same permissions you'd have in the app — it can search the drive, read pages, and write back, all within the token's scope. Responses are **streamed**, so set \`stream: true\` (non-streaming requests are rejected). Pass an optional \`conversation_id\` to continue a thread across calls, and \`GET /api/v1/models\` lists the agents a token can reach.
-
-## Token security
-
-- **Scoped access** — restrict a key to specific drives at creation, from the CLI (\`pagespace keys\` or \`pagespace keys create --drive \`) or **Settings > MCP**.
-- **Instant revocation** — \`pagespace keys revoke \`, or revoke from **Settings > MCP**, cuts a key off immediately.
-- **Audit logging** — token create/revoke/use events land in the audit log with the token identifier.
-- **Hash-only storage** — the database stores a SHA3-256 hash, never the raw token. Losing a token means creating a new one.
-- **No automatic expiry** — tokens live until revoked. Rotate on whatever cadence fits your risk model.
-
-## Troubleshooting
-
-**Token rejected**: confirm it hasn't been revoked (\`pagespace keys list\` or **Settings > MCP**) and that it starts with \`mcp_\`.
-
-**Connection refused**: check \`PAGESPACE_API_URL\` (or \`--host\`) is correct and reachable from the machine running \`pagespace mcp\`.
-
-**Permission denied**: MCP inherits the caller's permissions. If you lost access to a drive, the key or token stops seeing it too.
-
-**Server fails to start**: \`pagespace mcp\` refuses to start unless the invocation names an explicit credential — \`PAGESPACE_KEY\`/\`--key\` (a key minted by \`pagespace keys\`) or \`PAGESPACE_TOKEN\`/\`--token\` (a portable token). A bare \`pagespace login\` is never enough, and the machine's *active* key (\`pagespace keys use\`) deliberately does not apply to MCP configs — name the credential explicitly so the config is portable and self-describing. Run \`pagespace whoami\` to confirm you're authenticated, and \`pagespace --version\` to confirm the CLI installed correctly.
-
-## Using the older \`pagespace-mcp\` package?
-
-\`pagespace-mcp\` still works — it now prints a one-line deprecation notice to stderr pointing at the migration guide in the [\`@pagespace/cli\` repository](https://github.com/2witstudios/PageSpace/blob/master/packages/cli/docs/migrating-from-pagespace-mcp.md). Move to \`@pagespace/cli\` on your own schedule; the tool surface is unchanged, only how you install and authenticate it is.
-`;
-
-export default function MCPPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/integrations/page.tsx b/apps/marketing/src/app/docs/integrations/page.tsx
deleted file mode 100644
index 34d6bc2abb..0000000000
--- a/apps/marketing/src/app/docs/integrations/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import Link from "next/link";
-import { ArrowRight, Calendar, Github, Server, Monitor, Webhook } from "lucide-react";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Integrations",
- description: "Google Calendar, GitHub, MCP, and Incoming Webhooks — how PageSpace connects to what lives outside it, in both directions.",
- path: "/docs/integrations",
- keywords: ["integrations", "Google Calendar", "GitHub", "MCP", "Claude Desktop", "Cursor", "webhooks"],
-});
-
-const integrations = [
- { title: "Google Calendar", href: "/docs/integrations/google-calendar", icon: Calendar, description: "Two-way sync your Google calendars into PageSpace — agents read availability, create events, invite attendees." },
- { title: "GitHub", href: "/docs/integrations/github", icon: Github, description: "Give agents a GitHub identity — browse repos, leave PR reviews, file issues, all under your account." },
- { title: "MCP", href: "/docs/integrations/mcp", icon: Server, description: "Connect Claude Desktop, Cursor, or any MCP client to your workspace using a scoped token." },
- { title: "Desktop MCP", href: "/docs/integrations/mcp/desktop", icon: Monitor, description: "Run local MCP servers inside the PageSpace desktop app — your AI chats get the same external tools you use everywhere else." },
- { title: "Incoming Webhooks", href: "/docs/integrations/incoming-webhooks", icon: Webhook, description: "Mint a signed, page-scoped URL so CI, monitoring, or a script can push events into a channel or fire a workflow." },
-];
-
-export default function IntegrationsIndexPage() {
- return (
-
-
Integrations
-
- PageSpace connects outward and inward. Outward — your agents reach into Google Calendar and GitHub on your behalf. Inward — external AI clients connect in through MCP, and external systems push events in through Incoming Webhooks.
-
- );
-}
diff --git a/apps/marketing/src/app/docs/layout.tsx b/apps/marketing/src/app/docs/layout.tsx
deleted file mode 100644
index 4cbe45997f..0000000000
--- a/apps/marketing/src/app/docs/layout.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { SiteNavbar } from "@/components/SiteNavbar";
-import { SiteFooter } from "@/components/SiteFooter";
-import { DocsSidebar } from "@/components/DocsSidebar";
-import { DocsContent } from "@/components/DocsContent";
-
-export default function DocsLayout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
- {children}
-
-
-
-
- );
-}
diff --git a/apps/marketing/src/app/docs/page-types/ai-chat/page.tsx b/apps/marketing/src/app/docs/page-types/ai-chat/page.tsx
deleted file mode 100644
index 0fe9fb820b..0000000000
--- a/apps/marketing/src/app/docs/page-types/ai-chat/page.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "AI Chat",
- description: "How the AI Chat page type works in PageSpace — the conversation surface, the configuration panel (system prompt, tools, provider, model, read-only toggle), and sharing a chat with teammates.",
- path: "/docs/page-types/ai-chat",
- keywords: ["AI Chat", "agents", "system prompt", "read-only", "tools", "page type"],
-});
-
-const content = `
-# AI Chat
-
-An AI Chat is a dedicated conversation page with an AI agent. It sits in the page tree next to your documents and folders, has its own URL, its own permissions, and its own configuration — system prompt, enabled tools, provider, and model — all attached to that specific page.
-
-## What you can do
-
-- Create an AI Chat page anywhere in the tree from the **+** button in the sidebar or the slash menu inside another page.
-- Chat directly on the page; replies and tool calls stream in as they arrive.
-- Configure the agent from the page's settings panel: its **system prompt**, the **enabled tools** allow-list, the **provider** and **model**, a **read-only** toggle, and a **web search** toggle — all per page.
-- Keep your conversation private by default, or flip on multiplayer so teammates with access share one live conversation and see the same tool calls.
-- Undo what the agent did on any turn — preview the edits that assistant message caused, then revert them as a group.
-- @mention this AI Chat page from anywhere else in your workspace to pull it into that thread.
-
-## How it works
-
-An AI Chat page is just a page. It has a title, a parent, a URL, permissions, version history, a place in search. What's unique is the conversation inside: every user message, every agent reply, every tool call, and every tool result is persisted to the page so you can scroll back days later and see exactly what the agent did and why.
-
-The configuration panel is where you shape the agent. The **system prompt** is a free-form text field that tells the agent what it is and how to behave. The **enabled tools** list is an explicit allow-list — any tool not in the list cannot be called from this page. The **provider** and **model** decide which backend runs the conversation. **Read-only mode** strips every write-capable tool on top of the allow-list, so the agent can still search and read but cannot create, edit, or delete. **Web search** is a single toggle that enables or disables one specific web-lookup tool.
-
-By default each person gets their own private conversation with the agent on the page — sharing the page shares its setup, not your chat history. Turn on multiplayer and everyone with access posts into one shared conversation instead, with each message attributed to its sender and the agent replying to the whole room in real time.
-
-## Good to know
-
-- **An empty tool list means chat-only.** The agent can talk but cannot create, edit, delete, move, or send anything. Empty is not a wildcard — nothing means nothing.
-- **Read-only is enforced at the tool layer.** When the toggle is on, write tools are stripped before the model sees them; no prompt can override it.
-- **The system prompt lives on the page, not on you.** Anyone you share the AI Chat page with sees the same prompt, tools, and provider selection.
-
-## Related
-
-- [AI in your Workspace](/docs/features/ai) — how AI works across every page, @mentions, and agent-to-agent consultation.
-- [Task Lists](/docs/page-types/task-list) — assigning an agent to a task so it picks up its own work.
-- [Sharing & Permissions](/docs/features/sharing) — the rules an agent inherits when it acts on your behalf.
-- [Pages](/docs/features/pages) — the universal page model an AI Chat page is built on.
-`;
-
-export default function PageTypeAIChatPage() {
- return ;
-}
diff --git a/apps/marketing/src/app/docs/page-types/canvas/page.tsx b/apps/marketing/src/app/docs/page-types/canvas/page.tsx
deleted file mode 100644
index d6ce92d7b6..0000000000
--- a/apps/marketing/src/app/docs/page-types/canvas/page.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { DocsMarkdown } from "@/components/DocsContent";
-import { createMetadata } from "@/lib/metadata";
-
-export const metadata = createMetadata({
- title: "Canvas",
- description: "How Canvas pages work in PageSpace — custom HTML, CSS, and JavaScript rendered in an isolated sandbox for dashboards, landing pages, and visual hubs, and publishable to the public web.",
- path: "/docs/page-types/canvas",
- keywords: ["canvas", "HTML", "CSS", "JavaScript", "dashboard", "landing page", "publishing", "sandbox"],
-});
-
-const content = `
-# Canvas
-
-A Canvas page renders your own HTML, CSS, and JavaScript inside PageSpace. You write the markup in a code editor, flip to a View tab, and the page appears as a fully styled, interactive mini-site sitting in the tree next to your documents and folders — and you can publish it to the public web.
-
-## What you can do
-
-- Create a Canvas page anywhere in the tree from the **+** button or the slash menu, the same way you create a document.
-- Switch between **Code** and **View** tabs at the top of the page — Code gives you a Monaco editor, View renders the result.
-- Write a full document: HTML with inline \`