Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
8 changes: 3 additions & 5 deletions COMMERCIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ Most libraries in this repository — `@ngaf/render`, `@ngaf/agent`, `@ngaf/lang

## `@ngaf/chat`

Starting with the next published version, `@ngaf/chat` is dual-licensed:
`@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.
- **PolyForm Noncommercial 1.0.0** for free noncommercial use (personal, hobby, student, academic, nonprofit, public demos, OSI-licensed open source, 30 calendar days of commercial evaluation from first commercial use).
- **ThreadPlane Commercial license** for commercial production use. Sold via [threadplane.ai/pricing](https://threadplane.ai/pricing); see [/docs/licensing](https://threadplane.ai/docs/licensing) for installation.

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.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,4 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them

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/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.
**`@ngaf/chat`** is the exception. It is dual-licensed under **PolyForm Noncommercial 1.0.0** for free noncommercial use, or a **ThreadPlane Commercial license** for production use inside a for-profit context. See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md), [`COMMERCIAL.md`](./COMMERCIAL.md), and [threadplane.ai/docs/licensing](https://threadplane.ai/docs/licensing) for details.
2 changes: 2 additions & 0 deletions apps/minting-service/handlers/stripe-webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
deleteProcessedEvent,
upsertLicense,
getLicense,
getLicensesByCustomerId,
revokeLicense,
} from '@ngaf/db';
import { loadEnv } from '../src/lib/env.js';
Expand Down Expand Up @@ -58,6 +59,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse):
deleteProcessedEvent,
upsertLicense,
getLicense,
getLicensesByCustomerId,
revokeLicense,
mintToken,
sendLicenseEmail,
Expand Down
4 changes: 2 additions & 2 deletions apps/minting-service/scripts/remint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function runRemint(args: RemintArgs, deps: RemintDeps): Promise<Rem
token = await deps.mintToken(
{
stripeCustomerId: license.stripeCustomerId,
tier: license.tier as 'indie' | 'developer_seat' | 'app_deployment',
tier: license.tier as 'developer_seat' | 'team',
seats: license.seats,
expiresAt: license.expiresAt,
},
Expand All @@ -68,7 +68,7 @@ export async function runRemint(args: RemintArgs, deps: RemintDeps): Promise<Rem

const to = args.to ?? license.customerEmail;
const vars = {
tier: license.tier as 'indie' | 'developer_seat' | 'app_deployment',
tier: license.tier as 'developer_seat' | 'team',
seats: license.seats,
token,
expiresAt: license.expiresAt,
Expand Down
10 changes: 5 additions & 5 deletions apps/minting-service/src/lib/email.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ describe('renderLicenseEmail', () => {
expiresAt: new Date('2027-04-20T00:00:00Z'),
});

expect(out.text).toContain('-----BEGIN CACHEPLANE LICENSE-----');
expect(out.text).toContain('-----BEGIN THREADPLANE LICENSE-----');
expect(out.text).toContain('PAYLOAD.SIG');
expect(out.text).toContain('-----END CACHEPLANE LICENSE-----');
expect(out.text).toContain('-----END THREADPLANE LICENSE-----');
});

it('subject includes tier and seat count with plural s for seats > 1', () => {
Expand All @@ -27,12 +27,12 @@ describe('renderLicenseEmail', () => {

it('subject uses singular seat for seats === 1', () => {
const out = renderLicenseEmail({
tier: 'app_deployment',
tier: 'team',
seats: 1,
token: 't.s',
expiresAt: new Date('2027-04-20T00:00:00Z'),
});
expect(out.subject).toBe('Your ThreadPlane license — app_deployment (1 seat)');
expect(out.subject).toBe('Your ThreadPlane license — team (1 seat)');
});

it('includes ISO 8601 UTC expiry in text body', () => {
Expand All @@ -54,6 +54,6 @@ describe('renderLicenseEmail', () => {
});
expect(out.html).toContain('<pre');
expect(out.html).toContain('PAYLOAD.SIG');
expect(out.html).toContain('BEGIN CACHEPLANE LICENSE');
expect(out.html).toContain('BEGIN THREADPLANE LICENSE');
});
});
34 changes: 21 additions & 13 deletions apps/minting-service/src/lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,49 @@ export function renderLicenseEmail(vars: LicenseEmailVars): RenderedEmail {
const subject = `Your ThreadPlane license — ${vars.tier} (${vars.seats} ${seatWord})`;
const expiresIso = vars.expiresAt.toISOString();

const text = `Thanks for subscribing to ThreadPlane.
const text = `Thanks for your ThreadPlane license purchase.

Your license token is below. Set it as the CACHEPLANE_LICENSE
environment variable in your application:
Your license is valid for 12 months from today. Paste the token below
into your @ngaf/chat configuration:

-----BEGIN CACHEPLANE LICENSE-----
-----BEGIN THREADPLANE LICENSE-----
${vars.token}
-----END CACHEPLANE LICENSE-----
-----END THREADPLANE LICENSE-----

Tier: ${vars.tier}
Seats: ${vars.seats}
Expires: ${expiresIso}

Installation:
export CACHEPLANE_LICENSE="<paste token above>"
// application bootstrap
provideChat({
license: process.env['THREADPLANE_LICENSE'],
});

Or in a .env file:
CACHEPLANE_LICENSE=<paste token above>
// .env
THREADPLANE_LICENSE=<paste token above>

Docs: https://threadplane.ai/docs/licensing
Questions: reply to this email.

-- The ThreadPlane team
`;

const html = `<p>Thanks for subscribing to ThreadPlane.</p>
<p>Your license token is below. Set it as the <code>CACHEPLANE_LICENSE</code> environment variable in your application:</p>
<pre style="white-space:pre-wrap;word-break:break-all;font-family:monospace;font-size:12px;background:#f4f4f4;padding:12px;border-radius:4px">-----BEGIN CACHEPLANE LICENSE-----
const html = `<p>Thanks for your ThreadPlane license purchase.</p>
<p>Your license is valid for 12 months from today. Paste the token below into your <code>@ngaf/chat</code> configuration:</p>
<pre style="white-space:pre-wrap;word-break:break-all;font-family:monospace;font-size:12px;background:#f4f4f4;padding:12px;border-radius:4px">-----BEGIN THREADPLANE LICENSE-----
${escapeHtml(vars.token)}
-----END CACHEPLANE LICENSE-----</pre>
-----END THREADPLANE LICENSE-----</pre>
<p><strong>Tier:</strong> ${escapeHtml(vars.tier)}<br>
<strong>Seats:</strong> ${vars.seats}<br>
<strong>Expires:</strong> ${escapeHtml(expiresIso)}</p>
<p><strong>Installation:</strong></p>
<pre style="font-family:monospace;font-size:12px;background:#f4f4f4;padding:12px;border-radius:4px">export CACHEPLANE_LICENSE="&lt;paste token above&gt;"</pre>
<pre style="font-family:monospace;font-size:12px;background:#f4f4f4;padding:12px;border-radius:4px">provideChat({
license: process.env['THREADPLANE_LICENSE'],
});

// .env
THREADPLANE_LICENSE=&lt;paste token above&gt;</pre>
<p>Docs: <a href="https://threadplane.ai/docs/licensing">threadplane.ai/docs/licensing</a><br>
Questions: reply to this email.</p>
<p>-- The ThreadPlane team</p>
Expand Down
Loading