// june.config.ts support — the resource manifest / feature config. // // The agent surface (discovery + MCP) is ON by default: it exposes the same // actions and the same authorization the UI already exposes, so it does not // widen the attack surface. Gate sensitive actions with permissions, not by // hiding the endpoint. Turn any of it off here when you must. // // PURITY: this module is the config SCHEMA and its pure resolvers only. The // `loadJuneConfig(appDir)` reader (node:fs / node:path / dynamic import of the // user's june.config.ts) is a HOST concern and lives in the Phase-2 host layer // — keeping the contract layer free of `node:*` (zero node:*/Bun.* in this layer). import type { CacheStoreFactory } from "./cache"; import type { I18nConfig } from "./i18n"; import type { JuneDb, ResourceConfig } from "./resources"; import { validateOriginPolicy } from "./origin-policy"; // The DURABLE conversational agent (the agent/ directory). Distinct from the // discoverability flags above, which make a web app agent-readable; this mounts a // running agent: its chat endpoint + inbound channels. Its tools are the SAME // defineActions the discovery/mcp surface already exposes. Off unless an agent/ // directory + `runtime.enabled` are present. // Site-wide identity the document's is built from (see JuneConfig.site). // url the public origin ("https://acme.com"), used to make og:url, canonical, // and og:image absolute. Unset → https://, else the // request's own origin — never the build's prerender host. An i18n // locale's own domain always wins for its pages. // twitter the site's X/Twitter handle ("@acme") → twitter:site. // themeColor the mobile browser-toolbar colour (): // one colour, or { light, dark } for a page that follows the system // colour scheme. Unset → June's starter background when June owns the // page's look (see Document), else no tag. // shortName the home-screen label in the generated web manifest. Unset → the // part of `name` before a " — ", " - ", " | ", or ": " separator. // organization who runs the site → an Organization node in the homepage JSON-LD // (the WebSite's publisher): the machine-readable identity agents and // search engines verify a site against. `email` becomes a ContactPoint; // `sameAs` lists official profiles (GitHub, LinkedIn, …) and is merged // with the `twitter` handle's profile URL. Unset → no Organization node: // June never invents who runs a site. // jsonLd extra schema.org nodes (SoftwareApplication, FAQPage, Product, …) // appended to the homepage's JSON-LD @graph (a node's own top-level // @context is dropped; the graph carries one). They can reference the // built-in nodes by @id: "#website", "#organization", // where is the public origin + basePath + "/" // (e.g. "https://acme.github.io/docs/#website"). export type SiteConfig = { name?: string; shortName?: string; titleTemplate?: string; description?: string; icon?: string; lang?: string; url?: string; twitter?: string; themeColor?: string | { light: string; dark: string }; organization?: SiteOrganization; jsonLd?: JsonLdNode | JsonLdNode[]; }; // A schema.org node, as plain JSON (it freezes into the worker manifest). export type JsonLdNode = { "@type": string | string[] } & Record; // The Organization behind a site. Every field is optional: `name` defaults to // site.name, `url` to the site's public origin, `logo` to site.icon. Relative // URLs resolve against the public origin. export type SiteOrganization = { name?: string; url?: string; logo?: string; email?: string; // → contactPoint { contactType: "customer support", email } telephone?: string; // → the same ContactPoint sameAs?: string[]; address?: { streetAddress?: string; addressLocality?: string; addressRegion?: string; postalCode?: string; addressCountry?: string; }; }; // The site's short label: site.shortName, else the site name's leading segment // ("June — build agents into real apps" → "June"). Used wherever a tagline would // be wrong — the home-screen label, the MCP server's title. export function siteShortName(site: Pick): string | undefined { if (site.shortName) return site.shortName; const name = site.name?.trim(); if (!name) return undefined; return name.split(/\s+[—–|-]\s+|:\s+/)[0]!.trim() || name; } // DocumentConfig.deployOrigin from the config's deploy domain — the public-origin // fallback for prerendered pages, which render against a placeholder host. export function deployOrigin(cfg: Pick): string | undefined { return cfg.deploy?.domain ? `https://${cfg.deploy.domain}` : undefined; } export type AgentRuntimeConfig = { enabled: boolean; // mount the durable agent surface dir: string; // the agent/ directory, relative to the app (default "agent") backend: "native" | "memory" | "durable"; // native = SQLite; memory = ephemeral; durable = DO (edge) chat: { path: string }; // where a turn is POSTed (default "/message") channels: boolean; // mount discovered channels/* at their paths (slack/crisp/…) }; export type AgentConfig = { enabled: boolean; // master switch discovery: boolean; // Link header, llms.txt, sitemap, api-catalog, mcp server-card, ai-catalog (ARD), agent skills mcp: boolean; // the /mcp execution endpoint api: boolean; // the REST projection: POST /api/ + /openapi.json webmcp: boolean; // inject WebMCP tool registrations into the view // Which browsers may call /mcp and /api/ (#308; see OriginPolicy in ./origin-policy). // allowedOrigins — origins allowed besides the site's own, e.g. a separate web app // that calls the API from the browser. A request without an // Origin (CLI, SDK, server-side connector) always passes. // allowedHosts — when set, the Host must be an IP literal or match an entry // ("example.com", or ".example.com" with its subdomains): the // DNS-rebinding check. `june dev` adds "localhost" and ".localhost"; // add a tunnel's name (".trycloudflare.com") to reach dev through it. allowedOrigins?: string[]; allowedHosts?: string[]; // Optional llms.txt customization for apps built ON June (e.g. the Kura docs framework): // framework — replaces the built-in "canonical names" block so a meta-framework can point // agents at ITS scaffold/scope instead of June's. // sections — extra Markdown lines appended (e.g. a list of every doc page + its `.md`). // whenToUse — the jobs this site/product is the right tool for, one bullet each, rendered // as "## When to use" right under the summary: an agent choosing between // tools reads it first. Specific use cases, not marketing copy. // All plain string arrays so they freeze into the worker manifest as-is. llms?: { framework?: string[]; sections?: string[]; whenToUse?: string[] }; // The /mcp server's identity — the initialize handshake (serverInfo + instructions) // and the server card. Every field is optional; unset ones are derived from `site` // and the app's tools (see mcpServerIdentity in ./mcp): // name serverInfo.name and the card's reverse-DNS name. Default // "/" (june.build → "build.june/june"). // title the display name. Default: the site's short name. // description the server card's one-liner (≤ 100 chars, the v1 schema's limit). // Default: site.description, trimmed at a sentence or word boundary. // version Default "0.0.0" — set it to your app's release version. // instructions the usage guidance clients hand the model. Default: generated from // the FULL site.description, the tool list, and the llms.txt / .md pointers. // Overrides are validated against the v1 Server Card schema when the config resolves // (see validateMcpServer), so a bad value fails the build, not a client. mcpServer?: { name?: string; title?: string; description?: string; version?: string; instructions?: string }; // The durable agent runtime (opt-in). Resolved to full shape below. runtime: AgentRuntimeConfig; }; export type SpeculationConfig = { // hover-intent prerender: "moderate" (hover) | "conservative" (mousedown, // for heavy pages) | false. Default "moderate" — light MPA pages get 0ms // navigations for free. prerender?: "moderate" | "conservative" | false; prefetch?: "moderate" | "conservative" | false; // App-specific exclusions, ADDED to the built-in ones (agent surfaces: // *.md *.json *.txt *.xml /mcp — those are always excluded). exclude?: string[]; // "inline" (default): rules in a