From a5dd55bede0871bcc86d2875d845221e58bad31a Mon Sep 17 00:00:00 2001 From: Sam Kim Date: Fri, 23 Oct 2026 13:22:30 -0700 Subject: [PATCH 1/7] Fix truncated reference-page descriptions and regenerate the docs The CRD reference pages took their meta description from the first line of a Go doc comment, which wraps at ~80 columns, so every description stopped mid-sentence ("...defined by: model, system"). mdxutil.Summary takes the first full sentence instead, capped at a search snippet's length. CLI family pages now name the command and what the page holds rather than repeating cobra's bare Short. Regenerating also brings the AgentSession reference up to date with the per-pod cost fields and costTreatment, which had drifted from the CRDs. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/gen/clidocs/clidocs.go | 13 ++++- pkg/gen/clidocs/clidocs_test.go | 2 + pkg/gen/crddocs/crddocs.go | 2 +- pkg/gen/mdxutil/mdxutil.go | 29 +++++++++++ pkg/gen/mdxutil/mdxutil_test.go | 50 +++++++++++++++++++ site/content/docs/crd-agentclass.mdx | 2 +- site/content/docs/crd-agentidentity.mdx | 2 +- site/content/docs/crd-agentsession.mdx | 34 ++++++++++--- site/content/docs/crd-agentsessiongrants.mdx | 2 +- site/content/docs/crd-agentsettings.mdx | 2 +- site/content/docs/crd-agentui.mdx | 2 +- site/content/docs/crd-artifactrender.mdx | 2 +- site/content/docs/crd-channel.mdx | 2 +- .../content/docs/crd-clusteragentsettings.mdx | 2 +- .../docs/crd-clusteridentityprovider.mdx | 2 +- site/content/docs/crd-clusterskill.mdx | 2 +- site/content/docs/crd-clusterskillsource.mdx | 2 +- .../docs/crd-credentialupdaterequest.mdx | 2 +- site/content/docs/crd-mcpserver.mdx | 2 +- site/content/docs/crd-relationshipsource.mdx | 2 +- site/content/docs/crd-sessionhold.mdx | 2 +- site/content/docs/crd-sessionuseridentity.mdx | 2 +- site/content/docs/crd-sidecartoolbox.mdx | 2 +- site/content/docs/crd-skill.mdx | 2 +- site/content/docs/crd-spiceboxclass.mdx | 2 +- site/content/docs/crd-spiceboxsession.mdx | 2 +- site/content/docs/crd-spiceboxtoolchain.mdx | 2 +- site/content/docs/crd-spiceboxtoolkit.mdx | 3 +- site/content/docs/crd-spiceboxtoolspec.mdx | 2 +- site/content/docs/crd-spicedbbootstrap.mdx | 2 +- site/content/docs/crd-toolcall.mdx | 2 +- site/content/docs/crd-useridentity.mdx | 2 +- site/content/docs/crd-workshop.mdx | 2 +- site/content/docs/crd-workshopprobe.mdx | 2 +- site/content/docs/crd-workspacesource.mdx | 2 +- site/content/docs/oap-agent.mdx | 2 +- site/content/docs/oap-artifact.mdx | 2 +- site/content/docs/oap-audit.mdx | 2 +- site/content/docs/oap-build.mdx | 2 +- site/content/docs/oap-channel.mdx | 2 +- site/content/docs/oap-check.mdx | 2 +- site/content/docs/oap-class.mdx | 2 +- site/content/docs/oap-clean.mdx | 2 +- site/content/docs/oap-desktop.mdx | 2 +- site/content/docs/oap-directory.mdx | 2 +- site/content/docs/oap-identity.mdx | 2 +- site/content/docs/oap-idp.mdx | 2 +- site/content/docs/oap-image.mdx | 2 +- site/content/docs/oap-init.mdx | 2 +- site/content/docs/oap-install.mdx | 2 +- site/content/docs/oap-kg.mdx | 2 +- site/content/docs/oap-login.mdx | 2 +- site/content/docs/oap-logout.mdx | 2 +- site/content/docs/oap-memory.mdx | 2 +- site/content/docs/oap-pin.mdx | 2 +- site/content/docs/oap-plangate.mdx | 2 +- site/content/docs/oap-platform.mdx | 2 +- site/content/docs/oap-preferences.mdx | 2 +- site/content/docs/oap-sandbox.mdx | 2 +- site/content/docs/oap-session.mdx | 2 +- site/content/docs/oap-settings.mdx | 2 +- site/content/docs/oap-skill.mdx | 2 +- site/content/docs/oap-spicedb.mdx | 2 +- site/content/docs/oap-tools.mdx | 2 +- site/content/docs/oap-user-identity.mdx | 2 +- 65 files changed, 181 insertions(+), 68 deletions(-) create mode 100644 pkg/gen/mdxutil/mdxutil_test.go diff --git a/pkg/gen/clidocs/clidocs.go b/pkg/gen/clidocs/clidocs.go index 0fc5486d..4afab0bf 100644 --- a/pkg/gen/clidocs/clidocs.go +++ b/pkg/gen/clidocs/clidocs.go @@ -92,7 +92,7 @@ func overviewPage(root *cobra.Command, fams []*cobra.Command) string { func familyPage(fam *cobra.Command, order int) string { var b bytes.Buffer title := "oap " + fam.Name() - b.WriteString(mdxutil.Frontmatter(title, section, group, order, mdxutil.FirstLine(fam.Short))) + b.WriteString(mdxutil.Frontmatter(title, section, group, order, familyDescription(fam))) fmt.Fprintf(&b, "# %s\n\n", title) if d := describe(fam); d != "" { b.WriteString(d + "\n\n") @@ -102,6 +102,17 @@ func familyPage(fam *cobra.Command, order int) string { return b.String() } +// familyDescription is a family page's meta description. cobra's Short alone +// ("Manage AgentClass resources") names neither the command nor what the page +// holds, so a search result built from it reads as a fragment. +func familyDescription(fam *cobra.Command) string { + short := strings.TrimSuffix(mdxutil.FirstLine(fam.Short), ".") + if short != "" { + short = strings.ToUpper(short[:1]) + short[1:] + ". " + } + return fmt.Sprintf("oap %s: %sEvery subcommand, flag, and example, generated from the CLI.", fam.Name(), short) +} + // writeCommandTree renders each visible subcommand of cmd as a heading section // (usage, description, flags, example), then recurses. level is the markdown // heading level for the direct children (2 = ##), capped at 4. diff --git a/pkg/gen/clidocs/clidocs_test.go b/pkg/gen/clidocs/clidocs_test.go index 156b5086..d5da4317 100644 --- a/pkg/gen/clidocs/clidocs_test.go +++ b/pkg/gen/clidocs/clidocs_test.go @@ -42,6 +42,8 @@ func TestGenerate(t *testing.T) { require.NoError(t, err) fam := string(famB) assert.Contains(t, fam, "title: 'oap agent'") + assert.Contains(t, fam, "description: 'oap agent: Manage agents. Every subcommand, flag, and example, generated from the CLI.'", + "description names the command and what the page holds") assert.Contains(t, fam, "`oap agent run `", "usage heading is backtick-wrapped so stay literal") assert.Contains(t, fam, "--prompt string", "local flag rendered") assert.Contains(t, fam, "oap agent run pirate", "example rendered") diff --git a/pkg/gen/crddocs/crddocs.go b/pkg/gen/crddocs/crddocs.go index 7de360c6..69a9ba57 100644 --- a/pkg/gen/crddocs/crddocs.go +++ b/pkg/gen/crddocs/crddocs.go @@ -142,7 +142,7 @@ func overviewPage(crds []crdInfo) string { func crdPage(c crdInfo, order int) string { var b bytes.Buffer - b.WriteString(mdxutil.Frontmatter(c.kind, section, group, order, mdxutil.FirstLine(c.desc))) + b.WriteString(mdxutil.Frontmatter(c.kind, section, group, order, mdxutil.Summary(c.desc))) fmt.Fprintf(&b, "# %s\n\n", c.kind) fmt.Fprintf(&b, "**Group** `%s` · **Scope** %s", c.apiGroup, c.scope) if len(c.shortNames) > 0 { diff --git a/pkg/gen/mdxutil/mdxutil.go b/pkg/gen/mdxutil/mdxutil.go index ca201569..4b8425dc 100644 --- a/pkg/gen/mdxutil/mdxutil.go +++ b/pkg/gen/mdxutil/mdxutil.go @@ -44,6 +44,35 @@ func FirstLine(s string) string { return strings.TrimSpace(s) } +// summaryMax is the longest Summary returns: about what a search result shows +// of a meta description before it truncates. +const summaryMax = 160 + +// Summary turns a doc comment into a page's meta description: the first +// sentence of its first paragraph, with the comment's hard line wraps joined. +// FirstLine is wrong for this: Go doc comments wrap at ~80 columns, so their +// first line stops mid-sentence. A sentence over summaryMax is cut at a word +// boundary and ends in an ellipsis. +func Summary(s string) string { + para, _, _ := strings.Cut(strings.TrimSpace(s), "\n\n") + para = strings.Join(strings.Fields(para), " ") + for i := 0; i+2 < len(para); i++ { + // A sentence ends at ". " before a capital, which skips "e.g. x". + if para[i] == '.' && para[i+1] == ' ' && 'A' <= para[i+2] && para[i+2] <= 'Z' { + para = para[:i+1] + break + } + } + if len(para) <= summaryMax { + return para + } + cut := strings.LastIndexByte(para[:summaryMax], ' ') + if cut <= 0 { + cut = summaryMax + } + return strings.TrimRight(para[:cut], " ,;:-") + "…" +} + // TableText makes a string safe for a single markdown table cell: first line // only, MDX-escaped, with the cell separator escaped. func TableText(s string) string { diff --git a/pkg/gen/mdxutil/mdxutil_test.go b/pkg/gen/mdxutil/mdxutil_test.go new file mode 100644 index 00000000..314eb50f --- /dev/null +++ b/pkg/gen/mdxutil/mdxutil_test.go @@ -0,0 +1,50 @@ +package mdxutil + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSummary(t *testing.T) { + long := strings.Repeat("word ", 50) + "end." + cases := []struct { + name string + in string + want string + }{ + { + name: "wrapped first sentence: joined, stops at the period", + in: "AgentClass is the template: model, system\nprompt, tools. It never runs\non its own.", + want: "AgentClass is the template: model, system prompt, tools.", + }, + { + name: "single sentence: returned whole", + in: "SkillSource is a git repo that yields one or more Skills.", + want: "SkillSource is a git repo that yields one or more Skills.", + }, + { + name: "second paragraph: ignored", + in: "Widget is a demo\n\nNamespaced. Reconciled elsewhere.", + want: "Widget is a demo", + }, + { + name: "abbreviation before lowercase: not a sentence end", + in: "Channel binds a transport, e.g. a Slack channel. More here.", + want: "Channel binds a transport, e.g. a Slack channel.", + }, + { + name: "over the limit: cut at a word boundary with an ellipsis", + in: long, + want: strings.TrimSpace(strings.Repeat("word ", 32)) + "…", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := Summary(tc.in) + assert.Equal(t, tc.want, got) + assert.LessOrEqual(t, len(strings.TrimSuffix(got, "…")), summaryMax) + }) + } +} diff --git a/site/content/docs/crd-agentclass.mdx b/site/content/docs/crd-agentclass.mdx index 5d83da63..4a0f5e06 100644 --- a/site/content/docs/crd-agentclass.mdx +++ b/site/content/docs/crd-agentclass.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3010, - description: 'AgentClass is the reviewable template an agent is defined by: model, system', + description: 'AgentClass is the reviewable template an agent is defined by: model, system prompt, tools, skills, identity, channels, budget and authz policy.', } # AgentClass diff --git a/site/content/docs/crd-agentidentity.mdx b/site/content/docs/crd-agentidentity.mdx index 5ed61ce9..876d8c50 100644 --- a/site/content/docs/crd-agentidentity.mdx +++ b/site/content/docs/crd-agentidentity.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3020, - description: 'AgentIdentity is the named credential set an agent acts as: reusable static', + description: 'AgentIdentity is the named credential set an agent acts as: reusable static and OAuth credential descriptors that toolkits and MCP servers request by name.', } # AgentIdentity diff --git a/site/content/docs/crd-agentsession.mdx b/site/content/docs/crd-agentsession.mdx index 72860f42..74b685d1 100644 --- a/site/content/docs/crd-agentsession.mdx +++ b/site/content/docs/crd-agentsession.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3030, - description: 'AgentSession is one running instance of an AgentClass: a conversation with', + description: 'AgentSession is one running instance of an AgentClass: a conversation with its own runner Pod, budget, channel bindings and lifecycle phase.', } # AgentSession @@ -271,21 +271,41 @@ Status is controller-owned (observed state). | `status.effectiveSettings.toolGuard.namespace.rules[].rateLimit.maxCallsPerTurn` | `integer (int32)` | MaxCallsPerTurn caps calls within a single agent turn; 0 is unlimited. _(min 1)_ | | `status.effectiveSettings.toolGuard.namespace.rules[].rateLimit.window` | `string` | Window is the sliding-window span for MaxCalls; both must be set together. | | `status.estimatedCost` | `object` | EstimatedCost is the best-effort end-of-session USD cost estimate. Runner-owned; written at SessionEnd when reportSessionCost is on. | -| `status.estimatedCost.amountMicroUSD` | `integer (int64)` | AmountMicroUSD is the session total in micro-USD (1e-6 USD): the sum of every priced component (ByModel + ByTool). When PricingKnown is false, at least one served MODEL had no rate, so this is a LOWER BOUND on spend (the components that could be priced), not the full session cost — it is no longer necessarily 0, because provider-reported tool cost is always priced. | +| `status.estimatedCost.amountMicroUSD` | `integer (int64)` | AmountMicroUSD is the SESSION total in micro-USD (1e-6 USD), accumulated across every pod that served the session: the sum of PerPod amounts. When PricingKnown is false, at least one served MODEL (in some pod) had no rate, so this is a LOWER BOUND on spend (the components that could be priced), not the full session cost — it is no longer necessarily 0, because provider-reported tool cost is always priced. | | `status.estimatedCost.asOf` | `string (date-time)` | AsOf is when the estimate was computed. | -| `status.estimatedCost.byModel` | `[]object` | ByModel breaks the total down per actually-served model, so a session that fell back or auto-routed across models shows where the spend went instead of one blended total under the configured model. Empty when the runner reported no per-model usage; AmountMicroUSD and PricingKnown are then the sole source of truth. | +| `status.estimatedCost.byModel` | `[]object` | ByModel breaks the total down per actually-served model, MERGED across every pod (summed per model display id), so a session that fell back or auto-routed across models — or ran across a reap/wake — shows where the spend went instead of one blended total under the configured model. Empty when no pod reported per-model usage; AmountMicroUSD and PricingKnown are then the sole source of truth. | | `status.estimatedCost.byModel[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this bucket's share of the session cost, in micro-USD (1e-6 USD): the provider-reported cost when available, else tokens × the resolved per-model pricing table rate. | | `status.estimatedCost.byModel[].inputTokens` | `integer (int64)` | InputTokens is this model's share of prompt tokens. | | `status.estimatedCost.byModel[].model` \* | `string` | Model is the served-model display id verbatim (provider/model, or a provider-prefixed served model under routing) — never re-parsed here. | | `status.estimatedCost.byModel[].outputTokens` | `integer (int64)` | OutputTokens is this model's share of completion tokens. | | `status.estimatedCost.byModel[].pricingKnown` | `boolean` | PricingKnown is true when AmountMicroUSD is a real cost — either the provider reported it directly, or the pricing table had a rate for this model. False means AmountMicroUSD is 0 and must not be shown as real spend (same no-fabrication contract as EstimatedSessionCost). | -| `status.estimatedCost.byTool` | `[]object` | ByTool breaks out the cost of inner interactive toolkits (e.g. a `claude` Claude Code sub-run's own provider-reported spend) a session drove. These are ADDED to AmountMicroUSD alongside ByModel — a session's total is model spend plus tool spend. Empty when no interactive toolkit reported a cost. Tool cost is always provider-reported, never table-priced, so a bucket's PricingKnown is always true. | -| `status.estimatedCost.byTool[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this tool's provider-reported cost, summed across its invocations, in micro-USD (1e-6 USD). | -| `status.estimatedCost.byTool[].pricingKnown` | `boolean` | PricingKnown is true when AmountMicroUSD is a real cost. Tool cost is always provider-reported, so this is true whenever a cost was reported. | +| `status.estimatedCost.byTool` | `[]object` | ByTool breaks out the cost of inner interactive toolkits (e.g. a `claude` Claude Code sub-run's own provider-reported spend) a session drove, MERGED across every pod (summed per tool). These are ADDED to AmountMicroUSD alongside ByModel — a session's total is model spend plus tool spend. Empty when no interactive toolkit reported a cost. | +| `status.estimatedCost.byTool[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this tool's provider-reported cost, summed across its invocations, in micro-USD (1e-6 USD). Negative reports are excluded from the sum (see Anomalous). | +| `status.estimatedCost.byTool[].anomalous` | `boolean` | Anomalous is true when a negative (nonsensical) provider cost was reported for this tool. The tool is still recorded as seen, but the negative value is excluded from AmountMicroUSD and the session total — it signals "a cost report here does not make sense" rather than silently dropping it. | +| `status.estimatedCost.byTool[].pricingKnown` | `boolean` | PricingKnown is true when AmountMicroUSD is a real cost — i.e. at least one non-negative cost tally was reported. False when none was reported (the amount is 0 and must not be shown as real spend) or every report was anomalous. | +| `status.estimatedCost.byTool[].subscriptionEquivalent` | `boolean` | SubscriptionEquivalent is true when this tool ran under a flat-rate subscription (toolkit CostTreatment=subscription-equivalent), so its AmountMicroUSD is an API-equivalent estimate, not money charged. The bucket is recorded and shown, but its amount is EXCLUDED from the session total / admind spend so it cannot inflate the figure or trip cost alerts. | | `status.estimatedCost.byTool[].tool` \* | `string` | Tool is the outer tool name (e.g. "claude-oauth"). | | `status.estimatedCost.currency` | `string` | Currency is the ISO code the amount is denominated in ("USD"). | | `status.estimatedCost.model` | `string` | Model is the configured model the session ran under. | -| `status.estimatedCost.pricingKnown` | `boolean` | PricingKnown is false when a served model had no price. The amount is then a lower bound (priced components only), not the full cost. Tool buckets are always priced, so this tracks model-pricing coverage specifically. | +| `status.estimatedCost.perPod` | `[]object` | PerPod is the per-pod cost SOURCE OF TRUTH: one entry per pod that served the session (keyed by pod name), each carrying that pod's own total and breakdown. The aggregate fields above are DERIVED from this list — the total is the sum of PerPod amounts, ByModel/ByTool are merged by key, and PricingKnown is the AND. Each pod upserts only its own entry, so cost ACCUMULATES across a reap/wake instead of a later pod's single-pod total replacing an earlier one. Empty only for a session no pod has costed yet. | +| `status.estimatedCost.perPod[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this pod's own total in micro-USD (model + tool). | +| `status.estimatedCost.perPod[].asOf` | `string (date-time)` | AsOf is when this pod last computed its estimate. | +| `status.estimatedCost.perPod[].byModel` | `[]object` | ByModel is this pod's per-served-model breakdown. | +| `status.estimatedCost.perPod[].byModel[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this bucket's share of the session cost, in micro-USD (1e-6 USD): the provider-reported cost when available, else tokens × the resolved per-model pricing table rate. | +| `status.estimatedCost.perPod[].byModel[].inputTokens` | `integer (int64)` | InputTokens is this model's share of prompt tokens. | +| `status.estimatedCost.perPod[].byModel[].model` \* | `string` | Model is the served-model display id verbatim (provider/model, or a provider-prefixed served model under routing) — never re-parsed here. | +| `status.estimatedCost.perPod[].byModel[].outputTokens` | `integer (int64)` | OutputTokens is this model's share of completion tokens. | +| `status.estimatedCost.perPod[].byModel[].pricingKnown` | `boolean` | PricingKnown is true when AmountMicroUSD is a real cost — either the provider reported it directly, or the pricing table had a rate for this model. False means AmountMicroUSD is 0 and must not be shown as real spend (same no-fabrication contract as EstimatedSessionCost). | +| `status.estimatedCost.perPod[].byTool` | `[]object` | ByTool is this pod's per-interactive-toolkit breakdown. | +| `status.estimatedCost.perPod[].byTool[].amountMicroUSD` | `integer (int64)` | AmountMicroUSD is this tool's provider-reported cost, summed across its invocations, in micro-USD (1e-6 USD). Negative reports are excluded from the sum (see Anomalous). | +| `status.estimatedCost.perPod[].byTool[].anomalous` | `boolean` | Anomalous is true when a negative (nonsensical) provider cost was reported for this tool. The tool is still recorded as seen, but the negative value is excluded from AmountMicroUSD and the session total — it signals "a cost report here does not make sense" rather than silently dropping it. | +| `status.estimatedCost.perPod[].byTool[].pricingKnown` | `boolean` | PricingKnown is true when AmountMicroUSD is a real cost — i.e. at least one non-negative cost tally was reported. False when none was reported (the amount is 0 and must not be shown as real spend) or every report was anomalous. | +| `status.estimatedCost.perPod[].byTool[].subscriptionEquivalent` | `boolean` | SubscriptionEquivalent is true when this tool ran under a flat-rate subscription (toolkit CostTreatment=subscription-equivalent), so its AmountMicroUSD is an API-equivalent estimate, not money charged. The bucket is recorded and shown, but its amount is EXCLUDED from the session total / admind spend so it cannot inflate the figure or trip cost alerts. | +| `status.estimatedCost.perPod[].byTool[].tool` \* | `string` | Tool is the outer tool name (e.g. "claude-oauth"). | +| `status.estimatedCost.perPod[].currency` | `string` | Currency is the ISO code this pod's amount is denominated in ("USD"). | +| `status.estimatedCost.perPod[].pod` \* | `string` | Pod is the serving pod's name (its HOSTNAME); the upsert key. | +| `status.estimatedCost.perPod[].pricingKnown` | `boolean` | PricingKnown is false when a model this pod served had no rate (this pod's amount is then a lower bound). | +| `status.estimatedCost.pricingKnown` | `boolean` | PricingKnown is false when a served model had no price in ANY contributing pod (the AND across PerPod). The amount is then a lower bound (priced components only), not the full cost. Tool buckets are always priced, so this tracks model-pricing coverage specifically. | | `status.failureReason` | `string` | FailureReason is the human-readable reason phase became Failed. | | `status.finishedAt` | `string (date-time)` | FinishedAt is when the session reached a terminal phase. | | `status.identityChoiceParkedAt` | `string (date-time)` | IdentityChoiceParkedAt is when the session entered AwaitingIdentityChoice. The operator measures IdentityChoiceTimeout from here. | diff --git a/site/content/docs/crd-agentsessiongrants.mdx b/site/content/docs/crd-agentsessiongrants.mdx index f2749d1a..b47ccad1 100644 --- a/site/content/docs/crd-agentsessiongrants.mdx +++ b/site/content/docs/crd-agentsessiongrants.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3040, - description: 'AgentSessionGrants is the per-AgentClass declaration of the (resourceType,', + description: 'AgentSessionGrants is the per-AgentClass declaration of the (resourceType, permission) pairs that must exist as grant__ relations on the SpiceDB…', } # AgentSessionGrants diff --git a/site/content/docs/crd-agentsettings.mdx b/site/content/docs/crd-agentsettings.mdx index 9d2157b4..1708b8ad 100644 --- a/site/content/docs/crd-agentsettings.mdx +++ b/site/content/docs/crd-agentsettings.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3050, - description: 'AgentSettings is the namespace tier of agent governance settings: limits', + description: 'AgentSettings is the namespace tier of agent governance settings: limits that narrow what agents in this namespace may do, and defaults that fill in fields a…', } # AgentSettings diff --git a/site/content/docs/crd-agentui.mdx b/site/content/docs/crd-agentui.mdx index 996744d9..7e8e24a0 100644 --- a/site/content/docs/crd-agentui.mdx +++ b/site/content/docs/crd-agentui.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3060, - description: 'AgentUI is a bundle-authored DECLARATION of an agent-defined view: a page', + description: 'AgentUI is a bundle-authored DECLARATION of an agent-defined view: a page tree in which generative hooks are the agent\'s regions, plus the tools and actions…', } # AgentUI diff --git a/site/content/docs/crd-artifactrender.mdx b/site/content/docs/crd-artifactrender.mdx index 250e09c8..6e393dbc 100644 --- a/site/content/docs/crd-artifactrender.mdx +++ b/site/content/docs/crd-artifactrender.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3070, - description: 'ArtifactRender is one request to turn agent-supplied bytes into a', + description: 'ArtifactRender is one request to turn agent-supplied bytes into a deliverable artifact -- an HTML page, an SVG, an image -- through a named renderer plug-in.', } # ArtifactRender diff --git a/site/content/docs/crd-channel.mdx b/site/content/docs/crd-channel.mdx index 4f420401..bb3600ee 100644 --- a/site/content/docs/crd-channel.mdx +++ b/site/content/docs/crd-channel.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3080, - description: 'Channel binds one transport conversation -- a Slack channel, a scheduled', + description: 'Channel binds one transport conversation -- a Slack channel, a scheduled Bento feed, a terminal, a browser view -- to one AgentClass, and declares how inbound…', } # Channel diff --git a/site/content/docs/crd-clusteragentsettings.mdx b/site/content/docs/crd-clusteragentsettings.mdx index 022a414d..e6f57ec2 100644 --- a/site/content/docs/crd-clusteragentsettings.mdx +++ b/site/content/docs/crd-clusteragentsettings.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3090, - description: 'ClusterAgentSettings is the cluster-wide top tier of agent governance', + description: 'ClusterAgentSettings is the cluster-wide top tier of agent governance settings: the limits and defaults every namespace inherits, plus the model catalog.', } # ClusterAgentSettings diff --git a/site/content/docs/crd-clusteridentityprovider.mdx b/site/content/docs/crd-clusteridentityprovider.mdx index 4dc9e458..c32c3712 100644 --- a/site/content/docs/crd-clusteridentityprovider.mdx +++ b/site/content/docs/crd-clusteridentityprovider.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3100, - description: 'ClusterIdentityProvider configures cluster-wide human login: which', + description: 'ClusterIdentityProvider configures cluster-wide human login: which registered idp.Kind authenticates people (pkg/platform/identity/idp), its OIDC client…', } # ClusterIdentityProvider diff --git a/site/content/docs/crd-clusterskill.mdx b/site/content/docs/crd-clusterskill.mdx index 6eb8b263..702592f9 100644 --- a/site/content/docs/crd-clusterskill.mdx +++ b/site/content/docs/crd-clusterskill.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3110, - description: 'ClusterSkill is a cluster-scoped Skill, visible to every namespace. A', + description: 'ClusterSkill is a cluster-scoped Skill, visible to every namespace.', } # ClusterSkill diff --git a/site/content/docs/crd-clusterskillsource.mdx b/site/content/docs/crd-clusterskillsource.mdx index 296681df..422982a9 100644 --- a/site/content/docs/crd-clusterskillsource.mdx +++ b/site/content/docs/crd-clusterskillsource.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3120, - description: 'ClusterSkillSource is a git repo that materializes cluster-scoped', + description: 'ClusterSkillSource is a git repo that materializes cluster-scoped ClusterSkills.', } # ClusterSkillSource diff --git a/site/content/docs/crd-credentialupdaterequest.mdx b/site/content/docs/crd-credentialupdaterequest.mdx index 874e6ff0..3a49acff 100644 --- a/site/content/docs/crd-credentialupdaterequest.mdx +++ b/site/content/docs/crd-credentialupdaterequest.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3130, - description: 'CredentialUpdateRequest is an agent\'s REQUEST that a human replace a', + description: 'CredentialUpdateRequest is an agent\'s REQUEST that a human replace a credential it believes has stopped authenticating.', } # CredentialUpdateRequest diff --git a/site/content/docs/crd-mcpserver.mdx b/site/content/docs/crd-mcpserver.mdx index 5c547a18..1420ec51 100644 --- a/site/content/docs/crd-mcpserver.mdx +++ b/site/content/docs/crd-mcpserver.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3140, - description: 'MCPServer declares one MCP endpoint an agent may call: where it lives', + description: 'MCPServer declares one MCP endpoint an agent may call: where it lives (spec.server), the allowlisted tools, per-tool CEL argument constraints, the credential…', } # MCPServer diff --git a/site/content/docs/crd-relationshipsource.mdx b/site/content/docs/crd-relationshipsource.mdx index e8bdb05e..86d8b2da 100644 --- a/site/content/docs/crd-relationshipsource.mdx +++ b/site/content/docs/crd-relationshipsource.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3160, - description: 'RelationshipSource declares one upstream directory (Slack first) to poll', + description: 'RelationshipSource declares one upstream directory (Slack first) to poll and sync into SpiceDB.', } # RelationshipSource diff --git a/site/content/docs/crd-sessionhold.mdx b/site/content/docs/crd-sessionhold.mdx index f8c7b9c9..118c649b 100644 --- a/site/content/docs/crd-sessionhold.mdx +++ b/site/content/docs/crd-sessionhold.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3170, - description: 'SessionHold parks an AgentSession for forensic review and gates its return to', + description: 'SessionHold parks an AgentSession for forensic review and gates its return to running on a human decision.', } # SessionHold diff --git a/site/content/docs/crd-sessionuseridentity.mdx b/site/content/docs/crd-sessionuseridentity.mdx index 40cc63b3..a9b7785a 100644 --- a/site/content/docs/crd-sessionuseridentity.mdx +++ b/site/content/docs/crd-sessionuseridentity.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3180, - description: 'SessionUserIdentity is one session\'s narrowing of a user\'s UserIdentity', + description: 'SessionUserIdentity is one session\'s narrowing of a user\'s UserIdentity catalog down to just the credentials that session\'s agent asked for, for…', } # SessionUserIdentity diff --git a/site/content/docs/crd-sidecartoolbox.mdx b/site/content/docs/crd-sidecartoolbox.mdx index e0c6189c..80779fcf 100644 --- a/site/content/docs/crd-sidecartoolbox.mdx +++ b/site/content/docs/crd-sidecartoolbox.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3190, - description: 'SidecarToolbox declares a user-supplied MCP server that runs as a sidecar', + description: 'SidecarToolbox declares a user-supplied MCP server that runs as a sidecar container alongside the agent runner pod: its image, sandbox shape, tool allowlist…', } # SidecarToolbox diff --git a/site/content/docs/crd-skill.mdx b/site/content/docs/crd-skill.mdx index 896e1e4a..aa92514f 100644 --- a/site/content/docs/crd-skill.mdx +++ b/site/content/docs/crd-skill.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3200, - description: 'Skill is one agentskills.io skill: a SKILL.md (frontmatter + body) plus', + description: 'Skill is one agentskills.io skill: a SKILL.md (frontmatter + body) plus optional bundled files.', } # Skill diff --git a/site/content/docs/crd-spiceboxclass.mdx b/site/content/docs/crd-spiceboxclass.mdx index 299fdf7e..5ca7a27a 100644 --- a/site/content/docs/crd-spiceboxclass.mdx +++ b/site/content/docs/crd-spiceboxclass.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3230, - description: 'SpiceboxClass is the template a tool sandbox is cut from: image, resources,', + description: 'SpiceboxClass is the template a tool sandbox is cut from: image, resources, network mode, mounts, and the toolspecs and toolchains available inside it.', } # SpiceboxClass diff --git a/site/content/docs/crd-spiceboxsession.mdx b/site/content/docs/crd-spiceboxsession.mdx index aa7da5bc..769f6d43 100644 --- a/site/content/docs/crd-spiceboxsession.mdx +++ b/site/content/docs/crd-spiceboxsession.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3240, - description: 'SpiceboxSession is one live sandbox instantiated from a SpiceboxClass: the', + description: 'SpiceboxSession is one live sandbox instantiated from a SpiceboxClass: the long-lived pod that this session\'s ToolCalls execute inside, plus its workspace…', } # SpiceboxSession diff --git a/site/content/docs/crd-spiceboxtoolchain.mdx b/site/content/docs/crd-spiceboxtoolchain.mdx index a9a329c7..47bc0212 100644 --- a/site/content/docs/crd-spiceboxtoolchain.mdx +++ b/site/content/docs/crd-spiceboxtoolchain.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3250, - description: 'SpiceboxToolchain is a language or tooling overlay a sandbox can mount -- a', + description: 'SpiceboxToolchain is a language or tooling overlay a sandbox can mount -- a compiler, an SDK, a linter -- delivered by a registered backend and mounted at…', } # SpiceboxToolchain diff --git a/site/content/docs/crd-spiceboxtoolkit.mdx b/site/content/docs/crd-spiceboxtoolkit.mdx index 74b7d15a..0fc06366 100644 --- a/site/content/docs/crd-spiceboxtoolkit.mdx +++ b/site/content/docs/crd-spiceboxtoolkit.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3260, - description: 'SpiceboxToolkit is the machine-readable description of one CLI a sandbox', + description: 'SpiceboxToolkit is the machine-readable description of one CLI a sandbox tool can be built from: its subcommands, flags, env, parser config and default…', } # SpiceboxToolkit @@ -23,6 +23,7 @@ onto status. | Field | Type | Description | | --- | --- | --- | +| `spec.costTreatment` | `string` | CostTreatment declares how this toolkit's provider-reported cost counts toward session spend. "metered" (default) is real billed money and counts; "subscription-equivalent" is a flat-rate estimate (e.g. a Claude Max OAuth run) that is recorded and shown but EXCLUDED from the session total, so it cannot inflate spend or trip cost alerts. Mirrors pkg/tools/toolspec/toolkit.Toolkit.CostTreatment. _(enum: metered \| subscription-equivalent)_ | | `spec.docs` | `string` | Docs is prose about the CLI, surfaced to authors. | | `spec.env` \* | `object` | Env describes the environment variables the binary reads. It documents and wires them — notably which are sensitive — and is not a filter on the environment the binary actually receives. Mirrors pkg/tools/toolspec/toolkit.Toolkit.Env. | | `spec.env.allowed` \* | `[]object` | Allowed DESCRIBES the environment variables the binary reads. It is not an enforcement boundary: nothing strips a variable for being absent here. The environment a tool call runs with is composed by the ToolCall controller from the broker-resolved credentials, ToolCall.spec.env, SpiceboxSession.spec.defaultEnv, this toolkit's envDefaults and the sandbox container's own env. What an entry drives is keyed off Sensitive: redaction wherever the call is surfaced, which credential a setup flow must obtain, and which names a SpiceboxClass may not shadow through its own envDefaults. Adding a variable here documents and wires it; omitting one does not keep it out of the binary's environment. | diff --git a/site/content/docs/crd-spiceboxtoolspec.mdx b/site/content/docs/crd-spiceboxtoolspec.mdx index 81325042..dc3e178e 100644 --- a/site/content/docs/crd-spiceboxtoolspec.mdx +++ b/site/content/docs/crd-spiceboxtoolspec.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3270, - description: 'SpiceboxToolspec is one sandbox tool as the agent sees it: a SpiceboxToolkit', + description: 'SpiceboxToolspec is one sandbox tool as the agent sees it: a SpiceboxToolkit reference narrowed to an allowed subcommand set, with CEL argument constraints…', } # SpiceboxToolspec diff --git a/site/content/docs/crd-spicedbbootstrap.mdx b/site/content/docs/crd-spicedbbootstrap.mdx index 19760ee4..3f225087 100644 --- a/site/content/docs/crd-spicedbbootstrap.mdx +++ b/site/content/docs/crd-spicedbbootstrap.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3220, - description: 'SpiceDBBootstrap declaratively seeds SpiceDB state: an optional schema', + description: 'SpiceDBBootstrap declaratively seeds SpiceDB state: an optional schema fragment and/or a set of relationships TOUCHed into SpiceDB.', } # SpiceDBBootstrap diff --git a/site/content/docs/crd-toolcall.mdx b/site/content/docs/crd-toolcall.mdx index 8e410e82..ba15fc9f 100644 --- a/site/content/docs/crd-toolcall.mdx +++ b/site/content/docs/crd-toolcall.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3290, - description: 'ToolCall is one execution of one sandbox tool inside a SpiceboxSession: the', + description: 'ToolCall is one execution of one sandbox tool inside a SpiceboxSession: the tool name, its args, non-secret env, stdin, and credential references -- never…', } # ToolCall diff --git a/site/content/docs/crd-useridentity.mdx b/site/content/docs/crd-useridentity.mdx index dac65b2d..6e07a06e 100644 --- a/site/content/docs/crd-useridentity.mdx +++ b/site/content/docs/crd-useridentity.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3300, - description: 'UserIdentity is a human\'s credential catalog: the credentials one person has', + description: 'UserIdentity is a human\'s credential catalog: the credentials one person has linked, keyed by their canonical SpiceDB subject, used by…', } # UserIdentity diff --git a/site/content/docs/crd-workshop.mdx b/site/content/docs/crd-workshop.mdx index 1ac0806a..a59b42d8 100644 --- a/site/content/docs/crd-workshop.mdx +++ b/site/content/docs/crd-workshop.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3310, - description: 'Workshop is one builder session\'s isolated build space: the durable record', + description: 'Workshop is one builder session\'s isolated build space: the durable record of its namespace, RBAC, SpiceDB tuple, tokens and sidecar identity, created by the…', } # Workshop diff --git a/site/content/docs/crd-workshopprobe.mdx b/site/content/docs/crd-workshopprobe.mdx index 16958ade..2d41314c 100644 --- a/site/content/docs/crd-workshopprobe.mdx +++ b/site/content/docs/crd-workshopprobe.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3320, - description: 'WorkshopProbe is the tool-discovery probe object for an agent-builder', + description: 'WorkshopProbe is the tool-discovery probe object for an agent-builder workshop: the operator runs one pod against spec.image (or spec.script\'s baseImage, or…', } # WorkshopProbe diff --git a/site/content/docs/crd-workspacesource.mdx b/site/content/docs/crd-workspacesource.mdx index 98fd9261..721c8281 100644 --- a/site/content/docs/crd-workspacesource.mdx +++ b/site/content/docs/crd-workspacesource.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CRD reference', order: 3330, - description: 'WorkspaceSource declares a named, pluggable origin that per-session', + description: 'WorkspaceSource declares a named, pluggable origin that per-session workspaces are cut from.', } # WorkspaceSource diff --git a/site/content/docs/oap-agent.mdx b/site/content/docs/oap-agent.mdx index a65cff01..15aa7dec 100644 --- a/site/content/docs/oap-agent.mdx +++ b/site/content/docs/oap-agent.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2710, - description: 'Manage and run AgentClass resources', + description: 'oap agent: Manage and run AgentClass resources. Every subcommand, flag, and example, generated from the CLI.', } # oap agent diff --git a/site/content/docs/oap-artifact.mdx b/site/content/docs/oap-artifact.mdx index 4e2d4213..a93957e8 100644 --- a/site/content/docs/oap-artifact.mdx +++ b/site/content/docs/oap-artifact.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2720, - description: 'Inspect versioned artifacts and their revisions', + description: 'oap artifact: Inspect versioned artifacts and their revisions. Every subcommand, flag, and example, generated from the CLI.', } # oap artifact diff --git a/site/content/docs/oap-audit.mdx b/site/content/docs/oap-audit.mdx index 40ebb7e1..4ea18e69 100644 --- a/site/content/docs/oap-audit.mdx +++ b/site/content/docs/oap-audit.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2730, - description: 'Verify the tamper-evident audit log of a session', + description: 'oap audit: Verify the tamper-evident audit log of a session. Every subcommand, flag, and example, generated from the CLI.', } # oap audit diff --git a/site/content/docs/oap-build.mdx b/site/content/docs/oap-build.mdx index e8becb52..5c81d174 100644 --- a/site/content/docs/oap-build.mdx +++ b/site/content/docs/oap-build.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2740, - description: 'Build (and load) Docker images', + description: 'oap build: Build (and load) Docker images. Every subcommand, flag, and example, generated from the CLI.', } # oap build diff --git a/site/content/docs/oap-channel.mdx b/site/content/docs/oap-channel.mdx index 570f26f6..99d829d3 100644 --- a/site/content/docs/oap-channel.mdx +++ b/site/content/docs/oap-channel.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2750, - description: 'Manage Channel CRs (Slack, browser, GitHub webhooks, schedules, ...).', + description: 'oap channel: Manage Channel CRs (Slack, browser, GitHub webhooks, schedules, ...). Every subcommand, flag, and example, generated from the CLI.', } # oap channel diff --git a/site/content/docs/oap-check.mdx b/site/content/docs/oap-check.mdx index 057b610a..71b191c5 100644 --- a/site/content/docs/oap-check.mdx +++ b/site/content/docs/oap-check.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2760, - description: 'verify namespace, CRDs, services, and every registered component\'s health', + description: 'oap check: Verify namespace, CRDs, services, and every registered component\'s health. Every subcommand, flag, and example, generated from the CLI.', } # oap check diff --git a/site/content/docs/oap-class.mdx b/site/content/docs/oap-class.mdx index 52bd40ee..f4ae0c63 100644 --- a/site/content/docs/oap-class.mdx +++ b/site/content/docs/oap-class.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2770, - description: 'Inspect SpiceboxClass resources (sandbox image + capability descriptors)', + description: 'oap class: Inspect SpiceboxClass resources (sandbox image + capability descriptors). Every subcommand, flag, and example, generated from the CLI.', } # oap class diff --git a/site/content/docs/oap-clean.mdx b/site/content/docs/oap-clean.mdx index 2374713c..32cb81f8 100644 --- a/site/content/docs/oap-clean.mdx +++ b/site/content/docs/oap-clean.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2780, - description: 'Remove everything oap installed (CRs → operator → CRDs → namespace)', + description: 'oap clean: Remove everything oap installed (CRs → operator → CRDs → namespace). Every subcommand, flag, and example, generated from the CLI.', } # oap clean diff --git a/site/content/docs/oap-desktop.mdx b/site/content/docs/oap-desktop.mdx index 90c9d671..8aaec0cc 100644 --- a/site/content/docs/oap-desktop.mdx +++ b/site/content/docs/oap-desktop.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2790, - description: 'Run the oap macOS menubar app (local VM + built-in chat; macOS/Apple Silicon only)', + description: 'oap desktop: Run the oap macOS menubar app (local VM + built-in chat; macOS/Apple Silicon only). Every subcommand, flag, and example, generated from the CLI.', } # oap desktop diff --git a/site/content/docs/oap-directory.mdx b/site/content/docs/oap-directory.mdx index 55422c2a..45111251 100644 --- a/site/content/docs/oap-directory.mdx +++ b/site/content/docs/oap-directory.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2800, - description: 'Configure directory-sync sources (RelationshipSource): Slack, GitHub, 1Password, ...', + description: 'oap directory: Configure directory-sync sources (RelationshipSource): Slack, GitHub, 1Password, ... Every subcommand, flag, and example, generated from the CLI.', } # oap directory diff --git a/site/content/docs/oap-identity.mdx b/site/content/docs/oap-identity.mdx index cf1f626b..2f28de72 100644 --- a/site/content/docs/oap-identity.mdx +++ b/site/content/docs/oap-identity.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2810, - description: 'Manage AgentIdentity resources', + description: 'oap identity: Manage AgentIdentity resources. Every subcommand, flag, and example, generated from the CLI.', } # oap identity diff --git a/site/content/docs/oap-idp.mdx b/site/content/docs/oap-idp.mdx index bf09dd6a..e05ca2a7 100644 --- a/site/content/docs/oap-idp.mdx +++ b/site/content/docs/oap-idp.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2820, - description: 'Manage the cluster identity provider', + description: 'oap idp: Manage the cluster identity provider. Every subcommand, flag, and example, generated from the CLI.', } # oap idp diff --git a/site/content/docs/oap-image.mdx b/site/content/docs/oap-image.mdx index d7434cc3..0063252b 100644 --- a/site/content/docs/oap-image.mdx +++ b/site/content/docs/oap-image.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2830, - description: 'Work with local Docker images and the current cluster', + description: 'oap image: Work with local Docker images and the current cluster. Every subcommand, flag, and example, generated from the CLI.', } # oap image diff --git a/site/content/docs/oap-init.mdx b/site/content/docs/oap-init.mdx index f57d853b..147cdd67 100644 --- a/site/content/docs/oap-init.mdx +++ b/site/content/docs/oap-init.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2840, - description: 'Bring up agent-primitives end-to-end (build + install + check)', + description: 'oap init: Bring up agent-primitives end-to-end (build + install + check). Every subcommand, flag, and example, generated from the CLI.', } # oap init diff --git a/site/content/docs/oap-install.mdx b/site/content/docs/oap-install.mdx index fbe21abd..2ab8a461 100644 --- a/site/content/docs/oap-install.mdx +++ b/site/content/docs/oap-install.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2850, - description: 'Install the operator + CRDs into the configured cluster', + description: 'oap install: Install the operator + CRDs into the configured cluster. Every subcommand, flag, and example, generated from the CLI.', } # oap install diff --git a/site/content/docs/oap-kg.mdx b/site/content/docs/oap-kg.mdx index 6c1d5f15..70d58776 100644 --- a/site/content/docs/oap-kg.mdx +++ b/site/content/docs/oap-kg.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2860, - description: 'Query the knowledge graph', + description: 'oap kg: Query the knowledge graph. Every subcommand, flag, and example, generated from the CLI.', } # oap kg diff --git a/site/content/docs/oap-login.mdx b/site/content/docs/oap-login.mdx index 999979bc..d9c0941b 100644 --- a/site/content/docs/oap-login.mdx +++ b/site/content/docs/oap-login.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2870, - description: 'Log in to this cluster\'s identity provider and cache the assertion', + description: 'oap login: Log in to this cluster\'s identity provider and cache the assertion. Every subcommand, flag, and example, generated from the CLI.', } # oap login diff --git a/site/content/docs/oap-logout.mdx b/site/content/docs/oap-logout.mdx index c2eb0501..ed2de59d 100644 --- a/site/content/docs/oap-logout.mdx +++ b/site/content/docs/oap-logout.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2880, - description: 'Remove the cached identity assertion', + description: 'oap logout: Remove the cached identity assertion. Every subcommand, flag, and example, generated from the CLI.', } # oap logout diff --git a/site/content/docs/oap-memory.mdx b/site/content/docs/oap-memory.mdx index 070eec88..5d83fd44 100644 --- a/site/content/docs/oap-memory.mdx +++ b/site/content/docs/oap-memory.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2890, - description: 'Inspect and manage session memory entries', + description: 'oap memory: Inspect and manage session memory entries. Every subcommand, flag, and example, generated from the CLI.', } # oap memory diff --git a/site/content/docs/oap-pin.mdx b/site/content/docs/oap-pin.mdx index 0b66c756..b12a0975 100644 --- a/site/content/docs/oap-pin.mdx +++ b/site/content/docs/oap-pin.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2900, - description: 'Inspect and update dependency pin baselines', + description: 'oap pin: Inspect and update dependency pin baselines. Every subcommand, flag, and example, generated from the CLI.', } # oap pin diff --git a/site/content/docs/oap-plangate.mdx b/site/content/docs/oap-plangate.mdx index 2aecf654..ebc23eb4 100644 --- a/site/content/docs/oap-plangate.mdx +++ b/site/content/docs/oap-plangate.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2910, - description: 'Inspect what the plan gate recorded for a session', + description: 'oap plangate: Inspect what the plan gate recorded for a session. Every subcommand, flag, and example, generated from the CLI.', } # oap plangate diff --git a/site/content/docs/oap-platform.mdx b/site/content/docs/oap-platform.mdx index 6492e1cb..5df0c6d5 100644 --- a/site/content/docs/oap-platform.mdx +++ b/site/content/docs/oap-platform.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2920, - description: 'Manage platform-level admin access (the admin UI gate)', + description: 'oap platform: Manage platform-level admin access (the admin UI gate). Every subcommand, flag, and example, generated from the CLI.', } # oap platform diff --git a/site/content/docs/oap-preferences.mdx b/site/content/docs/oap-preferences.mdx index 08eebc36..471c3eba 100644 --- a/site/content/docs/oap-preferences.mdx +++ b/site/content/docs/oap-preferences.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2930, - description: 'Inspect a session\'s resolved per-user preferences', + description: 'oap preferences: Inspect a session\'s resolved per-user preferences. Every subcommand, flag, and example, generated from the CLI.', } # oap preferences diff --git a/site/content/docs/oap-sandbox.mdx b/site/content/docs/oap-sandbox.mdx index ffc4534b..b21e1f50 100644 --- a/site/content/docs/oap-sandbox.mdx +++ b/site/content/docs/oap-sandbox.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2940, - description: 'Inspect SpiceboxSession resources (per-bundle running sandbox pods)', + description: 'oap sandbox: Inspect SpiceboxSession resources (per-bundle running sandbox pods). Every subcommand, flag, and example, generated from the CLI.', } # oap sandbox diff --git a/site/content/docs/oap-session.mdx b/site/content/docs/oap-session.mdx index c4c8ca22..c9fcc591 100644 --- a/site/content/docs/oap-session.mdx +++ b/site/content/docs/oap-session.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2950, - description: 'Inspect AgentSession resources', + description: 'oap session: Inspect AgentSession resources. Every subcommand, flag, and example, generated from the CLI.', } # oap session diff --git a/site/content/docs/oap-settings.mdx b/site/content/docs/oap-settings.mdx index 2ad1b7a4..2c62ec51 100644 --- a/site/content/docs/oap-settings.mdx +++ b/site/content/docs/oap-settings.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2960, - description: 'Manage cluster-wide agent settings (security defaults wizard + apply).', + description: 'oap settings: Manage cluster-wide agent settings (security defaults wizard + apply). Every subcommand, flag, and example, generated from the CLI.', } # oap settings diff --git a/site/content/docs/oap-skill.mdx b/site/content/docs/oap-skill.mdx index 6030c452..f3ca27c8 100644 --- a/site/content/docs/oap-skill.mdx +++ b/site/content/docs/oap-skill.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2970, - description: 'List, view, create, and delete agent Skills and SkillSources', + description: 'oap skill: List, view, create, and delete agent Skills and SkillSources. Every subcommand, flag, and example, generated from the CLI.', } # oap skill diff --git a/site/content/docs/oap-spicedb.mdx b/site/content/docs/oap-spicedb.mdx index 51c82228..f9595fd0 100644 --- a/site/content/docs/oap-spicedb.mdx +++ b/site/content/docs/oap-spicedb.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2980, - description: 'Apply schema and check permissions against the system SpiceDB.', + description: 'oap spicedb: Apply schema and check permissions against the system SpiceDB. Every subcommand, flag, and example, generated from the CLI.', } # oap spicedb diff --git a/site/content/docs/oap-tools.mdx b/site/content/docs/oap-tools.mdx index a334d5ef..0d2e17d1 100644 --- a/site/content/docs/oap-tools.mdx +++ b/site/content/docs/oap-tools.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 2990, - description: 'Manage tools used by agents (kind-agnostic)', + description: 'oap tools: Manage tools used by agents (kind-agnostic). Every subcommand, flag, and example, generated from the CLI.', } # oap tools diff --git a/site/content/docs/oap-user-identity.mdx b/site/content/docs/oap-user-identity.mdx index 16c58a05..b65e5360 100644 --- a/site/content/docs/oap-user-identity.mdx +++ b/site/content/docs/oap-user-identity.mdx @@ -3,7 +3,7 @@ export const meta = { section: 'Reference', group: 'CLI', order: 3000, - description: 'Manage UserIdentity resources (per-user credential catalogs)', + description: 'oap user-identity: Manage UserIdentity resources (per-user credential catalogs). Every subcommand, flag, and example, generated from the CLI.', } # oap user-identity From 820e64aa6648c916e7047cf4e107cb0b3204bec2 Mon Sep 17 00:00:00 2001 From: Sam Kim Date: Fri, 23 Oct 2026 13:22:30 -0700 Subject: [PATCH 2/7] Refresh site metadata; add structured data, share cards, and llms.txt - Site description and landing title match the redesigned hero. - JSON-LD: Organization, WebSite and SoftwareSourceCode on every page, plus TechArticle and BreadcrumbList on each guide. - A share card per guide (section, title, summary) instead of the one site-wide card. - Every guide as Markdown at /docs/.md, or /docs/ with Accept: text/markdown; /llms.txt indexes them and /llms-full.txt is the whole set. Text responses are noindex with a canonical Link. - pnpm check fails a guide without a description; the 404 page carries one noindex instead of a contradicting pair. Co-Authored-By: Claude Opus 5.5 (1M context) --- site/app/(landing)/Landing.tsx | 8 +- site/app/(landing)/page.tsx | 20 ++- site/app/docs/[slug]/opengraph-image.tsx | 62 ++++++++ site/app/docs/[slug]/page.tsx | 35 ++++- site/app/layout.tsx | 19 ++- site/app/llms-full.txt/route.ts | 17 ++ site/app/llms.txt/route.ts | 17 ++ site/app/md/[slug]/route.ts | 24 +++ site/app/not-found.tsx | 3 + site/app/opengraph-image.tsx | 58 ++----- site/components/Coverage.tsx | 4 +- site/components/JsonLd.tsx | 13 ++ site/components/OgCard.tsx | 56 +++++++ site/components/SiteFooter.tsx | 1 + site/lib/guides.ts | 7 +- site/lib/markdown-routes.ts | 30 ++++ site/lib/markdown.test.ts | 156 +++++++++++++++++++ site/lib/markdown.ts | 190 +++++++++++++++++++++++ site/lib/site.ts | 14 +- site/lib/structured-data.test.ts | 74 +++++++++ site/lib/structured-data.ts | 126 +++++++++++++++ site/next.config.mjs | 23 ++- site/scripts/check.ts | 9 ++ 23 files changed, 900 insertions(+), 66 deletions(-) create mode 100644 site/app/docs/[slug]/opengraph-image.tsx create mode 100644 site/app/llms-full.txt/route.ts create mode 100644 site/app/llms.txt/route.ts create mode 100644 site/app/md/[slug]/route.ts create mode 100644 site/components/JsonLd.tsx create mode 100644 site/components/OgCard.tsx create mode 100644 site/lib/markdown-routes.ts create mode 100644 site/lib/markdown.test.ts create mode 100644 site/lib/markdown.ts create mode 100644 site/lib/structured-data.test.ts create mode 100644 site/lib/structured-data.ts diff --git a/site/app/(landing)/Landing.tsx b/site/app/(landing)/Landing.tsx index fd9cf8ee..ab24a4c1 100644 --- a/site/app/(landing)/Landing.tsx +++ b/site/app/(landing)/Landing.tsx @@ -16,13 +16,11 @@ import { OapMark, OAP_MARK_PATHS } from "@/components/OapMark"; import { SiteFooter } from "@/components/SiteFooter"; import { THEME_MODES, type ThemeMode } from "@/components/ThemeToggle"; import { DiscordIcon, GitHubIcon } from "@/components/BrandIcons"; -import { DISCORD_URL } from "@/lib/site"; +import { DISCORD_URL, REPO_URL } from "@/lib/site"; import { getAsset } from "@/lib/manifest"; import { GeoFigure } from "./geo/GeoFigure"; import { OWASP, COVERAGE_LABEL, coverageSummary } from "./owasp"; -const REPO = "https://github.com/authzed/openagentprimitives"; - /* The page's copy follows the repository README: its order, its emphasis and * its claims. When the README's story changes, change this page to match. */ @@ -1143,7 +1141,7 @@ export function Landing() { Security Agent Builder - + @@ -1443,7 +1441,7 @@ export function Landing() { Read the docs - + Source diff --git a/site/app/(landing)/page.tsx b/site/app/(landing)/page.tsx index d2522d73..aebf1f43 100644 --- a/site/app/(landing)/page.tsx +++ b/site/app/(landing)/page.tsx @@ -1,16 +1,17 @@ import type { Metadata } from "next"; -import { OG_IMAGE, SITE_DESCRIPTION, SITE_NAME } from "@/lib/site"; +import { JsonLd } from "@/components/JsonLd"; +import { OG_IMAGE, SITE_DESCRIPTION, SITE_NAME, SITE_TITLE } from "@/lib/site"; +import { landingGraph } from "@/lib/structured-data"; import { Landing } from "./Landing"; import "./landing.css"; export const metadata: Metadata = { - title: { - absolute: "Open Agent Primitives: a secure way to run enterprise AI agents", - }, + title: { absolute: SITE_TITLE }, description: SITE_DESCRIPTION, - alternates: { canonical: "/" }, + // The docs index for LLMs and agents (app/llms.txt). + alternates: { canonical: "/", types: { "text/plain": "/llms.txt" } }, openGraph: { - title: SITE_NAME, + title: SITE_TITLE, description: SITE_DESCRIPTION, url: "/", siteName: SITE_NAME, @@ -21,5 +22,10 @@ export const metadata: Metadata = { }; export default function Page() { - return ; + return ( + <> + + + + ); } diff --git a/site/app/docs/[slug]/opengraph-image.tsx b/site/app/docs/[slug]/opengraph-image.tsx new file mode 100644 index 00000000..f0a18899 --- /dev/null +++ b/site/app/docs/[slug]/opengraph-image.tsx @@ -0,0 +1,62 @@ +import { ImageResponse } from "next/og"; +import { nbWrap, OG_INK, OG_SIZE, OgCard } from "@/components/OgCard"; +import { guideSlugs, loadGuide } from "@/lib/guides"; +import { plainText } from "@/lib/structured-data"; + +// One share card per guide, so a docs link pasted into Slack or a search +// result shows which page it is, not the site-wide card. Prerendered for +// every guide. The alt is static: Next 16 hands generateImageMetadata no +// params under a dynamic segment at build time, so it cannot name the page. +export const size = OG_SIZE; +export const contentType = "image/png"; +export const alt = "An Open Agent Primitives docs page title and summary"; + +export async function generateStaticParams() { + return (await guideSlugs()).map((slug) => ({ slug })); +} + +// Two lines of description at this size; longer ones end in an ellipsis. +const DESC_MAX = 150; + +export default async function GuideOgImage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { guide } = await loadGuide((await params).slug); + const kicker = guide.group + ? `${guide.section} · ${guide.group}` + : guide.section; + let desc = plainText(guide.description ?? ""); + if (desc.length > DESC_MAX) + desc = desc.slice(0, desc.lastIndexOf(" ", DESC_MAX)) + "…"; + return new ImageResponse( + +
+ {nbWrap(kicker)} +
+
28 ? 60 : 72, + lineHeight: 1.08, + letterSpacing: "-0.03em", + }} + > + {nbWrap(guide.title)} +
+ {desc && ( +
+ {nbWrap(desc)} +
+ )} +
, + size, + ); +} diff --git a/site/app/docs/[slug]/page.tsx b/site/app/docs/[slug]/page.tsx index ad7ecf89..5d24d09c 100644 --- a/site/app/docs/[slug]/page.tsx +++ b/site/app/docs/[slug]/page.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; +import { JsonLd } from "@/components/JsonLd"; import { guideSlugs, loadGuide } from "@/lib/guides"; -import { OG_IMAGE, SITE_NAME } from "@/lib/site"; +import { SITE_DESCRIPTION, SITE_NAME } from "@/lib/site"; +import { guideGraph, plainText } from "@/lib/structured-data"; // Every guide is prerendered; any other slug is a real 404, not a silent // fallback to the first guide. @@ -12,30 +14,49 @@ export async function generateStaticParams() { type Props = { params: Promise<{ slug: string }> }; +// pnpm check requires a description on every guide; the fallback is only so a +// page under construction still renders. +const describe = (description?: string) => + description ? plainText(description) : SITE_DESCRIPTION; + +// The share image is the sibling opengraph-image.tsx, which Next attaches to +// openGraph and twitter on its own. export async function generateMetadata({ params }: Props): Promise { const { slug } = await params; const { guide } = await loadGuide(slug); const url = `/docs/${slug}`; + const description = describe(guide.description); return { title: guide.title, - description: guide.description, - alternates: { canonical: url }, + description, + alternates: { + canonical: url, + types: { "text/markdown": `${url}.md` }, + }, openGraph: { title: `${guide.title} · OAP docs`, - description: guide.description, + description, url, siteName: SITE_NAME, type: "article", - images: [OG_IMAGE], + section: guide.group || guide.section, }, - twitter: { card: "summary_large_image", images: [OG_IMAGE.url] }, + twitter: { card: "summary_large_image" }, }; } export default async function GuidePage({ params }: Props) { - const { Component, guide } = await loadGuide((await params).slug); + const { slug } = await params; + const { Component, guide } = await loadGuide(slug); return (
+
); diff --git a/site/app/layout.tsx b/site/app/layout.tsx index 2421d61b..2395e34c 100644 --- a/site/app/layout.tsx +++ b/site/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata, Viewport } from "next"; import type { ReactNode } from "react"; import { ThemeProvider } from "next-themes"; import { Analytics } from "@/components/Analytics"; -import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/lib/site"; +import { PUBLISHER, SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/lib/site"; import iconDarkInk from "../../docs/assets/brand/oap-icon-dark.svg"; import iconLightInk from "../../docs/assets/brand/oap-icon-light.svg"; @@ -10,6 +10,23 @@ export const metadata: Metadata = { metadataBase: new URL(SITE_URL), title: { default: SITE_NAME, template: "%s · OAP docs" }, description: SITE_DESCRIPTION, + applicationName: SITE_NAME, + authors: [PUBLISHER], + creator: PUBLISHER.name, + publisher: PUBLISHER.name, + category: "technology", + // Let search show full snippets and large image previews. + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-snippet": -1, + "max-image-preview": "large", + "max-video-preview": -1, + }, + }, openGraph: { siteName: SITE_NAME, type: "website", diff --git a/site/app/llms-full.txt/route.ts b/site/app/llms-full.txt/route.ts new file mode 100644 index 00000000..89645250 --- /dev/null +++ b/site/app/llms-full.txt/route.ts @@ -0,0 +1,17 @@ +import { allGuides } from "@/lib/guides"; +import { renderGuideMarkdown, textResponse } from "@/lib/markdown-routes"; +import { SITE_DESCRIPTION, SITE_NAME } from "@/lib/site"; + +// Every guide's Markdown in one file, in sidebar order, for an agent that +// wants the whole docs set in a single fetch. +export const dynamic = "force-static"; + +export async function GET() { + const pages = await Promise.all( + (await allGuides()).map((g) => renderGuideMarkdown(g.slug)), + ); + const body = [`# ${SITE_NAME}\n\n> ${SITE_DESCRIPTION}\n`, ...pages].join( + "\n---\n\n", + ); + return textResponse(body, "text/plain"); +} diff --git a/site/app/llms.txt/route.ts b/site/app/llms.txt/route.ts new file mode 100644 index 00000000..e5525b6f --- /dev/null +++ b/site/app/llms.txt/route.ts @@ -0,0 +1,17 @@ +import { allGuides } from "@/lib/guides"; +import { llmsTxt } from "@/lib/markdown"; +import { textResponse } from "@/lib/markdown-routes"; +import { buildNav } from "@/lib/nav"; +import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/lib/site"; + +// The docs index for LLMs and agents (https://llmstxt.org). +export const dynamic = "force-static"; + +export async function GET() { + const body = llmsTxt(buildNav(await allGuides()), { + name: SITE_NAME, + description: SITE_DESCRIPTION, + siteUrl: SITE_URL, + }); + return textResponse(body, "text/plain"); +} diff --git a/site/app/md/[slug]/route.ts b/site/app/md/[slug]/route.ts new file mode 100644 index 00000000..23899a8a --- /dev/null +++ b/site/app/md/[slug]/route.ts @@ -0,0 +1,24 @@ +import { guideSlugs } from "@/lib/guides"; +import { renderGuideMarkdown, textResponse } from "@/lib/markdown-routes"; +import { SITE_URL } from "@/lib/site"; + +// The Markdown rendition of one guide. Reached as /docs/.md, or as +// /docs/ with `Accept: text/markdown`; both are rewrites in +// next.config.mjs. Prerendered like the HTML pages. +export const dynamicParams = false; + +export async function generateStaticParams() { + return (await guideSlugs()).map((slug) => ({ slug })); +} + +export async function GET( + _req: Request, + { params }: { params: Promise<{ slug: string }> }, +) { + const { slug } = await params; + return textResponse( + await renderGuideMarkdown(slug), + "text/markdown", + `${SITE_URL}/docs/${slug}`, + ); +} diff --git a/site/app/not-found.tsx b/site/app/not-found.tsx index b9059170..a51a8f61 100644 --- a/site/app/not-found.tsx +++ b/site/app/not-found.tsx @@ -4,6 +4,9 @@ import "./not-found.css"; export const metadata: Metadata = { title: { absolute: "Page not found · Open Agent Primitives" }, + // Next adds its own noindex here, but the root layout's index/follow would + // otherwise render beside it as a second, contradicting robots tag. + robots: { index: false, follow: true }, }; // Every unknown path lands here, including stale /docs/ bookmarks, so diff --git a/site/app/opengraph-image.tsx b/site/app/opengraph-image.tsx index b96132cb..51940a21 100644 --- a/site/app/opengraph-image.tsx +++ b/site/app/opengraph-image.tsx @@ -1,55 +1,31 @@ import { ImageResponse } from "next/og"; +import { nb, OG_INK, OG_SIZE, OgCard } from "@/components/OgCard"; import { OG_IMAGE } from "@/lib/site"; // The share card for every page that does not ship its own. Rendered once at -// build time. The logomark geometry is the same as components/OapMark.tsx. +// build time. export const alt = OG_IMAGE.alt; -export const size = { width: 1200, height: 630 }; +export const size = OG_SIZE; export const contentType = "image/png"; -// The renderer's default font sets an ordinary space far wider than the -// non-breaking one, so the headline's spaces are swapped before rendering. -const nb = (text: string) => text.replaceAll(" ", "\u00a0"); - export default function OpengraphImage() { return new ImageResponse( -
- - - - - -
-
-
{nb("A secure way to run")}
-
- {nb("enterprise AI agents.")} -
-
-
- Open Agent Primitives · openap.org + +
+
{nb("A secure way to run")}
+
+ {nb("enterprise AI agents.")}
-
, + , size, ); } diff --git a/site/components/Coverage.tsx b/site/components/Coverage.tsx index 765aba97..f9b4c996 100644 --- a/site/components/Coverage.tsx +++ b/site/components/Coverage.tsx @@ -1,5 +1,5 @@ // A coverage badge for the OWASP page — the four levels the coverage doc uses. -const LABELS: Record = { +export const COVERAGE_LABELS: Record = { substantial: "Substantial", partial: "Partial", minimal: "Minimal", @@ -9,7 +9,7 @@ const LABELS: Record = { export function Coverage({ level }: { level: string }) { return ( - {LABELS[level] ?? level} + {COVERAGE_LABELS[level] ?? level} ); } diff --git a/site/components/JsonLd.tsx b/site/components/JsonLd.tsx new file mode 100644 index 00000000..9afdd2b5 --- /dev/null +++ b/site/components/JsonLd.tsx @@ -0,0 +1,13 @@ +// A schema.org JSON-LD block (lib/structured-data.ts). JSON.stringify leaves +// "<" alone, so a description containing "" would end the element +// early; escaping it keeps the payload inert. +export function JsonLd({ data }: { data: object }) { + return ( +