diff options
22 files changed, 3934 insertions, 0 deletions
diff --git a/.thumbnail b/.thumbnail Binary files differnew file mode 100644 index 0000000..12df967 --- /dev/null +++ b/.thumbnail diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json new file mode 100644 index 0000000..d745f07 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json @@ -0,0 +1,425 @@ +{ + "plugins": [ + "react", + "import" + ], + "rules": { + "react/forbid-elements": [ + "warn", + { + "forbid": [] + } + ], + "no-restricted-imports": [ + "warn", + { + "patterns": [ + { + "group": [ + "components/code/**", + "components/core/**", + "components/forms/**", + "components/navigation/**", + "ui_kits/docs/**", + "ui_kits/home/**" + ], + "message": "Import design-system components from 'index.js', not component internals." + } + ] + } + ], + "no-restricted-syntax": [ + "warn", + { + "selector": "Literal[value=/#[0-9a-fA-F]{3,8}\\b/]", + "message": "Raw hex color — use a design-system color token via var()." + }, + { + "selector": "Literal[value=/\\b\\d+px\\b/]", + "message": "Raw px value — use a design-system spacing token via var()." + }, + { + "selector": "Literal[value=/font-family\\s*:\\s*(?!['\\\"]?(?:IBM Plex Mono|Saira Condensed))/i]", + "message": "Font not provided by the design system. Available: IBM Plex Mono, Saira Condensed." + }, + { + "selector": "JSXOpeningElement[name.name='Badge'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|dot|children|key|ref|className|style|children)$/]", + "message": "<Badge> doesn't accept that prop. Declared props: variant, dot, children." + }, + { + "selector": "JSXOpeningElement[name.name='Badge'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:neutral|danger|success|ink|outline)$/]", + "message": "<Badge> variant must be one of 'neutral' | 'danger' | 'success' | 'ink' | 'outline'." + }, + { + "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|size|block|icon|iconRight|as|children|key|ref|className|style|children)$/]", + "message": "<Button> doesn't accept that prop. Declared props: variant, size, block, icon, iconRight, as, children." + }, + { + "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:primary|accent|secondary|ghost|danger)$/]", + "message": "<Button> variant must be one of 'primary' | 'accent' | 'secondary' | 'ghost' | 'danger'." + }, + { + "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]", + "message": "<Button> size must be one of 'sm' | 'md' | 'lg'." + }, + { + "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='as'] > Literal[value!=/^(?:button|a)$/]", + "message": "<Button> as must be one of 'button' | 'a'." + }, + { + "selector": "JSXOpeningElement[name.name='Callout'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|title|icon|children|key|ref|className|style|children)$/]", + "message": "<Callout> doesn't accept that prop. Declared props: variant, title, icon, children." + }, + { + "selector": "JSXOpeningElement[name.name='Callout'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:note|tip|warn|danger)$/]", + "message": "<Callout> variant must be one of 'note' | 'tip' | 'warn' | 'danger'." + }, + { + "selector": "JSXOpeningElement[name.name='Card'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|ruled|interactive|title|actions|footer|children|key|ref|className|style|children)$/]", + "message": "<Card> doesn't accept that prop. Declared props: variant, ruled, interactive, title, actions, footer, children." + }, + { + "selector": "JSXOpeningElement[name.name='Card'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:default|flat|sunken|accent)$/]", + "message": "<Card> variant must be one of 'default' | 'flat' | 'sunken' | 'accent'." + }, + { + "selector": "JSXOpeningElement[name.name='CodeBlock'] > JSXAttribute > JSXIdentifier[name!=/^(?:code|language|filename|showBar|children|key|ref|className|style|children)$/]", + "message": "<CodeBlock> doesn't accept that prop. Declared props: code, language, filename, showBar, children." + }, + { + "selector": "JSXOpeningElement[name.name='IconButton'] > JSXAttribute > JSXIdentifier[name!=/^(?:size|bordered|active|label|children|key|ref|className|style|children)$/]", + "message": "<IconButton> doesn't accept that prop. Declared props: size, bordered, active, label, children." + }, + { + "selector": "JSXOpeningElement[name.name='IconButton'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]", + "message": "<IconButton> size must be one of 'sm' | 'md' | 'lg'." + }, + { + "selector": "JSXOpeningElement[name.name='Input'] > JSXAttribute > JSXIdentifier[name!=/^(?:label|hint|error|icon|mono|required|key|ref|className|style|children)$/]", + "message": "<Input> doesn't accept that prop. Declared props: label, hint, error, icon, mono, required." + }, + { + "selector": "JSXOpeningElement[name.name='SelectOption'] > JSXAttribute > JSXIdentifier[name!=/^(?:value|label|key|ref|className|style|children)$/]", + "message": "<SelectOption> doesn't accept that prop. Declared props: value, label." + }, + { + "selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute > JSXIdentifier[name!=/^(?:checked|defaultChecked|label|key|ref|className|style|children)$/]", + "message": "<Switch> doesn't accept that prop. Declared props: checked, defaultChecked, label." + }, + { + "selector": "JSXOpeningElement[name.name='TabItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:id|label|icon|key|ref|className|style|children)$/]", + "message": "<TabItem> doesn't accept that prop. Declared props: id, label, icon." + }, + { + "selector": "JSXOpeningElement[name.name='Tag'] > JSXAttribute > JSXIdentifier[name!=/^(?:dot|onRemove|interactive|children|key|ref|className|style|children)$/]", + "message": "<Tag> doesn't accept that prop. Declared props: dot, onRemove, interactive, children." + }, + { + "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute > JSXIdentifier[name!=/^(?:type|text|shell|key|ref|className|style|children)$/]", + "message": "<TerminalLine> doesn't accept that prop. Declared props: type, text, shell." + }, + { + "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute[name.name='type'] > Literal[value!=/^(?:cmd|out|ok|err|com)$/]", + "message": "<TerminalLine> type must be one of 'cmd' | 'out' | 'ok' | 'err' | 'com'." + }, + { + "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute[name.name='shell'] > Literal[value!=/^(?:shell|panto)$/]", + "message": "<TerminalLine> shell must be one of 'shell' | 'panto'." + } + ] + }, + "overrides": [ + { + "files": [ + "**/index.js" + ], + "rules": { + "no-restricted-imports": "off" + } + } + ], + "x-omelette": { + "components": { + "Badge": { + "replaces": [] + }, + "Button": { + "replaces": [] + }, + "Callout": { + "replaces": [] + }, + "Card": { + "replaces": [] + }, + "CodeBlock": { + "replaces": [] + }, + "IconButton": { + "replaces": [] + }, + "Input": { + "replaces": [] + }, + "SelectOption": { + "replaces": [] + }, + "Switch": { + "replaces": [] + }, + "TabItem": { + "replaces": [] + }, + "Tag": { + "replaces": [] + }, + "TerminalLine": { + "replaces": [] + } + }, + "tokens": [ + "--accent", + "--accent-deep", + "--border-default", + "--border-hairline", + "--border-medium", + "--border-strong", + "--border-thick", + "--border-thin", + "--brass", + "--container-prose", + "--container-wide", + "--dur-base", + "--dur-fast", + "--dur-slow", + "--ease-out", + "--ease-slide", + "--focus-ring", + "--font-display", + "--font-mono", + "--font-primary", + "--font-sans", + "--font-serif", + "--green", + "--green-deep", + "--green-soft", + "--green-wash", + "--ink-0", + "--ink-1", + "--ink-2", + "--ink-3", + "--ink-4", + "--leading-mono", + "--leading-normal", + "--leading-relaxed", + "--leading-snug", + "--leading-tight", + "--link", + "--link-hover", + "--paper-0", + "--paper-1", + "--paper-2", + "--paper-3", + "--paper-4", + "--radius-lg", + "--radius-md", + "--radius-none", + "--radius-pill", + "--radius-sm", + "--radius-xs", + "--red", + "--red-deep", + "--red-soft", + "--red-wash", + "--role-body", + "--role-code", + "--role-display", + "--role-h1", + "--role-h2", + "--role-h3", + "--role-label", + "--role-prose", + "--rule-tick", + "--shadow-inset", + "--shadow-lg", + "--shadow-md", + "--shadow-sm", + "--shadow-xs", + "--sidebar-width", + "--space-0", + "--space-1", + "--space-2", + "--space-3", + "--space-4", + "--space-5", + "--space-6", + "--space-7", + "--space-8", + "--space-9", + "--status-danger", + "--status-danger-bg", + "--status-info", + "--status-info-bg", + "--status-success", + "--status-success-bg", + "--surface-card", + "--surface-code", + "--surface-inverse", + "--surface-page", + "--surface-sunken", + "--text-2xl", + "--text-2xs", + "--text-3xl", + "--text-4xl", + "--text-5xl", + "--text-base", + "--text-body", + "--text-faint", + "--text-lg", + "--text-md", + "--text-muted", + "--text-on-accent", + "--text-on-ink", + "--text-placeholder", + "--text-sm", + "--text-strong", + "--text-xl", + "--text-xs", + "--tick-gradient", + "--tick-h-major", + "--tick-h-minor", + "--tick-pitch", + "--tracking-label", + "--tracking-normal", + "--tracking-tight", + "--weight-bold", + "--weight-medium", + "--weight-regular", + "--weight-semibold" + ], + "tokenKinds": { + "--paper-0": "color", + "--paper-1": "color", + "--paper-2": "color", + "--paper-3": "color", + "--paper-4": "color", + "--ink-0": "color", + "--ink-1": "color", + "--ink-2": "color", + "--ink-3": "color", + "--ink-4": "color", + "--red-deep": "color", + "--red": "color", + "--red-soft": "color", + "--red-wash": "color", + "--green-deep": "color", + "--green": "color", + "--green-soft": "color", + "--green-wash": "color", + "--brass": "color", + "--surface-page": "color", + "--surface-card": "color", + "--surface-sunken": "color", + "--surface-code": "color", + "--surface-inverse": "color", + "--text-strong": "font", + "--text-body": "font", + "--text-muted": "font", + "--text-faint": "font", + "--text-placeholder": "font", + "--text-on-ink": "font", + "--text-on-accent": "font", + "--border-hairline": "color", + "--border-default": "color", + "--border-strong": "color", + "--rule-tick": "color", + "--accent": "color", + "--accent-deep": "color", + "--link": "color", + "--link-hover": "color", + "--status-danger": "color", + "--status-danger-bg": "color", + "--status-success": "color", + "--status-success-bg": "color", + "--status-info": "color", + "--status-info-bg": "color", + "--focus-ring": "color", + "--font-primary": "font", + "--font-mono": "font", + "--font-display": "font", + "--font-sans": "font", + "--font-serif": "font", + "--text-2xs": "font", + "--text-xs": "font", + "--text-sm": "font", + "--text-base": "font", + "--text-md": "font", + "--text-lg": "font", + "--text-xl": "font", + "--text-2xl": "font", + "--text-3xl": "font", + "--text-4xl": "font", + "--text-5xl": "font", + "--weight-regular": "font", + "--weight-medium": "font", + "--weight-semibold": "font", + "--weight-bold": "font", + "--leading-tight": "font", + "--leading-snug": "font", + "--leading-normal": "font", + "--leading-relaxed": "font", + "--leading-mono": "font", + "--tracking-label": "font", + "--tracking-tight": "font", + "--tracking-normal": "font", + "--role-display": "color", + "--role-h1": "color", + "--role-h2": "color", + "--role-h3": "color", + "--role-body": "color", + "--role-prose": "color", + "--role-code": "color", + "--role-label": "color", + "--space-0": "spacing", + "--space-1": "spacing", + "--space-2": "spacing", + "--space-3": "spacing", + "--space-4": "spacing", + "--space-5": "spacing", + "--space-6": "spacing", + "--space-7": "spacing", + "--space-8": "spacing", + "--space-9": "spacing", + "--radius-none": "radius", + "--radius-xs": "radius", + "--radius-sm": "radius", + "--radius-md": "radius", + "--radius-lg": "radius", + "--radius-pill": "radius", + "--border-thin": "spacing", + "--border-medium": "spacing", + "--border-thick": "spacing", + "--shadow-xs": "shadow", + "--shadow-sm": "shadow", + "--shadow-md": "shadow", + "--shadow-lg": "shadow", + "--shadow-inset": "shadow", + "--tick-pitch": "spacing", + "--tick-h-major": "spacing", + "--tick-h-minor": "color", + "--tick-gradient": "color", + "--ease-slide": "other", + "--ease-out": "other", + "--dur-fast": "other", + "--dur-base": "other", + "--dur-slow": "other", + "--container-prose": "spacing", + "--container-wide": "spacing", + "--sidebar-width": "spacing" + }, + "fontFamilies": [ + "IBM Plex Mono", + "Saira Condensed" + ] + } +}
\ No newline at end of file diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js new file mode 100644 index 0000000..f584806 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js @@ -0,0 +1,1878 @@ +/* @ds-bundle: {"format":3,"namespace":"PantographDesignSystem_56a1c7","components":[{"name":"Callout","sourcePath":"components/code/Callout.jsx"},{"name":"CodeBlock","sourcePath":"components/code/CodeBlock.jsx"},{"name":"Terminal","sourcePath":"components/code/Terminal.jsx"},{"name":"Badge","sourcePath":"components/core/Badge.jsx"},{"name":"Button","sourcePath":"components/core/Button.jsx"},{"name":"Card","sourcePath":"components/core/Card.jsx"},{"name":"IconButton","sourcePath":"components/core/IconButton.jsx"},{"name":"Tag","sourcePath":"components/core/Tag.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"}],"sourceHashes":{"components/code/Callout.jsx":"dc74992ca3a8","components/code/CodeBlock.jsx":"75f95cdb07d4","components/code/Terminal.jsx":"32207bf1f8d8","components/core/Badge.jsx":"c04491952135","components/core/Button.jsx":"c43631b0d758","components/core/Card.jsx":"b4474a239d87","components/core/IconButton.jsx":"bd55860ce51f","components/core/Tag.jsx":"c8a2e8432e9a","components/forms/Input.jsx":"4c58262a357e","components/forms/Select.jsx":"a2a13ccf0cb7","components/forms/Switch.jsx":"2737a3f2e747","components/navigation/Tabs.jsx":"d3f184460fa6","ui_kits/docs/App.jsx":"7a59cd523900","ui_kits/docs/DocsHeader.jsx":"2eb476264c51","ui_kits/docs/DocsSidebar.jsx":"a091bd208645","ui_kits/docs/ReferencePage.jsx":"d0855f29aced","ui_kits/home/Features.jsx":"48ac29e811d8","ui_kits/home/Hero.jsx":"2dc3333c9b3b","ui_kits/home/HomeApp.jsx":"f2a18138face","ui_kits/home/HomeHeader.jsx":"dfc98c37e66f","ui_kits/home/InstallSection.jsx":"aec2cdd7dae8"},"inlinedExternals":[],"unexposedExports":[]} */ + +(() => { + +const __ds_ns = (window.PantographDesignSystem_56a1c7 = window.PantographDesignSystem_56a1c7 || {}); + +const __ds_scope = {}; + +(__ds_ns.__errors = __ds_ns.__errors || []); + +// components/code/Callout.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-callout-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-callout { + display: flex; gap: 12px; + padding: 13px 15px; + background: var(--paper-0); + border: var(--border-thin) solid var(--border-hairline); + border-left: var(--border-thick) solid var(--ink-3); + border-radius: var(--radius-sm); + } + .pg-callout__icon { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--ink-3); } + .pg-callout__icon svg { width: 100%; height: 100%; } + .pg-callout__body { font: var(--weight-regular) var(--text-sm)/1.55 var(--font-sans); color: var(--ink-1); } + .pg-callout__title { + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; + margin: 0 0 5px; color: var(--ink-2); + } + .pg-callout__body :last-child { margin-bottom: 0; } + .pg-callout--note { border-left-color: var(--ink-3); } + .pg-callout--tip { border-left-color: var(--green); background: var(--green-wash); } + .pg-callout--tip .pg-callout__icon, .pg-callout--tip .pg-callout__title { color: var(--green-deep); } + .pg-callout--warn { border-left-color: var(--brass); background: #F0E7D0; } + .pg-callout--warn .pg-callout__icon, .pg-callout--warn .pg-callout__title { color: #7A5E20; } + .pg-callout--danger { border-left-color: var(--red); background: var(--red-wash); } + .pg-callout--danger .pg-callout__icon, .pg-callout--danger .pg-callout__title { color: var(--red-deep); } + `; + document.head.appendChild(el); +} +const ICONS = { + note: /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" + }, /*#__PURE__*/React.createElement("circle", { + cx: "12", + cy: "12", + r: "10" + }), /*#__PURE__*/React.createElement("path", { + d: "M12 16v-4" + }), /*#__PURE__*/React.createElement("path", { + d: "M12 8h.01" + })), + tip: /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" + }, /*#__PURE__*/React.createElement("path", { + d: "M9 18h6" + }), /*#__PURE__*/React.createElement("path", { + d: "M10 22h4" + }), /*#__PURE__*/React.createElement("path", { + d: "M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14" + })), + warn: /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" + }, /*#__PURE__*/React.createElement("path", { + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" + }), /*#__PURE__*/React.createElement("path", { + d: "M12 9v4" + }), /*#__PURE__*/React.createElement("path", { + d: "M12 17h.01" + })), + danger: /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" + }, /*#__PURE__*/React.createElement("circle", { + cx: "12", + cy: "12", + r: "10" + }), /*#__PURE__*/React.createElement("path", { + d: "m15 9-6 6" + }), /*#__PURE__*/React.createElement("path", { + d: "m9 9 6 6" + })) +}; +const DEFAULT_TITLES = { + note: 'Note', + tip: 'Tip', + warn: 'Warning', + danger: 'Danger' +}; + +/** + * Callout — a documentation admonition (note / tip / warn / danger). + */ +function Callout({ + variant = 'note', + title, + icon, + className = '', + children, + ...rest +}) { + ensureStyles(); + const heading = title === undefined ? DEFAULT_TITLES[variant] : title; + return /*#__PURE__*/React.createElement("div", _extends({ + className: ['pg-callout', `pg-callout--${variant}`, className].filter(Boolean).join(' ') + }, rest), /*#__PURE__*/React.createElement("span", { + className: "pg-callout__icon" + }, icon || ICONS[variant]), /*#__PURE__*/React.createElement("div", { + className: "pg-callout__body" + }, heading ? /*#__PURE__*/React.createElement("p", { + className: "pg-callout__title" + }, heading) : null, children)); +} +Object.assign(__ds_scope, { Callout }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/Callout.jsx", error: String((e && e.message) || e) }); } + +// components/code/CodeBlock.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-codeblock-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-code { + background: var(--surface-code); + border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-md); + overflow: hidden; + font-family: var(--font-mono); + } + .pg-code__bar { + display: flex; align-items: center; gap: 10px; + padding: 8px 12px; background: var(--paper-2); + border-bottom: var(--border-thin) solid var(--border-hairline); + } + .pg-code__name { font: var(--weight-medium) var(--text-xs)/1 var(--font-mono); color: var(--ink-2); } + .pg-code__lang { + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-3); + margin-left: auto; padding-right: 4px; + } + .pg-code__copy { + display: inline-flex; align-items: center; gap: 5px; + font: var(--weight-medium) var(--text-2xs)/1 var(--font-display); + letter-spacing: 0.1em; text-transform: uppercase; + color: var(--ink-3); background: none; border: 0; cursor: pointer; + padding: 4px 6px; border-radius: var(--radius-xs); + transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); + } + .pg-code__copy:hover { color: var(--ink-0); background: var(--paper-1); } + .pg-code__copy--done { color: var(--green-deep); } + .pg-code__copy svg { width: 13px; height: 13px; } + .pg-code__scroll { overflow-x: auto; padding: 14px 16px; } + .pg-code pre { font-size: var(--text-sm); line-height: var(--leading-mono); color: var(--ink-1); margin: 0; } + .pg-code--nobar { } + /* token colors for hand-marked spans */ + .pg-code .tok-kw { color: var(--green-deep); } + .pg-code .tok-str { color: var(--red-deep); } + .pg-code .tok-com { color: var(--ink-4); font-style: italic; } + .pg-code .tok-fn { color: var(--ink-0); font-weight: 500; } + .pg-code .tok-num { color: var(--brass); } + `; + document.head.appendChild(el); +} +const CopyGlyph = () => /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" +}, /*#__PURE__*/React.createElement("rect", { + x: "9", + y: "9", + width: "11", + height: "11", + rx: "2" +}), /*#__PURE__*/React.createElement("path", { + d: "M5 15V5a2 2 0 0 1 2-2h10" +})); +const CheckGlyph = () => /*#__PURE__*/React.createElement("svg", { + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2.5", + strokeLinecap: "round", + strokeLinejoin: "round" +}, /*#__PURE__*/React.createElement("path", { + d: "m20 6-11 11-5-5" +})); + +/** + * CodeBlock — a code surface with an optional title bar, language label, + * and copy button. Pass a raw `code` string or pre-marked JSX children. + */ +function CodeBlock({ + code = null, + language = null, + filename = null, + showBar = true, + className = '', + children, + ...rest +}) { + ensureStyles(); + const [copied, setCopied] = React.useState(false); + const preRef = React.useRef(null); + const copy = () => { + const text = code != null ? code : preRef.current ? preRef.current.innerText : ''; + if (navigator.clipboard) navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 1400); + }; + return /*#__PURE__*/React.createElement("div", _extends({ + className: ['pg-code', className].filter(Boolean).join(' ') + }, rest), showBar ? /*#__PURE__*/React.createElement("div", { + className: "pg-code__bar" + }, filename ? /*#__PURE__*/React.createElement("span", { + className: "pg-code__name" + }, filename) : null, language ? /*#__PURE__*/React.createElement("span", { + className: "pg-code__lang" + }, language) : null, /*#__PURE__*/React.createElement("button", { + className: ['pg-code__copy', copied ? 'pg-code__copy--done' : ''].join(' '), + onClick: copy + }, copied ? /*#__PURE__*/React.createElement(CheckGlyph, null) : /*#__PURE__*/React.createElement(CopyGlyph, null), copied ? 'Copied' : 'Copy')) : null, /*#__PURE__*/React.createElement("div", { + className: "pg-code__scroll" + }, /*#__PURE__*/React.createElement("pre", { + ref: preRef + }, /*#__PURE__*/React.createElement("code", null, children != null ? children : code)))); +} +Object.assign(__ds_scope, { CodeBlock }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/CodeBlock.jsx", error: String((e && e.message) || e) }); } + +// components/code/Terminal.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-terminal-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-term { + background: var(--ink-1); + border: var(--border-thin) solid var(--ink-0); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-md); + font-family: var(--font-mono); + } + .pg-term__bar { + display: flex; align-items: center; gap: 8px; + padding: 9px 12px; background: #232B3B; + border-bottom: var(--border-thin) solid #2E3749; + } + .pg-term__dot { width: 9px; height: 9px; border-radius: var(--radius-pill); } + .pg-term__title { + margin-left: 6px; + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--paper-4); + } + .pg-term__body { padding: 14px 16px; overflow-x: auto; font-size: var(--text-sm); line-height: 1.65; } + .pg-term__line { white-space: pre; color: var(--paper-2); } + .pg-term__line .pg-term__prompt { color: var(--green-soft); user-select: none; } + .pg-term__line .pg-term__panto { color: var(--red-soft); user-select: none; } + .pg-term__line--cmd { color: var(--paper-0); } + .pg-term__line--out { color: var(--paper-4); } + .pg-term__line--ok { color: var(--green-soft); } + .pg-term__line--err { color: var(--red-soft); } + .pg-term__line--com { color: #6E7689; font-style: italic; } + .pg-term__cursor { + display: inline-block; width: 8px; height: 1.05em; vertical-align: text-bottom; + background: var(--red); margin-left: 2px; + animation: pg-term-blink 1.1s steps(1) infinite; + } + @keyframes pg-term-blink { 50% { opacity: 0; } } + @media (prefers-reduced-motion: reduce) { .pg-term__cursor { animation: none; } } + `; + document.head.appendChild(el); +} + +/** + * Terminal — a panto CLI window. Render with a `lines` array, or pass + * children for full control. Line types: cmd, out, ok, err, com. + */ +function Terminal({ + title = 'panto', + lines = null, + cursor = false, + className = '', + children, + ...rest +}) { + ensureStyles(); + return /*#__PURE__*/React.createElement("div", _extends({ + className: ['pg-term', className].filter(Boolean).join(' ') + }, rest), /*#__PURE__*/React.createElement("div", { + className: "pg-term__bar" + }, /*#__PURE__*/React.createElement("span", { + className: "pg-term__dot", + style: { + background: 'var(--red)' + } + }), /*#__PURE__*/React.createElement("span", { + className: "pg-term__dot", + style: { + background: 'var(--brass)' + } + }), /*#__PURE__*/React.createElement("span", { + className: "pg-term__dot", + style: { + background: 'var(--green)' + } + }), /*#__PURE__*/React.createElement("span", { + className: "pg-term__title" + }, title)), /*#__PURE__*/React.createElement("div", { + className: "pg-term__body" + }, lines ? lines.map((l, i) => { + const type = l.type || 'out'; + if (type === 'cmd') { + return /*#__PURE__*/React.createElement("div", { + key: i, + className: "pg-term__line pg-term__line--cmd" + }, /*#__PURE__*/React.createElement("span", { + className: l.shell === 'panto' ? 'pg-term__panto' : 'pg-term__prompt' + }, l.shell === 'panto' ? 'panto ▸ ' : '$ '), l.text); + } + return /*#__PURE__*/React.createElement("div", { + key: i, + className: `pg-term__line pg-term__line--${type}` + }, l.text); + }) : children, cursor ? /*#__PURE__*/React.createElement("span", { + className: "pg-term__cursor" + }) : null)); +} +Object.assign(__ds_scope, { Terminal }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/Terminal.jsx", error: String((e && e.message) || e) }); } + +// components/core/Badge.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-badge-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-badge { + display: inline-flex; align-items: center; gap: 5px; + font-family: var(--font-display); font-weight: var(--weight-semibold); + font-size: var(--text-2xs); letter-spacing: var(--tracking-label); + text-transform: uppercase; line-height: 1; white-space: nowrap; + padding: 4px 7px; border-radius: var(--radius-xs); + border: var(--border-thin) solid transparent; + } + .pg-badge__dot { width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; } + .pg-badge--neutral { color: var(--ink-2); background: var(--paper-2); border-color: var(--paper-3); } + .pg-badge--danger { color: var(--red-deep); background: var(--red-wash); border-color: var(--red-soft); } + .pg-badge--success { color: var(--green-deep); background: var(--green-wash); border-color: var(--green-soft); } + .pg-badge--ink { color: var(--paper-0); background: var(--ink-1); border-color: var(--ink-1); } + .pg-badge--outline { color: var(--ink-2); background: transparent; border-color: var(--border-default); } + `; + document.head.appendChild(el); +} + +/** + * Badge — a small engraved-label status marker (Stable, Beta, Deprecated, …). + */ +function Badge({ + variant = 'neutral', + dot = false, + className = '', + children, + ...rest +}) { + ensureStyles(); + const cls = ['pg-badge', `pg-badge--${variant}`, className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("span", _extends({ + className: cls + }, rest), dot ? /*#__PURE__*/React.createElement("span", { + className: "pg-badge__dot" + }) : null, children); +} +Object.assign(__ds_scope, { Badge }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Badge.jsx", error: String((e && e.message) || e) }); } + +// components/core/Button.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +/* Injects the Button stylesheet once. Components in this system are + self-contained: they reference design tokens via CSS custom + properties and ship their own scoped CSS. */ +const STYLE_ID = 'pg-button-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-btn { + --_bg: var(--ink-1); --_fg: var(--paper-0); --_bd: var(--ink-1); + display: inline-flex; align-items: center; justify-content: center; + gap: var(--space-2); box-sizing: border-box; + font-family: var(--font-sans); font-weight: var(--weight-semibold); + line-height: 1; letter-spacing: 0.01em; white-space: nowrap; + background: var(--_bg); color: var(--_fg); + border: var(--border-medium) solid var(--_bd); + border-radius: var(--radius-sm); + cursor: pointer; text-decoration: none; user-select: none; + transition: background var(--dur-fast) var(--ease-out), + border-color var(--dur-fast) var(--ease-out), + transform var(--dur-fast) var(--ease-out), + box-shadow var(--dur-fast) var(--ease-out); + box-shadow: var(--shadow-xs); + } + .pg-btn:hover { background: var(--_bgh, var(--_bg)); border-color: var(--_bdh, var(--_bd)); } + .pg-btn:active { transform: translateY(1px); box-shadow: none; } + .pg-btn:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; } + .pg-btn[disabled] { cursor: not-allowed; opacity: 0.45; box-shadow: none; transform: none; } + + .pg-btn--sm { font-size: var(--text-xs); padding: 6px 12px; } + .pg-btn--md { font-size: var(--text-sm); padding: 9px 16px; } + .pg-btn--lg { font-size: var(--text-base); padding: 12px 22px; } + .pg-btn--block { display: flex; width: 100%; } + + .pg-btn--primary { --_bg: var(--ink-1); --_fg: var(--paper-0); --_bd: var(--ink-1); --_bgh: var(--ink-0); --_bdh: var(--ink-0); } + .pg-btn--accent { --_bg: var(--red); --_fg: var(--paper-0); --_bd: var(--red); --_bgh: var(--red-deep); --_bdh: var(--red-deep); } + .pg-btn--secondary { --_bg: var(--paper-0); --_fg: var(--ink-1); --_bd: var(--border-default); --_bgh: var(--paper-2); --_bdh: var(--ink-3); } + .pg-btn--ghost { --_bg: transparent; --_fg: var(--ink-1); --_bd: transparent; --_bgh: var(--paper-2); --_bdh: transparent; box-shadow: none; } + .pg-btn--danger { --_bg: transparent; --_fg: var(--red-deep); --_bd: var(--red); --_bgh: var(--red-wash); --_bdh: var(--red-deep); box-shadow: none; } + .pg-btn__icon { display: inline-flex; width: 1.05em; height: 1.05em; } + .pg-btn__icon svg { width: 100%; height: 100%; } + `; + document.head.appendChild(el); +} + +/** + * Button — the primary action control. Machined edges, instrument palette. + */ +function Button({ + variant = 'primary', + size = 'md', + block = false, + icon = null, + iconRight = null, + disabled = false, + as = 'button', + className = '', + children, + ...rest +}) { + ensureStyles(); + const Tag = as; + const cls = ['pg-btn', `pg-btn--${variant}`, `pg-btn--${size}`, block ? 'pg-btn--block' : '', className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement(Tag, _extends({ + className: cls, + disabled: Tag === 'button' ? disabled : undefined + }, rest), icon ? /*#__PURE__*/React.createElement("span", { + className: "pg-btn__icon" + }, icon) : null, children, iconRight ? /*#__PURE__*/React.createElement("span", { + className: "pg-btn__icon" + }, iconRight) : null); +} +Object.assign(__ds_scope, { Button }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Button.jsx", error: String((e && e.message) || e) }); } + +// components/core/Card.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-card-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-card { + display: block; box-sizing: border-box; + background: var(--surface-card); + border: var(--border-thin) solid var(--border-hairline); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + overflow: hidden; + } + .pg-card--flat { box-shadow: none; } + .pg-card--sunken { background: var(--surface-sunken); box-shadow: var(--shadow-inset); border-color: var(--paper-3); } + .pg-card--interactive { cursor: pointer; transition: box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out); } + .pg-card--interactive:hover { box-shadow: var(--shadow-md); border-color: var(--border-default); transform: translateY(-1px); } + /* top engraved tick rule, like a scale edge */ + .pg-card--ruled { border-top: var(--border-thick) solid var(--ink-1); } + .pg-card--ruled.pg-card--accent { border-top-color: var(--red); } + + .pg-card__header { + display: flex; align-items: center; gap: var(--space-3); + padding: var(--space-4) var(--space-5); + border-bottom: var(--border-thin) solid var(--border-hairline); + } + .pg-card__title { font: var(--weight-semibold) var(--text-lg)/1.2 var(--font-sans); color: var(--text-strong); margin: 0; } + .pg-card__body { padding: var(--space-5); } + .pg-card__footer { + padding: var(--space-4) var(--space-5); + border-top: var(--border-thin) solid var(--border-hairline); + background: var(--paper-1); + display: flex; align-items: center; gap: var(--space-3); + } + `; + document.head.appendChild(el); +} + +/** + * Card — the primary surface container. Header / body / footer are optional. + */ +function Card({ + variant = 'default', + ruled = false, + interactive = false, + title = null, + actions = null, + footer = null, + className = '', + children, + ...rest +}) { + ensureStyles(); + const cls = ['pg-card', variant !== 'default' ? `pg-card--${variant}` : '', ruled ? 'pg-card--ruled' : '', interactive ? 'pg-card--interactive' : '', className].filter(Boolean).join(' '); + const hasHeader = title != null || actions != null; + return /*#__PURE__*/React.createElement("div", _extends({ + className: cls + }, rest), hasHeader ? /*#__PURE__*/React.createElement("div", { + className: "pg-card__header" + }, title ? /*#__PURE__*/React.createElement("h3", { + className: "pg-card__title" + }, title) : null, actions ? /*#__PURE__*/React.createElement("div", { + style: { + marginLeft: 'auto', + display: 'flex', + gap: 'var(--space-2)' + } + }, actions) : null) : null, /*#__PURE__*/React.createElement("div", { + className: "pg-card__body" + }, children), footer ? /*#__PURE__*/React.createElement("div", { + className: "pg-card__footer" + }, footer) : null); +} +Object.assign(__ds_scope, { Card }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Card.jsx", error: String((e && e.message) || e) }); } + +// components/core/IconButton.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-iconbutton-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-iconbtn { + display: inline-flex; align-items: center; justify-content: center; + box-sizing: border-box; padding: 0; cursor: pointer; + color: var(--ink-2); background: transparent; + border: var(--border-thin) solid transparent; + border-radius: var(--radius-sm); + transition: background var(--dur-fast) var(--ease-out), + color var(--dur-fast) var(--ease-out), + border-color var(--dur-fast) var(--ease-out), + transform var(--dur-fast) var(--ease-out); + } + .pg-iconbtn:hover { background: var(--paper-2); color: var(--ink-0); } + .pg-iconbtn:active { transform: translateY(1px); } + .pg-iconbtn:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; } + .pg-iconbtn[disabled] { cursor: not-allowed; opacity: 0.4; } + .pg-iconbtn--bordered { border-color: var(--border-default); background: var(--paper-0); } + .pg-iconbtn--bordered:hover { border-color: var(--ink-3); } + .pg-iconbtn--active { background: var(--paper-2); color: var(--red-deep); border-color: var(--border-default); } + .pg-iconbtn--sm { width: 28px; height: 28px; } + .pg-iconbtn--md { width: 36px; height: 36px; } + .pg-iconbtn--lg { width: 44px; height: 44px; } + .pg-iconbtn svg { width: 1.15em; height: 1.15em; display: block; } + .pg-iconbtn--sm svg { font-size: 15px; } + .pg-iconbtn--md svg { font-size: 18px; } + .pg-iconbtn--lg svg { font-size: 21px; } + `; + document.head.appendChild(el); +} + +/** + * IconButton — a square, icon-only control for toolbars and dense UI. + */ +function IconButton({ + size = 'md', + bordered = false, + active = false, + label, + disabled = false, + className = '', + children, + ...rest +}) { + ensureStyles(); + const cls = ['pg-iconbtn', `pg-iconbtn--${size}`, bordered ? 'pg-iconbtn--bordered' : '', active ? 'pg-iconbtn--active' : '', className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("button", _extends({ + type: "button", + className: cls, + "aria-label": label, + title: label, + disabled: disabled + }, rest), children); +} +Object.assign(__ds_scope, { IconButton }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/IconButton.jsx", error: String((e && e.message) || e) }); } + +// components/core/Tag.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-tag-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-tag { + display: inline-flex; align-items: center; gap: 6px; + font-family: var(--font-mono); font-weight: var(--weight-medium); + font-size: var(--text-xs); line-height: 1; white-space: nowrap; + color: var(--ink-1); background: var(--paper-0); + padding: 5px 9px; border-radius: var(--radius-pill); + border: var(--border-thin) solid var(--border-default); + } + .pg-tag__dot { width: 7px; height: 7px; border-radius: var(--radius-pill); flex: none; background: var(--ink-3); } + .pg-tag--interactive { cursor: pointer; transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); } + .pg-tag--interactive:hover { border-color: var(--ink-3); background: var(--paper-2); } + .pg-tag__remove { + display: inline-flex; align-items: center; justify-content: center; + width: 14px; height: 14px; margin-right: -2px; padding: 0; + border: 0; background: none; cursor: pointer; color: var(--ink-3); + font-family: var(--font-sans); font-size: 14px; line-height: 1; border-radius: var(--radius-pill); + } + .pg-tag__remove:hover { color: var(--red); } + `; + document.head.appendChild(el); +} + +/** + * Tag — a monospace chip for languages, keywords, and filters. + */ +function Tag({ + dot = null, + onRemove = null, + interactive = false, + className = '', + children, + ...rest +}) { + ensureStyles(); + const cls = ['pg-tag', interactive || rest.onClick ? 'pg-tag--interactive' : '', className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("span", _extends({ + className: cls + }, rest), dot ? /*#__PURE__*/React.createElement("span", { + className: "pg-tag__dot", + style: { + background: dot === true ? undefined : dot + } + }) : null, children, onRemove ? /*#__PURE__*/React.createElement("button", { + type: "button", + className: "pg-tag__remove", + "aria-label": "Remove", + onClick: e => { + e.stopPropagation(); + onRemove(e); + } + }, "\xD7") : null); +} +Object.assign(__ds_scope, { Tag }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Tag.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Input.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-input-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-field { display: flex; flex-direction: column; gap: 6px; } + .pg-field__label { + font: var(--weight-semibold) var(--text-xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-2); + } + .pg-field__req { color: var(--red); margin-left: 3px; } + .pg-input-wrap { position: relative; display: flex; align-items: center; } + .pg-input-wrap__icon { + position: absolute; left: 11px; display: inline-flex; color: var(--ink-3); pointer-events: none; + } + .pg-input-wrap__icon svg { width: 16px; height: 16px; } + .pg-input { + width: 100%; box-sizing: border-box; + font: var(--weight-regular) var(--text-sm)/1.4 var(--font-sans); + color: var(--ink-1); background: var(--paper-0); + border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-xs); + padding: 9px 12px; + transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); + } + .pg-input--mono { font-family: var(--font-mono); } + .pg-input--has-icon { padding-left: 34px; } + .pg-input::placeholder { color: var(--text-placeholder); } + .pg-input:hover { border-color: var(--ink-4); } + .pg-input:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-wash); } + .pg-input--invalid { border-color: var(--red); } + .pg-input--invalid:focus { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-wash); } + .pg-input:disabled { background: var(--paper-2); color: var(--ink-3); cursor: not-allowed; } + .pg-field__hint { font: var(--weight-regular) var(--text-xs)/1.4 var(--font-sans); color: var(--ink-3); } + .pg-field__hint--error { color: var(--red-deep); } + `; + document.head.appendChild(el); +} + +/** + * Input — single-line text field with optional label, icon, and validation. + */ +function Input({ + label = null, + hint = null, + error = null, + icon = null, + mono = false, + required = false, + id, + className = '', + ...rest +}) { + ensureStyles(); + const inputId = id || (label ? `pg-in-${Math.random().toString(36).slice(2, 8)}` : undefined); + const cls = ['pg-input', mono ? 'pg-input--mono' : '', icon ? 'pg-input--has-icon' : '', error ? 'pg-input--invalid' : '', className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("div", { + className: "pg-field" + }, label ? /*#__PURE__*/React.createElement("label", { + className: "pg-field__label", + htmlFor: inputId + }, label, required ? /*#__PURE__*/React.createElement("span", { + className: "pg-field__req" + }, "*") : null) : null, /*#__PURE__*/React.createElement("div", { + className: "pg-input-wrap" + }, icon ? /*#__PURE__*/React.createElement("span", { + className: "pg-input-wrap__icon" + }, icon) : null, /*#__PURE__*/React.createElement("input", _extends({ + id: inputId, + className: cls, + "aria-invalid": !!error + }, rest))), error ? /*#__PURE__*/React.createElement("span", { + className: "pg-field__hint pg-field__hint--error" + }, error) : hint ? /*#__PURE__*/React.createElement("span", { + className: "pg-field__hint" + }, hint) : null); +} +Object.assign(__ds_scope, { Input }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Input.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Select.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-select-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-select-field { display: flex; flex-direction: column; gap: 6px; } + .pg-select-field__label { + font: var(--weight-semibold) var(--text-xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-2); + } + .pg-select-wrap { position: relative; display: flex; align-items: center; } + .pg-select { + width: 100%; box-sizing: border-box; appearance: none; + font: var(--weight-regular) var(--text-sm)/1.4 var(--font-sans); + color: var(--ink-1); background: var(--paper-0); + border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-xs); + padding: 9px 34px 9px 12px; cursor: pointer; + transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); + } + .pg-select:hover { border-color: var(--ink-4); } + .pg-select:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-wash); } + .pg-select:disabled { background: var(--paper-2); color: var(--ink-3); cursor: not-allowed; } + .pg-select-wrap__chevron { + position: absolute; right: 11px; pointer-events: none; color: var(--ink-3); + width: 16px; height: 16px; + } + `; + document.head.appendChild(el); +} + +/** + * Select — native dropdown styled to the system, with a custom chevron. + */ +function Select({ + label = null, + options = [], + placeholder = null, + id, + className = '', + children, + ...rest +}) { + ensureStyles(); + const selId = id || (label ? `pg-sel-${Math.random().toString(36).slice(2, 8)}` : undefined); + return /*#__PURE__*/React.createElement("div", { + className: "pg-select-field" + }, label ? /*#__PURE__*/React.createElement("label", { + className: "pg-select-field__label", + htmlFor: selId + }, label) : null, /*#__PURE__*/React.createElement("div", { + className: "pg-select-wrap" + }, /*#__PURE__*/React.createElement("select", _extends({ + id: selId, + className: ['pg-select', className].filter(Boolean).join(' ') + }, rest), placeholder ? /*#__PURE__*/React.createElement("option", { + value: "", + disabled: true + }, placeholder) : null, children, options.map(o => { + const value = typeof o === 'string' ? o : o.value; + const optLabel = typeof o === 'string' ? o : o.label; + return /*#__PURE__*/React.createElement("option", { + key: value, + value: value + }, optLabel); + })), /*#__PURE__*/React.createElement("svg", { + className: "pg-select-wrap__chevron", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round" + }, /*#__PURE__*/React.createElement("path", { + d: "m6 9 6 6 6-6" + })))); +} +Object.assign(__ds_scope, { Select }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Switch.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-switch-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; } + .pg-switch input { position: absolute; opacity: 0; width: 0; height: 0; } + .pg-switch__track { + position: relative; flex: none; width: 38px; height: 22px; + background: var(--paper-3); border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-pill); + transition: background var(--dur-base) var(--ease-slide), border-color var(--dur-base) var(--ease-slide); + } + .pg-switch__thumb { + position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; + background: var(--paper-0); border-radius: var(--radius-pill); + box-shadow: var(--shadow-sm); + transition: transform var(--dur-base) var(--ease-slide); + } + .pg-switch input:checked + .pg-switch__track { background: var(--green); border-color: var(--green-deep); } + .pg-switch input:checked + .pg-switch__track .pg-switch__thumb { transform: translateX(16px); } + .pg-switch input:focus-visible + .pg-switch__track { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; } + .pg-switch input:disabled + .pg-switch__track { opacity: 0.45; cursor: not-allowed; } + .pg-switch__label { font: var(--weight-regular) var(--text-sm)/1.3 var(--font-sans); color: var(--ink-1); } + `; + document.head.appendChild(el); +} + +/** + * Switch — a toggle. The "on" state slides to instrument green. + */ +function Switch({ + checked, + defaultChecked, + onChange, + disabled = false, + label = null, + id, + className = '', + ...rest +}) { + ensureStyles(); + const swId = id || `pg-sw-${Math.random().toString(36).slice(2, 8)}`; + return /*#__PURE__*/React.createElement("label", { + className: ['pg-switch', className].filter(Boolean).join(' '), + htmlFor: swId + }, /*#__PURE__*/React.createElement("input", _extends({ + id: swId, + type: "checkbox", + checked: checked, + defaultChecked: defaultChecked, + onChange: onChange, + disabled: disabled + }, rest)), /*#__PURE__*/React.createElement("span", { + className: "pg-switch__track" + }, /*#__PURE__*/React.createElement("span", { + className: "pg-switch__thumb" + })), label ? /*#__PURE__*/React.createElement("span", { + className: "pg-switch__label" + }, label) : null); +} +Object.assign(__ds_scope, { Switch }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Switch.jsx", error: String((e && e.message) || e) }); } + +// components/navigation/Tabs.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const STYLE_ID = 'pg-tabs-css'; +function ensureStyles() { + if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return; + const el = document.createElement('style'); + el.id = STYLE_ID; + el.textContent = ` + .pg-tabs { display: flex; align-items: stretch; gap: 2px; border-bottom: var(--border-thin) solid var(--border-hairline); } + .pg-tabs--pill { border-bottom: 0; gap: 4px; background: var(--paper-2); padding: 3px; border-radius: var(--radius-sm); width: max-content; } + .pg-tab { + display: inline-flex; align-items: center; gap: 7px; + font: var(--weight-medium) var(--text-sm)/1 var(--font-sans); + color: var(--ink-3); background: none; border: 0; cursor: pointer; + padding: 10px 14px; position: relative; white-space: nowrap; + transition: color var(--dur-fast) var(--ease-out); + } + .pg-tab:hover { color: var(--ink-1); } + .pg-tab__icon { display: inline-flex; width: 15px; height: 15px; } + .pg-tab__icon svg { width: 100%; height: 100%; } + /* underline indicator — the engraved cursor line */ + .pg-tabs:not(.pg-tabs--pill) .pg-tab::after { + content: ''; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px; + background: var(--red); transform: scaleX(0); transform-origin: center; + transition: transform var(--dur-base) var(--ease-slide); + } + .pg-tabs:not(.pg-tabs--pill) .pg-tab[aria-selected="true"] { color: var(--ink-0); font-weight: var(--weight-semibold); } + .pg-tabs:not(.pg-tabs--pill) .pg-tab[aria-selected="true"]::after { transform: scaleX(1); } + .pg-tab:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-xs); } + /* pill variant */ + .pg-tabs--pill .pg-tab { border-radius: var(--radius-xs); padding: 7px 13px; font-family: var(--font-mono); font-size: var(--text-xs); } + .pg-tabs--pill .pg-tab[aria-selected="true"] { background: var(--paper-0); color: var(--ink-0); box-shadow: var(--shadow-xs); } + `; + document.head.appendChild(el); +} + +/** + * Tabs — segmented navigation. Default variant draws the engraved red cursor + * underline; `pill` variant is a compact monospace switch (e.g. language picker). + */ +function Tabs({ + tabs = [], + value, + onChange, + variant = 'underline', + className = '', + ...rest +}) { + ensureStyles(); + const cls = ['pg-tabs', variant === 'pill' ? 'pg-tabs--pill' : '', className].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("div", _extends({ + className: cls, + role: "tablist" + }, rest), tabs.map(t => { + const id = typeof t === 'string' ? t : t.id; + const label = typeof t === 'string' ? t : t.label; + const icon = typeof t === 'string' ? null : t.icon; + const selected = id === value; + return /*#__PURE__*/React.createElement("button", { + key: id, + role: "tab", + "aria-selected": selected, + className: "pg-tab", + onClick: () => onChange && onChange(id) + }, icon ? /*#__PURE__*/React.createElement("span", { + className: "pg-tab__icon" + }, icon) : null, label); + })); +} +Object.assign(__ds_scope, { Tabs }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/Tabs.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/docs/App.jsx +try { (() => { +// App — composes the docs shell: header, sidebar, content, on-this-page TOC. +(function () { + const React = window.React; + const TOC = [{ + id: 'example', + label: 'Example' + }, { + id: 'parameters', + label: 'Parameters' + }, { + id: 'returns', + label: 'Returns' + }]; + function App() { + const [active, setActive] = React.useState('complete()'); + const [menuOpen, setMenuOpen] = React.useState(false); + return /*#__PURE__*/React.createElement("div", { + className: "dk-app" + }, /*#__PURE__*/React.createElement(window.DocsHeader, { + onMenu: () => setMenuOpen(v => !v) + }), /*#__PURE__*/React.createElement("div", { + className: "dk-shell" + }, /*#__PURE__*/React.createElement(window.DocsSidebar, { + active: active, + onSelect: it => { + setActive(it); + setMenuOpen(false); + }, + open: menuOpen + }), menuOpen ? /*#__PURE__*/React.createElement("div", { + className: "dk-scrim", + onClick: () => setMenuOpen(false) + }) : null, /*#__PURE__*/React.createElement("main", { + className: "dk-main" + }, /*#__PURE__*/React.createElement(window.ReferencePage, null)), /*#__PURE__*/React.createElement("aside", { + className: "dk-toc" + }, /*#__PURE__*/React.createElement("p", { + className: "dk-toc__title" + }, "On this page"), /*#__PURE__*/React.createElement("ul", { + className: "dk-toc__list" + }, TOC.map(t => /*#__PURE__*/React.createElement("li", { + key: t.id + }, /*#__PURE__*/React.createElement("a", { + href: '#' + t.id, + className: "dk-toc__link" + }, t.label)))), /*#__PURE__*/React.createElement("div", { + className: "panto-tick-rule", + style: { + margin: 'var(--space-5) 0 var(--space-4)' + } + }), /*#__PURE__*/React.createElement("a", { + className: "dk-edit", + href: "#" + }, "Edit this page \u2192")))); + } + window.DocsApp = App; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/App.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/docs/DocsHeader.jsx +try { (() => { +// Lucide-derived inline icons (ISC) + DocsHeader. Icons live here (a JSX file) +// so the design-system compiler treats them as kit-local, not a bundle module. +(function () { + const React = window.React; + const S = (paths, extra = {}) => props => React.createElement('svg', Object.assign({ + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 2, + strokeLinecap: 'round', + strokeLinejoin: 'round', + width: '1em', + height: '1em' + }, extra, props), paths.map((d, i) => React.createElement('path', { + key: i, + d + }))); + const M = (children, extra = {}) => props => React.createElement('svg', Object.assign({ + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 2, + strokeLinecap: 'round', + strokeLinejoin: 'round', + width: '1em', + height: '1em' + }, extra, props), children(React)); + window.Icons = { + Search: M(R => [R.createElement('circle', { + key: 0, + cx: 11, + cy: 11, + r: 8 + }), R.createElement('path', { + key: 1, + d: 'm21 21-4.3-4.3' + })]), + ChevronRight: S(['m9 18 6-6-6-6']), + Hash: S(['M4 9h16', 'M4 15h16', 'M10 3 8 21', 'M16 3l-2 18']), + Book: S(['M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20']), + Terminal: S(['m4 17 6-6-6-6', 'M12 19h8']), + Github: M(R => [R.createElement('path', { + key: 0, + d: 'M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4' + }), R.createElement('path', { + key: 1, + d: 'M9 18c-4.51 2-5-2-7-2' + })]), + Zap: S(['M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z']), + Boxes: S(['M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z', 'm7 16.5-4.74-2.85', 'm7 16.5 5-3', 'M7 16.5v5.17', 'M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z', 'm17 16.5-5-3', 'm17 16.5 4.74-2.85', 'M17 16.5v5.17', 'M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z', 'M12 8 7.26 5.15', 'm12 8 4.74-2.85', 'M12 13.5V8']), + Layers: S(['m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z', 'M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12', 'M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17']), + Menu: S(['M4 12h16', 'M4 6h16', 'M4 18h16']), + Check: S(['M20 6 9 17l-5-5']), + Compass: M(R => [R.createElement('path', { + key: 0, + d: 'm16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z' + }), R.createElement('circle', { + key: 1, + cx: 12, + cy: 12, + r: 10 + })]) + }; +})(); + +// DocsHeader — top bar: logo lockup, primary nav, search, github. +(function () { + const React = window.React; + const { + Badge, + IconButton + } = window.PantographDesignSystem_56a1c7; + const I = window.Icons; + function DocsHeader({ + onMenu + }) { + return /*#__PURE__*/React.createElement("header", { + className: "dk-header" + }, /*#__PURE__*/React.createElement("div", { + className: "dk-header__left" + }, /*#__PURE__*/React.createElement("button", { + className: "dk-menu", + "aria-label": "Menu", + onClick: onMenu + }, /*#__PURE__*/React.createElement(I.Menu, null)), /*#__PURE__*/React.createElement("a", { + className: "dk-brand", + href: "#" + }, /*#__PURE__*/React.createElement("img", { + src: "../../assets/logo-mark.svg", + width: "28", + height: "28", + alt: "" + }), /*#__PURE__*/React.createElement("span", { + className: "dk-brand__word" + }, "pantograph"), /*#__PURE__*/React.createElement("span", { + className: "dk-brand__div" + }, "/"), /*#__PURE__*/React.createElement("span", { + className: "dk-brand__sub" + }, "libpanto")), /*#__PURE__*/React.createElement(Badge, { + variant: "outline" + }, "v2.4")), /*#__PURE__*/React.createElement("nav", { + className: "dk-nav" + }, /*#__PURE__*/React.createElement("a", { + href: "#", + className: "dk-nav__link" + }, "Guides"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "dk-nav__link dk-nav__link--active" + }, "Reference"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "dk-nav__link" + }, "CLI"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "dk-nav__link" + }, "Changelog")), /*#__PURE__*/React.createElement("div", { + className: "dk-header__right" + }, /*#__PURE__*/React.createElement("label", { + className: "dk-search" + }, /*#__PURE__*/React.createElement("span", { + className: "dk-search__icon" + }, /*#__PURE__*/React.createElement(I.Search, null)), /*#__PURE__*/React.createElement("input", { + className: "dk-search__input", + placeholder: "Search the docs" + }), /*#__PURE__*/React.createElement("kbd", { + className: "dk-search__kbd" + }, "/")), /*#__PURE__*/React.createElement(IconButton, { + label: "GitHub", + bordered: true + }, /*#__PURE__*/React.createElement(I.Github, null)))); + } + window.DocsHeader = DocsHeader; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/DocsHeader.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/docs/DocsSidebar.jsx +try { (() => { +// DocsSidebar — grouped navigation tree. +(function () { + const React = window.React; + const I = window.Icons; + const NAV = [{ + group: 'Getting Started', + items: ['Installation', 'Quickstart', 'Authentication'] + }, { + group: 'Guides', + items: ['Streaming', 'Tool use', 'Structured output', 'Retries & timeouts'] + }, { + group: 'Reference', + items: ['Client', 'complete()', 'respond()', 'Response', 'Errors'] + }, { + group: 'CLI · panto', + items: ['panto run', 'panto chat', 'Configuration'] + }]; + function DocsSidebar({ + active, + onSelect, + open + }) { + return /*#__PURE__*/React.createElement("aside", { + className: 'dk-sidebar' + (open ? ' dk-sidebar--open' : '') + }, /*#__PURE__*/React.createElement("div", { + className: "dk-sidebar__scroll" + }, NAV.map(sec => /*#__PURE__*/React.createElement("div", { + className: "dk-navsec", + key: sec.group + }, /*#__PURE__*/React.createElement("p", { + className: "dk-navsec__title" + }, sec.group), /*#__PURE__*/React.createElement("ul", { + className: "dk-navsec__list" + }, sec.items.map(it => /*#__PURE__*/React.createElement("li", { + key: it + }, /*#__PURE__*/React.createElement("a", { + href: "#", + className: 'dk-navsec__link' + (it === active ? ' dk-navsec__link--active' : ''), + onClick: e => { + e.preventDefault(); + onSelect(it); + } + }, it)))))))); + } + window.DocsSidebar = DocsSidebar; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/DocsSidebar.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/docs/ReferencePage.jsx +try { (() => { +// ReferencePage — the main documentation view for client.complete(). +(function () { + const React = window.React; + const { + Badge, + Tag, + Tabs, + CodeBlock, + Callout, + Card + } = window.PantographDesignSystem_56a1c7; + const I = window.Icons; + const SAMPLES = { + python: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "import"), " panto", "\n\n", "client = panto.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "Client"), "()", "\n", "resp = client.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", "\n", " ", "prompt=", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"explain this stack trace\""), ",", "\n", " ", "model=", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"claude-sonnet\""), ",", "\n", " ", "max_tokens=", /*#__PURE__*/React.createElement("span", { + className: "tok-num" + }, "1024"), ",", "\n", ")", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "print"), "(resp.text)"), + rust: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "use"), " panto::Client;", "\n\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "let"), " client = Client::", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "new"), "()?;", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "let"), " resp = client", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"explain this stack trace\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "model"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"claude-sonnet\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "max_tokens"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-num" + }, "1024"), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "send"), "().", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "await"), "?;", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "println!"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"", `{}`, "\""), ", resp.text);"), + typescript: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "import"), " ", "{ Client }", " ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "from"), " ", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"panto\""), ";", "\n\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "const"), " client = ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "new"), " ", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "Client"), "();", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "const"), " resp = ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "await"), " client.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", "{", "\n", " ", "prompt: ", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"explain this stack trace\""), ",", "\n", " ", "model: ", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"claude-sonnet\""), ",", "\n", " ", "maxTokens: ", /*#__PURE__*/React.createElement("span", { + className: "tok-num" + }, "1024"), ",", "\n", "});", "\n", "console.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "log"), "(resp.text);") + }; + const FILE = { + python: 'main.py', + rust: 'src/main.rs', + typescript: 'index.ts' + }; + const PARAMS = [['prompt', 'string', 'required', 'The input message sent to the model.'], ['model', 'string', '"claude-sonnet"', 'Provider-agnostic model alias. Resolved per provider.'], ['max_tokens', 'int', '1024', 'Upper bound on generated tokens.'], ['stream', 'bool', 'false', 'Return an async iterator of deltas instead of a full Response.'], ['tools', 'Tool[]', '[]', 'Tool definitions the model may call.']]; + function ReferencePage() { + const [lang, setLang] = React.useState('python'); + return /*#__PURE__*/React.createElement("article", { + className: "dk-doc" + }, /*#__PURE__*/React.createElement("div", { + className: "dk-crumbs" + }, /*#__PURE__*/React.createElement("span", null, "Reference"), /*#__PURE__*/React.createElement(I.ChevronRight, null), /*#__PURE__*/React.createElement("span", { + className: "dk-crumbs__here" + }, "complete()")), /*#__PURE__*/React.createElement("p", { + className: "panto-eyebrow" + }, "SDK Reference"), /*#__PURE__*/React.createElement("h1", { + className: "dk-doc__h1" + }, "client.complete()"), /*#__PURE__*/React.createElement("div", { + className: "dk-doc__meta" + }, /*#__PURE__*/React.createElement(Badge, { + variant: "success", + dot: true + }, "Stable"), /*#__PURE__*/React.createElement(Badge, { + variant: "neutral" + }, "since v2.0"), /*#__PURE__*/React.createElement(Tag, { + dot: "#3572A5" + }, "python"), /*#__PURE__*/React.createElement(Tag, { + dot: "#DEA584" + }, "rust"), /*#__PURE__*/React.createElement(Tag, { + dot: "#3178C6" + }, "typescript")), /*#__PURE__*/React.createElement("p", { + className: "dk-lead" + }, "Send a single prompt and receive one complete response. The same call signature is reproduced across every binding \u2014 write it once, scale it to any language."), /*#__PURE__*/React.createElement("h2", { + className: "dk-h2", + id: "example" + }, "Example"), /*#__PURE__*/React.createElement("div", { + className: "dk-langbar" + }, /*#__PURE__*/React.createElement(Tabs, { + variant: "pill", + value: lang, + onChange: setLang, + tabs: ['python', 'rust', 'typescript'] + })), /*#__PURE__*/React.createElement(CodeBlock, { + filename: FILE[lang], + language: lang + }, SAMPLES[lang]), /*#__PURE__*/React.createElement(Callout, { + variant: "tip" + }, "Set ", /*#__PURE__*/React.createElement("code", null, "PANTO_KEY"), " in your environment once. Every binding \u2014 and the ", /*#__PURE__*/React.createElement("code", null, "panto"), " CLI \u2014 reads it automatically."), /*#__PURE__*/React.createElement("h2", { + className: "dk-h2", + id: "parameters" + }, "Parameters"), /*#__PURE__*/React.createElement("div", { + className: "dk-table-wrap" + }, /*#__PURE__*/React.createElement("table", { + className: "dk-table" + }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", null, "Name"), /*#__PURE__*/React.createElement("th", null, "Type"), /*#__PURE__*/React.createElement("th", null, "Default"), /*#__PURE__*/React.createElement("th", null, "Description"))), /*#__PURE__*/React.createElement("tbody", null, PARAMS.map(p => /*#__PURE__*/React.createElement("tr", { + key: p[0] + }, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("code", null, p[0])), /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("span", { + className: "dk-type" + }, p[1])), /*#__PURE__*/React.createElement("td", null, p[2] === 'required' ? /*#__PURE__*/React.createElement(Badge, { + variant: "danger" + }, "required") : /*#__PURE__*/React.createElement("code", { + className: "dk-default" + }, p[2])), /*#__PURE__*/React.createElement("td", null, p[3])))))), /*#__PURE__*/React.createElement("h2", { + className: "dk-h2", + id: "returns" + }, "Returns"), /*#__PURE__*/React.createElement("p", { + className: "dk-body" + }, "A ", /*#__PURE__*/React.createElement("a", { + href: "#" + }, "Response"), " object with ", /*#__PURE__*/React.createElement("code", null, ".text"), ", ", /*#__PURE__*/React.createElement("code", null, ".usage"), ", and ", /*#__PURE__*/React.createElement("code", null, ".stop_reason"), ". When ", /*#__PURE__*/React.createElement("code", null, "stream=true"), ", returns an async iterator yielding ", /*#__PURE__*/React.createElement("code", null, "Delta"), " objects instead."), /*#__PURE__*/React.createElement(Callout, { + variant: "danger", + title: "Deprecated argument" + }, "The ", /*#__PURE__*/React.createElement("code", null, "temperature"), " float is removed in v3 \u2014 pass a", /*#__PURE__*/React.createElement("code", null, "Sampling"), " config instead. See the migration guide.")); + } + window.ReferencePage = ReferencePage; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/ReferencePage.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/home/Features.jsx +try { (() => { +// Features — a grid of capability cards. +(function () { + const React = window.React; + const { + Card, + Badge + } = window.PantographDesignSystem_56a1c7; + const I = window.Icons; + const FEATURES = [{ + icon: /*#__PURE__*/React.createElement(I.Boxes, null), + title: 'Provider-agnostic', + body: 'One client targets OpenAI, Anthropic, or a local model. Swap providers with a string — no rewrites.' + }, { + icon: /*#__PURE__*/React.createElement(I.Zap, null), + title: 'Streaming & tools', + body: 'First-class async iterators for token streams and a typed tool-use loop across every binding.' + }, { + icon: /*#__PURE__*/React.createElement(I.Layers, null), + title: 'Typed responses', + body: 'complete() returns a structured Response — text, usage, stop reason — not a raw string.' + }, { + icon: /*#__PURE__*/React.createElement(I.Terminal, null), + title: 'The panto agent', + body: 'A terminal coding agent built on libpanto — a minimal system prompt and tool set you can swap out entirely. Written in Zig, extended in Lua.' + }]; + function Features() { + return /*#__PURE__*/React.createElement("section", { + className: "hm-features" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-section-head" + }, /*#__PURE__*/React.createElement("p", { + className: "panto-eyebrow" + }, "Why pantograph"), /*#__PURE__*/React.createElement("h2", { + className: "hm-h2" + }, "A small instrument, precisely built.")), /*#__PURE__*/React.createElement("div", { + className: "hm-feature-grid" + }, FEATURES.map(f => /*#__PURE__*/React.createElement(Card, { + key: f.title, + className: "hm-feature" + }, /*#__PURE__*/React.createElement("span", { + className: "hm-feature__icon" + }, f.icon), /*#__PURE__*/React.createElement("h3", { + className: "hm-feature__title" + }, f.title), /*#__PURE__*/React.createElement("p", { + className: "hm-feature__body" + }, f.body))))); + } + window.Features = Features; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/Features.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/home/Hero.jsx +try { (() => { +// Hero — headline, install command, CTAs, and a live-looking terminal. +(function () { + const React = window.React; + const { + Button, + Badge, + Terminal + } = window.PantographDesignSystem_56a1c7; + const I = window.Icons; + function CopyCommand({ + text + }) { + const [done, setDone] = React.useState(false); + return /*#__PURE__*/React.createElement("button", { + className: "hm-cmd", + onClick: () => { + if (navigator.clipboard) navigator.clipboard.writeText(text); + setDone(true); + setTimeout(() => setDone(false), 1400); + } + }, /*#__PURE__*/React.createElement("span", { + className: "hm-cmd__prompt" + }, "$"), /*#__PURE__*/React.createElement("span", { + className: "hm-cmd__text" + }, text), /*#__PURE__*/React.createElement("span", { + className: 'hm-cmd__copy' + (done ? ' hm-cmd__copy--done' : '') + }, done ? /*#__PURE__*/React.createElement(I.Check, null) : /*#__PURE__*/React.createElement(I.Compass, null))); + } + function Hero() { + return /*#__PURE__*/React.createElement("section", { + className: "hm-hero" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-hero__copy" + }, /*#__PURE__*/React.createElement("p", { + className: "panto-eyebrow" + }, "Open source \xB7 MIT \xB7 v2.4"), /*#__PURE__*/React.createElement("h1", { + className: "hm-hero__h1" + }, "Scale one prompt", /*#__PURE__*/React.createElement("br", null), "to every provider."), /*#__PURE__*/React.createElement("p", { + className: "hm-hero__lead" + }, /*#__PURE__*/React.createElement("strong", null, "libpanto"), " is a single, small set of motions \u2014 faithfully reproduced across OpenAI, Anthropic, and your local models. ", /*#__PURE__*/React.createElement("strong", null, "panto"), " ", "is the terminal coding agent built on top."), /*#__PURE__*/React.createElement(CopyCommand, { + text: "pip install panto" + }), /*#__PURE__*/React.createElement("div", { + className: "hm-hero__cta" + }, /*#__PURE__*/React.createElement(Button, { + variant: "accent", + size: "lg", + icon: /*#__PURE__*/React.createElement(I.Terminal, null) + }, "Get started"), /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + size: "lg", + icon: /*#__PURE__*/React.createElement(I.Github, null) + }, "Star on GitHub")), /*#__PURE__*/React.createElement("div", { + className: "hm-hero__langs" + }, /*#__PURE__*/React.createElement("span", { + className: "hm-hero__langlabel" + }, "Bindings"), /*#__PURE__*/React.createElement("span", { + className: "hm-langdot", + style: { + background: '#3572A5' + } + }), "python", /*#__PURE__*/React.createElement("span", { + className: "hm-langdot", + style: { + background: '#DEA584' + } + }), "rust", /*#__PURE__*/React.createElement("span", { + className: "hm-langdot", + style: { + background: '#3178C6' + } + }), "typescript", /*#__PURE__*/React.createElement("span", { + className: "hm-hero__more" + }, "+ 1 in beta"))), /*#__PURE__*/React.createElement("div", { + className: "hm-hero__demo" + }, /*#__PURE__*/React.createElement(Terminal, { + title: "panto \xB7 run", + cursor: true, + lines: [{ + type: 'cmd', + shell: 'shell', + text: 'panto run "add a --json flag to the export command"' + }, { + type: 'com', + text: '• indexed 41 files · 3 relevant' + }, { + type: 'cmd', + shell: 'panto', + text: "I'll add a --json flag and route output through the serializer." + }, { + type: 'out', + text: ' cli/export.py +18 −2' + }, { + type: 'out', + text: ' cli/__init__.py +3' + }, { + type: 'ok', + text: '✓ applied · 2 files changed in 0.3s' + }] + }))); + } + window.Hero = Hero; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/Hero.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/home/HomeApp.jsx +try { (() => { +// HomeApp — composes the marketing page + footer. +(function () { + const React = window.React; + function Footer() { + const COLS = [{ + h: 'SDK', + items: ['Installation', 'Reference', 'Streaming', 'Tool use'] + }, { + h: 'panto CLI', + items: ['Quickstart', 'Commands', 'Configuration'] + }, { + h: 'Project', + items: ['GitHub', 'Changelog', 'Roadmap', 'License (MIT)'] + }]; + return /*#__PURE__*/React.createElement("footer", { + className: "hm-footer" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-footer__inner" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-footer__brand" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-brand" + }, /*#__PURE__*/React.createElement("img", { + src: "../../assets/logo-mark.svg", + width: "28", + height: "28", + alt: "" + }), /*#__PURE__*/React.createElement("span", { + className: "hm-brand__word" + }, "pantograph")), /*#__PURE__*/React.createElement("p", { + className: "hm-footer__tag" + }, "Scale one prompt to every provider.")), COLS.map(c => /*#__PURE__*/React.createElement("div", { + className: "hm-footer__col", + key: c.h + }, /*#__PURE__*/React.createElement("p", { + className: "hm-footer__h" + }, c.h), c.items.map(it => /*#__PURE__*/React.createElement("a", { + key: it, + href: "#", + className: "hm-footer__link" + }, it))))), /*#__PURE__*/React.createElement("div", { + className: "hm-footer__bar" + }, /*#__PURE__*/React.createElement("span", null, "\xA9 2026 the pantograph authors"), /*#__PURE__*/React.createElement("span", { + className: "hm-footer__mono" + }, "built with libpanto"))); + } + function HomeApp() { + return /*#__PURE__*/React.createElement("div", { + className: "hm-app" + }, /*#__PURE__*/React.createElement(window.HomeHeader, null), /*#__PURE__*/React.createElement("main", null, /*#__PURE__*/React.createElement(window.Hero, null), /*#__PURE__*/React.createElement("div", { + className: "hm-ruler", + "aria-hidden": "true" + }), /*#__PURE__*/React.createElement(window.InstallSection, null), /*#__PURE__*/React.createElement(window.Features, null)), /*#__PURE__*/React.createElement(Footer, null)); + } + window.HomeApp = HomeApp; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/HomeApp.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/home/HomeHeader.jsx +try { (() => { +// Lucide-derived inline icons (ISC) + HomeHeader. Icons live here (a JSX file) +// so the design-system compiler treats them as kit-local, not a bundle module. +(function () { + const React = window.React; + const S = (paths, extra = {}) => props => React.createElement('svg', Object.assign({ + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 2, + strokeLinecap: 'round', + strokeLinejoin: 'round', + width: '1em', + height: '1em' + }, extra, props), paths.map((d, i) => React.createElement('path', { + key: i, + d + }))); + const M = (children, extra = {}) => props => React.createElement('svg', Object.assign({ + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 2, + strokeLinecap: 'round', + strokeLinejoin: 'round', + width: '1em', + height: '1em' + }, extra, props), children(React)); + window.Icons = { + ChevronRight: S(['m9 18 6-6-6-6']), + Book: S(['M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20']), + Terminal: S(['m4 17 6-6-6-6', 'M12 19h8']), + Github: M(R => [R.createElement('path', { + key: 0, + d: 'M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4' + }), R.createElement('path', { + key: 1, + d: 'M9 18c-4.51 2-5-2-7-2' + })]), + Zap: S(['M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z']), + Boxes: S(['M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z', 'm7 16.5-4.74-2.85', 'm7 16.5 5-3', 'M7 16.5v5.17', 'M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z', 'm17 16.5-5-3', 'm17 16.5 4.74-2.85', 'M17 16.5v5.17', 'M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z', 'M12 8 7.26 5.15', 'm12 8 4.74-2.85', 'M12 13.5V8']), + Layers: S(['m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z', 'M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12', 'M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17']), + Check: S(['M20 6 9 17l-5-5']), + Compass: M(R => [R.createElement('path', { + key: 0, + d: 'm16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z' + }), R.createElement('circle', { + key: 1, + cx: 12, + cy: 12, + r: 10 + })]) + }; +})(); + +// HomeHeader — marketing top bar. +(function () { + const React = window.React; + const { + Button + } = window.PantographDesignSystem_56a1c7; + const I = window.Icons; + function HomeHeader() { + return /*#__PURE__*/React.createElement("header", { + className: "hm-header" + }, /*#__PURE__*/React.createElement("a", { + className: "hm-brand", + href: "#" + }, /*#__PURE__*/React.createElement("img", { + src: "../../assets/logo-mark.svg", + width: "30", + height: "30", + alt: "" + }), /*#__PURE__*/React.createElement("span", { + className: "hm-brand__word" + }, "pantograph")), /*#__PURE__*/React.createElement("nav", { + className: "hm-nav" + }, /*#__PURE__*/React.createElement("a", { + href: "#", + className: "hm-nav__link" + }, "Docs"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "hm-nav__link" + }, "SDK"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "hm-nav__link" + }, "CLI"), /*#__PURE__*/React.createElement("a", { + href: "#", + className: "hm-nav__link" + }, "Blog")), /*#__PURE__*/React.createElement("div", { + className: "hm-header__right" + }, /*#__PURE__*/React.createElement("a", { + href: "#", + className: "hm-stars" + }, /*#__PURE__*/React.createElement(I.Github, null), " 14.2k"), /*#__PURE__*/React.createElement(Button, { + variant: "primary", + size: "sm", + icon: /*#__PURE__*/React.createElement(I.Book, null) + }, "Read the docs"))); + } + window.HomeHeader = HomeHeader; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/HomeHeader.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/home/InstallSection.jsx +try { (() => { +// InstallSection — "same call, every language" with a language switcher. +(function () { + const React = window.React; + const { + Tabs, + CodeBlock + } = window.PantographDesignSystem_56a1c7; + const INSTALL = { + python: 'pip install panto', + rust: 'cargo add panto', + typescript: 'npm install panto' + }; + const FILE = { + python: 'main.py', + rust: 'src/main.rs', + typescript: 'index.ts' + }; + const CODE = { + python: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "import"), " panto", "\n\n", "resp = panto.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "Client"), "().", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"name three uses for a pantograph\""), ")", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "print"), "(resp.text)"), + rust: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "use"), " panto::Client;", "\n\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "let"), " resp = Client::", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "new"), "()?", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"name three uses for a pantograph\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "send"), "().", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "await"), "?;", "\n", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "println!"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"", `{}`, "\""), ", resp.text);"), + typescript: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "import"), " ", "{ Client }", " ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "from"), " ", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"panto\""), ";", "\n\n", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "const"), " resp = ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "await"), " ", /*#__PURE__*/React.createElement("span", { + className: "tok-kw" + }, "new"), " ", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "Client"), "()", "\n", " ", ".", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "complete"), "(", /*#__PURE__*/React.createElement("span", { + className: "tok-str" + }, "\"name three uses for a pantograph\""), ");", "\n", "console.", /*#__PURE__*/React.createElement("span", { + className: "tok-fn" + }, "log"), "(resp.text);") + }; + function InstallSection() { + const [lang, setLang] = React.useState('python'); + return /*#__PURE__*/React.createElement("section", { + className: "hm-install" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-section-head" + }, /*#__PURE__*/React.createElement("p", { + className: "panto-eyebrow" + }, "The SDK"), /*#__PURE__*/React.createElement("h2", { + className: "hm-h2" + }, "Same call, every language."), /*#__PURE__*/React.createElement("p", { + className: "hm-section-sub" + }, "The signature doesn't change when the language does. Learn it once.")), /*#__PURE__*/React.createElement("div", { + className: "hm-install__panel" + }, /*#__PURE__*/React.createElement("div", { + className: "hm-install__tabs" + }, /*#__PURE__*/React.createElement(Tabs, { + variant: "pill", + value: lang, + onChange: setLang, + tabs: ['python', 'rust', 'typescript'] + })), /*#__PURE__*/React.createElement(CodeBlock, { + filename: "install", + language: "shell", + showBar: true, + code: INSTALL[lang] + }), /*#__PURE__*/React.createElement(CodeBlock, { + filename: FILE[lang], + language: lang + }, CODE[lang]))); + } + window.InstallSection = InstallSection; +})(); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/InstallSection.jsx", error: String((e && e.message) || e) }); } + +__ds_ns.Callout = __ds_scope.Callout; + +__ds_ns.CodeBlock = __ds_scope.CodeBlock; + +__ds_ns.Terminal = __ds_scope.Terminal; + +__ds_ns.Badge = __ds_scope.Badge; + +__ds_ns.Button = __ds_scope.Button; + +__ds_ns.Card = __ds_scope.Card; + +__ds_ns.IconButton = __ds_scope.IconButton; + +__ds_ns.Tag = __ds_scope.Tag; + +__ds_ns.Input = __ds_scope.Input; + +__ds_ns.Select = __ds_scope.Select; + +__ds_ns.Switch = __ds_scope.Switch; + +__ds_ns.Tabs = __ds_scope.Tabs; + +})(); diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json new file mode 100644 index 0000000..df50d41 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json @@ -0,0 +1 @@ +{"namespace":"PantographDesignSystem_56a1c7","components":[{"name":"Callout","sourcePath":"components/code/Callout.jsx"},{"name":"CodeBlock","sourcePath":"components/code/CodeBlock.jsx"},{"name":"Terminal","sourcePath":"components/code/Terminal.jsx"},{"name":"Badge","sourcePath":"components/core/Badge.jsx"},{"name":"Button","sourcePath":"components/core/Button.jsx"},{"name":"Card","sourcePath":"components/core/Card.jsx"},{"name":"IconButton","sourcePath":"components/core/IconButton.jsx"},{"name":"Tag","sourcePath":"components/core/Tag.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"}],"startingPoints":[{"name":"Button","path":"components/core/Button.jsx","previewPath":"components/core/core.card.html","kind":"component","section":"Core","subtitle":"Buttons in every variant & size","viewport":"700x220"},{"name":"Card","path":"components/core/Card.jsx","previewPath":"components/core/core.card.html","kind":"component","section":"Core","subtitle":"Surface container with header/body/footer","viewport":"700x320"},{"name":"docs","path":"ui_kits/docs/index.html","previewPath":"ui_kits/docs/index.html","kind":"screen","section":"Docs","subtitle":"libpanto SDK documentation site","viewport":"1280x800"},{"name":"home","path":"ui_kits/home/index.html","previewPath":"ui_kits/home/index.html","kind":"screen","section":"Marketing","subtitle":"pantograph project homepage","viewport":"1280x860"}],"cards":[{"path":"guidelines/brand-logo-variants.card.html","group":"Brand","viewport":"700x160","subtitle":"On paper, on ink, and the monochrome mark. Keep one mark of clearspace.","name":"Logo — variants & placement"},{"path":"guidelines/brand-logo.card.html","group":"Brand","viewport":"700x200","subtitle":"A parallelogram linkage: anchored fulcrum, scaling axis, red pen tip.","name":"Logo — the pantograph mark"},{"path":"guidelines/brand-voice.card.html","group":"Brand","viewport":"700x210","subtitle":"Light, straightforward, fast, minimal. Everything you specify, nothing you don't.","name":"Voice & tone"},{"path":"guidelines/color-accents.card.html","group":"Colors","viewport":"700x190","subtitle":"The only two accents. Used like a draftsman: sparingly, for meaning.","name":"Instrument accents — red & green"},{"path":"guidelines/color-ink.card.html","group":"Colors","viewport":"700x150","subtitle":"Text ramp. The darkest is a blue-black, never pure black.","name":"Ink — deep blue-black marks"},{"path":"guidelines/color-paper.card.html","group":"Colors","viewport":"700x150","subtitle":"Faint warm tint, close to white. No pure #FFF, no yellow cast.","name":"Paper — near-white surfaces"},{"path":"guidelines/color-status.card.html","group":"Colors","viewport":"700x150","subtitle":"Semantic aliases — reference these, not the raw ramp.","name":"Status & surfaces"},{"path":"components/code/code.card.html","group":"Components","viewport":"700x520","subtitle":"The documentation & CLI surfaces.","name":"Code — CodeBlock, Terminal, Callout"},{"path":"components/core/core.card.html","group":"Components","viewport":"700x430","subtitle":"The instrument-palette primitives.","name":"Core — Button, IconButton, Badge, Tag, Card"},{"path":"components/forms/forms.card.html","group":"Components","viewport":"700x340","subtitle":"Green focus ring, red invalid state, engraved labels.","name":"Forms — Input, Select, Switch"},{"path":"components/navigation/navigation.card.html","group":"Components","viewport":"700x200","subtitle":"Engraved red cursor underline, plus a compact pill switch.","name":"Navigation — Tabs"},{"path":"ui_kits/docs/index.html","group":"libpanto Docs","viewport":"1280x800","subtitle":"Header, nav tree, language-switching reference page, TOC.","name":"SDK reference site"},{"path":"ui_kits/home/index.html","group":"panto Home","viewport":"1280x860","subtitle":"Hero with live terminal, language-switching install, feature grid.","name":"Project homepage"},{"path":"guidelines/space-borders.card.html","group":"Spacing","viewport":"700x190","subtitle":"Hairlines, and the engraved tick rule — the standard section separator.","name":"Borders, ticks & rules"},{"path":"guidelines/space-radius.card.html","group":"Spacing","viewport":"700x140","subtitle":"Barely rounded. The instrument body has sharp, machined corners.","name":"Radius — machined edges"},{"path":"guidelines/space-scale.card.html","group":"Spacing","viewport":"700x150","subtitle":"4px base unit. Rigid and gridded, like engraved ticks.","name":"Spacing scale"},{"path":"guidelines/space-shadow.card.html","group":"Spacing","viewport":"700x160","subtitle":"Warm, low, ink-tinted. Like an object resting on a drafting table.","name":"Shadows — ink on paper"},{"path":"guidelines/type-display.card.html","group":"Type","viewport":"700x160","subtitle":"The primary face at display scale. Tall, narrow, engraved character.","name":"Display — Saira Condensed"},{"path":"guidelines/type-eyebrow.card.html","group":"Type","viewport":"700x130","subtitle":"Saira Condensed, caps, +0.14em tracking. The slide-rule scale label.","name":"Engraved label / eyebrow"},{"path":"guidelines/type-mono.card.html","group":"Type","viewport":"700x170","subtitle":"Code, terminals, the panto CLI. The machine voice.","name":"Mono — IBM Plex Mono"},{"path":"guidelines/type-prose.card.html","group":"Type","viewport":"700x300","subtitle":"The primary face carries running body copy too. A full paragraph, set for reading.","name":"Prose — Saira Condensed"},{"path":"guidelines/type-sans.card.html","group":"Type","viewport":"700x190","subtitle":"The primary face also runs the interface: headings, buttons, labels, table data, short copy.","name":"UI & body — Saira Condensed"},{"path":"guidelines/type-scale.card.html","group":"Type","viewport":"700x230","subtitle":"1.250 major-third ramp, rooted at 16px.","name":"Type scale"}],"templates":[],"globalCssPaths":["tokens/fonts.css","tokens/colors.css","tokens/typography.css","tokens/spacing.css","tokens/base.css","styles.css"],"tokens":[{"name":"--paper-0","value":"#FCFAF5","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-1","value":"#F8F4EB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-2","value":"#EFE9DC","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-3","value":"#DFD7C6","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-4","value":"#C7BCA4","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-0","value":"#161B27","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-1","value":"#1E2533","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-2","value":"#3B4456","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-3","value":"#6E6B5C","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-4","value":"#8C8470","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-deep","value":"#8F2B22","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red","value":"#B23A2E","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-soft","value":"#E7C3BB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-wash","value":"#EBD9D0","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-deep","value":"#245138","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green","value":"#2F6B4A","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-soft","value":"#BCD2BF","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-wash","value":"#D6DECB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--brass","value":"#9A7B3F","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-page","value":"var(--paper-1)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-card","value":"var(--paper-0)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-sunken","value":"var(--paper-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-code","value":"#F4EEE1","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-inverse","value":"var(--ink-1)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--text-strong","value":"var(--ink-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-body","value":"var(--ink-1)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-muted","value":"var(--ink-2)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-faint","value":"var(--ink-3)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-placeholder","value":"var(--ink-4)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-on-ink","value":"var(--paper-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-on-accent","value":"var(--paper-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--border-hairline","value":"var(--paper-3)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--border-default","value":"var(--paper-4)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--border-strong","value":"var(--ink-3)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--rule-tick","value":"var(--paper-4)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--accent","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--accent-deep","value":"var(--red-deep)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--link","value":"var(--red-deep)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--link-hover","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-danger","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-danger-bg","value":"var(--red-wash)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-success","value":"var(--green)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-success-bg","value":"var(--green-wash)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-info","value":"var(--ink-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-info-bg","value":"var(--paper-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--focus-ring","value":"var(--green)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--font-primary","value":"'Saira Condensed', 'Arial Narrow', sans-serif","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-mono","value":"'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-display","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-sans","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-serif","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-2xs","value":"0.6875rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-xs","value":"0.75rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-sm","value":"0.875rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-base","value":"1rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-md","value":"1.125rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-lg","value":"1.375rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-xl","value":"1.75rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-2xl","value":"2.25rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-3xl","value":"3rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-4xl","value":"4rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-5xl","value":"5.5rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-regular","value":"400","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-medium","value":"500","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-semibold","value":"600","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-bold","value":"700","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-tight","value":"1.05","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-snug","value":"1.25","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-normal","value":"1.5","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-relaxed","value":"1.7","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-mono","value":"1.6","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-label","value":"0.14em","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-tight","value":"-0.01em","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-normal","value":"0","kind":"font","definedIn":"tokens/typography.css"},{"name":"--role-display","value":"var(--weight-bold) var(--text-4xl)/var(--leading-tight) var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h1","value":"var(--weight-bold) var(--text-3xl)/var(--leading-snug) var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h2","value":"var(--weight-semibold) var(--text-2xl)/var(--leading-snug) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h3","value":"var(--weight-semibold) var(--text-xl)/var(--leading-snug) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-body","value":"var(--weight-regular) var(--text-base)/var(--leading-normal) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-prose","value":"var(--weight-regular) var(--text-md)/var(--leading-relaxed) var(--font-primary)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-code","value":"var(--weight-regular) var(--text-sm)/var(--leading-mono) var(--font-mono)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-label","value":"var(--weight-semibold) var(--text-xs)/1 var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--space-0","value":"0","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-1","value":"0.25rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-2","value":"0.5rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-3","value":"0.75rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-4","value":"1rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-5","value":"1.5rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-6","value":"2rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-7","value":"3rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-8","value":"4rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-9","value":"6rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--radius-none","value":"0","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-xs","value":"2px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-sm","value":"3px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-md","value":"5px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-lg","value":"8px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-pill","value":"999px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--border-thin","value":"1px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--border-medium","value":"1.5px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--border-thick","value":"2px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--shadow-xs","value":"0 1px 0 rgba(30, 37, 51, 0.06)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-sm","value":"0 1px 2px rgba(30, 37, 51, 0.10)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-md","value":"0 2px 6px rgba(30, 37, 51, 0.12), 0 1px 0 rgba(30, 37, 51, 0.04)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-lg","value":"0 8px 24px rgba(30, 37, 51, 0.16)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-inset","value":"inset 0 1px 2px rgba(30, 37, 51, 0.10)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--tick-pitch","value":"8px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--tick-h-major","value":"14px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--tick-h-minor","value":"calc(var(--tick-h-major) / 2)","kind":"color","definedIn":"tokens/spacing.css"},{"name":"--tick-gradient","value":"repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px var(--tick-pitch))\n 0 50% / 100% var(--tick-h-minor) repeat-x,\n repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px calc(var(--tick-pitch) * 5))\n 0 50% / 100% var(--tick-h-major) repeat-x","kind":"color","definedIn":"tokens/spacing.css"},{"name":"--ease-slide","value":"cubic-bezier(0.3, 0.0, 0.2, 1)","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--ease-out","value":"cubic-bezier(0.2, 0.0, 0.0, 1)","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-fast","value":"120ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-base","value":"200ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-slow","value":"320ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--container-prose","value":"720px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--container-wide","value":"1140px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--sidebar-width","value":"264px","kind":"spacing","definedIn":"tokens/spacing.css"}],"themes":[],"fonts":[],"brandFonts":[{"family":"Saira Condensed","status":"ok","tokens":["--font-primary"],"path":"tokens/typography.css"},{"family":"IBM Plex Mono","status":"ok","tokens":["--font-mono"],"path":"tokens/typography.css"}],"source":"spa"}
\ No newline at end of file diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md new file mode 100644 index 0000000..8ea0b0e --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md @@ -0,0 +1,144 @@ +# Pantograph Design System + +The design system for **pantograph** — an open-source project comprising: + +- **libpanto** — an LLM provider SDK, imported as `panto` in each language. It gives one provider-agnostic call signature, faithfully reproduced across its (currently three, "and counting") language bindings: **Python, Rust, TypeScript**. +- **panto** — a terminal coding agent built on top of libpanto. A minimal system prompt and a small, swappable tool set. libpanto and the panto CLI are written in **Zig** (native binaries — maximally fast and resource-efficient); the CLI embeds a **Lua** interpreter for extensions (about as fast as a dynamic scripting language gets). + +This system dresses the project's **web resources**: the libpanto SDK reference docs and the panto CLI docs, plus the marketing homepage. + +> **The metaphor.** A *pantograph* is an old drafting instrument: a linkage of hinged arms that reproduces a small drawing at a larger scale. One small motion in, one faithful enlargement out. That is exactly what libpanto does for language models — write the motion once, scale it to any provider. + +> **The reference object.** The visual language is taken from a **1970s Post Versalog slide rule**: an eggshell, bamboo-bodied precision instrument printed in black, red, and green. So: **no pure white and no pure black** anywhere — warm eggshell "paper" surfaces, deep blue-black "ink" marks, and two engraved accents (instrument red, instrument green). + +--- + +## Sources + +This is a **greenfield** system — no codebase, Figma file, or prior brand was supplied. Everything here is derived from the written brief (the pantograph concept + the Post Versalog slide rule as a physical reference). If a repository or design file exists, link it here so future work can cross-reference: + +- Repository: _(none provided — add the GitHub URL here)_ +- Design file: _(none provided)_ +- Reference object: Post Versalog slide rule (c. 1970s), eggshell over bamboo, black/red/green markings. + +--- + +## CONTENT FUNDAMENTALS + +How pantograph writes. The voice is that of a **well-made instrument manual**: plainspoken, precise, and quietly confident. It never oversells. The throughline is **minimalism** — pantograph is light, straightforward, and fast. *Everything you specify, nothing you don't.* + +- **Tone:** Calm, technical, exact. We describe what the tool *does*, in the order it does it. The pantograph metaphor is used sparingly and only when it clarifies ("write the motion once, scale it to any provider") — never as decoration. +- **Lead with minimalism, not magic.** The product is small on purpose: a minimal system prompt, a small tool set, swap any of it out. It is fast and lean because it is written in Zig (native binaries) and extended in Lua. Say *what isn't there* as a feature. +- **Don't claim gates it doesn't have.** panto has **no permission prompts** — that would be more, not less. So avoid "waits for yes / waits for approval." And avoid "install with one line" as a headline; the story is restraint, not convenience tricks. +- **Person:** Address the reader as **you**; the project refers to itself by name (**panto**, **libpanto**, **pantograph**) or as **we** in prose. Avoid "I". +- **Casing:** The product names are **always lowercase** in body and wordmark: `pantograph`, `panto`, `libpanto`. Sentence case for headings and UI ("Same call, every language"). Engraved labels/eyebrows are **UPPERCASE** with wide tracking (the slide-rule scale-label voice): `SDK REFERENCE`, `GETTING STARTED`. +- **Commands read like commands.** CLI examples use the imperative, lowercase, quoted: `panto run "add rate limiting to the login route"`. +- **Numbers & specifics over adjectives.** "Returns a typed Response — text, usage, stop reason" beats "powerful, flexible responses". Cite versions (`since v2.0`), diffs (`+24 −3`), counts ("3 and counting"). +- **No hype, no emoji.** Never "🚀 unlock the power of next-gen AI". Emoji are **not** used in the brand. (See `guidelines/brand-voice.card.html` for the say/don't-say pairs.) +- **Status is stated, not implied:** `Stable`, `Beta`, `Deprecated`, `since v2.0`, `removed in v3`. + +**Specimen copy** +- Headline: *"Scale one prompt to every provider."* +- Lead: *"libpanto is a single, small set of motions — faithfully reproduced across OpenAI, Anthropic, and your local models."* +- Positioning line: *"Everything you specify, nothing you don't."* +- Callout: *"A minimal prompt and tool set — swap any piece for your own. Written in Zig, extended in Lua."* + +--- + +## VISUAL FOUNDATIONS + +The whole system is an eggshell instrument printed in ink. Restraint is the point. + +### Color +- **Two ramps, two accents, nothing else.** Warm **paper** (eggshell, `#F6F0E2 → #BFAE8A`) for surfaces; deep blue-black **ink** (`#161B27 → #8C8470`) for marks. The darkest ink is a *blue*-black, never `#000`; the lightest paper is eggshell, never `#fff`. +- **Instrument red** (`#B23A2E`) = the cursor line, danger, and the single primary accent. **Instrument green** (`#2F6B4A`) = success, the "folded" scales, focus rings. Used like a draftsman: *sparingly, for meaning, never as fields.* A page should read black-on-eggshell with red/green appearing only at points of emphasis. +- **Brass** (`#9A7B3F`) is a rare hardware accent (the cursor slide, warning admonitions). +- Tokens in `tokens/colors.css`; always reference the **semantic aliases** (`--surface-card`, `--text-body`, `--accent`, `--status-*`) rather than the raw ramp. + +### Type +Two voices — minimal, like the product (see `tokens/typography.css`): +- **Saira Condensed** — **THE primary face**, used everywhere: display headlines, headings, UI, buttons, tables, body, long-form prose, eyebrows, and scale numerals. Tall and narrow with engraved-instrument character; it carries running body copy as well as display. Set in caps with `0.14em` tracking for labels. The `--font-display`, `--font-sans`, and `--font-serif` tokens all alias the primary face, so role tokens and existing components keep working. +- **IBM Plex Mono** — code, terminals, the CLI, tags, tabular numerals — the machine voice. +- Scale is a 1.250 major third rooted at 16px (`--text-xs … --text-5xl`). + +### Backgrounds & texture +- Flat eggshell paper — **no photographic backgrounds, no gradients as fields, no glassmorphism.** The one recurring graphic device is the **engraved tick rule** (`--tick-gradient`, `.panto-tick-rule`) — the **standard separator between sections**: short ticks every `--tick-pitch` (8px) with **every 5th tick at double height**, all centered on a common axis. The homepage hero also carries a slide-rule **ruler strip**: a literal printed scale. +- The terminal is the **only** dark surface in the system — the one place ink becomes a background. + +### Shape, borders, radius +- **Machined, minimal corners.** `--radius-xs:2px` (inputs), `sm:3px` (buttons/badges), `md:5px` (cards), `lg:8px` (dialogs). `pill` is reserved for tags and the switch, used deliberately. +- **Hairline rules** (`--border-hairline`) divide; **engraved top rules** (a thick ink or red bar on a `ruled` Card) mark importance. Borders are warm paper/ink tones, never cool gray. + +### Shadow +- Warm, low, **ink-tinted** (never neutral or blue): `--shadow-xs … --shadow-lg`, plus `--shadow-inset` for sunken fields. Like an object resting on a drafting table. Cards sit on a shallow `sm`; they lift to `md` on hover. + +### Cards +- Raised eggshell (`--surface-card`, lighter than the page), hairline border, `--radius-md`, shallow shadow. `sunken` variant inverts to an inset secondary surface. `ruled` adds the engraved top scale-rule (ink, or red for `accent`). + +### Motion +- **Mechanical, precise, no bounce** — like a sliding cursor. `--ease-slide` / `--ease-out`; durations `120 / 200 / 320ms`. Tabs slide their red underline; switches glide; buttons press *down 1px* with the shadow collapsing. The terminal cursor blinks (respecting `prefers-reduced-motion`). No floaty fades, no spring overshoot. + +### States +- **Hover:** darker fill (primary → `ink-0`), or a paper-2 wash for low-emphasis controls; links shift red and underline. +- **Press:** `translateY(1px)` with shadow removed. +- **Focus:** a 2px **instrument-green** outline (`--focus-ring`) at 2px offset, or a green ring + tinted glow on inputs. +- **Invalid:** instrument red border + red wash glow. +- **Disabled:** ~45% opacity, no shadow, `not-allowed`. + +### Layout +- Reading column `--container-prose: 720px`; app/marketing `--container-wide: 1140px`; docs sidebar `264px`. Sticky header + sticky sidebar/TOC; content stays in a measured column. Generous vertical rhythm on the 4px grid. + +### Transparency & blur +- Used **once**: the sticky docs header is `color-mix` paper at ~88% with an 8px backdrop blur, so content scrolls under it. Otherwise surfaces are opaque. + +--- + +## ICONOGRAPHY + +- **System set: [Lucide](https://lucide.dev)** — clean line icons, 2px stroke, 24px grid, rounded caps and joins. Their precision and uniform weight suit a drafting instrument. + - **Substitution flag:** because this is greenfield with no supplied icon assets, Lucide is a *chosen* set, not a recreation of an existing one. The UI kits inline a curated subset of Lucide-derived paths in `Icons.jsx` (Lucide is ISC-licensed). If the project later ships its own icons, replace `Icons.jsx` and document here. +- **Icons inherit `currentColor`** and ride the ink ramp; they pick up red/green only inside an accented context (feature tiles, active states). +- **The logo mark is the one bespoke glyph** (`assets/logo-mark.svg`): a pantograph parallelogram linkage — anchored fulcrum, dashed scaling axis, **red pen tip**. `logo-mark-mono.svg` is the single-color variant. Arms inherit `currentColor`. +- **No emoji.** No decorative illustration. No multi-color icons. Status uses the engraved `Badge`, not an icon. + +--- + +## INDEX — what's in this system + +**Foundations / global CSS** +- `styles.css` — entry point (consumers link this one file); `@import`s only. +- `tokens/fonts.css` — webfonts (Google Fonts; see substitution note below). +- `tokens/colors.css` · `tokens/typography.css` · `tokens/spacing.css` — the token ramps + semantic aliases. +- `tokens/base.css` — base element styling + helpers (`.panto-prose`, `.panto-eyebrow`, `.panto-tick-rule`). + +**Specimen cards** (Design System tab) — in `guidelines/` +- Colors: paper, ink, accents, status & surfaces. +- Type: display, sans, prose, mono, scale, eyebrow. +- Spacing: scale, radius, shadows, borders & ticks. +- Brand: logo, logo variants, voice & tone. + +**Components** — in `components/` (React, reference tokens via CSS vars) +- `core/` — **Button, IconButton, Badge, Tag, Card** +- `forms/` — **Input, Select, Switch** +- `navigation/` — **Tabs** (underline + pill) +- `code/` — **CodeBlock, Terminal, Callout** + +Each directory has `<Name>.jsx` + `<Name>.d.ts` + `<Name>.prompt.md` and one `@dsCard` showcase HTML. + +**UI kits** — in `ui_kits/` +- `docs/` — the **libpanto SDK reference site** (header, nav tree, language-switching reference page, TOC). +- `home/` — the **pantograph project homepage** (hero + live terminal, install switcher, feature grid, footer). + +**Brand assets** — in `assets/` +- `logo-mark.svg` (red pen tip) · `logo-mark-mono.svg` (single color). + +**Other** +- `SKILL.md` — makes this folder usable as a downloadable Agent Skill. + +--- + +## Font note — self-host when ready + +Both typefaces load from **Google Fonts** via `@import` in `tokens/fonts.css` (so the compiler reports **0 `@font-face` rules** — fonts come from Google's network CSS, not self-hosted binaries). The system deliberately runs on just two: **Saira Condensed** (the single primary face) and **IBM Plex Mono**. + +If you want self-hosted fonts (offline, no Google dependency) or have specific licensed faces in mind, send the font files and I'll wire real `@font-face` rules. The mono could become JetBrains Mono or Berkeley Mono; if Saira Condensed ever feels too narrow for long-form docs, a slightly wider engineering gothic is the natural fallback — but the brief is one primary face, used everywhere. diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css new file mode 100644 index 0000000..c6b7224 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css @@ -0,0 +1,13 @@ +/* ============================================================ + Pantograph Design System — global entry point + ------------------------------------------------------------ + Consumers link THIS one file. It is a manifest of @imports + only — no rules live here. Everything reachable from here is + shipped to consuming projects. + ============================================================ */ + +@import url('tokens/fonts.css'); +@import url('tokens/colors.css'); +@import url('tokens/typography.css'); +@import url('tokens/spacing.css'); +@import url('tokens/base.css'); diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css new file mode 100644 index 0000000..6b8272a --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css @@ -0,0 +1,113 @@ +/* ============================================================ + Pantograph — base element styling + ------------------------------------------------------------ + A light, opinionated reset that binds the tokens to real + elements. Optional for consumers, but every specimen card, + component card, and UI kit in this system assumes it. + ============================================================ */ + +*, *::before, *::after { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + background-color: var(--surface-page); + color: var(--text-body); + font: var(--role-body); + letter-spacing: var(--tracking-normal); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Headings default to the engraved display voice */ +h1, h2, h3, h4, h5, h6 { + margin: 0 0 var(--space-3); + color: var(--text-strong); + font-family: var(--font-sans); + font-weight: var(--weight-semibold); + line-height: var(--leading-snug); + letter-spacing: var(--tracking-tight); + text-wrap: balance; +} +h1 { font: var(--role-h1); letter-spacing: var(--tracking-tight); } +h2 { font: var(--role-h2); } +h3 { font: var(--role-h3); } + +p { margin: 0 0 var(--space-4); text-wrap: pretty; } + +a { + color: var(--link); + text-decoration: none; + text-underline-offset: 0.18em; + text-decoration-thickness: 1px; + transition: color var(--dur-fast) var(--ease-out); +} +a:hover { color: var(--link-hover); text-decoration: underline; } + +strong, b { font-weight: var(--weight-semibold); color: var(--text-strong); } + +code, kbd, samp, pre { + font-family: var(--font-mono); + font-size: 0.92em; +} + +code { + background: var(--surface-code); + border: var(--border-thin) solid var(--border-hairline); + border-radius: var(--radius-xs); + padding: 0.08em 0.36em; + color: var(--ink-1); +} + +pre { + font-size: var(--text-sm); + line-height: var(--leading-mono); + margin: 0; +} +pre code { background: none; border: none; padding: 0; } + +hr { + border: 0; + height: 1px; + background: var(--border-hairline); + margin: var(--space-6) 0; +} + +/* The engraved-tick rule: THE standard section separator. + Short ticks with every 5th at double height (see + --tick-gradient in tokens/spacing.css). Height = the tall + tick; short ticks center on the same axis. */ +.panto-tick-rule { + border: 0; + height: var(--tick-h-major); + background: var(--tick-gradient); + opacity: 0.85; + margin: var(--space-6) 0; +} + +::selection { background: var(--green-soft); color: var(--ink-0); } + +:focus-visible { + outline: var(--border-thick) solid var(--focus-ring); + outline-offset: 2px; +} + +/* Long-form documentation prose — the primary face, set larger + and looser for comfortable reading */ +.panto-prose { + font: var(--role-prose); + color: var(--text-body); + max-width: var(--container-prose); +} +.panto-prose p { margin-bottom: var(--space-5); } + +/* Engraved eyebrow / kicker label */ +.panto-eyebrow { + font-family: var(--font-display); + font-weight: var(--weight-semibold); + font-size: var(--text-xs); + letter-spacing: var(--tracking-label); + text-transform: uppercase; + color: var(--text-faint); +} diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css new file mode 100644 index 0000000..7fef466 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css @@ -0,0 +1,87 @@ +/* ============================================================ + Pantograph — Color tokens + ------------------------------------------------------------ + Inspired by a 1970s Post Versalog slide rule: an eggshell + bamboo body printed in black, red, and green. There is NO + pure white and NO pure black anywhere in the system. + + Base ramp = warm "paper" (eggshell) surfaces and "ink" + (deep blue-black) marks. Two engraved accents: instrument + red and instrument green. Use accents like a draftsman — + sparingly, for emphasis and meaning, never as fields. + ============================================================ */ + +:root { + /* --- Paper (near-white surfaces, faint warm tint, lightest → deepest) --- */ + --paper-0: #FCFAF5; /* raised cards, popovers, code surfaces */ + --paper-1: #F8F4EB; /* default page background */ + --paper-2: #EFE9DC; /* sunken / secondary fields, table stripes */ + --paper-3: #DFD7C6; /* hairline rules, dividers, engraved ticks */ + --paper-4: #C7BCA4; /* strong borders, disabled fills */ + + /* --- Ink (deep blue-black marks, darkest → lightest) --- */ + --ink-0: #161B27; /* near-black blue ink: max-contrast headings */ + --ink-1: #1E2533; /* primary body text, the default mark color */ + --ink-2: #3B4456; /* secondary text, captions */ + --ink-3: #6E6B5C; /* muted / warm-gray metadata */ + --ink-4: #8C8470; /* faint hints, placeholder text */ + + /* --- Instrument Red (cursor line, warnings, the C/D scale) --- */ + --red-deep: #8F2B22; + --red: #B23A2E; /* the canonical slide-rule red */ + --red-soft: #E7C3BB; /* tinted backgrounds */ + --red-wash: #EBD9D0; /* faintest red wash on paper */ + + /* --- Instrument Green (folded scales, success, positive) --- */ + --green-deep: #245138; + --green: #2F6B4A; /* the canonical slide-rule green */ + --green-soft: #BCD2BF; /* tinted backgrounds */ + --green-wash: #D6DECB; /* faintest green wash on paper */ + + /* --- Brass (the cursor / hardware accent, used very rarely) --- */ + --brass: #9A7B3F; + + /* ============================================================ + Semantic aliases — reference THESE in components, not the + raw ramp above. + ============================================================ */ + + /* Surfaces */ + --surface-page: var(--paper-1); + --surface-card: var(--paper-0); + --surface-sunken: var(--paper-2); + --surface-code: #F4EEE1; /* code blocks: a hair warmer than card */ + --surface-inverse: var(--ink-1); + + /* Text */ + --text-strong: var(--ink-0); + --text-body: var(--ink-1); + --text-muted: var(--ink-2); + --text-faint: var(--ink-3); + --text-placeholder: var(--ink-4); + --text-on-ink: var(--paper-0); + --text-on-accent: var(--paper-0); + + /* Lines */ + --border-hairline: var(--paper-3); + --border-default: var(--paper-4); + --border-strong: var(--ink-3); + --rule-tick: var(--paper-4); + + /* Accents */ + --accent: var(--red); /* primary brand accent */ + --accent-deep: var(--red-deep); + --link: var(--red-deep); + --link-hover: var(--red); + + /* Status */ + --status-danger: var(--red); + --status-danger-bg: var(--red-wash); + --status-success: var(--green); + --status-success-bg:var(--green-wash); + --status-info: var(--ink-2); + --status-info-bg: var(--paper-2); + + /* Focus ring */ + --focus-ring: var(--green); +} diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css new file mode 100644 index 0000000..637c1a1 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css @@ -0,0 +1,19 @@ +/* ============================================================ + Pantograph — Webfonts + ------------------------------------------------------------ + Two families, loaded from Google Fonts. The system runs on a + single primary face plus a monospace — minimal by design, the + way the product is. See readme.md → VISUAL FOUNDATIONS. + + Saira Condensed → THE primary face. Display, headings, UI, + body, labels, prose. Tall, narrow, and + precise — engraved-instrument character. + IBM Plex Mono → code, terminals, the panto CLI, tabular + numerals — the machine voice. + + Saira Condensed loads 400–700 (plus 400/500 italic) so it can + carry running body copy, not just display. @import MUST be the + first statement, before any other rule. + ============================================================ */ + +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Saira+Condensed:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap'); diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css new file mode 100644 index 0000000..b06cdc8 --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css @@ -0,0 +1,72 @@ +/* ============================================================ + Pantograph — Spacing, radius, borders, shadow, motion + ------------------------------------------------------------ + A slide rule is a precision instrument: spacing is rigid and + gridded (4px base unit), corners are barely rounded (the body + has sharp machined edges), and shadows are shallow and warm, + like an object resting on a drafting table — never glassy + blue drop-shadows. + ============================================================ */ + +:root { + /* Spacing scale (4px base) */ + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.5rem; /* 24px */ + --space-6: 2rem; /* 32px */ + --space-7: 3rem; /* 48px */ + --space-8: 4rem; /* 64px */ + --space-9: 6rem; /* 96px */ + + /* Radius — machined, minimal. Nothing pill-shaped except by intent. */ + --radius-none: 0; + --radius-xs: 2px; /* inputs, code chips */ + --radius-sm: 3px; /* buttons, badges */ + --radius-md: 5px; /* cards, panels */ + --radius-lg: 8px; /* large surfaces, dialogs */ + --radius-pill: 999px; /* tags, switches — use deliberately */ + + /* Border widths */ + --border-thin: 1px; + --border-medium: 1.5px; + --border-thick: 2px; + + /* Shadows — warm, low, like ink/paper on a table. Tinted with + ink, never neutral gray, never blue. Keep them shallow. */ + --shadow-xs: 0 1px 0 rgba(30, 37, 51, 0.06); + --shadow-sm: 0 1px 2px rgba(30, 37, 51, 0.10); + --shadow-md: 0 2px 6px rgba(30, 37, 51, 0.12), 0 1px 0 rgba(30, 37, 51, 0.04); + --shadow-lg: 0 8px 24px rgba(30, 37, 51, 0.16); + --shadow-inset: inset 0 1px 2px rgba(30, 37, 51, 0.10); + + /* Engraved-tick separator: THE standard section break. + A ruled scale — short ticks every --tick-pitch, with every + 5th tick at DOUBLE height, all centered on a common axis. + The short ticks sit 20% shorter than a plain rule would, so + the tall ones stand out without making the whole rule tall. + Drive size with --tick-h-major (= the rule's height); the + short tick is exactly half of it. */ + --tick-pitch: 8px; /* spacing between ticks */ + --tick-h-major: 14px; /* tall (every-5th) tick = rule height */ + --tick-h-minor: calc(var(--tick-h-major) / 2); /* short tick — half the tall one */ + --tick-gradient: + repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px var(--tick-pitch)) + 0 50% / 100% var(--tick-h-minor) repeat-x, + repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px calc(var(--tick-pitch) * 5)) + 0 50% / 100% var(--tick-h-major) repeat-x; + + /* Motion — mechanical, precise, no bounce. Like a sliding cursor. */ + --ease-slide: cubic-bezier(0.3, 0.0, 0.2, 1); /* @kind other */ + --ease-out: cubic-bezier(0.2, 0.0, 0.0, 1); /* @kind other */ + --dur-fast: 120ms; /* @kind other */ + --dur-base: 200ms; /* @kind other */ + --dur-slow: 320ms; /* @kind other */ + + /* Layout */ + --container-prose: 720px; /* documentation reading column */ + --container-wide: 1140px; /* app / marketing max width */ + --sidebar-width: 264px; +} diff --git a/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css new file mode 100644 index 0000000..be943df --- /dev/null +++ b/_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css @@ -0,0 +1,66 @@ +/* ============================================================ + Pantograph — Typography tokens + ------------------------------------------------------------ + Two voices. Minimal, like the product: + • Saira Condensed — THE primary face. Display, headings, + UI, body, prose, eyebrows, scale numerals. Tall and + narrow; reads as engraved instrument lettering. + • IBM Plex Mono — code, terminals, the panto CLI, tabular + numerals, anything "machine". + + --font-sans / --font-serif / --font-display all resolve to + Saira Condensed, so role tokens and existing components keep + working while the system runs on a single primary face. + ============================================================ */ + +:root { + /* Families — one primary face, one mono */ + --font-primary: 'Saira Condensed', 'Arial Narrow', sans-serif; + --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace; + + /* Aliases — all point at the primary face (legacy role names) */ + --font-display: var(--font-primary); + --font-sans: var(--font-primary); + --font-serif: var(--font-primary); + + /* Type scale (1.250 major-third, rooted at 16px) */ + --text-2xs: 0.6875rem; /* 11px — tick labels, legal */ + --text-xs: 0.75rem; /* 12px — captions, eyebrows */ + --text-sm: 0.875rem; /* 14px — secondary UI text */ + --text-base: 1rem; /* 16px — body */ + --text-md: 1.125rem; /* 18px — lead paragraph */ + --text-lg: 1.375rem; /* 22px — h4 / card titles */ + --text-xl: 1.75rem; /* 28px — h3 */ + --text-2xl: 2.25rem; /* 36px — h2 */ + --text-3xl: 3rem; /* 48px — h1 */ + --text-4xl: 4rem; /* 64px — display */ + --text-5xl: 5.5rem; /* 88px — hero display */ + + /* Weights */ + --weight-regular: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; + + /* Line heights */ + --leading-tight: 1.05; /* display */ + --leading-snug: 1.25; /* headings */ + --leading-normal: 1.5; /* UI / sans body */ + --leading-relaxed: 1.7; /* serif prose */ + --leading-mono: 1.6; /* code */ + + /* Letter spacing */ + --tracking-label: 0.14em; /* ALL-CAPS engraved labels */ + --tracking-tight: -0.01em; /* large display */ + --tracking-normal: 0; + + /* ---- Semantic roles ---- */ + --role-display: var(--weight-bold) var(--text-4xl)/var(--leading-tight) var(--font-display); + --role-h1: var(--weight-bold) var(--text-3xl)/var(--leading-snug) var(--font-display); + --role-h2: var(--weight-semibold) var(--text-2xl)/var(--leading-snug) var(--font-sans); + --role-h3: var(--weight-semibold) var(--text-xl)/var(--leading-snug) var(--font-sans); + --role-body: var(--weight-regular) var(--text-base)/var(--leading-normal) var(--font-sans); + --role-prose: var(--weight-regular) var(--text-md)/var(--leading-relaxed) var(--font-primary); + --role-code: var(--weight-regular) var(--text-sm)/var(--leading-mono) var(--font-mono); + --role-label: var(--weight-semibold) var(--text-xs)/1 var(--font-display); +} diff --git a/_shared/components.css b/_shared/components.css new file mode 100644 index 0000000..2b50d99 --- /dev/null +++ b/_shared/components.css @@ -0,0 +1,160 @@ +/* ============================================================ + panto docs — static component styles. + Extracted from the Pantograph design system's React components + (Badge, Tag, Terminal, CodeBlock, Callout) so the docs render + identically as plain HTML, with no JavaScript and no React. + These mirror the styles those components inject at runtime. + ============================================================ */ + +/* ---- Badge ----------------------------------------------------------- */ +.pg-badge { + display: inline-flex; align-items: center; gap: 5px; + font-family: var(--font-display); font-weight: var(--weight-semibold); + font-size: var(--text-2xs); letter-spacing: var(--tracking-label); + text-transform: uppercase; line-height: 1; white-space: nowrap; + padding: 4px 7px; border-radius: var(--radius-xs); + border: var(--border-thin) solid transparent; +} +.pg-badge__dot { width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; } +.pg-badge--neutral { color: var(--ink-2); background: var(--paper-2); border-color: var(--paper-3); } +.pg-badge--danger { color: var(--red-deep); background: var(--red-wash); border-color: var(--red-soft); } +.pg-badge--success { color: var(--green-deep); background: var(--green-wash); border-color: var(--green-soft); } +.pg-badge--ink { color: var(--paper-0); background: var(--ink-1); border-color: var(--ink-1); } +.pg-badge--outline { color: var(--ink-2); background: transparent; border-color: var(--border-default); } + +/* ---- Tag ------------------------------------------------------------- */ +.pg-tag { + display: inline-flex; align-items: center; gap: 6px; + font-family: var(--font-mono); font-weight: var(--weight-medium); + font-size: var(--text-xs); line-height: 1; white-space: nowrap; + color: var(--ink-1); background: var(--paper-0); + padding: 5px 9px; border-radius: var(--radius-pill); + border: var(--border-thin) solid var(--border-default); +} +.pg-tag__dot { width: 7px; height: 7px; border-radius: var(--radius-pill); flex: none; background: var(--ink-3); } + +/* ---- Callout (admonition) -------------------------------------------- */ +.pg-callout { + display: flex; gap: 12px; + padding: 13px 15px; + background: var(--paper-0); + border: var(--border-thin) solid var(--border-hairline); + border-left: var(--border-thick) solid var(--ink-3); + border-radius: var(--radius-sm); +} +.pg-callout__icon { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--ink-3); } +.pg-callout__icon svg { width: 100%; height: 100%; } +.pg-callout__body { font: var(--weight-regular) var(--text-sm)/1.55 var(--font-sans); color: var(--ink-1); } +.pg-callout__title { + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; + margin: 0 0 5px; color: var(--ink-2); +} +.pg-callout__body p { margin: 0 0 var(--space-2); } +.pg-callout__body :last-child { margin-bottom: 0; } +.pg-callout--note { border-left-color: var(--ink-3); } +.pg-callout--tip { border-left-color: var(--green); background: var(--green-wash); } +.pg-callout--tip .pg-callout__icon, .pg-callout--tip .pg-callout__title { color: var(--green-deep); } +.pg-callout--warn { border-left-color: var(--brass); background: #F0E7D0; } +.pg-callout--warn .pg-callout__icon, .pg-callout--warn .pg-callout__title { color: #7A5E20; } +.pg-callout--danger { border-left-color: var(--red); background: var(--red-wash); } +.pg-callout--danger .pg-callout__icon, .pg-callout--danger .pg-callout__title { color: var(--red-deep); } + +/* ---- CodeBlock ------------------------------------------------------- */ +.pg-code { + background: var(--surface-code); + border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-md); + overflow: hidden; + font-family: var(--font-mono); +} +.pg-code__bar { + display: flex; align-items: center; gap: 10px; + padding: 8px 12px; background: var(--paper-2); + border-bottom: var(--border-thin) solid var(--border-hairline); +} +.pg-code__name { font: var(--weight-medium) var(--text-xs)/1 var(--font-mono); color: var(--ink-2); } +.pg-code__lang { + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-3); + margin-left: auto; padding-right: 4px; +} +.pg-code__copy { + display: inline-flex; align-items: center; gap: 5px; + font: var(--weight-medium) var(--text-2xs)/1 var(--font-display); + letter-spacing: 0.1em; text-transform: uppercase; + color: var(--ink-3); background: none; border: 0; cursor: pointer; + padding: 4px 6px; border-radius: var(--radius-xs); + transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); +} +.pg-code__copy:hover { color: var(--ink-0); background: var(--paper-1); } +.pg-code__copy--done { color: var(--green-deep); } +.pg-code__copy svg { width: 13px; height: 13px; } +.pg-code__copy .pg-code__check { display: none; } +.pg-code__copy--done .pg-code__check { display: inline; } +.pg-code__copy--done .pg-code__copyglyph { display: none; } +.pg-code__scroll { overflow-x: auto; padding: 14px 16px; } +.pg-code pre { font-size: var(--text-sm); line-height: var(--leading-mono); color: var(--ink-1); margin: 0; } +.pg-code code { font-family: inherit; background: none; border: 0; padding: 0; color: inherit; } +/* token colors for hand-marked spans */ +.pg-code .tok-kw { color: var(--green-deep); } +.pg-code .tok-str { color: var(--red-deep); } +.pg-code .tok-com { color: var(--ink-4); font-style: italic; } +.pg-code .tok-fn { color: var(--ink-0); font-weight: 500; } +.pg-code .tok-num { color: var(--brass); } + +/* ---- Terminal -------------------------------------------------------- */ +.pg-term { + background: var(--ink-1); + border: var(--border-thin) solid var(--ink-0); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-md); + font-family: var(--font-mono); +} +.pg-term__bar { + display: flex; align-items: center; gap: 8px; + padding: 9px 12px; background: #232B3B; + border-bottom: var(--border-thin) solid #2E3749; +} +.pg-term__dot { width: 9px; height: 9px; border-radius: var(--radius-pill); } +.pg-term__title { + margin-left: 6px; + font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display); + letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--paper-4); +} +.pg-term__body { padding: 14px 16px; overflow-x: auto; font-size: var(--text-sm); line-height: 1.65; } +.pg-term__line { white-space: pre; color: var(--paper-2); } +.pg-term__line .pg-term__prompt { color: var(--green-soft); user-select: none; } +.pg-term__line .pg-term__panto { color: var(--red-soft); user-select: none; } +.pg-term__line--cmd { color: var(--paper-0); } +.pg-term__line--out { color: var(--paper-4); } +.pg-term__line--ok { color: var(--green-soft); } +.pg-term__line--err { color: var(--red-soft); } +.pg-term__line--com { color: #6E7689; font-style: italic; } +.pg-term__cursor { + display: inline-block; width: 8px; height: 1.05em; vertical-align: text-bottom; + background: var(--red); margin-left: 2px; + animation: pg-term-blink 1.1s steps(1) infinite; +} +@keyframes pg-term-blink { 50% { opacity: 0; } } +@media (prefers-reduced-motion: reduce) { .pg-term__cursor { animation: none; } } +/* panto TUI mock: welcome banner, user block, tool boxes, footer, input frame */ +.pg-term__line--accent { color: var(--red-soft); } +.pg-term__line--dim { color: #6E7689; } +.pg-term__user { + white-space: pre-wrap; color: var(--paper-0); + background: rgba(178,58,46,0.16); padding: 5px 10px; margin: 3px 0; +} +.pg-term__tool { margin: 3px 0; } +.pg-term__tool-head { white-space: pre; color: var(--paper-0); padding-left: 1ch; } +.pg-term__tool-body { + white-space: pre; color: var(--paper-4); + padding: 3px 0 0 calc(1ch + 8px); margin-left: 1ch; + border-left: 2px solid #2E3749; +} +.pg-term__add { color: var(--green-soft); } +.pg-term__del { color: var(--red-soft); } +.pg-term .pg-term__rule { border: 0; border-top: 1px solid #2E3749; margin: 7px 0; } +.pg-term code { background: none; border: 0; padding: 0; color: var(--green-soft); font: inherit; } +.pg-term__user code { color: inherit; } diff --git a/_shared/docs.css b/_shared/docs.css new file mode 100644 index 0000000..bfa1e44 --- /dev/null +++ b/_shared/docs.css @@ -0,0 +1,202 @@ +/* ============================================================ + panto docs — shared documentation chrome. + Built on the Pantograph design system tokens. Class prefix: pd- + ============================================================ */ + +body { margin: 0; background: var(--surface-page); } +html { scroll-behavior: smooth; } +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } + +.pd-app { min-height: 100vh; } + +/* ---- Header ---------------------------------------------------------- */ +.pd-header { + position: sticky; top: 0; z-index: 30; + display: flex; align-items: center; gap: var(--space-5); + height: 60px; padding: 0 var(--space-6); + background: color-mix(in srgb, var(--paper-1) 88%, transparent); + backdrop-filter: blur(8px); + border-bottom: var(--border-thin) solid var(--border-hairline); +} +.pd-header__left { display: flex; align-items: center; gap: var(--space-3); } +.pd-menu { display: none; background: none; border: 0; cursor: pointer; color: var(--ink-2); font-size: 20px; padding: 4px; } +.pd-brand { display: flex; align-items: center; gap: 9px; text-decoration: none; } +.pd-brand:hover { text-decoration: none; } +.pd-brand__word { font: 700 20px var(--font-display); letter-spacing: -0.01em; color: var(--ink-0); } +.pd-brand__div { color: var(--paper-4); font: 400 18px var(--font-display); } +.pd-brand__sub { font: 600 18px var(--font-display); color: var(--ink-3); } +.pd-nav { display: flex; gap: 2px; margin-left: var(--space-4); } +.pd-nav__link { + font: 500 var(--text-sm)/1 var(--font-sans); color: var(--ink-3); + padding: 8px 11px; border-radius: var(--radius-xs); text-decoration: none; + transition: color var(--dur-fast), background var(--dur-fast); +} +.pd-nav__link:hover { color: var(--ink-1); background: var(--paper-2); text-decoration: none; } +.pd-nav__link--active { color: var(--ink-0); font-weight: 600; } +.pd-header__right { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; } +.pd-ghlink { display: inline-flex; align-items: center; gap: 7px; font: 500 var(--text-sm) var(--font-mono); color: var(--ink-2); text-decoration: none; } +.pd-ghlink:hover { color: var(--ink-0); text-decoration: none; } +.pd-ghlink svg { font-size: 16px; } + +/* ---- Shell grid ------------------------------------------------------ */ +.pd-shell { + display: grid; + grid-template-columns: var(--sidebar-width) minmax(0, 1fr) 220px; + max-width: 1340px; margin: 0 auto; + align-items: start; +} +.pd-sidebar { + position: sticky; top: 60px; align-self: start; + height: calc(100vh - 60px); overflow: hidden; + border-right: var(--border-thin) solid var(--border-hairline); +} +.pd-sidebar__scroll { height: 100%; overflow-y: auto; padding: var(--space-5) var(--space-4) var(--space-8); } +.pd-navsec { margin-bottom: var(--space-5); } +.pd-navsec__title { + display: flex; align-items: center; gap: 7px; + font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); + text-transform: uppercase; color: var(--ink-4); margin: 0 0 var(--space-2); padding-left: 10px; +} +.pd-navsec__title svg { font-size: 13px; } +.pd-navsec--current .pd-navsec__title { color: var(--red-deep); } +.pd-navsec__list { list-style: none; margin: 0; padding: 0; } +.pd-navsec__link { + display: block; font: 400 var(--text-sm)/1.2 var(--font-sans); color: var(--ink-2); + text-decoration: none; padding: 7px 10px; border-radius: var(--radius-xs); + border-left: 2px solid transparent; transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast); +} +.pd-navsec__link:hover { color: var(--ink-0); background: var(--paper-2); text-decoration: none; } +.pd-navsec__link--active { + color: var(--red-deep); font-weight: 600; background: var(--paper-0); + border-left-color: var(--red); +} +.pd-navsec__link code { background: none; border: 0; padding: 0; color: inherit; font-size: 0.92em; } + +/* ---- Main ------------------------------------------------------------ */ +.pd-main { padding: var(--space-6) var(--space-7) var(--space-9); min-width: 0; } +.pd-doc { max-width: 800px; } +.pd-eyebrow { display: block; margin-bottom: var(--space-2); } +.pd-doc__h1 { font: 700 var(--text-3xl) var(--font-display); color: var(--ink-0); letter-spacing: -0.01em; margin: 0 0 var(--space-4); } +.pd-doc__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-5); } +.pd-lead { font: 400 var(--text-md)/1.6 var(--font-serif); color: var(--ink-1); margin: 0 0 var(--space-6); max-width: 66ch; } + +.pd-h2 { + font: 600 var(--text-xl) var(--font-sans); color: var(--ink-0); + margin: var(--space-8) 0 var(--space-4); padding-bottom: var(--space-2); + border-bottom: var(--border-thin) solid var(--border-hairline); + scroll-margin-top: 76px; +} +.pd-h3 { font: 600 var(--text-lg) var(--font-sans); color: var(--ink-0); margin: var(--space-6) 0 var(--space-3); scroll-margin-top: 76px; } +.pd-body { font: 400 var(--text-base)/1.65 var(--font-sans); color: var(--ink-1); max-width: 66ch; } +.pd-body + .pd-body { margin-top: var(--space-4); } +.pd-doc a { text-decoration-thickness: 1px; } +.pd-doc > .pg-callout, .pd-doc .pd-callout { margin: var(--space-5) 0; } +.pd-doc .pg-code, .pd-doc .pg-term { margin: var(--space-4) 0; max-width: 800px; } +.pd-section { margin-bottom: var(--space-2); } + +/* ---- Command signature header --------------------------------------- */ +.pd-cmd { + display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); + margin: var(--space-6) 0 var(--space-2); scroll-margin-top: 76px; +} +.pd-cmd__sig { + font: 600 var(--text-md) var(--font-mono); color: var(--ink-0); + background: var(--surface-code); border: var(--border-thin) solid var(--border-default); + border-radius: var(--radius-sm); padding: 5px 10px; +} +.pd-cmd__sig .opt { color: var(--ink-3); } +.pd-cmd__note { font: 400 var(--text-sm) var(--font-sans); color: var(--ink-3); } + +/* ---- Option / parameter list ---------------------------------------- */ +.pd-opts { display: flex; flex-direction: column; gap: 0; margin: var(--space-4) 0; border-top: var(--border-thin) solid var(--border-hairline); } +.pd-opt { + display: grid; grid-template-columns: minmax(160px, 240px) 1fr; gap: var(--space-4); + padding: var(--space-4) var(--space-2); border-bottom: var(--border-thin) solid var(--border-hairline); +} +.pd-opt__key { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; } +.pd-opt__name { font: 500 var(--text-sm) var(--font-mono); color: var(--ink-0); } +.pd-opt__type { font: 400 var(--text-xs) var(--font-mono); color: var(--green-deep); } +.pd-opt__desc { font: 400 var(--text-sm)/1.6 var(--font-sans); color: var(--ink-1); } +.pd-opt__desc code { font-size: 0.9em; } +.pd-opt__desc p { margin: 0 0 var(--space-2); } +.pd-opt__desc p:last-child { margin: 0; } +@media (max-width: 640px) { .pd-opt { grid-template-columns: 1fr; gap: var(--space-2); } } + +/* ---- Table ----------------------------------------------------------- */ +.pd-table-wrap { border: var(--border-thin) solid var(--border-hairline); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; } +.pd-table { width: 100%; border-collapse: collapse; font: 400 var(--text-sm) var(--font-sans); } +.pd-table th { + text-align: left; font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); + text-transform: uppercase; color: var(--ink-3); background: var(--paper-2); + padding: 10px 14px; border-bottom: var(--border-thin) solid var(--border-hairline); +} +.pd-table td { padding: 11px 14px; border-bottom: var(--border-thin) solid var(--paper-2); color: var(--ink-1); vertical-align: top; } +.pd-table tr:last-child td { border-bottom: 0; } +.pd-table tr:nth-child(even) td { background: color-mix(in srgb, var(--paper-2) 40%, transparent); } +.pd-table td code { font-size: 0.9em; } +.pd-table .mono { font-family: var(--font-mono); } +.pd-table .t-type { font: 400 var(--text-xs) var(--font-mono); color: var(--green-deep); } +.pd-table .t-def { font: 400 var(--text-xs) var(--font-mono); color: var(--ink-3); } + +/* ---- Keycaps --------------------------------------------------------- */ +.pd-kbd { + display: inline-flex; align-items: center; gap: 4px; + font: 600 var(--text-xs) var(--font-mono); color: var(--ink-1); + background: var(--paper-0); border: var(--border-thin) solid var(--paper-4); + border-bottom-width: 2px; border-radius: var(--radius-sm); + padding: 3px 7px; white-space: nowrap; +} +.pd-keytable .pd-kbd { font-size: var(--text-sm); } + +/* ---- Inline tick rule ------------------------------------------------ */ +.pd-rule { margin: var(--space-7) 0; } + +/* ---- TOC ------------------------------------------------------------- */ +.pd-toc { position: sticky; top: 60px; padding: var(--space-6) var(--space-4); align-self: start; max-height: calc(100vh - 60px); overflow-y: auto; } +.pd-toc__title { font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-4); margin: 0 0 var(--space-3); } +.pd-toc__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; } +.pd-toc__link { display: block; font: 400 var(--text-sm)/1.3 var(--font-sans); color: var(--ink-3); text-decoration: none; padding: 5px 10px; border-left: 2px solid var(--paper-3); transition: color var(--dur-fast), border-color var(--dur-fast); } +.pd-toc__link:hover { color: var(--ink-0); border-left-color: var(--ink-3); text-decoration: none; } +.pd-toc__link--active { color: var(--red-deep); border-left-color: var(--red); font-weight: 600; } + +/* ---- Prev / next footer --------------------------------------------- */ +.pd-prevnext { + display: flex; gap: var(--space-4); margin-top: var(--space-8); + padding-top: var(--space-5); border-top: var(--border-thin) solid var(--border-hairline); +} +.pd-pn { + flex: 1; display: flex; flex-direction: column; gap: 4px; text-decoration: none; + padding: var(--space-4); border: var(--border-thin) solid var(--border-hairline); + border-radius: var(--radius-md); background: var(--surface-card); box-shadow: var(--shadow-xs); + transition: box-shadow var(--dur-fast), border-color var(--dur-fast); +} +.pd-pn:hover { box-shadow: var(--shadow-sm); border-color: var(--border-default); text-decoration: none; } +.pd-pn--next { text-align: right; } +.pd-pn__dir { font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-4); } +.pd-pn__title { font: 600 var(--text-base) var(--font-sans); color: var(--ink-0); } + +/* ---- Footnote / source line ----------------------------------------- */ +.pd-srcnote { font: 400 var(--text-xs)/1.6 var(--font-sans); color: var(--ink-3); margin-top: var(--space-6); } +.pd-srcnote code { font-size: 0.92em; } + +.pd-scrim { display: none; position: fixed; inset: 60px 0 0; z-index: 35; background: rgba(22,27,39,0.28); } + +/* ---- Responsive ------------------------------------------------------ */ +@media (max-width: 1120px) { + .pd-shell { grid-template-columns: var(--sidebar-width) minmax(0, 1fr); } + .pd-toc { display: none; } +} +@media (max-width: 860px) { + .pd-shell { grid-template-columns: minmax(0, 1fr); } + .pd-nav { display: none; } + .pd-menu { display: inline-flex; } + .pd-sidebar { + position: fixed; top: 60px; left: 0; z-index: 40; width: var(--sidebar-width); + background: var(--paper-1); transform: translateX(-105%); + transition: transform var(--dur-base) var(--ease-slide); + } + .pd-sidebar--open { transform: translateX(0); box-shadow: var(--shadow-lg); } + .pd-sidebar--open ~ .pd-scrim { display: block; } + .pd-main { padding: var(--space-5) var(--space-5) var(--space-8); } + .pd-prevnext { flex-direction: column; } +} diff --git a/_shared/docs.js b/_shared/docs.js new file mode 100644 index 0000000..7e07310 --- /dev/null +++ b/_shared/docs.js @@ -0,0 +1,85 @@ +/* panto docs — progressive enhancement only. + The site is fully functional with this file absent or JS disabled: + every nav item is a real link, all content is in the HTML. This adds + the mobile sidebar toggle, code copy buttons, and TOC scroll-spy. */ +(function () { + 'use strict'; + + /* ---- Mobile sidebar toggle ---------------------------------------- */ + var menuBtn = document.querySelector('[data-menu-toggle]'); + var sidebar = document.querySelector('.pd-sidebar'); + var scrim = document.querySelector('.pd-scrim'); + function closeMenu() { + if (sidebar) sidebar.classList.remove('pd-sidebar--open'); + } + if (menuBtn && sidebar) { + menuBtn.addEventListener('click', function () { + sidebar.classList.toggle('pd-sidebar--open'); + }); + } + if (scrim) scrim.addEventListener('click', closeMenu); + if (sidebar) { + sidebar.addEventListener('click', function (e) { + if (e.target.closest('a')) closeMenu(); + }); + } + + /* ---- Code copy buttons -------------------------------------------- */ + document.querySelectorAll('.pg-code__copy').forEach(function (btn) { + btn.addEventListener('click', function () { + var block = btn.closest('.pg-code'); + var pre = block && block.querySelector('pre'); + if (!pre) return; + var text = pre.innerText; + var done = function () { + btn.classList.add('pg-code__copy--done'); + var label = btn.querySelector('.pg-code__label'); + if (label) label.textContent = 'Copied'; + setTimeout(function () { + btn.classList.remove('pg-code__copy--done'); + if (label) label.textContent = 'Copy'; + }, 1400); + }; + if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done); + else done(); + }); + }); + + /* ---- Scroll-spy: highlight the active section in TOC + sidebar ---- */ + var tocLinks = Array.prototype.slice.call(document.querySelectorAll('.pd-toc__link')); + var sideLinks = Array.prototype.slice.call( + document.querySelectorAll('.pd-navsec--current .pd-navsec__link') + ); + if (!tocLinks.length || !('IntersectionObserver' in window)) return; + + var ids = tocLinks.map(function (a) { + var href = a.getAttribute('href') || ''; + return href.charAt(0) === '#' ? href.slice(1) : href.split('#')[1]; + }).filter(Boolean); + + function setActive(id) { + tocLinks.forEach(function (a) { + var h = a.getAttribute('href') || ''; + a.classList.toggle('pd-toc__link--active', h === '#' + id); + }); + sideLinks.forEach(function (a) { + var h = a.getAttribute('href') || ''; + a.classList.toggle('pd-navsec__link--active', h.split('#')[1] === id); + }); + } + + var visible = new Set(); + var obs = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (e.isIntersecting) visible.add(e.target.id); + else visible.delete(e.target.id); + }); + var first = ids.find(function (id) { return visible.has(id); }); + if (first) setActive(first); + }, { rootMargin: '-72px 0px -68% 0px', threshold: 0 }); + + ids.forEach(function (id) { + var el = document.getElementById(id); + if (el) obs.observe(el); + }); +})(); diff --git a/assets/logo-mark-mono.svg b/assets/logo-mark-mono.svg new file mode 100644 index 0000000..ed9b65d --- /dev/null +++ b/assets/logo-mark-mono.svg @@ -0,0 +1,10 @@ +<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pantograph"> + <g stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"> + <path d="M8 40 L28 34 L40 10 L20 16 Z" fill="none"></path> + <path d="M8 40 L40 10" stroke-dasharray="0.1 4.2" opacity="0.55"></path> + </g> + <circle cx="8" cy="40" r="3.2" fill="currentColor"></circle> + <line x1="3.5" y1="44" x2="12.5" y2="44" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></line> + <circle cx="20" cy="16" r="2.4" fill="none" stroke="currentColor" stroke-width="2.2"></circle> + <circle cx="40" cy="10" r="3.6" fill="currentColor"></circle> +</svg>
\ No newline at end of file diff --git a/assets/logo-mark.svg b/assets/logo-mark.svg new file mode 100644 index 0000000..f577b74 --- /dev/null +++ b/assets/logo-mark.svg @@ -0,0 +1,15 @@ +<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pantograph"> + + <g stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"> + <path d="M8 40 L28 34 L40 10 L20 16 Z" fill="none"></path> + + <path d="M8 40 L40 10" stroke-dasharray="0.1 4.2" opacity="0.55"></path> + </g> + + <circle cx="8" cy="40" r="3.2" fill="currentColor"></circle> + <line x1="3.5" y1="44" x2="12.5" y2="44" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></line> + + <circle cx="20" cy="16" r="2.4" fill="none" stroke="currentColor" stroke-width="2.2"></circle> + + <circle cx="40" cy="10" r="3.6" fill="#B23A2E"></circle> +</svg>
\ No newline at end of file diff --git a/cli.html b/cli.html new file mode 100644 index 0000000..0b14e57 --- /dev/null +++ b/cli.html @@ -0,0 +1,35 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>panto — Command line</title> +<link rel="stylesheet" href="_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css"> +<link rel="stylesheet" href="_shared/components.css"> +<link rel="stylesheet" href="_shared/docs.css"> +</head> +<body> +<div class="pd-app"> +<header class="pd-header"> +<div class="pd-header__left"> +<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button> +<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a> +<span class="pg-badge pg-badge--outline">CLI</span> +</div> +<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link pd-nav__link--active">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav> +<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> +</header> +<div class="pd-shell"> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link pd-navsec__link--active">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<div class="pd-scrim"></div> +<main class="pd-main"><article class="pd-doc" data-screen-label="Command line"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Command line</h1><p class="pd-lead"><code>panto</code> dispatches on its first argument. A handful of subcommands (<code>lua</code>, <code>bootstrap</code>, <code>sessions</code>, <code>models</code>, <code>auth</code>, <code>help</code>) short out before the agent loop; anything else — or nothing — starts the agent in the current directory.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Command</th><th>Does</th></tr></thead><tbody><tr><td class="mono">panto</td><td>Start a new conversation in the current directory.</td></tr><tr><td class="mono">panto --resume [<id>]</td><td>Resume the most recent session, or one by id prefix.</td></tr><tr><td class="mono">panto sessions</td><td>List saved sessions for this directory.</td></tr><tr><td class="mono">panto models sync</td><td>Fetch models.dev and rebuild the base models.toml.</td></tr><tr><td class="mono">panto auth …</td><td>Inspect and manage auth sessions (status / login / logout).</td></tr><tr><td class="mono">panto bootstrap [--force]</td><td>Run the luarocks bootstrap and exit.</td></tr><tr><td class="mono">panto lua [args…]</td><td>Drop into the embedded Lua interpreter.</td></tr><tr><td class="mono">panto help</td><td>Print the usage summary (also --help, -h).</td></tr></tbody></table></div><h2 id="starting" class="pd-h2">Starting a session</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto</span><span class="pd-cmd__note">Start a new conversation in the current directory.</span></div><div class="pd-cmd"><span class="pd-cmd__sig">panto --resume <span class="opt">[<id>]</span></span><span class="pd-cmd__note">Resume a previous session.</span></div><p class="pd-body">With no flag, <code>panto</code> opens a fresh session. <code>--resume</code> on its own reattaches the most recent session in this directory; <code>--resume <id></code> resumes the session whose id begins with <code><id></code>. Unknown arguments are tolerated (and ignored with a warning), so passing flags panto doesn’t recognise won’t stop it from starting.</p><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Needs a terminal</p>The UI runs in raw mode and requires an interactive tty on stdin/stdout. Piping input or redirecting output will refuse with an error rather than half-start.</div></div><h2 id="sessions" class="pd-h2">panto sessions</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto sessions</span><span class="pd-cmd__note">List saved sessions for the current directory.</span></div><p class="pd-body">Sessions are grouped per working directory. Each line is the short id (the first eight hex characters of the session’s UUIDv7), the creation time trimmed to the minute, and the message count:</p><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · sessions</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>panto sessions</div><div class="pg-term__line pg-term__line--out">7f3a9c2b 2026-06-12 09:41 24 messages</div><div class="pg-term__line pg-term__line--out">b81e0d44 2026-06-11 17:03 6 messages</div><div class="pg-term__line pg-term__line--out">a0c7f190 2026-06-10 22:18 41 messages</div></div></div><p class="pd-body">When a directory has no sessions yet, panto prints <code>no sessions for <cwd></code>. Use any unambiguous id prefix from this list with <code>panto --resume</code>.</p><h2 id="models" class="pd-h2">panto models</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto models sync</span><span class="pd-cmd__note">Fetch models.dev and rebuild the base <code>models.toml</code>.</span></div><p class="pd-body"><code>sync</code> is the only action. It fetches the <a href="https://models.dev" target="_blank" rel="noreferrer">models.dev</a> catalog, walks the providers in your merged <a href="configuration.html#providers"><code>[providers]</code></a> config, and for each one cross-checks the catalog against the models that provider actually serves — then writes the result to the <strong>base-layer</strong> <code>models.toml</code> (<code>$PANTO_HOME/models.toml</code>, else <code>$XDG_DATA_HOME/panto/models.toml</code>). Aliases, wire names, context windows, token caps, and pricing come along for the ride. A provider maps onto a catalog entry via <a href="configuration.html#providers"><code>model_catalog_name</code></a> (falling back to the provider name); providers with no matching entry — or no usable synced models — are skipped.</p><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · models sync</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>panto models sync</div><div class="pg-term__line pg-term__line--out">synced 42 model(s) across 3 provider(s) to ~/.local/share/panto/models.toml</div></div></div><p class="pd-body">Because it writes the base layer, your own user/project <code>models.toml</code> entries always layer on top and win. Re-run it whenever you want to refresh the catalog after providers ship new models.</p><h2 id="auth" class="pd-h2">panto auth</h2><p class="pd-body">Manage the credentials behind your providers. With no action, <code>panto auth</code> defaults to <code>status</code>.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth status</span></div><div class="pd-opt__desc">List every configured auth session and its state. <code>api_key</code> sessions read <em>resolved</em> or <em>unresolved (key/env missing)</em>; <code>oauth_device</code> sessions show whether you’re logged in and roughly when the access token expires.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth login <name></span></div><div class="pd-opt__desc">Run the OAuth device flow for the named session. panto prints a verification URL and a user code, then stores (and, if configured, exchanges) the token. An <code>api_key</code> session has nothing to log in to.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth logout <name></span></div><div class="pd-opt__desc">Forget the stored token set for the named session.</div></div></div><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · auth status</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>panto auth status</div><div class="pg-term__line pg-term__line--ok">anthropic_api api_key resolved</div><div class="pg-term__line pg-term__line--err">openai_api api_key unresolved (key/env missing)</div><div class="pg-term__line pg-term__line--out">github_copilot oauth_device logged in (access expires in ~58m)</div></div></div><p class="pd-body">Auth sessions themselves are declared in config under <a href="configuration.html#auth"><code>[auth.<name>]</code></a>. The provider that uses a session names it with <code>auth = "<name>"</code>.</p><h2 id="bootstrap" class="pd-h2">panto bootstrap</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto bootstrap <span class="opt">[--force]</span></span><span class="pd-cmd__note">Run the luarocks bootstrap, then exit.</span></div><p class="pd-body">The bootstrap pipeline prepares the embedded Lua runtime: it stages headers and the base agent tree, materialises the default config, and installs panto’s pinned <a href="lua.html#runtime">batteries</a> under <code>$PANTO_HOME</code>. The agent runs the very same pipeline on startup, so you rarely need this by hand — it’s here for first-run setup on a clean machine and for scripted / CI installs. Repeat runs no-op quickly.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">--force</span></div><div class="pd-opt__desc">Wipe the per-Lua-version rocks tree (<code>$PANTO_HOME/rocks/lua-X.Y.Z/</code>) before reinstalling everything from scratch.</div></div></div><h2 id="lua" class="pd-h2">panto lua</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto lua <span class="opt">[args…]</span></span><span class="pd-cmd__note">Drop into the embedded Lua interpreter.</span></div><p class="pd-body">This is panto’s bundled Lua standalone (the same <code>lua.c</code> the agent embeds), with the luarocks runtime already bootstrapped — so <code>require("luarocks.*")</code> and any rocks installed under <code>$PANTO_HOME</code> resolve exactly as they do inside a session. Arguments after <code>lua</code> are passed straight through to the interpreter. Use it to experiment, or to drive luarocks to install extra rocks for your extensions:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code># run a script with panto’s Lua + rocks tree +panto lua my-script.lua + +# install an extra rock into panto’s tree via the embedded luarocks +panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><h2 id="slash-commands" class="pd-h2">Slash commands</h2><p class="pd-body">Inside a session, any line that begins with <code>/</code> is a <strong>slash command</strong> rather than a prompt — it is handled locally and never sent to the model. The first word (after the slash) is the command name; the trimmed remainder is passed as its arguments.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">/compact</span><span class="pg-badge pg-badge--neutral">builtin</span></div><div class="pd-opt__desc">Compact the conversation now — summarise older turns to reclaim context while keeping a verbatim recent tail. (Compaction also runs automatically; see <a href="configuration.html#compaction"><code>[compaction]</code></a>.)</div></div></div><p class="pd-body"><code>/compact</code> is the only built-in. Every other slash command comes from a Lua extension via <code>panto.ext.register_command</code> — see <a href="lua.html#commands">Lua extensions → register_command</a>. An unrecognised command reports an error instead of reaching the model.</p><h2 id="environment" class="pd-h2">Environment</h2><p class="pd-body">panto reads a few environment variables — for credentials and for locating its files. Configuration values can also pull from the environment via <code>${env:VAR}</code> substitution (see <a href="configuration.html#auth">Configuration</a>).</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Variable</th><th>Effect</th></tr></thead><tbody><tr><td class="mono">ANTHROPIC_API_KEY</td><td>Consumed by the default Anthropic provider.</td></tr><tr><td class="mono">OPENAI_API_KEY</td><td>Consumed by the default OpenAI provider.</td></tr><tr><td class="mono">PANTO_HOME</td><td>Override the runtime / rocks-tree location. Also where the base config.toml and agent tree are staged.</td></tr><tr><td class="mono">PANTO_SESSION_DIR</td><td>Override the base sessions directory. Defaults to $XDG_DATA_HOME/panto/sessions (or ~/.local/share/panto/sessions).</td></tr><tr><td class="mono">XDG_CONFIG_HOME</td><td>Locates the user config: $XDG_CONFIG_HOME/panto/ (else ~/.config/panto/).</td></tr><tr><td class="mono">XDG_DATA_HOME</td><td>Locates the base config + data: $XDG_DATA_HOME/panto/ (else ~/.local/share/panto/).</td></tr></tbody></table></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="index.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Getting started</span></a><a class="pd-pn pd-pn--next" href="configuration.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Configuration</span></a></nav></article></main> +<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#starting" class="pd-toc__link pd-toc__link--active">Starting a session</a></li><li><a href="#sessions" class="pd-toc__link">panto sessions</a></li><li><a href="#models" class="pd-toc__link">panto models</a></li><li><a href="#auth" class="pd-toc__link">panto auth</a></li><li><a href="#bootstrap" class="pd-toc__link">panto bootstrap</a></li><li><a href="#lua" class="pd-toc__link">panto lua</a></li><li><a href="#slash-commands" class="pd-toc__link">Slash commands</a></li><li><a href="#environment" class="pd-toc__link">Environment</a></li></ul></aside> +</div> +</div> +<script src="_shared/docs.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/configuration.html b/configuration.html new file mode 100644 index 0000000..1823478 --- /dev/null +++ b/configuration.html @@ -0,0 +1,80 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>panto — Configuration</title> +<link rel="stylesheet" href="_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css"> +<link rel="stylesheet" href="_shared/components.css"> +<link rel="stylesheet" href="_shared/docs.css"> +</head> +<body> +<div class="pd-app"> +<header class="pd-header"> +<div class="pd-header__left"> +<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button> +<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a> +<span class="pg-badge pg-badge--outline">CLI</span> +</div> +<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link pd-nav__link--active">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav> +<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> +</header> +<div class="pd-shell"> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link pd-navsec__link--active">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<div class="pd-scrim"></div> +<main class="pd-main"><article class="pd-doc" data-screen-label="Configuration"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Configuration</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">TOML</span></div><p class="pd-lead">panto is configured by layered <code>config.toml</code> files, plus a separate <code>models.toml</code> for model aliases and pricing. Define providers, pick a default model, and gate tools and extensions — everything you specify, nothing you don’t.</p><h2 id="files" class="pd-h2">Files & layers</h2><p class="pd-body">Four files are read and merged, lowest precedence first. Missing files are skipped silently.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Path</th><th>Role</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$PANTO_HOME/config.toml · else $XDG_DATA_HOME/panto/config.toml</td><td>Auto-generated by bootstrap. Lowest precedence.</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/config.toml · else ~/.config/panto/config.toml</td><td>Your personal, machine-wide settings.</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/config.toml</td><td>Checked-in, per-project settings.</td></tr><tr><td class="mono">local</td><td class="mono">./.panto/config.local.toml</td><td>Personal per-project overrides — git-ignore this. Highest precedence.</td></tr></tbody></table></div><p class="pd-body"><strong>Merge semantics:</strong> tables merge recursively, but scalars <em>and arrays</em> from a higher layer overwrite wholesale. A project file that sets <code>tools.deny</code> replaces the array entirely — it doesn’t append to a user-level list. Tables accumulate, so a provider defined only at the base layer survives even when a higher layer adds another.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code># ~/.config/panto/config.toml (user layer) + +[defaults] +model = "anthropic:sonnet" # <provider>:<alias> + +[providers.anthropic] +style = "anthropic_messages" +base_url = "https://api.anthropic.com" +auth = "anthropic_api" # draws its key from [auth.anthropic_api] + +[auth.anthropic_api] +key = "${env:ANTHROPIC_API_KEY}" # resolved at load + +[tools] +deny = ["std.bash"] # allow every tool except the shell</code></pre></div></div><div class="pg-callout pg-callout--tip"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">The local layer</p><code>./.panto/config.local.toml</code> is meant to be git-ignored: apply your own overrides on top of the checked-in <code>project</code> layer without committing them.</div></div><h2 id="providers" class="pd-h2">[providers.<name>]</h2><p class="pd-body">A provider is pure transport: an API shape and a base URL, plus the name of the auth session that supplies its credential. The chosen name is what you reference on the left of a <code>provider:alias</code> model.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">style</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The wire protocol: <code>anthropic_messages</code>, <code>openai_chat</code>, or <code>openai_responses</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">dialect</span><span class="pd-opt__type">"codex"</span></div><div class="pd-opt__desc">A sub-dialect for styles that share a protocol family. Only <code>style = "openai_responses"</code> accepts it today, with the single value <code>"codex"</code> (the Codex Responses variant).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">base_url</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The API base URL requests are sent to.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">auth</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Names the <a href="#auth"><code>[auth.<name>]</code></a> session that supplies this provider’s credential. (Provider-level <code>api_key</code> is no longer accepted — auth lives in one place.)</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">prompt_cache</span><span class="pd-opt__type">bool</span><span class="pg-badge pg-badge--neutral">default true</span></div><div class="pd-opt__desc"><code>anthropic_messages</code> only. Place one advancing <code>cache_control</code> breakpoint on each request. Ignored for other styles.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">extra_headers</span><span class="pd-opt__type">table</span></div><div class="pd-opt__desc">A table of static string headers merged onto every model request (see below).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model_catalog_name</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Maps this provider onto a <a href="https://models.dev" target="_blank" rel="noreferrer">models.dev</a> catalog key (e.g. <code>"github-copilot"</code>) for <a href="cli.html#models"><code>panto models sync</code></a>. Defaults to the provider name.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[providers.copilot] +style = "openai_chat" +base_url = "https://api.individual.githubcopilot.com" +auth = "github_copilot" + +[providers.copilot.extra_headers] +Copilot-Integration-Id = "vscode-chat" +X-Initiator = "user"</code></pre></div></div><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Unresolved providers vanish</p>If a provider’s <code>api_key</code> auth session resolves to empty (its env var isn’t set), the provider is dropped — export the key to bring it back. OAuth providers always survive and resolve at turn time.</div></div><h2 id="auth" class="pd-h2">[auth.<name>]</h2><p class="pd-body">A named auth session supplies a credential. Its <code>type</code> is inferred when omitted: a <code>key</code> implies <code>api_key</code>; a <code>client_id</code> implies <code>oauth_device</code>.</p><h3 id="auth-subst" class="pd-h3">Substitution</h3><p class="pd-body">Every auth value supports <code>${…}</code> substitution. <code>${env:VAR}</code> reads the environment; <code>${name}</code> reads another key in the same section (handy for a shared <code>domain</code>). An unresolved reference becomes the empty string.</p><h3 id="auth-apikey" class="pd-h3">API-key sessions</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">key</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The API key — usually <code>"${env:VAR}"</code>, but a literal works too. Resolved eagerly at load; an empty result leaves the session unresolved.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">type</span><span class="pd-opt__type">"api_key"</span></div><div class="pd-opt__desc">Optional; inferred from the presence of <code>key</code>.</div></div></div><h3 id="auth-oauth" class="pd-h3">OAuth device sessions</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">client_id</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The OAuth client id.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">device_code_url</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Endpoint that issues the device + user codes.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">token_url</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Endpoint polled for the access token.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">dialect</span><span class="pd-opt__type">"token" | "codex"</span><span class="pg-badge pg-badge--neutral">default token</span></div><div class="pd-opt__desc">Flow dialect. <code>codex</code> additionally requires <code>device_poll_url</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">scope</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Requested OAuth scopes.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">verification_url</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Override the URL shown to the user.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">device_poll_url</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Distinct poll endpoint (required for <code>codex</code>).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">token_request_format</span><span class="pd-opt__type">"form" | "json"</span><span class="pg-badge pg-badge--neutral">default form</span></div><div class="pd-opt__desc">How the token request body is encoded.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">redirect_uri / account_id_jwt_claim</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional flow details.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">exchange_url</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Enables an optional secondary token exchange. Companions: <code>exchange_method</code> (default <code>GET</code>), <code>exchange_token_path</code> (default <code>token</code>), <code>exchange_expires_path</code>, <code>exchange_base_url_path</code> — JSON paths into the exchange response.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[auth.github_copilot] +client_id = "Iv1.b507a08c87ecfe98" +domain = "github.com" # an ordinary sibling key… +device_code_url = "https://${domain}/login/device/code" # …reused here +token_url = "https://${domain}/login/oauth/access_token" +scope = "read:user" + +# optional secondary token exchange +exchange_url = "https://api.${domain}/copilot_internal/v2/token" +exchange_expires_path = "expires_at" +exchange_base_url_path = "endpoints.api"</code></pre></div></div><h2 id="defaults" class="pd-h2">[defaults]</h2><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">The default model, as <code>"<provider>:<alias>"</code> (exactly one colon, both halves non-empty). The provider must resolve.</div></div></div><p class="pd-body">Model selection falls through in order: an explicit override (a future <code>--model</code>), then <code>defaults.model</code>, then — if exactly one provider resolved and it has exactly one alias in <code>models.toml</code> — that one. Otherwise panto asks you to set a default.</p><h2 id="policies" class="pd-h2">Tools & extensions</h2><p class="pd-body"><code>[tools]</code> and <code>[extensions]</code> share the same shape: allow- and deny-lists of glob patterns over dotted names (the standard tools live under the <code>std.</code> namespace — <code>std.read</code>, <code>std.write</code>, <code>std.edit</code>, <code>std.bash</code>).</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">allow</span><span class="pd-opt__type">string[]</span></div><div class="pd-opt__desc">Glob patterns. <strong>Empty means allow-all</strong> (still subject to <code>deny</code>).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">deny</span><span class="pd-opt__type">string[]</span></div><div class="pd-opt__desc">Glob patterns. A name is denied if it matches any deny pattern.</div></div></div><p class="pd-body">A name is permitted when it matches at least one <code>allow</code> pattern (or allow is empty) <em>and</em> matches no <code>deny</code> pattern.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[tools] +allow = ["std.*"] # only the standard tools +deny = ["std.bash"] # …but never the shell</code></pre></div></div><div class="pg-callout pg-callout--danger"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Conflicts are fatal</p>The same pattern in both <code>allow</code> and <code>deny</code> for one kind is a hard error — panto refuses to start rather than guess.</div></div><h2 id="compaction" class="pd-h2">[compaction]</h2><p class="pd-body">panto compacts long conversations automatically; this section tunes it. The manual <a href="cli.html#slash-commands"><code>/compact</code></a> command uses the same settings.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">keep_verbatim</span><span class="pd-opt__type">int</span><span class="pg-badge pg-badge--neutral">default 20000</span></div><div class="pd-opt__desc">Token budget for the recent tail kept verbatim when compacting.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional <code>provider:alias</code> override for the model that writes the summary. Defaults to the active chat model.</div></div></div><h2 id="models" class="pd-h2">models.toml</h2><p class="pd-body">Model <em>aliases</em> live in <code>$XDG_CONFIG_HOME/panto/models.toml</code>, separate from <code>config.toml</code>. Each entry is keyed <code>[<provider>.<alias>]</code> — the provider matches a <code>[providers.<name>]</code>, the alias is the short name you reference. A referenced alias with no entry isn’t an error: it’s used verbatim as the wire name with default knobs.</p><p class="pd-body"><code>models.toml</code> is layered exactly like <code>config.toml</code> (base → user → project → local). The base layer is auto-generated — <a href="cli.html#models"><code>panto models sync</code></a> fetches the catalog and writes it — so your user and project entries always layer on top and win.</p><h3 id="models-common" class="pd-h3">Common keys</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Wire model id sent to the API. Defaults to the alias.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">context_window</span><span class="pd-opt__type">int</span></div><div class="pd-opt__desc">Total input context window, used for the footer’s context-usage display. Optional.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">max_tokens</span><span class="pd-opt__type">int</span></div><div class="pd-opt__desc">Per-request output token cap.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">input / output / cache_read / cache_write</span><span class="pd-opt__type">float</span></div><div class="pd-opt__desc">Pricing in USD per million tokens. All optional; omitted means unknown.</div></div></div><h3 id="models-openai" class="pd-h3">openai_chat only</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">reasoning</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--neutral">default default</span></div><div class="pd-opt__desc">One of <code>default</code>, <code>off</code>, <code>minimal</code>, <code>low</code>, <code>medium</code>, <code>high</code>.</div></div></div><h3 id="models-anthropic" class="pd-h3">anthropic_messages only</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">thinking</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--neutral">default disabled</span></div><div class="pd-opt__desc"><code>disabled</code>, <code>enabled</code>, or <code>adaptive</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">effort</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--neutral">default medium</span></div><div class="pd-opt__desc"><code>low</code> · <code>medium</code> · <code>high</code> · <code>xhigh</code> · <code>max</code>. Used when <code>thinking = "adaptive"</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">thinking_budget_tokens</span><span class="pd-opt__type">int</span><span class="pg-badge pg-badge--neutral">default 32000</span></div><div class="pd-opt__desc">Reasoning-token budget when <code>thinking = "enabled"</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">thinking_interleaved</span><span class="pd-opt__type">bool</span><span class="pg-badge pg-badge--neutral">default false</span></div><div class="pd-opt__desc">Send the interleaved-thinking beta header (honoured when <code>thinking = "enabled"</code>).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">api_version</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--neutral">default 2023-06-01</span></div><div class="pd-opt__desc">The <code>anthropic-version</code> header.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">models.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code># ~/.config/panto/models.toml + +[anthropic.sonnet] +model = "claude-sonnet-4-20250514" # wire name; defaults to the alias +max_tokens = 8192 +thinking = "enabled" +thinking_budget_tokens = 16000 +input = 3.0 # USD per million tokens +output = 15.0 +cache_read = 0.30 +cache_write = 3.75 + +[openai.gpt] +model = "gpt-4o" +reasoning = "medium" +input = 2.5 +output = 10.0</code></pre></div></div><h2 id="system-prompt" class="pd-h2">System prompt</h2><p class="pd-body">The agent’s system prompt is sourced by convention from Markdown files across the same base → user → project layers (base is <code>$PANTO_HOME/agent</code>, where bootstrap stages the bundled default):</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">SYSTEM.md</span></div><div class="pd-opt__desc">Replaces the system prompt for that layer.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">APPEND_SYSTEM.md</span></div><div class="pd-opt__desc">Appends to it — for small, additive house rules.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">COMPACTION.md</span></div><div class="pd-opt__desc">Overrides the summary prompt used during <a href="#compaction">compaction</a> (last layer wins; a built-in default applies otherwise).</div></div></div><p class="pd-body">On resume, prompt changes are reconciled without rewriting the existing conversation.</p><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="cli.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Command line</span></a><a class="pd-pn pd-pn--next" href="keybindings.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Key bindings</span></a></nav></article></main> +<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#files" class="pd-toc__link pd-toc__link--active">Files & layers</a></li><li><a href="#providers" class="pd-toc__link">[providers]</a></li><li><a href="#auth" class="pd-toc__link">[auth]</a></li><li><a href="#defaults" class="pd-toc__link">[defaults]</a></li><li><a href="#policies" class="pd-toc__link">Tools & extensions</a></li><li><a href="#compaction" class="pd-toc__link">[compaction]</a></li><li><a href="#models" class="pd-toc__link">models.toml</a></li><li><a href="#system-prompt" class="pd-toc__link">System prompt</a></li></ul></aside> +</div> +</div> +<script src="_shared/docs.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..f8ef07f --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>panto — Getting started</title> +<link rel="stylesheet" href="_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css"> +<link rel="stylesheet" href="_shared/components.css"> +<link rel="stylesheet" href="_shared/docs.css"> +</head> +<body> +<div class="pd-app"> +<header class="pd-header"> +<div class="pd-header__left"> +<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button> +<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a> +<span class="pg-badge pg-badge--outline">CLI</span> +</div> +<nav class="pd-nav"><a href="index.html" class="pd-nav__link pd-nav__link--active">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav> +<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> +</header> +<div class="pd-shell"> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<div class="pd-scrim"></div> +<main class="pd-main"><article class="pd-doc" data-screen-label="panto"><p class="panto-eyebrow pd-eyebrow">Getting started</p><h1 class="pd-doc__h1">panto</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">terminal agent</span><span class="pg-tag"><span class="pg-tag__dot" style="background:var(--red)"></span>zig</span><span class="pg-tag"><span class="pg-tag__dot" style="background:var(--green)"></span>lua</span></div><p class="pd-lead">panto is a small terminal coding agent built on libpanto. A minimal system prompt, a tool set you can swap or switch off, native binaries from Zig, extensions in Lua. This is the operator's reference — flags, configuration, key bindings, and the Lua surface.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">You run <code>panto</code> inside a project directory and talk to a model in a terminal UI. The agent reads and edits files and runs commands through <strong>tools</strong> — and even the standard tools (<code>read</code>, <code>write</code>, <code>edit</code>, <code>bash</code>) ship as extensions, so any of them can be denied individually.</p><p class="pd-body">There are no permission prompts and no hidden machinery: providers are declared in a TOML file, the model is chosen by a <code>provider:alias</code> reference, and anything beyond the core is added as a Lua extension. The rest of these pages document each of those surfaces in turn.</p><div class="panto-tick-rule pd-rule"></div><h2 id="install" class="pd-h2">Install</h2><p class="pd-body">panto builds from source with <a href="https://ziglang.org" target="_blank" rel="noreferrer">Zig</a>. Clone the repository and run the Zig build; the result is a single native binary.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>git clone https://code.tjp.lol/pantograph.git +cd pantograph +zig build # native binary at zig-out/bin/panto</code></pre></div></div><p class="pd-body">On first use, run the one-time bootstrap. It stages the embedded Lua runtime and installs panto’s pinned <a href="lua.html#runtime">luarocks</a> batteries (just <code>luv</code>, the libuv event loop) under <code>$PANTO_HOME</code>:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto bootstrap # prepare the rocks tree (idempotent)</code></pre></div></div><div class="pg-callout pg-callout--tip"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Where things live</p>By default panto keeps its runtime and rocks tree in <code>$PANTO_HOME</code> (falling back to <code>$XDG_DATA_HOME/panto</code>), and reads configuration from a layered set of TOML files. See <a href="configuration.html#files">Configuration → Files & layers</a>.</div></div><h2 id="first-run" class="pd-h2">First run</h2><p class="pd-body">Run <code>panto</code> with no arguments inside a project to start a fresh conversation. The agent loads your config, selects the default model, and opens the terminal UI. Type a message and press <span class="pd-kbd">Enter</span> to send it.</p><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · first run</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>export ANTHROPIC_API_KEY=sk-ant-…</div><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>cd ~/code/ledger && panto</div><div class="pg-term__line"> </div><div class="pg-term__line pg-term__line--accent"> panto v0.1.0</div><div class="pg-term__line pg-term__line--dim"> cwd: ~/code/ledger</div><div class="pg-term__line"> </div><div class="pg-term__user">add a --json flag to the stats command</div><div class="pg-term__line"> </div><div class="pg-term__line">I’ll wire a <code>--json</code> branch into the stats command. Let me read it first.</div><div class="pg-term__tool"><div class="pg-term__tool-head">read src/stats.zig</div><div class="pg-term__tool-body">pub fn run(args: Args) !void { + const summary = try compute(args); + try printHuman(summary); +}</div></div><div class="pg-term__tool"><div class="pg-term__tool-head">edit src/stats.zig (1 edit)</div><div class="pg-term__tool-body"><span class="pg-term__del">- try printHuman(summary);</span> +<span class="pg-term__add">+ if (args.json) try printJson(summary) else try printHuman(summary);</span></div></div><div class="pg-term__line"> </div><div class="pg-term__line">Done — <code>stats --json</code> now prints a machine-readable summary.</div><div class="pg-term__line"> </div><div class="pg-term__rule"></div><div class="pg-term__line pg-term__line--cmd"> <span class="pg-term__cursor"></span></div><div class="pg-term__rule"></div><div class="pg-term__line pg-term__line--dim">anthropic:sonnet 18.4k ctx 2.1k tok $0.03</div></div></div><p class="pd-body">panto’s UI needs an interactive terminal (a tty). Sessions are saved per directory, so you can pick up where you left off with <code>panto --resume</code>. The full command surface — resuming, listing sessions, auth, the embedded Lua interpreter — is on the <a href="cli.html#starting">Command line</a> page, and every key is listed under <a href="keybindings.html#editing">Key bindings</a>.</p><h2 id="authenticate" class="pd-h2">Authenticate</h2><p class="pd-body">A provider draws its credential from a named <em>auth session</em>. There are two kinds. The simplest is an API key from the environment — the default OpenAI and Anthropic providers read <code>OPENAI_API_KEY</code> and <code>ANTHROPIC_API_KEY</code> directly:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>export ANTHROPIC_API_KEY=sk-ant-… +panto # the anthropic provider is now live</code></pre></div></div><p class="pd-body">The second is an OAuth device flow, for providers that authenticate interactively. Log in once and panto stores (and refreshes) the token for you:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto auth login github_copilot +panto auth status # show every session and its login state</code></pre></div></div><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">If a provider seems missing</p>An <code>api_key</code> session whose environment variable is empty resolves to nothing, and any provider using it is silently dropped (export the key to bring it back). OAuth providers always survive and resolve at turn time. Auth sessions are declared under <a href="configuration.html#auth"><code>[auth]</code></a> in config.</div></div><nav class="pd-prevnext"><span style="flex:1"></span><a class="pd-pn pd-pn--next" href="cli.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Command line</span></a></nav></article></main> +<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#overview" class="pd-toc__link pd-toc__link--active">Overview</a></li><li><a href="#install" class="pd-toc__link">Install</a></li><li><a href="#first-run" class="pd-toc__link">First run</a></li><li><a href="#authenticate" class="pd-toc__link">Authenticate</a></li></ul></aside> +</div> +</div> +<script src="_shared/docs.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/keybindings.html b/keybindings.html new file mode 100644 index 0000000..09739d8 --- /dev/null +++ b/keybindings.html @@ -0,0 +1,31 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>panto — Key bindings</title> +<link rel="stylesheet" href="_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css"> +<link rel="stylesheet" href="_shared/components.css"> +<link rel="stylesheet" href="_shared/docs.css"> +</head> +<body> +<div class="pd-app"> +<header class="pd-header"> +<div class="pd-header__left"> +<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button> +<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a> +<span class="pg-badge pg-badge--outline">CLI</span> +</div> +<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link pd-nav__link--active">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav> +<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> +</header> +<div class="pd-shell"> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link pd-navsec__link--active">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<div class="pd-scrim"></div> +<main class="pd-main"><article class="pd-doc" data-screen-label="Key bindings"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Key bindings</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">TUI input</span></div><p class="pd-lead">The session UI reads raw keystrokes and turns them into editing actions. These are the keys it recognises today; panto negotiates richer terminal protocols at startup so the important ones (notably Shift+Enter) work wherever the terminal allows.</p><h2 id="editing" class="pd-h2">Editing & cursor</h2><p class="pd-body">The input box is a multi-line editor. Printable keys insert text (full Unicode), and the usual motion keys move the cursor.</p><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td>any printable key</td><td>Insert the character (multi-byte UTF-8 supported).</td></tr><tr><td><span class="pd-kbd">←</span> / <span class="pd-kbd">→</span></td><td>Move the cursor one character.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">←</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">→</span><br><span class="pd-kbd">Alt</span>+<span class="pd-kbd">←</span> / <span class="pd-kbd">Alt</span>+<span class="pd-kbd">→</span> · <span class="pd-kbd">Alt</span>+<span class="pd-kbd">B</span> / <span class="pd-kbd">Alt</span>+<span class="pd-kbd">F</span></td><td>Move the cursor by word (terminals vary which form they send).</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">A</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">E</span></td><td>Jump to the start / end of the current line.</td></tr><tr><td><span class="pd-kbd">Home</span> / <span class="pd-kbd">End</span></td><td>Jump to the very start / end of the whole input.</td></tr><tr><td><span class="pd-kbd">Backspace</span></td><td>Delete the character before the cursor.</td></tr><tr><td><span class="pd-kbd">Delete</span></td><td>Delete the character under the cursor.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">W</span> · <span class="pd-kbd">Alt</span>+<span class="pd-kbd">Backspace</span> · <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">Backspace</span></td><td>Delete the previous word.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">U</span></td><td>Delete from the cursor back to the start of the line.</td></tr><tr><td>paste</td><td>Pasted text is inserted literally as one run (bracketed paste), not interpreted key-by-key.</td></tr></tbody></table></div><h2 id="submit" class="pd-h2">Submit & newline</h2><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td><span class="pd-kbd">Enter</span></td><td>Send the message to the model.</td></tr><tr><td><span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span></td><td>Insert a newline instead of sending (terminal permitting — see below).</td></tr></tbody></table></div><p class="pd-body">Whether <span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span> inserts a newline depends on your terminal, because in the bare legacy protocol it is indistinguishable from plain <span class="pd-kbd">Enter</span>. panto works around this where it can — see <a href="#terminal">Terminal protocol</a>.</p><h2 id="control" class="pd-h2">Selectors & control</h2><p class="pd-body">Control keys switch the model and reasoning effort mid-session, collapse tool output, hand off to your editor, interrupt a running turn, and exit. The selectors, tool-collapse, and the <span class="pd-kbd">Esc</span> interrupt stay responsive even while the agent is mid-turn.</p><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">M</span></td><td>Open the model selector.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">R</span></td><td>Open the reasoning-effort selector.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">O</span></td><td>Collapse / expand all tool-call output.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">G</span></td><td>Edit the current draft in <code>$EDITOR</code>, then read it back.</td></tr><tr><td><span class="pd-kbd">Esc</span></td><td>Interrupt the running agent turn; dismiss an open selector.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">C</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">D</span></td><td>Exit cleanly.</td></tr></tbody></table></div><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Selectors are live-only</p>Picking a model (<span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">M</span>) or reasoning level (<span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">R</span>) rebuilds the active provider config and applies it to the running agent for this session — nothing is written back to <a href="configuration.html#models"><code>config.toml</code></a>.</div></div><h2 id="terminal" class="pd-h2">Terminal protocol</h2><p class="pd-body">Two TUI behaviours depend on the terminal’s keyboard protocol. panto negotiates the best available at startup and degrades gracefully.</p><h3 id="terminal-shiftenter" class="pd-h3">Shift + Enter</h3><p class="pd-body">In the bare legacy protocol, <span class="pd-kbd">Enter</span> and <span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span> send the same byte. To tell them apart, panto pushes the <strong>Kitty keyboard protocol</strong> (disambiguate + report-alternates) and queries the terminal; if that’s unavailable it falls back to xterm’s <strong>modifyOtherKeys</strong> mode (which tmux and xterm honour).</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Terminal</th><th>Shift + Enter</th></tr></thead><tbody><tr><td>Kitty, Ghostty, foot (Kitty protocol)</td><td>Newline — fully supported.</td></tr><tr><td>xterm, tmux (modifyOtherKeys)</td><td>Newline — via the fallback.</td></tr><tr><td>macOS Terminal.app (neither)</td><td>Indistinguishable from Enter — submits.</td></tr></tbody></table></div><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">No newline in Terminal.app</p>On terminals that support neither protocol, <span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span> can’t be distinguished from <span class="pd-kbd">Enter</span>, so it submits. Use a terminal with the Kitty protocol (Ghostty, Kitty, foot) — or tmux — if you want multi-line input.</div></div><h3 id="terminal-paste" class="pd-h3">Bracketed paste</h3><p class="pd-body">panto enables bracketed paste, so multi-line pastes arrive as a single literal block rather than being re-interpreted as a stream of keypresses (a stray newline in a paste won’t submit your message early).</p><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">What isn’t here yet</p>The input model reserves space for richer behaviour — key-release events and super/hyper modifiers under the full Kitty protocol — but those aren’t consumed today, so they’re not bindings yet.</div></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="configuration.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Configuration</span></a><a class="pd-pn pd-pn--next" href="lua.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Lua extensions</span></a></nav></article></main> +<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#editing" class="pd-toc__link pd-toc__link--active">Editing & cursor</a></li><li><a href="#submit" class="pd-toc__link">Submit & newline</a></li><li><a href="#control" class="pd-toc__link">Selectors & control</a></li><li><a href="#terminal" class="pd-toc__link">Terminal protocol</a></li></ul></aside> +</div> +</div> +<script src="_shared/docs.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/lua.html b/lua.html new file mode 100644 index 0000000..7baeba0 --- /dev/null +++ b/lua.html @@ -0,0 +1,66 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>panto — Lua extensions</title> +<link rel="stylesheet" href="_ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css"> +<link rel="stylesheet" href="_shared/components.css"> +<link rel="stylesheet" href="_shared/docs.css"> +</head> +<body> +<div class="pd-app"> +<header class="pd-header"> +<div class="pd-header__left"> +<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button> +<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a> +<span class="pg-badge pg-badge--outline">CLI</span> +</div> +<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link pd-nav__link--active">Lua extensions</a></nav> +<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div> +</header> +<div class="pd-shell"> +<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files & layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Tools & extensions</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing & cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit & newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors & control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on & emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime & luarocks</a></li></ul></div></div></aside> +<div class="pd-scrim"></div> +<main class="pd-main"><article class="pd-doc" data-screen-label="Lua extensions"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Lua extensions</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">require("panto")</span></div><p class="pd-lead">Everything beyond the core is a Lua extension — including the standard tools. An extension is a Lua file that calls into the <code>panto.ext</code> table to add tools, slash commands, and UI behaviour. This is how panto stays small without staying useless.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">Inside an extension, <code>local panto = require("panto")</code> gives you the API. The <code>panto.ext</code> table has three entry points:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_tool</span><span class="pg-badge pg-badge--neutral">model-facing</span></div><div class="pd-opt__desc">Add a tool the model can call.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_command</span><span class="pg-badge pg-badge--neutral">you-facing</span></div><div class="pd-opt__desc">Add a <code>/</code>slash command to the REPL.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">on / emit</span><span class="pg-badge pg-badge--neutral">UI events</span></div><div class="pd-opt__desc">Hook the UI event bus to wrap or replace how things render.</div></div></div><p class="pd-body">All extensions share one long-lived Lua interpreter, so module-global state persists across calls. The <a href="#runtime">runtime</a> includes <code>luv</code> (libuv), so tool handlers can perform async I/O.</p><h2 id="loading" class="pd-h2">Loading extensions</h2><p class="pd-body">Drop a <code>.lua</code> file into an extensions directory and it loads at startup. panto discovers extensions across three layers:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Location</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$PANTO_HOME/agent</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/extensions/ · else ~/.config/panto/extensions/</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/extensions/</td></tr></tbody></table></div><p class="pd-body">Project shadows user shadows base. Which extensions and tools actually load is gated by the <a href="configuration.html#policies"><code>[extensions]</code> and <code>[tools]</code></a> allow/deny policies.</p><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Name collisions abort startup</p>If two surviving extensions register a tool (or command) with the same name, panto refuses to start rather than pick one silently. A Lua command may not shadow a builtin like <code>/compact</code>.</div></div><h2 id="tools" class="pd-h2">register_tool</h2><p class="pd-body">A tool is something the model can call. Describe it with a JSON-Schema for its arguments and a handler that returns a string result.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">name</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">The tool name the model invokes.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">description</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Shown to the model; explain when to use it.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">schema</span><span class="pd-opt__type">table</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">A JSON-Schema table (<code>type = "object"</code>, <code>properties</code>, <code>required</code>) describing the arguments.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handler</span><span class="pd-opt__type">function(input)</span></div><div class="pd-opt__desc">Receives the decoded arguments table; returns a string result. May yield (async).</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/echo.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>local panto = require("panto") + +panto.ext.register_tool { + name = "echo", + description = "Echo back the given message.", + schema = { + type = "object", + properties = { + message = { type = "string", description = "The text to echo back." }, + }, + required = { "message" }, + }, + handler = function(input) + return "echo: " .. input.message + end, +}</code></pre></div></div><h2 id="commands" class="pd-h2">register_command</h2><p class="pd-body">A slash command runs locally when you type <code>/name</code> in the REPL. The handler runs synchronously and acts by side effect (typically writing output); its return value is ignored.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">name</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Command name, without the leading <code>/</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">description</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">One-line summary (for listings).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handler</span><span class="pd-opt__type">function(args)</span></div><div class="pd-opt__desc"><code>args</code> is the trimmed text after the command name — the empty string when none was given.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/greet.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>local panto = require("panto") + +panto.ext.register_command { + name = "greet", + description = "Print a greeting. Optional args name who to greet.", + handler = function(args) + local who = args ~= "" and args or "world" + io.write("\n[greet] hello, " .. who .. "!\n") + end, +}</code></pre></div></div><h2 id="events" class="pd-h2">Events: on & emit</h2><p class="pd-body"><code>panto.ext.on(name, handler)</code> subscribes a handler to the UI event bus — the same bus the built-in TUI uses. Handlers fire in registration order, and can wrap or replace the component that renders a given moment of the transcript.</p><p class="pd-body">The handler receives an event object <code>e</code> that is valid <strong>only</strong> during the call (copy any field you need into a local — don’t read <code>e</code> later at render time):</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e.name</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">The event name.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e.<field></span></div><div class="pd-opt__desc">Read-only payload fields (nil when absent, so you can branch on them).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e:getComponent()</span></div><div class="pd-opt__desc">The current native component, as an opaque pass-through handle.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">e:setComponent(c)</span></div><div class="pd-opt__desc">Replace the component for this boundary — pass the handle back to wrap it, or a Lua component table to replace it.</div></div></div><p class="pd-body">The events and the payload fields they carry:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Event</th><th>Payload fields</th></tr></thead><tbody><tr><td class="mono">tool</td><td class="mono">index, tool_name, id, delta, input, output</td></tr><tr><td class="mono">thinking</td><td class="mono">index, delta, text</td></tr><tr><td class="mono">assistant_text</td><td class="mono">index, delta, text</td></tr><tr><td class="mono">user_message</td><td class="mono">text</td></tr><tr><td class="mono">session_start</td><td class="mono">version, cwd, model</td></tr><tr><td class="mono">compaction</td><td class="mono">summary</td></tr><tr><td class="mono">custom</td><td class="mono">— produced by emit</td></tr></tbody></table></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/skill-badge.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>local panto = require("panto") + +-- Change how the "skill" tool renders in the transcript. +panto.ext.on("tool", function(e) + if e.tool_name ~= "skill" then return end -- ignore every other tool + local name = e.tool_name -- snapshot now: e is short-lived + e:setComponent({ + render = function(self, width) + return { "▸ running skill: " .. name } + end, + }) +end)</code></pre></div></div><p class="pd-body"><code>panto.ext.emit(name, data)</code> fires a custom event on the same bus, running both native and Lua handlers. Today <code>data</code> is surfaced as an opaque <code>custom</code> payload.</p><h2 id="components" class="pd-h2">Lua components</h2><p class="pd-body">A Lua component is a table you hand to <code>e:setComponent</code>. The one required method is <code>render</code>:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">render</span><span class="pd-opt__type">function(self, width)</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Return an array of strings — one per line. Runs synchronously (it may <em>not</em> yield), each line is truncated to <code>width</code> columns, and any error becomes a safe fallback line so the frame never crashes. An empty array is valid (zero lines).</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">handleInput</span><span class="pd-opt__type">function(self, data)</span></div><div class="pd-opt__desc">Optional. Handle input routed to the component.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">firstLineChanged / invalidate</span><span class="pd-opt__type">function</span></div><div class="pd-opt__desc">Optional render-cache hooks.</div></div></div><h2 id="runtime" class="pd-h2">Runtime & luarocks</h2><p class="pd-body">panto embeds a pinned Lua and luarocks, and ships exactly one battery: <code>luv</code> (libuv’s event loop), the single runtime dependency. It drives panto’s coroutine scheduler and gives extensions one rich async-I/O surface. The bootstrap reconciles the installed rocks tree against panto’s manifest on every startup — installing what’s missing, removing what’s stale.</p><p class="pd-body">Anything else is yours to add. Install extra rocks into panto’s tree with the embedded luarocks, via <a href="cli.html#lua"><code>panto lua</code></a>:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><div class="pg-callout pg-callout--tip"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">One-time setup</p>On a fresh machine, run <a href="cli.html#bootstrap"><code>panto bootstrap</code></a> once to build the rocks tree before your extensions need it.</div></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="keybindings.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Key bindings</span></a><span style="flex:1"></span></nav></article></main> +<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#overview" class="pd-toc__link pd-toc__link--active">Overview</a></li><li><a href="#loading" class="pd-toc__link">Loading extensions</a></li><li><a href="#tools" class="pd-toc__link">register_tool</a></li><li><a href="#commands" class="pd-toc__link">register_command</a></li><li><a href="#events" class="pd-toc__link">Events: on & emit</a></li><li><a href="#components" class="pd-toc__link">Lua components</a></li><li><a href="#runtime" class="pd-toc__link">Runtime & luarocks</a></li></ul></aside> +</div> +</div> +<script src="_shared/docs.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/research/notes.md b/research/notes.md new file mode 100644 index 0000000..2cfcadc --- /dev/null +++ b/research/notes.md @@ -0,0 +1,393 @@ +# panto — verified content notes (from repo source) + +Source: https://code.tjp.lol/pantograph.git/ (cgit). Only document IMPLEMENTED features. +Project self-describes as "Early". NOT implemented yet (DO NOT DOCUMENT): server mode, subagents, +MCP, permission systems, AGENTS.md automation, skills, customizable /prompts, native (.so) extensions +(planned "next"; Lua is what ships). Standard tools (read/write/edit/bash) ship as extensions. + +## CLI invocation / subcommands (src/subcommand.zig — printHelp + dispatch, VERBATIM help text) + +panto Start a new conversation. +panto --resume Resume the most recent conversation in this directory. +panto --resume <id> Resume the conversation whose id begins with <id>. +panto sessions List saved sessions for this directory. +panto auth status Show configured auth sessions and login state. +panto auth login <name> Log in to an OAuth auth session (device flow). +panto auth logout <name> Forget a stored OAuth token. +panto bootstrap [--force] Run the luarocks bootstrap and exit. +panto lua [args...] Drop into the embedded Lua interpreter. +panto help | --help | -h Show help. + +Details: +- dispatch routes argv[1]; unknown/absent -> agent REPL. +- `lua`: embedded standalone Lua interpreter (panto's lua.c build) with luarocks runtime bootstrap + completed first, so `require("luarocks.*")` and the configured rocks tree work the same as in the + agent process. argv rewritten so the interpreter sees `lua [...args]` (affects arg[0]). +- `bootstrap`: runs the luarocks runtime bootstrap pipeline only, then exits before any agent loop. + First-run setup on a fresh machine (downloads+compiles batteries, stages headers, materializes + config); also good for CI/scripted installs. Idempotent: re-runs no-op fast. + `--force`: wipe the per-Lua-version tree before reinstalling everything. Equivalent to deleting + `$PANTO_HOME/rocks/lua-X.Y.Z/` by hand then running `panto bootstrap`. +- `sessions`: lists sessions for the current working directory. One per line: + `<short-id> <created> <message-count> messages`. short-id = first 8 hex chars of the session + UUIDv7. created trimmed to `YYYY-MM-DD HH:MM`. "no sessions for <cwd>" when empty. +- `auth` (default action = status): + - status: per configured auth session. + api_key -> "resolved" OR "unresolved (key/env missing)" + oauth_device -> "logged in (access expires in ~Nm)" OR "not logged in (run: panto auth login <name>)" + - login <name>: OAuth device flow. Prints: "To authorize, open this URL in a browser: <uri>" and + "enter the code: <user_code>". Runs secondary token exchange now if configured. api_key sessions: + "'<name>' is an api_key session; nothing to log in to". + - logout <name>: deletes the stored token set ("logged out of '<name>'" / "no stored token for '<name>'"). + +## Config files (TOML, merged base -> user -> project) [from help text] +- $XDG_DATA_HOME/panto/config.toml (base; auto-generated) +- $XDG_CONFIG_HOME/panto/config.toml (user) +- ./.panto/config.toml (project) +Schema teaser (NEED config_file.zig for full schema): +- [providers.<name>] define providers +- [defaults] model = "<provider>:<alias>" pick the default model +- [tools] / [extensions] allow/deny globs gate tools & extensions +- Model aliases (wire name, reasoning, max_tokens, pricing) live in models.toml. + +## Environment variables [from help text] +- OPENAI_API_KEY, ANTHROPIC_API_KEY — consumed by the default providers. +- PANTO_SESSION_DIR — override base sessions dir. Default $XDG_DATA_HOME/panto/sessions + or ~/.local/share/panto/sessions. +- PANTO_HOME — override the runtime/rocks tree location. + +## Slash commands (src/command.zig) +- REPL treats any line beginning with `/` as a slash command (never sent to the model). +- Parse: first whitespace-delimited word after `/` = name; trimmed remainder = args. +- Unknown command -> error.CommandNotFound -> user-facing error message. +- Builtin command seen: `/compact` (registers from its own module; compacts the conversation). +- Lua extensions add commands: a script calls `panto.ext.register_command { name, description, handler }`, + harvested by lua_runtime.zig; main.zig registers each. Lua-backed commands carry a lua_ref. +- Tab-completion: planned (registry has list()). + +## CONFIG FILE — full schema (src/config_file.zig, VERIFIED) + +### Layers — 4 TOML files, merged lowest precedence first (NOTE: help text only lists 3; loader has 4) +1. base — $PANTO_HOME/config.toml OR ${XDG_DATA_HOME:-$HOME/.local/share}/panto/config.toml (auto-generated by bootstrap) +2. user — ${XDG_CONFIG_HOME:-$HOME/.config}/panto/config.toml +3. project — ./.panto/config.toml +4. local — ./.panto/config.local.toml (intended to be git-ignored; personal overrides on top of team project layer) +Missing files are skipped silently. + +### Merge semantics +- Tables merge recursively; scalars AND arrays from a higher layer overwrite WHOLESALE (no array append). + e.g. project `tools.deny = [...]` replaces the array entirely. +- Tables accumulate: a provider defined only at base survives when project adds a different provider. + +### `${...}` substitution (auth values) +- `${env:VAR}` reads the environment; `${sibling}` reads another key in the SAME [auth.<n>] section. +- Unresolved reference (missing env var / sibling) → empty string. e.g. define `domain = "github.com"` then + `device_code_url = "https://${domain}/login/device/code"`. + +### [providers.<name>] (networked provider; transport only) +- style (required) — one of: openai_chat | anthropic_messages | openai_responses +- base_url (required) — string +- auth (required) — names the [auth.<n>] session supplying the credential. + (Clean break: provider-level api_key / api_key_env_var are NO LONGER accepted.) +- prompt_cache (bool, default true) — anthropic_messages ONLY; one advancing cache_control breakpoint per + request. Ignored for openai_chat. +- [providers.<name>.extra_headers] — table of string header name=value, merged onto each model request. +- A provider whose api_key auth resolves empty is DROPPED ("export the key or the provider disappears"). + OAuth providers always survive (resolved at turn time / via `panto auth login`). + +### [auth.<name>] (named auth session; `type` inferred if omitted: client_id→oauth_device, key→api_key) +api_key: + - type = "api_key" (optional; inferred from `key`) + - key = "${env:VAR}" (usually) or a literal — resolved eagerly; empty ⇒ session unresolved. +oauth_device: + - type = "oauth_device" (optional; inferred from `client_id`) + - dialect = "token" (default) | "codex" + - client_id (required) + - device_code_url (required) + - token_url (required) + - device_poll_url (required ONLY for dialect="codex") + - verification_url (optional) + - scope (optional) + - token_request_format = "form" (default) [enum panto.TokenRequestFormat] + - redirect_uri (optional) + - account_id_jwt_claim (optional) + - secondary token exchange (flat keys; active when exchange_url present): + exchange_url (enables exchange) + exchange_method (default "GET") + exchange_token_path (default "token") — JSON path to the token + exchange_expires_path (optional) — JSON path to expiry + exchange_base_url_path (optional) — JSON path to a dynamic base_url + - arbitrary sibling keys allowed (e.g. `domain`) for use in ${...} templating. + +### [defaults] +- model = "<provider>:<alias>" — provider must resolve. Selection precedence: + 1) future --model override, 2) defaults.model, 3) if exactly ONE provider resolved AND it has exactly ONE + alias in models.toml, use that, 4) else error (no model selected). +- Model ref format: exactly one colon, both halves non-empty (e.g. `anthropic:claude-sonnet-4-6`). + +### [tools] / [extensions] — allow/deny glob policy (identical shape) +- allow = [glob,...] deny = [glob,...] +- Empty allow ⇒ allow-all (still subject to deny). Permitted iff matches ≥1 allow (or allow empty) AND no deny. +- Same pattern in BOTH allow and deny (same kind) ⇒ hard error. +- Names are dotted, e.g. std.read, std.write, std.edit, std.shell; globs like `std.*`. + (Standard tools ship as extensions under the `std.` namespace and can be denied individually.) + +### [compaction] +- keep_verbatim = <int > 0> — kept-suffix token budget (omitted ⇒ libpanto default). +- model = "<provider>:<alias>" — optional override model used for compaction (provider must resolve). + +### Model aliases live in models.toml (resolved separately). Knobs seen in buildProviderConfig: +- common: wire `model` name, `reasoning` (ReasoningEffort, default .default), `max_tokens` (default 64000) +- anthropic_messages extra: api_version (default "2023-06-01"), thinking (default disabled), + effort (default medium), thinking_budget_tokens (default 32000), thinking_interleaved (default false) + -> NEED models_toml.zig for the exact TOML table layout + key names. + +### Model aliases live in models.toml (src/models_toml.zig, VERIFIED) +Path: ${XDG_CONFIG_HOME:-$HOME/.config}/panto/models.toml. Missing file ⇒ empty registries (no error). +A referenced alias with NO entry is NOT an error: the alias is used verbatim as the wire model name, +with default knobs and unknown pricing (zero-config convenience). + +Entry table key: [<provider>.<alias>] — <provider> matches a [providers.<name>] in config.toml; +<alias> is the short name referenced as <provider>:<alias> (e.g. anthropic:sonnet). + +Keys: +- model = <string> wire model id sent to the API; defaults to <alias> if omitted. +- max_tokens = <int> per-request OUTPUT token cap; >0 else null (= provider/library default). +- Pricing (all optional, USD per MILLION tokens; omitted = UNKNOWN/null, write `= 0` for known-zero): + input = <float> + output = <float> + cache_read = <float> + cache_write = <float> +- openai_chat ONLY: + reasoning = <string> default | off | minimal | low | medium | high (default: default) +- anthropic_messages ONLY: + api_version = <string> Anthropic-Version header; default "2023-06-01" + thinking = <string> disabled | enabled | adaptive (default: disabled) + effort = <string> low | medium | high | xhigh | max (default: medium) + — only used when thinking = "adaptive" + thinking_budget_tokens = <int> max reasoning tokens for thinking="enabled"; default 32000; + null ⇒ max_tokens − 1; ignored when adaptive/disabled + thinking_interleaved = <bool> send interleaved-thinking beta header (default false); + only honoured when thinking = "enabled" + +Example models.toml: + [anthropic.sonnet] + model = "claude-sonnet-4-20250514" + max_tokens = 8192 + thinking = "enabled" + thinking_budget_tokens = 16000 + input = 3.0 + output = 15.0 + cache_read = 0.3 + cache_write = 3.75 + + [anthropic.opus] + model = "claude-opus-4-8" + thinking = "adaptive" + effort = "high" + + [openai.gpt] + model = "gpt-4o" + input = 2.5 + output = 10.0 + +## LUA RUNTIME / LUAROCKS (src/manifest.zig + subcommand.zig, VERIFIED) +- panto embeds a PINNED Lua + luarocks and ships "batteries" (rocks). The ONLY battery auto-installed is + `luv` (libuv event loop) — the single runtime dependency. It drives panto's coroutine scheduler and + gives extension authors one rich async I/O surface. +- Bootstrap reconciles the installed rocks tree against the manifest on EVERY startup: installs missing, + removes stale. The battery list is compiled into the binary; users do NOT configure it. +- Extra rocks are the user's responsibility. Install via: + panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command(...)' + (A higher-level `panto rocks install` is PLANNED — DO NOT document.) +- Per-Lua-version rocks tree lives at $PANTO_HOME/rocks/lua-X.Y.Z/ (wiped by `panto bootstrap --force`). +- luarocks MAJOR.MINOR.PATCH-REV version strings (e.g. "1.52.1-0") passed straight to luarocks install. + +## EXAMPLES INVENTORY (repo /examples) +- examples/extensions/echo.lua (632) — example panto Lua EXTENSION <-- fetch +- examples/extensions/greet.lua (719) — example panto Lua EXTENSION <-- fetch +- examples/tools/ (dir) — example tools <-- fetch tree +- examples/simple-agent.lua (3110) — libpanto Lua SDK agent (NOT panto-ext; for libpanto, skip-ish) +- examples/simple-agent-go/ — libpanto Go SDK agent (NOT panto-ext) + +## LUA EXTENSION SURFACE — verified from examples +Module: `local panto = require("panto")` (available inside extensions AND in `panto lua`). + +### panto.ext.register_tool { name, description, schema, handler } (examples/extensions/echo.lua) +- name : tool name (string) the model calls. +- description : shown to the model. +- schema : JSON-Schema table — { type="object", properties={...}, required={...} }. +- handler : function(input) -> string. `input` is the decoded arguments table; return a string result. + echo example: handler = function(input) return "echo: " .. input.message end + +### panto.ext.register_command { name, description, handler } (examples/extensions/greet.lua) +- Registers a `/`-slash command in the REPL. name is WITHOUT the leading `/`. +- handler = function(args) ... end. `args` is the trimmed text after the command name ("" if none). + Handlers run SYNCHRONOUSLY and act by side effect (e.g. io.write); the RETURN VALUE IS IGNORED. + greet example: `/greet` or `/greet <name>` -> io.write("\n[greet] hello, "..who.."!\n") +- A Lua command name colliding with a builtin (e.g. compact) is rejected (DuplicateCommand). + +### Where extensions live (from greet.lua header comment) +- Project: `./.panto/extensions/` +- User: the user config dir's `extensions/` dir (i.e. ${XDG_CONFIG_HOME:-~/.config}/panto/extensions/). +- Drop a `.lua` file there; it's loaded at startup. Gate via [extensions] allow/deny globs in config.toml. + +### Runtime +- The embedded interpreter is the panto `lua.c` standalone (Lua + luarocks). `luv` (libuv) is always + available for async I/O; panto's coroutine scheduler drives the libuv loop. +- `panto lua [args...]` drops into the same interpreter for scripting/experiments; + `panto bootstrap` prepares the rocks tree. +- NOTE: I have register_tool + register_command verified. The file src/lua_event_bridge.zig (event hooks) + is NOT yet read — DO NOT invent event APIs (on_turn, hooks, etc.) until verified. + +## KEYBINDINGS — verified from src/tui_input.zig (the input decoder; P1 scope, "minimal but correct") +The REPL input layer turns raw stdin into keys. Keys it recognises TODAY (document only these): + +Editing / text: +- Printable characters (full UTF-8, multi-byte) -> insert +- Enter (Return) -> submit the message +- Shift+Enter -> insert a newline (terminal-dependent, see below) +- Backspace -> delete char before cursor +- Delete -> delete char under/after cursor +- Tab -> tab +- Bracketed paste -> pasted text inserted literally as one run + (not interpreted key-by-key) + +Cursor / navigation (decoded with modifiers): +- Left / Right -> move cursor by character +- Ctrl+Left / Ctrl+Right -> move by word (the "word-motion path") +- Home / End -> line start / end +- Up / Down -> arrow keys (decoded; move within a multi-line draft) +- Page Up / Page Down -> decoded +- Alt+<key> -> decoded (ESC-prefixed alt forms) + +Control: +- Ctrl+C -> interrupt / quit +- Ctrl+D -> EOF / exit +- Esc -> escape +- Ctrl+<letter> generally (0x01–0x1a) -> decoded as ctrl+a..z + +Shift+Enter detail (genuinely useful doc): in the bare legacy protocol Enter and Shift+Enter both send `\r` +and are indistinguishable. At startup panto negotiates the **Kitty keyboard protocol** (pushes flags 1|4 = +disambiguate + report-alternates; deliberately NOT report-events) and queries the terminal; if confirmed it +reads Shift+Enter as `CSI 13;2u`. Otherwise it falls back to xterm **modifyOtherKeys mode 2** (tmux/xterm), +which sends `CSI 27;2;13~`. On terminals supporting NEITHER (e.g. macOS Terminal.app) the two stay +identical and Enter submits — there's no newline binding there. (Ghostty maps shift+enter to a bare `\n`.) +Deferred / negotiated-but-not-consumed (DO NOT present as features): key-release events, super/hyper +modifiers, full Kitty disambiguation — modelled for later phases, not active. + +## MAIN / STARTUP / TUI WIRING (src/main.zig, VERIFIED) — lots of new facts + +### Agent-mode flags (the ONLY ones) +- `--resume` → resume most recent session in this cwd. +- `--resume <id>` → resume session whose id has prefix <id>. +- No flag → new session. Unknown args are tolerated/ignored (warn). +- TUI REQUIRES an interactive tty ("panto's TUI requires an interactive terminal"). + +### Builtin slash commands +- ONLY builtin registered is from compaction.zig → `/compact` (manual compaction). + Everything else is added by Lua extensions. (Do NOT invent /help, /model, etc. as slash commands.) + +### LIVE keybindings added by main.zig (the interactive ones!) +- Ctrl+C / Ctrl+D → clean exit (UserExit). +- Ctrl+M → MODEL selector (runtime model picker overlay). +- Ctrl+R → REASONING-effort selector (runtime reasoning picker overlay). + Both are live-session only: a pick rebuilds the provider config and pushes it to the agent + (setConfig); NOTHING is written back to config.toml. +- Enter submits; Shift+Enter newline (see tui_input notes). Input box + footer + transcript differential render. + +### Lua extension surface — EXPANDED (main.zig wiring, VERIFIED) +- `require('panto')` is wired to the native `panto.so` module PLUS the CLI's `ext` subtable → `panto.ext`. +- Lua runtime is ONE long-lived lua_State; module-global state persists across calls. Registers with the + agent as a single ToolSource named `panto-lua`. +- `luv` (libuv) scheduler is installed BEFORE extensions load, so tool handlers may YIELD (async I/O). +- panto.ext.register_tool {…} → adds a model-callable tool (see echo.lua). +- panto.ext.register_command {…} → adds a `/`-slash command (see greet.lua). +- panto.ext.on(...) → registers a UI EVENT handler into the App's event bus, in registration order. + Extensions can WRAP/REPLACE built-in TUI components this way (e.g. a `tool_details` handler replacing + the default `tool (?)` component). Superseded overrides are released (ref + RenderCache freed). +- panto.ext.emit(...) → a Lua call that drives the SAME event bus. + (NOTE: exact event/kind names + on/emit signatures live in src/lua_event_bridge.zig — fetching now. + Until verified, describe on/emit as the component-override/event surface and cite tool_details as the + one named example from main.zig. Don't enumerate events I haven't seen.) + +### Extension discovery (3 layers; src/extension_loader.discoverAndLoad) +- base = $PANTO_HOME/agent +- user = $XDG_CONFIG_HOME/panto (or $HOME/.config/panto) +- project = ./.panto +- Precedence: project shadows user shadows base. Tool-NAME collisions across surviving entries ABORT startup. +- Gated by [extensions] and [tools] allow/deny policies from config.toml. +- (greet.lua header: drop a .lua under .panto/extensions/ project, or the user config's extensions/ dir.) + +### System prompt — a real config surface (system_prompt.zig, referenced by main.zig) +- The agent's system prompt is sourced by convention from SYSTEM.md / APPEND_SYSTEM.md across the + base/user/project layers (base = $PANTO_HOME/agent, where bootstrap stages the bundled SYSTEM.md). + SYSTEM.md replaces; APPEND_SYSTEM.md appends. Reconciled on resume without rewriting history. +- Compaction system prompt = COMPACTION.md across layers (last wins; built-in default otherwise). + +### Compaction +- Automatic compaction is armed at startup. [compaction].keep_verbatim default = 20000 tokens. +- `/compact` triggers it manually. [compaction].model can override the model used for compaction. + +### Sessions / auth (confirm) +- Sessions are per-cwd; created on demand. Resume by id-prefix or latest. "resumed session <8hex> (<n> messages)". +- Per-turn auth resolution: api_key is a no-op; oauth_device refreshes/exchanges or runs an interactive + device login before the turn. models.toml path = $XDG_CONFIG_HOME/.config/panto/models.toml. +- Config error messages are friendly (e.g. "defaults.model must look like \"provider:model\"."). + +## panto.ext EVENT SURFACE — VERIFIED (src/lua_event_bridge.zig) + +### panto.ext.on(name, handler) — subscribe a Lua handler to a UI event +- The handler participates in the SAME native EventBus the built-in TUI fires. Registration order matters. +- handler = function(e) ... end. `e` is a bridged EVENT OBJECT, valid ONLY during the handler call + (snapshot any field you need into a local — do NOT close over `e` and read it at render time). +- Event object API: + e.name -> the event name (string) + e.<field> -> read-only payload fields (nil when absent/empty, so you can branch: + `if e.tool_name then ...`) + e:getComponent() -> the current (native default) component, as an OPAQUE passthrough handle + e:setComponent(c) -> replace the component for this boundary. `c` is EITHER that passthrough + handle (pass-through / wrap) OR a Lua component table (replace). +- Payload fields by event family (from pushPayloadField): + tool : index(int), tool_name, id, delta, input, output (lifecycle: tool_delta has delta, + tool_result has output, tool_details…) + thinking : index, delta, text + assistant_text : index, delta, text + user_message : text + session_start : version, cwd, model + compaction : summary + custom : (no structured fields — produced by emit) + (VERIFIED event NAME used in tests: "tool". Lifecycle boundary names tool_delta/tool_result/tool_details + appear in source comments. Present these as the event families; the canonical example is "tool".) + +### A Lua COMPONENT is a table: + { render = function(self, width) -> { "line1", "line2", ... } end, + handleInput = function(self, data) ... end, -- optional + firstLineChanged = ..., -- optional (cache-derived by default) + invalidate = ... } -- optional +- render() MUST return an array of strings; runs SYNCHRONOUSLY (may NOT yield — unlike tool handlers); + each line is truncated to `width` columns; any error yields a safe "[lua component error: …]" fallback + line (the frame never crashes). Empty array {} = zero lines (valid). + +### Three override patterns (from the bridge's own tests): +- pass-through native default: e:setComponent(e:getComponent()) +- replace with a Lua component: e:setComponent({ render = function(self,w) return {"hi"} end }) +- claim-by-name (the canonical extension shape): + panto.ext.on("tool", function(e) + if e.tool_name ~= "skill" then return end -- ignore everything else + local name = e.tool_name -- snapshot at handler time + e:setComponent({ render = function(self, w) return { "SKILL:"..name } end }) + end) + +### panto.ext.emit(name, data) — fire a custom event on the SAME bus +- Runs native AND Lua handlers for `name`. `data` is currently surfaced only as an opaque `.custom` + payload (structured marshalling is future). A component chosen from a bare emit is NOT auto-mounted yet. + +## ALL CONTENT GATHERED — ready to build. (lua_runtime/extension_loader internals not needed beyond above.) + +## KEY MODEL (src/tui_key.zig, VERIFIED) — what the P1 decoder currently handles +Decoded keys the running TUI populates today (the model is bigger for future phases, but ONLY these are live): + printable chars, enter, backspace, arrows (up/down/left/right), home, end, escape, Ctrl+C, Ctrl+D. +Mods modelled: ctrl, alt, shift, super, hyper — but P1 decoder only ever sets ctrl (super/hyper need Kitty +protocol; left false). Key events: press/repeat/release — most terminals only emit .press. +=> The keybindings page must document ONLY the live subset; note richer keys are planned, not shipped. |
