npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

legal-terms

v0.1.40

Published

Configurable Terms of Service and Privacy Policy — a scannable summary and the full legal text, with every section addable, removable and reorderable. React page, plus Markdown/HTML/text renderers and a CLI.

Readme

🤖 Agent skillnpx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent --skill legal-terms-privacy-policy (what it covers)

legal-terms-privacy-policy

One combined Terms of Service + Privacy Policy, in two presentations of the same policy, with every section addable, removable and reorderable from config.

  • Summary — the scannable card-and-icon layout published at rights.institute/terms-privacy.
  • Full text — the long-form legal document published by QwkSearch, Debate AI and AI Broker.

Ship both and let readers switch between them, or pin a page to one. Nothing in the package needs editing to adopt it: the product name, contact address, dates, which clauses appear and in what order all come from props.

Not legal advice. This is boilerplate to start from — have a lawyer review the text you publish.

Install

npm install legal-terms-privacy-policy

The package ships TypeScript source rather than a build output, matching the other packages in this monorepo. In a Next.js app, add it to transpilePackages:

// next.config.js
export default { transpilePackages: ['legal-terms-privacy-policy'] };

react and lucide-react are optional peers — needed only for the React page, not for the Markdown/HTML/text renderers or the CLI.

React page

import { LegalTermsPrivacyPolicy } from 'legal-terms-privacy-policy/react';

export default function TermsPage() {
  return (
    <LegalTermsPrivacyPolicy
      appName="QwkSearch"
      contactEmail="[email protected]"
      lastRevisedDate="March 1, 2026"
      defaultVariant="full"
    />
  );
}

The component renders the whole page — back link, title, badges, revision date, the summary ⇄ full-text switch, section navigation and the body. It is Tailwind-styled and works in light and dark.

Controlling the variant

Uncontrolled, the switch keeps its own state starting from defaultVariant. Pass variant and onVariantChange to drive it from the URL instead:

'use client';
const [variant, setVariant] = useState<Variant>('summary');
<LegalTermsPrivacyPolicy variant={variant} onVariantChange={setVariant} appName="Acme" />;

To publish only one presentation, set variant and turn the switch off:

<LegalTermsPrivacyPolicy appName="Acme" variant="full" features={{ variantSwitch: false }} />

Configuration

Every option below works the same way in the React component, the renderers and the CLI.

Values substituted into the text

The legal text carries {{token}} placeholders. Unknown tokens are left visible rather than blanked, so a typo shows up on the page instead of silently deleting a clause.

| Option | Default | Appears in | | --- | --- | --- | | appName | "Our Service" | Throughout | | companyName | same as appName | Liability, contact, footer | | contactEmail | "[email protected]" | Accounts, retention, contact | | homeUrl | "/" | "Back to Home" link | | lastRevisedDate | "January 1, 2025" | Under the title | | effectiveDate | same as lastRevisedDate | Under the title | | jurisdiction | "the United States" | Introduction | | minimumAge | 18 | Acceptance of Terms | | childrenAge | 13 | Children's Privacy | | dataDeletionDays | 30 | Data Security and Retention | | tokens | {} | Extra placeholders for your own sections |

Adding and removing parts

parts switches named groups of clauses on or off in one go:

<LegalTermsPrivacyPolicy
  appName="Grab URL"
  parts={{ ai: false, california: false }}   // no model in the loop, no CA notice
/>

| Part | Covers | | --- | --- | | core | Introduction, changes, accounts, use, materials, feedback, warranties, termination, contact. Always on — switching it off is ignored. | | ai | The Artificial Intelligence Ethical Use Policy and AI-specific clauses | | privacy | Collection, use, disclosure and retention of personal data | | cookies | Cookies, tracking technologies and Do Not Track | | california | The CCPA/CPRA resident notice | | children | The COPPA under-13 notice | | security | Security measures and data retention | | thirdParty | Third-party links and social features |

