Menu
Public repository / f7056749a17a422909a828ea639ae776c4e020dc
AkurAI-Platform / backend/catalog.ts
Latest change d11e160f26a6dfe754f18d1060107de9d6b6b898 - feat: complete managed platform control plane (AKURAI-PLATFORM-10, AKURAI-PLATFORM-11, AKURAI-PLATFORM-12, AKURAI-PLATFORM-13, AKURAI-PLATFORM-14, AKURAI-PLATFORM-16, AKURAI-PLATFORM-18) by Ólafur Búi Ólafsson
/**
* Canonical typed product catalog — single source of truth for the landing page
* suite tiles and subscription checkout. Replaces backend/subscriptions.json.
*
* Every product has a stable id, locale keys for name/kind/description (both
* en.json and is.json must define the key), a public URL or null, a canonical
* AkurAI Build repository name, optional bundle membership, and a fulfillment
* strategy required when purchasable.
*
* Validation:
* - purchasable products MUST have a non-null fulfillment target
* - every nameKey/kindKey/descriptionKey MUST exist in en.json AND is.json
* - no duplicate product ids
* - dev/hidden products are flagged (not rejected — they are legitimate state)
*/
import { readFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
// ── Types ──────────────────────────────────────────────────────────────────
export type ProductVisibility = "visible" | "dev" | "hidden";
export type FulfillmentTarget = "provisioned" | "external" | "manual";
export interface ManagedWorkspacePlan {
price: { monthlyCents: number; currency: "EUR"; annualDiscountPercent: number };
includedSeats: number;
storageGb: number;
limits: { workspaces: number; apps: number; customDomains: number };
supportLevel: "standard";
hosting: "akurai-managed";
isolation: "organization-isolated";
fulfillment: "provisioned";
migration: string;
}
export interface Product {
/** Stable kebab-case identifier, never changes */
id: string;
/** Locale key for display name (e.g. "app.mail.name") */
nameKey: string;
/** Locale key for the kind/tagline (e.g. "app.mail.kind") */
kindKey: string;
/** English display text for the kind/tagline */
kind: string;
/** English display text for the value proposition */
description: string;
/** Locale key for the value proposition (e.g. "app.mail.valueprop") */
descriptionKey: string;
/** Category id matching locale keys (e.g. "communication" → category.communication) */
category: string;
/** User-facing visibility */
visibility: ProductVisibility;
/** Whether this product can be purchased through subscription */
purchasable: boolean;
/** Display name (English, for backward compat with payment article descriptions) */
name: string;
/** Available for purchase (backward compat: visibility === "visible" && purchasable) */
available: boolean;
/** Public app URL (null if not deployed / no web UI) */
url: string | null;
/** Canonical AkurAI Build repository name */
buildRepo: string;
/** Optional second AkurAI Build repository (for multi-repo products like Mail+MailAPI) */
secondaryBuildRepo: string | null;
/** Locale key for the action button label (defaults to "app.action.open" when url is set) */
actionKey: string | null;
/** Product ids included when this one is purchased (bundle) */
bundle: string[];
/** How fulfilment is delivered; required when purchasable is true */
fulfillment: FulfillmentTarget | null;
managedWorkspacePlan?: ManagedWorkspacePlan;
}
export interface Catalog {
currency: "EUR";
monthly_unit_cents: number;
annual_discount_percent: number;
products: Product[];
}
// ── The data ───────────────────────────────────────────────────────────────
export const managedWorkspacePlan: ManagedWorkspacePlan = {
price: {
monthlyCents: Number(Bun.env.MANAGED_WORKSPACE_MONTHLY_CENTS ?? 4900),
currency: "EUR",
annualDiscountPercent: 15,
},
includedSeats: 10,
storageGb: 250,
limits: { workspaces: 1, apps: 4, customDomains: 1 },
supportLevel: "standard",
hosting: "akurai-managed",
isolation: "organization-isolated",
fulfillment: "provisioned",
migration: "AkurAI assesses and executes supported migrations during onboarding; external self-hosting is not a supported deployment path.",
};
export const products: Product[] = [
{
id: "managed-workspace",
nameKey: "plan.managed_workspace.name",
kindKey: "plan.managed_workspace.kind",
descriptionKey: "plan.managed_workspace.description",
kind: "Managed Workspace",
description: "A private managed workspace with identity, Drive, Notes, and Tasks. Includes hosting, updates, verified backups, and standard support.",
category: "business",
visibility: "visible",
purchasable: true,
name: "Managed Workspace",
available: true,
url: "https://platform.olibuijr.com",
buildRepo: "AkurAI-Platform",
secondaryBuildRepo: null,
actionKey: null,
bundle: ["auth", "drive", "notes", "tasks"],
fulfillment: "provisioned",
managedWorkspacePlan,
},
// ── Communication ──
{id: "mail",
nameKey: "app.mail.name",
kindKey: "app.mail.kind",
descriptionKey: "app.mail.valueprop",
kind: "Email",
description: "Private email and a clean inbox for your own domain — no ads, no scanning.",
category: "communication",
visibility: "visible",
purchasable: false,
name: "AkurAI Mail",
available: true,
url: "https://mail.olibuijr.com",
buildRepo: "AkurAI-Mail",
bundle: [], secondaryBuildRepo: "AkurAI-MailAPI",
actionKey: null,
fulfillment: "provisioned",},
// ── Security & privacy ──
{id: "auth",
nameKey: "app.auth.name",
kindKey: "app.auth.kind",
descriptionKey: "app.auth.valueprop",
kind: "Single sign-on",
description: "One secure login for every AkurAI app — accounts you control, not a vendor.",
category: "security",
visibility: "visible",
purchasable: false,
name: "AkurAI Auth",
available: true,
url: "https://auth.olibuijr.com",
buildRepo: "AkurAI-IDP",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "vpn",
nameKey: "app.vpn.name",
kindKey: "app.vpn.kind",
descriptionKey: "app.vpn.valueprop",
kind: "Private network",
description: "A private mesh network of your own — reach your machines anywhere, encrypted end to end.",
category: "security",
visibility: "visible",
purchasable: false,
name: "AkurAI VPN",
available: true,
url: "https://akurai-vpn.olibuijr.com",
buildRepo: "AkurAI-VPN",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "passvault",
nameKey: "app.passvault.name",
kindKey: "app.passvault.kind",
descriptionKey: "app.passvault.valueprop",
kind: "Secret management",
description: "Encrypted secrets you control, with CLI, web and MCP access.",
category: "security",
visibility: "visible",
purchasable: false,
name: "AkurAI PassVault",
available: true,
url: "https://akurai-passvault.olibuijr.com",
buildRepo: "AkurAI-PassVault",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
// ── Storage ──
{id: "drive",
nameKey: "app.drive.name",
kindKey: "app.drive.kind",
descriptionKey: "app.drive.valueprop",
kind: "Cloud storage",
description: "Private cloud storage — store and share files without handing them to anyone.",
category: "storage",
visibility: "visible",
purchasable: false,
name: "AkurAI Drive",
available: true,
url: "https://akurai-drive.olibuijr.com",
buildRepo: "AkurAI-Drive",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
// ── Knowledge ──
{id: "notes",
nameKey: "app.notes.name",
kindKey: "app.notes.kind",
descriptionKey: "app.notes.valueprop",
kind: "Note-taking with semantic search",
description: "Private notes with semantic search. Runtime and data-handling details depend on the deployed workspace configuration.",
category: "knowledge",
visibility: "visible",
purchasable: false,
name: "AkurAI Notes",
available: true,
url: "https://akurai-notes.olibuijr.com",
buildRepo: "AkurAI-Notes",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
// ── Business ──
{id: "crm",
nameKey: "app.crm.name",
kindKey: "app.crm.kind",
descriptionKey: "app.crm.valueprop",
kind: "Customer relationships",
description: "Contacts, deals and follow-ups in one place — a lightweight CRM you own outright.",
category: "business",
visibility: "dev",
purchasable: false,
name: "AkurAI CRM",
available: false,
url: null,
buildRepo: "AkurAI-CRM",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: null,},
{id: "tasks",
nameKey: "app.tasks.name",
kindKey: "app.tasks.kind",
descriptionKey: "app.tasks.valueprop",
kind: "Project management",
description: "Durable task tracking with boards, MCP, CLI and web access.",
category: "business",
visibility: "visible",
purchasable: false,
name: "AkurAI Tasks",
available: true,
url: "https://akurai-tasks.olibuijr.com",
buildRepo: "akurai-tasks",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
// ── Infrastructure ──
{id: "router",
nameKey: "app.router.name",
kindKey: "app.router.kind",
descriptionKey: "app.router.valueprop",
kind: "API gateway",
description: "A single front door for AI and API traffic — routes every request to the right service.",
category: "infrastructure",
visibility: "visible",
purchasable: false,
name: "AkurAI Router",
available: true,
url: "https://akurai-router.olibuijr.com/health",
buildRepo: "AkurAI-Router",
bundle: [], secondaryBuildRepo: null,
actionKey: "app.action.status",
fulfillment: "provisioned",},
{id: "monitor",
nameKey: "app.monitor.name",
kindKey: "app.monitor.kind",
descriptionKey: "app.monitor.valueprop",
kind: "Status & uptime",
description: "Service-status information when a monitored deployment reports it; unavailable or stale status remains visibly unknown.",
category: "infrastructure",
visibility: "visible",
purchasable: false,
name: "AkurAI Monitor",
available: true,
url: "https://monitor.olibuijr.com",
buildRepo: "AkurAI-Monitor",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "dns",
nameKey: "app.dns.name",
kindKey: "app.dns.kind",
descriptionKey: "app.dns.valueprop",
kind: "Domain service",
description: "Authoritative DNS under your own control — your domains answer to you.",
category: "infrastructure",
visibility: "visible",
purchasable: false,
name: "AkurAI DNS",
available: true,
url: null,
buildRepo: "AkurAI-DNS",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "build",
nameKey: "app.build.name",
kindKey: "app.build.kind",
descriptionKey: "app.build.valueprop",
kind: "CI/CD",
description: "The internal build, artifact and deployment pipeline that operates managed workspaces.",
category: "infrastructure",
visibility: "visible",
purchasable: false,
name: "AkurAI Build",
available: true,
url: "https://akurai-build.olibuijr.com",
buildRepo: "AkurAI-Build",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
// ── Developer ──
{id: "framework",
nameKey: "app.framework.name",
kindKey: "app.framework.kind",
descriptionKey: "app.framework.valueprop",
kind: "Web runtime",
description: "The Framework runtime used by managed workspaces. Implementation and dependency details are documented per released artifact.",
category: "developer",
visibility: "visible",
purchasable: false,
name: "AkurAI Framework",
available: true,
url: "https://akurai-framework.olibuijr.com",
buildRepo: "AkurAI-Framework",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "platform",
nameKey: "app.platform.name",
kindKey: "app.platform.kind",
descriptionKey: "app.platform.valueprop",
kind: "App directory",
description: "This front door — every app, its source, and its real footprint in one place.",
category: "developer",
visibility: "visible",
purchasable: false,
name: "AkurAI Platform",
available: true,
url: "https://platform.olibuijr.com",
buildRepo: "AkurAI-Platform",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: "provisioned",},
{id: "gateway",
nameKey: "app.gateway.name",
kindKey: "app.gateway.kind",
descriptionKey: "app.gateway.valueprop",
kind: "Agent Gateway",
description: "MCP gateway for AI agent orchestration and tool routing.",
category: "developer",
visibility: "hidden",
purchasable: false,
name: "Agent Gateway",
available: false,
url: null,
buildRepo: "akurai-gateway",
bundle: [], secondaryBuildRepo: null,
actionKey: null,
fulfillment: null,},
];
export const catalog: Catalog = {
currency: "EUR",
monthly_unit_cents: managedWorkspacePlan.price.monthlyCents,
annual_discount_percent: managedWorkspacePlan.price.annualDiscountPercent,
products,
};
// ── Validation ─────────────────────────────────────────────────────────────
export interface CatalogError {
productId: string;
field: string;
message: string;
}
/**
* Validate catalog integrity at build time:
* 1. No duplicate product ids.
* 2. Purchasable products MUST have a non-null fulfillment target.
* 3. All locale keys (nameKey, kindKey, descriptionKey) exist in
* both en.json and is.json.
* 4. Dev/hidden products are flagged as informational (not errors).
*/
export function validateCatalogIntegrity(localeDir?: string): CatalogError[] {
const errors: CatalogError[] = [];
// Duplicate check
const ids = new Set<string>();
for (const p of products) {
if (ids.has(p.id)) {
errors.push({ productId: p.id, field: "id", message: `Duplicate product id: ${p.id}` });
}
ids.add(p.id);
}
// Purchasable must have fulfillment
for (const p of products) {
if (p.purchasable && !p.fulfillment) {
errors.push({
productId: p.id,
field: "fulfillment",
message: `Purchasable product "${p.id}" must have a fulfillment target`,
});
}
}
const managedWorkspace = products.find((product) => product.id === "managed-workspace");
if (!managedWorkspace?.managedWorkspacePlan || managedWorkspace.managedWorkspacePlan.fulfillment !== managedWorkspace.fulfillment
|| managedWorkspace.managedWorkspacePlan.price.monthlyCents !== catalog.monthly_unit_cents
|| managedWorkspace.managedWorkspacePlan.price.annualDiscountPercent !== catalog.annual_discount_percent) {
errors.push({ productId: "managed-workspace", field: "managedWorkspacePlan", message: "Managed Workspace plan terms must match checkout fulfillment and pricing." });
}
// Visibility flags (informational)
for (const p of products) {
if (p.visibility === "dev") {
errors.push({
productId: p.id,
field: "visibility",
message: `Product "${p.id}" is in development — not yet available`,
});
}
if (p.visibility === "hidden") {
errors.push({
productId: p.id,
field: "visibility",
message: `Product "${p.id}" is hidden from the suite tiles`,
});
}
}
// Locale key validation
const dir = localeDir ?? resolve(dirname(fileURLToPath(import.meta.url)), "locales");
const enPath = resolve(dir, "en.json");
const isPath = resolve(dir, "is.json");
if (!existsSync(enPath) || !existsSync(isPath)) {
errors.push({
productId: "(syste'm)",
field: "locale",
message: `Locale files not found: en.json exists=${existsSync(enPath)}, is.json exists=${existsSync(isPath)}`,
});
return errors;
}
const enKeys = new Set(Object.keys(JSON.parse(readFileSync(enPath, "utf8"))));
const isKeys = new Set(Object.keys(JSON.parse(readFileSync(isPath, "utf8"))));
for (const p of products) {
if (p.visibility === "hidden") continue; // hidden products have no UI, no locale keys needed
for (const [field, key] of [["nameKey", p.nameKey], ["kindKey", p.kindKey], ["descriptionKey", p.descriptionKey]] as const) {
if (!enKeys.has(key)) {
errors.push({
productId: p.id,
field,
message: `Locale key "${key}" missing from en.json`,
});
}
if (!isKeys.has(key)) {
errors.push({
productId: p.id,
field,
message: `Locale key "${key}" missing from is.json`,
});
}
}
}
return errors;
}
// ── CLI: run on `bun run backend/catalog.ts` ──────────────────────────────
// Prints all errors and exits with code 1 if any are found.
if (import.meta.main) {
const localeDir = process.argv[2];
const errors = validateCatalogIntegrity(localeDir);
if (errors.length === 0) {
console.log("✓ Product catalog validation: passed");
process.exit(0);
}
let fail = false;
for (const err of errors) {
const isInfo = err.field === "visibility";
console.log(`${isInfo ? "ℹ" : "✗"} [${err.productId}] ${err.message}`);
if (!isInfo) fail = true;
}
if (fail) {
console.log("\nProduct catalog validation: FAILED — some issues must be fixed");
process.exit(1);
} else {
console.log("\n✓ Product catalog validation: passed (visibility flags are informational)");
process.exit(0);
}
}