feat(license): add Lite SKU; remove user-facing free trial
Two coupled changes:
1. Lite tier
- New Tier.LITE in src/license/schema.py.
- FEATURES_BY_TIER[Tier.LITE] = {Deduplicator, Text Cleaner,
Format Standardizer}. The three universally-useful tools that
cover the most common bookkeeping / RevOps / Klaviyo prep
workflows. Other six tools require Core.
- i18n: license.tier_lite, license.feature_locked_title,
license.feature_locked_body, license.upgrade_link,
license.status_locked (en + es).
- Per-tool feature gate at every GUI tool page
(require_feature_or_render_upgrade) and every tool CLI
(guard(feature=...)). A locked tool renders an upgrade
prompt + Manage-license button (GUI) or exits with code 2
(CLI).
- Home grid: tool cards the user's tier doesn't unlock get a
red 🔒 Locked badge in place of green Ready.
2. Trial removed
- Activation form's "Start 1-year trial" button removed.
- license_cli's `trial` subcommand removed.
- activation.trial_button / activation.trial_help i18n keys
dropped (pack parity test stays green).
- Tier.TRIAL stays in the enum (back-compat with any field-
tested trial licenses); LicenseManager._mint stays internal
for tests and the seller's key generator.
- Decision logged in DECISIONS §9b: a 1-year all-features
trial undercuts paid Lite; paid-only keeps tier economics
clean.
Tests (+29 net): +17 Lite-tier unit/guard tests + 13 Lite-tier
GUI tests + 1 trial-absent assertion - 2 trial CLI tests - 1
trial GUI button test. Total: 1995 → 2024.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""CLI for license management.
|
||||
|
||||
Five commands:
|
||||
Four commands:
|
||||
|
||||
- ``activate BLOB --name NAME --email EMAIL``
|
||||
First-time activation. Verifies the signed blob, ensures the
|
||||
@@ -9,23 +9,23 @@ Five commands:
|
||||
- ``renew BLOB``
|
||||
Apply a renewal blob to the currently-active license. The blob's
|
||||
embedded name + email must match the active license; tier may
|
||||
differ (upgrade path).
|
||||
differ (upgrade path, e.g., Lite → Core).
|
||||
|
||||
- ``status [--json]``
|
||||
Print the current license state. Human-readable by default;
|
||||
``--json`` emits the same payload as a JSON document for piping
|
||||
into shell scripts / monitoring.
|
||||
|
||||
- ``trial --name NAME --email EMAIL [--years N]``
|
||||
Self-issue a trial license without a paid blob. Useful for
|
||||
evaluating the product or for support to repro a buyer's issue
|
||||
locally without needing a real key.
|
||||
|
||||
- ``deactivate``
|
||||
Remove the local license file.
|
||||
|
||||
This CLI is exempt from the guard that protects every tool CLI —
|
||||
otherwise a user with no license couldn't run ``activate``.
|
||||
|
||||
There is **no free-trial subcommand**: every license requires a paid
|
||||
blob from the seller. The internal ``LicenseManager._mint`` API is
|
||||
used by tests and by the seller's ``scripts/generate_license.py``;
|
||||
end users have no way to self-issue a license.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -134,25 +134,6 @@ def status(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def trial(
|
||||
name: str = typer.Option(..., "--name", "-n"),
|
||||
email: str = typer.Option(..., "--email", "-e"),
|
||||
years: int = typer.Option(1, "--years", help="Trial length (default: 1 year)."),
|
||||
) -> None:
|
||||
"""Self-issue a trial license without a paid blob."""
|
||||
mgr = get_manager()
|
||||
try:
|
||||
lic = mgr.issue_trial(name=name, email=email, years=years)
|
||||
except LicenseError as e:
|
||||
typer.echo(f"Trial issuance failed: {e}", err=True)
|
||||
raise typer.Exit(code=2)
|
||||
typer.echo(
|
||||
f"Trial issued. Key: {lic.license_key} · "
|
||||
f"Expires: {lic.expires_at[:10]} ({lic.days_remaining()} days)"
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def deactivate(
|
||||
confirm: bool = typer.Option(
|
||||
|
||||
Reference in New Issue
Block a user