For finer control, work by section id — include keeps only what you name, exclude drops it, and both reach subsections:

exclude={['social-features', 'california-selling']}
include={['introduction', 'privacy-policy', 'contact']}

Naming a parent in include keeps its whole subtree (['ai-ethics'] is the section and its four sub-policies); naming only a child keeps the parent as its heading (['california-rights'] renders under "California Residents"). exclude still applies inside an included parent.

Run npx legal-terms-privacy-policy --list-sections (add --variant summary) to see every id.

Rewriting and adding sections

replace patches a section by id, merging over the built-in one — pass only the fields you are changing:

replace={{
  contact: { blocks: [{ type: 'p', text: 'Write to {{companyName}}, 1 Main St.' }] },
  'california-selling': { title: 'We Do Not Sell Your Data' },
}}

add inserts your own sections, anchored to an existing one:

add={[{
  after: 'termination',
  section: {
    id: 'arbitration',
    title: 'Arbitration and Governing Law',
    icon: 'Scale',
    accent: 'slate',
    blocks: [
      { type: 'p', text: 'Disputes with {{companyName}} are resolved by binding arbitration.' },
      { type: 'ul', lead: 'Exceptions:', items: ['Small claims court', 'Injunctive relief'] },
    ],
  },
}]}

Anchors are after, before or at (an index); with none of them, the section is appended. order puts named sections first, in the order given, and leaves the rest in place.

Block types

Sections hold blocks, so caller-authored content renders in every format:

| Block | Shape | | --- | --- | | p | { type: 'p', text, strong? } | | ol / ul | { type: 'ol', lead?, items } — items are strings or { text, items } for one nested level | | cards | { type: 'cards', columns?: 1-4, center?, items: [{ title, text?, items?, icon? }] } | | note | { type: 'note', title?, text?, items?, icon? } |

Card and note blocks render as tinted tiles in the summary variant and flatten to headings and lists in the full-text variant, so you only write them once. icon names a lucide icon; unknown names fall back to a document glyph, or pass your own components through the icons prop.

Chrome

features toggles the page furniture: backLink, sidebar, tableOfContents, copyButtons, badges, variantSwitch, footer, numbered. badges (the array) sets the compliance pills — ['GDPR Compliant', 'CCPA Compliant', 'Cookie Policy'] by default; pass [] to drop them.

Rendering without React

import { renderMarkdown, renderHtml, renderText, resolveLegalDoc } from 'legal-terms-privacy-policy';

const config = { appName: 'Acme', contactEmail: '[email protected]', parts: { ai: false } };

renderMarkdown(config);                      // TERMS.md, MDX docs page
renderHtml(config, { standalone: true });    // complete styled page
renderText(config);                          // email, CLI, in-app agreement dialog
resolveLegalDoc(config).sections;            // the resolved tree, to render yourself

LEGAL_CSS is exported for pairing with the HTML fragment. Interpolated values are HTML-escaped, and emails and URLs in the text become links.

CLI

npx legal-terms-privacy-policy --app-name Acme --variant full > TERMS.md

npx legal-terms-privacy-policy \
  --app-name Acme --contact-email [email protected] \
  --format html --standalone \
  --no-part california --exclude social-features > terms.html

npx legal-terms-privacy-policy --list-sections --variant summary

--format takes markdown, html, text or json. Run --help for the full list.

Keeping the two variants honest

The summary is a plain-language restatement; the full text is what binds. If you edit one, edit the other — src/content/summary.ts and src/content/full.ts are the two files to keep in step. Publishing a summary whose claims the full text does not support is the failure mode this package is shaped to avoid, which is why the switch is on by default.

Tests

npm test

Covers token substitution, part and id filtering, patching, insertion, ordering, and each renderer — including that neither variant ships an unsubstituted {{token}} and that interpolated values cannot inject markup.

License

PROSPER 1.0.0