From 878f18a3bffb07457d96a20988c6ca1f0ff2c249 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 11 Nov 2026 13:36:49 +0000 Subject: [PATCH] feat: /privacy and /terms, linked from the footer The moshcode product policy (same text as app.moshcode.sh), rendered in the site shell without the ad unit; Privacy and Terms join the footer links and the sitemap. Co-Authored-By: Claude Opus 5.5 --- src/content.mjs | 2 + src/legal.mjs | 96 ++++++++++++++++++++++++++++++++++++++++++++++ src/server.mjs | 7 +++- src/site.mjs | 30 ++++++++++++++- test/site.test.mjs | 16 ++++++++ 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/legal.mjs diff --git a/src/content.mjs b/src/content.mjs index 5d38296..d1c37a3 100644 --- a/src/content.mjs +++ b/src/content.mjs @@ -270,4 +270,6 @@ export const FOOTER_LINKS = [ { label: 'Blog', href: '/blog' }, { label: 'install.sh', href: '/install.sh' }, { label: 'llms.txt', href: '/llms.txt' }, + { label: 'Privacy', href: '/privacy' }, + { label: 'Terms', href: '/terms' }, ]; diff --git a/src/legal.mjs b/src/legal.mjs new file mode 100644 index 0000000..7c0bd63 --- /dev/null +++ b/src/legal.mjs @@ -0,0 +1,96 @@ +// Privacy Policy and Terms of Service for the moshcode product: moshcode.sh +// (this landing), app.moshcode.sh (the app, including the pit) and its MCP. +// app.moshcode.sh serves the same text (moshcoder/moshcode apps/pwa/src/lib/legal.mjs); keep +// the two copies in step. + +export const LEGAL_EFFECTIVE = "November 11, 2026"; +const CONTACT = "support@profullstack.com"; +const OPERATOR = "Profullstack, Inc., 16375 Bonnie Lane, Los Gatos, CA 95032, USA"; +const mail = `${CONTACT}`; + +export const PRIVACY_HTML = ` +

moshcode is operated by ${OPERATOR}. This policy covers moshcode.sh, app.moshcode.sh (including the pit and the Moshpit name registry), the moshcode MCP server, and what the moshcode CLI sends to them. It describes only what the service actually collects.

+ +

The CLI runs on your machine

+

The moshcode CLI installs and drives coding agents (opencode, Claude Code, codex and others) on your own computer. Your code, prompts and the agents' own accounts stay between you and those tools. The CLI sends something to app.moshcode.sh only when you use a feature that needs it: an approval, a live session mirror, the herd on your phone, settings sync, or the pit.

+ +

What we collect

+

Account. Your email address and a password (stored only as a hash), or a passkey (we keep its public key and a counter), or Sign in with CoinPay (we keep the CoinPay account identifier). An optional display name. A session cookie while you are signed in. API keys and CLI sign-ins are stored as hashes with a short prefix.

+

Approvals. When a script asks for one, we store its message, the context the script sent, the script name, and your reply.

+

Notification channels. The channels you add: an email address, a Slack incoming webhook URL, a Telegram chat id, browser push subscriptions, or a phone number for SMS.

+

Live sessions and the herd. If you mirror a CLI session or turn on the herd for your phone, the CLI sends the terminal output of that session (we keep the most recent part as scrollback), the session and machine names, and their status, and we queue the lines and keys you send back until the CLI collects them.

+

Teams. Organizations, teams and members you create, and the sessions you share with them.

+

Settings sync. The settings snapshots you save from the pit with /save; you can delete them from your settings.

+

Credits and payments. Credits are prepaid in cryptocurrency through CoinPay. We keep a ledger of credits bought and spent, purchase amounts and status, credit codes you redeem and invites you send. We never see card or bank details.

+

Moshpit names. Names and endings you register or buy, their records (addresses, sites, links, feeds and content you publish to them), and offers. If you opt in to a contact address for a name, mail to it is forwarded to you through Forward Email without publishing your own address.

+ +

Cookies and local storage

+

Signing in sets a session cookie, and forms carry a CSRF token. An invite or referral link sets a cookie so a later sign-up can be credited. There are no advertising cookies.

+ +

Who else is involved

+ +

Nothing is sold, and no advertising profile is built from you.

+ +

What is public

+

Registered Moshpit names, their records and anything you publish to them are public. Approvals, sessions, settings, channels and payments are visible only to your account and the teams you share them with.

