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
18 changes: 15 additions & 3 deletions COMMERCIAL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
# Licensing

The libraries in this repository (`@ngaf/langgraph`, `@ngaf/chat`, and all related packages) are released under the **MIT License**. You are free to use, modify, and distribute them in any project — commercial or noncommercial — at no cost. See [`LICENSE`](./LICENSE).
Most libraries in this repository — `@ngaf/render`, `@ngaf/agent`, `@ngaf/langgraph`, `@ngaf/ag-ui`, `@ngaf/a2ui`, `@ngaf/licensing`, `@ngaf/telemetry`, `@ngaf/design-tokens` — are released under the **MIT License**. Free for any use, commercial or noncommercial, with attribution. See [`LICENSE`](./LICENSE).

## `@ngaf/chat`

Starting with the next published version, `@ngaf/chat` is dual-licensed:

- **PolyForm Noncommercial 1.0.0** for free noncommercial use (personal, hobby, student, academic, nonprofit, public demos, OSI-licensed open source, 30-day commercial evaluation).
- **Threadplane commercial license** for commercial production use.

Historical MIT releases of `@ngaf/chat` remain under their original terms.

See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/LICENSE-COMMERCIAL.md`](./libs/chat/LICENSE-COMMERCIAL.md), and [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md) for the full terms.

## Minting Service

The ThreadPlane minting service (`apps/minting-service/`) is a proprietary internal service and is not covered by the MIT License. See `apps/minting-service/LICENSE` for its terms.

## Questions

- Website: https://threadplane.ai
- Email: hello@cacheplane.ai
- Website: <https://threadplane.ai>
- Pricing: <https://threadplane.ai/pricing>
- Sales: <https://threadplane.ai/contact>
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
<a href="https://www.npmjs.com/package/@ngaf/langgraph">
<img alt="npm version" src="https://img.shields.io/npm/v/@ngaf%2Flanggraph?color=6C8EFF&labelColor=080B14&style=flat-square" />
</a>
<a href="./LICENSE">
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-6C8EFF?labelColor=080B14&style=flat-square" />
</a>
<a href="https://angular.dev">
<img alt="Angular 20+" src="https://img.shields.io/badge/Angular-20%2B-6C8EFF?labelColor=080B14&style=flat-square" />
</a>
Expand Down Expand Up @@ -132,6 +129,6 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them

## License

**MIT** — free for any use. See [`LICENSE`](./LICENSE).
Most libraries in this repository (`@ngaf/render`, `@ngaf/agent`, `@ngaf/langgraph`, `@ngaf/ag-ui`, `@ngaf/a2ui`, `@ngaf/licensing`, `@ngaf/telemetry`, `@ngaf/design-tokens`) are released under the **MIT License** — free for any use, including commercial, with attribution.

`@ngaf/langgraph` and all libraries in this repository are released under the [MIT License](./LICENSE). You are free to use, modify, and distribute them in both commercial and noncommercial projects without restriction.
**`@ngaf/chat`** is the exception. Future versions are licensed under **PolyForm Noncommercial 1.0.0 OR a Threadplane commercial license**. Historical npm releases remain MIT. See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md), and [`COMMERCIAL.md`](./COMMERCIAL.md) for details.
58 changes: 54 additions & 4 deletions apps/website/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,43 @@ import { Eyebrow } from '../../components/ui/Eyebrow';
import { PricingGrid } from '../../components/pricing/PricingGrid';
import { CompareTable } from '../../components/pricing/CompareTable';
import { CompatibilityMatrix } from '../../components/pricing/CompatibilityMatrix';
import { PricingFAQ } from '../../components/pricing/PricingFAQ';
import { LeadForm } from '../../components/pricing/LeadForm';
import { FinalCTA } from '../../components/landing/FinalCTA';
import { createPageMetadata } from '../../lib/site-metadata';

export const metadata = createPageMetadata({
title: 'Pricing — Agent UI for Angular',
description: 'Simple, transparent pricing. MIT-licensed libraries are free forever. Enterprise contracts available.',
description:
'@ngaf/chat is free for noncommercial use under PolyForm Noncommercial 1.0.0. Commercial production use requires a Threadplane license. Other libraries remain MIT.',
pathname: '/pricing',
type: 'website',
});

function SmallNote({ children }: { children: React.ReactNode }) {
return (
<p
style={{
fontFamily: tokens.typography.body.family,
fontSize: 13,
lineHeight: 1.6,
color: tokens.colors.textMuted,
textAlign: 'center',
margin: '0 auto',
maxWidth: 720,
}}
>
{children}
</p>
);
}

export default function PricingPage() {
return (
<>
<Section surface="canvas" ariaLabelledBy="pricing-heading">
<Container>
<div style={{ textAlign: 'center', maxWidth: 720, margin: '0 auto' }}>
<div style={{ textAlign: 'center', maxWidth: 760, margin: '0 auto' }}>
<Eyebrow tone="accent" style={{ marginBottom: 16 }}>Pricing</Eyebrow>
<h1
id="pricing-heading"
Expand All @@ -36,7 +56,7 @@ export default function PricingPage() {
letterSpacing: '-0.02em',
}}
>
Simple, transparent pricing.
Pricing for production AI chat interfaces
</h1>
<p
style={{
Expand All @@ -47,13 +67,32 @@ export default function PricingPage() {
margin: 0,
}}
>
MIT-licensed libraries are free forever. Enterprise contracts available for teams that want priority support and an SLA.
<code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/chat</code> is free for noncommercial use. Commercial production use requires a Threadplane license. Other libraries in the framework remain MIT.
</p>
</div>
</Container>
</Section>

<PricingGrid />

<Section surface="canvas">
<Container>
<SmallNote>
A license is required when <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/chat</code> is used in a commercial product, SaaS app, internal business tool, paid client project, or production application operated by or for a for-profit entity.
</SmallNote>
</Container>
</Section>

<CompareTable />

<Section surface="canvas">
<Container>
<SmallNote>
Commercial evaluation is free for 30 days. A paid license is required before production deployment.
</SmallNote>
</Container>
</Section>

<Section surface="canvas">
<Container>
<Eyebrow style={{ marginBottom: 12 }}>Compatibility</Eyebrow>
Expand Down Expand Up @@ -81,6 +120,17 @@ export default function PricingPage() {
<CompatibilityMatrix />
</Container>
</Section>

<PricingFAQ />

<Section surface="canvas">
<Container>
<SmallNote>
Because commercial use requires a license, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/chat</code> is source-available rather than OSI open source. Threadplane keeps ecosystem packages (<code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/render</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/agent</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/langgraph</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/ag-ui</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/a2ui</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/licensing</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/telemetry</code>, <code style={{ fontFamily: tokens.typography.fontMono }}>@ngaf/design-tokens</code>) permissively MIT-licensed.
</SmallNote>
</Container>
</Section>

<LeadForm />
<FinalCTA />
</>
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/components/landing/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function Hero() {
{/* Left column */}
<div>
<Eyebrow tone="accent" style={{ marginBottom: 16 }}>
Agent UI for Angular · MIT
Agent UI for Angular · MIT framework
</Eyebrow>
<h1
id="hero-heading"
Expand Down
121 changes: 96 additions & 25 deletions apps/website/src/components/pricing/CompareTable.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,102 @@
'use client';
import { tokens } from '@ngaf/design-tokens';

const ROWS = [
{ feature: 'npm install', oss: true, seat: true, app: true, enterprise: true },
{ feature: 'Commercial use', oss: false, seat: true, app: true, enterprise: true },
{ feature: 'See compatibility matrix below', oss: true, seat: true, app: true, enterprise: true },
{ feature: 'Email support', oss: false, seat: true, app: true, enterprise: true },
{ feature: 'Source access', oss: true, seat: true, app: true, enterprise: true },
{ feature: 'Per-app deployment', oss: false, seat: false, app: true, enterprise: true },
{ feature: 'Volume licensing', oss: false, seat: false, app: false, enterprise: true },
{ feature: 'Priority support', oss: false, seat: false, app: false, enterprise: true },
type TierKey = 'community' | 'indie' | 'seat' | 'app' | 'enterprise';

interface Row {
feature: string;
cells: Record<TierKey, boolean | string>;
}

const TIERS: { key: TierKey; label: string }[] = [
{ key: 'community', label: 'Community' },
{ key: 'indie', label: 'Indie' },
{ key: 'seat', label: 'Developer Seat' },
{ key: 'app', label: 'App Deployment' },
{ key: 'enterprise', label: 'Enterprise' },
];

const ROWS: Row[] = [
{
feature: 'License model',
cells: {
community: 'PolyForm NC 1.0.0',
indie: 'Commercial',
seat: 'Commercial',
app: 'Commercial',
enterprise: 'Commercial + custom',
},
},
{
feature: 'Commercial production use',
cells: { community: false, indie: true, seat: true, app: true, enterprise: true },
},
{
feature: 'Developers',
cells: { community: 'Unlimited (noncommercial)', indie: '1', seat: 'Per seat', app: 'Unlimited', enterprise: 'Unlimited' },
},
{
feature: 'Apps covered',
cells: { community: 'Unlimited (noncommercial)', indie: '1', seat: 'All apps owned by your org', app: '1', enterprise: 'Multi-app' },
},
{
feature: 'End users',
cells: { community: 'Unlimited', indie: 'Unlimited', seat: 'Unlimited', app: 'Unlimited', enterprise: 'Unlimited' },
},
{
feature: 'Environments (dev / staging / prod)',
cells: { community: false, indie: true, seat: true, app: true, enterprise: true },
},
{
feature: 'Support',
cells: { community: 'Community', indie: 'Email', seat: 'Email', app: 'Email', enterprise: 'Priority + private channel' },
},
{
feature: 'SLA',
cells: { community: false, indie: false, seat: false, app: false, enterprise: true },
},
{
feature: 'Security review',
cells: { community: false, indie: false, seat: false, app: false, enterprise: true },
},
];

const Check = () => <span style={{ color: tokens.colors.accent }}>✓</span>;
const X = () => <span style={{ color: tokens.colors.textMuted }}>—</span>;

function renderCell(value: boolean | string): React.ReactNode {
if (typeof value === 'boolean') return value ? <Check /> : <X />;
return <span style={{ color: tokens.colors.textSecondary, fontSize: 13 }}>{value}</span>;
}

export function CompareTable() {
return (
<section className="px-8 py-8 max-w-6xl mx-auto overflow-x-auto">
<div style={{
background: tokens.surfaces.surface,
border: `1px solid ${tokens.surfaces.border}`,
borderRadius: 12,
overflow: 'hidden',
}}>
<table className="w-full text-sm" style={{ borderCollapse: 'collapse' }}>
<div
style={{
background: tokens.surfaces.surface,
border: `1px solid ${tokens.surfaces.border}`,
borderRadius: 12,
overflow: 'hidden',
}}
>
<table className="w-full text-sm" style={{ borderCollapse: 'collapse', minWidth: 760 }}>
<thead>
<tr style={{ borderBottom: `1px solid ${tokens.colors.accentBorder}`, background: 'rgba(255,255,255,0.55)' }}>
<th className="text-left py-3 px-4 font-mono text-xs uppercase" style={{ color: tokens.colors.textMuted }}>Feature</th>
{['Community', 'Developer Seat', 'App Deployment', 'Enterprise'].map((h) => (
<th key={h} className="text-center py-3 px-4 font-mono text-xs uppercase" style={{ color: tokens.colors.accent }}>{h}</th>
<th
className="text-left py-3 px-4 font-mono text-xs uppercase"
style={{ color: tokens.colors.textMuted }}
>
Feature
</th>
{TIERS.map((t) => (
<th
key={t.key}
className="text-center py-3 px-4 font-mono text-xs uppercase"
style={{ color: tokens.colors.accent }}
>
{t.label}
</th>
))}
</tr>
</thead>
Expand All @@ -39,12 +106,16 @@ export function CompareTable() {
key={row.feature}
style={{ borderBottom: `1px solid ${tokens.colors.accentBorder}` }}
onMouseEnter={(e) => (e.currentTarget.style.background = tokens.colors.accentSurface)}
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
<td className="py-3 px-4" style={{ color: tokens.colors.textSecondary }}>{row.feature}</td>
<td className="py-3 px-4 text-center">{row.oss ? <Check /> : <X />}</td>
<td className="py-3 px-4 text-center">{row.seat ? <Check /> : <X />}</td>
<td className="py-3 px-4 text-center">{row.app ? <Check /> : <X />}</td>
<td className="py-3 px-4 text-center">{row.enterprise ? <Check /> : <X />}</td>
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
>
<td className="py-3 px-4" style={{ color: tokens.colors.textSecondary }}>
{row.feature}
</td>
{TIERS.map((t) => (
<td key={t.key} className="py-3 px-4 text-center">
{renderCell(row.cells[t.key])}
</td>
))}
</tr>
))}
</tbody>
Expand Down
54 changes: 54 additions & 0 deletions apps/website/src/components/pricing/PricingFAQ.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @vitest-environment jsdom
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { PricingFAQ } from './PricingFAQ';

vi.mock('../ui/Container', () => ({
Container: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock('../ui/Section', () => ({
Section: ({ children }: { children: React.ReactNode }) => <section>{children}</section>,
}));
vi.mock('../ui/Eyebrow', () => ({
Eyebrow: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));

const EXPECTED_QUESTIONS = [
'Is @ngaf/chat open source?',
'Can I use it for free?',
'Can I use it at work?',
'Do my end users need licenses?',
'Can I modify the source?',
'Can I redistribute it?',
'What happens to older MIT versions?',
];

describe('PricingFAQ', () => {
it('renders the FAQ heading', () => {
render(<PricingFAQ />);
expect(
screen.getByRole('heading', { level: 2, name: 'Licensing FAQ' }),
).toBeTruthy();
});

it('renders all 7 questions as <summary> elements inside <details>', () => {
const { container } = render(<PricingFAQ />);
const summaries = container.querySelectorAll('details > summary');
expect(summaries.length).toBe(7);
const texts = Array.from(summaries, (s) => s.textContent);
expect(texts).toEqual(EXPECTED_QUESTIONS);
});

it('exposes an #faq anchor for footer deep-linking', () => {
const { container } = render(<PricingFAQ />);
expect(container.querySelector('#faq')).toBeTruthy();
});

it('renders the open-source clarification answer', () => {
render(<PricingFAQ />);
expect(
screen.getByText(/source-available under the PolyForm Noncommercial License 1\.0\.0/i),
).toBeTruthy();
});
});
Loading