Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
];
96 changes: 96 additions & 0 deletions src/legal.mjs
Original file line number Diff line number Diff line change
@@ -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 = `<a href="mailto:${CONTACT}">${CONTACT}</a>`;

export const PRIVACY_HTML = `
<p>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.</p>

<h2>The CLI runs on your machine</h2>
<p>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.</p>

<h2>What we collect</h2>
<p><b>Account.</b> 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.</p>
<p><b>Approvals.</b> When a script asks for one, we store its message, the context the script sent, the script name, and your reply.</p>
<p><b>Notification channels.</b> 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.</p>
<p><b>Live sessions and the herd.</b> 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.</p>
<p><b>Teams.</b> Organizations, teams and members you create, and the sessions you share with them.</p>
<p><b>Settings sync.</b> The settings snapshots you save from the pit with <code>/save</code>; you can delete them from your settings.</p>
<p><b>Credits and payments.</b> 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.</p>
<p><b>Moshpit names.</b> 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.</p>

<h2>Cookies and local storage</h2>
<p>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.</p>

<h2>Who else is involved</h2>
<ul>
<li><b>CoinPay</b> (coinpayportal.com): credit and name purchases, and the optional Sign in with CoinPay. On moshcode.sh, crawlers that buy a day pass pay through CoinPay or x402.</li>
<li><b>Resend</b>: delivers approval, invite and Moshpit offer email, so it processes your email address.</li>
<li><b>Slack and Telegram</b>: approvals sent to the webhook or chat you configured; their own policies apply.</li>
<li><b>Your browser's push service</b>: delivers push notifications.</li>
<li><b>Forward Email</b>: forwards mail sent to a Moshpit name's contact address, if you turned one on.</li>
<li><b>CrawlProof</b> (crawlproof.com): a stats script for aggregate page analytics on moshcode.sh and on the public pit pages; and on moshcode.sh's inner pages, one small text ad served in a frame from crawlproof.com. The signed-in app is not counted.</li>
<li><b>jsDelivr</b>: serves the WebMCP script on moshcode.sh.</li>
</ul>
<p>Nothing is sold, and no advertising profile is built from you.</p>

<h2>What is public</h2>
<p>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.</p>

<h2>AI agents and MCP</h2>
<p>The moshcode MCP server is at <code>https://app.moshcode.sh/mcp</code> (JSON-RPC 2.0, streamable HTTP), and a single shared session is reachable at <code>https://moshcode.sh/api/v1/mcp/&lt;share-id&gt;</code>. 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.</p>
<p>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.</p>

<h2>Keeping and deleting data</h2>
<p>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.</p>

<h2>Your rights</h2>
<p>Depending on where you live, you may have the right to access, correct, delete or export your personal data. Contact us to exercise them.</p>

<h2>Children</h2>
<p>moshcode is a developer tool and is not directed at children under 13, and we do not knowingly collect personal information from them.</p>

<h2>Changes</h2>
<p>If the service starts doing something different, this page changes with it. Material changes will be announced on this page.</p>

<h2>Contact</h2>
<p>${OPERATOR}. Email ${mail}.</p>`;

export const TERMS_HTML = `
<h2>1. Acceptance of terms</h2>
<p>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}.</p>

<h2>2. Use of the Service</h2>
<p>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.</p>

<h2>3. Accounts</h2>
<p>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.</p>

<h2>4. Payments</h2>
<p>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.</p>

<h2>5. Your content</h2>
<p>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.</p>

<h2>6. MCP and API use</h2>
<p>The MCP server at <code>https://app.moshcode.sh/mcp</code>, 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.</p>

<h2>7. Intellectual property</h2>
<p>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.</p>

<h2>8. Disclaimer of warranties</h2>
<p>The Service is provided "as is" without warranties of any kind. We do not guarantee that it will be uninterrupted, secure or error-free.</p>

<h2>9. Limitation of liability</h2>
<p>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.</p>

<h2>10. Changes to terms</h2>
<p>We may update these terms from time to time. Continued use of the Service after a change means you accept the revised terms.</p>

<h2>11. Contact</h2>
<p>Questions about these terms? Email ${mail}.</p>`;
7 changes: 6 additions & 1 deletion src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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) =>
Expand Down
30 changes: 28 additions & 2 deletions src/site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -45,7 +46,7 @@ function section(s, i) {
</section>`;
}

export function head({ title, description, path, extraHead = '' }) {
export function head({ title, description, path, extraHead = '', ad = true }) {
const canonical = `https://${SITE.domain}${path}`;
return `<!doctype html>
<html lang="en">
Expand Down Expand Up @@ -88,7 +89,7 @@ ${extraHead}
</div>
</header>
<main id="main">
${path === '/' ? '' : adUnit()}`;
${path === '/' || !ad ? '' : adUnit()}`;
}

// One CrawlProof ad unit (slot 71955dcb, https://crawlproof.com/ads/slots) on
Expand Down Expand Up @@ -269,3 +270,28 @@ ${data.commands.map((c) => `- moshcode ${c.name}${c.aliases.length ? ` (${c.alia
Every command has help: moshcode help <command>, 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 ? '<a href="/terms">Terms of Service</a>' : '<a href="/privacy">Privacy Policy</a>';
return `${head({
title: `${title} · moshcode`,
description: `${title} for moshcode.sh, app.moshcode.sh and the moshcode MCP server.`,
path: `/${kind}`,
ad: false,
})}
<section class="page-head">
<div class="wrap">
<p class="kicker">legal · Effective date: ${LEGAL_EFFECTIVE} · ${other}</p>
<h1>${title}</h1>
</div>
</section>
<section class="band">
<div class="wrap prose">
${privacy ? PRIVACY_HTML : TERMS_HTML}
</div>
</section>
${foot()}`;
}
16 changes: 16 additions & 0 deletions test/site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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, /<!--pfs-footer-->/);
});

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(`<h1>${heading}</h1>`));
assert.match(res.body, new RegExp(`<title>${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"/);
});
Loading