+ +

AI agents and MCP

+

The moshcode MCP server is at https://app.moshcode.sh/mcp (JSON-RPC 2.0, streamable HTTP), and a single shared session is reachable at https://moshcode.sh/api/v1/mcp/<share-id>. There are no anonymous tools: every call needs an OAuth 2.1 access token (authorization code with PKCE, or device authorization) that you approve in the browser. With it, an MCP client can list your live moshcode sessions, read their output, and, only with the permissions you grant, send a line or a key, answer or approve a prompt, or cancel the session. Access tokens last at most an hour, and revoking the grant or the share stops them at once.

+

MCP and API requests are logged (time, tool or route, IP address, and the account or key if any) for abuse prevention and rate limiting. MCP actions are also recorded in an audit log of who did what and when, without the command text or terminal output. No MCP data is sold or used for advertising.

+ +

Keeping and deleting data

+

Session output is kept as a rolling scrollback, not a full history. Settings snapshots can be deleted from your settings. Everything else is kept until you delete it or ask us to. To delete your account, write to ${mail} from the address on the account.

+ +

Your rights

+

Depending on where you live, you may have the right to access, correct, delete or export your personal data. Contact us to exercise them.

+ +

Children

+

moshcode is a developer tool and is not directed at children under 13, and we do not knowingly collect personal information from them.

+ +

Changes

+

If the service starts doing something different, this page changes with it. Material changes will be announced on this page.

+ +

Contact

+

${OPERATOR}. Email ${mail}.

`; + +export const TERMS_HTML = ` +

1. Acceptance of terms

+

By accessing or using moshcode.sh, app.moshcode.sh, the moshcode CLI's online features or the moshcode MCP server (the "Service"), you agree to these Terms of Service. If you do not agree, do not use the Service. The Service is operated by ${OPERATOR}.

+ +

2. Use of the Service

+

moshcode drives coding agents, routes approvals to you, mirrors your sessions and runs the Moshpit name registry. You are responsible for what your agents and scripts do with it. Do not use the Service to attack, spam or harm others, to publish illegal content on a Moshpit name, or to get around access controls. Please do not send automated traffic heavy enough to degrade the Service for anyone else; on moshcode.sh, training crawlers and scrapers must buy a day pass.

+ +

3. Accounts

+

You are responsible for your account, API keys, OAuth grants and session shares, and all activity under them. Tell us at once about any unauthorized use.

+ +

4. Payments

+

Credits, Moshpit names and crawler day passes are paid in cryptocurrency through CoinPay, or over x402. Credits pay for metered notification channels as they are used.

+ +

5. Your content

+

You keep whatever rights you have in what you send, share or publish, and you confirm you may share it. You give us permission to store, process and show it as needed to run the Service.

+ +

6. MCP and API use

+

The MCP server at https://app.moshcode.sh/mcp, shared session endpoints and the API are part of the Service and fall under these terms, including the acceptable use rules above. An agent acting with a grant you approved acts on your behalf, and you are responsible for what it does. A grant with write access can type into your terminal session; grant only what you mean to.

+ +

7. Intellectual property

+

All content, code and trademarks associated with moshcode are the property of Profullstack, Inc. or its licensors. moshcode's source code is available under the MIT License, and open-source components are governed by their respective licenses.

+ +

8. Disclaimer of warranties

+

The Service is provided "as is" without warranties of any kind. We do not guarantee that it will be uninterrupted, secure or error-free.

+ +

9. Limitation of liability

+

To the maximum extent permitted by law, Profullstack, Inc. shall not be liable for any indirect, incidental, special, consequential or punitive damages arising out of your use of the Service.

+ +

10. Changes to terms

+

We may update these terms from time to time. Continued use of the Service after a change means you accept the revised terms.

+ +

11. Contact

+

Questions about these terms? Email ${mail}.

