Machine Mode Package
Add an LLM-friendly plain text view to any Next.js App Router site.
Overview
@prototyperco/machine-mode is an installable runtime that adds a "machine mode" to any Next.js App Router site. It gives every page a URL-driven plain text view that LLMs can read, with a full-page shell UI for humans to preview what machines see.
Features:
- URL-driven mode switching (
?view=machine) - Rendered/raw format toggle (
?format=rendered|raw) - Full-page machine shell UI with utility bar and copy action
- Server route helpers for
/machine/[...slug]and/machine.txt - Keyboard shortcuts (
Shift+Mto toggle,Rto switch format) - Client-side text caching and markdown rendering
Requirements
- Next.js 15 or 16 (App Router)
- React 19+
- Tailwind CSS v4
Entry Points
The package provides four entry points:
| Entry point | Environment | Description |
|---|---|---|
@prototyperco/machine-mode | Both | Barrel re-export of client + server |
@prototyperco/machine-mode/client | Client | Provider, gate, shell, toggle, hooks |
@prototyperco/machine-mode/server | Server | Route handlers, resolver types |
@prototyperco/machine-mode/styles.css | CSS | Machine mode theme tokens and utilities |
Quick Start (CLI)
bunx @prototyperco/cli machine-mode initThis scaffolds:
lib/machine-resolver.tsapp/machine/[...slug]/route.tsapp/machine.txt/route.tsapp/layout.tsxwiring withMachineModeProvider+MachineGateglobals.cssimport for@prototyperco/machine-mode/styles.css
Manual Setup
Install package
pnpm add @prototyperco/machine-modenpm install @prototyperco/machine-modeyarn add @prototyperco/machine-modebun add @prototyperco/machine-modeImport styles
@import "@prototyperco/machine-mode/styles.css";Wire layout
import { Suspense } from "react";
import {
MachineGate,
MachineModeProvider,
} from "@prototyperco/machine-mode/client";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Suspense>
<MachineModeProvider>
<MachineGate>{children}</MachineGate>
</MachineModeProvider>
</Suspense>
</body>
</html>
);
}MachineModeProvider uses useSearchParams() internally and must be wrapped in a Suspense boundary. It reads ?view=machine from the URL and provides mode state to the tree. MachineGate conditionally renders either your normal page or the MachineShell overlay.
Create a resolver
The resolver maps pathnames to machine-readable content. It can be sync or async.
import type { MachineDoc } from "@prototyperco/machine-mode/server";
export async function resolveMachineDoc(
pathname: string,
): Promise<MachineDoc | null> {
if (pathname === "/") {
return {
pathname,
title: "Home",
sourceUrl: pathname,
content: "# Home\n\nWelcome to this site in machine mode.",
contentType: "curated",
generatedAt: new Date().toISOString(),
};
}
return null;
}Add route handlers
import { createMachineRouteHandler } from "@prototyperco/machine-mode/server";
import { resolveMachineDoc } from "@/lib/machine-resolver";
export const GET = createMachineRouteHandler({ resolveDoc: resolveMachineDoc });import { createMachineIndexHandler } from "@prototyperco/machine-mode/server";
export const GET = createMachineIndexHandler();Resolver Contract
Your resolver receives a normalized pathname and returns either:
- A
MachineDocobject for known routes nullto use the built-in fallback content
The resolver can be synchronous or asynchronous:
type MachineDocResolver = (
pathname: string,
) => MachineDoc | null | undefined | Promise<MachineDoc | null | undefined>;interface MachineDoc {
pathname: string;
title: string;
sourceUrl: string;
content: string; // markdown string
contentType: "docs" | "curated" | "fallback" | string;
generatedAt: string; // ISO 8601
}When the resolver returns null, the route handler uses createFallbackMachineDoc() to generate a placeholder response that links back to the human page, the machine index, and the LLM index.
API Reference
Client Exports
Imported from @prototyperco/machine-mode/client.
MachineModeProvider
Reads ?view=machine and ?format=rendered|raw from the URL and provides mode context to the component tree. Registers the Shift+M keyboard shortcut.
Prop
Type
MachineGate
Renders either children (human mode) or MachineShell (machine mode), with enter/exit transitions.
Prop
Type
MachineShell
Full-page machine view overlay with utility bar, rendered/raw viewport, copy button, and metadata footer. Fetches content from the /machine/[...slug] route.
Prop
Type
ModeToggle
Floating or inline Human/Machine segmented control.
Prop
Type
useMachineMode
Hook that returns the current mode context:
function useMachineMode(): {
mode: "human" | "machine";
setMode: (mode: "human" | "machine") => void;
ready: boolean;
config: MachineModeConfig;
};useMode is an alias for useMachineMode.
MachineModeConfig contains the resolved provider options:
interface MachineModeConfig {
viewQueryParam: string;
formatQueryParam: string;
machineViewValue: string;
defaultRenderMode: "rendered" | "raw";
machineEndpointBasePath: string;
}renderMachineMarkdown
Parses a markdown string into a styled React node for the machine shell viewport.
function renderMachineMarkdown(markdown: string): {
node: React.ReactNode;
error: string | null;
};Utility Functions
// Client-side text cache
function getCachedText(key: string): string | undefined;
function setCachedText(key: string, value: string): void;
function fetchCachedText(
url: string,
options?: { signal?: AbortSignal },
): Promise<string>;
// View/render mode parsing
function parseMachineViewMode(
value: string | null,
machineViewValue?: string,
): "human" | "machine" | null;
function parseMachineRenderMode(
value: string | null,
): "rendered" | "raw" | null;
function isEditableElementTarget(target: EventTarget | null): boolean;Pathname Helpers
Shared between client and server:
function normalizeWebsitePathname(pathname: string): string;
function normalizeMachineBasePath(basePath: string): string;
function toMachineEndpointPath(
pathname: string,
machineBasePath?: string,
): string;
function machineSegmentsToWebsitePathname(segments: string[]): string;Exported Types
The client entry also exports these TypeScript types:
MachineViewMode--"human" | "machine"MachineRenderMode--"rendered" | "raw"MachineModeConfig-- Resolved provider configuration (seeuseMachineModeabove)MachineModeProviderProps-- Props forMachineModeProviderMachineMarkdownRenderResult-- Return type ofrenderMachineMarkdown
Server Exports
Imported from @prototyperco/machine-mode/server.
createMachineRouteHandler
Creates a Next.js route handler for app/machine/[...slug]/route.ts. Calls your resolver with the normalized pathname and returns the content as text/plain.
Prop
Type
Response headers include X-Machine-Title, X-Machine-Source-Url, X-Machine-Content-Type, and X-Machine-Generated-At.
createMachineIndexHandler
Creates a Next.js route handler for app/machine.txt/route.ts. Returns a plain text index of machine-mode URLs and endpoints.
Prop
Type
buildMachineIndexText
Generates the machine index text content without wrapping it in a route handler. Accepts the same options as createMachineIndexHandler (minus cacheControl and buildIndexText).
createFallbackMachineDoc
Creates a fallback MachineDoc for routes the resolver does not handle:
function createFallbackMachineDoc(
pathname: string,
options?: {
siteUrl?: string;
machineIndexPath?: string;
llmsIndexPath?: string;
},
): MachineDoc;Exported Types
The server entry also exports these TypeScript types:
MachineDoc-- Machine document shape returned by resolversMachineContentType--"docs" | "curated" | "fallback" | (string & {})MachineDocResolver-- Resolver function signatureMachineRouteHandlerOptions-- Options forcreateMachineRouteHandlerMachineIndexHandlerOptions-- Options forcreateMachineIndexHandler
Keyboard Interactions
| Key | Context | Action |
|---|---|---|
Shift+M | Any non-editable element | Toggle between human and machine mode |
R | Machine mode, non-editable | Switch between rendered and raw format |
Both shortcuts are disabled when focus is on an <input>, <textarea>, <select>, or contentEditable element.
Styling
The styles from @prototyperco/machine-mode/styles.css define machine-specific design tokens using OKLCH color values. The same tokens apply in both light and dark mode (machine mode is always dark).
| Token | Default | Purpose |
|---|---|---|
--machine-bg | 2.8% 0 0 | Shell background |
--machine-panel | 4.4% 0 0 | Panel/header background |
--machine-panel-raised | 7.2% 0 0 | Raised panel background |
--machine-fg | 98% 0 0 | Primary text |
--machine-fg-muted | 88% 0 0 | Body text |
--machine-fg-subtle | 72% 0 0 | De-emphasized text |
--machine-border | 100% 0 0 / 0.22 | Border color |
--machine-rule | 100% 0 0 / 0.3 | Separator/rule color |
--machine-inverse | 96% 0 0 | Inverse background (active toggle) |
--machine-inverse-fg | 8% 0 0 | Inverse foreground text |
--machine-font-size-base | 0.95rem | Base font size |
--machine-line-height-base | 1.7 | Base line height |
--machine-heading-weight | 640 | Heading font weight |
--machine-code-size | 0.84rem | Code block font size |
--machine-content-max | 76ch | Max content width |
--machine-section-gap | 2.5rem | Gap before h2 headings |
--machine-block-gap | 1rem | Gap between block elements |
--machine-rail-offset | clamp(0.9rem, 2vw, 1.45rem) | Horizontal content padding |
Override any token in your own CSS to customize the machine mode appearance.
Migration From Local Implementation
- Install
@prototyperco/machine-mode. - Move app-specific content resolution into
lib/machine-resolver.ts. - Replace local mode provider/gate/shell imports with package imports from
@prototyperco/machine-mode/client. - Replace local route handler logic with
createMachineRouteHandlerandcreateMachineIndexHandlerfrom@prototyperco/machine-mode/server. - Replace local machine CSS with
@import "@prototyperco/machine-mode/styles.css". - Keep existing
/llms*endpoints untouched -- they are separate from machine mode.
Notes
- App Router only (Pages Router is not supported).
- Machine mode is explicit via URL query params -- no bot/user-agent auto-detection.
- The package is additive; your existing component workflow stays the same.
- The barrel entry point (
@prototyperco/machine-mode) re-exports everything from both/clientand/server. Use the sub-path imports when you need tree-shaking or want to be explicit about the environment.
CLI Reference
Complete reference for the Prototyper UI CLI — init, add, search, update, browse, diff, inspect, props, suggest, audit, scaffold, migrate, registry, theme, mcp, doctor, and machine-mode commands.
Theming
Customize colors, surfaces, and shadows using OKLCH tokens and CSS custom properties.