Files
zeekay bba4e5ca40 A product page ends on one call: the block above the closing keeps the docs and the neighbours and drops its own Get your API key
Every product page ended on "Start building with X" and a filled Get your
API key, then one screen later on the layout's "Build what's next." and a
filled Try Hanzo, in a different button shape: two closing calls a reader
had to choose between. StartBuilding becomes Related: the product's docs
link and its related capabilities, no heading and no button, so the
closing call below is the one way in. And the open-source row no longer
shows a "0 stars" pill, which read as an abandoned repository.
2026-10-18 16:25:16 -07:00

170 lines
6.8 KiB
TypeScript

/**
* The product a page is about, as one value.
*
* Assembled from what is already stated elsewhere: the catalog names a product
* and states the route that answers for it, this site's page index names the
* pages the catalog does not carry, and `products-metadata` answers for docs —
* `null` there means docs publishes nothing, and the block drops the link.
*
* THE ONE FACT STATED HERE IS THE COMMAND. Every line of `CLI` was run against
* the binary. A product absent from that table has no command, and the page
* prints none; nothing composes a verb out of a product's name.
*
* `Product` is also the prop type of both blocks that draw it. A structural
* prop type accepts any object of the same shape, so a page could write its own
* command inline and typecheck — `AgentPrompt` and `Related` import this
* type instead of declaring their own, which leaves `CLI` the only place a
* command can be written.
*
* Every product page reads this, by whichever handle it has: the templates by
* route (`components/product/ProductLanding`) and by catalog slug
* (`components/cloud/CloudPrimitiveOverview`), a written page by its own slug.
* So one product answers with one name, one route, one command and one docs
* page wherever it is drawn.
*/
import { docsUrl } from '@/lib/constants/products-metadata'
import { getPrimitive } from '@/lib/data/cloud-primitives'
import pages from '@/lib/data/pages.json' with { type: 'json' }
export interface Product {
/**
* The catalog's product slug, which is also the last segment of the page's
* route. It is the key every lookup here uses — `getPrimitive` matches on
* `slug`, and so does the neighbour lookup — so the field says slug rather
* than id, and the stated key and the actual key are one field.
*/
slug: string
name: string
/**
* What the catalog sells this as. A `client` consumes the API rather than
* being one, which is why a missing command means something different there.
*/
kind?: 'service' | 'client'
/** What a reader types first. Absent where the CLI has no command for it. */
cli?: string
/** The API namespace the catalog proves this product by (`/v1/vector`). */
api?: string
/** The product's docs page, or null where docs publishes none. */
docs: string | null
}
/** The audited command per product, alphabetical by slug for diff sanity. */
const CLI: Record<string, string> = {
'agents': 'hanzo agents list',
'ai-metrics': 'hanzo billing usage get',
'alerts': 'hanzo o11y alerts list',
'annotation-queues': 'hanzo o11y reviews list',
'api-keys': 'hanzo iam keys list',
'applications': 'hanzo platform apps list',
'audit': 'hanzo iam audit-logs list',
'authz': 'hanzo authz check',
'auto': 'hanzo auto flows list',
'base': 'hanzo base bases list',
'billing': 'hanzo billing balance',
'bot': 'hanzo bot runs list',
'builds': 'hanzo platform builds',
'chat': 'hanzo chat create',
'cli': 'hanzo auth login',
'clusters': 'hanzo visor clusters list',
'computer': 'hanzo desktop',
'crawl': 'hanzo crawl create',
'dashboards': 'hanzo o11y dashboards list',
'datasets': 'hanzo eval datasets list',
'datastore': 'hanzo provisioning datastore create',
'dev': 'dev',
'dns': 'hanzo dns zones list',
'docdb': 'hanzo provisioning docdb create',
'embeddings': 'hanzo embeddings create',
'engine': 'hanzo engine serve <model>',
'enso': 'hanzo chat completions',
'environments': 'hanzo platform environments',
'evals': 'hanzo eval runs create',
'experiments': 'hanzo experiment list',
'flow': 'hanzo flow workflows list',
'functions': 'hanzo functions list',
'gateway': 'hanzo gateway config get',
'gen': 'gen',
'gpus': 'hanzo visor gpus get',
'iam': 'hanzo iam whoami',
'ide': 'hanzo code',
'indexer': 'hanzo explorer indexers',
'inference': 'hanzo chat completions',
'ingress': 'hanzo ingress routes list',
'integrations': 'hanzo integrations list',
'kai': 'hanzo decisions create',
'kata': 'hanzo kata run',
'kms': 'hanzo kms secrets create',
'kubernetes': 'hanzo visor k8s clusters list',
'kv': 'hanzo kv add',
'logs': 'hanzo o11y logs get',
'machines': 'hanzo visor machines list',
'marketplace': 'hanzo marketplace listings list',
'mcp': 'hanzo mcp create',
'memory': 'hanzo ai memory list',
'metrics': 'hanzo metrics query',
'models': 'hanzo models list',
'mq': 'hanzo mq stream list',
'network': 'hanzo network list',
'networks': 'hanzo web3 chains list',
'node': 'hanzo node list',
'nodes': 'hanzo validator list',
'o11y': 'hanzo o11y logs get',
'oracles': 'hanzo explorer oracles',
'pipelines': 'hanzo platform pipelines',
'plans': 'hanzo pricing get',
'projects': 'hanzo projects list',
'prompts': 'hanzo prompt list',
'providers': 'hanzo ai providers list',
'pubsub': 'hanzo pubsub publish',
'referrals': 'hanzo referral get',
'registry': 'hanzo registry images',
'releases': 'hanzo platform releases',
's3': 'hanzo s3 buckets list',
'score-configs': 'hanzo eval rubrics create',
'scores': 'hanzo eval scores create',
'search': 'hanzo search create',
'secrets': 'hanzo kms secrets create',
'sessions': 'hanzo o11y sessions get',
'settlement': 'hanzo x402 settlements get',
'skills': 'hanzo tools skills list',
'sql': 'hanzo provisioning sql create',
'status': 'hanzo o11y status',
'storage': 'hanzo s3 buckets list',
'tasks': 'hanzo tasks create',
'tokens': 'hanzo web3 tokens chain get',
'tunnel': 'hanzo share',
'vector': 'hanzo provisioning vector create',
'visor': 'hanzo visor machines list',
'wallet': 'hanzo wallet accounts get',
'world': 'hanzo world news',
'zen': 'hanzo chat create',
}
/**
* What this slug names, or nothing where neither source states a name.
*
* The catalog answers first — it is where a product's name is decided, and it
* is the name the mega-menu, the category landings and the generated overviews
* already print. `pages.json` covers the products this repo publishes a page
* for that commerce does not carry; its titles are title-cased route slugs
* (`scripts/sync-pages.mjs` spells them for the ⌘K palette), so they are a
* fallback and never a correction. Where the catalog's name is wrong for a
* product this site publishes a page for, the copy deck states the right one —
* see `title` in `lib/data/cloud-primitives`.
*
* Without a name there is no sentence to write, so the blocks are absent rather
* than addressed to a slug.
*/
export function product(slug: string): Product | null {
const listed = getPrimitive(slug)
const name = listed?.title ?? pages.find((p) => p.id === slug)?.title
if (!name) return null
return { slug, name, kind: listed?.kind, cli: CLI[slug], api: listed?.api, docs: docsUrl(slug) }
}
/** The product a route is about: a product page's last segment IS the slug. */
export function at(path: string): Product | null {
return product(path.split('/').filter(Boolean).pop() ?? '')
}