`; diff --git a/src/server.mjs b/src/server.mjs index bf734ea..c014ad1 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -4,7 +4,7 @@ import { footerHtml } from '@profullstack/footer'; import { readFileSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { renderHome, renderCommands, renderNotFound, renderLlmsTxt } from './site.mjs'; +import { renderHome, renderCommands, renderNotFound, renderLlmsTxt, renderLegal } from './site.mjs'; import { renderBlogIndex, renderPost, renderFeed } from './blog.mjs'; import { POSTS_BY_SLUG, POSTS_NEWEST_FIRST } from './posts.mjs'; import { SITE, FOOTER_LINKS } from './content.mjs'; @@ -26,6 +26,8 @@ const PAGES = [ { path: '/', changefreq: 'weekly', priority: '1.0' }, { path: '/commands', changefreq: 'weekly', priority: '0.8' }, { path: '/blog', changefreq: 'weekly', priority: '0.8' }, + { path: '/privacy', changefreq: 'yearly', priority: '0.2' }, + { path: '/terms', changefreq: 'yearly', priority: '0.2' }, ]; const PIT_FOOTER_URL = 'https://pit.world/snippets/footer.html?site=moshcode.sh'; @@ -131,6 +133,9 @@ export function createApp({ crawlGate = createCrawlGate() } = {}) { return html(res, renderPost(post)); }); + app.get('/privacy', (_req, res) => html(res, renderLegal('privacy'))); + app.get('/terms', (_req, res) => html(res, renderLegal('terms'))); + app.get(['/install.sh', '/install'], (_req, res) => res.redirect(302, INSTALLER)); app.get('/llms.txt', (_req, res) => diff --git a/src/site.mjs b/src/site.mjs index 95d4ea4..ecf1e85 100644 --- a/src/site.mjs +++ b/src/site.mjs @@ -2,6 +2,7 @@ // framework, no hydration, one stylesheet and ~1KB of JS for the copy buttons. import { SITE, NAV, ENGINES, STATS, SECTIONS } from './content.mjs'; +import { LEGAL_EFFECTIVE, PRIVACY_HTML, TERMS_HTML } from './legal.mjs'; // head/foot/terminal/installBox/esc are exported for src/blog.mjs, which builds // the blog pages out of the same chrome rather than a second set of markup. @@ -45,7 +46,7 @@ function section(s, i) { `; } -export function head({ title, description, path, extraHead = '' }) { +export function head({ title, description, path, extraHead = '', ad = true }) { const canonical = `https://${SITE.domain}${path}`; return ` @@ -88,7 +89,7 @@ ${extraHead}
-${path === '/' ? '' : adUnit()}`; +${path === '/' || !ad ? '' : adUnit()}`; } // One CrawlProof ad unit (slot 71955dcb, https://crawlproof.com/ads/slots) on @@ -269,3 +270,28 @@ ${data.commands.map((c) => `- moshcode ${c.name}${c.aliases.length ? ` (${c.alia Every command has help: moshcode help , or moshcode help --json. `; } + +/** /privacy and /terms: the moshcode product's policy, shared with app.moshcode.sh. No ad. */ +export function renderLegal(kind) { + const privacy = kind === 'privacy'; + const title = privacy ? 'Privacy Policy' : 'Terms of Service'; + const other = privacy ? 'Terms of Service' : 'Privacy Policy'; + return `${head({ + title: `${title} · moshcode`, + description: `${title} for moshcode.sh, app.moshcode.sh and the moshcode MCP server.`, + path: `/${kind}`, + ad: false, + })} +
+
+

legal · Effective date: ${LEGAL_EFFECTIVE} · ${other}

+

${title}

+
+
+
+
+ ${privacy ? PRIVACY_HTML : TERMS_HTML} +
+
+${foot()}`; +} diff --git a/test/site.test.mjs b/test/site.test.mjs index a82285f..9f69e99 100644 --- a/test/site.test.mjs +++ b/test/site.test.mjs @@ -184,3 +184,19 @@ test('home page server-renders the Profullstack webring row', async () => { assert.match(res.body, /rssamplifier\.com\/ring\/profullstack/); assert.doesNotMatch(res.body, //); }); + +test('legal pages render without an ad, and the footer links them', async () => { + for (const [path, heading] of [['/privacy', 'Privacy Policy'], ['/terms', 'Terms of Service']]) { + const res = await get(path); + assert.equal(res.status, 200); + assert.match(res.body, new RegExp(`

${heading}

`)); + assert.match(res.body, new RegExp(`${heading}`)); + assert.match(res.body, /Effective date: November 11, 2026/); + assert.doesNotMatch(res.body, /cp-ad/); + assert.doesNotMatch(res.body.replace(/<footer[\s\S]*$/, ''), /[–—]/); + } + assert.match((await get('/privacy')).body, /app\.moshcode\.sh\/mcp/); + const home = await get('/'); + assert.match(home.body, /href="\/privacy"/); + assert.match(home.body, /href="\/terms"/); +});