AkurAI Build
Menu

Public repository / e406beb5c9be1dd7717ab29cd67017503a0f0a49

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);
  }
}