Add unlisted hosted beta guide and FAQ (#122)

## Summary
Adds the hosted beta getting-started guide and FAQ at `/hosted-beta/`,
using the existing docs header, sidebar, article layout, theme controls,
and table of contents.

The page remains outside the navigation/search manifest, homepage
listings, previous/next links, and sitemap, with a `noindex` directive.
Direct-link visitors can navigate into the regular docs. This is a
public, unlisted page rather than access-controlled content.

## Validation
- Root and subpath builds and unlisted discovery checks pass.
- Existing static route verification passes.
- Desktop and mobile browser checks show no overflow or runtime errors.
- Table-of-contents anchors and navigation into regular docs verified.

Local preview: http://127.0.0.1:4337/hosted-beta/
Hosted preview:
https://aa3d8178.paperclip-docs-74t.pages.dev/hosted-beta/

Cloudflare deployment succeeded for commit 4b94d17.
This commit is contained in:
Michael Nguyen authored and GitHub committed 2026-09-11 00:43:36 -07:00
1 parent 27d7a5ab4c
commit 9f46125a8d
5 files changed
+173 -4

No files matched your search

+11
View File
@@ -56,3 +56,14 @@ Copyright (c) 2026 Paperclip Community.
The Paperclip **software** is open source — see the main [paperclip](https://github.com/paperclipai/paperclip) repository for its license.
The **documentation in this repository** is licensed under [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)](https://creativecommons.org/licenses/by-nc-nd/4.0/). You may read and share it for non-commercial purposes with attribution. Commercial use of any kind requires prior written permission from the copyright holders. See [`LICENSE`](LICENSE) for the full text.
### Unlisted hosted beta guide
`docs/hosted-beta.md` is built at `/hosted-beta/` by `site/build-release.mjs`.
It reuses the full docs shell and initializes page tools without adding the page to the navigation/search manifest.
Keep it out of `site/content.json`: it is deliberately absent from navigation,
search, homepage listings, previous/next links, and the sitemap. It has a
`noindex` meta tag; this is a public page, not an access-controlled document.
Run `node scripts/verify-unlisted-beta.mjs` to verify root/subpath builds and
exclusion from discovery. Preview with `npm run docs:build` and `npm run docs:serve`.
+100
View File
@@ -0,0 +1,100 @@
# Welcome to the Paperclip hosted beta
Create a team of AI agents to take on projects and ongoing work. Paperclip gives your agents a workspace in the cloud and gives you one place to assign work, track progress, and review results.
This hosted beta is invitation-only.
## Get set up
1. **Sign in.** Open your invitation link and sign in with Google, GitHub, or email. Use the email address that received your invitation.
2. **Create your organization.** Give your organization a name.
3. **Name your first agent.** Your first agent will help you plan work and build your team. Give it a name, then select **Next**.
4. **Connect a model.** Choose Claude or OpenAI, then follow the prompts to connect your subscription or an API key.
5. **Finish setup.** When the Review screen says your agent is ready, select **Get started**.
## Start delegating work
Your first agent will ask about the work you want to delegate. Let it interview you and propose a plan, or select **I have a task in mind**.
Bring a task, an ongoing workflow, or a larger project. Explain the outcome you want and share relevant context. You can add agents and connect the tools they need as you build your team.
Follow progress in the task, answer questions, and review results. When your agent requests approval, review the proposed action before approving it or requesting changes.
See the guides to [managing tasks](https://docs.paperclip.ing/guides/day-to-day/issues/) and [tracking agent activity](https://docs.paperclip.ing/guides/day-to-day/dashboard/).
## Help shape the future of Paperclip
Tell us what’s working—and especially what isn’t. When something gets stuck, tell us what you expected and what happened. Your feedback will help us decide what to improve next.
Ask questions, share feedback, and get support in the [private beta Discord](https://discord.gg/zTXFu6UR7) or through in-product chat. If you can’t sign in, email [[email protected]](mailto:[email protected]).
Thanks for helping us build Paperclip.
## Frequently asked questions
<details class="accordion">
<summary>What carries over when I connect Claude or OpenAI?</summary>
<div class="accordion-body">
Connecting Claude or OpenAI lets Paperclip use your account to run agents. It doesn’t import your existing conversations, local files, skills, or MCP connections. Add the context and connections your agents need.
</div>
</details>
<details class="accordion">
<summary>Will my agents keep working when I close my browser?</summary>
<div class="accordion-body">
Yes. Agents run in a hosted environment, so your computer can be off or offline while they work. Tasks can still pause for input, approvals, or usage limits.
</div>
</details>
<details class="accordion">
<summary>Is the hosted beta free?</summary>
<div class="accordion-body">
Yes. We won’t charge for hosting or agent environments during the beta. Your model provider’s charges and usage limits still apply.
</div>
</details>
<details class="accordion">
<summary>How much model usage should I expect?</summary>
<div class="accordion-body">
Usage depends on the model, task, context, and how often agents run. Scheduled work also uses your provider allowance or API credits.
Check your provider account for billing and subscription limits. Paperclip’s spend totals don’t show your remaining subscription allowance.
See [Costs and Budgets](https://docs.paperclip.ing/guides/day-to-day/costs/) and [Heartbeats and Routines](https://docs.paperclip.ing/guides/projects-workflow/routines/) to understand costs and when agents run.
</div>
</details>
<details class="accordion">
<summary>Can I connect my tools and repositories?</summary>
<div class="accordion-body">
Use **Connectors** to see the available connections and authorize access. Set them up in your hosted organization; connections from your local installation don’t carry over.
</div>
</details>
<details class="accordion">
<summary>Can I import an existing company?</summary>
<div class="accordion-body">
Company import isn’t available in this beta. Create a new organization and set up its agents and connections.
</div>
</details>
<details class="accordion">
<summary>Does the existing documentation apply to the hosted beta?</summary>
<div class="accordion-body">
Use the [documentation](https://docs.paperclip.ing/) for product capabilities and workflows. You can skip self-hosted installation and runtime setup—we manage those for you.
</div>
</details>
+24
View File
@@ -0,0 +1,24 @@
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
const out = await fs.mkdtemp(path.join(os.tmpdir(), 'docs-unlisted-'));
try {
for (const base of ['/', '/paperclip-docs/']) {
execFileSync(process.execPath, ['site/build-release.mjs', '--base-path', base, '--out-dir', out]);
const html = await fs.readFile(path.join(out, 'hosted-beta/index.html'), 'utf8');
assert.match(html, /name="robots" content="noindex, follow"/);
assert.equal((html.match(/<h1(?:\s[^>]*)?>/g) || []).length, 1);
assert.ok(html.includes(`<base data-seo-base href="${base}"`));
assert.ok(html.includes('data-unlisted-page="hosted-beta"'));
assert.ok(html.includes('id="sb-sections" data-server-rendered="true"'));
assert.ok(html.includes('id="breadcrumb"'));
for (const file of ['content.json', 'index.html', 'sitemap.xml']) {
assert.ok(!(await fs.readFile(path.join(out, file), 'utf8')).includes('hosted-beta'), file);
}
}
console.log('Unlisted beta page passes root/subpath and discovery checks.');
} finally {
await fs.rm(out, { recursive: true, force: true });
}
+25 -4
View File
@@ -385,8 +385,8 @@ function findHeadingTarget(article, targetHeading) {
);
}
function decorateHeadings(article, file) {
const page = allPages.find(candidate => candidate.file === file);
function decorateHeadings(article, file, unlistedPage = null) {
const page = unlistedPage || allPages.find(candidate => candidate.file === file);
if (!page) return;
const usedIds = new Set();
@@ -888,6 +888,23 @@ async function init() {
buildMobileDrawer();
initSearch();
const unlistedArticle = document.querySelector('article[data-unlisted-page]');
if (unlistedArticle) {
const page = { file: 'hosted-beta.md', slug: 'hosted-beta', title: 'Hosted beta', sectionTitle: 'Closed beta', navTrail: ['Hosted beta'] };
currentFile = page.file;
showArticleView();
insertMetaRow(unlistedArticle, page);
decorateHeadings(unlistedArticle, page.file, page);
buildToc(unlistedArticle, page.file, page);
hideLoading();
const heading = getCurrentHeadingRoute();
if (heading) {
const target = findHeadingTarget(unlistedArticle, heading);
if (target) focusHeading(target);
}
return;
}
const pathRoute = getPathRoute();
const rawRoute = applyRedirect(pathRoute || getLegacyRoute());
const initialRoute = parseRoute(rawRoute);
@@ -1090,6 +1107,10 @@ function buildFlatList() {
/* ─── Load page ─────────────────────────────────────────────────────────── */
async function loadPage(file, targetHeading = null, historyMode = 'push', options = {}) {
const page = allPages.find(candidate => candidate.file === file);
if (document.querySelector('article[data-unlisted-page]') && page) {
location.assign(targetHeading ? getPageHeadingUrl(page, targetHeading) : getPageUrl(page));
return;
}
currentFile = file;
showArticleView();
setActiveState(file);
@@ -1721,7 +1742,7 @@ function headingLabel(heading) {
return clone.textContent.trim();
}
function buildToc(article, file) {
function buildToc(article, file, unlistedPage = null) {
resetToc();
const metaRow = article.querySelector('.meta-row');
@@ -1732,7 +1753,7 @@ function buildToc(article, file) {
const headings = [...article.querySelectorAll('h2, h3')];
if (headings.length < 2) return;
const page = allPages.find(candidate => candidate.file === file);
const page = unlistedPage || allPages.find(candidate => candidate.file === file);
const CHEVRON_SVG = '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="m3 4.5 3 3 3-3"/></svg>';
const TOC_SVG = '<svg class="toc-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4h10M3 8h7M3 12h9"/></svg>';
+13
View File
@@ -1742,6 +1742,19 @@ async function main() {
await copyDirRecursive(screenshotsSourceDir, screenshotTargetDir);
}
// Unlisted routes reuse the full docs shell, but never enter the discovery manifest.
const betaMarkdown = await fs.readFile(path.join(docsRoot, "hosted-beta.md"), "utf8");
const betaPage = { file: "hosted-beta.md", slug: "hosted-beta", title: "Hosted beta", navTrail: ["Hosted beta"] };
const betaMetadata = { ...rootMetadata, title: "Welcome to the Paperclip hosted beta", description: markdownDescription(betaMarkdown), url: siteUrlForPath(options.siteUrl, options.basePath, "hosted-beta/"), page: betaPage };
const betaHtml = buildStaticPageHtml(
withStaticChrome(sourceIndex, releaseNav, options.basePath, sectionIconPaths, betaMetadata),
betaMetadata, betaMarkdown, options.basePath, releaseStyles, null, null,
).replace("</head>", '<meta name="robots" content="noindex, follow" /></head>')
.replace('<article id="article">', '<article id="article" data-unlisted-page="hosted-beta">');
await ensureDir(path.join(options.outDir, "hosted-beta"));
await fs.writeFile(path.join(options.outDir, "hosted-beta/index.html"), betaHtml);
await fs.writeFile(path.join(options.outDir, "hosted-beta.md"), betaMarkdown);
const missingNavTargets = [];
for (const { page } of flattenNavPages(releaseNav)) {
const targetPath = path.join(options.outDir, page.file);